diff options
207 files changed, 48598 insertions, 8973 deletions
diff --git a/.gitattributes b/.gitattributes index 9d7ac97e..a3a229d8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -304,6 +304,30 @@ demos/quickstart/themes/PradoSoft/mantissample.jpg -text demos/quickstart/themes/PradoSoft/pradologo.gif -text demos/quickstart/themes/PradoSoft/style.css -text demos/quickstart/themes/Simple/style.css -text +demos/sqlmap-sample/index.php -text +demos/sqlmap-sample/protected/application.xml -text +demos/sqlmap-sample/protected/business-objects/Person.php -text +demos/sqlmap-sample/protected/pages/Home.page -text +demos/sqlmap-sample/protected/pages/ProductList.page -text +demos/sqlmap-sample/protected/pages/ProductList.php -text +demos/sqlmap-sample/protected/pages/crud1.page -text +demos/sqlmap-sample/protected/pages/crud1.php -text +demos/sqlmap-sample/protected/pages/crud2.page -text +demos/sqlmap-sample/protected/pages/crud2.php -text +demos/sqlmap-sample/protected/person-db/person.xml -text +demos/sqlmap-sample/protected/person-db/personHelper.xml -text +demos/sqlmap-sample/protected/person-db/test.db -text +demos/sqlmap-sample/protected/person-sqlmap.xml -text +demos/sqlmap-sample/protected/petshop-db/petshop.db -text +demos/sqlmap-sample/protected/petshop-db/products.xml -text +demos/sqlmap-sample/protected/petshop-sqlmap.xml -text +demos/sqlmap-sample/protected/runtime/application.xml/config.cache -text +demos/sqlmap-sample/protected/runtime/application.xml/global.cache -text +demos/sqlmap-sample/protected/runtime/application.xml/sqlite.cache -text +demos/sqlmap-sample/tests/PersonTest.php -text +demos/sqlmap-sample/tests/readme.txt -text +demos/sqlmap-sample/tests/run_tests.php -text +demos/sqlmap-sample/tests/sqlmap.xml -text docs/application.xml -text docs/conceptual-structure.vsd -text docs/request-sequence.vsd -text @@ -311,6 +335,26 @@ docs/specs/application.dtd -text docs/specs/application.xsd -text docs/specs/config.dtd -text docs/specs/config.xsd -text +docs/sqlmap/latex/ch1.tex -text +docs/sqlmap/latex/ch2.tex -text +docs/sqlmap/latex/ch3.tex -text +docs/sqlmap/latex/ch4.tex -text +docs/sqlmap/latex/ch5.tex -text +docs/sqlmap/latex/ch6.tex -text +docs/sqlmap/latex/ch7.tex -text +docs/sqlmap/latex/ch8.tex -text +docs/sqlmap/latex/ch9.tex -text +docs/sqlmap/latex/diagram.pdf -text svneol=unset#unset +docs/sqlmap/latex/example1.png -text +docs/sqlmap/latex/grid1.png -text +docs/sqlmap/latex/grid2.png -text +docs/sqlmap/latex/sqlmap.tex -text +docs/sqlmap/latex/sqlmap_tut.tex -text +docs/sqlmap/latex/tut1.tex -text +docs/sqlmap/latex/tut2.tex -text +docs/sqlmap/latex/tut3.tex -text +docs/sqlmap/sqlmap.pdf -text +docs/sqlmap/sqlmap_tut.pdf -text framework/.htaccess -text framework/3rdParty/SafeHtml/HTMLSax3.php -text framework/3rdParty/SafeHtml/HTMLSax3/Decorators.php -text @@ -347,10 +391,137 @@ framework/Collections/TPagedDataSource.php -text framework/Collections/TPagedList.php -text framework/Collections/TQueue.php -text framework/Collections/TStack.php -text +framework/DataAccess/SQLMap/Configuration/TConfigDeserialize.php -text +framework/DataAccess/SQLMap/Configuration/TDiscriminator.php -text +framework/DataAccess/SQLMap/Configuration/TDomSqlMapBuilder.php -text +framework/DataAccess/SQLMap/Configuration/TInlineParameterMapParser.php -text +framework/DataAccess/SQLMap/Configuration/TParameterMap.php -text +framework/DataAccess/SQLMap/Configuration/TParameterProperty.php -text +framework/DataAccess/SQLMap/Configuration/TResultMap.php -text +framework/DataAccess/SQLMap/Configuration/TResultProperty.php -text +framework/DataAccess/SQLMap/Configuration/TSqlMapCacheModel.php -text +framework/DataAccess/SQLMap/Configuration/TSqlMapDelete.php -text +framework/DataAccess/SQLMap/Configuration/TSqlMapInsert.php -text +framework/DataAccess/SQLMap/Configuration/TSqlMapSelect.php -text +framework/DataAccess/SQLMap/Configuration/TSqlMapSelectKey.php -text +framework/DataAccess/SQLMap/Configuration/TSqlMapStatement.php -text +framework/DataAccess/SQLMap/Configuration/TSqlMapUpdate.php -text +framework/DataAccess/SQLMap/DataMapper/TDataMapperException.php -text +framework/DataAccess/SQLMap/DataMapper/TLazyLoadList.php -text +framework/DataAccess/SQLMap/DataMapper/TPropertyAccess.php -text +framework/DataAccess/SQLMap/DataMapper/TSqlMapCache.php -text +framework/DataAccess/SQLMap/DataMapper/TSqlMapPagedList.php -text +framework/DataAccess/SQLMap/DataMapper/TTypeHandlerFactory.php -text +framework/DataAccess/SQLMap/DataMapper/messages.txt -text +framework/DataAccess/SQLMap/Statements/IMappedStatement.php -text +framework/DataAccess/SQLMap/Statements/TCachingStatement.php -text +framework/DataAccess/SQLMap/Statements/TDeleteMappedStatement.php -text +framework/DataAccess/SQLMap/Statements/TInsertMappedStatement.php -text +framework/DataAccess/SQLMap/Statements/TMappedStatement.php -text +framework/DataAccess/SQLMap/Statements/TPreparedCommand.php -text +framework/DataAccess/SQLMap/Statements/TPreparedStatement.php -text +framework/DataAccess/SQLMap/Statements/TPreparedStatementFactory.php -text +framework/DataAccess/SQLMap/Statements/TSelectMappedStatement.php -text +framework/DataAccess/SQLMap/Statements/TStaticSql.php -text +framework/DataAccess/SQLMap/Statements/TUpdateMappedStatement.php -text +framework/DataAccess/SQLMap/TMapper.php -text +framework/DataAccess/SQLMap/TSqlMapClient.php -text +framework/DataAccess/SQLMap/TSqlMapper.php -text framework/DataAccess/TAdodbProvider.php -text framework/DataAccess/TCreoleProvider.php -text framework/DataAccess/TDatabaseProvider.php -text framework/DataAccess/TSQLMap.php -text +framework/DataAccess/adodb/adodb-csvlib.inc.php -text +framework/DataAccess/adodb/adodb-datadict.inc.php -text +framework/DataAccess/adodb/adodb-error.inc.php -text +framework/DataAccess/adodb/adodb-errorhandler.inc.php -text +framework/DataAccess/adodb/adodb-errorpear.inc.php -text +framework/DataAccess/adodb/adodb-exceptions.inc.php -text +framework/DataAccess/adodb/adodb-iterator.inc.php -text +framework/DataAccess/adodb/adodb-lib.inc.php -text +framework/DataAccess/adodb/adodb-pager.inc.php -text +framework/DataAccess/adodb/adodb-pear.inc.php -text +framework/DataAccess/adodb/adodb-perf.inc.php -text +framework/DataAccess/adodb/adodb-php4.inc.php -text +framework/DataAccess/adodb/adodb-time.inc.php -text +framework/DataAccess/adodb/adodb-xmlschema.inc.php -text +framework/DataAccess/adodb/adodb.inc.php -text +framework/DataAccess/adodb/drivers/adodb-access.inc.php -text +framework/DataAccess/adodb/drivers/adodb-ado.inc.php -text +framework/DataAccess/adodb/drivers/adodb-ado5.inc.php -text +framework/DataAccess/adodb/drivers/adodb-ado_access.inc.php -text +framework/DataAccess/adodb/drivers/adodb-ado_mssql.inc.php -text +framework/DataAccess/adodb/drivers/adodb-borland_ibase.inc.php -text +framework/DataAccess/adodb/drivers/adodb-csv.inc.php -text +framework/DataAccess/adodb/drivers/adodb-db2.inc.php -text +framework/DataAccess/adodb/drivers/adodb-fbsql.inc.php -text +framework/DataAccess/adodb/drivers/adodb-firebird.inc.php -text +framework/DataAccess/adodb/drivers/adodb-ibase.inc.php -text +framework/DataAccess/adodb/drivers/adodb-informix.inc.php -text +framework/DataAccess/adodb/drivers/adodb-informix72.inc.php -text +framework/DataAccess/adodb/drivers/adodb-ldap.inc.php -text +framework/DataAccess/adodb/drivers/adodb-mssql.inc.php -text +framework/DataAccess/adodb/drivers/adodb-mssqlpo.inc.php -text +framework/DataAccess/adodb/drivers/adodb-mysql.inc.php -text +framework/DataAccess/adodb/drivers/adodb-mysqli.inc.php -text +framework/DataAccess/adodb/drivers/adodb-mysqlt.inc.php -text +framework/DataAccess/adodb/drivers/adodb-netezza.inc.php -text +framework/DataAccess/adodb/drivers/adodb-oci8.inc.php -text +framework/DataAccess/adodb/drivers/adodb-oci805.inc.php -text +framework/DataAccess/adodb/drivers/adodb-oci8po.inc.php -text +framework/DataAccess/adodb/drivers/adodb-odbc.inc.php -text +framework/DataAccess/adodb/drivers/adodb-odbc_db2.inc.php -text +framework/DataAccess/adodb/drivers/adodb-odbc_mssql.inc.php -text +framework/DataAccess/adodb/drivers/adodb-odbc_oracle.inc.php -text +framework/DataAccess/adodb/drivers/adodb-odbtp.inc.php -text +framework/DataAccess/adodb/drivers/adodb-odbtp_unicode.inc.php -text +framework/DataAccess/adodb/drivers/adodb-oracle.inc.php -text +framework/DataAccess/adodb/drivers/adodb-pdo.inc.php -text +framework/DataAccess/adodb/drivers/adodb-pdo_mssql.inc.php -text +framework/DataAccess/adodb/drivers/adodb-pdo_mysql.inc.php -text +framework/DataAccess/adodb/drivers/adodb-pdo_oci.inc.php -text +framework/DataAccess/adodb/drivers/adodb-pdo_pgsql.inc.php -text +framework/DataAccess/adodb/drivers/adodb-postgres.inc.php -text +framework/DataAccess/adodb/drivers/adodb-postgres64.inc.php -text +framework/DataAccess/adodb/drivers/adodb-postgres7.inc.php -text +framework/DataAccess/adodb/drivers/adodb-postgres8.inc.php -text +framework/DataAccess/adodb/drivers/adodb-proxy.inc.php -text +framework/DataAccess/adodb/drivers/adodb-sapdb.inc.php -text +framework/DataAccess/adodb/drivers/adodb-sqlanywhere.inc.php -text +framework/DataAccess/adodb/drivers/adodb-sqlite.inc.php -text +framework/DataAccess/adodb/drivers/adodb-sqlitepo.inc.php -text +framework/DataAccess/adodb/drivers/adodb-sybase.inc.php -text +framework/DataAccess/adodb/drivers/adodb-sybase_ase.inc.php -text +framework/DataAccess/adodb/drivers/adodb-vfp.inc.php -text +framework/DataAccess/adodb/lang/adodb-ar.inc.php -text +framework/DataAccess/adodb/lang/adodb-bg.inc.php -text +framework/DataAccess/adodb/lang/adodb-bgutf8.inc.php -text +framework/DataAccess/adodb/lang/adodb-ca.inc.php -text +framework/DataAccess/adodb/lang/adodb-cn.inc.php -text +framework/DataAccess/adodb/lang/adodb-cz.inc.php -text +framework/DataAccess/adodb/lang/adodb-da.inc.php -text +framework/DataAccess/adodb/lang/adodb-de.inc.php -text +framework/DataAccess/adodb/lang/adodb-en.inc.php -text +framework/DataAccess/adodb/lang/adodb-es.inc.php -text +framework/DataAccess/adodb/lang/adodb-esperanto.inc.php -text +framework/DataAccess/adodb/lang/adodb-fr.inc.php -text +framework/DataAccess/adodb/lang/adodb-hu.inc.php -text +framework/DataAccess/adodb/lang/adodb-it.inc.php -text +framework/DataAccess/adodb/lang/adodb-nl.inc.php -text +framework/DataAccess/adodb/lang/adodb-pl.inc.php -text +framework/DataAccess/adodb/lang/adodb-pt-br.inc.php -text +framework/DataAccess/adodb/lang/adodb-ro.inc.php -text +framework/DataAccess/adodb/lang/adodb-ru1251.inc.php -text +framework/DataAccess/adodb/lang/adodb-sv.inc.php -text +framework/DataAccess/adodb/lang/adodb-uk1251.inc.php -text +framework/DataAccess/adodb/license.txt -text +framework/DataAccess/adodb/pivottable.inc.php -text +framework/DataAccess/adodb/readme.txt -text +framework/DataAccess/adodb/rsfilter.inc.php -text +framework/DataAccess/adodb/server.php -text +framework/DataAccess/adodb/toexport.inc.php -text +framework/DataAccess/adodb/tohtml.inc.php -text +framework/DataAccess/adodb/xmlschema.dtd -text framework/Exceptions/TErrorHandler.php -text framework/Exceptions/TException.php -text framework/Exceptions/messages.txt -text @@ -1118,38 +1289,6 @@ tests/UnitTests/simpletest/shell_tester.php -text tests/UnitTests/simpletest/simple_test.php -text tests/UnitTests/simpletest/socket.php -text tests/UnitTests/simpletest/tag.php -text -tests/UnitTests/simpletest/test/acceptance_test.php -text -tests/UnitTests/simpletest/test/adapter_test.php -text -tests/UnitTests/simpletest/test/all_tests.php -text -tests/UnitTests/simpletest/test/authentication_test.php -text -tests/UnitTests/simpletest/test/browser_test.php -text -tests/UnitTests/simpletest/test/dumper_test.php -text -tests/UnitTests/simpletest/test/encoding_test.php -text -tests/UnitTests/simpletest/test/errors_test.php -text -tests/UnitTests/simpletest/test/expectation_test.php -text -tests/UnitTests/simpletest/test/form_test.php -text -tests/UnitTests/simpletest/test/frames_test.php -text -tests/UnitTests/simpletest/test/http_test.php -text -tests/UnitTests/simpletest/test/live_test.php -text -tests/UnitTests/simpletest/test/options_test.php -text -tests/UnitTests/simpletest/test/page_test.php -text -tests/UnitTests/simpletest/test/parse_error_test.php -text -tests/UnitTests/simpletest/test/parser_test.php -text -tests/UnitTests/simpletest/test/real_sites_test.php -text -tests/UnitTests/simpletest/test/remote_test.php -text -tests/UnitTests/simpletest/test/shell_test.php -text -tests/UnitTests/simpletest/test/shell_tester_test.php -text -tests/UnitTests/simpletest/test/simple_mock_test.php -text -tests/UnitTests/simpletest/test/socket_test.php -text -tests/UnitTests/simpletest/test/tag_test.php -text -tests/UnitTests/simpletest/test/test_with_parse_error.php -text -tests/UnitTests/simpletest/test/unit_tester_test.php -text -tests/UnitTests/simpletest/test/unit_tests.php -text -tests/UnitTests/simpletest/test/url_test.php -text -tests/UnitTests/simpletest/test/user_agent_test.php -text -tests/UnitTests/simpletest/test/visual_test.php -text -tests/UnitTests/simpletest/test/web_tester_test.php -text -tests/UnitTests/simpletest/test/xml_test.php -text tests/UnitTests/simpletest/unit_tester.php -text tests/UnitTests/simpletest/url.php -text tests/UnitTests/simpletest/user_agent.php -text diff --git a/demos/sqlmap-sample/index.php b/demos/sqlmap-sample/index.php new file mode 100644 index 00000000..43c0b436 --- /dev/null +++ b/demos/sqlmap-sample/index.php @@ -0,0 +1,18 @@ +<?php
+
+$basePath=dirname(__FILE__);
+$frameworkPath=$basePath.'/../../framework/prado.php';
+$assetsPath=$basePath.'/assets';
+$runtimePath=$basePath.'/protected/runtime';
+
+if(!is_writable($assetsPath))
+ die("Please make sure that the directory $assetsPath is writable by Web server process.");
+if(!is_writable($runtimePath))
+ die("Please make sure that the directory $runtimePath is writable by Web server process.");
+
+require_once($frameworkPath);
+
+$application=new TApplication;
+$application->run();
+
+?>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/application.xml b/demos/sqlmap-sample/protected/application.xml new file mode 100644 index 00000000..7cfb440f --- /dev/null +++ b/demos/sqlmap-sample/protected/application.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?>
+<application id="Database" Mode="Debug">
+ <paths>
+ <alias id="Example" path="business-objects" />
+ </paths>
+ <modules>
+ <module id="cache" class="System.Caching.TSqliteCache" />
+ <module id="petshop-sqlmap"
+ enableConfigCache="true"
+ class="System.DataAccess.TSQLMap"
+ configFile="Application.petshop-sqlmap" />
+ <module id="person-sample"
+ class="System.DataAccess.TSQLMap"
+ configFile="Application.person-sqlmap" />
+ </modules>
+</application>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/business-objects/Person.php b/demos/sqlmap-sample/protected/business-objects/Person.php new file mode 100644 index 00000000..ad9da4b3 --- /dev/null +++ b/demos/sqlmap-sample/protected/business-objects/Person.php @@ -0,0 +1,26 @@ +<?php
+
+class Person
+{
+ public $ID = -1;
+ public $FirstName = '';
+ public $LastName = '';
+
+ public $WeightInKilograms = 0.0;
+ public $HeightInMeters = 0.0;
+
+ private $_birthDate = '';
+
+ //setters and getter for BirthDate
+ public function getBirthDate()
+ {
+ return $this->_birthDate;
+ }
+
+ public function setBirthDate($value)
+ {
+ $this->_birthDate = $value;
+ }
+}
+
+?>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/pages/Home.page b/demos/sqlmap-sample/protected/pages/Home.page new file mode 100644 index 00000000..6d059c7e --- /dev/null +++ b/demos/sqlmap-sample/protected/pages/Home.page @@ -0,0 +1,5 @@ +<ul>
+ <li><a href="index.php?page=crud1">Tutorial 1</a></li>
+ <li><a href="index.php?page=crud2">Tutorial 2</a></li>
+ <li><a href="index.php?page=ProductList">Product List</a></li>
+</ul>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/pages/ProductList.page b/demos/sqlmap-sample/protected/pages/ProductList.page new file mode 100644 index 00000000..904d411d --- /dev/null +++ b/demos/sqlmap-sample/protected/pages/ProductList.page @@ -0,0 +1,9 @@ +<h1>Database Examples</h1>
+<com:TRepeater id="productList">
+ <prop:ItemTemplate>
+ <div>
+ Code: <%# $this->DataItem['productid'] %>
+ Category: <%# $this->DataItem['category'] %>
+ </div>
+ </prop:ItemTemplate>
+</com:TRepeater>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/pages/ProductList.php b/demos/sqlmap-sample/protected/pages/ProductList.php new file mode 100644 index 00000000..a35c40ea --- /dev/null +++ b/demos/sqlmap-sample/protected/pages/ProductList.php @@ -0,0 +1,18 @@ +<?php
+
+class ProductList extends TPage
+{
+ public function onLoad($param)
+ {
+ parent::onLoad($param);
+ if(!$this->IsPostBack)
+ {
+ $sqlmap = $this->Application->Modules['petshop-sqlmap'];
+ $products = $sqlmap->queryForList('SelectAllProducts');
+ $this->productList->setDataSource($products);
+ $this->productList->dataBind();
+ }
+ }
+}
+
+?>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/pages/crud1.page b/demos/sqlmap-sample/protected/pages/crud1.page new file mode 100644 index 00000000..ce467ffa --- /dev/null +++ b/demos/sqlmap-sample/protected/pages/crud1.page @@ -0,0 +1,19 @@ +<!doctype html public "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+<head>
+ <title>Person</title>
+</head>
+
+<body>
+
+<com:TForm>
+ <h1>Person List</h1>
+ <com:TDataGrid id="personList">
+ <com:TBoundColumn DataField="BirthDate"
+ HeaderText="Birth Date"/>
+ </com:TDataGrid>
+</com:TForm>
+</body>
+</html>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/pages/crud1.php b/demos/sqlmap-sample/protected/pages/crud1.php new file mode 100644 index 00000000..40733dc9 --- /dev/null +++ b/demos/sqlmap-sample/protected/pages/crud1.php @@ -0,0 +1,21 @@ +<?php
+
+Prado::using('Example.Person');
+
+class crud1 extends TPage
+{
+ private function loadData()
+ {
+ $sqlmap = $this->Application->Modules['person-sample'];
+ $this->personList->DataSource = $sqlmap->queryForList('SelectAll');
+ $this->personList->dataBind();
+ }
+
+ public function onLoad($param)
+ {
+ if(!$this->IsPostBack)
+ $this->loadData();
+ }
+}
+
+?>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/pages/crud2.page b/demos/sqlmap-sample/protected/pages/crud2.page new file mode 100644 index 00000000..4d34e873 --- /dev/null +++ b/demos/sqlmap-sample/protected/pages/crud2.page @@ -0,0 +1,36 @@ +<!doctype html public "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+<head>
+ <title>Person</title>
+</head>
+
+<body>
+
+<com:TForm>
+ <h1>Person List</h1>
+ <com:TDataGrid id="personList"
+ DataKeyField="ID"
+ AutoGenerateColumns="False"
+ OnEditCommand="editPerson"
+ OnUpdateCommand="updatePerson"
+ OnCancelCommand="refreshList"
+ OnDeleteCommand="deletePerson">
+ <com:TBoundColumn DataField="FirstName" HeaderText="First Name" />
+ <com:TBoundColumn DataField="LastName" HeaderText="Last Name" />
+ <com:TBoundColumn DataField="HeightInMeters" HeaderText="Height" />
+ <com:TBoundColumn DataField="WeightInKilograms" HeaderText="Weight" />
+
+ <com:TEditCommandColumn
+ HeaderText="Edit"
+ UpdateText="Save" />
+ <com:TButtonColumn
+ HeaderText="Delete"
+ Text="Delete"
+ CommandName="delete"/>
+ </com:TDataGrid>
+ <com:TButton Text="Add" OnClick="addNewPerson" />
+</com:TForm>
+</body>
+</html>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/pages/crud2.php b/demos/sqlmap-sample/protected/pages/crud2.php new file mode 100644 index 00000000..46fe3893 --- /dev/null +++ b/demos/sqlmap-sample/protected/pages/crud2.php @@ -0,0 +1,79 @@ +<?php
+
+Prado::using('Example.Person');
+
+class crud2 extends TPage
+{
+ private function sqlmap()
+ {
+ return $this->Application->Modules['person-sample'];
+ }
+
+ private function loadData()
+ {
+ $this->personList->DataSource =
+ $this->sqlmap()->queryForList('SelectAll');
+ $this->personList->dataBind();
+ }
+
+ public function onLoad($param)
+ {
+ if(!$this->IsPostBack)
+ $this->loadData();
+ }
+
+ protected function editPerson($sender,$param)
+ {
+ $this->personList->EditItemIndex=$param->Item->ItemIndex;
+ $this->loadData();
+ }
+
+ protected function deletePerson($sender, $param)
+ {
+ $id = $this->getKey($sender, $param);
+
+ $this->sqlmap()->update("Delete", $id);
+ $this->loadData();
+ }
+
+ protected function updatePerson($sender, $param)
+ {
+ $person = new Person();
+ $person->FirstName = $this->getText($param, 0);
+ $person->LastName = $this->getText($param, 1);
+ $person->HeightInMeters = $this->getText($param, 2);
+ $person->WeightInKilograms = $this->getText($param, 3);
+ $person->ID = $this->getKey($sender, $param);
+
+ $this->sqlmap()->update("Update", $person);
+ $this->refreshList($sender, $param);
+ }
+
+ protected function addNewPerson($sender, $param)
+ {
+ $person = new Person;
+ $person->FirstName = "-- New Person --";
+ $this->sqlmap()->insert("Insert", $person);
+
+ $this->loadData();;
+ }
+
+ protected function refreshList($sender, $param)
+ {
+ $this->personList->EditItemIndex=-1;
+ $this->loadData();
+ }
+
+ private function getText($param, $index)
+ {
+ $item = $param->Item;
+ return $item->Cells[$index]->Controls[0]->Text;
+ }
+
+ private function getKey($sender, $param)
+ {
+ return $sender->DataKeys[$param->Item->DataSourceIndex];
+ }
+}
+
+?>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/person-db/person.xml b/demos/sqlmap-sample/protected/person-db/person.xml new file mode 100644 index 00000000..4ffe44d9 --- /dev/null +++ b/demos/sqlmap-sample/protected/person-db/person.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8" ?>
+<sqlMap>
+
+ <select id="SelectAll" resultClass="Person">
+ SELECT
+ per_id as ID,
+ per_first_name as FirstName,
+ per_last_name as LastName,
+ per_birth_date as BirthDate,
+ per_weight_kg as WeightInKilograms,
+ per_height_m as HeightInMeters
+ FROM
+ person
+ </select>
+
+</sqlMap>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/person-db/personHelper.xml b/demos/sqlmap-sample/protected/person-db/personHelper.xml new file mode 100644 index 00000000..ea2d4302 --- /dev/null +++ b/demos/sqlmap-sample/protected/person-db/personHelper.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8" ?>
+
+<sqlMap Name="PersonHelper">
+
+ <select id="Select" parameterClass="int" resultClass="Person">
+ select
+ PER_ID as ID,
+ PER_FIRST_NAME as FirstName,
+ PER_LAST_NAME as LastName,
+ PER_BIRTH_DATE as BirthDate,
+ PER_WEIGHT_KG as WeightInKilograms,
+ PER_HEIGHT_M as HeightInMeters
+ from PERSON
+ WHERE
+ PER_ID = #value#
+ </select>
+
+ <insert id="Insert" parameterClass="Person">
+ insert into PERSON
+ (PER_ID, PER_FIRST_NAME, PER_LAST_NAME,
+ PER_BIRTH_DATE, PER_WEIGHT_KG, PER_HEIGHT_M)
+ values
+ (#ID#, #FirstName#, #LastName#,
+ #BirthDate#, #WeightInKilograms#, #HeightInMeters#)
+ </insert>
+
+ <update id="Update" parameterClass="Person">
+ update PERSON set
+ PER_FIRST_NAME = #FirstName#,
+ PER_LAST_NAME = #LastName#,
+ PER_BIRTH_DATE = #BirthDate#,
+ PER_WEIGHT_KG = #WeightInKilograms#,
+ PER_HEIGHT_M = #HeightInMeters#
+ where PER_ID = #ID#
+ </update>
+
+ <delete id="Delete" parameterClass="int">
+ delete from PERSON
+ where PER_ID = #value#
+ </delete>
+
+</sqlMap>
diff --git a/demos/sqlmap-sample/protected/person-db/test.db b/demos/sqlmap-sample/protected/person-db/test.db Binary files differnew file mode 100644 index 00000000..a40c91fe --- /dev/null +++ b/demos/sqlmap-sample/protected/person-db/test.db diff --git a/demos/sqlmap-sample/protected/person-sqlmap.xml b/demos/sqlmap-sample/protected/person-sqlmap.xml new file mode 100644 index 00000000..8d5319e8 --- /dev/null +++ b/demos/sqlmap-sample/protected/person-sqlmap.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?>
+<sqlMapConfig>
+
+ <provider class="TAdodbProvider">
+ <datasource driver="sqlite" host="protected/person-db/test.db" />
+ </provider>
+
+ <sqlMaps>
+ <sqlMap resource="person-db/person.xml"/>
+ <sqlMap resource="person-db/personHelper.xml"/>
+ </sqlMaps>
+
+</sqlMapConfig>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/petshop-db/petshop.db b/demos/sqlmap-sample/protected/petshop-db/petshop.db Binary files differnew file mode 100644 index 00000000..9a4404df --- /dev/null +++ b/demos/sqlmap-sample/protected/petshop-db/petshop.db diff --git a/demos/sqlmap-sample/protected/petshop-db/products.xml b/demos/sqlmap-sample/protected/petshop-db/products.xml new file mode 100644 index 00000000..bf1453b2 --- /dev/null +++ b/demos/sqlmap-sample/protected/petshop-db/products.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8" ?>
+<sqlmap>
+
+ <select id="SelectAllProducts">
+ SELECT *
+ FROM
+ product
+ </select>
+
+</sqlmap>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/petshop-sqlmap.xml b/demos/sqlmap-sample/protected/petshop-sqlmap.xml new file mode 100644 index 00000000..f77fe2b7 --- /dev/null +++ b/demos/sqlmap-sample/protected/petshop-sqlmap.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8" ?>
+<sqlMapConfig>
+
+ <provider class="TAdodbProvider">
+ <datasource driver="sqlite" host="protected/petshop-db/petshop.db" />
+ </provider>
+
+ <sqlMaps>
+ <sqlMap resource="petshop-db/products.xml"/>
+ </sqlMaps>
+
+</sqlMapConfig>
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/runtime/application.xml/config.cache b/demos/sqlmap-sample/protected/runtime/application.xml/config.cache Binary files differnew file mode 100644 index 00000000..ce7bd76a --- /dev/null +++ b/demos/sqlmap-sample/protected/runtime/application.xml/config.cache diff --git a/demos/sqlmap-sample/protected/runtime/application.xml/global.cache b/demos/sqlmap-sample/protected/runtime/application.xml/global.cache new file mode 100644 index 00000000..b35e9768 --- /dev/null +++ b/demos/sqlmap-sample/protected/runtime/application.xml/global.cache @@ -0,0 +1 @@ +a:1:{s:35:"prado:securitymanager:validationkey";s:32:"e632288aebf1e51c0447ab4b701f1aa7";}
\ No newline at end of file diff --git a/demos/sqlmap-sample/protected/runtime/application.xml/sqlite.cache b/demos/sqlmap-sample/protected/runtime/application.xml/sqlite.cache Binary files differnew file mode 100644 index 00000000..be9b9cc0 --- /dev/null +++ b/demos/sqlmap-sample/protected/runtime/application.xml/sqlite.cache diff --git a/demos/sqlmap-sample/tests/PersonTest.php b/demos/sqlmap-sample/tests/PersonTest.php new file mode 100644 index 00000000..c40bffa0 --- /dev/null +++ b/demos/sqlmap-sample/tests/PersonTest.php @@ -0,0 +1,57 @@ +<?php
+
+class PersonTest extends UnitTestCase
+{
+ function testPersonList()
+ {
+ //try it
+ $people = TMapper::instance()->queryForList("SelectAll");
+
+ //test it
+ $this->assertNotNull($people, "Person list is not returned");
+ $this->assertTrue(count($people) > 0, "Person list is empty");
+ $person = $people[0];
+ $this->assertNotNull($person, "Person not returned");
+ }
+
+ function testPersonUpdate()
+ {
+ $expect = "wei";
+ $edited = "Nah";
+
+ //get it;
+ $person = TMapper::instance()->queryForObject("Select", 1);
+
+ //test it
+ $this->assertNotNull($person);
+ $this->assertEqual($expect, $person->FirstName);
+
+ //change it
+ $person->FirstName = $edited;
+ TMapper::instance()->update("Update", $person);
+
+ //get it again
+ $person = TMapper::instance()->queryForObject("Select", 1);
+
+ //test it
+ $this->assertEqual($edited, $person->FirstName);
+
+ //change it back
+ $person->FirstName = $expect;
+ TMapper::instance()->update("Update", $person);
+ }
+
+ function testPersonDelete()
+ {
+ //insert it
+ $person = new Person;
+ $person->ID = -1;
+ TMapper::instance()->insert("Insert", $person);
+
+ //delte it
+ $count = TMapper::instance()->delete("Delete", -1);
+ $this->assertEqual(1, $count);
+ }
+}
+
+?>
\ No newline at end of file diff --git a/demos/sqlmap-sample/tests/readme.txt b/demos/sqlmap-sample/tests/readme.txt new file mode 100644 index 00000000..c0d0afdb --- /dev/null +++ b/demos/sqlmap-sample/tests/readme.txt @@ -0,0 +1 @@ +The ''Data'' directory and ''Data/test.db'' must be writable by PHP for SQLite database to work.
\ No newline at end of file diff --git a/demos/sqlmap-sample/tests/run_tests.php b/demos/sqlmap-sample/tests/run_tests.php new file mode 100644 index 00000000..843d0a79 --- /dev/null +++ b/demos/sqlmap-sample/tests/run_tests.php @@ -0,0 +1,33 @@ +<?php
+
+//define simple test location
+define('SIMPLE_TEST', realpath('../../../tests/UnitTests/simpletest'));
+
+//define prado framework location
+define('PRADO', realpath('../../../framework'));
+
+//define directory that contains business objects
+define('MY_MODELS', realpath('../protected/business-objects'));
+
+require_once(SIMPLE_TEST.'/unit_tester.php');
+require_once(SIMPLE_TEST.'/reporter.php');
+require_once(PRADO.'/prado.php');
+require_once(MY_MODELS.'/Person.php');
+
+//supress strict warnings
+error_reporting(E_ALL);
+
+//import Data mapper
+Prado::using('System.DataAccess.SQLMap.TMapper');
+
+//Add tests
+$test = new GroupTest('SQLMap Tutorial tests');
+$test->addTestFile('PersonTest.php');
+if(SimpleReporter::inCli())
+ $reporter = new TextReporter();
+else
+ $reporter = new HtmlReporter();
+$test->run($reporter);
+
+
+?>
\ No newline at end of file diff --git a/demos/sqlmap-sample/tests/sqlmap.xml b/demos/sqlmap-sample/tests/sqlmap.xml new file mode 100644 index 00000000..3968fab4 --- /dev/null +++ b/demos/sqlmap-sample/tests/sqlmap.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?>
+<sqlMapConfig>
+
+ <provider class="TAdodbProvider">
+ <datasource driver="sqlite" host="../protected/person-db/test.db" />
+ </provider>
+
+ <sqlMaps>
+ <sqlMap resource="../protected/person-db/person.xml"/>
+ <sqlMap resource="../protected/person-db/personHelper.xml"/>
+ </sqlMaps>
+
+</sqlMapConfig>
\ No newline at end of file diff --git a/docs/sqlmap/latex/ch1.tex b/docs/sqlmap/latex/ch1.tex new file mode 100644 index 00000000..405256ef --- /dev/null +++ b/docs/sqlmap/latex/ch1.tex @@ -0,0 +1,43 @@ +\chapter{Introduction}
+
+\section{Overview}
+
+The SQLMap DataMapper framework makes it easier to use a database with a PHP
+application. SQLMap DataMapper couples objects with stored procedures or SQL
+statements using a XML descriptor. Simplicity is the biggest advantage of the
+SQLMap DataMapper over object relational mapping tools. To use SQLMap
+DataMapper you rely on your own objects, XML, and SQL. There is little to
+learn that you don't already know. With SQLMap DataMapper you have the full
+power of both SQL and stored procedures at your fingertips.
+
+The SQLMap for PHP is based on iBATIS.NET - DataMapper Application Framework
+from http://ibatis.apache.org/.The PHP version support most of the features
+found in iBATIS.NET exception the following:
+
+\begin{itemize}
+ \item Dynamic SQL.
+ \item Distributed Transactions.
+\end{itemize}
+
+\section{What's covered here}
+
+This Guide covers the PHP implementations of SQLMap DataMapper. The Java and
+.NET implementation offers the same services with some changes in the API.
+
+Since SQLMap relies on an XML descriptor to create the mappings, much of the
+material applies to both implementations.
+
+For installation instructions, see the section called the SQLMap PHP Developer
+Guide.
+
+A Tutorial is also available. We recommend reviewing the Tutorial for your
+platform before reading this Guide.
+
+\section{Support}
+
+Add Forum and Trac.
+
+\section{Disclaimer}
+SQLMap MAKES NO WARRANTIES, EXPRESS OR IMPLIED, AS TO THE INFORMATION IN THIS
+DOCUMENT. The names of actual companies and products mentioned herein may be
+the trademarks of their respective owners.
diff --git a/docs/sqlmap/latex/ch2.tex b/docs/sqlmap/latex/ch2.tex new file mode 100644 index 00000000..da135e9c --- /dev/null +++ b/docs/sqlmap/latex/ch2.tex @@ -0,0 +1,151 @@ +\chapter{The Big Picture}
+\section{Introduction}
+SQLMap is a simple but complete framework that makes it easy for you to map
+your objects to your SQL statements or stored procedures. The goal of the
+SQLMap framework is to obtain 80\% of data access functionality using only
+20\% of the code.
+
+\section{What does it do?}
+Developers often create maps between objects within an application. One
+definition of a Mapper is an ``object that sets up communication between two
+independent objects.'' A Data Mapper is a ``layer of mappers that moves data
+between objects and a database while keeping them independent of each other
+and the mapper itself.'' [Patterns of Enterprise Architecture, ISBN
+0-321-12742-0].
+
+You provide the database and the objects; SQLMap provides the mapping layer
+that goes between the two.
+
+\section{How does it work?}
+
+Your programming platform already provides a capable library for accessing
+databases, whether through SQL statements or stored procedures. But developers
+find several things are still hard to do well when using ``stock'' PHP
+function including:
+
+Separating SQL code from programming code Passing input parameters to the
+library classes and extracting the output Separating data access classes from
+business logic classes Caching often-used data until it changes Managing
+transactions and many more -- by using XML documents to create a mapping
+between a plain-old object and a SQL statement or a stored procedure. The
+``plain-old object'' can be any PHP object.
+
+\begin{mybox}{Tip:}
+The object does not need to be part of a special object hierarchy or implement
+a special interface. (Which is why we call them ``plain-old'' objects.)
+Whatever you are already using should work just fine.
+\end{mybox}
+
+\begin{figure}[!h]
+ \centering
+ \includegraphics[width=0.65\textwidth]{diagram}
+ \caption{SQLMap DataMapper work flow}
+ \label{fig:diagram}
+\end{figure}
+
+Here's a high level description of the work flow diagrammed by
+Figure~\ref{fig:diagram}: Provide a parameter, either as an object or a
+primitive type. The parameter can be used to set runtime values in your SQL
+statement or stored procedure. If a runtime value is not needed, the parameter
+can be omitted.
+
+Execute the mapping by passing the parameter and the name you gave the
+statement or procedure in your XML descriptor. This step is where the magic
+happens. The framework will prepare the SQL statement or stored procedure, set
+any runtime values using your parameter, execute the procedure or statement,
+and return the result.
+
+In the case of an update, the number of rows affected is returned. In the case
+of a query, a single object, or a collection of objects is returned. Like the
+parameter, the result object, or collection of objects, can be a plain-old
+object or a primitive type.
+
+So, what does all this look like in your source code? Here's how you might
+code the insert of a ``lineItem'' object into your database.
+
+\begin{verbatim}
+TMapper::instance()->insert("InsertLineItem", $lineItem);
+\end{verbatim}
+
+If your database is generating the primary keys, the generated key can be
+returned from the same method call, like this:
+
+\begin{verbatim}
+$myKey = TMapper::instance()->insert("InsertLineItem", $lineItem);
+\end{verbatim}
+
+Example~\ref{example:2.1} shows an XML descriptor for ``InsertLineItem''.
+\begin{example}\label{example:2.1}
+The ``InsertLineItem'' descriptor
+\begin{verbatim}
+<insert id="InsertLineItem" parameterClass="LineItem">
+ INSERT INTO [LinesItem]
+ (Order_Id, LineItem_LineNum, Item_Id, LineItem_Quantity, LineItem_UnitPrice)
+ VALUES
+ (#Order.Id#, #LineNumber#, #Item.Id#, #Quantity#, #Item.ListPrice#)
+ <selectKey type="post" resultClass="int" property="Id" >
+ select @@IDENTITY as value
+ </selectKey>
+</insert>
+\end{verbatim}
+\end{example}
+
+The \tt{<selectKey>} stanza returns an auto-generated key from a SQL Server
+database (for example). If you need to select multiple rows, SQLMap can return
+a list of objects, each mapped to a row in the result set:
+\begin{verbatim}
+$productList = Mapper::instance()->queryForList("selectProduct",$categoryKey);
+\end{verbatim}
+Or just one, if that's all you need:
+\begin{verbatim}
+$product = Mapper::instance()->queryForObject("selectProduct",$categoryKey);
+\end{verbatim}
+
+Of course, there's more, but this is SQLMap from 10,000 meters. (For a longer,
+gentler introduction, see the Tutorial.) Section~\ref{section:3} describes the
+Data Map definition files -- where the statement for ``InsertLineItem'' would
+be defined. The Developers Guide for PHP (Section~\ref{section:4}) describes
+the "bootstrap" configuration file that exposes SQLMap to your application.
+
+\section{Is SQLMap the best choice for my project?}
+SQLMap is a Data Mapping tool. Its role is to map the columns of a database
+query (including a stored procedure) to the properties of an object. If your
+application is based on business objects (including array or lists of
+objects), then SQLMap can be a good choice. SQLMap is an even better choice
+when your application is layered, so that that the business layer is distinct
+from the user-interface layer.
+
+Under these circumstances, another good choice would be an Object/Relational
+Mapping tool (OR/M tool), like [...]. Other products in this category are
+[...] and [...] . An OR/M tool generates all or most of the SQL for you,
+either beforehand or at runtime. These products are called OR/M tools because
+they try to map an object graph to a relational schema.
+
+SQLMap is not an OR/M tool. SQLMap helps you map objects to stored procedures
+or SQL statements. The underlying schema is irrelevant. An OR/M tool is great
+if you can map your objects to tables. But they are not so great if your
+objects are stored as a relational view rather than as a table. If you can
+write a statement or procedure that exposes the columns for your object,
+regardless of how they are stored, SQLMap can do the rest.
+
+So, how do you decide whether to OR/M or to DataMap? As always, the best
+advice is to implement a representative part of your project using either
+approach, and then decide. But, in general, OR/M is a good thing when you
+\begin{itemize}
+ \item Have complete control over your database implementation.
+ \item Do not have a Database Administrator or SQL guru on the team.
+ \item Need to model the problem domain outside the database as an object graph.
+\end{itemize}
+Likewise, the best time to use a Data Mapper, like SQLMap, is when:
+\begin{itemize}
+ \item You do not have complete control over the database implementation, or want to
+continue to access a legacy database as it is being refactored.
+ \item You have database administrators or SQL gurus on the team.
+ \item The database is being used to model the problem domain, and the application's
+primary role is to help the client use the database model.
+\end{itemize}
+
+In the end, you have to decide what's best for your project. If a OR/M tool
+works better for you, that's great! If your next project has different needs,
+then we hope you give SQLMap another look. If SQLMap works for you now:
+Excellent!
diff --git a/docs/sqlmap/latex/ch3.tex b/docs/sqlmap/latex/ch3.tex new file mode 100644 index 00000000..79b2c42a --- /dev/null +++ b/docs/sqlmap/latex/ch3.tex @@ -0,0 +1,714 @@ +\chapter{Working with Data Maps}\label{section:3}
+\section{Introduction}
+
+If you want to know how to configure and install SQLMap, see the Developer
+Guide section~\ref{section:4} . But if you want to know how SQLMap really
+works, continue from here.
+
+The Data Map definition file is where the interesting stuff happens. Here, you
+define how your application interacts with your database. As mentioned, the
+Data Map definition is an XML descriptor file. By using a service routine
+provided by SQLMap, the XML descriptors are rendered into a client object (or
+Mapper). To access your Data Maps, your application calls the client object
+and passes in the name of the statement you need.
+
+The real work of using SQLMap is not so much in the application code, but in
+the XML descriptors that SQLMap renders. Instead of monkeying with application
+source code, you monkey with XML descriptors instead. The benefit is that the
+XML descriptors are much better suited to the task of mapping your object
+properties to database entities. At least, that's our own experience with our
+own applications. Of course, your mileage may vary.
+
+\section{What's in a Data Map definition file, anyway?}
+
+If you read the Tutorial, you've already seen some simple Data Map examples,
+like the one shown in Example~\ref{example:2.1}.
+
+\begin{example}\label{example:3.1}
+ A simple Data Map (PHP)
+ \begin{verbatim}
+<?xml version="1.0" encoding="UTF-8" ?>
+ <sqlMap namespace="LineItem">
+ <insert id="InsertLineItem" parameterClass="LineItem">
+ INSERT INTO [LinesItem]
+ (Order_Id, LineItem_LineNum, Item_Id, LineItem_Quantity, LineItem_UnitPrice)
+ VALUES
+ (#Order.Id#, #LineNumber#, #Item.Id#, #Quantity#, #Item.ListPrice#)
+ </insert>
+</sqlMap>
+ \end{verbatim}
+\end{example}
+This map takes some properties from a \tt{LineItem} instance and merges the
+values into the SQL statement. The value-add is that our SQL in separated from
+our program code, and we can pass our \tt{LineItem} instance directly to a
+library method:
+\begin{verbatim}
+TMapper::instance()->insert("InsertLineItem",$lineItem);
+\end{verbatim}
+No fuss, no muss. Likewise, see Example\ref{example:3.2} for a simple select
+statement.
+
+\begin{mybox}{Info:}
+\textbf{A Quick Glance at Inline Parameters}
+
+Say we have a mapped statement element that looks like this:
+\begin{verbatim}
+ <statement id="InsertProduct">
+ insert into Products (Product_Id, Product_Description)
+ values (#Id#, #Description#);
+</statement>
+\end{verbatim}
+The inline parameters here are \tt{\#Id\#} and \tt{\#Description\#}. Let's
+also say that we have an object with the properties \tt{Id} and
+\tt{Description}. If we set the object properties to $5$ and ``dog'',
+respectively, and passed the object to the mapped statement, we'd end up with
+a runtime query that looked like this:
+\begin{verbatim}
+insert into Products (Product_Id, Product_Description) values (5, 'dog');
+\end{verbatim}
+For more about inline parameters, see Chapter~\ref{section:3.4}.
+\end{mybox}
+
+But, what if you wanted some ice cream with that pie? And maybe a cherry on
+top? What if we wanted to cache the result of the select? Or, what if we
+didn't want to use SQL aliasing or named parameters. (Say, because we were
+using pre-existing SQL that we didn't want to touch.)
+Example~\ref{example:3.2} shows a Data Map that specifies a cache, and uses a
+\tt{<parameterMap>} and a \tt{<resultMap>} to keep our SQL pristine.
+
+\begin{example}\label{example:3.2}
+A Data Map definition file with some bells and whistles
+\begin{verbatim}
+<?xml version="1.0" encoding="UTF-8" ?>
+ <sqlMap namespace="Product">
+
+ <cacheModel id="productCache" type="LRU">
+ <flushInterval hours="24"/>
+ <property name="CacheSize" value="1000" />
+ </cacheModel>
+
+ <resultMap id="productResult" class="Product">
+ <result property="Id" column="Product_Id"/>
+ <result property="Description" column="Product_Description"/>
+ </resultMap>
+
+ <select id="GetProduct" parameterMap="productParam" cacheModel="productCache">
+ select * from Products where Product_Id = ?
+ </select>
+
+ <parameterMap id="productParam" class="Product">
+ <parameter property="Id"/>
+ </parameterMap>
+
+</sqlMap>
+\end{verbatim}
+\end{example}
+In Example~\ref{example:3.2}, \tt{<parameterMap>} maps the SQL ``?'' to the
+product \tt{Id} property. The \tt{<resultMap>} maps the columns to our object
+properties. The \tt{<cacheModel>} keeps the result of the last one thousand of
+these queries in active memory for up to 24 hours.
+
+Example~\ref{example:3.2} is longer and more complex than
+Example~\ref{example:3.1}, but considering what you get in return, it seems
+like a fair trade. (A bargain even.)
+
+Many agile developers would start with something like
+Example~\ref{example:3.1} and add features like caching later. If you changed
+the Data Map from Example~\ref{example:3.1} to Example~\ref{example:3.2}, you
+would not have to touch your application source code at all. You can start
+simple and add complexity only when it is needed.
+
+A single Data Map definition file can contain as many Cache Models, Type
+Aliases, Result Maps, Parameter Maps, and Mapped Statements (including stored
+procedures), as you like. Everything is loaded into the same configuration, so
+you can define elements in one Data Map and then use them in another. Use
+discretion and organize the statements and maps appropriately for your
+application by finding some logical way to group them.
+
+\section{Mapped Statements}
+Mapped Statements can hold any SQL statement and can use Parameter Maps and
+Result Maps for input and output. (A stored procedure is a specialized form of
+a statement. See section~\ref{section:3.3.1} and \ref{section:3.3.2} for more
+information.)
+
+If the case is simple, the Mapped Statement can reference the parameter and
+result classes directly. Mapped Statements support caching through reference
+to a Cache Model element. The following example shows the syntax for a
+statement element.
+
+\begin{example}\label{example:3.3}
+Statement element syntax
+\begin{verbatim}
+<statement id="statement.name"
+ [parameterMap="parameterMap.name"]
+ [parameterClass="class.name"]
+ [resultMap="resultMap.name"]
+ [resultClass="class.name"]
+ [listClass="class.name"]
+ [cacheModel="cache.name"]
+>
+
+ select * from Products where Product_Id = [?|#propertyName#]
+ order by [$simpleDynamic$]
+
+</statement>
+\end{verbatim}
+\end{example}
+In Example~\ref{example:3.3}, the [bracketed] parts are optional, and some
+options are mutually exclusive. It is perfectly legal to have a Mapped
+Statement as simple as shown by Example~\ref{example:3.4}.
+
+\begin{example}\label{example:3.4}
+A simplistic Mapped Statement
+\begin{verbatim}
+<statement id="InsertTestProduct" >
+ insert into Products (Product_Id, Product_Description) values (1, "Shih Tzu")
+</statement>
+\end{verbatim}
+\end{example}
+
+Example~\ref{example:3.4} is obviously unlikely, unless you are running a
+test. But it does shows that you can use SQLMap to execute arbitrary SQL
+statements. More likely, you will use the object mapping features with
+Parameter Maps (Chapter~\ref{section:3.4}) and Result Maps
+(Chapter~\ref{section:3.5}) since that's where the magic happens.
+
+\subsection{Statement Types}\label{section:3.3.1}
+The \tt{<statement>} element is a general ``catch all'' element that can be
+used for any type of SQL statement. Generally it is a good idea to use one of
+the more specific statement-type elements. The more specific elements provided
+better error-checking and even more functionality. (For example, the insert
+statement can return a database-generated key.) Table~\ref{table:3.1}
+summarizes the statement-type elements and their supported attributes and
+features. The various attributes used by statement-type elements are covered
+in Section~\ref{section:3.3.4}.
+\begin{table}[!hpt]
+\caption{The six statement-type elements } \label{table:3.1}
+ \centering
+\begin{tabular}{|l|l|l|l|}
+ \hline
+ \textbf{Statement Element} &
+ \textbf{Attribute} &
+ \textbf{Child Elements} &
+ \textbf{Methods} \\
+ \hline
+ % after \\: \hline or \cline{col1-col2} \cline{col3-col4} ...
+ \tt{<statement>} &
+ \begin{minipage}{0.17\textwidth}
+ \vspace{3mm}
+ id \\
+ parameterClass \\
+ resultClass \\
+ listClass \\
+ parameterMap \\
+ resultMap\\
+ cacheModel
+ \vspace{3mm}
+ \end{minipage}
+ &
+ \begin{minipage}{0.22\textwidth}
+ None
+ \end{minipage}
+ &
+ \begin{minipage}{0.2\textwidth}
+ Insert \\ Update \\ Delete \\ All query methods
+ \end{minipage}
+ \\
+ \hline
+ \tt{<insert>} &
+ \begin{minipage}{0.17\textwidth}
+ \vspace{3mm}
+ id \\
+ parameterClass \\
+ parameterMap
+ \vspace{3mm}
+ \end{minipage}
+ &
+ \begin{minipage}{0.22\textwidth}
+ \tt{<selectKey>}\\
+ \tt{<generate>}
+ \end{minipage}
+ &
+ \begin{minipage}{0.2\textwidth}
+ Insert \\ Update \\ Delete
+ \end{minipage}
+\\
+\hline
+ \tt{<update>} &
+ \begin{minipage}{0.17\textwidth}
+ \vspace{3mm}
+ id \\
+ parameterClass \\
+ parameterMap \\
+ extends
+ \vspace{3mm}
+ \end{minipage}
+ &
+ \begin{minipage}{0.22\textwidth}
+ \tt{<generate>}
+ \end{minipage}
+ &
+ \begin{minipage}{0.2\textwidth}
+ Insert \\ Update \\ Delete
+ \end{minipage}
+ \\
+ \hline
+ \tt{<delete>} &
+ \begin{minipage}{0.17\textwidth}
+ \vspace{3mm}
+ id \\
+ parameterClass \\
+ parameterMap \\
+ extends
+ \vspace{3mm}
+ \end{minipage}
+ &
+ \begin{minipage}{0.22\textwidth}
+ \tt{<generate>}
+ \end{minipage}
+ &
+ \begin{minipage}{0.2\textwidth}
+ Insert \\ Update \\ Delete
+ \end{minipage}
+ \\
+ \hline
+ \tt{<select>} &
+ \begin{minipage}{0.17\textwidth}
+ \vspace{3mm}
+ id\\
+ parameterClass\\
+ resultClass\\
+ listClass \\
+ parameterMap \\
+ resultMap \\
+ cacheModel \\
+ extends
+ \vspace{3mm}
+ \end{minipage}
+ &
+ \begin{minipage}{0.22\textwidth}
+ \tt{<generate>}
+ \end{minipage}
+ &
+ \begin{minipage}{0.2\textwidth}
+ All query methods
+ \end{minipage}
+ \\
+ \hline
+ \tt{<procedure>} &
+ \begin{minipage}{0.17\textwidth}
+ \vspace{3mm}
+ id\\
+ parameterMap \\
+ resultClass\\
+ resultMap \\
+ cacheModel
+ \vspace{3mm}
+ \end{minipage}
+ &
+ \begin{minipage}{0.22\textwidth}
+ None
+ \end{minipage}
+ &
+ \begin{minipage}{0.2\textwidth}
+ Insert \\ Update \\ Delete \\ All query methods
+ \end{minipage}
+ \\
+ \hline
+\end{tabular}
+\end{table}
+
+\subsection{Stored Procedures}\label{section:3.3.2}
+
+????
+
+\section{The SQL} \label{section:3.3.3}
+If you are not using stored procedures, the most important part of a
+statement-type element is the SQL. You can use any SQL statement that is valid
+for your database system. Since SQLMap passes the SQL through to a standard
+libraries (Adodb for PHP), you can use any statement with SQLMap that you
+could use without SQLMap. You can use whatever functions your database system
+supports, and even send multiple statements, so long as your driver or
+provider supports them.
+
+%If standard, static SQL isn't enough, iBATIS can help you build a dynamic SQL
+%statement. See Section 3.9 for more about Dynamic SQL.
+
+
+\subsection{Escaping XML symbols} Because you are combining SQL and XML in a
+single document, conflicts can occur. The most common conflict is the
+greater-than and less-than symbols (><). SQL statements use these symbols as
+operators, but they are reserved symbols in XML. A simple solution is to
+escape the SQL statements that uses XML reserved symbols within a CDATA
+element. Example~\ref{example:3.6} demonstrates this.
+
+\begin{example}\label{example:3.6}
+Using CDATA to ``escape'' SQL code
+\begin{verbatim}
+<statement id="SelectPersonsByAge" parameterClass="int" resultClass="Person">
+ <![CDATA[
+ SELECT * FROM PERSON WHERE AGE > #value#
+ ]]>
+</statement>
+\end{verbatim}
+\end{example}
+
+\subsection{Auto-Generated Keys}
+Many database systems support auto-generation of primary key fields, as a
+vendor extension. Some vendors pre-generate keys (e.g. Oracle), some vendors
+post-generate keys (e.g. MS-SQL Server and MySQL). In either case, you can
+obtain a pre-generated key using a \tt{<selectKey>} stanza within an
+\tt{<insert>} element. Example~\ref{example:3.7} shows an \tt{<insert>}
+statement for either approach.
+
+\begin{example}\label{example:3.7}
+\normalfont \tt{<insert>} statements using \tt{<selectKey>} stanzas
+\begin{verbatim}
+<!¡ªOracle SEQUENCE Example using .NET 1.1 System.Data.OracleClient -->
+<insert id="insertProduct-ORACLE" parameterClass="product">
+ <selectKey resultClass="int" type="pre" property="Id" >
+ SELECT STOCKIDSEQUENCE.NEXTVAL AS VALUE FROM DUAL
+ </selectKey>
+ insert into PRODUCT (PRD_ID,PRD_DESCRIPTION) values (#id#,#description#)
+</insert>
+
+<!¡ª Microsoft SQL Server IDENTITY Column Example -->
+<insert id="insertProduct-MS-SQL" parameterClass="product">
+ insert into PRODUCT (PRD_DESCRIPTION)
+ values (#description#)
+ <selectKey resultClass="int" type="post" property="id" >
+ select @@IDENTITY as value
+ </selectKey>
+</insert>
+
+<!-- MySQL Example -->
+<insert id="insertProduct-MYSQL" parameterClass="product">
+ insert into PRODUCT (PRD_DESCRIPTION)
+ values (#description#)
+ <selectKey resultClass="int" type="post" property="id" >
+ select LAST_INSERT_ID() as value
+ </selectKey>
+</insert>
+\end{verbatim}
+\end{example}
+
+\subsection{\tt{<generate>} tag}
+You can use SQLMap to execute any SQL statement your application requires.
+When the requirements for a statement are simple and obvious, you may not even
+need to write a SQL statement at all. The \tt{<generate>} tag can be used to
+create simple SQL statements automatically, based on a \tt{<parameterMap>}
+element. The four CRUD statement types (insert, select, update, and delete)
+are supported. For a select, you can select all or select by a key (or keys).
+Example~\ref{example:3.8} shows an example of generating the usual array of
+CRUD statements.
+
+\begin{mybox}{Important:}
+The intended use of the \tt{<generate>} tag is to save developers the trouble
+of coding mundane SQL statements (and only mundane statements). It is not
+meant as a object-to-relational mapping tool. There are many frameworks that
+provide extensive object-to-relational mapping features. The \tt{<generate>}
+tag is not a replacement for any of those. When the \tt{<generate>} tag does
+not suit your needs, use a conventional statement instead.
+\end{mybox}
+
+\begin{example}\label{example:3.8}
+\normalfont Creating the ``usual suspects'' with the \tt{<generate>} tag
+\begin{verbatim}
+ <parameterMap id="insert-generate-params">
+ <parameter property="Name" column="Category_Name"/>
+ <parameter property="Guid" column="Category_Guid" dbType="UniqueIdentifier"/>
+ </parameterMap>
+
+ <parameterMap id="update-generate-params" extends="insert-generate-params">
+ <parameter property="Id" column="Category_Id" />
+ </parameterMap>
+
+ <parameterMap id="delete-generate-params">
+ <parameter property="Id" column="Category_Id" />
+ <parameter property="Name" column="Category_Name"/>
+ </parameterMap>
+
+ <parameterMap id="select-generate-params">
+ <parameter property="Id" column="Category_Id" />
+ <parameter property="Name" column="Category_Name"/>
+ <parameter property="Guid" column="Category_Guid" dbType="UniqueIdentifier"/>
+ </parameterMap>
+
+ <update id="UpdateCategoryGenerate" parameterMap="update-generate-params">
+ <generate table="Categories" by="Category_Id"/>
+ </update>
+
+ <delete id="DeleteCategoryGenerate" parameterMap="delete-generate-params">
+ <generate table="Categories" by="Category_Id, Category_Name"/>
+ </delete>
+
+ <select id="SelectByPKCategoryGenerate" resultClass="Category" parameterClass="Category"
+ parameterMap="select-generate-params">
+ <generate table="Categories" by="Category_Id"/>
+ </select>
+
+ <select id="SelectAllCategoryGenerate" resultClass="Category"
+ parameterMap="select-generate-params">
+ <generate table="Categories" />
+ </select>
+
+ <insert id="InsertCategoryGenerate" parameterMap="insert-generate-params">
+ <selectKey property="Id" type="post" resultClass="int">
+ select @@IDENTITY as value
+ </selectKey>
+ <generate table="Categories" />
+ </insert>
+\end{verbatim}
+\end{example}
+The tag generates ANSI SQL, which should work with any compliant database.
+Special types, such as blobs, are not supported, and vendor-specific types are
+also not supported. But, the generate tag does keep the simple things simple.
+
+\begin{mybox}{Note:}
+The SQL is generated when the DataMapper instance is built and can be cached
+afterward, so there is no performance impact at execution time.
+\end{mybox}
+
+The generate tag supports two attributes :
+\begin{table}[!htb]\centering\label{table:3.2}
+\caption{\tt{<generate>} attributes}
+\begin{tabular}{|l|l|l|}
+ \hline
+ % after \\: \hline or \cline{col1-col2} \cline{col3-col4} ...
+ \textbf{Attribute} & \textbf{Description} & \textbf{Required} \\
+ \hline
+ table & specifies the table name to use in the SQL statement. & yes \\
+ \hline
+ by & specifies the columns to use in a WHERE clause & no \\
+ \hline
+\end{tabular}
+\end{table}
+
+\section{Statement-type Element Attributes}\label{section:3.3.4}
+The six statement-type elements take various attributes. See
+Section~\ref{section:3.3.1} for a table itemizing which attributes each
+element-type accepts. The individual attributes are described in the sections
+that follow.
+
+\subsection{\tt{id} attribute}
+The required \tt{id} attribute provides a name for this statement, which must
+be unique within this \tt{<SqlMap>}.
+
+\subsection{\tt{parameterMap} attribute}
+A Parameter Map defines an ordered list of values that match up with the ``?''
+placeholders of a standard, parameterized query statement.
+Example~\ref{example:3.9} shows a \tt{<parameterMap>} and a corresponding
+\tt{<statement>}.
+
+\begin{example}\label{example:3.9}
+A \tt{parameterMap} and corresponding statement
+\begin{verbatim}
+<parameterMap id="insert-product-param" class="Product">
+ <parameter property="id"/>
+ <parameter property="description"/>
+</parameterMap>
+
+<statement id="insertProduct" parameterMap="insert-product-param">
+ insert into PRODUCT (PRD_ID, PRD_DESCRIPTION) values (?,?);
+</statement>
+\end{verbatim}
+\end{example}
+
+In Example~\ref{example:3.9}, the Parameter Map describes two parameters that
+will match, in order, two placeholders in the SQL statement. The first ``?''
+is replaced by the value of the \tt{id} property. The second is replaced with
+the \tt{description} property.
+
+SQLMap also supports named, inline parameters, which most developers seem to
+prefer. However, Parameter Maps are useful when the SQL must be kept in a
+standard form or when extra information needs to be provided. For more about
+Parameter Maps see Chapter~\ref{section:3.4}.
+
+\subsection{\tt{parameterClass} attribute }
+If a \tt{parameterMap} attribute is not specified, you may specify a
+\tt{parameterClass} instead and use inline parameters (see
+Section~\ref{section:3.4.3}). The value of the \tt{parameterClass} attribute
+can be any existing PHP class name. Example~\ref{example:3.10} shows a
+statement using a PHP class named \tt{Product} in \tt{parameterClass}
+attribute.
+
+\begin{example}\label{example:3.10}
+Specify the \tt{parameterClass} with a PHP class name.
+\begin{verbatim}
+<statement id="statementName" parameterClass="Product">
+ insert into PRODUCT values (#id#, #description#, #price#)
+</statement>
+\end{verbatim}
+\end{example}
+
+\subsection{\tt{resultMap} attribute}
+A Result Map lets you control how data is extracted from the result of a
+query, and how the columns are mapped to object properties.
+Example~\ref{example:3.11} shows a \tt{<resultMap>} element and a
+corresponding \tt{<statement>} element.
+\begin{example}\label{example:3.11}
+A \tt{<resultMap>} and corresponding \tt{<statement>}
+\begin{verbatim}
+<resultMap id="select-product-result" class="product">
+ <result property="id" column="PRD_ID"/>
+ <result property="description" column="PRD_DESCRIPTION"/>
+</resultMap>
+
+<statement id="selectProduct" resultMap="select-product-result">
+ select * from PRODUCT
+</statement>
+\end{verbatim}
+\end{example}
+
+In Example~\ref{example:3.11}, the result of the SQL query will be mapped to
+an instance of the \tt{Product} class using the ``select-product-result''
+\tt{<resultMap>}. The \tt{<resultMap>} says to populate the \tt{id} property
+from the \tt{PRD\_ID} column, and to populate the \tt{description} property
+from the \tt{PRD\_DESCRIPTION} column.
+
+\begin{mybox}{Tip:}
+In Example~\ref{example:3.11}, note that using `` select * '' is supported. If
+you want all the columns, you don't need to map them all individually. (Though
+many developers consider it a good practice to always specify the columns
+expected.)
+\end{mybox}
+
+For more about Result Maps, see Chapter~\ref{section:3.5}.
+
+\subsection{\tt{resultClass} attribute}
+If a \tt{resultMap} is not specified, you may specify a \tt{resultClass}
+instead. The value of the \tt{resultClass} attribute can be the name of a PHP
+class or primitives like \tt{integer}, \tt{string}, or \tt{array}. The class
+specified will be automatically mapped to the columns in the result, based on
+the result metadata. The following example shows a \tt{<statement>} element
+with a \tt{resultClass} attribute.
+
+\begin{example}\label{example:3.12}
+A \tt{<statement>} element with \tt{resultClass} attribute
+\begin{verbatim}
+<statement id="SelectPerson" parameterClass="int" resultClass="Person">
+ SELECT
+ PER_ID as Id,
+ PER_FIRST_NAME as FirstName,
+ PER_LAST_NAME as LastName,
+ PER_BIRTH_DATE as BirthDate,
+ PER_WEIGHT_KG as WeightInKilograms,
+ PER_HEIGHT_M as HeightInMeters
+ FROM PERSON
+ WHERE PER_ID = #value#
+</statement>
+\end{verbatim}
+\end{example}
+
+In Example~\ref{example:3.12}, the \tt{Person} class has properties including:
+\tt{Id}, \tt{FirstName}, \tt{LastName}, \tt{BirthDate},
+\tt{WeightInKilograms}, and \tt{HeightInMeters}. Each of these corresponds
+with the column aliases described by the SQL select statement using the ``as''
+keyword ¨Ca standard SQL feature. When executed, a \tt{Person} object is
+instantiated and populated by matching the object property names to the column
+names from the query.
+
+Using SQL aliases to map columns to properties saves defining a
+\tt{<resultMap>} element, but there are limitations. There is no way to
+specify the types of the output columns (if needed), there is no way to
+automatically load related data such as complex properties.You can overcome
+these limitations with an explicit Result Map (Chapter~\ref{section:3.5}).
+
+\subsection{\tt{listClass} attribute}
+In addition to providing the ability to return an \tt{TList} of objects, the
+DataMapper supports the use of custom collection: a class that implements
+\tt{ArrayAccess}. The following is an example of a TList (it implements
+ArrayAccess) class that can be used with the DataMapper.
+
+\begin{example}\label{example:3.13}
+An \tt{ArrayAccess} implementation, by extending \tt{TList}.
+\begin{verbatim}
+class AccountCollection extends TList
+{
+ public function addRange($accounts)
+ {
+ foreach($accounts as $account)
+ $this->add($account);
+ }
+
+ public function copyTo(TList $array)
+ {
+ $array->copyFrom($this);
+ }
+}
+\end{verbatim}
+\end{example}
+An \tt{ArrayAccess} class can be specified for a select statement through the
+\tt{listClass} attribute. The value of the \tt{listClass} attribute is the
+full name of a PHP class that implements \tt{ArrayAccess}. The statement
+should also indicate the \tt{resultClass} so that the DataMapper knows how to
+handle the type of objects in the collection. The \tt{resultClass} specified
+will be automatically mapped to the columns in the result, based on the result
+metadata. The following example shows a \tt{<statement>} element with a
+\tt{listClass} attribute.
+
+\begin{example}\label{example:3.14}
+A \tt{<statement>} element with \tt{listClass} attribute
+\begin{verbatim}
+<statement id="GetAllAccounts"
+ listClass="AccountCollection"
+ resultClass="Account">
+ select
+ Account_ID as Id,
+ Account_FirstName as FirstName,
+ Account_LastName as LastName,
+ Account_Email as EmailAddress
+ from Accounts
+ order by Account_LastName, Account_FirstName
+</statement>
+\end{verbatim}
+\end{example}
+
+\subsection{\tt{cacheModel} attribute}
+If you want to cache the result of a query, you can specify a Cache Model as
+part of the \tt{<statement>} element. Example~\ref{example:3.15} shows a
+\tt{<cacheModel>} element and a corresponding \tt{<statement>}.
+
+\begin{example}\label{example:3.15}
+A \tt{<cacheModel>} element with its corresponding \tt{<statement>}
+\begin{verbatim}
+<cacheModel id="product-cache" implementation="LRU">
+ <flushInterval hours="24"/>
+ <flushOnExecute statement="insertProduct"/>
+ <flushOnExecute statement="updateProduct"/>
+ <flushOnExecute statement="deleteProduct"/>
+ <property name="size" value="1000" />
+</cacheModel>
+
+<statement id="selectProductList" parameterClass="int" cacheModel="product-cache">
+ select * from PRODUCT where PRD_CAT_ID = #value#
+</statement>
+\end{verbatim}
+\end{example}
+
+In Example~\ref{example:3.15}, a cache is defined for products that uses a
+Least Recently Used [LRU] type and flushes every 24 hours or whenever
+associated update statements are executed. For more about Cache Models, see
+Section~\ref{section:3.8}.
+
+\subsection{\tt{extends} attribute}
+When writing Sql, you often encounter duplicate fragments of SQL. SQLMap
+offers a simple yet powerful attribute to reuse them.
+
+\begin{verbatim}
+<select id="GetAllAccounts"
+ resultMap="indexed-account-result">
+select
+ Account_ID,
+ Account_FirstName,
+ Account_LastName,
+ Account_Email
+from Accounts
+</select>
+
+<select id="GetAllAccountsOrderByName"
+ extends="GetAllAccounts"
+ resultMap="indexed-account-result">
+ order by Account_FirstName
+</select>
+\end{verbatim}
diff --git a/docs/sqlmap/latex/ch4.tex b/docs/sqlmap/latex/ch4.tex new file mode 100644 index 00000000..a4cbc937 --- /dev/null +++ b/docs/sqlmap/latex/ch4.tex @@ -0,0 +1,306 @@ +\chapter{Parameter Maps and Inline Parameters}\label{section:3.4}
+Most SQL statements are useful because we can pass them values at runtime.
+Someone wants a database record with the ID 42, and we need to merge that ID
+number into a select statement. A list of one or more parameters are passed at
+runtime, and each placeholder is replaced in turn. This is simple but labor
+intensive, since developers spend a lot of time counting symbols to make sure
+everything is in sync.
+
+\begin{mybox}{Note:}
+Preceding sections briefly touched on inline parameters, which automatically
+map properties to named parameters. Many iBATIS developers prefer this
+approach. But others prefer to stick to the standard, anonymous approach to
+SQL parameters by using parameter maps. Sometimes people need to retain the
+purity of the SQL statements; other times they need the detailed specification
+offered by parameter maps due to database or provider-specific information
+that needs to be used.
+\end{mybox}
+
+\section{Parameter Map}
+A Parameter Map defines an ordered list of values that match up with the
+placeholders of a parameterized query statement. While the attributes
+specified by the map still need to be in the correct order, each parameter is
+named. You can populate the underlying class in any order, and the Parameter
+Map ensures each value is passed in the correct order.
+
+Parameter Maps can be provided as an external element and \emph{inline}.
+Example~\ref{example:3.16} shows an external Parameter Map.
+
+\begin{example}\label{example:3.16}
+An external Parameter Map
+\begin{verbatim}
+<parameterMap id="parameterMapIdentifier"
+ [extends="[sqlMapNamespace.]parameterMapId"]>
+ <parameter
+ property ="propertyName"
+ [column="columnName"]
+ [dbType="databaseType"]
+ [type="propertyCLRType"]
+ [nullValue="nullValueReplacement"]
+ [size="columnSize"]
+ [precision="columnPrecision"]
+ [scale="columnScale"]
+ [typeHandler="class.name"]
+ <parameter ... ... />
+ <parameter ... ... />
+</parameterMap>
+\end{verbatim}
+\end{example}
+
+In Example~\ref{example:3.16}, the parts in [brackets] are optional. The
+\tt{parameterMap} element only requires the id attribute.
+Example~\ref{example:3.17} shows a typical \tt{<parameterMap>}.
+
+\begin{example}\label{example:3.17}
+A typical \tt{<parameterMap>} element
+\begin{verbatim}
+<parameterMap id="insert-product-param" class="Product">
+ <parameter property="description" />
+ <parameter property="id"/>
+</parameterMap>
+
+<statement id="insertProduct" parameterMap="insert-product-param">
+ insert into PRODUCT (PRD_DESCRIPTION, PRD_ID) values (?,?);
+</statement>
+\end{verbatim}
+\end{example}
+
+\begin{mybox}{Note:}
+Parameter Map names are always local to the Data Map definition file where
+they are defined. You can refer to a Parameter Map in another Data Map
+definition file by prefixing the \tt{id} of the Parameter Map with the
+namespace of the Data Map (set in the \tt{<sqlMap>} root element). If the
+Parameter Map in Example~\ref{example:3.17} were in a Data Map named
+``Product'', it could be referenced from another file using
+``Product.insert-product-param''.
+\end{mybox}
+
+\subsection{\tt{<parameterMap>} attributes} The \tt{<parameterMap>} element
+accepts two attributes: \tt{id} (required) and \tt{extends} (optional).
+
+\subsubsection{\tt{id} attribute} The required \tt{id} attribute provides a
+unique identifier for the \tt{<parameterMap>} within this Data Map.
+
+\subsubsection{\tt{extends} attribute}
+The optional \tt{extends} attribute can be set to the name of another
+\tt{parameterMap} upon which to base this \tt{parameterMap}. All properties of
+the super \tt{parameterMap} will be included as part of this
+\tt{parameterMap}, and values from the super \tt{parameterMap} are set before
+any values specified by this \tt{parameterMap}. The effect is similar to
+extending a class.
+
+\section{\tt{<parameter>} Elements}
+The \tt{<parameterMap>} element holds one or more parameter child elements
+that map object properties to placeholders in a SQL statement. The sections
+that follow describe each of the attributes.
+
+\subsection{\tt{property} attribute}
+The \tt{property} attribute of \tt{<parameter>} is the name of a property of
+the parameter object. It may also be the name of an entry in an array. The
+name can be used more than once depending on the number of times it is needed
+in the statement. (In an update, you might set a column that is also part of
+the where clause.)
+
+\subsection{\tt{direction} attribute}
+The \tt{direction} attribute may be used to indicate a stored procedure
+parameter's direction.
+
+\subsection{\tt{column} attribute}
+The \tt{column} attribute is used to define to the name of a parameter used by
+a stored procedure.
+
+\begin{table}[!h]\centering\label{table:3.3}
+\caption{Parameter \tt{direction} attribute values }
+\begin{tabular}{|l|l|}
+ \hline
+ % after \\: \hline or \cline{col1-col2} \cline{col3-col4} ...
+ \textbf{Value} & \textbf{Description}\\
+ \hline
+ Input & input-only \\
+ \hline
+ Output & output-only \\
+ \hline
+ InputOutput & bidirectional \\
+ \hline
+\end{tabular}
+\end{table}
+
+\subsection{\tt{dbType} attribute}
+The \tt{dbType} attribute is used to explicitly specify the database column
+type of the parameter to be set by this property. This attribute is normally
+only required if the column is nullable. Although, another reason to use the
+\tt{dbType} attribute is to explicitly specify date types. Most SQL databases
+have more than one \tt{datetime} type. Usually, a database has at least three
+different types (DATE, DATETIME, TIMESTAMP). In order for the value to map
+correctly, you might need to specify the column's \tt{dbType}.
+
+\begin{mybox}{Note:}
+Most providers only need the \tt{dbType} specified for nullable columns. In
+this case, you only need to specify the type for the columns that are
+nullable.
+\end{mybox}
+
+\subsection{\tt{type} attribute}
+The \tt{type} attribute is used to specify the type of the parameter's
+property. This attribute is useful when passing \tt{InputOutput} and
+\tt{Output} parameters into stored procedures. The framework uses the
+specified type to properly handle and set the parameter object's properties
+with the procedure's output values after execution.
+
+%If the attribute type is not set and the framework cannot otherwise determine
+%the type, the type is assumed to be an StdObject. Section~\ref{section:6}
+%details the types and available aliases that have pre-built support in the
+%framework.
+
+\subsection{\tt{nullValue} attribute}\label{section:nullValueParameter}
+The \tt{nullValue} attribute can be set to any valid value (based on property
+type). The \tt{nullValue} attribute is used to specify an outgoing null value
+replacement. What this means is that when the value is detected in the object
+property, a NULL will be written to the database (the opposite behavior of an
+inbound null value replacement). This allows you to use a magic null number in
+your application for types that do not support null values (such as int,
+double, float). When these types of properties contain a matching null value
+(for example, say, $-9999$), a NULL will be written to the database instead of
+the value.
+
+
+\begin{mybox}{Tip:}
+For round-trip transparency of null values, you must also specify database
+columns null value replacements in your Result Map (see
+Chapter~\ref{section:3.5}).
+\end{mybox}
+
+
+\subsection{\tt{size} attribute}
+The \tt{size} attribute sets the maximum size of the data within the column.
+
+\subsection{\tt{precision} attribute}
+The \tt{precision} attribute is used to set the maximum number of digits used
+to represent the property value.
+
+\subsection{\tt{scale} attribute}
+The \tt{scale} attribute sets the number of decimal places used to resolve the
+property value.
+
+\subsection{\tt{typeHandler} attribute}
+The \tt{typeHandler} attribute allows the use of a Custom Type Handler (see
+the Custom Type Handler section). This allows you to extend the DataMapper's
+capabilities in handling types that are specific to your database provider,
+are not handled by your database provider, or just happen to be a part of your
+application design. You can create custom type handlers to deal with storing
+and retrieving booleans from your database for example.
+
+\section{Inline Parameter Maps}\label{section:3.4.3}
+If you prefer to use inline parameters instead of parameter maps, you can add
+extra type information inline too. The inline parameter map syntax lets you
+embed the property name, the property type, the column type, and a null value
+replacement into a parametrized SQL statement. The next four examples shows
+statements written with inline parameters.
+
+\begin{example}\label{example:3.18}
+ A \tt{<statement>} using inline parameters
+\begin{verbatim}
+<statement id="insertProduct" parameterClass="Product">
+ insert into PRODUCT (PRD_ID, PRD_DESCRIPTION)
+ values (#id#, #description#)
+</statement>
+\end{verbatim}
+\end{example}
+
+The following example shows how \tt{dbTypes} can be declared inline.
+
+\begin{example}\label{example:3.19}
+ A \tt{<statement>} using an inline parameter map with a type
+\begin{verbatim}
+<statement id="insertProduct" parameterClass="Product">
+ insert into PRODUCT (PRD_ID, PRD_DESCRIPTION)
+ values (#id, dbType=int#, #description, dbType=VarChar#)
+</statement>
+\end{verbatim}
+\end{example}
+
+The next example shows how \tt{dbTypes} and null value replacements can also
+be declared inline.
+
+\begin{example}\label{example:3.20}
+A \tt{<statement>} using an inline parameter map with a null value replacement
+\begin{verbatim}
+<statement id="insertProduct" parameterClass="Product">
+ insert into PRODUCT (PRD_ID, PRD_DESCRIPTION)
+ values (#id, dbType=int, nullValue=-999999#, #description, dbType=VarChar#)
+</statement>
+\end{verbatim}
+\end{example}
+
+\begin{example}\label{example:3.21}
+A more complete example.
+\begin{verbatim}
+<update id="UpdateAccountViaInlineParameters" parameterClass="Account">
+ update Accounts set
+ Account_FirstName = #FirstName#,
+ Account_LastName = #LastName#,
+ Account_Email = #EmailAddress,type=string,dbType=Varchar,nullValue=no_email@provided.com#
+ where
+ Account_ID = #Id#
+</update>
+\end{verbatim}
+\end{example}
+
+\begin{mybox}{Note:}
+Inline parameter maps are handy for small jobs, but when there are a lot of
+type descriptors and null value replacements in a complex statement, an
+industrial-strength, external \tt{parameterMap} can be easier.
+\end{mybox}
+
+\section{Standard Type Parameters}
+In practice, you will find that many statements take a single parameter, often
+an \tt{integer} or a \tt{string}. Rather than wrap a single value in another
+object, you can use the standard library object (string, integer, et cetera)
+as the parameter directly. Example~\ref{example:3.22} shows a statement using
+a standard type parameter.
+
+\begin{example}\label{example:3.22}
+A \tt{<statement>} using standard type parameters
+\begin{verbatim}
+<statement id="getProduct" parameterClass="System.Int32">
+ select * from PRODUCT where PRD_ID = #value#
+</statement>
+\end{verbatim}
+\end{example}
+
+Assuming \tt{PRD\_ID} is a numeric type, when a call is made to this Mapped
+Statement, a standard integer can be passed in. The \tt{\#value\#} parameter
+will be replaced with the value of the integer. The name \tt{value} is simply
+a placeholder, you can use another moniker of your choice. Result Maps support
+primitive types as results as well.
+
+For your convenience, the following PHP primitive types are supported.
+\begin{itemize}
+ \item \tt{string}
+ \item \tt{float} or \tt{double}
+ \item \tt{integer} or \tt{int}
+ \item \tt{bool} or \tt{boolean}
+\end{itemize}
+
+\section{Array Type Parameters}
+You can also pass in a array as a parameter object. This would usually be a an
+associative array. Example~\ref{example:3.23} shows a \tt{<statement>} using
+an array for a \tt{parameterClass}.
+
+
+\begin{example}\label{example:3.23}
+A \tt{<statement>} using an array for a \tt{parameterClass}
+\begin{verbatim}
+<statement id="getProduct" parameterClass="array">
+ select * from PRODUCT
+ where PRD_CAT_ID = #catId#
+ and PRD_CODE = #code#
+</statement>
+\end{verbatim}
+\end{example}
+
+In Example~\ref{example:3.23}, notice that the SQL in this Mapped Statement
+looks like any other. There is no difference in how the inline parameters are
+used. If an associative array is passed, it must contain keys named \tt{catId}
+and \tt{code}. The values referenced by those keys must be of the appropriate
+type for the column, just as they would be if passed from a properties object.
diff --git a/docs/sqlmap/latex/ch5.tex b/docs/sqlmap/latex/ch5.tex new file mode 100644 index 00000000..076f6f5d --- /dev/null +++ b/docs/sqlmap/latex/ch5.tex @@ -0,0 +1,941 @@ +\chapter{Result Maps}\label{section:3.5}
+Chapter~\ref{section:3.4} describes Parameter Maps and Inline parameters,
+which map object properties to parameters in a database query. Result Maps
+finish the job by mapping the result of a database query (a set of columns) to
+object properties. Next to Mapped Statements, the Result Map is probably one
+of the most commonly used and most important features to understand.
+
+A Result Map lets you control how data is extracted from the result of a
+query, and how the columns are mapped to object properties. A Result Map can
+describe the column type, a null value replacement, and complex property
+mappings including Collections. Example~\ref{example:3.24} shows the structure
+of a \tt{<resultMap>} element.
+
+\begin{example}\label{example:3.24}
+The structure of a \tt{<resultMap>} element.
+\begin{verbatim}
+<resultMap id="resultMapIdentifier"
+ [class="class.name"]
+ [extends="[sqlMapNamespace.]resultMapId"]>
+
+ <result property="propertyName"
+ column="columnName"
+ [columnIndex="columnIndex"]
+ [dbType="databaseType"]
+ [type="propertyCLRType"]
+ [resultMapping="resultMapName"]
+ [nullValue="nullValueReplacement"]
+ [select="someOtherStatementName"]
+ [lazyLoad="true|false"]
+ [typeHandler="class.name"]
+ />
+ <result ... .../>
+ <result ... .../>
+</resultMap>
+\end{verbatim}
+\end{example}
+
+In Example~\ref{example:3.24}, the [brackets] indicate optional attributes.
+The \tt{id} attribute is required and provides a name for the statement to
+reference. The \tt{class} attribute is also required, and specifies the full
+name of a PHP class. This is the class that will be instantiated and populated
+based on the result mappings it contains.
+
+The \tt{resultMap} can contain any number of property mappings that map object
+properties to the columns of a result element. The property mappings are
+applied, and the columns are read, in the order that they are defined.
+Maintaining the element order ensures consistent results between different
+drivers and providers.
+
+\begin{mybox}{Note:}
+As with parameter classes, the result class must be a PHP class object or
+array instance.
+\end{mybox}
+
+\section{Extending \tt{resultMaps}}
+The optional \tt{extends} attribute can be set to the name of another
+\tt{resultMap} upon which to base this \tt{resultMap}. All properties of the
+``super'' \tt{resultMap} will be included as part of this \tt{resultMap}, and
+values from the ``super'' \tt{resultMap} are set before any values specified
+by this \tt{resultMap}. The effect is similar to extending a class.
+
+\begin{mybox}{Tip:}
+The ``super'' \tt{resultMap} must be defined in the file before the extending
+\tt{resultMap}. The classes for the super and sub \tt{resultMaps} need not be
+the same, and do not need to be related in any way.
+\end{mybox}
+
+\section{\tt{<resultMap>} attributes}
+The \tt{<resultMap>} element accepts three attributes: \tt{id} (required),
+\tt{class} (optional), and \tt{extends} (optional).
+
+\subsection{\tt{id} attribute}
+The required \tt{id} attribute provides a unique identifier for the
+\tt{<resultMap>} within this Data Map.
+
+\subsection{\tt{class} attribute}
+The optional \tt{class} attribute specifies an object class to use with this
+\tt{<resultMap>}. The full classname must be specified. Any class can be used.
+
+\begin{mybox}{Note:}
+As with parameter classes, the result class must be a PHP class object or
+array instance.
+\end{mybox}
+
+\subsection{\tt{extends} attribute}
+The optional \tt{extends} attribute allows the result map to inherit all of
+the properties of the ``super'' \tt{resultMap} that it extends.
+
+\section{\tt{<result>} Elements}
+
+The \tt{<resultMap>} element holds one or more \tt{<result>} child elements
+that map SQL result sets to object properties.
+
+\subsection{\tt{property} attribute}
+The \tt{property} attribute is the name of a property of the result object
+that will be returned by the Mapped Statement. The name can be used more than
+once depending on the number of times it is needed to populate the results.
+
+\subsection{\tt{column} attribute}
+The \tt{column} attribute value is the name of the column in the result set
+from which the value will be used to populate the property.
+
+\subsection{\tt{columnIndex} attribute}
+The \tt{columnIndex} attribute value is the index of the column in the
+ResultSet from which the value will be used to populate the object property.
+This is not likely needed in 99\% of applications and sacrifices
+maintainability and readability for speed. Some providers may not realize any
+performance benefit, while others will speed up dramatically.
+
+\subsection{\tt{dbType} attribute}
+The \tt{dbType} attribute is used to explicitly specify the database column
+type of the ResultSet column that will be used to populate the object
+property. Although Result Maps do not have the same difficulties with null
+values, specifying the type can be useful for certain mapping types such as
+Date properties. Because an application language has one Date value type and
+SQL databases may have many (usually at least 3), specifying the date may
+become necessary in some cases to ensure that dates (or other types) are set
+correctly. Similarly, String types may be populated by a \tt{VarChar},
+\tt{Char} or \tt{CLOB}, so specifying the type might be needed in those cases
+too.
+
+\subsection{\tt{type} attribute}
+The \tt{type} attribute is used to explicitly specify the property type of the
+parameter to be set. If the attribute \tt{type} is not set and the framework
+cannot otherwise determine the type, the type is assumed to be \tt{StdObject}.
+
+\subsection{\tt{resultMapping} attribute}
+The \tt{resultMapping} attribute can be set to the name of another
+\tt{resultMap} used to fill the property. If the \tt{resultMap} is in an other
+mapping file, you must specified the fully qualified name as :
+
+\begin{verbatim}
+resultMapping="[namespace.sqlMap.]resultMappingId"
+
+resultMapping="Newspaper"
+<!--resultMapping with a fully qualified name.-->
+resultMapping="LineItem.LineItem"
+\end{verbatim}
+
+\subsection{\tt{nullValue} attribute}
+The \tt{nullValue} attribute can be set to any valid value (based on property
+type). The \tt{nullValue} attribute is used to specify an outgoing null value
+replacement. What this means is that when the value is detected in the object
+property, a NULL will be written to the database (the opposite behavior of an
+inbound null value replacement). This allows you to use a ``magic'' null
+number in your application for types that do not support null values (such as
+int, double, float). When these types of properties contain a matching null
+value (say, $-9999$), a NULL will be written to the database instead of the
+value.
+
+If your database has a NULLABLE column, but you want your application to
+represent NULL with a constant value, you can specify it in the Result Map as
+shown in Example~\ref{example:3.25}.
+
+\begin{example}\label{example:3.25}
+Specifying a \tt{nullvalue} attribute in a Result Map
+\begin{verbatim}
+<resultMap id="get-product-result" class="product">
+ <result property="id" column="PRD_ID"/>
+ <result property="description" column="PRD_DESCRIPTION"/>
+ <result property="subCode" column="PRD_SUB_CODE" nullValue="-9999"/>
+</resultMap>
+\end{verbatim}
+\end{example}
+
+In Example~\ref{example:3.25}, if PRD\_SUB\_CODE is read as NULL, then the
+\tt{subCode} property will be set to the value of $-9999$. This allows you to
+use a primitive type to represent a NULLABLE column in the database. Remember
+that if you want this to work for queries as well as updates/inserts, you must
+also specify the \tt{nullValue} in the Parameter Map (see,
+Section~\ref{section:nullValueParameter}).
+
+\subsection{\tt{select} attribute}
+The \tt{select} attribute is used to describe a relationship between objects
+and to automatically load complex (i.e. user defined) property types. The
+value of the statement property must be the name of another mapped statement.
+The value of the database column (the column attribute) that is defined in the
+same property element as this statement attribute will be passed to the
+related mapped statement as the parameter. More information about supported
+primitive types and complex property mappings/relationships is discussed later
+in this document. The \tt{lazyLoad} attribute can be specified with the
+\tt{select}.
+
+\subsection{\tt{lazyLoad} attribute}
+Use the \tt{lazyLoad} attribute with the \tt{select} attribute to indicate
+whether or not the select statement's results should be lazy loaded. This can
+provide a performance boost by delaying the loading of the select statement's
+results until they are needed/accessed.
+
+\subsection{\tt{typeHandler} attribute}
+The \tt{typeHandler} attribute allows the use of a Custom Type Handler (see
+the Custom Type Handler in the following section). This allows you to extend
+the DataMapper's capabilities in handling types that are specific to your
+database provider, are not handled by your database provider, or just happen
+to be a part of your application design. You can create custom type handlers
+to deal with storing and retrieving booleans from your database for example.
+
+\section{Custom Type Handlers}
+A custom type handler allows you to extend the DataMapper's capabilities in
+handling types that are specific to your database provider, not handled by
+your database provider, or just happen to be part of your application design.
+The SQLMap for PHP DataMapper provides an interface,
+\tt{ITypeHandlerCallback}, for you to use in implementing your custom type
+handler.
+
+\begin{example}\label{example:3.26}
+\tt{ITypeHandlerCallback} interface
+\begin{verbatim}
+interface ITypeHandlerCallback
+{
+ public function getParameter($object);
+
+ public function getResult($string);
+
+ public function createNewInstance();
+}
+\end{verbatim}
+\end{example}
+
+The \tt{getParameter} method allows you to process a \tt{<statement>}
+parameter's value before it is added as an parameter. This enables you to do
+any necessary type conversion and clean-up before the DataMapper gets to work.
+
+The \tt{getResult} method allows you to process a database result value right
+after it has been retrieved by the DataMapper and before it is used in your
+\tt{resultClass}, \tt{resultMap}, or \tt{listClass}.
+
+The \tt{createNewInstance} method allows the DataMapper to create new instance
+of a particular type handled by this callback.
+
+One scenario where custom type handlers are useful are the when you want to
+use date time values in the database. First, consider a very basic TDateTime
+class.
+
+\begin{verbatim}
+class TDateTime
+{
+ private $_datetime;
+
+ public function __construct($datetime=null)
+ {
+ if(!is_null($datetime))
+ $this->setDatetime($datetime);
+ }
+
+ public function getTimestamp()
+ {
+ return strtotime($this->getDatetime());
+ }
+
+ public function getDateTime()
+ {
+ return $this->_datetime;
+ }
+
+ public function setDateTime($value)
+ {
+ $this->_datetime = $value;
+ }
+}
+\end{verbatim}
+
+We can use a custom type handler to intercept result and parameter mapping
+that uses the say ``data'' as one of its property type. The handler can be
+written as follows.
+
+\begin{example}\label{example:3.27}
+A \tt{TDateTime} Type Handler
+\begin{verbatim}
+class TDateTimeHandler implements ITypeHandlerCallback
+{
+ public function getResult($string)
+ {
+ return new TDateTime($string);
+ }
+
+ public function getParameter($parameter)
+ {
+ if($parameter instanceof TDateTime)
+ return $parameter->getTimestamp();
+ else
+ return $parameter;
+ }
+
+ public function createNewInstance()
+ {
+ return new TDateTime;
+ }
+}
+\end{verbatim}
+\end{example}
+
+With our custom type handler we can use the handler in our SqlMaps. To do
+that, we specify it as a basic \tt{<typeHandler>} for all \tt{date} types
+mapped in our SqlMap files
+
+\begin{example}\label{example:3.29}
+\tt{<typeHandler>} in SqlMap.config
+\begin{verbatim}
+[Our SqlMap.config]
+
+<typeHandlers>
+ <typeHandler type="date" callback="TDateTimeHandler"/>
+</typeHandlers>
+
+
+[One of our SqlMap.xml files]
+ <parameterMap id="boc-params">
+ <parameter property="releasedDate" type="date"/>
+ </parameterMap>
+
+ <resultMap id="boc-result" class="BudgetObjectCode">
+ <result property="releasedDate" column="BOC_DATE" type="date"/>
+ </resultMap>
+\end{verbatim}
+\end{example}
+
+%3.5.5. Inheritance Mapping The iBATIS DataMapper supports the implementation
+%of object-oriented inheritance (subclassing) in your object model. There are
+%several developer options for mapping entity classes and subclasses to
+%database results:
+%
+%resultMap for each class resultMap with submaps for a class hierarchy
+%resultMap with extended resultMaps for each subclass You can use the most
+%efficient mapping strategies from a SQL and query performance perspective when
+%using the inheritance mappings of the DataMapper. To implement an inheritance
+%mapping, the resultMap must define one or more columns in your query's
+%resultset that will serve to identify which resultMap should be used to map
+%each result record to a specific subclass. In many cases, you will use one
+%column value for the DataMapper to use in identifying the proper resultMap and
+%subclass. This column is known as a discriminator.
+%
+%For example, we have a table defined in a database that contains Document
+%records. There are five table columns used to store Document IDs, Titles,
+%Types, PageNumbers, and Cities. Perhaps this table belongs to a legacy
+%database, and we need to create an application using this table with a domain
+%model that defines a class hierarchy of different types of Documents. Or
+%perhaps we are creating a new application and database and just want to
+%persist the data found in a set of related classes into one table. In either
+%case, the DataMapper's inheritance mapping feature can help.
+%
+%\begin{verbatim}
+%// Database table Document
+%CREATE TABLE [Documents] (
+% [Document_ID] [int] NOT NULL ,
+% [Document_Title] [varchar] (32) NULL ,
+% [Document_Type] [varchar] (32) NULL ,
+% [Document_PageNumber] [int] NULL ,
+% [Document_City] [varchar] (32) NULL
+%)
+%\end{verbatim}
+%
+%To illustrate this, let's take a look at a few example classes shown below
+%that have a relationship through inheritance and whose properties can be
+%persisted into our Documents table. First, we have a base Document class that
+%has Id and Title properties. Next, we have a Book class that inherits from
+%Document and contains an additional property called PageNumber. Last, we have
+%a Newspaper class that also inherits from Document and contains a City
+%property.
+%
+%Example 3.30. Documents, Books, and Newspapers!
+%
+%\begin{verbatim}
+%// C# class
+%public class Document {
+% private int _id = -1;
+% private string _title = string.Empty;
+%
+% public int Id
+% {
+% get { return _id; }
+% set { _id = value; }
+% }
+%
+% public string Title
+% {
+% get { return _title; }
+% set { _title = value; }
+% }
+%}
+%
+%public class Book : Document {
+% private int _pageNumber = -1;
+%
+% public int PageNumber
+% {
+% get { return _pageNumber; }
+% set { _pageNumber = value; }
+% }
+%}
+%
+%public class Newspaper : Document {
+% private string _city = string.Empty;
+%
+% public string City
+% {
+% get { return _city; }
+% set { _city = value; }
+% }
+%}
+%\end{verbatim}
+%
+%Now that we have our classes and database table, we can start working on our
+%mappings. We can create one <select> statement that returns all columns in the
+%table. To help the DataMapper discriminate between the different Document
+%records, we're going to indicate that the Document\_Type column holds values
+%that will distinguish one record from another for mapping the results into our
+%class hierarchy.
+%
+%\begin{verbatim}
+%// Document mapping file
+%<select id="GetAllDocument" resultMap="document">
+% select
+% Document_Id, Document_Title, Document_Type,
+% Document_PageNumber, Document_City
+% from Documents
+% order by Document_Type, Document_Id
+%</select>
+%
+%<resultMap id="document" class="Document">
+% <result property="Id" column="Document_ID"/>
+% <result property="Title" column="Document_Title"/>
+% <discriminator column="Document_Type" type="string"/>
+% <subMap value="Book" resultMapping="book"/>
+% <subMap value="Newspaper" resultMapping="newspaper"/>
+%</resultMap>
+%
+%<resultMap id="book" class="Book" extends="document">
+% <property="PageNumber" column="Document_PageNumber"/>
+%</resultMap>
+%
+%<resultMap id="newspaper" class="Newspaper" extends="document">
+% <property="City" column="Document_City"/>
+%</resultMap>
+%\end{verbatim}
+%
+%The DataMapper compares the data found in the discriminator column to the
+%different <submap> values using the column value's string equivalence. Based
+%on this string value, iBATIS DataMapper will use the resultMap named "Book" or
+%"Newspaper" as defined in the <submap> elements or it will use the "super"
+%resultMap "Document" if neither of the submap values satisfy the comparison.
+%With these resultMaps, we can implement an object-oriented inheritance mapping
+%to our database table.
+%
+%If you want to use custom logic, you can use the typeHandler attribute of the
+%<discriminator> element to specify a custom type handler for the discriminator
+%column.
+%
+%Example 3.31. Complex disciminator usage with Custom Type Handler
+%
+%\begin{verbatim}
+%<alias>
+% <typeAlias alias="CustomInheritance"
+% type="IBatisNet.DataMapper.Test.Domain.CustomInheritance, IBatisNet.DataMapper.Test"/>
+%</alias>
+%
+%<resultMaps>
+% <resultMap id="document-custom-formula" class="Document">
+% <result property="Id" column="Document_ID"/>
+% <result property="Title" column="Document_Title"/>
+% <discriminator column="Document_Type" typeHandler="CustomInheritance"/>
+% <subMap value="Book" resultMapping="book"/>
+% <subMap value="Newspaper" resultMapping="newspaper"/>
+% </resultMap>
+%</resultMaps>
+%\end{verbatim}
+%
+%The value of the typeHandler attribute specifies which of our classes
+%implements the ITypeHandlerCallback interface. This interface furnishes a
+%GetResult method for coding custom logic to read the column result value and
+%return a value for the DataMapper to use in its comparison to the resultMap's
+%defined <submap> values.
+%
+%Example 3.32. Example ITypeHandlerCallback interface implementation
+%
+%\begin{verbatim}
+%public class CustomInheritance : ITypeHandlerCallback {
+% #region ITypeHandlerCallback members
+%
+% public object ValueOf(string nullValue)
+% {
+% throw new NotImplementedException();
+% }
+%
+% public object GetResult(IResultGetter getter)
+% {
+% string type = getter.Value.ToString();
+%
+% if (type=="Monograph" || type=="Book")
+% {
+% return "Book";
+% }
+% else if (type=="Tabloid" || type=="Broadsheet" || type=="Newspaper")
+% {
+% return "Newspaper";
+% }
+% else
+% {
+% return "Document";
+% }
+%
+% }
+%
+% public void SetParameter(IParameterSetter setter, object parameter)
+% {
+% throw new NotImplementedException();
+% }
+% #endregion
+%}
+%\end{verbatim}
+
+\section{Implicit Result Maps}
+If the columns returned by a SQL statement match the result object, you may
+not need an explicit Result Map. If you have control over the relational
+schema, you might be able to name the columns so they also work as property
+names. In Example~\ref{example:3.33}, the column names and property names
+already match, so a result map is not needed.
+
+\begin{example}\label{example:3.33}
+A Mapped Statement that doesn't need a Result Map
+\begin{verbatim}
+<statement id="selectProduct" resultClass="Product">
+ select
+ id,
+ description
+ from PRODUCT
+ where id = #value#
+</statement>
+\end{verbatim}
+\end{example}
+
+Another way to skip a result map is to use column aliasing to make the column
+names match the properties names, as shown in Example~\ref{example:3.34}.
+
+\begin{example}\label{example:3.34}
+A Mapped Statement using column aliasing instead of a Result Map
+\begin{verbatim}
+<statement id="selectProduct" resultClass="Product">
+ select
+ PRD_ID as id,
+ PRD_DESCRIPTION as description
+ from PRODUCT
+ where PRD_ID = #value#
+</statement>
+\end{verbatim}
+\end{example}
+
+Of course, these techniques will not work if you need to specify a column
+type, a null value, or any other property attributes.
+
+\section{Primitive Results (i.e. String, Integer, Boolean)}
+Many times, we don't need to return an object with multiple properties. We
+just need a string, integer, boolean, and so forth. If you don't need to
+populate an object, SQLMap can return one of the primitive types instead. If
+you just need the value, you can use a primitive type as a result class, as
+shown in Example~\ref{example:3.35}.
+
+\begin{example}\label{example:3.35}
+Selecting a primitive type
+\begin{verbatim}
+<select id="selectProductCount" resultClass="integer">
+ select count(1)
+ from PRODUCT
+</select>
+\end{verbatim}
+\end{example}
+
+\begin{example}\label{example:3.36}
+Loading a simple list of product descriptions
+\begin{verbatim}
+<resultMap id="select-product-result" resultClass="System.String">
+ <result property="value" column="PRD_DESCRIPTION"/>
+</resultMap>
+\end{verbatim}
+\end{example}
+
+\section{Maps with ResultMaps}
+Instead of a rich object, sometimes all you might need is a simple key/value
+list of the data, where each property is an entry on the list. If so, Result
+Maps can populate an array instance as easily as property objects. The syntax
+for using an array is identical to the rich object syntax. As shown in Example
+~\ref{example:3.37}, only the result object changes.
+
+\begin{example}\label{example:3.37}
+Result Maps can use arrays.
+\begin{verbatim}
+<resultMap id="select-product-result" class="array">
+ <result property="id" column="PRD_ID"/>
+ <result property="code" column="PRD_CODE"/>
+ <result property="description" column="PRD_DESCRIPTION"/>
+ <result property="suggestedPrice" column="PRD_SUGGESTED_PRICE"/>
+</resultMap>
+\end{verbatim}
+\end{example}
+
+In Example~\ref{example:3.37}, an array instance would be created for each row
+in the result set and populated with the Product data. The property name
+attributes, like \tt{id}, \tt{code}, and so forth, would be the key of the
+entry, and the value of the mapped columns would be the value of the entry.
+
+As shown in Example~\ref{example:3.38}, you can also use an implicit Result
+Map with an array type.
+
+\begin{example}\label{example:3.38}
+Implicit Result Maps can use arrays too.
+\begin{verbatim}
+<statement id="selectProductCount" resultClass="array">
+ select * from PRODUCT
+</statement>
+\end{verbatim}
+\end{example}
+
+What set of entries is returned by Example~\ref{example:3.38} depends on what
+columns are in the result set. If the set of column changes (because columns
+are added or removed), the new set of entries would automatically be returned.
+
+\begin{mybox}{Note:}
+Certain providers may return column names in upper case or lower case format.
+When accessing values with such a provider, you will have to pass the key name
+in the expected case.
+\end{mybox}
+
+\section{Complex Properties}
+In a relational database, one table will often refer to another. Likewise,
+some of your business objects may include another object or list of objects.
+Types that nest other types are called ``complex types''. You may not want a
+statement to return a simple type, but a fully-formed complex type.
+
+In the database, a related column is usually represented via a 1:1
+relationship, or a 1:M relationship where the class that holds the complex
+property is from the ``many side'' of the relationship and the property itself
+is from the ``one side'' of the relationship. The column returned from the
+database will not be the property we want; it is a key to be used in another
+query.
+
+From the framework's perspective, the problem is not so much loading a complex
+type, but loading each ``complex property''. To solve this problem, you can
+specify in the Result Map a statement to run to load a given property. In
+Example~\ref{example:3.39}, the ``category'' property of the
+``select-product-result'' element is a complex property.
+
+\begin{example}\label{example:3.39}
+A Result Map with a Complex Property
+\begin{verbatim}
+ <resultMap id="select-product-result" class="product">
+ <result property="id" column="PRD_ID"/>
+ <result property="description" column="PRD_DESCRIPTION"/>
+ <result property="category" column="PRD_CAT_ID" select="selectCategory"/>
+ </resultMap>
+
+ <resultMap id="select-category-result" class="category">
+ <result property="id" column="CAT_ID"/>
+ <result property="description" column="CAT_DESCRIPTION"/>
+ </resultMap>
+
+ <select id="selectProduct" parameterClass="int" resultMap="select-product-result">
+ select * from PRODUCT where PRD_ID = #value#
+ </select>
+
+ <select id="selectCategory" parameterClass="int" resultMap="select-category-result">
+ select * from CATEGORY where CAT_ID = #value#
+ </select>
+\end{verbatim}
+\end{example}
+
+In Example~\ref{example:3.39}, the framework will use the ``selectCategory''
+statement to populate the ``category'' property. The value of each category is
+passed to the ``selectCategory'' statement, and the object returned is set to
+the category property. When the process completes, each Product instance will
+have the the appropriate category object instance set.
+
+\section{Avoiding N+1 Selects (1:1)}
+A problem with Example~\ref{example:3.39} may be that whenever you load a
+Product, two statements execute: one for the Product and one for the Category.
+For a single Product, this issue may seem trivial. But if you load 10
+products, then 11 statements execute. For 100 Products, instead of one
+statement product statement executing, a total of 101 statements execute. The
+number of statements executing for Example~\ref{example:3.40} will always be
+N+1: 100+1=101.
+
+\begin{example}\label{example:3.40}
+N+1 Selects (1:1)
+\begin{verbatim}
+ <resultMap id="select-product-result" class="product">
+ <result property="id" column="PRD_ID"/>
+ <result property="description" column="PRD_DESCRIPTION"/>
+ <result property="category" column="PRD_CAT_ID" select="selectCategory"/>
+ </resultMap>
+
+ <resultMap id="select-category-result" class="category">
+ <result property="id" column="CAT_ID"/>
+ <result property="description" column="CAT_DESCRIPTION"/>
+ </resultMap>
+
+ <!-- This statement executes 1 time -->
+ <select id="selectProducts" parameterClass="int" resultMap="select-product-result">
+ select * from PRODUCT
+ </select>
+
+ <!-- This statement executes N times (once for each product returned above) -->
+ <select id="selectCategory" parameterClass="int" resultMap="select-category-result">
+ select * from CATEGORY where CAT_ID = #value#
+ </select>
+
+\end{verbatim}
+\end{example}
+
+One way to mitigate the problem is to cache the ``selectCategory'' statement .
+We might have a hundred products, but there might only be five categories.
+Instead of running a SQL query or stored procedure, the framework will return
+the category object from it cache. A 101 statements would still run, but they
+would not be hitting the database. (See Chapter~\ref{section:3.8} for more
+about caches.)
+
+Another solution is to use a standard SQL join to return the columns you need
+from the another table. A join can bring all the columns we need over from the
+database in a single query. When you have a nested object, you can reference
+nested properties using a dotted notation, like ``category.description''.
+
+Example~\ref{example:3.41} solves the same problem as
+Example~\ref{example:3.40}, but uses a join instead of nested properties.
+
+\begin{example}\label{example:3.41}
+Resolving complex properties with a join
+\begin{verbatim}
+ <resultMap id="select-product-result" class="product">
+ <result property="id" column="PRD_ID"/>
+ <result property="description" column="PRD_DESCRIPTION"/>
+ <result property="category" resultMapping="Category.CategoryResult" />
+ </resultMap>
+
+ <statement id="selectProduct" parameterClass="int" resultMap="select-product-result">
+ select *
+ from PRODUCT, CATEGORY
+ where PRD_CAT_ID=CAT_ID
+ and PRD_ID = #value#
+ </statement>
+\end{verbatim}
+\end{example}
+
+\begin{mybox}{Lazy Loading vs. Joins (1:1):}
+It's important to note that using a join is not always better. If you are in a
+situation where it is rare to access the related object (e.g. the category
+property of the Product class) then it might actually be faster to avoid the
+join and the unnecessary loading of all category properties. This is
+especially true for database designs that involve outer joins or nullable
+and/or non-indexed columns. In these situations it might be better to use the
+sub-select solution with lazy loading enabled. The general rule of thumb is:
+use the join if you're more likely going to access the associated properties
+than not. Otherwise, only use it if lazy loading is not an option.
+\\
+\\
+If you're having trouble deciding which way to go, don't worry. No matter
+which way you go, you can always change it without impacting your application
+source code. Example~\ref{example:3.40} and \ref{example:3.41} result in
+exactly the same object graph and are loaded using the exact same method call
+from the application. The only consideration is that if you were to enable
+caching, then the using the separate select (not the join) solution could
+result in a cached instance being returned. But more often than not, that
+won't cause a problem (your application shouldn't be dependent on instance
+level equality i.e. ``==='').
+\end{mybox}
+
+\section{Complex Collection Properties}
+It is also possible to load properties that represent lists of complex
+objects. In the database the data would be represented by a M:M relationship,
+or a 1:M relationship where the class containing the list is on the ``one
+side'' of the relationship and the objects in the list are on the ``many
+side''. To load a \tt{TList} of objects, there is no change to the statement
+(see example above). The only difference required to cause the SQLMap
+DataMapper framework to load the property as a \tt{TList} is that the property
+on the business object must be of type \tt{TList}. For example, if a Category
+has a \tt{TList} of Product instances, the mapping would look like this
+(assuming Category has a property called "ProductList" of \tt{TList}.):
+
+\begin{example}\label{example:3.42}
+Mapping that creates a list of complex objects
+\begin{verbatim}
+<resultMaps>
+
+ <resultMap id="select-category-result" class="Category">
+ <result property="Id" column="CAT_ID"/>
+ <result property="Description" column="CAT_DESCRIPTION"/>
+ <result property="ProductList" column="CAT_ID" select="selectProductsByCatId"/>
+ </resultMap>
+
+ <resultMap id="select-product-result" class="Product">
+ <result property="Id" column="PRD_ID"/>
+ <result property="Description" column="PRD_DESCRIPTION"/>
+ </resultMap>
+<resultMaps>
+
+<statements>
+
+ <statement id="selectCategory" parameterClass="int"
+ resultMap="select-category-result">
+ select * from CATEGORY where CAT_ID = #value#
+ </statement>
+
+ <statement id="selectProductsByCatId" parameterClass="int"
+ resultMap="select-product-result">
+ select * from PRODUCT where PRD_CAT_ID = #value#
+ </statement>
+</statements>
+\end{verbatim}
+\end{example}
+
+\section{Avoiding N+1 Select Lists (1:M and M:N)}
+This is similar to the 1:1 situation above, but is of even greater concern due
+to the potentially large amount of data involved. The problem with the
+solution above is that whenever you load a Category, two SQL statements are
+actually being run (one for the Category and one for the list of associated
+Products). This problem seems trivial when loading a single Category, but if
+you were to run a query that loaded ten (10) Categories, a separate query
+would be run for each Category to load its associated list of Products. This
+results in eleven (11) queries total: one for the list of Categories and one
+for each Category returned to load each related list of Products (N+1 or in
+this case 10+1=11). To make this situation worse, we're dealing with
+potentially large lists of data.
+
+\begin{example}\label{example:3.43}
+N+1 Select Lists (1:M and M:N)
+\begin{verbatim}
+<resultMaps>
+
+ <resultMap id="select-category-result" class="Category">
+ <result property="Id" column="CAT_ID"/>
+ <result property="Description" column="CAT_DESCRIPTION"/>
+ <result property="ProductList" column="CAT_ID" select="selectProductsByCatId"/>
+ </resultMap>
+
+ <resultMap id="select-product-result" class="Product">
+ <result property="Id" column="PRD_ID"/>
+ <result property="Description" column="PRD_DESCRIPTION"/>
+ </resultMap>
+<resultMaps>
+
+<statements>
+
+ <!-- This statement executes 1 time -->
+ <statement id="selectCategory" parameterClass="int"
+ resultMap="select-category-result">
+ select * from CATEGORY where CAT_ID = #value#
+ </statement>
+
+ <!-- This statement executes N times (once for each category returned above)
+ and returns a list of Products (1:M) -->
+ <statement id="selectProductsByCatId" parameterClass="int"
+ resultMap="select-product-result">
+ select * from PRODUCT where PRD_CAT_ID = #value#
+ </statement>
+</statements>
+\end{verbatim}
+\end{example}
+
+\subsection{1:N \& M:N Solution?}
+Currently the feature that resolves this issue not implemented, but the
+development discussions are active, and we expect it to be included in a
+future release.
+
+\begin{mybox}{Lazy Loading vs. Joins (1:M and M:N):}
+As with the 1:1 situation described previously, it's important to note that
+using a join is not always better. This is even more true for collection
+properties than it was for individual value properties due to the greater
+amount of data. If you are in a situation where it is rare to access the
+related object (e.g. the ProductList property of the Category class) then it
+might actually be faster to avoid the join and the unnecessary loading of the
+list of products. This is especially true for database designs that involve
+outer joins or nullable and/or non-indexed columns. In these situations it
+might be better to use the sub-select solution with the lazy loading. The
+general rule of thumb is: use the join if you're more likely going to access
+the associated properties than not. Otherwise, only use it if lazy loading is
+not an option.
+\\
+\\
+As mentioned earlier, if you're having trouble deciding which way to go, don't
+worry. No matter which way you go, you can always change it without impacting
+your PHP code. The two examples above would result in exactly the same object
+graph and are loaded using the exact same method call. The only consideration
+is that if you were to enable caching, then the using the separate select (not
+the join) solution could result in a cached instance being returned. But more
+often than not, that won't cause a problem (your application should not be
+dependent on instance level equality i.e. ``==='').
+\end{mybox}
+
+\section{Composite Keys or Multiple Complex Parameters Properties}
+You might have noticed that in the above examples there is only a single key
+being used as specified in the \tt{resultMap} by the \tt{column} attribute.
+This would suggest that only a single column can be associated to a related
+mapped statement. However, there is an alternate syntax that allows multiple
+columns to be passed to the related mapped statement. This comes in handy for
+situations where a composite key relationship exists, or even if you simply
+want to use a parameter of some name other than \tt{\#value\#}. The alternate
+syntax for the column attribute is simply \{param1=column1, param2=column2,
+$\cdots$, paramN=columnN\}. Consider the example below where the PAYMENT table
+is keyed by both Customer ID and Order ID:
+
+\begin{example}\label{example:3.44}
+Mapping a composite key
+\begin{verbatim}
+<resultMaps>
+ <resultMap id="select-order-result" class="order">
+ <result property="id" column="ORD_ID"/>
+ <result property="customerId" column="ORD_CST_ID"/>
+ ...
+ <result property="payments" column="{itemId=ORD_ID, custId=ORD_CST_ID}"
+ select="selectOrderPayments"/>
+ </resultMap>
+<resultMaps>
+
+<statements>
+
+ <statement id="selectOrderPayments" resultMap="select-payment-result">
+ select * from PAYMENT
+ where PAY_ORD_ID = #itemId#
+ and PAY_CST_ID = #custId#
+ </statement>
+</statements>
+\end{verbatim}
+\end{example}
+
+Optionally you can just specify the column names as long as they're in the
+same order as the parameters. For example:
+\begin{verbatim}
+{ORD_ID, ORD_CST_ID}
+\end{verbatim}
+
+\begin{mybox}{Important!}
+Currently the SQLMap DataMapper framework does not automatically resolve
+circular relationships. Be aware of this when implementing parent/child
+relationships (trees). An easy work around is to simply define a second result
+map for one of the cases that does not load the parent object (or vice versa),
+or use a join as described in the ``N+1 avoidance'' solutions.
+\end{mybox}
+
+\begin{mybox}{Note:}
+Result Map names are always local to the Data Map definition file that they
+are defined in. You can refer to a Result Map in another Data Map definition
+file by prefixing the name of the Result Map with the namespace of the SqlMap
+set in the \tt{<sqlMap>} root element.
+\end{mybox}
diff --git a/docs/sqlmap/latex/ch6.tex b/docs/sqlmap/latex/ch6.tex new file mode 100644 index 00000000..09a2be6f --- /dev/null +++ b/docs/sqlmap/latex/ch6.tex @@ -0,0 +1,119 @@ +\chapter{Cache Models}\label{section:3.8}
+Some values in a database are know to change slower than others. To improve
+performance, many developers like to cache often-used data to avoid making
+unnecessary trips back to the database. SQLMap provides its own caching
+system, that you configure through a \tt{<cacheModel>} element.
+
+The results from a query Mapped Statement can be cached simply by specifying
+the \tt{cacheModel} parameter in the statement tag (seen above). A cache model
+is a configured cache that is defined within your DataMapper configuration
+file. Cache models are configured using the \tt{cacheModel} element as
+follows:
+
+\begin{example}\label{example:3.45}
+Configuring a cache using the Cache Model element
+\begin{verbatim}
+<cacheModel id="product-cache" implementation="LRU" >
+ <flushInterval hours="24"/>
+ <flushOnExecute statement="insertProduct"/>
+ <flushOnExecute statement="updateProduct"/>
+ <flushOnExecute statement="deleteProduct"/>
+ <property name="CacheSize" value="100"/>
+</cacheModel>
+\end{verbatim}
+\end{example}
+
+The cache model above will create an instance of a cache named
+``product-cache'' that uses a Least Recently Used (LRU) implementation. The
+value of the \tt{type} attribute is either a class name, or an alias for one
+of the included implementations (see below). Based on the flush elements
+specified within the cache model, this cache will be flushed every 24 hours.
+There can be only one flush interval element and it can be set using hours,
+minutes, seconds or milliseconds. In addition the cache will be flushed
+whenever the \tt{insertProduct}, \tt{updateProduct}, or \tt{deleteProduct}
+mapped statements are executed. There can be any number of ``flush on
+execute'' elements specified for a cache. Some cache implementations may need
+additional properties, such as the ``cache-size'' property demonstrated above.
+In the case of the LRU cache, the size determines the number of entries to
+store in the cache. Once a cache model is configured, you can specify the
+cache model to be used by a mapped statement, for example:
+
+\begin{example}\label{example:3.46}
+Specifying a Cache Model from a Mapped Statement
+\begin{verbatim}
+<statement id="getProductList" cacheModel="product-cache">
+ select * from PRODUCT where PRD_CAT_ID = #value#
+</statement>
+\end{verbatim}
+\end{example}
+
+\section{Cache Implementation}
+The cache model uses a pluggable framework for supporting different types of
+caches. The choice of cache is specified in the ``implementation'' attribute
+of the \tt{cacheModel} element as discussed above. The class name specified
+must be an implementation of the \tt{ISqlMapCache} interface, or one of the
+two aliases discussed below. Further configuration parameters can be passed to
+the implementation via the property elements contained within the body of the
+\tt{cacheModel}. Currently there are 2 implementations included with the PHP
+distribution.
+
+\subsection{Least Recently Used [LRU] Cache} The LRU cache implementation uses
+an Least Recently Used algorithm to determines how objects are automatically
+removed from the cache. When the cache becomes over full, the object that was
+accessed least recently will be removed from the cache. This way, if there is
+a particular object that is often referred to, it will stay in the cache with
+the least chance of being removed. The LRU cache makes a good choice for
+applications that have patterns of usage where certain objects may be popular
+to one or more users over a longer period of time (e.g. navigating back and
+forth between paginated lists, popular search keys etc.).
+
+The LRU implementation is configured as follows:
+\begin{example}\label{example:3.48}
+Configuring a LRU type cache
+\begin{verbatim}
+<cacheModel id="product-cache" implementation="LRU" >
+ <flushInterval hours="24"/>
+ <flushOnExecute statement="insertProduct"/>
+ <flushOnExecute statement="updateProduct"/>
+ <flushOnExecute statement="deleteProduct"/>
+ <property name="CacheSize" value="100"/>
+</cacheModel>
+\end{verbatim}
+\end{example}
+
+Only a single property is recognized by the LRU cache implementation. This
+property, named \tt{CacheSize} must be set to an integer value representing
+the maximum number of objects to hold in the cache at once. An important thing
+to remember here is that an object can be anything from a single string
+instance to an array of object. So take care not to store too much in your
+cache and risk running out of memory and disk space.
+
+\subsection{FIFO Cache}
+The FIFO cache implementation uses an First In First Out algorithm to
+determines how objects are automatically removed from the cache. When the
+cache becomes over full, the oldest object will be removed from the cache. The
+FIFO cache is good for usage patterns where a particular query will be
+referenced a few times in quick succession, but then possibly not for some
+time later.
+
+The FIFO implementation is configured as follows:
+
+\begin{example}\label{example:3.49}
+Configuring a FIFO type cache
+\begin{verbatim}
+<cacheModel id="product-cache" implementation="FIFO" >
+ <flushInterval hours="24"/>
+ <flushOnExecute statement="insertProduct"/>
+ <flushOnExecute statement="updateProduct"/>
+ <flushOnExecute statement="deleteProduct"/>
+ <property name="CacheSize" value="100"/>
+</cacheModel>
+\end{verbatim}
+\end{example}
+
+Only a single property is recognized by the FIFO cache implementation. This
+property, named \tt{CacheSize} must be set to an integer value representing
+the maximum number of objects to hold in the cache at once. An important thing
+to remember here is that an object can be anything from a single String
+instance to an array of object. So take care not to store too much in your
+cache and risk running out of memory and disk space.
diff --git a/docs/sqlmap/latex/ch7.tex b/docs/sqlmap/latex/ch7.tex new file mode 100644 index 00000000..37a6a0f4 --- /dev/null +++ b/docs/sqlmap/latex/ch7.tex @@ -0,0 +1,3 @@ +\chapter{Dynamic SQL}
+
+Not supported in this release.
diff --git a/docs/sqlmap/latex/ch8.tex b/docs/sqlmap/latex/ch8.tex new file mode 100644 index 00000000..7d7e3576 --- /dev/null +++ b/docs/sqlmap/latex/ch8.tex @@ -0,0 +1,413 @@ +\chapter{Installation and Setup}\label{section:4.3}
+\section{Introduction}
+This section explains how to install, configure, and use the SQLMap DataMapper
+with your PHP application.
+
+\section{Installing the DataMapper for PHP}
+There are two steps to using SQLMap DataMapper with your application for the
+first time.
+\begin{itemize}
+ \item Setup the distribution
+ \item Add XML documents
+\end{itemize}
+
+\subsection{Setup the Distribution}
+
+The official site for SQLMap DataMapper for PHP is http://... . The DataMapper
+is availabe as a source distribution in the form of a ZIP archive. To download
+the distributions, follow the link to the Downloads area on the web site, and
+select the the source distribution for the SQLMap PHP DataMapper release. You
+can extract the distribution using a utility like WinZip or the extractor
+built into newer versions of Windows.
+
+Under the distribution's source folder are eight folders that make up the
+SQLMap PHP DataMapper distribution, as shown in Table 4.1.
+
+\begin{table}[!hpt]
+\caption{Folders found in the SQLMap PHP DataMapper source distribution}
+\label{table:4.1}
+ \centering
+\begin{tabular}{|l|l|}
+\hline
+ \textbf{Folder name} & \textbf{Description} \\
+ \hline
+\end{tabular}
+\end{table}
+
+\subsection{Add XML file items}
+After unpacking the source distribution, you will need to add two types of XML
+files to your Web application, or library project (and Test project if you
+have one). These files are:
+
+\begin{description}
+ \item[SqlMap.xml] --
+ A Data Map file that contains your SQL queries. Your project will contain one
+ or more of these files with names such as Account.xml or Product.xml.
+
+ \item[SqlMap.config] --
+ The DataMapper configuration file that is used to specify the locations of your
+ SqlMap.xml files. It is also used to define other DataMapper
+ configuration options such as caching. You will need to include one SqlMap.config
+ file for each data source that your project has.
+\end{description}
+
+As expected, the \tt{SqlMap.config} file must be placed where the DataMapper
+can find them at runtime.
+
+\section{Configuring the DataMapper for PHP}
+The SQLMap PHP DataMapper is configured using a central XML descriptor file,
+usually named \tt{SqlMap.config}, which provides the details for your data
+source, data maps, and other features like caching, and transactions. At
+runtime, your application code will call a class method provided by the SQLMap
+library to read and parse your \tt{SqlMap.config} file. After parsing the
+configuration file, a DataMapper client will be returned by SQLMap for your
+application to use.
+
+\subsection{DataMapper clients}
+Currently, the SQLMap PHP DataMapper framework revolves around the
+\tt{TSqlMapper} class, which acts as a facade to the DataMapper framework API.
+You can create a DataMapper client by instantiating an object of the
+\tt{TSqlMapper} class. An instance of the \tt{TSqlMapper} class (your
+DataMapper client) is created by reading a single configuration file. Each
+configuration file can specify one database or data source. You can of couse
+use multiple DataMapper clients in your application. Just create another
+configuration file and pass the name of that file when the DataMapper client
+is created. The configuration files might use a different account with the
+same database, or reference different databases on different servers. You can
+read from one client and write to another, if that's what you need to do. See
+Section~\ref{section:4.4.1} for more details on building a \tt{TSqlMapper}
+instance, but first, let's take a look at the DataMapper configuration file.
+
+\section{DataMapper Configuration File (SqlMap.config)}
+A sample configuration file for a PHP web application is shown in
+Example~\ref{example:4.1}. Not all configuration elements are required. The
+following sections describe the elements of this \tt{SqlMap.config} file in
+more detail.
+
+\begin{example}\label{example:4.1}
+Sample SqlMap.Config for a PHP Web Application.
+\begin{verbatim}
+<?xml version="1.0" encoding="utf-8"?>
+<sqlMapConfig>
+
+ <provider class="TAdodbProvider" >
+ <datasource ConnectionString="mysql://user:pass@localhost/test1" />
+ </provider>
+
+ <sqlMaps>
+ <sqlMap name="Account" resource="maps/Account.xml"/>
+ <sqlMap name="Order" resource="maps/Order.xml"/>
+ <sqlMap name="Category" resource="maps/Category.xml"/>
+ <sqlMap name="LineItem" resource="maps/LineItem.xml"/>
+ </sqlMaps>
+
+</sqlMapConfig>
+\end{verbatim}
+\end{example}
+
+\section{DataMapper Configuration Elements}
+Sometimes the values we use in an XML configuration file occur in more than
+one element. Often, there are values that change when we move the application
+from one server to another. To help you manage configuration values, you can
+specify a standard properties file (with name=value entries) as part of a
+DataMapper configuration. Each named value in the properties file becomes a
+shell variable that can be used in the DataMapper configuration file and your
+Data Map definition files (see Chapter~\ref{section:3}).
+
+\subsection{\tt{<properties>} attributes}
+The \tt{<properties>} element can accept one \tt{resource} attribute to
+specify the location of the properties file.
+
+\begin{table}[!hpt]
+\caption{Attributes of the \tt{<properties>} element} \label{table:4.3}
+\centering
+\begin{tabular}{|l|l|}
+ \hline
+ \textbf{Attribute} & \textbf{Description} \\
+ \hline
+ \tt{resource} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ Specify the properties file to be loaded from the directory relative to
+ the current file.
+ \vspace{-3mm}\begin{verbatim}
+ resource="properties.config"
+ \end{verbatim}\vspace{-5mm}
+ \end{minipage}
+ \\
+ \hline
+\end{tabular}
+\end{table}
+
+For example, if the ``properties.config'' file contains
+
+\begin{verbatim}
+<?xml version="1.0" encoding="utf-8" ?>
+<settings>
+ <add key="username" value="albert" />
+</settings>
+\end{verbatim}
+
+then all elements in the DataMapper configuration can use the variable
+\tt{\${username}} to insert the value ``albert''. For example:
+
+\begin{verbatim}
+<provider ConnectionString="mysql://${username}:..."
+\end{verbatim}
+
+Properties are handy during building, testing, and deployment by making it
+easy to reconfigure your application for multiple environments.
+
+\subsection{\tt{<property>} element and attributes}
+You can also specify more than one properties file or add property keys and
+values directly into your \tt{SqlMap.config} file by using \tt{<property>}
+elements. For example:
+
+\begin{verbatim}
+<properties>
+ <property resource="myProperties.config"/>
+ <property resource="anotherProperties.config"/>
+ <property key="host" value="ibatis.com" />
+</properties>
+\end{verbatim}
+
+\begin{table}[!hpt]
+\caption{Attributes of the \tt{<property>} element} \label{table:4.3}
+\centering
+\begin{tabular}{|l|l|}
+ \hline
+ \textbf{Attribute} & \textbf{Description} \\
+ \hline
+ \tt{resource} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ Specify the properties file to be loaded from the directory relative to
+ the current file.
+ \vspace{-3mm}\begin{verbatim}
+ resource="properties.config"
+ \end{verbatim}\vspace{-5mm}
+ \end{minipage}
+ \\
+ \hline
+%
+ \tt{key} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ Defines a property key (variable) name
+ \vspace{-3mm}\begin{verbatim}
+ key="username"
+ \end{verbatim}\vspace{-5mm}
+ \end{minipage}
+ \\
+ \hline
+%
+ \tt{value} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ Defines a value that will be used by the DataMapper in place of the
+ the specified property key/variable
+ \vspace{-3mm}\begin{verbatim}
+ value="mydbuser"
+ \end{verbatim}\vspace{-5mm}
+ \end{minipage}
+ \\
+ \hline
+\end{tabular}
+\end{table}
+
+\subsection{The \tt{<typeHandler>} Element}
+The \tt{<typeHandler>} element allows for the configuration and use of a
+Custom Type Handler (see the Custom Type Handler section). This extends the
+DataMapper's capabilities in handling types that are specific to your database
+provider, are not handled by your database provider, or just happen to be a
+part of your application design.
+
+\begin{verbatim}
+ <typeHandler type="date" callback="TDateTimeHandler"/>
+\end{verbatim}
+
+The \tt{<typeHandler>} element has three attributes:
+\begin{table}[!hpt]
+\caption{Attributes of the \tt{<typeHandler>} element} \label{table:4.5}
+\centering
+\begin{tabular}{|l|l|}
+ \hline
+ \textbf{Attribute} & \textbf{Description} \\
+ \hline
+%
+ \tt{type} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ Refers to the name of the type to handle
+ \vspace{-3mm}\begin{verbatim}
+ type="date"
+ \end{verbatim}\vspace{-5mm}
+ \end{minipage}
+ \\
+ \hline
+%
+ \tt{dbType} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ Indicates the provider dbType to handle
+ \vspace{-3mm}\begin{verbatim}
+ dbType="Varchar2"
+ \end{verbatim}\vspace{-5mm}
+ \end{minipage}
+ \\
+ \hline
+%
+ \tt{callback} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ The custom type handler class name
+ \vspace{-3mm}\begin{verbatim}
+ callback="TDateTimeHandler"
+ \end{verbatim}\vspace{-5mm}
+ \end{minipage}
+ \\
+ \hline
+\end{tabular}
+\end{table}
+
+
+\subsection{The \tt{<provider>} element and attribute}
+The \tt{<provider>} element encloses a \tt{<datasource>} that configure the
+database system for use by the framework.
+\begin{table}[!hpt]
+\caption{Attributes of the \tt{<provider>} element} \label{table:4.3}
+\centering
+\begin{tabular}{|l|l|}
+ \hline
+ \textbf{Attribute} & \textbf{Description} \\
+ \hline
+ \tt{class} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ The database provider class that extends
+ \tt{TDatabaseProvider}.
+ \vspace{-3mm}\begin{verbatim}
+ class="TAdodbProvider"
+ \end{verbatim}\vspace{-5mm}
+ \end{minipage}
+ \\
+ \hline
+ \end{tabular}
+\end{table}
+
+\subsection{The \tt{<datasource>} element and attributes}
+The \tt{<datasource>} element specifies the connection string.
+Example~\ref{example:4.2} shows sample element MySql.
+
+\begin{example}\label{example:4.2}
+Sample \tt{<provider>} and \tt{<datasource>} elements.
+\begin{verbatim}
+<!-- The ${properties} are defined in an external file, -->
+<!-- but the values could also be coded inline. -->
+
+<!-- Connecting to a MySQL database -->
+<provider class="TAdodbProvider" >
+ <datasource
+ ConnectionString="mysql://${username}:${password}@${host}/${database}" />
+</provider>
+\end{verbatim}
+\end{example}
+
+\begin{table}[!hpt]
+\caption{Attributes of the \tt{<datasource>} element} \label{table:4.4}
+\centering
+\begin{tabular}{|l|l|}
+ \hline
+ \textbf{Attribute} & \textbf{Description} \\
+ \hline
+%
+ \tt{connectionString} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ Data Source Name (DSN) connection string.
+ \vspace{-3mm}\begin{verbatim}
+ connectionString="mysql://root:pwd@localhost/mydb"
+ \end{verbatim}\vspace{-5mm}
+ \end{minipage}
+ \\
+ \hline
+%
+ \tt{driver} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ Database driver name (mysql, sqlite, etc.)
+ \vspace{-3mm}\begin{verbatim}
+ driver="mysql"
+ \end{verbatim}\vspace{-5mm}
+ \end{minipage}
+ \\
+ \hline
+%
+ \tt{host} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ DB host name/IP (and port number) in the format \tt{host[:port]}.
+ \vspace{-3mm}\begin{verbatim}
+ connectionString="mysql://root:pwd@localhost/mydb"
+ \end{verbatim}\vspace{-5mm}
+ \end{minipage}
+ \\
+ \hline
+%
+ \tt{username} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ Database connection username.\vspace{2mm}
+ \end{minipage}
+ \\
+ \hline
+%
+ \tt{password} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ Database connection password.\vspace{2mm}
+ \end{minipage}
+ \\
+ \hline
+%
+ \tt{database} &
+ \begin{minipage}{0.7\textwidth}\vspace{2mm}
+ Database name to use in the connection.\vspace{2mm}
+ \end{minipage}
+ \\
+ \hline
+ \end{tabular}
+\end{table}
+
+\begin{mybox}{Tip:}
+ Use Data Source Name (DSN) connection string or specify the
+ necessary individual connection parameters.
+\end{mybox}
+
+\subsection{The \tt{<sqlMap>} Element}
+On a daily basis, most of your work will be with the Data Maps, which are
+covered by Chapter~\ref{section:3}. The Data Maps define the actual SQL
+statements or stored procedures used by your application. The parameter and
+result objects are also defined as part of the Data Map. As your application
+grows, you may have several varieties of Data Map. To help you keep your Data
+Maps organized, you can create any number of Data Map definition files and
+incorporate them by reference in the DataMapper configuration. All of the
+definition files used by a DataMapper instance must be listed in the
+configuration file.
+
+Example~\ref{example:4.3} shows \tt{<sqlMap>} elements for loading a set of
+Data Map definitions. For more about Data Map definition files, see
+Chapter~\ref{section:3}.
+
+\begin{example}\label{example:4.3}
+Specifying \tt{sqlMap} locations
+\begin{verbatim}
+<!-- Relative path from the directory of the
+ current file using a property variable -->
+<sqlMap resource="${root}/Maps/Account.xml"/>
+<sqlMap resource="${root}/Maps/Category.xml"/>
+<sqlMap resource="${root}/Maps/Product.xml"/>
+
+<!-- Full file path with a property variable -->
+<sqlMap resource="/${projectdir}/MyApp/Maps/Account.xml"/>
+<sqlMap resource="/${projectdir}/MyApp/Maps/Category.xml"/>
+<sqlMap resource="/${projectdir}/MyApp/Maps/Product.xml"/>
+\end{verbatim}
+\end{example}
+
+\begin{mybox}{Tip:}
+Since the application root directory location differs by project type
+(Windows, Web, or library), it is best to use a properties variable to
+indicate the relative path when using the \tt{<sqlMap>} \tt{resource}
+attribute. Having a variable defined in a properties file makes it easy to
+change the path to all your Data Mapper configuration resources in one
+location (note the \tt{\$\{projectdir\}} and \tt{\$\{root\}} variables in the
+example above).
+\end{mybox}
diff --git a/docs/sqlmap/latex/ch9.tex b/docs/sqlmap/latex/ch9.tex new file mode 100644 index 00000000..b1ebb522 --- /dev/null +++ b/docs/sqlmap/latex/ch9.tex @@ -0,0 +1,302 @@ +\chapter{Using SQLMap PHP DataMapper}
+The SQLMap DataMapper API provides four core functions:
+
+\begin{itemize}
+ \item build a \tt{TSqlMapper} instance from a configuration file or cache
+ \item execute an update query (including insert and delete).
+ \item execute a select query for a single object
+ \item execute a select query for a list of objects
+\end{itemize}
+
+The API also provides support for retrieving paginated lists and managing
+transactions.
+
+\section{Building a \tt{TSqlMapper} instance}
+An XML document is a wonderful tool for describing a database configuration
+(Chapter~\ref{section:4.3}) or defining a set of data mappings
+(Chapter~\ref{section:3}), but you can't execute XML. In order to use the
+SQLMap configuration and definitions in your PHP application, you need a class
+you can call.
+
+The framework provides service methods that you can call which read the
+configuration file (and any of its definition files) and builds a
+\tt{TSqlMapper} object. The \tt{TSqlMapper} object provides access to the rest
+of the framework. Example~\ref{example:9.4} shows a singleton Mapper that is
+similar to the one bundled with the framework.
+
+\begin{example}\label{example:9.4}
+A Mapper singleton you can call from your own applications
+\begin{verbatim}
+require_once('/path/to/SQLMap/TSqlMapper.php');
+
+class TMapper
+{
+ private static $_mapper;
+
+ public static function configure($configFile)
+ {
+ if(is_null(self::$_mapper))
+ {
+ $builder = new TDomSqlMapBuilder();
+ self::$_mapper = $builder->configure($configFile);
+ }
+ return self::$_mapper;
+ }
+
+ public static function instance()
+ {
+ return self::$_mapper;
+ }
+}
+\end{verbatim}
+\end{example}
+
+To obtain the \tt{TSqlMapper} instance, first configure the mapper once,
+\begin{verbatim}
+TMapper::configure('path/to/sqlmap.config');
+\end{verbatim}
+The \tt{TDomSqlMapBuilder} object will go throught the the \tt{sqlmap.config}
+file and build a \tt{TSqlMapper} instance. To use \tt{TSqlMapper} in your
+application, specify one of the \tt{TSqlMapper} methods (see Section ???).
+Here's an example:
+\begin{verbatim}
+$list = TMapper::instance()->queryForList("PermitNoForYearList", $values);
+\end{verbatim}
+
+\subsection{Multiple Databases}
+If you need access to more than one database from the same application, create
+a DataMapper configuration file for that database and another Mapper class to
+go with it.
+
+\subsection{\tt{TDomSqlMapBuilder} Configuration Options}
+If you find that you already have loaded your DataMapper configuration
+information as a \tt{SimpleXMLElement} instance within your application, the
+\tt{TDomSqlMapBuilder} provides \tt{Configure} overloads for those types as
+well.
+
+\section{Exploring the SQLMap PHP DataMapper API through the \tt{TSqlMapper}}
+The \tt{TSqlMapper} instance acts as a facade to provide access the rest of
+the DataMapper framework. The DataMapper API methods are shown in Example
+4.11.
+
+\begin{example}
+The SQLMap DataMapper API for PHP.
+\begin{verbatim}
+ /* Query API */
+ public function queryForObject($statementName, $parameter=null, $result=null);
+ public function queryForList($statementName, $parameter=null, $result=null,
+ $skip=-1, $max=-1);
+ public function queryForPagedList($statementName, $parameter=null, $pageSize=10);
+ public function queryForMap($statementName, $parameter=null,
+ $keyProperty=null, $valueProperty=null);
+
+ public function insert($statementName, $parameter=null)
+ public function update($statementName, $parameter=null)
+ public function delete($statementName, $parameter=null)
+
+ /* Connection API */
+ public function openConnection()
+ public function closeConnection()
+
+ /* Transaction API */
+ public function beginTransaction()
+ public function commitTransaction()
+ public function rollBackTransaction()
+\end{verbatim}
+\end{example}
+
+Note that each of the API methods accept the name of the Mapped Statement as
+the first parameter. The \tt{statementName} parameter corresponds to the
+\tt{id} of the Mapped Statement in the Data Map definition (see
+Section~\ref{section:3.3}). In each case, a \tt{parameterObject} also may be
+passed. The following sections describe how the API methods work.
+
+\subsection{Insert, Update, Delete}
+\begin{verbatim}
+ public function insert($statementName, $parameter=null)
+ public function update($statementName, $parameter=null)
+ public function delete($statementName, $parameter=null)
+\end{verbatim}
+
+If a Mapped Statement uses one of the \tt{<insert>}, \tt{<update>}, or
+\tt{<delete>} statement-types, then it should use the corresponding API
+method. The \tt{<insert>} element supports a nested \tt{<selectKey>} element
+for generating primary keys (see Section~\ref{section:3.3.3}). If the
+\tt{<selectKey>} stanza is used, then \tt{insert} returns the generated key;
+otherwise a null object is returned. Both the \tt{update} and \tt{delete}
+methods return the number of rows affected by the statement.
+
+\subsection{QueryForObject}
+\begin{verbatim}
+public function queryForObject($statementName, $parameter=null, $result=null);
+\end{verbatim}
+
+If a Mapped Statement is expected to select a single row, then call it using
+\tt{queryForObject}. Since the Mapped Statement definition specifies the
+result class expected, the framework can both create and populate the result
+class for you. Alternatively, if you need to manage the result object
+yourself, say because it is being populated by more than one statement, you
+can use the alternate form and pass your \tt{\$resultObject} as the third
+parameter.
+
+\subsection{QueryForList}
+
+\begin{verbatim}
+public function queryForList($statementName, $parameter=null, $result=null,
+ $skip=-1, $max=-1);
+\end{verbatim}
+If a Mapped Statement is expected to select multiple rows, then call it using
+\tt{queryForList}. Each entry in the list will be an result object populated
+from the corresponding row of the query result. If you need to manage the
+\tt{\$resultObject} yourself, then it can be passed as the third parameter. If
+you need to obtain a partial result, the fourth parameter \tt{\$skip} and
+fifth parameter \tt{\$max} allow you to skip a number of records (the starting
+point) and the maximum number to return.
+
+
+\subsection{QueryForPagedList}
+\begin{verbatim}
+ public function queryForPagedList($statementName, $parameter=null, $pageSize=10);
+\end{verbatim}
+We live in an age of information overflow. A database query often returns more
+hits than users want to see at once, and our requirements may say that we need
+to offer a long list of results a ``page'' at a time. If the query returns
+1000 hits, we might need to present the hits to the user in sets of fifty, and
+let them move back and forth between the sets. Since this is such a common
+requirement, the framework provides a convenience method.
+
+The \tt{TSqlMapPagedList} interface includes methods for navigating through
+pages (\tt{nextPage()}, \tt{previousPage()}, \tt{gotoPage(\$pageIndex)}) and
+also checking the status of the page (\tt{getIsFirstPage()},
+\tt{getIsMiddlePage()}, \tt{getIsLastPage()}, \tt{getIsNextPageAvailable()},
+\tt{getIsPreviousPageAvailable()}, \tt{getCurrentPageIndex()},
+\tt{getPageSize()}). The total number of records available is not accessible
+from the \tt{TSqlMapPagedList} interface, unless a virtual count is defined
+using \tt{setVirtualCount(\$value)}, this should be easily accomplished by
+simply executing a second statement that counts the expected results.
+
+\begin{mybox}{Tip:}
+The \tt{queryForPagedList} method is convenient, but note that a larger set
+(up to 3 times the page size) will first be returned by the database provider
+and the smaller set extracted by the framework. The higher the page size, the
+larger set that will be returned and thrown away. For very large sets, you may
+want to use a stored procedure or your own query that uses \tt{\$skip} and
+\tt{\$max} as parameters in \tt{queryForList}.
+\end{mybox}
+
+\subsection{QueryForMap}
+\begin{verbatim}
+public function queryForMap($statementName, $parameter=null,
+ $keyProperty=null, $valueProperty=null);
+\end{verbatim}
+The \tt{queryForList} methods return the result objects within a \tt{TList} or
+array instance. Alternatively, the \tt{queryForMap} returns a TMap or
+associative array instance. The value of each entry is one of the result
+objects. The key to each entry is indicated by the \tt{\$keyProperty}
+parameter. This is the name of the one of the properties of the result object,
+the value of which is used as the key for each entry. For example, If you
+needed a set of \tt{Employee} objects, you might want them returned as a
+\tt{TMap} keyed by each object's \tt{EmployeeNumber} property.
+
+If you don't need the entire result object in your result, you can add the
+\tt{\$valueProperty} parameter to indicate which result object property should
+be the value of an entry. For example, you might just want the
+\tt{EmployeeName} keyed by \tt{EmployeeNumber}.
+
+\subsection{Transaction}
+The DataMapper API includes methods to demarcate transactional boundaries. A
+transaction can be started, committed and/or rolled back. You can call the
+transaction methods from the \tt{TSqlMapper} instance.
+
+\begin{verbatim}
+// Begin a transactional session using Adodb transaction API
+public function beginTransaction()
+
+// Commit a transaction, uses Adodb transaction API
+public function commitTransaction()
+
+// RollBack a transaction, uses Adodb transaction API
+public void RollBackTransaction()
+\end{verbatim}
+
+\begin{example}\label{example:9.15}
+Using transactions
+\begin{verbatim}
+try
+{
+ $sqlMap->beginTransaction();
+ $item = $sqlMap->queryForObject("getItem", $itemId);
+ $item->setDescription($newDescription);
+ $sqlMap->update("updateItem", $item);
+ $sqlMap->commitTransaction();
+}
+catch
+{
+ $sqlMap->rollBackTransaction();
+}
+\end{verbatim}
+\end{example}
+
+\section{Coding Examples}
+\begin{example}\label{example:10.1}
+Executing Update (insert, update, delete)
+\begin{verbatim}
+$product = new Product();
+$product->setId(1);
+$product->setDescription('Shih Tzui');
+
+$key = $sqlMap->insert('insertProduct', $product);
+\end{verbatim}
+\end{example}
+
+\begin{example}\label{example:10.2}
+Executing Query for Object (select)
+\begin{verbatim}
+$key = 1;
+$product = $sqlMap->queryForObject ('getProduct', $key);
+\end{verbatim}
+\end{example}
+
+\begin{example}\label{example:10.3}
+Executing Query for Object (select) With Preallocated Result Object
+\begin{verbatim}
+$customer = new Customer();
+
+$sqlMap->beginTransaction();
+
+$sqlMap->queryForObject('getCust', $parameter, $customer);
+$sqlMap->queryForObject('getAddr', $parameter, $customer);
+$sqlMap->commitTransaction();
+\end{verbatim}
+\end{example}
+
+\begin{example}\label{example:10.4}
+Executing Query for List (select)
+\begin{verbatim}
+$list = $sqlMap->queryForList ('getProductList');
+\end{verbatim}
+\end{example}
+
+\begin{example}\label{example:10.4}
+Executing Query for List (select) With Result Boundaries
+\begin{verbatim}
+$list = $sqlMap->queryForList ('getProductList', $key, null, 0, 40);
+\end{verbatim}
+\end{example}
+
+\begin{example}\label{example:10.5}
+Executing Query for Paginated List (select)
+\begin{verbatim}
+$list = $sqlMap->queryForPagedList ('getProductList', null, 10);
+$list->nextPage();
+$list->previousPage();
+\end{verbatim}
+\end{example}
+
+\begin{example}\label{example:10.6}
+Executing Query for Map
+\begin{verbatim}
+ $map = $sqlMap->QueryForMap('getProductList', null, 'productCode');
+ $product = $map['EST-93'];
+\end{verbatim}
+\end{example}
diff --git a/docs/sqlmap/latex/diagram.pdf b/docs/sqlmap/latex/diagram.pdf Binary files differnew file mode 100644 index 00000000..5e64e388 --- /dev/null +++ b/docs/sqlmap/latex/diagram.pdf diff --git a/docs/sqlmap/latex/example1.png b/docs/sqlmap/latex/example1.png Binary files differnew file mode 100644 index 00000000..b5241de6 --- /dev/null +++ b/docs/sqlmap/latex/example1.png diff --git a/docs/sqlmap/latex/grid1.png b/docs/sqlmap/latex/grid1.png Binary files differnew file mode 100644 index 00000000..845b9581 --- /dev/null +++ b/docs/sqlmap/latex/grid1.png diff --git a/docs/sqlmap/latex/grid2.png b/docs/sqlmap/latex/grid2.png Binary files differnew file mode 100644 index 00000000..dcafc33d --- /dev/null +++ b/docs/sqlmap/latex/grid2.png diff --git a/docs/sqlmap/latex/sqlmap.tex b/docs/sqlmap/latex/sqlmap.tex new file mode 100644 index 00000000..57faaab3 --- /dev/null +++ b/docs/sqlmap/latex/sqlmap.tex @@ -0,0 +1,143 @@ +\documentclass{book}
+%\usepackage{graphicx}
+
+\usepackage[pdftex]{hyperref}
+\usepackage[pdftex]{graphicx}
+
+\usepackage{amsmath}
+\usepackage{amsfonts}
+\usepackage{amssymb}
+
+\usepackage{fancyhdr}
+
+\newtheorem{example}{Example}[section]
+
+\renewcommand{\tt}[1]{\texttt{#1}}
+
+%---------- fonts Type 1 -----------------
+\usepackage{times}
+\usepackage[T1]{fontenc}
+\usepackage{textcomp}
+
+%------------------------Page set-up-----------------------------------------
+
+\renewcommand{\baselinestretch}{1.25}
+\setlength{\hoffset}{-1in}
+\setlength{\oddsidemargin}{3.5cm}
+\setlength{\evensidemargin}{3.5cm}
+\setlength{\topmargin}{0cm}
+\setlength{\footskip}{2cm}
+\setlength{\headheight}{14pt}
+\setlength{\marginparwidth}{0cm}
+\setlength{\marginparsep}{0cm}
+\setlength{\marginparpush}{0cm}
+\setlength{\textwidth}{15cm}
+\setlength{\parindent}{0cm}
+\setlength{\parskip}{0.75\baselineskip}
+
+%------------------------------------------------------------------------------
+
+\newsavebox{\fmboxb}
+\newenvironment{mybox}[1]
+ {\vspace{-2mm}\begin{center}\begin{lrbox}{\fmboxb}\hspace{2mm}
+ \begin{minipage}{0.85\textwidth} \vspace{2mm}\small \textbf{#1}}
+ { \vspace{2mm} \end{minipage}
+ \hspace{2mm}\end{lrbox}\fbox{\usebox{\fmboxb}}\end{center}}
+
+
+%---- change link style ----
+\hypersetup{colorlinks, linkcolor=blue, pdfstartview={FitH}}
+
+
+% Pages and Fancyheadings stuff
+%-----------------------------------------------------------------------
+\cfoot{\thepage}
+\fancyhead[LE,RO]{}
+\fancyhead[LO]{\nouppercase{\scshape\rightmark}}
+\fancyhead[RE]{\nouppercase{\scshape\leftmark}}
+%-----------------------------------------------------------------------
+
+%----------------- TITLE --------------
+
+\title{\Huge \bfseries SQLMap PHP DataMapper Application Framework --
+v1.0
+ \thanks{Copyright 2006. All Rights Reserved.}
+}
+\author{Wei Zhuo}
+\date{\today}
+
+
+%-------------- BEGIN DOCUMENT ------------------
+
+
+\begin{document}
+
+\maketitle
+
+\pagestyle{plain}
+\addcontentsline{toc}{chapter}{Contents}
+\pagenumbering{roman}
+\tableofcontents
+
+\chapter*{Legal Notice}
+\addcontentsline{toc}{chapter}{Legal Notice}
+
+Copies of this document may be made for your own use and for distribution to
+others, provided that you do not charge any fee for such copies and further
+provided that each copy contains this Copyright Notice, whether distributed in
+print or electronically.
+
+This document is largely based on the iBATIS.NET -- DataMapper Application
+Framework Developer Guide.
+
+\chapter*{License}
+\addcontentsline{toc}{chapter}{License}
+SQLMap for PHP is free software released under the terms of the following BSD license.\\
+Copyright 2004-2006, PradoSoft (http://www.pradosoft.com)\\
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+\begin{enumerate}
+ \item Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+ \item Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+\item Neither the name of the developer nor the names of its contributors may
+be used to endorse or promote products derived from this software without
+specific prior written permission.
+\end{enumerate}
+
+\begin{verbatim}
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+\end{verbatim}
+
+
+\newpage
+
+\pagestyle{fancyplain}
+\pagenumbering{arabic}
+
+\include{ch1}
+\include{ch2}
+\include{ch3}
+\include{ch4}
+\include{ch5}
+\include{ch6}
+\include{ch7}
+\include{ch8}
+\include{ch9}
+
+\end{document}
diff --git a/docs/sqlmap/latex/sqlmap_tut.tex b/docs/sqlmap/latex/sqlmap_tut.tex new file mode 100644 index 00000000..728fc91f --- /dev/null +++ b/docs/sqlmap/latex/sqlmap_tut.tex @@ -0,0 +1,96 @@ +\documentclass{article}
+%\usepackage{graphicx}
+
+\usepackage[pdftex]{hyperref}
+\usepackage[pdftex]{graphicx}
+
+\usepackage{amsmath}
+\usepackage{amsfonts}
+\usepackage{amssymb}
+
+
+\newtheorem{example}{Example}[section]
+
+\renewcommand{\tt}[1]{\texttt{#1}}
+
+%---------- fonts Type 1 -----------------
+\usepackage{times}
+\usepackage[T1]{fontenc}
+\usepackage{textcomp}
+
+%------------------------Page set-up-----------------------------------------
+
+\renewcommand{\baselinestretch}{1.25}
+\setlength{\hoffset}{-1in}
+\setlength{\oddsidemargin}{3.5cm}
+\setlength{\evensidemargin}{3.5cm}
+\setlength{\topmargin}{0cm}
+\setlength{\footskip}{2cm}
+\setlength{\headheight}{14pt}
+\setlength{\marginparwidth}{0cm}
+\setlength{\marginparsep}{0cm}
+\setlength{\marginparpush}{0cm}
+\setlength{\textwidth}{15cm}
+\setlength{\parindent}{0cm}
+\setlength{\parskip}{0.75\baselineskip}
+
+\hypersetup{colorlinks, linkcolor=blue, pdfstartview={FitH}}
+
+%------------------------------------------------------------------------------
+
+\newsavebox{\fmboxb}
+\newenvironment{mybox}[1]
+ {\vspace{-2mm}\begin{center}\begin{lrbox}{\fmboxb}\hspace{2mm}
+ \begin{minipage}{0.85\textwidth} \vspace{2mm}\small \textbf{#1}}
+ { \vspace{2mm} \end{minipage}
+ \hspace{2mm}\end{lrbox}\fbox{\usebox{\fmboxb}}\end{center}}
+
+%----------------- TITLE --------------
+
+\title{\vspace{-2.5cm} \bfseries SQLMap PHP DataMapper Tutorial
+ \thanks{Copyright 2006. All Rights Reserved.}
+}
+\author{Wei Zhuo}
+\date{\today}
+
+
+%-------------- BEGIN DOCUMENT ------------------
+
+
+\begin{document}
+
+\maketitle
+
+This tutorial takes an ``over-the-shoulder'' Cookbook approach. We'll define a
+simple data access problem and use SQLMap to solve it for us.
+
+
+\section*{Legal Notice}
+
+Copies of this document may be made for your own use and for distribution to
+others, provided that you do not charge any fee for such copies and further
+provided that each copy contains this Copyright Notice, whether distributed in
+print or electronically.
+
+This document is largely based on the iBATIS.NET -- DataMapper Application
+Tutorial.
+
+\subsection*{License Information}
+SQLMap for PHP is free software released under the terms of the following BSD
+license. Copyright 2004-2006, PradoSoft (http://www.pradosoft.com) All rights
+reserved.
+
+\subsection*{Disclaimer}
+SQLMap PHP DataMapper MAKES NO WARRANTIES, EXPRESS OR IMPLIED, AS TO THE
+INFORMATION IN THIS DOCUMENT. The names of actual companies and products
+mentioned herein may be the trademarks of their respective owners.
+
+\subsection*{Remark}
+Original writing by Clinton Begin, Ted Husted and Gilles Bayon.
+
+\include{tut1}
+\include{tut2}
+\include{tut3}
+
+
+\end{document}
diff --git a/docs/sqlmap/latex/tut1.tex b/docs/sqlmap/latex/tut1.tex new file mode 100644 index 00000000..8f7a4a26 --- /dev/null +++ b/docs/sqlmap/latex/tut1.tex @@ -0,0 +1,257 @@ +\section{Test First!}
+
+Let's say that our most important client has a database and one of the tables
+in the database is a list of people. Our client tells us:
+
+``We would like to use a web application to display the people in this table
+and to add, edit, and delete individual records.''
+
+Not a complicated story, but it will cover the CRUD most developers want to
+learn first. :) Let's start with the people table that the client mentioned.
+Since we're keeping it simple, we'll say it's a table in an Access database.
+The table definition is shown as Example~\ref{example:1}.
+
+\begin{example}\label{example:1}
+The Person Table
+\begin{verbatim}
+Name Type Size
+PER_ID Long Integer 4
+PER_FIRST_NAME Text 40
+PER_LAST_NAME Text 40
+PER_BIRTH_DATE Date/Time 8
+PER_WEIGHT_KG Double 8
+PER_HEIGHT_M Double 8
+\end{verbatim}
+\end{example}
+
+\begin{mybox}{Tip:}
+ This example is bundled with a SQLite database file ``Data/test.db''
+ that contains the \tt{Person} table and some data, ready to use.
+\end{mybox}
+
+The first thing our story says is that client would like to display a list of
+people. Example~\ref{example:2} shows our test for that.
+
+\begin{example}\label{example:2}
+Tests/PersonTest.php
+\begin{verbatim}
+<?php
+class PersonTest extends UnitTestCase
+{
+ function testPersonList()
+ {
+ //try it
+ $people = TMapper::instance()->queryForList("SelectAll");
+
+ //test it
+ $this->assertNotNull($people, "Person list is not returned");
+ $this->assertTrue($people->getCount() > 0, "Person list is empty");
+ $person = $people[0];
+ $this->assertNotNull($person, "Person not returned");
+ }
+}
+?>
+\end{verbatim}
+\end{example}
+
+Well, Example 2 sure looks easy enough! We ask a method to ``select all'', and
+it returns a list of person objects. But, what code do we need to write to
+pass this test?
+
+\begin{mybox}{Note:}
+ Save the PersonTest.php into a \tt{Tests} directory. The unit tests are
+ written for the SimpleTest framework (http://simpletest.sf.net).
+\end{mybox}
+
+Now, to setup the testing framework, suppose you have the \tt{SimpleTest}
+framework installed. Then we need to create an entry file to run the tests.
+See the \tt{SimpleTest} documentation for further details on setting up tests.
+
+\begin{example}\label{example:2a}
+Unit test entry file, \tt{run\_tests.php}.
+\begin{verbatim}
+<?php
+require_once('../tests/simpletest/unit_tester.php');
+require_once('../tests/simpletest/reporter.php');
+require_once('../SQLMap/TMapper.php');
+require_once('Models/Person.php');
+
+//supress strict warnings from Adodb.
+error_reporting(E_ALL);
+
+$test = new GroupTest('All tests');
+$test->addTestFile('Tests/PersonTest.php'); $test->run(new HtmlReporter());
+?>
+\end{verbatim}
+\end{example}
+To run the tests, point your browser to the ``run\_test.php'' script file
+served from your web server.
+
+Let's see. The test uses a list of person objects. We could start with a blank
+object, just to satisfy the test, and add the display properties later. But
+let's be naughty and skip a step. Our fully-formed person object is shown in
+Example~\ref{example:3}.
+
+\begin{example}\label{example:3}
+Models/Person.php
+\begin{verbatim}
+<?php
+class Person
+{
+ public $ID = -1;
+ public $FirstName;
+ public $LastName;
+ public $WeightInKilograms = 0.0;
+ public $HeightInMeters = 0.0;
+
+ private $_birthDate;
+
+ //setters and getter for BirthDate
+ public function getBirthDate()
+ {
+ return $this->_birthDate;
+ }
+
+ public function setBirthDate($value)
+ {
+ $this->_birthDate = $value;
+ }
+}
+?>
+\end{verbatim}
+\end{example}
+
+OK, that was fun! The \tt{\$this->assertXXX} methods are built into
+\tt{UnitTestCase} class. So to run Example~\ref{example:2}, we just need the
+\tt{TMapper} object and \tt{queryForList} method. Wonderfully, the SQLMap
+DataMapper framework has a \tt{TMapper}class built into it that will work just
+fine for for us to use in this tutorial, so we don't need to write that
+either.
+
+When the \tt{TMapper->instance()} method is called, an instance of the SQLMap
+\tt{TSqlMapper} class is returned that has various methods available such as
+\tt{queryForList}. In this example, the SQLMap \tt{TSqlMapper->queryForList()}
+method executes our SQL statement (or stored procedure) and returns the result
+as a list. Each row in the result becomes an entry in the list. Along with
+\tt{queryForList()}, there are also \tt{delete()}, \tt{insert()},
+\tt{queryForObject()}, \tt{queryForPagedList()} and a few other methods in the
+SQLMap API. (See Chapter 9 in the SQLMap DataMapper Developer Guide for
+details.)
+
+Looking at Example~\ref{example:2}, we see that the \tt{queryForList()} method
+takes the name of the statement we want to run. OK. Easy enough. But where
+does SQLMap get the ``SelectAll'' statement? Some systems try to generate SQL
+statements for you, but SQLMap specializes in data mapping, not code
+generation. It's our job (or the job of our database administrator) to craft
+the SQL or provide a stored procedure. We then describe the statement in an
+XML element, like the one shown in Example~\ref{example:4}.
+
+\begin{example}\label{example:4}
+We use XML elements to map a database statement to an application object.
+\begin{verbatim}
+<?xml version="1.0" encoding="utf-8" ?>
+<sqlMap>
+ <select id="SelectAll" resultClass="Person">
+ SELECT
+ per_id as ID,
+ per_first_name as FirstName,
+ per_last_name as LastName,
+ per_birth_date as BirthDate,
+ per_weight_kg as WeightInKilograms,
+ per_height_m as HeightInMeters
+ FROM
+ person
+ </select>
+</sqlMap>
+\end{verbatim}
+\end{example}
+
+The SQLMap mapping documents can hold several sets of related elements, like
+those shown in Example~\ref{example:4}. We can also have as many mapping
+documents as we need to help organize our code. Additionally, having multiple
+mapping documents is handy when several developers are working on the project
+at once.
+
+So, the framework gets the SQL code for the query from the mapping, and plugs
+it into a prepared statement. But, how does SQLMap know where to find the
+table's datasource?
+
+Surprise! More XML! You can define a configuration file for each datasource
+your application uses. Example~\ref{example:5} shows a configuration file for
+our SQLite database.
+
+\begin{example}\label{example:5}
+sqlmap.config - a configuration file for our SQLite database
+\begin{verbatim}
+<?xml version="1.0" encoding="UTF-8" ?>
+<sqlMapConfig>
+ <provider class="TAdodbProvider">
+ <datasource driver="sqlite" host="Data/test.db" />
+ </provider>
+ <sqlMaps>
+ <sqlMap resource="Data/person.xml"/>
+ </sqlMaps>
+</sqlMapConfig>
+\end{verbatim}
+\end{example}
+
+The \tt{<provider>} specifies the database provider class, in this case
+\tt{TAdodbProvider} using the Adodb library. The \tt{<datasource>} tag
+specifies the database connection details. In this case, for an SQLite
+database, we just need the driver name, and the host that points to the actual
+SQLite database file.
+
+The last part of the configuration file ("sqlMaps") is where we list our
+mapping documents, like the one shown back in Example~\ref{example:4}. We can
+list as many documents as we need here, and they will all be read when the
+configuration is parsed.
+
+OK, so how does the configuration get parsed?
+
+Look back at Example~\ref{example:2}. The heart of the code is the call to the
+``\tt{TMapper}'' object (under the remark "try it"). The \tt{TMapper} object
+is a singleton that handles the instantiation and configuration of an SQLMap
+\tt{TSqlMapper} object, which provides a facade to the SQLMap DataMapper
+framework API.
+
+The first time that the \tt{TMapper} is called, it reads in the
+\tt{sqlmap.config} file and associated mapping documents to create an instance
+of the \tt{TSqlMapper} class. On subsequent calls, it reuses the
+\tt{TSqlMapper} object so that the configuration is re-read only when files
+change.
+
+The framework comes bundled with a default \tt{TMapper} class for you to use
+immediately to get access to the SQLMap SqlMapper object. If you want to use a
+different name other than \tt{sqlmap.config} at the default location for the
+configuration file, or need to use more than one database and have one
+SqlMapper per database, you can also write your own class to mimic the role of
+the Mapper class view by copying and modifying the standard version.
+
+\begin{mybox}{Tip:}
+ You can also call \tt{TMapper::configure('/path/to/your/sqlmap.config')}
+ to configure the \tt{TMapper} for a specific configuration file.
+\end{mybox}
+
+If we put this all together into a solution, we can ``green bar'' our test. At
+this point you should have the following files.
+\begin{verbatim}
+Data/person.xml % Mapping file.
+Data/test.db % SQLite database file.
+
+Models/Person.php % Person class file.
+
+Tests/PersonTest.php % Unit test case for Person mapping.
+
+run_tests.php % Unit test entry point.
+sqlmap.config % SQLMap configuration file.
+\end{verbatim}
+
+Run the tests by pointing your browser URL to the ``run\_tests.php'' server
+file.
+
+\begin{figure}[!h]
+ \centering
+ \includegraphics[width=0.7\textwidth]{example1}
+ \caption{Green Bar!}
+ \label{fig:diagram}
+\end{figure}
diff --git a/docs/sqlmap/latex/tut2.tex b/docs/sqlmap/latex/tut2.tex new file mode 100644 index 00000000..b5389138 --- /dev/null +++ b/docs/sqlmap/latex/tut2.tex @@ -0,0 +1,128 @@ +\section{Playtest second!}
+Now that we have a passing test, we want to display some results as web pages.
+The following examples utilize the Prado framework to display and manipulate
+the database through SQLMap. Since SQLMap framework and Prado framework solve
+different problems, they are both fairly independent, they can be used
+together or separately.
+
+\subsection{SQLMap and Prado}
+To setup Prado, we need to create the follow files and directory structure
+under our \tt{example/WebView} directory.
+\begin{verbatim}
+assets/ % application public assets
+
+protected/pages/Home.page % default page
+protected/pages/Home.php % default page class
+protected/runtime/ % run time data
+
+protected/application.xml % application configuration
+
+index.php % application entry point
+\end{verbatim}
+
+The \tt{application.xml} and \tt{assets} directory are not necessary but we
+will make use of them later. The \tt{application.xml} is used to define some
+directory aliases and override the data source definitions in the
+\tt{sqlmap.config}. This is because SQLite database files are defined
+relatively, otherwise we don't need to override the data source definitions.
+The example \tt{application.xml} is show in Example~\ref{example:2.0}.
+
+\begin{example}\label{example:2.0}
+Prado application.xml, defines path aliases and override SQLite database
+location.
+\begin{verbatim}
+<?xml version="1.0" encoding="utf-8"?>
+<application id="SQLMap Example" Mode="Debug">
+ <paths>
+ <alias id="Example" path="../../" />
+ <using namespace="System.DataAccess.*" />
+ </paths>
+ <modules>
+ <module id="SQLMap" class="TSQLMap"
+ configFile="Example.sqlmap">
+ <!-- override sqlmap.config's database provider -->
+ <provider class="TAdodbProvider">
+ <datasource driver="sqlite" host="../Data/test.db" />
+ </provider>
+ </module>
+ </modules>
+</application>
+\end{verbatim}
+\end{example}
+
+The entry point to a Prado application in this example is \tt{index.php}.
+Example~\ref{example:2.1} shows the basic \tt{index.php} content.
+\begin{example}\label{example:2.1}
+Prado application entry point, \tt{index.php}.
+\begin{verbatim}
+<?php
+error_reporting(E_ALL);
+require_once('/path/to/prado/framework/prado.php');
+$application=new TApplication;
+$application->run();
+?>
+\end{verbatim}
+\end{example}
+
+Now we are ready to setup a page to display our list of people.
+Example~\ref{example:7} shows the Prado code for our display page. The key
+piece is the TDataGrid.
+
+\begin{example}\label{example:7}
+Prado page for our Person list, \tt{Home.page}.
+\begin{verbatim}
+<!doctype html public "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
+<head>
+ <title>Person</title>
+</head>
+<body>
+<com:TForm>
+ <h1>Person List</h1>
+ <com:TDataGrid id="personList">
+ <com:TBoundColumn DataField="BirthDate"
+ HeaderText="Birth Date"/>
+ </com:TDataGrid>
+</com:TForm>
+</body>
+</html>
+\end{verbatim}
+\end{example}
+
+Of course, we still need to populate that TDataGrid. Example~\ref{example:8}
+shows the PHP code for \tt{Home.php}. The operative method is \tt{loadData()}.
+The rest is supporting code.
+
+\begin{example}\label{example:8}
+\tt{Home.php} class for our Person list page
+\begin{verbatim}
+<?php
+Prado::using('Example.Models.Person');
+class Home extends TPage
+{
+ private function loadData()
+ {
+ $sqlmap = $this->Application->getModule('SQLMap')->getClient();
+ $this->personList->DataSource = $sqlmap->queryForList('SelectAll');
+ $this->personList->dataBind();
+ }
+
+ public function onLoad($param)
+ {
+ if(!$this->IsPostBack)
+ $this->loadData();
+ }
+}
+?>
+\end{verbatim}
+\end{example}
+
+If we run this now, we'll get a list like the one shown in
+Figure~\ref{figure:2}.
+\begin{figure}[!h]
+ \centering
+ \includegraphics[width=0.75\textwidth]{grid1}
+ \caption{A quick-and-dirty Person List}
+ \label{figure:2}
+\end{figure}
diff --git a/docs/sqlmap/latex/tut3.tex b/docs/sqlmap/latex/tut3.tex new file mode 100644 index 00000000..4e16ab28 --- /dev/null +++ b/docs/sqlmap/latex/tut3.tex @@ -0,0 +1,227 @@ +\section{Test, test, again ...}
+Of course, tweaking the Person List display is not going to be the end of it.
+Clients always want more, and now ours wants to edit, add, or delete records.
+Let's write some tests for these new tasks, as shown in
+Example~\ref{example:9}.
+
+\begin{example}\label{example:9}
+New stories, new tests
+\begin{verbatim}
+ function testPersonUpdate()
+ {
+ $expect = "wei";
+ $edited = "Nah";
+
+ //get it;
+ $person = TMapper::instance()->queryForObject("Select", 1);
+
+ //test it
+ $this->assertNotNull($person);
+ $this->assertEqual($expect, $person->FirstName);
+
+ //change it
+ $person->FirstName = $edited;
+ TMapper::instance()->update("Update", $person);
+
+ //get it again
+ $person = TMapper::instance()->queryForObject("Select", 1);
+
+ //test it
+ $this->assertEqual($edited, $person->FirstName);
+
+ //change it back
+ $person->FirstName = $expect;
+ TMapper::instance()->update("Update", $person);
+ }
+
+ function testPersonDelete()
+ {
+ //insert it
+ $person = new Person;
+ $person->ID = -1;
+ TMapper::instance()->insert("Insert", $person);
+
+ //delte it
+ $count = TMapper::instance()->delete("Delete", -1);
+ $this->assertEqual(1, $count);
+ }
+\end{verbatim}
+\end{example}
+
+Not the best tests ever written, but for now, they will do :)
+
+To make the new tests work, we'll need some new mapping statements.
+Example~\ref{example:10} shows the complete mapper document that we've called
+\tt{personHelper.xml}.
+
+\begin{example}
+The new and improved mapper document
+\begin{verbatim}
+<?xml version="1.0" encoding="utf-8" ?>
+
+<sqlMap Name="PersonHelper">
+ <select id="Select" parameterClass="int" resultClass="Person">
+ select
+ PER_ID as ID,
+ PER_FIRST_NAME as FirstName,
+ PER_LAST_NAME as LastName,
+ PER_BIRTH_DATE as BirthDate,
+ PER_WEIGHT_KG as WeightInKilograms,
+ PER_HEIGHT_M as HeightInMeters
+ from PERSON
+ WHERE
+ PER_ID = #value#
+ </select>
+
+ <insert id="Insert" parameterClass="Person">
+ insert into PERSON
+ (PER_ID, PER_FIRST_NAME, PER_LAST_NAME,
+ PER_BIRTH_DATE, PER_WEIGHT_KG, PER_HEIGHT_M)
+ values
+ (#ID#, #FirstName#, #LastName#,
+ #BirthDate#, #WeightInKilograms#, #HeightInMeters#)
+ </insert>
+
+ <update id="Update" parameterClass="Person">
+ update PERSON set
+ PER_FIRST_NAME = #FirstName#,
+ PER_LAST_NAME = #LastName#,
+ PER_BIRTH_DATE = #BirthDate#,
+ PER_WEIGHT_KG = #WeightInKilograms#,
+ PER_HEIGHT_M = #HeightInMeters#
+ where PER_ID = #ID#
+ </update>
+
+ <delete id="Delete" parameterClass="int">
+ delete from PERSON
+ where PER_ID = #value#
+ </delete>
+</sqlMap>
+\end{verbatim}
+\end{example}
+Well, waddya know, if run our tests now, we are favored with a green bar!. It
+all works!
+
+\begin{mybox}{Note:}
+Though, of course, things usually do not work perfectly the first time! We
+have to fix this and that, and try, try, again. But SimpleTest makes trying
+again quick and easy. You can changes to the XML mapping documents and rerun
+the tests! No muss, no fuss.
+\end{mybox}
+
+Turning back to our Prado page, we can revamp the TDataGrid to allow in-place
+editing and deleting. To add records, we provide a button after the grid that
+inserts a blank person for client to edit. The page code is shown as
+Example~\ref{example:11}.
+\begin{example}\label{example:11}
+Prado page code for our enhanced TDataGrid
+\begin{verbatim}
+ <com:TDataGrid id="personList"
+ DataKeyField="ID"
+ AutoGenerateColumns="False"
+ OnEditCommand="editPerson"
+ OnUpdateCommand="updatePerson"
+ OnCancelCommand="refreshList"
+ OnDeleteCommand="deletePerson">
+ <com:TBoundColumn DataField="FirstName" HeaderText="First Name" />
+ <com:TBoundColumn DataField="LastName" HeaderText="Last Name" />
+ <com:TBoundColumn DataField="HeightInMeters" HeaderText="Height" />
+ <com:TBoundColumn DataField="WeightInKilograms" HeaderText="Weight" />
+ <com:TEditCommandColumn
+ HeaderText="Edit"
+ UpdateText="Save" />
+ <com:TButtonColumn
+ HeaderText="Delete"
+ Text="Delete"
+ CommandName="delete"/>
+ </com:TDataGrid>
+ <com:TButton Text="Add" OnClick="addNewPerson" />
+\end{verbatim}
+\end{example}
+
+Example~\ref{example:12} shows the corresponding methods from page PHP class.
+
+\begin{example}\label{example:12}
+The page class code for our enhanced TDataGrid
+\begin{verbatim}
+ private function sqlmap()
+ {
+ return $this->Application->getModule('SQLMap')->getClient();
+ }
+
+ private function loadData()
+ {
+ $this->personList->DataSource =
+ $this->sqlmap()->queryForList('SelectAll');
+ $this->personList->dataBind();
+ }
+
+ public function onLoad($param)
+ {
+ if(!$this->IsPostBack)
+ $this->loadData();
+ }
+
+ protected function editPerson($sender,$param)
+ {
+ $this->personList->EditItemIndex=$param->Item->ItemIndex;
+ $this->loadData();
+ }
+
+ protected function deletePerson($sender, $param)
+ {
+ $id = $this->getKey($sender, $param);
+ $this->sqlmap()->update("Delete", $id);
+ $this->loadData();
+ }
+
+ protected function updatePerson($sender, $param)
+ {
+ $person = new Person();
+ $person->FirstName = $this->getText($param, 0);
+ $person->LastName = $this->getText($param, 1);
+ $person->HeightInMeters = $this->getText($param, 2);
+ $person->WeightInKilograms = $this->getText($param, 3);
+ $person->ID = $this->getKey($sender, $param);
+ $this->sqlmap()->update("Update", $person);
+ $this->refreshList($sender, $param);
+ }
+
+ protected function addNewPerson($sender, $param)
+ {
+ $person = new Person;
+ $person->FirstName = "-- New Person --";
+ $this->sqlmap()->insert("Insert", $person);
+ $this->loadData();;
+ }
+
+ protected function refreshList($sender, $param)
+ {
+ $this->personList->EditItemIndex=-1;
+ $this->loadData();
+ }
+
+ private function getText($param, $index)
+ {
+ $item = $param->Item;
+ return $item->Cells[$index]->Controls[0]->Text;
+ }
+
+ private function getKey($sender, $param)
+ {
+ return $sender->DataKeys[$param->Item->DataSourceIndex];
+ }
+\end{verbatim}
+\end{example}
+
+OK, we are CRUD complete! There's more we could do here. In particular, we
+should add validation methods to prevent client from entering alphabetic
+characters where only numbers can live. But, that's a different Prado
+tutorial, and this is an SQLMap DataMapper tutorial.
+
+\begin{figure}[!h]
+ \centering
+ \includegraphics[width=0.75\textwidth]{grid2}
+ \caption{Person List CRUD}
+ \label{figure:2}
+\end{figure}
diff --git a/docs/sqlmap/sqlmap.pdf b/docs/sqlmap/sqlmap.pdf new file mode 100644 index 00000000..d228a53d --- /dev/null +++ b/docs/sqlmap/sqlmap.pdf @@ -0,0 +1,10097 @@ +%PDF-1.4 +5 0 obj +<< /S /GoTo /D (section*.1) >> +endobj +8 0 obj +(Contents) +endobj +9 0 obj +<< /S /GoTo /D (chapter*.3) >> +endobj +12 0 obj +(Legal Notice) +endobj +13 0 obj +<< /S /GoTo /D (chapter*.4) >> +endobj +16 0 obj +(License) +endobj +17 0 obj +<< /S /GoTo /D (chapter.1) >> +endobj +20 0 obj +(Introduction) +endobj +21 0 obj +<< /S /GoTo /D (section.1.1) >> +endobj +24 0 obj +(Overview) +endobj +25 0 obj +<< /S /GoTo /D (section.1.2) >> +endobj +28 0 obj +(What's covered here) +endobj +29 0 obj +<< /S /GoTo /D (section.1.3) >> +endobj +32 0 obj +(Support) +endobj +33 0 obj +<< /S /GoTo /D (section.1.4) >> +endobj +36 0 obj +(Disclaimer) +endobj +37 0 obj +<< /S /GoTo /D (chapter.2) >> +endobj +40 0 obj +(The Big Picture) +endobj +41 0 obj +<< /S /GoTo /D (section.2.1) >> +endobj +44 0 obj +(Introduction) +endobj +45 0 obj +<< /S /GoTo /D (section.2.2) >> +endobj +48 0 obj +(What does it do?) +endobj +49 0 obj +<< /S /GoTo /D (section.2.3) >> +endobj +52 0 obj +(How does it work?) +endobj +53 0 obj +<< /S /GoTo /D (section.2.4) >> +endobj +56 0 obj +(Is SQLMap the best choice for my project?) +endobj +57 0 obj +<< /S /GoTo /D (chapter.3) >> +endobj +60 0 obj +(Working with Data Maps) +endobj +61 0 obj +<< /S /GoTo /D (section.3.1) >> +endobj +64 0 obj +(Introduction) +endobj +65 0 obj +<< /S /GoTo /D (section.3.2) >> +endobj +68 0 obj +(What's in a Data Map definition file, anyway?) +endobj +69 0 obj +<< /S /GoTo /D (section.3.3) >> +endobj +72 0 obj +(Mapped Statements) +endobj +73 0 obj +<< /S /GoTo /D (subsection.3.3.1) >> +endobj +76 0 obj +(Statement Types) +endobj +77 0 obj +<< /S /GoTo /D (subsection.3.3.2) >> +endobj +80 0 obj +(Stored Procedures) +endobj +81 0 obj +<< /S /GoTo /D (section.3.4) >> +endobj +84 0 obj +(The SQL) +endobj +85 0 obj +<< /S /GoTo /D (subsection.3.4.1) >> +endobj +88 0 obj +(Escaping XML symbols) +endobj +89 0 obj +<< /S /GoTo /D (subsection.3.4.2) >> +endobj +92 0 obj +(Auto-Generated Keys) +endobj +93 0 obj +<< /S /GoTo /D (subsection.3.4.3) >> +endobj +96 0 obj +(<generate> tag) +endobj +97 0 obj +<< /S /GoTo /D (section.3.5) >> +endobj +100 0 obj +(Statement-type Element Attributes) +endobj +101 0 obj +<< /S /GoTo /D (subsection.3.5.1) >> +endobj +104 0 obj +(id attribute) +endobj +105 0 obj +<< /S /GoTo /D (subsection.3.5.2) >> +endobj +108 0 obj +(parameterMap attribute) +endobj +109 0 obj +<< /S /GoTo /D (subsection.3.5.3) >> +endobj +112 0 obj +(parameterClass attribute ) +endobj +113 0 obj +<< /S /GoTo /D (subsection.3.5.4) >> +endobj +116 0 obj +(resultMap attribute) +endobj +117 0 obj +<< /S /GoTo /D (subsection.3.5.5) >> +endobj +120 0 obj +(resultClass attribute) +endobj +121 0 obj +<< /S /GoTo /D (subsection.3.5.6) >> +endobj +124 0 obj +(listClass attribute) +endobj +125 0 obj +<< /S /GoTo /D (subsection.3.5.7) >> +endobj +128 0 obj +(cacheModel attribute) +endobj +129 0 obj +<< /S /GoTo /D (subsection.3.5.8) >> +endobj +132 0 obj +(extends attribute) +endobj +133 0 obj +<< /S /GoTo /D (chapter.4) >> +endobj +136 0 obj +(Parameter Maps and Inline Parameters) +endobj +137 0 obj +<< /S /GoTo /D (section.4.1) >> +endobj +140 0 obj +(Parameter Map) +endobj +141 0 obj +<< /S /GoTo /D (subsection.4.1.1) >> +endobj +144 0 obj +(<parameterMap> attributes) +endobj +145 0 obj +<< /S /GoTo /D (section.4.2) >> +endobj +148 0 obj +(<parameter> Elements) +endobj +149 0 obj +<< /S /GoTo /D (subsection.4.2.1) >> +endobj +152 0 obj +(property attribute) +endobj +153 0 obj +<< /S /GoTo /D (subsection.4.2.2) >> +endobj +156 0 obj +(direction attribute) +endobj +157 0 obj +<< /S /GoTo /D (subsection.4.2.3) >> +endobj +160 0 obj +(column attribute) +endobj +161 0 obj +<< /S /GoTo /D (subsection.4.2.4) >> +endobj +164 0 obj +(dbType attribute) +endobj +165 0 obj +<< /S /GoTo /D (subsection.4.2.5) >> +endobj +168 0 obj +(type attribute) +endobj +169 0 obj +<< /S /GoTo /D (subsection.4.2.6) >> +endobj +172 0 obj +(nullValue attribute) +endobj +173 0 obj +<< /S /GoTo /D (subsection.4.2.7) >> +endobj +176 0 obj +(size attribute) +endobj +177 0 obj +<< /S /GoTo /D (subsection.4.2.8) >> +endobj +180 0 obj +(precision attribute) +endobj +181 0 obj +<< /S /GoTo /D (subsection.4.2.9) >> +endobj +184 0 obj +(scale attribute) +endobj +185 0 obj +<< /S /GoTo /D (subsection.4.2.10) >> +endobj +188 0 obj +(typeHandler attribute) +endobj +189 0 obj +<< /S /GoTo /D (section.4.3) >> +endobj +192 0 obj +(Inline Parameter Maps) +endobj +193 0 obj +<< /S /GoTo /D (section.4.4) >> +endobj +196 0 obj +(Standard Type Parameters) +endobj +197 0 obj +<< /S /GoTo /D (section.4.5) >> +endobj +200 0 obj +(Array Type Parameters) +endobj +201 0 obj +<< /S /GoTo /D (chapter.5) >> +endobj +204 0 obj +(Result Maps) +endobj +205 0 obj +<< /S /GoTo /D (section.5.1) >> +endobj +208 0 obj +(Extending resultMaps) +endobj +209 0 obj +<< /S /GoTo /D (section.5.2) >> +endobj +212 0 obj +(<resultMap> attributes) +endobj +213 0 obj +<< /S /GoTo /D (subsection.5.2.1) >> +endobj +216 0 obj +(id attribute) +endobj +217 0 obj +<< /S /GoTo /D (subsection.5.2.2) >> +endobj +220 0 obj +(class attribute) +endobj +221 0 obj +<< /S /GoTo /D (subsection.5.2.3) >> +endobj +224 0 obj +(extends attribute) +endobj +225 0 obj +<< /S /GoTo /D (section.5.3) >> +endobj +228 0 obj +(<result> Elements) +endobj +229 0 obj +<< /S /GoTo /D (subsection.5.3.1) >> +endobj +232 0 obj +(property attribute) +endobj +233 0 obj +<< /S /GoTo /D (subsection.5.3.2) >> +endobj +236 0 obj +(column attribute) +endobj +237 0 obj +<< /S /GoTo /D (subsection.5.3.3) >> +endobj +240 0 obj +(columnIndex attribute) +endobj +241 0 obj +<< /S /GoTo /D (subsection.5.3.4) >> +endobj +244 0 obj +(dbType attribute) +endobj +245 0 obj +<< /S /GoTo /D (subsection.5.3.5) >> +endobj +248 0 obj +(type attribute) +endobj +249 0 obj +<< /S /GoTo /D (subsection.5.3.6) >> +endobj +252 0 obj +(resultMapping attribute) +endobj +253 0 obj +<< /S /GoTo /D (subsection.5.3.7) >> +endobj +256 0 obj +(nullValue attribute) +endobj +257 0 obj +<< /S /GoTo /D (subsection.5.3.8) >> +endobj +260 0 obj +(select attribute) +endobj +261 0 obj +<< /S /GoTo /D (subsection.5.3.9) >> +endobj +264 0 obj +(lazyLoad attribute) +endobj +265 0 obj +<< /S /GoTo /D (subsection.5.3.10) >> +endobj +268 0 obj +(typeHandler attribute) +endobj +269 0 obj +<< /S /GoTo /D (section.5.4) >> +endobj +272 0 obj +(Custom Type Handlers) +endobj +273 0 obj +<< /S /GoTo /D (section.5.5) >> +endobj +276 0 obj +(Implicit Result Maps) +endobj +277 0 obj +<< /S /GoTo /D (section.5.6) >> +endobj +280 0 obj +(Primitive Results \(i.e. String, Integer, Boolean\)) +endobj +281 0 obj +<< /S /GoTo /D (section.5.7) >> +endobj +284 0 obj +(Maps with ResultMaps) +endobj +285 0 obj +<< /S /GoTo /D (section.5.8) >> +endobj +288 0 obj +(Complex Properties) +endobj +289 0 obj +<< /S /GoTo /D (section.5.9) >> +endobj +292 0 obj +(Avoiding N+1 Selects \(1:1\)) +endobj +293 0 obj +<< /S /GoTo /D (section.5.10) >> +endobj +296 0 obj +(Complex Collection Properties) +endobj +297 0 obj +<< /S /GoTo /D (section.5.11) >> +endobj +300 0 obj +(Avoiding N+1 Select Lists \(1:M and M:N\)) +endobj +301 0 obj +<< /S /GoTo /D (subsection.5.11.1) >> +endobj +304 0 obj +(1:N \046 M:N Solution?) +endobj +305 0 obj +<< /S /GoTo /D (section.5.12) >> +endobj +308 0 obj +(Composite Keys or Multiple Complex Parameters Properties) +endobj +309 0 obj +<< /S /GoTo /D (chapter.6) >> +endobj +312 0 obj +(Cache Models) +endobj +313 0 obj +<< /S /GoTo /D (section.6.1) >> +endobj +316 0 obj +(Cache Implementation) +endobj +317 0 obj +<< /S /GoTo /D (subsection.6.1.1) >> +endobj +320 0 obj +(Least Recently Used [LRU] Cache) +endobj +321 0 obj +<< /S /GoTo /D (subsection.6.1.2) >> +endobj +324 0 obj +(FIFO Cache) +endobj +325 0 obj +<< /S /GoTo /D (chapter.7) >> +endobj +328 0 obj +(Dynamic SQL) +endobj +329 0 obj +<< /S /GoTo /D (chapter.8) >> +endobj +332 0 obj +(Installation and Setup) +endobj +333 0 obj +<< /S /GoTo /D (section.8.1) >> +endobj +336 0 obj +(Introduction) +endobj +337 0 obj +<< /S /GoTo /D (section.8.2) >> +endobj +340 0 obj +(Installing the DataMapper for PHP) +endobj +341 0 obj +<< /S /GoTo /D (subsection.8.2.1) >> +endobj +344 0 obj +(Setup the Distribution) +endobj +345 0 obj +<< /S /GoTo /D (subsection.8.2.2) >> +endobj +348 0 obj +(Add XML file items) +endobj +349 0 obj +<< /S /GoTo /D (section.8.3) >> +endobj +352 0 obj +(Configuring the DataMapper for PHP) +endobj +353 0 obj +<< /S /GoTo /D (subsection.8.3.1) >> +endobj +356 0 obj +(DataMapper clients) +endobj +357 0 obj +<< /S /GoTo /D (section.8.4) >> +endobj +360 0 obj +(DataMapper Configuration File \(SqlMap.config\)) +endobj +361 0 obj +<< /S /GoTo /D (section.8.5) >> +endobj +364 0 obj +(DataMapper Configuration Elements) +endobj +365 0 obj +<< /S /GoTo /D (subsection.8.5.1) >> +endobj +368 0 obj +(<properties> attributes) +endobj +369 0 obj +<< /S /GoTo /D (subsection.8.5.2) >> +endobj +372 0 obj +(<property> element and attributes) +endobj +373 0 obj +<< /S /GoTo /D (subsection.8.5.3) >> +endobj +376 0 obj +(The <typeHandler> Element) +endobj +377 0 obj +<< /S /GoTo /D (subsection.8.5.4) >> +endobj +380 0 obj +(The <provider> element and attribute) +endobj +381 0 obj +<< /S /GoTo /D (subsection.8.5.5) >> +endobj +384 0 obj +(The <datasource> element and attributes) +endobj +385 0 obj +<< /S /GoTo /D (subsection.8.5.6) >> +endobj +388 0 obj +(The <sqlMap> Element) +endobj +389 0 obj +<< /S /GoTo /D (chapter.9) >> +endobj +392 0 obj +(Using SQLMap PHP DataMapper) +endobj +393 0 obj +<< /S /GoTo /D (section.9.1) >> +endobj +396 0 obj +(Building a TSqlMapper instance) +endobj +397 0 obj +<< /S /GoTo /D (subsection.9.1.1) >> +endobj +400 0 obj +(Multiple Databases) +endobj +401 0 obj +<< /S /GoTo /D (subsection.9.1.2) >> +endobj +404 0 obj +(TDomSqlMapBuilder Configuration Options) +endobj +405 0 obj +<< /S /GoTo /D (section.9.2) >> +endobj +408 0 obj +(Exploring the SQLMap PHP DataMapper API through the TSqlMapper) +endobj +409 0 obj +<< /S /GoTo /D (subsection.9.2.1) >> +endobj +412 0 obj +(Insert, Update, Delete) +endobj +413 0 obj +<< /S /GoTo /D (subsection.9.2.2) >> +endobj +416 0 obj +(QueryForObject) +endobj +417 0 obj +<< /S /GoTo /D (subsection.9.2.3) >> +endobj +420 0 obj +(QueryForList) +endobj +421 0 obj +<< /S /GoTo /D (subsection.9.2.4) >> +endobj +424 0 obj +(QueryForPagedList) +endobj +425 0 obj +<< /S /GoTo /D (subsection.9.2.5) >> +endobj +428 0 obj +(QueryForMap) +endobj +429 0 obj +<< /S /GoTo /D (subsection.9.2.6) >> +endobj +432 0 obj +(Transaction) +endobj +433 0 obj +<< /S /GoTo /D (section.9.3) >> +endobj +436 0 obj +(Coding Examples) +endobj +437 0 obj +<< /S /GoTo /D [438 0 R /FitH ] >> +endobj +440 0 obj << +/Length 374 +/Filter /FlateDecode +>> +stream +xÚRÛJÃ@}ïWìcî¸3{Éîc½T…ªÁËChclmHkÅ¿wÒµh „aæÌÉ9g‚Bñƒ" Ô"S”%1š÷”˜ð䬇Žë=C©¸`@íwA¿Àh= òBš@дࣼw8ÈPÌg"è4¨ ¾0ùø!¹½¾¼*êT’UÉð|‹“bUp·.›ô)¿h +endobj +438 0 obj << +/Type /Page +/Contents 440 0 R +/Resources 439 0 R +/MediaBox [0 0 612 792] +/Parent 449 0 R +>> endobj +441 0 obj << +/D [438 0 R /XYZ 99.213 706.052 null] +>> endobj +442 0 obj << +/D [438 0 R /XYZ 99.213 688.052 null] +>> endobj +439 0 obj << +/Font << /F71 445 0 R /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +452 0 obj << +/Length 1098 +/Filter /FlateDecode +>> +stream +xÚí™MsÛ6†ïþ¼•<Å÷Ç©3iÚ&™¤iÍäÐéA‘‹m,z$ÚNþ}@AبJÓ63DKïì»»xH,$R`ûG +c%¬PX",h±¾¾ÀÅ•ýä§âœ +DŒ´×3.åˆcS,¢–ßþ¨Ha?RZË·ÁEh‚Œ(–—¿—ß·Û®Þvûê峋–Ç€A‰¢œ9ËþýÝ•ü™{ñ"RoƒŒd±µ—@Þ„)ÄŒ›'õ
è)(A’<›L¡\QÄl“N+4Rç +
’Þôy}µzW-¨ÀåÏm׬ëIÅ‚"Iè8‹´b¯ÍaHk3r¿k²EÛ& +FN-zPg‹ö’CѶÎí~Z*#ˆõ¥ÆÞ“ÅuÐR‰¤0#Ïæ}®Rl›§Ä©•êl¥^Ò»’jA0ÆåÓm·«ˆ.ÛËÛu×´ÛIéR#J“dÒÒ½ÌAƒ8ÕI©áˆU0¥‘ú¥õ"’;_:ø¦!¾èP¾Eûå]EDYï¢¢¼Ÿ$¤„}êˆqBiù^§¡-ä†Ò@•}àü…ó¥Bù—¹ä“5 $Ak4àš¤f‘›äÆ FÔî_«%XŒ°¡%±õ‡-õؾެºo*!ʽ{B¯ÛcÏr}éÞÜØË´J´} +ô=
“ +¸I*±{†AõÇi˜àŸ´îË‘ª÷Áe»ûk +,±¬]¾3‡M'rgÍ(·Zó˜Ý>R +t; 2 +X‹¨â‡D@F½ÆA&ö§žÊW¿>±ºq×]ŒÞÔ{ëº"å¦í¿Ï>üû¶Ý¹‹ëîõf×þY¯» ËÛ¶pZD Nj° +Fôák¯çs@W耮¤ú®È,v€Ûyƒ7á8Pš¼ƒÄÝnð~])Ñ?ùšŸ»ï›n㮯º•»êi$åä#JíÐj}"ëéÓÑkÀüŽ‹4™ßì‘““ùé'´bÌ ‹“Qf› +k4uúÇÕ›Vendstream +endobj +451 0 obj << +/Type /Page +/Contents 452 0 R +/Resources 450 0 R +/MediaBox [0 0 612 792] +/Parent 449 0 R +/Annots [ 454 0 R 455 0 R 456 0 R 457 0 R 458 0 R 459 0 R 460 0 R 461 0 R 462 0 R 463 0 R 464 0 R 465 0 R 466 0 R 467 0 R ] +>> endobj +454 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [98.216 506.128 138.395 515.104] +/Subtype /Link +/A << /S /GoTo /D (section*.1) >> +>> endobj +455 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [98.216 469.369 153.608 480.248] +/Subtype /Link +/A << /S /GoTo /D (chapter*.3) >> +>> endobj +456 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [98.216 436.415 132.308 445.392] +/Subtype /Link +/A << /S /GoTo /D (chapter*.4) >> +>> endobj +457 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [98.216 401.559 169.219 410.536] +/Subtype /Link +/A << /S /GoTo /D (chapter.1) >> +>> endobj +458 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 377.472 176.951 386.319] +/Subtype /Link +/A << /S /GoTo /D (section.1.1) >> +>> endobj +459 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 353.365 219.65 362.212] +/Subtype /Link +/A << /S /GoTo /D (section.1.2) >> +>> endobj +460 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 327.201 169.618 338.105] +/Subtype /Link +/A << /S /GoTo /D (section.1.3) >> +>> endobj +461 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 305.151 181.782 313.998] +/Subtype /Link +/A << /S /GoTo /D (section.1.4) >> +>> endobj +462 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [98.216 268.372 181.942 279.252] +/Subtype /Link +/A << /S /GoTo /D (chapter.2) >> +>> endobj +463 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 246.188 187.322 255.035] +/Subtype /Link +/A << /S /GoTo /D (section.2.1) >> +>> endobj +464 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 222.081 205.304 230.928] +/Subtype /Link +/A << /S /GoTo /D (section.2.2) >> +>> endobj +465 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 197.974 213.254 206.821] +/Subtype /Link +/A << /S /GoTo /D (section.2.3) >> +>> endobj +466 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 171.81 310.439 182.714] +/Subtype /Link +/A << /S /GoTo /D (section.2.4) >> +>> endobj +467 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [98.216 137.088 223.167 147.968] +/Subtype /Link +/A << /S /GoTo /D (chapter.3) >> +>> endobj +6 0 obj << +/D [451 0 R /XYZ 99.213 688.052 null] +>> endobj +453 0 obj << +/D [451 0 R /XYZ 99.213 541.802 null] +>> endobj +450 0 obj << +/Font << /F71 445 0 R /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +470 0 obj << +/Length 1854 +/Filter /FlateDecode +>> +stream +xÚí›moÛ6ÇßçSèÝl`fù,(:t[[lkn3°Û^¸±–pìÀV°åÛï$‘-Š'W@4ú"Ný÷ÝñüÓñø–QøÇ2k g"Ë©&TñìúöŠf7ðΛ+æ’+¬†×o.$£„Ã<'V;Õ;‡'ù%P3& Sy¶Ð¹!ÔVêo—WÏ^ç<³ðq‘-ÿμÆI–ë?f‚°ù‚QEg?ìÊÃ~}]nö»ù_˯^-[ëÌh"¸Ê:ÛQ´^‚ú·ŒBÿd¾P”~æÒÚÿÇÑgøÑû&³Drƒ}^‚}=3Õ7a#W\ o"®:D±&?Q/¨ÀušRî ýíãªüj®Ôì8_pø}³k~®šß¯J÷êÝê^H;[RÊw›ëúê÷mñµûànÎÔìáŸ9ƒ×ßôs 8%J±,ˆ8J‚× Ã‚GƒeO†õ@ÑHÈM‡× éèªÒÁh’@ÄÝT%ŒiA§iA ë®X7ôüZ®Êâ¶Ø•Ç~èœ)b…Ì?Qè^ƒÃ9ƒñ‰“`žª%‚&’ðM$á}C5š<‰&âN˜œaÎESä„æU†ñÄêD^ãЬ'qN >—sCgwEŒ§ÄH‘¾b<
ˆKA´¶']—)‘Üv$"¹íªIIwIT†&9J¢×xyKâþàËäûÃþºXß8”Œä,ÏO1‡Nƒ†Ãƒ÷N¢¹˜Ö2Á–Ó–?,§}C5*Åænâ$” +¤½¦áOºIzù±p3ôÏo£ÅL.ˆSõx5ã4x¹†&šŸ„ð45RÔ)t‘¯¦Cùjú†FÐEÜM-0y2mðÒé4Ývu¼^Ýmv7
Á¿¿{Û¼8>Ü~˜³Ù~WQ
‹šgÓ¸Š:
ì•Ò'‘=F¢SØ!ùë°Cò×74‚ân*vPh™a8vNã±ó3öËûr¿xSìŠônæþiÎÕ¬¨—È1uJmUøŒ©s40Ÿfü$°Ç?y§D²Ù1ˆd³oh„AÄÝD¥FÔJ”A¯ñÖ]í³×F;&Œ¦+Éó‡å‹FšÔœp;a¹º‰[LhK-Ï‚°ZÌFƒÆîZÌ0ôKo1±œ¶°b9íªaÕ)X1w[LYáCs´ÅôšVåZÌv©½(a…Ý”ËWÛâ¦èvýý²,›sNg÷åÀâ'W„–1Äd:
(7ŒPnN}TL¦ðC×á‡$®o¨ÆÏ$ñCÜM•ÂŽ/°ÄÁM"Z)7ë¸BÖƒÔN° +‘Œw-aECEÖ…5°iÙH°ÐýžeúÓºÁ8íŽât¶{fj†“‡9ˆ¯©ƒ{.ΰÓxˆ9ñÝê°º-ÊâPâD8ç`†pΰ@g¹É‚ +l£AÇÑØ`Q`‘Äuh"‰ëq7‘Na¡-¥Ók<âL:¿Û®ŽÇ˜ÏªIÉó³øÇšË,2æÓiБp«ˆ…ÿGò…ƒ™ KUK$–ª¾!œHÌÝT"µ†É?ÞñO¤Ä‰<Çûm9X,2ö,a^
5ˆo`9ßhÐApxy2ˆË[Çciì@EÒØ7T¥‘'È1wSA•Ðîæxsê5Tu¨‰º©+¶äyó:<•´ó><¯7t~¿3ÆíwbùëEò×7T3Ê’Œ"î¦2Ê +™ ˜§?8û¹FÒß=×Húû††!lŸkÄÝĹˆ€däƒ×8PÇNFž‡»Ê/>eÛ.~Ø-D9°o×hСø}»p(sßKU‹$–ª¾¡É<…$ænâI1ƒ&˜¦®58‘×4HœrpN,“‘/FvCªsåÁ¿2€µ°*bhŽ
: +endobj +469 0 obj << +/Type /Page +/Contents 470 0 R +/Resources 468 0 R +/MediaBox [0 0 612 792] +/Parent 449 0 R +/Annots [ 472 0 R 473 0 R 474 0 R 475 0 R 476 0 R 477 0 R 478 0 R 479 0 R 480 0 R 484 0 R 485 0 R 486 0 R 487 0 R 488 0 R 489 0 R 490 0 R 491 0 R 492 0 R 493 0 R 494 0 R 495 0 R 496 0 R 497 0 R ] +>> endobj +472 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 676.874 187.322 685.841] +/Subtype /Link +/A << /S /GoTo /D (section.3.1) >> +>> endobj +473 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 650.95 321.547 661.854] +/Subtype /Link +/A << /S /GoTo /D (section.3.2) >> +>> endobj +474 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 626.963 216.93 637.867] +/Subtype /Link +/A << /S /GoTo /D (section.3.3) >> +>> endobj +475 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 602.976 235.839 613.88] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.3.1) >> +>> endobj +476 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 581.046 242.714 589.893] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.3.2) >> +>> endobj +477 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 555.375 174.869 565.906] +/Subtype /Link +/A << /S /GoTo /D (section.3.4) >> +>> endobj +478 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 531.015 266.803 541.919] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.4.1) >> +>> endobj +479 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 507.028 256.691 517.932] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.4.2) >> +>> endobj +480 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 483.041 244.388 493.945] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.4.3) >> +>> endobj +484 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 459.054 276.227 469.958] +/Subtype /Link +/A << /S /GoTo /D (section.3.5) >> +>> endobj +485 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 437.005 217.399 445.971] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.5.1) >> +>> endobj +486 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 411.703 277.175 421.984] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.5.2) >> +>> endobj +487 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 387.716 291.621 397.997] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.5.3) >> +>> endobj +488 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 363.729 259.242 374.01] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.5.4) >> +>> endobj +489 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 341.121 271.197 350.023] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.5.5) >> +>> endobj +490 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 317.134 259.242 326.036] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.5.6) >> +>> endobj +491 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 293.147 265.22 302.049] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.5.7) >> +>> endobj +492 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 269.16 247.287 278.062] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.5.8) >> +>> endobj +493 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [98.216 233.036 284.287 243.915] +/Subtype /Link +/A << /S /GoTo /D (chapter.4) >> +>> endobj +494 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 208.915 199.058 219.819] +/Subtype /Link +/A << /S /GoTo /D (section.4.1) >> +>> endobj +495 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 185.551 293.005 195.832] +/Subtype /Link +/A << /S /GoTo /D (subsection.4.1.1) >> +>> endobj +496 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 161.564 243.391 171.845] +/Subtype /Link +/A << /S /GoTo /D (section.4.2) >> +>> endobj +497 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 137.577 253.264 147.858] +/Subtype /Link +/A << /S /GoTo /D (subsection.4.2.1) >> +>> endobj +471 0 obj << +/D [469 0 R /XYZ 99.213 706.052 null] +>> endobj +468 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +500 0 obj << +/Length 1655 +/Filter /FlateDecode +>> +stream +xÚí›]‹ã6…ïçWø2¹ˆV–lC)´0Ûn¡P¶aoJ/²‰;2™ÁÉÀn}e[²Ù>JÝìP&a/&Lξg+¯e…%TÿcIQÎD’QE¨äÉúñŽ&úŸî˜Q¤\V(ýzàÍ…È3"Dž,xF +eÔïTFòÑS3‘šé*Ë -jõË»wï3žú¿‹dùWb5F²Üü1K '|þçò—wïs锂‘<×kÉf[•ëãöißÊü€RžF·:«íç9§³—cYkïî—]}\愧*qÕõúµÔWÚÌ<õ; ó…¤ô[ýH‹âg +Lèúi÷ò8€§È‰ê,<SJ„¾X½âú|
쀧‚(UœtðÑœ‚-ðÖq¼
5äæ£ä‚tSÑ”0¦0ºFcÑM#‹ëçå×çò"èºâ +¶XƒõÇ/Bˆ^:s )IQœ{a:õèui$Më„™Ëòþ˱Üo¶û‡þ¼ö‘\Õx4äNý¬â¤ÜÇqµiï +ÜEpé&âÊr=4GGXÅ•¿Ö—,¼âÆOª£ìˆëwpqâ·ºÈÛ0F¥›Š®ÒSsät„ÕXtÏú~Їý¦ür‰Ã^…ã‡#Pöp„ßÆŽ@þ9Fa †Q5Ê(H7•Q½J±±Ã–Q£±Œ¾Ú¼âÀò +:è–W¯ƒÛòÚ¼uèoÃ@tAºî‘ TDÃ{½öA`®ñcÃ×’ QúÌHš}Ùív¬ Èæú0h+Iendstream +endobj +499 0 obj << +/Type /Page +/Contents 500 0 R +/Resources 498 0 R +/MediaBox [0 0 612 792] +/Parent 449 0 R +/Annots [ 502 0 R 503 0 R 504 0 R 505 0 R 506 0 R 507 0 R 508 0 R 509 0 R 510 0 R 511 0 R 512 0 R 513 0 R 514 0 R 515 0 R 516 0 R 517 0 R 518 0 R 519 0 R 520 0 R 521 0 R 522 0 R 523 0 R 524 0 R ] +>> endobj +502 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 676.939 259.242 685.841] +/Subtype /Link +/A << /S /GoTo /D (subsection.4.2.2) >> +>> endobj +503 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 652.952 241.309 661.854] +/Subtype /Link +/A << /S /GoTo /D (subsection.4.2.3) >> +>> endobj +504 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 627.585 241.309 637.867] +/Subtype /Link +/A << /S /GoTo /D (subsection.4.2.4) >> +>> endobj +505 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 603.598 229.354 613.88] +/Subtype /Link +/A << /S /GoTo /D (subsection.4.2.5) >> +>> endobj +506 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 580.991 259.242 589.893] +/Subtype /Link +/A << /S /GoTo /D (subsection.4.2.6) >> +>> endobj +507 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 556.939 229.354 565.906] +/Subtype /Link +/A << /S /GoTo /D (subsection.4.2.7) >> +>> endobj +508 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 531.637 259.242 541.919] +/Subtype /Link +/A << /S /GoTo /D (subsection.4.2.8) >> +>> endobj +509 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 508.965 235.332 517.932] +/Subtype /Link +/A << /S /GoTo /D (subsection.4.2.9) >> +>> endobj +510 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 483.663 271.197 493.945] +/Subtype /Link +/A << /S /GoTo /D (subsection.4.2.10) >> +>> endobj +511 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 459.054 228.666 469.958] +/Subtype /Link +/A << /S /GoTo /D (section.4.3) >> +>> endobj +512 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 435.067 242.255 445.971] +/Subtype /Link +/A << /S /GoTo /D (section.4.4) >> +>> endobj +513 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 411.08 230.071 421.984] +/Subtype /Link +/A << /S /GoTo /D (section.4.5) >> +>> endobj +514 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [98.216 376.958 168.562 387.837] +/Subtype /Link +/A << /S /GoTo /D (chapter.5) >> +>> endobj +515 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 352.837 241.29 363.741] +/Subtype /Link +/A << /S /GoTo /D (section.5.1) >> +>> endobj +516 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 329.472 243.192 339.754] +/Subtype /Link +/A << /S /GoTo /D (section.5.2) >> +>> endobj +517 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 306.865 217.399 315.767] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.2.1) >> +>> endobj +518 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 282.878 235.332 291.78] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.2.2) >> +>> endobj +519 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 258.891 247.287 267.793] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.2.3) >> +>> endobj +520 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 234.904 225.459 243.806] +/Subtype /Link +/A << /S /GoTo /D (section.5.3) >> +>> endobj +521 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 209.538 253.264 219.819] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.3.1) >> +>> endobj +522 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 186.93 241.309 195.832] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.3.2) >> +>> endobj +523 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 162.943 271.197 171.845] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.3.3) >> +>> endobj +524 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 137.577 241.309 147.858] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.3.4) >> +>> endobj +501 0 obj << +/D [499 0 R /XYZ 99.213 706.052 null] +>> endobj +498 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +527 0 obj << +/Length 1876 +/Filter /FlateDecode +>> +stream +xÚíš]sÛD†ïû+tÅØÙî÷®rÃÐB ”Ҧܔ^ˆDÍ(vÆ–ðë9+íZ«¯ciKI2½ˆ]¿Þsöõ£³»Gb …,IS™HÕ„*ž\Þ<¡É5|òíæ’+ÂR
¯G><Ö!lrÄ
IµÀ}²ºö’W‘š C¨ÚXBS§~vñäé‰áI +_ÉÅoIÐxÉÅÕÛ™"‚¨ù»‹ïŸžXÕ*#ÖÂÀNRÝÝæ"‹k")󒬪VůsNg›ªÖ>ùæb›ç–e“6±ÁTƒKž
f™8y2?R”¾÷?2M?ÔÐïåOÏ_Å$‘Ò`þ æooç¯ÐƒXDð)«ÅV‚dl• ijqn½&€«qpWùzSVçÙím±¸l)ð”îE°å„)D9öt"ÜZ¢:Ó¸O‰øÕ"‰øÕh”H¸.•lo*9äB
N¥×*
NåbS–?gåf¤¦*M¸ÝHe —:‰òé5è$¸†ÕÉÊÎ$>,1ÿiu±±±±?P
ª™ whù¤ŠX¦pP½&€jqP×y™_VCJ…[«õ^”JJl¢ä†”z
:.¡Øë´3ƒ‡¶øO¡‹xÛ¢‹xÛhºH¸k¬²Œ.Qtƒ& ›âè–ÙßwgËìj¤Ä:ÚöÛ¶*N¸¶I”ÞH‰m4è\ÖŒwæðQ¸ù+-ææWÌÍþ@5®v +W,Ü•V)C´à8®^pet÷ë»lqUæ«!²Ú-ãr/d
%œ¶áG‘õtÜÀ©j|<ûÈ꤈-¤ˆýv@Š„“~.Å÷©—)—œ€P²qH½(hHåüˆQEgÏ7ëjy3‡ÒFgsKgîô_¿ó€®ÇÎ÷:ÕIsô€ï4hbá„'öxÄXÜ‚‰XÜh˜H¸.˜|o0i + ¤8˜^Ó€©<˜§7·eqYT
Œ¯ê³}óNøs6á’A¥“Ir„ËFƒæåà5\uòºÇ+÷¿ÀqºÅqº?sZ²I<‘pÖMiU݈ÄðšOíñ|¹*nŠª˜s5ûcÎÔ,9]7o~a¨’œŒÎ^Ãb¾¸þ¢ùôt+:|ó6’Îüÿ>[.Ë<[ÀW>×>6"‰’nr¼™€ë\Ö™Ù§ø~˜[ü0úÕøñ)ü°pVG©¡iãç5
~Æã5ÐCögQý³wžq·#Õ1%ŠŠ$ +9R
šWXµã¼Ëb‡KÄâ–KÄâþ@;¸DÂZá\̹ôš†K¶“KX¶ë²öWåËÕò6_UE>$’i"¤H¢`C"½Í¨^Ô•èdô°×ë|"†·|"†÷ªù“|"á›LjΧ×4|¦žÏ¯æF‚
çéeqånÕ¾øœ5/^×=PWYeZ/ßì˜-Å\j¢yšD‰Œt:
š-WŒ0ÙIöþ÷Švà‰xÚâ‰xÚ¨ÆSN≄;°| +kIšrÏ iðdL+ŸÏ—¥#²X.|9…Å}º¢j +Õ²{”J¯A“w‰â$^ï³q"fc ÄÉÛ™X¸CAÔ’¤o‘5 îU'›×g€d±ŽÏ<ìø¼y“-®ü‘ýøÅ艆B‰„k/Êsx¢ñt2p¸")SÉüïê膈9-†ˆ9ýj'›@èoqX]HJìŽg”‚&`H€4¶°ã
BŸmIò.ËM]ÙìËá½8£‘DGîå44»m!Œ²{€…±±%±±?PM`:I îÐBçÃð>OÐxy»"/×Eåû;?¸†O½@ßù‚·\y07@bYnýöKwÿ(ä^g«ì&¯\[}ÇñHKŒeI”úðzôt~Â*¢…ìÌï#2<Å2–)dZýv0…„eÂĘtSþ1Ï#nIEÄ;ksóš q©Õ}C0äyvù»ä|y•—ƒßYß°‹Æd4hÁ 858¥ú¹2ÄJ³ïÉ'/¨ ©§ïêyÓ!h
p-þü&_TYåÚ¤PËM+C”êæ6èYÕ4¡Ð²ŠzlYÅW#æðöjÄ,îTÃ6yæÂÂøT ‚¥Ñ=FÐx k$9Ør–gëí}¦Kà±¼kÞ½YCýžù=ìÛ³W®Oÿæ/ï5ǃ
’(™‘ýl-AÛÙ8áû²Å¼iQCÌé´5ì§8l;ËiJ´Æ` +š€÷¨œžüˆðÙ!)7Ib¤'ÚhÐ<BO4Îã±'ŠP‰ÞR‰Þ¨¦RMR‰„ënGìžÛ·%°VcÛ‘ q©¿ùún‘Ý—þHõÓÙØnÄè4‰†Ý8
šCð'NB
;™Â €Q³ïÔ[õäÔ½ÄEµ~꧋u••eÖ6׶íŠ×yµ¹\›Ô?y'¹þµé5hN[+¢¤T:a…D²qPÂü[Ép!|¨ƒ¸@þŽõ—Ãhÿ +endobj +526 0 obj << +/Type /Page +/Contents 527 0 R +/Resources 525 0 R +/MediaBox [0 0 612 792] +/Parent 449 0 R +/Annots [ 529 0 R 530 0 R 531 0 R 532 0 R 533 0 R 534 0 R 535 0 R 536 0 R 537 0 R 538 0 R 539 0 R 540 0 R 541 0 R 542 0 R 543 0 R 544 0 R 545 0 R 546 0 R 547 0 R 548 0 R 549 0 R 550 0 R ] +>> endobj +529 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 675.559 229.354 685.841] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.3.5) >> +>> endobj +530 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 651.468 283.152 661.749] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.3.6) >> +>> endobj +531 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 628.756 259.242 637.658] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.3.7) >> +>> endobj +532 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 604.665 241.309 613.566] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.3.8) >> +>> endobj +533 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 579.193 253.264 589.475] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.3.9) >> +>> endobj +534 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 555.102 271.197 565.383] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.3.10) >> +>> endobj +535 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 530.388 229.693 541.292] +/Subtype /Link +/A << /S /GoTo /D (section.5.4) >> +>> endobj +536 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 506.296 222.2 517.2] +/Subtype /Link +/A << /S /GoTo /D (section.5.5) >> +>> endobj +537 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 482.205 326.169 493.109] +/Subtype /Link +/A << /S /GoTo /D (section.5.6) >> +>> endobj +538 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 458.113 230.499 469.017] +/Subtype /Link +/A << /S /GoTo /D (section.5.7) >> +>> endobj +539 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 434.022 217.339 444.926] +/Subtype /Link +/A << /S /GoTo /D (section.5.8) >> +>> endobj +540 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 409.93 247.625 420.834] +/Subtype /Link +/A << /S /GoTo /D (section.5.9) >> +>> endobj +541 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 385.839 261.343 396.743] +/Subtype /Link +/A << /S /GoTo /D (section.5.10) >> +>> endobj +542 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 361.747 307.679 372.651] +/Subtype /Link +/A << /S /GoTo /D (section.5.11) >> +>> endobj +543 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 339.594 257.129 348.56] +/Subtype /Link +/A << /S /GoTo /D (subsection.5.11.1) >> +>> endobj +544 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 313.564 379.778 324.468] +/Subtype /Link +/A << /S /GoTo /D (section.5.12) >> +>> endobj +545 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [98.216 280.823 175.197 289.799] +/Subtype /Link +/A << /S /GoTo /D (chapter.6) >> +>> endobj +546 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 254.694 228.546 265.598] +/Subtype /Link +/A << /S /GoTo /D (section.6.1) >> +>> endobj +547 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 230.603 308.437 241.507] +/Subtype /Link +/A << /S /GoTo /D (subsection.6.1.1) >> +>> endobj +548 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 208.569 218.923 217.415] +/Subtype /Link +/A << /S /GoTo /D (subsection.6.1.2) >> +>> endobj +549 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [98.216 171.867 175.765 182.746] +/Subtype /Link +/A << /S /GoTo /D (chapter.7) >> +>> endobj +550 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [98.216 137.088 209.269 147.968] +/Subtype /Link +/A << /S /GoTo /D (chapter.8) >> +>> endobj +528 0 obj << +/D [526 0 R /XYZ 99.213 706.052 null] +>> endobj +525 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +553 0 obj << +/Length 1926 +/Filter /FlateDecode +>> +stream +xÚå›[oÛ6Çßó)ô˜ +x»È&2§±’Éì·SCØÙ9£Šž¾XV›Õlw]ÍW˳ß'?Ÿ<›tÖ™ÑDp•yÛƒhõ_0"´ +ý“³sEéWþ!‹â¿qô~ô> ¡”9öI8 öIôÌÔŸ„*¾8ˆàUÄ—gTÂäG3ªà +R«i)å¥ÛjºXÌ—7gç~¯>–íÃÓi5½<c§ÓõºÜ´ú°²Wϯú¹ñ\ÅuD2HÎiÐp¹a„r³î½¢9 R8O R¸¾¡"î„ɉæXENhZP˜ŽChENc!lš%‡²¼)«ÝºOà|[mæïÏ8=ÝUs@qØG¹Ò$×Þl”:«Aãã#÷â»Ç=4Å RFÏ Rƾ¡"îÆ2“ÆœA«qrËàãÙ¬ï×Ë—íÃ;JùÂÒ8¯ÊÛí?J¨ÐYà6Â_«AcãÊÍø^l÷(OaˆTÓcˆT³o¨®¦¦Iw#Çce µIŽŽÇNÓb(ìxüãjYSw³Û|ù_!*½£(œVƒFÌE +øCñÇ1AVªŽ<¬T}C8y˜»‘
PA#aJ¡
Ði,yÝ ÜðÀu½˜—ËjØôdN¸ÔYàjÈ•Õ ñpÅ ý½pÌò% RS RÓ¾¡ +˜gäuÿò¡
¿nVƒÆ/„€ï$Û‹ÿ[F5RR‡¾¡2–„q72x3‡ÌjZÈÔ?„ìÙ¢¼6?# Ͻõø ÚjÐ0¹ ö‚¼×Ë[¬^ž9¤^}C˜CÜY¥ib!Ñ‘Õi,s¤‰ïÑ…QAaÄ0]K¾_oV@b5/·?´ÊÐhqn¥Óʯ…Ë•°ÆP2bŒPÙjÐDZ*Ã4î5•X½:*±zõ
5Tò•˜»±TjXÀÒ§Òj•ü8*?G˜¬-éÜ +˶G¶
sº´«gœTÁ +B!– îa)¬MNpEŒ*ö’»ÓƈH9<ˆH9ú†€ˆU$ˆÂ@»Â9l%Ca—X߀”1ðd‰¬>¯Ëç@Ø¢ÜD „ÆÆŠÂjíÀ=àŽÒ&+æ0q+ÁRT@™E˜Ê]]²¤HLȃ˜.PÏLƒ¡Hbˆ|#)÷\C«qÊã9„Îøi>‹BØtF}|g.R`!«á ƒÈ"¥Õ i +I‰¡b/Í»´:Á +à D +Ð7t€A¬Þã m6?0ÆA¨Ž‡pK˜íj·¹.S“Fö%´4¤*’ÖÅjÐL…R$‡×ÂL¿‘^™ K¹#K¹o¨!O¦ÈC+<’<a98#OOÞ¶Ùk‰P§`ädyNIÍ•1r@ÜHÐ4x.ˆRz/û¸]˜b)Ÿg©_ßPèJ2Š¸s·m €F`ÔÞÞ1ŒÃä>èÜÌÇf5NR‡VÔû6P·Ûî<äÍë—@¡?öhÏFês‘nk§ž¦-3Þ÷=«AìŠD¨óÁí»%¨!”éc7µyrSËišÒtWžìæ‹YWŸéðk¬sø¤Ü²mÒn™Ú:!ë»y}7dy]Æîzpmöó‹Üõ¨5hVî®G˜ÕC¸ëΉ‘Âõ
Å9ì¾Äˆ»‘M½ÅIœ³;eµ;fºÜ-ªùzj¾Ÿn#{ZRòYà)rÊÔHÐhì!SÌC?dÂJÚá‡Õ´o¨Á/yÍs7?hÖTâ»
Nãð;°ë5yººmcÓMcÍ‘QhÂn–øSƒWëúy8§†¶*•Ê‚°‡•°47$]ì¥ö¿Qœ‚ÉÑÃ…äØ7t +‡¦…Ë]¦|ö×z±Š\Üèf,²f,½“¨ÇW/ÜÛ6«ÝÍGocÀ-ÌÈ`•§£ãyX$Yï(“
‹d5hÚ¾S…ØOûßoKH”ž$ʾ¡ä îƶ%ÓÂÔÙ·kKVcÉéFÅËm¹©¾k?õ·kXä—ö—§°Œ¯`z[Æn? +ð¸Þ~¬5hlîöcÛûv†•Ñ󇔱o¨.cž¼{¹É30ÑK‹[‘Ó8þÜíÇ×»róù〈ÓÕæÕû?ÊëáÖyÖ{—!nVƒ†Â¥ †±0”‡ö?bµíÄjÛ7„3ˆ¹Ë ® ÀÉÆ1("¾œo‡B(B +| ´4Î
ɹÚäaÿ—™H¥=H¥û†ðˆ¸Ë£¤„8 wÇ£ŒðxUÿ˜Þ”³(˜õuG˜ÎâW"EŽGdW«a@}µŠÕÔ#ˆÔ´o¨A0yss×íx¤Jñ}D·Ïi‘L&‘"xfu8ŸRÑx+ÎÑß«F1endstream +endobj +552 0 obj << +/Type /Page +/Contents 553 0 R +/Resources 551 0 R +/MediaBox [0 0 612 792] +/Parent 449 0 R +/Annots [ 555 0 R 556 0 R 557 0 R 558 0 R 559 0 R 560 0 R 561 0 R 562 0 R 563 0 R 564 0 R 565 0 R 566 0 R 567 0 R 568 0 R 569 0 R 570 0 R 571 0 R 572 0 R 573 0 R 574 0 R 575 0 R 576 0 R 577 0 R ] +>> endobj +555 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 676.874 187.322 685.841] +/Subtype /Link +/A << /S /GoTo /D (section.8.1) >> +>> endobj +556 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 650.95 277.522 661.854] +/Subtype /Link +/A << /S /GoTo /D (section.8.2) >> +>> endobj +557 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 626.963 257.757 637.867] +/Subtype /Link +/A << /S /GoTo /D (subsection.8.2.1) >> +>> endobj +558 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 605.033 251.032 613.88] +/Subtype /Link +/A << /S /GoTo /D (subsection.8.2.2) >> +>> endobj +559 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 578.989 288.043 589.893] +/Subtype /Link +/A << /S /GoTo /D (section.8.3) >> +>> endobj +560 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 555.002 248.243 565.906] +/Subtype /Link +/A << /S /GoTo /D (subsection.8.3.1) >> +>> endobj +561 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 531.015 331.769 541.919] +/Subtype /Link +/A << /S /GoTo /D (section.8.4) >> +>> endobj +562 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 507.028 285.273 517.932] +/Subtype /Link +/A << /S /GoTo /D (section.8.5) >> +>> endobj +563 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 483.663 281.05 493.945] +/Subtype /Link +/A << /S /GoTo /D (subsection.8.5.1) >> +>> endobj +564 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 459.676 320.003 469.958] +/Subtype /Link +/A << /S /GoTo /D (subsection.8.5.2) >> +>> endobj +565 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 435.69 301.334 445.971] +/Subtype /Link +/A << /S /GoTo /D (subsection.8.5.3) >> +>> endobj +566 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 411.703 334.111 421.984] +/Subtype /Link +/A << /S /GoTo /D (subsection.8.5.4) >> +>> endobj +567 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 389.095 349.941 397.997] +/Subtype /Link +/A << /S /GoTo /D (subsection.8.5.5) >> +>> endobj +568 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 363.729 271.446 374.01] +/Subtype /Link +/A << /S /GoTo /D (subsection.8.5.6) >> +>> endobj +569 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [98.216 328.984 261.553 339.863] +/Subtype /Link +/A << /S /GoTo /D (chapter.9) >> +>> endobj +570 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 304.863 277.264 315.767] +/Subtype /Link +/A << /S /GoTo /D (section.9.1) >> +>> endobj +571 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 280.876 247.147 291.78] +/Subtype /Link +/A << /S /GoTo /D (subsection.9.1.1) >> +>> endobj +572 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 256.889 363.451 267.793] +/Subtype /Link +/A << /S /GoTo /D (subsection.9.1.2) >> +>> endobj +573 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 232.902 434.155 243.806] +/Subtype /Link +/A << /S /GoTo /D (section.9.2) >> +>> endobj +574 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 208.915 257.368 219.819] +/Subtype /Link +/A << /S /GoTo /D (subsection.9.2.1) >> +>> endobj +575 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 184.928 235.093 195.832] +/Subtype /Link +/A << /S /GoTo /D (subsection.9.2.2) >> +>> endobj +576 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 160.941 224.034 171.845] +/Subtype /Link +/A << /S /GoTo /D (subsection.9.2.3) >> +>> endobj +577 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 136.954 248.233 147.858] +/Subtype /Link +/A << /S /GoTo /D (subsection.9.2.4) >> +>> endobj +554 0 obj << +/D [552 0 R /XYZ 99.213 706.052 null] +>> endobj +551 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +580 0 obj << +/Length 421 +/Filter /FlateDecode +>> +stream +xÚí–MkÛ@†ïþ{”šîÌììǵ% zhÑô`œÄâœ6$ÿ>£tUÙ–½.”œtØ…ywޗуVh¬>hRB6Áz°Bf¶œX3×Êճ‘ +|nÐU¶ó,ù¾£F`ƒn|ˆ`S§þÜN>]2I³iïL¯É’öæG¥AêÉÚêÛïÛíóeR·_§›úg{=¹hÿZ¦
)šÁ`¹—”BE$»! nDýßhq)½±Ãÿ-ctà\(¹—”Æ|ЦsÀ‘©H«¯B†„øÏŠg0ÌšžCŸ9lk–j;]=Lg¿ëÕˆCð,fÇbbÖs!ð^Ž÷â",ü°Ñ+”tÊ‚C«¯Ž2•ç T[”.ë)Ê,ê5 dEÒŠ¾¬o«yÝî/ž¦ËÍýíÈKõkÎfÇeÌeÖ£è!â^”.ÏãY˜û€gaî‡^ñä“xìú‹½ç!Šåÿƒ&28tG±Õ«Ÿ½É’.ÐãâT ¡OoõnV«endstream +endobj +579 0 obj << +/Type /Page +/Contents 580 0 R +/Resources 578 0 R +/MediaBox [0 0 612 792] +/Parent 585 0 R +/Annots [ 582 0 R 583 0 R 584 0 R ] +>> endobj +582 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 674.937 226.794 685.841] +/Subtype /Link +/A << /S /GoTo /D (subsection.9.2.5) >> +>> endobj +583 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.074 652.964 216.632 661.93] +/Subtype /Link +/A << /S /GoTo /D (subsection.9.2.6) >> +>> endobj +584 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [113.16 627.116 209.19 638.02] +/Subtype /Link +/A << /S /GoTo /D (section.9.3) >> +>> endobj +581 0 obj << +/D [579 0 R /XYZ 99.213 706.052 null] +>> endobj +578 0 obj << +/Font << /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +588 0 obj << +/Length 524 +/Filter /FlateDecode +>> +stream +xÚmSÁr›0½û+8Š™ 0“4É´ÓæRnm2ÈFŒ!œñßWËŠÄm<´z«·ûv—eQê?Uå,‹¶iASÁ£æ¸I£ƒ÷<oXx‘sAYUxûŠ3<§yZEÉE„ûzsû´e‘wmËmTï×,¢d´QÝþ"ßÕAöqÂEJ^ŒÓŠÿÔß<‹GŠHi”Ýl!<˜Q«)N2–³‡“×逴¦™jpˆåáÂó([…®½±Íl21äm@xžÂC9´ˆ#žœÕ»˜32;m† ÁM®Svºñ^Ñš˜ rÒ +Q\'ƒ8Ÿj…òXN«<_Êk}^ +2FNšNÚ˜•ä Ð!ˆxFç^tÆ47] †F!©
„Ù‚>DÿUnT>%1Ž€81ôi:8©‡øCxeõ¡!pž7WŠ|ëTPáÇþÞNßt·ñ ð·|sñfEõªqÖº‘}ŽAèš…g´Â¥^´åiyñSÀmEûµ¯>ÂìäéÁ„¡ÂéUÒ}œ r3?¸úëOúòX£ã7çZ_¤“?ä8B]p¿ÇÞÄ?€'+jùÇ Sƾނ`ª7@ÿÜ°çÙÏh©qóX¿/]ئ2£9˯nex‘|<Á¼Ø®Ìï«lÙNZÿŸj]ïϹþP—endstream +endobj +587 0 obj << +/Type /Page +/Contents 588 0 R +/Resources 586 0 R +/MediaBox [0 0 612 792] +/Parent 585 0 R +>> endobj +589 0 obj << +/D [587 0 R /XYZ 99.213 706.052 null] +>> endobj +10 0 obj << +/D [587 0 R /XYZ 99.213 537.068 null] +>> endobj +586 0 obj << +/Font << /F71 445 0 R /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +592 0 obj << +/Length 126 +/Filter /FlateDecode +>> +stream +xÚm11F÷þŠŒ:4&iš»¬‚ +ÎÙÄIQ:¸úûÐÓåÈx÷1P?w.0‘!UÛ+<;9%†JEvëÿ +ÌÍ&‚VæUiŒä¹ ²~•}¤Ýqpt+(Ô;f0”¸_6ïÖÚöçtˆ_ªŠ¢’ÿ´Œ} +endobj +591 0 obj << +/Type /Page +/Contents 592 0 R +/Resources 590 0 R +/MediaBox [0 0 612 792] +/Parent 585 0 R +>> endobj +593 0 obj << +/D [591 0 R /XYZ 99.213 706.052 null] +>> endobj +590 0 obj << +/Font << /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +596 0 obj << +/Length 1478 +/Filter /FlateDecode +>> +stream +xÚ…X[s¢H~ϯ°æI«"¡¹)³O(D{ÁL&µ³DÈ„*pÜù÷{šît*UáôéÓçö}}IÐH„4ÒuABòh&j‚¨J£}ú Ž~ÂÌê1ER¤k LNUIQM/<,‚‡§çÁÔl><Š:G‚®Ž‚蟱ì㬌'ÿƒ4Ò]“‰©8šj²0ÓPmæ³7áq2•Tqü‘TØ®·THJ6SÄ1•Ê|‚ÆÕy‚ÄqX0eâ°Œ#::eQÌUŸÌ¢Š‹”ùÊ?:sùáO$u|N²ŸTµðM*W"JHòHtE©“_æG’Çï"ùùYÑ’(*Sø¥=²RŠ0Ê}š4ÕüUñ³ªŽ_ŸžÎçóDSÇ‘•ùG%ìóæÑ@,ãp`Å’`%/¼Œ‹_¤Žã¨IP’6׋£¤¬Šä}"A[ª$Ï&SY†ÆeN + ˆL[BøNA© FT +¬¥”‡sÖƒ6Ë«dª˜°~¦ƒ‡zçÂÌ´IÉÌx2'ždÍ4¹þ +GÔ‡„»¯!×^@JÜgÃÛøöæ½€”žýÛðÀ ¾,Ê÷§4ΪžeõÏ¢'~oäàŸ‰iG~Ø1pu¼ÔzxÑ…0·IâªÙ7™¥ a®¢ûÌjmn3‹ÙæÈ·™u'Zˬ{á8³.Â9qBÛ…tC„,Lki^#{5‡†Ä sÈ|i–ù(¯$UIàIÛà¼`Ú4üM…wæâTßæÄC•SMœE`ΦyH€5Í«¡3ƒ’žß“ð HÚä#þ¨ÈSÎ +¼h^#kÝi‘à +ö׆mßÇÑ}u,ïºÓ}l
α±°‡rl«ã0šØ³–Aƒ‚‡/`tÎf +k-q3²¾[ +àï§ÕuÆëú×(BR5ŒòÖÊÆ+ËYZ×ë\’Ê+öº
´ûÍ:ìtxúj¼QB¸»¡üxÑÍ‚Æ]·£s¸=Ž(tœÅÃÌÚ0_pÓúžs8||y–´¬[®Iˆ@H~ë-2—)wŸ"Iïi ‹3A—5î…´ ù¯‰ÿ ê +endobj +595 0 obj << +/Type /Page +/Contents 596 0 R +/Resources 594 0 R +/MediaBox [0 0 612 792] +/Parent 585 0 R +>> endobj +597 0 obj << +/D [595 0 R /XYZ 99.213 706.052 null] +>> endobj +14 0 obj << +/D [595 0 R /XYZ 99.213 541.802 null] +>> endobj +598 0 obj << +/D [595 0 R /XYZ 99.213 434.339 null] +>> endobj +599 0 obj << +/D [595 0 R /XYZ 99.213 396.481 null] +>> endobj +600 0 obj << +/D [595 0 R /XYZ 99.213 358.623 null] +>> endobj +594 0 obj << +/Font << /F71 445 0 R /F72 448 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +603 0 obj << +/Length 1108 +/Filter /FlateDecode +>> +stream +xÚ…VÝoÛ6Ï_¡·R@͈úVßÒ¥íR¬[†hn´DÛZdQ(§ùïwÇ£d'v°ŽÇãýîx_^ +î/¢ƒµàú×Rıõg2Ý¢‡9+õØ5Ö¼°`zõŸ*Í@;؃ѽªˆÛõºTÕØ+'¥{’@bFµSq:Ç¡n7´%‰óã›ÔPöuÚý$ahy±»z‡ÎÔæñŒõ5*…À‘±U½Ù¨ÁWV{Œ€lÜ(béµ; +Q•ª33îA«nÊP#Øæšñi9Ï)7€B˜("g'™YÉÐ ;B$<MĬÇ&D&Ïá„(¸ˆòWñ&™×a‚àÀëÇVîê’<·%þ‚³qÀ£7|E^v•DÞôôe°ƒ£¯ M~ЮëÁôõ +Àh”Ë’%&G/ÛAÚW +endobj +602 0 obj << +/Type /Page +/Contents 603 0 R +/Resources 601 0 R +/MediaBox [0 0 612 792] +/Parent 585 0 R +>> endobj +18 0 obj << +/D [602 0 R /XYZ 99.213 688.052 null] +>> endobj +22 0 obj << +/D [602 0 R /XYZ 99.213 481.777 null] +>> endobj +26 0 obj << +/D [602 0 R /XYZ 99.213 221.216 null] +>> endobj +601 0 obj << +/Font << /F71 445 0 R /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +606 0 obj << +/Length 857 +/Filter /FlateDecode +>> +stream +xÚUYoÛ8~ׯÐ#D\R¤®Go¬$îÆGm-Pô‘˜†X]+É[ôßï𰃶0`¿ùæàp†¢>õóG”ù)I0‰#¿j=âͽG#t”ðçÏÒûã.üç óËgŸ‚ã4?9NXî—õgt|)ßZŠóT³RL9§Zû„I„ðOÑÎÈ¥‘#ï϶ç,ÁœEΚaLÖ®ÑgÉ)Òæ•÷}FÚdiþ?üö̉Àþß|£]{Ey®Æ¹^1Nb®kñ÷ùñk¨Û;`–gþ7 ¦yî·bR·o¼ƒ÷þìËêßZ½Uú8☓üçµw)%i†‰üAu•„“æÞ?®ŠÄ`·£l”œ¬Üw°f9ÝZ?Z¡–S5ªaîGK˜{‹W£³ó;¿H«lÅ0¨îët„ŒÔ««èŸ_3AhÁxT¢qQ‡á’Ê)ÂS?¿èŠÃéCÊqι9j‡F¶²›Å¬únÂ'JÄpN©¡Ü4F&aèÕM³hþ 㱚Ô<ÜXt’Ò +6GƒT£ +|Èú†.¨©&È»‡–2ˆbô¯ÎA6ý ]÷í¨jùÿ„M¾ÐøŒêŽã1:B¥M]4¢&½2$š©·ˆ"¾!€PxjÀ]Èó}2‚¤åŒ²ê[¨P}Úš|”Y¾ÁõXØâ°=ëÚiÕ÷þè¤ +pkwOdiÉÐp²Úùeಾ¾¶ûóÙ¡a©Æݱ@ã)&)³³ŒYRÍs8C?Î׳Œã(Šlíjw)îÒÝŠÎáeÀb4Šêwbs{©¦ªª…»ûEøsD+n¶výP¸¯Å~¿Ø”«âpcÑâÓn_u»·ëj½{\KGY8mÐ9gåCᨛ»í~½(pµÝœÐmuxã–ÛÛëbS)×o%¿¥mu¢•“õ¤2 +#YÍGÛ‚vD§Na;+CÃØ×0GÖ3 ã"këàEŽRuN%¾[áÉtÍÏÉ(j ýÓŠñïÉBÁuö` Ü rôd^¦Ìâ½éïNŽöMxã¡Îæ”ÿô»v¡\=>gðÍqTôcœÓ}è?2#Ð3endstream +endobj +605 0 obj << +/Type /Page +/Contents 606 0 R +/Resources 604 0 R +/MediaBox [0 0 612 792] +/Parent 585 0 R +>> endobj +30 0 obj << +/D [605 0 R /XYZ 99.213 584.59 null] +>> endobj +34 0 obj << +/D [605 0 R /XYZ 99.213 503.295 null] +>> endobj +604 0 obj << +/Font << /F72 448 0 R /F71 445 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +609 0 obj << +/Length 1060 +/Filter /FlateDecode +>> +stream +xÚÉ®Û6ðþ¾B— +Q(¼ç9({VÄ•]‹<gm[í r=§©ÓµÄü`ó,ücPDjÔÆÄ ÝÃðæI‘$ +9á›EX{r¸SB¤ä 0Uv»‡fåœh™è„çÒ/ƒw‰®®¡,5"e¨‡³0)8Kwhî ê$*Ú΢¸Ìªð%)xEÐæ?ÀÚ»!I zÜÉ*2^íñN`½Ó€õkTõ#VŽÊç‰Ô§mÑ r}œ€È¡ñÀUæ gEعÕi<¨‰šØ™V=Bð’±eÄ9Mɺõ7 `®F¿lëûÞçˆxVÝñ&«)¸ñþ-†)u¹æ§ïçõ +endobj +608 0 obj << +/Type /Page +/Contents 609 0 R +/Resources 607 0 R +/MediaBox [0 0 612 792] +/Parent 610 0 R +>> endobj +38 0 obj << +/D [608 0 R /XYZ 99.213 688.052 null] +>> endobj +42 0 obj << +/D [608 0 R /XYZ 99.213 477.043 null] +>> endobj +46 0 obj << +/D [608 0 R /XYZ 99.213 368.643 null] +>> endobj +50 0 obj << +/D [608 0 R /XYZ 99.213 218.514 null] +>> endobj +607 0 obj << +/Font << /F71 445 0 R /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +614 0 obj << +/Length 1719 +/Filter /FlateDecode +>> +stream +xÚ¥ÙŽÛ6ðÝ_¡·Ê@Ä¢$Jykî °I\¤@šZâÚJdIÑ‘Íþ}g8”,Ý-X
‡Ã¹/ß𲌅BzŠ'ŒÇ¡—VÜÛÁÍ«•p# 4O7«Ç/Uèe,K¤·¹õdš±8VÀ'c‰Ì¼MñÙ¶þ²ùÍ’)–)¤RLD˜âíëu„þ¯öÿ
üþÆþa1æ·G ‹äô:\1÷¾àðð\PÈA‹ðDÒ‹KŽ"bè‰êéƒÚ¾±<^]á!Xš&Žêæ‚xLªÏì²õwgë¬ßêÅföû™˜%q„^ÿ¾úü…{Dè·g2K½;€9YæVQ¤Ü¹Z}\½ŸyѨi_]rF,âÙÃQv*%*ÇY›þCÙMþ
€h„”›×7ÜŽu>”MM§²Î«µðÇ¢¬wOÐV„’eBXNM«;=À-ЧÂÿøþ-yS„Bÿ¶k„k»f×éÃa¦>ݬEìëDõî6Ùí8¸— ä`Óõt"ö†U¹ítwïØVºï#ÕuA´Eü:Ád äREÖ+VB^Zž4ã`@x²œè +=h‚tž›¾'x–2¡í:äþ¶Í„U³+óé
°]<{¦óý¬Ns;˜:{Sœ‹ë¡¬,‡+å{]ï¬dê¿ÓµÞ‘ká^¨{mãìîÉM2óºFGÝúÐt† H•„(¶înìg~¼{K@ÑäãÁÔƒãjãOòÎèÁ1ÒŽ³nÛùýÖwÆÔD¬¯ØÒVº¬ƒ¦B%#å7Û¯&Ç܈§9 5}(è‰Ê]ÓAþwƽ„ÄÌM1vZ“ä©¿±iX(WE»Âs®kâ¿uoŽþƒ•ÕüŒ·!°òâ$b±RË"?/vpF*¼PÙÏ?ö ÕžFñÕ¾®9£–r•‹}ÃA–Xœ1‘L¦©†,Šk½I@÷R*YGÛ¨7 /…Þ”ØF,¡—Èă{¦Bic½Y‹Ô/Û's[>RƒM ”3Iˆc2 +Š¸¸'ìTû€ï÷Íh‹•C«šîÝ|ûc_ZÆÃÚ\”B”)ÈØäß”$] +Cï˜|ÿ9‹¬“(R^€Ï²ÿWW8…Šá‹ë…ºV +æµ%”Lev=ð¾¯(áIlI–°UæX´ñøÍAxÏê-…”%Ö!Ð#øU¹à«Ipgâê¦0M4—ån´) +8%?î’ÓìGœ¦OÛ•‡–¹9ºD5Ü·¸D)§ábIgeì1'î4íá3€p²àMoBt¸ ݦ«è U:E¡Õ:îvu±<Ž«‹5¥»²ÍTØÉù÷æ–ðš>¤ Š¦ó¤Œ;ÚÑ_™À¸4Å#:Pf££+ðH‹ +Ä"‘›}ÙÓu?˜– s·7!ÐýìÀÅØý0€V„¿ÚÖÔýÌËÝ¢/¨¯LíÆò+«Š ¶Ã_,†ø̼)Á¬*“Ý—æX'¨ä˜`jáLŠTB’ƒjè4µ\xpNz<ÌyÖÓyÚ”s%±>/÷ù·Ú2iT₦N££’…¾Î&§#fÈ¥uÆ®>KíÎôcu¹»¡‘JÚу£çHrÙyÆd81A¢s9ÓÏøKA¶jØendstream +endobj +613 0 obj << +/Type /Page +/Contents 614 0 R +/Resources 612 0 R +/MediaBox [0 0 612 792] +/Parent 610 0 R +/Annots [ 616 0 R ] +>> endobj +611 0 obj << +/Type /XObject +/Subtype /Form +/FormType 1 +/PTEX.FileName (diagram.pdf) +/PTEX.PageNumber 1 +/PTEX.InfoDict 617 0 R +/Matrix [1.00000000 0.00000000 0.00000000 1.00000000 0.00000000 0.00000000] +/BBox [0.00000000 0.00000000 600.00000000 507.53613000] +/PieceInfo << +/Illustrator 618 0 R +>> +/Resources << +/ColorSpace << +/CS0 619 0 R +/CS1 619 0 R +>>/Font << /C2_0 620 0 R/C2_1 621 0 R>> +/ProcSet [ /PDF /Text ] +/ExtGState << +/GS0 622 0 R +>>/Shading << +/Sh0 << +/ColorSpace /DeviceCMYK +/Function 623 0 R +/Domain [ 0 1] +/ShadingType 2 +/Extend [ true true] +/AntiAlias false +/Coords [ 0 0 1 0] +>> +/Sh1 624 0 R +/Sh2 << +/ColorSpace /DeviceCMYK +/Function 625 0 R +/Domain [ 0 1] +/ShadingType 2 +/Extend [ true true] +/AntiAlias false +/Coords [ 0 0 1 0] +>> +/Sh3 624 0 R +>>/Properties << +/MC0 << +/Color [ 20224 -32768 -1] +/Title (Layer 1) +/Visible true +/Preview true +/Editable true +/Printed true +/Dimmed true +>> +>>>> +/Length 4330 +/Filter /FlateDecode +>> +stream +xÚí]_·
ßO1Ï2õ_€a ¾$’桸 ŠbÓ&M±š<í§/)i$Jù»øìÀ>/ +w¿¡(‰äˆµ«¼øÓßþû÷Ÿ—_ߨåõg7Ë …ÿû¤|œ/'µBŒzQ«R&}jãðÑ¿NjùçrzñÇ[µüðËÉx½|¢uZ½õ6.—
kŠIûÅxµz´!ÐW.ã'ϧ ÐÁÝCžO:˜ÕXƒâ®¡ãJ&Öh +ü +¹±‡°Fe´^x—!µFŽ“Ôh +š¡láfª ÒA= +^ELA¯&|ïMØ2MjNM¬Š›¡z„µ*Œ©IZjÒ¡Ïï†Ô„ñùi +éÏb:¹±£ŠwYŒužg1¬YLÊ´ŽÈ!‹á\CÇ•4cÓÉ +l¥OZ.©RJ¸²~–ÙíQb¿ûáH3ˆÔ¯g‘ÁÑ€íoYßg«]Þ…!(Jån$ò>Âÿè]ÑËO˳šÒã“_–
à>Þšˆƒ„ä{6PrRL ^çÜÒËǦŠ¿!O Üz +rÚ +Ý«Ú3}>ÍHÒÇÔù´ÚKà#Ux;Õ¾æ-aç“„ÊóÚ/»]•2Ø0ÄGš¨^¹©ói¤“ëÜ3³9z‘n¦·‰n_=”0Ôˆ€îç6Ÿ\µñ´2À<«=Ûèò–Æ0I"ó }ØÇZ\þð'ò&ÑØøß¿Ü_f^Èž‘J·šóLo!©#HŽ¨f¦§@=R•·Ñ=ÐôÑII»yÒ»8ý4¥,ÏG+%Pó‚õHgªU–Gª„¦N—wå˜ÞÍŽ£ÕÓfºµhHgû%Œ¿ÝÓl¨¨¯Úxr þˆËõ³.oÙj“$2o‘Ї}l +ÔòDÞ$P[ó»Uæ~ +ó%(ðÂóŒºÕ‰'rK@L&JtžõzòHÖN÷ðÖÇ%a<Î38¦wéÁU%/àåå‘&ªƒ¢ÄÁF–÷òˆÜBÚô¼W¿fzkБ¾8ôÑI_FÆyQ=#¸êá ¹ÀG\VŸmtyËVc˜$‘y‹„>ìcS.ð!OäöôŸÝÓƒPD2Î9œÅŒTÚ„5z¯aGŸ·Æ¬1Ú”¿c¢u{í£ÜŠCTžaçîé×”¼Jœ“ÄèLƒz§Ø¡6xâMçIÒ¤®¿,ÿF…©å‡(CíÉÍ·¿kW\"ïýÍòâöGµüòãòù7ËŸO>Ñ…&_\å} Ÿsúò}þ¥ÞW§|uÕÕ"·9°ÐôÂAX“ÃWƒM,Ä5h‡6b*hê®éª[ßUÖ~(’Ö„™2×wj?ë%ö«²>¬ÎÙ¿É•Æ=2áP¦GÑ%L ·?¶Gô‹¦`|Ĭ)ÒE ‡Ò¼˜—~ÌÍ¥pB"æfu1äÅ]B¦ãªT2væµ[U~Àâð½Êס¸D(6Á‘)`6®UªÆåèfÃeu„õ¹;Ôç°‡xSi®÷bÓJ +Î&–•²þÍZÑÎR'šä戚;âÕO4Dv罈éŒRy³ÔÉQ +àl«óí +– +Ïè\tmUþŠ¡^õ1VB ±{ôšEqªåö>zG¾åqŽùÞpïÖà\,oÇÍoŒŽªÞ)®<…@¬o—Ä‘ +¨„d¢“Áú~ïyEc +àÎÃHw~ïa&1éQ`ñ6ÜâäÅp,ÂÚÂòa‚8V›hM@šöoB?Ô4' ƒE€À‹~EçYâóT<Y +ÀcLT÷ä`&¢žqÓ=¹®›hVÜT$[‡l
."ˆ¹ç¯ÊåP¦4,ö¼ãèV«I6®UdWpô9\åXî3&àT†—Q5ïæ‹\jöZZù +ŠÙ3ò™CN²òåÚŒËé&dM Õ7)¹Ëí1æ Zb_.ÿ'×rš£ŽÇ‘~ù‘{òYVþ6‘ndõ%Òk@1r‹N«¢®¦S׬í÷¼’-%pp+ž‘í âI-ýšÈæXèÕPƒr¾¨õ"ÃyØšªYˆ³@v“À:E‘W©Ps\ÑÈà¦ã™í¦bŠ¶¤;˜e'@²qX
&N¦$Q6Yª?WO¶r¶e¨|šW5|¶Ìý˜§è¦a(læH´Øa’—W’paÒŠ›×$0—dË +`qR‘·œíhe°ØóÊ–À]½UÅÑÝ& d2×Y¤)¸&`£
+endobj +617 0 obj +<< +/ModDate (D:20060310121748+11'00') +/CreationDate (D:20060310121408+11'00') +/Creator (Illustrator) +/Producer (Adobe PDF library 6.66) +>> +endobj +618 0 obj +<< +/LastModified (D:20060310121748+11'00') +/Private 626 0 R +>> +endobj +619 0 obj +[/DeviceN [/Cyan/Magenta]/DeviceCMYK 627 0 R] +endobj +620 0 obj +<< +/Type /Font +/Encoding /Identity-H +/BaseFont /HZYKOU#2BTrebuchetMS +/Subtype /Type0 +/DescendantFonts 628 0 R +/ToUnicode 629 0 R +>> +endobj +621 0 obj +<< +/Type /Font +/Encoding /Identity-H +/BaseFont /XKYCEO#2BCourierNewPSMT +/Subtype /Type0 +/DescendantFonts 630 0 R +/ToUnicode 631 0 R +>> +endobj +622 0 obj +<< +/Type /ExtGState +/SA true +/OP false +/op false +/OPM 1 +/ca 1 +/CA 1 +/BM /Normal +/SMask /None +/AIS false +>> +endobj +623 0 obj +<< +/Domain [ 0 1] +/Encode [ 0 1] +/FunctionType 3 +/Functions [ 632 0 R] +/Bounds [] +>> +endobj +624 0 obj +<< +/ColorSpace 619 0 R +/Function 633 0 R +/Domain [ 0 1] +/ShadingType 2 +/Extend [ true true] +/AntiAlias false +/Coords [ 0 0 1 0] +>> +endobj +625 0 obj +<< +/Domain [ 0 1] +/Encode [ 0 1] +/FunctionType 3 +/Functions [ 634 0 R] +/Bounds [] +>> +endobj +626 0 obj +<< +/CreatorVersion 11 +/ContainerVersion 9 +/RoundtripVersion 11 +/AIMetaData 635 0 R +/AIPDFPrivateData1 636 0 R +/AIPDFPrivateData2 637 0 R +/AIPDFPrivateData3 638 0 R +/AIPDFPrivateData4 639 0 R +/AIPDFPrivateData5 640 0 R +/AIPDFPrivateData6 641 0 R +/AIPDFPrivateData7 642 0 R +/AIPDFPrivateData8 643 0 R +/AIPDFPrivateData9 644 0 R +/AIPDFPrivateData10 645 0 R +/AIPDFPrivateData11 646 0 R +/AIPDFPrivateData12 647 0 R +/AIPDFPrivateData13 648 0 R +/AIPDFPrivateData14 649 0 R +/AIPDFPrivateData15 650 0 R +/AIPDFPrivateData16 651 0 R +/AIPDFPrivateData17 652 0 R +/AIPDFPrivateData18 653 0 R +/AIPDFPrivateData19 654 0 R +/AIPDFPrivateData20 655 0 R +/AIPDFPrivateData21 656 0 R +/AIPDFPrivateData22 657 0 R +/AIPDFPrivateData23 658 0 R +/AIPDFPrivateData24 659 0 R +/NumBlock 24 +>> +endobj +627 0 obj +<< +/Length 97 +/Filter /FlateDecode +/Range [ 0 1 0 1 0 1 0 1] +/Domain [ 0 1 0 1] +/FunctionType 4 +>> +stream +H‰ª6TÈÌKIP0Ô3 +endobj +628 0 obj +[ 660 0 R] +endobj +629 0 obj +<< +/Length 391 +/Filter /FlateDecode +>> +stream +H‰\“Ëjƒ@†÷ó³lÁÛ\!5
¸è…Ú>€Ñc*ÔqÍ·ïdþB…9g<ßø•Õ¡2ãw7µ5-¼Lçhž.®%~¢ó`X’ònh—…g;6–ùÞz+ÓO,Ïyôá×æÅüaßM'Ú°èÍuäsæ_e½áQ}±ö‡F2yQðŽz•/}mFâQhÛV_–uë{þ*>WK<
œ`–vêh¶MK®1gby쯂çGŒL÷o=Sh;õíwãByæËã8‹@GP(I@Ï C l(DÒ z=‚°§ÀžY:€RЄY$fÉð‰7d”‚($P©P)P©P)P©n•˜SaN?` ÚhÒ ´á$NBÀOÁOÂOÁOâNPÂVÁVÂVÃVÂAÃAÂAÃAÂAÃAÂAÃAÂAÃAÂAÃAâ;h| ½Á¸%àd~Ï_{qÎG/¤=dÁÐý‡°“å¾ëz³_ +endobj +630 0 obj +[ 661 0 R] +endobj +631 0 obj +<< +/Length 234 +/Filter /FlateDecode +>> +stream +H‰\OkÄ Åï~Š9îM¶Ç ””BýCÓ~ +ÿîíJÄíÊBJ½xßYŠ ˜Úø` +endobj +632 0 obj +<< +/N 1 +/Domain [ 0 1] +/FunctionType 2 +/C0 [ 0 0 0 0] +/C1 [ 0.4039 0.298 0.0314 0.00391] +>> +endobj +633 0 obj +<< +/Domain [ 0 1] +/Encode [ 0 1] +/FunctionType 3 +/Functions [ 662 0 R] +/Bounds [] +>> +endobj +634 0 obj +<< +/N 1 +/Domain [ 0 1] +/FunctionType 2 +/C0 [ 0 0 0 0] +/C1 [ 0.4039 0.298 0.0314 0.00391] +>> +endobj +635 0 obj +<< +/Length 1279 +>> +stream +%!PS-Adobe-3.0
+%%Creator: Adobe Illustrator(R) 11.0
+%%AI8_CreatorVersion: 11.0.0
+%%For: (Wei X Zhuo) ()
+%%Title: (diagram.pdf)
+%%CreationDate: 3/10/2006 12:17 PM
+%%BoundingBox: 4 163 583 667
+%%HiResBoundingBox: 4.8931 163.3535 582.9082 666.25
+%%DocumentProcessColors: Cyan Magenta Yellow Black
+%AI5_FileFormat 7.0
+%AI3_ColorUsage: Color
+%AI7_ImageSettings: 0
+%%CMYKProcessColor: 0 0 0 1 (Global Black)
+%%+ 0.46 0 0 0 (Global Blue)
+%%+ 0.8 0 1 0 (Global Green)
+%%+ 0.33 0 0.73 0 (Global Lime)
+%%+ 0.3255 0.4431 0.5373 0.1961 (Global Malt)
+%%+ 1 0.5 0 0 (Global Night)
+%%+ 0 0.5 1 0 (Global Orange)
+%%+ 0.43 0.28 0 0 (Global Periwinkle)
+%%+ 0 0.25 0 0 (Global Pink)
+%%+ 0.5 0.9 0 0 (Global Plum)
+%%+ 0 1 1 0 (Global Red)
+%%+ 0 0 1 0 (Global Yellow)
+%%+ 1 1 1 1 ([Registration])
+%AI3_TemplateBox: 298.5 420.3896 298.5 420.3896
+%AI3_TileBox: 0.3999 -0.0704 595.5999 841.8496
+%AI3_DocumentPreview: None
+%AI5_ArtSize: 600 500
+%AI5_RulerUnits: 2
+%AI9_ColorModel: 2
+%AI5_ArtFlags: 0 0 0 1 0 0 0 0 0
+%AI5_TargetResolution: 800
+%AI5_NumLayers: 1
+%AI9_OpenToView: -356 894.8896 1 1388 953 26 0 0 10 65 1 1 1 1 1 0 1
+%AI5_OpenViewLayers: 7
+%%PageOrigin:-2 -171
+%AI7_GridSettings: 72 16 72 16 1 0 0.8 0.8 0.8 0.9 0.9 0.9
+%AI9_Flatten: 0
+%%EndComments
+endstream +endobj +636 0 obj +<< +/Length 11614 +>> +stream +%%BoundingBox: 4 163 583 667
+%%HiResBoundingBox: 4.8931 163.3535 582.9082 666.25
+%AI7_Thumbnail: 128 112 8
+%%BeginData: 11463 Hex Bytes
+%0000330000660000990000CC0033000033330033660033990033CC0033FF
+%0066000066330066660066990066CC0066FF009900009933009966009999
+%0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66
+%00FF9900FFCC3300003300333300663300993300CC3300FF333300333333
+%3333663333993333CC3333FF3366003366333366663366993366CC3366FF
+%3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99
+%33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033
+%6600666600996600CC6600FF6633006633336633666633996633CC6633FF
+%6666006666336666666666996666CC6666FF669900669933669966669999
+%6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33
+%66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF
+%9933009933339933669933999933CC9933FF996600996633996666996699
+%9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33
+%99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF
+%CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399
+%CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933
+%CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF
+%CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC
+%FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699
+%FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33
+%FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100
+%000011111111220000002200000022222222440000004400000044444444
+%550000005500000055555555770000007700000077777777880000008800
+%000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB
+%DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF
+%00FF0000FFFFFF0000FF00FFFFFF00FFFFFF
+%524C45FD2DFFA8FFA8FD07FFA8FFFFFFA8FFFFFFA8FD07FFA8FFFFFFA8FF
+%FFFFA8FD07FFA8FFA8FD52FFA8FDFCFFFFFFFFA8FD2DFFA8FD25FFA8A8FD
+%0EFFA8A8FFA8FFFFFFA8FD54FFA8A8FD05FFA8FFA8A8A8FFA8FFFFFFA8FF
+%FF2752527D527DFD0452277D527DA87D7D27FD04527D52527DFD40FFA8FD
+%11FF2752527D7D7D5252FF7D7D277D52527D7D52527D7D7D27527DF85252
+%522752522752FF7D7DFD04522752527D52FD51FFA87D272727FD0452FF7D
+%7D277D5227277D525252A8FFA8FFA8FFA8FFA8FFA8FFA8FFFFFFA8FFA87D
+%A8FFA8A8A8FD13FFA8FD0FFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FD
+%1FFFA8FFA8FFFD05A8FFA8FFA87DA8FFA8FFA8FD3CFFA6CEC8CEA6CEC8CE
+%A6CEC8CEA6CEC8CEA6CEC8CFFD6BFFA6C7A5CDA5CDA5CDA5CDA5CDA5CDA5
+%CDA5CDA5CDA6FD5EFFA8FD0BFFA6CDCDCDA6CDCDCDA6CDCDCDA6CDCDCDA6
+%CDC8CDA5CEFD0AFFA8FD5EFFA7CDA6CDCDCDA6CDCDCDA6CDCDCDA6CDCDCD
+%A6CDA5C8A6FD0BFFA8FD52FFA8FD0AFFCFFD14CDA5CEFD0AFFA8FD5EFFA7
+%CDACCDCDCDACCDCDCDACCDCDCDACCDCDCDACCDA5CDA6FD3AFFA8CFA8CFA8
+%CFA8CFAEFD26FFCFFD0ECDCCFD05CDC7CEFD22FFA8CFA8CFA8CFA8CFFD0F
+%FF7CFD0482A682A6827CA7FD19FFA8FD0BFFA7CDCDCD517C517C277C7C7C
+%A6A67C7C51A6CCCDA5CDA6FD20FFCF7CA6828282A682A67B82A8FD0CFFA7
+%82A7CFADCFADCFADAE8282A7FD24FFCFCDCDCDFD08277C512727527CCDCD
+%CDC8CEFD20FF8282ADCFADCFADCFADAD8282AEFD0BFFA87BADAECFA8CFAE
+%CFA7A7827CA8FD17FFA8FD0BFFA7CDCDCDA6A67CCDA6CD7CA6A6CDA6CDA6
+%CDCDCDA5CDA6FD20FFA77CADAECFA8CFAECFA7A77B82FD0BFFA78282CEAD
+%ADA7CFADADA7AD82FD24FFCFCCFD13CDA5CEFD20FF8382A7CEADADA7CFAD
+%ADA7ADA7FD0BFFA87BA7A7CFA7AEA7CFA8AEA782A8FD23FFA7CDA6CDCDCD
+%A6CDCDCDA6CDCDCDA6CDCDCDA6CDA5C7A6FD0BFFA8FD14FFA77CADA7CFA7
+%CFA7CFA7AE82A7FD0BFFA782A6CFAECFADCFADCFADCF82FD24FFCFCCFD13
+%CDA6FD21FF8382A7CFAECFAECFAECFADADA7FD0BFFAE7CA7A7ADA7ADA7AD
+%A7ADA7A7A8FD23FFCACDA5CDA6CDA5CDA6CDA5CDA6CDA5CDA6CDA5CDA6FD
+%0DFFA8FD14FFA782ADA7ADA7ADA7AEA7AD82A7FD0BFFA782A7CFAECFAECF
+%AECFCFCF82FD27FFCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFCFFD23FF8382
+%ADCFAECFCFCFAEFFCFAD83FD0BFFA857ADA7ADA7ADA7ADA7ADA782A8FF7D
+%FD15FFA8FD15FFA8FD29FF7DA8FFA77BADA7ADA7ADA7ADA7AD82A7CBFD0A
+%FFA782A7CFAECFAECFAECFAECF82FFFFFF7DFD2AFFA8A8FD27FFA8A8FFFF
+%83A6ADCFAECFAECFAECFCFAD83FD0BFFA87BADA7AEA7CFA7AEA7CFA782A8
+%FFFFFF7DFD29FFA8FD27FFA87DFFFFFFA77CADA7AEA7CFA7AEA7CFA7A7FD
+%0BFFA78282CFADADA7AEADADA7CE82FD05FF7DFD13FFA8FD13FFA8527DFD
+%15FFA8FD0FFFA8A8FD04FF8382A7CFADADA7CFADADA7ADA7FD0BFFA87BA7
+%A8CFA8CFA8CFA8CFA782A8FD05FF7DA8FD26FF27A8FD16FFA8FD0DFFA87D
+%FD05FFA77CADA8CFA8CFA8CFA8CFA7A7FD0BFFA78282ADA7AD82ADA7ADA7
+%AD82FD07FF7DFD11FFA8FD14FFA8FD16FFA8FD0DFFA8A8FD06FFA78282AD
+%A7AD83ADA7ADA7AD83FD0BFFA882827C827C827C827C8257AEFD08FF7DA8
+%FD1CFFCFFD1FFFA8FD0BFFA87DA8FD06FFA77C827C827C827C827C827CFD
+%0CFFCFCFAECFA8CFA8CFA8CFA8CFFD0AFFA8A8FD1AFFCECEC9CFCECEC9CF
+%CECEC9CFCECEC9CFCECEC9CFFD19FF7DFD08FFA8CFAECFA8CFA8CFA8CFA8
+%FD24FF7DA8FD0CFFA8FD0BFFA7CEADCEA6CEADCEA6CEADCEA6CEADCEA6CE
+%ADCEA7FD17FFA87DA8FD24FFFD057DA87DA8FD0CFFA8A8FD17FFCFFD13CE
+%CFFD17FFA8FD0EFF52A87D7D7DA87DFD11FF5227525227275252FD0DFF7D
+%A8FD0AFFA8FD0BFFA7CECECEA7CECECEA7CECECEA7CECECEA6CECECEA7FD
+%16FF7DA8FD0DFFA8272752FD04277DFD12FFA8FFA8A8A8FD0FFFA8A8FD15
+%FFCFCDA7A7CEA6A7A7CEA7CECECEA7CEA77DA6CECECEFD15FFA8A8FD13FF
+%A8A8A8FD28FF7DA8FD14FFC9CE52512752F8272651527C51512751272727
+%CEA7FD0DFFA8FD06FFA87DFD40FFA87DFD13FFCFCEA77C52527D7C52527D
+%7CA77CA77CA75227A7CEFD13FFA8A8FD42FFA87DA8FD11FFCACEA7CEADCE
+%A6CECECEA7CECECEA7CECECEA6CEC9FD0DFFA8FD04FFA8A8FD44FFA87DA8
+%FD10FFCFFD13CECFFD11FFA87DFD46FFA87DA8FFFFFFA8FD0BFFA7CEADCE
+%A7CEADCEA7CEADCEA7CEADCEA7CEADCEA7FD10FFA87DFD49FF7DA8FD0EFF
+%CFFD13CECFFD10FF7DFD4AFFA8A8A8FD0DFFA8CEA7CEA6CEA7CEA6CEC9CE
+%A6CEA7CEA6CEA7CEA7FD0EFFA87DFD4DFFA8A8FFA8FD2BFFA8FF7DFD4FFF
+%A87DFD15FF7DFD16FFA87DFD51FFA8A8FD14FFA8FD16FF7DFD53FF7D7DFD
+%13FFA8FD15FF7DA8FD54FFA8A8FD11FFA8277DFD13FF7DFD54FFA8FFFFA8
+%7DFD11FF27A8FD12FF7DA8FD31FFA7AEA7AEA7AEA7AEA8FD1EFFA8A8FD10
+%FFA8FD12FF7DFD1BFFA7AEA7AEA7AEA7AEAEFD0EFF7CA782A782AD82A782
+%7CA7FD19FFA8FD04FFA87DFD08FFAFA8AFA8AFA8AFA8AFA8AFA8AFA8AFA8
+%AFA8AFA9FD05FF7DA8FD19FFAE7CAD82A782AD82A77C7CA8FD0CFFA782A6
+%CFADADA7CFADAD8282A7FD1FFF7DFD06FFAF84A984AF84A984AF84A984AF
+%84A984AF84A984AFFD04FF7DA8FD1AFF8382A7CFADAEA7CFADAD8282A8FD
+%0BFFA87CA7A7CFA8CFA7CFA7A7827CA8FD1EFFA8F8A8FFFFFFA984A984A8
+%84A984A884A984A884A984A884A984A984FFFFFF277DFD06FFA8FD14FFA7
+%7CADA8CFA8CFA7CFA7A77B82FD0BFFA782A6CFADAEA7CFADADADCF82FD20
+%FF7D52FFFFAFA8AFA9AFA8AFA9AFA8AFA9AFA8AFA9AFA8AFAFAF84AFFFFF
+%7D52A8FD1BFF8382A7CFADADA7CFADCFADADA7FD0BFFAE7CA7A7CFA7AEA7
+%CFAEAEA7A7AEFD21FFA8FFA8AFA8AFA8AFA8AFA8AFA8AFA8AFA8AFA8AFA8
+%AF84A984FFFFA8FD08FFA8FD14FFA782ADA7CFA7CFA7CFAEAE82A7FD0BFF
+%A782A7CFADCFADCFA7CFADCF82FD25FFA9AFA8AFAF847D7D84A97DAFA9AF
+%A8AFAFAFA8AF84AFFD20FF8382A7CFADCFAECFA7CFADAD83FD0BFFA857AD
+%A7ADA7ADA7ADA7ADA782A8FF7DA87DA8A8A87DA8A8A87DA8A8A87DA8A8A8
+%7DA8A8A87DA8A8A87DA8A8A82752A8FFA8AFA8AFA8AF5227275952F8F827
+%2784A8AFA8AF84A984FFA87D277DFD04A87DA87DA87DA8A8A87DA8A8A87D
+%A8A8A87DFD04A8FFFFA77BADA7ADA7ADA7ADA7AE82A7CBFD0AFFA782A7CF
+%AECFCFCFAECFCFCF82FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FF
+%FFFFA8FFA8FFA8FFFFFFA87D7DFFFFFFA9AFA8AFAF847D59527D7D7D5259
+%A8FD05AF84AFFFFFA87DA8FFFFFFA8FFA8FFA8FFFFFFA8FFFFFFA8FFFFFF
+%A8FFFFFFA8FFFFFF83A6ADCFAECFCFCFAEFFCFAD83FD0BFFA87BADA7ADA7
+%ADA7ADA7ADA782A8FD23FFA8AFA8AF7D7D84AF7DAFA8AFA8AFA8A984AFA8
+%AF84A984FD20FFA77CADA7ADA7ADA7ADA7ADA6A7FD0BFFA78282CFAECFAE
+%CFAECFAECF82FD19FFA8FD07FF7DFFFFAFA8AFAF7D275227522752275227
+%522752A8AFAFAF84AFFFFF7DFD07FFA8FD15FF8382A7CFAECFAECFAECFAE
+%ADA7FD0BFFA87BA7A7CFA7AEA7CFA7AEA782A8FD1FFF2752FFFFA8AFA8AF
+%7D7DFD0459527D7D59597D52AFA8AF84A984FFFFA8F8A8FD06FFA8FD14FF
+%A77CADA7CFA7AEA7CFA7AE82A7FD0BFFA782A6CFAECFAECFAECFADCF82FD
+%1FFFA852A8FFFFFFA8FFAFAFA9AFAFAFA9AFAFAFA9AFAFAFA9FFAFAF84AF
+%FFFFFF7D7DFD1BFF8382A7CFADCFAECFAECFADADA7FD0BFFAE7CADA7CFAE
+%CFA7CFA7CFA7A7A8FD1DFFA8A8FD04FFA8AFA8AFA8AFA8AFA8AFA8AFA8AF
+%A8AFA8AFA8AF84A8A8FD04FFA87DFD05FFA8FD14FFA782ADA7CFADCFA7CF
+%ADCFA7A7FD0BFFA78282A682A782A682A782A67CFD1DFFA8A8FD06FFA8AF
+%A8AFA9AFA8AFA9AFA8AFA9AFA8AFA9AFA8AFFD07FFA8A8FD19FF838282A6
+%82A782A682A7828282FD0BFFA77C827C827C827C827C8257CFFD18FFA8FF
+%FFFFA8A8FD06FFA9FFA8AFA8AFA8AFA8AFA8AFA8AFA8AFA8AFA8AFA8FD08
+%FFA8A8FD18FF837C827C827C827C827C827CFD10FFCFFD23FFA8A8FD25FF
+%A8A8FD1BFFCFFD16FFA8FFA8FD1FFFA8FFA87DFD0DFF7DA8FD0AFFA8A8FD
+%0CFF7DA8FD15FFA8FD05FFA8FD11FF7D27525252A87D52FD047DA8527DA8
+%FD17FF7DA8FD0EFFF8A8FD0AFFA8FD0EFF7DA8FD14FF52527D277DA85227
+%FD067DA8FD09FF7D7D525252FF525227522727525227A8FD16FFA87DFD0E
+%FFA8527DFD0AFFA8A8FD0DFFA87DA8FD13FF527D52277DA852272752F852
+%525227FD10FFA8FF7DFFFFFFA8FD17FFA8A8FD10FFA8FD0BFFA8FD0FFFA8
+%A8FD1BFFA87DFFA8FFA8FD2EFFA8A8FD11FFA8A8FD0AFFA8A8FD0FFF7DA8
+%FD4DFFA8A8FD12FFA8FD0BFFA8FD11FF7DA8FD4BFFA87DFFFFA8FD10FFA8
+%A8FD0AFFA8A8FD11FF7DA8FD49FFA87DFD0EFFA8FFFFFFA8FF7DFD09FFA8
+%FFA8FD0DFFA8FFA8FFFFFF7DFD48FFA87DFD08FFA8FFFFFFA8FD08FFA8A8
+%FFA8FFFFFFA8FD04FFA8A8FFA8FFFFFFA8FFFFFFA8FD09FF7DFD46FFA8A8
+%FD16FFA8FD0BFFA8FD15FF7DFD44FFA87DA8FD16FFA8A8FD0AFFA8A8FD14
+%FFA87DFD43FFA8FD18FFA8FD0BFFA8FD16FFA87DFD40FFA87DFD19FFA8A8
+%FD0AFFA8A8FD16FFA87DFD3EFFA87DFD1AFFA8FD04FFA8FFFFA8A8FFFFA8
+%FD18FFA8A8FD3CFFA87DA8FD1AFFA8FFA8277D527D5252277DFFA8A8FD18
+%FF7DA8FD3BFFA8FD1CFFA8FF7D5227FD04527D52FFA8FD1AFFA8FD39FFA8
+%7DA8FD1CFFA8A8FFFFFD06A8FFFFA8A8FD19FFA87DA8FD36FFA8A8FD1EFF
+%A8FD0BFFA8FD1BFFA8A8FD21FFA8A783A783A783A783A7FD0AFFA87DA8FD
+%1EFFA8A8FD0AFFA8A8FD1BFFA8A8FD08FFA8A783A783A783A783A8FD0DFF
+%CF8382ADA6AD82ADA6AD57A7FD09FF7DFD20FFA8FD0BFFA8FD1DFFA8A8FD
+%06FFA88282ADA6AD82ADA68257CFFD0CFFA857A7A7AEA7ADA7CFA7827BA7
+%FD06FFA87DA8FD20FFA8A8FD0AFFA8A8FD1DFF7DA8FD05FFA77CADA7ADA7
+%ADA7CF828257AEFD0BFFA782A7CFAECFAECFAECFA7A757FD06FF7DA8FD21
+%FFA8FD0BFF7DA8FD1EFF7DA8FD04FF8382ADCFAECFCFCFAECFA7827CFD0B
+%FFA857ADA7ADA7ADA7ADA7AEA782A8FFFFFFA87DA8FD22FFA8A8FD09FFA8
+%277DFD1FFF7DA8FFFFFFA77BADA7ADA7ADA7ADA7ADA7A7CAFD0AFFA782A7
+%CFAECFAECFAECFAECF82FD04FFA8FD24FFA8FD0BFF52FD21FF7DFFFFFF83
+%A6ADCFAECFAECFAECFAEAD83FD0BFFA87BADA7AEA7CFA7AEA7CFA782A8FF
+%FF7DA8FD25FFA8FD2DFF7DFFFFA77CADA7AEA7CFA7AEA7CFA7A7FD0BFFA7
+%8282CFADADA7ADADAEA7CF82FFFFA8A8FD25FFA9FFA8A9A8CBA8A9A8FD27
+%FF7DFF8382A7CFADADA7CFADAEA7ADA7FD0BFFA87BA7A7CFA8CFA7CFA8CF
+%A782A8FFA8FD25FFA8A8A8FFA8A9A8A9A8A87EA8A8FD25FFCBA77CADA7CF
+%A8CFA7CFAECF82A7FD0BFFA782A6CFADCFADCFADCFADCF82FD27FFA8A9FD
+%06FFA9FFA9FF7EA8FD26FF8382A7CFAECFADCFAECFADADA7FD0BFFAE7CA7
+%A7CFAECFA7CFA7CFA7A6AEFD26FFA9A8A8A8A9A8A87EA87EA87D7E7EFD26
+%FFA782ADA8CFA8CFA7CFAECFA7A7FD0BFFA782A7ADA7ADADADA7CFADAD82
+%FD27FFA8FFFFFFA8A9A8A87EA87EA87EA9FD26FF8382A7ADA7ADADADA7CF
+%ADAD83FD0BFFA857ADA7AEA7CFA7CFA7AEA782A8FD26FFA8A8FFA8A9A8A9
+%A8A8FD057EFFA8FD24FFA77BADA7AEA7CFA7CFA7AEA6A7CBFD0AFFA782A7
+%CFAECFAECFAECFAECF82FD27FFA8FFFFFFA8FFA8A8A8A9A8A9A8A9A8A9A8
+%FD23FF83A6ADCFAECFAECFAECFAEAD83FD0BFFA77CFD0482A6828282A682
+%82A8FD26FFA9A8FFA8CBA8A8A8FFA8FFA9FFA8A9A8A877FD22FFA77BFD04
+%82A6828282A67CA7FD0BFFA78282A7828282A7FD0482AEFD27FFA8FFFFFF
+%A9A9A8FFA8A9A8A9A8A87EA87E7EFD22FF838282A7828282A782827CA7FD
+%0DFFA8FFA8FFA8FFA8FFA8FFA8FD28FFA9A8FFA8FFA8A8A9FFA8A9A8A87E
+%A87E7E7DFD23FFA8FFA8FFA8FFA8FFA8FFA8FD0DFF7DA8A8A8FFA8A8A8FD
+%2BFFA8FFFFFFA9A9A8FFFFFFA8FFA8A97EA97E7EFD22FFA87DA8A8FFFFA8
+%FFA8FFFFFFA8FD0CFF2752F8FD042752522727527DFD26FFCBA8FFA8FFA8
+%A8A9FFA8A9A8A97EA87E7E7DFD22FFA8F852FD05275252272752A8FD0AFF
+%A8A8A87D7DA87D7D7DA87D7D7DA8FD26FFA8FFFFFFA8FFA8FFA9FFA9A9A8
+%A9A2A87E7EFD22FFFD04A87DA87D7D7DA87D7D52FD40FF7ECBA8A9A8A8FF
+%FFA8A9A8A87EA87E7E7DFD38FFA8527D7DFFFF527D7D527DA8A8FF7D7D7D
+%A8FD26FFA8A9A8A9A8FFFFFFA9CBA8A9A8A87E7EFD20FF7D7D52A8FFA852
+%7D527D7D7DFFFF7D7D7DFD07FFA8272727A8A82727522752F852A852F852
+%52A8FD29FFCBA8A9A8A9A8A87EA87E7E7DFD20FF7D272752FF5227275252
+%52F87DA827275252A8FD08FFA87DFFA8FFA8FFFF7D52FFFFFFA8FFA8FD2B
+%FFA8A9A8A9A8A9A8A9A9FD21FFA8FFFFA8A8FFA8FFFFFFA8527DFFFFFFA8
+%FDC0FFA87DFFA8FFA8A8FD79FF5227275227272752272727FD75FFA8FD04
+%7D52FD057DFDB6FFFF
+%%EndData
+endstream +endobj +637 0 obj +<< +/Length 15468 +/Filter [/FlateDecode] +>> +stream +H‰ÄWéoã¸ÿ ÿƒúa€E,"EE]žM;‰ƒ‰³ÛÙA(c+‹^3›ýëûHJ²œcšÁ h˜ÄÖ;ù{|õî/×7ga®îå!çøèÝ»¸–Y«êsÇ‹²ìš¶Ö¤“§Æ3¤¥Âq×Kþ,ë¦PÕ¹áYî\ëŸü"ç_ί›N:'§š¾,ÚR'/²umg»üátô 6’¬6u1r BÜÁäûÎõ¥–‰TWåEµŽÔçŽç`N&¨Ã¹¯¹?es(2ÅZnFe Lf4øŒ0”¨U·•U{]«•lšX•ªnÎø1«œËl
œÌù$ËR}u¢2[ý6Õ™«ªÕ²ª«Y_ɯ×7—K-ðWgYËûnµ‘íåÍTãJÊ\æoÖ/Øݼ(%Är›µŽo"^Ð;³ÍÛö§ÍÀwC÷ï.¶@º‘m +áŒ:Ħ,FgctµkÜÖV´Áºé×P+‹ºXÕùqΰû*z_ù¾ˆ|
¤ÿoàèÿ‚á¯ß/ o[Yõµ—Vy¬¶:Öé_pÜÔb©Ö=wÿ`xàµÛ}>>¢û{§ZÙÀ&JéÜ…nùEB;nÚÕ +À£‰Ä}ÖH÷Σ¨,5¿·4È»b×àÞͳõZÖýðÝUQC"<”òw'ëv£º&«r÷f•Õªr×]QÛ¥|hÝEê`YÛš +³Àý”CÃÄã6»láàž»êêZV«Gxàî}~“Õ}sáòîJí{›uþ ·EUT îâ_¬²²R»yÜmdåÖ¦*A1w·ÙJo +‡äî`‚f׸íWÕt´BÕn»8>e«®•î¶s° ®¡å+8cm%ó¢,3°7jÀ†¶Y³êJ³#!4ó÷.«AGÝdåƒõÑÄ
M>€©Ðz'§Úø†#úPpÃÔû-¸©QwS£VÒ‰z:ê]X©ëâb"s1ʤíƽ2ÁÌÂ*,¬Âb¢°°{ZŒzÛ®l‹]ùè.šRçÄí +ªõòâK¡ 6Uí¬§î`?ݨóhÙ Æã@>>Z¦¶ï±Ü-hõ“OïL£I«•ÒפsçîðòìVòÙ}"á>Q°í}ùë±±=m°
—™N.wò;63¹ÿÞ…`“gw*÷Ö
Xgwàýο5%}µ3<qS˜ïkÕí.ªu|tbï¾×Y»´¯n°–f«ÔÅ—8ËŠÝé7 U\-*k»îš³Tªí÷=kt³Õê¼ÅÉ(ü’`þñ8ƒ©ºÛ«—ì¿À½¢û·0˜t`^òxÈ=×ø¶€‡¢ÊAå¦+Z¹G¤¶;ýfáÜl²&kÉùDò-ûOä\ä'17Ô´ú"Kµ“{úHÞæü’Õ»7çq{¯Ê¢Ùîc2¡Œßßb +î0õd;æ>ôç^ŸéR›VÓÙÙñq¢jZmïkÝu«^øn«*ÛÊÜY÷$ƒ—¨PÌ`&7× ¿!„0"ˆ"1Äáb+P€B¡%(EsŒ0ÆSìa†9Üâpˆ#ã§xNÁ„J<Â'põ#‚$$‰IBR2§ˆbJ(…wDo$‚4¤iBS:÷‡=âQÏó˜Ç=߃›´z‘{‰—zs7=†L1Æ™ÏܼC±˜%,esŽ8æ„SîqÆÍËêß÷Èlºß€pÀ8 pH¬€®<€wD<`6¨aYÔîù€RÏà"`b +¢`lê!
½…<ôCaFa&aÎ#áˆDf +¢0Š¢8J¢4šÇæIâÅ,汋8ˆÁzÅqœÄi<‡9‡!¹(¤Kxâ'" ’0‰’8I’4™C"㔤4õR–òÔOE¤a¥qš¤i:Ÿ#=÷Žô”²
íé„¥ý|ØÏØ}¡LKeZ,Ór1vuÉØ¢I&ÓÖ6‡çóÖŽ.Û&¦3×”LX(¡ƒûÖN‡QõöË®Erã +Ã÷óúfÁ¾ôU’Ê„€JÕ2†$„ì¥/–
31K`kƒñ¿ÏS§¾5êéé‰ÉUú˜ÝõH]#ÕyÏ󾥶PçP6Ô*eB-¡t¨9Ôä +¸LÈÊWüÈ<ª>Tª
ŒsØBCY_Èw•2¡WQ*"¡²æªêÏR•©jõÅŠþ_¶ªsU[YKSU[MAÎ]ª>V|þ Ù(Z/Û(\¤‹p£t½x£|½€£„½ˆ‘ñýY¤œÄìå%íEd„Èœ°)H@1ÍH]^s3Ôêu7Çç—þOÒëAzÛJ']÷Vé•Â0V`¹Ï->¾l¼
¡ÁùÖêì{vÅ6ÝÆC[”lØ4¾'”®Ð|úÛ®áAÏLÅÊ|,LËÌÜŒHi`–:¦ªi6fÌ2m¦A)ÌßÌ$ŽNdŒ_wi¨žƒÃùC6»<¿eRöv²²ÌoÊËÞö$pfÛ»<ÁÁüê –ù-“s¶ÁÂC +Þ¼1xkðæ eÚ0À +6áE»‹ÿ‹sÓ;C:ýl¯4=,ðý¶§ô÷.„þV¾ÛÉ÷Ý +C€ê(+¹µfYÏèÖ4²®[Ùíj“߀®µÕœœ´Ä% õ(™‰˜éI—¹F›¿z±åA¬Ù™³³çI,z ú³vvmŲ1m±íF¬Û™73nD´‚so*£˜ùä_L€¼ˆ±vgîÎÞ]¡§Ú|®Í®›§¶šÛ ÆÚyÃä2³sW³÷æÙÍî[Oo>ñæùE·ÌlSDؾšàd½’ã{=_˜bf6ÇÚ4É^å2Ç^çq’½Ú‹Y®¸×øtø*w8~Œ]v½Œã‹0nT•Ê›—=.»\öyßé²×e·é7ëÇŽGZ¯‹ž]¯ú¾ï|è} UÙÿB‰á1‡E
”*(u€PM¡§†ÿ“6“vnâ®°o"’:&NÄ'ÅTôô¡Erа Ä +å·ña$,÷©cêÍ5¼¥$™ÜRýµbÅ«÷ìª{½XñÊÕ¾V¬øêõ‹uñ#‡’ÝOþü2áé¹È-óзE¤¨âÝÅ[BJÑ.vuùR•ÉŽ®úï5óØs©ézœšVñ–òû¯ÝucîùÛÓo§¿<}þ—K=é߬Uføk%älÂŒVXìhìyìˆì˜ì©ì¸l%m‚«Vø¡¡ñ ”vœö¤v¬v´ö¼vžj%6m‚7P'+±¥æKM™=köÄ)¸ÃŠ/ø“)TUbQ|–ÿŽ÷{ÚŸG’Û$Iq‘üç2Þ&É“<¦$eÍ’œøÕ›u!o¯_ÒöäþlÙåÖ³¨a":æw€6£ø‡§XÅ6a'”WW³<Ž¾1¼%ÈTãÇ/rç•FOîèäNžiÀÚ®/Ž'×oõcÅéÁmI>GUãxtõÅ÷ô\ºãÆ1üûקÇÏ¿þüõtþýÉbõÿ¬:ã˜ØUC¯VkîÒjû!X¤€Q†Œ}Ô¨ãF9öÁa±âZ”ê\U}F©_©ø 2š”á¤(û˜RF•*°„ÈR†–2¸¼„Ëkh @aÅ:Ð`å(»€“B£¶;)`äþׯ{\ö´îaÙµ5u¬ìPÝ“2Ö»~ÙKvcÞíd¹wuø«÷¦ÜâíÙ¡ +zuÄ+£]ëê@W„9 p¾ŠÈ™¯QJÜ´ùÒR‹”‘Z}±¢•:Km¾Âèù
‹ô¿Æ”Ôj’ˆàR2Á¦)4pö[Ð^Àý½~ô»ð<éMÇ¿t +NJtÃω‡&QðœØ׆©îë"ãæD6¦7̸§Ù–Ö…iµ"tœ{öÉÏ=“fßM?Ó+Hupðp$p,ð4p<"2zA£ŠºÏññMd¹…+—©R3å"Q<OàJI”·ó$Ñ$¾+Óû¾²ÇÅŠ®¼·ŠßùIžVNŽ<ìÀÅvG”¬~”ÏT”>vtP9r²£ÃJ:®TŠ;ÒÜ‘ê +ÝíçÖ
‚¨»u¤֩ßMé,Sae>Œ0jNi™ŸIjbÁ,žprõ–|@k„a›pÌ‘ÌÊ2‹0M×f™fŸC”Æ1o½®¦âm!åX!ß’Ð"Ô!)MÂÁ1d©AxȶtÂÅÖ'1Ieg!¤J®’ß|šÓÂË9¤½Q¸©BŒê…Ÿ|è´Ktšž%aú¼i„«KH¤³ðu +™U gam/á3ž9X’‚}"¶Bß5¤æE(¬C®v Û§mÙj·AþÅܹeš&j<8eöÊè–â—â–Ù/£cfÏÌ®|SH½3»gôÏì ÙCqQñÐì¢ÙGqRhè¼4»iöÓਅ§fW¾šµ
£^4Ý5ûkvØè±Ùe³ÏF§M^ëÝV’öšW‹ÖÆ ²>j+©j
jŠÎ½7»/“8zpváä∨“40¥ÎÇž§~ß–À®ç¯û»ÛØõü…‚nJ`×ó +º)]Ï_0è¦v=Éy熶Ï_¡ç¾ãE¿ÃL¸7uï'c’w¯Ðþ€õÌNfý\³>p>“¾IsTr>RÞsžäXp^eÎŒo*Æ_&|à;Ó8V„to_Ð}è~‘ít'±½"{亽ÂõTg¾Û4Á‘ê%Ó—Šé‘èª"úžçÑÇ$»œš“jNr?úx÷áSþ‰ù…ë<Kwj:=´ü¥;¥ø‹\2œZ¹oÎß|ý~Í^=w÷ù¿Ð´ò÷M]±æõ{Ýšê)¿Gþ‰»n ù‡åõéüüøý×Ï_žž
?1O?}yŽ?ûîôÍ?>yþçÏ¿}ûæËGvãˆÂùû& '6ÉæCr8”Zpäàâ». o Éÿ½¿ªîæ³ÉyÑx€Y²§ØªSç¼¾_ UœÚwÝ11°˜Ö)eb`0SÊÄÀ`Z§”‰=À´N)Š8À´N)“‚>À´N)“d]¦yXÉ–ˆø·ÇÂG,ŽŒ¶ˆX"i#bõ<Ôè-îH.Ì|…?E½ƒfm +i¡’Jéi³ŠÉ å”PO
µPQ%õ´bƒ<È +i•W
‰qÆÐYõ4lÍeÐ…öJ识[è°ƒ{˜Á@“ti¡Íú¬ÑY-„ÚA¬=i Zœ*”k¡Þ +®¡âJî æžZ0Pue[¨»„Âk¨¼…Ò;¨½'«
Ò&ƒô-ä_Òjšs¤-t¤tÔ0´‹Œ¶ai%m¤¦´´•Ž´î‘#†v“Ñv,í§¤
Õhø–ÆÔ‘Ú=¢Åа˜#ËÒÂJZYMKkim Þ#m-/£õYZ`I+¬i‰-±#É{äA”e4OK-i¦5M•9Ò^;R½G$ÚnFûµ´áR+±ÑÚ‘:è5wSÍ6«ù!'ÍÔús8cUTâ&ò8žŽþþ³ZÏS-vMÛ{¤º%=oÑö“XEÜk7ˆ÷þf¯"^q/|ÄêQ”‹¿xDµÚ¨Å_/b8ßgÜWÌ{½¾<ã¾bÞëõå÷ó^¯/ϸ¯˜÷z}yÆ}ż×ëË3î+©#TMŸ«yÔ2¤cho~–jÒ?¤ÊP_ºìQU†±ÝÐeª2Ô׆.{T•½¾lé²GUêkC—]WeƒêZwëkŠàº¢¹®¥®é¹f›Ú+8oƒˆî[w—+l·[r®ô +"ÍSôMÿXùòŒÄZânq¸+,»G@Äø“‡/"®¯þ6Äè9»ù>ˆxeÄU¬äÿ3æ!n(þo6çF¢;ÄÜ=`@Cq÷¶Ïû¬O{Cû>@‹¡¿&jì-FÛ¨±#´úk¢ÆŽÐb‡‰;B‹¡¿&jì9-Æ™—š™Ö•ý[m²rVþÿ’»IRV'VUÊÒ*?%'s²üIøóëËw_®k×7”…Œ1uÆTÓP»Ã|¤¤–·ñÎ\ßf³0¤˜EÚæ#¥Öê\ßÉ®‡!ù,ÒÞ0ÉÙÏWw3Igµ7ÌG2F÷²HªÅY Q§¥»|×üd¿\>ßüíëû·Ï?üöã—oŸáÞ§ïþøüý?_=ýããýo¯/³ fOí»fÌ ¹®ÙîòÝe¼ä¼ËzçAÄ…¸Ì—Ü×ì÷ù/àj@ª€:À£T¾¤\=¸Š0BöZR®2\mHu¸ú +q5⪄: ¾Å9©W/R1®f\ÕHݸʡv|õ¸ú‘ +r5¤UDEH%¹ZÊU¥*ž•a_Ug•†JÌJX·J{S’W€dàØ™’‹ "‹ 1_ÀFPDPFA7è«ê(šÚ5ˆˆóˆ ‹"¦Q&× E+=á¹-üVÂk&¡æ/O6Omï”–g³}"‹ØÞy"îíööÞFw3ìä3¦6fi1ºO˜Ú˜¥…Öž0µ1K‹Ñ½ÉÔjnôzê©ÖœÕóª4û[Ýç‹ßɉ~†«X2ÓR>“ +ŽØÇXŽzŽóÜY{á<÷úcºg”:e¥]W.a''s[Ïn9¿õWê*2Ëõ<×3]Ïu˜-Üê”ÖŠ_#»äØ5˳pë\sU×’m×|;0®îd2:T,®F;%O~ÆÖ~Y=hgô[V%ù)y«s“ÊGRW'¹™gn`iÇ +kd˜ÉÊSâF¤Óîòq¨²Óð‘‘maÈtV{£$NK“ü®ùÉ~¹|¾ÿøÛ×÷oŸø;íÇ/ß>ýNßÿüÇÇǯ§óýöùþñÛß^_–w$2Qßõø¤,+[W”Å!e;e‘.Ç–Ã]Ó&”Ê…±gJ§á×Q +¢åDM‰ž ¤/¼íÌ[Þ^1‹‚ÙäÌ*ev ÚúÂlÏU«f©f
%k±¬)cužïYmǪ[V_³%»aÙ•ŒÝ1e‚þ¾°cgv®a+v²`Gó"SCgŠ=~aÏÏË¡=÷„—puZÅ3Á=Šb¦F±Z´Ð å+þF4ž¨ÃD=§é(õ3ÏVQú,*°âj †Tôd—àW/2òh! +>jpM¸(™$,îZºQŸHKͨȅÜ(¾NÕ¶Š´”ÍIuYT‹LH¹Kd¬Pcþ>fpÈщ!ï†ÌšdNÈñôÇÓ
‚@Û÷ÿدšÞ¶q-ºÿàM€vc¢(Q݉ú(º.ºšE AE½hûÿñÎ=—¤åرÝÚ3袋‰§²DK—÷|%‰3¸½ÂËÕ×;¼o‘»ëˆæ¦ÈÝuÄWäî:b‰+r÷gbGß]Ñ‹;g@é=ˆ~ +) +6ÂMWä5aß(ð(jé!& +µÕ¨¬RI§0à±hUV«¬W9žå€–5‹ªUtK•+k´ƒú¥ +¦F+:¦J&Z¦j¦z¦Š¦š62sÍ´‹Ð5|EËTÓÒ‰¾‰Â©Æ‰ÊE&Q:hÕNõNÏѾ‰ê‰î‰òAû0P¢¢€ª¢‚“šYK5¬Ojj¢gŒ‘P¨Œ£F!90žÔÉIT2É–ŠYÑúióÔΆúÙ2¾i˜ë©¤‘j:PQ¡©ˆu +i®4ΨԀ©FUç†Q¦U5¸²)_Ñ3j®+™SY«Ä€§ªT®TÊkÝÞO–jK…Eu‹êw*eO¬¸M¢ãšŽÖ+8÷ÇþóñïˆGü;âßÿŽøwÄ¿#þñïˆGü߃x»º»?noþòWZvåÌÚ¹Öâÿñå×Û›»ûCßÅŸr{./Íö†½/äêˆ ¹ë¿øûióøùÇÃãËÓæW:Ÿž_6ùܧՇo›ÍÃ÷§ÇÕs:µ²ÝÇÛ›ƒ§å¡VñQßa
b©•È~‡ÂŽX&/ÙkúzM^¯©kI\]!BY·7ÊÚV¦«LVJU$ªBRJQJPJO$§Û›DNS!&WH)$:”Š9’P“¨Oä£Ä£´£”Ón@6Y(ð\òdM!ÒÀgìøœ=ŸUžvà™Z‰Ã²‰då-Pxö:Ñç;)éÊ›¾]Wè7ò-‡DÃ#ñ>'26|o)<;ßß%j–>x}L]2è¡4Ý'ª–þ…°'%mvu{ƒž)}Kç\"ñLãM¢ré¥Ò¹t´O¤.UbWjŸ„°b¦x©J:^„øªÇ¿º¢½NaÅ+ôöŠ;´'}ÜÚž!1P¹Dmfr…°…ð…2†p†°FÃñUóÓqó"ä-š'<B&.6>FNVi’-Rc$ÖHØEøe¤ºÍdá<q²Jà›<|¬ +X7Àþ +tëo‹®yEÕæP¦8pU’§”ïC¨vÓAµ®ê¶ÚW›S7äÕ
@@ºÊ6;¿RŸÓ_,Aþ°ùhÈR‚(ù»UQöŠۙS%äT`§“5žQåÀŠoñŒê÷+îžéΨp¬°b Ìœ®æuâ!+ù|Ô,ǪX–•3ÙÌ¢wSXÓUÖô§·7øK½ªéž!¿Rd¬ºI¥?¡‡cU©,KS×Ìš›Ä+æÑ£‹…ÜK©»¦S‡
Ð"?*ßê+è¸|ÀY0ˆYóY5§RgªãKÇ\Ñ}W}*æ)ec²{#¡J¯áXzX–Ѳ3‹.8`H¥nŸ¹A®³Õ¦¢šèVh›ôlú «4µ˜ÌV²Zfi¶Ð·«ÄÖmhÅü]%¶nC+ÂÜUbëVòæ®[·¢þûãØZööJvì-[öW˜àƒ«ëÖßµâžÞK§0|ÅÀo}26Bò–ßš–O£ùž6µ`zÑ'1½@5ð{קÌìdSÉ#½Àº‹îË£™T“ʳTU¶TRl*#%o‡šX£8M?¦êYúzÐÿ‡&•g©ôª,ªh© »XÛ™5±4*¨]ˆ©4LèKèO衱碴cßTzUU²RJÒÃ@3I¸A© ÑÈÓ§Ò&é+`yD ¶Ñ#”dTÜUzU¶$v±&ÖÈX‘†Y•ÒÐ6áò¾Í8ƒG°rĈth~‹ÇõàmGG+nt¢OŒtpÞÊc:¦Ä&ΘšÓ#~ÓÐÈ?_cÊ*L›égLÞˆŒ˜ÅÙâå=fÔaZmoºÓ;bŽ#æ¹ã5œLfËrBd2NBÇÝo¸ßŽ{l¸§#÷±çεܚ»c¹÷ +pLVÀåØx`d +pÐ`z\Ï€m +ú8F<èÄ‚V4èE‚f(—+hgýœ@CtdAKô$AStß´µ‚¾N 1"3Z3 7š\¯\ +ÕÂåèÂeçÄåÅqÑ\,.¦ýÓ»c×LÖ"óR%jJƒ@w?pŠdô>™Ÿ åé5¦ßþÚ#:Oc‡?€ý°ìÇ@õi)pؘùçÿùòÓ×O_¾}úòñ‡jÀ׿¼|ñãoü›Š¿ýôöÛ·÷_¿ N_ß¿^ÿH Pþž„˜§dõÝüa$ö{<²H|ó{ÿÿ³ÿôô—éç,Ó//iþÍk.CÝÊ÷q²›}ÕÍÆMóôíÙj…/±ð~‹'ûk,¤‚ê1—RKTNŸ·‹Èr‘öLºœ´œH?˜®f?TMÇ¥¦3òi=U|*C|߆Yã㹤Ÿ%4i™”n†Äõ4Fz†IÚÙ©&‡C‘N+¿D“‹Ž“’Ÿæä¡ó"ŽMã ñ7K±Zx ÎÑFÖk;Y-¢'I_‹™¬ƒÇÖt8 Ÿ¬›å-=¢0<¦…ËÒø½¤äŒg]™T&=¤1ËXÔ/¥Iãfg<”1¦Ý)ߥãñqÊC鸃¶;ÑØÚ⾶ÑäÏ){Ũá‹ëŒ¬]—üË“‹š²ûilÉw)F<†ÄÌq 1
j¦hŒU +`‘4‡µ0Ï“T‡•4VX×ù(…Eh³ á}
Š)(‚9à(‘D(¼Câº)y&ož$(“xqŒþ1Ž;Á#X…ÓºZ"¬”cåÈÛFoãP9.OzG”$–@†žÄ“±E-z_«E’]À[ ¦<yÁ)Ü0¤ªœ{<^ëöÅ›ºMѱèçÉì_/£(èƒð&ô(nºÄ-x_PÔ<öz‚ÆÚrÜüâå 凬^wQ¨è.‰Žq°{D³»ÑîÚ½[:ßµ•µCIF”©|m¿°¸è]ÓpÕPG é/\ëóh“¦=Íëä‘ãºL +•L©Ü‹qèã´÷Vw«öÊÝ«LŸ¢}Ž‘†o~ÏùVåÓ}™ër®ÏÉqââz<©¢ª ¯ØÄ“4ô!ŽáòŽwñ!”ˆ·iìÂ0Ù?”^¯ª½÷uÈ“Ä}©¤±—%£yˆô©šÆ8ç’@¡›/1¦4V&ýüöКŠïÈøŽ‹a¯n«¶Ù¥vpVq
}öì:LJ¶€Ó©NŠˆ*-¨{l “ÇMvåã¿J“Þ$¿ð›¤$qgKªÐ(¼’ð-µ”.¨‚ñ°Ï}¨òÌqRS8I#Á™CÝÒBýïÓdKN=y
ÓÕ"Ž}IrLÈRÛyÆŽ<£Z"o\Ú9|”›"¤ßù>QL{¼þøwly¨ç©ŽÌÖÑö¯ªÂ¢ºã¸îäw±ÏQÑÜ«ÑæÚXx€”šf~éà^,ïÄôPqÒ¯ËÎ}£~›øNHP0‚#«Mæ®!Yµ„ÖóÝÍ=äw)1Ê›Ûò,Ó˜Ò‘Æî³çH½(~bʬnŠ¯EÇZ
$&iKõ$Ê(Q°Ñ}&òÕˆ6¢òx€'(•cS<@ƒEiƤ…Eƒ‚Wia¨%?4]ƆVIà\ÎX_”ÆZ ¹ªñ^I(\Ê.›’P4S#i&i–D]žÌ¢RÚQª÷¨cö€4Ë'n¤™¸–KÕ\/íqõ28n¾C£$ËE{GÜòVíÒÆåø$¦å‡ ˜2ÓbÌ]Nf!Œ•Ê©–…&¥Ù”†Âš©wÌ„™®Kç¼ÿq^mÙŽ„ p+YAŸˆÆEÍþ‡¢Á13÷ܯˆ¶€EY„Êž9ÂüLÅ[nž„ZÂsZNÏiYŸÓŸÉ²¥—†,Ûš-vâ±[‘>bKn3–ƒÒÎrº†y°œÚ¥{|(2tE¥g^Š +LÍä•(4Ø:â¡×U³:¡ë¦ž¡«'£Ã®þعpS§‹0Û“ê+F÷MæT-ŸÈMhy<Ìï'ÖÄÔ^®“jå[‚ýúÐî3Q¤àc;*
L{dc{ŽÊ}%”ÔŸ#9µ&pEÈñÿŠ,¤²MIeÕ.aJ0.ßïo&2КZKêþ ýYõãˆ>fJ°ß{Þ벓H$™S]þ-ÈbƒŒVŒ]»ì³M€UI³wé_h8½ä*®Šî¤J—*õj½ZÓ¹D•`•„8·3Æ>€€zó‰šsØM…^‘á ËÜâB°¦+õ–ÏÜbÕbjC”ÉF1“U»?¡±`t´@NWÅ~wfübüBOn»öÖ'i8YuL•ôEûÂJUÕ<ÐAíQ¦¤šÁrUGÂÒÉÙ¦HR¨Âg_$¹ë"zéÜ$¡,{Íœ\."¹ +endobj +638 0 obj +<< +/Length 16815 +/Filter [/FlateDecode] +>> +stream +H‰”W;®$9ôØ;¤³nAÿÝî`½5hgPØÛo„$*%¦ªºƒy¯Å—’È •W¨åÊᕼó×þã_ÿqérŽ4–êíåøÛÙ+Ó^"×É[傽uìñ/c³¥Í”R/gñ7뮜_!¥r9{ä)z¹‡¶š1äÚ¿ÉcûÚgqíS›Ü«ãË$gøÛàdÚ]¾?ŽùÊÜŒÈÚ¦_üa^9]æ#~ *ó‚{O?–{ÞÃX#ð¯ ÚéÜ鞇3ÚÙSDÃ9Wè47ïÎyÿŠ6Zû-¶@K*5^\eï¸.9ßËú29¸ë§ì ±XÄ_Óåù»¤«ÐŽ?æßǺmr<pù†÷!‡\Ë%+‡+›ìˆ;’ Wq/SÐ<Ö¼’…]Öâ[ÛäñGSÃ4zn&h<e¦X1‹D +ƒybGBËVÔïakÞÓˆk®– +#pÈ ©0HÜAܨ‡>6×ÖºCO]<èÆjó¤{G‘]¸µ|k“” Ž6‚•Šz¯D1«NWå±t›Æë6ÛË;XÛÖ
÷¡¦Úº¤]Má"â¦à
tcj +Ù‰ÖæET‚âh4"<ÓXÛcBI×Eq¬œÁþ8)%¢0Øινøg2TÂy¯rwƬ197´j´/[™‘¡e*¥Fî»Uµ¶IªeªlCç4˜ôôzL·¿žHxcè'
Ɔ“c'ïùƒ< v×aÁl:lë·ïBTû&}¥SE¶GFÖ¬ýàôíwßÓ®ÄYã«ß +¤ŠìÛÉØ„?Üëõ¦ÅXH€rH¸“én,-èÊÄ/êxIXîÂ)‰§ ¯-OÅÐÊfVÖÝo…À_›‘H@ò¤êÒ•ˆ¯¾~›¬6×J.é9z¬Ï}Þ»9÷ãkz$Ç'Ô,ú1ùz%Ô]áÉüÍChåkDêMùö^o+š-b”<am¢—»©Sµ3/
š0ͼˆGÈèˆÌŸe@®é¢‡/oàÙ‡ñC©ë~øMSmuvû¿‡wÂ@F<Y«ûÂ9ÅÓâ©þðN©TµÛf¬T½&ÏwJ¥ +v{͘†½ÚÖµçmX»“ôõ-ðÛC7Má=!ÙÊx¨×êÐÅ€qR^õ]SÓFšÄ:ØÒÖíÜO\¢Pˆƒ<ÅXò„1CÄÌõF+Óð@ô—CRÞo’õÏá^sgù¨¹Ëµ/i†„ª¬¡…ø^ +— ‚.2ä6Âà6¦`Ïuq3?m)Ibj/™zI’Ù¬šyÆ3Ö„ñtcáÓņ+ó)![z¦2Õ¿
Ÿ±Çðv +6TiŽ¨ì³¿×+ÚbDêñDÀæqÕ+Am^œ‘§¤8;ËVÕöúî ìì{¦‹â[ÊÂGOȱÕ1o0¤h®§™41#?ý”Ò^:a/‹çÑúZ‹«ÛÊ{Ö!‡Ùí¹“ +ˆ;&|½#¸àbSM™à!ƒÖ˜{ðeR)¯ý™dUkí$+O¹´>åt!¼W£‹*¥Ó-WvG±ØŒ4Ä:ÈVrhúcv®í~Ô¨)µQi;$ùA*Jn¯J]³‡ÂVůZãÔ>4[³:jî´<°¬á|Ïm*µB3âË{ªÁΊëÄ18ãL£šjL|è,@&°áÌzÇËÐê æ:slÛ¡}?µA(êù1÷1y¥#ÚÈ' +"nYoYM³¯ÎràurŸÚTóÒÎ[§ZP墫éTr+ÎEFœwsÄmž½W¦û:¼oL 4D³Œf¡/Tuó™æ»'Ú¿¾C#•Ò5¬Ô[ p/ +ñ# ‡T¥®vÚW&5ÈeÀ‚a0£Ôa(¦$Ù7T5ÇëFOÒâÃõ€j#ŽÚÌ. ü +òï·Á«à|À}ºéìvùY}©oK.Èw'N/Åðãèû_w¼Wÿïï¾°cùïÿ®ô O¨÷úÄ]ŸYÛ#ìôN‹èÔn$qq
„û±m‰Ôö%ªë±§òïkJçÇݯm*Y¼o’Ñœòd#9‘lÛ¾<´Á4Œ¡ßS +•‘ö²cL¡|¨½C‰>Šø\ë'JxÆ[ìËVô5J"D¾Qs5xbPù9êw!8GtØzd±[ë=¸ÃíRÕÝžêÞû/8WïÞ|Ã¥xã˜<·ÂPi¼ù9é³€ƒÆi†hïMMcâèìl¾µi þ Šá V=$IÂ3CD¯G<d0>,FTšÃ&ì5eœÓÐ5 ß5àPSbh{TW2‹šÚ\ÿC¨y[mŠ·ÞÀÚÅEH¾!¤d&©ßûg’ZKþ˜þC‘<ÊèØ!~r‡7ÅäTfb¼!°Íþ +I÷*°O7Ö<‰Qòˆ3ž)#ÙmÑ*#Ùµ" º¢Xû’Šq5ù.kÂwX¥úZåiƒÇ/ÎzäÜÐCÁn#¢& "¹6gsΛÎÎý¹>&@ÏqWË®Þ[õÿHŠÊÜl°ÈÞ(ÃØJÅã]GžŒ‹Ïà&cK>ƒvá—¹îô3‹ÛcÏìF°yf˜+ ë²qcÏA‡•âc‡m¨ÛôØËá
ÔT£åRš…¸`èôÍ(.UåTj2¢D5°>3_ôit!Ü„Xµ%?„X=g?˜½žeöÁÛz`’/›˜6ç=> +8ƒØY%µV¥Õ[óZ
©<VW@£äˆ$`ñíÑ9Œ?-LX.î¡3{Âê@0ÜoÆVQ}:d—Ø·ÜØG#Œ%ÔLôјÈ
Î<dPhÒ&ð?ÕSQswŸ‰lî1£‡ÐaR©q¦ÇÝ‘qwVÖ¤½T!#3søIdóá~‡uIîD8kêá10aZ³Yüú•mÛÍ'êŠÙJ÷ŒcÆϾɈŸùUÓw"W<œÕ}<±›î‹i×È!:Q*‘§d’žepôJÊŠ±·RoÜ¥¨íH{™tq2s¸xĉ$•WÌ¡ï¥ÔEÔÿ]Íã×—êG¿¡p*“.ã¡Öš‚4E=‡ó NÅ
—â>sûŠQ:ÓÓÞ4V˜·’¥}=1lÂè_OߺÌ3.$gÜÀ*/tá9v("›Àð4‡Žƒ®=ÅúK$¤§ø¢Þ¸Ï»Ît©ö"V[E‰œ¥«œu9çÜ*çœ?È9V9çiŽ£¬®§c–s´ÍrNÒ¯kôô©OsYû½bìÏò8í<²&Ëþàû¼”ðµîÁö—ýKÓ¼?ÈYàSu¢EGzê©ÏPÐpÑp:b®ñ2.Ê‘´ÑyyJÊ’¹]Ÿ +²}êy
ÚW˜ÌµWÎå֘И9«ëJǼ8wËQ×7ògkí!t¥†bZR;“äV“äV¨fÉm›½ã§|UBÐùû£@í²‡Øbº>b¤¸]÷uízOx\A«A}D~§Zß)V¨ÖÓ^ÊCü*I[Ÿ’Ý£ñŒnRµõ,;µ
ƤñZÕžz1–¡=íáÖ’Àÿ"På¡O³vmé):ñ§ˆÏ@{£ý¥Íåî9¨ïUw´(-5iô ú2úHÓ#ïȹbV¼}äö:×ýµÊ3oVy&Þ-òLŒ"ÏRNæLl?“¨Kæô½Û»GçÄèWýì㪟×vÒ³FO¢t53+â~ήƒÇBÌš·Oätb°…ÞvèRp$´KÁŸ%Ô¬Rp‚ÌŽ«©ÔµØšÁ4Ã=ÔMWÿŽZg唺#'Wc¹ejå +¾Í4|o+£€h0ÙBET*4Žq1Ck@›Û`…oý»øÜ~ÇÐÁ]@€)×cÖ÷ú0ÕŸ‹y¼ß‹î
Õno~¯nE‹Ä´Ù-·áæµ…wÏÆÍ“ÏbMW³þnVæa‹Õ\ÌTC,±/3ø aûZQøYŸCßz,dP–›Ë,!mbT5[rñYs?Rö§ÜÿZó0\ÒNŸc»s^ƾñ™ëÅÀÒ”¨ÙàØŸ¢ZÅHh=Û¡—{Ôƒsq M¿Ž%þ½Xóîx.ûÚœøQMŽŽoWooKòÜ£¯Îí×+XÜ.% +´WH|‰Ì’ç +«‘) ªjd¡ë\[¦6ÆÌ€ºC×¾6½=÷?‘6B¦¹Ž˜jÄÇÕh|ûà™üz¯Ösì®C–fÞl©™¼²ã1fÈ,Djð)ü¨ïµŸÀvá>ëƃs»‚ÞæpŸËÄ btˆ[(”Þu¡}{¡ä\„–q9¦û£´^"çå¥aìîðÌé$î¢}J6~)òàˆx¼,i©Ä¤sW’`ž³1£ +?vNœÐ6ã#¿Þ!ç5Û~à§fy†¸ä9—êÙ·’åKv†šä±”:¥‚ìr±ƒîzFæ¹ð= {äÔHºÛ¶nüÖ²S_ë¾ß¹!¾
å•Ä‡¯rå‘T»e\“ÇÒR£I†Gý{4fE#û`œ÷A
‡¡2¢NÏœ|Q¾*y9ºKâX©£Ü,Þ……[ÜèÆG®³‹\rÄÝœ¼m ÛäÔK’o6j±ÔíqÚÒB|¦KÉw•¦Š¸ÜÔ.#¸T®Ù9JK&T>¢ôÙŒK(v@vfKˆ¨ /‹]äbà°?²·¦øm[Ú«Ô‹Ñ:[¡†‘âÁ£TrDZÔÖCú8É}ƒ€˜ôƒäD—hÖ&,%ÿµy¢aÍG°3G¬òñ³•5WíPQàŒ!'¤‚0 +;K µ†ªÆG ̹Î2óœ
´¸&3oõ_ÅwL–)ɇt¯d£ÉèÈXŠÖ4íÓý]
{×Þ1iÂõ‡3bĹê!Óç§g·ðLôáâ¦K f"zÅô¶Æ¹¸Pyä*hˆ™€íBh¦ý&44ÓÆ{yŒíì"êÏÉ3É^‰×búVŒ5µËÊ<êçš8¨ãýƲ ðÕŠ°ó»päFRþÚ0•*Dä</:.Æß‹Ñ·ÙïJÓSv—ü@°Ÿøµj†Ë‘q&ûÖü89ÇšI’éˆop{hÀª…F~$=Z£aGËzKJ:¤Ó_‡t£mÃÔ<Ãö’›… +8Gñïùøõߧw>KŽ—ÇÝx¼¶+sžà¾qÁY^¡Ë´û»¥ƒ¾éjA¾Ú?·ƒéÇ¿þý6uÔgU×-=yjÛpùþMéß&ÛþÅ£A‹Ö#j¾ý¦fî~&Çrîõž½1Væë›`=cý’M°žêhNý¥ä|¦¾¶^qv*ãGܽª±´bj/yÏÒà»vkU±sUæ:Íì¹³ë¡3q@°áƒrÊ +íªŽó]–å¼òWÍv¼J0CµdÜ—Jª& Bgé“(i½¿?J˜}XëD?%¾`ÝJ¼ÕHIÞFBê„)3¡žCX“¸+]e0Vâ.ÞÆJü¸4Iêš_´ÎÉâ%‘ #¤YKdä3Ç4Œ9¼-‘OÒ¬œ¤Ù0vQW +hž±Òò]¡éþ¸ñì!Ýb>Ì$¢Ç2AB5A“ PÎ!ê2 Éu*¿\gƒLeh"LÃP£ÔD²ÁKŠ¶žÔFýçÝÒ}…¿T SšÌ2“
@!ÙTiHŽn$;ÔŸÔ׶cp?‰‹eÄKÅ|H´¸œOï¾²=¡ÏxŒ]ÎwÂäÑå¼–š+Õü +éÇy@œ3Ðò +d¹¨QcÃË¡‘,/χêø^$†}(p“YÎÍsw.0d¹;ÆLˆ VÖYÒ0^#¥gê¾S«x*O4ZˆOJô˜Á3™Ï@š8΋:*H.ðÛìÚ®iËÕ¤öÏé +íZó¨1Je…ú¡ãŸE]Æq-÷7yr ãÝ/Èÿ^ñ=^ûgÆÖW*aôJ’¸3P£I×yFŒH0t\åâÙta. +Ÿpp‚EƒÉD\Â\kÝè)¸$)@zÈà¹;ÙkÿœûÇ{;öÄ(Ø”¥D?T8îþèýr]|¹š—ã>+²Ín¢
Ž½¤Ùp>:RΣ#©CÓmDú€ý^0_«³Á¨«mƒÞ%×Ö>‡{KÎ[VTêv·.Y:-9Ñ“§å1 +ðJÆß"î§Ò“{W£¶M,WwîÆÛ¯€$7_m*Ål•{®o#`Cöš +éÕPe£Ö¼Œ5œ
,ç¿Ë80ÎãÝ +%>”ùÐJ\:;ÎXã¯gsq5 År‹+wîuþ$åÞ´ì÷aœ‰ÊíØåzjâN=R8ps^+š{QiÛy»Ä븗GŒÂ|…T‚¦æû´3±:ñfuD4M+9-¬wçZª „õž>°Rb“: +‹M··E»ˆÛ¢]ÄíC»òDé +2Eã‰s‡æCÇ…‚Ĉ±Î˜E8ÚÝ¥[P·¨Õƨ’o÷ +Õ?>UUá/ÕTU¬OHJU•y£ˆÉ2¿(bócQÊkdXè!Í—´è¿ð×åEÿVC~×£:ž‘m´NŠ™9ŠW–dŠÖÂ_ï–¨53÷]*´½ÅvŽ5s>Î¥5¼Ôp3 V“ðƒ¢-BªP%¶®JlµÁ.êu*…êO%aõÛw-ª\{Ï"Ÿ|ªùö)ßÄyýÉýùW¥œâËLÁ#O:F®ÿl‘Jƒ#k +>Ú¢"ÍW>³8O×IÕñk¤¢ø$RQ|VÇÕ¢2p^ª?jz¦¥iú“{47}Ž&†ZèÜJ4ác>Ž)VðסZ(r¦XùqúWvsÔAÁˆžuvt‡jÃÈ[—`4zà^¾Ô}˘‹„Œ¼Ì—Ò5H7Ë¥rþ-î5 +†iL
üÚºDÇtg!y ¾8ú¦ €Ñq6»éÎVªã|O§àW¿©`+S‰1ş븉ÕÑŸ?FÁæ;âò^Óc}¯ú®vî_f}T‘ͬ%®>¼û&£W +3WÀ]Å%ß"²§}{‹jxds3ö:d–—Ï:Q7–Îoç¤&3Z¿§æ{ ôwF‹ÔéÌLDæ/¿3e£]·Ä_új¼¤#ÐZiOÞjLåœq«JƪcsÁɪÒâOñ®ˆØk Ùx‹=ç†LÖ+VúyèÃÃ%±€êZî—Âu”r8Á9fdYl³ç®á¤¤IŽuMÍs¿ZÇ·óÆÓX†q*¾\ˉ9ÿžý[èSŒ@ÿq‰œ/mÆîÎîªq¹ËsNgH‰‡°ÂFW]¤mI‹0Ξ:Ú2€ÔsÛPd’ø~vª:0úÜÒª {µ[¥ÀÛy‹f±¨”Œ›cƒ‹À9w’aùB›ªŽuÈLŸrah#¼èà8%ížl9K…JàÎ8¥kL¹BÔûñÙ$Ŭ8«q½/=‘}}OtQ»ÀÅQÒ÷aÄ–à0]1¥ +ÅISS©íS S/èô¸clßFb„Gª/=ªˆ£»FBñŒ^íêÏ$b(åG×áV>Àí I
Ùϸ^àWÍavУÍt~ëÕ½¡†7Yj +rÅÓ^’Ö0,ÜÉ£šK¼Ÿ9ÓXØá“lryßä4ÞSÔœpÑp²¾cø¢<}F&Ýâ•RaÚ7D¹w9w|‡[K¹»°¼2/±A÷ +B æõLî{üÁn"=Õ|6g8ÐV¼øhRW>UÚõËÉ}Ý)XÅòžð9Öù°R¦™P3ådûPz-jâ# +.¼Œïœ‰Î{¢9ƾÂDE¬KìnÕ¥ ìËí5T¶@°gºÊ´þžv»cïÙW§÷::+:O<šÉV\£¹È&,Íjšõ¬d׿!Z¶g!PS¸ÆdÆ<j +”§ã¸áÍné†î]¤˜òûcT[˜t4øîÅÔ‰&ƒ©†d¢5‹Ú©„ŽÐ‘-çnzo±/ÉÎ +" +³í«`S+S5m¯¶D`ÑÊçeÝ.ÀbeA9:uÊl ÖTàrÖuûàA³¦¤<x¦‹Â”7þdgÊßÖóå‚k¶½ÿqÎh¯ÕFOO’}4ˆJUMÈã >l#Ø7ŒAf<Ži$/6ÚÚøФj˜k×YøâZ3,ÄsƒŸ¸YÂ&u—JØlp3aëІó¼ºAk'—¯…'æ]¸y‹]-F–%¡{¾°, ÛFؾ0šµˆOŸ€ +³¤ +[Iµïa°Ö/YØåê®\οÖêær‘[”41×Âm[µºxÑ=<oIÁ¥$Øs¥r² óþb3'ÅæÑÓuRvO“R‘ž +*Uu¸ãÇL´£)ðœ®BµN«¦ÔnóÃB#ÑløhØ=®JàQH<éA¸Š¡ª¯Äko%LŠ!U£¡/EŒì8¹ŽM׉:£Q†“¥±u¼Æ`Co“; +*W]Š–lð™æ°°•ðÏ9ê%‹¦´Z+¹•~AO’c…ëdäPa¬Q¹5”üÔš°Ñ8SI ž?JCßIÊðúqÊú1¯d¬™§A_ݸe¨I€¡I"¸ˆ)Ç)‘ŠÙÇmîüm†HîƒT0I9ÍLŽ +v>¨c]‹¶ä!…+%Yb,¹¤¡m馡òjtü¤”‰¸NŠ5ˆCü‚Ô_}øHÖ=¥øº˜ï¦º¹Jõá³Eٌĩ[8S²<ï=]Œ’ét›!€}!'–;ÌOJ$é\[×’¿¡ +¸ã® è±S:ñr©kÉÄxõ)4Ûø9ꃔ™Ò -}i
r@a6’7ŽþcOEÒÍ…wãØkæVt§_•¦67½-ën¨ybî-»k¢´DôL±(´âÜÎÞ»¼ˆþHx+FK‹)à´ì[ü‰Æ¢x…Öê]mõƒs×’î‘gg¾KÖ£t¿6OÊ:V÷©ÜÏdÛõ¶1X5röëþªaê‘™gÞ^¹i&°™à¶Y˜Sµ¤òî]•ážïJÓqÉw’‡ +cŒmŠc]ÑkŒé¡h
<¢Ð<cÕ$Ú&Zƒ&išT$c ’$’²:×ëNì U +Õ7²@)âUÆ$;#k;Œmã¦+F?¶N’D}k¥Ø.‰%c{—f‚9EOªÁn²Úyß`‚§¡ÉØÃûëTÞWïƒ%?â¦àÉÔ?&èÕ?&ºq»ŽE0Ò;,j_åÍJÊe.§]ÉùŒ/áæ”ÂÍsäZ°¥®ªä»–æê«V)Ϫ(ÅõQ›ˆïU›ˆó¢MŠó”S…nâ„HÅÁm]ã‰M‹Å¤ujl»/ǹfçš¾Qø‰¢€±³ E£åÈÌ|LªrqÅTе}ª*sS¸7¹Tí\ÕÛÒ·—H^ +Ù"6WÉöF‚Æ$.YÞ¬[‹œå1Ž®r—ÆÍ¢.8ú«« ‰§ŠU@Îñ{‚R]º°ŽEEdbgWWº=iqgvw{©ûJu‚ôµGE×i†F´kVsG[:ÞŽswÄ<°ö +$=võxŽ]¿B¶—ÕX$݇ *mGk÷Uê\ßÒ&úŸ»Ã…-g6Ý7ùÑýùzÛ„Qßá AÿmûZ’´¼oÖ›XqK3½.仃¾jQ‚uÛ¢·Wkb©™]aÝU©ûtÏœ#|·åówA˜yræÑ›©ÃUˆ’á«4êÑIt!MãS‘Ý^a_M$i/ÉBÛÁPúD*¸Ž•ê•Æ<‹D”Im‡;j«Ûiì KȺÉÝLE’1X—\6ðîmc=ѧºiP6§vÝFóíZ@–ðÅž¬£•®íÏñ!×DtCWRìèÚ±¬£Ç…£¸ Àëö‹C¡ +Ô\†¾KÙrVö܈KTîpô¡£Nú6ØÇ~IÑ£™Î7›/²^u‘ûµ¡¢™.+Úbj±¦†8d£zÇ9•£m—€¶Û;¾xñ„”Üð|Ùz9|ÜM +&4nËÚƒ"í.÷Q"—KâÎÍþFÿ¥ã˯Ž§Ÿ?Óêxö._›0òÕû÷íyœ¤/þÇó±¯1˜Ç·‘( ÝR36Z’( Te wèNJ.‹'癊Pù}>–5+pÀˆp=É•éܺGåÌ +bu%ÿÍ +šÍ¤ã/Ð^þÐg^\wÃóí½ ÒRÎgl~>ÍI·Ù¶YíÅD–qo¸±&'Åð¸“'ÃÍO[0I>ÁOs &˜CþúЬžÉ‡È•–ÖCAŸzªP*}“ò´µâ' ¬Iã ¤ŒPÙ +M¦5/`ÀàÑûÝr;èÌ5`™½ Y ?¸ +Ùƒ;ïšLÑrü£½éí2saä6ÃíE¾G¶µZÖ?êÀgj4=˜¤¥ +Ï +VFLèeDÿé½ÑúOVöóMKÿíÝmüþÿ'fÿs“Ûïo_ÿ5ݾùŒóïþ¤¡lCjתçù¤çóŒæsw.ÜÏ»?š#° +“ ¿PS2ž=KKF›—n/ÿ/_þ~ …Iô`(ÔH9|¦TŽ25ÖKèYi3+Ï7#£ªN&Ëyð–ŽÑú‘´à¶ÉǼ¨Œó"(íË"AOiã©'Ù¦nO5g°Á@Ä
\ŒtÊäd¾+ŒÙdJÙgøqvž1û윇-v¯Y›Z(Î춒ɷs¬6V‰z¹ªH¼°Y?ÐÉ¥Õ‡¹•Ø¯Fï’Sê +SOCˆS‹kÈk)WzãÀ .¯= œÒ ›1UáD$OPœmb![S*z¼ÁŠÀezÄö±Ñ±•ïªV›ï€!!4Ò@Í$w´ÚÌoá!ÞT–ÛÒ¡2•—ÖÆ +ÉÑŠÃ^×Ñç‰YDÁC'@¬´èF:«Ä›¿¤{úï“ dË:Æbe@ÓG`-À*µXúŽ^¬¹$Y!—
k å-ñ뻢Õq¢UêR½fk¹2¯î#¾ •a{†œÝh:
ú)cl´¦Þx€þ,ÄaIÁWÁ +“÷È‚F¬«ì9VÓÞû1®O
òM1©iDZ!ºd=¨4t«²ÀzM¸@KèòzD +‰3å(mõ”¤¢@¡ÎU36½{Š(YjM*- Ä{qB{ð`¢Ö¢«Ã&U‰ÌÒÎbŒâÄ(ô4ž²köq +ܬq¸âD}YÊßðùtKŸbá5^ÖC¼ Zg6 +º£dNöJLÜNŽÍPB5"pûrÓ´Þõ'6Z9ÑVÞÉ®L)ç!ÜTà.BUp½€ÊÞ¢jã€jŠXÅ +0ÁÒTÐ8EK ö8À*£Ë±há ÙÏãÞj5XöÂÐØy}šÿßœZÚ\÷tÅYÒ +ñà)W‚½œ–›½Ëš—N\#¾ üˆª +-+¦qVuºíy‹P„r©Ç]MwéSÛ:¨ÎTE³Êšô?²Ë$Ëq†¡WÉ4r8TßÛ +ÍjTö:jÛ˜&¢“DtxH¬ÑòA¡* ¸*e/¨ax +`ÈÕQ±äåÒ˜
Zjð¨ýš¦Om'ÀátùºçÂÌNèià˜“]2÷§¨ÚßÝàgÙÍ_@ºŒ÷¶´³ìnH‚Ó6Ĩ8›6Š}f["óxxø,:>òàîpÖÙ¡Kk󌸣~28h3Ågc£/6#>€ô‰WIjæeð0‚ÇÛ8«ƒOÂjÿTõ"RÛÙªy¹Dh[—Èà)r&Èà™›VѨ~¸@r)"††D–!ÓˆlàIžn®Zì؈8¢‰È’‚Y'â!˜TŒàÔtêÇñ'±‘Q‚Ç3~FlÓ¸ +G$ü=O‰›ÐE‚Ž65„[؃dc˜rΦϛž¸Õüe¼~_½5V™4eáG”ôRühÁP³ó!"ÓdëðI˜„ƒ °,Z>ˆ48î'GÍÃE¢¬«@.$r/cÙè—ÏŸ²^HÆÖØT`õ¢F¥½Ht‹fÄorµ#D2‘ÇÙÓ¬ÌäÅd¡yÅ$¯k +%Ð\L¦p˜Ä\+£vn¬´'ØQòzŒLd.H•² +9#(ã"±‘©_û5(Þ¡1%P7¶ý` +endobj +639 0 obj +<< +/Length 25389 +/Filter [/FlateDecode] +>> +stream +H‰dW»’$9ó7bÿ¡œs3D½H}ÊZklÄ:çÞ÷ ++ø˪5÷ýËÊU.Q®ê}àþ5ü(6üó£ÓíZ½7íkþ«O˸>m¨nùgÄeÞã³ÖµÚT_p"²Ú¼YÇ‘¼'Š8q•Ëf5"ÒG3"d¬ã RFuÂdµØÇÛUG[Ddµ:‰HÁõ>®ãÙa!@¢à+›ï¢¾ +2'•"1³/š/Áž:{r…ÎŒƒòak¸oÞ½a¨¤ +¾“Š +Ña>î]0»`~¾ª=¾¦Òc¦Œ}GÀ<ÖIî,e`ð³M–|~î¶WÉ¥¾›,æÈNµªÃ½Œ
ó¢cb^ôš%V:‹‚Èòg_t8_ +æ¹ìá/zÈMöNd+l¡ŸsÔ(’52¢ïÃ
@V%…!gÝy‰Iãp3†¼ :cøbÑÝò±r[£õ2owRØ 7»‘¼7†rí¬êŸ™âŽ(qôš!×Ç9ŽF•i¶ ðÏ
1Âçz1à ÌQT<]”™è¬’e™ï€m3Óî°DH—H†Q($Qœ?mmF‰Mùå¯ð¾‰êÄMFÂÈ.Rrþ9—@´iz"ñvS3F‰=žÕ5{h8¾¢Ÿ®é§'Ð-^rÎIµ™æ÷h-Å:Ÿ¹±í‰Ì¨9´è‹Œ×l0ÒKшÕ1ÚEƒ™
lJ—£Œ%ϤŒÏ…ñ²‡šÎ~äÔ°dfÄX÷ª4ì|¨Ä›<ëª^¼Õ}cì²0#A7š%3Zkñ¢tPÉ°l; ã
ï5Ì÷ð›Ì¦Šêt·›—¦éFEž÷œÊYʺxƒ•*«®ò7~Á!¦O#žbi?s}\ßÉ-šÛ”ªúÑÃù6òÎIZÓQn)ðÅ猋Ÿ–æïCbòÍÔ¥%O6ë}ÅØ ù!†f"‹3քϲtì>“ƒÇ–`¿«)Tfs!lUSȳÍ1-c¶£ÖÅL›zbþð3c²[Úìì'®|\“p‰€ª¹/—7M¶´9 +—ËØ釛• ßlŠ(2Ý+
FÍÎD÷Üê¯=ö#‡&ÑÒRPsîÓ4¦ô û=º07e˜ºïLÈ#͹ _¤³¾b@s¸–b‡.Ê£ Î2ù5™ñeÌþ0c1ôm’”V†D—ÏÈ[”Ÿï›‡Qøo7Ë^öF÷‹˜n¤¢ñ1b&1°ˆ›˜eœø¦›™–I“`t¥³Õå°˜¸üO2ƒkEI»Ö +øíɃÒ42A• Î…¨w(®<7+I%œS¤ÌVŠ¸Ü‡cðª§÷cÑCH®~®»an*TLEnö2‰¦AÖÊÏÌ +3ÁϹCꆿž5™Þ^¾"ökæ–Ýá&h2<¥†…žQÀqè‘,¯9î'Rîͨ@LÀlî*¸;û…OÁ©gäjMîÓ8m{çÄ…úÕ³L-Ú}GŽ7&¡›œÊÀ¡¶(YAeP9ËäqBÍ\âáÆÜV¾/-g¬UOÿÚ¤-sK1:¢è‘è²)¿àý¢†ƒ’ŽÏ!ãè22cw736‹:.f}½ij¦ª¶gß:Â!˜ÌÌ=IÆV +^eOÀ‚7±R^@ñ†[¼ª6 +:Ô®çì’¡ü¥[Åõ}ô†á½qÌì–›{oÊ;¹ž«7›i?ÃÕ´0avkÔ89`•–=-OÈÞˆ({š,©»Ê‘ÞÍÁ‰tÎMÁ)vÛN%#þ
»^L ¬#_=<Œ{Ù"Ër)qô«3€a>šgM|»o]—!Þ•1>%vĢΕåRåñÛÅJ +æÏs%$ù,gÿú(ï£5¹rQ’ÃfÒ›À_«58<Ÿ¥×? ›Ë^‰-W%Êî
óDuo gêMÏ‹ú ˜ZøÜýžj§ÈËò€9‹ ÀâŽÀý[b.‘ïý<5ÂÐj–b;Öä +`½šÄ
ÊI¨ÛòIëŒìÙ̧)a½»²ˆ^x–}[. ´«¼:¸ ™ +NŽBž†ðw +’ŒøX׆>àçRû? ‹J±ÆÛÅñ<gâãüð4¸ + ˆ†íÂb˜ß[[>J‰ÕÚì“=<Ð÷ÀÁOµp:9.²|ý(ä6Ý>·íÅNõÑYÑhVÑ´‹À_n—Â6‚á×ËöÁ8ÔPœb‰Ë²ÁÐYj½Á϶/ü¨f»÷¿×¹øMÒ‡+¸'éȽà âQÞB,çÓM÷ +”kÕ;#ïŸcát•>{zÞsîÿÑ}ͳ¹ +0Áàz8§ùHðcŽ + Ô8»a&<ƒÒd[€ôð½qJ½>ç>]³Ç. +“æ¶Faâkijc’㈤a¼ +p¬âÑ÷á]m®} +Ι¨Eå +‹lj|‹eG!Ûe”£•½E®ðuÒJ(¢t@z+B …=( ‚ØϨVS8@: +? Œµf_¨›ƒÂVö‰VC'cHp”¬Jì„á…²Ò”e85°#U•¯£ÞQþ5¢d%áÁ”ògSˆ0s¤Ðáüƒ˜³kzPÙÎ.%òôÉ/Ñ–¨âT|Ivµÿ1^-Érã8ð*u‚~Aྀ×oÙ1÷ßNf‚”Jê˜ö¬lÖ“(2ä‡hÌqëŽÏkBüÜÖ}Íà<¯™@þã¨kþ)«bédðî˜WPCƒ¶Á†üBr4”Õx’ÈZ’Ž)®ûsvÃÖUjêL5øÁéú¬í¸q®Ibiþº&ZeôK&‘•¢ìË‹œ_G}\óO´svù*:ùô,§.èm,ͱŽOš£M/˲JÌ +¨.Ȼ߷$±YaÉ!·^EYèÜ›`ÉÓ¢÷¼$SG÷%åUqõaü¼ú¸ãŸ2'?ó¢EJÒuI|@¥\¥eIŒ¥ Åk3/$ܺÔuu,Ây㥠:Ù¢¤ÓÇ—3Ǻø»a«Ìá¯+úÑÒãæÛÒ¿ú¸äÿΕ؆ú‹·*5ÿ?g° 4ª!¿¨ZD•1Ý´žg¸œÑO^—[-Ͼ¦’®‚»»ÎÇÕ$Ï»‘±n…„ß‹Ð1ñùÇ1†õß³#¼©£]¢ßÉqfÆ?ý +A—múXë`\1y +ú`¬Jfƒr"Œ ößšbŒßJavÅÅQv><÷çØj✽íé' +[|ã,
Îâñ¾c#Ç¥y@œÀDf¸‚(eo€QûÁÚZÑoR!ö³°^®1†måMJè킺¬Uuv”Î,›ZBp îD¶0c°2I33½?Îe‚Þå4ÔMã>‹H€Ú¯5Ǿèñà£µß +_X
~Ú8ãÀÑìóBìèê.¬ÓÐxv¸œÜÁš‘I77ÖÎþa—F'Ö††Ô89:åCÓå5F2vµœê¼ÂÈUãìÚéhvgëþnPP"ÚêÔís‰vaLL´¡ƒâJ+KˆÇËh~X£T +ð…u.<I»L(Þ^Êc"]½ß\n‰:.¨iæ€þ”8l‘ô—ϱ֙U{ú*9G>²öér Š0hfJBÉ)‚Iè#‹j°p¸FR¾‚øëCÊÎÃé'}>¶ÐäZÕ £4Ь‡©ññ•î£ÍÍïê}îQ/œÇhiJ^$TgÁNŠž)´Ä™°oœkö°ï憰UÓº$®¢Ç§B7Ò<ê„Ø^PÓÚ +%™-CÊh°tõddÈϸoÈK¦ÆF_
Ì“Q +ÉÁÜ<«9c^J9|cìsQ‹¤!\˜˜÷`ÃK}Ä…yËÂÀ-Á™P{÷‡èûæ]˜/Êâ¶2nš\`Mer1˶k¿·—K?=Ѫ}ûÒí{sah_˜;¦$Ô]Zi-Md¥Ð'æôqäø‹•ò%î@c-ËÍTW—?q{@¾ +ÙšÊf_})Ø0?mÈ™\F?‹#8T >¨¿Ï\‹Cí8:”>NhµtŽj‹^)ª’\õ•‚Zi“xD\ÞѤª@>&Ÿqq1ukÖƒ÷(< Œ÷¶¾‰KC² +ƒ£©°mcÂ|xÇßÊ!Ú`#hma¬ó¤¸ªEÁr(†5mëÄðIŽ[Í4.Ü
·˜Oá–6 +·gæJ15þþÅMÙnÑ¢‡›âóå9‰©ý\v¦Ð¡¥ÿ)C©ˆó7{pc²p}Y;·‡ÜèUó×ìTÝZd‹µl\ÊŠhU¡†E*©À¹¢ES¥8_Éä
Y†¦uŠÜÖÄàhÝaãÇ#+3n}ÆÆÙ=Sp{þNÔ¢èVı¤ÚjtÚˆE1ÿ2«)BT/ò7J¹U·2tÂ`&¢%J~m‚„þSmlM¿îü¼ +N¬Æ˜u?ô·:×C¬i²ÁzÄØÍ4ÆÂ…5’½fÅ™£Æ!FeP¯(án‡*oÒ )M +¯¡Y4}Lwe+é\qK
”Ý…²OÜ`ñ}tKÿYüÖúðrƒñempkX > +•ÉÝAϦ +“3€ƒ–ݺ]f {Šðj9•ÍV¤B‹r1³žó± +£@“×B8‘)IhKL¥¨üÃŒ<:bS0†Y}öš6Õú{î²ÒÝ›%+,ÏèÔ$7¸ÈÔø,µÙ_djBÇ/
0ôLÍlOv£šbêY*J»PF‡ÑTÛï^ž¶º»s\]V8–žPš¶x†ãßÒ׸Èiûšœ@6í£OºZ¼p’ç4âµ'·oe«SÒª>;9Í2i|'YýÝšV@û>ùšêY“‡´lŽdIËü§Ã¬«Ð9¸Ýë2‹KÂÈK²—
ÙÏçDý2›î¾§sÅH@·bv¶¸+ªƒl®cKE’L%‚¬Ö½¢~•×ô½8k ÀÑw{µ3‡8¼xÓ4uè©@Ø…p¨`RŒlAŒ£ö·¾çvkf“½Zýšjc(Jc<&ú3É:±&Ïb)OÿTÙЫ¢RçK˜9†åsŒÁ‚Bædkâ£úØò+àòyŽ´k1÷©–†Ÿ[¥m|½ XU=ÁýðÙÚvp-Í?0]s5Xr~Ù!E‚Âë“K¤ws‡g•ÜÜ–X'€¤§½NýF˜¤`RL½X®O”O‰¹²TQÊÖ\|mÉRåÙ‡XÜzV
ˆ¤,^IyFî"Çn,ô£@
l×j‡œsxŽÚÒÇúDUÍUrÔCl;ª5së•NVr_Ê·X„ +.¹'W½Cƒ±×P^ùŒSod³øzû>ö‹˜XŠ à)kü«·–fjúC¬fb² yYQÒ¥¼Äì{<ÍTŽ½ÙÜŠùGUZ¾dB²ÕT–Ú"TPP®]HÄ,šV>Äx‚r«°*AAŒxê ç ÆgtÎŽ\BqÖê!—;êjØ+Jg`¯S¿€Å„T"ÇÎd1s`´«5Fû*1Y•4]æ[S4‰úOºÀ¤hqk©ªÆfÜÊÖæ4p{òAe
Mÿn'o#ëO¤èS™j…iÖqd;r‘e
|948VöšGq¡ï…Œ‚!£í‹Ìµ•óH°Òy‡F:¡e#{û…Œü§ ÀAvˆØ–u{èǯêê0–@RVîýÕ“û*Ö²u¢,µ†zÍE + +sô8ÉL‰~)¼ø)·&ç4?Ótö8 +ò¢úª¶S4R±k‹žê»zM[í¨ß3œŒŽ6ž¹€æX»»Þ_^ƒÕZ&™dE-:ÙÁuÉ;D‚ScäL1W5 ¨c5W +¡úŠßÝœa/òÆ¥^òF¶Ió·£¢Þ¶‚ ³ÞAw‘Ò;Eíj;ÄŒú%è°/‰CãLP>Ë®×¢2úWæhXF¹³FΑÝÈ6gú‚9¼* +Ÿmæ,Q<àÞd_¥ïžE<oЋҷ6ƒ89 +bÝõwqûZ”oZã‰Mz¶µÈŽàÜÝHûZ^^ôúPåÕÑTySã’ò~ÄnKû¸¹¥EDƒ… +8mcÖÞ'„c…¹Kr„r_Ùü \vî‘Uý§¬3¡A¬*ÊÇ
P¿²ª îµ>kr¤ +IˤƒÂWò-¤Ê*€ýÇ;Lö¡Z”u´Üû¿©¥Hä"…Ïvz;ÝbÖi^¾póì‹á#ÖT©©Ë󃽙Դžð)AB2×£Wü¦UྚäÖåVË~jMŽTkV0)eÁ‹¯Œa¦IŒU:Cð˜äV±ð=„ëE û0MÊì¼Â¢þÞe‡ÎIQ©RÝܦeØÉPuLLÊÉóÇ¿ûŸÓ^v§à»‰ÐG²|Fö‡Ÿ‡rœó üÁ¯EEòj‡Õã—vx9ñ³ïäè“íË°—Ú‘=/¾:ÕºÇHÁbGç&u—Ð(ÀÀoî{ÄM«ÛþõÑú{[Ck + 2,F¿ý.§NË9´ÊÈi1j
yÛTG~ZêÙÞðåW%>£F¿¨~˜žß’Ñ_> +Ÿ
Ü–fytÕuMšÌ +*
ç\9ò{}9{|Ü›d€˜âµ¡®$“e±_lñr_Îr¯¥¾EÃÙ+QyÕî3þÐú€–‘t¨Å-¯ù@Ó:¶BIJº§8G[HûÔ=»™sG°•ÙÙD§¿§VÇÐ`åǤU@µtÒ²µh¨áéóH‰©»$2®æM$8œi™ª”H§Aœõ¹Ê +ÔÕƒoÜëËÙÕzoJ’òAí!N[Ji;Œ¶VžbÀ5çúÖÌÇÝ¢LƒúÍêƒYŒ£¦WX©¤Ò~ŒëÌ1í¹gx(Ø®ÞScJ†h¥Ø8Œ«’ÊßS¨3/››Œ°H²Ç¦ôñ+{SGyF)^È÷™ÂªÌÌ”<¬°Ìr8Rö?C3Peá³Ë
3’ßu0F]Û¬ï¦Jw^®üžÌ¡ÔÁÇÉ™ïµ.bVUz+Þ +L±í¤ÿùÍê“1×2æpZ+ +ÛþõaîL^Oÿë´À¼zïËɹ•2[ÎÙͽR±Åœ”ƒ™%ϦGÿÑ]cÑ‹øx0óÅDñ_Ì÷™ÆΣâCåÁž tçgþ4Ôa‡jXZŠ‡ÕØôs}I;j{ªÝ¹¶ÕÚ:gyÐxÒŒ%·ü#Ê®oÑ|›Ù^"ñÔo\Ò2¡é°e6´Ò‡Y'Ãw +CFIþüfõa܇Êÿ§2ž»¨¶“ÌÆÃ<iñ¡ZLýaf—>Ã̆B&éð®¹*a¦zÎü=uºÔa`Γ™>•šúÒš»Ô̘iƒF¦z˜ï3`ÖL¶Ÿ)θ#Ó“¶MWѼæÆž‰ná}Ïæ\häîÞTñÚÂJ«ùÜÕQáo\S“SŠ!W½YYÏÆÓrLÀŸ´> [ÚªñèEß 4½ú@¯&Â-ó44&è@/ $ Ípéœxù+Çý÷”* +CIζ‚Ë)KjÁ™úZyÁù>¢XQ5f½7¥u£ˆL+¹ÆKsøâåÕßëÙ£øÞTõ’²4$$êúLôM)wÖnN].ö®Æ¡4Ã,ûÏoRÈ¥[1¨AýHˆ²‡O¹a.änB“;ýJßz1ˆB.ºOÖVç±LryPîT„”å.ºœ¨6‰ºï:4nkÒjº+øa£æ¡ñË´*
/È‚›Z²©ïSÔ[ïòbvuÜ›*Ý¢Có–WÕv´ÍC@á/^[Qرâ-9wY®ä©d™2¼†ßÉÂœ™1Âm|“„y;2h¾ñ;b<8\Uî=*„Åà9„iÍöAØ»YÇâ²^ûî`\㟗ñ}›Æ‹±-]SO”ü3!³AÁh¨¬§a“¿*1ƒïõ…œòï¦J7m©;k&}ü¡j;«ñ®½?Ä–ƒ»d¾Ü¾SãguwÑÕOÈ)b_‘E>¡A”û?3—1á(½º”÷F
uc§þ]éCe悃YºÄ™Dq\ÆRx2›ª,®†öקÊ-&ú¥|Ÿ¾LÜ’øA·+³¶S•¬%k¡Ÿe†bĸ#c(Ó‰…Ž÷^°öè=t´Lñ;ó tdÖy–WÇ36sẆ«Šh¨²Í͇ÔòLÒ [P Ë'rSGô k£tþn4‡!o½Vüý¼%—‹ò‘æwK3¬™¨—ÎðÀE=Ù¦Tõ®XÖ{(cPî¡Š—ò}ÊlO ¤™qW^ÆžF0žë¨…•î +ÏËs}ÛßgÅWÔ£w,8‡ÎZ²¤ºI¦mýúY/.)fÏcO›¸¨c9cG™¡Ì_Æž"Íe\š‘âzŠ¯S·F v+JãƲ«–‹]~•%÷•¯$kdÔsï<RC¨%JÁï2ãi=ÆS¶B#jO÷z‰õ./a[Úûˆ>[/MÉd贄 =Åb®ó£Kò[î.Ù²;à7¦áÚU·%ÉLÖeú&zéS-¸•ì{^ºnmy† ¬k4öc,dó<Ï ?1¿\©K&r®GØñë¾Ï€¸³Ù¡íšáõF´,xçÛ*ã¾ê +Ø{¼J‘÷Ðk+¸3z ûcÏõå(c¯˜_’„KƒðLÀ9iŽ2Ä÷Zå +@Kß…uhÈuÝ„Rh3ïÄl+¡wÂœ™‹`Ô7^wG1#rl˜›!™#â̓áSMûÁHãÖR¿Ù‰—ÚLxS‘9éœêóªJØ|ÒS23Û¨ƒ©Wõû¢rAR¦)åü*[hɉN¦ +”E‚¼3iÕ™_ Üsõ¼ÒwègI +TöQÅWo^åÔPÃÔ}P©^¾4ÃR7–bð‰Êá òP¤ìÑMWYšÚÊoùãºTøHFYÞ +«¹QÍ,•<žþÜ»¹«*…ªz®PnTÒTÕšJLý^§êdõšn
¬ ¨ºí”UmT\Bî.Ï7TîÂ3ÒӨ𨦬àeÅuŸ¨„s¼²*‡v¥@ÕQú¢"fàýqÝ*8EfÍ°0 v8y°Òƒ™9ãpjÑEãWÅ×M890Ñ8áfMÉøûÔnLÅÊÌa:R“ZÌuò,jPê* ™¾þžPS‰¯M÷YS’:Eøæ‘°ëCž +Æü•O{XÕî5û2ÅF•†GÊéëÇ]áœædFvÙømNÊ]¤.…½ÿso¦¦£±§I¯†Óš^×M©ß˜*hƒ“qÀIÞ…šJ×VqéjáÖ¶/P&Nûä=y£—<ßa͵ץ•r¡LƒÁEJ’¤Ì¬Ÿ—}p™*a$lÓ(ÁÉJmýPjäfF}P×Ô<hr +É’×y'“mÇ¡4'ªÃõi%µÌ˜ä©jQ¹™ç¤`ªÜ˜ÒðWþaX8Œt7Üè’^9Ë[BÍkz·øŠö%s‚r[>¯ûàÔ:‰T4àhW&».úÞAµ0(J–ë*û7 T5¹tá°ªcíT V£ûÖÚõÈ#V+`
—2ÅWÊÁŠ‚¦ƒöñÎj]¨Wœß¬DéóÍh]UŽ°ª¢8D•¨qíÖîƧ¨õ7‰·‹ôÇ´4ôA«6Ô|L /$ÔO¾o.¥«bI¡"é<´¢ºp#Æ… +ò¢Z#ëØz6‰ø‹Ö$E«ú\û²U— +Kw§Ä‰Þ'5,Ò4µÈÐ*^7ÉÛ–¬ª¬ÅX¦±aIËÇ徺É8Èå +ëðãºTã¢ür«¤×(Ôwßý#
êmD[ûsïªz…FãGîÑl=Â*æÍ;³ZÇ‚ªüô)[‰¾S¾ÕîœÙZ4HÉ[ªùÀêáœÖ,vÀ² +:³oZ=Ø“Ö¶Á‡–ç!í^`9ÅìCÕu÷ä+?pa¦˜qlAGh×—½³žõ.Ý®˜etíÞ¦5¾/» +š“Cµn+Š|" Ù%IŸü{z| +“ÕG1‰_{ÃUb(º¬‰àj˜²ñâ×cðPòÀ%:¸ÜÙõr8%S›Aëqã˜hrøÑqY¼.Vߧßâ¡ÀR,5"Ñ/ +$§(}95+¼ÓØ¥¿sk®tr$1:eë~Ûu0ëpŽ´<ÃlÙ?Œ¨ó+l$¢Â‚ÉLÉÀ
Ϭ¤•s~²’ûM:ìaå)ÝÂ+td˜>Iß?XIH¾°ðö¤T/JM'È,"}0µ)eÄ¥¹"m¯°í7ëÄvN1x•ñÂ4«ð@µ9ÿÕ8¹}-L– zü±è½ Íf»Q&*%IÌïd/sú„¤,KǶ·òØÄwÚEHDœ@É>íã¾1›I3"•ÄŠ•F¶ìkìv¡c¬¼
éRüeD©"{ñ¥Œ¶†3¬r:¢ÂßZ€c¯xoÊü•j‡VTCÿå'c7Rú“öú +‹FÇ×ò/?éRc|¸Ü‘Â[´Ô5Ëû2^àïÞ{øe~Rc¢Hq-Ý0sîÔQ’fTa¾¤™ZÚ× +³÷’•C÷xóÞÒWB^KSrýÏ{H¢ÈkôÜÉt€]É'"-'‰i–~"RÖŠ6@E*² #,›.¢Ý9:™ +oü‹ðômZ1VÓáy@~„GM»íÃáq°lîø\uî’Æ+>˜\ü<JD‘0ZЇ’*IGRçBô™ˆÞN®Ð ªÜÑÌრ\´>Ñ|«ÌêŠ+5Ï ]¿)Êvb=.q´ŸÔÃg‡®º…íÞÄ"0ö„y-ŒúfFs»¥W{}¨ÅèîU×QDê Q€O¨Ò¦–¿ L\ï.Ý¢nþ ùlé…¥·øà\¿ïPt9:>žóħõðŠõ‹U%[܆5]HëyÍݵ)÷åH'˜.±1-ŠÉî橬.'¢™RsgÑç&
Èú<í¼Wö`È©šoX®&P;žJÝ› ;3:–w¹ÃEaÜk=œ¯vŠÇ›Ä³*Ç+[HÝïP7¬ËŠfÏŒ+©×…€ªcW%ºâY«Eº'îø‹xÖ{Ñ ó×gPñ$˜Qs3\âEé}ï´¸ÅæZ»““+³&Ù^²ÞnÙã9b¬¹¨~‹aû¡hÖåzLC]˜÷«À-š[æïÝ,¦®ŠƒÃb20)š (½})MN8»‘ut3ųŠ®¢;“0ýÝ•FÑ+Ľèf +DØåÆÀU_kƉ{Jº7±õ8a¢ÇeE¢%F‘똗bÏ( +Ž,EtâütyM…œm@¿‰iA9ý1²â3.ŽLQ–ðÙHÞï;5¨<lÈX'œyÄ»*NWŸs8s—RšuŸú›¶qþß>O»šýÒ¥þF +ÁŠ€ÎðvAú;s´*…Áê (=‘áT/}Ç—Ìù»cã‘” ßÅU{ãErokÑè/éôñLÑü˜òtK{–>÷0vÑÐn‰–úd
Š#ºÑÀ'üÍõÎË»’‰¡[©%ðÿ±Ç
´Ý¦N¹µþ¹@)å3ÛÒa*_’" +üÿxºôØ|l«ÿ Û¶háÉß…v›û>WœdŒéCú¥t—e1lÁV‹^y“BØhÿÿNŠg6誰ÿ}ƒ½Ìlª–ÄoJaiæ×…?‡Áܲè®+:MX3ù\æë|—œÍ¤à ëÏ),´¸éÒKI’3ph
éÑäç2ïì<æÈžu5yA5Ó©“”;–ÉÆ‘¬ôZP¯éֆܲ¯)¯‹0oû0ïwŽÿÒ/eT¯¥ºoWJU54j`;¾œÞºkövíÞ¶Œ‡|¸Dî±dX±8ÌL1ÏÊ +jã%*kû{8³&i’…ÃQÄ€â#ߥ¢„bÝíÖxϺå”̬҇#³î²#PðvyV§žä'{–š3^éüSAE®GÜIÞt‡ç'Õ‡zG4[hñ½×]=µ÷…‰X¤|åÍÕOŽÓN’ñ¢™’CÍ“²ö×qQg…»õ˜¸Byq?OM‡åÍÅá¹h¤d]¸ º^K”IÔ²~NuE€áü=‡Ð¯y=VzÀ_wx€›g‹ìóÚ&ßµÚ)»’ +¼;1ÿ=œQeÕqn*ó%™qÏÚ}×ìNùæ,öˆÊ¬—ËeMîUBC¶áø
AF¦†'T^ꢀÀÞ”iìMü!çož›¸Ÿ–ò-JḞ=”bu‰.御A#VÌdŸ„½¸žI’m)L‘ûR„Õ¯£P¡¤Ï†5Ç5¥'1‹vhƒi4ch]-i0-;¾É«Ìn¦Um"ÖÙgIP Q©Å<¨KtÞÑyeºÚmoj%öýçäWÂK³šgrÂÔ ¼(‰éªg©R“‡ü–“Uϧ‚ñYËVFÞiñ£5UC‰t +ÑKu.ì´0<™]‰Áì§Ê®T©þQÄ¡q#_ÚCõ +ΪPVww+ŽÁE/Ó’Å&U;›ˆÄóì÷yæ9‹ŸIº¾&ÞÉgp֕˸o‰<à};ÚÓ·Ó‘Ñ´s«•¡%ÿú@ñ…¿²¯Î?ðW<üÕX[AÞ%|ñw ¡„T3ÀŸ¥4º9O뉹ëþ|úÑ/a²r(õ+Búö&å®g„(ª–óc=¹G§çSª?AËœ +kÜ;ñlâ» +}¼¯ìd/äe¶ë
ü™uR${–È#'U]6ïdV~ÀH/(œ…þ´~SÆ.öÆï…zª‚:šHž»¨è€k³ÔÝjNør–@¾ÄtH-(΂4ífŸ"¬.}æY>À÷’¿ßQ`YFzKeíù¯ÍùÑ\³ +°O“t59í#ùo5ªiÆPñàú⺨%ÛYBy’÷ëÀÜ$Å”S~ÂM¶pž|žÍªàCVéäÏú"_§ªþ¾DÆUák+– +¥6ª]»¶§‚m»ëôŸ•wr÷V
ýReWÛðÿ¹®v$Ynx•¹€"øHÚÏݬ-S1®Î¯ÌÁ®jk‚=¬*2ä'@‘¶QcÜ&Ç+Ù8ø¤¶´)J°LçT]$@¦E«P}h_høPM±ÛþReFÆâïRa€WW#i@Ȉ +Ìù¨¼ð +3…åf8 ˆWU7øø +\V€ÁE*Ee-„ÔT3Ör(I<
;A*xg„¯A5§«Î¢pYU&+[ÒùóøöNJªÕzÈjÄK„,G1>9Õ¢ËÅŒšåÇ}“K€ù(i÷ÆõU“Ïk‘ôfhÒ§ 8œóJâÌŠÀM Ÿ©Ç‘Iʱ„*®\Þ’Ô»üÜ¥ŒP8ƒ“í›õØqØab@%‹ÖÄ’ò±2éÿЛc
z +ù€9ÖÎį;ÚdF-8cNiCQ}C`xžh‘ñ<ü÷ ĉÕ´>A}•£Ò"Ââ„1Ç*ËQøaxåX1|x¹5ñ–:|†wÇòÌ¢–YÅ•Ü‚î,Ujè&’‘Q ç¶Oǯ&Q+3¦H?dá"Û@‡i²(Q¯ÀÌEmí4ûâNç£ö¶ýV¬ÀºM3õQ#:'Èà–R—í«ÄX@+}}“%2:ÅÓ)R›Éy.ÿŒýøeiõ„öU”Z—FQáÐY“SŽ%7ëA†YÚTêÎÁ#üK€Éèä¥gy«A‘È-hNŠ¡Þ ++çXkí¶“óŠþA§[1D™:Âþ§„<•dÖ´ˆÃ7“ÃìÄRdMóAYÂ/ë®j¾ +Öïh½Õ8Lyù÷äÓß‚«oÊí!@þþ†ÄÄßè}ñ[nA(qÄõúr¶½ÊZ,ëÒóø¤ÿ ãõ|hk‹ãJ½‰ø
oçh]ÿÕ¶ã¥ç +æñ‚/øºàŸ'4Á#ÜÐêJ4‹DÆI½L™œ”ßsi
½:æbãì)N!῾{š‰NÈ¢ùHlö +u(êû²/œªÌÿˆIKœh®'™]< +ñi¤ÉTEƒ¹A• iøÊ)³#Êh‹ŸÜ¾’
K”ä1x²úF¥˜àvü +ç]|ÒL Kzù ÔÖ +ê!ç;¼ñ{Åu¦‡%wÛéì¤Bן7Á‹Äœµë³ +¤×M_ )©¡Vø§Â¾ +„Zx;´Ä`ÄM„D´ãÜ"š4aôê>×EhË¢ À¸vîFƒ•I¨Ñ^•†Ux”†/ï.Òéj÷OOìy¸v ëŠ0—Iu~±Û™PÃ^r@^Å,ŠùRÔù¾ê"ØW¡~fEWÉs&J½k¸×€Vÿžk5Ê +)þuÓF<ÝŠì…Ç.R8UC&Bƒ2NZÁ£‰5¼M¸ÌËRŧ¢¦£œ¹‡ôY¿))¶¦àÉG÷øÝ<Â<`¥{M„:™µá‰ˆhh)üP:.&×Ĩív Šþýºë#´21òÁš»)¿RÞ&ÑF[;aò|F%ièí„IuLî~ÔŽŒÈ°‡¡È=kÊ~'×xC2z‚j#PÄ®Mc\C-.»Ó +Óiû´“D›þ¹£õt‹&ÙC»’ª*,6糖å™R/NðëNðºWµ“1Aï¦cÀ¨BfäÊ~P3³$iŸTÝ‚¦Ú"ÅÐÚCXJ/IÝØ\«Ý˧¾§‘ªÖ~¾/û©™f.òîŒ ,äòÀ©U@1á!N§Ä•˜Ü5td‚Ëçr{¸å7·PF1) ’í%ÐÊ› éèô™‡*Ä`´Ã.HRM€äÌ» +1Ô0:^ëQr:*ÆJ²¬BÆñ®w~8s¤
LIEl@¬§CÛŒòÐe8ÛûÂÔÛ®÷2xqðWú}Ù'L%Ìv£;N1z…R—8uRã «zôxy¬uq +ðH%·vtq?ìx7QØF’Ó©" ltõ“L5ýB‡€‘jE×À³^:& KaN†ÁKÌ ÖVØ*8›×àtNr>TV0–nC ii¨ß·}…xG @}ªO™Ê2×*êÐ/PVÐFÁ•\ÒûÅF¼% bS9I @ôÊi m„>‘B’&´£ÜÖ>Wò‘0h“Æð²ÕPmÐÁóháT™xFe,¿—1&¢%\býuÙNÔfyÊõÀ u3²ðŒñ{¾‡‰Ž-¾×g“ù]ˆüžïöùÿŸa:7F=ž4·)‡ƒÜ-€Ÿ±ÁrG-û‚ICèĶöÞÖõt*çú€+WAÛbÂr“<ïe8ö×]ÿ~©¤…·D?q•0ucf"é„©ÓŽ±V”vÂ$‚w,;Ffçü?at¢xüJ½0ÙDËt‹ü–Žh·-˜b€žh™„IüMìXbथÁª®&”•ÄCÊg·Ÿ¸ wxPŠh¸ ܯ۾€ª•ÐÔ0˜NœDãN¼²æo¶2ÚÝüÞ6êoÑÖùcq©ã5䀻 oÓîmƒ¿zð=¾ÙA/¼=l”$šfÄåmD&êœa¶£u–Üÿo:`Rq·¤$y¼Y†µì†Ñø¾í+ÊEеh" +Í«ñ•7"«ÛXÜC˜‡¢Ø…ÅIÝ5ÚÂ*LÿÜ•ì¾æûᘙŽi`“£ÏA¦&ôè[TMß-Âf!™6Ô§€Mr²¨qgšÚ¹d9TÄVM/%ãERµ5P¸J8hëõÅfRÝBÉC‰P™bLœlhÚ\ÏA«*<¨~î-χèf3I_:'·Ý¢»|‘ñᎰ¡Ëº™CǼÈÈi—¡âˆ¼†ÈŒGÞèE%€.é±9ýq• Hj
úÎ÷t?€!i +˜P°aô÷ÒC-èU.0eŽÐ.VUÀX„¥$£ +0ÞãÔu€©2tK©Ke%“a +=Õ,¿¨«ÜÉ__:‰–ò@.±%±šž¾§ZròAFV±€&艇Œ§UL¯@[9TM¥áyè9mò†‚®ï¢<øß; Ž¹ôˆÃ麦iðn®ÍÚÚE'·ê,…KîDÝ#[™E_#þ@h‚:+ò\‚AšNÂ:Ù9 _»&,¯Å¡ô%3†ez:å%+ü0á´’;dKÕ‚7Vð§¥mX¤)/,M´‘ˆ'p"v‡ÏJæ^“*Õã’¹Š8(Æ•e·˜ + Î#¡lØìŇcæˆ2g!:÷ÊЀÂÛË^Ù£m“ß-©¥3ÞzGÂQeþh¶ÊŒü5Û¿˜äµKÇüqè>°äë4šVÞ‡W`! +/œši¦BQ/4scSéjèunòúXz‘N ›žó¼àl!pšÞY¡ë®µp£õÚ¶ÖÅƧS¼( +üvÅØw°‘©êOÓ2§M§Åák²œÆÚf{–æž²ýŒ~Õ1-Å«ïñ~ ËK–#©:Ï#k¿¯â9^õÀR5oÃRSà:S’¸ 7³Ãã~9 ÃjõË-”Ë;º÷ñËgu˜ =JÝÌ^’èÕ“A’Am×1˜;*PÂÉ öºƒsöýñžãÃÈÐÕ™(´ O™.M캹 ;íb +=uyaÙ›SÁ—Î,ã:üëî µg”4Ùßñ‰ë-=¢ö5ÛXJ·ˆÝ5…–ëWÈ +¯Soé†>›|wQ}}™f²é,A')%e÷¿šRÒ¼ž”½5íF¿4¸ ØØ¢lWç=( ‘êu»ph)Ú›•õ¡*œQ%ÑMôü‰TcöôÂ^g¤¢O×ÅïЮñD¤åÏCëöó=ápRGËÊÆ\æHöIÐÅÆm¤Ò4/6)5¬\MO£(N9,‘ù•1.2ö]d¨âŸói;äYL<Q4›ùÏ* IO;J)l”4Û…”,Bñ^í
jR‰Q,KÍ·Ñ+r h-.û4ºs:F„ÅÈÄ»XºëÁeˆÁ¼Ý ׇ#ZŠ:=ñ5‹]ª–#«‰ +«³MÓ»¯K™÷ç{¶QqÔ•i-‚%-Ì~/Óæ$•;A¹<‹ZB*ùVÖÔY¶Bÿ\¡ìŒ;`ìÏøÐVê”›yÕJPHºµ\Gn`(øé²Fî¶<ÔX~1IÂBFZñ&ßB~2ˆÄŽéP3¡Æç\R¾Èøxœˆ
Ö瀺™6Ò¦²ì¡räc«›Xs/—9GÞŠ^oí1Su+šæÌnøÚ׈?ÐÑ‚¹Gèð3úR öA§„éÑUµ·–²iI´<ƒ–rèm…CD#Ý™„wÀ‰;h +ÖkÜ¢› +endobj +640 0 obj +<< +/Length 25253 +/Filter [/FlateDecode] +>> +stream +H‰\—½®æº
Eû +Jµ©·²ÄÆW#„Ò +¨Û5Ð#HâXê@º¶É »vÞ1[ÜQ·rÙýK¿~nÑõ,]ïìW-ù„#L?¨´Ûº?™` +š5Z‘føŸvý1\£Ý6ö¯`bùi ¾æ~Á¼z,×@è?ây‘H×°]šHô?•E‚àÒB¼µ9ž$,£Áµ9)<õì
‹^ÈK¡)GµqÏ΋jƒFÕá@£aì3ñ@Ñh'w”í=`´-¹.Á$Û€¡|㑇F'KøÂCcνEh.n‘'ì©eˆëgP/ðŽ9=pœ²¿ñ½t=ºøà>1(xl^‹¾92û]œ[‰T¹t>¹g#AB=…ðÑÖF‘Î1ÊKØ&ºrГjrù†ÙÉÄæ¢`žÁ£.î‹ÇžÜxŒŠgpÍ`¾j s¯ ^<êË|yx8%îáQ}ñf¼žð¢P>N¢¨&}ؤ +ÉlnÄ• +²¦ EQ’‹M +¥î—à£C"Îhl§eÏš08Fý(bŠšw¸¾ÓilêÃeTlË~`T“‹Œl2Zž²ô„AVÐ…|` ™ö$ ¨‰K׊KŒ/4ÂÀþõŽé£È‘†á”d¹éQ&]ä,YÉÿœc2~UJæ!ÉPM*N5áº<–¶3¤à®è?(ËcŒÊ4$QîÔX¬pSÙh<2²ìƒPÊè:V Ewý?0v´½‡E(A]6ŽúQÖËW™ý+ Š2”%-úOd†Qñè¿ÓÏ=<¯dþIÙÒ·ª&ßÏVSÓARô£Ã«aè½í'GFNf¢%ûV¡—
xyò,üå)û´i RLÍÉD•´$øa±Ï!1eù|u ÐY¶Ð‰ym¤ÙFÕ1½æ£l>›Ú +hd®U”C£OÉLsÑÓsí^ aKˆ‘ †<™]|G;åuªªÙ“$¢Pf"ãxwI~ºFû°(:«Z²Î)ÕLuH½QT{6Í_$"®C"æ ®)c R[fÄt‘xÅó"A‰aR‡wv‹ní!Q4’e>Æ +ƒ“
.wf눎
ª~w„Ͳ©þ>-I4›\¬âJÍe.ë–Ñdåq¦?$J‡¥¾›fERˆ¶·DÒ6 +ì\ýå¯Sß5¯µl=—3Ò?+ËñŒhøßP.±
‰?r´T©WÿAàÅNv¨>Ú2O“ùbGÂ0„‘¬§!YYù奫K±;zБ^GBKò÷ù¤”jm¥ÑŠE™‰´Û®ê7Q*}j¶ºñˆf®!FS“+’Ø=rnµ:*9<ðZRýõtY=¢íe+
—*ªK%§-ís¶|݈ÚQF6ŽWÔLã^ð²³oÙÍy _*Zx=:zš0Úš„çÌeñ98‡\01Ãâš GN-¿OekÉËð˜B¤ŒÞ:sDõÓ(tšž_ԽĔƒ¥a8/ø6ø;|ôÄtf¤)ÿ³Blÿ>xÑ°õÞ¨°a©U¯ŸcÆP¶\‘y6,Û3”RW{2Q×Î$CßÒ‹’ïEéÅ·Æ5Ûé`Î…Ud<?Cáúˆœ8 +v¥TfOWùB¤á{!Õ§û×æ§fžAž$÷˜mjh:ÊÇïgj‹â1r&uy‹…kKßrƒæ_Ç ÙJªÖðBõ¨!èZ#£chq9ò9…è°¦ÚZ¢ÿqaNÁ²z"L®yú]Ý#û›cQvh©ºðûœ›¤¢~PPÉNçÒ¹½ˆ¾¦·cGŠ‰³¨á@ýšñ™ÿÔT|B—,ú]²?´üµ¡òg*¥æšné#o©yKÏ'üü¸c‡c®Dm—©Vëá8X%omŸÖCŠëŸ6¬éI¹•^ÀK=GUŸôåÅ 3ݲæ.5æjGÚƒ€tu}¶õ1žD_J;gªˆÑi«¥èš:ï +¶Ÿ#ªê»¡ÜyhÐcÔSÍÀ2ä*žEtE¥â:y“ÿ¨#f±¢hI"ôý4a8nûj¼«'êô’¤†"z\MõãЋ„:Oõ—34”\¢9(làK<¹U.Š˜Öê9µŸq®’-}h@E”øEe®¸zÏY«J‡L Y+¾z5¬ªÿŸì2I’7¢èUâ2Ã<Ü¡×2ÓZ˲ÞêüzÏFddo*ŠL„¸ÿaºP”AWzøÜ*焹ª ÍIgmþhíWòEšaírÃ} +…k™¬EPÙõ`{¹mã´ë ˜Bßµëßå|! +§1ÖqÉþøsx0H—"‚pÿ>Ê8§!ƒ>Ð-__ +U?fÓöÍÖno1º"#((1Ê0Žã; Œ¨—‚UÊåsò!jÇȯ®]ãÑ@óžÃ÷vå|ãfT¤i¯à…÷üUÑWBY+°8Ž`þu±nÒá_y<X´¥ÊŽZeÆƺ/ú.-(ªðÕ¹?†*
ä,ŠA”}ö€"•à¶UðUUz¦×¬ÇÑS|I],²ª]ÁÕXœ%ÒÅUñÅbΤóï1%·ˆwõúUÐ'©Ö#ÞáŒ<¯‹CøA¥ •òàÀ߃º§Åç@œ£Ëôš
-†ç3l¡N¨ù3Ž¯ã8ªÍ..üz½~ý‚p8Ê @”šYæ=@Hš¬7A=„1vœNÝÃŽH—ÕkžÊÈW=_0ôa+ÔÑlR‡ý‘‹D.;¸q½=b?Ä–W™áAñ`¡&îs¸¥pùX“AK +EÎ)R£Ëå<=»åöÃ`Áª˜†õi²ƒ€õ§ø,Æ]$TDë¼Pðò#Òt€¦oE„öþªç ±‰“T'$Ö?gKkØ÷W.5ºá°GAb (2€ù("<P$æåU
¨Ëè¶_d,Þdi%ÜÌ…bÖðDþ3‡§FOgñü@A'̾>‘ Ǧ
‰fa+<ßõ|!QzŒÆI[óJ’?/ÌdhÄ.¼ðïË¢RwÖ™ò·yÎ#ßÞX'ADÁE÷Š2HÍ=TK¿ˆ‹ž’jÓ™ÜmÒw<S‰1⪎c%\›ßUÐ^ {1«ëj—V¯®Bò³ /(Rw<JxË9Ò:Vv<HèMôLˆþDß)¦¡(ó$“¼¶#\A +‡Â¹Üʪ_†H¤¹óE‰Úi¡ÖŒ~Å äràKmÖœbPjyüŸ¢öˆ˜Ùv
ŠÁKW¡¸k(8oBÆEœ*÷ +–B¦ÕQG€ù~ô…®!SŠÃ?¡8úÝ{Å£òÖF„cY¾ÂØzb_ù™üó%¥u*|}†˜‡+ˆPìÍÄÐ"HÄ»)ZçÌ)8ouŸ%´³4«£‹‘¸A‚7²FCeOˆÍš³méÁ¾êùÂáØêr,Õ<)4yõçÉs-ž$Öµ‰\mX°Íز"õÑWhžö
$"B\¿¼“Ç1ÏÞ˜ÎÅËfbQã4å~c¼‘ˆÈX.Íæ/Æ©ëtïÄ8œzXC¯¾§übÎÚspçWA_PälñùXªÆí‚¢'jÜÿ¾=‹»ó7ÍÀˆŒ}ñÛIÃ2]Xê1O| +ÓMW”)ȨXLZ'NeiS†M%çÿ9¦µð½»Ò¥ÎÕtpÏâOÌ ÂÞŽñÇ©Hµùúþ¥*½~UóÃ㮹ZŒF¿Â9»é«¼6 |WÖd€oªGóf:¶(ë Én¯ÿÄ,P¶¬ž5âÚ¯AÅï¢Æ\tvW[‰ër¾<î:ÝÎ(¦…v”¦³á¡ÉJpè¾õ46ü¾9fª|uzvA;›ft¯¥:JÁT¤rM|Ǹ.¤l+ôåð4G,’H;½¸èûM‡¯úœê<͇èáUG-•V×Ø÷€gçg{¼9>‹<sRPøÀÆ4§6Íóð +ÚeAw&†ŽÌ5R7?Úq+nˆ7ûçn»µ:d̯Û$.j{yZWO‰!;ÓÌKo-t…¿Ï°)9msÎ0! ×*'†,ÚÙáïÎàq¨ú$2ítÙgŠ1ÒчÊ~÷Χë¦V›s\&°|=:ûZük‰5bBSƒ³(Õ0Ö1ðË0öÈ-åq¦‚«p†kƒ@;]ØÕDzž]¢çâ(}FâíîÎLÀ8k´žÃÚT¶ò±ýƒ6sŒ·a®
æiáz|2g.dáB6MÎïèؽ¹Ž…ã÷íø@øn^“k½Övâ
~õ°Ô;S<Яn+Év-ç°FÈ/8NSð
vk`\Ãföy\½ŠeöØ: ËMÛg5̈4?cÆì…!½ÆQ§Õ³a†,|’2öØfN%çæŸ +žŽ)» +˜žÔn"Ä/³Î(»³}e[îŽe+0*bÊüPY±odä0,@ÕT®{I÷ õ÷’/7›'ËÃIÏζÿ·«¨û‘)öÔl6~󅳂¦1©püÑt É«“1ž&®_7Iý;¾¬gêí¹÷×ÝM_ <Âdjïjkú>òO/¼A/]:¿v¶K#Ì=ê‰w¤{»¿ûܘ]âBˆê6lánÛã¿›EGò¸wÿºìÚfSíËôžˆ»Ji¼þ±‰½ª5… :ðXŠ|"èûn¥ÙlD¨îg'**%÷û§Œâî F½"_´ÂÏsì4ÚûýÞÓQ¼dFTôý¢g¿-¢pEx°g>²hg{!|}?m–‹¡s{êøzN·Ò4ùÜÝO\Œj}Å÷BöÁKô
>³j÷}AϽÍι•ÍäàdßTڈФl:QâNŸnu|ÄF#ð›°70ÿ8—GÆ ‘Í úv”“T¹)áëþèÖ +¤ŠO +mLÔgP+1½l˜KñhàêØo±Ç;˜@ikxJð½FkÐY¿úô×AÆÿ,£Ç!¦Ú†dæâ›ì·Z"f +Y +7)›¥8À©$É”æ«<g+4‘y•Êq;f§ÉØ,i–€ó~‘–J¹`Ù#JˆiéôGÄüTÑ‚S°Y›b-ÚíýœÕÎ9D²ïa=À?Ëý}p÷#h±®õçîgŒ®i¹íE +˜ñ,2–
3²æk +·Ô]VpDQãâ&z]X¦YëäÁ祔eK|Qþ™:ã@!,‘[~hð1ã†EV»~HçóT€NrDA +7dß¡Ü—#›Ÿ¯aH’\™ü‚èP×w¥TXûX' +¢=îTûnYmŽe»GÑïe¿¬O•ê7IBó™ÍnºþP¹Š9µsÓœÙÛêra©B©Ú{F¸`ÑPw¬u8 +îSGÄIñR9è9òÂCiW›ét +½hT“\Œgð8©lnâ妢Éq$Í +ßL~Vg:Wä[½R«ÛÙ[äK%9¨}“CŸ¾Ýûl/çñq6›Ÿ¹ÜÀÈ9;ª3Ú°¸ºÔmj9h›ý¶Å·ì¢Æ@Àmˆ±`¡ŸIÀ¨ª<²ò"-œõãE0
¡”nm0,ù`€/MÂQ¶º:χÿo Ýd59+¤¸")îa4‘=ž\¿üÄxÍ<ˆK
‰]ˆJŠÒÈÌ<\Ãi:×ZfÐø”,7?›Ô6Ù@ô<nÏdãû¦r3‚ï‘ÝdºlS¨1ÝþÞš÷ÀŽÐJpdëôE#)¨ZÖdG +RŸ;(Z®dF5¶¡ö¿nêÌ°ócuFØá‹HYÖ„ÿîcsQAjïPã´V {2ãòPŠî{‹¬ôï=UZX™ bVì6a¶.dÖ$³#]QM§ÃÃß +•ÐQK›!{ß*úóAí¶üc2””)[
1_hÕ–3®|:q…9×£Ó”£)@NY·_L€Bã.&3MKz¦ïQ3ŠQ”k±õ›¶Å>²ºv+•
….™ÊïáÃ?ð-Ï΂= o»[9’×·*reríU‹\£V‡l6{NE´ ´ñ³RïT˜O´ha>\Ľây.—òrFóCÌõ@»•Ýâÿ#~5Ó~uŠûzQc¦‡hˆwV4µœ¯—€‹f¸ÃŒ|áIªÐËÔöTœÔã¥æ¥Ù£'9~Ñj’8õèmñ +%ô©u³êÁósÆO!âî?ÆYÛÚd(,N_ +¥ +¸â7MØy~æcr}ÜËYǵ*§çÚjx¾Ãwk3FmòyhÅC÷ÿõm ïÎõî|x €{“Ú^¦$Õ%©±Wˆ‡fHHÛÖöª€Ì£|x +C"%p
J|«:Éëu# +52‰G^ºa%¥0@”ÎyøøwÁC@Á)c’@•ä,ý¾p”ºr£"¨$•Â +E‰á'Z?@-ù·;1g:ÙEª¥Uf¾ÆcŠ?Ûc(Õm´¥žwuÇ*É.>Ìý BúYp•+·ið’'¤±)óœw'Ò±Þe¾r=šKÉ×t¤÷;Ÿ&%Ÿã8k#ôԞþÿuë—çYñÁžÂ¹u‡FÐÒ¬`CtIõ~r*xÄ.‰=d{•b‡]¤ï2Ø3„†ÎòÄf¹«á{žW°ôÓ +<ÃÉá–sA:¨uÞ2_D¡ò$ž¾EÉ
,Ç`܇Œ¿á¶¡#_ÏétÂW™H9+:±ìðès¦æÿ?ãÊ4·ª¶h±J«í©LYпŽ|I“x^?&’W>àŽme;åN¨ûê°†U³½;â7Âr_BN%¿Z”aªŠ½˜o©©¤¶S”0Ž×Ï•¸åÐ1õ0RÏ_6ŽÙåÊ\¹vîÔÔâål’$f¼Ü„9uù^ Q„y5‘]@¾RT¡|ÙIÕ½¸ˆÔÒIò]SåÝʾÜXkªÎfæËŠ»üsü^bK05‘pÚ<_¡rÊŽû¹ÕØÁ‘Ú•Ÿf!»ûgÜ®Nù<_Â2Övp+j o× òê9Ó¿g +åáQ.ϳ]íqÏo•ùTL¶W^¶ƒ»f}œÅÛ!-dÂ÷žvDLöS'Òp +&îôþ9Ù´C!°ÉI†…æ´4Œ«¯6¬´6q OÚÒÇÌ©<ÅB6j²ýK +*Ÿ xêÝhÞ“s€º‘v÷æ½:©>úC¿×ò0È?ªCr8FýˆK)d>‰i1‹ÞÀ†¹g™Ïôìêõ…‹‹¶…:M%ÐX!ÉÐ=`Õ¥“Å!ŸKÆ+.ÒéÚŽH¯ä‘¿È#Ú +Q¯£ÐZúÈroLÞ“:oµÇ'>\8é›ZÉH-Ós& ×<ÊVnD;uÝù$¸P°ÃÃœ”`úh|XØ¥H°"ç.ËȾ,ë°…´æ,ÚåwÈ×ãó´×Á—5=}*™Þ1®~Á +vRTr>4,(¦¦#e{Û€ÇÙ“Atì9”× ¯VÅ·®yF°ÛW¾H³LÙ +Þ@"Üü9žµp>`|0AÁçfçËLÛ%a1îG–ã7Ÿ$`.ÎÖ
ÅVU,OGÝ‚éÑ#‹‡ÓÞ +skK Þ¢x\ó Õ0Õ0©Ëã®î²–‹Éuj¢«ø’TmŽ¸™5ébðsê…Ó§Û}]>¦±c¥zls66á3‹ÄMî7úÒj?ôŒé +ËØa,¾@U͘4@-9.Õt×Ul“=è ^œ%A‰Jr_ ÞåH_¸IÙ +õ8{UsƒÈT9YÚš‡O”fCHVL6\ +èÕ“¢m*ÒÙ¤—ÊBuÍIÅó +Œ8 … +Ë’ß‘ÇŽÇ’Ö›’bUZ
]9Y¸C¸†;±žßþ„»þrÌs6„`UsD#@zÕsŠ.‚p#Ñ”K}ËBE™î±ÅA&Ô®[éÔa™Ý‘À5g€©+,ÈMòÆmAÒáãÛ»{WÊU +û–.F*9æ¼$¨£Qù‰õî¤]Ó +!j +Ì*›ÔøR[IyB~ ÆÕmiƒ„ãß+CÏÖ. rî¦lµìy/¡#óÔZ3§ióA"ÕB*¾Jwe•[bVÂéIR!~œ|aNÞ®( S¤_ŽËs +ZåË“ó¶6ñÿ!âž¼8k!#RДq›Y·)c +-ªé‘˸ûdÅi.N$Òº\·½ÍkDÔ«c¤;"þ×YÃ!è`5À»ßç-ëõ +Ÿ©™«í©üþª Ci"WÊÞJw¬;Ι܋lõòblñLàÀ!·±Í#‹·h·!XŸ÷yÓ³©¸ +6
Ç}ø„k%¹L»zú„˜/¡ÛdVªuº©‡ÓÀ…ª]-"Z@ +Öa°s|-¼†ZHà´cffèn`b®‹êÁ?c{èžt0‹Ûr¬CÁƨD…¯5êsÍ{ŽGì&5k0¿œÖýüI†X'¡-Pt·Cjᄶ׫¥fæ„ò_ùW»&BôîO¦Û.î²öÿÖ%Ÿ +YȽ-ט+¨Üo
¶’>üÆ’Ö7¦|¡”†æ¼ÉÒ£Kè@1J¯š~tQœqEóêþr,»Ÿ'\0®3îÔµžÁÅÂ6¯¼xÎ…ž«s]¤”hìYè™{¥q¬gpáºÊ5Š‰àGÕÙ–û<•v¸?‹Ô`
ìQëJGå™ ÐÀ鉞–(‘0ãJÕ7<‚ÝîX$pyz]'ÀyøNŠO"ÀkJË`\ŒqÌr„2++vPVŠÈ<9
+‡J²o¯Ù3êÉ’XÙtæçA§çJ§]æñ +\Ò3Ëì⬤ðœL†ŽY +9þ|ÊoÜE!+Ç5R~Î…¡SçÊг#\)|çJH-îq!=ÃÏg¦Ë¾ÌR{OŽ{2¥ýkóqYKŸp<,
Åq‹tGa§E9W†—Á…{·ßa&‰§%"7Ò·•RÏášoþ*'ßÇßO³v0Cº:ž[Ë›ý{Â8mâ¹ÐÝ$£Þšž®ó\öô ô±çJŠ£"3mlxbâèjpÃ;o ‹mÌš%Fü¸aøõƒ¹L`èuY"7þÇå¢?xÆ%ºm<F³q`¼œ,]HTu?Þhp„öD£ƒq!¶Surü +v3eÊÉÐ-l,
œ™Ò–ã×Sþ®E®y™ï‡j÷MÁe YÌ)ˇä}]bˆ”MSý.nÖš™1dâŸF°ÛÏ?Ä&zR†tºkXs-müå¹²¦7ï¡ÎYžÛøíåÐGé×(ÄÞD.¦Fµö8¹Á+êzâ"÷G‘~•%0Ãûaé/I×€÷b<~Çéço`^óÿÁÄ¿‹Å¢±Z:ÂZå• +t$qe©²ãNáÛÏÀ2ë¤oÒ÷g +|òdíDÜ×v^$òÀÖªÁÖ€þÇ;{¿‚æeXØI(âLÓ¼ø¼t 7ŠœcÒSÞö4 ´ñ!þ(e¦wéÖë£óù](;Z\¥íæèkwÈà Ú¨·p4‚VºçºV ~¦Œíq¬ð¨ƒ«fo›¢¬hè‡~Öªî«¨Æ +æì¦YñkxãË4À¼ÑÄç“:Ý_'Øo~øë?©;Y4ëQɬL€†1øç±ac%¯ðÔ1E‹Zï6£CÇ> ™jDØuê|ÙÇ{
H›® ‘‰»/Eõë@j„/¥8nPìH³,¶©HéGgl ++¢Q9‚ q_¸±**º¸ +ó O@ÕeSõú°…“ÅGWD[È©„üò´~‡˜£Ï±Ù„–ÚÆÐnƒ³;Þ†Äç…ËÈïÐ%õWX +LÜ …pÄ£rü+ÜQ¡?ÌIxao) ½¢–ÃW˜õBœ“è0f/b¼‰¡Ùé¸yËÇ+Rl +è×;ëâû/)óÓ¸LG¨X¾‘q^´8¨7„Œç©ÍŠYÀÙV\DÃ,L +$1¿Ô·…‘~{îç‡Æ€a=Xèé~Fcâh&óÙÝs +^úg´r[ºEe’AøÛ¢ô"hC¯Ê}GÜY:´i8Š¢×(Æp]dþŸÑÆl’±FòB°'¥±QÉѲa¼HX¯8¦sÃF1¥nÎÉüˆ° ía[½ÞŽAGñkY.5ÊK¬Ã}>ú®J»›jŠ³p@ÌýqxhoY„¶p›ƒ7ëG€’Úœ÷Š7Bü1„õ°üÀ")¿ °ÄBÍ¿‘KýE3²k«Ô1‹.g +C\ån
ˆ$!‚i,Z›ÊƸ€wÞ_tg¶¥Ó‰0(‚¿öÖQœ§™¸†"Ä%Y–Ê;ÉE›U>¶îÞ®zIi$º%ÕH§#9¡J@N½2ñhs^ÅAÄ<êœWDêŒ<öiî¬Ë=Œ§½ˆÚ–&¥—á9‡P!Äñ©æýÙwò'ÀÏ.dz(蛪Z™jEçI©Ä%ŠiÒ{ÐÑ
ckméo'â2‡… j™ãf$ã“3ð‘²|™®}Ù/h,;)ù3Xš6W ƒÓ…wJ0ß+n[ØÚpr@‡xûîÕÓHÓsƒ†d#¥Õ…
½l0LÁ¡…¿#p—É +Ð+Š}=6!Ì8³GSÉ«s L«†… +tzNDx‚êÕjÛšÒpÏ¥n°íQ%Êt¥‡ŒRƒW·æŒýE
Ãiž#lhÁ¿6` +´5 +jžÓ 4+B@ú¬fIðoh ˜ ðè¦,ÒÄ.;H'~ø`f‘Mcp)ðDz¶áŠŽ¢‚o <bg‡ËE¦J_VæU¡ÙÂ÷0ܢɬ|s1 +Â7aÇ„*ÇKkÛµÙšÁ¤ÝÎeá1ƒ¯‡Doð(\®Ö=0žÝbÎ’Ý{턇M—<G¶:ÔgtÍÈÚ +ô2%©‰¯AEr¦Y+DA¨ÌåË×±±CÂF4¥ñļ+Áz\MÁ:b%P7i!G@Åë>p¨hzc8jÿ˜ÐØ5A¯ÉyBÑ*`uc:»fu«ÞM}RÒznÜ +B-×dt`hל2IzꋵY… Û/A§8Ê-Ù^D´UÍo
ô2¿«„UɤJ µD=Z2D™ƒÕ63㧜w&æ8 ,CZtð˜ë£.çŸ/ùû»?Ä¿ùþ»?¿ùøñí‡÷Óðû·ÿx÷~š~{úõ×?||÷òæûÓëýôæÃÛóêWŸ˜ihK‰ÍwTXÖW~ÚÛïîôí¿ÇóWüÿŸX>¥ÓŸNý›;}÷Jöo¿¿»9¿—«MŠìýßÝ×ë?ïÇÓ×c†IÅCÿÛÆÿå3S‚|3·û†×Øã›ö¯Ï§?y“4Óíôó+ÓGYã–†mâÄëMfä/Èü¬ZZÄm¾ìßA¡aG±Øâˆ0Ž …æª
÷×Ívªå‰²$ê¡q¾x¼‹£ŒOÛµ„q9~ÄbµÛÍÁV¤ëÞ&5›ÒTc¼àŹ‡Æyúràq‡ö $É,ŽZx6‘ÐÊyl²×½±Îõv|0ÿ‚q¿ù¼‹#Ù×Ç}ŒxܬKu3£>tÇuo¬î–/ŒK:°Ã—#»@foÚ‡Eª©ÖŽ
™›±ìŠ*£Ð‡¶°CÉ£ÇÛ‡QM"/IZ¯ª/c“¤H}¼.YÑÙÖáË‘Ç'ou0|µáÿº7è›/öÄÖË¡m¾y|~,˜¥Ñª™$bšÃi³‹y⇑ÐتŽlëðåÈã“Íý˜Û`6żôâ4h3š/&ìçëøåÈç“ü„¦iiÛd?¸î¥ïÒB!X®‡ÆuüräsÊ/ñwu–@Ùк˙nÿØvÐyôy%Œpn× Œ#øò
ðŽahœ‡/Ÿú{òöóð“‡ïëÎÛΑÀî‚?6æ]`÷þžD£ôOî M,Nð¶BnXwDhœ‡/Ÿú{¦1h‡’Cû¶Ö&—1ºaÜPþYã:~9òù$S ÐJˆ¿©µ ÂeØ·œ„råyh\Ç/G>?3`”¿;L.cÜÕT¨6üÝ•>w³ãµÏ±Oa²là;íd÷õÈ4O^¼=yÙÒ7ò”åûº·°¬ÝIñHÒó×·g`'Uã½êjŸjdÙ +X”N
‘ôJó¦F¢¿U3äÿÎYc®ôÒ”¹ïu +ûGÆfMŸñ‰ç'éñ\umÖ⾶é®Ó»A-ÀÅ/'}™4ù€|V€Œ¤°^–VŒ1ä)0¯Õ©ãIÆUsa;„2Õ|2¬wõ§jEP~él4Ú÷ (H²·ÜM‚Bã;T¦y5Ê$x5€®…šù~Taf
Ïp7‹;ÂË£{I>Nç¡îtÂV3\êî›Æ-ÝHH%¬YL®°(†™eV«7TTªÖ¼]ÙÇ@‚šÒQ·½GT0\·»¢nÅäGÒ%1ô³£1±€‚0Þ{M¡¦\Cbë3×5òæ”w5Þš^ïÃþwƒbó˜%ZïH_è,¿X_-_uí«ÍDá÷´ïg{/Þ×[Œ¼¸¿7–$Ò;ê87 ɵÄ:ï +Äøü(b¡\ø‹„A¢Vn.8§f³Óqø覘$æúÃUy· +m»+h9k”m—%Æw=ÝÝÌ[︲ÝÿÌ[µq}KÜA n™{Fz‰m_Õ +pàݨefŽv±hZc*=×ÆË€°DaFú,¬n‰â¾ ©š2ÈâGš’Ã^l`Ït@¶/¹Mj’å᧱Xñ¨:?.°êw¯³…»ŒÍú…¿…#ÆctxkÇ +…ßý’šJEH!µƒlpbô|ºÉб$±~ô´±3ôžŒ¯P‚'è-dgÔ²ˆ¦ŒuA‚¾¯™ÉÙN×h¯a“F¨aN™ëÈ.“,·r^ÅGà<¬}¯ûþÛŽ +?.Å–;XcxŽ«‚8u$ëˆ<lQC’s)ŠóÍ>®˜+Ke–o©¯Øæ Yï!/¢¬”oÈ¿" W¦>ÿ:K%ö‡ÊÜjP;¸Á@0èé{öù-9-b1§à-r_Ï#¼¦µrçD¹„Š#\ŽÖz<×̨ãÍ?”øòK‘r¿ñ³X¼ +™vlZ=îîS€`6r{šˆW*ÄëbÛzž3„ +m>þªoú©,'Šõ-¬è:Ç»¬
áÆ?U-Æ“ï’ò±žøªè™<»t÷¸(óŒoÇq³w÷Å011EK÷٦СÄû@¬mT´ëÀ/ùV¢~I¨âž‹6=09IåF¦¹|*]NN*ÿ6˜¥(vßÖ†Ò¥¡àxYœ®<ÎÅ[9m›– ( øRƒeÀ*j3l>s9EÞ^P¢äÌ?DýÏ#¡cîr‹¤ksÇV•O_±Ä´Ö¯Z½Î7ç›Q‹Ç`P×ð4ˆ" Û0aP¼×DPåÿ(â’&°¢2çKÐçñ]ë:k}‚HÒÜ£Ü%ÏsZÚp‡dLÀñ4n?[Wy…›¬S¹¨mÖžXŠDQ–ˆ)ÆZÞpó5…˜`kg»í1¿3ü7H³îõœt†kYrb¬{Å“a%ÕqÚ¬ì`ÏÞ¾KÙ¨2ÊlÚƒ$úYÐÜÍŒ¸ËˆYgÉ–¿šVICñwð~}†f뿺bHó¥:9Swb˜Ò–£ÑûÅ3É<QgšÌš†Q6ÎÂH³é¡†”.êiO}6:Õ@µy,~T:OàjÔ&f˜wË 8˜ÅÌ"—qs~\3@†YçÞò&ùaC„ùÉŒ(ùÅtšš7 ívLß´„ÌcãìûÑÞœÎl¡m±Ð³òUÔ#Õ2oŒÑ²ÏGjfp}sŸ°j°0IfÓ‹¡U{Ý'Uõr'+%¬Òq +B¹ÓýÜ`;FbÍà&ìÔÊkzZ:qgJyýïEÇÐ!"^iQŸÒQkѲÁw¼Þ°PŒ;«Þ3DµòOTe/±~ˆAt"öïCžçɳd7ÚØb‘|ûøF³¦k^Fmn2ÂyrµÉD¡á““åØ‚à«°FÕ¢[!LoͬÖ&gç&¦!»šÊÏ’¾'7ÞY{ GRºêBßv ŪõyfaójŠ*«m@< Bi¿ÕÅñI§¹álP‹ÁD¿Š„ÐGr݉2ƒ,üy›9€÷owéÊ”`3Ðw7¹PTöðpÁO1"!ô«"å<DD$5jú%RL©VErCh>`WËÑ
°a„EñhvH ÊaByª¯ëaãYÛåY0Ýĉͥºf›W`±a“lÓËûˆˆYRHœëùm¤nq`‡TÉðƒÑ5jQ÷ºïEŠ±bßKz
P`¹Ÿ‡—¼È±ßµTQ
èvyË_^߉ Ÿ÷ä«»=–l`QùtH’–¡‚tŠw6ÈZQ8ÛS—0‡ÈhBÆ-$Âh$Õ¬Wá¡a?¶R« +f +çûïÑƾHƒ&lpîÌL‚Ëÿl1ìº<¿ÏÇlOO¯“gvã%‚6õ»`…YÆ£·ƒû¯¼—ÄQ¦-$ÿ¬ØS¼oÒš£C›JKê>¼HBõ}ÏØ/&oņ=êc_[Ðãæiï¿.–Ê8¬§Œ<§‰ àñ¹¶~>ˆnå^mûÆß6ù†°!=ÃI·òó< æY³ˆŽ@©×La$“PîÞ¾G±Åêj´@¢’÷|ŸOóɤ(/ÇëñH¾ÜFäÈ|`êKp+úÓE@:zæךŒˆQ²Ç›Œ¬;Q”Í)âkf®¾íDÖ¤1?v"dveôñ€šKS»
%)„ ÆÏÁËu ôÞ«¥ç’£†î#͸K²«‡ËUÅ®²šˆA”B%"ƒfM@ù÷9ÇvuW÷|$bƒP¸Óþêá²Ï±'q'›ú”…pR¥€Ý +€Ô.=¯f×ì×CyKôqfaþê8ÏD¯l½Œ—Ž$ñJb+1@EV{Œf( +ZçÁ8)YC€U)ÅñXèÎâc&õðØ„>f˜³ñ…·¢D÷µ êÓJ±rÅ1ÉZ&9ÿêúÐmÏÕ{Q¡ÙG†Iç˜tŠD!‘`àƒJò¶í³ ŒY¼þH!˜ãi.bbãœÇ¶±¨ÚD1Çel@¹4…@d!.D Óô5&\©Vk‹¤nxAÓÉ”5&X霌‘Cg˜‚8.À¤3’¼€¯D)¯ã¸²¢ÆtCÙÚu˜a +•-:´¿%³·×\̈!äµ n¶¯Mà÷ûÆÚ€™£m†#6éø}à×äiæ>6ži¹BG©›V'`BìCÖBŒMñÁÌGƵ‰ßŸf¬h,<xhÚ+m-QÎ ˜‘¯ (ã„Ð6Q‚“ ÁÕ…z=%ò
oKŒÏÔqÇé/AQRÕøÄÙ”U,_”AKÚ “f˜-Œ“õèlßô›ïFgO¦º˜ÕËtpÐŒuÿ¸±P®Ð'Ɉm‹üâB‰”Åèxå°»†Øâz¬°ïÞÙÕŽ|9·º.µxŠýZVtçA,d®“!KE,VMJON˜Åj|P + èc\D$_" "n£Ð°8¡Q–9
h‚N.½x¡#ס8ðÂwÜ£ŽÃHf4ê¥ÐXJH€P7Krs¡†0$<Û²ÞHcÉ¢)÷ÁNÿõ†í¯4.£“›…w*ÔnÝÕ+ÛÑh"àX’g> ª=¥a`ÂH +Д3…,(©
Y•™a@žßàž6X®1LXƒ-ä‘î° hÅh]PÈÁ¨ÀuSTÖË*©dâ ]^íÅ÷O¯>ù¿¿ù¼þõ³÷ß~ù·Þ~ÿ>¿}û¾Ó¯¿úãßø×ÛO_}²þõ@Û鸅’ÌfŠ7CJ¡ Lžþÿ/=ò#Ùÿ¾þÑ¿‡ëòøâñ翤Ƿ¯hÿúö÷Ë©öÄutØ^ﶸα÷%Û¾÷½ûñ{ÿs¶?â?¨’ôx÷òáïg€(*¬“ +¤ïÍŒ…sÓëàÚZY¹r×Ê~®¤®ò ¦÷¼Vb…Ô}»„‹Ù½Y+í"b;3‡±»ŸËhÛѽΕÂÉ“+e7öØ.Û‹„/Jf<o—Ѐøc:ù0ž+Ñ×–ñ¼¨bR±3ÛöLˆ_.q 4¿Åïø*jÂòfó»;7¿5VÖÝ8=0Ö=@ÚŒ‘ Ô¬-èâQìsI¹ç,ïÛÇ‘ò3@šŽDžéÑuæŨ±}?SûK+[wÀi¾â÷ò$I>9à=ì0‰íJI%ïÂ厖K”¦e7rµã#ªN=˜Ð÷X-ÇÆŽo_¹gª1—èüÝéáÕ}{
÷PsZµ‹.™Z‰{¦â…¦ö6œ,Δ€ì;=4é õÀwÛéáFÝVÖyËNgŽëL[ ÉmCãâå¢Çò¦ìô9£±²îƨs0^ê‡eûâÍÊ„Ì=è«P¥kÊ?Fmë86vÍ#‘u§Ç‚nÝA??fByeþpš¯ø½ƒ|§Ç/´„‘Ðþqäh5Ú4ñ J£]Jõ¥ÓdËfL±2¶¯•“+·VÒ2Je7ªŸÉ´~7–ëJe7½no±¸(ŸÛK¬œ»1‡óàعµ¢™6ŒG§ï´ CÆq?rìw·8q^Í´‡#Þ£—»c»Öýå(ලí+‘ÔÙMG(F¨åeŒc7ÛLR=Š¼¾öæ¥t¿Þòó…ôð
3éÐ(5€¢4®XÏ +rw'óÐœòÒ@%Ê,Æ>™˜
…q郤 Ä4ë{ƒu`Xݾ¿‰RK®‡BµÚ!µb¶}X0%1Ïææ!*i§çilÞ¼ð Þ0u±
Ξ- +‹îQùfã™Hå@¡Î³–k·}+ ¸
o,¼‡Æ‚©ik-ʱ.ŸuŒÏTnÒX+6ƒ}bˆ¤üáPøÕ*¬ 9·É€åºž1PAŽXj +ÎiÑ–&â{aBb“&J#áqSC`_Š¸Bð˜È¥ÚÊQÓžX¼á·4£ƒŸ!gBÐ`ÝÇR)N ÖN¬ˆà»¥>LHV¦Èê. Ñzª-ýqÏë‰Ì‚ÀÚ@vÂ@ÓÙ&ÌbwØ› +°»/»¿„½…^Ä@1ó‚8ŠÝ"æ6AЛƒ`êA3Ž–”›KíÕ5§Ý”ZqÔR³ +±ÈÚ;Å` ¨§k·¼î0h®ÕÛH, í¢Â€â&b;“æ…Ìd».M +]¸çn>½@É4ŒùŠZ=îFòŠîºi=h,¥${œµ& À™-•%ÍT3šj¢ž€„
%‘2@L+Eæïµ½]ÄÄЂÍÊ‚›-ŸyÕ2 d"xH¹µ¾ÍŽ½|D±à5ë{ÁíFàde
}£ûùD 1Ö=DaVv¦"‹ußl ¬³„Ä.N4QÕ€etoYŒB…Ý^žÈ’Z ÔycÌAÓ»zí÷T?ÿ‡öÑ1, @ÿ‰«0`‹Ç½ŽZ"mš1ôXÙÇó +tÉEÜÝ4ó¶sªÓL©P*Þœx˜ àð4ÒZ6™)0˜H¬Ñ¸üúuDýymܳ³aã$Eˆ(¥„7»)‹‘’ÝXžÎ“ù™´±d㱨Ïy-*´ÆVr)SÑÎÙ˜ +c3¢¹©È>»˜Uy
ŽÅuh)G²IÝÕþÈÝ2’N5ÿ&õñÍ]Iäà¡V×’i¡Ï™ Ò]ZÖØÁA¶¶L“ôT#Xe‚§ç668s‘/ªcmܯ]Å)l,–t-#Y»b2]Ó>Leê›]–2PÕ¬š\©È,”Ótj Á,•Sœ¿:ÿÿThßA}°å“·YÕD™n¹]Ì*Teôju~¼ 2ó⟫ lå¤/;a‚4QpŽu/Á¸ùQWžÔæeJ·¬.gMÖÏ^ú&Ã"›ý`Ÿv´ƒá°ÖIÊÌi‚iaÑß’›€¯faiÉ«<É`/°Æ‹ŒÁ*´&áÔU£KÜʈ©„H>’UMÕ+‡wœÝÜŽÛ6GÚ;>é´àSˆXK‘dò +T;™h¨¹ý;ÑœbÙØð½¹×««ÿDs +}xMVõ•#—5Ù\?ÓÏdõIR¦{²ø=Ñ2YõI¨ î‰GX
·É<ý êæÝie•r´_¹ùå1™¶ÜÎ%ÒH9W¦½ò9=g·æYÔ=¹ûEz¾Èß.Ç‹J¸ƒýá3)ay„•¦soÇòËÈz¹ÅÊrz3R¶¹\ìŽkÇç¸Êš|¼)mÃlt³ô\IBƒR ìË‹Æ=ûÍ+Ÿ_ÙnéÒ+¢ÀŠ¬‹Hª7ÙÝos“ÝÏ+.²ÏɃìù‰ìùNv¿í»ÈŸÈnŸÈÖOdOdÏw²/ܲÛ;Ùr®|ÈÖOdÏOdÏOd·w²ë«‹ìñNv9q²ïø_ÞœgNyÈïdËé;—?‘=ÞÉ>£å [ßɾx}ÈnŸÈžïdŸÔý^ÒîP-=™´yêx/Þ›ÕÓ“=ã:Í•î6°Nÿ*êñdO{å1™½tÖ™µ§?{öPhÒŽÏuøçÒ:æ²Ë×s²Æç'Åš÷Ê™—¿>§X™í¥4¶JÉN0DÝõË÷#£`ûéó\™ÃJù\™ÃÈgz§F0Ó¥Ãò{Ï~®”x‘Þ“ëEéZ‹•gÁj¡d®j3Þ~ú]Ãq·=—‘Ï=U÷Êç™ Äžy.^ö< +·{ìEûãXr¾áª²Í{xgÙ¢hÍO´kàzÑ^Ã;í¦(ØG,æk
°•á\J͇á•Ò áÚ³ß´Ï +Á©–ŒÜð3„Ãy0‚fW¥Üvš-bÆÑ2mÌ®õ{ÉÄ&fÁ©}PdO êFŽ»àrŒê2—[¹í"ÅB—j^a[JÙºœú4FzŠ¶”¿ådM@ʼnæîŽáÙ"C̀؄cîBFu6™³X +Mèaù”T1FÚÆn¦k$¯fvøö~|ŒÔ !d¢¡öBòõéC +nÌ‚Af°GŸÓlY€âA…î@TiÕ8‰² Á%hàD´*Óü8ç׫[O"dTj'muâêŒë*]ñû%„Ú +endobj +641 0 obj +<< +/Length 22530 +/Filter [/FlateDecode] +>> +stream +H‰”W»®d¹
Ì
ø:Ù´!Š%Å“n<€£
pblêïwUQçt߆gÇÆb§!]=ŠÅb1úsû?ÿþ·ßþðñïùˆþ\>ÞŸ½
-1lOk1aÏÙm>¾ñ“¾ž+rr2ÖˆÆ}ûæØWÛïZ”Ͻ?|aÖÇs¸ùÃóÙ|Ç}f<x¾u;;cÑìXϱÛz`“=®gò Ïð:0v}ƒ+î¾æ£ïg3ŸÜÐqWŽ§'öšÏ½¶sœùÁpÕl-5i³?"Ÿ¾]'\û8ÉRO3ÜGÿ¶Œsk“Áƒ}ñÀåf¿½¨07ñX<ºg&·ö,5î³^¼·Î \¼CàÙ€Ð~F®ÅñÞÈhkgì׃þɬñÀ?øÈ·Ç¿„–÷å\_¢¾ëÏÍ°ã ºHNÛŒ{oŠpçU. mï^“m‡¹šë›\ +N}3øæo‡»ã¸ÄN†ÓWpþm ×±C¶WÜŸX`@ +2“Ù5×Å\ qd8GO'Ù¸1¯J ŒžÚÄV¾80vädo +n3ð16©w’8)¶Mkû&A÷)6Å +úr\ƒ'®Ÿ“`²À‰u’ªáDlHd……Ý,ˆð œkqÇ
ŒÆPwåÕ—°¾³`p~p*~1dP;„ Ÿöä—·‹Ø.¦rx;2î÷Ã’1Vêz$ÎjÅ +qçÕ"âë®'×͇¦€ýÍ•gö–a¤™úˆê¯¤1YU@¯ ¦‰j[øª¢)¢MÂû"5…#›7
dµ4ÙµhFUú2ÉDö¥ˆ÷±×MƒˆY†ž†•á$
¨×¬+^5Ö+o1‘3dj·TÜÛ¬”†
ëï’ñOÓ"ÄÊRú÷ÛN¶0þ¢ ýÖd£6¹³Ã<°fòš4½ÞZzòøXSªÞÖ9%rBSŠ -¼‰Pª„Ê0P®#Îê´gå÷KˆÙÞÙ–zˆ=úmËÅ„÷ؾSá×N¡,Š®çË)ìf +û:ÕúR‰ÝËÉ›ñ’8øŽ0^›zINåìrµŒèU,Êß!-öÞÕʪG¢~ôÌ}Û-Âbe…·ê±Wu$º,ðŠîuçJóøÞæ¬ Q&
€B17QÑfxéÒÄ‘Û²5Lqb±ÂÇ˱.W +ËN©²—8Älê‚5œ«%ã(p-ŽÚºÃ[Š²k‘2ÚªÎJ’?bôfßÿ ³;ïÊpŽ&µÝÙŠzÈ™JÈp¬›H„ˆ^76òôßZ´I Å+/g«ÅØ\û‡ÍÒŽa¥³u"À‚xqrn`ùy×ïÿ;c±U´¦<»?Ï$*΢5Ÿ°ìÚWÞ o†ñP(ÈåÒ¸ÈÀT^¤/å®\Ô/Æ2ZÀ`^-d¶c˜hóQ:’©×ªÐÒ\U‘°EvüùÇÁ¶Ý*@©ºˆ»iR)ñ²“Ò¶D cã +QLšÚ0n¸™Ú@êõý¬)äùºÁFn +6¡;(Æ¡ºÎz³„ Ï4$CÀ¦ß£Ìš©>wU›ðãqÏ5n÷èe +à¡ÓºB±m«@é:Øc²Ú`ßÀ‹$ºTEÁØZ!«ÚUa‡
-Ää@×û»* +ÆÊ•<ßÑÿkó†åo¹`½‘dúT§MŒ“¥†;0¹Á9gŽ†YCÜçŠÃ2ü +CÜåôÆJpŒãVž<¹Å´Øb&à–( +ïg¾}Ä!X9 +<{Ñ•’'z¸-φêF‰7âÆdóñgae ÿƒ7XkPõ?Ðü™”"â×ÅWRˆtt¾ÆIclÅ)ÜYVãNÃ0÷ºÜ1‰pÛÁó•× +ŒW.e!JëmvåïfæAËð…´´ÉË¥^œ¶¡O¯ê„É`ìøR\Dsä?°û•ÎàâVŸÛ¾«öØ°Ž±*ÒÔžPÁµN¡´{°§Ä$1Æè§ð§mø~-¢ûc£7a•9)«$[‚qYœ+‡Ûv* +³¸ÈаbÓ+®Z×eszLÕÍ•hÁ€ú(/w%Iv#†þÊ~‚öµåêû…ƒ,Vw—4»#k‚œ®*ÈDk#òúQZ¶9œzSCû*6¤¹\°ÄL,Òa¤æxh#a¬æ:Í£:8Žò"¢¨fˆR~d^£¶E‰³^ËeÓuÍ›‰Óèþ†
¦Îj3dœ +˜Ṳ̀•_ +d
kd¦TÈ’”ò„ó«ú§B˜uêG”à¶ËT@Q‡£ŸÈM²˜fˆá^®µ…çß×(=?Ê-d°n½AëÒ5$“ËëŽãïæzmö…èkç6õ×±#¼YgëœIþЩÓËZÎËÄ—8˜2Ò›õ~Üó¯ï&ï³îù0
‘˜á÷©ÝqÇ'L¨=†zåtMÛI®QÄBÐŒèÆ8–hd]Éuúó˜B2A©qÏŽT—‹šâC?2UºÚ=\gÓaÙTyRþ–XÖ™aߣ‡X'¢¿¯Æ’6Õ:•r‘×j^èë,×ÉÚ¦ö•±ß"ÆʯºRÈxIôÃH2dZ;Hž†˜2ú\ ×e““å`¸µÕMÈ,æý€~ÀÍ©>æþ +Æ"’ÑŠ¦¡Öãqk/ÐæD²ŸœýúýøåbHaÍá _>Ÿn‡¶f\‡Lª?¬v½8Ø›äS¤CöŽíljÌC8®{ÝÏbƒcysJ<D‹—i½ø²Ö©§ó*Žú×^
‘ÛõZÃê¯Ãê.¦~V¹ëÃêj&hbÇ«©Œõ"ªÅêYo&*Š†?âK¯!5¢¦Æý°dÎF +£å¸ÅµÓí
ô›9'Ø$ +wÆZ¨÷´íÖüaY߆TÁŒUR¢nôdêØß'ˆ¯8öŒ…>*TGõ +rp8ŽÙš#uv¬mq5ø3‹¨Zzu +
c÷;iØÉD’ÒA4~´Þœ<ÊEr‡äC|¯Ë
ÔV³‡Ã`ÍKóˆ°öÚ&~ÖXwó^ºßPz?ª›E¯®~ +jà{¬—{ù¥pþ›õë7v¤0˜CÁ0¿,-pb}ÞsJ›#H—A²ÙªJæ)oç¯ïhv¹ÕDó·¬ÿY½3«¦7ƒwe +Öºp~^O§¿¥ãà⪵`½Õ:nøÚìo³Æ
CÊÚ~Ò#Ø"§sá0hE•B-¼^ÔWAZt^B-¬ÓÊ*„<¥0Wu½¤\g$Sõ1ëR`öŠ¤}j gµPeMÌ¡î~ja®%Õ!Rõ¹BÈtø à
+tÊþp¿qÚ#_kIÁº>b9ÍàÜfþK:¿1rC6ä÷ÃàøJ„z_Ò!!Ø\ÿú\ÏåbÁ•šêÉ…äÓYžw#Þôë"þ´ßHrɬͰïrzŽÍš‡ÊCTœ6”«Ñ*‰?¯:¢²WTlÚoþÍR¡™ÓeŠg<‡R¾ñ›‹õÕ ©å +¶í¦´4^qL§¨>fØ©ºSrX®ª¿§m‰¶m×H^ªþjŠ[†×Þú|)F´xÇ<‰ù¢E+îKvíþZGÌ&Ö¬-ÇŠ˜–fR´Á,¶ƒ6Cº¥˜ÌŸ{’ªE籌MÒ@¶P!þ¨-*Šºç¡þ~«ÇvYýÙ˜gÄÁ÷ûõŠ«ë¾ìO©rØò¡ú3Y›‘þ´–¸]ݨMit€@÷µò¢Èê¢hj¨v®ê'dãüãú=®=.yCŠ•´, +/âPó!†¡*|>¡ÀLÎO³>îøŽÎÿ0_@Ùcî¼iC +yeSn5&3.B:Ö +Rô÷ŠšW³öL©®æ¨¼îR‹.HpIüs˜í`Þ ýðÓÉךŠe7|6ý`Ñ4 €¯éó›×®:¦úö›”À¥ØêMtF +s›})í| ûbíדå=@fñƒ¿žOŽÁ-ž=Ȩ‘9únvZMgZý>(šÉwܯMÉ(c«2ÇÔÑ0µ-#@}/ÆËõ—¼õúH¿í–©Wõ_9sQG"™LG…tW5¢Ý˜¢ÍžQü"6_E §r÷^g¼ÝÕºÐÔ7#™Â™ÉD¼g’ÅP;˜×í¬kyNÚMìa“9K9¤;*ñ‚ZkÙ>Ä5¢ÄU Oz&Íæñc/<«P +9ß,fû“'çèÑáùÛ5Z…zí)J +3ÉÇ2ft½Öž>µ¦©¿\kjÆÕiOç‹Uø,Ä´ö@ÄëzGÀº)7h^ÆZ:\ÏÐMk¬µk'MÐ`O´,Ó#¢àÇ;Ò“¢õÓ‰nöh‹K[™F½Ÿ¶¥ìÜIù™ÞJ&‘ø xAW³IÒ0eá?N5º™¦ØØL‘ÚÚC‘±hŒ>NQ4rAæû±ÌÊÄ“%Œ/¬‘WÖ‚$УۛZëÀ£§¥ŸI3cýš7bøgÞˆø¢ÍL +*ßX.´;§ÈÃXòËod2—8ô¬¶ZÑL(üÜ@0˜£µžjS-—hyà”69dnáÕ"Ò 3TVô›¥Ë²"a\²Aª)c*Y¦@‰0ÆÇqШ>áQ£Y’éjsЯh9ƒFnß°ÿC|æq’qi?ë‰ã±Þ¡ªXÂӓ׎)¨!W¤¤^žTKóåSÖìø`Z†äA–þ„Q\:Y8¾PÄe^X»/Y&ñU›•,r3ÃÊËŠ@Èa&Uºb©ÄpTOÆE5F²¬ºŽb…üÒaPa=¹ +¡fAq¯BÉåC‡—Úܳ¹Áó‘°³¯@\»¬ý‚Oòu˜°Î⤠Qr¬¡õX&ˆã}™51sôdgÔý!R
±›ËèŽRn u…Þ!þ̈ßGz×K%Ð/?鈎›Ë¬)µBŽ†þþgrT“ÃÅæ;NŠ|îb|ò‰:cÉÅ‚åp’ÏmXïmZ´tãS9բȤÍ)æf3¤ö ÌÑ=za•·@èã†ècÆ~1¤Mµƒ*€×wòÉs—!Ž9ð±(ôU¯V4k£{•»=à@«Mk°(ãœÆ'“VЖG쪯zmn¤†äë%µ×Nmû²&ªk=Ö|Eõ1"”›ÅöÖN’j§5>Aý—nÃ뀳_,1«Ëxº¸$ðD³"0Ê:·pÉÁ‚‰ÖPÇ;αˆKé¦XbÔ¥bKÃÃÖ)ûÀ®½î±N·ìl’yýP6…“Î2òü
U0þwªð…Ÿ¨âÚë!÷Þã59zóRE\ï—ÔJ +—¡A¡qoS÷Aœ%?à“×°ÙÊ/Gæ,N¸žNUpÍkΙµÏöpd¤ÐáïjMX›·¡;ꟼ“M3T1«š3Ù©ÜWd=nìüü¨éN²•“z¤ª.ï.6> ‚Û“tüå~ÂZ–·²H’ó°zTÙÓÇ;Ôÿ"¢ª¥„_f8¯ñ΢/¯5òœîá‘ýæ¬ÂveŒŽÙA GÝFý"-ÊXúz~²ë™õF„JåÍAaYã°uÞn$dƒ6å‰2äBíY°òá¡æ2À9œ#-ù-Ì$eŸô)wh2ÛÇaHZt™rŒ¦WŠpœê·ËîüŒˆjrDê<ÂÁ' 7Ƚ9zrC‘§…µ¬'uô#gâF^–3]´R_¬ÖýHwNW…[‰‚j3f•M -¸ïhÿeêðOóçÁE3GÍágÌ„•yè¨÷TÉ)ãa“péôÂ/bÅ––“ió¿5DŽñk¦¬`Ê¡— +ê²/„È +ÑrTxõôÛ“Ã?*Ñõ42àkí}Tæq?¸îqDÀ*^Ú}Å"^Ì(v‰ÒòÞ#ÎS̉Fp¾<ÉŽ’-R!Diá}pb…Š˜ù=_págô‘bŒßz*B¾Îôvã¤çÒp¾&›(‘9$€¥º1—„øQ‘lÁNž™ª,Ÿƒ„ã‚Åï`ÿ"std”&ñ?®óÉœ?²T/[|—Ž¨Âº ¬‹=Â'ø÷q—n›ÿù‘y÷ü³‘âRŸóß(q…5Ëoèwó@Ù elÎÐe•ÎRw8ùþ=Bø×ú¨¯ÿ¾GôS¹Ë৙0üã¾ÏÝÝc~sT‚ŽiµÍˆ¼SAíñ’yTWÝRðîÿ!¥‚ÙþìÞ" èGê®>bÈÃø&«žu‰›ÿ=•lÌ=-ÞÑ™äДH©WÁ´$Eøåà
‡ÓÚ>NDÓÕûMìõÎÞ”lñÈæ]Â`‰¸U
àØC¸…“™OˆC4FQR-Ë!÷h·!¬ù +dR·-TóÊ›?[!ÎØ«ñã41Í'\Ín+r‹gP¡·¦çžF¤ý~l:¦5¡†d¼Ãò'qíAwÞÉ.ŠFOWsÚè¤GQ]Çk°z`Ô)ª; +MtìÝHaÒ\Û˜F¼çGÜë=NɪN6Î"$é‰.ºÝ<òìõäÎ$t”†éryg¬)EÒÊ[ÁŒE +ÐúvId¼[H—%‘@òI¹Vœk’¨NÉ+Ö÷%8™Èó“JeKÙõo¯ÌI.¼¤ýs\¿+×DžÈFÕlDj¦Tpqä]˜ûvh~ÇùjÂ%üUÞÍÈ7gN;œlá†ëAhÕ+8w+.„K¹÷öTäp²Æ» +Blµb1¥a›Æ8xõ}ªî9Cdq™¹]EÑ7'#c±?cùEAîo/Nø<l7ò J:ˆ€LžÇ’ biÂká`E|kpdlŒ6fšV™eË-Xä`$
Å™wŽùG½VFI/›•´Cx‘‹¶>¾å7Ü¢ŠÊ®z?7˜)QÐ/ÉOÉON¬G`z±IJúÇqJ$Eaå~}qJåÞ¯üß_Ϙ4/aR±µí«Éa‰GZ³x•Í@ý±‚ýârŽ ÌtÕ¾Ü]SVD&ÏOW¿ ŠN²¹Š\×~A™‹^Nôpë +‰N[S•²Jh ]¸EpU¾k‡‘Ë÷PÂòî¼ððîLð—^¥õ•º; +”k»˜*íþp"ŠÂ¾ßƒíl6gTùÉ™ÿü&«E“‡Ú¥x·ƒa0?õ0]S=À-ÉFù“zu‡ÙË=6KÝUL +áõ‡&íeÅ6‡;¡M³•4mFN +%óªRc.%…‰²;œ,Ëvr&ªã`2rN¤W×ô^_î·>\”¬BŒª®8/ü[à.ŒiMõ.*5k
=þ$ðè)^þÈõ„ QE#HdX¤æjæ¼öÀì©p¿öN«€¼ë¾µX>8‡Ngl÷,á‡{è±~ì¼]7SK&KçWÅdÚæ[ÿzø+•Ýë+)·GË Œ’Œ6qUu!ÙCµw[bLê¬ß×åR•ûz©È†\––H:ï}ýEƒI_SÜI½²®J’t0—-Áµ®ðÃûNaŽ†«Oo<™Á0éЫÃÜ][0ã·
DéÔ—í›3¿!1*xÓÕ&à|˘‰/ñ£¤ÕéO¾-ºpc9òøOWS¿}uïoÈM}M…ê{5–«|»ºc¨Í¥Z!á„ÌÎ +[.݆mÕÎʹNçÄÐ(Îæ×5wÔºU×ÏêN3õäF.ìWt ø§g™mžB[Æd‘dÒʱPÝ+0ôm,·È4,c¾B³µF^öb«‹‰CÎÓeã6*² +geÖvÃæz@'„«ä´¥¼ÙkÆ¿a®ýÉRˆíqŒÇuL}—تªÓWüŽ=Ó°d…qhß)¶‰ÍqTd]–t¦Ìî)ì^¤@óEv·*š]ò'þ®^ÒúuArâÆ"„ŠœÓ7|¯w9É·ËÜW¦eÛH…Þ:ÅW:Š•—ϼù°UÓ“¬Rç<EЇ’&×mµWáXœ@¤^ªç`³kã¹%ßJ>€Õä2°CP#²»ú’³\•ÍUl£xëâXb8½ÞçSRñœòâL3€”“†ZR
ÃX$DÑj–Ò/·¨Ê&–{2.Û¨d‘àö‹Ì^ç
KT5öÖ5?®Äly´h 6´óˆILªF L-AmYë^Wb’ð’ÔpUYÙl‰±½ßOì*Å*a”5j©Qãesù› 64kð¨„ã,ñšãR4¹é1_co;ÿ@Ä规`ñf»àªîW Q …P© +V§Ù¤ÿÖ^¿ñ±‡@C^PHIJ—t,?ãÆ[*Ô”4÷"Üëõvƒ?hÄi;ž’ÛCFM +U*bZºµúr,Ƴ4‡uî[¥R[jœµìx»`Ò?â‰n9òˆn^9LRÐî„<²ËÛ_nCŒ˜ˆÍmÄm¥ÔfSËaË1YÇ3‡™‹˜îˆ†Ù¥Å¯Wþ†¥™E•Â‰ÍО¾[Ô$®âØ-t}¬ +žvk¸Í’ +P,9TitŽeŸYIªº¹ëÃæGós£8|Iÿ”“<í¢ò>ý©W éË™?BRµ ña»XªfŠ”«ô"» ¹è*U`Uo1ݬ«îQlïžî»Ýl•™ý*·’&«†'fúJÄ¢ÆÏòÔý +&ûüzâoµV\Þå?c ˶õ¯<Ä|ìÖíеd£c™!ã¶n¿"ÃIÌ
+Yo^½æ.õ_aιîÅ×rŒG6#–|¼å¹—Ymź¼Èë™ï›}ɨ¸/`úÕª'êïDž5èÄ™0ìÍ¥íž
XÒ)×4Á´—f¸þä×ÚƒDy„Q +{{#Ó…5¦’ö«>q};ߨÎ|WßG‰ýf¢.¿.˜B† uÔ›3ØDóqtê'™ÅÕ“e«EþuIÖtjÝíø×Qd–Ó³+)àÀ“ÿ<S…t–MyËìsÆ&µ¥¬?Îú*1¥¯ÌÅSSÁCK<üÝ*Ö¹GÆ©°²¨žk¦èN§¤ÚbÍ#€±„[Ó“@j…%}YF§¹ßR(™½äõÉ_Ä郪n ĦdXáq‘$åCþcáàP{ä˜z£Ç™åiÉM{öt5‹>/×£¹£©}ª¦°98:ïOåþíP7…ËÀê5滇·“4Xck b¸³ôÈY³’2›æAÆbµt½Š´]¦©áÎ%ϯ¸ÛD消* ËZöf&U×›ôT"”âeÅ3諲–]«;®[Ž8ùZ¶]ÊÞðñVÕ8š»5\Û;|•#sÝÚˆ¢¡ã\MÓEq?#>iãB4Ù8 ÛÒO]T?TÌwÕ–óz¾6Py”yTZ²æÄäÉ·’®ÊéšöŠ±²Žu3Ë4í¾©ƒ/]•¦Ø¶îLuè‘*=n²&õÿd(;TÔîWN0ÄÕjnXÛ‘?K†˜ÓögŒP¬Ÿ,Xômöà«>0óHlÏe\#2nìÝmêøB-ûã>]ö«e×Û×sÞ½û®
ÖZΧêeÇ‘¾Xå£-nbì Ú2jöRK¢Øß®%yãosTÝ—½öç(eÉù‰õÒ8˜ZºbCïjú|<gï¢å·waàëÝêϵü8”\ IPÑë?Õílðß#AÒb¶};Œ#Ò#Ÿ]4Á–ûÅbÉyÚª,÷Ä,[¥?býd²<Þ~#&²;kûÄÖ#kód7=²ñ†Ì«b.YfQWU|MUH“£g| 5ÒX@0鼂özÅì9`.’’ºHDxŪÞÒþxÎß/1—Ë^ïÖChùq–sV µ>åS[[‘7Í{R#yb¶}ó[ò5 ?»pRsßggûJÍ~’€O[iéznê“@zÇú•É’#6|Auem{˜Óê×sž]
!wì™ÒÿüÆÎcÈ ÁðcO§1õVé\lxÙ÷Ô” ?‘õz2¦»a•dçàN³Œ"QŠnÈ Æ©¬·X&Á£ÄËË_Îã¹ãÛàé8 ÍoYÊPÏå¦O4Y£^ 74Qå˽1K›tÄóŒZŠ5èRž¢ê‰²Ð®„–k™ï«ÆC¨‡&ZÔ*³I†dµ¡Ä¬†–>Š†Vs÷$ŸO(³y´u0œ$K“b1bNPª‹N‹ö`Ȩymù¢¡Uñœ¯1œGŠÑË´òµAãïÐ>…¢$¸5·lMC™Ûw_¥Í5XÛ|®,d'“ê¯7þ` +j”%8®2!(3”%Ä@õ”-ÃO ÝÔ²dA_Ò¼ Þ28¢bA›Í®TÚ&,WÃâMw-u«IX¯-´À2Mö†4NzX +ÚÈR)ö†¹–[§G9à%Ö˜iø‹wÓ1`"ÄßЮ:”
‚›HzÇ¡™2ך˜áý 6MŒ¥Ýú&8çsåwp¾±|<6è1=–U¼aëÕv¦*êuca#‡ ®Nwl× +Já´–bRöÛ]²‚Ê•ÉI¬ËXÄÆÊiãU +6>Eâ~ž™Ñ¶Ô"aR•xµšÔ&¸ÿXÄát˜“¿Æ€†'Ñ’£ùÉ“•·ÊÜ]²“êþóžð}¡ã˹Œ}ÓŒ:ŒÖ'<-„
j×Ù£L–NUŠUèêŽùùð'Xk㦖[ÂDÖX@Þ1ãB´#»ëÓ±²ª88®ÑÍŠ©_hš;„gJEcAÑKH:½køK†Bæ¶VòKȆîšÏ#o + äh0³“ja)ëÁ¦ÑýÂƦ +B7)œ\[û1ž²àò¸Vw¹~L"¸'BÒ™ÜO63þºî _B
O€m¡˜Dd7íЗjýD8@>jR¨±Z%$áqÛ[‡àZqPŒX³É~ïg8ô%º_´¬Åƒ³¢8<djXчo†Y öÉ#‰Ö*ד…w›A·lm<NØ_ÎÙ'Ñ¿þ=T^mPF¡§qSÒ¼hÚ)Ùô•èÐsQã‡ó,ý0‘g +{8Ø-Ð=Nߤº/gÃ"k/Á—µºU…"»]¬{¶%^ãs,ì{À¿ gëL<YÉ4¸VïœB/=øfÖ|XEi\({„/s•ƒ·,NÅ ¥voD’Æ-Å…ØwE$[ÜQ'w8³@>+[LØrdjO‰Z·‚ðpLs9ˆbòS%QñI€@¸nÁ¼×ÍAz°+?“ªÚœrå$ëéx¸ŒÅ×õµµ$‘,ÕhsAVÃ/ã)ïø_I™Ìø|-]ˆpo†ˆØâì¡=É‹òº†¬[£¯SbTK>€LAÏÑœ»’ÅþýÝ|ŒG“8;FOcȲ…r‘*åÄh-fî;òg…F–Æ‹Ö G¦¾ýþNû=lü{)™V +œr9ÕN8‘Ê*ÈŒÐñlÄÕÏ„7m$©=Ï+Âïɪz®S´óÕ$¯1÷I¸7ðV‰3݆®Ý kÜ)¼ ÷Ô#Šd(†gì +ZÉòëQè€UßÂɳE<)9^OöfŽ×“Á'O®x3 +gwƒ$‡0À94¶„#ß[FÜV«x×N.{³“|Ž»´óÔEþÐœ^L³=XLëF#PnéZ˜äÔëaŒbÉ®ŸX<Sãç¤^y!¸'µºynŽ\èÕÙDpFDLêÂ+vý œºuuÒ¹£,à{fù÷×e÷9´©ÁÏÿ±ˆõÚžËvsÞy®ŸÑ¹`ÿ¥)X±sXk™Úï̵àAxMB•k¾™&IêÂCÕ7…ÒÅ~ϨX .¿}âðï¯Ën?´ö%yÞ±ní™ÅÀÚoRÌ#-KóÑ2"`?ÏÎP1éóeðІ›à}Cyô»#a\c¹{ÞòýåÝÞÜÔC‡8 ž+c.„þ‡]iéŽÂ£´»JïÆ®R`Äè +¾ÁtkZŸÖÅÅùoؽ~“cîÍ@ß¼„6\_×}±è%¾iÔîEÇ,ïÀ¸Ùø¹?¦ÃMlGÇØÂúÝ"ô¥Ë¿¿¼Ý¤‘]2ÃþXÿ¨)O^íÇ…ÇÙ}²†)ï õÉNô Å-¬/uæÓ„=
b!÷'W6åÝ«+ÚØÝã‚ÓÄ…Ù„±L6¦s{@¹÷}x>eÒfG.=
çßÔo…‘´½¹Ëc€Áx®÷¹¶ÏªcÄí
}¢÷€:›-Â…Y/Ø-YâB©³W¦ü” `¸|v™œŽ37È(BÛÌ@‹Œ¥´È~›lMLsi«Lcäð©«â{Þ7/_-õtÔ–AiþA]Ú6
öû=Màë‹dîð£Å@™)„z†uˆMLÄÃ쪉-®iu,8’pŸÓ(•oT†°pÐðÙQƼq~Ì2Ý|ŽÖ–ÔÐþÆÎÒâ—Zí™?ÅKÖEëÜç×+”åÊcL”²®Sèá«ðiÅk #bÀ´–óàwZ. i}%Þ0áWiå½—ŠªùÇ\ïM,,Ä ‘ÇSìþ°#µbkå²ÍáFçùÓ¦ü´Ž_ÆãAÒ‘Tá%:þõ
Ÿ…RÉ×À/BA¨~?ûN
®ñ[&÷àvònLTZ2zm=w ˆH¯šê! +·¿ö¾ +ºà!¶U[…‡Ó\F®MØg¥V ì,(Px +p|ˆ+G"ªÜNFx¾›–pšaj:'HC"~òV S +¸—FÑþ?Õê +~ë4å©$šÚ×y²Ž Åc–©æ—ööððKÅô +šÂCeYU¤p:ŒüF0u!ƒ<ž +EU?ÒQrŸçB²iÔ‚ùS¡°„mb¯< ”u°itrH5£§„^ ªU”ñ¨òÙµÐÚdë~/ŠßHCÑhP¾ø58PKÂU(”Žb܉3qSLOŠNç&‰£Äjðtiâ©Î¢Þ$÷;Ó†GØ.&¼%™F]L`eç†à¢Ð:<àáÄ:½‚ÝÞ;nJ<³È: ò«àL"@cb«"@ +8…P× üÄ«g£{GŸ¬X–8ܧɒâÇÊ« }ƾB!IÇê#€Ã,K›•)>[Ö—Z© +N³À Ìi? ™ªÈ¬$Ü(™s¾=œè<‡K!YO*‡<at•’䉆âÃW+¥“¿H›ª¡3*ë¸q£ž$E¥ÇÈ—&Ì2ÇJ5ï*`~1tVkcŠ{uìý +{ßÑ„¸§ExU*B@ôâUjÇz
dìðԨa6EQÉ6@N1SG½ïGÒ¡ŽÓÁÀ×(e&ZBkÚ×à_Ó¾L$d\=ëñÈì\ X2 +Ð×eešZçõšm‚•ž±e¾Ad;WÓ:¢Ÿ?üˆ3˜ÊÊ°—1¢ÉØh™ƒá"O‘‘$òÚCØ®©U¿ÜMJCG=¹d駻l,vÉêÒ«¶˜¬,˜ÂN$’¦òJ^í§Û¥¤C§¤ûZe‹ùòñá¸S½¢i±^ŠÞóàX«]Œ9%³ôT¡(ûÄ¡Ÿib¾HV/ßï÷¢âßUFû†þW·€\ö¸cDåD^¾’ÓU~´Mm¨êm]³áy.Ú€ÖmZ•˜ë2Ã\‚(©
²žfáV³¦F–qÂŧsáötØh k+rWà‘±í(‰*¸m7Yè
ó Fq¾Çã_p=×ÞÆ +Úïy!+yÉæ>wîæüâÁñ¹Ÿå̇ÉÑ«6@AKe{Q==ç2v¢ÌAß.m£Ò½¼ß«û.í»ÅÏE½ë.2g¿×ün¶ÍÍAYçÎÝœ^<Ø_8÷ +‹ +Æ?ùJew±›Ö~ÊΖcç…0»ß3hæÙ|ÍƪsÛ¯ŠÂá.!®[$[E‡ UçÜ+;0wu™ïnaƒIU¯Esª!’uE²– +R8·3é“–y»uþ¸jðÿî_x&l„‡‡[KfŸÌÕÜdÖ©£Ë#3a=ùp‡R™9Xj>æãèÄ£è;µÌmïÈØÌð¶yë¿XUe@c°‡¬µ¹•l»!°!ìbb‘n~ A,ËÊ +O‚Y•FŸ$þÁØŒr
h’0œ`ÆCm0òŠ÷kã$i%û°…ˆ³)kB[¬R×åîûêãù‰ÏF¤‡‰æ¶ŠKW5¼¿6ë·Ñn^øÀj-9é6‚l¬H.™Ä9kõuÒ¡´”ºm‹4Úa—Ï"Yûm2àæ&-퇾cé_ZgoÿøçÛ¿ÿõG•¿ +ZæƵÜB–¯]/“ãP*X•Ñ¡0uŠÿ¯ƒÆñ b(²ed–x·ØÝt£™)•9Âg+š$ùܸݼ¦~=\ìùÏ
¿ú_6Çq7÷ðBªÌŽž“çÙ˜†áÄGæƒD
2ËÈ ƒÖ¹ë=—÷%³Ì¾ãXÚý8öu6¢Ó`@—ô&Î"ƒ + +™úÝ‚µä©ÝFÚÚ0Í÷\({§‡¢ŒßðÉ£ªZ¿þv"³{‘ ì®!ŸÝ¯ßÞj…¸LÖѾ÷Þ@ŽÝ²”®qÂ{Å,åjŠ°¸Öø¦BKdiµì4ÌâÈ$ÌøNnÌ®|öÀõPüžöÆ{³ße9H`3ØÙJ›P4Yèš®¼BRxÓëÂ{óp\ׯ|‰Ôû‡ÔB^‹Z–-²È^YEî÷I„}½˜7«æ¶?üû!k^ÙÆ®õÒûÿ¬^™,ží:äÖÿ†&d3õãi;áÊø¤P–Ž¬M~ÈH,mkÕÅ?dIÀ±*Ösöuò`Ç°t¨–O/ Í•,ýd
¨ìÓ\nÌšKtÖɲ]ñÚõ¦gT”Z.ÂðŠŠâN²ÝÔ²/k¸ç=Ï©lÜ2ú7Ó1bŠ§5ñ‡”r§çM¦šõ3æã>6¶’É^ºPüÍ`¾…dŸÜ>Ýñá¨Õå²j/óÑujz 6ݨàÒCõÿ(/{ä:r _E'p‘lþÆJ«Ê±ãM÷ü‹$gåWòº6RqôfHh4úwãÀžÐ9Ÿ*¤öÞé6ãæ!E§ÒÕ˜0biöÔª¬|DýM¹Av/ÜÆ™4„¼Ï#iŒs¤L X#g‘Ÿ•¤/ê…™OðÈJà\gÊU_Žø?²I~ö·¦_äYöåP æ+=K“ÃÈíšÛzR·’tÇ +ß¼´òö¾on»¯wbùV¦.`ëV¬{÷1Y¦¡äçÁ”ΧŠ‰ZÞò)s{ÖOtã1õ\èå¶Î&z¸¦vj.: +:xAñ7ZÖ¯Ð[ÅÆ5Ž*[»¤¾&„u¸×‘+¬£Ü¹CÐÁzv*gbÈä»l1 +=Ö²‚1P—x\ Ž}ÏòÁåQ A=6öõzÓuam[ćÀ)ŒcNÕ晊„®1óvÖy8h€MavÞë¯À¶Þ;ìÔôÓ*u¨¹?`w¾+ˆFHÑ/0~vî?„½™Ñµ [²»K@¯øvàߢx³*&6é™W"ÇÇ"¶X“À1Õ¬Ø÷´ŸÆRÚ©¡÷ +³ÅÂp÷ˆU›Ýsz¾±™°®››ëGpZ8µðX)Äc8Ùù?N9èÎ÷™8N
be0YLdéݦà0z‰ôÉÊØÒq<¡J'D¤3Ê硶é"ò8÷+9Ÿ™üïد::‘§|½iuKëûc©ûÆÙÆœ÷Üôqµ"©ÕÓÔ°¾8ÁÜßæŒ4¸¶¯¥C}DfXÕ†s8_1“‚%Þ-û§VÆvI2W¾¡ŠQ‹ܘ¯®@4
øáîú*ÅÒ‡G—”ÏK³>‘¿Zxnú¤ÿfÌý|«Œ´z²ËUKòËÛ¢(Åò$ØÅBQÔ +!½_úLÚgºÿàÏÚw‚¸>µ(ÅËwæàìÛöÈFèçö?|«…š+ÍB满ε
ï¾4‰ù;}ÙwÐøÉa|¡’²6Åã$Øn3;¡—{–ö¤¼^ 5sU¿AXqµzôÛ„ËsÝ=òŽ0:éJ±]„ØΨ1›¿‘Øæaœkñ¥äÜí¨Æ26ѹ/M½tŸI²´±ÍÛ¦ý¹H@‡/·ï;de*¬Û»!·U®SAõ×þå«ö§iÿBâ¿ý™ +endobj +642 0 obj +<< +/Length 22178 +/Filter [/FlateDecode] +>> +stream +H‰”W»Ž]¹Ì
øNâô‚¯~0V:± +ŠÙð'XŸ+êú’¯c«Ì8Ö¼v|Ѫy)UÚ++{ëbŸ˜ŽHá2ýw‰”^HGÜHgšôI&›1
E.n&©±)… +pO‡×m_@•¤Z»™PA×iù蛲¨•,Èä®ÑÖQd…l¥”¬|ʣל´»^ûEÖP‘v±›YAÜÍ,'(‘f˜ÐH¯:>³*;òŠÉá#ÁõtÊ…ó±ßOi¹ÒšÞª
– +ŠJ-o¨´T± + Æö&`µ£4àÑÉy +í_Ÿ¡)1º7fÞ‘ü¸$“*r¯7¢¨Â$«ŠAU›©UtÂÿ€hEí\gVUdW†"$!CÌJ†$ã\0ø¼A:8ó¾A:k Ú©(±„BíaB™a/L ?vâW0Lò–!é”ó‘[3œ óù +A:GéhÐþ0Ëd…!Ë[Y™êþ¤bä™Í…çz
93“èÞ…Emž1Et¼ö™öGz#È–C# ?&#¬Ë”p¤à³oãÁŽbÈ+IŽ¬ùß×GÝ»gÄ21,ÕU#ºF¸+„×WD>f¢}i¶î×ÖˆØó^2
‚´·x°s„¢uÊÜÿ„ ¨TÀq¬+Xw¼có•p•GËØ¢E”m‘EÊ pHµÇëUÊø*¨éc4ìH/Îb(Q5áchw»‡=œœ&ç&ûZ4p;Ó K2q{&ª€ªQAQpjÖö^J => ºa´X/>Lnºs:³Õ +¹ÑRã6úL‘‡JPm‚M1
£3üzé "nnfmï N}€*,óP) +H-¥"eä&¿?òš j•BXÿOyµ#G’+±«ô 6˜ü&m¹c+BöÚrçü IVu?é)dÍT‰]$$`‚ŽçÒæƒÆ=˜Ä +ãèK‹†½0að!/€ü¦¬ˆlˆHUÖç2–é¥*³#’r»úSUÏ%MIfò¶í¡E‚~SÇ`çà˜à¼ØÆYŸR×KÞÒÆ2 }—ŠÌk¦5Ò&5ÈÅ2¶ca“µ´¶Í +Ê7cÔ
L¦8382§“M'ܪ(ÕQE_ã6#èÔŽàQ±¾ñÓø€tê°G8PÛQdŠ`¥Ü.8Ž\Ä™J;Å98±AœL%5á¢ÑŽ8™ÄÛ ÏÆÎqÍM5Pé%¦?øý‚î2¦Æ½6‹*çÂäý:DŹÉ`66šIèyŒ¡ìm†b!|”#ž‹Î‡üTmU60NDAˆº
T óYõªxæ½¹\Ç8TA6:»$ÿØŠÐê<‡Z=ñŽÐZŠD^¡xá9JàÙòÏxñ46CVçÀg+°pÎyˆ£\¾SbÞÆÁ‚~.ÓÈ–§²k>N‚sº‡säYNhÈ# Hýb{N©Ï¬ÀÂvS¬‚djÂßö¯'²Æñ¸½0YŒOª·ýrÌ®Ž—§ûm% +Årþ³X,,2‘R¶1)-`bã,ØëZí×ÉDMʾÂk/Vþìü•g„¶£ÝÒàˆr½^úTÖ±ƒ%u©Ê4çýE™ +¬÷B*’®bÉÆóß68÷ð¥J¨ãÚþÄÇ6ÞêºuºOñÃ5#ýùêè?¹1
¤£y¼p‘šú‹@ön‹HxªÈ Uz äù<g="ÝWI"‰ùŠìO‡ø®êQCÔó_t†8+r•ášÐß-Æ¢È@ѽx–vÕUÎHŸ#îvܪcC¸eé—eÀ¦ÑÕcऊøˆ(K_÷œû]çŠ3á·0Œqºeöè×÷Ý2ý~`W¢|ºÏï’]ú²17mÆí±G€ 3± I¾¯x2
¼1°îž€ßZåoñßW…ñ½m£ïðTfD¦
]ˆ'€ö¾–(ö¹!fÉìxqHú(†úYŸìA†Î@Í`É5¦#ºAÒ/ÔKþ]˜7'£2 ‡û¡âùM2˜Ypú¿×´äÆ1f_é^§Ø¸ÊûZ¡vDG„ð3À&¡ÓÓœf“Ðñíup¼”‘ê)†¡;ÿ¿ÜðsÜ(ö˜KÁ0왪Pë-Õ3_"¹ænË}®QØÙè¡ËÚÞwž4Wšo%9ÊRœb”†«íÓ¢7·Ÿ¹›’äì•TÛQl§óhpÚß´±¡§ÉêG~µ©uW5ˆRP[ +ì¢ÆIA£å"-¤…‘Z +«²ÔdïO}W0\‹†¤ÚÛDÎÄž‚9r <œ4J”[òb˜Tµ•¸ ,±¡È:(çB¬£½2·¿4( ‰%§ö¸ÓfNm~È$Áq“Áfø‡,¥ÊÖ”F¾˜«†³¥k|
w!-’è7ÌѺ¹@&³’¹•úÖäÁWy¿œ9TdâCij +lX·Þt$ÓæŽoŽV÷¼ý_<øÏöQcUbØG
L¯`vÃnGTðÔ®\Á¾ÁÌqLÓ,k^áàs¾‘”¦9)¾§8Ar¥«-‚gý.á`÷-ã:‹Â(/†üú܆CÂÎÌm!Òm0ØzŽã(®°º«X +ØÀÁ¸C.SÑOm;n}ì`âŒòK·™o_Œá„· àJ³Šš®(ÇZXߺ:À +IA5 ÈV8ƒ +A·8åz•iÅØØæv§¶D¼ÁupÏœŸàÚ¨Ùñš[‡é¯àê¨+ÀѦƒ¶ð@À +/²O·+ËJ°ly€€¼º`Ë#<¨Ì¸)0|ëó)òN*mëR¸‹Ïc8r‚:¤Ÿ µêÎaœ—88óòtÃ]JTîàÚéeð„@f³<Œì„i¦`50d©€]Ÿ¹º_H‚~ñnˆL1ìí†×ªïæ E”· ´I;™>°ÌÈCIYòJ +Ȉ_Ea}ClµVy°½ÈÁ–òðãLgÄøÜK•–ðx/ 8e˜¬+wI£OòÙvÐè€ 4ùð·è(»±ºê-Ëx”Q,uVSß·öÐ÷3%¦™6WFlßÉ`Lž²†ï£cÔýmÇË7T6³8k8…€“—+øi.gœÕ[—¥Uƒb +Sð À™J»rú:8ª¬¥š0Îv•¿Li…‡2’ŠÝÐ/ì5êÙ>©é;U¡«¹a‰6Öm+FÅÅC›#JÕÐäÛåTèØêAƒ¸–àž=*{Õx¡ “tºn¯"㱇æ~ðø>Î*\΂ƒa<wåÇ +ì”M¢aCAµ1/êHꢙœŽ±”öP³¦W¦Ëõ+½´s8°ŒxDKVof‚ÇAioŽGîŽ=øAC½ƒ8«¹(Råú«gdc´‰<Ä …eÍÑÈù(€‰¡1sAÈeÙ¶š¤çñ[8X~¤{݇AÚ2†n9•Ú¢Î(¨fd¡z9Që]5§±å m„ûãx¬£-õ@˜ÐŽXaÅÅŒ‰Êð’>7“h*&·ü)јÛÒð7[ÌRírË9/í†z3$gqž˜fqÚœ£ +P‡¡¤§HmÔvJâìrÀç©ä‰|¡ÑÕO IfïI¾9‚É—6<"ßë0NI2RL‰ís†y.š ›äU,Pì¬R¯‰^º†OÙ¦¨ø¢Ô“!ñ–‡ÅAÖ¯ÆW—h…ñ.1¾€eŶIŸiŒ!JÍ<”²KK ìS趛†3fÊT±F‹ÞÓVU›æmÞ<kÛbø¾„ïôu%Òðý
Ó4±ÛyãɦU5×”åæ¶#é¢Ät–mÒãëhÿáÓa¹²;›Æ’ôF†ÙH÷±`Í3`¹¸Ãr5²—:µ¦ªµúøh64i\zÎNç?ÛG2êF®XŽ#!%kŠr‰Ó‘UiŠÅoF)Ã~春ÚÙAÜ£Kæìà''¾>ÁÌøÜŠøèdj›ë9ÒzÉ7¶'¡<órnËUµÆ~TIüõʼÐ9ƒÚ,K‘šJ¹ÿŒŸs$èl!óDŽ ++•p‡¤V‚ˆaÒúÜ=h +‰jd·¡ ›¾¦t[N +fÀ*,\@#U»Üz
Ý´I>¦)ÌÉž‡ØGX)¸Ñæ`^ó¶@ؼÍ;cíÞ9T½–¨Ú‹Y•ã¼òxüÉßÄ<— ±1ie07pÍüà}µ•0åáö}ls¨@æ±ã¬5#äÖ +3o <®Ä®mÀ{v2.<ªf¾ºL·ªÀIµÇrèw E[6d(›ºÛA€}f™†D
S…ãÛÌd¸Á‹â£Åq=ƒ7eþ•§7AkÕ*A±<“ɾBh[ôàz*‹`lïtQˆ)ßY«äVb qioÀûáðòPdƒð‚y»8œ+Ú¼ø“ ¾½ +4¯œ}FÀ¼Øa'áí›Äóµ›õ´Ì +™¿îZìøZÁ{ŒEÄ0™Í=°Q¦À2‚ŒäU +`X–Gƪ6»*p˜4q(NRòrò´h»u1Ö˜|&ªÅ¤fﮑXWuJ|>¶3¡Ç[:Ð>NÌk{Æ„•¯abÖI´/©irPÏMCç4èIܱð\<Òtœ…GésÊ·iÆ6ïºËâ¬HtV÷›hñO+}Èâ\KwY‹HwðÜ¥r›,}x—£ç1K‹Y¤Fe‰3ßÒ©¼Ý +è«]($¦øH +A©ZÜèÛ„ÆLß+Ó\£BšnE“Kƒƒâ^|D“¯º˜<y~õ÷OÞxþöúáíO7¯Þÿrù±GßÐ|güòãË·ûðþíÍ—GOŸ>¹^?¾ûþ§¯¸øñå7\ú{þÁ€öÜíŸEÛ`¶HâÎÜxT³†“Þsi1”ƒÜä>«Ýµ.£’–D,¦9åà‹:¥ƒÑ@àÀdž 0èùèã”Éo~fºàûæKûÕ¯ø²Ýúú…(Ón£fãß…Ç*ÝbH¥È¥áL½Ó˜%š-E|Ã"vÈâ‰Í®fu9w9ç +ÒJ~±¸Í²œÉ àÙuüµ‹U%3XÊ ù¶C¡x¥ØƒnI0ŸyÁ0É•uë´˜œP6æO&Ä«K1ÀÞíqZ˜‚Úe]£ÂLöé5‘¿b;§^š¼á™2š+áµzwò‰»ýäs]-Jñ«”èäéyC¶
£ÓÝTnÌ/(:‰3PŒü6‡["ì52té·<=/×—.Ö@„… +@óOÏ¢(K,u?Àžš@pT€ŸMÔ—%)MѨ:dÊAæhjwÖ|¤Y¬}%ÌcQŸÎ/Íû¡NgÞ€ðpÖ4y®äì>ŽpŒ®14áò +ðŸŒÃ·ëÕÃüçtëF:ãì}cI¶õ ÆÙ”)4¹srÛ¯ m³ƒØ.Žƒ¥¹ +mÉä-5‡{•ºƒ|G¸¸6jc‡1;ë¡%J½ÇÄÑÜèõcÀ=ðñMKA »nm»WîQBçd=‹K®)×ಳ¶kÙ€àªA*s×Û +P)I.Uk[ñî'ÊÞ›§/N³£Å®Ø‘õÝ—fOd׆›¢iN©;ˆ\³•Û´ª¸Éz,ç¢o²ˆ|2p +t6¸Õ&€HóZðˆõä(VînÌti–±«ƒÁZ +TÝíÆ +ßÁ Üܵ]ªÊ¬ÔÄX9û±÷À›»ÛÀ„o¶1ê=ÎlÅeáÿÖ–¼S·]¼C«ö» X1fÍÀ§èŒÛ“tò=´×œD¼ÙÍÒ‹–|WçYÌ'ÌW€â¼æÂӇܷðˆe˜9ŠL_“a‰Ñ@n¶ðªBOÙ#p§uDC¸ +—”¶Ù’yË×NT³¹Q‰WÃ5{$†uõjpœ,¾1¢z±Ð#«1
L‘“† z»Wm¾pú_¦ËkxæÖ(Dì‡Åi¢¹·9¥O”>ó§DCÑ´ìñPÓÍ‘o.( +écëcàÃÃa8Ÿ pfáaµ¸¬k©¦ŠvÉö"ÝðÜ‚•
wÁg–gòEj^Ü +YPŸ=ÜpõÎ3¥ @3~,›nÖaO±ºè!(kâ¹…r»ëÃÉ‹%ʲc†0/â]ïÛÒ(#¢*&;x^[ñVðF%¸˜ÅÁØgZè!Çpi‹A
P{\·ó"hŒ;ßLóX€ +âÒ¦V²Û·´Í›ƒ6Ö†eãd½@D!ùГVãÆ ®ŽÄÀ×,&yÆ‹[¦Ù‹*·Çéj‹5[<®LW! +Úž‡§²=äÏðÔLÞX¶íž%Ë-ç°JnžçíBb‡¯\/ +Ž-“‘èÛ¥¾Aô;úVYfêXBŽ[ï] +åN40nñ=º÷c÷öŸÔÝáâ\õÖ(žâP4º|Þ‘Zè^#÷Ÿ ó0¶úõÑ2gË¡š <Îø(ì\Ïà5äÍ…`êÇ–ÁáÃË`9%dðª7ã¾T”˃»È7ÅwB>ŠóG”n¸îÞÀÄtí_†¯tßâÛŲŸ?5#õÌþr5šwËñѾ4QŠv„>%¯Ju¡û£'%g7
˜ç2w_¯FãS¶o¥3 põd›ë3¸oG‡'½»qפÆYÑ[Õaæ±ÁQx2›B{üòÅB
•¶Î¼+ÐH¶=ƒ»ØC¨RoçÑ + +—ŽÃËJ§ãž±•)fâ½!ú8§kB}Ó=9CÚ5, J mºÞaòý§˜š-—âÊÿÈ4ï…æ‚/VfÍ)µ°_ŒÙ³ë4ëÃ5eZ8½Ü¡š±¹Ý/7ÕUçf~Íy?³\h‹ðöóÚú”dXºÁÕGSòdÿ¤vì¶ïÜn°·÷'\|¬ÅFõÃëf,l–è8µÖ¢?ͦ›íÑÕŸrø0g<š)ß&Õt{HXŸj!hGßÑùR’f]mrìAØzô-{Ÿ8¿)õò-SÈØÏR4r[eÜòº^nô¬Œlæï]¹P6‚÷ªÃ™sp
9üR +œ+Æ_»ä•£q“¯m1·,øbù>Û¯ØcÎvù¼x[7¹»Å§f{ã~øþ´Ø£F¥?¹¶¸irMOç½Ü\>óà5g—¼‰vÚ¤Fórø‚w³?|¸4׶»Sv¢ + ›?ƒ·<œŸ6ï¿ØûÊ‚[0Cji ¾ñoñÒ—bÊ¥°•ÞhvJhvÊì“âñ‹ÏרIh©cµg]ÇøV-ŸvXiÇ X+)^˨UucÉH¼ä» +|Øvß +ªh +DzóÚçÔü@Ø™'Ö“´>d¨‹¶y~š@p#‹HqÂ/&¨ðm”ø"4\[1伕.[&ß®Ï-Ør¡…–.à¶úp†·Áý©KIŒ,,~Q‚ÙtAXS#ÆN«§ÞÂBãËéÊ@H³Tu»ÅÈ÷ݳõÇáHê%úˆe¾¯kxþiÚWZm¸Y®˜!éÜðr%Æ´Šeka óÕöÏAî¥,VÝÿŸíª¹Ò,Õa©L}ÀëÙV/ÿFƲ¹ÕçíªÕ|¾àI>OÕ +v¼—ÍFÚFù8¹ä…âKÞœ'—»">^ùâáÛIѤ@ÿVÏA„±÷ +“zò > +ºšx›e§ZVq%Í\Ïyq³ÑŸ Âá3K¦·\!ópN|,Û'‰OÖ½‡»ävØ©–ŠãcT›ßæâ¹oÜ-ßpbCò“mÑ“n{äËSv/WÑl‰ä…c
pötëñŠ‹/Ò.|˜ßÀwKw¥v°—_vÝ%À5“dmΊìj ƒ½zåû‚P€”ȉÿ0½§û‡®/‚ˆýb¨øœ¦È^ài¹¹¯Vý¬X|NñçÉVbµ§ÐcÛa@¾²Ê½‡¶m9³à„ +ük¦¹ºd‡ñÛ,±8@´îdÜGGó¬²ÕAöô*îÙ4" \ÿFyÝÿE„åÉÎf5~FÀH‚é·ï{ÿÍo´É‰õïµýœƒöáÚ¸$m^žêø<IÌÎtîžÄÜ°ƒd„¹ª–V7Û±Ô^<$ ˆ’æ·m» q˜-ç€úqÚ‰î¶ÖòaNÀ±âÀD`uM£»kaië<²ÃómŒ¼ÄÐLéJp'^ãùoF¸ñ‚˜Éþ"Ûòç`Ìʼnœ÷óiw‘CW~A$ú™›`Îe+?u1€œq*ç—Ä5‘…XhÎ[€ úñi‘èiÙáØ!늱G 089µ}šl+ +hÁVÝzFEh`~vSËTœ–wðQÎ<äÄ}¦sfWøæóS0߶ +‰me̓9"8¡ÆÓÔŸ1¯r?‰¿zô‘ €#JgÓòc°R'Áâvåú‡ß7
þt¥Ê Í=W€ZÃÜö³“î¹;Å–eûÐ{ŠËÔ: Žø–ùeŸ!{ŽƒKù†–ÖA"´Ï@ôE›¹qä·~žC|òÑ–vòX'ˆÍ3AÏ¡UC)Ñ2¾¨ý$Þc¹I§Asèdä}ÊÓç^’X>Â1ùaaC` ±õhî÷w +VaãÒ´yŸÊìŸ;ßYr3?6±mFÈ!¶ ¥ŠÐÃAàðÒ®Œ ËX·ËuAŸÖNœ”Q€kP>—+ëÄuŽ”"ÁÑìòÎùGº1ÂeÏŸÀ2¿²ÈmwÙ™óvˆôÒ +`z¦kÊ•`ºR_Rw¥7zÞ¹KdBÝ>ÞÏ؃”?y3Hîx¿Ç}È}â¬Gÿþ¼`‚º|?ÇPˆ‘”ûœ‘˜bjÐt‹Wù§™_Ó¡}ÚINt¯ŸäÞõŽàr«T`üz¶Ó¸ídØ£,›s«±BÔÉIp|Ç2áehÉ÷üž¿¯ñ;ˆõ¹ŸÿY-µ{ŽqxX{Qí‰Ñv°mX÷Îîµ]»åtë‡!è;j¾°î¢3„¡"ôm
ë.‡Š5ªEü[|ïmd}¯HéÁ•Ÿ´”ˆ|êu»
‹Ãkî|ÝYàèÅ0À§‹þ0‡åC”Š>bítÐíÞ慯μ׵à.õÅ$ËÑ|£»à ð‹ÍpÅ8)eA¼v'øAÁÚôüqg‰…¸kjá“•‚^eŸó=$ 7œ¯íùðe
pIW¼}^'÷“oÔŽÎÛ[ÃMßz*¬eÞ`^ +À3>*³©_^Óðù7Ƀ`ò.jgÚYÓ몦k·<¬M3ÂÊd®¦aÑ!ÿt?¬›½-þkÄ- + Y‚kžìíM‡ÔJŠ×õú\J³¸ +¶ëÄ„èûaÀ%É5-… jQ˜ƒ£‘€Ì·»ŽÎÖVw+û¯ñÂÍÛ* Üa‡{¯PlOÆ«õUEMpu”`7RÞr&Yþ¸Rk€2íÃòN_bS
ðšÏ{øî+WÍ'·¡.^'"JKQÑ9H»ÆÔ|ß̾FÔâbûíªÉÒìÔa;ê0Œ3ý¯'òE2t%“wú)”?®±õó<Q)Iýh«(~ub•.bÇœ·@óE\Y ™Æ mÍYµ¾n…šóg\9‡ëq+˜jdfG[ß +“YM‡Û6ö¬ÍSÞ¯ +3óU,ú1f(»M*èa»ÎÉjyO.(ò9ø9OŽÝ= ò8ѽß;|Cp•àG8äTFešŠ¬™î%––œ‹¶á[½¬¼¼KNfög#è-;¼Ó˜á¿¶ûF¥Š…·g3•3£S¼À%éZ^ŸWãçà‚5ªuäácècÒkñÞeŒ[¦’wƒ[U…ÒŒN»ÏÃGñ½Yá±ß1–Œ6~Š‹@£lØ×–6{&%_ûN:qòÖCkçà)FÝ;€HŸ’½s@8æ&åºîÉž
_ƒŠéW‘BÅhÍ_õÌÊÖe0ñ?ÎÃì%ůGQÙ¾îz…ÿË€2zî]?WŽÜè]bÝïãDDo0¡â:¼NÆ +e_M å!¦òU,ö$þ%† +3Çn]vø,Š¥³ópûÅÁäW¬\FÕ•ø"çϳ3”ƒÃaçÕJšfT
Ž`…ð¡™ +—;·Þts7€¶4kÃ=XIv˜Ê•Ü +Ü]Ù -Y<Üë4™ÙÄHÎX’f1p,9qòÀ¶ë/ï ËôX‘yYx¡©ëÚNÛ³öÈkIÍ^…]èÃ%ÇÊDsoþh§ñCüW«n§Gé¦üM6ì°Î„Ye<O…ø¹‘Ú2h@© Î!²oÑø;d¿©ñahãÊs
*?›ŽÏÆ¡nÉFýÞ«ºûy—üAm'Èm‡þUΨÙr5é4sá\f¼rovï¡£ÎH‚ºa¤¹ÈMUVûŸE¶C•·éDpMå h)®ÿ¼¸&;~¾¾[×Òøήåï–×–ãD0å=DþÎë®rpÊøÅÆs‰ÑÎ/<ª+„é™x3¹”£qÔe›Þ†‰ÝÔù÷+²‘VѲ‚y£yZ›T°Žý`/S)§Ÿ7?¯¿ê÷Ð +Ëg2ãÑÔiÇ_/”ºúѳ=it*OÓ5®“˦†‚O1ŽµÌ¡Ã¥sQYe»ÚN‰ŠÁó¿
µPTh-;¡õÆ`“Õ +~Ô]ÄÆk‘XPdN&žtª{›HìÎ6âÁP—{6Îζ²Å}|ÒA¼‡o2éÚ<Œ3õ€; ×êåµ÷Kª†ÕTmn8>xÝ7yzÎðQþ²
§;εÓÝNê +À9Çøº
–%ƒa¦(óeÊÔš„Ä%Ù.½‚.^«Ýß“óDÂ`g/ê6–ºÕ¼)l—óNÑ5î(cKu¦˜:|rÄw³Í5í܃A-9.ãò¾Yn `oŸ^¤Ëü($ñ‘j\ý¬õwxjEÆÎ_“f~²Þn˜ió¼{ýÕÍñ¡nZ×o]ñ +Œójü´êzÀ•ÁÅ˺V¦‘í\®‘hœ©; ñsñ¯µüVÐ`á¯×ä¤/ÙñËZ%´¹Ÿ\)UȺK_¼Ä1>–`gÃBÔ®ú`c’/•.ccÊJËx2Cô¦ÈÛµ¶ž§pׂ.#¥õ¨ùÇõ0Bt40~¿a§¶{ozJã
FZø¼¤÷€ÛdË © –IçƒóÿœÐ9/A²ô³˜Œh÷¢¨ï»Aÿs¹äªÐìšnçñ¦È™ØÎãòçlü¢…ÀžÊå¾h?~mTì‘)Ø…Üò¤É7|—lK§F£KmhyaÙÔà†åÓ `õž +àú&긪_œR}\ +Çõ®{ˆ<š8–tKµk^›»²8p÷Cêüz£š4ˆÊ0~ª&OßøÝ`ßš‰+>
†Éõ-h¾¦¸Q“,0ÀgbeYa¹f`µ}+|?rÖlæz¯ì³,-@Xv–E$@À‘pXYé õ²µ¶¼X2H›é>q¸2Ô‚ÜBhOsJ-CBÌ(Ùö~^+;¦”Gt’EsyŒÝ(ÄÈSk–}<
IZš–/T–QB}÷ÿ +0 +endobj +643 0 obj +<< +/Length 21519 +/Filter [/FlateDecode] +>> +stream +H‰dWK²]9 +œwDïÁèHãšz\û_JƒH¤ãª™O—Ã7I~ÖÞük:ÛÏ¢¹ýO¶ŽŸ=DÔŸÍ* +ÄÇ‚
V£zk¤õµõ³Åa ì^Ã+’"pÍ-œ/P|Ì7
pNê D¹-xÄ1eÿþ}ñÍÔɈo_\Í`|’œt8·âñ´ +ÙŸ^T•–õã¼á\z˜Æ +=U/„fþ±Z/úÍ|€-|®XLº×HXí—ší7°¡œ3‹AcØgqïåXGæËF»÷Eºà÷TË%PWY¾£QÅ¥ÁW,˜ŸÏqÍà[Ô~ôyàœGÊåÚ½ïE½EëQ¤– +êT;&6Âôg™0Þ7ÉaA'†s*·c»gvß*¹K5çRyù©{'‰Aë +ÀúD;Ó9¬dpgÑÖ>| sÅl¹Áiµ~rèÉÃËUìOáõh¥~awÔNÂ3ÞG¬ÚÒ] ¨8ŒŸ"«D¾*õ¤ ÖF`|¶²—Fn·Zw’2h] üP¥{S„t·V4ˆÔ퉃У¿»–tÂÖa ø0Aj`_RõóûºëTf1žžÁ”ik,ƒåò˜]9R^£'f`IÐÛd¸(ÒûÔ’etù¢÷e56¾jt–ÂB=šáËa~”⬬9ˆ'o9ÖG߬Yò! m‹Öõ8C˸ºoáz(ñqL‚¦°M 7«@<Q€ÛýåÚ\~qÍU–?…÷Uø ÕLA7Õ}…(ü¥°¿e=1LS‡Ñ¤šñXá± ƒC`é«„¦•àkédFÍ» ¤sÞ:cQÄÕûz@þ¬q>Ê… 'jaή`ôÜ·.îje<µºxM¿ÞÒ¸j€ÝBHN€åÖgX˜5Þwꇥcqn[≌辜ÛÂ’
ßu’
h¢;ããY©ÌÈøx‘+@¨8¼ýO韶n‹±CêP×?þ‰;4:yÄ8ŽãŽ‡—±h'µ$Ľ
spg¢ÈPû‡‡¥tzŒ^€ÓæÐ@×ú"ÀL‘®ÖÔ¦¶tÇÄ°}Ä4ý“Ã0U)Üûnê®@ð̪5îË4"8n›S3ÝjþÞ2]rNwr<f¡ÎÌyh”̦Š¹óí ~Ô™½ÍáußTì%j‡¥(´tžÛÃ×R¿Š¨Ô,$ɬ¡ ƒ K¬\€}j¾îºË¨·Í7ÃRe¸Y“ ~'û½ü"ÌS5¡~ÖÇ5ÿ!yÓ²ø5$ àl%Ü«¸¿í¶ì„Øn¾öšŠy‚¥[<“:Uu´>oZƒñ*–ÝÖa,;&ÖzÓ•WmüÖié“€ï›Ñ?Wëçv\‚ÈÍ—Á)×]¯Ø½£hE>Œ³`•¹5Ys×À«1®<ùQg³{…µ°Zèšy½LÕ3…2š¾Ðô 뤴G䕯ߨΠ+ÊÏÏΟž÷L;K4N€Ë¸Ü`-ªÏðý"\59÷ Šyï$©£yÁéM@ýÊv$HU;ÅñÕEz%h ÚÕWO€f«ÿ]·í¾ŠiÀ*=;òT?ÀÉ}‡omË©Ÿq£œWK'JEáßÚzÐŽ!~9œÞÈçWØEØ–¨íàÆ.j׫#p(™Î +
`á +£Ù"˜.§dk;¼—zÐÚV€IhÖS?1ó÷K¥ñ‘‹çY`ç¹h” xn;ØÚø+ו¹Â»êyüð¬:€VÓ”x³å¤'Yv'2#\su<*ÂMëoT#ضòr1üô)n=üZ÷•{g„ú]>Ýb˜õ:Äñ!•;)À +Í°ÜgEˆpÒÆ:uHî:™0( eg„¶ŽhçyÀe±üŸ!¿—3áp¦ +ëõ"Œ9–¸«)Â|¤qBˆz¶Lå]ýÓsÏç,x¶,mŒ«eت]ísù' R›CP«(ÜÏ·‘fÈÔŃþߨ-™;Õó]—3 +ÆþÒðÏ•°·8ƒrÚæÉÌÆGfï*â¾ÅæÄ]µà¢ÅƒˆišÖHÃâßèÊ`'od£‚ÅØ¥û±÷wÃBó¨2¾Y‹pzì1€‹ýܱð.„q,²}5à-Yntö˜ï¨8ì;•ƒ91h!W–›“ú"@îòpY$Ïé^DfpÏþ7øÏòÝ¢|ç{ð§"/²2pãÜM–;N6=@èÛ¬9ŒØ +Jï٨_vD#¡í4/5v1#¬¡âðOáTÑèÁƧF +·4VzW¿][Ä{
&š[ +Ó¯|¢#„â•ßf>fçáÆx?‡÷PX´+lë™Û¿*ÿ3'¬ˆ·ï‚5õKÎçpÍÖda7¬3îXƒþüdÒœÍvÞGk+l(;6ár)ifõ¬†štŒ¯oé³å]¼wSƒ%þ¾Dt^Uç!Å3ª˜¯û뙳ÎX#]”>PÏÒÛÔ2ÈÐÕ+øïÿ'òÏßzÞ¹–:„5Ê/;N¦”0rgÓÉG{ï%p
qêGmãçkêð4ÆÕAµåDw$…·¹^„a-#¬¦3)ü,%öÇþæÁöµq;Vœñ+‡‰ËÀ>KÅaÖ«6媒,TÀ +'¸u°<“ŠÙ==‹nÉ&c¯2“w¢=ý[esw"í¼ÝE~wÆ-;oQ«Ä²Yí÷ +ÿ¾÷…Á¹_ÀÒ>|{óV‚; ΀*Ôøÿ‰£nÈLRzq\K“jž%FÓ†•îíó—Ý̼ÔØFæªì38à iÑŒàæmÑ–]$ß"-t!nB±}£Q½L‰£}ØÀZ‰~ÿ#½Úu6Ûu[ ï0uŠÀùVï”Óæ œnÞ¿=”EÊköä +° +E¡wwŸõRG_þº`cWa +‡3dóvRºOÍÒTYÍ´\ódû7Óª¨ø#'íÔ´AêaŒÍgðuSDÛA)gh4•Ãæ—ðÖãÌk| Ï’°¾ö£ìúyøÈz¤¡óF²fÚ+×Ģ͂ZÇ”GóÏÑìŒÝ .jyz¶$ZPÀ¾—Ü) à>›¢vYJUPé +<§2³«vÿRÝ¥Ä}-·¬;Áú'Nö}ȬøT}9œ3Á(ín„E¶Ã¯t4!ÕÂjã=aAϼZ–5<óK¬E—Øoõm#ØÒÜÙ}ÊÛC/ã°ÍÊ[<âC
1©º ‰lßZªÞ²ý ?x̳R…×È +\ûºª‹‡ÂÁaºÆáâo³—võÊßçÉÔ@Gć«‚d%|`o5õY²™¾Ó1?7FQ%D‚¨„3l€%$nÿÔ²§ò.YJnDýÞaë¸XÈG×̯•Ë–FÆL‚è.é²êMý×oF®UÂgC^ë5š»déð×ÒRý–WM]¹©'Bsym‚e›¸o¾G†þàÂGnëw`¦×”Ñ\¸¦›b4§9FpY'uk>È?ŸSÂGÄêvÖt˜‹`íM†ìþ"?Ñ9^ä1•Ëå“Ä¥PSŠW‘é +g˜×Q¢‚ǽ‹ó‹•.Uêä:w9 +2(7c5f¯Ÿj´¦[¯ÎÏ!,gÿÃuÈ¡êív_ykàk¨øk)‡’® +çk~ +÷Ç ¼µÌbXã³íÖU2¸¢õyûw¹¥6Òú +bEj‚Op-í¥U÷~‘QŠ‘ë½òðÞmÆJñ“œïç5':{ˆ*¥/ +/ÆÃ+íëµ”ž®Ñ¸K9#X›bHMUAb§ùp™žøØ[ôÓC`ø–ck“„œúsõÅV¬?þëFZ&ãÐðš-W£ããÞéâã6È· Þtp^ÂpðŸ{øn…{¸äáX ÷ø׿ÈíçÍý?þûßÿ0ÿ†®?“Šæþö—ðéjÀbøšË‰µ±J'8Æ<Ò†ië·ô'%šÈVYŒ¬ñŸ9¤òð i©ª/ÉLoôq¤fÍL:UfhìGòpƒ¿^zÛ +ÓÃÔÉýùÅŸŸd°”8šÅU4“™)A®ˆ›ÎOÿŽo
ðñßØU3ÔÜÀÞÂ"ò$4ç˜ÍÃÛ shÍm¹e›«cÒé±<lˆnO¸‡Kõ^(Ágî +zÄW²ål~N;Çg‚«Ã÷½ŒâÏpU$šïUhÏ,%«]æuÀËLÙêq8è +ã翈Ÿ¯Ó‡~‚›t\ëWÂë²!`ñH÷tNgo»Äj!x7á_ŠÀ—t¼ÅÃ# +5ø{ +¬‹•²]³ÞËå×Å+ègf×ËkwNFjØÚ?<æ(D4ÀëÂþ×Èù†íÐ/ {ÙÆ7/Kø¥¡a]%ĺÚ'@ßcá*\n-•‡OˆPï&ÜûG„íÅ?7ƒóm„'»°ª2¹ýü¤=æ•œ¸Ñž¶^ÚâÎßaÍ©ø¾^â|¢,¡iðQôÝç£#_xï[ž ªÈ°
âw ~ñ°ŸŸÿžØËÙUìa_õöK¸„Íkù3ÏOû'ãÕ’]ێ¦’!¡î™EU½;ÿn ƒì}ÏIÞ«^¢8lŒA8:`=ð¿ãÜOÀ´<ÚóPa_>ʼno[øâùV{vè6`¯_âÜO€‡åŸö”þË'~Çßâ<Õ%ì1È gNà4àm„3d;,|¹uú;̳{È §¢z»GZM›²-æëài ™ùüSx¬Gð-ò™´QÂOÇ…ûbုʖmtøT«ì¹Ö/–žYТãÿÿÞâÓ¶ü¢c¦oˆÃùÔì°9b00k]kÖKÀâ=Š,Ùî?FÀ§ÇEƒŠÙ×9ÜʈRz>,^¤eÇÓW%`¿ö®Æà Á…XߌЫæî4F_y’M°…CùÌáÿIOÎ~úH¯HÉL¶+Œ§Q3Fè‡à<B*•cÚÃr9ì2Ç¿¥Ï}–I¸’ÊôЀáïGN÷yç`¥íÖ>£P¾ãð‚›)Ó®ç)=rìK#WªHÛÂÌ:<â´6on5‹»ï\SW?`.´‚lo„Å•”‹ßþ\¥Åoe¢Ù¾ÿFÀ•ƒÓÛnˆžu€ã<›n!øVáSüYhsjóq%§£C¾e ÀM[äÂã`ºuè‰Kw¦§\ƪsJáá"ÛyØŽ'iTÃ\p½Ÿøª¡…þ¹231œ¥ôã·»>‚ÿ!>[l-º+À=| †EØ-„Ö}ë1×~X;cÉÃ(Aÿß.xf³TIþ™eÚƒ:òÑgðÒ‹xm#ñ¢Ë.þçõxbž÷ŠÝñžë«ãU5+9Ój´cù +ãÍôådd<Љ !è +ÜDûÍå—oŽðmûøìüf#A·-øø~:‹a—.½ni;³®ÁÛ±¡þ€Yôv6
¾Ã¢·ØŸµp½þ¹µÝ +ŒÂ6¯Ž¯°ŒÊÔÇãd°œÃ$†{àz¹Ùpž9/‰MàL$x VŽ³|rà\¡ùÖ}Eåp0¾æ6‰ã¹ÜqÒÚ +žØ’:ú±žX^¦¬k*bÍÑ!œbÍX &8óÛ‰ +žÛ +ƒã>¼®K¹nx»¹?ÔG„£à<6K*ÕÌuïÓ8ø©™ŸfXW?Š+’ƒ.òbðG‚sÍËÈ:;#ŒB½Ák??÷›nÿ—DÒµv*\ez¿äAIËhêè„yFÀk}ýùÖÄd¦—)~>kÒ’¶-[^B¹]Ò{‚X©¨ r˜C}îå(¨°WªªeñW¥šØ¨F¯çÿþ%çkÁa?]‚š>®Cïbœ½ÝOg¶ð/ÿdþá:ÆŬy!˜9]BmÙ¹}?ÒNŠk¸›Þ¤-ÔÛQÐ÷ì°ô)¯£<Ít½Å`àfZjÉ4v»9ڤЬóü}XéyLÜrÞ'˜AÃû–Ás*sä[òíMÎv{¶ãs›ýæiÉÃ}¿Å9÷7ìéü0À²X`ÇÕÈ„Õb‚÷'K/,k êYÖ8VÌ79x³<<ã}vc„·žƒ_²…\àW7[A©’-U¶'©àµk¥x“l^mšÎÿi +V/oԮɛ{®FgQ–ÉÅ×ɽ½.RÊœ‡f»0ö¹½ +¤„€‚Iá:D¿vÃ<Rñœª1¦ÔvJ;ŒµLyÑöê‚þaa[Z)ᣉÅÆÄçþqPŠë±©8At4ˆ;s
mWnbóÏõìÏÕ1¶Sv‚¥É˜ýE¢ã€Ã:
± + ™—ùÀVç`{µð,©O®”yNVf?}y¨yTPX“l ›½µõ(pªžuÜ/Óx9y=ΛÅÁnÓ¶¬€?n½„0q¨-Ô·S®ŽæÝU¾ÌCÿL=˜Êy)^QÅ€ÚÛ=$ÓnðÒÇ‹ëxŒ]¬©yÇcò’n†ïic +]Üùæ铱={¾½þÇ£¿öâOòýoÞ¾üðñÿþÛ›ÛÛwn}ùî‡÷7wñgßÜܼ¹~÷vkÏŸ>IÛýñú§§O>οiË[š^ÿ¬ÿû3~û°Ÿ¶ºýeûîŸi{ûŸ}»€Œ4Ì¿MZÂM'b¢.Ò¤…ºã¯îâ¬0uq|鹄õܸÅl«±ADuþƒFLÚ„Ûí=h«˜øø>_I9ìè ¶†ƒo +Š¦}˜&˜`â’˜iFr'^»p&PÁÐÐü¹Rµ½æ[X`®¡ú +ÿþiüSŸ¹Æe1ò¡O²™‰«¥Ž+,ÐÑ&µ¦a .¡m’‘ãÔ
çBÖàl¹¸†Á62üJy~µLë~WÃJÎf€òÚ +ìÀ…/»|ˆ˜4@êc×½¼ØÜœSh(…¶Ó0|òy_qÈ$a Ÿ÷
ÅÛÜKÜ£³pêÌÕëÒ¦ë®Ã/ÒJá%pâMƇÖVV¤-W + +fBhè«Œµ¶—
¹ÅtâRâ9BnNÉ4nVoÒvUÏO“ßÏ}8ȼ;vÁ‚šXbºð¹¹†‚©é`/ÞÖqÈÄ¥Á³F‡¦+Õæ6@¦h:Ä{UDJz*²y¿^2WIJ6Mx6ú½‚|¤"‘µ`V‡‚,Ç’¦µzÕ6¯Lc±ÓµÐ¡Î¥z…T½ÙbF @€±2ŽÛÃ@BAÆŒ¡ + +®ÇhÉîÈa©›êXQØžý¹N¶ðö±žëh„¿‡ð k<ûÈHVìñÑìê +=/zÉ50v®ÌÛ!%Dñf¸‹‹Ø!k„c’– k™Å‡Ì‰}I;±î¥ŽóK:—™"ƒ¢Ýå¤B=a”–Œ ~ÓýFÒòsdCãZ‘…Œc?d«5¦ñXvx[¤Ÿ˜Ë°ÃÑ4R”A+v[ñV +Ø[´‘ŽeN[ãÓv@ÜÒr3[Öt˜±¬Tø·asø$ìÙ¶Þ>mŸ¤u&»[6.ŒJt‚Ì+–ÅÁ<—ˈ‡©)âñ¢6Ù_Ö¬ +¸^‘%¬E(œÅK¤ Š¢Ú«GHôJñPâYy™àÙú7ƒ£v?P@‹}8ŒcöRŽ®Ða8l±"9ÁÈP–xñŒÅ%õ–fÏU÷k©²Wô}ÝÕ4
1ÚÉ)ó¾2Ù¦¯ÁîÕ{ØìÉ
Óô1ð\ü¸–0{2|@ûÅ´ÃÃÓäRˆâÞVÒ “춴æ~ua×u¸ØÏ^—ÄkºQô7TÙ¿ož}ñÝW¯>=üáÃíÇîß~úéò[Ážx°½¸|õ÷‡Oî¿¿<ýúÕííç»o~xx+g_\~#'', +,ݪÄËã-GSºÁ=ÜÌËïöb +¦LþXäêëè°ŠY`qñjÐ\òP¦ýêÕ +˜ÅLq)ž/ÛÜþš9úöæÙ¼<qyó'ÿâÕŸÆw¼÷úÓçÿýçooÞº7ôõûï?Ü?ÆŸ{ÿöîý»Ëxqó,\^Éo~¼yöYÿ
—x úÏ›Ÿä¿þŒ¿ýØ—rùËåŸÿ +—w7øßPBĤ–¯t”çD²?ð¯ãèbô7ºóëÇqÎðë8÷ÇUÿ*è?(Çå#¿!ƒ(«¿v“Kwï¡xlºÌŒ#Øáœb60
))d‚KðVU8^[ÔÕ
HP¦Œ Ń‚Â÷A¨N4L…\
ƒgÁGUe:0m ‚J° +Ó kº%‚‰3&F7‹ ¬,BA²'ÑQ‹¾¡˜™(CZke‚f÷Ÿ‹ý1Å°ÁT¨óìÃק~«pɈ,–Tª¥³ÀÅojHŽæ‡Uê¸î…/¤¬ Âz¯Ad+ë +ÖÀ}¢`µÍÔ¡þCƒ¨Wc×®ˆ5
Ì9U‚xàUT"Ùa®ó{K·ÃXöÍÀZ›,Í]8
Iõ;¨äçaÕüð-âßAðú– º–A‡RqÍŽ€˜’@Ó{a…¨Øå=ØIµŠlÝIJЛ|BëÃF)˜Jš gÏY×
¯¿gª˜I0ý’H{€PM2±+ +¶qä“4»wÉ?—Y¶â¾Fn[‚RÀ&ön½B=†â:÷öä<Á0ÓÊÑc©cEˆeÚÚ>¹C$Ìuîžz>a +³e‹[ðÃQZ› £>)ßJ#BívXµ¬EèÃøY“ìßšÆðsDÀ-á{‹ýg$’¨û<7Ù>µ«<¤ª¯t,AÝäÌÞ»
{©üštärT2ØT‚õ:)Z?£×L*s=Šëf} DnazÒêtŠæŠ,ÍEÛe¥xžÃ¸Ð¼*8ØaF2ön'Ñ·ý è)‹“xkš€(Š„®!’ü´;ˆGZK|PŠOåÈÉ—„ÕBª&֌϶Ö=ÖÏT™*ê½ÛIÚœzÌRtë l=B6Ã$ÝàÌP tG˜l25ÜÊΤC»¶uîÍf-æÙlµ·ê™SI†ÙºwdõDŠ8 ËKäjŠ¡ƒ“‚›€³UHW$¶Ø€/y1è +¸ï3à(œöœÌèÅ0ô#˜Zù©ü¦g'‡C±Ô(Z"×v°„¾®0_’N½×04¶RƘ¼ðÑîYh‘L¯p6ÝÜ„Éš½aLf'ëTºž•²a;Ž»¤Œ¥l˜”TìÁ®6ÏÈo©zµ
Ý›ŠžÜ +H¯q¼|MŸ rjÖOŒÉ{¡¤Ë´EÒ‚G;ëJ˜RrTÔ„ x(OIIò·yçIILèí(¿31‰:KÉ-ÂXÒ^ØÚÒ8×FÁÍó(£ƒ÷Ög„)â4»Þô|S–Z·ÃJÇŠ«¶jé^‰‰ ³c(˜ + ªíÛ¾Š…ÄRò—òr/÷ŸÂþMöTô³:x:H‚‘I_,žMõI³9äKœYV!`ú½†“]^wcƒÚq¾.—þ]Êq! ¬ÆÅÒïÖ – Œç¸ßy)ZŒá¸ìt` +r î3dEf¬QÆ«'¸‡AÙ#Øî ÀAÃŒÆÒ)öVC&¾çXåˆølß»×y{̺H/ÿÂú³”v½¯,…·Üù³Î:RßwÌ»×¼‹ +4дfk ôhûç´¯ÂH×Ç6m÷:DQ +4 ЩÀz +¼Ï|-Ý?]Ì#œt¡ï[°(<É*æoÊ38^5”›ön1?ÈÏ8×nÙ™r'ÝÔÜÔLëÝýÇ…ÈxmǤÿµ•áH|>záw·>^LK鹌™ x]GöUœ]ÍîZ¼gq®%I%ˆº ¼w¬ +iJŠ…|žb¿=¤ÇÞS kþqÝVy&ñ;ÜCF6/Ìt zï’™½^…øÆŠn×2$ØÇ£xŠD‚»‡Úq§uû§*ü´H#E¼U Û1}UÉ¢SíÜZ½û¥4½^SQà‚ãy™öTøÎ@zmÚ¦cÞoí«Qp,ÃiäëžùJ—^4°6ÍÁÙËÕ^âÿ»*Tò±ºÅoú2Ó97•²>F™Çó¾ÏAó“»ÆéÉR™Èï\eªc¿ +Úó4ª€W3¯#§8©J¶GX«ÀkÚdÏOµt*×KŒ0±Õ9 í¡ûOUÙóÓmÖ-Œ~´¸õY÷Pí„¿MV’pI6`>„ç ×Úð;¬ †‘^ÓT`Äw‘ø_[N¼œÒNk»ÞS¨ÍÑιLáÛï)Ûó¦Ø¹Ä`?&ß›bƒq‡8Ê´£«B⣲eß”ìWŪ![˜ 'ûzªñhùÜt
îi}a
S‹[Xx)+Kå`vª>h¬&éóˆ
aëeÖêy@¦¸gèÏš^Ò\üEX].ºm3–%KŽÄË<~ÿŸ½„ÏFu]é%\¢ÝÙÀâ铨xÀé:0ÞzK—bצXúu¼ÜL{½këʯÊr18³ë6vž×úiÏ_?Îôøåå+Ñ +éÉø<P¤¶ú[Ìãõ 7á_ +Líú§œSÑpo<…•¸ iàÐÛXöu‰p¤PíÁ³«vqÍÒ”j‡á·ëõy{s +X˜>ž%Á·
Ьx© ðλöF‚A•Wî… ö"•_»SÂÅïÖ©1 +rª‰3ùd +me70ËSyuž®ª?üñìÄ&h»2ÜÊ°›+g_Í1ßCÜIÐߌBpxÓ5–q¢Éõg»"ãJ]ËúìÚXR-;:@iÓuŠë~HÀÇvy¡m4'ÎYug–&ɵ¼œO&€w›ÕþcÜÅs‚†aøZª +=úO˜õε BW£zdÄ8ñYÔÆženfµaÙÁp§lÑÀ~¼ì\6¿*dÚÛâÎèäyIÚX¥[€`Kì+ª%=·¹´xˆèôÊqOd·é)ã_ºu°Îâ5ÁìÄgÃgöQ›ýèÅ“v/JfGuÓ>š• ó-ðŽ´Dÿf_!¬0H/]¦Q×ËStx˜Y(C’ +¤*Œº`¥P€åƒrL‡(ПN<ªº¨(A÷âîy^…îšø•nX0ܘ/& ¯S3dªÌ¶uÜk³T!ãÈÓ£ix +ŒSÏÍŒ¢k +ib
¾ô|smx¾Z|S¡åÆg©ó¡š‘A?£¦ +àXµcZº€] +†wFVXkTUhÏbÀŒfN"h›žŸ5ò[C¹EñE{Îh—•‹/R@ÛÙã‹ cÀkG/hãñB[/8´7 +ج*ü0KCíÚÔbˆëî!ÓK`›eÙÇ7R
\7·\û +Uˆ_+ß(? +D–JY!ÛS7€ÞÚ)7 +ŒÍåE˜íV‚ÙÀÚ~9´×
Wœ³V¢˜þ +€[n?…Ÿ£ÃŽ,Š€Ýj%:µ™¨Ç{g~uX
¤•‡àðãßØ +kRˆ?ÊÜyE@1W÷J{ÏÃÑ».vGúðµÒïƒø|¨±œ
á5¨öŽ¥ ‰åpìÉòFÝ`Ö¨Ü5s„FÕ“~k¨euÒ¿Ba
l3j[Oš¿ëM…¨®öàqÂè)¾À1ÂÏïr¼* +€/ðð|"´¹vƒLÕŽËÎÏbvsúüŠe +½¾z$}õ6´ý5qa‡1cÃ-ç«ÆÀ>Šbã¬Lƒùk`\Ì_%7nu^ØŒ×I0
7ÁÞ\àô]újð"^¯ØTƒçèSW°ÉN³Ð¦œ=PÁa%€[ß.˜VsfÖ£RÒ¶æd Œöå¡—Ã œ–[£&¨ž˜Ï!†|œ£2ñí:lí©¶†-ÀÕæò¸àö-é/JÐÌÅh‡¿˜RÇqFD›†ôod@ld +»:Ù°>’uhÒ^„¾–Ïqy8¤ljª 2Aß]ó/
󰢪YºQ[–s‡`¹->\Î…Kw€GOžÖ2“˾%´:„ŠI‡ÿüñÇÏ÷ë·}ýõçÏÿûù£}åÈÐ×Ø4ƒ},fÚãë¿…«Þ—–º1¦hb}÷+й5ÆýÌôs÷ôqxîæw+¢öU*ÑW%¬L¨³Ü˜Â¥í +õ¨žjÐN>
ªszÝ!RJÐüŠº=(˜N‹…)‚tø´VS£Ý““ó´IçH>YVé"n~GÖ2ªÀj@°ºêû"™_»”»XPM[dðr‹Vûíhðó¹Lä nÝù†Xݘ…gXq¼Ö:ÁÏ&¸Y]!‰ Œÿ*pM»³–›cú\øä+ äcÖa,¨ÇÌ-#ÀY| +üxZ¸£µ'ËvŸ þÉ1¬(@2`Ž/Wè9õñ“â:XŽ +p7' @¿ÒÇÓÁ½uØ/°üGŒŸË(åáø—YT„(›ëj‡ŒÆÔ@Q¨Ðð…«„bÉ0ÅŠP… +ÞÙ~àÝòwàÍ$ic”.l8L½ ¬‰äF/¢:@‡ß6>Y@8+9éÕ ÎãrFQœ—F5 +DY2CSö!W?}Š×:›*ö¨€3âèÎ+ÞÀ|
F@f–X~èÎÏùvÀ¹ÀS{NB;‰‹˜i*Zæ +endobj +644 0 obj +<< +/Length 22087 +/Filter [/FlateDecode] +>> +stream +H‰LWÉ‘-9¼OÄøÐü +$–ó˜òý÷a’ªo¯³U±$Éã‹÷]§GõžŸÿþýçÙ³H$p~ÎÚû§@ñu +Ü*‰ùãg[a²ñ«
øc*7ðó(±ÇaÞÏ¥ÃmuŸ´Àü°ÕU¿”~þ¦¦ÇãpyìêiÛ–×ás'xž%§A±¥ípÞ÷´^gïsÝ:»iÝåaå6@G/Ͻ[Ç.—»ú쳬@_Æ
.ÑÓ¾.š(à6çrW»³û6Jö<o¿dižÔÇÜÎgÁœ¨Ÿ«}vÙBdd‡×¦¶z®äA®^þeàÄm²h×J„Áá +ÇžL$ÕD!™jcÒá6"6\0°3AKuÛ†ë6‡Enc詃뼙ÜQk‡Ïð¸}KÕý<l£„}ÚI^R¼OFª¥kYŸCU‰ôðñÆV18àFÆe÷ĉϾMÞõÅ(ÆÓ7Ñ•ý•Âñ`Š¤á§²ëAó ªWƒŸÍ›Sx½i@qóÒºMƒ +LŽL51 ¢·‹Öôë(D÷-عšç®búÍò]—зE¬ö€‹g8\”Ÿ~ïrÑ>ìN\lËÈH‚¨q¢¦àƒ—µÀͬƒ=ÒÝ“dßòë\âqÕ8‹ ®·|ØlIÃWÞ™ƒ ÝŠ#N¦'fÆka^Œª q¸NnMT„ÿè8†œß~Z4W[8(œÛãlˆ%-8x—XƒHpϬ‹¶€›;ñYÃÅCÛ,f q…„KÞÞbˆŽÀ›{~g² +”þ„A†ó>.Nr¨µÂ®NA/Yz\:¯o|âĪê¸[ÄÈ+ +ȤÅ.sÐu’IÔ‡A]’~Jo(|” +/øˆ1;«-Mv§úè܌̪Tý¢ŠHÊB´Ó™´«Lg®Ò>dã4he.’½ K7ý²àæëÉ’7 +2›ù}_o
9šòÃáQº^ a.µ¤…*x±åL*ÄFaeÿ·Þ8ï,áWWCóÔ‚”ô¡¯ZåV*pqÓ@H©¥}xy kY£(ƒ˜ÄæPy¾´…VšËA÷»ù¹-¢‰åNk¾ìz“î»5sxˆ¨ÈáÂLƒ=ëƒ1bnµøæ뾩4;Ö¾4× Í*aTqÀX“WÀbIie +¦-sÓÃÙ5æ"=ijìJ
jÓºé[¨šá‚
kLÜW¼ãËNTú GcQ;[Û/:%3±ê¢æwÅ »¹@º§Y>•}{@#!ÂîîÝ+FŸ6˜ÍQಠ+̉9£¤«7>‚ •þM°Hö|äÒàßw/½5ï[MgM·Çôè8nÈæÝôÝl/ªLÞjø + ×:pooÐ鳃çtЯWémÄÿMﶒL +üÔ+ó/fe¤Á¥-ÌbPxâ ñöŠ˜_=-Âê°û¤Ã +y—œ«qù€ûœ³]*=îÞ‡ƒ +Ì}à{p€Õ~ç
Ð:ãÃ;Ûçh c±) +|Í#ݲ§uN¶%¸ ++ùrÏJdþŸœ¡³õèã¾¥²,ƒ‡%:ÜÖÀ¬ +0hBˆ“ +1ÃèÍSQ9'–èÔÃv,5½ÚÙÍêk[g›™nà‹i„w*í¬/¯àßb< § +ûÙÒÀ%r‘QëujS‡_‹‚‰&
i§-P„Hõ~Zï`×]E NµbGÙmsÿ¨•;®ÿíÐ!Ùt±9§°r®‘šZóCë¬|Xê¥W¶¤XÔΊËΟ{Œz‡*ÀeÉíž—ÈËr +…ÛÞS²°žv./6Ÿ’˯~ºó8¥/¾;èX RÇ:º^c·SæFÛkºôŒPN]‚¸Ö v®"lÊ$vÉ¢}mVT÷5E@çuSóߟbz$EiH#Jö¯†b—ª£"ôýE€kjMý”v¾ ^+Ú6µÑðÈ„DvûW‚Ö»š!W+I[ó*5¡³o}ÁµÖuxëðà€„õ“j}øš¦Ã÷²s +ÌMÛZ™9ð± B.µs~ž«ë9]d§_×aôøP,EÁ#ù %Ø‹YŸÓ‰‹ÏT;‚"ÆÝJ§/* ¡¼Þü\bþ +!®ÀÏŠ¡§n +¿©ûõ?ʺÈçÓJèe"Ỷ%|ge÷›kà ½¢CåáCy¥ô¦h9•OíóV-€—%«“ßæÞÑÅ$êœÚÿ9£@Šp¬ê€dÒaÑ&Øcq\-X¾l˜¶/F¦.<ÝuxtSék®ÐÔC”Õü±ð\ԉד8öLM[+Á\Ô ¶î5žwCó¾`Ú"è°£–¸ŠÉ™ådï£jt';*@¯V¿s8ÜUµô"Àªô³½ÏíGÌà Á +Š”Ãî5òŒKÑ9úzRi£¥ +£Ëàˆ
`HƒÁªLŠx7ø¹£V E¹¦ÉÉ×D€"ÒöIQ·7¿Ç7£¦ó\çù¹mýÊžåwXÂi~q漚òž +±¥ˆ‡ölì™ïϱsž_+ÖnnBû±´À¢Êª÷žÙŽ
¦8˵¤F€Þ +¬ºšÁ6”)j¸ëÙ"ÚýÔcǧËöhË%8Nj=ñ–™•+Ÿ °×dŠ°O![®LleŽ€‚ß–‡‹¬E#Wlþ-¦h…Ž¥æ;Ö§ +è¥Zœ[*LE%G¯z©²Á–{)ØJêIׯ& +…N÷<Ö¯æK”ùø»,n2~WüXè_3äåÉ¡£í3BŒ¸ÀzŠ©°‰. +ÝK*í ‰9 +j.ßïïǬ֯…Ìâ,¬\ÏÛÖêò +s@›0ó +#'‚uŸä…³vY"S|24˜MöÖìv•/7†õðc¨T_¼*pKÿýýÛ?¾»ûûÇ¿þùýÛ¿+ñMññŸ,3¬r8Î%ÿ©/¶VxYôæ¸ù€&Ÿs©÷ù€½ª“–á}À5ƒ±Ï~œ7Àqâö-+ü×þoîXŠ"w´œyóÒºªÜ!ãÐiãa£î=¼ƒŽ£æéT£ê“ASª;ð2‡üz$FX§ +ää{7ÅÑRˆÇ$¼_FÙœ«ôr@_΃£oÜ–Ü….XÞEz«ÝàÔ`g²Ž€Ã·z¡AïìüÜŠžäòuëúøm!û +Dù3q[à¦Ø
»ÛÕ½èÎXmÞ+ÎZ@(•&$áÒCí˜6€«Á¡éâ +¼+˜ûò¹$aj!TŸâáh]d„Ú¶ÁÛÆÑ·`Ssâáð–á÷DËî°ýÑ#1x7€(˜k'ÛØjë¾Ý%‡ +ÌĪ¼‹ÀQ¦sh4
o„¾ÚŸŠ†™^A(/Ë“ØŒÞ8w.¼Äçæ»ÔÞ“ ^¡“aûVq¸õO•\e:”hºìç\^Ãå…*Ñx¹:_yøpûð,çˆ)îØÝýà%àXû¼êˆ>FÛm6J²9C`ïÍ'ïyÈ“‹U‰ú.C;OÉ@ûÅßtõ¯úÿ¦„]ÚP(¦ú;+t…Ó +~_µÕÒvP4šj +”2“0ˆ×peT
3ÁÛÀá~8nlœ +»ÊŠ±ëµSÒU°sþÈiØžTâ.Œ÷Z;Ì•¦¢vßq|“-tšÝ}FK¸›)ðìt“d÷—DcZ„¯“‡8Á –ÁdÖ1mÎÂË<ÒÄ´G¼ÉÂÓ#Z{;IÒWüµsSZ…|s"íH¼Sâïb„–j +{]M¢Z3".NRš\ OÖg«µ‚¡»Õgå֘ºeøÍ!dCÉŽ6?EØ}Ÿ¼„kË-guUf´’ú§2~UpH’ç™Ø2Ï‹ÔI +AÆÚž0¼ AÜëY³tõ¦Ä^.pg&¾‡c?|@Í9@ØüËÓ_G‡Æó¯ó.ÃÇ”bbp|oœ0žs΄o´3z´lcìÌöGF(}ñsœÍ"™GÑö
™'h¦`%µN2Â;%QáH˜Ÿ›b« +¾üÍ%¾ºn›OWC¦ë6ö{sXÀ‘mC/Ž_X‘L¢i*ü†z£=6r™»žÆ.ÀpçC™’ŽݟC¨è®Éá#Š´:õ'ëò?«%G®‡]¥O0ðGþs”,}ÿíåê¤^e$@3n=K–HÊýmDèð=±9 + ÜöìÂÍ×[‚Ë¢Z¸A‚ âíM‰" +P$HpÄN~ª¿Ê8µÁD7yØú‘BÇæàNÊ÷‘èÉIF¿•¯–þàk†ÛT÷ÁšþÅ\WŽª°ƒFÁ»å>ppì*Íiõ¨ +ƒÕ;Úf ÿ1T(zQàlç:÷Ù_F9"F-a‹‹ïŒŽUŸ4nëÊ’½#NŒB¦Èœ1í|²urQ!ã« á°Ã¾«TÓ»—c-?W£,m˜«þèh|yça=<fbœkë50¼"ŒsÅÞÈoM‡és`Hœ‘nhÈ~~Ü¡¾êÐy£+rÒzšSmš%9÷”mÛ«¾èmŸ°m8ÑÂÝtZØ&©/«'A>Ký£/xtŸìÆ£7yt1,Ï£?zvRŸl×öèæ>Y¿7—øè&Ÿ”>p´‚æg”ª +këN8ð´âç*@ ï0Š ”—œ\æv6>÷²â“<;…Ã7Ž²(Åþx4â8u“Ù=Üs½µùõTÆNfÚ§ö³î´‘B…4}népˆG”…¨é"8bä¶l鯌 +ësµ—õ±]…{–ižÖQÔ½Ò~©ù Îë1Žµ
]¡A’¾\áÿ–QÃÕqÛùÍTžÖ‡ë:lØtíP°½3>7{nàp™Åžñðð'“(í£]‰Wij3ϧT€XM¹§ßÕ` +[,è†Iðùo?ÚÊØe—fæ'+¬q<j–¦f +ìa + Š¯y:$¥ž,3#4_}ž™‡×ÔC‚¨kÎúåVì|ònµ·ÃuÍÓ·³1r Î|]a†
9-<@ê˜JÍú䇻N¶ZOF +¦©é¦H”ÿž„ ¡ áU¶ýÇE‡h‹_·¸x`¥E +hpóqˆ¿ù}x¡é–óç§ðâ»êRP«çY +ðZ– +Æ +&û€à,'Áå}KðT_g«a'añsÍâ0º„`›¾üÎéä¸N´õ0ãצt°»zÈÚk>OëÄþ½—˜ÝÛ‰¸YÞXŽ`‹á…¬¦öó¸û²^';dz¯èì¡Uüô%>}³òáÿpöãÿ +3A÷]Á&£»:†$K»Ê-D[ °KÔSÀ~Ž¥Œ Å\´îjqó™Še +„XM hB7›[âÙñFf‰‡2ó°\¬¢4Êk„õ ¨^wèx¸9"lØÈa¥¤ Ó ø»{gÒS¡s3 &E‚ïlÊINªKByÅ)Eo¯ù{àï”è³,îW´V–,À¡¢žQdäÐJÀ{F¨¡÷l +àfP’È}§Ô+•JUÜ~æ+°m987W¿n„›š¥DÅL!Úf¿9”)ÁçZûæêxßU‡M;ÀbtÝ殈`Ý+shÁü“†²fi9ñÐYH_(Ñc¶êSȆä-F¾Û8u¤œ¾ió¨ñZ³lÈÝI9ÂËìÞN +þÿ®2“óZAâÒf§]ãŽ7æ²è1›qáúÙy(îç'F<À^¢æøÕŒŽˆ¨~r¯+¤Ö<ÂC;ÜXkÌ%#Úجçv4õÎÈò}šqÖ¾<5äƒkzß3._K6$ÝÂéÊaR눇ßqÔ:˜#”Ó_~”‡-ÖËÓCs ¿¶°?'6ö«è +Ÿ +š«R5שvm.mãΉOlÙa´.Þƒ3Þ»‹[¦Å%ŒÜl:ÙÚ}K¶*ÖÂÜ.EHí}®^ØÎÐfvòäPª6ùÑvf„–š
ŸaWßR¥ŸúŠpM3oQ÷uxb/d:¦×®9®õtø¶¯±ÖrýغÝàb’úGŸ•–Gv [Vñv º:ôþèÓ)àä7¦ ‰Rçê™ÀÊ‚÷jýiÇ9ü‰qÛQŒp®XUœ“íêʧۉãʤ5AK%`Ä鳄zÖg*!ï`iM +°?ªg_¶@SITzmË™>aŒÐn#èêFð¡
—›90lÝvÊäSivŽ³É±JÔ+i¦>qÇë †ëCêF)àYIfÕM›û$s÷0A¶’ÒmYî +»v¯ÁÎŽÇ5¥ÐƒkÜÆƘ ]©"pgƒ¸„¨ŽUò7n¤1]®"X/zœk%z«ªÎVèLoÉ<T€%l' +R©"¦+û Sn‹ÚP»ü‡#q³Ýí^KƱQ—n9'RMQÛ¤„Xœƒ$lßEúýŒG‰IÉøî²ýjÝ +_3]JKÕyÅ‹™t[+iÞÈI˜ZÎG¢zû*\ € ½ +€7£ñÚ:Û‹¢â‚M`4+Á+ËÃsÐ׆ʳõ.`3{Þ¢Ó|tÙªƒ_2*È™ÖÅPÅN]Á{éOo“÷õ–òàXƲyM¥
bŸée«Ãû¹oU(GîÖ.æŒR`Ksx¶Ý¨Ð¸ùí)—¿ø´17±Co‡Ô!Þ¼ñjr +ÅB.l×)…l õì"§ º—(PnªSíÆ +ÿ;ðm>]Ƨ%ù4/_NçS]ìe7-$×ï‹›ÂRF¯%µs3ˆ2}ˆDöHaGÖ<¯Ý1iûJ–lý
¶sKźڸ†Ã«å×Ây +À6›®\Á"µë¾³ó,6NCl›ŠPÚ ¸Xm6nÐVOjÜ{°ÝŽQÃ_ +A HÄÑ‹Åöú'ÁÅÍK•›}•(žüßZe+¶ù½ +Q+‡Øêh¥_!è>ñ–ß$³$Ò¸kܵ²Cžµºj¿üˆÓò°E÷›Âѳ*~O’Í.C™Žl†"<‡#í꽚sàx^1o +'X[㜌Y6“#Sy\ìƒÇš$Ã̤…hÔBž‹2ÀÓðìE["já}S‰ŒÎbâ¡KCu¤qÃ.½ðÊd¨°5$.r& +”F+"Ì"ÜV’^|«lá|7¶Me=ô/N½S€µNÏ鼫:JªàHp´I*ŸeÆ.ã´Ch'-÷òR
G{‘3Ðÿ¸[ŸLDªÔ[_‹Ž²þ¥ rCÙMbñ4A¾bX'hÝßÀ;Ì1An rMÐçHr#Ô¡jþb¾0v Bª@¹…CÏRÄd>nÔb7³a‹‡{òT“âØs{œþDh·ô¾×UÝ°ôGm|jù_ߪÞ
9×þ9½ò[øY]TÒÙ9½¥3É=Úœ˜RÛS
eãôþ’
ša€½NíªšÑGr>{Û!Âó°å¬ôøˆóð€Z9 nSÖÖÆUÂÀñ¨ŠDÎò•Ÿ{T,ÀÖ2¸’_eà]ž†äÝ!|¶ìÛ`8µÁÔ\ë²:Zw\—Ò˜²õJò™ÃxmÝÀÊ•V°ÁºrNiàn¬ +|Íâ
ÑR'/ôô¢½›¯£Ø•èGïN3Ù/¯OK·!aŒ:‡g^+qÞ˜ d3
ó¶Æ¯ÕÁœõšôl±€·ÄyK[°’A°¯æ¥…‘R…ð+ô%Œjü±SPðÒsðÊÎM[’è¦ÖÇ$—ÁLû,vÄá6ëÿ¼?0síïË5È]½IxÌð‚änX|l]ŒYJW„6Œj9øÙ+,ÍoÑ…·É¹<-†]ÕÁpÉ|a¨Ï!8vÚ·³c¦ôÊ.[ª©®
NÖž[WÜ0r.JK‹-–2ñ€[¥8 +Ž0•Ð±Ä½r+cçò^sl'ؼñ +G‘ÞUM+ +¿žüîÉ*ŠýTÜ<Š¢à‹ýê•ð=/†ÿ¿……ÄÉñS+ž¾xÛDYGÓ]`?Rzqà%
£œªƒ]+¼GÖGÀاWOùL?\«ø0:ŒPÖq&ûÞz¿Ÿï\bûÿàÅ?þNé8µ¶ù +ªŸÍo=>=pËYâﬣks£ÅVØ%8ÑÃÏ
Ì\j§¶Ã7L…Åe>S3Ò";èbIqßôÁÈ^ªÄš4áú9œ‘ÓZRûãÌ…‘IÆ¡nnL™1¼¦"üOSVïÕ$‡'h¹äÍò~´ +†,H9“]YÐ^&wN±o„þc# d_ºº§qq[úUx
7¡³($ëЇOI—£¾à
^½Ÿ;ßÕ==ÉrI¦ÃªÔJaÍ·´ÛGzÓÝrzôŒÝLÿ£¿·íÛ›¼ÄÝì×ϵôFgRø{ßæÞON§-³Ò}‘ù*¶Ü*%S±ª`|ñëœ +új™e˜9d·ç«óµâ&G²±¨w¸‹3-+FWp±Wyù¹ÌêôU]vîØCû] ö]¸ÆËNÍnÙT6änª+΂ÈE»éÓ§çn¹Ï”õÏÑíÏGÉw.o즻bzÆ(+×ã:©ðN÷í“ßöÚ*È$Óƒ!ÕF•²¤I€›\ˆ%èûg‘iõÁØûƒ‡‡5öÉHgçlU s6Þ +™k)®_…Ç…ßÈÏ!Ñ~±›õPpg‘:¯Ûãá„Ïz›ï*3ÞÁœÜ²*Žu#À—I¼ wÏච7@áœ[€ó )Pe€Ûºï:áÿså½ðÜÓÜÙ[)Ò£Â)ÕŽÑÝ{T÷Ñ Š…-ŵš– +Ú,HEÔaT¨Ô¬ß;6Rmr|óZ¬£Í• o³Ê$âÿV¾×Æš…nœ‡eX9¦á" ïh +î\
€
ʾ+Ÿç÷z¹°&¿ßGM#ËvxÃ¥¬Ãf¯A¥ÀºW¬3<KÈ[îbmdua²è‘Vf%<³µ_Ì‘©<\)„°ÙÉúîQèË 16¸ˆ-=Ë +û[ídÒPUåá°`¯ÞÕÁE;ªÙ¿*Â9½jtz¼b¸Ä7):Äzâà±Ë‘uï0z–+/láÄô °€ q±1Òîe-DvE€í½‹ÆÒÃ'7lÞ‹É=×WF˜—'Zk}QÂ$7f¢ñañsëÕSKƒÝyƒS›¦éËww4Ò75HÀÿÕwñÿ(A¤ùz€Vd +ÑæqGöûâHåv#x{ݵž ÀVeu;Z½ÔÚü¶#ZP× 4v#VõÊÜ9h
"Ã$'-;#ø'QÜ=å@ܦI{² +øIÓÂeÌÔÒ«k$à[TsËL°>I„Æÿ%;4ûrõÂwòKDÇüí¸¬ì!Íkɬ«Ã’Fàéjm¶ck•V±5gÅf(‹Ÿ×oúâÝöL’/1ƒŸ“)4 \HåÚBˆÆ2¹=¤ +‡˜RÿSkX($ü})ž[r…ÓCÉ•8Èy$AKÂŽEðËŽk]YœÊD_³d;€#s>°][⃇ãÂs +7)µ’<ŽgúënIêlà;Ÿ<VjÏ鯧k¿Wò]ÍC*4ãûÎà¤ë^òkV¹W¨ŸÍöÚÒFKö™°+`bê×lœÎl½î«¸häfJ½+P™–øÞ9Ž¸ÖöÜ8lÍ$wJ%8G™6*¦i° i£âœªŠïUxW¯^týq†ýã£9Yñ5r¿ôøÔa`¿~K9ž¼_{ʘ“p!rQ]ÓSe™è‚ÖkõLÿ¾©…ß›î¡ +_fñ6_‹z°ïbû¸v6îÎÔR¿wOg›½@
Õc}MÞª´ëEÛ}‡°é:±š=5è‰ üžî§nvfƒÍ8*5ÄŠ--Ä÷Þf‹\.Û£–J6èCɺyÖ0b
ùò +CA™Ù¶aM?o¹Î¸¥b‹µ»-@ ™;©ác£ ¶ZõfÀmèÂÍÓ]ž;LŽ/hÐÚÜÛb•ÿ¼YW‡fÑo/¡CRÚ’Ô]MPž,pàXs<ìëÌ@X¯AÐìÀЙ÷%fd€5õjF›Pø´“Žm‚W©´ñÕyVSS´™£‘ä; +endobj +645 0 obj +<< +/Length 12945 +/Filter [/FlateDecode] +>> +stream +H‰ŒWÁŽ\¹
¼ð?Ì19¤A‰¢(žý
Áæn —Åóÿ)‰Å×ãí×cŸ 擨bUéïßþ÷ý›|œÿk}üùýÛ¿|MyLoãc™÷‡G¬Ï£E$nK?6ÖÍ;±ÑœØèÞë#>~äÏûCÉŸ«·-:Áåó`ŠšC7†¿¦¶* ø™õÄ%¦sñ±°í
Ž6ÊàZÆ£Û¬ÅSƒ‹uêbÙÙàh1¸‡öµÂìÈ˵Ø=ªB¬I0ÔZØ"+Øc蘹‡Y‡w¶\æÌṠºoµ‡u¿ö0ÑÑ›qñ\Y`“`5àn>+øc¸ôÄ犬°2l$¸º(ÁÙ;Ë>VÖÃ{Uð¨µ>kSÍ ‚8ÜÂèãÚB<´)'þ'¸Z¶AP«Ÿ +øS{[ *øø¬°ì\þ:×ÊÏEk NP‹`7®káÏ&XWþ¾
ì˜Þ v‚ÒÔÜÄÇϺJÜ+NÌ«1¹qíûZ.}r¡…iÂ:Ø1KbåÄÁCÍ.oíÉG'£å!ºZ‘WÛØe;#Ã.ús¦–+ëÆŒš)°crÍ:Aœ|±36õY,¼Íα^c±‚tjÅûį̀>»ÐŠmUe\ܵ>w˜B ÑɲÃÁµ§´X}.ÂJZÄëÄœ‡žãy p·ú=?†û¢ŒM´1ÁfZz§KIç>çµy`t‰ûîS;?§&“`ëYšE$™ž^áѸX¤Y”VÙ±µ+Õ
·úem”ê‰×çf‹V`« +QxTøï÷oÿþþ->þñÏÿüñ‹3€-Û~%ÑgÁw|»!æ;¿¡üípÜÒ(9Ú’9~¥ü•GOã[ÎßLÇÍ$½öîÖ>ë´¦ƒ>攨ù€öÎÄâƒ2
ÉLÐÄâ©\>‹—h -.ÿ€p?£.PÅ5Á1VÊ÷ñʃ+™ùáƒuëA0,Y‰ª}Ùº¹‹qJ¼®wSvlÅ:[Ø.ÂC@.ë;/„‘˜î);0DÐÚÎp$(Ö”Ó½X³
Å“4c(ÝbE‹F7ÿ;yñ›{gzgco<ïÎßéëÞúó;3ãü·á>PܦۜrjÞÇ)_¯ð³ðÛû¾eÆ=}õ çØ?pÒÚEħ%Ž
cJ‹W¬ +·UŒ›š")xþîð·zÓÓ—þßûÅxÄö¬Ï¢›ôFQ÷'³æŽžŸBb!×q!œx‰OšÍB.‰FOè3…hµ4£œº§p®~ç£5¹*H8)e–B‚ÙöÜCÉÄÖC¡A‡Kâ”rðnhÞLØí,Þî픿уwâÑáAÄËeºU+m›yöõ6ßÝû²Ì@X‰ûCžâ½-ÍøðS {J0@åUB£Øüº5à3;éµxdsЦÕg™³°oÿRa¿™–&^£ÐFÒós ¢µœ¾XL䨡§A怆ÄÌ)A•l£'® +É'ÌÁJA´zNß—ƒ½ôñ÷Üî•óŸ…ˆvþJÐu‘-Ú,Ó1ØÆ =ž|fqH]‚k¯ +KËŽ0ô_º/˾…>± +´¸èÛÃ(>GåG‚#z‹‚€w@+6ȼøxÍzNxo +Œ±5ºÃìR{@†_)¾ìšñŽ®ØçäJï¸âŸu›mT´ö§´ïõp+¥‚ç\‘Wý9™k”IéÖ¢ÇRzŸÂJ«=èvêKpâªÀ°°v¬`24=óߧ~‘¸Ÿ¿À^ç鳸\b¼ç…tÍé(½¸Œ”ÏÉÖóË3ñœ±ÌO +]êð€O®ÉµšÏ6ó‡àí¯µU`%QXxã¢Ü/Rxp±BìÃ
zn1¼iI!¨ðe1Zd0fõÛ=óCƒ–,í‰i«À™7wçâv5®:Š9žëT½v`º +ŽaÜ#;ÀhÕXf'¶Ýþj‚Ê ¥sëÝ\ÕÆyb-ÀÙy.P³‘`Xo_.BŽhl|j-náº×õBŸX6¢×øظü\¬bgi·Ìò= +Ь±ìy9±ô0çy/é +(`&þ•¡"8r-¾Ñ*´œ`»Ap¤çÐôZȃ2P¹¸'H3Òb8þWѹœÝ·²að²ðñˆ
ÚõÌy=ÜZ–?výÌßCMÝ*¼õ<¼äÈeƼmûì—ÈxGî +p« +{–šŸ÷ÁÏ».O\#…‰z#‚°
«>g›é9¨Ë{ºøÅÝÃô9Õ½i5}r¬íŠs3S/ua§Ç*¨nø»$t>s°@X QX0Õ³^3²9zISÕ¹[¥cKj¥¥åœà[V»•°Þ³‚ªOÉ“rC>,!ÅãlóæG ìœRQ-SÙ=YVæÉN~¼°¾Õ÷kóGI7îøÿŒWK‹¥Ç
ÝüzHvU’êµ´‡,:$!“ò€î…ccòïsT:ªïöôív6CsFW¥O:’Žø†³Æ +é˜1Äpy=äß]¨C,‡î‹"ÛvÕÁÝÐ/…Ür»;¨5ÉIýæÇ¿e|×íËÞ¼åZó£Ž{cÑ÷t„y»zç¡e2÷ +˜ÍîÂx܃c }VxlM
MZ%ÕÌUS7U´'gö‰èÖM +Yhœðñ,Ý„?¾Ýüô™!ý)ë\y¥Cìè‚6;O‡hp‚Ë®×÷.ØøXW÷œ<î9.”/˜ÍØCjéãa¡ë6¾UücâE„®ö +P—«%‹=„_wÚÍ•JÖqÅÌf|e>0æ2ùsYšíت×ïËÆÛmûÄ]ƘTó}i|¿Î~ó~+=ýZ4–2Áü(¼Ô}ñ;húÂÃVó;c}åsÚ*Á}<0·f#ÀÖosðEίr “S˜)몆¢™àЮQ"eÔ{ìÐÃ4@ŒÝ™ojì»øDé¡
רÛí*ÇË>܇9·2ÏrËbÿÀå'mûÈ”†âØàúº¨æ¬ v.òñþsW–pÔ‰¬|QN$v‰—aMvñ +Ý „í#§4-îˆÛ°Ûݶô0ìánWš–ت—ùSÂV“‘]IÝuSF¾GÞ¼Ü(Ùó·›äçšàÔôPl\ÐÉ£YñRR¦A*ñûfmdw%{JÕĈC7[3yt@œœœ Ê\º)[²²Ü
—G!®0alÚ“µõ_&øÃÿAŒ}3î ÏOœôM-ä ´Ì¼OÔЖ9O\BĶ¾Zåˆag¦"nëãŠÚ:çŸÖ3Ë›5º¾^ðn®Æè˜3Yw?Ÿ¶¥’Å,I\ˆÌ‰‘\¶Ø¶«žªwÊÆ
ÛŒ‚בSS×2‚¹ + µW•ËÃäCÕåÚ–°š9Z±E'ƒÔÁ‡÷Úî}Û•W×0ÕâM\šŸ
|Dsõ<¼Üpƒð74K",£†‡Õ+qi´µfÄÚd¾ +áŠçj ãF_Ž…)àj ¶@›'#Ø‹m°ÏXçéQ‚è5 K9\õz_Ä4¶};옦‡Òj¾U£•âs–æö.µ¦×¬wŸéáÚ§kæë¸hÖuõ•ižjÉ.¨‚¸û˜…³+†:“ŠSÊQ °ÒœL·yóÂFÞœk+«Ýg~õ.l€Ðô\e¬ëÅÉÙS¯oÜB`¥rÝ3ðzTñò ZoÏÏHÅÁ,ý€%GžF[ × ~kÑ”µ™©DŒCð^0Î&¬¹ºÎLGÔªÿñØ÷‘Šft¹Á_úÉA3‹!«gy[Þ—ÀÕr—ö>Å.p™Mrmè¾ÛÄ2IžÆáè ÿßu–ßƵëÍB +°Ìœ*5é¡Ö”ˆPWvyÐ8zÔF1i¼ûÚÁz-¢õíJ¥çë¤ou°5?MÊñ ÚÉšý½ÇC•’ÃÃ…,Áš‘b;õ2ùƒ{$Œe‰S|’1°1“¤Ññ³¨æ(®ZÓƒœ}ï÷4_ºzëýp»Š%Åqy3®ãc‡±$YQZ:Á§@)Wé¿àÏ-{u’ÿWjà(ëL?1“6¸LoŒ“B%)äJâ ¶ãÁö„ +寓ŠïUó%vndO¥gxÀ«œf!7äè-€mþBYÄ+³cJKŸ4ŽsçN·ÁÍ¡€E~ņK'…÷Îãy²§4ÞgÅ…#S7Õ¡o9½f(/ß;‘T_®“Av/ÑÓÁ‹ž4M>€˜jylÖ _õ¹˜Aï9ÿ!=ì.‹ŽÕ}Y8Øf[7Œpôþ7m\GVwÖqŒG}¹òv´#ÅœæzÜ!èÔ”"[F;X¨!±´ +©:RªîSÒpä¤oÝxYR«P¤5wK,#og$‡”änœV"Ë",ϲšU@IMëÎÁÖO`^1fjþÕN%Nˆ0mu§q +ON/0›µËÃVÿ‘v(úmìÇOº¥@˜E¡sgG¯,XËaÈQIo©€vïÑÃÌÕ¶œËÃÕqÙ†Š-–릚¤‡œx3æø™)x£½,1Œëª™ÉÎQËØVÇ>‚¬.µçshÍó]FÆ@Ñã{éAÁˆW-=Ê’…+.ë RöSë[l\e$´hrGª’hݲ³›uz€¿rŒ'Dó]WRYu*OUòdwN€Ó¿nœ.,®‰“Áu°DÐY—вoÒʈþGxµcW–ƒÀô$$ô‰:ž%Læý§S +]¿gO'}ú”õ¸AQ…Uc$µŒòìGGœkú¼Ic÷U‘sxó=Þã“8…*&Ò +Åi+ñŠTÍšô¹V¡»ª
Ì•3nÑÞ‚ ^G€{ÐP Õ°ò-&…¯Üqk>èjkàNWnC}ø&µeÿXE£SÌ€òêK};’,9Mî&šÚãÎdžÆzÚ“B—o¬è®ÚµYñ2…V;«³+ó•…¨E¨ +’[@ç0[KLHÊŠ®/ºú]˜\Íò–ÞWâ…<V..ØGÑA°JÜú¹j{>*ð½„Õ¨ìxiÊ[—Mž1fÉûÕ˜#À|f;K¿Úz]¿Öøïš,ö˜]‚›fQ[OA™ÙC#Í(ð‘ÚiÑÊŽ%ãoÕ‡L)#DÕ©ÑWâ2'…ŽpJºj9þ_è`ÎLÐ}*¶êŽŠ0¢v7ÂKnŸ™¶$Uãe U¾C®w>CŒ œ°$Vèˆo…Öh°»¦Gî؈Ý''@:
ÁÆdláãN‰¥£e/®ÙìÄudW®ÓŽO—À¶ãŒ™<BÙ܆/9<nìòìêlHZ=Œp¥]1Ù=¹ +Ôˆüó—0ù:vÔ*/Ï‘°ìtòÞ.™ìu‡ŠBœ9SÉ}‚F (^ÃÈ r,R€«Sýžöv°D§lm="L6ö¿ŽT´Ä‡óž`÷[8· *›îkוƒ7Kk;~§T†¹°a`ÓÕo„c¯.Ï +3>°´Ô_µ<‹þnÖûf2‘šG÷äÎ>_‹þ˜|…*Æ»‚ü´àg¶ô½^¸€hs~-üÀÑ:Õ°^… +]lÙ +”ä¨ÛˆœçfO¢§OGxÅ5=’=iÉ´ úû…· +¼Ñ'ñ¶JdF¡&æhÙ ½5"CßØd?"W$ ¨ÜÈ%§epõ¡´®‘9·äñ•‘áA¦lêÅ=ãð}p°Âe ÂâJ=_Í]7‰n s8Lã;ÑØÌÁÓç䀿æX¡NÊÈÏ
ÝÔzXÌQ´>VX½çây#Ïë¤Ôu—ße\'¥>AškIjIå°/üæù…^%Z÷R~Ú/ºŠŸÑ;©b +3®eÑn`qx„¤|Ïá.Œé.Ì|Á}~åÕ‘òâ +ÈäcBôv±y.Ý/kd»Àž™º—/f2M°œj ¤!¯<0Ð!'›éæ 6NÿŠØ „#=À¥àÚV`úœ¯Õ4Á6¶ +8,Ü~´ÀÉÒ(4Ù{ +M5ÕK2ÐÍ™ß![ +Ú:G `³J¨Îžxu7bBlåÍC;âp
GGî•rSWðÔÔ_XÙøÜ=5h•8|6˜·ˆu¤ƒ#\î•Ž—»ø‚8ìs©ÄU”9ŒÅµ÷=‡#Öƒ7s¸¶jçD'[#͆󑧇…Ec£Û–O6n!‹Kš•ú>y£t‰óGÒ;…–|a¡Q1Çȧ#s=õN&mqºm÷ŽA¨ÆÛs0Î +4°="<9ÓÛhŽ–\>i“†§ÆNnËısµ·rÚá‘bt‡é’‡M]+EËrþlÒ +Ÿ¶ú¤š™½™†œÖF”®É¤FjpM”Cý¡ÇÃli¤Î_7Â’ÒÈäû‘Ûè…l Åú‡àÒ—R´œ]êâdΧfá2©æ’lK)?ßóEx]M†WÚ¾OŒZJ^àà ++´2}À)¥Îx¡ºP
ÍÜP_6ëÔ°¾iKt•ùÅ+ú¹Ý£³êùŠ’'¯€eVæN3>7ÂaßÃæ)
ðö~æ]¢R„¯™už-·Ü8)‡ª2^ +Ø'în:«ÊØ¥ýweÀaŽz¦á¯89G1m™?‡Ïä;öâr31I¦ –^´cÓ‘ÝjaeàmÏ¢¾mRîúM#¼Æδ ¾Yt#XÑÛ‹€ÏÁ!äaÕmz°Îpã/ºÀMqR:ž1‡;•Ä
}>ÎåN¸ÜötŒèÛ1jÕÜeýqØ8]榑9cñš„¾ +ï´Yºë±r&Îå\¶Ip`S^ùå îàôDÎÊáðÁœr®‚üqRk?wÕ¸Ù¡_Î+ e6iÀzæÑo#µ +ön„êâb/7S¦%¸½‡‚Þ+n+fëGÅOûL’³›€bÿêšAŽfdÌKÛ™õíïAäÿe«\'Üh‚ÿ&®IkÎYõMÀÅWÆtY,ÀÙÏ!X]øÇ¿wù#xÝeåkgÑ·ë+öÅù¯Í<baËI>x¬¯7È+”È}¸ôß·Þ0?gÖv"ù›øÜxóE#@Mö¼‹l€ÏË^"ùÅû¸ë/†¥œ³ö;üú\ïn&Åu;>Ó©šŸÂûLݵÉÃSï7)‡”Ò_0zÆSAøìxæ®dF×ØfD5q6KŒ“§Kž¼¦&Ò‚Ãpøͦƒ`¨JNãnNM+Ad’¼`Ï"‚ÈÍ¡5’[oÃØ·Ë¥åÄ¥ÉîânÕ3K»L•÷ˆç¯Ú2‚…tæ®ËÀ‚vqk«êÎ9÷ƒÒÒ;ì…÷q¥Ü¹' +»yîˆ +'„ñÁmq±ÕS)Ò}c{Eü¿HòõMq;-e>
Þ·Ã]‡QEV¼Æ-™+¾Ó¿Êëîg®dú„TÈÅâù‡¸°&æ']©'g¹þÿc¿l{ÓÖ¡8þ¾R¿o&uÒX …¶WЮU¥>éÒíî]e‡ø^cgŽCaŸþ:OòP‡´‡;4i¢Žqþ?ŽÏÿœÓK_$b;Žv;ªóÁ'Äz±‘uêl9Nôzé'gI®wNûaêô[+íy8’E‹}£ÕNÈ–®¿êžgqÝ7ß +Ç £/ÆÑNbfj]
ÉëÎ’®kdpÁbÙ&³o¬„³Ói'oS“Vû0%¢Å¤ÅàË:Êsã×uNÚÉ涑¤h\åûñŒÔQ¦~Ù8=‹ƒ ~ÑÓä7l%ö£º¸F¡ñ¬z’êØâTT~×K=IÕÛ$ÂqØO4Í‹FµROR'ôΖ±ˆ +¤’§ºÐøXãôĈãA$\ì¦d¤Ý„ª¿ôGïßÿ~ñó»Áuï鳆Â÷œ$%,\í«Owœ=Â$a“f3^â a«OîÜð™ÑŠŽ¾]]ªŽ:<8^~n|R}¿½¹ã>ÙáòûÆñHªƒ&£ù”2õ°üÇпo|8<(~<CÔžãk&ןʅ=<ù7ãG_Œ}Š™‰Óû?§;L‡PK`í¸ ¦$œ!‘—ó²¼#©#I¥¶ßTêÊ6umû{áFõ$=vH`ϧ²$˜DvgŒ<|)ð_ýú-•Êé.$ªŒú,ó§÷¦D³à²6$¬UÝY&Oi:„V€b|î†Ä¶¦ÿÃË›³ì6>çS—{DB¶<´q_˜øJ ×!&¤è–¿q„•%\»5gA¸‹’\[®Ë#¸–T¶>]êÜ× +¶ÿÒïQ`ôóC˜çž‰%ËŒ:Å0Z°@bÕY“‰£o’ Ñ$²shiQ×Oê:èÉ€ÏÑpdùʈÔ7|ü6åûÌR»†¼,϶·¢±Æãl7>ïG¹_>Ê٩ΕÞqâí‡9èÃ(¯z»Y® k?Ëíg¹ý,·Ÿåö³Ü~–[Ç€6ýìg¹ì,ª‘ØÏr»1Ë]!ßóbCêÿ.u*EÅÑg\Njô©Ü¨…™
‰¨¨Mó¤uggìÖ¸³®|ã™àÞ¶=¬½•ðÓ†‡/XÀ®%4(ÊKßý‚Ê« ûµôí/,†y,¹Øô)7A‹¦8¶S¨8aò¦Js»E¿Ñ_é¦ê¼[-Hw¢ÖP×ì£(˜ë~ê)ÚЂñ³J~þÂJè©\¿÷ËöíËà¯hkIhì½M“S.>=;D–}iKdH‘ƒó…L<2QÆ.( 5å9®¸$~™»œa¦Ï+”Ëäå—žsæIT"_ª~ã¦çœO]î•g„nãôt€4E¶NX[‹2â¾0ñ€º‚…ÓÎóp$yY9M¡JC¸–TöŸì +wCb[Ó_§€Â*>5+(¨,Š+èfÅç‚x.E&žb&o‘¸UµëH1yM‹ ?CXÙý›"â:‰êRµÊ‹s1†éSlJ.´HHDyõY¾ù¦|AâË«Ïõ&¢úÔ5Œ%o\rÎ9åâ©}sÀå¦ÒÀ3}‰bG$”×ÔNP‰R·»¢¢ +ÑÏ/Fò¯‘ûh¬}„œÀmìoWÈ÷<‚ØúÚ2õF·¦ +FQ¾[-D³ "¼qr?úbìSÌJ8 £ †-)½ãÄÕŒ¦ÇÖñ¬6$”2Ï#_ +üÃW÷HŸ5-PÅ>£>Çü齺X3\ÖA¯Tý$®Uáùf†HÓy$´ã£pwm¶ÿ +endobj +646 0 obj +<< +/Length 3495 +/Filter [/FlateDecode] +>> +stream +H‰ì—]oÛƆïô?覀 +4Ç’%ÁÑ•e5B€Ú ¢$8wÆŠ‰›.wy–KÛʯï’ú$—ìP²Ž"HbY+ê}vfÞ™ùµóÛÏ?]\iÍVÿþüÓ¨Ó}…T>¼ð.|
ò×ìÄ„{†+Étñ3Fóyb ÎÚwbÃ|âbó«‹ÿÝüqk¦ìrû¼‹™ýˆ\þ#YÙñ®×¯8rÏD²>3è\¼“&ÿ®YEë7Ÿ—†Ë‡‚ð·?Už)2.`ÂãH0Bæ†E2†ÛVÜœV
q"“KJ1ÙŠþwVˆ¬Ê²%’*>¥ÐI%Õ¦Ðs«ë€I b<£4ŠtE‰ÈU_ä{<–ï#%>W}‘/ö˜ +ÊS<V9è ¤¢Qæ ±ñ'pÏYú‡ÔŠ“W~tÙ߀^¢Ã3Õ{ÙW§¯ ÛM +¥•Ü''ÁùGbÞF‚H$Øù⾩þSr÷¹B¯óÅËÞ/”ntå6Ъuò +#W›Ú€·EyŸT>7# W:e 5
mªYpÐÀ%RhfT‘€Ë^vëkèS +Ïl€ª³=ÍÎ7O1¦¹ B0´.g +9°?_â%’T¯ÃPUáTú%—()Kõ:xÍÓbœÐ$o@/¿Wƒ,ûêô5áþ|d#•¼ÖêôßTÿ)ù»õÑ»½zw÷?‚ø ØêŽÖåú®a,¸0 ?Knb”LÍ¿‚gÆ*‘¾=5V”àr å”gö:CRê@:RXtFöé³/Ó·Ù§\Ë~Ý„u2ÉCFz±w#ÐôЪû*Š¦òC¸ ŸI©™í£A¯3èÈeþ–¡È¶;y›q ˆXé7‚V@(ú ¾åô{¤ˆ2ÉE ï¦Æ°Ô\«bn„g/ÝI?ÏKÂDÔ0³®T’”WJw°|Ÿ^ém‡ æÑâÚi/Riˆ
Ó…bâHÔNyiË>-wØhv£3“nÇ¥n‡;Ryw ÿè%vÊ’8æLŽŽßàTdÓ0xýÌ«)šªžLvñÞkTÎL%Úƒ+ŒQYýÔØF©¦l?{ÎÒg @¯)Ñä•í¾X$1üÁ—±@í^Öîekœ¡FK
G¶„ad _ªÑ=WÌHƒ?RšÉ%)äçYØĦ^xJ(RÞõ[ÛÔ\Önm%XíÖÖnmíÖÖnm囯4ÙÄFxfa¿§Ö8ýªG)4º§ûÆÃÄx‡}CŠh'OµS½Ýs‘Kñ׋¥‹Ù*wvZ<gÉí´%éšî¹ç”Tu‘$NôÂN3;[ácÆM^¹3®¯¯ÿZÉÌÒqc¡„æˆ/¡lyÕ)²œð"UÝp¶!Ô%º"ÀÑÈ,Ïîú̓¾Ó’oÅ’‡5©çŸ¢ÆJªÊÍ©‘ìXË.=“Rµä†m¾T£¥£uæl§„Ñ=WÌHƒ?RšÉ%)ä-œ3îmOÞf€{-_hfþËs8M¨ÆÕ’Ú +ýÍâ”ã°víÝØ5"%‘ŠÐ^º“ƒž—„Ölq«ëJ%I•Õ¡tË÷¹á•Îwh‘°ó)®ö’Ç0qL<°©‚Ú)/"ÍaYcê‘‚Ùhv£3“ngÛ,¸ä•E×PŒvúÙ&<›Ñ³‰ðÌTccƒ@f_aµ¤@q^õ(±èvûà“ÂRD;ÙxÉœ’°ëM½”x³1½OH¥îV}1Ö5ßNõ¤&¨2ý°³ÃÌNWø ÑRÂÉKwÀ6éôûcdg>‰ç18G~൒YßÂI™Ž+¿XkY<œ‚«c#|EõG#×*ŒT\=_Q˜E~Xk'JTæð\Pœ™J´W" +-œË£"ÐÌÔ0„Ê06²—ý#;àæã•ÍêÜl`]7SÍ¢€{”˜N5Ÿ/I\•hghLs„`h…'g9°?_¢H¤Æ¸T¯ÃÐÇgQRý†Ë3‹Ãe ^ï´msL›#©âxBw¤ØìOmŒdþ¶96´Óš#-†Óš#);±9Òb8¥9Þ€^~¯ÆXöÕékÂ}ùˆF*6MÉ…gø¦úOÉß«ÞëÕ»»üÄÁVwU¥ØÌåú®Y,¸0 ?Knb”LÍ¿‚gÆ*‘¾=5V”àr å”gö:CR]ê@:RXtFöé³/Ó·Ù§\óÊ~ÝŒmNYǜɱH¾—‚p£™WSœÛ23S‰öàJD£DT¶ÏÄÆŸÀ=gé3P.RÞ›W~ô¸ó~±ˆÍJúe£2ŽÔ4øäRî EDÍʪÇ_¡Ukh#©^‡¡jè ÊðØ.7Ïí55Ê‘¶ÏÔÛrþ‘YOSÍ¢€{çê«ç,`¾z¸ë“ºÙiïl74"£&µ‰¬ÝЮW”hÚ
í&§vC{C‰áÄ
C»¡µZ»¡µÚ“74ܼ_ô‡¿PºÑûÆݛģñÀ} ý—=R ÕE˜ +j§ÜEŠ€™I
‡àÒ‡·#©`èGæÀßja¾dº‘»Yhæ&nIÝÎþ±…4NžÅ©}f+ºSgH";uÄÙd¾·yuøºÖïQâ:^„Š
7^ð‰‹TRͲӔØrú‹p\âeÕ§DÃe „fÞ +ÆÝ?¿£Àµ +#W·³,Úÿ‹ý2ÚM†Âð«ôf—ëX·JU¹*E ISwA×[db‡xõr¢Øôí—ªâÌ!£ô|‹‚u>Ûç÷ùö +ºóuÂ: ÛuŠ›J¬(&”¥¸SIÄ°p®ê<”ˆ”j31!±Te¿Í Ô"5î¾M'† íË:<6ÓA*#ÒŸù°ë~£höKf@YÌó¯´D‚Û +endobj +647 0 obj +<< +/Length 16507 +/Filter [/FlateDecode] +>> +stream +H‰ìWÛnÛH}7ààË +NEBRIµ‚ÿüãZÉäOw€&Äÿ¼½vý +Ÿþ +"Zœ‡60R·F.yÀÌ)\x0šÂR$DI®ãRcBŒåP›2TŠ!t𜠡R,“mˆ(̸<ZZZƒu¡
o¯‚ ²vä;AsF8<¨ Ž
¶<lL +E»nÁÈ ·×0
” EF)<ìúÙÒµ°¯$n‘SÒI,5bJ
Möõát2ælP§F(̇ ¾ZŒjå +ÉÁ 88NL§véDô9ôgE+£`ã¢w¯ZÑ}¬ˆd;$ÚBƒj!;ãÄôzß<PfšHÚRp—
:¾g..„*µ:ûŒ‚Ê!8U! Âú‚T Á²R0~u²1cØXƒÅ„&O¼7»ÄJW+'ô˜U +Ù×Lbu9ØÎ!ƒÝÅ…•ÜV0†‚âˆÓÅaÂc\a-CõáøÑNÞf
6“›]T‡àÉv)q—1juˆ”‚Ö~ŽÈ`r_GàÃHäïÜYAÑÈ- +‹—4 +Æß +©Ú¦Ž…ì¸LÀP¡ð|¡Òò{qáŒpe•÷ Q×·)”‰BZaÛ X‹˜èÖ*¿Û÷ÔÅÌ9*<bPÀε+Ø@Ç5|y£ûDMè?ô¼AØbÊ8õö3ØÁòGC/açJXo¬£Ôf°3ºÂÄRÉr#aµ +á“~O0PŽýƒ.È;unéÍí +Z¶µèG˜ãî~L{AC³c«ÊQ-ú;ŶWtvËâ²aa +ò|ZÚÉàwÔgŠ=¨Ì¼še–h˜ã~R4¦¡ö·}¾8âMl{ÑÆ7yÿÝŠ96Ý*Í©Ç6½Î£ø + …éîFÃ0X@€š'g[Ì+’L÷Zíî.^R„ùÙAf¯ÂÜ%ß°Þ—ë©‚Å÷RñµÃ¾7‹³¿tøö˜`¬h]&
íá¨èNáššÝiø¨¸lxÿéN×ÜM÷]}¥;ÅsÛŠg?—óe4¿’ +<+ÌøB®„1N0RÌÙRÚdóGB?³¢ê§V¿YÔm<jõP)-úÍ(
¥ú‰W‚ùÔ_ÜY{¬Ú|ý>4KíúýæìÌ@3¨tEÜ}EQ¬ýen²à,UïEf|£a7ÆšÑ +ägèʺۀž×šÁ¤ÔXÜ +PËùÆ
¹V:Ãzãks¡gYl£ÆÈN0A—m¸ÄÉ‘¦Â Î{ZÅ4`cfýˆ¤:ÏXö'Eí +R@ïhÅV÷Ï6 +ƒ±Í“YïŸq+W(ú¨ËïIQÝ6_ÉQL)*T‹š°v›+#fZkÅæ‘Ý9!Ø r¬ª6‚öx]œ¯TJVF4Hn-Ðt–ýIRElXvSàá[ ¹«Ç¨4ElEÅØï;ø¾Â^Tß]7| +™Õ£ž³ô?p7_M½©~÷uÔöŸßï¾m:8È©¬nc&~=VF?+yƃ}jqèŸÏí7‘íón/'øþäs°ÿÞ
rOîôØTÔ³Oé–åelIfÎк+u5þmÔ“_T®°_ž?,]ͨI‹ˆ4õÅ55dUQÜvE?˜m˜¾¿÷ù@ôÅ{y¨œ=R®j +–%npXlÑZ.γæÚOîßa⩦ÇéÑÉžÖˆ!+¾e’·~yêt>"PQu9("Ô¯·Õ}m£ÔÈõ¦@Ѥ VŸþ„(/£ÊÉLIïIÀc(XáˆN™ +<¾ +™&Ù«amÊvÎíüåi'¡jm_‹ø^DȨ]6}0ŸºN(ª‹…ÚÞ9惥4‘ÝeƒÎèq8J¡Ÿ×ðÆ,¢gj÷_@íe¶)Àܹð“€÷ C7û“äDkû
ÛÚÛçTÿwUÁû5œêÅ +Û<^tK¢‰µ=Xhøï–ÝŠ|š¡åðÐËnU +ãé|8ßÄ×c†8beÝg|®püÿþ² +æîm0g/züéˆ †!ö—zf‰RýU‹$®ˆ¬î‘pöq™bšÅúJȳÙ$~£X-Gï:¿`A¶gåkpì˜KoÅÚ3CDnïÜ¢MÐâ>hŒlRg¨Ár|@Ð%ýQg`T,VÄ›T}Iõd@µQÏåX@O¹"q†ã9b +)û,YŒ•Ÿ?‚C†ˆ&ãö(WÀGKV1YAv¾Íš4:«™ÿ8ÂjX%Ðœr +ƒ7Ý*ƒÀ¼Ì@¡á¸š°BçN°kØÔfC:AYÕ¿Sbº·‘¨ 3ÅØ}BLŒêýë‰RVGtů™]Ìö¢Ò™åˆ®&vKÁè:KèæçWz!пðu<{~¼øüÂ/¿zxÿ»×7ï_ßß½|øxüZFôËoîÞϾú&ÙqsÿöÝý‡»WÇÿ~xùîöx{ÿêöùñK.û
ÿàÿ/~Á?}}úÓË÷ïoî¾ûðþÍë»Û?Ü?|ÿîáåÇÛ‡Çÿæ²
~㟿¿{õýÇ·ÿº³¯oÿýúnCϾ~óáöøöþáÝ÷ÏŸ>±ã+þñâǧO><}òþO4Îv|Ç?ôßùcŠ¿¼øÈ¿ý?ýØG=¾=þþ;^=Åïþüô‰¯å™õ)Ëd»ZKÂ[WÀ«xnT†Ø0Ì:Š za*±>]+é?—Xœª»H€MFˆ`N¶vƳ^7*øDeO5rÀÒœ +öʨPh|IÛÓdÍm?‚_®>÷)èe£™lÄÞ*”0"ã0oúš·›ÐS¿Ã#8—ªð’7>«K,ÆÊõ¤ÆD<qMËv…ap7UÀqõ-ÙR!¹p¸Á)Ké! + põÉÞ².`9Ûˆ +”g¿w§0gi9ñÚ•zwãIÉÖ>ËJ—CôáOá‹kÝ‹×ÊÊfˆWëlW×@…<º§ Î;è6F Ô"@ÈöºœÂw¤Å->‡ä’µ1ôèG-·ÖÄGÕùÒ ®º4ív~
xÚ7y*±8Ÿ‡Kycn3·~DñûéšY¨Wm§ÓÕ]±Ê¥óÖמ֓B.žM#¹Ô³À‘½qóÕyàszç%ÆÑ5c1d{)Úä¯
‚Deahɽà°s=ÖÊ€œ]q´ Æç wëY2°ñl%/% +¯°v˜·
¶|J)Öò +±‡On`pbÏH›f + ®}ÄS8Þb±‡¡7/YáàÎql°;Ø”?úõ܈"]àÀf>—Îýï[„¿ÐÞØYGgélðpÖ‡@ ¸]üxüõ\¼Z,®¦†èÂê õÒô臙úÞ/ðUê?‡YªWÍ#‘¨}ÞQ׸YýtqKäÏÁj½ÚCóÀæ—ÞEŒ:Y +¬K·‹æ¢Ù8+ì#£mA±xa@KEe1sD«7XÈ]abï#æ¢"ƒºÁ’³KfÎè¬ÒÑ
z…Ç8ü—/f± ¢¦PB0\Œa[™ˆƒ#VPòLr´T'ÊènÖsV~p¹šHbQÁ¿¼\ÛÐ<¢¼.øÁ¯!fÌ [}õ!T๹§¦!PGÑkÂúÆŠø[`(48Ê]ô*ಳ{PúîEX2[š\Aä._ uL›'‰GWÃá‡: +tËÜðHáìSóë͸Sä¨]>CóY#á0ÀóA0ÍÜU¡–ÕöM®mk7þ)è÷DÙÈ'Â;çÉñŒ=…AÇÞ}1ºº*F\Ó“ÀJJ·HòðÀIµõ<red!ØwN3Úž)0åv¹†\Tú¹ÆÞnÄ/ÒÎäÅ}-Ìlºü³B¸˜ÿRÚ^¼ÔÔ©oú½:Yªþ³‚yN¹âÞÍ,}]qÆÿ÷¡¥†¦Ðõ+RáJ¡D}¶ýT*0Ïœãsè˜]Y/kŽ=üø¬V©{"˜q»æ¸¤ÀM!Ï=Fñªˆs+¶@1Øà¨ûÀÊ-CpÇÃûZÛ\dëÌ¥x”,š5îºÝ9ØyRˆEÿ¯à›[j€FÏóQ‹É0sÎÁ09ØSC¬,Sb ýµº™xKy‡/hß´Ö¦º ϹÇEy9qðð¬FÖG‹h™‡7†}žç<Ç•#–œŒ€·¹ô>°«Õ_ÒàuvY\Èpg.WÏÖïÂK#^¸Úˆ¬éæY ûGsOc_Ç“ìu7ÎðC0™èßÊ–<B +LÁD Öιl"çM¾l³¬ÊèÓxÎ~g§·Éây… ¨¿ä§ó0þmòÈêWâ +^´áÑ; S÷+Rƒ-iÃO½¥mÕše½¥ÙœaG5—Þ
%m_OkNëâr3‰‡nÃÖâ@¾1·Ñeƒmô¬y +Œs6„ÃÙ®XìMëf]=@〨,¬Û¥‚ñ#ZLOÐÎ
R k‰@š—¨`‘ؾeøMôa'_›2›sùå +œ$Å"*\ãuºÄÃV@dœ K¯¡–Â$ÈÀLQ a¢kÎÂk²ý5Ë)DƒÓ¼’Á¼Õ†øèî–€WèB¢´Ò$ºµ–úèd”ç|¦®¶äÚ%û]o‰3À~÷°)Ím +ÁÑW˜ŸÄ¨¨L¶®p‚/Ý1æWÿê£}šÖ¨P'®Í¬ƒ”Z÷ÈçJÍ{‚áš%^üpL.|cz ÚÿQË.[—¹£çÉëU…gÁb,UȘŠÞM–%]¯Ä}]!b‹Q3âRJܽ#À:öS ¢•å6ò'ÂË]×·½¿ƒë&uW½ƒ©N›¼À¦IÒðÛÏâM·ÛÓ$Áný¹¸Ø3õ’u?«¥°)!‡²Ü@z—;ðú¤ž&s! +1¢«Ï´›íŒ²9¿Î¼BHÓØàŒ +Ƴh®ºD<µåOFHØŸz”\·¨¨~>qü<ŽóÖiŸw€Õ‰cŒç›%öì¹lðå<²½%CèK£r…eK¨¥^=Â:loö·ó¬ch{ËĆ·µ-Èy’ÕyVÖ +Ìj"û'cªÂY~GׯsÛRÁ(äó1 ÔuP># +·; +]ÆMÅø¬ë¾öq8k·ÅsEi*œ´¾Ë%’²´¿¥ÿ5z™â¸>z’‹ÃÅx$Œç6\Àk‘Îv~æ‹-§˜µ=/Þhg¾üÃ`â +݇1\»Ùº˜Á»ÎÃÖk'ô¯08¿âíá·aßßa}t&R{|E)ù*¿½¤GHî÷·Ì¬ÌõzLv1¥¬ê_<Æê‡ãx jðå¯Ga’ùlvþŸ©ä«˜y/M[å{Ý??뀞hnozçø¹ÿÓù]Y½Î;Ðokðu·¤E>Ëü€°"´ÁañyáÊ'Oð’4~Ö£Áïw·‡nÞaC®^ñÈøþPÓu7LÜFɘ¸Ú_÷…-ò/Âz±Ç.ñ©Èu0Ñû ô°A-ýëîx¨w«·lnVÚÉY'‹GlRç¤ÀÇ*gT0,ŸåγÝý>¼””éVc8UÚsì
Tzw÷¶f+îz ;ÚÚèç;n'áñÑ+‚ÍÐ]ZðŸÝ5o*þ³¹B‹œ[èxRÅС¢LTñœ]˜XµÙô‹Øv=@Ã<Îõ•]ͯÌÕY‘$ø¯m,[$¿ËÜ(¬¹™¿€ås8Zª›£Ó/õÛ=ÃËoPmÚ¼‚ÿšâ¿'æÔ¼ëu³‚–Ñ •Òfì-+ðⵌçáÎûì
em²+k•O¤+•vX!ÒH[Ø¡ÖZ®ŸC%MúOàyçÇüçè¿…rŠaF˜Õ“rjr:k‡GOî|ÜþUV“ô´…ù†r‡ï÷á-?s´žðÝÏ&>:Ü>x‡û{\,!½?õɧÚFµÖ+¦‹Ú\0°PÅ¡«éZfþûõ¤3£Xƒö´%¢^ñ/^ûþâŸè½á³`˜;ä匱÷rÞ™ ºà¼EŠ×è½ ‡qa@¸» 9Ez3{‘yäÎ+©(JïµÜ‹BÒnfË÷„/°ó »cïÐÚ„#€x[ÎÎ
±YV¿a9Ac½)¹ÈaÌ¡ª(¨"ÁêþÖC–_ÂäÙ9¼SQ.ŸÎ°kŸ‰RÅc,¨ß°8bµ "Á0É`IÅaJÙ`™ü;I†Û·E5BÛI‹€m$Ê¯È +kã2:î°Ã^k\_á‘ÛoMW‹ÞŠ¹CÈš†Œù[I^7, +)È»ï½ÔS|F@Ûç¶ÿ܇r²¡q\“¤Hý7žrÂ*²JÉ>¸a»±ÀàÄõ½CWÞ˜Z·BIU2^Éy«?‹¼ó^ɧџ°ÔP68#¼=ZHgÔ¢>kAðÈVGxÅ«ËaìEUR†Cñ(L,7ikM»Š¢¥¬¼çQípë”ÎæÞ"˜Øv.Z¦0—B§–p9æ~FUª}䊻“&'x¿!ÔæË¿Óõ-Œß0Åá°GM/T”ݧGX¼ªa8áPßaÓ:|=#¼9Œá›ß¡–ºÃ"O[d…)vƒY§Ü ¸",Þuþ2$÷+ÄzE—Ú¸/m£žPÑË#ÔÆvˆyfoðá<eiºŽö˵XÒ¢³°žp@ù[h¹@âÊÈÊSÀˆûpŽeL8+C’b+¬œ‰âñBM%2´¥N¥)Üî-+—Áüá\ÄSøЋ‘\“‘È`ÐnYÅl^œH_oƒh“¢2SîQwh qUô`ÀRd±Z1иõGÅÒÊqÅŠèÓ#,>bÖL IÊj`Ìé0ÁÁ–d+ +ØÊÊ㌳víÁ 3ô€p
ë÷õËjÀtÏ…Ž,2¡yb4Ýh5öL>Õçó0Ï6¹ +cmòmƒWÜ´"¯Ãp¡É#óãE f|}ÑF¬“.;Ç{üô¯†2òeÞyýûZƪú0`ê17²ƒW¸ËzL"p€Ã\`)•¿-³Áiå}EuôgW˜iëìPï´ƒ']CÀr’"9˜%ÑØÛÌ:¢®ˆy–ÆûÄZ .£Ê eÔ¥+9PÙÇûz%१;pÈv¾¦]Ⱦé*[{@TxjíÌÂcuõ}€§µ?Œ‡+Le–Û'ž©yj¬ã>>K’¤¬>E!v«¨Ùx~ÏŽ\\:õ†³§w(ÀîFZÕ· +>•v¨Ðð®N\Â_Fn¨óòÒL£þ©5—Ü[EÇR×ì/LuŽà±?‡¡}ôÞ•ÃïO;r,n]ïßAýeh6g¶>{Á, +çÏXQ¡'Æ`œ)¬RÀí~…ï¦ê#çÛlbJÓÿ÷šN·q?.‡z>¼Ò¡/þ1Û㦠+¥¥Ó_Zå`RNÔõ乸¦U{f#öÐm 8¤xú0;h딲—ú4a˜ú¤]êîkî{}õÏSÚ·ëèX)ðlÕñˆxŠ†™ýYŒn”z
N†J÷E¯Y§°
'q +;Oé¾®7(2ï«°¬£íýì> ãÚ„Qñq³!]VçäµñøXÜ¢{ǃ_ðû®'áTL¬â㉪w]sA\ÉQ¾Îò‹¿¯ +ßo„=¯°1u;ÉG]Ãp‘=üëŸÿ€ÈÈ¿>Ÿ*ÿc¼§ôõï÷šüˆÿ
Ž"zŽ>(¥§{!ÔÈ÷Ï^|Êo{ r`y@½«7ƒïä¡é8Ç+¯¡²§ˆk?s]ãÐt†Ö&8ÚãüÊHÒØ´SVù´²¿y3 ¯gg~Yü’±ïw”ÃF1NžÕ¸‰Šk®÷{Ë»ÿMS›ßP\k.O‡A=ô†¡¸™’Z™Ú˜!PÑþ"3]ä£F€6©GuW³®¡Æêâ€5÷þäÓaçaöäz™kÃUDh6¯S{ +6¸j!ükŠÁ€µ#™™ÄŒ¦õN,겂¾úÌÎ[õ”“x6÷®I†ŒÁéþPfmY÷]¯I„âeC?3»ô÷°Ðeå +T ËÒÑcÿÔmKrÅ«Ž,…·Ñ¹p¶v±4ÞâvºëØĦða]Qü ŠM 2ñeÇ„ :á:¾ÚOåëá!CrÍ×ÐaUtûß×.;hyÆ÷]® h™tœÁ#Q™|ÿX¬tËd·ŠlŠÑ~ñîõvÆÜlÇì«Lv™«½- +ÌÑþ@z[ â][qdÓ!…ˆº—•Wöfo€©öO8¡›ÌÙêöò€Î5_/N.q²¾*»ÅÕÀ9eƒ,fk¥a¢ì= ‚Š`bSB=DBfiûƒ»¿¼³[MF¿5Üv-E‰á<l2Z +lÔa‹‘æö#0 +xW3‹ºØü…úÖ_¨§XRõ‰€†R“ßE/‰¯Ž :ŸŽÉR0Qî£Å|-f +endobj +648 0 obj +<< +/Length 18723 +/Filter [/FlateDecode] +>> +stream +H‰Œ—KŽ^¹
…çzÿr¡)qì©ÇÙ€ž¤35лϑ(J”nÙÉÀpù3‹—’ø8ÌåÓ¨Å'J,ŸüãŸUB}RÍ“gjåóAJøé›yˆ1fÇ;äGJýäVÉbð¼Q¡Ë8=!6þœßšðuŠÃ˜DØ}Naeb÷åÁøáaC0ó ©V§‡ö€ÐuŠöÔÂQaH¬÷ O–6¢
KÉv“ò›gŒëówÃñ@ß,†ý¹eÜüI¯ÀJlô¾‡ö‰íº‡mœp“‰_÷0à7™)Ñõé¡XùóÓŒKæ·g{匊A³ÌO.TíÈüÔ–“òØ"NXžXs›0¦&.›‡ ·q~Z™èææà<2=-„ö2æÜê/áüÖò°ù +ØÃ\‚AûÖ„ßÌCÁÛ¿Œ7l¹Ê€åAöä!ÄÄb(“æÉä +sQ!s,²¦n‰s´ú‘²¨HŸ¿”ãüHxÇO8Ü0'‰;4óÌOŠŸ2‡ôùÛ>—‹æ?-Ï…¤µ…Dj-˜úR¶²}9š5}Ú“ãìéI¢B.¢ÅS5MäA¦®2ò¼ÔFÓ87}OyZ +ÃÓDóZé5=„§j‰K€ç™×èè70æÃæœkýÜnK
©†ì,W©”~dA;É3+Ãj›'î° ZW«¢P8Ïœj‚Þ“iÄ€ê½ ³Rh|<©—i‡è&4nW³Å¦K7Ž±&5æHésÂŒ_KqÂZãü§·µ(/Dfü%´À2“åaÇæ>‡ùTS}"kqâ©JÞbæê¸zIðk8îa^$R¼Œ4ÁO±‡Þa/§”rÈtA|5D6žW]µÃ–ô1Û“8Ö sÖÇÄL“ÔìžÉúòó]¯½©FóØÜÎ</‰nä–ÙÁå¡•1uú{S5c´«üò°¿%©¬Sx>Nw{X݈5ͦÛé!£=H|o8>wBLl"ÙPoñºIôJó‰k
tÅ€þÛ§âó°ùÈ=E&. +cLùºÞ‚©ò·€*RcKóé)¤».zç×6žž§ËåZ£ÌüT²k +KS¯³eÀßü=¬g$J†Té þõù·ÕLa¡Æ½óf|ö?7‡´¬³#X5v˜ +Y‰jÍpïz;úhéݮۖÒe›´úƒwX3•]Æ«¯þµÐÊUÍgîjÆxHQ®öó%<=ìn73ë†Pqoدë/[k•Æ®¶Úqš.Hf-NçÓC÷}Ñ’ºm%òñ}?oúaÙÃióuãåªÂ
"ß!ìÈÜÚÄröeÝhs×S·‡Ù€Ï9[õ‹asˆDº®IUPßÓ”ðaäã_÷ˆ›\!ª3¿ ¥Ó„+Œ„ÁïÚ̆q;† _Wa“þÔÂòhGõ+Á³äÊ—%ž†ª5
³ÏæCÝ¢kè«/•ØïdÖ–)ü¤Ê‰øͤãæ¥ÍÅ×ËÚÑ$n=äëÒº—ñÆEõéÐŒ]:~ÉðÍñ‰·èÿæL{‰¥•Só¿Ö]’04ÉÁ/6*güÜÕ„¯ê0Þû’W`˜ˆ«=ìϹm7õu¦±B)5;¸N±ùZž*¤Z¾ïÑCw{Ý÷¸#p»Ð^Wçz³"(*<ýÚãÇŽtº=W²m||nÇ°ã²O)[ï`ïã>¶ßÁ-X;— ‚Své¼ùZ¼<‰}Ci¼¶1Èù:Ú_¼Ðd‘ëZ|,4Å.¡½ö¹-J\¥B¾ÍR¥Î]ÕÀñÝÚÅl#[Áû–å´ >بÅx·ÿÅ}3îP"óïsdvÙQK²~l#Ç%_3›LW?î¡
eÊÙö1ݦ/óçTYÆX¼î]¨{¨yŠÔ¾P’ùlkŸÍk¢·1!¯õ´àµ-ßßüK'ã ^.¸<›4tWa’öpkâ÷w±9ç¥ý¼ÝsìØ°ˆ˜´À&¼–ºÃxËSçÖCü‹ÞZÖ=èËÃß°]ÅÛ†#Ù~¾_+ëí2'|)õéù¼—ðû•§Rÿaé3Wå(ã|%ü4^òj—ÒÚþGÑÍdÕ—j®c1¨½eÈåz±ItßZõµ·žƒ‡ëȯýÈ–ª–ð`{©¢"k©â°Ú&b˜™K8îeŒØ¿ÀhoºàA
qI<!##násò)|N8[w‡¦e&\¬ÿOÏ'\“¢q‚@©äfŠ^?xÖukÏšmZýîiåùšw¸¼•Íñ)ƒÁ¶’â`ÂÛÙñݸ¨ðÞÊㄱê¨`ü¤(Al× +ÇW]‚i³ùý)<Ѯ웞ý¡?E¹óŽHÞ¼ñøÜwãÛ~…±ëâ€%Iu§Xž7·®³?åŽÍ,¿ð@ØÕ>G½zE0W¹Ù§ÌعH¡{ˆJ‘Ú·Œ gïÜ}–!qÙý©4…ÎÃêp€”æÔ\½ðêßx„2.
Í>D¢õ辘!S§®¯ãúlQ8.ûœB¨h…HÞ3ä0<TˆAÔt?òÆæÖ¤—v~Îó56=³÷v;à +xǶŒ÷ÑüHGûÒ…îŒ!Ô0ç<…x
ã®rWSt’{éç†Ki 1—âáò AÓÄïˆ,*®C|i¼azC†¸4>¶eLh'šªóÈ +eq–ë=MùØNc'Ï<”^5ßnS
~¿µ‘“Ž¨¬Eà)ú_°Å][è³îêWè’bØj3h–¢KÇ7ó°?å ó°µãÔÕK|íá
SõsB´ !9°
RŠÛƒqDµ)n
X(œ"°âcæa)vp)ËCdžn9’A4ÿâ,—ã„RþÜËЖ’×"´üNcì%u<1`T&$¬r_y°5iY¢‡Db‡L¬:Ôá
µSWÈ’³½&õ‘Ù·LÂVD›Ÿs‹4K¥]0Q—, +ç~ÑQ`þyO‚ûçAgPV4JŽãÄó¶Õ¯ñâÃö£bÃydúî¡ŽHˆ•dÞ%Äç¿Á+Ã;•_Ô€-A;Û@©ÝÁž…Ÿº9“úòJGõJ¥Þ÷Hèl´Ï~ÇÇnxÁ2t?@ä0"¡,ȧÀAóäœÀ¯›$¥Š·ðG#µ?k'¼ð°$ûþ÷¹4‚ä¤ö,‰<÷@Xɾö¨ÍqÑô ÕM'·T¿Þ´¸k˜àûôÔŒXí&aE&ˆ¹5Œw‹:‡åP7NêT"6IdˆMÊâîj +uˆáĶ\ +ÅîÚ›±:¯ö¾y˜r!³_Œp}ÃÅÏ’«ñÝü‹%mF,Ș˜‡pÂñ6”LÖá7ª¸ŽuîëK4Ž`EŒˆ=rÕàœ«ñà•]6ž´ ®º¿ó®ß/)£I³_—æ9ö‹ïºn ûÐ §"27´. +½»õSÞÜíl‘Ðö‡õ{H[éÀe"Ñì¹ESãú²ƒ-Ç´qRðÃ]Cfò̥ŧ¹r=ì€H*»†´ÆeâoðêÝŒ†_Œõº]kKz²þÀðÆÑtÇ30^Ó¿õ32`“‹Ï¹ã˜©x“ø¢,yó*Ÿï²mdÀ«´øÚ^K ‡(<¢ŽÙ†ãüâ;¨™<õ +ªäõòÃx ©ØÛ£U߈ôà¿xK|¢çøZtdç¼<ÚKùdÙ©a_zù;0ánÕÏ$†—Zé#8v²«† ®{ŒÛ¢çÒ²¦õΰqµ7ø7`õ¨!‘Âqo_à~G?¡_âs¡úh‘Q‡gh)8ÆG 7Ÿ--‘¹%wu±è²§§#YÕ5ÉÙ‘ó†ïsI³ÏÕñ€]R='{ú=Æ‚È)ú m^Dl÷‹¬C,šáçz +ÎÝð)žº¸<•„ée‚^QÛ6žU¤þ/°0·v8ƒ×ë)ò¢g€kûhɨíz·øœ¥¤l ¤Ñ?8xmµ?¦ßß‚²5¨ÊªÁ%:ðííÚàüÖG†+8Ž|¥Ý5ˆÂA’ÖìÀ{ñ‚÷µjÄý8šgàØb"³¾kÜ`éÝÁiÇÖk–1•Pkà¯ÉyøMrظ%AÌø«}˜aÄNsa´å>±<Æó–†m?o +ÂjJ2—Rý[R²ŠÙ;Ã
;GCBý5ˆcS;¶³f‚×.ã‹eõd‘|dX楙0ip•ðO¶Z!6׬A9ŽC¸Êجyív
k^ÁîªB=5å~Hà :p§²‡™§!€Ô!å/鄯âÁݬ,ÀÔÇg;ø‰ŒY=Ž{
6ðl»ûyìΆQ“§ÅEú=(8#:¼up8îUÃ\•7«m¹ +Ï’«_Žˆ¹2€ø+GÚ%ÀäÏyÄG¨ªþš¡ÚGaêó)ïS´Ñ½¶Ù^Xj'—Ý…Ý÷€`á§ûß宣GŽCá|}‡Ž70$Š¢¤¸9`â6óû§{(’ºTµ°?ëgQ/‡Éê]“¸Yå)-ç%¨n²Òaªlƒ’7Þ‘JøèïãøGCîi%eÑë1˜BŽé¼§¶¨(>s)r²–ì0[—¬’ÝlÆܦҴþ@ˆŽQöÃ+ºO‘ÜìÜ’Ö-²‰N¦Õ—%MµRáÕ^üºË€TvA1j÷³•y<Þ¡¾‡È^Ôqwo‡¤a0Ge‹dBجSbå +îÐvAµ +ÞªÙ+ÝŽ\J°Ráì[€¶~IoÁGL×Ê0 ˆÌuE?•»Yh }‹\þÇN€Ã½˜ÄVÍNæÚÉàÔ¼ËB
%¬F»)a…Ý»`{CœÂ,™ØSØm×Ì¿áå +[aÏl!Ý~øÀ2öîãã†bSé}‹¯uÁµ€r¼Á½_‹Ûº¡Ü•ƒBkRRìK…ksX÷vö»Ãž*ú9ª³°ÃŽ¯~þÆ·ŸÇ»¸J-˜È*;~½yî×9›l½ŒP§€÷»øà€eªÀRÉÒ¾ïÄCUƒíwqɤ‡ËZ¾Š¥à¨5ªéq‹+ñ|ë¼Ùï.⼦+[@&1(™Ê‘6cÖµî%îW»ŸE»‡0ð¿uÅ1Štæ—ûƒ×Õ`Ð_®BÞ¬ö~æ´-\zVvU”©´æ°çîÍEóïkEw\R¼a{3Xöóyð_.l.e.ѺDúøø6 +ŸGYEëôѵe••xÎ0<©‘Š.éæÆ«(â$—vS-¥yY¥¾:p©q8´¨B“E +3eUkê:ü-œ·8/ÒK?ú†•àÃÕþá3`<ÊvÏávÃòè%¢!kL©å°ïðyçHÞäsÑ»ÈÖG…;D[œ.!îdŸT6OuU·c¬YèQÝd£h¥(bãèñ1ÓX\‡¿}8ûƒïŒø¯9k–{â²ê-IT[Ís³¬‰–Ͼ¨{㘛¾Â¨{Â17ÍERW_4´i™…Y>G·+p¦´®¾ò½oqU–”½Aì +Ö•ð̘ +‘ lžhV™þžµÖü$ü9ò?>ª{N^#²Š¯6•WÓ&…¯6{F”ៃp‹$o»,×\NeRû‘ã®Võš=Kkn@êXmîÃç5º‹o«5ã¡V„Hækë +q™Î=VÛH[Kõ=¯•qÞ2»ð<×cË +¯úÃKùáÙïK“Ê¡ñpÏ}Ø à¡×eu™À)Ó<Dƒ³Áº7œÙ°«GcªsÌ—
rYå‡oB„E£nœ!Še3–ÅNfÐ¥ýµ^¸ÉyS0O8¸,¡b1V¨*äØ.’/±¶hâµZ±Ê!$×zt¾Ë[Gç½´sp?ànBÎë±a¯3ì +‹¬§¯ÃOBèw˜£ô¶€‹uòÃŒÿÿøö!î.d:šÑ×…N!`B•Qñyå[µhBéè¸{Øêꢶªlsl.ýea¶÷o}Øîa'ôa[0¸eµ!ä-Ï‚ïªC¼³¸4.K©.‘Ûs/暴áâôjÀ‡ÍE~[÷@ç¡V¾Âƒý1.¾Ÿb+m6gS‰{Üáš’lç|>¢ßë”ÌàØ\VrX—dôüÇn‡þ^Ýô'¸ÔµÌŠoRˆ_^êÕÔÖdå0ÀºçLÞ©)’¸Œ +¨}ı"ÝÏJJÑR=a2zýÚ¸\æQÒŽaáïŸÙvSvá-c…-ΰ¤Ìƒh£E« +¤ì#F“+'@Añ~ÜïPmjìic›@–ù>6¼¥Jy˜ÕÝ ;m\YA©Ü™¯•¹òÛçÞlS:Íž'!©–ünˆçBæöØÁ"õnˆ¼K|L]ˆØrðKBN¸u¥;ö´°9Ú›¼>—™äwl)·Ö-´ÈCŒô–cóŸÒlsÀžÐ»lƒE¶ŠtµBÝ–sOy*~ÎÏÖÜÄÇïÙÉœë×AÉîR ÿê¿â׸B~ÆÖaªû_’^B>’ô—àIgËæ¿…è!9ÑÛÂÉñ—úø\µXœ"t¬cnùP5€ScöÃì
~%Õº-ø F+³DïCÙŠ
(iÞÚ\–Ø~.NÝú2`e[•CŸàqrYèm¶’ÛÂ><¥ªBôÒb¯æðO*Ö9K;:r©†€ä•raß®ßo&cŽƒûçÐrú/`´]²uPZž7@–b)Öb<¢úÐ5—í(rrƒˆsþœÅþ6Çz‡»É®è +^† ³ž×P¨Vb&FžmC»’è+,¸¼Ô! +
a4ñë¦ßëìÚ¾45ˆ_Kq¸÷þŸÛ +4uö¨{ë´ñi$=5TÊ‹N½Ég3Äá¿ßa"©5Doµ*/9Óݨ=zùàž}‚B³]ûå;#ÄòµÑV~;XV~‹É/ñ× ã +&Ö¼T¼Ÿ_k+¿Åæ•$–Iúù|ŽÇ{õµTƒ÷F=/‹úýÚ,¹Tß/¾@ªu^àŽ0Ň› 3cÛ««¢V~],Àïý +YL
|_O&ÙiÍAÕO¼¢k¡Û;|Q$‡Zt*F°¶»ˆ>2 "ífc¹Q^| uüN³qÕ]M|$ +Úãp¯. v‹¯+à˜›up¸º§.´F¾ƒË|„ŠñÀÌ+4]Ë`»¦ÍÑüh9Õ|Ä’¸ +ßÙJeH˜aÏÀ©–']¦TWÕ~Äã0÷Åšæ>Ñ}ÎFŒ/^®›¤¯»…U}ÂRëúˆ‡“²üŒ&a†^˜#e‚Å/f
}žvFÊZoçCç˜ßàÛ}>›<À30¯ÃpŸƒãeböÃA´Rw°‹êÕìuɺ +-`+Ñ#:‹—D?ÂËàs½Ö×£cÃ#^à£_ ‚h:ìk‰sg7X>Á=lËèyĈŠýâÇ +bñƒåëz@ƒˆ{¬¾àJ´ß6`„ˆÇÎ`UçÃKOB']Dô¬Ÿ=X î±8¸}þyè¹Cdø:|+/ìy Uy‡³qëÒÙÀEG~¼òÐ|BÃ
Í…í;d~ÿ0êþáàÙÕTß^ypGhUmèC?9ó4i Í«ø{+f¬Ÿ4ÐX¼ƒÀD5w#T$G +ÿIe—øô%Wh«KpxÑö£S<WèË +gKE÷âk*IêAɆ5¾nY<¯± +w¹Xá–ÆbzùQ/™<øVÿÜbyð´¦ýpWóD.®Ó]f;¾bs3 +©»RW!Ù"ÄßàÖA$¿Ý¼ùxø¯w/L¤¤–Ùê¥yíUž.f›~<B·ÍÆq>„¶Ð`•lm.ñ»©%·FÇp7²‘À*&
†È5„‹ÞÓ-*øÅàâ\TŠý¾¿˜Çróp4S]\óþOÕÏÎêFc úds‘ÌrXšk'( ÓŽãá½K™æNñ@Èç8K…&¿›‘N8šûÊV …´BøV,FÀÛH7n¤¬ÐŸQ^ ,Ä +û£mHF—PWíî³ÎÚÚ«/™Ò`¹€À1‹ÀI„¸ùYºÛ¥ÌÄ]Ö£¯7\Y³
FÖ¸›ê¼áã\h êô9Öª£‡9'ûb£|?;è´µ)[©1B‰°©1B11´|Õ ,”xÙ´kÊ’x3À[Rôguv9¶pD3M]œÌaTóþÚ75ñÁçB°U¯äN
oÌB¼S6—i37‹ \–žð†š‰ï×ü +ž,°–Ÿw„«Ïç~‹jeTâDn¥ÏWáá) ÑÅ6¶…rÆRqµñg„_ÀÒû:-šžø;í½wyHï*ódrÂÆs
hÑè»ù¬jØfúF5kò¼û¸_%ƾ…Mê_ýFfd\ÁÝÞ~Ðn³ocÉ +¿’ u¬„¦c:"@Yï]îDŽžqà‹R…„¨'¶FO_[‚˜©3.˜ Ž ß? ¼oVWè–p}çQ#Dvº„÷?à=}qC&Óƒäa +ý¹²<«úÔ™gOkÎze—4±²ôsšd0奕«û/3n,íÛ}£hýô‡TeGBùÖª¡;7\!Ü~ƒ¬YP°g^±dî +n|dk_8xkßý4úõÉJU–œ:óyÅÊZKÍÏ^<ípOv¸X_ØI¯Áˆà¸òm0=±²š3D°ðCs›@c¬¦8©*Xº0:v,~/Åß8Äû–ôerQË1ÚÕOÁ±æ³'Ýopãñ~C£8*¾º-SÎæËh[On‘jß‚/)§p…æ•`áÁ.+±ð£257X~Ã;¹!ºÄÀ5Sg?)ƒ*¨ÑÛ´”ã<¦Hçç²
a±½×@ØòÝ
÷ç +¦ŸŸó_w€šm.Ôoa0ãÊ)ƒw¨j5A‚•C¸}²²&ÁÞçãÎæc™ÁëV±qÅ„9zXØŸ™]µº€þž6Æ8l¾Yï~‚r(\AĘL¬ôZêš6Öm˹’ ¶˜D!üiç÷§h;%7ìóî„' Þxs•Áû«¬=»ÈŒ—;Že»
EsžC !ŠúÆ8z³¨ÐÏóO½ù“ttn·4úÖEQ¹¹‹^ …™†ÕzÙÆRu"Sx8h.,…—Ô³ï–`c°ã8® ñ\/ +GÞÊ8øó&ÁÃÊ‹‰ +Z—'ì5 +ž¸ÓÁgù„^Q~/±¸®xC®¥:L5’¨põ®†'(•¸öÐsúBÕ8Z"„f¥ÚÌb¡m=–ŠV\pæÉôçGeÏ›ë2Á¤½.øñ°“Ž>Ãÿ(‚—‡uùd"fï°EÿÜ–ŒKÐÆ>ep¯°$MÅIyš1£ÄÑc²oÚÉ9›:8³C*½>æ¤×¤•ECmÏÑ÷ªÅ0¡[ŸR†¤VSÙ3+ëô¢—%bK·£Ç£iâzZ%‹Öiz'©9™Ü÷ê«d0˜±a·„ +Wh%íeQT™ÓÒá÷ÛÃœ”¿þ/øÜ7áÎ]@±pº·vÂVçÇ<rmŒÑBUWð4]²¾O'}õ;8˜è•9ví…ϵ6ë”1×øÚ·X1Á;3
$qQ·íÍÊœqˆ'ÞKm¦¡ÞñCëdÕçfÂa\›Êh®n¶Ø¦×߆³UbÅ)eÛ +Hµ Çp¨êHaMeF`ÉäŸrêú¶!0e¨üÁ‹lV2ºe¯#æAæ§d¡A¼Œ¬/’VL4"ÍÓ Ò”›i„àŵäªcæå¡ÆøÉ>k*Iô}Q·P»Ó¡tzÆpT¼žŒ¡§‹qn*fNõ0£ø=8î*}ÝnÜ1à´{ŠK^»Ý(t·TÃ8à›ŽØn—‡ìÉ?*Jví/XØ’«ú9>ߘäm}Ý̤ééÓ؇Ïk©óõ=÷0·~Ÿð@©Ö—ñ†‡Û
ðGE³lIEu8,•²A̲=B€âÝ¥V†<rchæzß°V;†ÿa¼Ü¢t#‘üàác>ÝrÈ‘¹‡åYÔ)./‡ó/“¤Ãåasžô6Æ3H%Ÿðƒ‡ÃxC-iOˆb29<$Ô¡nCDÞÔÙì„Û ŠE9àò€AƒÏOã%ŸžPG‡Ÿð°yjª–5%P§¿
WÁ¡o˜o˜ÍÄàå#ê¢ü(Ù}Ü Y„ +máÙù
Ó €5wŸ÷d#œQ¯~ÃBü†Ï>ÇÖn#†¢cØr°qÕMiÎl¬×ø<æ^³óϪ¢mžZ»=l!>Ðý¿ÌE©Ô9fy¨¥Ìƒ?Ñ.F<ì±:\R-Ý<@ +ãHïi¸uÈëЫcü»Qx†I„¢f̽·Mq/¾ž>QHš#¤åy¬^¬'8¾Ùö¸Ù^û¤³ztÀ‘µ~žz÷ŸÆÛí +Ÿ8Íáo¼,·Ç…þַό܌rŸÄT‹ã{Ç]šç¼= ”SÒWtyÀKimï³¾=|„êöçN +ÒèÝV{˜ou¾â×p¹ÝžöÆlÐ:†s»µÙ¾8Jl{£vPç„H±—‡ÍØ’³Æˤ6ó %Ž¤0õÿêrµñ:àô +.̸g>Ÿ¾þ–³÷t1,¸ÇðÂØçóqû¼³|nDÇ}· )ü×svŸæ NÍøò£3÷ï¤G›èÝ¡övƒï· MÛ¸*ãžÒ̵á÷ëõÒŸÀÈ9íýKm÷ƒ ¬Å5$mTûïuªåt¬—Œ¾ThWR€1D:.ó-aEy?:¸„,f,†%nó[å–/…g|ì%Ä(ƒÞ¸38a×=.
r™K>bไ^1ð³œ~×YÃí•Éuœ»·/ ‹ÁÝQñÆ,Õc8˜åúŠPŒ÷Q‹LÆ"Šãš±*•’§%Zj]{뻄^Ù°˜ËLúGV…©„:aJ¥-XÜ”¹j1ƒRå(T…ç>³[r=JÞr.ý#å¾Ä–‹\ÆÕf·©ð,»±Oy«¹Ó°Ôxjã{—…–£gP×ZÅÁS¸íÆÖ¼¿€ã¬kæh~v·îÓÖ+†hʯú´ò39Zrž»ånl ++Ô’¤P!·ã s¨,½!_³Bùî ++ijý£dKmwE¡jÛm[½Ï¼ ]
&8N´{ +‰†þýëû·?þûþ~üùý[è‰~„©±ÐÙ#Þ/þ YÿÌÃ2D8áÛ¸!ˆ.6þkA† \x +—,Ã.ó”m€ØFË„£{j"°¢qçÝC mŽ0öJ‘¿)“$V˜“yØyáúCçLØæj¹AŽÙFçÔWŒÁ±ˆªq,RÖfnchzV!²Ë :Éäa6Äç:ošªNÐ8ŸeÄsJSø9ˆôöñuBÈ3Àq\œGýÂ',øáL{+MÖÌ +Sxdüâµ<â3¢5ÅÜï¢"MºJ8˺5JÏEŸ +endobj +649 0 obj +<< +/Length 17107 +/Filter [/FlateDecode] +>> +stream +H‰„—IŽ$9E÷
ôâí5pXçQâþwè/‘”dæY¨M"ðRN£(ŸÒÆ?:T?&6~þ'Vð'›Ð>Í*',ZR?þûŸÉíSʈÃ\zZP“áP¤$Z*þº…››ôüÜ ›ÐÊgô@½[uÄš€7£Ì•òp5 8FKHûd¹,.øYÀÂÍ¡è†=á°Ë‡ˆ£Ñ§Ât®´â¨JnA>Ãl¬jøïÇ:ð‘ŵQÞBt¬+÷OkFÞŸâw¼N„q|¬Ñâò¡jöãu‡Lµì3Ì€ú)Öö%nN£ô¯Ãµ0mÈ ¹È±PUÈ9—1âp
h=ÌöÖ²9/>ìXXî°±T‡¤‘!¸ZSqXÔ®[NUvh[ÈtH<‹o‘Èu±naY(³Æ-¸Ù>o7ª†r¦®ý —ô~Š‰‹¾_âñûã U¶ž§ó÷M¢&øSj‹oɈŒÎ‡˜Ð¨Q¦SƒaÕÉéÓU=
µHcŠ’)uÁ +cˆÇ‚f´} +Ö܆O<@—øÐSê%a”`5Óá²€”%ü×ç0ê=áC1ZfÁz?œ“'>W|ýðG[{ÄyŸß|Ì° +˜¥Wè:5ÌÕ¦ÓWmÎV²˜ûÃ.Ç®ýÐ+ +^F÷û +oG3¿$¯mX(Lqå2ZZÀ%"b•·Œ{p´zŽÃTWÁrˆš+QãäÎÉÌ´+yμ™®Óx
ÍÙÏà>eˆ§Þg¹GY´–ß:Ý¥ùûžÛQ.>äû,¾F¹À?“4\¸Wíy¸´ÕÍrF{#ÒHÞ§…>êÔÖ”‡+qNR~ª—:¥®d2ÌÝ×KîÞöìz”0DQÂ?¯¡‹ ÝYõ%Ø:†äÕýð´iÎÏMÞŠ„ðí(1ŸaÈŠÜñØ–¦I-z¶‚Í×(u¸ +ú "]¬?ô§…\)‘9ìãZ>‘¯¼/q–Ço¶õô
;bó½é><Èâò +øçLJ…Í¥§OŸc¿Í7ËWБ}m6!~ÓÂá|>—»Ñ¶žBŸ/M,\!°'<º,„䶇
‡÷R›Êî[‚!ûaêZÐÏàÒkWUÜfÜ*uF9Šo×Õc´/¸ôÄÞŽ¬J¨ï%¼\‡Ö˜ÄÙ»¯ÚŽ¾ùÕ¢u>[É™‚¸ýP£Šó¬m@)òžågeš~Ÿíèð„o¸§à
×`Û–=p¼&‡RA±NØÊ-ãZ]q +‹[‰!|ˆäyx‹Ü3ºÂ…Óßz
e·¯jÏٗĨù¥Ô€¨f/“¡ßHiøV–Ùý‘øF÷ŵô(/¨‡Ù!O¬"¿Â1øÂÎÄkòÁÖÄXQ-z„ÏÍW,ßï²?¾”'·Ù`[Ø÷zÂZÖöò²€7A6¿Òà>¼{…!í[$Z_=f•>{ú}©Òž©8LjÚön|D¡¥N‚B#Yâ°°µ€§Ò¯¢Gg¶@¡1.ˆÏM•ò´>ü¦…ÃWÓ{‡©G£‘>â€ÁƒxZ@ýqX}ý˜í±¸…ÅZø7’Ç힟ƒ^ao—chÖ¶÷4ˆÿàƒÓB‘%~f/Å°þ +ïjnÛ‡äÐ+äÃi<=T÷„×¥ R·Ýƒ‘â°qMèÍâú¨ñ1ÀSm,n¹À Ç0D÷µ’Þ‰±ê e³0gþ¶Å1‰þÏwÕëjVÃÀ‰w ¦¸Jbç¯^DEõ•èxÿ–ql'>9÷R¬´;ëÏ'qì™ñ´þ0X£(Ø\-7ã +çgÃêš.‚óؤÞ98ó‹b½x ã)÷hûmÝýŒVœ3ûS˜
q¹î ë¬@g-R¼;+#i‡Xð³¥
ÜcÕĽâÇp§UýÛZVi(8jÒ±oå`,þ„w?á]Æúçm“ +¥IS©ÙîÇKàèWwoò–µ°÷´Ýo.vÀªÓxvƒ×ëÓû KÍýgð¢¯Ïiä¤ÜÞý„N-lxšºŽ¤±`I:ða*¬M?ï©íû
{W¸Ï02{ðhÓ‚1 ÖèÚU×€ ¢2´fÓR‰µ’
SÑíÀD9°qeû5›þkáKCÝ‚Éǵ‰ÛŸüá°»ÎP´žnÕ2´¢ä
°ueÀ,Èuú‘m@²¥É®©\Ô:Ø:@Dv‰ˆo.:`àœbCÝO1Ý'ÄÔöÞ¼è¥;(8,bG€ëlµ_ìa¼‡¯ÙuÅÞ=eØx›ºBìÙG(€£×À!ä†ç¹_"
ŸŠ9Þiuì[””û¥6Â)ê.Ãö}}2x†òd8$«Ê9´žÁ˜lg¨´´Bðœ<CkÃØaQ¨¥-Å´qéó9CÝqk>ün.æªâÖÀï~à#òÒ’c )Àä
9èͱ²x¤\|cn& V³üUý œº=Ì ChËDßBb0úåqÄ€ÌÏßcŠX-VCKùD?à¯BåSwÊÇçÏIÝ£Á³O=*RtA¯X,éí2óôw²§K5›H4œ~“ÙÁeìž“`>ugH¤‚Q(Töàiaa‹«ù.<±¼ñAÍ;CÄS5Z; cwmýb~°;Þ¾vsçKs•nÇdÛŠ³Òæf€øMàæƒü%w ;•ñ:C¡Ù_ –¹nšY_™ÂlIŒg8 ¶¤™‚ÿ4vü †3ð¼LšZM,kE;Î åæ;Nñ·ôŠÛ®¸ßÒ»¤`§ØèdK°WQš±È—>|áoõATl}®djÑú¹dý úóX†Ï»ŸPªGL´Ÿ ʉ|®œÅ¡¾=SàÅ”×AeCT0«Ê‘˜î~£—iiÑåœÁ{Øæê(Œ?Ï +ãs½97›,î_°»áÿž3<3ÈØh¢ +\Æ\`A»§ë·s`uJYfx›„Õà +VïST—Ë|,¤î’+¶{©-Ív]ÂŽpw<ïf€5S¿Z²è£œ–”מ?d…°`<pU^|>)V"e
9Kî¤xIɹl½ë3×z=fþhƒC*³)>SÝRc °aŸW†çSl‰WÞ;P,Îiê"Ë_mÏ`·{fÀ-F¿”`*‡§¬f‰MZM
¾K¼‚Ó”5m]-¯e³*(ýg€ZÔ¼–´>‡ )½²e@›ÈeÆoi„òœ×ºZö¦ÒÉÀÚuÚ°-rŠzàm&~ª¯ùH»À~´ä™Ìi]9f +Îéæ8Ó‚“1b†üµîàÄâÀ}dS¼Ûlâ¯ø?§u¨ðiI=;G >Ñ +NJr«s6°¨tȃ
yÜ:F΃É?×õW†zôoí±5¦XâeÍÌ5¸vPi,Šòëî‡É¯àuc+Ó»ÒµÝo\’ +¤Ì?ùSxu…)¦?E<CÖ„<Ï ä8kˆ´àÁ>ûu‹òÑ}e¯›Êû„að¢%šä´zDÔ„k¤µQlf)l¡}諉ͼ:¤¡¢f0q‡¶ÈÁÁ™³i0ƒÊÇÓ.Õ9;`ÓÛl±à&G«t9§g ç´‚™ÈuPkeHCÙ©¾\„)?§•ŸÅ}YÞ#€o©äýþóOé—ß´µAùÂDë–KsÿuÜÇÓj+ +Àeù88
kZd)#œvϧÚ-ó´u`QP,¾‚©-Ipl—– c´3›Ïˆ²u»Ö ÑõUùd é‹5<x.Ç!`-}ƒ:ún{<÷î™›Õ!a¨ÙA\€ÝhøyÙ#†ö°Ypj©†²+ȳyÇÉ?=„,XW9o%KÐu’´q™!é~ ŸgX‡Úý%`×ò¼ºA»E“·¡Uô%õßg¯ê|LÝA“?y¹F–!‚à}ÙØ•vãg€%û¯3 Ã??ÿôëŸ.Û +s*xŠnG\K‰‹e3%à\vÍÉüÞÊxËtÅŒQÚÕ6qúCq0bêã)o<$'íoV,sÈӖk¡'#Ð'•û¤ŒwQ"àf^n³'0¥¨ôoõ¼1¶ÌjL[9¿0êí|êýóm:ãùA¹EšÿüŸË|©¬ôÀ*‡r¯•óqVÐLÑþSì/=ð9«+bçH¡Ã§þc¼Ì‘äèq(|•¾ÀTp_ìrÛVÄØeŽÆUÄûyI +õ‘Ê›ÜT)ns3jjp(ü-Òì¼ßšÙZ¨"¶æ¨Î;‚“àØ.¬,<¡± p9öº[x{x™ý·>Ü8ÞP¯Æ¥»2ZH²ì„)Í&÷vEÝr?â/Cõ¯7¬_áë?¾‚ê¸D#¦æ…§¶xZý²•}ÁÈ;„Zªî6€–3[n…U TT¨ëð„Yš=ИBK• _–hVXZL/¾}@ðâ¥êÄ(Í,Ud+½`˸íó´Àü´ðBRNñXHA¬Ðl7_CôRe6f©ÄÖ¤ÙÊ +Í(ßúŠ,¬ Ëa‚(¤Ip^š~G¡'ò +Sjí—{ôK²›*Ô`¨™îo¡od®¬Kĸ"¼kHyOõG’<·²\˜ÃhmmöàúÖË·oÉa=¿óç”l?o§>›çÐï+ëeX¨UóDÒ5#ò,ÏD/íÆìÛ'÷õ-L”¯9pÂÚYkˆÞÙp[ !u~Å1†ÏÓ‡^EÑH?FÙ1!÷äÔI]Uá'º¨yÕîÔ™%ú=³Öùåµ +Ã?§a#ça4dzrÁ›žœ¹¯*ljöÑæ!Réà=èuµX&%ìÃ¥9±åÕxT±Ú~½AwX¡1«°ÄîÉÏü(xZPn>§p;¦Uéo¡…é¿…P£>-È€s‡nÇ,´·H<[9®üÆšò;â°JLm½6èÕÏä—,qù¼w<©
¤
9E-ãÊirݱ‘&ºã±Ú·ç‚aî-Qê„—…ÝDdÃêPûxíoá²Ó¡!Ì!bRaÉ©ÕÊpW«®'æ°t·ÈHàg[PŽ®ìÕïņ‡¨vK–…3ï½I ;v4YwXÃn6/‘5;bÏóðÖî‘4qøtå%3f-Õ[0Üy")aK;¼`ê¼àH?¼`ƒ,ÔÕ@r?ëxßRz~qŸðJi0ÜuDêópCŠ?‹ÜëÕüwÕJV˜{PÀ#§¯ódŒ#Ý+‘¹Ð·ðjü)Þ- sWn^ze>ìïë-È q‡,\W{þ_œcÞkRŨ‘‡ÑûyžkC²LúQ¢Ñ³£ <æ0<‹–ã8∮ܴ£i™û·Œ¬VgWõ>èݘŸp9öËôãð[(Ï–h4?ï?a;ª‹]i¾FŽ=jß,CSì‡<4Å>üí•ÂÅ×'ìœw)"Sú¬ +™½.ù:4x÷·öŒ¥ÙëŠY€œ»ÁœÉ[¼ëP™Œ,˜ƒ}˜>|×ó‹Bl`xŒ"µ½àS,¼9¬f+®œò_|S¾›”…oo·"¹}xŠ9F¾…]"é»Æ-òVÚìòö¯Œ$Hõ”ƒG®INQ®ž‰z±[Ûz—¿ +h•àë^@»gÜêõÏÙê]'®gˆ?Ä¡ŸÓ^s>õ•ƒÒ¢ á[‹ +°Ì]¯ÐäºÄAJ;Θ³o>ææjÖÂlyž|s_š&ìš8ÜŸ^giê»Y&ÏnÙeó–¹úsU“vZfœ_l +nmÄ[õ¶náŽ%0—ÅX`øë‡Ãù1"…ÝÁV)ìùs( ÖítsŠÈÏÙxÍJ•Ò0—h·7J?ð¨°&jiô-<rZ2¿`|ŒJ]ÇÁ6h3
ð¡¨ákS|28¿¬²¾ä÷xŠéÖRÇêˆñË^Xþ~Ö8 5Š¿Õ +ÙÅS©woY9é® ‘AGˆ¤Çw`ñnøZIÃ8{&˜òEàè9…x %óá1"왫ÕZ˜A›k‹‹gÔs—Ão¡|‹!û +KNfä÷Y¦‘$Ò¨é,TQ±…KÛälÍ÷)Gœ$ê•Æ-&gÜ4ÊëPcÁAUZ>OFG~eï}èWæ×ÛçÞ˜EüŠÛPŸbA¸®’q©"ÒûÐ3åñAå)/£ó¤W†”«!©ÀU.û¥ƒ²_èöK#²UÓ¦ ‹ï`5Ëzh÷Õùf×Á?ózÌ)ÑtÓp&înèx¬”PöÇH˜Tp_²¬Ä¾š—µ°ƒk#øòvl‡a´•ìà³³ÆÞ‹ `+I`î™-”Ž·~Š=üæsƒÊß‹ãpnLàeäó]¥Jä¾ÿu$ƒ„™<iŒšÃ
AëÇÖ&p¿¦8¬ŒÃ:žË@4£¬)9Fþ- +D†Týö“5K›Zé·ƒ¦‡WÉøÛ™òÜOB.SµŸ:Ü iXçêíð*
zö×1âFÅzÚÂÊÒ<J¹Ðù:K£¨³lÒ‚3PÍbs¨©oæ§Ù7R«ä¡7–×ÀÒËYæ]ºîë:r¹IªsþKÄN¸b»•+,ð†%É×…ôp‚¥Ìdžr[€À¨GšTžL½[÷I7;¥ír[<Ú +Úšô(7•ÀPï!wÑPÕ¨‚u]».I0[6Ú\Ô=sÓ#‰Ø°p™Ý‹—¨~ó¹„g¥^äàÊÛ…‡#k˜ü¦®3¤•>¦.ÿµúEq>áeõvý˜eæ÷ +×—¶jwK¶¿Â>º± •‘s$V¬'ô\×B#]u-4K€ì¥{]Ø{!í°_åá-8³bøÍ‚ªõ€Ù5†…´Y‚ç9þGx¹äH–ÛPtnÀ{Èô£>ã˜ÖØÀ—P»÷•HŠ”^f²O3ø(‰ŸËr~.`,¼êÉê3áܸZû²%‰ã»œí…äs¡¥ìNwÃuŠÆ ܤ½sk×~À%áw螟§iÿçØ2ÕNR‚aÉõ™'à1ržˆçÓXv†©q®Ê)Äfwi÷‘Ñãˆ×ÃUŽÑÖ©í!
Í4ݨ +ü×_/Ï7ôk¨ypâhðq—´ß*æ4V脘‡þÈæ¹½*‘ŒÄ;kPYÙ8½Ÿ +ÏÜ4‘ÒƒhÌÇ)D“þ¾$€ß¶^ø .©»=‹<,ƨ’Êeõ(óžÛ
E™o-fÆ[Æoá¦ÛÁ5FW‹Î kÒJ¼Jà¯&kèª*ø?·‡„#qɘ€=ak’ +]ÅÙnx–§½¾+.ŸëÉ9F]}]ó䱺üñÐe ALa´§Y}›Âw½}SEßý÷<Ž"ª ×Ë]ÆŠ}ÅOV¢6In#“%ŠösnçϵãÈQïÎâÏá…$=ãÅr8Òff‡~ÿ?§˜›áûç¾·£wüj4ÖÄW*â ½ÂRȶÂbxþ¡Œ +B«õqC
Ê· +9®‚a;BH‰î# èñþ´÷ÏÏKåŸß-PM›“»¶ì]¥ý<f”4¶ÆÐÊ•Ã(¨b¢ÇæÁ3©±Á:ò
¿Ÿö‡4Ð:À_1Ý°@¤§8tDÀWÁ7~ÝZ^It°«æ8âÕ$ð¤èè»ËÄ´¾j¤³Ë`ó +¹ eíU€Ñeˆ
$ág§;`É•¾ó ½õ0ù7ZyÂSŠgðÒä‘w`€ëöŽS¨Ô|_ÝV=Ÿ—ãîÁneˆ®ÛêE\uÖƪïcRÓ²Džç‘g¨†DWN¦—&Žgî%-ÉÒ«ÔpKìg9ÜAI¤û*k#þŠ:/nemw óÈ&§Íwæù—ô÷]Ë^¾_rv@¬· +enâ%{,dYŸ½Ãó(׳»ÅŲIv‘÷5»ý¦eƲTÝ°§M™èî²·SS1nukÒT¼£›hÒÍÍ®ÉÛ¢u@]önjŒ!V¤±Þ–&ýúìÎ\–n‡Äpš3!‘4!ÅVÍCo12G¥°1r«e<ÿjë1#²Ý‡!^U{/Œ;ÅʼS!ñ+¿°|ît;‡15õ`žQ-%0 …¥—½[TèàÕTbSàô«“ã<•[ýã॰R(Š<W •y®™= ÜgA®Ï…TWžFôÿÄ7)ä˜Òåa™!/¥pÍ(ƒÛƒñ} +”fÖ· ”9† +!KõyÉžûåÅÇ} +´ã˜¢ƒ +«tz\Ÿ»a¦-Ï;`T!´Í¢B²ƒâ¥YË}dèˆÀwÄ`pEûðàîê?=NÑQÝ¡ºS¼Õƒ¯~+7û–Ð+`¨ÿ0ÈU–Ć~Ò´o a$zxð5ë=ìzAœiì3¼Õƒï†–[äò±Ün¥Ãp 'q·–Zfwkï¬RÝßêaɺ륄—Ÿ-06®Ä–ѥ׆výVµrn"¿ž|ÙŸN¬çâý SãÅð¾—1^,ÖÂ0g¶&æbT? +í~Mtð|¤ºÄàùN?ƒÐ-pVC¡ŒÖ&D¦Œ£S…–Äãe°q˜Âï„b¹=”Dy‰AñC´ÕL'„š]…W+Ý.¼á/ˆˆÈ,ð@Ã_©Ì„$ö®›`Äí—Õ¢!'.À¶Ú +ö +湄5öÂè—‘Ò;ã*Æá¡ÄN§œwPVŠ·Æ`\·‡Ÿ<$¢ñXîb€Cz@¼PtP< “´”çÓXfK¤oy,FàmöÑÛ˜jò{Ø eÛÔ³¬ƒ±Ó•‡öô€æz:D×"¸ßBl1Dt1Jír‹Bí]`Hu/¯Þxq†¹‡ñ€˜ºäàG= -,>Ê*‹Ñ:xçÃÏæöÇpm¼Æѵ«‚WÙMŽlŒFY8«Xë*Á†÷{×G½‘ãQWñ‚È—¹#-رãé= J(æ¹rG´.|ï5xØÆz:v;ÚJ³3†Â:]cøh›÷°ì¯wŸWá[ïá[ãµÙÚ[üÑkï+ó^ãýFò?1æO|$°{zË´Ÿ %O«Áö`™Vye˜0E¾Éàƒr«—®z9c8‹ÈJãhZ³®©ZuVÒ»µwmY["ýñjI’lÕ‘[©¤ñsÚ»¨é·ÙÛ};HB"îëA™Eyê!ôqŸë’®±<™t|§òdÂý1%êñeƒlºDdã0cÌŽÔoPÖ_Ýnº³:Œƒn·R«ìî2¸Ü$,æ¶ÇÈú"©§.;`ïnðj¤üa°H§NÆZ]cÎÓ˜Í +Õ<¯6jhÏ$·€½±|Þº¢·ñt¡â§‡/àc<,ü'a–Y¾+–k¤°Oc[çþŒ“D¿ëSࣃ¾`9ûÔ£°'±2Ÿ³ˆî1ç¸/ŽÈÁ‰“pÇt½¤ß`¬ä<(~§n¿€+ÚÍUG«':¯¢³Ä0‚T4Ôm_“p*W•:^8(縌ãHž‚5fþ*e¯Œ—µüs‘A£ìŠMnŸì{ƒcªÏçÔΆbè ߬÷'™Ïþ”îô‚j‹ß óÀÒd‚³Ãvø´ýž!°š8UyqUM˜ç%¢G“‰GŒ˜©¶^jÒãÔíTHRŠå‡Æ*9µÜô¸Ì*Á–G@Öx¤bêÕ:[8^*–||€-ò\Z6zÈo¯å:雲õ>kyøUËbµ%Ü't«,k'¸XÉA,Ñ{Ýa×HR^ã8åÐŘ·>k‰ÁÁÒn¡»¡@l_y~&
FÕjsÆl ãœåãažÑÕÿƒ[•bÀó +Ý’WÆÈœ'Œº!™q
d4Tñæë[†àÜ#ÃÏuÕ½ÂÙ +S{Ð YçðAC—(ÊN!›Á:,¡+j&™âTV‹bXö*Ìt*Ò)¾÷@¢\~.‡/`oÍy +Tlmj¬êl˯ã¼\™bÊcÅ€jÉì +Z)VÇ( +¬fŠˆÜãWcTЈš¥€¿Ãê»…ƒÑŸœT®äò;î;¬ïè?7Lƒ’·Ýß¾kæ¨^¥˜Ÿ@ΠĸܮR²Ô~÷0ZOWÂø¾Œ¥˜è½„Çw¼`™â—yðW=˜ñî'P†^ë—ž%þÄàFˆw€ËÙ3™vÖý ´ÜlÛg©åŽ!€Ì»Ùxð[bßWÁ‡ÆîMå=,ðΤîÀÿè÷çV«]ùáQ-æaÔÐη˜Jt¤vµò‹»-Oc©Ó)O'C=ŠZÁýšÒÊhnй5ðŒá£ñuÜ1È® |c엱ќTŽsò/ïþ+èoadÍåÁÇð<o¡øäðQ·‚"•@ARÚΗ§)tîüÓƒòPT'¨²y0ÍK—ñáÖÀØ1¼oÖ øð0£Íƒ°‰ó8¥uAÉÃóÎØ8à‘^}M!éOE9òn5‰h\U-{úBb;Øþ,‹Æ_Ü*(÷}bpi÷àJûyÖù‡®UvzÖü”ÉY^°5·Ï
&zƒPeÉaìÉ
þÏ
£™Z½<ÌÇŽñêA6ÜÏ®j‡ƒ=KÄGe¶nú÷-¼[ïà#¸žìCénc_ü;±68s×ý”{BÆ~ß—µ‡1LSp¿ë³PŽê²ïw?š¥Œ ß»2¿Ë~7¯oß¼w}jKm”Úÿüç¾oBÿŽþ»¥f»Òï¿pݪ²Ûí²‘Þb3Ã]ÀJ“\`þ¬3å.à/ r†ÛƒrŒÃØÀ}5®À>xXž`úé)ôëápÕúî¯ô“ò³&+Žsâ™[±Üj÷ÄûâÖÀU¨bÐ`˜µ¶‚ê›£Ç +KœÆÙ‰™û/þÔÞÒ–¨ºš~{0œ¨¨qZ ›£ ¤*7??½Ò`¼U!Òá§dnŸþk,¦F•Á2jƒ~zçƒ|‰e]`=2Hy逆 æá íyWÂPúiœÂOÌísˆÃ<äÐã-çõ*/®®L¸:uzÀ6·àB!=¶R(§fЇ¯·¡B ƒÌì,Q²¯Õ'›þÂþÌúÆð#2ÈÿŸO¬ùÀ[öHŒS-‡úò6³ýö§ºŽ0[Œ§^0’ÌC
«¤€£¬ô´<Y`YA=K6CñR¸(–íÔt7ˆhƒ(³]”ØâyÕp*©ˆqnu£1R.èµPu·´9ºlâh˜\øU1R(Àð–ÿGx¹ãX¶ÛP47à9ÔúQŸø¤Ž{8±x³÷?¢¤s«¼î^—¢x(r3I“L¹s`0Zk +].‡rÌ’àÍ +¿JvÅPs[<^JzVòÕºLûúmG9vÓDßÌCšìyŸ4½,¥å31Lßs9p^b2ãòê]
~ðÀžo8BO +cÌY`+y]ÿ’—Ñ1Jc8e¤¹FÛ@OকçVåO™ŠsdHf/ØÐcÍCG;|\ù•RâL6ôˆ.·À3*"©JÎC´ßãÍHå‚æ&)L¥Fù=©™m0bØ›5>Ø"MzVnM_¤ÂÁtz‚õÁÓÍA¼~nX¼Þ‘NÓ“‰†§›ªbÖC-tO-eó±ßIZ·›:„û(z²áÿgîH4òd͹‰Špþ™ÀLRˆ³]„ª°¶ÑrÔypÎ*熄¦¤°ù +_E€Î:=ßp¹Å”Ì…¶–‡2ºò€{_ÆPT£ÑåVáò—ñè·PøÜ·øÁÃz{`‚ç}倵J…ªÛå¡O±tÅ‘ÿ +Ÿ·‡Mµ$™„´*{]4©ìZ ++PUžZ†>Zê¸4šn +chÅ=ßTÞYžjNácº¬·§NtñøQi¦¶ËaÌövËð÷m¼©`¶1tƒuÄòå"z缪œ²?Õ®ÂÇ<t’ضÝÀ÷¾ ˜Ž?V¥ë²ãÈ—}½˜lIûžiœ¦íÃ?Ù÷ë7È¿uA‚Ò‚~_÷/7h‘)|ÞǬÐ!úÓ¶YÚ¦ðûÞäc‡£’AßcÆ@\»`•>}z=À2%ã
cö—½x„¾lª®×Ö{B=ËàŠá£ñG·Wê#:ðËÞ1áKQ5÷·ÛÒr}ßc®‰€‡6X0ïp̺QK?òܸ´aÆ…øÇy€>w8«Ü0~›Èºþ26¸¹
íO1ÌœÒÇI`mV6{ˆe +ðæºñnnwXiíÇع;`”6ø˜3®"9 +tÁK™N>ê¸7²Cܸ»N2õƒrZfQQ†‹'…Ë.Q^Æ"º§xâfyCˆ³èœ¢xw@LásßAoãkȽ$þCXvÛû£’,´®ë€+V‚Þ¤sO™zsä²JÇ6 ©X1âê×
G¶m%™?©ƥÕL4O^õcÛ:T³{ÀßæD^d™¼Ñ½ß.)¾iü0[¥/ ¶ø¢´9Û’`›úÆlkE±åÇ—½mI±UïXR>öûVBº–LÞa»×Z’06»,‹xóØ—fÿüûßæƃ<¨ÌÃùGÈãë_|‘xîµ@h!;_ÿ^,vãbÌÂcXâˆ[r©Ô‚HÁ’©dá(1FÇà=fD‹Ñ«,ÿR‰{G-hZ£rµ +¤Ë +endobj +650 0 obj +<< +/Length 21786 +/Filter [/FlateDecode] +>> +stream +H‰„WM‹ ¹
½ìèK.TlÙ²ä@asœk†ÜB,ä–Í!ÿ>O–T媚IÝ4OêW²§£ŽB¿åq0ýÐÑú¡Ôéã럞øTmá\X‡|TfùÐÉì`£Ú>~L©4×*}9ËцãèZÔ£u[åžrŒÙúÃY*:¨µq¦"Ë(s¼O!‡–1‚¡´¹ô¨mŽG`zô1å} +=py¡—– íj©š1ðÑKo†Ïƒ¦ÌÃ<dòŒS4íêà Ãî¼p¥‘}®—£Ò“`G°ç5Ä»ÅÙo_»ƒq\€“G½®ár^øók×Ð+âªõu
À;S:Ñê h§H)ÑÅpá½KfõBŽÚÌo…çɱóý"騕Ÿ`ÅÑÊVD$ŽúºÉöLÉŒáÇGJ(>÷ì~Š¼öíÈC€_žOO‡È³Ž{;j™ý’\U¸‡q2\o±1ôƒUææ÷£S‹#ÏÞÒ9Á`ø’Î4ªlÎw¼³µñ")“e‹ù öŽ¶q†‘á—ò#U6†+OÚ1Šô÷Ío×ëwälHYG~Í–½ç{ÎèËüñô\àÉêî`o#Aüô7ƒ…*;.ãlŠÑV;žvêxÐâšZ‘7nj‰.ÎÕ.œÔæw~g˜G)ÔÎ;H£¸;iÜÏl”ÎuŒg`—ç3†ÁþôÃ(˜@…ï÷ +ðdÈÎ3ŽI%¥P¶ÄöáyåtFÓTÜÔç§h ü>Á7ðjì‚ùo?|úÓŸ~õ—ßþáç_þøÓ×_~ú×?ÿúó>~gØŸo¿ýoû½~AÕ¨qT5¢ûÇf £‹ÈûÚ{Lqj³`Šâ¶â89„c†rÉA49˼r‚*‘o¥ðz©äè\É-Œé²ÜY’3mHy‚ýh]‚„ñ£¸Á±Iu>1±ÌY˜¢îxº˜JK&ˆÂ]Ò½´ +®çdŒñq2@žu¶tŸRB¢{¸˜K…bˆi×UÃRgJpÁàÈ™§-@Ę TðKËo£ÔX^Ð
G¸6”’a¼§qR\–UžA1RÙä¾g`4z„kº8ð\ÒRwuß㪻õ +ló\ °ñÙì>9.¹ZíÞ õVzìhšã¢Îg¹8Ä„©';¤kg÷TÒ¹mÅfŽv3PD¯R®>mäöñeaò—+n +÷"ÁQê:º©‹îƒ˜»[z”=>©_"[§ö +×£°ŽcÕu¸Ç³€CWÚñ$:‰ç »s¨´[\‹U!·}rŠ)ÏšÄÖOX¥;SMw¦“˜$ÁNy“ëÎA(Îr†m¼feR¨eë‚æÞ%^¶’· ;ŠŒt¯ñVl‹]añ»…¡3-˜´áŽ¡73¶“¸©[n\%¯iÌ<7J0N¢Ú4@lcâ (Ê<‡–`ܵ5øÚ%ED…ƒ&æ£VP©^ö
M¦mãfá1Ò}b9Hß`1Å‘ +u0ûú$2^êz +Ê@Ú9`ñ)M¦53bìLñAüÕÎ(VG#9mìa´Â–î] »è6Ž˜)jsk
<óÜ+þföíᲕɗË}éäeY·k ÔŒ7H˜õx~JaÒ:+¶šåŽ¦««Ÿ„nóF¥(ײV2%À|Ö•Z£â°ôèÑ +ýktæ'a´½
+ß»lEí!?Yë@4ûNtº±:HÅIuf°t«üe©¡j +¯4«¶ñyªNgu[ÓN†oÏá +…º³UÔ³äª)ֽʫÉç–îÓ7™´á³v
€K žÈ]B²àDc\çè§Þ2„¼³°t]‚–Æ>C +¿ç½ãIðSEQ€ç‚Ô"kŒ-£ú‹ÓB.K +ºDxƒ§]ÍðçñAgÇ´“l/Jü9è„3ŽuVЮÓíÆå6ê‹£ +°CŒ~‡¾xXN †\‘nò8À®ÅsÓ*GËk|Ó:Üü¬ª§a3ï[ÉÁªà”ÖÜd,VÁíS|H÷üﬥÓÝêÕ¸ñî3Aæ±´b½>–<í±ïàžàœŠÊë[Þ:ät¬½¿Dö—¸ó§º +)~]¨î8ë«zÅ–ˆüOa¼
±&(8©‚ϧ¼Ah£÷º +X†g€]…¼hN£yk¬§í¥mñunÖ°ÌáO«!¹z8|6)¸Ö +ÐóŸÇÇf£Ÿ¡¥<ó8Töù+ˆNØ»×8ËvJ¯Ÿ|ùµ +v +>º…9h b%Û}ª†‹´ä÷xûVlš’0pTÔíÖ zH&ÿ¢_'f˺Óè—•³ŽHH’ǹbô±êJUÁµ%&j[>QöcˆQ
F¬.XéÝû±QªW;íZ©:cÆÉ3kqa.[p`™Û‰M‡k}Q€´ù' +pã]œã•LÚ4ÐÊ”¹jЕ¥¯L~Š°Ác¿Ÿ%-M7‹ß…PáÅà:ãìÖó þS}ìЊ‚e'$´¢‘lÜ|Úܨ:íOÉ“œã’w ¼„ÈäY›šbÞ{KMÔjZZ´”1¼}QÁLÕ«´Øðúcçqh&[g0^õmÞ v¨U£ßŽ;4·/YýûÍÝ 8ù$Éíf»rTåuf‚c½.=!OÜBrg˪íô1;ÇRöR#²˜H¤e¤ò¾ÞÒ^Ÿß@ß…²)ï÷XdÜ܃}Žƒ›$Ï!âÏ×Mî&)ífyv +¿åù Ↄœo‘ie°#O+3Ó,–ô¾óx,k–Ñ|?íðN4…¹2'£[ÕÑÃZ¤lÅÐÔQñÑšÇHÚ RÙÝzŸTÚâÿ{8h¯GPÅœyon Τ&öf3‹|½«4Fƾ0dA©ž ¶ž.æ1}ÐWñ-rºL2’9õô8»ÈŽøf.Y!°lñ<ÁââV1Ÿ‘Š‘Ï'ÝIÄæÀ½ëM¶.Hâ–lpƒ}$Ø8Á`€KŠìÀt<JFf™ÍOCÃ̧C¸ +®ôBç +2¤ÞËGXðñ5Ò‡Peò¸‘}m¹t/w!ÑÝñZd¦ +†-ÿg¼Ú®+¹aX+[AŽ(Š¢ÔÃ6o÷ú¾4škoNþ®Aš£A ÝÅävPË•¦4~ˆïU„.õHRé"+@tí: w›K^ÒÖ[dÍóÅžu¥_Òö€í-Óg—\ èÙßO4#>íl1Q +[³Ò[ã}Ñ•Œés%3O
<0
ªé§# +
-šXÃ(ÌÊUÂÝ[D`W2ø€1L©¡îæÉOŠÏ}uD
c¤•~Ò9áÔ@§lÉè€`˜,ÜMz8•’#Ûz(Ü"ÎHI©Á‘øÙ—¦3“©#A<¶ddÝ5 5“=YN:õVé{{ò=†Á«F[']£åìñªí“+jT›f¦7ø©Hh1‹°r¥ïÚàuÙ#çñ®áÍê‘Ö‚RÂWôgÙ‡ SÝ5Ïl2\5ÜÃyšhf:ýNpŸÂó˜U×FWÁ\Õ‰j/©»lÞqڜ֞Ý1ï è\íY%(=(Š×2ÊpIôÄ]#=›”¦p°Â4ªp+×ÑÐ/w
ðU¦ƒ´Ò¹N:ÑWÊï½4bOÆÜn©ûF¸¨
à”…õ©ˆ ‰ˆL>ǣÌùªÁѶökJ~rµ•àé!ˆª5Tºf—G‚,2ÖI×ÎÜÜú®ñDôIïç“ɦò×ÁtE¾ƒWSøÑöÿ‘=~ªûÇãPr^|Ô-Nõéi¿¶Ô}Ï|»ã¥í-"æ:="”ÃeØ,2W‰²˜ìMzm…g ŸÂvuGbêÙS‚BR’z»f€¯3¿QÝJ£^Z}å´@|lj¾¥½GzI{kÀà%°3;!rõ¡§þ&N—2v¨ûÙz’®2ìƒ>N”ÎCOàÌÆO¿àà}ƒ§FƒŸ‹›…ÄÙɦvGZ¹ÆAlš¢å›ó³CªIЧ0’ÓtÁí
¤¹pùÌSãŽh“ïéòC ¾NÃlnS~†)$ÈÐO,Ÿþg”ÊkUÓ +ÆL85,2By[Dg¥SKSº ùͼj,ÝifF£“¾Æ‡íñLÍ2I^5ˆs… +Ùœé:â^Ö_»Å”ˆ^HoØö5šs¿8ZCŒTÑõaaO¤KÈ`€´z‚ÕúŠŸ@¨±×y¸bôÔYm½m§müœb xwT¾k¬hDð(8ž@n- +ÇË5pÊໆŽéEêÚ[p·Ú N<boUkxÆø +‹°äÓ»Aü% êô¾'Öme™Í_jE>ÁÖû'˜…¿þw
NóÝòþPÂ÷™édTìéË4Fµ€Ðù¶^"³ˆI¨Oú°küõN{cf<;ö¤9#-\‡Ü<;èVŒÃ]r×f.Y±þJ·10;ÁLæˆpv¦6ÿ¥¾Jvë:Žèž +IxRrA’%`&Gô@š$zˆ °"ÓvVá`u±"©n6ÃF/Ž¦v>èÑ›ÂÀn!uR ¼ä¦‹=ÉìU}B0ÍLòŠxÏIài»¸L¦‹$1cQT·ohùÐÄÁ!Ë¥ëiꌢêa.àipÙaÙæ¾&Y‚4£´' ŠW@çÒ £d÷x¤©¹óJGô™Ýdž +ÈYÁ`\ +V~>†JcØ + äH¬ ^©æ1€ª+ãSA[¢‚±ô´
DaŽÎ·Y`¤®5¥<¥€â.dhöQ@æá£ëŠJ‚:²_iZ
oŸ²è³‘ü–øŒ§èúp€¤ÚR‰‚Ìr¤Ð(ä=æÒ¹YV^2~h«5<§Ø¼zqNÀˆ`¡ÂHøB'õY–4ÕkÜ´û”þÒïèG^½ž.ÿJ¿Úémk‘&ù½,›HhÖÎ’¿_Ÿ%y8^ðýþ½Ãñ…1YÌ©H\(ŠØQU¶zTâ³+üb;ú•öSŒ´Äz'MÖaibÅ®6Å+;Ö„ÃX..ˆ¹²O[ ~Ãv350(1ÆA7»™xâV$qºw4u‚Ø5°+ ÇN¶Ð‘jÑãf¾1pÑF8Ûö)ÇM@,»•JÕè°ƒDÛ$JÓnćpX³J¼wÓúÂdŸØZ±02aA*ÒÖ‰içž5 9‹J4k4<ô ,¡(è…)6ðzÐáCÚç]!7CK+–}(¶â-p´/‚‘Ħ,’, ¸ÀÀzcð¡(ÓqP1Kp;PÒ[„‚mÍXX‰m0RŒÀ´‘QDÛN]Âc„%¶8Y„°<V~s äŠUAÞ
¢ðâaEµãX8õx°F¯t¦©X™±°²‚^a‹h™è™³•èâjiS5 ÈbVÐ1`0ð:ÐÛó’tDæ>$©ÉOë+[QníX˜™‰~Ý?™gìR€¡Ç!‚£»õâ]ÐmÕšÜ+œt¤¨ÇMâN )IÁ\šbŒÊ¨
ïA˜ã¬]ºéJ úçÞS–ñ Ym4ÓmŒ§.1ÑiÛZ¹Æ«’OÕÁ +ÓŸ¦¿ýÝL7ç}{~&}Sš;u’ì8q´;íE˜d¹FÝRò®"ßÁ#k¨ÄèªðÁ+ew\(©ÆHׯAµ5¸M˜U^0Lª¼¾·YôLb¿Ó(&÷§4\Lîï”t
×
˜v|ÁËPü=Çô¡ÅÎPìÈ8n‹ùC¨Ø›3㦠úé£À½?ØÿgYã/¹5ŠñQ
œÁ¬x}4bÎ=:`ÐÜjêßé|ùc'cCõ¡D_õxžáµ¹êÅÒ½ü]ë›Îrõ‹&ÙI’ÝtÚ¦^Û¥¶¹z±•ôJÚ+‚Š9n5ÔRc¡;%¹[¼›Xâ2i/¶‚!Åw‹aãõÞoÿ:íÛÛO?=Ý?LG˜òÚ'·é¸CÓý¶çü×/öÛƒ{¡ãšx%1í\dU9q$¯2ÇJâE7ƒ2U+ð_K‡ûu«Øbyû`’"ë º_ƒÕ¸C(ëï«ñl=IpR:3q&â*ƒY#¨oÐμÁ/Ö8%¾Í¸Û +àõ£YøÛdמ…² +í³P% _eQ…ºUaÙ`øa~ òÏXJ®·f>Ü>¡[LõÁˆèÔÆc(–:s°Ø lØ1û»ù;çus~–ú³–}LSå7X…HöLBl0ÛçMu_ðßåàóò0@Œô4h~f£Õ³|¹GVØñ;½¼á‘øVÖF,²ºÄI塽Á‰b5J˜–‰SûPðˆéhÈé‡ký8;¬ÆIC¤™§ñB›ÉÜ{ +Q܇CVA_:x*øæ[ðzn‡‰–¨á0(ÛÁ8§Z‘Ån ‚RÖº´´IºzÜÑè°ÜPa8™+Õã\‰‚™ègðjÞãbF…Â
§T<‡µ&ˆ‘«¼—ƃO¦¨†ä•…“¬ÉUð”ä,J+Öêç6¶ñâukZX°Pü
j¥•ý‚èÓ‚zjüÛcg×H°ö¹Å«_ÿ‡ójY±ä8¢¿Òƒ=‹QfdäËà…ÔÞ\0ظñ¬„h°ä+Áxfá¿÷‰GfeUuM«`Ñu3"##Ί+ݾûÛ—ÏŸ~ù×Ãïøáûçç¯÷¿þúå'ùx¯l¡ÐŽl)B,cðÐ*ÅSëí_KÍÓ™Þ’:
†Ñ)oŠ™kóxMsD¯SgéÎoI
QçíÚÌÝg|)Á ¼š¹B…oÉÜßýn‰Ff} +ç˜Ë«™À¯–7dªYïŒÆS6¨ø%<bI¯¦N%WßriLu%o,hËš7y_‹‹Kzõ¥#AÁ¿%5Ä_ËÉn7ajË\¨¿šËÓ[ú'
Iœœd®hãì {¿óë3ž»þöÌ"'Á¨ž!¤1dÔÒ!¬ìÒ¯-VCñ
©E‰b‹¼¯-ÏF<ÆØü¸¾'@¥^~óNøýÿ—Sü_néϟŧ=þûë`•NFéÊáùË&0~¾4J6 °ÐUL&ÁÁ:;¡|TD%ŒM3ŠAPH²$†äÊ´ËÀà x”u…Œ¹¡©©”b؇X&¶¼Ÿ&gªmÿ±ì÷N`ÕÔZ™:™BK\§MŒ}¢ '@±P7ãeUFP¡¡Ss“ÕLºŒ S·@gŒû!2YY^ÁA‡2åñë…òÈ…]#íKKPúkdÉåÖÿ˜Ò°xð3\š*R¼D©m¨Œ¦—‚LÕÚK¨8B Oí¦“Šlŧh5Ôà‚¦» ™Áçakµ¶ÿÚyK׈éÌ€€2OØ⾟njªŠVq¢O%ˆùaU¤¨&eŠ;#³gÅ®Þ +·ïI•|)£!bU;ÂÐAä“$qŽ:D¸Óä¿m
Ú¡÷<˜¨«lnP +}x>Üè’Ì›…DzaŒ{ M‡ ®Aó-”%h5Èd–R÷ËXòPéPÇå ®ã3.J•ÚÇ@Õ¦ú W0ÇZCk#CÄÜâp˜ËÇ2O½îÒ‰&°C»'ov¬F*ëä¼ðÀ¬}˜A‹(Þ–÷ÏYÝ‚û–øV0¦1;û«áÕz£~ê^¹T#hÍžl +KÉLÞ:t?5‘€lh¿ÇºF…§ +Mé+‹Ž ŠÓÇtG0 rvDhÃ}.W2[õ¥ä•t1J5š³@8Ò©f\‘Z%*s”“ñêp!6B§Œ³é^Ò?‰Ž—]¸Ô(6»QNNZV€Ö±ŸÖ“ªAlKjy`®hBò8f²N®´®6™'Çvõ¢árÛlZÌäK6AgÕà&ß]Ì~ŸmuBû¥}<uðí„fî7¦0OÝwáÑ$˜RÍDVî»ÑZ•KÎ,~‹³’aaC;tŒ½JWÍÃWÏw³86£d>’Cªèñã,&WAû”aܪ¦G:(Ükç“n¸;È.Ãm‚4†¤£)•[‘ФÅÔFH¢!°g1ò¬*§Tʈ¿x°%A\í–©óš†þÓÎXèêÁ&•¸”aº¼JihØì²T ‘²µãÜÖOFxÔ8$—›ÜÛ°)AQ¯ 5κøXí–]-Å;©A¶úÅ>ÌáI`Is5âIKl-ÂΆªž +_m<¬&à¶Á³‚>]3ñT‡}ðn0pB?²ÉNPçc.P[-„8§©Oë ¨±X?å:œ¢ÝÄ‚§.<Nºª‹ŒÎ¹ïƒÆJ +I<ÃÏ «ÇŽ“QFüqœ +¿GÊØ ¦ïx +á¢Aƒ1BDB…Ä!±G£ü{ÖªKï>¶åè¸vŸÚÝUÕ«¾ÒŽZ¡L(³<ðÆ’LšÑ3²1Škh󉼟*â˼—tà^Ìš.—G +Ÿ[O:Ørœvùñ'h39t¿ø9ñ˜¶Ôìzƒ¥„–x€þZ›JS`–f3UΊX2G™_àƒ +2À»:¦Ÿ=¦aÐ}D»ãLVЄqËK¬ÁVBZW‡ãИ£äÝ`,åTñ¢¬Ê‘¬Êæ +œ4{‹)ØMr*—ú§O@DSN‰5!¨÷ÆËf`l5úHñ`ï?nÍ<‡¤U?í> È“" +dE€HÉ0ñªÆê0ª¼‹¬6Ò½ùD +7(eÚ¯’'”Æ’¼¿Vk˜LAªf,!n@Ëî0Ì;ø>r Ùa™ãx2áÂuB±þ™+\(ð$M‹÷mÔÛõ€ê=\FÜåQìRÔ²·ptyWïÙ;"šˆ¢xɺsN!¹Û
*[Õñ šLT¦wmHÇtIíɨ5c|ÝÜ1 +ËoãPÕ6éÄ6FŠCßX³q6]¤TD?T*жpáÅÈ,Œzú ”'{‚«Ý–ô¤Æ8Û>ŒÆ«Ÿý•Ÿß¼º»ÿí‡w÷noÞÞ}=~E[8¾ùöæþxñêÛŽw·×Ÿo¿Ü¼?þ÷Ÿ·Ÿ¯ŽëÛ÷W/_pÙ¯ùÁÿo~ÎOYÿüöþþêîæû/÷Ÿ>Ü\ýþöî‡Ïwo¿^Ý]þf/^oþ†?øŽþîæý_¯ÿuûÉ
¿¹ú÷‡7½øÃÝÕÕÍñêóçOW/Ÿ?Ç+~¼ùéù³/üò=?ôßG~öÇ›¯üëøö_Ø~:Êñ§ãïÿÇûçxö—çÏ;ÐH‹ŠqIEŠE£ù’¨5€IÉÆ9 À¡ñIo +&„9´ BÖv¯œ‰‰¼2WÙá³'I&3|fL]º°!BTÍšb–RƆmoƒ8²0‘|8JŽ)%lí÷#®ÀÌQf.V†ÖM!Önì…$j{)Û7Nva*ˆBãÇÛy/Œ¼Y W÷pÚ)´c-NÂn‰óˆö‹Ç‰@þè¹è¹ +ÆCfF^9âüE}‘ù±"1ßé¹Afº²:ÁbqBã#Ϲé‹G^‹¡5"î2¸[è²(°tês‘z˜›‡an)ò¡Æ‡Æ¼ïa³·óuŽî<¶<ø»ØÎàSPÝÞ×bò¤œ‚ž<8i3n§`/’HÒ>ô6=ò…±?\iÐýûâìÐ-l„÷pi\%ôxñHkñ4Ë;ßuF’Ðf{ˆV'4Fß0»‰m¬‡µrÏÅhëÈñ<Eë¾aŽ”æ¶ußbº¢õUs*"Nî¶1_™4kvŠl¯c¥ù‘{]…ºŠ„#‚Ëž‹¾WµÕéÜÜ2Lɵ[øÚ³¹6»ye“7ËÝràZÆÝú/È–o}Ëéôðènª ‡JüG¯òI>‘ê—™ãÚïpÌ:¾â4#©þ‡’ŽžPWp¸l¢Ô0Žˆw¾s݉¡îdŒ½‡»SIÊèˆK±Ù¥*&àBúQG#Ç¡Y +Y€V¦Ð¢^ˆìMùè1.Àß CnØïZ/äU¦,µ²&9/“.!_e“ÛŒVNSg#Twšj•êØÆ÷ +‰¢Ã!Êc%])¤¤v†Wu¬uvÖm‰Š¸a€Äu²°’·—Ë\‚# ªZpé–ñB®¤52YrÕK]BÉîGÓ©džÕµc~"?ªF«Ú1ÀÁ9Yá+>êÑ/~VøÎÀÛkL“¶Ö.í:ÄáGB´¥$¯#drB²Êcp“LpÅYš#е)‘ ͺfŽTNÛ’ +èbºXÊÜAÍ'r~˜¶Î2u¨ÌSr{L!§Ø}$Œr(œ˜Ü¦)9ò[”©0F¹PÈisQr¨::±PU6QÌb€‹c5Ù+îŽ\¨E/“ıB«Z•l˜¨=å㩸Ìà+³Ö³K
—¢b%+¨ÏÙV¶ÐüºÚQ&³N+f”XÔ¤"RCd©!OEÿØÛj´¸ÈMóÈ{¾ÀõJT-h¼¹Ð +QVç«"¡Ðã²Ó¶0j1¡Œá‹ku·Ñy:®0 È°éöj‡hÆà3›StR_º,×±©°³ùï¸"Qc͇YSÔ•…°µ†&©€’!üÕ"™FNúºTbòË Áñ•–Šªoû†Ô¨æ¬Élf8æ’5íÁ±r -EÁª“ºz`“ž5hwA•!Ze³G'uö”îº&±Þ&Pnz©A¹ÔÍøÚ‹½çì<V?5’J!×ãÉk±-hqÝœ7–ê['[Žq¹“†Êœ"Ð8KÕÈÆÀ4£
ÜN§_ÄxJŒ!m( +h#(Êu^ûï¹6Žbì]ÈÕùPïÊã$¬ü "P4IwP”ëÚ¡:ÏdÐÑZ5á¡ÚÆJ0\€ƒ%ÎZâ ª³Ë‘Y¶A•ž»›’K¸u5DQÌjŒg³e¯ÝªahÞÛUiY"Œ¤å§ë·eÖµ‡®%ö5R5å` ëÞD+~—’^´5n5¢ êÅnQ75:n?äcHRÈÆÔï1FKšÀB²á©D¹Ï@2WÀó€d2fõµMJ©«ýÿt—[oÙ…ß‘üú &£°ï—åK‘Í(ä‚EÈ1p6:1ùßgUªîC¦-Б]g»z÷ÞU«¾áîìCik]Ë`.N «ÐeR]Ø®©QÍtœ*£2ˆÁ59K!¦m»hÐj&©ÔÆ#RVÖóÄb7Û†Zý„Àõÿö€)]T±áÙúpÜdUâpôÄ,.§äÁs(™¤€ FI% +ÙWó€\™
clµx=·Š¸81ë¬dDâP5eŽßkJ$Å e2$¾érSþPÊJ¥`DÚ3m~±zfS™C[M¢Ú¤9ÌÍ¥@‹njZuSÁQšV¼g6û¡9D<Š¦ˆ×€ûjjÕÞ·á‚#ÉQ§¢M¸ž^ uµ+‡ø0$7[Š±g:̧ćÅòµv +x~eÎþ8uÜN1LæTÑ3Z„S†i]V
‰æíô0V׶š>áV6ïS™(â²£¿-nÞ::b˜¤n8©c”C¶U¿5€Öè¹ÊË1^âv´µE_Œ²‡Mì¡øâ’áÉìzÒå[¸õ{à,T£å÷€#IÓ‚Udåt}åª;RS)viP5ó(¸ƒ´eN%*àMmË ÓŒOëð_¡%ÎúÀès¬cÅUÍRóAˆñ7Ô‚¿¸ÀËoBO£aôplªq¶Vb<u·i0Awž:†‚q ÔŠ½ N¬ªÑ˜´‰.HÕŠm9¤¾ž‘Lz=hl¹žþìngå=Fò-£ +Q–ÙâÉ]£Q8Îh&ïu“¹šx«½á +•k:M±—]k¦ÜF–¥wÁã*kA+ey0ýà2*ªOKâî +ßP$W´G“èйua(%î¤pŒ ÎJP—$§!wêJ¦{e¼šì!8¢ñ_’Þ³ û_‚ýÿœOK—§IåØ{„®¼*¥¢í7tUÍ sUôÜ«7í† ™ç*d"ÄJ–x?¨·¿Hh~8ý¼ü͵Þ^P†`·=ƒx†²¡°>ÞiÓä8uÄ…¼½5 @®{ÎkÄê|¢[I öýU`}0@sC%Ö‰éZ ¨àÜ:‘¡‡â»ˆVR^HÑ{àbL°,0*AôÚjÄ`<ÌÊà ×Ù‚m(¢Xæ`ð#Û2‹#¨Ò36Î:’vŸ[¦!Nx]ãô§:O4Ðï9¡éÓ:¹åÕê´ä4GN™T5}ÖàJU3¤üÕbÉ0‚J®DOÈxõ‰iÄlœÚ”Š%Cme]¼)Ù‰B"SÑa%
& µtƒù0·`훳Ë嫸¹À'âkwŸ¢ä-nLŸUµø%¦š°ú/Ö³ÄQòÓh¹`ö²b¢hŒ%¼ê”ed×îëVŒÇ[(Ųfgãù@“B¡ÅiÙõE'\ºN;»;<vÎгüéäÁ÷Ožn^\žß\^_î–ï$öèÙãåÉë›ÃåÕûåÑëgŸ/ž}¼¸z÷ãÙ͇—//ßêš?Þ»æ/wŸ/lÕ÷òqòàT—KŽ7ƒç
ô²‚†mɯq"ãzdÚëoJ¤|N&¹÷2¹üÄ-ÿËEŠÃû §@ìúW™ß¸D”€|üwé‡i0z“5§ð}ö‡9ºd«ÿÃ)8RŸ³t¹jC†~Û%¶!cÏl#¼èzüÐÇ!¡ÂÌŸá‹
rÓí׬ÂΑ‰+Þ“
"ܳ÷æOŒÒ1Éÿ§ +xÃuÒ"|]"Éú,€íl‹ñ¦î|„;½Sk:2¨ XÒˆÖpsm•ƒø«Á€¤Ã¯ýÃjâøépçÁ æ'ÔQp™;$ÀíS%¬V HDÖF¡å¸EÏ£ÉÑÃÉÒ>>ƒ, žü³Öíªôˆ,½€º½êx1\¿/ìí¾!çƒo…ÍoÓÃÅt9µ‘v¨‡owíCÝ—ˆÖÕ3·„‡/ýY¯;E—÷[´ñ]…,Š¼Ìµz㈠9©3˜¡˜“rY»·—x2Ï&‚Dt¿
qk0ÏÙ\£ëI¨áÿþ¯ ¢eС~É‹¸/;›O2bIsį[ûnÁ ¼iòço}ŒÑ¼º3ÏOx»MÑ,oM’8t1½í~‰Â!´§ßü+ø½^ªQ|<£¤¹´¨iö!Nû¬óðº@›¡ +™bGwªO•Ça ÆeÓ¶RM¤y0æë w«Ôkëê¸À(
áØÁÓ")8ÅÏL‹÷¹§fZ¢¯ž½ùÖÖ—8žxt†Ð3æÈJø’àèôgl’<n¯õ£p~É· +›ê¹Øp÷]cüäÀOqõ^³ÝjW5ýF>+LsÐQÊš{¢T«a¬ùã ã…]'Ò@÷)Î^Tâh*½Cèå9‡C8âÓ˜<b,yzlˆŒ®«‚âoe¹s1™fF@[mL¢¸&ÅYŒÒ‡œ3sÇ™o[üuhŒûÄ;K,‡Œù"(è9ux°MxÀ€'„˜àCÀO}gPó„uàýÿàj¿åaí–8Æ·øSà œ?¨ïÕ«}{¸’Ic¡èÉo“˜£âÕó°!pñ½Õ³Ü0>Æ4ÿˆùÀ)žñõ–ç”Þ[õ¤Ïl´Xö¾®\¼qøõ쯞 ï=5P¶Ž<³ø!æáKÌFKÑÞl[tgéZÁ +®À›ù(ÄŒ¯ñ@ãÜÏŽ1æÍ‚Ì|ͽMâ¹öÄû-¾{Àõie„ÔW€ÛDò’hý‰Ÿz +ÝGéò«N‡xrÝjÆŸ)ñx¹qèâƒñNÍxÙ³ !öÛ?¢=Ñí³^곎œ
’"ÔËHÓÌSo:á-2¤®ä…QŠù–)/Žf½DEƒýM‡Ð——®&:2À8²Oçkâ¯7§@ˆŸØAºŠKCQüQô‘¦2‚Ô˜’¤Ø~†N*N¡b–-DÐüÍ=Ü0¾))¶¥€ûN^Yè}C-ÖyxÇîOô'>§™w0#´–4t]2¿1áTKYÙ¿5ݶ›j©OÚ*];iXT.C±60¬{arèë–ÎùˆJ[ĵìΨ‹Ðë}ê®ÆA‰rЋ+œBí°`î6挙ýùrŠ÷eÐZ8ÇÑ9i)›½7›Oô'âæ~‹³³º©£0kÃ'»2rïOd-¥¸ÆWšôãåÇ° ãÐ3…Dü\õu#uüV0°>5cÇ%ëÇ«äX¿ ;EŽóKë× òzR'dÇÄoK#VÐ"îR¼¸¿ÚÂ. Âè=ÊŽakË–³ÊR¼ö%þTj›Õ^ºÉ¿.ûšÇ +Mž¡ â¶Så—²õÈIÄŠs‹Ã +¶?âxâ)ËNÝÏ—Î hšÜVÉâ~⬢ƿ楉¡ÁsfÄð>Ø«•¯0¼F¿[b›2 +Ì.Ú©¡¥S’nçŽ_l¥…ŸRûPpèwÖ·=¹á,c^ºà“¯%.3D;Ê5Ù%ÚãjÝÚPõvµ€Ft¼Éüüü•7·*G»ÿÙU5ž~½½}Âmâeðãÿý÷¿ ;H¡&X‡ÿô(®¿jù6rrEeÇ
ü]º€ˆ™µêÔÄù¨ÏÆ×HÑÄ4™ttorUê +~XÖÒkL¯M16öÎ=YÙhÈ4,>'j|Qœ{rãµübYk… hußj8𚎚)Ñ“^ Ú~å¹/£uŽ
„;»þÓ8mŸ\U·ð[·g§>n}žÚB©ÄÈ-{åÔmk¿ÆÐxm—5Ų¤i‹Ç3DQŸwˆ¨öôÆ¡ŒÇž:ÙXV+ŒÉû‚ø¼-d¶VÈýnUà3oaT–ì–±"N;€ìž„¡@öŽç%*kd˜R¦Tdª¬7PCn]»MJLÑöx¢O‰é‡¿u†Ý˜$on¼dCS|— G£œûæ@eÜÐû¬â?̲ &{ÏÛIù-|ô·VØ–× +endobj +651 0 obj +<< +/Length 23529 +/Filter [/FlateDecode] +>> +stream +H‰\W;Ž%9ô˜;Ô&!‰¢HÙíŽ=@Û¬³X·Ï¿A2˜¯w½î(=&¿Á |ýewÝGýÜ/?ûv\&ú’õõãÏ? +ßw.â"ñV°•Øy\—6¨g´1¿¾Ó@àc ñ«ùxésìx€öÜeÖàòKP½= +P~·àzwácjÆ0‘rÛ™Fük¬ªÃxd,+_oX`ë9ó¬Šmž=°›f`û¹W”ÇÝÒ‚ (ÉÎÂÿá>žsæ,˲<L¤zÊ,‡'bÿ§â@ ‡z7¬2qPBõ2ýõ³ßçK<Ê=.
QµÐg ûñA¾ÇPôŠÓ²œ-å²Ï+_¿úñ:åJ[¹˜û1Ó]àõnÃû™²Ó¤Pn¥^S¹î‰¦£ay„ ]ϼGø
¼™¸úg?ž's\Ö®dd¹â˜ÏºBlÏ=C‡VMQöÝ”¸Œ™1„CtÁÎÔ×Û!‹èeùÐÿkÖÇSÑ!³È¶6´::àYâZßÃï1¥,ȱβ¸¶Òé½=£ÃïÌŽ¯®^<9¨3Eéœßó˜TêÆSØ#›Bï@
m››ó±ûòŽš©·{¯ÍŒžs¾ +·|‚̺[üî·Ø°‘%ñk •ÁÊ‘/PÍXÐ~©É)|°
eˆññ‡U®iw¥¨/1¦UéÀåØ*ü€s¿*i÷ÖçÌ8OƒóÞ¯îž^”i€Bš^užzÉ ;Æ•à°MPä!7ªÎʮÀJpƒYÅø1Àu³·\eÀ=¶ +®°Ip—8¨s¾;Æz¨÷ÃÇ)WÓì*í¤¾RãfSÛ/yT¸–"•T»`’*é,À$?^I%›þö×âm’#R³[´¢ë±‘Ä'Éñ‚¦Ö>=%ëgA‹LZ Á~hf8S¾¾Ç¦4í|Å%BQˆ$ÏêÎU-K‚sÝ™qq¥=·gÄSÄ–Ìéh€ÏÔã‰W¦Ö‘’SžFPi"Wý눑ßÛÝâ)Ûõz9fú`HŸ÷à)"—RLjÃÌIÐkn +f
QûÝ ú˜áHâ‰Hê°IŠ$¸§7Ø’)qmÖ·±&û0òívv@k¯XÈŠšÕ°q±MìêÁò(¯ÉS°$è}z_£k¬¥Mòš\ÞŸó“ÕmoÚÀê3'Zº¾uðóÁ™Î¾ ¹•A²É³—ëgkì÷LœIÛ8' +úL0ô°¡õ³¡'çžOúnôœY‰
€Z +5öÓhõ}EZ'w’êq6auX¥XÎlÕ+®Ÿ×ÀÖ<œbÿ—øí]3Š(êa]àb;ÿëÏ? —Þ¢Þr
±Ÿÿ¢ùw!×úBöû +ß]êiï + wFÊvŒÉ|€«Á˜¯ï£Æî¼; +U‹øp‚"µ +-8Ö•QÜm€ë oR7ÔM©[I³>j.dÖÁÔ×ßMíËò:ƒ…w¥‡Ó.½UhÊe5s‹¼{¾—úg¸NŸ‡£È=ÀÒ5 ®ß”BÎcâ86/×aŠºj’Ý1–}}F;N‰R!+o=FÄçä>Å
Ë¥…Cs+è7¹”=:v‰À¿‚'!/Ò$NªÖ~„)X1µÿ´Té¾Á" ù@U"©Ü£ó.ÄôÏÞçДi +Çî+aÜÛYE2~¶å#FõP‹NÊøŒŸ~ýz“ÕPPR·äªÔÀ€¡iLrs!¦è¯…6#¹eGœV²Úò†f͵ +qÈäïZZ9$=QYyY®óê[”OçÉ®r\ÙŽcéи¢æÁ~®ì<ûr¹\¿"2"«›ø ÏÎÊÊŒ!âìáá“]› oR|‚“—ÁS_f\iH ef‡?<:gÍ>`yÍ뾬M+IAýæƒf{Žã§X7Ôš¤¿úÍ6Àô)‚ +žœÏŠ)½5ÇÉv -Ñfº`¬–fâÕå?ë#Q‚ó´'¾Å|Ķ>|e?86¸\ ÞkÄÎÏ-ÚiÑI¨AÑ%°äîúPCŠÇÛ·÷˜pV¸áòòåz ͉¦:›§Áºù~oy¶ä~~e…@èy$xêð áÞ[ž¦7Þ’13ÎUpÙ/xpý ÒDxŠþɵÜîÂ÷ëÆȶWà\J#oPA +ŒkhoJANÜ=OWßs³¯ãäùØ<ߌFÒT”š5¸“eÉ]…Wbû~J¨rÜÄ>ù¾D&Ýysš¢ +ü¿‰›¾¹‘íÞlߎKÙ"‚öS +δyêÚ¡†½²AqŠèÔH›ÖK}_ë¶o™S9)Ê`ë}LÏ4$ë; +#Æ›Èx™ˆø/ÝS¿†khWºÐDng
ºŠøþFrèTðÔ4xzxk€ñ>?y +õº´Éøð;íöî1…ø8Úò¯4ò§Þ.|¶2íú箄Âÿ•gÉSÊ!p™Èû½ føŽ<¤ËÓà-#^QþQý{@ÏÖÐ>£Í*/ú+rÞ“ +øùq§±Ð‹†‹·[bœûðR'͸Š±±å(s3öõë|h»¸šWx™êN]ž{¼ap×ê‘EÉ´B +ôBY¥Aà8 î5½º_›x-æŠÎc ‚ûwH?¢sv¹¾Æã1àæRàÕñ`Ó',;‚mùË4×€eºÄˆ%#€9P°0ù8íÍ5™í¤"”ö¥¸ez$‡r¡Ù¸g
¡þ¢Anø¯w½pÒ£jÈà‰`û.E2?]áOÞæ§ßŽ6äœöäÃ.˜ ²kªRŸyl»ÍÄå9c#¾ë=°"n•Ì¸»)Øîë‘q0¡kr¥õ*“Vê‰ È¨”ú~¼'x÷øKÔƒ£/“ö+ùbí,ë|ÖÉÚEc[wî?rmlŸÀÉÇøµ'$ƒ‰
Á£
ÿL°Ç|¼ë~Æ9†8ð9dVv3Lÿ(£çŒ—Øs€û89æÅ™r•ð¸³³úÛƒN&x2J|/äJ7E¼¯|òLóyU¾w=NCšõiC„ˆÀAl×7ž+–‡J—ðlW»˜¬\ÃFƒ{†¯³ó)Âê +ø•I¯ÁH·SD,„þܯÙ5X×ÊÁ_¼î*ÿ£»ZZõ:Žà^ ÿðmÄñ¼ÉÊ‘7!‚œ‡ï*YŽ…lÅå‚þ}ª»ºæ|Bd¡WinŸžžî®ª8%î5+…ËÙgÕw¥³êÖc(§tðLR:u=ÛŸD^4áÜw6vpš‰gR
Àp*–WÎ^È‚Ô{Z:¹ÕÑÅx£ÆP¹î$ˆ=T¢w,ÀÙEïcŸ±,®©Fó5|±¢ó,2/³xéß]ðãÓ'ɺ)߈ +ƒR›h?ÏïT§½Ø•1~!Þ¹H¯“4WÚ™ÝÊ(°ÄÉœÎR2œv +x2°ñ† +÷XÊII¡÷LäL³EóÏ9ëEsifÙÚ$Úûp¬v^½Úg‡±<!˜ËÕ{oŠ9×8|DÓBÑš*Ù—"̹Ôywú\¦ps#/…ÇÛ!r*C:Bò$±Ïþ!ÉpMwÙ’m)åî¿=èì^Uó¢Ý· –s¾×æZÄÛj\ÂE£áóýxg
¿cè![Œ`âAJý#“z´Ûw0°àF:ìrø"WHIdîÂfüDžF×»Á—|«!ÂL4;¦»*»{‘^Ž +ÞN0ÑY ýiÖ¹Ÿø¥ðF‡—ì…‹„/äøÛ©pðøNЗ¤ƒ+£ÒÙk÷ø¢‹Â¯\þ‡EK&aªÎ^N(îÁS/åXÆæ{#®¤^ø¨Ã½°u?;¬¹6• +×ô Ã¥¸`îN`{ôÞ9‘óùbñ*÷Ÿ•ì°Q
¦ÏMpš7ÃíJµN¶p"åÔا‹²J±zÑáÏËå¡ÖM·çU’s ðÙ”®ï:Çv¬cÓ0³ÄÃ%“…×ÖŒv«92Èô¨l‰ZXÇ$7e-‘öY"‰ÌÇÞLñBXCó‘Ì],1I›¢áµ"ÄÖ³;Z›gép ¦1phÊËîûÊ!e‘JÝôŠ¶Ó¶8̈ >uø¶J¯ÛÏ5Š‰Ðú\N‡'êðZŽµ
_\·H±óÀúÜ^K;ºE?!l[¡´Zoù®ITÑGQPÍ!hzðZæ‚"f<)ä#šMrŸg˜®&Mb {½ÓùX$ü¡8¶™JZûó@cKÏÝU Ë’.F7â·šƒ¥ ¾«hõN)Ú<¬z÷Xèä²vè]j?Æ„±Ýô +³M
ò#䥰{è½^<9Ør±2ö’YاEô<N±è%°|”ð™Ô‘&Í ¶1a¥c… Š¶í«²~·tºÁó°¹&å0â-lhÀ¥'‡Û°]þ’¯Œ8œgÕÕà×Z€wþxWhMØO}»H`¡¦yç CÇŽåÓCA5öß…¤,P:hÀL?ñ%Ó&Oô«Å¤yŒÚß d@C¬Ç,GÙ>²—¡€¢úR„”+‹¤„ƒYƒ]l]²¬¼”ªÅ£VÃe8ˆÿ/”ƒhÈCvÅ>[bªZl€àaµØf›¸D(NùˆðãÓ'¨M¾A•›ýx§ÚHÜoî/,ú悦ŽŽØxË5{ˆ2ߟ4ó.CÜ
”ŽÖøùˆ„>"ˆ›š`†VB—¹Py<¸ðùït¶Ñ c”«{&5±ƒ»•Ã¥0K<?Vï7=ݪÚiÌKe6E°÷}mÚn–8Üì#!™é¾¬Ëü›}ý͇߾}ýñíÞ¿úðéö{Þ}óüöõw?¼}ÿïÛ³ï~zõë›?þüæý}õñ§?}ûüö[?óçÿ{æoŸ~}§þ`¿=}òw<¨=¤ÿúþ“ýþðôɸ={~ûþŸOŸü×°¿ø`÷ú¹wú¡?ô–º=Éßêàóc—bM¯Î‘Y°%·—ÄaìŠF,ÒƒPgÌ—[¾h2{5bšÂV +|è1Vsú\Ó¨»öç *0ÊÕÜéTÓ ›ìé±éËŠ©z<i ÝZUÒû‘HOÞ]Á‘³³6§Ê3~˜x":wÜ@Ùòÿè®–UM*:äÎDHÀ˜ºíºèÈœLDB‚Ð#iŽiÔ64M oïÚ{]ÿ‰à$tß©¿j_Ö…‡8…fÀ¹•ùˆ`¡ÎúRö; +‹ú·Xr| +ÃN”§?«c¡VI;yršë\OïÎþ±øgÙ¡0Tc¨[¯÷d¨-Ö~³–.'HÎ$(âMÞBûâ>~ïh’ƒ +!0Hˆ{+/cJD`<žXË2ƒê¦&ÞökCáÂÛ:]u“"xêõ%¸5ÞŸ›K`5WÎãééw°œó‡2V¤0Ò áÈÆøçîd¥žO'‰)è|£Þs¬Qo~JR0xB²Š‘mü÷ƒÐ½ ¾vo’Ç$ +<ãZ5ùêÑpÑ}aéײ4¨=½åaî¨.O;ÇÄÚ,ºó¤f†%£¾VE"ù7KE‡îãäÇAÇ4u¶¯ÅŦiÞ= +¶!ÿVÚÐC‚c®«–^Tï€}«Ö»˜c?ì"ææ·Ý·GžŽäåÎËÚzØ·âN:d~‹p죓þÁñ. +Ÿ©YwÉa 2A[Îåßv2cQò ©ë¥Ë´ÎS3èìŠÊÙ˜Öpgù·ž¾‡Ôg¸O8{_ì¤}s^Hœt°-[²1žùƒ39#Ê-97#Ø<äècܵgâvóö
£ßM&T@/]{…ÈtQ¤r€?ÕP£VŽËq¡nC&Wv Dànãžì±L¯ƒxÐUÍ= +«(èà9pg|ú%~ÈgÜ‘…WpÇç† 0ô…Æ“KŸ•8ì6³d~<ðdµ¢·õØé„õ|ýû"Ø° ìݱQ_q©Àyd$¹¡ù¥íiFØñb]ß–¬:@if "V©)ƒxË×}ÄÚ‰L
Fœ©¼â6t¨´ä¶jRÛ2>à
FÞp§H`Ëžó +ª6ãÞA´x’½ÛZ7’ +æ<ÛvÍVÌƨ{É’È9§7=âôeŒU ðä88Þb %Oè|EoÏšmu¸8§¿ä u55s
ª…G»ìZãN8ä› ðîDàóâ‡C‚DÞýòS€]:ÁG ¬ +FºU×ýtÝus–ù<Sª7Ø ƒ%>XtCðÝS?‘F ´¢¿vÃŽÓÎÉ1ÌÅZÌzHZã¤Oñz9‹ðÿ%»Úu,»aX ÿp Ëo×Ón`ê¤ ¶ï%R>“¤ÛåøúÈzP$ph=Y*Ôé*¡œynŠßÀGýùuáz®'›±;YXvM#ó÷qÅypÁ_¿ÿV^øVÇ Ù«¼þÎ;W÷§ü¯ÄÇj•xíé ˜¯‹Z"Á)1FGÉr|'.Û‡[Û +yEtFÍÇÆt8ž·võòäžb ƒÍBz¡)@»CŽ–v"Q½¯6 +n"»Öq¿ãYopÍ xóQh™„oèOÂ;“`”dŽûÞNáä1¸RØ›bðÒß,ÖYï”cì;בß:·°^øoä«:üéÜ“¬pxÕ¡‡
H¾ÁEÛÉ,8Îyr¼è¤fuIùt²}ÜoÁðXÊ~ÀP“p®uÔ7¥ÏJÝ96všÀ•4î094`ÚÒª@(—Ž.ÐëüÎ'H`N]‹BfÌöM‚ƒ£$8ËxF?íãõŽ¾EØ¢WP+PÜœí°ák‡žªª·®Îë\Ô¸¶¨:r$B:¶§5QŽ_²K鬃ƒ¤$gÉeæ8‡ã›fmr4â +),(°ßeø,½¡‚Gu¶p#ôÛ^Iã}ð‚ ‰wî>\LqÃB—Åa$jMyÖe„N•Õ[w
+I\Û·2K¼îM# «4½r¬ +Ç% ®ÖtC]T=.¥£wƒË¶km!·ts°8VhÀZ![ªòÈs¿B +åæ|\‰¿\–1€È’vÏUÀ˵t?®-f+Á›ÒÁú¼ÂGîHæÙ Ü.9†ôkÌf¾7ÚÆ•®Çp_Qwlkë”Å ¦X8bÉÖšN†pûȉL
í.T3}²KaÂ{²ì³GK„‰O1Û¢°™ÕÂ1&¥. +\‚Ó¥A— ·šŽKI€W¬3ùµ‡ + `!·<m}ËCŒÍì\¬'“Æ +«˜aÎýïT8±Éˆ3ç.‘¸pé˜ÔÆ*V€ëö‚ãs(ÜüØÌEÓ°–ÏJ‘q>úcê·gîì]€29Mtš
µä¢ +!\,ÁÂ-áŸÂ¬e…]@\D†ÃõNá°ÕQºýÈRhV!¥1ÉMmViu\ËËÿõ#¼“k
x({ðÊU•T ®*Ÿ³þ•‡vFöëÉ=˜ÁÍÒóŠÂÖSTÆÓP6Ò:D‰Ø#¢BƒÒ¨Ô8 +Iå9S%§÷ÂRºÏן)QžÆIæjdòh=Tûõ™{¼·`"DÙÓa{BFþ?óf sU‰N²ÀŠü~]¡T«žb:¥óƒ&HŸCÃIFâ0Á–°CßT‚ÅMãW>ÉæÛóêDk›7ƒ!£¿6'ÄpQ»»¸ lg€Ûþ§½—ßP¯íèÚP'Ÿ©ôµzq¸CÒ©€°§°à Å%µQµ `÷ó@ÐbçBôl¨PÈ/žT1æOåàÁ4I¥š“wÏðÜL݇7÷”?Óîë›ÝiF<pÈx]^K³tí¦Ônþ÷ºîf£¤aí½ä]Ê~åáU9Àº„7·Ívn—÷]î>ìxG¸Ê
›NÝUjëz#h\íï|[’Sc÷9ÝSÿ¡ºÚu,»q`¾ÀþÃ|!‰zÆ:6ÐqNŒMçû·(VQ×ÙLµ®ŽD‘õ°¥×@<äÑbïùÒ&ƒÖçá‘~à®L_GžÃÁU2-ù +À^»Þæ°Ã\ïûäÚÆ'Ãã†"Ÿp‚ûÕ‘éÍa˜8ÄÊ\
¤&ûj±¹¨†ó4&À“XrF +Œˆæà~Rk@OF7£U
=b `\ÕN(=4DG(‚XÇHØ`ŸÃ¡¼ÿ"6Æ'µäX;NÞv×mú\çÃ#Z̺ƒÑüzqó¶ŠðմÛ”«Dç`ò0øÌ>ƒÓœAæí0êíRp<r§Ù?{œJßòM¨Spôä#ìMqr¥ŒÚ8U‡›9†7åø½»¶Ãìä«Šá?uàå +Ý-)è‹nXúë:É‘t<ºùJÄšLy(;JtÀ1òÂ~2Hš”@!FnäXï&læ €ÛÂù±®˜M5¦¬]àÊWĘÌA<xtæƒcHØÆ‘ rðx c/vøÍÙyqÀ™“ÛûÐ € Æ«ç–| Wv#¨¼ä+WJõ€ža(]5¦×»ïæ"Q`åZù’^šÜAŒ¶-%>×dʱøry€ÕÔ]wÜ°ÃßÿýOù…2ŒWýU~ýó.m䤅Ìö?á$mØ’ƒŒ¤’µëßš`ØQÛ©¯d2WÀËX\*™tAë$Ã{¬fÉp†ÏŽÈƒ3œbšXx€LŠîÎÜsƒ¯iú•M½I"ÀôÍù§«æ³»±\4ˆ½Ÿ(9|{e +AÀ™5.NãÜ0—•G»3ñ-—ÝæáâûPÊ¿×]î‘{68Àþð‚è—ßZ¬#÷Ž +A%€–ªr}án!wZ’'|Eê°ƒýگ˒ãmKh
½ÇÆõصõ“Ï{*m’eRq<zø&{½LR2zÓŒp±N›))K'ílºŽ">Dup08\&íK|q
üõ„õS+“ HMóoûÎ?œõeûàòÉÅ[U] `C€øn8ÃE·pïÙËsMî°m©\»—ºQ‰àá-~ÂvÈÖþèfEèNv«ýhñ‰4â`½´âiäc"(Ïñ¦µŠ=ͪ¿÷nÜà¾|–#wCDJ;¼ñõ³©8PVãa²¡Ž€!ØKùËÙˆUW›°-Öª9Ô +ïÚø +ÏK.²L¯YÐ#üÖ®mlæ¬#? —‘³Ryaɧ»8·•úV_éîFô4pÙ§ô!þ–ä,§å+ÖÀnÔû‘¿´j¼Ãl¡Ëð—ÒÏIñ.$ȇ +GÊ¿gl÷ysI£é¾~¼^{<SÑQA4 +®¤¹ÀC¸D0ëÐz†¿WÜA‰kÇ°gˆ··”’ +ØnAKºoÛßð™~aÄ>Cu €,Zp•Bкç¼ÃõëRólo®K€‰®G!}ó¼É_C|ùUÇ"´2hÙÖb#Uj¥5ƒâ>} +¨ô-Y)Ö‡ÝgGô/bÄŸrå°†×æhù9auwèk†
eo,Ú{¹)Û0ø×®Üi»ìyt“nñ×醞JÙpe?›øμN“Ž\¶d`2…~¼Ä«r¦¢Éò0ÁV¨ðÊMêá®}–°u¤¤p´±ƒ.€KD‰R¸Ó©"¯¦•y)æ3As/¡”õ¦ +-fÊTuo@ Kî\CæÖc~
\{]¸`€Wú1gÊýQOXÏ&äþ\W5|9vLé´Îóh ùS +xO~ë2LÞ ƒ™iB^»x´#®¤ÁlQ„7¸¶u÷eX€ó`µƒsݤ ½_gpÖyz¡™ðÐTÅI¡6mÄ÷ÌÉáˆÙ%²45-ÀVn7„1jàÇšKÒ¢f;ÂØà¥ÂÏd +5¢çzMkž†A ÍcØeq€ç<³ãy_2~9Onú@Î4•}+ËØ +7dˆÊ~ô’%¬/«ûÊ¡„ì1|¬¼C‘Õó›’g¯’´e¦ 7%¯“@EsüÇÌøi0Y™íLp9‡sü$Ú…7nJPt0YHáq©Â+¨±FÓåqveãÚ›Ÿ +GͪÐSò¦ÝÀóŒžZsÕHoyRÐ~Æ'MÀæ[Ò¸‚;ÎônìS×!“ã\0 ¬º3‡¾ZŽê)Ž©-Sd'‡²Ö¸.m¢°_l ¬_¦°chÈñl–—hýòy + +jz iõíÄîà éÝVÞ EÇ] o{÷ ±ƒÈ÷çpñBKÏ=½MÕÕ4ÕÂÀ-è@º¦¨‡Å?CYhZ*š
bî&n׺•˜sË.´ ¹îîjUõûhÉ°6KζH«×sì‘¢LÌ×ÓC¹JÔa€Ai6J²·óŸHDðÈ $¶,˜\Òœ–¶1¾á“ƒi •¶»;nËh:Wá¸{‚Ý@EO,îÙíÅv^ŽøY0Èt#(lŠû<;Ô½†]ßø=ïéóX Õ÷Ècºì+`ü¾Óðb~8bYÏØålr½§ +þ7tpVÂ6„—
‘݈¢H8Úæþ.øc£°e†hrzÜ&dS?×ïÈ¢„ÎÜõ|
δáõò0$¿ÃºcÄîØ%íœEPpD^mã*-¶ò|™8f`úÖ1PÃX8‹ÔŽ"ÅKó
+<ÀvÕŠrÎÀöÞqOc;-ón}D»€X3²Bƒ
©ñ,ÁÓ +ªî׊™Ã–ÅÞÅ… +ZCC8/ ýŠîDµwïólï>*AÑCuÌ +Ñ’'=×ENQnfYQæŒG×$û¥XþѵFöSò°£Gvmð_ºË¥ÕòâŠâsÁïp'‚BÄz?ÌÈ>NBz¢wšc«Mb+ÍÍ¿}ÖÞkí:çÚdÒœ^ÔÕ®ýøQeßåÎ4'Ÿ¿½É=‡-„9ãEo…²Ií³¦Ó>Í¥ª»ž*õu xË[Ï0‡>ÇS8YU<1Â*wð^ÃÐuÜ 5E£Œ<[pêÖnxÛd[¹º^—8i1hî»wžcqk£òYW@>~®â"oFq¯é+íþ‡\º8ÐäÔiî°ëX³KT8góÌÊ6C¯q +qã©CĘ +Œ¨3˜ÅäÙêÍ~´È1ÌÊÝJ“ˆ9R4@»¯±CšY8áˆMTvÕ¶'hÌ1͵^Â/ÕTfè«iq*ѶhbŒNñP<ò‰0wèšÓŠg/Âë”"IµÓxð®ï:-ÛÀ®MQ¯:—‹#ÄqÂàúÔ‚ä.eEŠ–™xòæ&NðBßFÔb„
Uo#×wÁ&W"§aîÎ4Uw”±Yi.zâá×´pšÂ$Í¡Ó^:¼y]û|_Õ”g8£,len£ªÄœX«FnóRnR·°èmY†:y ¤UEgËQ0kÜ.W«Æ%‚çq”¤·¬7Á!+£LA^ê=.lTr Ⱦ»×#¾CÜ3ÈÏÝÂ<Mr|®±®vÊèJE®—Ôcâ‹éÆ;ÞwŒüªÚᤫä!™Ó +ø[]kñ§›ýÔÓðŒVIP—%«Fë¤tQ›³ª<‹ñe«ƒÔúmg
x"ÉÛ€©Õ÷ 2q€(:üòp¿€½è³'ÔTÕÔÃÖ‡)mÀmñYÅ‚×Á~2I¾|ñëXb;ĉäHÃÂÃå3¦uœÇX[f€~À3>8‘"-!@â"ëãup“l#Y:¢[më)¼4ñmÞFs¦@ìbþ$‰KíM$6ó‚„f|Ñ&Vî^²tpTîÉað‹ûôƽ§‹Áq8¯Éyó‘XTP†»˜^’Ûö!,œ]–é ¹H«m;P’º‘9·*˜K41&2M4º¾„‰ BÀPœ%î&òt- ÓHý¶ÁÌq´ë +÷àýAFpš´áÈ´–VœÀtÃSêYY…Wñ?GKΉ’]f-‰ðP‘Ù˜Êó,®iqqÏ6`õÜÒI,}H´q Tr[ÒmFh[ˈjþ‰:<´&Nì«3 'ý© ›ƒ<ï +(µ_Kc?Ñ-îD]"ËEÚ%´Ø:>’c065zm%»¸ˆ–wÀKôê†)•Nšë&æYnb?bxÛ¡g}Ãy®âå32rE=;,ë3ã¥3$Î+èÆš}S¾Mc+¯#¦q'*ŽÐkŸ-nËØÚÎ0™d6RèýNÀ"ƒNwèº"kGß!wÑ D7G¼[´‘wè»h1Œ®V›†‰Ü<É z¾ß ×,S‘Ýi—% kÛÀv¨c@_dG'æ5ËOˆÚ9Ï#26§‹?-&ßëI4Z6Zs°íŠJ'«Áõ%O‘ùobžÞ )æâˆw3äN7½ºÄ$÷6sâƒD™ˆó5ꙇp½Z5rd`SBYO¡$ëw¢Î`ºWËýb´ÅÅ•öÖDdzM•¢Íx¡yü9»b†µ;-UÅ$Uš8W¾uÓ[ùÓâŒÑW”g‰ö@/Ò«ï`I¶ÑÖŽGI½© + +&ñ¹hT“5åì&ãÃR‘°}kü½Ù%ÎóŽ‘ÓîXf–É·_ОVåhÛV¤jk `xü,pHÁ)=uw áÞˆ F^öÆó¸n0˜&f+Ž-xDÓæ]*ãü9™©áZ¸ÉµLWe2ƒ–ƒÝ©ì¬û(;ñl€ósF.\æjÃÛ ëê þÃDMªOB÷=Z'ÁH˜`_ ú#6”é\|ƒ
íIG!X*Å×°¯'BcÃ[3YvSà#µVCoÇ Sgî myìüf)¨o7gÏ
ÔLàØYn›F:¿ebí(ÆÜan±Lú,\ôAWç +R´›%Ø6Vf³ºjn¦Ær¼Hž Xí4‰ƒ±n +6Ú®ë®Ôæ!kÞp_«çX|?#ØGf’þº‹Ž$È´<Ù<‹omð'^ÙA^/AžÜié¾ ›Õ¤õºo'gW˜!(¡•íÌMžÌépp„M‹'¶CQßKÈí¤æíz^sÃÅXòs¾•š°3|$¢~Û|PéQ?gÎî®PlŸŠa®â9ƒçÈàóÎ$àÐ!q~Ï`{ÜÁ¦5‚Ý©1RâëÚy6;Ùmjf4Sãc±
ýœ±1G!Šª*¾wèÆ@Ù=;¦îœöíìZ+@I0”ø99tµá#ö6f3Àª¡‹D88iè¿øX|‹BÅ: Ÿ¹7¤ÝÀ¾æ ›0nÅÞG΃ N4œe«™‚5g«mßiF‚ç¾£&„ýçY™(¿ý>L,¾UU"[—i1K›MJ…XF ¹o¢ÑðXÒÞ¨æµJeûÉ@¯n/¾¤Õ(ñcý#8Š=¶Á6•àL"n_é€ÄHáÂcC8¹÷(àAWë
`ðÅ5;UUx¬l<y é`ØŸÀüõ€ô˜Úš,v^9)´Üi~BoCFËŸ¹_ªøt‹òŒ!´/´½JZ° +endobj +652 0 obj +<< +/Length 22819 +/Filter [/FlateDecode] +>> +stream +H‰d—M‹eÇ
†÷óz°!s\ª*•¤dew–Y%ðʘ†„!؆aÈ¿¤W:÷ŽgÓ4º:*}kð5…ø僨Ùuöæ=ã\Ìz +.U (×4±—·÷ïÀiª×k¬5SØ7ÏÈ—°hÃ±ì ¾Bù¶ê*îêKXdmØ ªTp°Ø@CwÛÀ×Ú»lÛS¡a]²¨4¬MÚ¶6\·
ë:¦ó+a^´ Öô¬‚ù…|ï<ְÜà¼
æ5÷¡Öàï)¸d=Á²Áù\çwÂÃ_?Ó0ÊÔðœÓpRkpÎðpñ‚n„C—M‘‚spFtÛ¬W¨ÿÎÆ_ +a‡c)AƒÊºYú¦Økÿ~Lâ/EÅ&|´àÚZVmÛÔœËÖ(á9•KØ¢ã9f³5¨²4¼C\d?q¨eO2@åÉ3‰2±Û¾Ž¿¿Bl +
ì1¡Ji·ð4ä1Ÿàkk°u¸¹Ax_coCŽâ¬õÄ;‹*ñÜßeº¦ŽLí +öÊHŽ $æ‚Ç)œ³ž8Ôö‹·ç°„ó:‚šØ^øÂmC$ï&ðUöú™”Nwm5£aöÚO¨TJÌø–Õàƒy-*¸*bÓÃÔŸ÷¼`cP:ÜöW³!ãh›ˆöz‚·4Æù°?VáÃáö¡ÈÒKÒ~xàÔç‚ïÎü,oe“kT‚:ñ,°cr A'¸‡ãpgÿCó^"[8&á¢óH„¾;“dÔ I…qWhnx'‚Œ#øì˜Ë‘h|ºL†à[þ©;½Ed‚—Å»…3¿âÉf4z“æ'øÚTÔ¾Ðõä1 +ÑêûiÃq—éZN~¹ïÐtãÙD +®uζ!øÄ+ž„}¸l)ç,|Cø¼`iˆñÃåŸáÁMáqœjéI…†ãO£ãhXxÅñæC\|Òj
S,=é͘’ +N»á8ÛEâèªøÚT²Tip#¡6a™P¦'Ÿ„Ü‘éÂ’0ŠfqAŠ.åÐë`q§Cp'§>G†t˜w»q8õ¤µäaÝiÃ?ß¿ûÃOß}ÿéó_>¾}þøÛ¯?úßËŸ‚}óa¤Û·/ßýýó§¿þëå›~øþíí?¿üí·Ï?‡ð·/Ñ?ÇŸ÷ïÆ‹÷rµäÿø0ú÷Ã>:êÞýùKóyP äƒ] +4ºÆ¼ëM3}5þÃÌwÈSŠñ£Y¥4x‡Ñª7åÅ€¬Ô•éf* o]BÎ×̬Ôhg˜Œa°ŒðBNÿSÌ;ùÍî%)¸qóS1ð’7˜àÆxú4Tq¬^7Œ‘pîK—ϧÁ7®‹È +z»,µ2ù<ú!&^òˆ.Þ!€£ÜàÐÐÍr§P~ôä«Tr±žîÆÙ\Á±ÓÓÙÛþTäç=Í„Úiá˜öЪ{h¦Øs-9KÎýäQ0ؾ|x×"ðѵ4n„»%—S1§é9"xżÙîðœè.e‚sŽÕòÙéžÜ9¼Rk%C2)f“õ±1¯Ø*£1¼âÎé̱³ i¡&FûÚû#”ÌUÒ076yACž(Ï‚ˆX@ú´öã'Þ¬Ú©Œ5¬! <¬%…£ÚÁ6ê¼æs¯ ¯]ýë±k†2C’úh¥3ïCÉ>ÈæÿwÖ\pÉ~pcwêçsÿ +¾ïÑ8ðC|ÍüL¬G„ÃþÑÂRNÈk°>u›pÄÎõãã{lm0*8†úÂò2г~ìûÔ÷ÜŽs{UcáH
¹ +ų(§}ÂÃ¥àD½vvšJ¹RâðèåKð{¼a#¢16Ú„®yhØ»Þp&Þà 4Ö]iKßnNkpŽñ\«œ¼×ž]—G*êèã…™n?x±°6Ç~‡fæ™Ë«!ë©ÓÃ/°Õ6Äm×WØ¢¼oCí>TÐMs2äi8•VÛ@È:œŒ¼VU©å¥“0Öz@ÌÚ€2z?Iî›}s¦ +œf=ô½qœ¼
nn“wñ¥:|7š?¢¹êx9l³£)ÆOðÍ£ú•°7f'W½9Är0—÷na>ñKX×À4Œ’P¶p@®ƒ¤`MAdFùå„éñhUîS¬°Á¦·‚ˆ7WÜîè“hùjßtR×2žPâªzëq0ÎH¯Ïû,ŠËÍL!ìç¥5ÌK熯!g‘ó…µÂ9ø"üÞ‹°l桸% ·—Xk:kíÁuaA÷‡’Aí=ÞO^7¼Ç³—ñþJØÁØô¢«ÈÚøÖÑ›^œ•¹§Ï]Bα԰§I¯
V(¦qYàôì]Øž…ßûÃ|LÄBwb²ùÚßxç÷.^¹—ûKi¨ö’pÃÆÃî-‰çÙO¼–·\= +¾¢žfy`4{½÷¼Á·¨Zˆ*‡ž +Äúc´Fuôû¤r˜ì†·#‡x…Ío„57àí‡*ÕæÝß‚û’¢=UÐbÝ^>hY÷°J¸MŸàkû‘÷…0«Æ›'gAÞë Þ‘Ù÷¾öDÏ.-=÷F@©«ªà]› \Ö É|R¦Ê‘ñ²« EÏ›Þù{)$Ð|±-Û²ÛS’sj¡S -KIaè¿ÿFÒÈÏ»é%l¿zdYÍlVR´PÖ•Ãœ×a=ÝfkVf½°ósñôhÞö˜MG€æ* +c^yçbÜ HH +rÀÒ4Ák,åFâÖP<V$z洛˜»=¦€]·S<¹Æ¶ÔxI½ú±œ*ê“~î‚æö°â>]™—ŠJ‚.’Ô¶¨ûKËwM¿± +ÚÌQÕKJó[qG¸(NhÍ-|¤Í×`XÊû}„˜{ÎE8²¯7ºÏœßN[NY3·¤á5d©ix/íñn^\º»ÈœÎ„šÈÀ5®Š‰m±,,µ*D’ XµÓ²¡õœTþ|þì‡?^¿ùúøËÃýã×Ͽþ{÷“a/^g¶±_Þ½þíñëÃç¿î^¼}ûæþþŸO¿~yüh‡_ÞýhG¶ž?+wXVŠ¨ø¼ûw¾O‘*‘ +ºPî>%^kÌ6²iÝyD7$“Òmú¦'8j‚Ö}¬=p¨ÞÀÇ^§wšð’
¯ÌM +}f½©´eY=ï––0é5¬L\¹–ct$ }N +T~âCŽ¤ñíꟳ
•JI2±qx˜(¯Ci•k0D‘¿[™¹ó[Û«{V#$Æm—¸WguSxmjà8e@–’O´í‰Böb²[´o³œ`cO‹E8¹¨ð°P¦Ø8ÎÅ~r r‡•Xx÷aROÔC‹N†1Úh·pwÆcÛbGí_µí„Û9«Þ"<û4èw¿o\û6+ÏrVÍZœßã½HìÈ$/`ž¤ó>î¯ÜÒÔà&+.6Gh=Õœ#3 +ÓñÍÊiùh˜Ó¿cNÙßòl/ÂD\šˆ·Žn8í¥>äá±BA׌Ã@oŒy´Æ,¶(\?ËÙueP¡*+PZ19Ž³
?¿ßcÖü= +Z5P¥&¨K²ã7”£íÊÃcuêøÖÇy3›ê*™ƒ)áýÒY†a4{œ@ë[Û]s(Ì ×>CÇ:IåX´óf|Æi†vz x—Å¢•µ¶/rA;îÏ–úÒÌ?ÒÖyMIÇ*Ò®úhÎôN—]uœ•|z¸¤1,»$ØZѼoêTðÜ*éÔzÉ {4¾°BI$½K²ô¥(g%gÝÂ50ƒÖÒ=x´º;«Iÿ74dù6ñT˜”j˶nk_NÉ2›Á(è~nµ ÔÍ~6,wÙŽ%rRØ®Ÿ'jTFxÂmâdà R-!úÆOR‡Üg°
FཷHaÐîZ¡22Ð:ˆà‘˜†›œ¿ð +Ó°™¨ò +ÔˆìÇ~y÷ú·Ç¯Ÿÿº{ñöí›ûû>ýúåñ£~y÷£ýÙþyþ¬Üµ«øŠzÝý/5¿6¿ÛŒT>e¤ƒl%»¨KÞ¾ÑÁ-é=2©Ò{²nz֙Ƴð¤‰`MOÉ[ÃvÅ€ÍÙ3ªù@NÝ<©¶Æ/f”ÅÃãP릸V¤Ñ5³)Š²/BðŽr\¬|!Ud½¶–‰Àè×\w"”— +B™¬¡—XèMXaÙ¡Z²ð°Zì%2À¹æ%XÐ%šxôþ¬3¶”Ön`£±2ñ4öµ¤ÚÈ*ÔÔgmVí|`¿NÞ¡IÑ–UŠ¶zZ¤†Ö%¨9=¹„cÖ·b^K–½z6ŸdXO'Âözi×.ñ”&þ[žõeÎQkû€ Úö8)à?Ä—ðƘý®ûé°HU`Ê©;9ì±GÒ¢f„”Û5úÜ"˜tÐà‘ð)ˆ¤ +;fÂÿ2“È—"¢²?¥Në!¸FKÚ|⢊=Y,Tý}FÞÔnO"kÉeä¬óû 2£Õ$G€MJ.©†Nþ‡¯åuܧ€\^žýÿY¯–»Ž¸7 ¸RÈý`¿da7A8ˆóV†0Vb!–lò +¼±âJì*¤ŽjÓ‡°í«3ÛîD_Îx=|™Ä?—W4Xr‰#^šÎ-iøvÛ–4¹á™|±Qp¥Á‡F.ºgx¯Ûu›a¼{Wvi-ÝóϸÃ-À4.õö° .Û,°Uú£b3pFÖÀ`$UÇk4È°æµÀÆhGùöÓµÄw6|k0¹ÊTN ·&(4†æL=ˆ‘÷ÔYikqãY—aS¸»*(1Ø GB$ë1“—Pñ¹T¨E!Ÿ)%þð;†h:y>*ëý°š¡åò‘[m³ +I¯\Z!.k†v\Ã-jÙ1,z((àBÂZá4B§÷eóI¶#hÌŽöžM¤F樮˜Sj4N±Æ’9] ÅÎ;u{Ø” +iÂE¡;Q܇¤†+H®ÉÉoìÔùÁÔªòENÁ±ÉD…—´N0µXtS0·ÁÔ@Ãìú®…î+¯ƒ¥
Æ %†{$ÜÀ»˜\}DÀ©0È¿H¼dÍ1Eåø·èVïjj»9îb Vî‚%ÍK=VD[7,³s™,Ârl® Ör<´ÞøDMiÍÁ<'s3f +·¡º|Ê 5?\Žóò §±Ý`eÙúHÁ¶óqSRBý—$Tz¸ÅÎ;O97/.Y«MÜ‚XÁ‰4p+ƒ‘$ÿ‡q‘zµL›¸ï¥¨ëžÃ ‚ôß½ÔC+J™pÇP8ø§H«[æé“âj¶÷¬íh¬ÕÛ¼àÅ‹M<æ-høìÞkÔÌ€M@Pcí+·P<:ó½î÷²GƵ¤ˆ!õX%wZÛífñÌLB°ÉŠLö}5׺€Gk²ª/ƪÃÄ d óe\s£Ü…‡P„Pò+Z uTTV½vD{lUŠK|.q<hU§Xh¬fÙnrÙ]B2VPc_ZûW°ÜdàÞžB?2î%Ú°m¬å˜†vÇÂiY!ô\kL_mA[ùîZé¸÷ÅÐ:ÎV`mÒðòŠ—Hzšñ1ãz²ÍÎ\ÅD×:ßÄÚðÈ8D<æ¤ YŸà!Ô³â9ŒWK%8;‡[ÛÏ‘ïLƒšD˜É+Ç܆qç¤Ø{Ý ,wH??: +¶;:5©4$(µ_À»ðйÈ>2.u’÷0€©w&Gç-C˜Îª+±œ‹ õ$W‚wÛCŽË]Œe·¾*AÌÛ!¬ubÀÈ qŸ¥Ó¸l%³…Û<Ó¸€;†#Ló(8ð®3ræP°ŒMü%º +x™)ræ9AÐ3žGTÛ;h``Ρ…çd
x5è‚á`L^e3Ÿ¼SyÔɵ¢²bÆ*œ2WСŠÅÁÜA+ÓJÓA›Æ+h/Ù&çܻտm-¶ëÒµ/Q·vÝ™ÜѲµ8…é·f¬~ÐI6\þñä“O¿ýìóŸßùæþý›ß½úùÃí·Š=ýM‚¢ƒä^ÏnŸ}óþç7ïþy{úÅŸßßÿòöÏ?¾¥ÆÏn¿VÓßé'Ÿ¤›Îž[¾i£ßþ
g*ßÌÞˆoƒúš…x%;e×±–ŒªñaëT°¨ŒíB|¬…ÑÌP—ÚSÏÖ&Å%Úé|¥¢æÒD|rsúÿÀÙ.mW“*ðÅWé»oϋ´MÃRâ> +V3ê÷XÉÕš<¯Q#CÂGYû?½ÔÓÏ/&ß|ÿê§×_üðúÝwzõþû?|6üŸ6ùðÓëÇoþW}v¼¸ý{ñÁŠàöâ»'ŸôÛÓg·7‹ÿö‹ßëÏ_ý¢?Óíkÿï£ +RH='zÿô[d§‚.UïM,:.ãßÆ ´C–ê'múR'àzf +ÍAÕ`^‹ â¤9Ö%Œ«ê7±
á5„ÉAPçÞâC¸KOErƒæ4=¬ùVŽTÈÅTj/x=sº7õbƒBë·¿…)~ísøA/%3çh'µh‹d2ê<Ñé‰ðÛlšHkóU÷H©UtrÔxF^|«>Æp0ö“]Œ.I*¾GÙ„peµ6ZÛšðòRÓ¾X©pãU.(ˆ;ÕSزrG¯ò«ú%‘½¢Ó«Wô’NM˾ÿ¹©®ûó$}tž4îDøs+X€‚–èîÙvéŠl-×BáÃøÉNlGÓ*@÷ÖD6Ðã‚:©ã‰rvo(1o `Ø«Ú¾_Cɉ‚žtk‹5ß2?8“ëÅ®òzƒÎyì7ì!éõßèX¬ÃLÁi³@AóÛG¡XêU‡J¹}ªl° –<\Èô…΃RŽ¹ö…ŸäÖö¤æ;è£#ÉÒÅÁ’Ói[œäÖi^Ò uU<Še0ÅÚœŽ%ªðPS/^384‡‚^^ ±à +<µDÐâ~ÁÛ;þ1©õÔMW¥h#Ok/ÕEsëlkUÖQ6}=÷7ÌÐäþ\C—§ÁVÉ*Ï#Í#…›{/ÀQ{t…¾³ƒÙ=¬×)=±DO@#æ¦Æ
Te>Ê`‡¯Vû(-NN½ÒsÄA³p Ìyyo˜c–‹‡ÖˆOÎᦛÖ$¶|ü+ÆÕu»˜þŒz²âækpÚ0’ÏZ^˜TñàÞE–g»ŸMŸ£™}‚)ØÛ#¹Ë ã(Y¢@èXù:|Èzœ
V¿ÊÀhÄQPø=gÓ~Ÿ žs›~2ÆŠ|c+eß +±lÊÞ÷Jev2R²îÐOÎEó®SÁÁÁV$'Þ]"¬ÍFY×Åiñ“R<¸ÓP?6¼÷1È%Jƒ‹Z}Ô*2ß\i”¿e´ +
òKÎâ×([”h9ŸÅZ4ÐÌ9È®™Ð̳\^Ž&“ ½>œpSÌÈFzÁè?Þ»’)ÇÅX.Óà5Z,ºsyÓóX™Ç»K„Sx2““èR“Hs(Á¼|eÏÉÝInN‰”6í)VKÕ`Û<‡ßE)“‚Nd¶À X‡Î*œæ-K!ŸÍJ¬J娞º0Š•Ð*¥‘u†m“X4ÁDS\;Û~ñ2¬ÿMvÕ$ë•«°ô +ºþgšqWõ8{èõ? aßz“ÔðÇÁ„àþÔs¾±¸·¤™jã6-GÁé,‚óhxo(šÏ;,Cš}ë± BìšC×løç}áVŠå˜ +…kå¯ÏÇh3©å~•ÇÅÓ™'Ã:’êc8×Áàë@HÝãöÔ³äcí=÷©Sa‰öºÆ(¯æSR¾eƒ<»OèCÝAåìb8T_ëqsœ;Üz¯þE¸Ô-êŽfÑ N”˜‡€Gãæëcáøj§á}¬eýi'£‹1N£9®âäéÚzvj-x ÇUPûÓð¦ÿ=ÇÝ—,5+cq‰\€.Ù³)ëöH0Šî¼$Åê(é½ëaP1ÀEû@O²ùjô±âúƨÁÈìÕx£fŽäA +è[‚?›¢ÛCz‚‚Ýy|4]»ëÕÎ~ Ò~B›çp3Ÿ¶Ô(&rtñãa{P>©¸–Ep¼_!Àb5]®š´Q[—Xº˜ŠýT#%ñÍÄL:Éiž¯pÓÌwŽr„¥j½{ß܃ä0m¾DoÐ_Ð…å‚ ˜—Å´5Žáþx‡ÏŒ|,!C°Ç€ +µhõ¡^µõ
BÔ¶›µ´ì5’Ï|÷m«D°½ôoÐéø×çdaã ëú´•¯ø¬ñèc™µÆ'š´A\cÆx‚£Pö窴ˆviyŽ—NÑ9Uû"!ȉñëóáhTSRzPÏ’ es:ŒhÊB]úN³Áõk˺;S(ˆE·¤ÌØêÈ·ŒáÆìNew²Çë™dÅóÙb€j^u. +ŒüîC‘|”ÈÉb.K;®£òÁ…£çjÛM/&ÉŸ—®¼§–$ÁÕ’A6JõÓµ›³µæ×Æî'„_Øó•8•§¥XÈž°uKíûØ~Á²®ªÕz +0júŸ6G†]ºŽ3?½çb?;úÕ6¸3=.ЧbZ¶ ¦¹û8$4,ÁjóЕ··ÄwéU\КxŒïŒñP ÅQ”ÉQ¾ +«GL—o+ˆÊÕ'!6AšýóU˜í°”-¡ÉWØJkUÿ} +R”aÚðÌäâ‰Ýðñÿž‡ÃEÝL]“‹ýgÕu÷=]yž=¬°¯~gTúpÏ…Ì»E€ Õ[€óÎXLͱ
៟´lP€]ò‹ øøÝï–ÏL9åï7•,¨Ÿ–ÿäËí}ʉN}ýçó1}åñ‚b8{"¦˜@«ù4\8ŠU{ãÃÜíl?|“﹟¼}ß T±Pp4 pUNd‚#O„x•¹òxm’/F™8Òq›âe€5ti|@ižƒ®vø˜Ò4¿K´Dßr³åý%èè(úöŸoV™•¦«fb8¿¸…ä%¿MýÖï=½ßelöÜrmCjûNðGjÏ–¤ãE
Íã‰uù6¬se)ʯ¢ËL9æÚÊBÐË#ʹêE0!Pnžû_‰ú&ØVÿ4Wx§¼ BªŸ©éÊæ²ÕÏ»Çêßšn¿ŸÌ.…‰y‰Q•L†™;tÜð¸ßC êX²”L.·¢RFúë4›ú›'ý“\·\ÁùŠ'a51€øÄÚ–`0c€˜ú퓃d–Z¤ÅÁ±hXþÜŸÚ|+»|Äþ¢QûÄ£.Ô +ÐÕÄÛ¢Ãáýö‰4Š|MÕÁ’3þ‚·ÃITVež°ëŒ·\,ÛÃ!ךék"û‹‡šŽ#SK\"›\M°e=P×–šùî»>bÍJ‹¯¦Oò]vä¥ïâjŒO©u+˹ffB†=·äFК=ójýÔCaûØIŠæ76VT}” âýœU¿‚GÏw8\Ñï5ÁêS>ðõæ'³ýëø˜ÚQòbû$V¼·,ØVœ×++{}‚–š]Y¨¤fï’‰†48zàæ>Ú磫eY1¦0ý ±E&Æa¸Dé%18%ÚõѸŠ6Y ËZçÒzÀ\¤Z BúùGR=+êdtCœs/ +úO…0Q#‹Þ÷í߈½÷¥ß`â×ün«zø!&#ˆjˆ55’M1{ƒÅÆa·™Ç!Böy‡}|̺êy‚5^£‹ÙKŠJb’Ì«•ÙX›çÌÛŽaÉMCl{,=Áž… +Ðl&ˆó"SX¦€ Š7¿¸%?´Õ ÷7Œj–ïnû;]¶e1ˆ*zò¸·ù´93¾5ó&¦½!@×€#ˆvÉèë©S{²ËçÿFÕ\pç“ ©Ÿ„rŠl
×ÇÛÈ‘bÛZ‚§_ +•ñm–ŠbD|=EɹAM ã³Þ³VÇëq€Àœ‹Ü!°Eu¤™Æ5ñ^ÈfGïÃÒ[è‚ ‰Móy-6»j§â¯ö%kQž!üí*Ç‘lÇWùGÐFI´Ûm{€o70^¹uþ‰à")»ÆK„”|—`Ä)^î+÷1ÁmÛsÐöµ +$Û¹—±’×Õµ½Ç7«½ vëŽOëg°á–'¨è×·i9gP÷Œ¸Ze¢GM¢ñ$ý;©ˆ"&Aç¶ö™ŠRêï~ýX±*Y\G¸
KºÇ£lWsœ œ›ö@è©Û¨E¸ŸTXÎ81¥Oîw9XèQ]ꆬ[ý:’å +E\@€Áû +‚äK7pP\kàÀÀY§slñ§yÌûX>UN†zgÉ٪ʻ‹T‚mì +Éý¤µ +[íÖšƒ“†°›"áÊ!±+¢ñEÿÈÆbtê0µmtŠdµŠé;’®}:IR0{·(©½ƒŒgWÉ0öã³Gk³„’7—½ªp9xCº‚Ú—upZ¤²¿»÷±@ã8ÊàÙ«(ñî.óHÉR¤ g{À6l‰hBáû^Gë&AÞr‰äh5ÀR8ì@ÿóÀ¶YÆA‡Á5q_3À¹|ÑÈú¶Ä¿Œ¨U ©ÓÊÐ2«g¨>ÉOy’•Ø ImÉh;@^íeúÉ*#tÿaŒ|¢=·õnxÃÀ(rõÐð“Íí`‡QAHðÈ•bäWjJ=V°2#„¶§áˬØûn“Ôi,o 4°
»€(ÆI°—Š¤pI×ýS7
I+ªÙÁ:ŒÌɨìm¯#;G5šµ:0÷DÙîuÔ6\âW°ˆDyÙAb臷Ôê’Z4ãªn3ì̾b”¾•’HÀPY"áÃ4Ýlt.ÛFךƒ*ïT ~*cîLˆÑäÄæ©É#Þ’
j§ÀKYVRu+¼–´m Ë°;o*°ž°Kª‘;ÒÑQõ2c:XAÚ ÅÕ}Å«ÅŒ<àð2ø-¨¶íjK§ä’µ7
”ÓwÙ'r_zS7… œ7ƒ-®ÌÈ‚190ÙòÜŠœj¬ó`Eª^*Õºº¦(é–pr3t,#ÞÒ@Û!Í+Ø IœWX6&WÌm½ï^É*:W!- +ãÁ¸õ¥X¸x5N©F13 AɺpÌeíÌ ¶§}ƒÓ ¿Q‰v$±Y±5qvKH6d[:@6²WêŽfÇ‘ûÍ@g·ªØfhq¦ƒéà*©ÝjP-?õ+;H¦?z¿´H÷):\!)³t?ž^Ô¾é:&sé(}îu€Ÿ6“ôQ—ŽÞØ@Y«¤œX=x~:Q )¬/A1OŠcbÏ£=òXlŽ¹*™“$l… SM';±ƒ…z€ZM<o:´)»ÄˆÀÞ‹ß,´fLcX"@K3å «ð„&ç×Àæì®4ͽ‚–»Ê“fž€Ë@!0¸{:N~x\ à(ñŽLÓOÞï.“J_B¡6_ +·n)4o_É9šÒ(Íy[Í '!•†ž7ÊÌŒáF³C›™Ã#žÐ÷WïéAGŸûÑYW:HtC¹ØKxIZhO˜tyÉ;n85À x›¤UÛá2f·½Äk’"‹Ü’`Ïá)—C€ûãA–[ÉÒԲЛ^îA\[~E\+w²+ACªƒmh” +Ž).êFyWEèñF±.Xí(ãKÁíë_WVFˆ6¶K=Y`MÞi³Û…&P4IAê0wÛÞ„ÆƇÐ=„ø™sçn V¸€ø&ùjØ÷ñ¤¢YBGëVí’ÙšT™6á%yšÑ4XÜ{=¤¢•lÕ:¸Ø$ÈÙ¹ûÎÅV Nݯ˜¼ŒFµYU%BÆ,\#;ÃlÈëóV£œÙÙ¼nÑyØé+GëÇd +ŠÞ¥Ch‹ñNë†l]Y7™NõËób"ÿä«wn.?~„‰Wß=ÂD„yK>%¡Y,ÿ1|
§`Hýïd”>³Ä=£“`
2"{„öô\÷ä¬Ùûäì>Ù±a¦”}‡Å‚¶Q ?¬K†kÆeLi±Kéê]K™mÀõ³1ˆ5ÆÄ'Ç+bas
Ø–hzwì‚8HçO®ÖHÌؽD“ +n‡ûL¾Êæ;ap5ž¨Äü0¢".OÙœ*| d&p÷sÞÙ€G+Œ ɘ4·áG®+2´í0‚6””d*ÍÖÕª˜‚Þm§eø…”á~¼ŽÁ+r-¿N› dk¯±Ñ4-eÉÚh*Í5tØÛF1Ú³?8÷ÂV7B
÷ɰP°šbÇãú‚ Å¿œÖ[Ø!´~ì`;ÎLv#$s-Ü9ÜæŠS8ˆæŠr—ÁOyžêU}󎧋†ÞÜA%ÞAvDAÍËú§iéŠå¾^Ç{Z^†q¼‚‘1JÍ +çÞ}š-Œ«GÄ[Í‘oÁ½pàÒ| +JÿzØC|IÞ[wU+-ÉÕ¢Þ“MVáG-¶_kóêÉò×õ¢ÉºÈ÷4–ÞîQp¥Ùã`Ã/CC#m½YJ¹Ìc!ŒÆœlû8ÇäIÞÈñ[ª3Úmëq¸vp{®+Ó¢µ¦5¦x+ +@¥žÚþýÑ×£EoÓyV°ý +"Ü‹¦%@Ú¾ï¶ÊIYgœ$ ÅH;c©F¡¾Ðõ†ëƒÇÒ0%ÛóÜyÁ5ìq"èFò¾n ¼ÏáßYûýÃ{§ÿ‡ïjÙ츿¢M +öÞۆR®súj[Veóôuü¥àt$hªÒ–GˆN+~µÉ<ªGâ'Ýñ´í¤•W0½í‡Ý{û¤«ZÁû(Ö±òâx-œÛù§]zPÒ´,β½tŒ +¨ÛÏá)?*øq} +‡û,£-c’÷û +Ë»|®ÀCG‰Nñóð2gÆ@ClSC©ÑjýÒ%_¢ÆÑUºrl!‘êØ NWM¬8Ñ;‹½S3÷:¶dÊÙè”KÊú2op‹¾Þc»ëjÃWö/n€<|È€Û'ÌðÉ·!
Ò [Å…¤Á•Tï¥}Œdéó®o}µq»³e«z²ÑÄiC-’T¸ÃœúÆ.ý¡~Š³¥ë(뢯C^ï×Þ6​1[^Ó%â†È]†µ&@ZAÚ€SûUÁTëhY #û²Éï#)Þyö1ùÃË,ÆÖܸYÃ
Bhu*n¶N°Yf'¦VØ@%3ÒžÚU©UŸ¯œ[øZ’¯lNÅAÓN¶!xW§4”~.’cžDÁH»4ñZöˆŽ…ææéu\Zéyt<[í&ÍУ—)Ë\eMµoŸ?ÀŸçe¯¢{ô€Ú¤³äIÕ½áÎÅ% +ÖÅ©¶Oš¬â¶“IU¨N×]+A¬õs¦±ÃÅyxà©Ëùa¥|Ê‹‡îŒrm[@ÛøSßg™Ãíja+]Ug-1«³dâ¾-‰’è%T‹|“•?kµæТÞN.–}mE +VÛ&Ûº"r˘²2Ø”`KBÈ(‹Càïp‚Å\ŒÍ“'?|‡_‡LŒµ8Ì‘8ÁÎíÃÁט3êzîðò¹Ö’}nÁòÄClpÜî@ƒü<sXÃF„»ùü!ø§Ís¿ÍN_ðâ¸+[wÇ¡?º¾Š÷&x6ôØÍ©O’‡Ý|ùÈmØÔÔÑB,qƒŸWh•h:Ì”
s0SZÝy¡7](ð ëMѸFëרUÙqÉÒš1Ìø!!)KT +Îœ¼ÌЙìʲj†°
¹æ¢£,t¬ +Ï
¦_Là¸Lp®)xïŽqg,z°<Él|÷å%¾…fíñÖbà )-Bµ]EšÐ +À'?|‡4·â’‡ïp‚6£JIs$žÕlWî´Þ6£;ÅtÑÁÙôGòK¢‡”ÿÏXnT‰‹d¹U½V¾WŠî¦Ì²U$TÍÌ‘|sêÒ"»›\K•X~¶ºÚ0€mëö½|r +î›Áôx¡´
ÜÙOÉ{[+GÑUTzàLE…ä·¬“ÞSŒ¢æGˆy½¯,ûÛª!ÃEÈ’çµ×Žðá˜|?Ǹ5*QæokùŒÿøš:Ò&Þä¦Ú|ÕYØÑ9¹ä¿½ÓÂV^ªîIW=É:I1sI:Rµ‡Z¤tt2ð–Ëp<»Ý&¦i«'@5©¤`ÿ_,ËŠÓè-m®+¶Üµh~ÒùpËSqͽÌpÇqhñÌÅÝͺ„ÒÁ{:= šC´ŸÖW¹æÜ¿'7?<8«Ÿ‚¾<ãûWžwëüctDh×ù3-oâ_Í}]»4cq?Guâ3ÝzjPqh áxx€’2‡„‡Ô{w°nÝ1æSê´¡g¶ØŒºPo®.úÑù–fŠò߯e#Þ'%õéSŽÓ0‹ææ/ð'?÷á€rrð„goÚç°é˜¼—wxµh慄™pýîïÖ@ØMï7Å,ÿÆ^4G³Od§‡†,•rhvh±\¨b³“Ϫõ×pd©Õ0Ý—4q̼ë#سaÄý +¶2Õ:ÏÁ³•¬Ñ"Bßç-¡ŽµÒóR&´ +žúgÑò×ÌÖAFLÅÖD§fÖ i³:hûþú>Ë^αÜíXú-ŸaémÛtPNG%s7>äa†$ø þçÄOõÀ“•Fo…ûVp4Xfåîñ[è)hß&Ü´n:š&¦¸'Æ;zÝ¥bicò€lÙù0¦õñTŸj´”K7iêg¶’/•´*9óSQ–󗫪e,¨QÆ„çä Vyze%3SÑ¢è§ß ¨g +ÆŽràj½¨/ZñOäRq%ÀÅL”ÒéuD}M€3@ÐŒ[6{þfWTµæ>uɬ஥ÄT,3} +‹*>×tV6åR@\:üpS¼²Pþ û~º3嬇Y:ogÅí”ýFùúZ©bL`+ÜWÛ¦r‹•z¸¯–œhûËÙaW׊ÓU—Î}g€ 0¨)g~ÙÕ×òìf]ý…°Ruò;Xdòt#:”º»ÐˆPŠs»*’س¡8Ö3‡*TP5 +.:&Ðæ×!’T†h º“&ðK²2Á຃֬Öã ¢Ñ} +;‡¡ ®çɇòž_§Œ =0Nfœ²:¹MŠÄÑ_zÁnÿsÊ4§–À×VÁÊÜÑ00£ÌsŽÏÓŠþÉÎ$ÇÕ*Ù©K‡ÆªÑYàn7h4‰w‰â£ƒGÊn7° +VŽ.tت¤#ÞO_=áÛ¾×/̤?$‡¬Qò—kešÐ'Ðû8X—p¢†žWOÑ“:Çáꮺ73øRIî äå<üĘÜÇè2^°ÆÅŒ³%~Þ´RV¨Êf=Ðñ¬Ã‰ +l®ý=VÚʱB¤#'.
Çftè|tp^ÜUÀ¸†êç·{¥çX™‡ÃúRÅÚHWÕ骫[)/FOÒd©|F眳¯pZEsD³ª¯¦«Šê‰!HªëòRj¿cÌTÇTvé+;²åPðt’ì£B9Ú-E%Ìc`‰õ˜À´j$0pÁe|¦àt.šúà²<FQŸ1àa×4Ñ.ym8„:ýê8f™™n 43£8ñ¨ã¼¥®¨"öÍ̓,6Z{|>Ÿ‡ÿÀôºÌóJÊ-à¯çìÌ)IaYz2{z{ìbÁJ›ÂÏM”\Á[/\Q?´*äHm®<dbZ^+c®dña¯`K(ð`v! Ó‰(Šî¥1É÷s{F€©UEË+Kê5Œ°‚¦ÏÔ×â– VÚòôTCxà(»i.n×ßHæo`o…›—=ª\‹¦ì^‡Œ;'ÂJ+l¼µæŸ?ÌC,¹« +¶¥CÙ--8CÒ:ÚB9
6=œQ…ùˆzí“ÌØ*Nå)‘KsP̶?ZBxœ'XÂ(½xG‚Þw&¾smµYo°Ž(ØꯎöÉ’Ab|ôì|ל=Í5_]¸:d]âñ.ai¾³Áƒ¿üÏúøñóã÷—ÿ”KanòªÒ†a$Ê%Š‰L¯×®Áúÿk=SZ¬^KúÊdP4„>~ý·~ÞïúËgVCkÖh+(òýŠñ°€¬9A-‚ü` +endobj +653 0 obj +<< +/Length 20710 +/Filter [/FlateDecode] +>> +stream +H‰ŒW±’e¹ Í]åèd«ìÀ¯„BÄã°S—=‘ƒ©r6áþ¿tõvf×Nººj.p8ÌN¯µh}üM—ñ‹~]³÷wâ/þÓ/ÿ.‹QX:Ëøø,þž¬nY¯ÞçtGË^]d~‘’mP^m²8(/5[ßÒǯÅk„Å„úG€Ó†ÃóESS4obSÃ0ç²sÚF€†€Tæ ¾hô7Æ*aaÁ?FÐ4g‚Bc&8zK2ñLJàë––IÒó¸õ>êø
âR\>FÇ·¦ÙênÁZßÇ
aIñÌ„cË|!äÇe¹Ò‹G’H/½¤YD/pûøÿh¯{ÍW§xÈÞÆ÷Âí%=^“_ËÖ~3”D›²Á†ãgm
¥]'È~zîÛ_cXãQeáXuŽplÓôã'Q|9‘þÃØÇ_þúñ¯ú¯ôDŽçê»ýáÌæ»[t…eÒŒ\9È#AæuÀxM€¨‚{X¤,ƒç¸þÄ1Òúîã-ÂÏs¯_ß¾sGpßbQF§l5üŠ×rP_«Eú~E4*+?U븖©ªy¼uã +ÀðöùX¦ÑËNïe£,‚JÏOÊ8AžñÝö²¦š>¼öNìíð^Ô8ž0G rß™÷5½]ÐØuÃìu¸¯žLCf‚ÈøQÏíÛKi¤yÓ<¾d&hœeâ-
þlv$H¦·}®y¼åñ.V™‹,@ðÒ|âpËnÜí#å*ô`zßôzYtÍYŸèV•(Ä̬òìK)}h·»<¹ïQ癜ó¤-Éû«â·¯OGÌ9êÅ—Hvæ¸*¡7xþ¿ü–ƒQb#jf Óqþûe!¦¥õªb9:®@Ñ<Ùœ®ÒžjšÇ‰9¯Æj«Úµ®½¾6ßÚCûf·T™zØZ åþá.¿Ï忾º5q‘²y DÓÖªgØ¡™™³²ïZ{tÀÂJþ@ÖP¯R`GÍ'¨ÄüþV.Ú¸4Œ=Ô$OløeÙæÕÚôæQК1ÆüEÓèÌÓ¢CcüÚ˜\Ó[NZ›¯F»c;æ-GÍsìÈ;hhÐ5ž(\¦E’èÅs,Ðjµ'QÏÕâ-&¨I’13û~å•#˜Ùû8ꂤÄY[1ö!ˆR˜5•G¢,§ølúÕSÑà“Ò‹8¢!MŽˆIüù¸p‹f‡m ™“+A œ`o+A0{Ô,|èyê¥l]å
9¹_–œú°´F%ÛŒgÅÏbFR€¢O9mKr=²Tê¡$²sP±(µë›Â›9Î068ˆÄ¼™æŠã-Õ˜7z¨1TØD¿|y\4©/nA{.xjØãîy€mæqäG\}$Ÿé•>B1º,AŽZ@MšcŒ `î¥CÚÌ`y‹oå¤dÿþm™ÒéTw[dT‰»äMpÔ@Ÿv„ÔUA2îˆ0†óøSß¼ky_ïŸEZ"Õz[—öP’7Rš¨ßÇZ=‡â®ãhšn”;J]ƒdÁj(“Ó»Èo!ʘEŽž1ed’%'¬Øñ-&¹\Ê¥ÔØŠMÉÈIT²Ö1εÀ¾K4¤|ù€…fèÖÝóZ¬Õmó)$
Óüø)áÿd::%mÞÚœÙÛ g,ŒšlÛ2én ÝKÅŒoƒ4 +”-htÊÿr7†Ž{_?åë‰8—³Q] +!g»åé;µb––lã#½ˆ¥I±
ž‘ob¡52
ÚŠEöŒf¼(±·lç…ξyao˜áÂÆ[ù ³ÉIŒôâ(•=â䨓|÷зÿ¼Ç«nSQ(b'먮=ÑK1+ƽ
[^ïͻr±æÄÖg:oZêˆZ¤ZBx+%”bÓEOÆÕo™Ç»ÅpLʹ-‘ÎG1êGtoqˆÖ#Ô]ÖQù.LW÷›÷øö*%Ÿ½[QìÏ“÷Sg+ô-^mžA$ŸÒ¥O`P,57Ç=<`éE’›?ŠKŠSC&ûñ9’9
÷H’"Z5Ù{£7q 3wD¦œdÉ- +Íi{ÑéÔ +0”%ŽKù`NºJ-â 3÷EnÚcŠ`ÙœºnêÔì‹ØmX9JZd&š©å¨ƒ.‰±åà蔬ľfœÏº®+šI%é Doú¦åXSDzûÓs{A½i&a'.-Ù¨®[ËÄÖš'fïãcƈÍ0¨|@'”LÔT´nÞïc‚Z¸R̳¾ˆ‰1¼bÓ^zÎÇò%ùUl…aíçŸIeøq¾w™
vÌò|&“˜«h'žë©¥,±îWÍ©
¢`üã×+3c䦚›fè`SâJ9ïž*ðú$¹hN‹ô»v dG(Vy³¬˜¿°,ÁÏ«ÌYòpGÍÎÂüOHJÕèðaÒö¦?PO9*¬o¤y[&ýàEþÄ
¿Ü>äl,‡Â•ø†X'[1sã—ßð}€B’µ–Ù0¯Úï·%aEó_h¼ë–5ï”pm‹nv–VZù¼¥ÿ~üî#Óþ× Â¾H>'QçÄ.A„æ´!iÁ:V³DV‚Ú˜žòD봾Š"7©Ô á$ŸásÀO6§<ñ÷V.nÙý|,«á$,†r¤‹
Í×@ tâ›Exo*ˆó©ÖÜê
œÞÚVL[*‡HÊç[*'“ãc«3ù,óìÖÐPÛ¨ÞDŽëa%x<àmÚ=ž›i¢5vÂœšîÔÒý΋8s|t'ØMeN{é5 œ¡ˆyoh‚=ÄçßN/õ´ÝIY2=£›æñµ}ìô̽÷:(b jkýöyÅaÁî62íQ 3Á!šàÂy|Œ`µ°`ªœt¹¥sz_ëYð¬»$ã)¶•ßõ…dKA¯þeõ„‡=R#íÊ‚Oš=~|zòVòØÛÀ×+9Ò/òµåÐö«®ÒóÚ:ÂËZ{å}ØÆ^:-–TˆÍ-CPyâjòøÀ4Óh¯ãY›ZŒÏ
¤l X”–Åst½RÙ,Ã-|S X6
>»ÞÌï–ä¾Ý9Ý8@L–žBxK‚Dt7çò¶NKÎD€Lèe~è +C¹hëžã®ÑwÏÚR¸ŽÓRŒëlå]2ÆKB<ß5˜{¾1ïSCÖH$TNe>Xc‡×¦»æM«»æÕI¥‚+•ù_Pý7|Û'g×Õ[áöÎWu1/ÚG¾›³‹÷Ç«gk -.»‡´*“JË|äÞÕc¹æÅ0¸2cÔçÌë7ŸK!o=ŸkU÷Ù |"hé¥1ñùê¹G.‡ÓLžàÞx˟׵ÚRpùxo×NÔm2EŽÍ&.RœkQ®;°11 ´#™xuugD »/È +L» ™{öIf¸9Aô•5ù£è稡ê í| +wî)ß–¹½¸Éz”í<2 +ßð3ÃŽÂ}õü?ƒwÅN:ÛþøOÐùº(]ÝÎá„¥B¶¡“DfØ«Õ¹æŽýôiê]
—8=¢Õí+HÛÐó«3Mk :9sÛ‰F|.€‘/øÅ(doZ€cŠíÔÀ»ô“á³Á¢tôgï(lP]‡ë2dGú4ycʯ{%yMRFÝIÑ@‹QÑ窤™YóŒpfúÜüÊ°öÈ0häü›Œá|døULdæIôµ ûÀyp…/à 7¾&Œû¯^ÂCd
• f¼d®üì438uæ1Y<o +xÊêkú6XA )û]þYÌ]Ç`;æ=Ä-3EøŒfQX¸¬1(Á™ÆàûpŸ™Ý©ÏÇóv~?ë°oÅéí˜-çöWK +jµT©U
_s1e ‹ö<˜–óD†ÍJÞ™½ò'XÈ5Öš‰fÎCÆw,rBS&]¿k +*U0÷À-š,r÷«E9a¬,û„; +‰ª7Ð1”jwÏÚêAáe°´¯¹5ͶR170êý¸Jj²®™™ N?‰Ú+VÆŒ"ØT¥>ÄQÏÏÂËHh= õùd–lÌ÷äPзÈm¯á±ñ sñË) „Ä©S@#Ï|X¦ÅsøÆÏ;3N&ÞЂ=,OÐý¬Ïñº;öRmÉ/†Àómuà˜!—"èMñ øuepñEÐ…ø9¦™¤ +DpŒ•~a©^βWÂ2:¼Ÿ‹¥þÏ%† Ä¡âSÐ9˜[Ш9—ÃØÆ +``¹^„€Y²õ0¥•BwJY¢³¶|+ŸåÝ‘-TY´_O¦RBŠM*A«‚ÐLH€ÝÇTyFv±ú˜ë†ÜvÎãÔGOV‡“ƒç~˜@z29ÐïB½ÏÜ2¨ƒýÊPùÅÕÚ:’}o‚4«ÆÜ·Ø¢=êwÁ‰%UWëu-Fºv]ãs×Ðóyi$ǪŸìkœ\MA¹w’ª.êítÕÛí££ßú<{̦?/ÄAþëÊHÏcúvGPWu—WÂ<‚*Z +‰æ ™Àá?¯ÏSjâýº¼ +Í1rn|‹þOðf}å‚ÖƒMKŽ_ÜçÌhbƒü1$uÛ8ïܱ%ô O¢›ûw[=kÌÀcÎu)Ía…–aÌ…}XÌS8?õ’o¯òŸû¦{F0˜ÃÚZ7ÅöBúÛî[P‡2€ö5ÑÕÀ\üû¤`ÝÃ=˜%ç’Œùñí9žùÓþØÿøçÇ¿ÿe¥[£íBF‚@Ý™8(2ö·òÞä’8[BÈ Hü~§A=3µÀw’EO0Ä +%JÚ]PÝ*t®-E;Ïl\×®7_åzNœì¼K]vo”¤j{š×ÒûõÊDó»¦Øb¡Õ}ã›?¾Óg˜?Ø^2Ìêu⸶ûSh +)Ò¡f0ëÏEÒpf¡uÑáuµ×µ;tš3ÓE$ãкæ‚[ ñoP´*®i»Â¦r¡¡…+`ÏžŠD.#æ-ë[¸°Y³ÚØöƒBLǨކž +À¦È¶~‹dqa 4Ý{óûn¼ß%¼ÁïU
Ÿ^33S7MîÁ @î_ç ÊÈVoñõëš3Óê“nÉ CÈšÙ"àˆi缄ÕôÙvE3ï Ùăx –
•53(³‚½?Óä™uúON`Ä›Oy«m¿³œã$í9 2½¤/:r¸Ø|œIjŠ;sb„hÞàµ~|ûê¾aŸÝŠ¡9¤ãõ6=Cµ;f¿ïLFÖ#ßÕ&M>Ý–:L؃$N'°ïûë©}¥}5Y$«+¬.Gì×õóVò¾VO)w1§œ×]òÙ3’‚ÊJ×l'èŽf$Ç™ûLÁS±žC`^)h×Ð’aýuЌ嶸ž ¡>Öè”uœ +¼þseÒNà/¥‹;C!zìÌ‚ð7¹\ÜrüûY`ÎT±är”ç(È@°ùËÚÁM5¤I¤žq%üûIÀ¥ôL4Ó;¶Ò¸_ñ$‰aBzÅ
ÔI + +²q Ð;=±ª +OÒ×ÕBÀÒi‰ÚÃEí¾;:Â/dÅ»á÷Æ) àDþæ3ßv
œTCŸpxƒý-üì9I#÷‹™£ðÑe$?Áfë#è»ý®QWî©gpDÕ»ùµVº“ ^û59Kkñ*Ϥ½WL4Gg¿Tªg&gf<fï#CʧP¶àî®ùÆEË»:Æ^…RÀ¨º‚;Oy°F«â`À”;óÖÁ{|sõKîÚ,Ã2ãÚùÎPýFw¼3=÷ƒ¶Ìº¦Pë;Hz‚cfpt»ûN£gf$l"ØzKíÅ’ŠKh”Arâ8§j~ÈüäÖy¿ŸÌè-ÑtÜ ¸Öü,…!pÑ
5žçÙ¼A +ª/ùxN YyNØVÊ`ù º
®j×8¢_eÕroMÌŸ ƒ£Õ¸Žñ(+%ÂVÒoÕYdZ‚/X´úì|\ãFåëy¶«÷¹|í%‘ኲë(ÐÍ4+Íõ™ÙùÝnr•RôŒQÂ_‰ +–Ëô»úäT¸p4+—C´emR¾=å×G îÒkvÁ®&ðìœ8¤¶Yf¼Ðõ3øéÝ^íu‹(åR®ý—ñjçÎ-µ¡ÅeRä.DíÖí$™ÚµËùÿÙzÇþ&ë6^¶„9€¤ý v‚©ï î«–Òƒx× ´‡s¿ÝåýÑòêbg0÷rµürìTÔ‚uxS¡¬Â[.WœäØô~öXaì@l³î=.œTLEpHˇ%¾%'<G¼.f{¤ä3ÉóœãÛ]ÎUÿÐëíÿ|ûïô׿ôGMj"ßÎÔ»žú:™IúyÍpà§=¿â§GË—v]a{ŒÖîjÕ¡Pi‘£·J=
Ðt§©A5n5‚
´Ä©``Çl÷~‹‘Âd™§î€
_ÕªDüj‘ø¡Ì<3ô+W¯é>U‡3žÕãÔ+¸·@3w·¤×òëÖàì›±¾¾sU¾ÓÌ¢~qoä{[8/×IA_Ú“ôJ©'¹ö~UrÏh€TÍõ¶ð_æÙ„ý ?Êv§TìcÃò°µH¬öp¹pQÝ3Vƒ+SÆdÏH1@ÙýÍ+©³…¤Þß-ÒâN…5HJŒ4ìxÙÇØ®KÀÞ1]ÒeoìoË@JÔ°³²¿3'Ó㞣G?–Ö襳HfÆH‹äê΂A\=6Öú]í؈$*Ë)¯ÇØGKúã&tÓ’«Y²'µ7Ú@>]ÈXY¦7fA?5‰"â´Ñ«÷è…ÕÖ;¬ž*1}uq² +¢ŸgÙ;ç ½TlS<h>'ö`3¸€m|írö¸ÿyŽ63Øå9£,È£û–\)Þ ¬›À#µ:=Sâ¹ðë„¿Ô 0`R
¡¹„—F0Ì Æ.„ïú0îÄl·~jt›sT¬/§šév@ÍhOœLåÌ +Á†öÖ’_]H.}GŠ`á¹"8FÁ>륫—šWuC
‡£¡¤†VÿÚKkè.4ϼœÒ#c/õqeâ»õ—ø/À» +Ô˜ò¢ÓÖçŠR e ÌSSˆ=›ˆÎÊt%j% +…(o³ÞG§pb°p"ŠŸJÜ-4ÜK¹Ú¿]¥fHÄ3TZ.G7…hoæò®²®…Ùé…n³j8¡øÜKO¢wÑGy?{ð’DºÈ]3sfFëue¬”a6Úõ¬""g¸Wñ¦6Žñ=¢10-½Û¯G¨dn
ˆ¸jÜJý~VüÀÔW–SÕV±á;KñëHó +aÄ!ÓÊ’Àµœ’ +›Ín8È"ÓKÖ|´[H‹ ص¯`.2ÆcÖà]þq?&Ef•
(Áœ<"¯›»s{(yQ©Y¶ànpõ:#èÿÇ[ŸŠäYVÌŸ©ÿ¯;#;C‰p€F +sÇûèAâJu³Öû
+MeépvHgÎœI½çZ,øÖz+eŽ+¶´Vëtü‹2> ˜ÂJWÈ<9¨8£Û¦~'tx '`Œ’´ce8¸Èš„á!dìV*u²dóÒÂhæZ>(ýç•ó×xóöaÿíõùþúnwöð4ýNXýÍw»ýôòíwÖLçw·÷w»‹éçÏg÷—ÓíÝÅå«é·lö{þÁÿÿðþYííßÏöûˇÝû›ëÝåŸîÞÝ?œ=]>,¿™Á{|üãîâÝÓí§»›œ\^]ïôòûëÝ5ãž_½0Ó[þñáËÑ‹Çá?3ýÈ?øvú+ÿj'ùóÃÓÑ;ýÿkrÓ—)LßOÿø§™.Ž°†^ƒÓY¦#lD”ß2Zr€ö¨í¡á§‚;©ògM×Ô10 ¾u(@<bðõ¹G<fàIjz‰c6IQ<·íð¢¨LQHýlüFnÂ9ÌR¨ãÕ3qû,³'äx…Ò·A=›xîx¿Ëi‹’6ç†ß,qè|04øFNmçr~ç±/yñóî¹$}ùÚ£K€sü«éÍ»ýÃõîjzyròöüüñöÇ»ý/“Î RÀp{·Ó—#Þ3@óËXËs\vüòØzce`¸]“`Édáé=‚²Ë-i +f‹:Ò*|z +o}à†V0~áZÁü ê&ãVŸ4|ëô÷×çjøH ›Q»=Ä=˜7—h6üÿZÓ»¾ú¼ŸÞíÏÖRÙÊ‚ZÎ¥7 +Øn×.áÖªŽ±˜þ4Äl-C"ÈÐe- n'¼(Í’¬mø`ªH¾Õh÷Q lÙp®äXíY×:khí¶_œg}~à?qFã§ËÂðp!˜mœ/ZÕýᆳ½I¢ +7ÄÁkë?m9˜ŠKk{{çVêöí<+ÿþ8äWçìz¾Ûbè,ÑSùµÝ¶‰LnǦ?ó¿Æ`NM—‘øã»hí8Ȳv_©»T( +Á—\±9;>³mê¦ÄýÒ×ùmö9€í +;à_~Ì+AÆ’øê#Ï_}ܬé[^×£ôŒ$û¼R$¯ðt·!¶:èXBâ uµ ôÀã=mH’ÿ–¢o`Ð’cÝðu%‘ +5€”µ‚ˆG--ó;@bc>l®²+!䶡³¹˜(u0+ œhðQ© +Lhǃ‹@’ßQtµ€QïÇJ)¶a.sz( ÑGVòL(œÐ|°´°„ÒÎVôlù¢ÜŽ>(]ñ&'Õ¾&”¢äÃå& ¶PËÈŽÙ;hY¡âCÓÏžê]GSxPÁ*¿.b ]I!D5OÈQ¹+˜siP&G7ú@Z«>tø9I7ÖgkÄ´¾(˜M˜Ã—š9ÈÆ©9Z†*'«qfmàê‰7G)¥(äcVsT–°¾š€HAÈO?û°HjWDZ’zèXöÚ~8´¶<<VbÈ$+)JŠ„šŸIÁ’jŠ9Lž6Î5Ë“hIyÅ’<má_SÐ'
ÿšÔGÍ™ÁG„¤••,)°l+u +hÝ£dBb±à‚Fƒ•LPˆT0Ÿ1.²±NbêÖ$ºÍ1ßyIÑæ u‘ãø(›æÕqnŽípÚ<†K"‡6Ñúv=çZ0‚)-…F©
S,‰ôÔ±)E (Ú‰tp‹‹€ã¨)[rTcIçFkJP·íIx˜LsWÁ +—¶apÍ\» +ÀÒ®÷|@Ÿ1ûsŒ)Ê]¶@ø©]°r<ƒ<’j4¼S‰å)Š.$½+ØF" +Â6Íú©WÐ…¬–!
=+©¡®D—›yö’Ž‰ßG1ÊB0IÄIsA sgôÁ’6j€àI³K
̶m–Ó@X!¤¦¬ˆÏIƈÙqÑûñl‘m!Gf`]ô’H·æCåQ#¬FÒv#;$i5oÑëæpìˆ7mŽÑUú9Üò±6öj.Ì-ïæð¬ Úvƒoš¬dÓ•5·³ae¾jß"H
-Vt)læÅ^5gïAñ<¹²y«ÈnÇ$ÝexœÐ›çá[2ƒ÷¶§L8å I@HÆlæh&$:HAÔr?GOÖ$Rx•Ù(¸ÒJO뀴LC¼õf}¤ ‰aYŽ +ìNö4µ`#ˆ-ÍŠãÆœâ`›‚ñ§ƒ‚w¿WXúÈã¡%b½L6ˆ8|d–º¢¶Çg³û,³ +ÚX5p–³ÃÀY®·`ékz=L¹.w‘À{.g +.z^M€ÚÏúE±ËžûVÊŸ×z_"@ïÕÑqç°´¹ÛÆ›XÝD+!h‘*W;zç•m+]DŒ»£VåóÃG< +”Êjç¯$
[Ž”#¥ÓM<øU¸Œ“ê&{å@Ë“ëWï ×Í¡A—:]´@‡`WF™÷æah#ýkëäS‘¸’‡e´žƒ"Æ@(Ø)1=t6W¦szÌæÇ›¿‰k,Ý
G‚1ڊꓱNÃbR3G,‡ãæÇDVùRç=KÏÓ”s³Çie&`Sv¬ëº\ÔˆûìL]Pm(©ˆÂûMmáºN…Øà~i¶õLÕ˜®œo>ˆùÿ3ÙJÄNGmÍb~‰n‰£aèœ9|„"RùÈ=Ãí º$”ä–ZªKËù^·2SÝN5ï()áBñ©:†š>]„>„%VÂCHB{ÇâwèN€F*‡v
™Z”梿¶ Ø9ä×Ö¿*ÿËâÓGÈeÝFL±-¬–2Ò‡ëðr£>Lž¾‡ãöæq´Ïq`…YoaHÐêÑ>ÕCËWßË÷±Ð¸:îåûññbùsT›)"´ærHV‹²R ¢Zðœ³Ö,Ãq´ìÚ;GYwOÿ^ë\‘ìx>º\¦Ìñ”…^…Vãðá›Ú°õ5DXît +nÎÌPApÕñtcœf‘¿–jU!6¦ƒRz®‘u6qemš¶ÍWoÚ,cÇ*ˆ™>¢äEÂ,ÄÑÓú¨º…åqdÇ+‘\Á2J…ÑIÅîQŒea.M&k-î³€‡EÌ×.ªÛ@Ý2×°ãJd*[ÔG)K¤µ$À>×ãcü5:¯°Hº˜kVñ‡[h®y:øpø³×VJËƃÝs„,iÏ彡ÉK[-4ú¨E¥³´v•¼7,Í/ +`a/2F rÿãÓ–Ê™Ç'…ˆùæx`ü%Ûüx“øä’ÞŒþB;4HÎïô€iWU»/¨Bý“Ð[©YLÕÓ–ŠŽ¬ ·Ñ_BÁ59ñ9Ž·çøÈãXóä¥$ ÆíSõxí/¯Ê1õͱ«Oqz¹x+s{i‘¢lô±£Àç³ÐÝeû}U@s𧾶ãó¥¯?û=£˜W‹K¾‚|ŠH¯¬×G«ã{d‰Î7®öýð\v‰€¥V£$±2ìGrÍ|nEâú/j8K¡›Ä +ps}ò^AÜÊxWüj¡þ,Dq¼XG«„ˆ¦ºÞ5àÛt-\ +2û&±W3¹Ò¾79µd€{ñS¸ÍkET°6¿•{£TK©m^¨L´Ðª|ª +ÎÉë}³UC-ºeyƒÝ£û%O}ª=À¶ú8]4 +zv=E=±@t·:ÿ¾Tå +Û§.:6-SÈJZÛEÇ42Ê8fíÝAc¬¹<—'cÖæ¼FË(ÖÏciÍ(·ŠJø=Ãj`œW†1ÈÓ>¤âµÏk*‹ÛqéÂÛ‡MÍÛGïy²«*>ç`¯a¡ºãÀ†5=êµ +}}Lå~Ñ +>Â\à©ÌÔ&þˈ½ÅTC™âU§j} +Iu¡©~«ø…ÿ^Mâ¼8Ù>§’ù‹xaaFåî…f¶ŒC²äñÞIͱÁ¾år§Šà†W¢é‹ëOªº»‡Â"TÚÞe=,}kår:¦16À6Ö£Gum§°P‹…§{‚áƒr—õR¿¶Æ!-‰rIYݘu +ct”‘d+•¯ÊíÆòDÏ!àšÊÊ4KO:ašGé˜Ð+>ÙÒ9:½SÏ‚D ùÀ)r2±Çàb:;¬ŒN™êz¶Œáóᮨ¬Lɸ¤Íe÷ähicYcè1¯ùHaæRNÒ—1,/,Û;´Ÿ+‡Wc*-ZêMžíª,SèZN_s™©BnÐ9JæÃËîɽV«‰"[¤µÆ.YIrƒ*yP$¼U°PÆ¡ÖÑn¨uáXN,ŽV¬ú”ªËck£N±¶9á²RÅ E[¦©¸Sõ#UGO„¡¢[Õg•GFt°h~—ä‰ÕkûdicÖx1Z^Í1ŽØô¸JpÞ:˜OÛ)—•*:¸OqjÛ—=Ìž¬Ë©R c-Ù›HÕ¨¶¬+Sa¦ÚEÀ3Ï~´eèNåSk®«±—cëOÒ6”c43ª¦8á²RE/±Mbì²oüÍ‹v##Ž¸/*Ã̦©Ö[F“|Ÿ0
o«Ú$#ºÕ• ORÉ9ôCà¡O¸¬T©ÌJßS‰}Ù·€& +‹"'c-&õU»K˜M÷¥¾b¨Ô÷.U<¤.oÍ!øªÆ4DzŸÒ7Ëwö ÐÁ«Ð¯>·í”É‘(€Û>6]vOMnLY)*Zñ¼œç±hû¦æ5òÐkŸÃ¬)yèA—÷ÖṳɈªëù•ÑèÕÊ„Qd#ÔR)†ßN©¬LÑrf>
/:·i¡¬)â)É
(&ž¼`lYë ¢]cá&z3cð»fŒbËŽKŒÁkéDÓM,ñ|Euqƨ™å<½Wo™{cD;—•jEMÎY€P±§KÅ÷»xtì%´ž¹KÂÈRZÃ,¬æ)LJ„›£*FtQpž+/ûqÁŸ%œf/yÆ-ñ”`«ƒ«ù +â@åÈ4.™YêL4;!Š´è=)XŠ¶?
²ðtAA–ç ³ãa¨OQ——Ð8ÅPÔÈzÆtè3X^%ÙóÞâNÑÆJåH×Ñd‡€¦63mU<xl¶Cm\7a,-ÛYÖ(g‚(GÖ(Fù +£»Æ¡£4§Ì(jÆDÆ—;ÆÁójòD©¨þ}r\¬‚e;e¹BC©ªATçeòˆX‚§Çj|Q$tßS<!P–ëî…-¯ÚVÉ(9@½¶I²7“‡Cr½œ#tò 罚=Íb·ÜccQcîa;eù©C@óˆµ.‡ [À“Û„VÔXwº/Œ}¾Åf–=8žù+dFÆã±3s({û¹1QxËùÊu8‘éëÅA–¶Ô¶SŠÇ·`WH;¸¥¼uDzÔõÏ)®%·a‹mÄ]#Ç‚½‡iŽ/§Êr”[{›úÆ÷5Ya„ŒX=cJeOøõ\X2ŸR92'„ÙM%¤*)¢,“
áú±EÔ0+ÍK!O(}âA”£9VŒÑûZ›°›Ïyy4+[ÕšA‘é +Rš÷“Üe÷ÔH¢Ž<I{¶ØsÕ”U~ÑLuôrÅà$cŒ\¼bD×#¤*Fª—”£dt¹-•’x|fÙ&vä!GîÀe¡ÚP Z8£ +Ou®Ÿ&Y +7“L>‡:Ožœ¥¶ }m 'TÖ4§£é +Dì²ï€ÚÅÁzLŒeWŒ9©)öÑ‹ ýæ]“£ +Ö1i?4—"Óõ^Ô˜“×VŒn7½$KõÚQwûÀèŠá]ÌÛ‘#M(ôp/ÈÊÞÂ-)÷Üë1YÁ‚ÌÁå9È ‚‡ ¢×屩æð«vL¢Ä¼<Ø1«¢ 5ŒbºÅw3¨,L” '±vÙ=Y•0´]“û€Ñ©@¡JaV¯Á°–»™1D£d›ÉcR#WA1J‘gÁGyÂÉ©hlÑVÇäâÄÊYݪ¨·uÑá—ÝÓs²]R û<¤#Ø`ê´Lu‹1¢7y—0âuøÎŒEï…´|_0¬‘ôÝaਜ'¬²aLG¬‰0ž€·‰Ì0³ožµöXkµI/¦9ÖÔ£zºã”Œ±¿Œd5tÓRœ1X¨ëPWDÓßãÀdMe£”Ð.}g"êuï¾äåâ:ãØ"”lÃdïSôØ|…ª§J«#£”'ÂPQì&³$üXŽ‘E1Ê8—•*›ÅHñh`t5—ÉÓ™ +ß¿Ÿ>ì÷hš± +-tm½ÓÅÃq-ü4rA1ÆÁÒŒ‚È‹–éù¼ý_0ðO•ôÊFN<ÇO“U#Fa#†¾f]¦”ÈYHöBu‰oµ%Y¼ñŒOXš'< r”N-öþ'ûUÿUD«&ð†@kÑõ£²`ï}ïÞ÷ÞÅ(tQkJ_Ö4í²°”–nR–¶ `Pü ÔP
¨gfî{»+Tc꾟l“îvöíÌÜ™sÏœ1¤;±Âì
0
}ºpÅ ƒZˆA˜’o +fP tGÐz2½¨ï˜aÁClx‡‹1ÔCrÆR2µ…Töñx,ZÊÍ@·‰´yŽ)HÃhC!tP¬Ò„=‹‡6e0”Uœ°òá@„Î:ßbã$á,EÛ½,µu"K8qÖ¶š!^7‚^‚VÆŽó‚ +ô½Äo,±ž)ê¤oHŽ4—užS “Sìòw| +ÈRS +Y;ŨbÚ%#@žqJF¶´»QÅ“D§oyFÛt"ׂd’×@Õj¹?Ĭõæ=„"Û`ÇDLg + ÈL ¡Â†ŒÅ·UFOÆ–tœÈ|ç<P0Gµ]ÌÒ•$ +²kMø^:’2â6ØÄx•*ñ +5#ÍÈò +¹+1fP”.¡~ÖP²qLNFý/,š•†Ëa±£zž&·Žº#]« ;ŽdéºÅ¥ëŠ†ÔÔÃÔ…À"é}^ÞÝk”óLx³Œ`c"¸ŽïwrVÛ³Ù\jXôSŒ@<¸ŠL"Ü5küØW6#Zâ Úè6Tžž’ˆ°Å byfÊ#º0†oâIæƒÄ¢ÊA¥Žx3 ^Ä„2Í%‹Àz1_À8 e"e 9 Xý ú:Ìî{ä9ÇZ#Jÿ¹R òª|š$–ü–ÏoîÓ…â‰ùùêÅzpn¡Z›©C<j÷Nw6 +]1˜S¬¡£†ò µsøää‘d[¡ú?ølf¶2òz±J'W¯Ì)æjçØÜÍo¾l~º;‡XöÔõ»+ß/OäëÕÃÍ;Þ]:±«û±Ì™[ž<º³06ÐõPƒ‡š?>yúðv=îz¨ž½S7<ý}åjÐp¾}ôǯ·/ì~ Æ/ß{üøÞâá7ºª§8qmå··¦Þí~(´ëÆO¿Ü[ü(‡P=¥éë÷þnþ½®ñÂiÏäç?Üÿj¦»€ïíå—]'.}ç‹šéj¬õëù¥döÆíkÕ°«±6z-c¦—n,Wþ&Ö R7oÝÀ¯Å#³K—¦m7cmÜÂov˜º8_޾ꓽk–ÄÛ‡Þò"8vêü©OÞ\õÉukUÄ[ÞÖ»ýQ¶%Ç*Óã{V}´wýföé~ÿ~pÿñÉcû_YõÙ—6ýs°? +endobj +654 0 obj +<< +/Length 25748 +/Filter [/FlateDecode] +>> +stream +H‰œWiPSÉÎŒãŒóÔçè” Š¢‚%‹-‘ ¡X$"ˆ‹¡Mö-Å&Âb(•ÇJ&€€ŠÅ ²ˆRˆ•}) @È»7÷f¹óxô¯äœîþî9}Î×_£PˆCVC_½GøOÉÔŽ€EïDšüÓæMÈ;ýÏ!{Üc¤!+ü+s`zqú¦-?¯JËÕWÙ¶jP4u å‘æoܶ}½‘íÒ4Å[ŸÓm-{Êî²›ƒÁ~¤[de6¯ +mfm‡7TݳŹ^»J4S@Xò‹Œ¼¼ì–õ@alìMŽ‹”Â.ÿÐ@O¬"¢ÍòÊ*Š¿¯Ê–`k¦)+jVwˆKŠö±E*ÝÇtO©É¬ +ƒÞ%f߇óKÎÍKq8&}ݶcæÆZ{¤;¥M3iP(Ô™kiô8³ÒMÉÄŽ`©ó€íP7A€Bi¸§W6·4VPý픥-=Œ¹ä~ÙFGÊJ„qÐÀ +„Ú-Åu€˜VÓÑßÿŠI¿}^Z…(˜»úúyX©¬j·®%ÁÎ\Kú·YRþìý6Æêk* +¶: éÞeìäj´ÆÀv¢-öXmiQÃ<±~pzqa¬¿!ûšŽÿ¯(J¬ŸÍ‘µa1»àd{ú/&©ñ›Ë]üÞYm³WÒox#)3-ìzMP»ÏHDD†5§4³–¹\îÌ +`{p»ÅÏM)<í¡}@‹Ëé®Iq®¥ Ì!pÀoJµWãÒ³%:Yéˆ<MwºEÝöµ‡4†’Kæ3^C¯ŒwÐÝÀS÷Èmùº$†Å]é~”xñ0Ê<é)OñpÙ_[éÞB-ÿ»Ž½ÙÕJM$0´SPlbÄUôTDû•tM€>ÓSæ6üÅÔú¡yq,.û[GI° ðPªfÚ¤)ËCÈËÇìÈþn‘«ó¨o\*%ÜÝÊí¹ÄÞêùÇ¡gAƒApiטxÿP3íÞ9QH•+à-{YÀlJxrèí +¼ ¾jR¾þ°{ +<ÿÁÚH*’¨Og%° +ö½íÛ<‹Á)Y1^¹U6åXÆìÌh¾æ<p¿µ 6ó®&‡²Ï|)숥Ñ7··¬6)Œ’ém¯E¹sײh)· ‰q̯â݈UÃÇ:Hˆct,ŠcÍ10(ƒÈ'Âû†ËfýUàÍÏ¢ªCpJFò-‚ˆºGÃÓiÔhØkÀ.çïq^\,”‘RývLla¸>Á¥ìÈ![ν¯£\ä)9œ2ã&òÈ؇õIÈʦøa¡$'4ý œW”‘À¢v9½¶gL¬ðÙÃuq&(.¹Ï¡ÀX}ý0ˆWP¨3䤜Ü?œL“~‡–vJ¢êÍê!6wþ}u¸¾Ð¤ášVÛ;. +¶0T¼jto– +«¼êÓy4¤å—KÏ}"íÅù&eQcÜaÔI*{?¿2ÛWÙÔÝ2˜}"´8ÿ¾*H‘‚SÆs–ð+–¿·^åu´Ú¥(Z-ÚET i‘"îdR|pÐÃÉüÃ<gªûLàhºÝaöOÂÁf{K¼Á h¿ò¾Õ–˜ì.
äÉÄ}ÖÁTzN¬«¨ “·ô§d¥‡9&ñxÈŸŸ9“¯óá×:ÀU™
ÓÐÉê̳çyðÔÖÕê˜éc„ðLß75ëŠF‰m·¸lZü•UÏhå¬LtЬD¦¡=2ç ¬‰¿3ñ‚oc~<Û_Ê?e]ò©¯ö§(®,ÌÏ©rÿÝluvÅ5jÚ~÷mIDÁGÜߺì(D
/%ê"(ÈÃP¢Ä¢ ¥°>@!Zˆbñ.y‰ÁUy8@á +< ^ל±¿%ˆ!—´u©¬Xú‡W‚ĀéÉ{×Ûß½â®Þ¹µOî)»1ùŽüjÖ×ý»Ý:'—#¥Ý–ŽéëÓ×Ê^6ú—Nµ\ü&R^çN( öhÂ.¯?Ù…X{¼ º®äR¼ß¦ºèô"Eƒ3ÖÛc9tZ&þàãË~ÊÏÓªûTßXWÙ‰5ÊÒ…A±GÚÎ
°y»2+[:žÕå\¼p¥°¦m@NÝÐœããˆuû…k´£8q.6&¿Å<™GÚï&)Ó÷ãU¡ñIQ:;Â-ävSß›¡¾ÎæGMí}êÀ1vþ·Ðn¥[j¥¹Y7¢–8¹Få<ì3ûæéÍ)¥[ö%%íÛBÙ€ËUÍ+¼ñ†¡!ƒuõ7fG ͺy[Z4ïcocþ‰ý?Ülè¶üh ñßAÊqJÚvèhÒžuv7M¯„ÂÇw<¾ôÝÏ +Wô;gáÒUÓ2`3kWõÏêÊj[ú¬ù½®=«ìïWG|ŸšºÂV¸¬q^ñoñë¨:;Þgýš¯7í:˜zþzqýó“Í +Ópo¿Aãë:¹ZŽJë`ð%îøÜ +wkœ£‹l†WÅÙééÙ·ŠÊë›:^¿Îj=•?(ûÛ5$å\ZÂ6Þ¾¬ÚîqËÂ4öw6=¬¯oxÜÒñJÿÆáàæhP—¼¿gn‰MÏ<wX7ßË+~ün©f|3 ×
šÞ½FcÐ/yü¸†Ÿ¾’óãÁuÖBÍ Ì¨~gYÿ»Á}ÅGõ9z5ÿÆOû–Ø”å—ÿthâ“5C[~ì“å‡.•fìY¤…šåw®²Ë8qŒÉš©·>SÛûRI}}ñ…p-Öçûsÿ30qˆÉÛÈ‹ê̘S7jž>o*ÍøÖF†+’‹Û~dÃ]ªj›_õëÛ+Îû;k fnû±ºgr +›´õý£i¤«2Íæ=!îÎnìŸøçïc¦Þê´5Z¬E17›§P…6¦¯;·V‹åwû™ã¥`jlð·Ì-Z¬%ñ…θ÷4Cs^Ø\-ÖáéÃm¿·Xƒå›ß2]šº«N®Ó`}qàç'Ó¥·²Ã¬'>ìÊÃiÒ<>ç'¯°býÕÿüýî)ÞË3õ=¸¬QÇÊ䢶éÇ[Cë/ Ë5³wßõFýt6ÖS›ùõþ5ËçLIû´6ÔR˜²Åz w‹ºÖÐ3…/0}Y•¹üf,ç¿Ó<]Rë~›°•´Fïü©¬ýw7b#]u¹G|Ьó7êjÍ‹iëo)ÍŠÙD[±fnNÊ«›0cÛýÜ”@7í1›ü瑼šŽÁ)V¾ÉÐÓ\q-eDz¿h_,NŸéŽäT¶öO©G^·7eÅû.üÄÉÖÈÍÑï=険}6ÒßÑXrýÄƒ9{†¦Ýn蘒ÒƆõÊòÎøf±³#çôZù“®ÁE3ö´ýVúsÚ÷9ã"Íß}>ÿþÓî¡A3õu6Õ_;íëñN$̣Ƕ،‚ꦗzÃûÀ™ŒÃƒ}Íu÷n^LÞëãö{HØf/JȸUÑØÚ¥4ŒŒÇ&·
Lc#†A}Ï˶'u%73Ç®Z8k$ls=·<{íNùƒÇOÛÚ;_¾ê}=øfdtlÌ4.ªÉ46:<(Ï[?¨*¹“wñXTàêE›GÍÃwoâ©Ìë· +ïþZR^
7JŒÙÝÝãhÝÝ]/;ZŸ4T•ÜÍÏÍJ;¿û—_Ìž,lÎ.K¿Þ¾':.áÈÑÔ3™¹wï•”–•—WØ[yyYé½¢‚¼‹§RE®[áî2êñ«ûÒe^«ÖíØ—”œrìxê8vüXJrbldÀÚKÝLš¸wÛìnžž^ËÇ3//ÏeK‹S€òkÎÞÎÎK‚}Ýuaº9{oX†ŸÎ®ž¼7x¿Ò…†ù…`Ï*ü˜ïæà°3XI,À>š˜¿xçÎ b¶«§Dx„÷]$±Rø)1ë‚øïZüˆÙŸÖË8sñ~¦.¥ÌÜBußùùÚûÝ‚ví +ö·÷{Gø…„Yֻȩx;&A4A<7DâO^ði;|ÚMpÄJbóVŠðA¾ÂßÁ¿¾3>Ìù†Ë(âKsyDùLDˆ&yž Ä4)p<Oìý‚H2¬Èb¿DÒ’pŠ,) +H +$Ës<ø%Š”8•Ã^@Š“ãZv2$¢E;E’ÅÿóQ"P$E‰@Å‘4bÂÖ‰H^b)줒§!5M"ç&P)°4R†lä,)HgN˜')ÄðØO‘"§'’’+–8 «:A°TFYò…ì9 +‡D’ÂY`.¡8V°‘’ä@0É(aYhP" (NâÕL”’—Ȉ¼RÍÑ‚MÍ‘œ_«9H$°…ýf•çd8¨L’i` Y#ûp³8 hÓfÊ´M¢åµÐmÈaÜ +cˆ%…u"0ÉÂw6” ´@r,âÕ‚yN朦m©h–ÀÐb`D&Ý’/!TG«NJPD¢vÂAäXFÓ¥8NDE“‚ª$‘,‹äNðJŠJ”ª´ã8pŠ+)NžBr' +爧,9X"3)²jãA‘À,v"’¡m^,äC9j +<‹°Ê!Ž!9‰*
P-Ö'Àþ¢•Hš“ý,`Ђ æË,¯Àñ@M„%]–³˜æmãjJ +k´d/É41nØeñ‰¯ò (®4Þÿí.[lU6µ©T6©ô¦Š³fèîéžîF—,㨀¨À,È12F†A ʼÀ«‚Š…gb<’Q£[1ÝHXVËÈ%`¼ +ï/´JNéýÞëc˜‘k¶jkÚóõ÷¾ß{ßõ¾÷µžbx #¦Î&bÂ!f(3H3õôn“Á×<ÇxÜ!ÈdZ®¨`'͉Œ§ž>ƒ<cåpÂÑ—½Ž‚’‰yœ|óÉjéµê/ò"÷Êp?@]g=làxAZhY=®±ÔP𥲠+(½x‘ƒ\RC˲ÐzȽ‹ „ˆ¥ht¨ SÐ,`ä|IX@ä´ ÓjïÂA3ÁòÚÅæÙ*Aeœ+7\èÏ8Ïß&c23ÍV™æ0[lÖL'I‹cH8Zèb‡ŠÀC±Dá¡ q!!Ð…ö$œœƒAÊA¨F„s£ç@Oš‚…: +\÷Tp9lVºhq©2€y¨¶¡§ûéi4Õ
dA‘vçLkªÝa;p:jƒfZÍéf§Ã–'÷ÈBÃÃOÌþÄîÈç´»û\k2jzAYÎütk²[—W{`ÔÙz;ùoœ.7ÂòùUöŽÖ4Çé)Ô›{}=L·e.ÐZxÓ_Ð3Gû™lK·koFkš-Óýe»MaˆLÐTB™ 5Ò]¼P)šL$9Š‘„iÞh¤ÑœŒŸÂ +U B¤ÞÄÇõ³æg×Ö""éÛg€x~%yö9´])bîÎy»’Ë/Ÿ›ÎÙ{ +&dþƒ(š¬%}’Š0šBÐOÅ=IºUEØÎv`€ôSA,<×'õ×cë¢ó³ÔÚÅ̵!pì·/@ìæÑ ÝÙBµIÒ#WL‹9Ë3ÔÒ뀋‚XzËõHê¸]5ž(¿v¯BRsVQs›w¬°ƒs°ƒŽ–"âð©÷Êéq…ÛÊĬ•kÌà÷½·¼ +“Y½¼¿@‰r‚º +—¡-¯ùvŒÆ†`¢Á46_.cÙhðõ莌͑Q2Smö˜' 5“F<œÇÃ~õÚL.fzô¶Þ4&¥df„ÖÌ—Õu< +{&§;{&™ÎÖ_“Ýv„0ðØ“è;2³l½U¡ïz:v iöFÐzçklá1ˆügu¥–YLA </~CY,¹¥‘3“Åis'¤n€=†ý–u°n‚ôAÛ@“1\˜iV²bÙpÜTœº4kí5zÆ +£í›Ã”SŽåœo€æ’©Õ{ýî2Ω +·ü¤XvÁ´ÛhÒ_íÂfwI[J@mlb&À"ìõVÅ£ëz¬aÙ*V‘¾u(ˆYE>HJ
Ñ.ƾŽ1’
cG¢²Ëìm¸H¢¸¡Œsek”ì +¹ŠÒéëÿ=`j©®”pÝ;Ø)wP¥Oص>SÝzÏ•«h|„Æ_æR_ëè7ßdÇâx"yÚÒLÎ$²ìÔa©0ò2x<`–L¥<ðå|\§2µ3ÜNüÍõ€3!ËnÝ$ɘ|iiÞHÙ~{Á€© +¶FJг,ñÐîTÇìÝ2“B¯4µrUqxWÉÜ©lcs0ÀD4-&‚GÉF4(Üv4*Ýb¥Ž"Èä`RE§#óè?êó)ÖžP×t¦áj/˜s¡€I[™5ߪi±ÔŠ 6‘¤{Ë-ãgÙΫé°:ŽB™@l4’FŸºGfÑé-„mæÐït8kt—%KzŧRãá ¼…}Œ&°5Q$×yN J°[‚¼·‹ûhôƒ®û,û8vu¨ï·}ç¨zŽÃ–jYšÓsJàe™ñ-’eÁõ0!¨·‹'Óä×€ÀÆ&<aÒß”Z.giym#ª°PNn Å•„·ÞÏB€â¹Ëì‰E‹ •74›¢‰¸§4=Œ”c‘Cª]]Â1ªm%<¼X—qÂáAÖIQÇçÎZ‡ÛœÆ@TÉ,§2<h´h1éÀ5}
µš¶=”UПpK™Je‚*TÒ vG@àÏ]ÃÿŽ +ÈŽaz,ⱞ5Â?u̯¡Ž<D#„ýJýÊ£UéÁB*xð¶²ë1åv·ìzU×a«9&rIk?pnåˆDè+Ç(äÛÓ±kS2/¡ï|!‚•ÏÇEç - >žª-("/JÆU!!Þ2v¬z¤mJz:oÞ}¼nÈ5ÐGóÁÂ.–’ôЧÇÕÓÿÖ .-Uˆ +w¬ñ3Û[%êJ£w—R†zYO§ÅKòð„ $<æç8𖵆PÈÅ¡ UüÊ…üêpEÙ \cƒä¢ƒ¢‹Õ1OíhŒw¯Ísr%¢OaÜjÞçY•Ç´X\ôàVšÜn‰¾\A¤óÇaC3¶³èEEÊo¨"w°.Óíûr<þ«†ø#xèGºí‘2“LÞ·{CÉ›-z9ÎX–äm{JÎL™çt{[¶X$£'JîÂ/ogG?¿jÇõ¶©`fŽ¡;?wu-ðO`#ùán}¹ËÍdY1?3.c¡3¶æ£{ rV¶H¾PEôDt™)œH¶úýÒ –ìÞ°`²±<UÖ¨;%oœ0&·-ñôpV3C°Ü×tË'®ñ“1€HŒâü†°oy¥yÇ}L‡,¸ÂGÕÒÄï¿ýûë—ò¡¾ýçë—úñÓ/?~üúßßûøã¿þöçï?þúhå_?c-ìS4 +ÿücB + +Ì\³g3t6äà&˜ŸçƒáôÎôý¦¬ölË,¾ý¤±d"‡.èö[üãbèéSn+? ‡Ïµõ̬Àü¤“ÈÝu«v–Ô¶¾2wOaÏIp„Xq¡îtÁÖü$iâÕTdÁç7÷hÈ£B +Úþ€Iˆ‹¹¦0c?Ëkr0–1Îo°+ÛOØ—‡‡R4í2“Ïn•n€é¼±ÙR·‰ _q´Ý®l<¨´TWoæËØ=
?ë@ðávƳS‰`S²þ¦m'˜;.å|f…xGÈÑê}{×ò®»*$]êq€Ü+’ƒ¿‹Æ€èÃvðÕM!VÆøÐHéXøüú–õm}>(¸ïÀè®H…Gn° +i€É +éúíõì«?®ön8wèŽS†¹ëzaΖZf?\Ïeß˪£È…§KŒ¤§¥¾‰atNd‡#ÝÞ8”þ}¤uºSÿd£dÝrÞç5¹Ÿ{rã>²|9§Vö8ïÔWîÁՆɸÖOMmù »w iÙf$'‡Ø™¾³Áy§¾Ò{mÛþßÛ?zÀÒš7ˆ§·éÖô¬L9Þî¼S?Òvfi_ä¢ûÎ{ô#ON}Å·»b:5”ƒ ¯ôÞߧaåÛ´üê7X æ_²?ggíÌÍמ;ïå(
¥kƒDÝt~œ82ŠÓd +ôŽ +1ƈ)fÓã|ŠJØËh²˜âV¿6IÓ¨ûê?×i´úؾz™>Ec4Ùì}hW”¯;c†c$,i¨5¶ÂÖbL€É±È(S»â˜bzŸjWÊêoø@Ñsý'bž¡&#tã†Æ©4F¯Q™âdþ–á¹3´ KKÐôç$í@Z?Ž$Ó¿8ìê2ÉKL<>!`"¡PÌ‘|‹gôR|¨§ø</‚/Æ ’Äy„@ DJ/ž€Š°Ú˜„&|12ö"x|>AÐÆBÅç‹ R„C‚—ˆV’pYŠ<>%àc1çá8E‹`MD@ˆH¨x8Ÿ$1Æ11ÝJŠGi!"SH/â‘8NZ†£}Q¸Æ ]dÆâ‹%Eà<!I ì>Xô¸€'†c§ÐüP8ŸGòa úæ% H¤'x1==„ +±sÃÄ€ãÄ!ãØ&6IìÄ±Ó +ÒÊSµzªeº1‹y¥
XŠOÔ@‹¥ˆ<Ђœ<1±l+£Bù·ziDÀEÐV£dµ& +«#쉵OL‚B—Qˆ[£Y€“4 WÈ‘ª!è¸i¢]‚Ý šZ¼Rú¨ÖÁejI1¦Ôµp±µ:Å"–) AÎÉZ’$
8¿£:8eúÕ¼(zEÒ€\¥fPå5 £TChs¹ãhÂKç4W›uC‘s•JÂ첈[LÁiÉ‹\kÇÅg+
¹0I´ÒiqüB‰T–²RÒE¡R¹¤•dMaj7²CàÒã…èQ†i6&gR€ +…’ƳÐ8£’
ND
ÞðœQÚÀ7 +1CULPÛË!µ|áv3:v +Î~(6/a€:mB³ +wÚ•±¤'+CQIk,Ç(3<þ ¢ÀE"gÆUaBn5Ç’ÒÌp,îÜå,”¨$Œ‘J©Â±Ò†¶!Dž”"V¥A§¾Õæ¹Vˆ)(€1s¤‘å✩?’M2ç„Js B:SëAÉ2°«“$Ò»(•¨U¥ÚF;
s‰¤¥KK4ÓVäl)V©1ôT®aˆÑÔ†-Ö +›æÎn*«rn-š'
R«EPµTO°+lÇ%@‡¶¡Š„†$$]žëZ;/a—4,VÇ.{󵙦zj9Ýöò{Œa<;ÓGžKTÛ¦s9†R1µÎ–4H®èFÛb@·AÒw>ç~;T©Aᛩp0WcT¶AÐË*'IÈ“H¡š}i;LT*Oõª…cpª¢1o¢&<Fê¨<‘«:µ —p„± ãÊhwÒ€` :pPŠ4Ê6„ kˆ¦sòAŽ¶R't‚
vŒ_ôÂç6$¡ +ÅJ¤z˽M™5ÇîÅ‹M,N²-Äl²4°Hó™*}OówÝ +
Ôîs»`6ƒÎZËÕ‚Á‰‹›Îª"67Ës{‘z<œŒ<3Zó…o–&¶óF¥[ô¦óy½´D˜¶‹Ç"RC2Ø¥ø ‘…ÉYf0
æM£¶85•ZSäé`m“_€YvÞ@P©JFX_%šÑ‘ +àÎy£²Œ% xÐÆ.w$5†Jc‚ňI<±ƒ×‘2°4/ú+Ž!*Nïq¢IbY0›È£S½ÏÓ@Áˆü2hhR¹æ8ÜPP;@nyL¨¥˜÷y‚e—Ë4¨)
½:;D‹ÿ{':;d«kpjjäÈøXëàÌÈØäøÔlKåkZë@a4êg +Ú–Œß£ÓKYÁv:©¶zvv€",ÊOjÄÞ +ÌMmÁÏ7ã2~þsQqòý«onϺg^>É~Þ³íèÜÑ=uðÄnn_OŠw½qå—¶,ÐÐûê{¿:J {f,&ÿª™˜8ýÊ1.ÇsÇnO…ïüüƒ‹ÏÝ7_ÅØ/>¼ðÝì<uŠrïv³2ë{áü[ñ톙çŸBë6™ø¸ùåw?º0ß•-g?¾úÚFº³s/÷Yf6,Ïvž¹|)"cŽ½þ +Å`ÝÖÄ2£?ýøƒ3}MGßÿìêÓ|;ý£×†3? +»ë궒äÈ/’T‡o¾Ý"¶š—‡2ÈZ·¹ÄøÉYe¾¾IÍ6F˜ß]é¢uu1ckýf`cÎÅÃ÷•iðD+ðâ†ç/‡x\;ÅBäÖ^i¬™Mþ…I`j’ùmúÁ[×·¦Z6Ü¿wzAäª#Û¾Ô¦”‚4sJ&»ÑDwóî¹Pn«ÀëQÓ××TÈÑ#é`UZ–cOµüoz+£Çj?»ã³ýûUÌݮ_9aEUy6²M³©¹?ˆàÙ½›W¿oéìÉÑËgNíû<%$»°¾ã%o÷—áýK5sbw¾¤Õgû(ôÕëމώqk.ôKÇ‹VgSEëÒÄÊKO^ýVâêºUé±&¬½ø³0ºZ|éµ&û9qDoX4¢7ÄÑôùèŽÞ9·9·/ÄQÙôêò¤íê¹ã»W¼àÖ¨Ö+î<ë÷ýèóGªÏþpê#@mWì;×ò óyWwwwß÷ŸÝ¿q²zþ¬Q/{Û¬¡ÓK7ì?úïïNëÕëìùËm:ŸFÜ8]»¶xtÿï›5"=· ¨ø#»JJ>™SV}èøÉo÷®-‘üê×_²’>ÈÍËy?é~ÿÿ²¥4<{Ú°´‚´¥¦$©ÏAC’½T°¾“–_‘§,cÔÇCò +†e¦dæd§å•8o*vÞø}NÎLçµ!ÉÒQøñÇ%Îè´ü¬×_©_«õ‚úÎk¯;)“ô>¿TŸðV^uE!аÌü´©33¦õ¶™™››™ýAo{rvQF^AÌ°%µoÈÁr|¦”¨§‘ð4žŠæŒv&OAδÈIyGý§
ÀÃx'„oë_J^àQ¨ÿ!gìÀ¿ö%æ.óv„lj‹„Î,m'ØE’ûÊÎ],ï£à>QFérŸ§È8—S_9ûÔÅ>‘Ž1 + +x`ä.—œj#r¹`\¥ë{L:é&ázDP°x
{Ì1Fáyž2B”BRó±ÚK"— ŽCßET¨D„p1ÅÂ1Ù!Á,FˆÛ€±ÎHoGaa¶£®ô11PJÍ÷ˆ4ñ2¹Û݈+¹Ôôø® +g$\Ä!t +˜„!#LžZÐ Œ™Ø ÚÒt :ÆÈBžé½`ÏÄ&cìÄ•—Vzñ„”5âñ÷ÇÜ4+1*ƒ¾éK +3+ÀÀ•F’T‰@‘iQ0¤CL‹<AÄœ &kˆ +Ú`†™&uÂy:#ƒcK;\[ô¹¬OnäyšÎb’N5)à +ŽŠ;ˆà²ûÛµÙ2 +ñŸFlÁŸÎÅp:I¹¹Td"Ú¾ÝÄ´vÊÿáxü'Šð³GxêWŠÐñ×¹°k}çß<€Ï]íòp>÷®=bùc‘Ü»“nðä/¥KW÷ó ¸¶£Ó3ŠÒ£Ÿ÷J†ÒQ~,ú•ûÿ™îÅLîã +ŒÑý”Û‡{ROuˆ–?Á +¡{7¥êͯÎáŠ' +E™±nM>ælúðÏÅüiJý²,Éõ“\1|ã«Í‹JG¹ù¥(%¿Êå×?sÅŽý[¸pòî}Al°zežPœ}¤U „OlʃÈ~⧡ü }®©¹io&®>üÅÛœýìm[ª„bí1 þr ¼vðÒªü7ÂÞ¸öi‘cÍ'Þ›3JÉÙÚx¸AœÅü/=„…ðÚÓË¡’ý×|„ð‰%BºôDëæ#;7í;}åÂG³¹¨þ[WéärxMvËC`×¥„‹ÛFò€y—¹·õêÅÿÀÏ™·Xxæìºì*]ÙTâósÜš›º±)áÆ¡ùL¸àÓ›¦z<úöi_Ò¼{ÙKË»f,ø¬5âÂNp}`Ç…Q¸åðÚU»›"µ.}±gC¹›ÚJўˑêç÷¬\³ï\8j\?÷ýÕhIó¡5“½€Hk85yõÜ÷WÂ÷gvÌ‹ó”Ò¿]¼çŠÛÆÅuÃ| +O §’}' Ñ°™¬I¬foCá +
ÛŽ^FŽmû°¥˜G
ü¥)X³eY`¹é¨“|uÛ3à=—Õ¹3à°À_®CMGXñå`¹!¡®Üá&D\u“è¶òPãÀºnù†]3-“Èø`^BÝATØh8†%1(¢2¼lúgꙶT'2-@ |&ýD°ÄåÁ½ì˜f
p3±\u€‡bè¦í +-!ÄÁGÒ€ +¦zØj¨gȦCPXâXl!£ ÎŽ:¶àá`;â¡éTÆeÙ*€)r¨ð©cZTcŒezZ„øÙÌÐ!4Dý·t‘ $¶8À†° +FÜûÄA>P'¼V˜"²¸PG<ÉMS³\Ÿ‘R „2aA¹ÊóÏ…šó‡ EâIÚ‚1ÏEdŠo©NÂl7Äjñヲ‚i³--Ý«nÔ†O$ËÕÃLþ¢,¼¶yMFºø1MØrÿe»ZríÈuØVî +¶ü“Ç÷á=гL{ýMRr¹N’Ip#û¨d}HŠ_·Þé›>eCˆc7eÛ +ÈJŽ§p;Ô/ +‡ŽÙ¶ûÝÐ+‹%õx|,nƒz¥NÔ}4Z¼…Æc¾0<³øÂgÄÊj„*Eˆ½[6S– +„ŽñSÞ&ÒÌÕæø˜Ìµâ€7dL‚F + +ˆ©$Xåó,àAÆܸQàÄO’3¯CŸ¶4¶]q>ß"}=>(‡[¢žgÍðî6=l=Gï÷¢|¿j†ª€®‡Ö(Œ+Á©µ”Oí Ø> +Gf)Õ…ÂØó“ÞlDØÎ +ÿ±(ŸŒé)秕Ÿï“²ód$GÀ¸&_ŒéÙ?2–·¼ +m±š:m‘ñ»÷È7¹“û’*ÆÚrw£h46JOî}°8Bj‹)E7ò”›±”¬ÆXˆV:¸¥5Žê$)3FƒévÄ~šVß”T<À:ÒPã,€ Ÿ%Orôt …-×&{eÂ_¹*Eæ‹XK³%ÚqÕ忬ÒV‘B÷F(Æ]Íïl¦…?ñclno~æÑE /p‘aû˜{ùl}âôé]5&# +›V?çf·h·y@Vˆj“ȇÃh6úQósGÿÀ†Š6Hò®Ô*5ʆ?9£§¤×:’"ä%E<¤MÁ«Œär` = +e5vÊ›8+·D¢7”!"'qå<ÎJ˜EG&\ÄÊ&(Àª‘ÊÞR”ïX
ÌÈÜ#Û)̇‘³ÚÒ°ÇF]7`ã7çCªé‘Ó-é—ÿ#l8¬éÇâ%R†É)Å••{”üîZWî°©>…“€ü
ÝâœéYf³•ìfå§é¾æî<P¤‡‹Ê06¦XººDž›!*€¸¼{6–‰½h£ +½ˆ–žFëø0Xù²ïÑj"l …Vs‰†´ +þ›8ÀÊQÙ12ƹnïÜÍŸˆˆÆíÉå-gc:ƒXtmg—–PÂñ†Ä)·è(wßGµ‘ñN=îw¸]FÁ<,{ó¾^^Sº#7ÚXÍxw‡w7ž^?αê]ÆâÊØ"²Rã³u»[ê¤-%§ÆzÕÙLxx¸¼¿…Ù!91K¹«´¹çv’×ÇTÃö‡³/1(°1”ðq÷‚~—%Á¶†ÙÃôP)5¦jEƒÛ:¢×% +endobj +655 0 obj +<< +/Length 11328 +/Filter [/FlateDecode] +>> +stream +H‰¬WÝo_G}äÿá¾ %¢½Ý™ÝÙ!P©Ôª"B•qL1ÄvdWùï9ó±÷ÞŸí< !„ëÌ]ÏÎç9g¥-_¶‘ií½·¥×ÔÖBl”VÎT–‹³§ö¼¦"Ãç5S-j”5s#3ÊšZ§ƒ1<Ôµ–"·u*îV¨W3ö•Ó°“´Ruyãú*‚#°—5¥2¦ªvx¬D©˜±àלÝXSª3äÔ²^ÇiM£ZÊ<Ö’jöÀjnnÄ]yP–™#Ø©“Å€\ÊV4îê–Vé9ÊÀÜůʥ´‚¬=qs;ñð$êš“g6ðYZ›´îƒΛÍCOÃ=ô,‘°Šúhnäµå®‰1£Ž2Cà¶Jã°÷„?ScݪPw#’p¨Â¨£Í*¨½DG•uœ×•Íœ1 —ó0E=ÃXÒv]ñëܘ<ZLdV·áaàHé>½¨ciðÔdôÍÚãw©±ïÅH%ì5{ÕYæui÷p¨y¨Ã?Î^üâǯ^ßÝÿîêâþêöæüîÓò+µ¥å«onî——¯¿¡´\Ü^¸ýxónùÏ?Ï?\.×·ï._-_è±_ë³þœ›·¿ÑŸ_¶®ûpÚÇëýËìdõ/j34:úÐoÆî'ß„Bë&þ%餻±‹¹ú½„Ñϵµ¡mÄÔj^ÈL-Ü6á0–‘8¢¨ùàƒEjî£v,ô_r÷²Ôp48•0VÃ.3úÏÄ7ï[Su’m=¨ou’¶uZ|.§>VÆ à`+jP…qPGjdlyòEȈ3EêD•§ÅÀÀE§>\eÍÝY‘¨tãÜ Xú_Luö<ëú×Û;SkÎ}÷ž›q‹“Ÿ%â ÄCŽm³¦µ†±Ê¦Œ%Þ}T ôœ +€¬Âìuî=6j+)Œ5 À]è¤ìéÝä0‚m¥æðVVøžDÓ¼nøMë6£À—Š¥Œ/\8Ž#‘æF.ÌŸE"÷!ˆ¿‰‡¾â8jFî”’¥z„Ød™é8;äC|LŒSšsc—é"—zHÐO9˜LŠñ#F-6~Eq#z“I¹Ð>_=‡ÏÖœ›`Ôˆí±ì˜´`óZ¨îð%Û0ê&šÇ)Kºd<©ûsâcÐð/¥·Çq&¨$‘0Ž$q[>l¬~i}Ôø›¢Ž«qª:Vð8d:–2N|$"ÍÒ})Pz‘•á’žÌ¹Ç÷Ö¤}IÛ©uÖ´û8*d§e˜PÕ’êÁ¶¾KqJéÉE@³ ýjÄì4¡|jiz[ByŠU;Ñ<Üœ'±1ÅÈS¶sÀ „iÍû#‰6ËÓT.‰Í8)‡¾(²6ôÚŽKè@…rh¸ €ÞÝHÇÓ½&²¡{"l<M +99‡ØmFü†Ý9ú@ÔâÇK¥y%Séî˜Dr 1òin„&Ú}$ É##Y.ÐqÈ Z.(dA +¹iœ’‚ÔÃS“¶$m_dŠ‹®ûé”x㡶´¤ù„¶”å8z£[
È)Oc£JFUö›ç44}ˆ‰êÙPú)%ZÜæŠH)qÓOä‡
ÔÿIþ@çüRÚyúîüþþòîæÛ÷ï¯n.¿¾½ûáÃÝù§Ë»Ó¿ËËWËÛ¿àzÇ¿¿y÷çë¿ß¾Ÿ†ß^þtu3M/¿¿z¸¼[¾¾}uþÓ嫳iy?Þþ|öâ£þò-~ +2¦éðjj)mš¢~ðù®À«¦\‡ó÷ÂXvñ +E›ÃƒÃÅ1H`Gå–g7ãÁN +ÁöaÍôw[9¬á8lÝÿlÓ +z\|J ”ÓÌhº(ÆZÅ ]7yØ;¤×»[Sr^ÂZ2]-Lk=¬–B'•Vjöw«ymÚM·&t†éô6pF_Ô¨âÝ{!ªBS,òÐ{/fÆúyLKT|ð[ÎqœÖ÷böÇÛ#ÏÁ¡Ò$DÎÖX-3Un&¨/{D t˜Óm¸¹ƒP"Q0l![ëªWY_Ú¦‚¥«Ú™=›B¯( +TXüÑØò˜í^¤ƒéš}ûÇdr¿Cû”–#Û¶(ÊFÜ"͘ЧV6<uTsôD¶æÛŽÌé‡Ñ<ªÅ¦%šÇàÝÕ|ŠdÓ0Zá8K£LÞœFñ2><>ŒÇ‡ÔÇ·ñ¶ˆhÜìÆØ™÷˜\ÅÏÅk6Õ¾xZ`o‹‡0¨ºž +‡jiÇ^Ô€ú£ð½ÕÔÝÛ$ÇH9{S°{Sräa“eÅocy¶>O1ë9¢IŸM¢oÊÒ\+_»]BP +:DLZ—az\Gª£¶e:](&®T°†9HFÑJš2H7›‚\0yyß0õþl$G£x®î•UÄ2û®MžÅC%ñ'\öÕ ‹† p"Ɠ긊aŒ†œá‹—Må’*Ñ) &À`õGîÖAP/ŒÙNBCQåÝCå!È^w£uöáqÀÿ¥¼\Vm½Š(Ü|‡Ó±cÇy¿6$âÚÙ€Q1àÛ;.UÿZñl!vB2÷Ÿ¹jÖeÔ7h`çÐÇÕEÆ!'»èçRn.Â
ëq"o¯Ã
•xÍÃi³"?lJo:_¯ýêãJ̯ݾÑïYMÄÆtC&ÑäÇ.Ñä‚íÕ%zêYƒÈd¾nØÜQOMBÎèê'lœÁu?ÑËÚ¾ ж’&UÒ´ŽI0…ŠµÛ(Jð×íûIãÿïeÍô8wxJ™ÛfrnWœwfÔ':²…
|ŠY¬é8/{¶l´¢ÅŒNÕ$ñõ¡ª¡ÔèÑ;˯Թ|»/¦‡ø«»z<J÷µÚˆ¾^ÅÚÆQì/ÑÔUº’‡0q¼ôÿúx%1¼Å +Ç{§—±_°Š…и¤È…Åîq[ä4÷¶”âW¢ducñ´¬ã Åã*uÞ¤/8°Ó
ÔÏÅã‡tøž¿¨;³w7mí +À3¡ +c³k‡]Úͮތò*†‹½íq#àÝ•ˆˆ‡b÷Ëgô"ZºïÀ'#õÖwŒnX±9^*‡¿¦Ä.mFA¦f/œž~®²?&YŽþº¡iÿDWyHR› +û|Û©F=¼¢f]4f +% +Qß÷^?Žâ%œãUEåT¹8‡sÎ'šq8ÃØþñü£þ…RÀ߆Þ_¤ÍUÂhÕ"µ¦ž-?ôÊå¹ÐûAK±à1u›åsíÁÝÒ¸¾-˜ ½Kù˜Èh¬ ‚0O¾ý& +æ¼sÕÇL¸ØÍ`ÖAdfS*È~Íd͈óÂd»ÉKqþ0¨\c¶íW§{1z´¤5E@ÍÈŠÁ½êl¶Ýs º7‰ÎO€(¤¶ÇüR[ÂBÙìpèî(ÏD,³%j—“òê”’]Ç=¡+apˆuù@:*Xn³bÕµcÆ)¯ÅÀúuz“M(â4uÈ¢±€@RÕ<Q
ø.øÁ:vÁEˆÑ_Õ’ óÒ¢•vîL ¸mdíä!|ÿÈ
•D‰úÜÎj5Í$»¿æo5•oŸt¨œ`åKß4ðÕëžÅ¯U1ÿbÞ7•|^û$¥¤…ªAÏħÐÛ–{#ƒìè1»HÛu<‰–¬7·(—ïno·F{ ÙVòB);â´²Rî:=QªÀAd4+.m&ºn,M~àm£¡÷WµÅ=)I$´ïŒÖmæRèT¬uvþ3yìɶA=®!cX°Ù„e¢…9AT¦ÏÓþ÷Ÿ¡ž~ÕRx½ihÇ{þdÍ,(À]…ƒ™¸÷%gËÙRJÌmy¶„LŽ‡tu,c|Ònìþ>ÆŽn¯7sÚ×ÈôIm½I¹ýžÔÆÇãÜ"ÝÇYQô|ߤ<Åj«ZzU’†¦No
Û³ ¥ +œvn”‘ø6®J +P`ŸW¢BkÅãÞÚcNª_‰G½ZdÔÄ">è˜:ÕGZ°xÓÓM8¤ö”õê‘ç•øË´0¼‡ýŠÏRƒªû¥µÌëf€ç¶S¡áì3€<è=hI÷Éó~óêvŒã‰îyüĬ<µ nõ|AÛ BÌÒœoZmšG5Ní¾-ò“ylG¼L»±=aD+’ B l´½*íÁ`“Òçëéx[»Œpn®ÉÄÊÅ´cÁ¡ +yÞ¬ø‘¬VˆWaä^ ¾®"+KÙ'Y +T¦í5†ž0VKYÄGǪ"€”VI'D°¢ZîúR«u¼nlh‡¨1ØÝ\»æ¨]ŽC"÷0^Î"GCCS;DèÜ*YÎ"U¢ “E1
¼I'‹a+DÜõ¥Vëz]ÛÐ Qm°»¹vÍU»¢/ZËÅnC˜úþõSYì£ëÍÑø^¾´ræQîÊæƧ^‹ÍÚ~¾Àï½ã¿²ñLk4)3±3[\žŠi‘žd3±7›0ùºyQ˜Õ×ižOOféÅétl·~.Ë|ˆ¾q1D$‡7lÝ°|ÀB:ð.Oç⦼,u…r£k͉€Nì}Oó*÷H¹ÑÅæüð€–]ÝxÖ½Äø”÷]Õîקi~&dzéñqž>Qí}ƒ¤x_œ9›Œ®[Œ“SR>—ë©ö~†gbßèÊŠñôûOÁðÇe^ÎxVy¶Âj£Ë=ôn1Ɇ[bÛ&¶zÆk^ˆ²ÕÉþJ)Ä?æ¿¡ü÷f“öú:ýìíý„²ù¡ûÕÝÎ͹lnÇwÏøÃõýAîÖݸ‹ ŽzŒ?d˾©Ms=¾kö>’öÔ:ß@š¹íÇ&ÞKrü彿ïJYUçdbœŒÑCñ¥(Òól"ôPTÿ(e†‚ƒ§î£vÖd°k‰ +]S$¸ N<ïQ,"\ëš>~`Úx¿Ÿ'x*ÛU¸Ÿtí1;q¤x|Ó–X×¾¸N¾§/¹³i?S¥‰Yƒå‡óë<»ÜÜ~(Ê«‚¿#ŒŠ²ØÃO0T¼Àò|ñ{V¯_—焯ï¦9¼ä#H‹i!Ì#Þbû‡vÏ‹ÕÇç)^ðÎÇnsÛNz9·®¢g¸ùÇ_ò½™ÝLz\¶Œ˜”óƒl\‚’NxÕl´¡²ƒÍ
¹íŘy‰œã
·_p1 +ÈX|Àjõ°âèš¾ý†Oa=W +z îºê¨ÿ?råÙ¸tg { þy fP–ÛÚ¨ £Èãºôµê‘ºGjBjü©uª¬{°þy°Ž~°~:Ôe7 쪷¹7|½2=ÄXÿŸSnvy4)31šLÄh6+¯N³tr¹Å–:^ÚëÛéô‰¾Ÿ/N¦veUʺz†Ÿ§óüy¤$î;ÈR¸t9N/ßs1 ]_SÒÊbmÅótš;â'ÄÓÛ¼é#€O܃O>wNL¹ì|6=Øül’lz°éÁ¦›G +9ûúúÓ“Xm@ƒéLƒ—`Eñy¸ŠV» ¾ç Gù«¼Ù§›ñϸúCËÛ˜à(ƒpÄeê¤D„ºvNÊÈrE,kÌ…Å›ÑîSûgàóÅš\DNÑr¨5 >N,~ó|bãÀp²ºû€cÊÂGrö|8M‰ˆ¾6|ÀhžÓíó†&Gž*«azœ+½îŒ²¥4)!•àIñ@cŸ:ÇJ˜”?äZ³¤…ò°r‡fQÇÐYiºÇ¬ÂSmCpªªÌÙXðCí›ëÂØÚ”AßÂ7Ö.=ú)þ=›Šƒgg[ë–û·§6òà·;/Çݧ±3È¢è +|ÚCíÌ X½;žä£)±í¨šRÙÿÄ=®8º›Ù“3Ÿ?½=¿¾îšàÃýÿë7øóû®ÎæËÓ®õíwsx6® ãoÿÿ¯ÈEèªB^^]§/oK©¹ùøŒÓÎhâ÷µãˆš€‡*w™æ <=Ù +lA÷Þ€‰ë-£×„:k·ÈE½+J®Ýmïï,°³IÔÈë6vvÕŸ1†nšBÓk©!Tß8‚êÏœAò.¦YM²Y_[,N|ÂCÙÜ73
S3Ó/ÅÍíØqíä–q¨«¢+RÉ°›Géí¨üRH`²ò€'?õ0uþ@/¬ßrX"ËgÖ.¥wTêÚÂÚðÑÃû#R¯:ˆ„3 v³u-×:Y¡W”÷ã¾°xº0êüQ…£±N`"«Ýʸ®L'ùón:jWqF`‹t±ž“ÊíA#â3=à ”ͨ<¾Õ½‘S+úÓ]Dí*°àÁÐjÄ÷ô(•÷ùt3þ9 +#äýg”-“–{ÌøS©Š½!ך%-4àIw¾²Üã*Uþʪ ùK´ ºh{B,ç/äÈEB®]÷e +±î 4HÌ´D´êF3O%z¥T¢›1fõZ"Ó¦Ñ ›3a|i§±PèѧüÑÛ¥r@”ÆÖÄcçmæ:©í·‘‡×#€| nÍÆ1{Ú„#4³h|¥Öi¥mÿ”OõFÂuVüâåbå‹w”×=5óÜ‚šw*•ÅPýÏ©;T
¡ÄÎßÄæ²ÿù¨ÂK{Šo Ãq©eCIht5¢)Xì¥ñ1‚Äe£éÛ%×W]ëÚ‡“[JÈʧ¿,N«Á4-¥`ÚyL@©¾‡(½ +–ËGíOäçL˜áÚåd‚-^ŒÒ+ŒøL¤¨2HÈ&_òm°‰ó2ä͈µ?MÜ+•˜_?pç‚—;s(âY +Ï_ŠXDlÈbTÎV>Ú!ןP¶È¾—ZÒ.‘5+~5eJqÏö‡ïº~L…òô¾ëóóH™u°_»\«¹ÁâœzKs+²zÄíSâò$ÉÂwmº K™1C‰!£q|w§ãXM³k¦6ºCÑu•þúrà¾W+ÔéFj±™…Ó· ×¾ +Îq8Úäp”ïá‰Dÿ=×úãQòr´ÒH‚¤Ó5[‡’´iG
©¬!ºaÇõŽ±IGšG9ªÈ "
™sêÚáE¥±ù𶦹Æ.§¹Ã‡†Ä÷ {äA +endobj +656 0 obj +<< +/Length 12786 +/Filter [/FlateDecode] +>> +stream +H‰ìWmoÛ8¾Ïòx]ìÂZ[²lÇI´Ín^»×KÑ+Zbd6´¨¥¨¤î‡ýí7¤DI–íØmÓÔ·ÛH#Î3Ãy†Ã™!A/(“DÔ‘?Íô=ÛyÒtqL½ì{çI
þdh$gŒ¬T9¢ž¤<Äb†öQóœË!ñ¸ð‰¯¥Ê@ª¿z;O¬F·×uÕ°œŽúo¯Ý±Õ—åì¡#[ï`¡Ë™úúþú +œöâ«‚(b¹$ˆbKÌÐkôÜ+"X€6Ÿmr àÚí–õeáì¾åÿ=)ÂaY8\FBÿ«®“¶PÜ'«ÑÚð¶”nIacµëó<ú|ñ$ôKTúLÃ5T¡˜|vµpÇPÂòÊGÀ>%N=³dg6Óßn t]÷GùQgþÊu¦ý×®3
«ÓÞƒ0tõ°Òk;?f–µäÿ«–ìoM-é>B-ytR½oF +tŽ?/QÞ¤Bm–ù]˜9¬^ÆSzM£¦ÉÃæ!P¹%9òKíµ‘ÄÞÍgÖç‘ü†Ü¯ÔÒìBdàõ'’‹#겞˜Nz<
˶{è·j½¶³Ãrà°:Vå´°R°KûÜs¨•=Z¨T?sºVšÅ+c´û 1úÆ·RQê<ÆØRåÑç¡ŸP¹ŠÃýÊ<¼l>™,âWL&÷S<ôù˜ ]®¹˜.öÖ\„Ž±7i`-o¯%›6ל†4º,aß0Ölº¨9$ÄS~K^]
dlÈËoòå¶éÌa2íóˆ’l9[h8à4”óñƒ}}>5mÜi÷ŒmÁ%–äꈂd–º
k×Ù +4ž?ÏÖ]7p +ÓDÐ;Þ°ÑIy9K¦Fb«Á.óZGCoòU•0Õ*Ürñx†`9?iê–ʧt—Æ¿ž+ýF»¥~©½ì4;ûøèÃE?%Þ„”Ï·ÑVö:ê—½—¥,“s\;l"uz)€{<w1i˜4 ¥ŒM“'£"¡ßÅÕ•Ô«A0½KÏPvwÌu²‘II¹2åºæâåà’•‘$„Áá2š†r*¼di5g¬ò‰Wö‚„qF_ìu.”±%gÔ÷ñnfkM”¡%G$&Pâ_qA?©£Xc¥ +7–LÁs'ç„tCgŸu\¹Î +ûçä2_¯5Z +ƒè¦fæU´™_q`±™¾ÁjE(&t3µ©•."IaZC£ßÁЦn/(iS£ˆ +“¬ë,¬V|ÉÈfŠ[ž j/ƒ;¥Úv©›àÜry*0¢¼€”ñŸOFd¦ªÊÜ”9ld¦ÏWuV÷ƒHg€ª^y2èUöÊÚ~UenÈyŸdÉ6s½=kàF¦›ø¢J¶¼Øº
ܾ«Ê÷µö£Ûû‚fÑô‹¦žuO_P)uz§ÕÑ}¼íh¢Ž:èÏÝ"¯Ò.]µQôî[LÇù\ŒÅåyçÉy”M×ZfæëSƒçæ•ê4Ž‚8‚×LŸ3¦f=ó¶ò@Ðk +MUD,¼ÉT=µôÕš âhB½º¾ì³ˆ3yâA©™Eúï#Á£HM"ÆDæëç0Vä#fso2æj‚UJ<Ã+–éxœÚ|…©@.f¬¾øjHY8,m¥ÂÑl:æL™ú›.ÝxLô¦Ÿ3.¢ ¯ë¢<Á“PûúB +m=÷‘7@qJ<v//€ò]ßNNvåPÕ >©œmùݼƒo4®ax@_³ˆNy¦P‘Éúk:¬ +6KéØ,¥c³”ŽÍR*6KéØ,¥b³Âæí6K©Ø,ÙÍNiŽÍaÃá>)›¥Tl–R±Y\ÈæʧbÆRYLAe1•ÅtTÓQYLEe1•ÅTTC¨\âlÒPYR9ššâ•cÝ'¥²˜ŠÊâ•#F#ÛÌÝ æÖÊÒ,ˆ«›2<J³–†€Ç¿¬Ö9yG%•à®ÂZÊ', ì&;$0ÞA&ëÏ슔Öz¼;ý…=_ª&;Õx¥ +n~IœŒ·ÁÙeзq&qÑÖ͹íÓ·ë&r¡ü™s³&q›³QLj)Ä¿UDX]¸K¦‹£bÜØ]H¨ŽMvÀQdUu‘Ö¨T@÷{ºh‚”ÊBZÞ^”Ö΀˘9ï*!¼ƒÄØk½?`‰Ñ-ÔsÁ¡Ó‚Wõ^בàQ‰°L†Ó”ÒÙùÓKg9S³t¦¾SŠ‚Ú\ÛÑGm…ïÑl#D
¸ˆ·éD˾gsqÃì%!c\%©$ˆO" Ô=drs©ƒWMK$ªñ¸è66±:€Ê#×ô£©›š7*DåÅ«øä +—×Æ +UêâwäW·¡§.æÔ-ê¤a|©ËùB„ñ Q>; 7ú¿Q²î«Ð•Óé%.Nxie9ªq»ˆÒJ‘÷2kœª¨³¦‰Ëé?E=ü_4h™¸¢Žá_—4`ª¦[_Æ‘sŒBÙ=êÞéè#Dpü¯ëF’陈(uö¨k*º‘DYÜq°a0f‚9&5Mg0FTKéêpz¢þËoº»cá^9Œ½ðÓdbóÕ€³Rc7SŪ=Bðé1ÛÊXŠ‰ÏÇüH¼ÀìWÍÔ\˪Âç[IúU†‰Ð䃩ûãÿÿç¤éÿŠÚ¥›J£±“¯‚o
9ž×Ž +ËEšýù¥Ú<S{õì•\*¾•_VsǯGÛ÷[¢¶ö¾™ÎÇýŽúkËþ^_ZûG|·v|×ÝeƒåK×z§¸g=ï£Gs°ñZÇŽ7¸OK7öíjÉìöéÄèèmTjÞ×¾rTѪuóŸÚ}]—N¯ç{+¦þã$ÏNŽö›mãàÛæ[Åñ·vt²´’£[ËWkǶa’f”<ã%2y8/6÷ÎÏ‹½[±Ìk~Zè]î¾Þü(‚ùóQm%œ:)ä—*Ù«•i~¸_TóXç®önë5‰,Wו·;øõÇ×*"e»¶ÖnÝW›¥›k»Ò®WóÇË¥Nɼ姣Ëâ~çÊ6iþtÿ±ÕÞª~ÕšÛõÎñ¾ +¢;uv'¿àáJá +ýXã—{Áï„míðº0 Ï"=¶Z»ò¤æã‹¿ÛûÚ9Éí¼íÿ†Ù¬¨<Tg£>‘çöÓUxÔ“í—ü^ãE€0Àã¨/Ëç…Qzóä[xÔƒ¥UB¥o$ÂL_JO¹/òÞ°¨ôûz{cAÔü`UùþPšË>Èý+~7aÇýRÉ}½µŒVXT±^nD-|]6Iö0Õ¡À8ðËXïß„G=Y:X‘Î~uB£žœÊ…–:¯céÌfÿc½:×SU¢è³`EM +E)¨*Œì÷
·8Ôá¾?_70¨©q (ÙX$ê 9ªðU¶yÑ5FôÖù¬†Ffœ‰í«¨£j=MvEW‘Õ?§“ÔÕ×n3R˜)#Q-ÒgÀØj_ÜÊ]Fê÷%jÀj_®Kê˜ÉºG-õ<o´„bxj涑$Úö8'˾¸Æ°Ø[JW¨LèbÐÃBàØ»òl¨=@e<F_‹Ý΃°—Si £ÂL»˜Ý£Qs»Hj±/רõ(åÅ¢˜Â¬îe0îv=L}è´ Qò¬Üí8HÔµþÔQ•ys\ŸWGj–i¶14jÑkoJ©X… +cÓÚæ-Xw[ìåˆC}a^“ZJˆÃAµßWPŒÑÝþË2‡E]´ýS ƒÚ1ýÃʃB0Ky±ýz ‡¼H’‡9Ï‹º·×]µÝdÄ_yUÉ4C¯({,Dªïª÷ÉX<ßÃàPCx†âqvŠcZA娄CÖQt·ÈŒèTðîbwšg#êaCÆ5ÔcÒeð•è»NÀpé6›»î‹îý¡™ jê¶Cå<'_c¬Õ•-5É<:*ìа/º·¾òHöÚ"™Â5ªm¿OÖ5môuÏÏ6§4N{³Ý¿ˆ¨“G‰M¤úd Ù²™ê ‡ë^-7ÉÁ&‚=mWÙ¯–áôb¬ùçŒ<²ã>ŸƒF,˜SÒ•ÑÔÎl1Þ6›@ˆž,?,C†õÜò†=
Vr‘ö4ÄѵÑ)æã…ýœçÈÎ;ö´èZ'9ÃéiàÂäå#ŠýüÅò>‘1§!™”c̓zúAí·¤5]ÿNÊõƒµDš“|}=m ®å{újŸ<gÒPzB$Žý|dmØÓű²•
§W¤-[\ºŠýük)%¸SðJ5êÇž˜<çK¼a/¼¯'õ"î”$È\ÆžŠ|i2»C™ç’÷9Op6gsê—1NŸ¼Ž8¢7å)»ÕCR»yŠ>oŒ²4yês×`w©ªë^ÆCgÇí Ÿùœ7†éÕŒ'²
&×+Ò’³Êñ©]ò=‚Ùo©ÖG³Î7öW¥añÒÔ'Ž÷]»+½9”¯þÖ¡”ŽaCDYil]ˆ² +CÿíÞØþä«ÞzXžq%v³Ø‚ᘇæˆÓîÙIÃcoœŠnY€ò.«ÿäùÅo„ÈVkØÂQñJB˜Ñ¢])@¸îtODÞ¹—±°áž›Û«‰H¦ÛþñI°¨Mlµ¥‰{ +d³‚z^ +ÏÀ&P]‘ÍQ„¨[…†ûNÍ3sßP1HÐÄVß+€¦]WŸß´÷öìMýó©„Ü4”ô½ +F«/¾þÝäΧ5ø¯hßá°Ü½”2ÉH]‰þY“)`l†&>AÒ ã‰c +β©)}ò¨¾~ÚW|Ó‡J±ðõ±—ÄPíY{·ü¾s%*äF›m6«}f› +Ê4‚¤ÛÒÏó™`W@2¼‚×*m3€ç°(ŽàIJºvD¸vÄ~ñ¡Ld´«D.¦í•;¬Y§y#ÀÝK6i @ßµî`°v”J¢*é©=™ážü6á0L“ã5ÕgšŸˆÈˆ€¬øPDt‡•m4²üMD´pd+_ +\>Ù"L<©ýŒßÈ’ŒIèµEE%^ +R/Â]U¡Vë¼YÌ\˜f±’Vo- +Œù†bžmmLÔ£!àêÆƼu3t~Ǧ"ÕO„²c%ÿ¨¾`Ó´¸}„Í ¾©ÓÓ”¤CŒÆæ…{L\, çyóû}LF(\L¦éÂ)Œ¸ôÛÜ(Gïo$Ø8&Mmœ¦kô˜´=Bš¢ØÒh~""IK²…âßÖ¶DZâ`hD÷·Vœ|‘¯
6˜‡ÌÒmbéôB2k¬›;fµ·ÿ‡*Ý&EÙ˜i1&¨â5ÎUÐÍÂjnpùSFnT£Òó +ªNûõè…»jº©ïÆ|p@<qýÃ$™åP(ìÒ‰°l[œP9fJÅûtÀL»`DédŠBfuÿ°~¬è›©®.hYäŒ×ÖJ¦=:ñƒ°´#ßSµÀÏj¡ž}F9ìµEŇêæ¾ÏGõˈèÖ©ÞœRf66JTüö“tG½Q + ‘KwÂùÿS(Aòª( š$ÉæoÖãŠi^äA¢fÚ-4-Ù‰^[Äa3Äë?Ö«´+q% +þ@¾٠AБUAQFÑ'*ûÿÝ@:IwÒ|ÉqæpnߥnUÝóe"ßÖæ9ÍÉžƒq£\B’–œŸ¦yÏ}â¼ÞÛði²9)tnÝiZ~e²ôúiâ{Ã)f‰V\ò}382'ðŒ>4#ÈìršñÞ‘–|æøV³6MÃþÄ»•ÉØ7_T|‘̬f“‰vq[âõÛe +Ëòhé’ÞÜ/ÍiH‹˜i‡êúÌ¥—½°*×{ÆÏЮÔ9ÓMÉŸ½‰xç‘K•É@pÒiƒîœ¡ÕÓö2ÉWÇb¼;ˆÖ=‘§$VP*Ö¡ùõÕHãyX4¾^»19wï›á=‰â eùÑ2}VlÝ0lb+}ûNî4œ¹+G®~ÕB» +EV» pN6¦Y–å1‹sfCšvìú%{é þ›•Z´@9$I©EèJ=ñÞ¬˜©“E©¨JíXo@æ¼s¥Ï ±¦tz¼rG©Ñ3j–Wtì¤ÔTØu.æH©h[8ñÞ:ÛBj…{²qcÇ+ªRƒ´˜s¥V¬ºñ<1tÎäâ+5:<€X›•úum¡Ô… ³KëP
M©A‚O[›É½o ‚À«ÁA.ßiºa©<œÚs£#§ÒéëÚ}/0tݼ®xæy&¬²Ñ4"øÌ:wg|q;x˜ù}ÌYHjû ¹¶? +u²N°,, G¹‘*¢ýðße;UŽ„ÚçEc)¶Z«j9ß8û:”^•=’²7ö¶ «¦] T3ÝðÒóF&!<C&"P’,jqÛ[rÏBhoÔàOÖìz3÷20¹)OÙÃ! §zÎ7$5(Å°0•H)ÂjªÃs{Ó¤ÁxäH¤Y<Ý°Zoõö[èX-ž ¹o†‹Ó’ªÃWfY_œð¯%þLµðßKt(ô¢Ã…Ðý¶ª±ÜâÎÔª'Ζ!^h
C[os'Æï>Këujóá‰ýv^=ÑûÉÒ/žX3/y"åy
þ5Ïx¢Ÿ_}ø‰Ãÿ®{¢b·?ñôM¾$ôæ?3éßCå¬FßÃÒHœôÊ7õкX+{¨‹ó˜·þºgP#K±pÊ#´ý¶Ù¤9¤¼ûû&{ù5½–¦±«:â™$Ÿ}É·Éý©tµyYü®HR9
ûi§Ðf¹ÞýÇØ~Wà÷³±ÉÒ[a ~÷–`¬Æ[ÿâÙB.ÓL¿“=ç¬Õȱ<Ò;ÊÜJÿV3¸¡ìcö¦¹ìf?×›BdsØš°X
B‘²/ï˜oC††àñÇ›\#3ü¼)î©Ã÷S¯Íš¹Ämóýü3ì.¿ÓëuÉœ•Ô…Ú¤¾…¾ðK×Ô#¥âO—šà>»¬Ï·ÊîÆÖÙgÖëÌè‰!A0‘ŸCû’õE¿oÈqš9AîæýóÍ®}™Ñ;Kv¹?!bhû¥?O²mûr³ßtñ•”â°;X‚|àüEœõÅ4üi†,i/@6ŽaHÛ…/‡LD@ó3PMg„tî
^.4Kª<Å#_Mþ
íUðŒáa‹W3¡¶òª"N©›áU¡îŸ¨I@5Êþ.{W¥ÿ¶C| EÂoqᎯ< ;øßÎk°^ xÒ+ô¾=뺔ë&pͬïÖ6j¤ +sì Áß“®ª6÷sQìܵ…Þn‚Ñþ„PD†O¯Ô¢A©"ÍbøÚÜÒî<$¸x íWjà(&['è$;xF±$Ö(ŒY Ê ²›ç¯IÙáVݯ¹Í„&߸‘Hi>ÊHkÊæ(k<'õKÐäO™È"Ù˜E´PpoT‡ÇÍW#à@ìÒÅ‚$Çœs1b]0x†ŠBò÷wžf¸³Ùfµc0ÜzŸxZ﹟Ää,°ºÓC´ž üä,Ðйä“z˜0ô9›Sœ=Ü_kZíØmý¥±›D`·y=ÙÐ:ŠÚÑôRÂ/’R¡ÅUƒæ…ó½‰ì÷ñÀ3JÈç]µeRý¨hDHã‘×0/xßï̇–˜Ò´˜Ïµ¦±–`ð-ŽzU‚âÄA,Ó¢çdÞe,'U=YÓ2®kN¡i‚–â Å÷Îâ@ +Ótˆ–ÏдEäRÓ»n›vk<M-´5†Ïè6¹NÝäcÖxšÚ¡ŸÀÃúӚëRC6s”i/µ1j5dë¦0aÁ¯k÷ï};Hh¨“Ôn“zrV‹?9€…Vz¯¨!
V¯Úb¼.^<qhƯjŸ¥Øjªå|ãìkÿô›œ|L$[…"TC$=’W¿êV!üI1_Ót—
'«Ÿ“!6Êå^HVשßxçyx
R¯¿>ØfcV£ÓgSá[7n6ZY·g#·HFGyNmÏTæ•=‰yÁÓ÷q&BA°Ö<·¶]¢ÎÆ8sF_ËÜMÔ4y¼bpŽNº™b5·¬ôº™ª>æ›ÖÝ<̆néHÒ¸-¯h·AÐE¨#¥q[Þ±^‹ðŒBÆŸL +ßé+Œ*–ãoêå¯%ª‘JPj15Mý¨M»ÍSšV1¹¢£›–®Ü÷oÝi…ˆ¨W+
ªpƒg¥rº_8Ïé`9ôiñÒóF>¹U¿€gNn•ùª£°À!-+Ϫ_3¬ÐRp…LåÄ»•ñ3j©ÅÍõ¹ÀÜ/gI¾:þãÝA´î‰<%±WŸ7[Ö)á^ AAõ×µÃQ™/S<Á•ñ8µž—uvÚÐ k!1¤¥ùQ àÇ>¤šñ‚·AÎyÁ8Œ :ä«ì ž7Ÿ¦Ó•¨”ëçšDðZ;aÈŒÖ$Œ¨SJ +éW‚âÖ¡Àéz¸ŸÕŸÇ,»E§ðÌi‹B0$¾¹1¸žû‡›OR;Ö +~šfröHK®BÀ~ÏAHÓž“B’—ÜôYæu¾LäÛ†yOˆK6&C‘Ö´ kMK¾O^öÓô+–£ÅÁ‡»$šHŸåWFãí<'h”–y‰iÙçdÚ9˜“ +h‡š‚L9ížØï)clîÌW_d3|Ïü‡ÿ[ø.aç’^¡÷íY×¥\7¿ZèxX¦d`!i<¯ãz£pÚ£˜|ú¨*Þoöi¤õ`=AÈúº)HÓ6~ÀÆY©Ì²•>+¶nXæõÞŽsÚlMs¸u£i~~´SÖ—|ß̬sR©“!-âڱ愞9¤5ã½#WZ•5ä}%-¿Án“˜Îá3Š«zÓ´Q`xÚQí;$ì#§–˜PH
d7Ú1ª.¡jhÒ3g•ak
Æ 0ì;&µ™×oãì`ŠÚcÝ&S:wµc†€•¨£™3j1éHoöáÃÕ¯º#®¢TŠ°ï%Å€A–ÿg½J»ReâøgAIÜÃ%÷,ÐD»ä–¤éÕÔ|Ê®[}ÿg”uÆôMçÔ¡ùï¿¥:Ü^lÓN'dÃ0†'+ÑÁYOªcQ«.øKéeçs?cQÕ†&Ï|·;‚¶0Ÿ¾s–ƒ¯Ô¡9Ù
˜b<J®:ObðSyÒæ*Íï0îOnôê3Ó¬v†£^AïÉΘ ¯ß† +6¨ó0»;9âGq$ÎA¯Õ8ß +2Š«ƒ1PþtSæ”GAK§ÁT$0E&ƒ@ŠÀ‚4Ô¦ÇÊ°s òpí¸à_ú+m¡õq—C„†büë¾z ¾ƒÌG/¸iÕ±ìÌ¿»×][J.ƒÎÙA”3wΧ#™³µÑúhm$ bŠÅÊ2]Ûp¢¸-³5úC+©!‘wZÐT”ÃW#oôá59~øçû²þ†]0Û8vàBŸŒò KÙsÒÀÁÏò&q×ë4/Ñ9.«9ÃP˜¤‡×ºVSè¬Øä
AÿtŒ8šBó,ñ ÍÔ.Ò/–Ü8åɘµÿ1zÆá6
ó$Š©¿˜z¸Õ™Ú:ɘxÉ$‡ó.=ŸÃÔÇ°vzâ S?+a +'ž¦‹þ>iÓ~ÏÔx„»SkJcj°öÒ=Ž©û¿dj|5ã+Sû3Ú)šÖTÙF‹iC>+’®C·¨;ÇÂ)ønJ]ZäüÉËjå'M +EqæŽ}€w†QÇq?t—{jª]kê?„QgÎÎ'ãî)Obn?çÆ‚åÁÅš7È•8ÁÇ=—õQPÀ´AJ/mL†[Úã"÷0»e@ýÓ=+ß¹h«¨Ð€5–T‰¨N;O” +þ"ÁO+TDß°ñ”gsßcÚô[Yù½ô”hg*ïËJ(T¼]¦ètvQˆãTIì š~Íãïáä>¿‰y¸zÈ{sd^߬fÍèPÑ›õ@~.ró ½ÞgjÞîf58ÎË÷…ý.é‰ç²Îß0ʧÅ\e »>y³ôOéÖ'ÁwÁ«µÖ¿ÃW“ò’U“òÒéVà•¤ðé¿D~LðäÕŸÿš‚ï|±úø¬æa}?~êý*‹zæºYŸ§Á¯Ao)ý²Jìv*M4…Ãîã«R§ôOP½‘²9â3ïaöé&®CGÊ#'›üËÛvï7zI5jÊòî™´o§-y¥´Þv¨±üwƒ•%;HkÖ“W‚÷a%ÈŽÄ‚tƒŽ ‚‰$¹¿níKÊclv +Bë VéI‚Õ'ï‹kv™š¾/ÉM°EҾ̣ØsΆQäù® %È•ÚáO¼ñ$«0iŒ +û›¨Éaõ°i†À¨¨¡Üú›‡QU·ÆÆî—•er¬$!@6âø7©“:°Æêý‡“ç}õo€å¡0H/ág•Î?ý‘JíŸn\ù@çL +6;2gÊ@q1±á>’‚•ó”ÏE'R•¾,ò„I¥êå'‹k4;ŒbpìücÞ#XA¥ÌZnÄâó ùúí‚`ªA'Ê¿¾Ð&Í
F•\¶‡C8KÏê!p·/¨*ú7‡²Ž—_²uÒª1´ÑÃØi=4Ôš·”ZZÿÃ!4¡–w·qÈvèwDï=’O½sô¨F¼nYA˜_ºV듯?eë/á +-æÕqTà GÅe‹ƒƒ» ~|lmÒçðM‹x/Ó4v>wmD€(øQ}‹g+Ž³Ý2"§¸¥µ`¶}÷%ÃçÂhi]_¨Uv(¨è$}H˧#N\ÓlÇkÍnº= 4cF:ê’g<¯qg, ÎØVÍ À6ÛèÕfØõnÜÀ4a#XÌä +@§£¿Ñjï\pþݽF`D³V³¨DŽÛj^½ôvÞPººÆêã)=¢¢P˜ßW–éÚ„áDq[.fkôÇ1ú¬ÊΘ ïJ&¤ˆ7«*Ê…ãµ×:–.ý/À +endobj +657 0 obj +<< +/Length 18826 +/Filter [/FlateDecode] +>> +stream +H‰¬WiWêÊýþÖz?‚)˜ +Çx¦‘nrùùºÄIý0øˆ¡Y±M·àäÿþÇÇe»ã+>ú6_s’¤øà‡²· êÏà}]´09Ž•M&qö€z“³TÅÞdPûÂÁBúmYMB+
à&¾Žy»÷—К½7~¡¤ýAðNdh·
æ¯þçI‡øê5—Ù¥žå+ð·¥}tø:º®¡°¥TiR‚Ù¼ló*/¤®¿îbwOð7ÆÏ>¾I7ù6?9´F™uVPï/\›lºQ»tmJõiáŸ|Üe¤…%,ŠÕ|ü¬›Øtê|ùñAS„+||•\ŒQ`ý2ý¶ZënĦ@ŸSÙwP¥ãš› +A“fÉ•Ì +äó9ª9ÿÎ.{š0 Ð,Ñnˆ€’ÐŒ:G¸!ŠÐŒæŸRÕß¡5›=ª\™Ü²œ´‘–‰›Æc=×Úv¢0À‡=@e˜¦â” +êé›ÓD6Ò– +›Ûq~þšá£,Œ¼lÖÆ‘x}¤E ³À´@™‚ìPCKJuUô-úúfÙUe.ëWT{ÐvŸhµ´¬ï2 ŲÝví2 Ù²>7h°I±æ'ìH93¦ìÃêÎ)È‘"&AyY—Œ˜Pœ•Å1!!° +Klj¨QcQ!²\•ï
ãÎZö’0roJ„\§#WXG% +¸Á…kS&LÜWÑ”£èP§‘ÜxÎR¼l|> +“»Ï¨'['óÞ¨÷Žæƺ"Ž¥ˆN_ (*ÂOGÃŽSE«¥HËmÖ/oèÚüŒBÐœe07V0CY!VüPÚ`øžö?Ñb¨gcÀˆÃn½±ÀP'›=ŒâÙš ö$󒔪k´$7˜=©ŠÙ“ +{AucZ€_¼Ìáƒ?MB©~ª§‚áa
\!u:ÜÃÉ“ÃëФno÷±å´˜7-˜Güzq•ªŸP€ADÍ1Ð0qPÍÝ:¨J*³.\r©åÉ1@"›ã°Ž R“QÄt úû÷L¨N© +É_¨&É'Ö’Í´aH«² +ëP»h>áT°PùézöÌÆÑؘNvmû˜Œ£ð8¬wÁÝÿ=€˜4™nìÃòÙŸ +@Ö!ÇrÀ ¼¬K¿fcPÈ›È;44ÓŽ(V󼦛ÎÕ^®ƒº¯„Ü)‰ÊÇÈ1“áN}Àœ6u°ÅHRá3Q^ȪºÌK©²÷kŸRR¢b_´ 9›zšMq™Ê¡<n.xßðïì2jjÈ¿`ybßM¢&ÒÖúÜ_IJÝví7Èí†%s—8rPƒtÍ».ù(´Ë– GË;õÄÓ0F^¶|¡N“OÄ_ðNäó.„¿ùÈDêéÐ$N©7VJ=X"Yb}!¥·¤ƒ•Ãº—þ8WjØðçd¥¹‡4hÔº-ÊË(5@îšžll‘s ÔˆlÈS8X^F©¡døBJ=r×°û÷™æT©õUÝJ©r¿UjýðÀ)5hûúI©{Ž”ú ¢RW¶•ó'÷£x$°ælÛ>í73ßIÏϹ[ÜFN¤Sà<».º¤ýñËî Ä™—z`@JY¾‹Q%µq˦5ªAÕ·3£}áÜ`jÎOGÃŽ“vKû#B0ÿU´9 +;ÈV8Áã.¶}L,pÔAx ã¡æÞ¸*W&·,ȼæ…üàCŽ4;¡¢‹{æS*D®ô¹±Y›@"Áƒl”Á¬ˆÍ&ß›`ÏHMo°DÄÚ·E`Ù²†7:ãvì6^k®í˜ÜgIgl¨Y¿¢R¶Ûž²j)\ˆ4eÌ{£ÞÓè
éP:®y Ptp¾7$_œ†=XÃ$¹Ò§Û·™S0§
€) +Db÷ï™Ý<®£ß¹vî_Q»ƒPç¨Ï:Y¿Ž¢V…Ù ¥ÑÝ×”§¿Ëw8¨ñI¼/£F]³ fµoèBn¾ìpe ø³ÏßÄ耛]€ñÀM€îâ(b1:¾¨fêFtÐ
>@P‘øƒüf_¼?¤ˆ¸Y»K^‘&ÀRÃód_²|Ô/»è užØ¤ªïƒÚ¥‰NQ:p³Ù(çZ¡™Øhô5J<ô +)6ÜŽWÛX?Çk|poíõ¨cÃ+—žoèU»Ìøèöî³ð¢ ŠPydåo½-îâûc«üíO{Ú¿E{0v,$ÒÅíwâ¦ñXϵ¶èþ¦€¾SKåañ7ÇÑ"Ü`.((?¼;×úvŠJ²yu(zufnVeÁ;‘7;à†´ÜÉÀ$[´<1›
þC Õ™¥ó +ÄP°ÁP‡/xrMÄCÍ=†Ýý#1´©V±?Ùlà€, !Ò(î!øa´9Ç—êUÁ§zX_¨d“/Û¼JqKjœæóíªUgYl©”AßmsÁÞŸ +eæÑ;nRËBäW–a^X8÷ÛG„fäIà +Þìû>oÀj¡L_žƒ!‰:Îþwf +——w-²ÂßÐïù®÷¼Æë™êí,OJp*CÐ +XÐü–X¹rÐÈØ´¡|Û©8F¨ÿÌ;ר6'•ìÍ5i‰9ᨛÆâ´ÈØ… 'pËzs•Ð«î4Gž÷XŽaУ¥ç`F‡ƒT¦Xë…Bø€„-Ãã|@á"R IزÔuRí Œz㊤4E˜ùd<î¤ûàI¿<¿'b£"¬{“¡ +m¾fú–ŽhlT‘ôS+ŽœŠì|¿ìÊKdsC“3Y$±doË¥¬Ó4r)ß÷ÿ1T®§jªµVô…ËædDï±(™ƒl7dO"}‚Q‚¡™ˆ;iÿuq'Éû-‘íQ†ãæqë‰Ôÿt'KÚ0ÓÈ +–ŸÓðlËìŒg
Q3nßeC1®?ÑfÌhŸ ©UцFŸÕ»-îu’mh³n +½ap’_FH>¦_ù²{¾Ðžì÷]®›g0\;ÀsùÎæ9 ê©J‹€ÖÉrìÈô† +Vh'Ýe¸7zë{iwÞqÒî&ȧ÷&×LúÉ€óõ\u9Sœ=ÑàÝæ/ö¤@û™»I`!U>ðˆ“†á`øÑ·V0EªôFŸ?Å'So‘‰L¨ßƒ½9?)›`ö¾V\ÛüTþø$AÚ¨× +o4Á`Qóõµü<nŸÄÂë.ÁøÀÞHm1ΨÍmÈêñ9 ¨±Üʇ[Vºšo
Fø)kâš+ŠÏeïUI“=;£ÇYý‰o€CYMßeCW“vkXèr"T,Kú‡®FÁn¾To·“¢±³!®§8}æy)ßS—©²3k?ƒÉsg!6…ðVe7Á}&á-WHõG¿êž‹´Î¦î¹{}£ŸÄœÞ @pdà‚®Ö±¾ ÜÆ,ðW«Ü²ã”EìÌiïû¯ƒþðJ…œª1 +ØŽzô,®ßÙ\FÎוVvâ™®¡HÿN]îqu¬^H‚+µšÙd|ù2È9¬/'ž™rÖÌçÖîåÊݷʹÝ-®U²cþ–véôcæôûê÷Àž-s'X*/©U˜ò¦kÝÆ<ÏÜ Â®-goø¸ä.5´¬ÂùßÓ|žÉX«qt{ö…°‹²³ÝT—RïY=3ŽgÙéG¶Îó¾=G9¾ø†Çõµú»ÕGî1ÈìEÏæ8¦ópÄ‘øgy3QŃâr¯SŒ‚0–fלyXþ6+ô|±u›•Ý(´œU¾’ËÚ¢<oF"µAa×iz·åÏæ¢ñ½jç=Õcg”·ÛF£4cÿé?þ¼¬ëLìs˼%üCÓFó¼ßB™6óçµ,b Œý2í0¦j{ÞYÖ•¨ïãõ‰cãqÏ'¢uÊñž¯R¾ÔËŠ‰MI6Ç&žÙ\«›cóæMÍ¿Ô<ÿpñÇÅ3½§µ‰Û_‰D¹¦ù‹†-@…ƒ,ã +U¬?áP"]NB
ü-hå¶Z ùÀ×ðs¯›äüµ¼Ž3lfˆàJE7Eñ†©óô\PöïU„¡˜U SðËõéF2î*„Œìtj÷*#„yå
4mËŽOþkT·Vf”. a(?܇õ¡W·„c«ÔC½™/ÑÍâS bA])Šky& žØ[Ì“ÇÕ…Ù…’±÷á*S²Ùàú
6Û øýWÉÌú3zOÎá@׉^2º}+ +¬ Ìê.ëïÝ‘z©ñ}^cåz>êií.½We½g}«C„I/‚/}вýýèªú÷€zü}Ç{îH9?=ÊÙf)gµÛ£l®± ìŲ?pÒ(ç¯S.u»õL¹~Š™º#‘Gîµ5 ³6uý,‹ºu-üWl>¬/ +¯j¬—¶qÍ)}äŽp‡ÛlÜ•ÿ¬W×vêÈý2 @ Å&ƒÉ`#¢ŒÉéÿouÇu|Öøμ°¬îrWÞµsØ Ø¯ì*š\7_m‡q/Z.Ÿ +YFµ3ÃÞHÈ×zËðPó´j-ÙþDSY«ÁîâGˉ¾nêýl›£´ê£±ÙEüZ'4m¬rÈhèAÑH]âCÝ;œµd†upÄIAiÍ/ø•¾µ|êJÆFÛ75«4W}æËpv•kqaˆ‹¡Ḃ"cÊÈ®u2ÈG7SÊx9<?@¾ò4Xâ‹.ãÃüê¥ôVù×”ªï +c«R)Jð¿Ln¿<¼%È:aïÏÈ\0C#•ÞQ¤ÞRÛƒeJób ’>ÒmOœ’Ìßrq‹]Q´X*É÷ÆuHú¦¤…?Ž—ï¯b{wôò×>nÎý"â@|êÍ¥{²–ÉàØžÖùwý²#Å(:.äÞ£=£skŸ=½çA«±wƇ•ÆºÖLB1ŒNæK +@㜵4ºd¥<T¿ŠlžŠ} +¯5ÁH¾’OœÎ•ö`¯øäRePØ2_EÇ"ɤeØÐNWŠuYÁçÏX‰ú‹E©,·JPoøú.8"•²sÃøSŸ°ž^lQëÉÈe¹}0Õ»èuŽy¼%h~hxIöDŒéBë,Û]@×HÕhôì[8–ÃæöŠÕ…^7¾U)ù:‡þzç²åøº_«Q†vÄ~µ¡çE°o•6ñ{k¹?Ýy &(aÃ4:Êòú`â +ÝzS.gï" F^y§;D'È¡d‹‹ééúÍÊalmÇWƒ‹0ëcbÖg›Äùr“Âã¥î|;zRŸ*vÒr™èTψRYýÁ&gKC–¿(zö!äö”²1ë¢ü¹ð€œN_šÕJáÖ2ôG&X3kêμáƬz6zÛ˜í®Ï$7™;ìi£‡[%!æÅ^Á´›)6^ep•þÆœw鮵lœÇ’ÛÈæNA‹ž±•ÆJNraÙÝEÚ`X:j-ÎùÔ-|¾T–P#Và«4¦;2f¸•ÂCòA… -'r]¨Q%ƒÓøð¯Uã ýAûOª‘ù"U·9è¨Zx$fŶ*›z;¬8DDoé».þ²± µZ8h®{öåµ60cÏÒ%u6ÕÂGßHÆ°?Û Ê†läL¾w••¼zÎ,á‚‹Þ:ô[‹Õ~XF¯ö´e§qA¿¿èf´ÅÞäÐ!9Ç5úh-z”ðþék^õ½•b™ËòÜØ$šqØ’ž(8³ÃPø)Eù'}ÿR‘Íë›÷»ùëОLÒÙ»l‰ß{ñ;kíTØ© žV¢9ãÉíW‚nbÔ~2Ý—ÖX·®~£¹˜ÜÓ•oÜ/[ +Ô +Gj¸÷[¤÷$eÖc¾ÉÙñ˜¸Õ×iÉž2Z醟ÅdK¯³‡.7ïÔˆ×0…Ä[ñ;t݈A)»<虄ëBǧ¢€®Ça 9ÆA{‚xD.“Ý'—úãåpÕ+*CJðÓYs·ÙÓpljŠêHèzT¹~¤fFý‰b`+ZËgÿºhèslÿˆ¢å×(ÄfÏÞ^»6HB³t’ƒ¼ßÁSY Ð
+ó–dz„Ľ"}+Ó˜†à]Êÿ¸Rx<ío[ ICmÃïàayú(p‘4iϵ’c¼]be†gÈÎ4“ñ<Ÿä¹øÑB83“+ºTâM[æ +fSæ#ê:Í–§ª"„O›G¦%–"ß…éçmŒ½cñXûüŸ…}l!¹ê»÷_…“ÜŒqm"Œú7òŸÝü*üæfêO æWòÿ$†oÙ,j¹»C€¤ÔÜ` +àŒ”¢|o&G@nsI²2„$>ûzb&ÆVžR·±ãêöC>FTÐc¬ÀÑG±àõ7?§¤sr$Zß•…ÐlË`ÔÚ£ñÕ?‡}h,Ûƒyôä3™ÏZÄêz«ÐÑ¢‘î4.¿\ E2êñòd›tˆà²€]j°±÷ÓÞ\«y³ðöëZlEu]êbÃÚø¶ #<Æ“ZÆ¡'3ì²:W…ðjß¹…>Î{%@Il9Ú‚~r”¿Ë~›·ô™yù«î +{éS +.ˆäËF£ +Q#EçvüÓéÏjÁz´Ðî—hÆëüâ«OɨæË̺ÃíÒªÁWåfpã…ƒÊ=Ö´¹9è +œ4#¤ÅåË‘—/ïöÍߥÅåˇNû«´¸¬+´h®ìÌl?ˆ@<y+gn"{3×r‡_DÕoáE+¹Ã/‰Ãθƒ3`É€ÐÕQˆÐ +œK;
2ˆù3–@7ÿ:- +è´»Á(@~üo( sß` ß¿"‡ºm)kµù¥µû•º[‚aïRºÄA3s=wø%q +TÝü#輪./Lç¼+{l‹TÝü#è4®ºù3–@7ÿx{VÝükÛó´0?:'ã²2k¬P¼©¤" mpHEÀ(`ltçÆØ:Š +íŒe½EýpêÚŠj?ÿ(ªË²›¯.)ÔÓÐi®>Ê>`_y© I7?}D§®MB b¯Ï =escäQõÍÔåL=Í÷}³sù”ºä ù„’¶7<¸èV»u³Oö ±N‚[ÐÕã»K©ê<Œãy,×þ„o $re+J¼¨pj«&>„˜
ZØ +¬ÜêRræžZÐG²©<L*Ò ·böÂ,£ez>d·õ@û,B÷Ó:ðÂvVOM¥(w†±U*ÎLj‡,s±þ÷Æ»ÆRÍ£¼6 Í08æõÌ*lØ7- ©o/÷Ájï˜WÌÓNR»lR{ŒP7¯`5Áì|ú’zbÞlŽ§`.ø¼·&:MF*÷ºyÁ'qÓ8ú„šœ²[pê÷Ýî“&£¶kÝú¥O`æÖTõ‡·#Ó) +\N·#b%2^·NÁqöðdêYíãÉ„½ë!„‰Þ‡“bÄæ2i2iÕo¦ô'o¤½Kw6ñØ¢®µ@ö+üúêR)íZ y&™ˆBÙáúØWˆÙ7…º#xÚº¥üq´í²kL*`¼—ÎúêÇZ»ÐZ\Û½"-.¿›e/÷ë~Ië5 µ9< +7ÍFú{iËtº—’ÀZ¬õƒÖé]R~oIîóÙÜ~‹ælt¯[¿×šóëÌ-kÁV1‡Ö]Z.uâ»YáÓ¥û®õ#«ž=Š:íø®Ô—ëDœy}²¤æAÙ “6>C¼Ö4p˜›ˆž{Ó0©|:F/$ìA˜–¬N¿È&‰dpôÔi˶ä]Ó—ÀŒJ3xmŽ£âT+¶>ªÛ=s–½ `ëfl_Ùó4“J{oùñüõêîKœk¢Ÿ…Þ$7:¤Ü$:PDz‘¢†’ïÿνQAÝuwßøF¦œ3gÎ\¿KT¢¯/ŸžK”Ç„–%z2‹G¸},O®:Ø9)wŠ:o™BD~95H™¦6 ˜]M)eoÈÇÎÕ¿-ÍIGË«C²
Çšy >Þ°\µþ ©õ^š`ã +ßY-±Z8ÄÄä]°ó,ïÅ="2Sám·:c£°|ïmÃwªP +„"Å»g›´Ï¢ˆÀ rÎñjK,×ýAx®ŸÆš?7¡Õll÷+–ó'œáBzýèüSóNºñœ"´Ì,—v5h›i3 $ë…¤î/™¿¥Íûqµâ2é;2žUcµ#×Ò þ¸ÄÞT¯}o +a1Pƒ!WUZÅÇ|„Å#–‹{Nðëç@"$ƒÉss´Äëh𬩇¡n6ïkpvž¦.f¼0áe>€0<Áå/4“TDðòTJ8Õð®D†p´Áò¨vÖÆL/[Ìè·m¶<ñûþó2½Å¤}m`Í÷>ÓTÖë“KSQ`C»úþ@Kº±c~¹l†?\·²KGæçO=ÿ¶á”î™–?|£áЂ¨ +Ð/Ü!aTe³â¸RéÛ©#_ÞÈõãy{õCžƒëF·¼yá‘š˜/Zò±þmžoÕ@[¶ž‘ÝÄÍñ!1gvN.Kf}‘P§ÇA +ƒTxÐj“O…¬º=!É4Îö;-“RÇÔL1k=d€ åAn™™÷Äã9ñ@å^ÈÍ–µÁÈú¡š‡”˜EÁE˜ÃœCã†Éû¤ï)UŸóòPƒ•tŒi®È=ËIŸïÓKÑ +.VĶpæ9»SèÅàUô¾IÌÊØRðH7ÇžõU¾©~ݺ½4ÜÉ»CÆssÞÀ_å a®R-!&ÅEv© +«»7.£H|S˜Åyß"€hæ_¤Ý¦ +ýÛÌsU﬌—ÇÒ‹:3óBh¤¢ÙYÚôêº9¼³È>{pUVcê̶sÚð0ëÁr#!GáU ¸µIÒ¦ÉD|ä%$Â(ôÄë”iÂ]«KÉJn&䱟ª ì &K«dÐÛ^ñ¼R4'ˆÎUû·A¼Ž´—Ê2]7¥®§ÐüK‚P…þÀn6j#Å=ƒP ÄVÀMã¥òÇxéÝZð¥
£Õò?9ÕdB)×û|u5•;z13=ôˆˆûqe?]Ó6;Øø7Z°Êúpe{0⛢(ñR}ÄeZ¾[\o;Â.g -ÄoÓ©9V»%ö©Î +å) 'l™EÁõTDj¾‘\5[ê¬ÖG¼ŠmGÐÒ–›†ñ4Bl5hÄMÖ¤›™µÒpr€„sMƒ¶™@8:* ñ"Ÿþ5Ò´—€¬©³¹†ÛÆå…ÌK“Fêµ}ç
îjëÊŸA0
Š´p¬Ó%»E?«ÌC#.G3ye:ÙÅMY÷Ëb¤T™W©f²Â]õ¹*f›ÊVs±Ôõ.¦ 1æmL•‡Æ"Ò™N´Šjž±ñLš%„Ü1@®$ïêÑ3©U‰o˜‚L@C¤êÎ×#;>Fâ+ssoŠã¥°sº² Á¥ñß
që'ÚC·F#à~v´ç#†Äªã/§õ¾rÔÒYã<-ÂЉÚÖsj4!ñ•a¾Ô
"úÍëÇž ‡¹ ª‘Ǧ¡ð³Há{óÌŽX©ãû¦´œª'zYHc$§ËÖèA@Ë» æÆEÑy?/¤¢u5v<§NeP×8b—-.Ï1ÊžsñdÒ”/õˆ5ƽD…âÁ5•2ñ£Ç¤«”êjäˆ.kðãY™›ºgk´¸ + 'õ“áÙÊ\Y‚áWÊ!yÇݾPVž†N7…&ùžürMB·YFifØUvDœG¡û€ +©Zeg/M³Ž×LÔWöÄüÉ‹1uVó›ÎTnàfáe—êÃ$ÑwD솸ZÍÇÉZ£#—`³VeO§sì>
¿Ûp\æ¦úî…"§~˜êÆ«ïÊmm²Ö„/<õ©Dl^¢§Åë‡Àá]ïÇ@h-ÝÌ6¹`æÐÜDé§ÚG®µÊß¿hßws³RnKì +{+ß:‹x#™Í@N&4a6¡@aɲË]Wz†ûƒH§³B4{ÿÛ2ÉÉ:&Ή9‹ƒ³ ¤wˆz±Fo¬6£<ù9q5‘×áP©¸%Sï¥ÔÈÕÇú92pä´ÄG¡$ÜêJ•éØŽ¥þõ6X¶R§õïZ⬵_ʉQ¨déÿ²>zG]6ìþÍÀü›1¼ÀG«¹;Æ©j ùö™»†‘FÝæÒ0â÷æµâ] Mð¸ic)-¤b¾3MM·—¤›Ž‘‡0NiŽy.l81÷SzñѴЃ-W)–WÞü£`}P+¾|‰‰Ã>£‹¾±¡£†"1[6>?ôÎbÎHWßA38fd‰œþè2xo–ˆ]Ù*qåX +Pâ-#ÈÏ ÈCìã´€Q°ñͺ F@3d²çÊä‰E¾¢ÌÐg€@T ëzƒ ++ãÇ ªÆâvvJ_
ÞêÄ®>K#t 9M©ô»gˆ5÷Xtq™å›ÿEÊ´÷
…âW¹\½hå"ÑV[bˆ½Â™¥óŠû§>]¢G†îRZîop`6’Äè|ŽpP]–&Ø3yË_²š}G +€mỿ0ãÓ‘BË5¥Êè;ó¢´’Ù1¡”ÿ†@±·§C5Af†#gD +}…rÜn´h†~òZNS ÿEž¢“á¶nØfÎîÄa´3cLŠ“3>cvv1ˆL=†0OÔ–t?‡~ÒAl¤¡*B"Öá9…Ðg‘•y|ágêv4¦w¥w|›¥NcYÇ]˜
Ø…Rhᬆê~VM<(¯û$Ì’aIéõ£ 4JJçÜ \ÖØÔçáìÉÍHh²y¢†Æ
u¼#²)€¶f)š‘-{s¾‚mmѽÒ'&lÇíœ$cv¼T72×Âxw —òˆÜS¶ªù7$ƒÓÒ?ÏM +DéX«ùÍ·lmú§žû—ï¤_µKlœ¹×£µ¸B "á´@hƒ©Ûõr.’gÊb²’òú ¤/rÄÄrÇ7Ÿ@Ò¬>N]«ßß_Ep‹†¥›ý~€gù4Øtæìµx,ñ]ÓöŸÒ*±Ž¢mà[`¿ñg½r{Û½ÙKŽœÔžwg¡ø™Üñǧz¡¤Ý(S Ö&]ÚO +ŸŠæœŠ™ì‹7½ûø„|ÈoþêcÊ•[Ô÷ò¡Nƒü®TȯþKô4ÊdGotñ÷Š—Þ©lH¢ÍOȇ’ÇñPUÎd +~ßo©/u]!n›Eð@5ÅŽ!Ïÿõ}‰|/]æôÔ·¾@g:þ'
ײЈ+/ÁÖ’ó¦S%4Þ‘†h2AÕžÄLŽ¨yëÚ"¬oÜÆ?T)¸—‡Ýns«ÞS-ͻ૆àóXÆÇ ØÒ·œ/"°v
Í͸æa¿F®0‚§²øoJhUE„ä®iÞE->äh¸MÓxÓñI™·û“·Xàå™>™$…£ +õxÍ>,)!
ÖŽØ\¼ÙWêûÉÎ7·yƒ-L÷îˆÇ€–êRžª1®’)·|k9eÉû¤õ®øŽ&¾é&¦Üc:; ÂA€áµÂ¥–4½lP²]õS0ÿ Zqæ2’N:w*Ž–ñÉM¢ÿäž +]Åy7DÛ\ûDÎ5mƒûˆoè¥0ß’-ËßC9oH«yˆÅwÉs¥)M1¸yÿŠÜéôôƒÛ} _-ìÞ¸6ýÑúÜ4ë}ÙiæKgý}€§èªÈ0Ž¬Ð좫sœwîRfðmòšCðºçôEª>kXÜQÝp/YCͧێØBý'Φ8DŸ×Ü#²·›¼°4BMV–6ùYæÌ.ªÏ“œU.2üS$élå +¶Iœ4¬÷:¿‘ Fn +OOuP\ ÍëÀŸjÙ + +3ñ˜U¹=]/±ïsûŠQq¸ÖdÔ,«Íá{ND·ù +ÏœH/¦w|‚Üúî•ú½0]ý-×Ã1ÿÚßäúœÙÇÒ©¤ºÍ·‰!Ñ£*tnÑ#[ª4D
öÍÜGîW‚|‹öy +ÒŒÀÈT`¦¹øýIùêLä% >¯8â˜gIfæ§è¬$(|@êbA~N +%m¤¡qazeÍw@„Ïÿ³GCNá<VÖ%)ܨ”|Öø‚mw±`±µV©ÊýÕ‚2 Ýñê&ÕØíо¹g÷ødÿù¯Ë‡ÿãs¸lvY´Ó¨â>ñ}Î"°V¤'Ê
ED(ß‹£ÝšB¸9îàftëëÕÏè¾Xsì}¦@g¢¶¢/YåæA¶C ß«´!1+¸õ£½´R\sk^ŸÉ$ƒQ”êp76ã|ÃÛYAR“É^ÛNèì[í䥑ÕQÙàÎœ1`ÄŒº&§ã5;øG£_…¾y
Â1sGlí»Ý£² G2!Š7林#Ï tIm µÈ~Ò-Ï}ižÂ/BGy~Ì>Ë‹d*NžÇ.a%ðµCÌÀb>”½Ó.¥uÔ¥™c¿¶Ö b›”<ç¹4›k[Ç-`óÏ—†Í©Dv6—Û±Që¦lóM™Vö=|šÀ&É3^1\„^rLˆLÊAê¶ñÁÍh®º|s-wá¶Qžk=O»e€ÉÿûVâ"SšÉ8…Ô¹«%2P—;ÿöÂg%âˆð’Æ”LnŽ—ñ é½²
¥µ×äñ¾‰Ñ¹=k"ü¢üH±“*Â)¼+´¶·ò<©N¡‚{¤%ø<²Š÷´‰Fîp<õ²H)Ó‡}Ó/š9A7p6i«ÄŸ›æs&æx~HI)Ù5S%ë¹Rî9øÍSc- +xLïÂà,Lý+¼šw–7˜=¬Åº#‰…K™ÚÃzÅ3î“ÂlÔiäXí•Œ{ã€Fé<W~TÑ-P³ >ö^3@úåT›Ëz¿l'jþõ¼ÿ ζÃÿ桧òKúö‘0÷á￰ñ +)ß±BøIÌhšÚ…WoªoÏRVÐJÇ’Fr°a*°Î!ž3u:ª£Vr!•ÊÛ…°;Æ)Ó?6=ãÒ§mxÎ…]ŠP=KêÂWÛà¥‚ä ž‹ºNê{œpxxRt>΄¾njÅÐå²Vÿ›0„òA˜sÔ˺ F:ú±Œ&Óå?ïê+žÈŽÍ,¤B€V´~£)BùêˆÜ&Ù7±ðR`ÅáŸë‰(wb1-Ççï% eÖô-×Úܯ`Ý¢òd¶˜V.…ð‡¹FW€ŽÀAŽU|ñ#Zó2o«¢¥‘?‚ù™FªÈc(e TAñÓô§ª—Ã8%Z€Z«ÓQ£ÎÈ(Þ‘àWÙý@¬¡ÂÀ_Ð:ÍÝü% +UªW´Ûݤæóæ²³Âqãrk•æTè#Étø‹ø@¯D"%¨Ò|’fÔfÜ'Þõ +ßuLj‚sYÑuå¤YQ± „s-n†u“’¯ (ö0¾…Æ\|ÃOh²Â3ö~{ë"ôA»{u(°‚˜ ÿ²*~§ƒF½š|Ð<ÞV—)gGÊRëSd@T¡„ƈN°~jÛ"ri•‹Óz!þF:~a½‰n5%¥\€€b»âv°7d\žØ7mñ aÐø–ýb+µâÜè3©¶zw^‰3>Êø{«h'®]˜Tƒê1œì±dR‡q‘ØÅÚI¦(aÞ¬Óa9~X<ÄÌèÜ-H⻂’í%]}Ú|È’j½¨Ó=m‘ ùbŠ?|°˜;–‡ìŠ†¼Ë×3V»a6ãr¶yVjtš[ìq¸*00?î^|ÃåƒàÕf_y!eêfÂ4ÄÆd×ÆdzâÓ¾ìG-[^îÔ¦^x>¥m’žÕ¯M°îVfDzF±”À…Æ\‰) tæ4· +ê‰áòÍŽÒË‹á£_ ÔÌŒØtH/Ï—eHGsãu›X:ª˜xÃ6(.(=¥_âŒÖÎ] +m±>ú†·˜R(ø…õm‰'€ Ï‹PéAš0 +ŽÙÇ”ž´Øþ3]Þ<÷Ö÷ñvv¤¦2 ¦\<¸›ÿe}Ÿ*߆i‡S†fíË¿·>•Åß +endobj +658 0 obj +<< +/Length 24362 +/Filter [/FlateDecode] +>> +stream +H‰ÔW×vâJ}Ÿµæ#HBB e L0Ic“1&Ø&êÿï©œÚnw÷ô}˜-#«êä}öŽEÉC2ÚHùLjK«‹#ÿßÿ„”-%oi3†þÇ•›v,:'5©˜ÊëJ. 7=7©?mʽÔe´Úž$NÐvzsT¬&åºØÖ[„¤Ä‡TAìW3±_iÉ¢‘\’&±˜;Ú¼å^ƒ{ßëz´"ÔýöxÔ +jÒæ©ëŒyŒîpQ¸4µÇãTVÉ‚+ø®“±mãj¼ÖÛ¾?vcTãÃÙe÷¤—:vààë‡-0“nÇügT!Ò§Øw¬”Ô¶‡«gk»ãÀ®NqpLaT3Ï!¤çsXg^ReHó')¨×}7øR0ÃpÓ+v¾f~”…¤Z)R©·v4YÙN?O‡"™†îº¡ +³¦InÝC©ÚBš01–Vr›õn—ÖG0ãȶx‡\¨'|°‡l¿U¤ÃäÛ›×22\`}°òëÖöœ–K Z× þ8fŒ‚˜à‹NÍ ƒcÎK¬&«áksex)tÜÿr‘iOk®@›Ãõ¥|nË1U± ?ªŸù f°œPyÒ‹..‘WV¼1å^ +O:TÓµ:Í}"g¸\Ú“Ä´ü&ßx‚’öÖñÚxL—ÊÖ|³WÀñøQ÷2öÄ•Ž¾e.55©y¬±ÌŠ„t0}½9ÙΑ¥-Q¶ß×äèlù*fÎ;¬’aÇ퉷í#ûu¸’ XÆuœfŠ©P?.Ã~gDŽ8侑XÜ[¡÷Jš"P;ã1$·íiªàs(,£Ün1,cÝ›‹xýˆ/ÿÑßåFf¾Gï6fù¹§;•m¦eZýà'˜ùà*Oø˜+äBŒ™—g9žXnÚ\Xe®pùeçëÙÄúH+Šúø +äPõÓš4ˆyÖܽ“pèâµ™©Ð'í3ØÔq\Ú×:¼9›cÇÄå 8ÖÝÊ6ŠÐ¢vÉ?‚Â}û2_ǘѮãj⶟•JÁIÁºß«ÜA¨¹|.&N2.®äÝ»ÛáöŠæ«öø¢7î:ßöÆ*ˆÆâî’áΘÑdÁ·G¯©“3€†S=Ýn`núvìÏ«ÙEfÉIÏ +ÎƯWÂ6 +á‚UTßXƒèë¾)´ #E 4ÄWn@ šÂÌÅAâ§pâÇ9牽wC+7PÃü +qV#–ü,`æ÷“‘Þó…¥qö?ë]–ø…&GÑü²Ó^”…N«æ¸pî ˆ9|P*•Ö]’6½’2ŽY—V&ëGÅ’¬;Ζžr`†ëC°OÉ#TgxP8£;óP¸+¼Ñ¿3¬;*ÃZ¥ÒÎÀ3"I6Y&°Ç’¡-s}Zõ¼Àíâ{‡3Ðv9ŸÞxŽæ_)2ÅZ.ÃëN•Šl†³Ô±Æ¯F«!ðzÄgáòñDD-%sªÞA ”q"ì&öbრ“^7L1W‚C²“X*Ý2grSâ|6S"»& kãF‘˜Æ‚» +Oòx>¿×ÝâtP¾D|Å®ÏyæÂktÀ±eO^ÁÌçbàæÙû;b +ã®õùÈ®) Û"kßÈuRaJ"›ÂssNüà‹!¾i=ÇÓÖxÁž5D
ý;êî}ùXnx¶8êVͼ¯á‰æcüFðXŽ(
µö3~sú‰Fæ$cÊ1Ær€péP¾w.Z6µÒx}r8oJD=ÑFÍÖˆHÓ±¢C|Z]}«Øè`«¡büÃzy¶%®Dø·0¡„’N ½ ÒAŠ•ðÿï™ MDwï—}tUfæ”÷¼'œ;7¥ÙlÀæÉ„ ¢˜'ÙÌ"$0yk$q” +à× çQn–tRátn‘.×°Ým³ ïAŽ™UØK«#øU‚ÏC?¨i^nM‡pÁ„'sçä]r$V{…ªî!á§#°æÒœN”`¹ X.è¸ÂŽcok6‚n¤£Ý A^)lVòŒ€’5–™Ô²„¾íNŽbjëÅ3 y$ØÀ´9ÓI¡¯t€—4T%æEAƒC² úÄ·ºÓ}…@ö4òa”oFØ"p¸þN1µ”ÃNÔ•ÖUòJóT£BÒxÏÂÞ4A²6¡ÙLaš¿•}pÌøûö]ï4…\&JÑïÌÂÁ
²S…—‡¥;~,ƒYKš™¹kó+D•™’P!ã
®IÃ2–N÷KAÅKG5z°
,íÑid`yeÓ‹¡ óÅ”âeu¥Ø@Wnº¦£N¹þvœFg½)õOÓ@Å
‚BÏXbµpÂË^—ÆfÔ)÷/#á“Øt)¹/{ù71<KÃfýoDìEíS«ÑyI¼³oa$0lcéÑ}Oå¼Ê')œ>ƒðÖýp4½FîüqX +myÇ+f&ÔêÂÆ· kåö{WhÝ<¬sZYPО‘%•áO|ÁÇpdÞ÷,½!„—²™é^uZ"‡¤=÷8»•Ù‰$”1”Øq¡ +ý5<„`ûH¼{t–KxWŸÝJh§0(7žX=îB ÄN®ÍÚ<°z„6ª8>-`©‚\¿Íñ«`lù9ºÈ·ö€(pdYf®¹r7uÐxBqîäŒ9o¼Õù¥6#$‡áy‚8»Í‘1*BSÛ4töåѣ÷æ¸.òLƒ0'‰o¬ÍŠ:ú=ý´â +݈w¿R㾉´ªÏ#CbEXhs\ªô€fæ½–±|Ý=V+WHKo%‰òÓ
ÉÜÏ8&7)0…táPà˜ëé:Y¨ø‘ÝåãoËÏ1±+ÍFÌ»¡sÕöÂKW´üª=Òûœm‘J˜|{1’wXÑp€0Nl–Øx<+š|(¸ì”LÝCì8.`Ë$е´½ÿöXÚýäåÓM‡¿EL[©+eÆ jx©(Mù®|mãE½æÇ~d¸‰LÈ‘þôYæ-þþ;m -7ØÕb +Tž¨Üi)ÞàN¢KnFM)’ž“>ãäâÅcq·‘›Î$Ò<žDfÓØŒÈzö:Zh5ιÞjYÏ‹ª¤_ü“u +ÎBƒ +ú`“¨ +;ÊšKvÉœX‰¾Â©E·–ĺUQüѸvÄì +ôþYJÄú?è šw”” £ö„7{–ðG¯ƒà²mÑ‘¨‰ù\¾´HÐëk'4^8*@;šgts¹Ê9„êI>>áq[G¯ñD§|Fîuß…
«þǾáøà²RP«vEþ´óq(7ùpÈû/ßI¤ÏŒ¯_uß–'O%Àh}8 [ e©¸Å«šð¹ÜNŸt“Ë!£
Ò]Ìt¨iY®‡WkªïŠk?½¿6ÔEwÔ}#?Ô-)ÆŒ³áOðÀoM´ôI8KÔŽ @;-LA¬êª…›çk---Œ“ÓBñgs¿CÿÔ.òÇ\IË
rn£ÒÑÏI³!f3ýêÆ;¤)0FæBùP¥ýÚúx—h®Yß7Ê—@Êû¡1³ÕŒ½„ží— +é£x5Ï‚Äòz·4†db„Ù€ê7'úhŽ–¼û(z!½6$^êžþh¥”Ñvÿ†Ôû3ݹ%úníì| Þ;Ü®’y«…NÈjs¦“²³ÿùv¸=//¨úÄ7±næOoun
un¢¥nä.Ùè:4úJÕ'•†p¬jŒF÷:v"<1ÑÇJ«"×ì€79üQ‘ûâ[ËmÞÚn)“Úüb!JËêqÚ{Pßrí…Ç\OÁ§ø뤖۰5<XмÁ±üænÊÎïUæðiŸ—¡*E(%2ßA¹¢O*%tÌSŠ—Õ•Þ:#x¡×üÃ~þ[épNÃn4 xˆ¡‚Fêœæ®Þ—5•¨¶ç°%œÈ±@óÕ¬6•òO3ó¹ËNÒÛ4ðŠØ._üŠ‡òp;Žþöò\K^¢ðµ H/Ù „^ÒéˆÒQzQ¥ÈýŸ™¨ˆ¨ßùÃØefÖz×Á©®<ë´SÞ×wyá³Mb§Ô唧®òXš$Ôµ;Ùa×Ï·ø‘ÿ vOZ‡ÃTä—Y{ŽÕâžÈààí°L4g¤åñÚÔÅÈæÅ’eÿ–o>÷Æ>{¾oäÊè7½ÁzìoBÙyÿ¬ß0±9¦ÂÝж…™kµp³"ˆ¨«‡A5 +ð*Vo†D›½8 ‡5€:Õ&«‚µN¡¡°YÇpʾ±Ç÷p”_ÂkánÅ÷*|ˆøâ#ùìÌ„ŒTdä”ß—»åî
.!À¦¢Kr…·DõH9À*Ö¦tà©ü€4X+/úpþnÁ +¦=‡]‰-{!'ŠÑöð¥v˪Ó"ÀX+–ùfàY{Â:Æ+»’ÓE|ÉŒ3ZÜW7Âh¥Ç²½•" +&É ’ÖºÔ©“×€:«ÔÍË3“üR`²ƒª@”‡ÆpìØ«våCåàÎ,œ?"rV¹…¯ub¬ÓÔª€ui+—²^}gËL–a匉àqçrî!íŸIã•JÁù
âî€ _¯ÃL¶ÿÈ~Ü;/-„а5S•Õ=ÈaÍxv&MÂYŠ°h2«Ü·»`:Ï8‘{ZxŒ}[D±µÃþžç!æ驯®„f²Rç¥T²Ãw7¦+ô,£[‘4Frv&wì¦ãÍ„áhÎC;}GçhöðØØâ ýv›#Á¬œím¬jL#vé©ï#øh ;½$Êj×ÆÛ‚ÎuÅ®×ñn™éÕž^Ð=éJlä6ÞM÷/Æg»—¥÷Ú_”ËÈ„e‘¿V¯ÄÂúmvÄnæ•éN[ZbüÃôXf4ôÑ7‹Ö»ªP©\×ñ 7Ô÷½¾M`u3ój¯ÖŒ/ƒ~oWª›
¥’Š¹—8u°Mo—oôñHW?äèêG€vÃw+qQJ8Á ~Kr͹4ª»ëò¤¼é~Ö-G– •Ã!MÊRW¸ã«ñHÂS/Vµ7æ°/-˜FV¹¥6+®°ßµæbdd;¯ÏV¤’ _Ö†•P\yoÞ
¡‘61‚Ò‚&}‘ŽèÄ0‡v%X›ç5—Rk¤…\ƒ1ŸH$eVl’Ì^ñK˜Gô±6ܹ> +º +4ìz‡ +¡jŽ—þ†ô…:¥„+1wã2¬-Édþ!ýŸBXæÐÿ`è +Æõfü\¸dR ÷Ëü9à±¹4ðI¾¡I9÷-†²^äÍ,çDÅ«¶Ë\¦ó͘i…™ñM0N‹†bƒÿ0µû‘½b»é‘È ½bÒš
À0¹ÔŽ.þKíYÝ‘ÃSN;1Ĥòdã`æ†öümZÑ=*Ø\û +!>èUåfÅ*¡òxçž:Méç2ÐsŽ:ïËY—ÍA·Å>lÅœî™M¯œ\Mk’u!ªd’¬b*Fa³Œê{‚‹#ý«OksC‚@á$x«Ÿ•=3êÀó°Ö†r¬8¥QÑÚÝçäÖF Ã_Òu~<ÁöѤR˜™D:ôÇ9 |›BÜÒ2½…`ùC[zl+Së ²ÄÝY¦ˆöhÊB($AÅ~’¤½ž +‰Ä®äv4m€9r÷ÆÕ¿,?€d>ÎvNòQbrvÐ>ÚWu‰áرWí<8ʇ¢qVNÌ‚~ÆEä4…ÆA—Úpp{_CÒ¸÷r +½,—a«ëŒNÄÓf³¤Ì½>{Ö꧃Ȗïø+G'ŒîðÌ„¼Czyàd'0¼VQl*=ì4âñÐñ]å>x›±•ü¾ãÖ5´ _\º&eìžh¥Y¥} ¥;‚ã ã´<ŒòÂp>çA¬¹Ÿßôe®¹Ø;Ž †³nQÆ1ç³?Ê„l˜éè! 7 +<FÂãb^ªUÓ\Ëú”ׄp¯i‚êAàLu[½„wÉâ…BŠZ‘(Éy +77ì¸îÎêZý›Ø7»8GbÊûcÜC…¾“øz.“Ø‚\:¾e-JÞélàuêYÝB½°{—TÆ„ÉÂíÚúUK3ƒ’)i4i1üBu”ÌÁÓ#óCüAÝ?£pB?ë_Šª@6í8Wj›ù¢zº
æâ#(Õ€Ö{48FTP1^ÐϬmEz频t-Y2ïfšè‡ ^Áû7rÇ—ßÔ«»ÀÄóyNzÜ°—EƒU‰ß¬â¤ßç“è] *ž÷·Ôwù`Tü_QOw$„*›ü]ñR5˜¯Š*”»@¾kTÿ&zB)°Lj +3]MÙ/2ÅK‘>jÔü‚ún"jÚ6c·–aÄ[ì÷
Þ¡¾›È‡1ê?R_þL}7‘dÏÔ§×K¿Tõë°1(¸‘'Ê{E +¶!Õr–`:^¤vglŒ¾kµ:2ôðmIcw»3ý.ÝÞí¸ý–òuÊÛ~)Q>‘oR¾FªNù">eSÂeÊËieø{‡‘^Ò׿ó£ü®†ß•)û&ôìç‹Ñ” + [ëÙ[𬎫ÍÐI…šÚÐÑlá鱩_ümÚÖAlžLóGÅ_Y»Ýƒ²„ìYêkj©!&»} +zÏåõKoÎú†L<ëH…j˜xŒQbk×K€$¬ä=ðDw‹§Y¹¿Xæ›ù;Y CŸK‹$&È‹Pqjö(t•FLJiay´°ÓÛµÉnø +€¬u¯gÚ¡ÊžD=´ eGÚb(„ ?}ß¹žoZ§î«¡¦Uu÷Î6]ã«ž“(uìnîmízƒÄ35rMj'H Ò©ÊËU( är£.q¥Ýauñ˜ÖOª‘ÙڄЩ‘´´$µ1ôîÖVÉiòÜ䘉JÓ•Ž7ëûsOÔ×]Ï¡s=—Ü~بIÉ`Zûy3úœUóRËtÊØóõSW +>…ȉ¼»xÀ¦Ô/éœÆ“»9PhæLÑd«·üÙ¢/HïŽ]C®féO&Eðƈ‰ ÙÜö‘wmK†~ÈTo¿|Q"šrÒ¹'ç$!r22ávmý +V0(%æ,bSN?a€Sv”ÌÁÓ3n åþI
?Ø0›ù´ej¦Þ_H!ÖÆPÄXzÓ;ÎV‚¿Pÿ’B…ÿ¦†7¥ÔÂA![ê¹|Ø_ÔP—?W%'²x€K}B&>hã†ñ‰ +aÍšò<÷l€œ›<|¤¸^[Á¯œª9¹ˆp£õj°xÅXThÃx—¿Žø´reP_öÇflyPÒý«_?—3=甑P„ yÔ—©MlSAqŠr½üJáè‚q’«¡YD”‘PÎ,ØÙ·ÀY‘ቌÀSŽµ¢nÞÀÙp]åè@8ÂI‡rè’‡5+¬¸Ö¶ÎÄLªó”{yR§dP¯ø]ŒÕÙ†ÈÑ¢‹û]Û/&’N“.É,
–Gq:÷†Þ.¥†3]9[íI²žƒ¦•š
†s.¶JÞóê:ëÜ]X"LJ+²9‰Ãl‹¯Êd¢2d¦IùÒž=7–óõ}”·š;Ûêl”:håúâ”NÀ¬ºðO<`MÚ8í„š›aiõVÚJÆã<p ¾Kõ~±‚ÓÚ”T2m#yFMMß¿ rœÿgä?{×oöËÅ“ýæ#4´P»9i¸ÄäÛêAëS©YEµ¾Û°ÔÁ ?*X+F¨S|3E^l¯??ù(Wvƒíá"™÷ä\un·mnî´þ=”o-¡_².q½-@šOµÃ~Y¸ÆÄØÔyHòií„Æ?¯Ð%5Çãôl‰u¾;êúÚ•lùÒÄTfí<Ê>áÝK¦yÛ:ØZ•lw˜éwûJC&ÀÚæ{¹±8°&y'-_oàš{½8ÚgÖ”ñ6>„³ÜE`ÐL8FL¬;q¡‘Nä°ª¯b’iºP0‹±jj“Ñ¥nÖ@pÛVÞ·Öf“I•¬¯läµ™þ/°ÔVèÂ^gn–Î$ÈmR¾ÞZ¥V©Ád2&,PÜíADêb÷9g~ÞUƒâö¹—ý}ëÒªËÌ¡ûíOU`sŸ›|ÝŸºš¶€¾h¬™ó;ìþª-qy“~9ŒàÜ‚Ë,Œ¬»l{À¡ +À9ÕªšÂaSš
”‚º-¢×B%
8è9Vn7s´>ܸåÑè‡ +4Í\hà_ +à'†þ…þ¥ +7RIâSÃvs'&‹1ã*RrÊÈ•QrÈ_!0^† ò%9:¯uìÚü¿x5ºäØ4?p›w©HÒh/9HU…‡Õ1Þ2æÒ@6†‘M¿-¢-hòÿ¬F*d7ˆ‡ékOÈ’ýõ¨ØÇÀï–a‚”mk0<ÆQÓŽÖäÜ+ÅmË«)‘ößùä|¯)ô¨ŒM··¼«vìªíÝ—á
xvRÍû¼°šuÓS©n¹÷hkÆÿh/ϵԹ- +_K +Rõþ¿9WBÑ-È~Î>b@’µÖ,c¼³=šBDîu¢d±Êͽª¼EªÐbÃ臼?(^×r‰+õÝ„v +Ë{f€ô¤AvéÚt¶Ö¦·‹'h£Šã8}áD ?$Ít}¸œ’3ˆ[Ó0-þ9°È/íTe§š+÷Sa¥šÈî áÈË»ï&ìióÀ`§¾ä¾6¡d‡áù¬BË»7KbÞuÈkÓйµKcžW-"âJ¸(lÑÖ$=Ìˉ%ç@»¨ãOõãˆ+v#¾!üÄ’¾‰´ªïφÌö‹0Óæé†*÷Ð3ÛZfëïÚšZé|BZr"ý2E‡ÌY}QN2/BʼBSìpt”û³åHL`™ÿ1,–Ĭ÷òPM÷>8æ‚IjOŒíÆÙ²€öŠ~›Mô
A4 9±r_7P4ùPpÞ)+¼¶ëÅvy¶Œ„{ÒŽ²‡•Æ¸_|BªéáÉ7¨iu¡ì¢ÃèW
Ÿ7ÊAS®”?ÛxV>Ûaó£4•Ž<Žß±o„Ð>’ÛWl¡3fßßÕbß$F ç¥HÌ{æ +ÄÜ
B–€Ù˜pAß–”âÕXài~@)…£œ’ YŠ*•A +zª@'z}¬xFŽß'œÀN Wºa>¿iJ‘ö|ÃgXõæ-ÝtÞ!ã 4’Mæ—ç±wb¶šdŒr¾n´¬çà …òXü
¼mMSE§YëP²‡%ØQ^ù;ʪ—£PâÝ^¡ø^åD}[QÑàv¤ð°¯Ñö»,‰Ô¿ƒªùlMIŠºì™ÃŸýàü~ËD¢¢ôæwùS ú^#Lh©€ñÁ{o)ñ±p¢n.W9‡q¨áã‹Bnê†':¦à¹O[™Ûú5}ÃÁyÅTQ«û¿}<@ÈwŸg‰Íoß ±ñá×OÝ¿h»Xíq¯n·-càkP¹j^ÊïôQ÷nNä‘2Ú ÝÅLÇ;.§ëáÅ+†ª%¸b¯µŸB°?ÿ}›¢±Žº/‹VÚ^?Dì–$[ˆsá/úAÎ*µôQ8KÞ-BvZ¶ +ÞV·¤yþ,¤ù–h ™&`2Páý¹ßà¥ö{¾8¾Ÿsk¼·—Š“Fàv/b–¬ÿ8ñ/c s™úÎ"ê\‰‚ùÔ'a24v²˜pßO‰>¢âÍ,¨‚e® >n+¨x.õOêKU¡ÊiPÜÜãYäCM»°Á§$¯Õ(l"ý¶ÁŸ¨ï,ò‘¾ù7ÔÇ̳ÈGpð2õ…˜ÚQaèË?”x‰ÓæòrÃ%Õ—^Ùaq‡ÆÜìO6wO°ÑRl +DóV0c)œ_ ƒ—ÎcÐðNÕG•†xˆ\¥sàn4ÑB;ÕNžœ45ÐÅ”ÚüN»-¿HëuH›³\¡øhèÅVÔ |õ +üx—‰) ,Ò¨mŽ‰¿‡ôçu×M¨³išV+%æ$Èx¤K;IÆ*y¥¹gíøÃO§Æؼm™#¿ÝýämñÙG¶7jÉëmèQ›.ã#Ò_&qRiÄ|.qø·Ë9/.Ÿ5b¤ÎÃ~#íâ²~:Ó¶ì–â/CЩ +úý×Q¡Ígîñá÷L\ì6YS}ŠnQ0ó—0à÷ñáËÅ.ÕÚ[8—ÂpÚ§-qÓLœ¤ +òFFf^©VäJO§¢vì7¨P²Fp0X¬¯.pãBËÏOHxk&ù ¸±zÇkc%Æò»S+(ú-,£wËG,”ª>l°NB]§q°4Ðí8á);†¢½Þuì#uÚ +Å +Ìt€ª·ú7G#øŠªÿQ_f[©sK~B Y‘¾KB H£ˆJ/l¼ÿ_µÑíçâÜ8pY+U5¿š“v«à–,!žWü¿HÑx2Þe†w;#nª£HÁ¡ùL&ªÈåVÓ~›P}K7ª5øpÁ…˜ºÙÖÀ4ÏýV˜dµµ•¾v)áÖzÆCöæ\á©‘uãëCöú¼H +Á’Ö~°?RO‰FŽe4.9Y¼ªÀØ#À)pýâ…ÔȇšZß—TÈã6Y‚ï%¦:jgf.W3qm&™xƒñ\é-߸ºÇižó¸á,ñTIá@CUËç*r_G\ÁsqÏ™åÌ’(Š'ËI>BY‚>m¿~µf[J—ÃZ=µôíùýÜ>žôŠ‘‹¿ï¡Å4Šøñ(:ß3ÅcYûrS´~³%Aûn‰Ý´Dd. +Òpc»yË¿ñv^`{i¸Ó¼¸Kw^ÜZ¸¤ëVÿXÄWX!°<ÊgÄDË0Á1xó«>‚(Ê¿š–#lù™ƒcˆê¨¤KèVHäYî…|Ä©ê"Nç}WǘuJ¥øƒÛ᜹à'þ€ÁÀ_pg9~Ê@ +@žNÿ€”ÿÿ÷AîØ›Od©›Ëœbà/h{è3ð— +œcà-´žÆ`·JûÚ+MPg3Dž®u¬9 +ìvÆr“ò´x6jÑNX?;±ÍmcæUª¥¼id•Ðõ-«9ü7ÁƒŒ©nàÕ¾Ó#)7¡û®tiT:ØìËð`Iß5JܘѮTCìD7*=†0QÁ=B$½9(ü(óIõçǦ÷•¥ŒðÃòX%½ö&üÑüFាUà¡ËS ŽùÆ~uÁ·sÀÐj3=;»
çÿÄÚ#ϤØR
IgWÎl¢àšCÑÒ^)Öu„@£Î´2áZYü¤BãýÌ]HHÙ¡Žs7†œYœ>Së{–¥ðøhIÉ£)ØŽ:Zuºš¸MéE™LTN^ŠKq¯IžäÂùgÕòö·áTo¢V’o›:šÑÞ +°+Ff ÛZ‡©OSÕ’L +ô•’×[€AÍ9^jvÄ»^ã{%ã±Q¹$×kâ
˜fQBò€‡QôyÉ^SP§¯h{eô›øw ³Ü3j¿½6ÜYÅSyxÁø5øÊJüKáöÜ‹ˆv«Ùⱂ]¤˜Šor¡%…a`“”êcÉ®¯í8BipJ!¾¢ +‚?è[8Fe+a9˜H;(+ö·ƒòÈbxbQ7%yùHF[Æ‹!&«ù¹ÚãCméÖ5˜ŠÞÍEïõ¼Á^›°¦úÊ$ô—8yªÁºÆbü”)|AôÈÄ—[éjÇü¶,bfÜ.–Ecbª4•Iã!EG5Yɇ»ò2âÞRŸF½)þØE;à„¾ÈñÜÀxÒxFäûÞ^—°ÍSq@F§±Çž€Q§ÇÔš/;‡uµÖzR<iJ«Ájj¼þD•|$ãoõ(§Š›~Sj(¹gjÛp¼wn;Û–0³ڵƻy˜˜à¾ã"?`Ú;*hÌL ÀÌ*0pÏúM`_ÀóCþË8Åíi¤\ª Uä+^}Üè0ñÕg)>jÏ5õí!ª1Å yÂ>gÞxèô8ÞXl±u/8Ú¦u“læu‘¸ìá{Çëæã½ëÈX)Íå˜ÍtË„Nx)‘2‚t^(+éWá§*öõŽÑT~íRØÁ[Í£—ƒénF™™æ58·Rœ¿¡#AÕ’aßFRœ]áíêŒàJ,ò‰?ñ'_š2îÏÇs²å ³‹õi¶}lûötkì^èuo>‚Ç„”˜;YíâH…?Ô͈d4*¼_žkßƾ«Px–r{Ä~÷(¡¿N|óÀÞ±Ý$ækG<tÝzîˆnÝêp‚<ØÕh5¿gÔÛãuƒqÊo¸‹i9r%àÈdÌó%€Ž€_‰¹òOáA‹Övò –³ŽùAÓmPì)OñBó¾Öù@V3—¾˜ÅS¡³ôn,–¼íÒĽÁ{óþÙ¹3n®0¸W¾íWé2<kUe¢˜Ä»¦E]p¬§©û=x½cëijtû½ëûÒòaÑéú’›<ñ‰œ~ÂTÅA7Q/¸]G–'íôb`Ðçy˜t÷Éõ)Aôž¹¢ý®àú¾´|ˆÎS4¼U‰gùbþÄ¿v}_Z>£þÑõ ®”Q>ì”åƒc¾v}V¿l®>ý”oÔ ;ñŠ©RD~aªß!=¿–7Ò¬—çvâZ…Ÿ…f:¸Ð«1Ʀ†fz˜PJêû_é¸ +bM$dèÆíY¸‡H ›nÉæ9Áïà‘ +àÅLJÙåvK唼ãË.hX¹ä%0é .h)7zp"zÂ’Mš‹«#Òï{n!1 +üÊNÄUU¶¡¿Ks—M[ò;ÒéÑ•TodGË9¥žfè’‡=ª½Ý²»ÉK™““ËÛLžÍ÷Âä®›&Ïˤdò°’l2g3fÀÇžíZ9÷Ü»z +\"Ì[©^ïÔX형umÉA¿ø%ÎÖN‡áE¿âó TÖ9Ðܸ|4@÷ïá Q©Œt–ÒªyBø–ÉqŽpËÿ„¾Ï¶Î-=Ì4ßÖ3‰oñsiíy€ÀŽ8ø÷¦êÆÍE„‹ +w*ÿN#E_ZÁQ.Õü,™;®âK-pÓUDkE¡u,r()mò°)Ç-XuŽ94Ò÷Ø?<;‘PâU…Û”Üqà%Ä ŒÀmh”{”‹Ä§Ñq›ß$;h{ªAtÎ_in8Æk³ + ®rE#ÜÊ¡}Y*“Ÿ02“M²âDˆþ˜%¤Î?54fzúˆ€Q;ô´#Ó¥»Æñ|äºWÒZô5ZøBÞç<Ã'¿à&L¤ð†tÙQÕU·ëŸ¯O¿T 4oÁœQ²ü +z¢ú) ‡¬°šäWF»Óθ¿Â“L +?ݳ/}>ĸ25 \§ÓÄŽâ“8ü¸±ÃáYT™¥—ºärF±TÚº¿!ü¡+¬K¬î›'u{¨˜
W{„å4ô úšêÁ Y.³U†£L‰¢îÉòLî¹Çý ×Ä[¿½çãÅHé·]}®µë|ãm5,<OÏÊ0|¬3tñ•arÃÝ_ýÌDÆýa‰*M»à RÕ^‘•çÁ˜½%z¹Ø3‘iöƒš ¶;˜„w$}kKú¢ñèv´Ó<ÂÁÿ‚!Ì£|Á¯&wßQÄäÀa.1ï¿^n0ï9ð*guèGòŸm~‰ú.">að¡…PÝ¿î´3¤6Ÿ°s–ihOâ§B,Èdº@ôkc„¼8£{)Tœ$þµ>«© °c9f$KØÉFÇ7¶uÄFÉËÖú¤š–©)¯‰‹’ÁŽ•3<{ò/†¹Àð/+ô˜Só†7£|‡ûäÔê"˜mO€³°ûžI£òD_î
?h¨7àöþµä(±°bF9c"Ô·'ž‚ÿ>í>Ú6uúòÒ«;MÂUìe³÷Znr¦B½ÒW‰U³‹\b «§ù§LvmIzDï¦Ò~!úêÝ™¾—8'Wêzf߸n.XPÆå †IÑ°B"¬ÐUó߀Ù*Ã0ç`¦³nú[Ï\#â9½ï4~ i¿p½öm§¶È¥²î,%áÜËM”2ˆ8€}k¿5ÂI&ç&ã’à wó¿L™Œ×w½ÂÔ\r”h›XXlr˜„5Z³u³„¡Ãu7=Ëvqnü]ƒu«Ì[¹à¤™HZЉ¸N1rù½Ä¥•}ãnÖje¯NO÷|ZkRÂVuf?®6tç?Ú˼¿t¯‹â¯E)1„%A‰$æ*5—¢”Òª1ïÿ·÷IíUõ<îýç~nIœ}öYgíï"fSLÌñ©F`šn*ø×Ý%»ä‚lüât ¸%ä4ëÇëÕâéÎéÆǯk»•`‘Çaä’Ñ%^BQÄ4¥ô°œâX[g¨-x^à©ìàý^ƒ¾Ìø/£Ö@Oû
$«7A`œ˜Êu7Ÿ,“{¨ +W6Zæ6oz±Ñ"]úsÉnv¤ûï0—0ôŽtÇÜÆÿ‰¹¸Ìžtÿæ‚NTGbÔQŠMz‡Ûo¹áÇnžÛ«Pvœg¡×=æL¾UãÿRÚð¡ÚK›QíXsR:ÂÆD¤ìß«Š`y[yæ9òSB£¸|Ä
B!7”êxéf¡KÝÁÁq@ûŽ)Øé°°Çòß+‰ÜÃHÎÐ:ã$-¢§³|´y€¬ +hð†6lǦ| õšÚh¹G%ù18{ß
‚³Ò¶'<À3…ç6y²ÿÝ 8ß6*HCš)Þyßšqi'ý¥ÂšÏL•{×ͧ•å®9fOS(εÑòƇnŽy}JgÆìHçÑ|á+×ÔxTo#¢yãÖŸõ¹Ž“\ágdˆ…nô^Y÷¸E:Ÿ_)rL¯À…s¬h‰¡läÍe~Ë7v
Ÿ·Y›‚?àIw¶y‡¡—×U5¬ïÏ- +¶ºQ¾ÔdVënÒϽúõç®»|°)xã¦ÁTŒ:„¤Êg‰óØÚU +éK3cõµ:î£4—Åa{†‚ÖVîÌÒ†¥øpp–äå—ÉZ³cóïû"Eñ¶&˜âô +ßõé®D÷õ™Ê„Ÿ ÁeT_O‚¢—yS(;©¹™"Xg‚Θ•Á€i¢*‹1~ƒ[óT—y(ïTe²G„œšëoœº”f©Ôt`ñÑ*¸¤¾z´•¥~£Hþn¸õ:¾ÙFí3qî?VoUã9–¶Ÿñd`«äL¨œ…ª9Üh¼Ø@€9ÔÉèbÞ¼ +¿”¿‰ÀÍwS¼AÔ÷?(¾ñZíWàÐ:~èfÎPý¥7i½¾#ùæK3ìÒ·Ê F4Šáék«à¢ÜÅ‘‰¨úTuh r<ýº=ÂQf£®1 +Œ6-ȃúò¼hÐÓº9WêÏ~Â_¾A8,s5‡ð`}ÓŽ^CTÿíM{ÿ[F#üEüóòP >¦k¢I²–„,îvI(ôFxrâ?A4ÂgØ´cD«×~E´é†ð:~¶H>ë²yJäD~‚‚ƒÈ ÎÉs*ý,ïI¤ÓÖäžS?j½ë‚ǃs~Iðhp}•ï +Ï!" +ÃBN)?$ë ’'•WD^ÂÖQu—ª;ãbâãŠgé[‡‚I€Hà +8Tj_k«³þÿ“,À¡»Úvïó¥—½V’•<ϔĻ4'E)‘ÌÒIN¯yEíªW BíâL¡ØÐûJ¹Ûq”¹%0r"ÏÛE=ãoª¯tLfÆýÿSÅÆîîÚìKÛË{KUÉñ¿T¼é4˾V:ebkÿù¿©x¨i¿vöÅóDfƒ¨@ÿÍwUñཨ܃ëí’¯XÜ¥i×£èjJº´áÏ3séâíjOS½t$ ++]à„Î鉹TÝ÷wD¡I¦©ÍnZqìl]·Œ)5°7ô/"ÏÉæ)…”ÄOiQM…ЈbG#ü±eVÐ’MÞ
÷Vb€TüUHÌö³Â“±Ç ÔDk”·Ž>ZeÝ)OîØ Jf6çä|¾…¶Þr¹Ùí¼¼¾òõâëâØÑ!•ì®ô‘Íâ üiTúÛŽ›™dü c +Ø¡hÃÝÑB2ó‚UøïÒ4œ¿š‘žÈXÝ“½ß¬aâ&3‰yâÇi‘Ì¥h£1_Ó¿FÛ #Ohæ5eÈÓ©3ƒëŒaâd877—ÓßL§¢ˆßØUq?—ýZ{bÙ)·vÀºú÷y zô4+»[·)YeßÞ”Î÷ç4“bФ{¹ÎúQ¿êcÿ¼ž^"GÑ9ÈÉãmzeEÞb³I_l +á<113É´åÏX<(åé3,Ne5*-M‹\Zß?ŠõÁzB„˜w:«y@ï…´Ö¨x"glè4Hzaºgëa–…•ÞQÿ‹oÎ’#á†DöQ`wSò7#¤›zùëÊÁòòmå:´*s{°“+Ä×»\ðxêø;EªÖÖ3a{ˆað°w +Î<êm¢ +“†^Q¶ö2Ôì q
§_-Ú¿ +¯7°ƒQ̧ط +hû›o{;œ*h<y+êrg§fF»§œãQ]_½>¼›O7x+TBú˜‘"KB
·Êë:® +«Ö"%OÖt +endobj +659 0 obj +<< +/Length 12122 +/Filter [/FlateDecode] +>> +stream +H‰¤W×rêZ|Ÿªù‚H’BaKIälÅÿÏÚB`ßSÇ÷žºó¢²À{¯Ô«»É$nÜÿPîújíåꌖÉí»zÒ›AZ É‘Ôzá +¼NF²ÒB˜õÔóÕŠ K#ûþ¿úInô’Í1ÜÕ
![7“æôz|KË-±õßÿæÔaBHJÒL5CÞC9Æä#d#Aks´~¸è™…«æ™ênnÓŠb_—+Çè|©7‘°7ïÕ‰æÔq´L¼4á–Ø ÂèD!¤uν#]ìMSP›ÑÚŽå¨âñM€ÌÖ¹:i†Ëì&}áÉÛÿ±z3'ãJPsŒdÏÇDÄ,»¢’¾pµªþñ*ˆÙ½·U±alq5#69ÔºáùäÛ•åq¨ +wlãÚ½Çýøå©gbYZJ5‡×¨p‘3å–ðõC¿ƒ7Š£blv…þ´ß‰Ü™/oô§Hp›¶; äïæö¯‡VrØÔ&””xrtR›Ò³aøbÏ©3¬³‰šôVÊCGæÏ´r垬¯D>,²9º#®øP¥´&tmw¡¯ù,|‘!¸ÑG-¯ +¼ry‚5ß.%ÿ¾u£jðG0(ÙîÆ +FÖ×–Šk—ÇXž£êVÏ©r1M ½NF¡Ö%« ’ð€‡ÔJ_Òñöm·cøˆÐþúÂxßÁî5Sú.Ú^fCb®ŠªãiÉ%6jl&)ÿk×Õ@Ê<ù;r',Ùrç³JM9²p„qÇ!ÐudoK-©ýj¼Yû=$Ö +b˜Œ»aU:|>[cRžàÛk±gæd’¡ÞÞàf©GåÕáÑ;ù86Ðîäâ‡VNŸšÝ7C—À[åz‰`Ý×ÉœWÇPG-i¡L1¬/•=Þó–_f+êÞ ¨iøŸÅOGÀ÷’aÛEîÑ]Z~Ja¬ü¨³›
S:†å¬7x<-K••cèöz;DåÛšã}\fS•œÖ5™ºdN·’¦L‚½a㑹Ÿ¶½s¡¥â«7>“¾ðá2ƒ¨JGb€ƒÎþk Ú]Pi×=þ’àOÙÕ1C7o¸*;¡‰w„¹ÓTèbK›TEžŒ~ý†oü—aΚNör<>§‹)£¯«ÆyzÝ„0°Ýñõ½RwÜ3ãƒZ±§©§âLv3ìÛ*|QŒ·_ÿ\&nþDhVõnv˜£¦†êB“¤:ŠÍekãŽô-“‹qšSu¯¿£qµ/}wÓohyα4šÇ
(»HBúo9UzO\ÑÒy•¼øxÙßõáúªŒ÷€ZÎI+@¿0_Zñ]5îZaþ*W?‘«~ýؾînZuëucmòaa×þèâj|Ýý²³â¥ˆ¹5ÉL^;¥ŸtŒøë£}ÏNL9`Å®déµ×>öc¬“-×Éû¶;Uê¸M=‘ßRhE·ÆR±Î¾ÊÎFéì0;OÑ©¿¢©u€Ã#ì>Ë>qú®žp¿rÂzã%ãíq‚p| H+u›°ö$ñý1yF^nÆ:¡ŽÐ" è¡Åz„Ôغ&Äræ»–ÉžÃ(>9õ0Ù +^c´¡Ø¿¸Ÿºç +¶ ÷l•¨ô»¸@n j†Aeå+°+áç³êÓ4ODÉ ð¾6»ÞH”ÏEƒu±3ð +é éŽûÜT?vÆ‘êÖâ›{Ñú¢lΚì5Ý9çøS‚Sqß1yåî–ÌhNàÀÌ
´ÌÛ¦^Š(oý2jÆ8SŽZ5å6óxnjïi~ u5h•ÏÀ†ÖL+m(ãýüNã}¾” +—¤9£ý±0…^ÐùÏY#ÞÇ-ÊqMh%ö
Avî„¡mjv¦Aä*0I^9F&M$’¤m¹·rщjákÆûÖŠ/„—ÓÞjzˆÁ|ÿm»û*³@xaÊùaªç뤄–2Ä–$À(~xµ»¥ùa3„JT? ¸+q~›ÓÁ´Ú–#ãH7¬#’Jd½ô!´¬òœetrµwBènöás“}Qk)z‰ÔVx/o
0ùY(êEÖønð±¸f#M +P¥˜±¯ªÈ¡ÞçÖæ¼vžÎào”¶& Ä +‹wS û]ع~¸êt“éz…NÓüÒKÇø½^! ca·ø¿±gâ8 Ȇ"ÝNOÝ©ËC!täFˆt±›ë©D`šÿ¥æ P@²I|·6I©Ã¹‰[ +E½îæ–z0MÍüç$[†³5w§_Ù_qáEªT˜gÙ4¬¹ùËe:ê硾W®U…ê$Þ?ŒÇXrföiçÂsåh-ÔIÝ býÿcÍtPÏ@çªÆg÷#.f«C<Køm;"QC»ÙòxþSÁÞ¦*¾Ã¡0Ý•6oŸ3qÒè¹£F\‘zcdßÜQd©<]Ærÿäº t;8Äòÿ;™‘I~&ûf•¯±ÇSÎõbª¥§‘˜PC=!ë껦慼q/#×Í tG"íSl¦Çu‘ˆI¾%S̳+™Vz¢ýÆÇOa9¦gqQI–yXkTùÅ«ÖÊô¤aŸóE•õwçÒ¨ê+lÿ¶þ»N(3p ë¡fèžr;í¡fèž‚06C÷œÖú5C÷œ‚ø›¡{NÂ<ÚÝsBXL<ÖÝsBh<l†î9!thŠöÍÐ='„ZàÁfèžrÇó¡fèžBbâÁfèžrOóP3tÏŽ =ù`3ô÷a7'ä^ÚCÍÐ='t5H3C÷œ +ó`3tÏ }w•ô1Mv1p+$sŒAÔÎíj‚ŠËj 9çiezЃ«-E¤h¸›>&ëÚÍ4q¦6éO·g-by±Q˜Öª'çDzPë‡N`Õ‘cUîpHžžP®™öDãÁ7O¤ð%{ü2“Cߪ裈T=¤½o¡ðSHÍ=d£_ö„mßÁMc¤*7Fõ„_¢ðòbâÇ´¢«Ê‰›!›uKð»Aã›ÒRå—ü°Z;ƒ"%ÎV·}æ»GcEJ9uÁÏ¥ÃÛü!XeÎg×%¸t 'jàƒYkÜÄ.
)›W$?c6‚,6çdlëÚ"ÉxÉþ´J’ù_Ú«s=qd‰>Ñ#‚29GE”ÉI¼ÿnaƒðÌÎÝýãϨ¥®SéTXƒŸÛr2Lµ O|³9–[˜ØÐ?_µÙ©…Ö·
Ä…`Q´Q’C¹¹Š<‰K1é¬Ç–ž¤ºGªÊ–4å^6Û¾ •#«‰ÅšI[´L1úMÚ…RZå&ü.eÙC¹16ð?䦂q"„Ì„gé&w\è;TU‚Ù1|fO/I+b¶çm»[Ö˜Ãôî½oPƒŒ0)hü0+´Y®ÛüÁ,€~@yŠì|Ha˜õfòÄ1›<²6„JâØÁpw¸ˆûupM¦í…ÈQ´m½]Ã1H°Vã,wç†÷2®´·…ƒ‹‡WK4€(Á¢xñÂ&j ˆ¥¶¼ÃŒƒŸÁÆB¹Ø¸°>´b_Xò}MžTÕ¯•ãSîµµ$H[ëNÁâ̼3i×é04Ç:>‹ÄÛÌ(mŒK¾, +U™l,ü7l}á> Œg³PC òψ>t+¬é„å+Ã1”›:ÉíÄå²aàMQxñ&= 1@‚´TO2½SâÊ,ð¸ø›häJ(t!#Í-õÒíFv¬ÈÑêqŠòu¦£Äû`½°…ñ•Ñ(ÝTøºa£LÏ`ET“ø€±Å:u´Jyù²^Êža5eÍv +±¥-PPFÍïò°}‹+yÔ0²çt~Ñ™*T¤š¼© +÷%÷w'‡ˆ!hXíϯVÃnGܲÿlÀ;(^ãzÞÕ<D,p
¼ÚΫýqÝÔ~(Áo¹ÆA{Lw,ðZ3®)B:½ˆ«é¡ö2®¨Òâìö ++mDæBXò¶î`±Órñ ++÷>~ZdO1rç&Äаz§àä%,3¯1mš¥g˜P{þV¬b¼ÕbB•öO`ýS2ÝÚÿÌFNÉL¯Y` ½ì‚ß· +ï\¸>SÛ•Ç›¡B‰ÜÒå$Àß<é¸(D&›•„KÄ+ç¼=cuZ]™æîˆ
‚È›êOgÖÛ[L—YõøÚB?I¿}$OmëÔ—}’}1Åí"=wšöÞÍ
V…øzðg&§µÂºXóC1ôAŠ…Fh®¥…ÐŽ*R¸KCA©E¦]жњ +þ•ù3 Gö +z·ÚB²WDÜ¿öhRvã™}{%A\h-?cEsŤ€›—¯{î5¦Îe×`lbyô,ff@nRv¸<»C}ƒNûƒÄ /û%ÝKLø(ñ)’Ú !r‘E,ÿ^qè6'LÛJꜮ(ZKåñô¼4B¨»¾îµÅbb'^©cZ8Õj¹)Lw—!ÎMt§÷ƒò¤ÚA¼œ÷1)¦“åZȸÖA^݈³6Ù–ÕóíLMEœP{JäøˆPvÿG~µv¥ÑsÑï®Å˜¶Šàg2™V+w/ØzA¥µVQÑaµ~y~û{Nn“,hßvõY¶’LöÎ99{óKµ·ñt£={ +|Q€SéÀ,ô™,dveg/UÉÔQ›$ÜE
E䔶’Váä¾·v÷Æ¿Óô¡3ÚGnq.çuíõfö}üöãÚ·«F{>Û¹ÏáµW{<ñÝd%y4ߎƒëc<ëdû:ë$Ý®V=§½àêíì}àd2ûoÇùz¡::Ø¡EkÏÊ^ò^_g›[k÷¼ˆé×÷ÅäN3ÕûCà#+ÒÓ¥X™ÅF1qåÉ×ÏúçÙÝ™»9-㜥à<_¿Ù|k:ç[ï0¡sdzå,ŸêßW¡.Uúòs!S)Ä›˜‚åüÕìº]Lle<YãçÖÒ…%¸[³çï0¶ñ&Y‡@]kçšÍ£óµÊž¿_Ì'/wÅ!ðd빯¨[ÕdÕ ~ouþYM-ãò–uZh_Vê?zùÎE¿Uo÷ {ºöë½þâœfëf»\{¨CŽYÿ3á?½À°ˆoÇà`où<6•`£
+i”Û°ÈéR¦ÛË7/zÍN»Ö}0Ò¬¯º]>ØÈiƒ>…ÑËFödžÂpø)i,à@ÜéilÊ42øQý›ê3t‡Ÿà#eù>1Ì”iÚ~7s‹?ãá'£ú€hZ7Ð÷Ý Æ¶q|b—1øm/6e»¦a¶Ñ’M+eSàe;~Êu<=Ðëxì;›"žÍšelâX›câi“9°ùxÓ vrø +ŒDÒ¨a +Q+„ß"°©Çbf.=M\TÄ‚C´XpÈ 2‘Êð–ˆôXà=Œ9ÑTáË‹XÀÇFÒ…>°&ÞžGص@m+š¡#óAð`Sž‚«–?cË@ñ•pÆ<g-Ʊ¹ÈñÞ±”Ù°¬JýGÏHcp,»õúAû²c,XkÕ7Ú—õØ™²´ßëtTÏrl +ÇYì•Õ4ª<Xû26åJ™Ÿöï‘ÇüÒ¢îK,ê¼Ä¢ô%µ_bQ2Ù¢ý§®kM¼®¸Ì‰i) +¶‰ßãâ¦ó<ï6xµq±ò„U~á +(íZ#µS=ºÄC¢ÿy)ð˜x¹ ¾-â•Cb†G ¥”‰Àg—£‹…É@|_¦!ZäGç†&g–ÃEž‘CÎgt +pŽ* BÊzAÈ?Òå7'R®5B9…uy‡ÿóRà1sù)@ÍÐñ6¯ï©·4ï8º Ró¹’Ô -Õ,O@SCH«Ä4Ñ +®ÖyFØÙèá°ìp“AÈ™·¥Ùa¼µöoö=QÍZ#4Sš°CJÿyÑþØÊ‹E»°<¬-J9œoØÒýŽŒvápÔ|íÊìPÍÜ0Õ£H/ºò T±Vë<#Ú‡^ÅFF»°9,ÂSÖV¾ykíßmq¢šµFh¦:4a‡”þó¢ý±7Ü‹vån8’uÎ8ÒÖ¬Œyee´uTÜ_C•“aÚGÚZ•‡%*³˜ÿŒhŸàÅV1“ñ®xrÄMã*ñ{ã]73Š)¨‰:¤òÿ%ÚÕ?ŒYâ±=€°ÄØ*zL+E¬ÀÃ]ñ +<¸¥Ð’iI4’–˜Üwn2ÛîñÝÑÙüê×ôóFs—à>:›ó2%nÌlÚ”míQÉÊ¡7ZøzX±ð¾U]X/”ͯÈ[Wûàëˆ[…pžÀÅÖ¤O»}Å<òp°¨OéXÓK”ÌzŸþúüX²Nä¾ãoéûoø÷¿a}ìL÷÷îç‰îâoú¸pÍgàxmE7¬¿¶¦WÑ^"fÿGA}LjºábwÛ}zør
ßÏ?
*á-sLÊ2%‰KÔLhš©åi&†¹•ÿ¦?il¥¡ ‡ØSû™cx=2Ú…èjÇPFõÂã§ZătÖZŒÙÏ„›méÂVc¤IÛtKo“z +¶3 /¨#0{˜ßh›à’pA+©›JÝéäºì¾z¯-‰K¸·±³½óèH' +ñS¼ÞöùËB"Š¨Xø覻ƒ» +«,ª·üÌîw`a¸‚ßRþ6æ6ƒ'+汓oB.Võ󸼪²ý¯«íu¼—ÙyÓˆkqãõȨ!È ÑIJIÜÃØÂd1°xKt×E•ŠÁËŒœí¨#Ç>`j–ÄQZAöy]e$Ô%5äÁËìf‡ÑŠHKz¿*p Ejz=É/(+èŠÇ‚Ž#sÆe©…$Ü<üÆ8ÛIZQà'hºé¤íµž§ûkOܼðDótT +šòÂ=ÍÉœÚÑSŽþZ-º7AФÁý5¶ñ³â*Û$u$1~“‘¤¤ F¼_ƒ/FéÝä"N‘•-+ŒOGz‰tMÏ)…ª]c0à…iܘ>X)ëaùJÝ$,ÑGñoyÅ*ñtè5²ôš‰H)Š¯ayÔžøÓˆIì¼þà§óö?Ó3ÌSAýèìÜÙÞïoö˜”Ê´o¼iµhí38LFúT^°÷T#ƒ
'Ò³’VÙKŒh
ÀÆ4»ÊFGÁYT2Q·Á +ñ\Æ£è +MÞ™åˆ`šÏÃ
£°©ÁÃNC»Æí.»pHŒhê!¼G§‡#r`÷øz+Žç](Ž7¤WªbY°k#mÄÄþ˧ûÛ/ûËîx³y¿Ý>\¼¹?§µcÍölqM8
k\\ +ç¥5*—¼¢š$Ê ÝD7Ì0ƒa,؉ +H¥® +æ%Íq¬ëiË«dTÂ[6jÈÎ+ömäl¡ÚÚd´½G·ìС(Д岩W +.”‰=ĤD‡ö*5HÉûl„¾¥}
ŠÝCz@¹Áw”Ž‚ \Ói:¥#ía¯¤
CdÀß°Qz“qzÙƒìS0/M²7Ø5ÓfÈc¦Uì§óLkeL)¹21ÚÎ:•Š˜<žbÑšHKz]È[ð,Œý.`É€‹rJ%£z`,Ú +$3Ðê’”Gh=˜ÁÞ:4G¯F€§\M.ù‡—Ñ#êÆOù“Oñgª'Mé¼Êš†ÊJÔÕºCH¯QϬ‘QGðh.$©@²UËàÜÁ±º—’AÌ”ºžJ(‘֬ȴV'&UfsTNí(¨šJMøTå:ÅÔ\ñœ™–®æ)Sâi¦"÷]—%*£µy‡_{çbL#@{0%]ž *é<•M&+¾¤%41|”ÌrÒÛ¤FП>o +6ÓªÏ1´:$3è˜A1g™IºƒGqù•Vc +ükÄ(Ã8±,Dº&©ÿÑRGb#ŠÒJ2‚¿¡(…K
Íb´8ªŒ• +´+|Ççiœçe`´…Mm³YaÓ$«Nøäå(Þ§R§¥x£/<˜Š_Gt©&Þþ]ù?90Oóß?—ÿÆ7l;Î̇);Æ.°CÆ; +endobj +660 0 obj +<< +/W [ 3 [ 301] 15 [ 367] 17 [ 367] 29 [ 367] 36 [ 590] 39 [ 613] 43 [ 654] 47 [ 506 709] 50 [ 674 558 676 582 481] 68 [ 525 557 495 557 545 370 502] 76 [ 285 367] 79 [ 295 830 546 537] 83 84 557 85 [ 389 405 396 546 490] 91 [ 501 493]] +/Type /Font +/BaseFont /HZYKOU#2BTrebuchetMS +/Subtype /CIDFontType2 +/CIDSystemInfo 663 0 R +/FontDescriptor 664 0 R +/DW 1000 +>> +endobj +661 0 obj +<< +/W [ 19 20 600] +/Type /Font +/BaseFont /XKYCEO#2BCourierNewPSMT +/Subtype /CIDFontType2 +/CIDSystemInfo 665 0 R +/FontDescriptor 666 0 R +/DW 1000 +>> +endobj +662 0 obj +<< +/N 1 +/Domain [ 0 1] +/FunctionType 2 +/C0 [ 0 0] +/C1 [ 0.3922 0.3098] +>> +endobj +663 0 obj +<< +/Ordering (Identity) +/Registry (Adobe) +/Supplement 0 +>> +endobj +664 0 obj +<< +/Type /FontDescriptor +/FontFile2 667 0 R +/FontBBox [ -86 -262 1082 943] +/FontName /HZYKOU#2BTrebuchetMS +/Flags 4 +/StemV 92 +/CapHeight 715 +/XHeight 523 +/Ascent 943 +/Descent -262 +/ItalicAngle 0 +>> +endobj +665 0 obj +<< +/Ordering (Identity) +/Registry (Adobe) +/Supplement 0 +>> +endobj +666 0 obj +<< +/Type /FontDescriptor +/FontFile2 668 0 R +/FontBBox [ -21 -680 638 1021] +/FontName /XKYCEO#2BCourierNewPSMT +/Flags 6 +/StemV 40 +/CapHeight 571 +/XHeight 423 +/Ascent 1021 +/Descent -680 +/ItalicAngle 0 +>> +endobj +667 0 obj +<< +/Length 16052 +/Filter /FlateDecode +/Length1 27336 +>> +stream +H‰´UTÔUÿ}wf +4PÔ^*0*">!•,|¬ZKkn)>ÒLëYjî–i%¾5Í'“¦•š¯Üõ…ôÍðXµÝ³çìž¾ó¿ç~ïÇý¾{ÿ +a%W3À DI‚Ò}¡ºø0pžC{$`E£X€»7ŸcÔ—™¼+:J•“³KW·–î<<½Z·ñöiÛη}‡ŽO=í×IRk´uþÏv +éölx·çºG<ߣgä½^Œêïc0Æöë×ÀÀA/ÅN’˜4tØËÉ)ÃG¤Ž5ÚdFZzFfÖKöØqãsró¬&NÊŸ\0¥pê´é3fÎ**~eöœ¹%¯Î{íõÒù.úÓâ%KßxóÏeoýeÙÛËÿúλ+V–¯Z½fí{ëÖ¿¿áƒ›>üH®Øüñ–O>ݺmûg;þö÷Ï¿Øùå®Ý_}ý͞ʽûö8xèð‘£8vü»'O>s¶ê\uM
Jå®tŸœ +‘˜EE´”êűWœSÌR”*(ʇ”-•ñÊTåhå¿¿[’§ä#ùIÉ_ +“ºI‘R/)J2HEÒi´QR·Q·UkÔþê®êQ¡qÒxhZk|44~š MœÆ¤ÉÔ•ë>ÐíÓÖ]¼.®;ßõõöYÁJŽ}O|ñOqì¹{‘bµ’”” ÊQÊÅ~Å~79v©$9b‡K=›c¯þ]ì±½8vûæØ{Ccl\Wݧúúúó@½7ÔÀC/ nkÝæº +{ûë¦ÖÖM©+¨‹=_~¾{mãPÔ–Ô–ÙîÖ–ØnØ®×ÚdÀVZëe±I6¿š±5Glεg«ËªoTϪ×ñʯžP=º:´:¬jYÕ’3תr«’í~T_8†¯Ð1œá
ËGˆÔ'æb•xOlùwý“œfÉÒ†õo$Åb®ÙˆÕ˜‹zeø¯bæã¼5¸‡RrÁ¼ë¸…¸×ÈÕ¸†w±·p¿b6a/öàC¤!‹‘ýÈD%öá0à á'dáŽà(>ÂÜÅœÀq|.ã +^ÇXdcr0¹X‰<L€1 “‘LÁÏ|2Ó0Ó13°
å(Â,~ó^Á\ÅgtŠN“µ$wjE +RÒ:KUtŽTäDäI^äLÕTCµd£ïé<µ¦6äM>t~ÀCÔÓEºD?ÒOô3]¦+t•~¡ktnÐMºE¿ÒmnÍIúý“îÐ]ºG÷éµ¥vTGÉ—ÚSêˆïqžž¢§ù¶€ü¨“ ~•B)TÂI8“DjȨ
i…‹hAIGþôŒpnD$p?P +-…»h%<„§ðE +¦êJ¡Â[øˆ¶¢ðí±ƒÂèY +§n¸ˆK¢NÁ†³üæŸC-N£†¢¨7é©ESÖ“ŒK})ŽúQ@i½Dñ4˜h%R
¥aXA/S2¥ÐpA©4’FÑh2‘™Ò(2(“²hYðeÓX‡å4žr(—òÈJh"M¢|šL4… +i*6ãc(ºàSlÅW¢7¶à|ÙØ…y¨aX†_°kñ-vâKjûx ºÈ·`;ÚóòV†òËŒúK¼®Ø—ýWíæ™Äo0ÿ©TÇxvy¯„ëšúÿ*ybò=Ø‚¦;ö9ó*±€'s9óÿÅ߀
:ƒçr6k”±N¾œ«{„/Â(‚§e
@¬ðå®îâ_Ê žÿËt”ßÙ$žH#Y(EwJQôV©ß‚\¶Eè€òß\¶ø£y&ƒQ(ŽÐbEŠE1Kì¹®}¸ +áØÎñþopÁëGØûa{?þøƒú¡H îß/®o¬ÑÝGß;êÅ^/Döìñ|D÷Ю!ÁþºÎZM'_o/Ow7×.ÎN*¥B‚ÚX“$û›d¥¿6..ÄNkÍÌ0?Â0ɳbב%“CMz\SÏšYOhê4õÍšä)õB¯`ɨ•äƒ´†'$3¾Ð M‘ä«|Wú;w&Ôj¶Œ¾ƒ$“I2ʱ–R£ÉÀþ*Ü\c´1™®!Á¨pucÔ19@k €(r "ÀY!àân++tFs†<8!Ùhè¨V§8xˆqø’bdg‡/)Ûž3æKÁ;KlóDš)¨e†6Ãœš,+ÌlTª0––Γ½‚ä@AœvÁ—KΔƒµ£¤eg†4 Y¥óÔJ¥·ÁÉk¯^yœcnä8é<oÃŽÚKl>&–7áàÜ8C®O¶ç2›iLÈÅ É
´„´Ž›¡
J‘…É.ÙÙ$ñj—7IšÍMZµ½UFSãW`ñ•‹Ó¤`>}ǧãå’¬ð7¥¥[ì»9³Tk04œ[R²¬70¢77Öj¬e}³‰‹È¶CB²ªµÊÞÚèfHöd'&;LÍdï¦ôF+9Ôh°ç%KM††í¾´ ÉÛã»zc£:ŽøÎî¾{ïî}¶œ¦w\Ì™›ÃØN]xÂîÈ5* ¡~Gr1
ì•Šƒ¨QÛ–¬ +Ëi?ôCˆT©R‰ª$2Á©U[EµAŠ’h¥R¤¦@S\¡ +¾»ÎìÝ9ÐÐ>ûöÍÌîÎÎüvfvËT®žÜo=•aYì˜mîÇMY=8ãïÝ?ûåÝ{1>÷ÇýÖĬW@ø +I_v)éή¹ŠË%ÔŠjúö_£ëƒÉs½Íˆû¼Uh·PÂ&¹µ;\Ü.ÅÒŽní‹ûxrÖ‡á*µD=¤ÑÖŸ£.ASûs‰B¢úü“Zk6im³Æº\,ÛT]çšVM‰îxÀÀ‡”j5kÚm'',jヶ3Wïm˜¹(ã¨F‰hcñY¶=î'÷%IŒ!o»O¾Öjó;’ùgwùj·k99c$ó;fHšì©ŠX|fÛ,Ãpò0qz6V¥CXyff†’ñ¡™Ý3ßœ¯L¾œŒ»É™“ùüÌÁÝ´¬ÎWξÚ:;ôì»{ž"ýÉm{g’;ü¾V”Û—“u–÷?ï×ꩨêÀ +²õd¦Ÿ=éÁôŽ]þ‚‹—ûéçýwñŠÔ¿{káäØç/Äó””“”„ÄĉayÀªð.^œh|ë‚Çؤê•J ø=óÀ”̨ˀí™çU™[—q”ɪÌS2zÖ¡x^“ò%üÔÙj¯Y;.ÞÇuÖºQÄôã€,cëK¥EX?RÄWgGc$iKD“‚•&9Þ"Q+áiL_Šo—_ãšX˜ôl}*aÖ”ËÜOÞ÷bé}÷jHmêÚ˜ZݵqSfCsS4p5óÖãßxns÷ð·Ë¯å~´gíÚ5Ït
îö~ðGÒøAå&Þ>W0›µy-)Ñ-²bXH1•3}“3ÓaS›mYDå#ÅÅbg(•ÉU¤^Þìû›ñ+ðU( ‰>+²5¶°Nïq6Sáîp6<–á©\“ßÄYS,0ÅtgªT/+ïsûÖßø¨µê?Ù__séâ«KÆäœvñ¶à«Ì¨Üð^»<ïx.¶Þcå,ß³[t› ²æ°9jN˜2å +¿Îh+\7Gâæ©Fâ¶RÄ=Ï&dAÒÈ—çH3ÿRÛ‡Ä@J§¿ïm 1掹‡]1œr¶ÓÙçrÄ®ˆ”¼ÉËÉ7#9 †¤r7dð§ð¥îôòS,ýásšz±éì(B:‘ˆ<‘‰³~S6g64DÜÄ*øeùO°òë¸û—ÞïßóÑòºÇ´«üjùwK74íþ‚_70‹ ÿŸáÚyÊ`6A@Þ qųM©Çäïz6c»„ñs×æ"<›À±-BùŠ·Š0±MÂÃÎ[°îˆiZ]¶È”LÖA—uÐå2èR.ë KŒQCi
YµBw¼&ZFf=GÀ0L^Í•X-î,c8RL—úŠÒÈwv`ùŒ$#l3q¥4yþ<Ÿ<Ïß+õi¥Ó</˪˜‰Ûˆ™Æ~ì52&ùyyYÞw¥db€É¬Îª±‰ÙY÷ŽÕ½cËÞ1å«{ÇêNQ
)&>ÅÉ7–
ˆ3€|Bo®}n»{rv¼³#CÆ7½~ž¿®-Ü»?‡ÖF+7µŸ¢µÜò¢à)ØTRÅ‚ñþ¾÷%'µ‡zBÂ@¢'”ù¡7Có!=0tZÖÇÝ‹^åiƒúƒÿšõ}‹‹ví¤Ð®£ +A æ2*'–…u +³ðÜRùØ„¥wSùÃúé·O?‹5‚)ZùÔk!¯íµûÜÔíàxp]®ŸvV +Br„µ:Òâ:ì²Mÿ¡¾l£¸®0>÷Þ™ëÝ™ÙÙ·Y¯Y/kÖ6Þ¤$6¶×.M¶€ñ[l¢m¨[”ªj‚!‚¤ýÓTMS¥Ò~UHp#µMª +ñRh…«6¡
-qªnŠ#ÒD‰äÆKÏ9»ë©ÿ@eÉ:;»÷Î̹¿óœç輎ƒA!BýáeýásúÃIxYxùÈè +S¹[QÆxg¨ÝéqV;¢Ùì0W™Bh†ãSLzL3‚ËÍn¨PWxÑéÖxÑ€@Ú ¾™LS#hSöµ+Ÿ©p)¶§ ãÛoj(pKSs+ ¥QÀÖœ}wß9{.õÀ#:ÛRx°ž/ž=‡=mUÁágßõ²4ó!½…?i×^G™§|˜K¬Š¹•l +¸Ùµ¹6!§B2¥³ªÃ,ƒl–A6Ë ›eÍ2¿`–M´=$ Õ‘Žúêlõ`µ“{$϶’–D°$‚%,‰]™€Gìf¶0bÝZ°ÛÐ*Œ"œö4Ø«óÓE>ƒ
Áöàö Ž¥cͱÑòY6XÞ7Áy¥±dËÈ(k‡4=)<5eJ¶™ÏR<P¢U»ž˜ýè¹êH) iµo|ƒ±Ùž#'μú‡¿l>Éjü*ûû«¿¾Oû>p=ƒè¶±0oWØÜŽkgqš`ž\tØÜjrfª†²Â²ª"|ÕH‡¾ªäÞËQŒÎ*{¹º\Gƒ‹nö2± Á¿©‹kˆ Bª•O‚Oé@ ˜¡ÑÊtjåÑÊÍ—çâÉhñ.8ñŽ0pÞ«‚lulSl{LØ1O…ÁnÆ´²êǛĺ+©YPîœÃµ©±©Hk1¹Ÿób0rL£s +(*KG»BíNWÐŒdO8möDÆis¢màñ3‹`Ò‹¿ì¡-“˜n3%ìHnHŽ&Õƃúñz>dA½COµ¦†S"U©TvdM6f2Ŭ۪B5%6$F¢¹º£zCõhµêõµûz|›|Û}ZSŠ øרN¨é®Ð`h$´-¤†åJ½SWè9ñ1XPöèÁÄ<çP©Ñ|74}Ê>ÿ°)Ekÿ}ð{ïmm”À¨««Åù´©1ª×%žp(‰F#0¹ª5©{º³–Ž>±òÙoïÞ:uðõ_¬ß¿bãhCï–m»*|òÎÉe‡¶>º¾ç‘ÞÖÇÇö_XùËy-[¾–½ïŽl[çé½Ï_¡>~M\V—)q%ÍÞ<XGf/vÔðQð~®›šF0äl8¹5 VÌ겸rÈ:c½e]µÀ8¥Ã–Öêvºî°»Õs÷¸º›îóöí +³°K7ìÃmÃåa Œ@à ,Ö0õmºº<ìÃ[†ëæ÷g5¦h>*qÒJ´R#•Ðh•F›F+´ÚvÁD\ÃñWüE}¦,رKTF`VTîƪò^iæ¥åZõVÞeÐÚk[‡³ÖkÊò)®Fy/¹x/ËéI¬Np‘ÈËÃ’‚„o’Û%r@ +þ=þ~Áݡ𗽜Ži¡ +çöÛØœRÚQ( + +>.:f¿¤wFoè+&ûrÎ dû>ø|‰ ÷ã$²3è‘Š…úÇ<,‹ƒ®â!ƒè©¤¹©’†&=eßcÓ¬D¢ç©LGš#Ñhô„#‚G\<·ˆ»Dò"€i6ñR _ÑUÁ¸éÅ‘ˆ÷ +£dþî^¡û‘ñüúÐ,”ú\¾1Ý™ósØÍ‚µ„gÒÍfÞ¼Xè‹ÊbÏoli_Xôw3,yæ…¯;ֵ㙣lÝ;m¶r=K^ÿ€êgÌÄEËæË/v´%1ë@¸Z„Ç”»‘[@YŒ2qXgë?Ñ_Ôè¿×ÿ¦{ø€ÆFä6¹Kî•ãR«—YÙ%åg—˳ò‚ô)Е¸L‘Õ’@“·-7æ²d¼«û;ŒG]ìfÃÔɸ‡Ü3n…‹–SÁ;tÞ¼MUåÏ“‚ó¤g.ZV܃Ü×qg—%úYÿ˜Í²v—=hتmãv6ÕšMçdWáÙ4'Ù„M'l׌é{ô ?uZ«Ö©
h*×ó
²]öH!ä°Ü*….£’SûÁMdñQ<Å®ÓLïéKÜ]4(hO4…åðž,‡ßaìé·;XÉ*ó!zš–(~‰A.‰"~CÃ!Æ{B«C¼Ôó>#T¬T`Ŧã¿lWlYïÍì›Ùítßîvg·Ý²»`[d¥–²=äd04àØ´'í•À5-xç 5=à‚Wz…Üáôr
Rˆ¤‰ +I4‚ž 99¸k¢ùÕN}ßïìŠþ³ùξ™7ó>ßï÷óý|:Ýa†Ã ±ú¶kÓBê¼Q¸Ëg'Â
¨‚ ¬h*l¹M¬Éúªª¬d@ IñC§j\KÍ©ªT‹FG{VõÿäÕeÕ«6î½øý÷Þßwî“ßv>ó•9[Þ¼”yÎt·wí©NÎØ30NõÃý;¶_^@×åZ6½ž}UݸìòòÁibI +ÔÁêž#{wüèLòXͯ.ýøîñC/vmßø¥‹¼iMúdõ¡_8÷/õÞ¦ÙµßÚÚÓ½q›sýÀk/~qgòxýÊÐzÊÝ\¸qóÊ®ƒ?þk TLùlbàž\ìQ÷¾üõýý›^Þ»LZÇßxÎä/'CH! ÑIœÛ”‚ S +’]ädðÐNŠ0ýÙ>A‰Pa +ìaÂݢМ<&ƒÛî„;8\?°}°B=Éë?ÙȈÂ+„ü
- ~Çæ"Û9~£|F€Ë\IÜa<MÒ`²_Ɔ]†3)M\WÿÎUd«¤]RÛØX;»qQf?]î9Û8.k=š/»äñ +¥-LI¸ OepíDžJ +aÀ%Ï©R §âãòúc[‡-Â+Š)ž;1éÜáf·¹ÙTŠr~±<ì7Âä‹ND¢s‚4º:ÝÚ”kÃËyÒxZ/kÁ«ô¸{þEÎÀÙ\¥ûÑ'˜<8ðaßnåÍïÝø½dÉ·ÇÿÎ4É’^rþ41ò.Ç€ö‚´C`Ï€#j†eT£Éh3zŒ>ëq‹Wq…³¬F-ZEÿLUB¡,` +€À´jwªBáPÕï¶ïïl`¢QœP!ìà +’'Ž°"Žˆ¨h>ÁQ àqDw‚1—ä©ñž€BÞ¬¦[z•®}"g:æL·opà-:æL/dÝ]€ïÒñõ:T€WêA5g +C7njy‹û4ä“u—Ç85Ú¤JiÏÖ…oUíœÿÒUºš½ö³=-ÏÞRkÞ<ä¼0¶® î
‰ ”Ðùv…eÃG[6ëA¥
-Ïð&ÞÃU®Â*É,xj|Ç +w6®äæ‰ÂA¨£;Šý:$Ë.2@Ž’AržÜ!¼4“vÒKTBP}œY$‰î. ÛÔà½Á"'¨ÄId½B»uªô˜´7DY£ ´¬,}%´>¤˜¨›Íœ™Á™šSt.:ò—îS@Uç꧿‚ÿ yZ:®´ë·:]ñû-n¾ÄA_Hú¯"ímœV} 5•/¤¾k·gôæ'ãä-ÿÞ€skç;¬ôu®;£Î˜s•Î¢Äyõ£_Ó·®@>v9jµÌG1)£Y;RŽ5}§œFZšŒûk2f +aPÚ*jâ0thÀê¨Eʯ?XþZ±±PbkÁ:bqœ´¦fpÒìö;wwSÐ.|9ïÝwÎ=ç;çž«.¶¼÷ÒÁ·nâJ ÿmõâ×æÀP(C#BÅ¡c!u4÷T®Ìµ|Vc;ÎìlQcg‹{¨¨±uQcë¢FÎÛ—³Çöy£¹%>V³/xee‘™£ÏÏÙp,;'¹¯ô–”_}<âÆÒe.ŠYFe‹6:ÔÞ[q»±²äàšÁ_©Ò]¯xØ#r²¼Œ=Ž¡³ñ’"Í+gôˆÑÒÎæÊâ¢ú¢®"ƒnˆÊa‹°9» £@ðFX+<œ‰ÏgßD):»> 6¶öÎ÷\wn¤ˆåQl‹XP +¹hÔòQÒ°GŽ2"F½×êµ{+½cžé«D˜+ø&¸O6 ƒ§„ó+[ +##ø!Ò,ômšš¡Æ‘Û¡#‰°N_Ñ2<èÓ7‚Dßát^…a𩤊¯g•c_7*õµÌNs$ž.”§MêŽÝ;×Ü:}Ì_þÛ–.ûfby×êeÇ÷숿Ծá7×LišWqÿ††Ýóãem•Sªýáúg˜-MlI³•†ÑñEM<5À„É‘'‹£¢3$BáøY>Ã'Ø÷¾Æôa4ÀÇQ€éC9úPJß{˜ú\Ê0q¼èãixúxb*Ñ8}HëCŠI”¯¿Ï«É“AWš¶Û≧Åöüvúæ /5àÊÐÍ!S}%5Èd½¡Ó‰4`¬¼¢ÊŠÊªð˜acÂ:Š¢ú&ñú=÷ã’ïݺhæìkV<ü³ŒÇºÏÌLžêü´sÙØm×¾¼öXúÔøH•’G—ⱈfVGDä4Ú¶#¢NÓálsT§³Ï9ëN€cÀá›&kïdãÉÑŠâ×ÜÌÞ¢ððF¶k¿Ž$‡‹œal‡Ïyž´½¨óò²Å˜Çóód>Ele/ÀÓz¶¯§¯vžâÙ<}©óô¡ÄËxyFK®/'×ïX™jõ‹…Ò••Rbé²¾¾D:!E*Êo¨Â}£¨ÒÒƋɧ¾ûÄÈC‡ +{ìþ*¼eàÎ:Ùéí
S—?(_a.`»¿«r²¨ïE)§Ïiç*)ü¦%-ËTÒPØë>V\)Ç{Õ¥%(šO÷²^^uÙ„ü-=(|õ Lß«»ÆÛ«@Kþ_ìÔ;+.g¿s̱&i6Åt16×ê°Ìb9IÖËV¹RšÖj’«…R´|’Ø/dLT )Ô„aQk–*ôÃvû•ˆª˜ªRmªC™¤– +¿%xÉ>dî’QŠs²º¶tˆ8ˆ8ÅUv)¬5ožY$*„¨¾òdå ‘/ƨ›=ùo¶ ²ñçà“ŸÜ+ÆgRæ;L\Çgrî;Ùä{&®“¦ãŠé³ü¶áX~E¦céƒhòQSÌ_å¯ó·ù;ü&Λ&S6Õ‚Œ +*Iã<ðcÝqXïÍ´¤'õ¾¼oì9»'?ëô uL\
_=M‘õ©ƒÀá!ݾèu%ŒgÙW¢ºô +ÞÊ~!à Çs{ØšYssOs*+™ÿˆŒýÛFÛ¹—•çh£Ù…>¬Û‡9˜•ˆMŽ«ä3i^ký²’mÇ|ä˜EÜdä÷‡tBë8b9@M:À׬²Ó½h’Î-eb*£XWyžÞ2KR›‚Ôf9Úl¾ìúñ^Z>”s"@°˜š“¡k'ìSMûÕhÛMŽùl:ר£´Xûšûw +C̬ˆ§/Ñ“’šÚÍ32àÃÁŒˆg`0”y#`n’§v¶G)Þ?ìÍð§'ùÓò}Yþˆg丱þÌülo¨duñdiª þPXØyR’:u.9£D9ð"[P +Y~LE.NËS27ésŽ2–n_aßdßiß%Ïvû)b +m +e.€Ld µG.\¥OAª´n‚FʬOh‚Ë“¡TUJž¾½2”‹$™é/r³¥/áÖ/¿ô~égÈ;](ÇëXò…w– ˆJ‡±‚3e4[÷\‘ì‹WVÆk‚J[»3gJB't®€ÞPÿÙ9“„W@xø„:·x&WšWy]̱½Ð¤ë©rõÔc+yÿ9/ΖX ½Ž-zåÉ9jU3fV‡õ+¬È¯zϨPv†žÅ#_^™)TzŸJô«¼Î_I“uÙòD„*©B{Ô³¦©R†}‡E[2^a$æèª,=aE{6_AÝ{•Ñ@gtÔ™¨|£¯ž(ê#™J;SÇýe¼.êû•žoá=eD¾CÅ»7Vðë|TS¡÷È9¢»êleúñXg,ÒuØÒXô†"OP†¬ò鈱rŽee#=jtåVZºWw¯en¶Òg©üÒx-/ï±í-Mù-O*ÒätáäW)²IzGßÓT²YÛ\ø5úŒ”Bŵ|ÀóVâmu&ÃÊ>E–̱Îæ—¸÷j^ðéÞ½z¶lA‰Å'6ÖÌ׸È*>4~+ó"ÑXñ©V
Ïp1¿"*Ÿ®kÜø5JS·/å'YBY€¾¢…VtÖåXäc%1[‘¥•±ƒÉ~Q?Î.÷Z<ÅÕ{ÈÒ_X£%Óšó[<\ŠïU–ôF{D}°9F”Ñhe\/”SåÖͳh›g]é)eR²¬5-Ix–÷Å$Õ|ÎÿLŸlEMP$†ÔS²ä;¹ÌW‚½‰Í}S†Îè»ô}¤ŠyfMaéoGGŠc¸cˆü‘îH-#Áè»2.£ä=Cs†¹•ûËxHíavQùšŠ±-Z}þq·¢‚Ÿ°°ŠO‡vN)GÝR¼ÅI¡ª¨†êR4Ö@M)Mk£ê¢ê£.CC4Bc4AS±r3±o´D+´F‰èD\Žv!Ä6É’‡Sôöì‚®r+uGÉÎ=ѽÑGÊà¾è'' wÙ \‰Á‚¡â•Ã¤¤!yjFcŒÅcÅ㥸ž€‰¸“p®Ç
¸“q¦hfõiüdhHM3MΞ£úË“,Ò–/Z-Œ_(¹q–Àsp‹hå6ÌÅ혇;p'î’‚ún,À=Xˆ{qîÇx‹°XŠë‡ð0ÁR,Ãr¬À£X‰Çð8žÀ*<‰ÕRp¯ÁZ¬ÃÓ´a=žÅsØ€çñ‚ß/b6c^ÂËØŠW°
Û±¯â5ÚñºäoàM¼…·ñÞÅ{Ø=Ø‹÷ñ>ÄGØ'Eú'ØøŸá ás|/qGðŽâkÃqñ§oñ¾Ç œÄ)ü ÕÜø~ÂÏø+þŽàüÿ¯t0ÏàþF']tãßø
gqçñ;þƒ?p Ë8Va<«²«35X“µX›uX—õXŸ
x²³ ›ÒÃflÎlÉVlÍ6lËD^ÎvlÏLb2;2…Ø™]°“]ÙÝÙƒ©ìÉ^ìÍ>¼‚}Ùý9€9ˆWr0‡p(¯â0çŽä(Žæ^ͱÇñ¼†8‘×r¯ãõ¼7r2oâz™FÓég3™Å +>…_ůáÓøuü~¿…SXÁiœÁY<s8x¿Ï„§ô†«…ÔvË +6ÛJp¡ÞŒ:;ný–]×MRµÜ†î8×B;©T>j]‡/›ŠÕÒ‡*V³©i•Šnºuø^’šFºJg·[úu]Ò<Šœµj–©ïF4ÎÒTEkY¦Tå4å44§QýNºßIå:§9Òvu©ÁiŽ_lxšÇPƒÙÎóf“Ó<ïez]¨nóÑQ««¤%N-ÿŒ÷oqZæÈJE¯†F\_H+¼›ëQxµe˜µp›£«Â©-œ69Ýö(´É2¾
‡/µu‡}rÖZº¾v-Órª††ãÅÎ6ìºæɨfZ®ÞÐ
-¡ÚŽÑ°Lï2Q]¿}ÖòUb¡É¾Ly{bµ¯sl¡©×x§ƒtÿ„WØó +Ó]-<Áø>¡M¸Ÿð +|Áj!f.i¶ÁinW5|©çÛx݈øθl—êVxÙ¨5µàŠÖŽøYËu#xPvŒÄl_C~‡îyL…'ôþrõn¹F·Üáö'‡òb¼ñ¡mVL®²<âÇ +Ýf%±F×+‰ïz%5xIfß4"¯'ت[’ÊQÂ]¨É÷
ÚPO +5!TN¨¼P¡&»*—JxäºJVÄSDEDQD”´ˆ’QÒ"Ó´È/-â¥E~i9-"§E䌈œ‘3"rFÌAFxd„GFxd„GFxd„GVxd…GVxd…GVxôæ%+<²Â#+<²½y#rbDNŒÈ‰91"/²Ê‹\ò"—¼È%/"çE伈œ‘ó"rAD.ˆz£ < +£ < +£ < +£ <&…Ǥð˜“ÂcRxL +Iá1Ù«£¥ëZ(E(ñì¦2Be…š*'T^¨‚PÂC½œ'zµ"g›û‡Žhœãç+F«Ònî4ô›ñJO‡Î³÷~Õ¡s–T¾`ê…TÖO‡C|º/R/Ò4ëQƒ™î®L5_ÄgúÆÔûÆÌlkPÒ¬k4ªºdx™õs1ü\fy._¼gýuÙàŒg/bãZübŸÇµž&¥n6»¾Hì²¥Ílhðú«¹ns£+æ»Âì¦oj¶å¸-öAÕ¬u³« +OÑòS\à)Z‘¥nŒV7Ær_zN߬°)pÙ¬ò)hó)Xõã·ýø«<~›OÁª?mÎñµ¾è7úôFŸ¾ÕÓax½×`!g›oýiBFΪœ5Ýãø‚·âÚêiiÖÒ £œÖøÅ|›±Ö]Y¢7ºJÚàoyä=ªì
]h›Uï{Ø®™Þ&G¯ÀòÕoV`íƒëÑ,^
ršNÛÖ[†Õâ1 +“YE²u–LïBNIûo:Pþ~NÉÀ +Åö5MÍÕ«Q˜3ö†n=áÖáYàÚ‰ï×»:áÀ4›þ‰4“NÃKNšQ +YNŠG™L̲us»Ýhèlë»!vZŽ^-ÆVž|„Š<|áëQ‚¦“_{Œi2i}K‘M@Ð +X,– +œ:$ÅüЙGÐЇÉñ(ÜôaÀ ` +±GmÇû¹®óéßgMøÿø¹§ÿ'À +endobj +668 0 obj +<< +/Length 18865 +/Filter /FlateDecode +/Length1 43174 +>> +stream +H‰œWxÎç?ÿÿïýäÄ¥AJÄ-‚¸GĵnK)Ö|‰ÜÈm„¦êQU÷–¶kסXmC©n³²ê.mµe+cíVzÓQ÷D¨§ö{O"¾Ðn{–çÉùÎÿ|ç=·÷÷?ç|âˆH¨,HêÐqŽLŸ3Ÿ’sŽ3®S|:©›@ÙÄô<oá¤ÃNáóZ‘†ÓçEmÙâ½$}DÄoQfaVÞ®ðñ"±ãE<då>–Ù÷ý¤›"ñ±"Ø—á¾£ÙÑ7DºŸ¡½Ù=î&’mÎçè켢⣲s2ŸŠ¦ä¤{%ü}'=#Ò`]ž·¸0`~p±È„ÎÔÊ÷æeíèz˜ÏcE"ÖÌ.š{¶ì€ÈLÆP8+£p_Ùš"ëËio—Ø\%FfˆãåM1”È[âá‡#.«`Ä““ŸS$QyÓs$&3'ß+±¹9Y^‰Ï›Q$‰ª)JêSL®ê$9WêÐG¨4—·Ñn´§©g«q`IàÀóAI!¥!»CÞÔ“½»’"1¸ÑÐW0©HC²‘ƒ˜bü +vÚ`ÞvcvÙhDè·ôTcÒJ4QͦÖBu¼
ì)4´Öèî5ø¡>cqªßÀ¬n3)¥0ÍÉuæ:•Îçcç¢ëºã}}šîÖ§éa}š~Ö§éo}š$õé«™ªš^ÕLSÍtÕœ~':c3«0htU’•dª$øve$^zùbÛFÙ(ÐØF&6 +4µVÑÀZEC4#ÄãZ£'Éà)R?,#ÿ<¤ª¯zHSéêaºzÈP™ê¡¹z˜§©‡Åêa¹z°ˆ[bŸ•[ZèáV*ç[9G+çjåµrAZ¹à{j|ŸjFªf´j¶VÍŸGhEùÔ¸³Jº¨$Ð÷Ýb'N’!2ÒFÀì]Í>P³Òìƒ5û;‘f_®_ÓŒ¯kÆßnñ>µ©£Õbkµ£;«Å.j±B-VªÅÕ5ãüYÌù³œóg5§ÉyN“Ëœ&•œ'7ð5{‰§fR&r9ñèžÎ»³5¦´j’úÕzò¯õdgR*üÊùÅ÷º%g`[¾Ýí9{pŸèÅbFrêŽA +ÆqöNÀDî-SáE.òn-9'—r¯á4¶}áYNäõì®ØS6²¿¾È®²³y;»È.ÎæW¸Aìå|~[Ä~Né×¹Käœ~—ۄԧؗ>aŸý’}ú,ûžÍý»”ÍŸ]…3µ¢V\éè^âÝ;îU·œôš[)êX¬17vü +‹$̈ À-ão<Æ?|cê˜PnÂLˆ©gêúh§vSÓÊ41-MÓÜÜo›(ÓÖ43±¦½ic:˜v>Ú1Ôîfúš®&Ñô6½LoÌ@ÓÓ1Éf€lYÛ»±7cïÅÞ +o#œ•ŠaŽ¬«/Žc¿ÇAµž‚k=…ð.BxÃuQï,wàöxÏvCîƒÄš-y +‘²F¤¬’"ÿ~ËJ‘Sä³ZŠÿÓ”°bº]Ð]æ2%v÷pïyº LCÓÀÔ÷‘EQÖÉÄ™Ž>²`ʆ›af¨nW¸Kð„9䬵Øô¼àéʂͪ>™w¦îñ¸Dµëz\Ãߘó–-TÓÿFäpDݺi¾øfHmnV’8·ø;Tô·dÕdÕïÌÀX>‘ŠF?…|ð“BYà<á¬qJœÎËÎGÎ-w²û–{Ð=¶»µÂÖwkyØ›1fªyÄ<mžû7éeÅ•à{_ß®vAAÔ¶º¡Zlj†q‹:ÉL&‰I&lj1®—ˆ»Æ#(›ˆ!"(*›ˆ‚» â‚â¾/Q»mpAÚîb= Öá=IæǼ>¯º^-§«¿:÷ÞïÒjJ¥´›®Ðmmž¶H{GûDr’ZIm¥žÒ§Ò8i‚$M•æI‹¤x)MÊ”r¤ÒEéºT©ÕWÊ⻬—²Iö“ýåžr€ÜKî/Ï—Óäõr¶Akp543
&C'Ã熡†å†#3JFgcS£»ÑÃØÖècô5¾oüÆ8Ê“yºxP˜â¤¸(nJ¥µâ¥tPº)Êx%X Q”(%VIV²•-J¾R TŽ+§•+ÊMS€©©Ÿi˜i„i´i\…¶¢I…kE‹ŠwíÌ^ßî§2UV»«j/µ¯Ú_¤V'¨sÕHu¹ZÃëóƼ)ïÊûóù`>œòI|_Ê—ñŸx:ÏàY<›çò‹ü2¿VåWÕ»*¬êQ5¯®©áŽw#C’ƒxæàI|)ˆâ—5PG<DÖ¤Š|:ˆ†RÅÑJJ¡M´‹.S…6W»[{FkMÜ õ‘† â“~“¸]¬O’dW¹¹,;ˆw•{ÔOÄ³Þ þ™aˆ!¦ŽxA¼¥Qÿšø0ãHqùwˆRG<FIR²êˆÄ/â=ëˆ2V ƒ¸{EW;ÚÉ Þ^}G§T¿PÕÙj„r^ÏA¼ïÍ?â_ò¡‚ø>…G;ˆ'Ö?*ˆ_Äă_¯)[ã&h:ÖXØ1€jgË„›bOë±µ1Rí[ݾڧÚ[ìΆ™¢Û„ÑÂaø5nágøqnãçøéÚ+yÛXž,>±|>á?ð±ÜŸ+ +îr|_þͳÅb^ EAñoŸ¥¨Wó÷M¯4óõjê=Ç[w~HuÏ/z¤ÿuuÑ_½y$ðͧø?†R!BiÄÁMXÑ «!ÒD7!^ÇBˆ;<„%°Âà +/à"F`$nÃí¸wâ.l„1óÑ]° 6”¢+ºán,@wl†Q¸q/îÃýx +?Ã5|€v|(jñ#|ŒOð>ÇX‰/ÑUäX…ÕØAÔi`ÈÓ0r 1«Çê³Ø‘5dN¬kÌœ™kš2W憘;k†Ñ5g-XKæÁZ±Ö¬
Ó³¶LfQÌÀŒØ»2Oôg^La&ÖŽy3Öžù²0®m¬uf4Dß*Œ4\ôK5±¢ãH¾•,+S³AVŽ°Ðí®öhök ³:É욳ª®§*.u[¸çÍC‡µ?vxû³×æÞ†U²—Le\Ó@ÓPã$*!Š?–"#ÖQ:§Ê¤,Ú@Ù´QTÁʥʹEÈ6ÚN;h§¨‹y”/|¤€öP!í¥}´ŸP¤CTL‡é¥ctœNÐI:E§é¥stž.ÐEºD?‹ªz…®’™,t¬TB6*¥2*§ëtƒnÒ-ª Ût‡îÒ/tîÓ²ÓCzD±¯ÓzJÏè9½ JØ[Xvƒí°Šðl…mpPôJûa1<Å[lÍ…H€{"×ÁØ–b_Q‡– ˆÅ°çà=¼O“(ˆ‚i2-¤©"?…Ð4 +¥™"Ç-¦0 +™.’f‹¢%MK…,§a«(Q˜Yœð³xšCkh-%Q2³²fc¥¬Œ•³ëì»ÉniÚhôŽÎ¥Ög%x%‰b öV†'5¤•tõê7hèÔ¨±³K“¦®nîÍš·héѪu}[Ù`ôôRLí¼}ÚûvèØ©³_—®þÝþÔýÏïôèùnÀ_zõîÓ·ß{ýüëûüíïþã£?ôégŸÿó‹}9øßCþóÕЯ‡}ÃGŒ5zÌ·cÇŸ0ñ»IA“§L6}ÆÌY³¿Ÿ3wÞüà?, ]´8,<"2jIôÒ˜e?Æþ´<nE|ÂÊU‰«×¬MJNIM[—¾>#3kƒ&{㦜ÜÍ[¶nÛ¾c箼üÝ{ +÷îÛ èà¯ìWÿST×?÷Þ·+òE×/ˆ€è[ŸeEÅpqYŠ%¤ ‹îR£ ++©S¿ÅVm›ªIÑ´Éĉm‚Mjf´f±†~EüÚjÓdl¤ÆIÛéȾÛÏ[Jè?Pxì¾sï¹÷ÜsÏ—Ï9{ò§>nýät[û™³çèü…‹—:/_¹zíú?þéæ@W<ÐtÅ]ñ@W<ÐtÅ]ñ@WüÿÝ+·@;)ï±¢’€ÍòfïçSc=xà!)ùe`·÷ÓóçÅó‹ð·—õ¼À‚À/›ÁÎ +Kl¥BŠhGg!1EFb|ñœ…]^:-–D8ešüšWZe%½É²x§ò.°ì.¯ñ#Ù ÷Ê×h}#’B'ä4Y‡]e +²˜K&±f~[(Ôb´Èy²R>CzŠŠi#¸IÀ\^.Ê‘)—C1nɱíBþÈøs o#²ÿ*]GèGr//¿£DʦràòØì×°d+Ýd,e2ÛÂðÕŠµ +g» +ÒÉ®³/ÊV`®ÏR¾‰¿ÌóÞÉ?²• +Ÿ¨/ +5uC¸æ¿Žøx:þõº>ÿ¶¨• Ðx\Øonè]Í2?«ÆSƒî÷YÄl#ªäáZ׊ºu•çjK,Ÿ‚g6² ŒWã{x#×ùU<÷ù¿D²pŠéb†ÈÜf«ø îóKqC|®p%V™¦”*”SaYfÙeÙki±|l¹cµY¿Û‹Þ~íKoVrÄ +ôÅ\ˆ;üÏbëøöžÄšqZ’(ÅÜÍg£äC”×ÑÈA{ñ«ÇÎG’mPÀ”Á_å©b¡’,¢Ñsc\ηð +~Œ½ ¿d ÀŠÙkbgõ°Æ,ZÎ_¡ñ|%x.£¿³M,™û + +RͱV‰ŠG&ºŠ©üþkt5^¦ö_éÂÊêÿZéêYéê[Élje¥:U¦êíyšz„•—ø@ÿ,Oó«úÝ0]¦·‡éÐv;6¨žÑ5yªÎªGÏ_]ôò ®)*Ò¹«"SÔ2 +”§lbq9,Lð8Of§ˆ(¥'hy=^Ë35ÐÅDOÅ2½¸ÄçÉK´Ûý©N¹—j•:isõ¡Žðr‡Ñn}PøµÖ¼
5¨MÎãÁmGlTpD/Ó–U,òé¢Âož1Ìsóô¸ç?ýŸ!„wû¶>ÊMAÏèZÕƒ[Uýߣ\»ùí÷Cöò‰ù`>ŽÞ#–ª8oöût¶GªæMÌ[õܯJó˜3åª>X›«Õ—àš„ Nó×Ø&$¸ŽÊ[”àQƒ^Ÿf×ç$jþŠ¼1M#)8Í¡x—ߟ“êl²
ë1lÓ¡½DtÌ£DU/L…—›Táü>Ë2S#mBW—ªÐħáNæWU—f`þü»ôeðH>ØÚ2Íys¿n™hÓÔà}üª +hwÿÖ¦¢wÆ:ÑvŸLÒŒ“¾Pÿ!;zJŠ"ƒÜð)tÌ g¦:WáµÚJ›ŠÌGÅ°m…?s*Ìo·›n8â¢Jô%¾ž±J•‰É5Õá×yÀäȉ-39rú¶4Dòa´D±zDrßÿPÛ¨žšLúìª~a©VXRîS=Á@¯m½ýF=üŒ>^/¥pûD"ï¥x¢s”‹ú›_´þoÖ«?8꣊¿·÷#wÇåî{ß\r¹\îrÉ%~\ÌQ$!IeB$52¢µM1Ä¡% R&BÑÚ6µCÒ@óhqP$ˆ +2¥¥AƆBø1
áëg¿w;ñ÷ÝÛïîÛÝ·oß{»÷ž1?³îÔ«;,ðJÂþ²#ÊŠÇ£u…-øuh7å*ýóhYLÌ#3òGögŽèÏÞh€ÀƘ¿¤²±Ñ6b¬/PccYÐ_Ö¸¢qe‡Ö°*èW‚;Bë欈[´CëÚæ;R¶½‡¨áðVAµyë¢Öo]\YÞ©ù·.)oC@3{ÅcÙ+ïôEtª¦Êž_öh>ÃÓÛ/Ê!_g„¨A5ê½_ÕÁ¤Ó,qSU‡ˆÒ†R€xEF +,cfYÑÎÓ(W»G)ÀdíÞ±t‡Õaqˆ.í>)Ú¶G\1V» Ž1¥;F;²Ôµ–Ìt• +9×”˜tJÔq%&ÕdJL+¡qöØÄì‡w¡.6S*Û"E_ЯÜí§ÒR¥_éW‹‹]j±KVÅ'Ìþv¤R*¡T¯Ç›âMöº½&sº/×éí3šsCy¡1¡±!£y”Ýf·Ú-ö»Éle¹²#äOJ‹p¾9'BÆñ:öyQ…ìã"T(P声¬òÇ¢äo¤é±ÂÓ?^àã‘dWf’·Ôéò”ºd•’™©–fuhƒ‘¹ît*Ÿ‚ÊëDåq”e•ëNID•Áy†LuTi
UŠle¸½ÉäßN·g´\5ºTØ×g<²ŠJ7²H±+8YI0³B¹!üŠŠ”iS'OJñ¤à—0”ÜP0K$'»Ñ÷¤Lž¤®o¬Þ?osaƧù› +3?«¤,™=Ö›Wü¹œŸšWüøöƒâòù‡6wfQ`WÉÒgγ"ÛY»ÂKëëzK‚ÞàÃ+=uçJ²¼Ù€1²(BVr`k›jñuh÷#N—™,V_Ä·P]è3Z]âU²ó‹«b·;•“V‹(*›L‚OZbJ‚êsw‰Kˆ)×¼E&«Åîîn±y¯Gœ‹ØhËÅkHaå„X‡„óŸ‹zrwyXêWàDáÒþ~v©žbR†JÔâñ©¬Ü85¢3q-×ì +L¾æ` +T4eêä€Ë$UZ4eÚÔi¢‰ý™ii™C_“5ûÞp[^›Åk¼?ø%O’ššª&yŒ–š½.g¢Åª¿ÛâÚWþ¢ýËÎð‹Õ¢g+‡s”ߣ¿õžvá£&Sš%]9_® +å ®¢‹¢„®rXZùQ"åĺ
àµQð j§·!M22—kÀè«>E3KWÓêç=ÈÐvcŽPþr§ÅàôZ°. +]1ÜâðT ²Ïþº.µ®îæRì£Bֵഊv+éåÓTú%½Ëy¢œf]ãpÎÁwcÿ*ÈRËTTi§ö!ÎùQø¼É« ùªKÈAIȉz¡ç+àåÂ$–C{Q ð ð”Ð…L$8C·rÏäÐÞRìÙ Í\¤~Ö†h#¸ïÁ~°žƒŸã/rUÌâÒ.ÀSήÇ9%~O맰g“ŽÍèa÷À9Ž…ЛÄhë$J>;`‰‹¡E‰BÇzœ°ú:Ê>ÚGçi½ÖÇ*Úümˆ£¬‘÷UÓO© Y¡tô‘!ë(ÆoÀ¨œ—Okzkâ
€3†oÀÞ!ü É,êÀ)Î÷3vBn+%i·s!ucLðÓü4½ß:Šk.®¥¨¦6c-|·¹^<øãØ®€GïÖg OŠé4ªÏïë2Ž9ðwiÓKúþ*<n!Ôô8bþ¦ÞŽy£È',ðn¶PD{ +îæÈCýû^î®Á-˜ÌýÅà#p8?fðÌæþdðlŠgrù#?Sù“?Ûùƒ£ù_|¦¦?7ø„ud:Ñ3 WŸùÂà9¸<“Èæ3oå¿mð)(ÌÜEO¬,e=7ó}ƹ"3÷?Äô÷?ÌôŸ1>Æøo75Ò¸®‘Æu4®k¤ñlŠG×HãºF×5Ò¸®‘Æu4®k¤q]#ëi\×HãºF„O¤âäX>g<—¢O1þ7Ƨ)–™1~ñ™£YÆ¥øŸe=?ž¢?G²G3~’y´ÎÓ)žçSøKÌÿã_cü㳌s,c)ÿÇR¶r)z.‰¥]h„ØààSÀ{eh0¾øÊp XÅ·qºÛHw™C ÅCùb7˜nÿŸšŠ=ÏlãÚ=ži%|j{ó°„×Ìl©+(ááseêèƒb©-Ô!„ÐÁ{uÀ«eöªß]æ°‰Ïé~z^^B+dUÀyÔâ¢/!~‰j¨í[ª£&cáõHﵦmÝÁx70ú=ß^8˹¤LUñ½ÉZw‘Föþ÷,¤’Ÿ.ú¦ØÊŠÀwâq˜BµLÞÉ#)Ú«£Ø„Ûh½ë«˜uÂo#Uà}ï¯1}
)Ûx§ºÜÄܬáµÁÔ2äa‚bp¹Jj 'ºŽ²Å¹nﺽ,F¯{(À)úÊ·\:JÝmî þÚå(›s'•™6ËêÞHüÑ™k2¿ö„ºßã®Ü¯’iuÖBÕ“œEêÓ7µ~ï0_€~$9×6ÕC2“t\ÌAÉq5ŒU|#ºƒ4ã«qöšæ+0qQÆdJKltd¯jº‡z¢Â«T{]1•ñæƒ*ô2Gµ7Sº¯»bв¦S®;x§ a£UÏd;bmê¶)û;HñØb”ª|¿ºN{×eG[XƒÔGð85¦}^‹>¾õíÒÚ®r¦õ*µy‚…© v±Ç¦úVǧ™)ò®Éú“¾ +ö苹þ»\Íô¬¨™¾èsÈ«§H›3Nú½x´_éî¦yEÝ ó¯WUËôGÒ¥û{èaõû£Ä±VŽ2LúßAºdÝI4?õló÷Õ Ü—ï¾fŠ/ày^5S³Ã30NÍÇ©~¢O¯IZ«SþKô
ÖQgKG x¨×qR1{_®kOäm?˃Î0r9à‘Ž‡:h¹§açÿ +RgvÌk¸K/â.)ð>o³¸. Ìávà–áœÃ¯óøeÁà‹p¤®ÂÜA H;UK¡gËxn(b¾è*`ûW¼Ã“ïUî_Ê)ùy‹§„â9âéEò>]ïM_»7e=1÷ˆ2³±?‹“¬—à:fŒÖêþÓDl´%““&AlòHZaškr»Ž¸>õÔ{¶Òèd$ÙoǬ‡»F¦ögÁZß]®›Çš\¸g"lq4÷^5ÿE^¹I“I®Ï1÷®^'ý5âóN%åE
ú§‰dݵÌÞGó7Ú3‹¨÷ô™)™ +bÅóÄ–[o¨HlÉH†YMT-¯íЕ¡Ø”ñrG†©¼TXšç7\'¢ ¦.lÉzÛ³Ã×SŸJÝÙÞ(›·÷E9´«²i‡»"¨=Ôeʺ)ʪp}áÈPÙôÚ¾BUQaóv¹´^Z])—noŠÛëâµÒêÚæöšX¹¹µ¶¶±¶YÎOä'Ê
7*É$áh²-T×%zæ1CA=´[®°}4‰©hGRTº¢´IÒ :ìLÛ¯b6H:׌H‰-<ב>²ÛõPʦôUA¼‰b
»#EP!ÏQRíq†Û¡ÒEe¡¨º¡t”×µ0höý +ÐVP—Ì#g_®Šé ÝJ[¡jt3ðe: —£Ä)ÌU/=aÄmѱ½¶]ñÐí(’*-];¾'£ˆƒç(0&S hÔ’Ž[sÁÈfÑW®_gY»Zu©¤¶'Bn°‹D9·hOíwÊs›.„F˜/ÂÝH鮨a.˜ÄØ"íŠçF
²ƒºtº›vW ÿXªV—×ÏÐ^CœRœíwÅ;m±'ð±Û|Ahüf樴½*¶fÇ•1÷À`øć•”.."]1âëňn¡e;ª_c +Ì6^×VË.÷Û™(B;¶Z&†í1+Î_[X¼ ç¯ÍÎ-ÌÍïÜBâÜüüÂÞ//ŠÅ«W–®,å'Jµ–‹Å8ŽÍ¤ðNÐ|5@O«â–TÊ“á
¹uj_›Z†xâK +îbr½t}ã¢HÆDŒlÔœ¡E¶\©†.z»âè©“”ÛÒÃv±ƒpÞÐzb…´»¶Jͽ‡[®r¢Êö/ +öš§@,©&¼P#Ï®°Š ª]WŸØ‰tÉ&N&j€¾ãA[µÚŠ= %ÎjJeWh‚q¿±^%†ÏÎT§M%à&,< gņjzŦòí¦,6£·_Æúò˜R±ô*-BoEÓ$Ì
›¼;7yöùœŒg"+;Ê·ðýï¼%ß·y_¦=œOq™û™2g~‰ðQæ癟¤tÙ¼;%ïfÝr-¹GëËžÉÎgoeof¿Ž÷%ä¶ùfÕì‰
ë§Ö3ÀÿVŸNiú¤0üûòEüß +`Á¾ßè9$ŽXÈ@Fá†1<ïNÀ$äð;G`ž8 +ÇàY8_ð|NÂ)8
gày<Ÿ¼ +ß·ºz™u÷ŒÙÓzYoëc}Ÿõ·6ÐÙ`bCm˜
·6ÒFÙhccmœ· 6Ñ&Ùd›bSmœÍ°™6lŽÍµyˆhl¡-²Å¶Ä–Ú2[n+l¥²Õ¶ÖÖÙzÛ`m“m¶-¶Õ¶ÙvÛa;m—í¶=ˆd{mŸí·vÐÙa;bG혷vÒNÙi;cg휷vÉ.Û»j×ìºÝ°›vËnÛ»k÷ì¾7?í‘=¶'öÔžÙso*_Ú+{moì½³÷öÁ>Ú'ûl_ì«}³ïöÃ~Ú/ûmì¯ýƒ!2¢ *¢!:b &b!6â .â!> !!1’ )’!9R %R!5Ò -Ò!=|à‹ÈˆLÈŒ,È +?dCvä@NäBnäA^äC~@ABaø£ˆ E1G £$J¡4BPeŠrCyT@ETBeTAUTCuÔ@MÔBmÔA]ÔC}4@C4Bc4AS4Cs´@K´Bk´A[´C{t@GtBgtAWtCwô@OôBoôA_ôCÀ@Â`ÁPÃpŒÀHŒÂhŒÁXŒÃxLÀDLÂdLÁTLÃtÌÀLÌÂlÌÁ\ÌÃ|,ÀB,Âb,ÁR,Ãr¬ÀJ¬Âj¬ÁZ¬ÃzïnÄ&lÆlÅ6lÇìÄ.ìÆü‡½Ø‡ý8€ƒ8„Ã8‚£8†ã8“8…Ó8ƒ³8‡ó¸€‹¸„˸‚«¸†ë¸›¸…Û¸ƒ»¸‡ûx€‡x„Çx‚§x†çx—x…×xƒ·x‡÷ø€ø„Ïø‚¯ø†ïøŸø…ßøƒ¿øG#HŠŽ‘‘™Q•Ñ1“±›q—ñŸ l>23 “2“3S2S3
Ó2ÓÛtúЗ˜‘™˜™Y˜•~ÌÆìÌÁœÌÅÜÌüÌÇü,À‚,ÄÂôg0A,Êb,ÎfI–bi†°Ë2”åÆò¬ÀŠ¬Äʬª¬Æê¬Áš¬Åڬú¬ÇúlÀ†lÄÆl¦lÆælÁ–lÅÖlölÇöìÀŽìÄÎì®ìÆîìÁžìÅÞìþìÇþÀÄÁ¡ÆáÁ‘ÅÑñÇñœÀ‰œd³9™S8•Ó838“³8›s8—ó8Ÿ¸‹¸˜K¸”˸œ+¸’«¸šk¸–븞¸‘›¸™[¸•Û¸;¸“»¸›{ø÷r÷ó +¯ò¯óoòoóïòïóòó ŸòŸó_ò_ó
ßòßó?ò?ó¿ò¿óòóÿòŸ÷ÝC”äAI‘EQMÑC1K±GqOñ•@ •H‰•DI•LÉ•B)•J©•Fi•Néå#_ePFeRfeQVù)›²+‡r*—r+ò*Ÿò«€ +ª +Ë_E @©¨Š©¸J(X%UJ¥¢2*«P•S˜Ê«‚*ª’*«Šªªšª«†jª–j«Žêªžê«ª‘«‰šª™š«…Zª•Z«ÚªÚ«ƒ:ª“:«‹ºª›º«‡zª—z«úªŸúk€jkˆ†j˜†k„Fj”FkŒÆjœÆk‚&j’&kŠ¦jš¦k†fz>[s4Wó4_´P‹´XK´TË´\+´R«´Zk´Vë´^¼öߤÍÚ¢Ú¦íÚ®€ÝÚãy`¯§‚ý: ƒ:¤Ã:¢£:¦ã:¡“:¥Ó:£³:§óº ‹º¤Ëº¢«º¦ëº¡›º¥Ûº£»º§ûz ‡z¤Çz¢§z¦çz¡—z¥×z£·z§÷ú ú¤Ïú¢¯ú¦ïú¡Ÿú¥ßú£¿úçÌÁÑÉ9ÁEt‘\dÅEuÑ\tÃÅt±\lÇÅuñ\|—À%t‰\b—Ä%uÉ\r—“H*—Ú¥qi]:—Þù8_—Áet™\f—Åeu~.›ËîrxZÉår»<.¯Ëçò»® +ä +;Ï/.й¢®˜+îJ¸`WÒSMiâÊx² uå\˜+ï*¸Š®’«ìª¸ª®š«îj¸šî®«úËä_VÕ3¢5\.LÿAž%èǽ±÷ÎñúÙÞ眃-MK3Þi`)ÌÌœ»03s.ÌÌÌÌÌ\M%]ôž¦¾î®®¯ªº§zúÜ
î÷€{½àÞÀacðAÀ¦@—a¢+n€Hasý…äP@ ;°{°pÕ_¸Ü +W¼ßqNµ]O‹nK‰N§žÎD¢ýw×tqs¹–ã2š2žDb¯1^`8._æ1>Üu3Ché¬ë*(”€u©'ðÑ8¹diºdé¤Ô˜â£zÒ–›©SKs‚¥9§F<ƒ +K±_˸/_±§vø±ÁÐkÕD^àºãRÃ`…,øA³pá4ΓpÇâfŽ–;0Nñ<âã±HŠ#ê¤Yê`Eš¤ùªõJµêkòÀR°¹®Ï+Õ¨®º·¶‘ÔÜŒC¹2º±µ¤\ߌÅT+
QýFL1ÀðHc'Ô¹hxà’<4‘$»†Ïf¿â‘ÏWîR®œ)WsålX9¤ì¼:J/ðÒ5ü•ãAX9›‡Í%ÞÃfжë|¤XRØ CäU姪NIç§ò¨‡é´lòÌ9‘m+×#ízR®ì…ø–(¿+Y:êüo¹ÆreŒ#d©LZs9_‡ÿ¼™.g¼\ÎxJWÛOšvÇ¢n‹P›ÐbÔ#Ô%Ô#Ô'4 4´¨wŒqô,GË#{-²Ò"+-²Ò&+m²Ò&OÛä_›ìµÉ¿6Yn“å6YîåYîåå CâèG‡8:ÄÑ!8<âðˆÃ#8yñˆÃ#8¼EžiFfôhFfôhFŸ¼ê“/}ò¥O¾ôÉrŸ,÷ÉrŸ,÷Éò€,(Þqˆc@âÇ€8Ä1 Ž!q‰cHCâÇ8†Ä1\ı°b9j¢½{¬CÈ#Ô%Ô#Ô'4 D-âXøÜ]ĦgÈÀ~Îtºmüv;¼Žõ‰Or1M#
gø +Îñ¦ÈYfáœ9Ö¦™ÒqìpŠÈ™—£²ÈŸOE°Xàw;‹ùœO ã3r1wó Läu+W¢š§È–—sÀShRÁZǦa,"æc»X De‡ï°¾&¬y•ÿû©_„x¥Øæ“ +þrÓ‹¸‚ÝHʤ‚¼†Åb; +3ÝóÓ^›]å$^%1’U¦‚X!*2£Â`G'œä(+ȇRP%ýI‘Z©l,'‘ŽÉI¹ç KrÖ=åuOÏ×Íššê÷¾yÝýÞ×_ÏôìE@:˜º`êzX%8«orȽPsÉç~Î àX‡â²Xõ +¤c¿ÐgJ!ò Ôàêk zY>¬ãÂœ\ ¬#‚V´Š07ÒZa½‘^¹ÊÇ´Ô™²DÅR:Š0. + +L"hqC)ßPÊ7”ÂÓ鞣ØPÊ7”ò
¥|C)ßPÊ7”ò
¥P'ÆNƨdžR¾¡”o(åJù†R¾¡”o(åJù†R`B'xŽ…<×ò€ò (ŠPÃC
5<ÔðPÃC
5<ÔðPÃC
5j0Ô`¨ÁPƒ¡Î…¥ÍµÕÙÙÙsÝëéúZw*휴›Âv]sÝP˜H˜äøRúÑâêÚêÚÊõ4ýUs1ý¤Eqφ㠳$L0ˆ±«7–[Í¥¥ÖÚõ°¥òrº¶òñêÊZ9]km\_]_îÙø4•àcQ&æšH„ñ…$<A½^"LS˜Ea$1ƒ *L°`LXYo~¢è`sisC³ò–¡g9ÝXlÝH?YĪ˜¨ÆD5&ª1AˆÂVL°b‚[Fpa‚}kêyªžfŽzë®· x´¾´º¾´ùѵ_KbG›c„\9BH<Ø<$hæ’›cæ’—9ó—›ßŽõÕ×[B[bØ×4p—¼T\â,&̓÷jžä æIÜŸÅó²ýÍ·=3@ˆõÎhuþ#32Ý‘,ëÑ™N‘[™NÕ[ªÏ¦zë0Õg:Uouª>sˆêoÇz/èµö\€ +«@
³zvªÙ5:—žvZ¤Z4Ó"=L‹F§i§C´HÑBž\’`oª¡ŒJRYTÂ~‰þôÀm6GÏæHèÈh^ÍIÍ»ž9 Mð™Ó 75œÓÔ65œÓ,75˹ƒ,7²œÓ,75˹,7°¼t,¯è„·ôÛq%{;nI'¾
®wnZùåg3?í*ï)Ï”çÊûÊʇÊGÊÇÊ'Êו[ùsÒû Sž+*)ßT~Qù%å—3ßTó›j~³¥üµÌ/ºÊ{Ê«ñ‹jüb¬|¢¼ª·˜Õóâ,TKo%¬÷_<±Ÿ´çxmŸÜëÒ=è?Ã>¾Ï‘¾Ýçê“öë&µ=jO©uYd·TĶ*d§¨‰è¶ì¿ocý…Újÿ¢&"(òˆ"(òˆ"Sö¾U°¿²¿lŸ©Pé‡^=Þ×OÚ¬ÔŠöömkˆrÿRùå·Éÿü]åïØ·ÛoTê=t_°ž“}AHkÛmŸo°¯%ˆªìä‘©Ô_µw‰Õ.±Ú%V»Äê9ÙeÝ¡øÅw(¾#ã;VA¦S©ØmœPõ^ûŠ}Éb”âå/Û—Ú¬ò¸~Õž£Ô{ÒÞ·ß'»-í‚´
i·dï–Ä©Ä©ÄSO),ì¤a+ÒkÿÜþ…5F‘ŸÙïJÑž±¾O¾A÷¿g_þ§öyéBñïÿ1;Nþ]{VÞ_ ûiòïнðçíÙötÅߤûê+R=Ÿ&ÓÄišD‘mj÷©=“‘²[ÔžR³åÈ‚=M×9ºêvfÔ(Gzj–m×蚢ë-û-êy“ƾI¶fWå«4ªJ•ª¤U•2Wi{ª´=U«dWÉž¶Ë¥V£v‘ÚUjÝ”g‚æM¯ ª0aŸµÎP®¡âçÖ ùÓÊWŠ·SäOo·OUjõžâCë"µ«ÔnRû¬ø°Ý}| >HãÄØIj
jÔ¶¨Ý£¶GlMe=µ¾âTqÊnv=ÝcªU&=3ÿÝ×2ÿÒI6P_·ÇH¦1ë5›(å1Zj~W¡V¤GgÄzLí)µgÔ„à#$Ɖ1B¡ù#rÔ9î9µÔlzˆF(ÿÁ1Ýrv…Ú¤‘EDG)2Jw£4g”ÆŽRôÙ‚œ!ú/RÛ¦öXõ
ˇyX>œÃ”k˜ØN’’h€lÅn{öIßÂëõˆtoP£ÎâRóévG<!EñORÏ”±MmZ·ý5]ctÐ5J×0]Ct¦‹vÐ>E»w—®mº~O׺>§ë6íÆàÞøãñâB[Ávp/Ø¥oŠMº®¯Öz',Ë:~¬|²~´ØeÍ[NáÒþYÚuikÒ¾R;9ïügÞùǼóÇyçóÎóÎ{óÎì¼39ïìk¯Œ;ÿwîŽ;—ÆpÜ Æ>îŒ;õc…+…Ë–cý]Ú·¥eÒKûZárÛ±zþZøÐ*Ó_y8ô›Ê‡ö» +íÊï†öËä~›Ý}˜¹7DðËŠ;´R™È"?ÈÜ™¡¿uQk®ð'«T¯M”þYZ(ÕJIéG¥³¥ÑÒHé{¥Ji°|¼|´Ü_~©Ü[.—üŸ±2æiˆâø;'…4Ð6P„,E¤¶,:ÄeéФBàØd/-að„PÝ*]ÂeAjEéØÏ€*E:—Åfê§`ΧèšÞ»˜&¨Âº{ïüþ¿¼çø^.©dJIAj)ôË&ˆ}XšÉ ›I¢MÊuFA+Œ° ”.ðç OñjñøïCð4þ§f„$ýá#dX„/zàíX*/˜^8;ØæEÓãßïú!ß©¸ãÊ—ÀŽ’†Î²|±âG@È«³‹lì)ÅÏøA’\\PX>ÙT77Þ¾³§˜zlÍÑ¥šã7âIVø¯æóŸ+”¿ÆÅ`…zâÍÕ´=?RŠÊÇŽ”:êGé®Rt¶1žîÚtÄ&âv::Ɇhw¸œR@nÝËI.7Á%ݱ]¿eJ’)M2G“Ì‘dŽb&1dô1f¶ºdôÙþ=&÷ +endobj +616 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [385.804 235.584 400.25 246.488] +/Subtype /Link +/A << /S /GoTo /D (figure.2.1) >> +>> endobj +615 0 obj << +/D [613 0 R /XYZ 269.126 275.535 null] +>> endobj +612 0 obj << +/Font << /F72 448 0 R /F71 445 0 R >> +/XObject << /Im1 611 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +671 0 obj << +/Length 1586 +/Filter /FlateDecode +>> +stream +xÚXmsÓ8þž_‘i™9g¦¶%ù¥GwPŽ@h†{`7VƒcÛËýúÛÕÊŽ'½~¸é´Ú•iWû¦u¾
?N?™ëð¾o{Ì–nºèÙý[XùçÄÐ@†-̯“ÞÃç¾ÛYèñþd†ÇÏ…cBæñ°?‰?Xî`(m‹é¿¼¦=Ƕ^>M^êí>}ÜÍmæÚHÃ}Wƒ¡X†Ü—Ö3Øââ”çXš¾Æa43!À’P®õNÿ}ÕÈhTô†ÜyŒë½‹IsËÆ’yRà¿õ>|²û1ØãeÏf<ú?€¶™†ýEO¸(ßðiïº÷¶9‹Öà>z×>“JW0a‡ÿiSTÉÅm2é(ÛÀíª¹"b•†Êg8r+2Õ2Ž*u‚ŒÜà³ÕâFí®UäWZ?Jâ¢r35TL'&f¥PÕªÈT^äÂÛÕ…£.Ç2L}xD÷•*ÖÐÄ(d¦Ë$»Më7°ûÈÕÏÊt˜kè +¡
€ûBÏšæi +È$ψ×Òp¼ÁJb3¶ô"°ÆÉס¢EÒˆeTDUq„Zr;Ø,ª\¥U[èj@yq/z™åst´ç–Ë4J²až¢ mŒD¢#–E²H@—*A¯}8R_VªõR±úl—³ÐqôÙ×¹¶¯oý˜GY>ÎUi‚'M‘póÄL¥yþ•æÒá0*2×ùª õ¨iy¬ÃA‘ªP?
¤´J‚Í)ÜšÝ4»HnáóŠ8ÜN +Ò“x“ \\©¬tkŠïÐœ ‹È6”&L"p“›òB³³"_ìˆ.!?‰‚<çñ7O!€OÈ£hÅl€x>=à[Þ{ù¶…=äÛ‚ú<X¬_©59ꌆwƒw<þºûâïh±„»sq‡ûÌqûBr&|Ÿ.Žóżk]Ý€‡-tç¥Ú9P¿þŒ3§#XúÌr[òŽ©kÌÝeÈ$÷¶$–óÍC†2£ñÏ×c“㪜ɲªKèL"ÔõcÛÚXE•áÚÌýR&©îtÃÄ@ÚnÒj0ìòü;…טŽtèR6†ó@El‰ŸÌèý†h[ÂUp¿ix +endobj +670 0 obj << +/Type /Page +/Contents 671 0 R +/Resources 669 0 R +/MediaBox [0 0 612 792] +/Parent 610 0 R +/Annots [ 672 0 R ] +>> endobj +672 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.124 450.324 158.042 461.228] +/Subtype /Link +/A << /S /GoTo /D (example.2.3.1) >> +>> endobj +673 0 obj << +/D [670 0 R /XYZ 353.277 468.421 null] +>> endobj +669 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +679 0 obj << +/Length 2322 +/Filter /FlateDecode +>> +stream +xÚ…YmÛ8þ>¿"(8˜¸¶,¿õpW´Û·Ùë^gÛÙOÝ~ðØJâ;ÛÊÊNçòï)ÇNÒ,Š‰Þ(R$‘”.ø.òÜa´HƒÄb±(Û›`±•÷7!S¬˜d5¡yýpóü]*¹Ÿ'Ñâa½ˆ²Üãøä~勇ê«÷óòÛÃ/–,õó©R?”0ÅÕËU"¼Wö÷~CïÁþ¾µ3ŸÇ½G‰/#·[,Wqàù¸#€§‚D +‚.°#‰ +y^6µôž=j=ôƒ)vÏh¢Ôê²Ù›Ææ–ln2Œp!N@] m¾ +µýßN÷VâÙÝ>èšÚƒÞwCˆ1M
‘„ù£ÆÀ;E)<Ä¡M^6±ør¹ +áâzw—ø[Å ªòyÊ®Kž]£;4‹mÔîÌ2Ì<1ìååà±9̬t'+ +S]¢0Áè€CB-ö0hÖ݆V€EáànàFƒ)'<Rkl[²Ìßê‚R7û¶cJ½žI@êcÑ3¥È´Ž(ª»²ÙW|”Ômé!â¨êB`‚Pªjoã&É_YÈvÍÒ +ЮùŠ4ÅÂbuK£*óÉöÜšÝ1™óÀˆ`zÞË©uÊe¡êôÖAw‰%¤\ßi#"?2¿w•'2d‡"÷ÊÚ §÷-Ô#PŒô¨R„öÔX"9ùÁR£õÐ 4œ†o {äi‹]Sóü³j¬ ‹†¦Çël×™v!ì€õ§ÏÏ¥ \"ìœ{¦©ÿ‹²Ú‘÷Õ÷ýo˜+ î|âsÃ4`=jëCÊ×<ƒÅB*š)Ì”#1,ºêlÎÊʽWð™‘9©sPË(ˆùŠe
ÏS´PpQ@¥ë‚-;9B€_P™[œ"X-ðrOÕNÙŒj˧M=GM@L=³ï†ºÅ;E!fT‹ +qb§ÖÔ’ÆС腬º]ˆahp´CDéͶÅ#Ô’|€×ð–¸\µ3bàaH°ƒŽõ’€àlƒj2
»x4l9ñNÓÓxdãay÷Ü™†#˜þ^[«=Ñ®,]©³•d#A,X=¼IRzwÇCQ§t›žL=(·ïÜ +yK€ˆ©›Œ,ÍšÚfc ‹£p©0,Œkù¥îÂœëGvÀ¤¦O’\‰a~iùÖj, ‘Ž‡ˆ CàöÒ:•*mµ}Háä¯È&jéÐÓn ‹XVÂ_6eê½êiÀ +Å1—®4Ÿ*àÒʽL.è"õÓ4»ú nBsþñCQ8ò±/4!â³7w˜ûa”]•çh®.ùLà‡Ë±£á}¤Ã8ÂÏœ¯4’c¨x<><lAè€t|Å]²`øYš_·à‘æÇdš¿´àyG^è,8ø†ÃE~èlÏLÊÀ›™‰^UmÝÕûá¹=¸G¨km–µÅÞìy©;‰Cƒ*Ú™6L!.]Çæ‘䇆e’¿²ëa£Y¯Ic«N¤ý[Qív¬¢Z]©æÄ +endobj +678 0 obj << +/Type /Page +/Contents 679 0 R +/Resources 677 0 R +/MediaBox [0 0 612 792] +/Parent 610 0 R +/Annots [ 680 0 R ] +>> endobj +680 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [186.474 626.12 193.448 637.024] +/Subtype /Link +/A << /S /GoTo /D (chapter.3) >> +>> endobj +54 0 obj << +/D [678 0 R /XYZ 99.213 577.054 null] +>> endobj +677 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +683 0 obj << +/Length 961 +/Filter /FlateDecode +>> +stream +xÚ…VKoÛ8¾ëWhOKkV¤Þ½mî&hÐMãKÑöÀH´$z%ºnþ}gHJVmÇA +Y}!<X$!¡ærÊBr|[Þšã-2<ÍSEÏ=LÛ“÷ŒÆŒOÛèêÞ|?˜ïÝ©?È"-Ü·•“ƒE”Åd‰)ŒéÚšÞ +l<yi7®fØæ{c¾WóÓï
Èn²¦;£|F%Á¸°;|tr;‹wåâTÌÒ˜f|,á
¼ëåt'Ó%4Mb¼‘ÿ½/ßB¿‚Û»õB¹¿9¤¬(üÖ‹yRæôÆ{ðî'_v/õí©s
N/“$Írg9ÂfË|Á¾rž×ÆXAY”ûO'ÁFÈÅh<¦€›Gû0 ŸÚÁe¤1©® éTÀˆ¶òF<$?–iQ¥j·Ô“Öé^5VQ³ØÞׇ«„bpZ.ZÙi¡kÕý…ÆŒ(wf@N¢ÓªÕ!PÝíäÜšQ–r¬M`ß Ó"ŽMÄlÖ"Qz(<Y6 ‰Á®µv«ÓeÝØCGäÊ$VjÕËŠ>CÀ4)^`Äó<%æEN\ˆw Å¥€#+fg´€Ú(pÚ¾ª»zнÐÐ%Õ»î™ë„õáþƒÖ»~7îuvµô@AŠöÙ–²ŒF,¿ÜÒæù–:Ì‹-½ïÐÒKÇ–Î.M¥,Ÿ÷4C5XGª±ŒìYY£!9¬ªdãL£›m¯á 9ŸªµyFžAWaÅvÛÔ¥yjpAIB†ÑKÝŠþÉ*ðŒ²ØÈf{æqM·W6µìÜãÙ´˜vgK‹zÌmà +endobj +682 0 obj << +/Type /Page +/Contents 683 0 R +/Resources 681 0 R +/MediaBox [0 0 612 792] +/Parent 610 0 R +>> endobj +681 0 obj << +/Font << /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +686 0 obj << +/Length 278 +/Filter /FlateDecode +>> +stream +xÚ}R¹nÃ0ÝõíÁ,u‹c¸i3¥2™´K3têï—–e§9 $ñ½ÇSÉÑÀŒF[ˆ¼£"øÏJéŠè*¤û‡Ydõð
0r°?Á&Fï£è0Ë»fÙîóºÀ"rPµ³":xŸÛ.˜æ±ØXÝäbûòó6sO!:;±MÛyjp`/’,ÌY¤þZQ;$Á¨ÅÝl_ŠÆꆆƔBEm®4¤c6ži,‹kÝÖZçüTŸç¾Ï“ñ¼ºþ£v{‚ƒLh-'ø•;¡f†£rÆË-Ö÷·zW¯³Öè“4ëÖ'„¤b¤wéî&$‹N»Û‹ ¯ˆ¡ìtY˜71œT¦@¹‚‰^endstream +endobj +685 0 obj << +/Type /Page +/Contents 686 0 R +/Resources 684 0 R +/MediaBox [0 0 612 792] +/Parent 610 0 R +>> endobj +684 0 obj << +/Font << /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +689 0 obj << +/Length 1145 +/Filter /FlateDecode +>> +stream +xÚuVÝoÛ6Ï_¡·Q@Ì‘"õõT4k×uHP3°ëh‰ŽÕØ’!RéüßïŽGÙJâÁ:ïû~w¦LüdR×<“*)EÁEž%ÍáF$póéFF å\ÖÐW.Wy¦¹u²ZX¸[ßüük)“Lð¢PÉz;{É«ŠË²LÖíßì—9z;¦«,L¥ÿ¬'Í˪D‘¬Š‚g`ÅÿLËœ
ãS×?’ÊÎïˆú`¼!êÁÝÙ’Ô\é"Z*%Ï” +–—éJ +!ØçÞ©¬ØÐNï†>ªfIÍkŠ4eVñ¬¨ƒêç-øQ;
©dÑ?R)˜é=ü€ßœ=õCšåp¸»Ë!"k†þ›Ùã4Zº0}K7]ï¼Ùï‰ûÇ×{HësÖF?»H|€H,ZNeÎì~8†šÂͧ©k£eg—ùÉK~ª(¡.òÄôÞ½{[™q%ã=OWJäìnŠÉv[2&TLVª¬y®5M󾨴¨t($ßE}à´[(a¢GÌJqŠ=G[ +5#¦ÈyVk±}ê®ð¼ +£/ŒÓÉÏöºl'p‚®óÓSÚÒ=@ýh{‡õÓû
d1‚*·tí]ê¶@f$6XÙw™[Ðy)tÂ4CÏóT4 +8]ôiâù¯‡ûYÔ5cwôC€jÜKvw"æä°noB¿ºðŽÏ]cé0Sµçã8 îŸê-q6'ú^æ”-"kÎÏŒ³Û·Ð¤h +;DüT¬Ùw6 +endobj +688 0 obj << +/Type /Page +/Contents 689 0 R +/Resources 687 0 R +/MediaBox [0 0 612 792] +/Parent 690 0 R +>> endobj +58 0 obj << +/D [688 0 R /XYZ 99.213 688.052 null] +>> endobj +62 0 obj << +/D [688 0 R /XYZ 99.213 477.043 null] +>> endobj +687 0 obj << +/Font << /F71 445 0 R /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +693 0 obj << +/Length 1313 +/Filter /FlateDecode +>> +stream +xÚW[OÛH~ϯ°ÂJµ%<ñÌøÂÛÒ’.åjv(ªÜx + (¨ +g 1"QdÍFÌã@æw>º]vºô™oi©MuÀ=†Ž6±C”ùÊyí“„ë: Ès\‚1$ê1iÞ84°kÇõ >Y¡¿ýõ!iõ%™k"_1öŠ¬ÉJƒ•¿s±k‹—çäåpal¹Œ£ +ÍŠÆíESVY’ƒvæ1 #ÓøänOr)û¢ej! +C•3£§ÎfóÜ€õ…$W]HBêú•HLÌP;Ï~8PÅF¾s¨,Z¥ãqûÙ˜‚ Áuå
!ècê†ÇZåz +-çA݈A穬J~õ` +µÖˆ¾¹{6þL£v·4=:
à!ø¿ºÛû·ïE_>²JL›üEÿRq]>7òì»r½2Ç0ËtoË%ÔgøÿFh»m„¶•1¨q¨É½½ÖyÙ’²uÜàÈôUd
³ñî¹!%øÏ]ë“Fzmž™ëß/êÚ¼Ÿ +ÚӔÈÑb^#òÉ‘ÕB&µ…„‰ê–džçsäÁV ðRçÿÍk£E»=ø`û¯«4ÏIxP®ÛáõJÙªíµì´˜×-ÂXñ=¶bñ¾¬V +§¿Žk‘C…Út'„¬A[Þê!EŒ°WÿÏZB†ÿfA¿‡éÁë†ÚgúÐÒSWÔendstream +endobj +692 0 obj << +/Type /Page +/Contents 693 0 R +/Resources 691 0 R +/MediaBox [0 0 612 792] +/Parent 690 0 R +/Annots [ 695 0 R 697 0 R ] +>> endobj +695 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.124 614.29 158.042 625.194] +/Subtype /Link +/A << /S /GoTo /D (example.2.3.1) >> +>> endobj +697 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [264.212 133.442 286.13 144.345] +/Subtype /Link +/A << /S /GoTo /D (example.3.2.2) >> +>> endobj +694 0 obj << +/D [692 0 R /XYZ 99.213 706.052 null] +>> endobj +66 0 obj << +/D [692 0 R /XYZ 99.213 688.052 null] +>> endobj +696 0 obj << +/D [692 0 R /XYZ 99.213 615.287 null] +>> endobj +691 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F97 676 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +701 0 obj << +/Length 2120 +/Filter /FlateDecode +>> +stream +xÚ¥XësÛ6ÿ®¿B£|0Õ±`âA‚èÙΤy:IëÄîd:M§CKpĆ"‘ªâûëoHJ¤Þu<câ±o,~»‡ðGÇJFùX†1 #6^¬Gáøì¼QG1s$³Í7£³W’Q1ßÜ1"f F‘˜«ñÍò·€OgQûŸùqLÃàÃô÷›-»$JnΈ h3|ÿ*<›ÃMCÙ(¢1ŒbGzÒ—Dêw¯§3.E0·ò~Âɳ¾D–Çó¢'QιÛuFq:$‡&DRå(ßöä(£¨‘cLºB“^ØÉK›àÕÜ~~²Khø]ÁñÏ]WíÇÞ³'$¤Þ¯Óž=R؃ZµR?L#˂ü'™K"„ç}jöG/oš|i2*"q$L¶|ýö{8^Bfý8 + WÉxãP¥Æë‘`Œ¤›ç£ëÑ»FîÁZ®¡ä!#!ƒ„ßÑ<®ŽË„$"T‚òâÅX¦ÄàÈQJá„'ÉXqÁM²™‚WRÆãY”(B!»šEljMo“NœÅãq,ätbc=/î¦, +Êï!à"Á±1HÈw»lñ‡¯ó´Xh§5~çEžníj +lé6]ëZo«æd¥& +oðuzl{ǾJ§,þšÒ(ðJð³N7½ÄqU§µ^ë©×ygR¯¼Uy9Ñ ü\¹iöÙ˜¦=YV}œW.ª‚„Š¤.œx'²};GÞY‡ÙF<‰º‡; +èáiLÎ;ÞÄadˋɼ¨ô¶¾ÚZ–»E=¹4¶Ââp£ºÌ,¥ã,êGÀi¸*3£ÁÇ0 +ÝÊóåéÉ/tµØf›:+ £VÍáý•æ;]!—õd¾|â„<±ÜÆH”ðĈø—•1£!‘‘7“ëü¬ñõò[™q³2'•Dà•K.oÚIJó•ÞÚg¬Ð(õ ä’»c{_3“Ób9 %"QH½N̤ŀbÜ—(H‚Šà®O¦Q8»Ó¼*qTÙ +Ïê%€TºÚå5.–w¸V{ª +Ú¼E
ÊE˜?o§"´8«¨Wâ,¦½ãYfËâdJ“ vë.|No‰«»Ê‘_¿{ƒ+iž¥UV|r¦lqµ€»Ä¥öú4È`†if-ú÷½½Õ‹ÕHW椄ïÖìœ2i +µži%_³ªv«ÒYÛ°½”äÐG#µõÑЗþ»[¬âW<xù5]orÝ» +ŒL¦ØÁR›D(2ÛáÚ3÷–:È59ø‰ +ƒGDFü(HhÌå?‚&ð¥âÑ_Õ[’^½4MyÒ1ú(=V1AD¨4ý/ÓÁendstream +endobj +700 0 obj << +/Type /Page +/Contents 701 0 R +/Resources 699 0 R +/MediaBox [0 0 612 792] +/Parent 690 0 R +/Annots [ 706 0 R 707 0 R ] +>> endobj +706 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [301.138 503.255 307.614 513.268] +/Subtype /Link +/A << /S /GoTo /D (chapter.4) >> +>> endobj +707 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [426.266 437.737 448.183 448.641] +/Subtype /Link +/A << /S /GoTo /D (example.3.2.2) >> +>> endobj +702 0 obj << +/D [700 0 R /XYZ 99.213 706.052 null] +>> endobj +698 0 obj << +/D [700 0 R /XYZ 99.213 423.79 null] +>> endobj +699 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F85 483 0 R /F61 705 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +710 0 obj << +/Length 2274 +/Filter /FlateDecode +>> +stream +xÚYm“Û¸
þ¾¿Â³Ÿä™3CJÔÛLÒNzÙÞíµisÙít:—û H\[,9’ÜÍö× @Y/¶“ÙY$€ VþÔ*M…¯‚U,#!C•ïoäj=?Ý(Fl²aþôxóêϱ¿JE«Ç§U µ~rRéê±øÍûqýûã/‹4FT,”@(öþ¼ÞD¾÷Ö~~€Oå=ZúÎÒ‡±'‘Ь7¡ôb¥÷Ï…"?I¤ûw+÷£•ûKßÛÏ¿ÙÏŸÖ› Ö ãÔAùy9…H‰Øwg–|»†ù=®ÔBŽJD¬RF¾_ÈIE†ƒœ“ys÷8lÍ°y¡ˆBóåæ·ß媀MüåFŠ MVÏ@K¡Òtµ¿Ñ~TÌíêæáæ×AõE+uÎB_-Ó¥#$áhi`zø_E|ëÈ®áõ!k³½éMû>;ÀJ¤ôÊâÍí¡mŠcÞ¯•÷ûo©'¯²®{sû:oÿ€k^)%R0ÉFi‘j=Jã@ÚÁ´ýË›Ûûâö
Ûœ÷j<ÆÁ¤SX¡ŸŠ$IÖ}©`º{rµ lµo÷õzãËÔ»ûšÁJö‡ÊÌwIéXH°QûBl[ìh·Œø8²²CoFðE¸ÍEÚ¾ðº# +!Qûpš}#b½Á—ñ3y9bC +K´myUSo1o"›Dì=
±òÆìý•ý.«‰º`08÷À¦éwÌ¡¯N.ÒL-t‡p؃S]3˜Ã\×8D)Ò@è}^ûà]=*oê®,À>^[Ö[â=ﲞ,ôÒ‰ØæXON0†úc[³Ä²çÐ!ej1{Ž‚ª¤ˆäX`GVV²Ó÷mVLÀü$C ƒ¯SïsÖ®Uâm×!@9–Ì)jX5 ªF`§¡ÿ…c[V†f_ŒW˜ƒØ—žq2ͱb/êú¬åu?—ýŽ™
;6V4^tÞ}Kž÷*¨·BHß:†ŠÒÛ7¼ŠÑ›|¹Ç3‘—½* ¦ºç^Řë
å\2ÑÈ1ö-ØlO&ç0Ý9aö>Ù.Ê'ÄŠüNQ¥§%Î=¤Ö@äŠHä¿[S`CSÚFψ²eOm³'êB £º ŽV:‚ZP«ol‰CoFð…æ"/o ”¹TSÝó-aÌU„úÛWxd\Y7ì I¾sÝ}}3‘O„ NlyuÝŒ¹®q&ˆ\Å'W…36ë¦'b—a +t‡0rÈbàOÍ1ß
ײ[Æ©U1vËìp¨Ê<ëˆÓQðœS[Õ '¹žXVUœÕþµVvRŒÌÜpN5H–vÓh¼
.$ŠÂÉγ²çš¡©+¦žw†–ˆX`Éù·6¦0…XæI̲‰Šë-eHOȵñ„¬Â|’Ò¯ËEÛeY4ÆçÅ +Ìtxü8Ù<–œ—ÿ`ÿC¥½;r»ö…Ï„”<¦jàôcáe
>¹\%<8¼:¸ ¥ÑÚ¸ÛckÝñò䮡^š°»ƒï#6•]tG}%cššÕ¼Ã‹ MÐ Ôê;Ñ[C9ÇάýX˜tLNô›ŒôÎœY[Qv9ÔRšµ ÑœNÿºüŸ!&Uì@t´q0O»ŒÉH¾ž 뀷¶¬
`Q©
ÄÝN5 +k`|f¬5¹ÕÙìY{Õla@uf96ûd/Ó*~Û6Ca†|…Á‹µð‰Á^Éu$"q
Ö%¥œÇÎÚ¿ÒÃ5È÷¹b—j< +Ú°Hì0¢mš„Ƨذ÷VÛ
áÁ –9È £xäØ“ÔÉãü!˜§ÝvKUÖ‡cƨ´³q +%ãñl.›ä„]<Â;“0ľŽ6Å=Àw£òMÔàü·~`H¡•¾úãÐ ræºùd‚“Q‹¼ä~[Xjú?”Îyöendstream +endobj +709 0 obj << +/Type /Page +/Contents 710 0 R +/Resources 708 0 R +/MediaBox [0 0 612 792] +/Parent 690 0 R +/Annots [ 712 0 R 713 0 R 714 0 R 715 0 R 716 0 R 717 0 R 718 0 R 719 0 R ] +>> endobj +712 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [146.089 568.885 168.007 579.789] +/Subtype /Link +/A << /S /GoTo /D (example.3.2.2) >> +>> endobj +713 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.486 515.086 158.403 525.99] +/Subtype /Link +/A << /S /GoTo /D (example.3.2.2) >> +>> endobj +714 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [332.922 515.086 354.84 525.99] +/Subtype /Link +/A << /S /GoTo /D (example.3.2.1) >> +>> endobj +715 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [360.581 476.232 382.499 487.136] +/Subtype /Link +/A << /S /GoTo /D (example.3.2.1) >> +>> endobj +716 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [284.379 461.288 306.297 472.192] +/Subtype /Link +/A << /S /GoTo /D (example.3.2.1) >> +>> endobj +717 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [357.732 461.288 379.65 472.192] +/Subtype /Link +/A << /S /GoTo /D (example.3.2.2) >> +>> endobj +718 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [426.196 266.876 448.114 277.78] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.3.1) >> +>> endobj +719 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [465.953 266.876 487.871 277.78] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.3.2) >> +>> endobj +711 0 obj << +/D [709 0 R /XYZ 99.213 706.052 null] +>> endobj +70 0 obj << +/D [709 0 R /XYZ 99.213 348.42 null] +>> endobj +720 0 obj << +/D [709 0 R /XYZ 99.213 199.13 null] +>> endobj +708 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +723 0 obj << +/Length 1942 +/Filter /FlateDecode +>> +stream +xÚ•XmoÛ8þž_tÎ9ÌšeY~®;ì®Ã¶æ[WŽ£6¾9vΖ»u¸¤(§~IÓ†ÕE‘E>¤Âçüãó$a>óÈ™'ýy¶yókXy;ã–õ,nçÅröôMäÏ–„b¾¼B1A胘„…"™/׎X¸Òs˜ù»‡ÜsΗË÷f{Ä’wÎâ”á¶çÀä;‘Õüõ×füj¿k¯TxŒÇ¡Ýw>‘ +ÖŠØ®.‚ƒh0bÙ“y…0ô¥Y³×˽ö^’,”zàŸÙÅ¥7_ƒ·ÞÏ<&’xþÆ`P’Ì·³À—0Šì¼˜Ï>íeјmvr¸ôxÉÔã±¼;<çœñ0ž‡QÌ<òøÅ.ÓÒª>Kw§'w³wÒ+avr‰G5.X£}/‹´iNO2ü +™l ãB}m9>÷Bºãv{ì‹4(ÂãÝa1χºGIÔñ×8„ŸÀÉ}ÏÑE.¸XÕiömÀ²¾D¢ï@Æà5ãzZ+¢U;WeZXi¹&†¦ÚZQÕC +Ð
Ü\”€º—êt•6Ê¥˜Ô渢+-?h#%µðH¦ o5íM| I‰/?ȇ[Ëìö¸§˜;HI>yiú°Â†Þ1À[žãê"Ž?KõÕ5ív›ÖùOSÁ+tà¶IÂb/‚п&6*¯‰Ö´»]U[h›q<ô”Ñà®t¯0Ä IÀNÄH¢€Àêª4þp' +vd¸Á¨s¡w"ÆTO¥Nà*¾üðKgÁ4ÙÉwÇ1ÊkKÎ(1Llר¼$®seB{|EÒÃß%t’A=·ÛcŸ\ÖX$ǤÐI¨†Ø 4O~Ë#žãúF‚?!émðàèµw,Ó’ê…,ÞA=\LE??N5ýI¨dºendstream +endobj +722 0 obj << +/Type /Page +/Contents 723 0 R +/Resources 721 0 R +/MediaBox [0 0 612 792] +/Parent 690 0 R +/Annots [ 725 0 R 726 0 R 728 0 R 729 0 R 730 0 R 731 0 R 732 0 R ] +>> endobj +725 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [146.17 473.195 168.087 484.099] +/Subtype /Link +/A << /S /GoTo /D (example.3.3.1) >> +>> endobj +726 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [364.714 458.251 386.632 469.155] +/Subtype /Link +/A << /S /GoTo /D (example.3.3.2) >> +>> endobj +728 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [137.142 319.442 159.06 330.346] +/Subtype /Link +/A << /S /GoTo /D (example.3.3.2) >> +>> endobj +729 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [202.923 289.554 209.896 300.458] +/Subtype /Link +/A << /S /GoTo /D (chapter.4) >> +>> endobj +730 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [320.52 289.554 327.494 300.458] +/Subtype /Link +/A << /S /GoTo /D (chapter.5) >> +>> endobj +731 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [256.214 166.842 270.659 177.746] +/Subtype /Link +/A << /S /GoTo /D (table.3.1) >> +>> endobj +732 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [508.469 151.898 522.915 162.802] +/Subtype /Link +/A << /S /GoTo /D (section.3.5) >> +>> endobj +724 0 obj << +/D [722 0 R /XYZ 99.213 706.052 null] +>> endobj +727 0 obj << +/D [722 0 R /XYZ 99.213 459.247 null] +>> endobj +74 0 obj << +/D [722 0 R /XYZ 99.213 270.752 null] +>> endobj +721 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +736 0 obj << +/Length 1265 +/Filter /FlateDecode +>> +stream +xÚÕ™Ko7Çïú{Ô–åðÍÂà:n^pÚÆ*zrP¬m,@~DZ£ñ·ÏpwIÉû`U™B[ØÐËÑìŸ3ó›%%È(þƒÌZ€gš*B%Ë®n&4û‚3¯&ÐZI±cóÓlòÃÏše–Xųٟ‚PfÐ%ŠÛl¶ø8=Ë?ÍÞÖfšXí¬4ÁÑ©›}ŠMOëë¯x…鬟×ãá³ÛG("¸ÿ4ÏI§ÄÙÒé½1NŒ‚Öö—Úï‡Úï»zü¦¾¾¯¯¯ò‚k>¶×} +ˆÂ4n_,§9ê›åpÔó†h°åEÏ%\Êàg Kg99Ÿ…Ô„äI¢¤p‰ù:ùø‰fLâÛ %Üšì/SÖf7Á$Žtû÷jr9ù-øjæTÖ|j¨¼Eƒó€ÓJKœV¸:=è‚1AŒ1Y¡4`°ä`-y#oãÂ0Ë
Î?¯Ê¼`XNàGÌæ~vÝÞÚ,¿µƒj^•7åm•Ã´¨ïÛùrUßÜtcˆ«%š‹lGQ_¶Æ%aT +Nd@<Î\[‚‘ +sÑÜ,@+ÆSVQtÕŒZëŽ+`€Ò5FS2bºMØ©5†e…E¢$–'ãu4/CˆêÐœ7¡éF†áSµÔÙŽ÷õzgQŒQb].v´žVÕzù9gtúP•=‰˜<ke"‰³¨DŽ¬Vë®Ä³ëåjñ$”½*TaE¡ÞYT¨ +íàHF<¿’1•x6ÀCH¦XË m •ÁSÒÞ[ÛÖzHì#ª9MŒ)„$B·ðPÇrœÈ¿ÃÕm‘ì¨bR(ÇfžÉÎt™ƒœ~«ÊÛ~óÄ&Yu@6’Ϭˉ•{S™BßËHV<—Bbƒ<—)Vs„7eR0Ý7>’ÅwÑÏÍÜ@S‹:f£`îøK +endobj +735 0 obj << +/Type /Page +/Contents 736 0 R +/Resources 734 0 R +/MediaBox [0 0 612 792] +/Parent 690 0 R +>> endobj +737 0 obj << +/D [735 0 R /XYZ 99.213 706.052 null] +>> endobj +733 0 obj << +/D [735 0 R /XYZ 267.578 686.279 null] +>> endobj +734 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +740 0 obj << +/Length 1824 +/Filter /FlateDecode +>> +stream +xÚXY“Û6~÷¯p“¹1’¨sf³M✻ÍawÚN’®ÅÝÕÔ–\INâþú~ (Y>r$3K +S'±UZ…ó;Íšfï.O‹EæÔË°ÊÀÙVTM‡¥tʪeʦ)Ê[¸bÜ +Ð஀5[Įˢ›5e®Iíăë9ÓòúFw{«Í²-ÖËSNõÑ2ñ2›Š×eež ÕðJ‘²™å9y]ì;ZeW¼?".òn§3š1¤r¯¯}±¢ªˆ( »ª!|[7¦ÍB‹Î ¿®.ØíêºZþ°H=ÖÅwÑ¥\N2¿º†cEÉ +@áϺèfJ +ðÅvÒS‘5—÷ãÈ¢CIßßÝÜî3Õãï83¶ØT4ºÖø4PȽÅøƯ')â‡ßý=hÇr<<x1šf'„Ìò£CEÝ'Çšþ¼¦–endstream +endobj +739 0 obj << +/Type /Page +/Contents 740 0 R +/Resources 738 0 R +/MediaBox [0 0 612 792] +/Parent 746 0 R +/Annots [ 742 0 R 744 0 R ] +>> endobj +742 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [314.274 382.625 336.192 393.529] +/Subtype /Link +/A << /S /GoTo /D (example.3.4.1) >> +>> endobj +744 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [173.244 136.954 195.162 147.858] +/Subtype /Link +/A << /S /GoTo /D (example.3.4.2) >> +>> endobj +741 0 obj << +/D [739 0 R /XYZ 99.213 706.052 null] +>> endobj +78 0 obj << +/D [739 0 R /XYZ 99.213 688.052 null] +>> endobj +82 0 obj << +/D [739 0 R /XYZ 99.213 629.741 null] +>> endobj +86 0 obj << +/D [739 0 R /XYZ 99.213 485.81 null] +>> endobj +743 0 obj << +/D [739 0 R /XYZ 99.213 383.622 null] +>> endobj +90 0 obj << +/D [739 0 R /XYZ 99.213 241.509 null] +>> endobj +745 0 obj << +/D [739 0 R /XYZ 99.213 137.95 null] +>> endobj +738 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F97 676 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +749 0 obj << +/Length 1530 +/Filter /FlateDecode +>> +stream +xÚíX[oÓH~ϯð¶/¶Tžñ]*+º‰@›”Ä]Bn2mMìàýõ{fÎØqâP±ˆÇU¥øÌÌ7çúÍT³àjaHµ5ßòˆå2m±XÚ=Œ¼P…0Äì`þˆOžûLIèÙZ|§ÙŽC,€žxv¨ÅË÷úÐø¿’0Ÿ„¾@ù„:6(£/
Ócú…ü½†_ªÇRŽ¤<kçîLxı›Ù¶aº–NÖÒßö1›UØ©Ô;“z_Ky,'ò÷…aÚ¾:vèÈ˾%@µ£#áÙƒø6©§‡Ä§¡B^õô„ÄvÝVÏ.AsDq[š¶x.ñ\GæóàýGK[B_
,b‡öd‹Ð0ÔÖ‡¹ ùª½ÌoZ]8æi8ëæùf2pÔûWÂløz~@¬pGº^9"Æè[²Þ¬¸a2È™M@Åa Ô…Iž¯íÔö,7CÓÛÉ»º kû<ÍJ^T¿÷‹äB&(Se(«¤âkžU%zY—ivsº¼ + +^Ö«JÍNÁ@V)ÕÕvÃ…BÞ˜*ò
x²}z2^ª.e–¬û®•yt
U:æñtøz<jIÀÆ$ú+þóâÇ/æø…Ž•æç³éJ£€ #æ+çOövʽLfUŽÒõl:ºi|û`¹Öõlô |΄4ŠæÃÙø:O'0¨ +ù%YÕ¼ÜM9M—§g§K^.ŠtS¥yv*±èç‘œ?é,2á$Iø0M]¥‹"/ó;ÃüÊØœ_8¸\`s<Š&ñ8~‡a¾ª×ÙŽ®-W–oWs,ÿ,ßþ[³ß÷·WŠãU€%.vê&>,Jªï?/«óÓæ÷ãE+zöl¿4IÙ¡Ó#l9`µÆÿFŠQ¦©ø³m)ók˜ðî"ü./æñ§ñdÍà3Þîö––`÷×òB^;”"8þ }Üß\/pÍ°E-»³]¸:6·¯ó{žñŽþc +}‡øÍý Jîûg<DaŽ`nN>ÌÈwôêy
×NÇÓI&NRŽ=@±«dƒ²`‰„ÍÎÕ¿‰¾¨+„™Ð±mg!´½¨`ÿ6¯ßlVé"ô@dÁ?×).Ñnèëox¦¬>( +מ>ïr¡:ô?]Ó¯'Wý).= Í–(ä·Âû/i^—g†é0&\Å¡u²E!Ë+¸Á\Aˆ?CåçJ‘Ì|¿iÅ÷üÁœÄêíøˆæ»ZAø>sôø÷¯sÄ!^^Ð!ƒIC«+„[Ž_¨ò%ᶄ<©Ôh“(„è¼ììÜ<E;©«|
e\€ï[ÃsuH³˜~›´ºse5éGÄB`$kž0çí.”;•i»ûë¡pòÆÂPæ
ðØ ˜&¤áÌp,ýf„-á?ä_%_ôˆm¥DQl¸bEPd\âªQo–»jHæ0èK€T7¡CòLZª7›¼¨øR¹ö\&WN% +ÓÕ/קµ,ƒšŒÙ¡ +vJ}ÝAo· +„Í¿
Xà\-NÑ!¢læuKøj[^{>¼ +endstream +endobj +748 0 obj << +/Type /Page +/Contents 749 0 R +/Resources 747 0 R +/MediaBox [0 0 612 792] +/Parent 746 0 R +/Annots [ 754 0 R ] +>> endobj +754 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [302 151.898 323.918 162.802] +/Subtype /Link +/A << /S /GoTo /D (example.3.4.3) >> +>> endobj +750 0 obj << +/D [748 0 R /XYZ 99.213 706.052 null] +>> endobj +94 0 obj << +/D [748 0 R /XYZ 99.213 271.212 null] +>> endobj +747 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F85 483 0 R /F103 753 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +758 0 obj << +/Length 1240 +/Filter /FlateDecode +>> +stream +xÚ½WYoÜ6~ß_!쓈hR<$µšº®ƒ´€á
ú…¼âÚjµ’¢#ÎþûI{ºk#X#røÍÌ7µÄÃðG<)QH¨a0½Åj‚½{عš§8•`Cç—ùäì·(ô$’‚zó¥†a"‰•Þ<ýèÓYÀ±ÌÖË‚`>û<gŽGHFút(‘¶¦Oý:¡9¨C´ÖÁ»1ÿßkÝÉå|px‰#Á™v÷Ëäãgì¥Ú» FTÆÞ#È)½Õ„…¤È½ç“ÛÉÍ€e÷„gOb‡áaz<٦ή.C2ÖH`ñÿnÑ(F1ãÝÂ^cÒ£(æ+YåmŠh{4B"d##{ ˆŒq„Y<&žx1¤E˜´PŠ$žˆB“ØdçzU•u›íOC}&PÄ¥KãüAÍJ˜Ÿ*R•Ú·®qËåÒ>[Ð3H1‘bH3xg‘~¾W…ª“V]ì›ä Âœb›Ü;“Ã.í³If!ö¿Î÷õTÍBîVò²Ru3zc…ºìîò_eš÷Ú`PB®™"!m[¬º"M +}ˆ2ÿö潨ߴàûJmc7>aŽ“"µ/e‘¯ÚÖáñhÝh‚ø×Ý4áÁ‰¢t+ ±k‰3’8ø»Ô¢
Ú2¨Už´YY$¹ñ;ÇmVWIUéÈŠ9°Væ`‘EL'±Vv5±K +MÛÚ./ëde¸|œAû—õ¿yHZ+Uu©õ¿f©PúõE“¹$0ßmt× ly¸TI°…¥¿íjÕh®xlÊo¯¬A'U“¢f-ëaÏ:µS¿VUž,L²ì²¬æH¬š‚gûP6Ê9ù׃*úÕîÉÅä%›¥Ãä¦[¥¥‚8B˜&-4]æ¤uÙÕnS©´yceÓ²ZHìcQ3Ö·S¡“4ˆ¯eÐúʵ¯YÑ´*IÑî g2‚ ³eœ?ÇFk£úlœf/Š»ŒE^0Œü—é Œá€œ +endobj +757 0 obj << +/Type /Page +/Contents 758 0 R +/Resources 756 0 R +/MediaBox [0 0 612 792] +/Parent 746 0 R +>> endobj +759 0 obj << +/D [757 0 R /XYZ 99.213 706.052 null] +>> endobj +755 0 obj << +/D [757 0 R /XYZ 99.213 594.206 null] +>> endobj +756 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +762 0 obj << +/Length 1836 +/Filter /FlateDecode +>> +stream +xÚÍX[›F~÷¯@û„¥2a.À%Q6É6Ù¤Ù&kKQ•FƳkTðnÜ_ß3.ÆØÙ¦–Z´˜3çúË€-þ°†ˆ`j®\XñzâZ·°óz‚
…cHœÍ‹ùäÑϱBúÔšßX”1ä|BäÓК/?Û/§_æoY€Â@R3 +Låãû\ýÿ +×rHˆ8çÇÜvž¦’9|¸×þ{Ÿü¨’¬eÏ—êÙH + +Þ1yÔŸ(ì™4j;ÜHwò 8·ÎÖ̦œ €Z=u÷m‚D÷¡p9€*ò@ñ`?Ž +U†5 +=v*èyP4Móz>äÕ
R
¢‡ ×ñ:äŽ(ÖB®§XU˜)í`TwU)ƒEWbMuÝTM‹Ë'uƒ—幆:µYÂù¶wp>b|çžñÛýºØ øzí ˜ÿQݘ»þq1áú3Æb{£@øˆV-„{Z†pœ§›uÖ¼mq;Õ3eº‘þùôæâúÂM# ðC°=…½lܶgp–Bí)Ô:iíe!â°uô£ªÔZÞ§zM4£Ìï
…˜Ão G&Š<˜“]¸ÃÏšúâÈ«¥ÜEªÞè‡ó½h×»òZÅ!¤w‘çî~ßÌ¢/@ÃF¾ZF¥ŸêHß3õÓÝ”À…¢LòÙÞÃà61·gÂÏä3ëø”(ðY›ˆÊ…r£¼5×=g6ÔN|ÿõ€¥ö&ExOn 'Îу蒣òÆÈó¼yp=2~i¼§ÚŒ\&àëä/¸‚ËÇÀ|ÑÞ0Œ©l÷ÛÝØt/"ùeCR7ù…¬ÁFÇ¢¨e(¬½\$Ù2‘Q»K–›(=|wˆš[ßRÍiu}ì7@Õ~V*¨mEjî~7yšæRØý.žèÀm„CF`v4k:’ý0C‘ä-é+̇‚š/Ùû’þ¸?ë?endstream +endobj +761 0 obj << +/Type /Page +/Contents 762 0 R +/Resources 760 0 R +/MediaBox [0 0 612 792] +/Parent 746 0 R +/Annots [ 765 0 R ] +>> endobj +765 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [386.768 151.898 408.686 162.802] +/Subtype /Link +/A << /S /GoTo /D (subsection.3.3.1) >> +>> endobj +763 0 obj << +/D [761 0 R /XYZ 99.213 706.052 null] +>> endobj +764 0 obj << +/D [761 0 R /XYZ 283.582 307.307 null] +>> endobj +98 0 obj << +/D [761 0 R /XYZ 99.213 218.498 null] +>> endobj +760 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +768 0 obj << +/Length 2024 +/Filter /FlateDecode +>> +stream +xÚÛnã¶ò=_aäIV\^DŠÂi»ØnÒ³)zI÷á [Š¥¬…cK^IÞ4ýúÎð"ëf'àÉáÌpî#¶ ðÇIB8‹˜*B%_¬wtñNþ{ÁFèPÂÎ÷«‹·?Ä|‘D‰ÅêÉDŠ™„(‘,VÙX†’ÄüJ+Fƒû埫Íõ˜$1ÞæšH LͽÕR°àý2J +¾¸^u&ìŒ,‰’ðËÅÒEÆþñ‚‘èÅÀ”°$Yì.".ŠÝz{qñ[GËžÍ9‘ ¶ˆ&3ÃŒ‘DJç1(“Š5¡ÎaP¶Š`TôP…$Zye™ÓUŸ‹‰#mÛºxXrÚ|ªøP +¡ áÈêw“/C΢ οŠ:wÀ¶Ý%ÉâczTYϳÒûºZ2|-²¼Á¤ö Lw¹Ýx¬j»Õn +‡Ó´i›ïò²}k΂§M±Þأݡi-úƒ»(‹/Çî© +¦‰PCŽÍ¦B“=5VéL8@ªŠ}4ôs&&tL˜ö1‘–™'j´¾®ê:oöU™åç)ŸPD²#Øñ›Î>3Ü”„B!ú8HíáàöÂFèõÛ¢„=ŸŽûõ³±G1õÎùHÈáU³&cn©ø,s3áåå˜<ˆéû÷>}ô”mBWŽ§É£O1‚œ÷MÄm–"Žö1ÛöO(7ŠÕ¥÷¼“:>¢šGö-ïTì0&~I)”–o/‹²ÉëvÉ‚ŠFvX·¡Á»´ëmÚ4ß^ÞÚ£Kã0>i|jLyxhíì3ÐÏ.ßõïfÊéKP¶Öp©.ömQ•—oíípŽñÛIø žõÈÝǾÅ*˜‡9z¤<ÒH-'5àôhY”me¡Û»_¯~ÿ°²‹OTÒÛ»«¿€éÍÕpõ×Õõý‡»›ÛÕͯ¿ +³_]Æ÷ß½y‡‡ÿ9£‰sqM1F™Á½ÁbÄ©ÌÌ"MÜ%*b/dföÐ'yrLòdff +[´xÈ{æç<Ç!䈊twÓ…©¨W¯aÓ¸"îšA
¡'B+`Ú(X¶OK˜)*»èÜŚŒÐS±ÝZÈ”hÃV…Óº©÷ˈvâˆö˳±5¼Cëhäl€,¸ñÜÿö“¼àc ü +¹†Î°ù¨M‡‹cW€H¦„í:7dvõð<âÓõ"v‰ƒ?Ÿ®Ñô…6–w=—þ¥’ÁXò&_W¥“é()¼±/¬i}ŒÆxõUéÊfE„sÅÛ +`Mm2ÎL]ÔP3䬨†¥†–§.Hb}
Lb½HrÐXSY¨9ì÷UÝâ¤0í8öTåT”¹]ËC7ç¦!‡Õ®2†ŠYŽ6ùŠnœoQ®ÆqÊóÅmï}?‚ãI«_mŸÒ#й%\ë"c®´Y›K¡ÒX(Å”[øÐ䇅Ÿ6yi!ëG +endobj +767 0 obj << +/Type /Page +/Contents 768 0 R +/Resources 766 0 R +/MediaBox [0 0 612 792] +/Parent 746 0 R +/Annots [ 770 0 R 775 0 R 776 0 R 777 0 R 778 0 R ] +>> endobj +770 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [293.947 557.273 315.865 568.177] +/Subtype /Link +/A << /S /GoTo /D (example.3.5.1) >> +>> endobj +775 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [147.176 337.488 169.094 348.392] +/Subtype /Link +/A << /S /GoTo /D (example.3.5.1) >> +>> endobj +776 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [319.435 253.802 326.409 264.706] +/Subtype /Link +/A << /S /GoTo /D (chapter.4) >> +>> endobj +777 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [219.256 166.842 233.702 177.746] +/Subtype /Link +/A << /S /GoTo /D (section.4.3) >> +>> endobj +778 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [185.2 151.898 207.118 162.802] +/Subtype /Link +/A << /S /GoTo /D (example.3.5.2) >> +>> endobj +769 0 obj << +/D [767 0 R /XYZ 99.213 706.052 null] +>> endobj +102 0 obj << +/D [767 0 R /XYZ 99.213 688.052 null] +>> endobj +106 0 obj << +/D [767 0 R /XYZ 99.213 630.318 null] +>> endobj +771 0 obj << +/D [767 0 R /XYZ 99.213 545.328 null] +>> endobj +110 0 obj << +/D [767 0 R /XYZ 99.213 239.887 null] +>> endobj +779 0 obj << +/D [767 0 R /XYZ 99.213 140.008 null] +>> endobj +766 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F103 753 0 R /F85 483 0 R /F97 676 0 R /F105 774 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +782 0 obj << +/Length 1968 +/Filter /FlateDecode +>> +stream +xÚ¥XÛnÜF}Ÿ¯ ä‡åa›}#›mÀ+Ù±œHìC,èJâb†dHNdý}ªúÂûHÆ.hšÍꪮêêsªH½þ¨—$„QîÅaDBɼíazwðæǵ 2ÿܬ^¾™—$âÞæÖãB)Гˆ'Þf÷›¾þcóQ‹Å$‰Q*&TpPŠo?¬ƒˆùoõÿ+øOý¿Óãënmo""‚»Õ|ÈÐ'(úÿžbœ¨ˆZÙÏZïµÖû“_êÿŸôÿ×èè_˜|˜o!¢$FíÅ‚{¼ÛàÛ5ìo³æF3=T‘˜&Vò癞„p);=}€nPrõnÓMwx’DRàÁü¹úíÐÛÁ!~\…„'Ê{€qHh’x‡•`F±}Þ¯nV¿tºÌ»È3«–ò@pX+/`°ÑèD®Â8üF±"aÒ§ +í·›¶è㻯é¡Úgë€AÌ8‘„M¥E±×«Yv"SÓàn÷¶'ÔÐöM•móÛGc»½ÏÌaÐPö«„$ ¤¶9*ÓCÖfõù>m#=4¡"ˆQd…òöÞhNÍÏÕ‡+3ØêÕzX€¾5•ÌÓÓ5±Œ%,–C§O‡½—ÕÎ+9»Áý½jÚ´ÍYÑB~…¡Ÿï^ŸõSÔÿ[;3¯Æ~¿>»ªËÝqÛž½Á]{”’R6 ‚$BhÝyÑdµÓ[´¥]]¾øõ|cþJ÷GpÝoÌãï¡_ä»ß›Ç»¬ÙÖyÕæeÑÍUu¾Í^€$ÕvƒÃ¯^v.¼±W‹º
BB/Œ„178Ù&Ü™ó—€ îÖYsÜ·?§Õ‚¾(&R8LH۶ο¬YèÛl~ï°Ae^ÀQRºÎà“¶cê£%=¹ÏÚƌˣl!`eÑÖåÞ<ß—k&ýó°KÛÔŒòƨҩõµÓm›íÌ«Ûº<˜æ»hAqkË[ókuýyÌêÇu$}8êÒDwæ;}Ûr<úz`À§“Ö(`vH«Jï(‚{Wš¹ò˳mkÆU]V@yÖ ÊÖ¡Ää–°YÅ“p"’Ú[‚óõ¸Ü+¤gT6Q貄Ï'1 !EF–'÷ÒÉ<i‡!‰¢‘ÁƆµ1±HM*
o3°!sY÷ªËÏ7œl"×d{{ÛµÖ†?5ÏÛ²EUYìòânn1à!:/Ûì®
M+@[16} +åÀw,£'Ie sŠUœÈ"ðS´ò”ñŽW¦Ö—ˆehþí•("cuêÔ† +G¡Ó¥YNhÍcrJ#½QÔÙÏR܃ÃdßÄ.ÙSìâDÆØÒ`ÛU(†!Ë2[C.ÕsäbU[j2Hñøú,ß9=%¢@ÕõÅ./Î^¾Y +n§«úšôù— +SŸÀ"$”Õq)o&ê8I¨Cà|·ÐþIv.:Ø1–lm†6—T\rÝ_ãkn¡aVÐG:·
|}o»G(:yà·ê¼µACÓ”BéÚ…{P›?Ãû½¨ÚøwÒHÌH¹ÈLvn'a„J1rpV^PÆM€{é“<UPÖ)’€ü³½4‡R ¹ØK›CqvR^‚3²Òc-œp` +ÙÄ–«K +-{cAÖ;ç +#ðJ5<7 +xä1ʈTà7kªü¼ú¡i/À²FlžDìcصÓüb[é` îð³7?Qyºø¦\÷g#ÛÓâÛÊ<iqª¨Cl奾s5EÀ¦´Ã<˜DX3#˱zü]÷R˜Øˆi¡cU•54a€TÐø—·f^7v8xXS¼™VOª1_Û^ØÆéÛWÙ
vKweñ<RÃóA7„!F +endobj +781 0 obj << +/Type /Page +/Contents 782 0 R +/Resources 780 0 R +/MediaBox [0 0 612 792] +/Parent 746 0 R +/Annots [ 784 0 R 786 0 R 787 0 R 788 0 R ] +>> endobj +784 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [276.144 508.362 298.062 519.266] +/Subtype /Link +/A << /S /GoTo /D (example.3.5.3) >> +>> endobj +786 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [146.031 278.366 167.949 289.27] +/Subtype /Link +/A << /S /GoTo /D (example.3.5.3) >> +>> endobj +787 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [194.936 209.646 214.861 219.757] +/Subtype /Link +/A << /S /GoTo /D (example.3.5.3) >> +>> endobj +788 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [266.304 136.954 273.278 147.858] +/Subtype /Link +/A << /S /GoTo /D (chapter.5) >> +>> endobj +783 0 obj << +/D [781 0 R /XYZ 99.213 706.052 null] +>> endobj +114 0 obj << +/D [781 0 R /XYZ 99.213 584.928 null] +>> endobj +785 0 obj << +/D [781 0 R /XYZ 204.577 511.515 null] +>> endobj +780 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F97 676 0 R /F105 774 0 R /F85 483 0 R /F103 753 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +791 0 obj << +/Length 2221 +/Filter /FlateDecode +>> +stream +xÚYmoÛFþî_!¤_( ÚrßøR48‰«µS×VMÐâÚâE²ä*>ÿû›Ù]R¤HÙÁÁ€¸\ÎÛÎÌÎ<»¦3þè,Ž £|úñ%›mvgþì¾|<£ŽbáH=š·ë³?„l“8à³õ=Š11 x<[§y|¾¾G̯lÇõ½Ûùßë_{Hâ¹YD$ö
ßzΩw>1Œ€Þ[Â/ó®ÌïÒÌ|2ãu'¦³B"8wr#5œD4è´ ¤?¤k'{$ ÂÃÈq,Gò"#é¾^öì;ØÊNÛÊCÊVöù„ìЗ[×FÒù]™ß·àUêýá4ôŸ-×]» KHüç쯿ýY +ÁþåÌ'<Žf0ö ãÙîL0 £Ð½çg·g¿w²ì7p£ášÊ n~<‘0tF)‰¥tƒ6aD|—0DiA}Þ#å’DAëŒZ5û\¿Ë“¦qNëK(‰â6ëìnÎ|o¯Õ8Jm܇¬•†gu?_ÐØ÷Kñíè© ‘3®’j,W‚—cêè²ÆÊ+JmM¥6Ùßg*}=_0ŸyOåÞ~Ú%O=š9õîŸNN‰E|Ò)={‚¢Íû¬h´JRØŠ¯·Êjø6gÒKò½{-ôVUÇá÷(^ð‘ +Ðîqp%²–Ô5äŸ6Å$ŠÚúáä¾°á<òæ’)…Dƒ€¸TZxYžã(´±€™d¯Ë]¢³M’çOvj—T•e=]Ú92˜Ø”ù~™[4ö=+Žl˜µSï.iZÕeá$nfKèT*¤‰NÌ2™[&ß—y^b¤»ÁýÅ)L€ÿ&»*Gè@ÍÖ’7öub_…Pód¢ŸhµS…þ×ĶŠëÊÊ
™û˜éíIßµmûòû†Œê» àΙ ¦Bõ›lâ–dÑ£ikr¿‰cInILÓë{´8ÖM!©X>«¼¥i‡sHú +ØÇ8´í¬ýMu»¼\¾[Oì¶ëåÍ×Õ{+1iìs•¾>Aùaus»þúéüj9äøÕþ”€å;uŠ÷ò|’õ2ANèÔ;Åùvu³¾øúþ|}Äú6«õö=jÕ'µ~^®>^¬¿þúqÈúYe[½*€÷×,/ÀÉÍ)VÄÕPÂ…“p"0>Í÷‡›ßȹýíÓÉç‹åͲ£éBñÆ>~ø†íýÛ &ÂúóÏ;ÐÄ9áÌ"5X¬mu8. Ð+Ðî$ñCñCýà(núÅÁQ/zä£óűÈ/ŽËQ˜È@÷q9r4Ïk<Ô¶lè?“¨ˆb]jQŸÝC/€~פёÛÄ
ªº¬T3åÞ³b“ïÓ“ë᧠¥Œ´5k•Ž5šHÏ¡é·ünëí&°2”Æ0Ž¾OL·
Ç +Ê£ß'ÅíHý³lˆ:Û-ÚíÏ ?ù”4îa3ƽ¤H'à" Å =¬v[·Û·C±'AP‹co™lÐÐÂZjÐQ£ìÔ¦¬¡We‘6v Úb(ê@VaÇIžˆÂ:oßSåÀƒÃ—ÄžÛóC³NeW$½»'û´bapûû¥4*ÇJ´ÑîõÐ`ðuß Ø²~o%
ü +ûî 6 ¯§G–µq$¨]Ì ’PcÑ[ÊÀ©°#_òwI§¾H“:=2ò^%z_Z*¼Ï[UØy‹ùòß@C6‡¬XL!±Zcªý*}jçâönÏåÝ¿wBfxæY ¡:_¥v³Çª²Úçí<·Ž‡iÀÕ›õeÈœ/aÐîÊcÀóÓf€÷‹r¢£³ßšÝ_á}]î,²Ì0øg¯ê§y =Ò¶ÆIlãð‡
7'ßsÊ]Ò5vMÁ'œìWkÊÑW·È«¬elMÝ!
§R…Ç‘¢Ó85à‹ò¡wçði„…ñ +kå¥Ú§Ã™ãÉCeÑàU3.Ã)¥É|¸`)<“÷x ÇIãxšƒý};¹U–T?UÊqcEè}ƒ‰½®Œ@wp,|øâK?sÔ…R©Ja†âò"Ú˜†vÉ‘]cqtæã¡ïåe’ÚQ\&ãžÉì¨ÙcMã!5 g6¥f3š‰Ck#Î)…½£Ä +endobj +790 0 obj << +/Type /Page +/Contents 791 0 R +/Resources 789 0 R +/MediaBox [0 0 612 792] +/Parent 796 0 R +/Annots [ 794 0 R 795 0 R ] +>> endobj +794 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [145.718 351.925 167.636 362.829] +/Subtype /Link +/A << /S /GoTo /D (example.3.5.4) >> +>> endobj +795 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [216.501 223.407 223.475 234.311] +/Subtype /Link +/A << /S /GoTo /D (chapter.5) >> +>> endobj +792 0 obj << +/D [790 0 R /XYZ 99.213 706.052 null] +>> endobj +118 0 obj << +/D [790 0 R /XYZ 99.213 688.052 null] +>> endobj +793 0 obj << +/D [790 0 R /XYZ 99.213 600.466 null] +>> endobj +122 0 obj << +/D [790 0 R /XYZ 99.213 209.93 null] +>> endobj +789 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F103 753 0 R /F85 483 0 R /F97 676 0 R /F105 774 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +799 0 obj << +/Length 1427 +/Filter /FlateDecode +>> +stream +xÚ•X[›F~÷¯@«<`i=; 6‘Üì%I“4M,õ!‰ªYÀkZ®Áݬªü÷ž¹€Á€µÕJf˜ùÎe¾sæÌa‰‡áxQ„(a^€%‚zñv†½{X¹‡X8È¢ƒùy5ûá& ^„"ɼÕÚcœ#LCÐ!É"o•|ö_ο®ÞX€¢@£D8¥zõÕ|!©¿4¿à—ø+3¾6ãìÑ„Dœ5Òl¾ØG‹ý߆(C¡$û«ÑûÑèýÅŒ_›ß÷æ÷v¾`ÇëÈ«¡’ ¬Ú«‘í±ÖÁåü[ÍÑ@ Q@"‡|7Ð!&D«çHÐ'œ]¯ÚдÁH +®ó÷ìóWì%Ä73ŒXz0ƈD‘·q*`¸÷|öiö[«Ë®IÏJå qOç’B„mdÛ]žnÓ¢®æ +¤,÷{õ¸Œã´ªæÄÿ‚±óq®*©7ªv“ª°ƒ»Ô>UšØÑCVo¸[¼Rµz§v»t?²ã”.Ž(#ž„\Õ™4šë²è`ìÉp‹¢÷xýMé]Z/HœÚ&¤dpÖxƒX‡sG‚‹<ì™_6‡ ø!š$ë’n ]•2MµáRuV—ŽýGûLçûßê´H2ˆ]q?f–D(»«·YU-R f2N„¢€Ê.UÓñ:b
c¡ÆËA´I—kC:Æqy(ê—ež§±Þ2T–(òS³K‡iwF„£ˆs£æ_3Å&"½ùÝá.Ïb+»>N~SIòQ÷)¤=Ÿ)k½2ÇàéÖå>UñFkiU8îi§uêV·ÑÓWô¬ÞdÕâ¸ÕÕ¥%~4" öe¾7Žº(žßq\îW¥Ö,‚?@¤sP§äÿÜ·Z¼ÐJoöåÖ8·Ðñx(õýìB¿~ŒA5•½ƒÕM'"¥tê\õ®‹…¸Aº”cXÚ¢¦º¨égµKãìÆT×6†…Ž¯]Qêätc8™æ€Ú×z³/÷›æ%:LiˆDÐ\[9¤òKãËÀ_!QȥéºÞgwú¼ê.Z„þʪŠd„8zdþ3§ÂWùAWÁPøåZ?'\pË0ù$dÛ1ô<²F²êhÄL¬yn§ +µ=ñDÙׯ>Øw +sçh¥ÇkÈ%Óž“'E_ +endobj +798 0 obj << +/Type /Page +/Contents 799 0 R +/Resources 797 0 R +/MediaBox [0 0 612 792] +/Parent 796 0 R +>> endobj +800 0 obj << +/D [798 0 R /XYZ 99.213 706.052 null] +>> endobj +801 0 obj << +/D [798 0 R /XYZ 99.213 675.933 null] +>> endobj +802 0 obj << +/D [798 0 R /XYZ 99.213 324.868 null] +>> endobj +797 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F97 676 0 R /F105 774 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +805 0 obj << +/Length 1586 +/Filter /FlateDecode +>> +stream +xÚ¥XmoÛ8þž_ad_œÃ¬J–ü"`= ëºC‡îMq8ì†Á‹•Æ@bg~YÛûõG‰²cÇI;Ü¡@LIÔCŠ¤HªÌ¡ðÇ)‰Ï¸ÑÐÀw› uî`å ³žeñz<¯ç““·‘ïH"CîÌ—F„>ÀHréÌÓ/.Ÿyu‰ù
Z:dÔ½™}¿3Û#"#½ÛI +Î~7¡„Ëعš&¥³™? +s +Û"šC<VÙ?Qµq˜ÛÀ$£”ÚI‹ä²ëÉ^¸jy¾$qïEÓΟ¨¹Ð&Ó! š^eUm%AÙ +FbS=H$° þÖßµ›^–Åe~ºþøæö|Žƒû•*U;ÿæÛùÙä»|ƒS§øyökÔ4—MpÏô½Œ¦ƒœCV1õ^×ßØ?ZÕvÆÜç„Æò¹²f¹½û¸ÌìA/laLD
eï'HËó´Ä= -ñ¥î°ÞÄ\ûìl:
eÚ$USêç*Åá²(‘°‘a¹êUR#ÕTªB„'®´H^«¸cýˆ£Ûªýru=Ô½ýjÁ· +),‰€:p¸“ +\ë¢É"%¥BBi„£³Eª;$NÝ·z¦°À›¢åN¾MdÛÖÁº¹m•vG á&©A:Á»r·’ï(>8#dŽ/9ñ©ÿ\lZn¯Ç>Ž”=H-=Ë…wìÈÝKËóŒ´!P×¢ìõô<Õ /=âgŸ +ê¡VyZÀ´Hö‹…XBÞ BÏà‘|_fµ.ùfpócý)lñ(–u˪ról21ôٮ³†—er×F™Ùg!?_‘ŽzŸl»Uá¥*-w‚Ÿ*³]—Ö@ÕHl±‡Uå²Y¨VÃÓ›žÆ<sà[*¸Óvj¥6ÇúG—RÊà—z•ï±^¥eÁ҆ť«k¨úl½Æ(䬺š+Kø‹é +—ª•zI·ÍÃÕéñ¢»«j^ò1'‚‰'ÿ[³c>‡wyÜha>ßÔ>âÇ’þ`tvýendstream +endobj +804 0 obj << +/Type /Page +/Contents 805 0 R +/Resources 803 0 R +/MediaBox [0 0 612 792] +/Parent 796 0 R +/Annots [ 807 0 R 809 0 R 810 0 R ] +>> endobj +807 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [173.244 571.653 195.162 582.557] +/Subtype /Link +/A << /S /GoTo /D (example.3.5.7) >> +>> endobj +809 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [147.556 319.936 169.474 330.84] +/Subtype /Link +/A << /S /GoTo /D (example.3.5.7) >> +>> endobj +810 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [130.595 292.106 137.568 300.952] +/Subtype /Link +/A << /S /GoTo /D (chapter.6) >> +>> endobj +806 0 obj << +/D [804 0 R /XYZ 99.213 706.052 null] +>> endobj +126 0 obj << +/D [804 0 R /XYZ 99.213 646.297 null] +>> endobj +808 0 obj << +/D [804 0 R /XYZ 99.213 572.649 null] +>> endobj +130 0 obj << +/D [804 0 R /XYZ 99.213 277.041 null] +>> endobj +803 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F103 753 0 R /F97 676 0 R /F105 774 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +813 0 obj << +/Length 494 +/Filter /FlateDecode +>> +stream +xÚ…TïOœ@ýÎ_±á$eÜ_,»I59ëyj«V%écz¬Í%p×Fýï»ìrp'hC²òÞ¼y;3@6AJ%%X +•§±‰’î½ðî¼›^Ëa¹¬©=ˆ)ŽÕxd¼s5B€‰D"»=˜-—›§uóóüäS{#Šó=ôtUÕÍUVêHß²ÿsæe¶*,/Êtr—õXmJÓLŒ·üzBêóA½lŽ¶U ¥t˜ƒœÆ*?ôº™…SIÐÔ×U®«ã×Ö§o&lè—F¯óz?»©ý 3•®ŸŠæ2ûsè¯Ö¹~Ñy”õÅ"‡úGïÚ´fœÙ_¯{:îše¾EÞoÃÄÞIœðÿeüË0[${‘¶åomWn\éô›"Üendstream +endobj +812 0 obj << +/Type /Page +/Contents 813 0 R +/Resources 811 0 R +/MediaBox [0 0 612 792] +/Parent 796 0 R +>> endobj +814 0 obj << +/D [812 0 R /XYZ 99.213 706.052 null] +>> endobj +811 0 obj << +/Font << /F72 448 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +817 0 obj << +/Length 1631 +/Filter /FlateDecode +>> +stream +xÚ¥WKÜ6¾Ï¯ðÑĪ%ùÙžš6-4EÓ] (Ò4¶6cįø‘Íö×—iç‘ h1ÑE‘ù‘#½~ÒËs¡¤öÒ0a¬¼¢Ù…Þ{Øùy'Y"R±yôÍ V‘ˆÂÜ6žßï¾ù)•ž +E’hïþa¹%Î2!ÓÔ»/ßú?M?Ùa¨8ô£ý»ûWt&i–â™Ð’D(P‰â¿íeè›Á4v=ôÚô#Q¦-‰xÙÖUk‰>;1òÊËEN6¡~-ÒD:ý¯»q‚syäß½ù‰Ø'3ÙƶÓHf°´1öa/ý¹¦Ïƒ-°Hè‘e +Ó£7ãH¬éhâ}Ú«Ø7õlyÇðÕÃÜNUcÅ>ˆ"0îèÛµ¬òÑ9t2‡¸¥™ÌÁÀõà ú$#‘G‘ói°E7@dt’úÕt$ +Ì@"ó_þHŒH="O)ŒNØÒÚZ[’ìÔ§àËÌo]h:R¤,óÛ¹9X0| Ϫ]NZF[Ûb¢Í5À >C®øß“T]¬·{àµe«»á†§MGo“B¸Á@ytÇ2§½qD‡TžpÈÓ%ä0ãD¶5Å‘ûÚöØÕ¥K;تFÚ¬ÛâUKÜiZ|?HÀûc5n€óUÓƒu5ÛsثПÙÚnº´íXaÆ|ÚËØGS1½Çª-8ÓK{Ú»žrìíR†–º›ˆÀ°âŠ¾Ut‰ö=kj]Íå…Oˆkc>` +òq˜Ú\?<ácW=«páqzÛB —»÷+ˆH™†¤žŽ¥P*Ùẩ<CHIèä>îÞ¾½ä^íB¡óÌ{:Àʽf§ÓLdQÌßõîn÷fÕQÎB¡ôµ¸3!h!Š„Ïuh¡³ÌÓ©HTt%`gš&^°qmAÇ€(A ’Z‹\'Z›Ðâ«ÿÚMöÛ³VQ@ÇDkÐí êÛ–.Ô:ÔPYÅTuíˆ_Ê?•ý+õmNÝ\]1ÃG×ÒZ1Z¢|¿"%–‚LüÇc…%€rfžºÆLUaêšõ5¦çs$Ü>€‚Ÿ*ëPÖØÊ@j‘@äÐVJ!å·pIIäéFA7¾È„z¢íêù^Ç€RJÿþå1of:ê쵘T1×ô`2ëîJ
ø@àÍ“lä®~8g!h.µyò + +F¹ü:³/×üG)Šø„ÿ},èæ"UÓ°ô"ì ú3¨kMÍœšær+©2óôT™€Ç€J1"<I\Õ/ E’/BðÆ)DþÅgÓ`£ºÄE•ˆTežLr‘1,"xÏ¿oæªE88Imî¾PÈ +endobj +816 0 obj << +/Type /Page +/Contents 817 0 R +/Resources 815 0 R +/MediaBox [0 0 612 792] +/Parent 796 0 R +/Annots [ 819 0 R ] +>> endobj +819 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [425.732 166.707 447.65 177.611] +/Subtype /Link +/A << /S /GoTo /D (example.4.1.1) >> +>> endobj +818 0 obj << +/D [816 0 R /XYZ 99.213 706.052 null] +>> endobj +134 0 obj << +/D [816 0 R /XYZ 99.213 688.052 null] +>> endobj +138 0 obj << +/D [816 0 R /XYZ 99.213 293.478 null] +>> endobj +820 0 obj << +/D [816 0 R /XYZ 99.213 152.759 null] +>> endobj +815 0 obj << +/Font << /F71 445 0 R /F72 448 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +823 0 obj << +/Length 1763 +/Filter /FlateDecode +>> +stream +xÚX[oÛ6~÷¯ü$ËD‘[“¡k²ÕE›zŽ7`HƒB±•F˜"¹’¼4ûõ;¼H–uqÚ"€E‘¿sáá9G!†?âH‰(aNˆ9ÂuÖì|†•ß'Ä"<ñZ˜_W““ßBêH$9sVw
h$âL:«Íµûzv³z«Q!’¡…ˆø8ÕꛙǩûJÿ.à—¸+=¾Ðãe³·‘À8òY½ÛŸyv‘ÂbØÞD@c.,ÖÈXj^3~ßH¢Z*±ã©&b™Þ÷IÄÿ@Ž±åjæ±Ð·S—ú÷|À$ >«Éç=r«]5ï4ù¼ÅzÑg¥‰ fíû&@”’pú½Rl“‹UMôˆƒ# 2¾L®o°³(z;ÁˆIá<Â#"¥ó0ñi +Æð‘`õeðä¡@Xîc•ìd•¶å‡‹¯ÑÃ6g…¨òA¤k( TH…Ξ¶'¹†tEƒ¹hÂA=_´e¿ÊŒØxF±ûµŠ‹,JÍÌb&°3¸ÑC+fú}´íƒ1šû¾ßÖnÜ?{¬ÖR}YˆÒòå6*ŒJ¼Ç1v“ÍéÔÌΈkæ›8«’»$.¦JE‡$ƒ +{. ÜFk ‰ÑM[Ì|3½9Ó<ø€b¯Ë˜m‘oã¢z2Š‚šö]Éš0^¯ót÷NÍSÃn†p›ÛÕÓ6>n¢*ºÊX½
#+«¿~·‡f»4ý+Jw€o†Ëx›Fà’uü +âé®îcãévbÑrúˆ¤”Þ©„> +D]þâTß#2ÏÒ'#³ˆ¿ì’".ͼUôݘ``„¢ +¥pPÍ•SMK“%`¼+õýÑÓ{Ð@MS¡ð të5y¾!¹óo©ª†„[Õ$êMøžª(¾[ó_£¯@*óA‹îxpBrü¡ÐïúdøãŠaŽs,B¹ò®¯ðŸ¥³§©%ýåâ7“endstream +endobj +822 0 obj << +/Type /Page +/Contents 823 0 R +/Resources 821 0 R +/MediaBox [0 0 612 792] +/Parent 796 0 R +/Annots [ 825 0 R 826 0 R 828 0 R ] +>> endobj +825 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [147.642 407.471 169.559 418.375] +/Subtype /Link +/A << /S /GoTo /D (example.4.1.1) >> +>> endobj +826 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [174.709 392.527 196.626 403.431] +/Subtype /Link +/A << /S /GoTo /D (example.4.1.2) >> +>> endobj +828 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [234.329 156.885 254.254 166.898] +/Subtype /Link +/A << /S /GoTo /D (example.4.1.2) >> +>> endobj +824 0 obj << +/D [822 0 R /XYZ 99.213 706.052 null] +>> endobj +827 0 obj << +/D [822 0 R /XYZ 99.213 393.523 null] +>> endobj +821 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F97 676 0 R /F85 483 0 R /F105 774 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +831 0 obj << +/Length 1518 +/Filter /FlateDecode +>> +stream +xÚX[Ó8~ï¯èÛ¦ÒÖÄ×Ä+„Äj‡VÓ7à!M<Ó¬Ò¤äÂ0ÿ~íã4¦PÐH_ŽÏù|î.]ÆðG—ZFù2‰‰%[æûE¼¼‡¿)ÖH²žÐü¹Y<{•°¥&ZñåæβŠM×ËMñ1«µŒ#âþ3Þ¼yö*•ÇsŒ¦È±'žû щÝVD&wÿ])0yé?NfïüçÆ6'³çR%',¦È÷…ߟÞ&% MpûfÜž€Jä·«µ¢V’bü÷ãÜúÆo-‡ÅÍfÔè¨sI”VŸ_?ÇËtÿf®ÓåŒcBµ^î‚I%8¯·‹÷#/¿pÜ©9óI&ˆˆõŒýè’R¢¥DZL*IIŒö#”Pyó )—$UÁ*ÏY›íMoÚwÙ!¨rÊÎA’g}ß–Û‹£¡7ݹæ×”$Y®9p`ÊÚì̹
)˜#eßC15¨"4 5•Ù›º_xG–çæÐw~Ò?¬h5¸ƒP©ƒúÇP½¦ÁÊâ\,Í‹@ð)–qk¾ek +S”R3þ©½½=Üo½©‹] A8îÍ¡/›:«,wr´ÜQ¹\¤ÎÖpTsvŠ›>Á-çL6g1Oì
&Gƒ¹Û…ûΆ R鵺;ÅàXÚfEeôµ,Z/óŸ¡.¿HŒi¶.?Å13_¼kpÐÏûX5NűÊ~WÖw‰°þÊzD,æÍÂ}d{³ð«Ó'VçWÚ‰Ïl#8‚·ÌØF‘Dˆº\]ÌÛDzϳڶnEéýBßàw‡;5h1ÝùoV7°ÛÎX'…œ©‚Ø©qÎ&î˜FÒáÐ ž‡]™ï¼à¼£±ACJ@Iœ;k¸¼=³Í:ÀÅcæy†§„2~J*ÔD¡’èeUyîàÊÓö¥ud;·Zà1÷ +²Ýp˜UU$eê: +é’"`‹ª˜jï'ò;ÄGÊ].\Cò¾®˜Œ²jp‘¨è®mö~§ßÙ€öã‹ZÖZ_«1Öò¬
ŒŒÞ·Ò…ñ0À£!ã<Îcì&÷i/±}ôTÆ$äb¨¿¨2ð!µjÌ´æÎâ¹39Öµ}ºr_VYHx¶´
+„{¸¸.ëû“ä™WYבc‡üIØâEékHÜS‰ lÒŒ¤zºDŸ%Ð3\u +¤áJ7¾.Ïw1‘¶ÆH")ûNSÁåõ¹[ú<6ZF»¦*¬þ´ŠšÚø5W4`aïÝVFö~šïʪð4&ÜÄmô»ùîÁ¬žëö?o)Ëæý.Qhcuê=ö$‡*ËEgZ´o¨7h¿Û÷oÑî6·e½ÃñÔW:
©?4=ž+ŒUÕX7zðs¨¬9¤õàa™M›vd“A¨Ÿ(²ù´±#3`å(qY}çŠîuó8ÃNAÇ›ˆ+ë V„Á³äš¦2Õ„Oøɘ0&/ô&•±3Ö¶\Rv1*&Ü”z +Ç‹¥øbÉ„ÏÉv%óŸn€0=ÇÐ[¡œÃÚóÎ sÄIôº÷ôûdU‡¥‘¦ŒðTŸ–F[7(ÃE,»äJµ5ÜÏÁÛG¿Tâ’ß’¶qJºŠ¡°Ù!À
Û»´Å…¡sU +V|$:p] lê—s€·²rïÃÞSØÃ~kõÀí}c¼¨÷Ӿܻ²ÌÒ¨ìñ‹óÚ_1íšeÌ42†…®?ŸÂõç¯k¿—!ñp(\ä¨ÐšGÍà öåý…¹®Ég~ž7Õ°G>>r§œÑÜK7Œœ=¦Ð ³2'Wõ6…ÜÊ%îmrMø²†o½¿K5sñ›)’kßøˆü¹WáSñì¤Åe©¾üºða + +?œKú›endstream +endobj +830 0 obj << +/Type /Page +/Contents 831 0 R +/Resources 829 0 R +/MediaBox [0 0 612 792] +/Parent 835 0 R +>> endobj +832 0 obj << +/D [830 0 R /XYZ 99.213 706.052 null] +>> endobj +142 0 obj << +/D [830 0 R /XYZ 99.213 688.052 null] +>> endobj +833 0 obj << +/D [830 0 R /XYZ 99.213 593.018 null] +>> endobj +834 0 obj << +/D [830 0 R /XYZ 99.213 516.16 null] +>> endobj +146 0 obj << +/D [830 0 R /XYZ 99.213 423.317 null] +>> endobj +150 0 obj << +/D [830 0 R /XYZ 99.213 320.457 null] +>> endobj +154 0 obj << +/D [830 0 R /XYZ 99.213 196.65 null] +>> endobj +829 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F103 753 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +838 0 obj << +/Length 2247 +/Filter /FlateDecode +>> +stream +xÚÍYKsãƾëWð²ÊœÌß6ÙM"×jc[´}°} +D#5Ó<ˆD1‚¥ÆxŸL6qŒS$j&™"C®:[•aÎF/ªÍa[{u j`&“°+kš}1ŸH>>4ù¹ñ§ò •£©âÌjb?[‡Î7 +ð‡“C¬»â ¾ì ç©„(jzê|I£¦¢ç2ÿsYæýÙf"Æë0WfÛ0ªVôÌè±Ëö°ÔäûSÚó—Þ¾º©öqi·¯ùò°ÏÙ…±J1 .p«
ڦ²D{^B]Q‡–(4ƒ5(lŠ{¼í'žo‚ªš‰o!5}ÑfÜjzæg˜i°ü²Ø狦¨<d,“.½ì¡Ï l6‡¼>5ŒÚ—–ŽFgj«X +åt*˜Ñéëé%¤„,wƒé5¥É)°MõåTõ›8ТQØ}B +4cB•`9gܹn"ݶÇsÍLBÎøy’JoŒS[€CXæïûrá"±«Â)°zjeO¼÷y½Ø»è螀‰b‚‹·½”$)z$Tº¯Ì«Æi–h;œdÞ«ÆAZK*o÷åîÐ\òh‡Ôxôš`Ñ£]Ñ +mZ•›—K}ùzýÓÔØ.NuÍ¡`笷ڿÍ5i½…G¯HÖz´#[åe»îÒ·ðëÏQ£·âšKƒ[Csô¿ ¾…_¯ˆ×úµ#à¼h/Ùlsѳo!â›&«0$C¦×'c³ö:\50“Ø®êWáêr>{Ùå\u +Ï +ZŸ'v¸”®@ÌŽÊÅ‹ òŒ‚_aïiÁ +_M¤¦èöBAÄ䧯¢IˆÐ(¶Oaà/sp¥§taÞ´ávL UœÍB>d°OŒã›¶äŸ~JH){K¬–q(}_eSd“›!òµÀ÷¨£vª,–žä*VRJêr-X2¯‚¥f*)Á¬7B%› +ŸP[xIjÆó0lqÀÎŽ
¤ Ñ‚quüRo_Íf,„?Çïˆ'ô|–ÞÚý +tP®"îC4V«@ºMmÿO˜Ê ãAžËa%\\¨5T†ÓP +endobj +837 0 obj << +/Type /Page +/Contents 838 0 R +/Resources 836 0 R +/MediaBox [0 0 612 792] +/Parent 835 0 R +>> endobj +839 0 obj << +/D [837 0 R /XYZ 99.213 706.052 null] +>> endobj +158 0 obj << +/D [837 0 R /XYZ 99.213 688.052 null] +>> endobj +840 0 obj << +/D [837 0 R /XYZ 252.051 631.525 null] +>> endobj +162 0 obj << +/D [837 0 R /XYZ 99.213 536.004 null] +>> endobj +166 0 obj << +/D [837 0 R /XYZ 99.213 350.308 null] +>> endobj +170 0 obj << +/D [837 0 R /XYZ 99.213 241.889 null] +>> endobj +836 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F103 753 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +843 0 obj << +/Length 1783 +/Filter /FlateDecode +>> +stream +xÚXÝsÛ6÷_¡·Éw5Ç/QÒÞöÕ½v×eÙívÝh›‰µÓ×$9iö× (Y±•&[{$ +AÁÕcgÁ1ò<£ì± Ã0Ò*2m^# 0‚¼}ŠÛÿ
€
bLCÌn¦:ñÿ1 h‘Pî8G‹,‡*x*)‰É}•ó +ºQw¸ªzy¹¥gâ,íhÛ!ãÃ{!¤µ†Ê ½ö›,ä°kÑd+7¸njœûK ãcHOO7>Ê€ dÝ +j+4ê@fõ=Òð4T!OKb&¡²¶ý«3ÍäMØ{á±At€cgÉȡ@5Ø®¢ þ`—6šÆGñ)<’Ž¼žžŸ†•mð,îêÁ~¢É’ŠPtZ Ô¢=q£ØYQ
bñŽÂ<Å·yÌ‘Åðz‹}Ëd{–åŸ~2lžôge;±\üX£¸aÙ$
%ósEã¯>—šþ[ûôIendstream +endobj +842 0 obj << +/Type /Page +/Contents 843 0 R +/Resources 841 0 R +/MediaBox [0 0 612 792] +/Parent 835 0 R +/Annots [ 851 0 R ] +>> endobj +851 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [301.523 613.111 307.999 623.124] +/Subtype /Link +/A << /S /GoTo /D (chapter.5) >> +>> endobj +844 0 obj << +/D [842 0 R /XYZ 99.213 706.052 null] +>> endobj +174 0 obj << +/D [842 0 R /XYZ 99.213 587.854 null] +>> endobj +178 0 obj << +/D [842 0 R /XYZ 99.213 521.142 null] +>> endobj +182 0 obj << +/D [842 0 R /XYZ 99.213 431.495 null] +>> endobj +186 0 obj << +/D [842 0 R /XYZ 99.213 354.679 null] +>> endobj +190 0 obj << +/D [842 0 R /XYZ 99.213 219.024 null] +>> endobj +841 0 obj << +/Font << /F72 448 0 R /F14 847 0 R /F8 850 0 R /F71 445 0 R /F103 753 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +854 0 obj << +/Length 1246 +/Filter /FlateDecode +>> +stream +xÚÝXkoÛ6ýî_!$_d fIIÔ¨‡e±»¹HS7qó¥+Õb²äIr²õ×÷ò%ÓÖcA±CD¦¨Ã{HÞs®©Ã/±¢9ĵì#Lk½aëžü>" +1Q‰ùm5zõ&p¬E¾kî-' ($&B¾Y«ä“}1þ¼z+PŠ +ñ\ˆÉŸþ1žøŽ}.®K¸{%ÚsѾnÆ6®<WöÆŠmı†˜±*¬ä¸qeû]ÃäV¢Ú¬C$¢"½kEÈ¥Þ\ËÍxâžêº×YÇ’"Ø3|Ñ +îÂSªžÊ—"øˆ:oGuBRµ½79ùŽá×m4_5ÚhÔC‘Êøkôé3¶PÑÛFnZÏÐƈD‘µy…V î³ÑÍèCK>ó-9ªKˆÔñ‡£a%ª)ùAˆ°bœ'ã‰ãvÌ?B;ße™ìx;ÔŽ³“·%ÛfñšmX^ËŽ4¯‹ƒ¡Û¸Œ7¬.Ó¯Lżùp)U×b(¨Òž½zTQs6&Ôþ[…¼/v¥lÉîx³ÍX%£WŸÐsu±âÛ[0!Š<O,ê¹Lëšå€#<§õ£l¥y–æL¶ÅdÇÄf5++ÔJ‡—X¾!/Œº/!#·›´·[AøÔærQrr9æ&”4$ט;ȧa'>LÑèÏ¥ä ¦{\ˆh ëÁëf_‘H3 B<íè]•æûÊSÇ…À …íªgoi <(ª½{l`Å*CÚÚc
9\Ãü’éIšW¬¬—e9OvëúD>RšeåEWÕô +ðÖòúýìãÅJÞü‰)^^Ïî€i1;Ó€ÙÝl~sq½X®ï¯ +pš°j]¦Û:å+ÈO›“ŽI¾~uœF³ +B°^˜¾z¡!õÂí«CäM½8fïª&ýÿ¶^ù5r;æç¼ý¡²'K#„/*(¶¯¢hÈO_RTEá;ËáSxSå?ç‰ÿ¢Ôtdþ>;ï!}îRˆNsy}æ`n¼uDÝe-ƒû\‰¼¢U¥¨àó©™®œÖ•¼ÑWuàÕ…á˾
lŸž5Däd·Mb>-æâþ|½.v9¨¬¾Mã…pñR˹êÑ·£õMáí88H¿É¤°J«;«0woÒ²ª¯€J‚§J‹²›Wö
ö¿Œ»†«^þ678z¾‰Óìp¨è:O’’ñ#muÆO6Ó +Þa󇳽Öà‡³½•òâŽñq¿naPY<¥ K¨á´ç? +endobj +853 0 obj << +/Type /Page +/Contents 854 0 R +/Resources 852 0 R +/MediaBox [0 0 612 792] +/Parent 835 0 R +>> endobj +855 0 obj << +/D [853 0 R /XYZ 99.213 706.052 null] +>> endobj +856 0 obj << +/D [853 0 R /XYZ 99.213 660.989 null] +>> endobj +857 0 obj << +/D [853 0 R /XYZ 99.213 517.137 null] +>> endobj +858 0 obj << +/D [853 0 R /XYZ 99.213 373.285 null] +>> endobj +859 0 obj << +/D [853 0 R /XYZ 99.213 254.713 null] +>> endobj +852 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F97 676 0 R /F105 774 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +862 0 obj << +/Length 1933 +/Filter /FlateDecode +>> +stream +xÚ¥XmoãÆþ®_A\¾PEµÙwr‹\€kîÒ^€K[ùPä‚‚i‹)Eª$uŽÿ}gwvIJ¤å …sÉ}8³óÌ+Å" +,2†p&¢„jBv‡aço+æÙL0Ý®¾þ>á‘!F‹hû`ÅHÍAŒ!Z˜h›ÿËõFј¸ÿÃZ3ßÝþà^OˆIìÛ<% +S÷Þv-Xü <þÑý¿ÞððäÖ? †HAR¡—4Q©ö»ÿt2nìaâsIÌÊR½™IR„sæwÇ3…ó}rÿ?¸ÿ[¯a<·³|õa;p;°¯ˆVÒ2ûŸÕ/¿Ò(/ü°¢D˜4z‚5%̘谒\Á*ñ÷ÕênõÓ ÷ÀH÷Ö’#—DR3÷dª&æS+&ÒIJ(:òi_´…=9HÙ0IŒ”hýnלêþ_ÁAšÒø-^¾ú˜åД%g¯|óõé˜g}ñí%Œ%p´$RiJ„<‹ÃK3@ZÊ"ž¸Ë«Œ °#•j‘10GÑ«bÜKÄàʣϥ"Ò4âb‘rNI
ŠFÃ0yX”åÚQ.1BGš2"ÓÄ1õcÓ‚o€rA”N|ø}¬«². ;¸ˆY›Š¾hñö;»âqÖzÄ>«óg\>4Ö²ªÂåoÍ}÷g»Lãû5§ñ©ÇçàýW½‹·ôBA:ÞWG7û|ôˆ|½aqÑíÚòØ7m8Tcˆx{6L
”[«ê“;Õñ—5WqV +¼m‹c•íŠCQ÷VUqYãN†—]s8VÅš©øw|Ðõko
cÔâó²ÎO]ß–YµKQ?ö{‡P1¾
4ÖY…ìCnŒì§œ¨$ªòOÙqî)! tš½B ‘Þ`̯{k”Ç"ëJðŸRP(/ÒCš¢Xÿ‘ôà¢ÆûõÆæ”à™¡œüïyYÀ]’l”ÆZ0ɨBjßCl1T¶Ž1l!£PTîz™¬Í‘©í:‘1FÜݬ¡©_tó‚i.¡)óIo¥*>¶Ù®/w…u¼iÍ Ÿ?•.üR¦”×9>í÷Y«¸bäï†ë<.û·=Mw +êÊú±òÆ<•4öº›‡¾ð§Â89¯È’$‘¡#•u_<Âëó(‰zœKpw‚™8Ž¥g³ ~œÂR@cW‚¡sœôÛÌVÐ%™åÈZ!yüÔBF¸g^`sšÕ°Uúw²ºE5÷¿;—µRx×ÀÓ]êŠ Òí0>ÖÌÖû *ïÛ¬}žŠÃÏTQo5sÒqjú8ºôýBÓÛYße „aðe^ñ$°˜biâ¼lAuõ¼Ö67‚ãÃS9ε!©‚\×C’aŽÛöÑ#n'% 7øl*»ésŠ°™îÔ¸6u¦û¢•Ìu†}ª±Û7ÖåOž©/CæàíÉ†È +ÏfPí“™3…ã(º`!30s}Õ æEæ5¯é,¼T¸dáTáCÕdýbbŠÄŒƒû|b‡z—@ÞœîçÃc ¾:‘×i1/Óä1¯ÒtEßHÓ…ÂEš& +¯}á!ÿ(Q æ%–$…Œ]giļ̒ǼÊÒ}#K +Yš(¼ošjN·¿:°W( +Y6Õ–R˜=ÙõP!3ŽÕ$„Xmb6/‡ß#çšþ·^endstream +endobj +861 0 obj << +/Type /Page +/Contents 862 0 R +/Resources 860 0 R +/MediaBox [0 0 612 792] +/Parent 835 0 R +/Annots [ 864 0 R ] +>> endobj +864 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [268.854 456.988 290.772 467.892] +/Subtype /Link +/A << /S /GoTo /D (example.4.4.1) >> +>> endobj +863 0 obj << +/D [861 0 R /XYZ 99.213 706.052 null] +>> endobj +194 0 obj << +/D [861 0 R /XYZ 99.213 544.957 null] +>> endobj +865 0 obj << +/D [861 0 R /XYZ 99.213 457.984 null] +>> endobj +860 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F97 676 0 R /F105 774 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +868 0 obj << +/Length 1278 +/Filter /FlateDecode +>> +stream +xÚWmoÜ6þ~¿ÂH¿ØCZo–
´²$ÛR4kšÜ>EQ8g_âÖgßlß²üûQ¢ìó[²aà“¨G¤D>$êðG8&ŒrG! $s6»UàÜÃÊ/+j¾…øÌOëÕ›Ÿsb‡ÜYo¦(‰(¨‰IÈcg~vϼ/ë÷¥H¬4H*8èÔ«¿z~ÈÜSó½†/u×f|aÆ7ýÞÞ‰àÝnáù2p‰Æ°}jˆÂ‰ÃÈbÑÆÑ‹ã«Þ3V©/X•h¢VÓÕÌPL¸#;x—[ÏçJXÑoæ{¾p¥|Ö)¿œ)ç°*í*êø`”_´^̵²ˆD²Ó:÷$ŒÑÿáý½ÕÚVëž={$ ÁÀŒ?WŸ¿N +,z¿ +#çÆ¡qììV‚I);/V·«O½.\ܵDDÉA¼ÀDêPA¸MLðL¡ŠH€LDz>
‚À=ëäÉógíA€žöή= RR'»¬ÍêfîÔÀñ¹ +‹ùÈbóPi7=6H‹™ÉAÒ +„Ì&ÐÛ¦MÚl—•ís΄a¢«‡&/ïÒÒþɸêg
*ue 'ÉY¡63qF¡“Y¶ƒ9ud,!{èrGˆ?ÀtÖ± ÖA´)MMs‡å¨I]“Õ‹Æ;ÌÌ:TŒcÔB8¢ˆFæOÑ4¸-"REÏ…i¨ð?„I'Ís¡e$|6TK¡9¡jäçCrħ9Ò…ÄBÆw†Ò
¥/OßÜgíu]¥‡GÝöåãs¾;1¬<1~‚œêÉQ1h²ê’YÐò¡Ÿû”(»øÃp×Q¼«»¾ùxþûÙÚÀ|ɉ‚J?ÔþøÕY=ÿzvºþzyŽówøój“´—)œÿÕB¥JÊt°ùãùÅdk•f¸Í_¸ÙÛ7/%³n +endobj +867 0 obj << +/Type /Page +/Contents 868 0 R +/Resources 866 0 R +/MediaBox [0 0 612 792] +/Parent 835 0 R +/Annots [ 870 0 R 872 0 R ] +>> endobj +870 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [112.881 625.771 134.799 636.675] +/Subtype /Link +/A << /S /GoTo /D (example.4.5.1) >> +>> endobj +872 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [146.406 466.369 168.324 477.273] +/Subtype /Link +/A << /S /GoTo /D (example.4.5.1) >> +>> endobj +869 0 obj << +/D [867 0 R /XYZ 99.213 706.052 null] +>> endobj +198 0 obj << +/D [867 0 R /XYZ 99.213 688.052 null] +>> endobj +871 0 obj << +/D [867 0 R /XYZ 99.213 626.767 null] +>> endobj +866 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F85 483 0 R /F97 676 0 R /F105 774 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +875 0 obj << +/Length 1149 +/Filter /FlateDecode +>> +stream +xÚ…VYoÛF~÷¯ ôDÕfO@ 5’ÂEc®Þ\?PäÚbÀ+<ûßwvgW"%Ù4œùæúfwHPø± M g"ˆiD¨âA^_Ñà,\1‡\–F _0n—DÒ4ØÌ"ü¾½úð5f§$ŠD°}òYT’ÇÁ¶xoöY7ê~½áŠ†jý¸ý}$‰“ØøÐ`E„CH¿×ÃTˆþ–uƒsàAJRÌað‚Ä[„ØÕ—í¡^&a* ¤‚J"‰-Cÿì÷³æ<z3ƒcs³¼§!MvyžŒr™ö„Iy?—¤DI¹ÈUè!ïˀɿ×L…YŸÕÙ•åËJYS‡·MU6•G¿…îË|¶:ëPhwßu>:‡¾ít?–˜’‡c{ŸËÆe5l@cŒa•$•®ölÌvÙ` +‰døcÒýë:R!Yo„Ç™ƒÍõ +§j8ïÏ\Eç +RYwm?f{|ÒÙ8õÎPؾàj +8 +¸Ä8ƒ:y¿›Ücβ§ñìÝA‰2Y¤ßî]jOñZÄ¡SYšáßÑÌèŒçX@(õÏó¬QH?‘_ðËcI"*æ-¾Éók;]œäÙC–5®7…m]\¯Ž*ÞPWùTê~e¯u¤H¬–‡ã!¯²a¸^Ù?ÒÀ›~õxa<è—Q7 +rˆÍr¾€à{=91…qšÈÜŸgúRxendstream +endobj +874 0 obj << +/Type /Page +/Contents 875 0 R +/Resources 873 0 R +/MediaBox [0 0 612 792] +/Parent 880 0 R +/Annots [ 877 0 R 878 0 R ] +>> endobj +877 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [132.162 455.011 139.136 465.915] +/Subtype /Link +/A << /S /GoTo /D (chapter.4) >> +>> endobj +878 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [339.953 356.381 361.87 367.285] +/Subtype /Link +/A << /S /GoTo /D (example.5.0.2) >> +>> endobj +876 0 obj << +/D [874 0 R /XYZ 99.213 706.052 null] +>> endobj +202 0 obj << +/D [874 0 R /XYZ 99.213 688.052 null] +>> endobj +879 0 obj << +/D [874 0 R /XYZ 99.213 344.491 null] +>> endobj +873 0 obj << +/Font << /F71 445 0 R /F72 448 0 R /F85 483 0 R /F97 676 0 R /F105 774 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +883 0 obj << +/Length 2064 +/Filter /FlateDecode +>> +stream +xÚµYÛŽÛF}×WóD£v_y1ì +Iyâ`?~Oß(R¢äƒ…Q³X]U]×Ó4›Sücó,#œ‰yBcBŸ¯w3:ÿ„7?̘çXz–å€çw³'ÿLø<#Y,æwÛ¹¤ŠH‘@NFb‘Íï6ï£ïï~²l ÉÕ&„š·¯Ë˜G/ìß7øË¢;»~i×oû½½ +CCØKE#bxé€9(⌰Œy^'ñ•þ«ýûóBP¨;SÁ3Bî×gB3"”òo‡v¿3œ³—w½ÇzŸ*+iüõÇìýG:ßÀ·?Í(Y:ÀšÂÈl¾›I®°Jüs9{7ûw/˽‹çn×Tx—DÒì<>©:žŒÅËx')¡.<ï«CYþ–—ýü¦_¾Õû2_°hwºên>š£AÍ’a»”n_«K½îžß´õNÿ«»×Í».ï?öu¿ä;=½ÌÿúòsožßtÍAÿw›—íÎîË^¿Ê«M©›ç7ë2o[RÅ.eBRÎF[ž|7!çY£ÛCÙ!F©BH¿ø&î%C>ÞóÄmzï¿;Ï&È’[Þ«ÅRpä÷Ÿùn_êÓœaé±Jd„!P6ÒæEóÉs¼Ä<p/ìg5y*ÒÖ
¡„ŸéŽ‘°q6Ö}’eçºÆAFã-Ž-i„<qç¿jòõ([ݵ¨6Å:ï?ž©ÞwE]å¥{Ê»®)VN£C§[DFñ8º»×Îå£4ÏRÂ8÷EZl΃ÂPNqè"#ÁÞ”Öý6úCÑè· ²í›zÁTô¹ØhϘ»ìà©€³ÇYi’v±ä±Š¶uc±sƒ¡´¾f:O¯¹Ñ[ÝèjqJ¡’§ŒIàfw[ í’%äôA&sP£eØëvGFÄxBÝ™©{½.>PÊu{rˆ-Ú†#Zo½\_5ˆˆ bä—7¯Ð8¹ÎtsRªpRk‘ð–áS‚;¡§å#>V7h+ÏVTpjÕ¦u&r†èNÅÔz(óþÕ*oÝRFuåH^¥ŒB30Ä]¾ßÕ'oAá©ëºêr¨$¡—pA0rÌù¦ƒfÆHêãѷ󸩘ð4±Ía˜tk¤]dºgБ†_ÜCuØÁ,jÜ£ +T°ˆ<„ý¥îôÓ¾F{N0Äiè¿/¼›ŠîÞû9oлaÛ¾t{{’}M·ÜÚ.ÄÛÇÏý¼bfržlèëÖæ•ßÒ4¹o®+šro™GÉø1ñæˆ7FÿÑÁß©¥I)“ùÒlû›Éƒ°rù¥dXd|VÔ©6ZŽ‹ùšzÄàK +|÷òOÛƬ
2äl4ªåiËž˜µKÏiñîa,y–±ÌÙHP’Ð8´zm-™.%Iû[Éé ‡|7#°XyB«;·°];ó?¥Q[÷›WµAðSã +9ÎÔ£æU¢Â!ûÚóp_¬ï‡f$vÞzŠ™õg*§)‘Žzì5Í‚$Y˜¨vœÈè…t<ÁsƯŸð,úÀ¹hàÁBNœŸS"û9|ýü´·"€æA CùÑŸËÃÆA
åÞ 4‰îÔ´)¯0Üeñÿš;BSº=¢wgÂ]4KÇ·—ÏfT˜KŸ±(‘Ѷ©wf¦#ÆI0ÿŠŸà~dšàhãå»TM†DöÄ#¶Á‹±‘C0ºñ;¿›'½‡I’>Ö{ÃdJ¥/aèÐ~Ö¢ÍZoš¦Äù8QߎÛbW”y3ÀMfÈ›õfØÕmvX|avÙ{çWÇ1Çx¢ÉßÇŠ“Ô©{L“÷ÜSãØàa®®ãÁá.Žc@KBÑ—mS]°4*ö#™A +q¶hïÓEÞo’1‰³Ë˜¼çSŒ0’×
h#Ý5ÜìˆÙbÌÌ€e3°=á(uØç“=óˆÚÇ)rf*È>ðŠó•eÇD–*3ŸpnncèîYv¼?ÚòÊRQÅÂÏŒÌÕ¾£9 ‰E{XMXHÂ~y\ÝÙ3VZ{©En±úÆêÃܺu»·aSveA]=”“–pëâã.¦1\`f¶u…ã[ÕƒùF…;íEìôˆâôØéX
ß\Vÿ'ìÄQŒ1M¯b'–fhFáŸK‚ž„¼{vö]ŒqO&Ͷb‚`ªSl5ñí‡PuQÿ@"F‚`Ὼ¯-ùz÷æºeºûFkOÙøtB·ˆ‰àéõïNªŸ“¨¢ákÖìö\"ZC¨¯âH‡x 3@M'ÓÎ0 +endobj +882 0 obj << +/Type /Page +/Contents 883 0 R +/Resources 881 0 R +/MediaBox [0 0 612 792] +/Parent 880 0 R +/Annots [ 885 0 R ] +>> endobj +885 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [148.347 536.013 170.265 546.917] +/Subtype /Link +/A << /S /GoTo /D (example.5.0.2) >> +>> endobj +884 0 obj << +/D [882 0 R /XYZ 99.213 706.052 null] +>> endobj +206 0 obj << +/D [882 0 R /XYZ 99.213 396.037 null] +>> endobj +210 0 obj << +/D [882 0 R /XYZ 99.213 209.253 null] +>> endobj +881 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F103 753 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +888 0 obj << +/Length 1487 +/Filter /FlateDecode +>> +stream +xÚ¥X[¯ÓF~ϯð£#ÕËÞ/B¢ê¡¢zÒ'àÁIöWŽ|á”ßÙ‹'ö¨;ÞËÌì|ßÎLI0ü#‰1ˆ–(,4ÙW8ù+¿HÜ‘Å-ÙdÏ/›Õ³WŠ&ɒ̓SÃ%5If’ÍþC*Ö™À)òÿ³(Ú¼~öJ‹ó9J•ì¸ÏÇu…ŒrË %âêŸëŒ€’»0܇á¯0¼ Ãfn€ 0 £Ša}ê¸FŠ¨¸|7.OìëÁ;°!‰3/iú‡ÿ?ÈoýüÆË÷NÃên3o¯@Rpº/«Ÿp²‡0¿^aÄŒNAƈ“Wœ +Tü.W÷«÷£®°îøSKH ÊÇf*’‚Œ+ç“Tᢈ„ËÌ&[!zZ +'àâžRãaP¤¸ûRCLë„`D©ZLyŽ)%Cî‹—RžÈ¤G—1d˜ôp ÝEþmÝÙŸGtÇÒ6Ý—1Ì‘=.™ä
àÙ
ÂSζ?ÓDÌfŽ>“-AœƒŸü»ß¡~¥ïfFv;9šÌ›&\(ª¶Ë«Î\ä-€S +Uz¬›%«Ð3öÄ}§ñþªô»CQîƒv;ÜÜ`œäyå„û÷o‚}P
eÉÏ}¶v<T‡êGÁƒ'6eÄ4¿)·ÔýàØöÜúÕ¨ñÛ‚: ÙHÝÚù`›w>
!üjPËƧ±Ûø©~•Ãm§Õ&|á%÷‡²ÁÍÙÆÅâxnZ…|¤ nØãŸw‘oX†šäu}SA+߮´iO¡OO2××êËG~ßåç4/ÔèØ>ÜrhZ`j—\Ó–<‹ýxX…¹*\˜ÛÛ¤€¢ú>븡MôǯÌnñ!.Gß3”Ï&0UÄïÊÚ}0ÌCÃk§úÔ—y #ÝäfWÕý»4¥#MoèÎë²?VÊ +×SLQcú€¿ìäÆ'&EŠžøq¯â—MÖìO'ç-³¿œ@.€_ƒç׆†ßõsKÿü\ +endobj +887 0 obj << +/Type /Page +/Contents 888 0 R +/Resources 886 0 R +/MediaBox [0 0 612 792] +/Parent 880 0 R +>> endobj +889 0 obj << +/D [887 0 R /XYZ 99.213 706.052 null] +>> endobj +214 0 obj << +/D [887 0 R /XYZ 99.213 688.052 null] +>> endobj +218 0 obj << +/D [887 0 R /XYZ 99.213 628.015 null] +>> endobj +222 0 obj << +/D [887 0 R /XYZ 99.213 496.758 null] +>> endobj +226 0 obj << +/D [887 0 R /XYZ 99.213 413.042 null] +>> endobj +230 0 obj << +/D [887 0 R /XYZ 99.213 314.589 null] +>> endobj +234 0 obj << +/D [887 0 R /XYZ 99.213 212.44 null] +>> endobj +886 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F103 753 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +892 0 obj << +/Length 1829 +/Filter /FlateDecode +>> +stream +xÚkÛ¸ñ»… €D¬H‰”T´.éµÍ!éõn·ý’ËZ–×êéáè‘=÷×w†Cj%K›,k
Éá¼_÷Bøã^–1Á#/ ¥ðòzzpò÷·E f8oîwø["¼Œe*òîO^JG ÐɘŠ2ïþøÑ»ÿtÿƒAKX– VÂxQ<ýÇ>PÂÿÎüþ~¹oàï
üótwb)àànË} CŸ!n8CvŒg<ã—(Þêÿ6¿ï÷QìV,DÆBwëÊhÆ")íé\î;ÄÜ}?Yl²©dJÆh¯Ï»ŸBï¶ýa²(K½G€C2óê],$@‰]W»»ÝO-:SÝÚr1‹ÃlÃ?Üãœe 3 +O2©$e!¹G²ˆE¤$£j$Yªœ¢y[uó®9¿YƒÌ©¦œ¥™ó‰†®<ìEèC±6nÀy™zAÄYsçþl1Sù„ÉS&2ñ¬ +Ç
/X00¨§nr‹p(š5^Ã:‹Ð¡U±¡K0<¡ÈˆIàx¡ç±Ó5ª5€ÙªÊúfN@=–,å” ñ7ôx¸¿^Š
b1T‘¾07m"¿,5Ó-ÖóJ2•ðç²Rø%X¢'B!qÌÊG°r¨®tÍËÓu‘RÂ?êAt¿JU …bÍ2Z<]ºIÚų('¥U²…«É½Q&);ái³3‹);ag–°¢ì`ÊNDYfgÇþwÕpnLJ3!|Зž £åÐ`ÆÙÓ³F£~AsÝrìum¡cyÂÂcÒè–›)ùX†»ò›‘´Ll½nP²zHh½Î
eó@8p‹ïÄšiäº!à`ÀP§±¢=“ˬ M…“}—Ëa Ö^?æV6ÝÓá_…•3&(Å6´zSäzÄèˆÂÔ72ás*]v£ÒÍè,ÚÙ0 mìŽåЬ‚ãÒ*lh ¸ûé=.2-1SŽ¢0{r™2fƒzªEˆøK(ñM‰ ÒÆbU¡{¾Êà7EJ.¼#ÒÄ…¼Bi,dë"º&·õ|^@¡îuw¥kÔæÀðBNš HI +Ò7ýØŽžYDԄʶ¢*iq¯àið-ŠM;ºÛ*¬=姄üì:²|pUªìß•uYéŽöŒþ°7 ¦U_ºBpj‡‚6\¢Ú¦s¸Ò¶^×;¦¸«µÿÑÝÛ³îÖ/†ò9M¶¯×d`8æîx›‚P,‰‹Òv•FÚÈ òöýo6hDlz_“J};µÎYpP©0¥.’œ)¸>·¾-ŸÐ·êòá<x°[nB@ØÄ +<¡€õT +påâÚöe}N~³Ï
Û]–Jù»œJ˜ÄYë›]Þäã¹'ÏLž‘L]CÐ6 +iÇ8™¬zžM=¨’ËÙ4G÷©æ +fŠ_îÙi¤'ºïÇz9´ßJé@BÄÆK+Ý
ǩͯ #–L#ÓV6ã6'Ô7s¢3ÃÂÛ:×”3żŒ¿ðõ,Œ‡ñ‹fÀ(M¿"ÂŒj2ªgFAð‡DfËlP@ §Z: ÁFc†„ÌP—áÀÞbìbwY™DÃÙ”îQLeÎ v±ÇÉÉöSð +L5*[©ƒN‘x"Íæé3«»Ú>]‡ÅŠíæ(\áMèÃâÚŽ +Nاһã+7iˆŒ¥ÁtþY<p.Ðyµ1˜üéwA°yxÃíÞ2ËœýpÇØïT¢õp‰‚² øËñYÞC%{75Yüê™oM)¼*òø«ŸïžPV_ï¢P±t"‚¢Dê–‘ûÌ´æôcBÍendstream +endobj +891 0 obj << +/Type /Page +/Contents 892 0 R +/Resources 890 0 R +/MediaBox [0 0 612 792] +/Parent 880 0 R +>> endobj +893 0 obj << +/D [891 0 R /XYZ 99.213 706.052 null] +>> endobj +238 0 obj << +/D [891 0 R /XYZ 99.213 688.052 null] +>> endobj +242 0 obj << +/D [891 0 R /XYZ 99.213 581.055 null] +>> endobj +246 0 obj << +/D [891 0 R /XYZ 99.213 414.944 null] +>> endobj +250 0 obj << +/D [891 0 R /XYZ 99.213 309.232 null] +>> endobj +890 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F103 753 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +896 0 obj << +/Length 2276 +/Filter /FlateDecode +>> +stream +xÚ¥YmoÛ8þž_aä“ÔŒHŠz6\Ûì!‹´ÛMÜ»»‹…l³±îdÉkIÍæ~ýÍp†²lÉÙ®"¾g†Ã™á3´œ…ð_βL(©gI‹Ð¨Ùj{Ξ`æï’)æL2м]\\ÿ¨Y&²XÏ_M+`“‰Xg³Åú—À\ÍM÷Wsû·Å×?¤æ°N)¡âäàŠïúùDd NÇÂ$†g®æ˜ÜÒç‘>ŸésOŸÅX€6 eßÓüPñT$2áéÛ~z ?õÚŒX¢øXÜ_jtã×~D·‹Þx½yˆM„¦ûãâ—ßÂÙÌüãE(t–Ξ¡ +™e³íE¤´î—?÷¼hÔq«¦NʨHDa6qTr&¥ÈŒá³Bâ$!•Ð"¡ÍËPHÁziì êÊòyÙY6Ógœy;æm»/–W*ºÖŽM>"’j6×Zh\k;><™Š¸·ÿ©øG‹,‹&¥Ï54Vy…
,-}ÛÒL[Ó +Ú»µyÕkÒ2™ ž7¶òcvR¬Á†G{\ÛÖ®ZÜŽ‚}lÔËÁ4µûc‰Mð‡â §™Ÿïa÷÷Ôy.p“ØZ2“ç}Ѷֳ®OD¬ó6G' ºÄAúnW7Ek=»MŽÇñµ¨÷<ÿ…¾yåÕ_Ö]µžØ&Y^éxhì,ϦC†Æù¼,k\ñÌý—º£†Û|»†9åôùU)½ÍŸŠ4":¯ºíÒî©ílMy$ßíÊb•·EÍS_jži_v– ÇÖºžÚh³q4oÏÖ l¿¡>Ú»éVêå<ZT-žp"AH·,ëàᄺÎ{C™ÜÔkŒzYg‰ø -4Ý Å‰óppŸ‚¦’`UÃ@ÕæÎÐÏiÅ6oW1½âã]ÑÓ49;&åÞèðQ$#9àa!'E"’(c‚þ“†„–Š{F¾!É9} +6xÀ8œÓõþÛÛû[êê²ÛV°3H†g¾ž1x¾’˜á[êĹ.¸Ä_ˆ¯=8FÕRë¹h7Ä%Ÿ8àU
–prŽ6ÎV§ 4|aòï +ÜÉå_gÈÖôƶéJžüïø没X¯Žßþ™ow¥=$:3"LfFK!cI(Ç÷OLð0ÀL<Pß C'ä©`¼«²ÌK>Á/žæU§ŒP¢á®H(
BÓT(uÖÉ|@ã±ÒÖ"Tò$$²]‘'w*
¬Š“W…{š‘tÀ~‡øAÅ(=ÿ>C¹ÆÅ2öª€DÅÉ +uŒOÏ›þ@ë6?„FÞôLâꃽÓÀE@‚~ë›Ë'ÛÎ!O¯;D2íœ(.i~UæMssé¦Wí¥Cÿ³ã–ùÒ"n.‹µçS—À²ÌÍ姇÷¿ß½¿¼þ~"üÏ°YÛfµ/ð"ÁDs`Ùó{ûøîáîÓâî§ÿã¦[¾«×á&˜>~~ûû»ŸÞßòT-o.çxYxAó)s\÷vž(˜@µH‰Œ‹¼;†oxYŽ¢!Ê\½¢#(RýÇSÏä£pÊòlΑ1*},û4™æu‰'ŒP"¤uiÈÞd¡Ñ {¸»w—£Ø
Íà…ò¦Ç½•‡ÌSB ™*ŽQâ£C‰0û*wX‰¼ü‚Â#ø%£j®¢¾`á` +kl-™¼_WA¸³Ì¡ßxe·P Ù©‡‰^¾û$)ªÕÀ
¹j/ƒÂz>«b»B98r²±tIZ.GMËÃéÐ3mÜfõÐ>ã{²3®°2ÉyG††† §xÃÀW3é«éŒ«iÙu~›±—eþúÈØWiVz“…
µá…»>]ƒf¤Uð¡ÞÛ‰mÜ[ÿHê _ÖX&c“ß\œÔ0š¼7Ê?àÚŠI1| ¸™ãA÷uJs=ŒFæá +,Öh¾fÕ±}`±ŸÜ‚¿1 +.|×õªc'Ô2œN>)´RÿŒRæÿy¹ÇHßM¡ÐR¯„ú’Ý¿¸{ýÍò»‡ƒYø8àoµqÔ±ˆú×쳉õMÿœâûyôê5’ñ
Æ"í™ œ +ò¿$Œ%ý Ý ‘endstream +endobj +895 0 obj << +/Type /Page +/Contents 896 0 R +/Resources 894 0 R +/MediaBox [0 0 612 792] +/Parent 880 0 R +/Annots [ 898 0 R 900 0 R 901 0 R ] +>> endobj +898 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [394.074 528.008 415.992 538.912] +/Subtype /Link +/A << /S /GoTo /D (example.5.3.1) >> +>> endobj +900 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [148.402 344.43 170.319 355.334] +/Subtype /Link +/A << /S /GoTo /D (example.5.3.1) >> +>> endobj +901 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [296.292 299.598 318.21 310.502] +/Subtype /Link +/A << /S /GoTo /D (subsection.4.2.6) >> +>> endobj +897 0 obj << +/D [895 0 R /XYZ 99.213 706.052 null] +>> endobj +254 0 obj << +/D [895 0 R /XYZ 99.213 688.052 null] +>> endobj +899 0 obj << +/D [895 0 R /XYZ 99.213 529.004 null] +>> endobj +258 0 obj << +/D [895 0 R /XYZ 99.213 272.426 null] +>> endobj +894 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F103 753 0 R /F14 847 0 R /F8 850 0 R /F97 676 0 R /F105 774 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +904 0 obj << +/Length 1742 +/Filter /FlateDecode +>> +stream +xÚ•Xßã¶~÷_ᇕ˜!)RÑ¢@{I{ .Å%ç<Ih™»«D+¹’|·èÿÞJ–mí¥Å+r8œ_œo8´Xsü‰µ1LŠtóŒq-×å󊯱ò·•ˆÛȲñüe·úü¯¹\f²t½{X+®™JsÈ1,KÍzwø!y³ùi÷u`Ë™É=W΄J!Ô¯¾Ýl3™ü9üÿ"Ù…ñ—aüÝ´wR‘fÐ0îÖ›æ ó¼|Æ<*’‚ #"/Iü¤þ¿Û¤êîTHÃø¸ë›;¡†¥ZÇÕ¹Ý<çêËݱ)¦šeZùxýsõÃO|}@l¿^q–šbý‚1‡‘fý¼RRc”Çy½ú°úv’EkÙšv-–Š)nÎG¬…`6{ãɦ,/§ãÑ,e†œ<±¦šÙèhmÿu~×ÚCŒÆ\d›rùì0tÕ~#yrÜ}d·&cy±M9òC‡-ß÷nƒyžOqG¡gDz</–˜ Öœy¾hDýR
O~”-kÉ%㙌Û{W»rXȼ‚)_×1´¤¡jUi'ÍO*;š´‘ÄaÓ·Ã ê
2úž]3ü~£uÒ{cÖ[at@ÕV(f” +vt®?ÕC¿ÙÊž ¿=Õšî}}Øü(KjÏ€ÃwOUÜWÚ†Çêäcup´ÁFºëÚîÙ6e¤ïÛ¶¢–3}®¶çªy$†àUP•µ} â´z‰5â:÷ë6 +Š÷µzlp®©É’lnùp~`§š¶Ò×Têm˜Ž ž’
#Š@×O0Ôƒ³5Q^¼&Eø tt„Þ¬ vnè‰ûŒúׯ„µ³M¿àÛCY:Ä£‡6’)í3k7«TŠ¥*+.Ï,‹•Jm¶‚s~˜ö½L®"À0{;ú|W" +ULâ2‹MŒäâ8@(Ž‹1pD¾ÂÏá0<K_rND<ù¥ +‚ûýÛ÷4¸T=šÏœé‰b£‚ªÐl€J[ºÏî/-YL©±-ûjw¹¶Þ •ö¶üå>7…0Lë±ÍBàRA8 ÷ƒâhù-!”ðc:ð +yä¿Ï +t¦’ÉB-¿•ˆe;ã{ñÉÑØŠ,ቂ + +Æ°Ôæ)úß‘<O/š"¿¾=Dq+èáä (4¾S§‰]0BÒKšDÿqz"üi¡ F™ãþ8;Ý:PúÑ›bë“#u{‡ãh©ˆVEV{À3"ƳÀ;ü¦AµžM +ªŠR^ÔQõ@VOo-p¹ÆîëPHÁJ!±áÐN²âƒ“Æù°ØîL<±Ja¡D&àvùèyqÑSá»ÄZúÖd{:yòÑ+×”ójœt][_|µo»_Ø+¿v)SB}ò¤ËÝïGxú²bâ-H‹[Eã÷šþQ.Úendstream +endobj +903 0 obj << +/Type /Page +/Contents 904 0 R +/Resources 902 0 R +/MediaBox [0 0 612 792] +/Parent 880 0 R +>> endobj +905 0 obj << +/D [903 0 R /XYZ 99.213 706.052 null] +>> endobj +262 0 obj << +/D [903 0 R /XYZ 99.213 688.052 null] +>> endobj +266 0 obj << +/D [903 0 R /XYZ 99.213 601.647 null] +>> endobj +270 0 obj << +/D [903 0 R /XYZ 99.213 469.951 null] +>> endobj +906 0 obj << +/D [903 0 R /XYZ 99.213 359.515 null] +>> endobj +902 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F103 753 0 R /F85 483 0 R /F105 774 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +909 0 obj << +/Length 1199 +/Filter /FlateDecode +>> +stream +xÚ¥WmoÛ6þî_¡ù 3GJԆؚv[±¶[«aÚ"`d:&K®Dņý÷ÝñHÅŽÜAÀ"wÏÝ=<òpøAQ°HÄAÆSÆ“(¨vÜÀÊÏá4VNeu¤óS¹øîy+Ò8(7#Ó` +–ÆEP®ß…Ér•ðÙ_éÇ©àáÓå‡ò…5ÏX‘¡uVIÞÐîOЊ·¨–Ë•”ák+y9™M^ãˆe<svå6eIžºÕ¿-ÆïõÙIŒÁ/3¤Œ¥…p«?Z¤Wö÷ÒâýfÇÏìø‹Ïʉĉæ2•HáÇÅ»<XÝ/œÅE`Ì™(Š`·Q£ÌÍ›ÅÛÅAbÖêÜŽ%‘d’_Ü2)ÍrÆiÇʦÌó䈙œ‰Üs|£Í=Œ™3˜¤LÒéí´Ùvëå*Êx¨š¦[FIxp.»n¤Ó‘`ßw•§M²µ2êZ
š„=ù´+·ˆ¥šÑ/Õ7[·¢–"ÜÝÓ¬6¤°UÎïµÖ‡3}çv)’P¯Âf¤I–æÀ¢¬Òæc¶èJøâz©ö{ëæª]ÓàZoº•dA®AVôí´ê–¾ÀAŽjÎRO5åü´QÀÌŒì4aYâÉþvŽ‘çâ‚>³g1ËŠ|‚±¡uçw™ÇkêáSaà1b3:3PZE1+„øl¡eÂçTõZýJ~m£ÚJŸ9²\²\ŠGñIÅÅ‘„݃ÊÐ4>Þ@ÔÅ"Ä/9$ÖÖÆj€uRQ4Ý«ÞÔÕØ(v·×”¶€t“$9©¢-TKãKªÍ~ÍÖ—Iq_«ê"Ž;ƬåëK+ÏÂa«úº#Áa«{·Vd#ÂMm,vD~{t“§¡òúP–›±y ¤JG`@ÚÚ:³$Å¢7deº ‰kË¡Å©wnä,îÁ@¢º½wær=¢ÉŸ}h1x{^÷ƒqÅYuíP¯§ÃGÇqïø˺¢a ›Ëeœ„`á²Ùí,Y‹ ‘K²“&8o†¤º:Òµ7ëq%»›Õ«Øz¶f•rF4Oý_+£}?–ïûúÖR‹ +
cìhº)dJ< +endobj +908 0 obj << +/Type /Page +/Contents 909 0 R +/Resources 907 0 R +/MediaBox [0 0 612 792] +/Parent 912 0 R +>> endobj +910 0 obj << +/D [908 0 R /XYZ 99.213 706.052 null] +>> endobj +911 0 obj << +/D [908 0 R /XYZ 99.213 137.95 null] +>> endobj +907 0 obj << +/Font << /F72 448 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +915 0 obj << +/Length 981 +/Filter /FlateDecode +>> +stream +xÚVÛŽÛ6}÷W‹>È@ÅåUÑ&@š¦M‚æÒ¬Š>löA+Ó^¡²ìH26A‘Ïð¢‹uÙ$haÀ¢ÈÃ33g¨ÃxR"J˜áaA½l¿ÂÞV~_‡$`~IV—¿EÔ“H†ÌK¶ÇqD!“^²¹öŸ®o’—!iT„g@ªWŸ¯ƒúOÌÿ[ø'~bÆÏÌø]··3ÁB°Ðîë@`i,€[C” "‰ÃZÆ+Ãþ—ùÿcÍ0˜›˜ áv׫ ©DL·:ôûJ#WÏ’N±NSBÁµ^V×7ØÛ€¶/W1{÷0Æà¤ôö+NŒ"÷^¬®Vv\v-ô쮹ôpû¢Ø(8.¤#ÊàF1Â²Ï écwN;„Ñícº?jPÒ‹è8P"`Sy=íÄr›†p;Ó$nm?±ê,zXŒ„iõO~M•ä{eqC6"Ë·Ž¸ÿéè‚xž–›BU“|YuBù"dƲ=Ö„‹©’¢ÝÈŠ´®á°`Ü{Þ:cfó5ñµØ{U6ø"¿èiZ·iöö G’sCý¯™£Iâæ™™?žn‹<³TÛS™5ù¡´o;Õ¼Sõ©hÀæ{,ðuSåå†ä›øí|¥šSåKu? +®ç#†ú'ÃL‰>·Fáë‹ãøëοM«OüFUÆÎÑLØ·ï !ßžow©(ë&-3uض1©>,Ò“-ÒsÁãĤWïþ¨MŽõ8'RE¾ÏˆãâŠé7«úZÝ¿ +mµø{Í!oÍ|å1ñ§Ê²SÝözŒýƘ½k¿;ýrï&³´´ƒS܆»9|^Žl\}(^¥Ç:ÇÊ
ì<Ø•{6wió£Jg
ûõQeùö“£lì3ÝÓ>nÓ’d—óüÜô5âñT¥Øv[ +·‡ÊVA¨%S^":ä25Ó¡ÿvÝQÛ-Ý>=ÕÆŽ<ð´òÀÀÊcÇï1¦p® +Ø8_€iÉì`ÌRk!³=Œ-õ°‡ŒwMllý¬?µmlh&AÃ^ ¢NÑV:«Ê¥Ök·$‡ÓOÄ7õªv©WµíÈõ›“«„½+Û|w3SI†ÑÕ&<OÀe)Z<£–WO<ºÐ§ìÂÕTŸÓá]ŒäÅ¥ef¨/§€‡L̨¦TÖF[Σè>îW +Á‡çÍÂu.fˆþà
¹‡L.È.ÏqG¢]ãxlHPŸ˜œ±ôÐnÜ(endstream +endobj +914 0 obj << +/Type /Page +/Contents 915 0 R +/Resources 913 0 R +/MediaBox [0 0 612 792] +/Parent 912 0 R +>> endobj +916 0 obj << +/D [914 0 R /XYZ 99.213 706.052 null] +>> endobj +917 0 obj << +/D [914 0 R /XYZ 99.213 309.806 null] +>> endobj +913 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F97 676 0 R /F105 774 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +920 0 obj << +/Length 1341 +/Filter /FlateDecode +>> +stream +xÚ½WÛnÛ8}÷WéÃÊ@ÄPuš +?ۈ劎X×D/©>
·$´'psM5H[EqY4U™¡¸””V(VQHœ}Ô¤eih¯EA$,tZû®™§OëÚAôiM‰+T´^iwÉ—òºÜIeš´Ö‡Á>OPËê;ä„{fTãØÖ,~I‡µäá00®ÝüŒ ~Ä!Q1×#<ä÷}â2M/RP=iIJG4-ÚêÁáФ®bb|û +€÷ÝÿŒz{çUõîi.YKNÒGUÿjõ/2h#p¡Mr‡ºã½!Vƒù±÷zµìF-DuD]ÊûèéØ.hyþIç-fàZÙ®ñx°E칿îˆk#/¾œß¹hf‘ŸÍ:Ò³¤uñ›ì×r‡š,¤(åÂ#Yt
^°÷â9šÍvÐHu6[ˆ¢õz·ù¶+ÕÐ<â殎1“mÜè6õ0ÃR%Â~aÛ$„çE¿zÐÈ1išœ0D"ê¸J7’¼tY5«2ÇýÜ-oçŸg«1¦Y‹J´ñàx‰Ã›Q¶oŽZ{Ñåãj¼/1Â|„\ÃX+öåÉM6Æ]¤OëïéÅ.tW +–ðJÁ¢ºR7·µÀ…öâ+YÕiñ´Í£ïÒ¯Æ#G(jÆ˺åÉÕªÉ#õH•î¹.X}ñëu9e®ù\h>Ð#ÞJÙb\i{$àðnfð2p_£i[=ô3÷
¶$ÍŽx‹xÞ¾çC’Ö˜ÓIÇUpxòž"¼æáµQÂcÇï”óŽð½^ßýë„·Õ…éê&T®Š^ÕªZ©èFøQ>þ"„2¼§D„=ì1"l!ÿîß®»åüÛbŽ&åíB¯çGó›ûÙrq·ZÜþ¹¯‚¤ ;Üñ&}1G~ò<EøÉŸÕ;Èà’8ð.èŒH¿|ИÛ_{CO¿ +endobj +919 0 obj << +/Type /Page +/Contents 920 0 R +/Resources 918 0 R +/MediaBox [0 0 612 792] +/Parent 912 0 R +/Annots [ 922 0 R 924 0 R ] +>> endobj +922 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [255.497 474.375 277.415 485.279] +/Subtype /Link +/A << /S /GoTo /D (example.5.5.1) >> +>> endobj +924 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [215.844 259.632 237.762 270.536] +/Subtype /Link +/A << /S /GoTo /D (example.5.5.2) >> +>> endobj +921 0 obj << +/D [919 0 R /XYZ 99.213 706.052 null] +>> endobj +274 0 obj << +/D [919 0 R /XYZ 99.213 571.486 null] +>> endobj +923 0 obj << +/D [919 0 R /XYZ 99.213 460.427 null] +>> endobj +925 0 obj << +/D [919 0 R /XYZ 99.213 260.628 null] +>> endobj +918 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +928 0 obj << +/Length 1626 +/Filter /FlateDecode +>> +stream +xÚXmoÛ8þž_adÎU²ü +¬¶¶w×a»uM†û°^¢6Þ9vfÙíúïåĉãl(ÓÔ#’¢(ñq…ÃáO8IÂ<!ˆ‡Œž³X¸ó +À¹{{wõïÍÉçôxñ˜æzKs¦09¥O…Ïß7³_é:ÕZõE?‘Ü™JɤŒ
öÃýdêÅ‘»(›J«—ð’„n½RZ¡DµXÙ÷FiÂ=eyNRQÖV5}.«ÿhBf
>—Å)µ$©. ¢7j‘Ý?“2mýçͺ°¸çMIJ3Š¦õú8ñ—oee͸Öf ¨Lzø^b6U¹QU(¨Ð´®«ìëÄãnS+Íl¦„¤ÚTù!TXDuÍÂÉTpÜ“*[gu†ë~ÄEï”nòZÓË?<àSp$ fàªxxIC7E ÀÄsæMYæ*-`Ž8¾a>ó<j»Rxn•#2‚}P¤[–Åo»5½Rþ
¸Ä§p+U7UAºÔ>˯ßÔÂÎxÊêIkXO¶ÉáM5®É`†ù‚óºOb³~ÿÖèžÏ”f¶)Qìf\¦Áç†þf}¥œ`†„„8±ˆxàj,"î»÷eU¯AàÞÜÓÕvR€zȘKU/µ5°)7MÇ„´˜ÔRŒSß}|÷>ÝÐÀ‚eÚײPv¦
jÏ:°Uûˆkµž°ºõ‘õf…®UºÄ‚á‚–äI»$(¹(Ù¹aïTxÉnE
B£-4¥Ç±àÐ"‡’ï¦z_™Ú¶FóTkã)ÞÂôªD[OÍάÛëéëçà8ÜZ‰OÉ"8^æîÄêÁ"¨EO;ð^—;4iO,=ßЀ‘ìûîÝÛ„9íñÀzd½~'WB«ˆ8tÈäx'È´ƒ!_bç˶bZ§M¹@Ž®T0+ŒN:o1=ïÐóv*„ýxÏýLåpPàDÛË”¶¬mX¦œ2"c–Äa7¨áÌì°½îÙfÆBLÿÓ&6j–Ùò|Lï·U¹lx]–MQiœ +ûKú|œÑÍ<6mÓ9ÒZ»–h¯yÑÞÛ'ú¾*×mÿpõér~ªcÃÉ’!g±O–O3T>-ähùxCåsÊù¶|½+Ÿ®ûweº<,í"Ê3sÝdîUSZ®[»…¤[*½¨²M•…JGú¿TgìPµ³kT;¦Eì—Ú”Z%ÄZO t¬ÚfÏpã¯qƒáš{ÕÞ½h ¥0çcCÁl$–=‘,^]Ï.ïnnç7þŸ]œ(¹mô¿@}"K} +YäS[<rAÙöFÖÙê¦AË<@™iz¦4€Å6—×ÿˆ·!“:ÛvOš@¥×ÐôYT,Ó:5q‰–¢ã°J!òSlrJáÀÓ.x9¯ìPiU䌀ÑÎ"@•.
sj“MJÚ”Ù«é:K«*5Î"ìÌÉ:-Š©5¡Rå6¢V·]Bu´IHáÛÒ&’ +endobj +927 0 obj << +/Type /Page +/Contents 928 0 R +/Resources 926 0 R +/MediaBox [0 0 612 792] +/Parent 912 0 R +/Annots [ 930 0 R 933 0 R ] +>> endobj +930 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [500.997 500.624 522.915 511.528] +/Subtype /Link +/A << /S /GoTo /D (example.5.6.1) >> +>> endobj +933 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [500.997 185.236 522.915 196.14] +/Subtype /Link +/A << /S /GoTo /D (example.5.7.1) >> +>> endobj +929 0 obj << +/D [927 0 R /XYZ 99.213 706.052 null] +>> endobj +278 0 obj << +/D [927 0 R /XYZ 99.213 597.112 null] +>> endobj +931 0 obj << +/D [927 0 R /XYZ 99.213 501.62 null] +>> endobj +932 0 obj << +/D [927 0 R /XYZ 99.213 383.94 null] +>> endobj +282 0 obj << +/D [927 0 R /XYZ 99.213 282.347 null] +>> endobj +934 0 obj << +/D [927 0 R /XYZ 99.213 171.288 null] +>> endobj +926 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +937 0 obj << +/Length 2200 +/Filter /FlateDecode +>> +stream +xÚYëoã6ÿî¿BÈ'ùPk%‘z°½-pçä)ºÝ\â¢wè…,1±îdÉÕ£Yÿ÷7Ã!eZr’m±@ÌÇpf8ó›µãÿÀÂæ$~ìùQèäû…ï<Á·E )VšdeÑü}³x÷$t„'bæl‘
C`#¼˜ gSüìFËU仞ú›šqøîzùËæ;u<ñD‚§C8¥
Ï}ªÐý¨þÞ©¿ßã9÷Fÿ5e³ÔýXŸ¾›1¥™á}¿\…¡’hÞ7jŒëîF-ÜZËÈlq³m1Z+…9Zâ·ÅÏ¿øNVûná{L¤Î3Œ}/ÂÙ/xÁ(Ñójñ°øçÈ‹ö@ouê’á9ƒsIê¬B¸K|朹“¸2ø“ÔóÅè£4:ó*¯)Ðme7TýÇì +‘•@Ý®¡€ñûÞ´$[}³ÞÜ^ß%ˆÜ#í4 +Y÷˜ÊŽË8"‰\ITöó¸ÏÏ"BµúýUƒž*Æð;îï³ÃA¹3²òYGs[m˜nåäì[¼Qi¥±g2Ž2\ 4ü›Òí’5MKýûRܧŠIäDAâù~ðVÜkê•E> K÷áTvmAp.yõDñª´s&@pác3h/dÚY¥°£¡Ó5[%ئÌUôѽ‰\«B¨üWªØ´Î™t‚^:¤7ë¨þò”{œ]¾£)Ñ
]2˜ÕhC‚·4þT²/š7ˆàTœ¼*ÜÐ̤CµÒ*¨ÈÓ3ñ·d°ËÖê&†?™³R‹q™5Qß4/Z-b^$¾¬Á±h_êp‰*œú{¹‡€š¶8YAIL®ëf¨MC‰|ýF—C¹™ø¨]x)¬®/Ñyä/ö©ÓòcÛìIÒÝý§ë×ùÁ±›”üQõWJ>9é§]†N¾.>© œ"(•”²£I©[Ùm²n´úBæ“Zhá0xãodC½²Èç±<aùræ +åà[xyAJµ áÍxlÕß09A8òRð€õnÝ`qùLvºƒÞ uÕ³ËÅò?OOÚH(ïǘqá%…-\Sg-áËh¶G ĸ!iV©u#&°4öRslå£rl0ÈÜ.#ì¾WŒ¥î÷æÉ„¸/G!]³×R¨ˆ¿–FôêêÊZ*€!
è»ý/@EÏ1.çù±¬ój(mÊ´&ØIѼQçõX¯Ue§VBRb¤ê°ù©»Y¦S)>X3Í –ãAu]Ú>QRAí<«*ê …!Ë•òC°ÃµÈ/ªj÷ò=áfÖK)«û³ð¶z6;Z[´…NbqyjQô»÷Œôq€ê´Â<uz¸Ù—±›ûñÑuB`lª~œžŸú´©xÇV’…
ÕxÁïÐ
UH h嚸&Ï2ÚÑ삯C‰ˆoênW¾¢ÆE,NÔçÔ´õ¾TŽuýé³åk
XÜ5U1.Â3ƒQÉ9(ŒöºÎ¨«%uºg‡>{x+˜çzÎ+$B‡ö©ß°Ln+úkEs°¾~€ôºòÇK‘…j%!ªÚîi¢¿ß„J3JI`+•è¸J‚fpÆÆì>Wt®ôÑ'4QrñÃ
’h¦>©DŠlåD‡ñÒó‹>ËóÈúF/ +ew³rg‚Í|³:õ
󼚩&¿
ÒúXqásyÊ<ðWÿoáD2ëÜ™{éÈoÈÙTPɇû₤ÿ†Ù&Äendstream +endobj +936 0 obj << +/Type /Page +/Contents 937 0 R +/Resources 935 0 R +/MediaBox [0 0 612 792] +/Parent 912 0 R +/Annots [ 939 0 R 940 0 R 942 0 R ] +>> endobj +939 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [147.376 567.646 169.293 578.55] +/Subtype /Link +/A << /S /GoTo /D (example.5.7.1) >> +>> endobj +940 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [188.178 513.848 210.096 524.752] +/Subtype /Link +/A << /S /GoTo /D (example.5.7.2) >> +>> endobj +942 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [274.043 387.981 295.96 398.885] +/Subtype /Link +/A << /S /GoTo /D (example.5.7.2) >> +>> endobj +938 0 obj << +/D [936 0 R /XYZ 99.213 706.052 null] +>> endobj +941 0 obj << +/D [936 0 R /XYZ 99.213 514.844 null] +>> endobj +286 0 obj << +/D [936 0 R /XYZ 99.213 292.806 null] +>> endobj +935 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +945 0 obj << +/Length 1680 +/Filter /FlateDecode +>> +stream +xÚµXmoÛ6þî_!¸&o+R%KÔI»kë9.†¡-ÅVb²å饩ÿýîx”¬;]W"¾ï9>w<Í
þ¸†LpÇðmÉlOËÍÈ6î`æåˆk K‹X-™ç‹Ñ“¾0BJÇXÜ®í1×ñAOȤ‹Õ{s:ù¸x¥Ä|ú(å3î: g™XR˜çêÿþss¡Ú—ª=oÖ6Ž„zµ7±<Ûd(k·„k Á¹–%×Jû;õÿ׉cÜ +Cº¶N8‘ßi‰y+ki«%>ˆÁ¾JuŽYÀø +ˆiª—ÝÒ·³°@¿¦p*,^UðÍ•{J`–Šu&$Æ::\'™ô½Ž» +9®ëº~¦q™p¸áÁñO$o’°"Ää-¡2¦Ž eÀQqIÿ!äZ¤
‰®æÌsƒ6ö9¡6ÇÚtL qŸÀIRˆ>Sbr‚fkÉ'®gjÒNð%æ‚i-³OwUæ^Ë|÷Œ}hÿÏÊfiÛf²:MŒ€y0“È2Šâl¬ChüLE¨½nlhÕ´¨Ž‹³q²ªõd)è¯6Û³ñl~ñçÕÅøɳ#iâ„šU\,ódÊ$ÛT6ú..¯§ó«Ùâêí›oRŒ§N(ÞÑ:=_ ¥4ADÕ„Miá¾F³Žqò¤á»1 ®q<õ_ñ˜…§ŽºcY£?*€Àê]þ ‘C ¾#þø&†t¼}zfŒ +-IK`DÕM¤uKlŒ ®êCøî²]•RzÓm…Ë£ª0ñXär‰Ï ®Ë[Å£j¤C• û‰Ôµ£G°NB$UŒ«VFT1v‰W]ÛÿÝÞa¾Ù»æ9Ú®z²›¿Ô™E³òé¨ò-áñƆ".ädeMã hô6¡ kÓ"¥ùûîÎ:ZÆE¡•¨r¥Œe²[s33Ì!”qU?ÙÂö¶KD‡´Ö;ÍÓ¤Ô4¢š–'hñpg½x-š ÝÂ…ž*oK¦9Uæ¸ãtBñŠYŠÎV8±¸
©ãB[ÙjzÜ¡¢7?qj\+‡Ôù`{6ÊáÇ™(ÁÄ¡*”þá…) Š¾O°VYSçÔ3Ɔ<'÷<xó¯=c´´Õ>*z*O?c‚ýìþ3FË<Œˆ§@v7Ñž(¹‰‰€r•4ù}ž€9ÒSUªW7V¡Ô‰hRßö*,…YR‚¢©æ˜Ô×oÔ¼¬Êø)D>–ÛÛ˜¦oá6¤JDH¸8œî©Ro'xÆ×Յꨃ‹?$(%8‚JTC/pÌv +ÐξùÇjáˆ>D]ª—©ç@½5Çåú!bIQTNq‰âE¬~½@àœÑI”j¨ç•67¹¥¯"WÕ¯ñ#?s¹ûà¯j‘ß[² Q‚ºn¨þõgˆô^%öendstream +endobj +944 0 obj << +/Type /Page +/Contents 945 0 R +/Resources 943 0 R +/MediaBox [0 0 612 792] +/Parent 912 0 R +/Annots [ 947 0 R 949 0 R 950 0 R ] +>> endobj +947 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [224.966 645.049 246.884 655.953] +/Subtype /Link +/A << /S /GoTo /D (example.5.8.1) >> +>> endobj +949 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [147.307 277.929 169.225 288.833] +/Subtype /Link +/A << /S /GoTo /D (example.5.8.1) >> +>> endobj +950 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [202.4 151.898 224.318 162.802] +/Subtype /Link +/A << /S /GoTo /D (example.5.8.1) >> +>> endobj +946 0 obj << +/D [944 0 R /XYZ 99.213 706.052 null] +>> endobj +948 0 obj << +/D [944 0 R /XYZ 99.213 631.101 null] +>> endobj +290 0 obj << +/D [944 0 R /XYZ 99.213 218.498 null] +>> endobj +943 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F97 676 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +953 0 obj << +/Length 1875 +/Filter /FlateDecode +>> +stream +xÚkoÛ6ð»…—~‘׊%©w±èœ´HÑ%iâÚ¢m&ÖjK©MýïwÇ£d=\÷±Á +ð´ŒçÈÏý5±¥´.×:ƒQZ'z$ø\/NÄCá\s{¨G1±Îë!Ï€…Â7'N5·Wš6ÁS
Ïôx½‡›Ë|/2§ßq×Ì`|Ò‚aKàéÑé¬1hcrh¸hÎO£·ïùx ¦9ẩÂñ=Àœ‰(oF®ô +Ì|=º½nhѨ¡Oíóž']æòè›îC‘ü dœ¼'@#Çó»<[V‹²xÓбʕJiCúe\ªJË‚æj"<ëjQ• +}.Cë9.d9¢@Û¿l—V’¥Š—´—Ýr–ª£Ž`ý=°5èm—E®«5é +”¤·ÈMøVŒ‘ea•ñšfš+|Ê€CÂÒPC×ÙJÑvZmæ*ï’+4T@,Z¾ÑFâ†_âÍÝZõƒÇ
=pš?ö}‡ ×'—ãF~k0®Zί±íúÀù}’:yYÄëAĆ‘èòÁ9ÈÑã!‹xØáxŸ¬×Ú{¶‹E$ò:Nÿ@å¸Ç!Þ‚%äÖ\Ñ÷ü¡x‚.åXÅ1úN@±é§Ë¤ÂàMi7¬t„b·pHv1Lƒ¢«‡q•–f¯å„§üà ógÀR¿á.|Ñ
;ìAb}ÖjQë`QOÄÞDjzgAÐæ«iáj™B¯%“LøaCeú-WEµ.ÿˆï ÿ¡þ&Ëã£B‹gCâ‚{0wmB:"”Å:.Šã#“×GOu0 +5d©²ÊSµ¤Y<Ï>+]AöèÇü3äëø§N8nÿœƒœýrz‚8}qqõ7ÍîW*WÍÆà~vBÓcú<ø¯+õàûüÙ½´›¬r]h9…F¿ÐÍ™tê›'[*½¸IÊävâY`Z(W +%ª +5-Ýxã^û]¡YkAªõ²ÞÞ‰R¥¨K(ÚÞð´T ˶|¨lš•ÝŽr•”Ô‘ã„´`—ñ<.”n.u¿‰µéZ™íé*¾ƒâÐo¶$ô¸¾pÇRЃópÃ^cÛ-ôAûÜ'‰:øC¾!óàeØáÛëîjœÃÜÁxèw¸é2ŒJo²Ü¨µ¶2fÔn-XÝz‚D@a“‹•Žg`õ½“à +„û-z¬ëj +A°¦GŸCeô¢‰Ä!V1ÌsŠ& +Ò¾¢<Dþ'::Rru¥Qßâ8M1‡4%=5Ý^¢ÌÓ¥*šä‹Mêeeƒ
¾ˆ1V³uòSÛPÆ¢S…POè!Ÿn#á^¬iW0 ;¸þËÍ ˜cár&xðì5Èv{N=‚õc[ŸŒã®×å<x2Îa†pÍ{ŽßáùFÞ-z5®À²_;â|cÜ`ðNÁŽûíäÀ“>äÞwªÆ>(xŸäWÿ—pdÈ º²”A9̯KÙ™ÛÝ+::k«™à4 PóNô}}(ØÙWþæ +÷à_±;”=VóYØA%\oðGŽùOpÈé_–pˆ$endstream +endobj +952 0 obj << +/Type /Page +/Contents 953 0 R +/Resources 951 0 R +/MediaBox [0 0 612 792] +/Parent 961 0 R +/Annots [ 955 0 R 957 0 R 958 0 R 959 0 R ] +>> endobj +955 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [484.97 659.993 506.888 670.897] +/Subtype /Link +/A << /S /GoTo /D (example.5.9.1) >> +>> endobj +957 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [262.618 214.663 269.592 225.567] +/Subtype /Link +/A << /S /GoTo /D (chapter.6) >> +>> endobj +958 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.124 136.954 158.042 147.858] +/Subtype /Link +/A << /S /GoTo /D (example.5.9.2) >> +>> endobj +959 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [307.808 136.954 329.726 147.858] +/Subtype /Link +/A << /S /GoTo /D (example.5.9.1) >> +>> endobj +954 0 obj << +/D [952 0 R /XYZ 99.213 706.052 null] +>> endobj +956 0 obj << +/D [952 0 R /XYZ 99.213 646.045 null] +>> endobj +960 0 obj << +/D [952 0 R /XYZ 99.213 137.95 null] +>> endobj +951 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F97 676 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +964 0 obj << +/Length 2411 +/Filter /FlateDecode +>> +stream +xÚYmoÛFþî_!¸J"†K.ß‚sÖÎõ$ÏQ?Ú¢ ¤µÅÅUIÊŽï×ß3;CŠ’ì´W—;³ó¾óB«I€?5És?TÑ$
?ˆÃÉrsLîùþL ÆLPf#œïæg¯ÿ‘†“ÜÏ“h2¿›è öu”‚Nî'Q>™¯~ò.§¿Ìß9´ÔÏSÂJ}¥#%è?§³$ô¾u¿7øUÞÜߺõípv`%àПŽ§³8ð|Â
FÈ=£Pù*W‚Ë?9ê?ºß÷Ó( +Løÿ‚Ï õj·¾àÇWEµ3_}Áêƒ]¿9É *õuN"ÜJ(_¸a ˜)w…‚<ÿã4!=d:~6M’ÛÁ8TÉ‹tÜ© +Æry“É9œcPüxÓÜ+žDÐ…épkˆ·(r}LJžìNÈ".%”XbŠô$çLO¬¢ #ÞôhËnWPúáW ê(Ƚ²ã-’‰žMÁÌiçH,—¦h·`c*„êŠ_ì‚,úÝH÷NF6þ=×™ÞŸ¡|EÞ LÄ;}.cQìÝ“NÜÃ5#õ8¹ÌN$ÛÙMÍVrê`gSÞ¯eY,¡{U=1ÂÂðîY¼hÉÆLÂ2¼ rø@?¶\
ÔyÁtXõ1lW׆ìT4¨êÑ NµÐ…‚òªŠ_ì1Ö‘](ÆA!¦†,μùÚ9)á +G¬(Y„ï!6¹yJ¥iÀ— +„ôB„¿/ex[Ú]µb´ÁÏ\ðÝ^!XÙyÈ0…Ô.ˆÃcÙ˜n×Ô®Ø%qî}·Á\:Íaö®#õC=T(§¬NUB‡}·¬%—íÓ*¡-.…š?<`gK25–Þð™ƒó˜ÃÚ{œ7Ú5)@t!WfkÐÔÂÞŠh¢» ѧ‘Øãv¨zF;ó;úʲ“ +VúFŠáÏa]\\à¡É-þIÆÊSL\ÉŸ™åBÌrH”£Éé¯a3šô´N'³á«×_Ÿ1°…n¨#»¹È
l˜G ÛèóWh<SÉm* "Ic·ûþ$ßž.mEÎÞÃûMC~Z“¯ŠÍŠ~†2§QKqë†(‘åmÛrÁW$mPèr’ÀÇí
apP§1[Ü +~ýðæCXqÓ¼.·ÔKhÅ]ûó¯ò>ðzŒÍHý”Fý˜òà%K[wEYËŒ3Â!CñV)¨. Ê©bt_lM¶Ñ*"ÓÍá7gh<ÙT:Ar”6]É¡©ÞüâÚç1¶\rê# +$Ô)¾}âS-qdáÈ¿}ZÏà-Æ—€"š«ãï™Õå,•o +ó÷ÄÿôK|èy(8½â¢È«>†g‘¯•þâÿö('ÿ^ˆV²±ÕÉ1£Úê †Óÿ +endobj +963 0 obj << +/Type /Page +/Contents 964 0 R +/Resources 962 0 R +/MediaBox [0 0 612 792] +/Parent 961 0 R +/Annots [ 966 0 R 967 0 R ] +>> endobj +966 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [460.473 316.251 480.398 326.264] +/Subtype /Link +/A << /S /GoTo /D (example.5.9.1) >> +>> endobj +967 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [132.939 302.552 152.865 312.565] +/Subtype /Link +/A << /S /GoTo /D (example.5.9.2) >> +>> endobj +965 0 obj << +/D [963 0 R /XYZ 99.213 706.052 null] +>> endobj +294 0 obj << +/D [963 0 R /XYZ 99.213 224.064 null] +>> endobj +962 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F97 676 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +970 0 obj << +/Length 1216 +/Filter /FlateDecode +>> +stream +xÚµWmoÛ6þî_!¸_ä¡bHê}X +¬vZ¸H7ñ€
]1(¶’h‘%W’›æßïŽGÊ/RÒlÈ`ÀâËñ¹»çR”°8ü„ÇL +× +yÀ¸/ÅjÀ˜y?ÚÂÑ&ÎŽÍÛùàè](˜ÅkͯÆ$ÀÄ,pck¾ülû#Çç6SÿBýs3nG_æHÈâ1\Ÿ 7Ÿ¸ú¬¤}¦þg¸Â>UíÕþ½]ÜFàFLò@¯î‚‡Lxîöiu¬ÚsÕžª6Y~ìzó¤ñ6ëx¢ÜPÏ^ŒIHBeb¼á¸r&µ3¾D°Áɼå¿ÏßCö¿>áÖ*õaÀ™GÖ=´9qlž!êçƒËÁ§‹æ nµª¯Ø¾ô˜ÇãVC +ˆq*vs›V)$$";«éY”ô\Ü&Åžkô˜S£n’&]¥ECÝ?¹ÏëTÏ¥#áÛß“Õ:×ÉU‰Cßð/SR’qlÏ
ZYäÔZf×#éÛ×U±Ð³Uúu“Uér?”E²©Óƒ¨.?ž%k,âÅž§Òœ$M3ë´9.ü*Y¥èè~’.«;Fl|æe²Ô#·# ©öº* y ^Rë'‰(ò·4K.FeóÓ¬nzt/Y,Ö²Ú¸KÓêõZÓW#ÉíMi1Ê«¿Ó…FYAôòn9Òuw˜+dΗvyMÏæavò$èîétd̢ȤõuE`¿Ã‚—!ï¨â5dÚgB1( +mnÊêFn‘c²è„$ÜùÑ9ÆýB6&ÅYU.7HŠ¡ +ÌÈ@ìQšgwh®p8 +pÐÁ xè\ + +endobj +969 0 obj << +/Type /Page +/Contents 970 0 R +/Resources 968 0 R +/MediaBox [0 0 612 792] +/Parent 961 0 R +>> endobj +971 0 obj << +/D [969 0 R /XYZ 99.213 706.052 null] +>> endobj +972 0 obj << +/D [969 0 R /XYZ 99.213 631.101 null] +>> endobj +968 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +975 0 obj << +/Length 1538 +/Filter /FlateDecode +>> +stream +xÚXmoÜ6þ~¿ÂK?̷ΪäwK6ɺIš&·CWŽ$ÆürµuiïßeŸïÎ鶢(p¦(Š¤¾ˆ©°8üV’0WxVÄCÆ×ʪ·îaçÍL Lj8#™×‹Ù‹_#×JXzÖâÎòyÀ|/= ½ÄZäìãùÇÅ[-±$B©ˆ ߥ¸ûÛÜ ]û•þ½‚_a/4}ªéëáì`ÂÁB:˜;·Êò‘poÈL$ÂÈ’Æýwý{>÷8˜Û3á&Œ÷§.ö”&̳;öû%g§‹±Ó€…x}š}øÈ°};ãÌKbë3ÐœL¬jæ»P‘Y—³›ÙûAí…š +OàúÌçÉD|„%|æù¡vž| +£˜q +èsGpÎá6ð±á×nŠ¼¨ïçŽð^>DÜÈRfŠèó¢S‘ò€‹ÃZ¤uNÄÅá%lˆ}t¹åx>K\2¿x(´šÐ¦odwEU”iKLÕS=HbˆCÑ‹©UªæÂ.šš8ém3úØògäÅöíÜåöJm[hîh-çn/nTÜ·2UÒØΚ:“ÙÉWò –’µ*Ò²\±}/[U³ªâ€W‡P$¾¯¯Žnx~`ç©J‰*ê¹ +pÐB5*-1Ÿ|JFdê+º^b:Š×;‰”îT^¼ƒ-ñ¨Ó¢ÐŽ¦¬ƒk²Òb<Õªu€Ñlc¬"zû7ïádÙ©õЂ°,‰‹P\‚>Zª%©¢¦µÒ9YÚI¢.Ž?Oà}Ž©£À‰*ýóÅÈnÎÓ[@Ä(©ZtPê<ô ~œÃ£¤{
Hä2-±Þö¯K°¾Î·û:ï0 ½)\7s¶7 øÌõ„p—yn0=]‘ˆ3’é_ïáå4w/¢‡š/iµ,Gø”3±k\„ Ú¿j½—Ù3ãÈ`^„`Ûü7Œ~2
&PòØő?öóI´F²ÚÝ8ØC«Aw¡2¾H—ÝK{À+aš‹ãxG¦:‡Šüè Ó·s2=oH¬%‡¤H&+¡Ï`±áæÁ¶òQvåt +Þ‰¥lÕúèà,ïÁË
&ª”½ZüuvrðâåD’>¡æDvY[,ÍP4RÙë;9½9¾>»Zœ½»ü_ŠM]Ÿ£âNM(G‰IHõˆõýàõ 9ËáxoÖ™çÅ +endobj +974 0 obj << +/Type /Page +/Contents 975 0 R +/Resources 973 0 R +/MediaBox [0 0 612 792] +/Parent 961 0 R +>> endobj +976 0 obj << +/D [974 0 R /XYZ 99.213 706.052 null] +>> endobj +298 0 obj << +/D [974 0 R /XYZ 99.213 688.052 null] +>> endobj +977 0 obj << +/D [974 0 R /XYZ 99.213 536.397 null] +>> endobj +973 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F97 676 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +980 0 obj << +/Length 2647 +/Filter /FlateDecode +>> +stream +xÚY{oÜÆÿ_Ÿâà +!Äày}öñì/ÆAIvÕ)_ˆÃÈ‚üزøpz)à’E’f~ÀÎP4%į3ÃØ5=O +þÔU?ð¨½áïUw®¼¶×ƒP~ +â@=‡bÀrù
ib±s?˲ÅRE~Ev³¿ôC1˜i„kU^<éMmÖÃU×–àL|¿½»,†×å&Ù]±3ƒé.ë¢ï/žTÍðÄòs?…+ÍÙw¦ëá]±w\—ûŽdãaÉÈ'"\”úY¨îæ%
5e0è¥òSAþÙ•òsxÒ|Óµ;Q̇÷/¼¼æÉíÖtÆÁ_þzùâúWÈðú%ƒ.øóÕç¢ÍW,Oûi¬ï+ëÙ¤-Z¶„¨ÿFÌY99q㎪|•fù|`pO*Àþêù°dĽ?ñç›lëq¨Úæ¯Çîv:ð“4å@0v6®ï°.ͽakhy7¦€--áÚúó¹Š=Ó3á°zÆU}?Ê‚¦új·¯
ôF‡3åS +'W±ªÃ¢§VÃÁ=ÝZ× …Üþ"ý²'+±óJ¾CËß•ÐUͺ!xiJàoÁŸ›QÔ“ÛÕ¦èÿ0z(•ÂŸSD^”ë¹Ï?ô}dX€ +À‡GŽü>ík¸¾RHºÑéx¥Ñ¬ÿÎÈÉ[-2˜[bã[®“E”E~š%öVÞÿ†½E:÷Þ¶EY5L¢ÀûÜ#}g¸ñ7tImE÷MDŸxbo”¨a뱞O~Ø2ÌKÂ/„Ëm5ly¥59©çŠ}5Œù”¦_wÕŠ®ž°ûŽM³jÇŽ’Ä9ÊRÏ×çqìõâÚ²íRiÔ +kÊÈDÖÊBEþa®ØØ[E°àÏo5:ଋÂ~@QŸ£Ì¹¥ŸâN+3 Æ’@P£MÙ5gëg.&œwmç$éFÝ´#×mMñÔê…Å{Ó
•é'W££rÂIˆÚ±>…/‹×󄙶)Ùi«rÄ1,ì3(”Ò4™ïcÑ%#bÖ}·ØtˆSF8»vldoJvvi1¤håõ
s¿kG¡ïŒI +¼~\-¥¸â
¤¾`,‡V‘ jâ $·I—QL‚iLÿ?¾ n´š‰5_pë¸[1¨êŸƒ‘JEF@xKP‰‘¬ƒ§#@S‚f¸;Œêê_dÅÆ(7-ñk49ï|8Rãaÿâ^¤²„6‚¡Ú?q¸÷`ÕÝV½yʵCÛ°Né³’„O8UÆÁÌ1Ýó—sU&R¡´d½{ºI€0EêO§t#–ïˆ +5Èè02EWW°˜4¤‹seE Ì\‘q¨½-ûº•%¶¹£=²„{•‚Ò˜ÕzËT’®Nj&ক½Ê¶ùú\¡ œ·]gó-ŒG©0ó~hO¨tW°kh]v£p?í¦Ùj·ÓYp€¬IoDó0§Z$îscO÷b÷°Ö®ÈDZÛS%J'–½:^}õ݃Ömièiç¶@ÈOx¡Ùû*I’gÕÌEíälǺä)·F<&7 ¯ðYÛêž +r!CBAM¹6<[¹\ +£Åo|6ÌOa¨/..ð‰èBŽš¶(OÑZ%ÿOÓ¢iË©¥ŸZ¤?Þm-©¥‹¢t±œÞ‡þxˆÎ,´Ýe"ôÂé¼3Cw¬£DÞUéu+„üaº×…PÞ.[ô.($Äü¿'u›;qA[¯ÐƒÌµÚ;ߢ%µù“+HååG–ÑÓSæÍ›ˆã· h‡&æ§secͧÚCJ?jŠ†
UkNÊÓsEœKÓîâìa %Üýh+Ô +¹¸”qÿOc³©…ˆK bsÇ +endobj +979 0 obj << +/Type /Page +/Contents 980 0 R +/Resources 978 0 R +/MediaBox [0 0 612 792] +/Parent 961 0 R +>> endobj +981 0 obj << +/D [979 0 R /XYZ 99.213 706.052 null] +>> endobj +302 0 obj << +/D [979 0 R /XYZ 99.213 587.806 null] +>> endobj +306 0 obj << +/D [979 0 R /XYZ 99.213 239.049 null] +>> endobj +978 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +984 0 obj << +/Length 1651 +/Filter /FlateDecode +>> +stream +xÚÛnÛ6ôÝ_¡9“·ˆ)ê6,Ú¤ÝR´iÚxC[Œ¤ÔjeÉå¦FÑß!)ËñeAÀ<"ÏçÎPLJ?ê¤)a4pb?"~Èœl>òpò×ˆÏ xœ'ÓÑɳ˜9)I£À™Þ:Ü bà“’(HiþÖ=›¼Ÿ>×h1Ic…Ê`ªNÿžxsëß+ø¥îTÃO5ü¦§íEH°ÔáÄ}—(\€l1JhJ
.r¼ÖÜÿÑ¿/&â¶D°”ø–êåÓ”ahN‡z_+ÌÑÓio±Þ¦!‰B®ìõeôö½ïä`Ûç#ŸiâÜ샒©3q›ïjt=zÝó³ÈAª]î 'ÜOûǨŠñÑ;Å„†î·Rvòxâ,q›VÀݯ괨ÕsË[<Y5Kd9_T+<¼›€DÝáI×ດ +œ4,pYˆVÌ‹®h‘®1Le37xµ +È—ó?E×µåÍ„ùî²+µ”¸öVø»¶=j ¥Ê”£‰˜ÙdÇxʇ—æ„Ì\çïÓ‰G£¨‡b„¶¬@Âk…¡°K#ìò1bÒpà +¤/#ô¾p +qÆxrPºÅÙ¥bíÙäoŠ)‹²þˆâ.Y3_4²ìŠ
ëB\í1K•B¥×@±½æàjý†ijÌcQ”~¶…\V¨)é £”¤áfÈq ÄúùéXU‘u^Óæ*_[QƈUBÊÓq£\>þ?¶H²h›EÑv«Óq™[.ªÕ§ãWoÎ?\œO—ÿ6™
Gê^9Zvg×Ó½, !´«yQwªÆÞó\<¿ÈOQûcsz™=TáÇxŸÐÆÖÖ:®PÈ’Vu$¥›šžô38½*{@ÏM’e'@}u³MîĹJYPÒê+ez‘} +‰ÇU£‹Û`Ç«fñë$T>ÇýÒ0é,giGõјö0d4ñEÁý¼"weæ3%i·:á¾6ø!ð°z:ÀÝWO-ŠRåûF²²|«éÐ&ÅØ¡qL"ðÙ]ࢠuÀ·1öÃl +@ +¾oUáH
fc¨OŸñ(ot\T7b íCtefR˜Cj*Æ'+ÛlY‰3Ö(èÁ|¥X Û¢*
å¬\H5ð²È}¢‰a2RŒp`oÍ–ÃY’Àuôä•è±FHÍ«:õqš€È ø<Éfe•#§®¦Eâö;?ô»¶($ +žk? Çx4H¢”Œ™a• YX¹3a§„N jDŽ,zR´’póÉ6‘Í*ÙúñµÌÌ`†®n¥Pv;Æ=‹µ”ÅÆL÷©ÑõMm˜¡9/d¯;0Ûc3}S÷cÁåïÔraJü4e.꬀SŽGsKCä~ ái™=¤„Àó,Wé:g:ù=U_8HóúWðÏ×#5»PPÈ‹âGÿá‘„S¾ó=À[TCÿG¿o+û0_³±’þDW&endstream +endobj +983 0 obj << +/Type /Page +/Contents 984 0 R +/Resources 982 0 R +/MediaBox [0 0 612 792] +/Parent 961 0 R +>> endobj +985 0 obj << +/D [983 0 R /XYZ 99.213 706.052 null] +>> endobj +986 0 obj << +/D [983 0 R /XYZ 99.213 646.045 null] +>> endobj +982 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F14 847 0 R /F71 445 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +989 0 obj << +/Length 772 +/Filter /FlateDecode +>> +stream +xÚUÝoÓ0Ï_áÇö!‡¿#„4Æ@Œ
ÆV$ÐØCh³RÛlm¦²ÿž³}I«´iŠäœ}w¿ûðÝY0ŽŸ`ÞƒŠ9nÉÆóŒ³;ä|ÌIä$’oɼe¯>8É<x«Øè6Àh+ƃUž&×3Ì
@\E\e{boF§ÄwCª@›Aû+JÉÁy\/‚\ÅõS\Gñü¤ê¼Ñ¤³„ôyÇëqO"ÆÏ[9M†.wa•ƒTR<ßõ Œ!î÷ˆq6T<z™<1’p|ÀoåÀOÇ{üZÌÏY¤S<?ö` y›“‹lRóˆâëQ„fA¶Ì\RÚvŒ¼ÊB4†u¤qCJ +ç‚CØr+wÉh,;uåÙ°kt(ÎÇìú†³ òiÆAù‚‘æ ¼góLKô
ÝHûYv•}ë°“µöõ‚æ8—H8PÜlËôe5øB0Y€çîÿn)W@¡Í^·8Ë„B©ƒ0Q‰#L¢¢´ì¡(PEÁ¤/íÞØFï°mrk +pÚm]0ŒÂÚpu›À+ˬC¢ˆWø¥nª×Ý%w¢hÊxÝ^sµzš5á®ýà¼|HÄ¢œW«D–ËŠˆÙ§Ã:,å31gõ#ÙÔô¿'ù÷eSö`'Õ/¼£Å´™Ö‹tö³ªÕ,›
†0ƒçž¤^MÒvºÀ‰¥¸Â!!Щú)DÊrŠ. +§Išxã2˜Ã–_V·Õ2‘Ñ_ü—é×¥éä/SR+5ºDŠ×¶\/.< ûa.16Üÿ~Nÿ‡eý3]Ü‘÷$Rž¨ú¶ÇêûÂä`ºèõ´¹G¾i•LÂ[=”cÚFÐmþÕã,ùôªj&©X6…Ùª0+@ȶnÞ¬"ÀÛÝòÒ8²Ú×bY×]ͪyµh ?&´wØ_X·›Ú>Ô¸×ǦNyqËå¡«5ZË»‰òò)Îà,0gÿç[](ÐBï}ª>ã8›I"¾Ô¢Ÿ-#5h4±i-ý@mÕ#endstream +endobj +988 0 obj << +/Type /Page +/Contents 989 0 R +/Resources 987 0 R +/MediaBox [0 0 612 792] +/Parent 991 0 R +>> endobj +990 0 obj << +/D [988 0 R /XYZ 99.213 706.052 null] +>> endobj +987 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +994 0 obj << +/Length 270 +/Filter /FlateDecode +>> +stream +xÚ}‘=OÃ@†÷û“!Æw¾xT@H@®SÕ‰ +:0ñ÷q.—Ñ +E²|òã÷ul¤Ÿt–!QD +ÞN†àC+÷ÆV¢«H·bn²¹ºK%2äwðÐsRÁÈù¸onÛCÞ,¡¤‘Jh=«èX}h»èšëŸ5Ú&—|Sò×¥w±à¨swh»@
Ž,àÙÈY´b+;)E}WâcˤvgN殧3QA¡V×s#i6yÙزӀ1øq__f 8ên·†¥‡oÍI‡8ï‚f©¾?Í`^©aêºtž™Ðœî¾ÿ÷†=£·þâ YÏÛ3T¢,Ùýý³à<zø•™~ +endobj +993 0 obj << +/Type /Page +/Contents 994 0 R +/Resources 992 0 R +/MediaBox [0 0 612 792] +/Parent 991 0 R +>> endobj +995 0 obj << +/D [993 0 R /XYZ 99.213 706.052 null] +>> endobj +992 0 obj << +/Font << /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +998 0 obj << +/Length 1394 +/Filter /FlateDecode +>> +stream +xÚ¥WYÛ6~ß_aøI"…¤î" IÓ¢Ám³Þ§¦´L¯…ÕU‘JâþúÎpHß›-Xñø83ü8—ùŒÁŸ•e$x<ËY±T̪ö†Íaç§î‰H#^f0¾²¦"‰VÎÂ# o–7/ÌùL°(ËâÙr㵤Eñ<Ÿ-׿o·r0j\„"eA¶øcùžÎ$Q^äx†ÍÂ,‹ˆ´pYm?ôkÕhwBÌʨ$%x ŽòŒÛ÷}‹ø8 >/DÈfRšæuG_IŸµ4r%µËÑ
žºÏ}¡™éé[me÷èºYð€@öƒmõf«F-˜¥ÁrQ°ÀI¨Ûaì<³àŸ5¨qÓì*õWò +Íú
!噎ÌÞqM›Ÿ„ˆ½[Y,$„r¹FЉèYwJj·÷Î@íºdö˜¬°…áîã"aÁƒ-°Xž¹-¦VÈÑD ìî{BšÚËdÏA]k–e.)šÝ .™Y”"
4D+lŸ¬ã `ªe jìi,i«j¤v»È¸v19´#,ÿˆoj›„aiÓ;}çÀ
¨Ûà©í™™¢LTB(wU3-m%?cHÄYB¬”KõH+P˜Ä<x#µ`«Wé3,@ŽŒ!hhÕ%7M[¶ÝÀJ‡Ù¡tÂæÞ@ïE°f=õÅ•w‡cØ0Ü£ã‚9ÏÅEÛ{[lÓ£ÚöTp=·lf‚ÛåД.±sT£ò +ºS¡}gÛ(;rš÷wÆÕÚ¦<ò-§××PÄÊÎYS›KçE€²6Tî„'×K)!„*é‘[Æð£¬ØAöÒø\‘ÿw©éo{°Ýendstream +endobj +997 0 obj << +/Type /Page +/Contents 998 0 R +/Resources 996 0 R +/MediaBox [0 0 612 792] +/Parent 991 0 R +>> endobj +999 0 obj << +/D [997 0 R /XYZ 99.213 706.052 null] +>> endobj +310 0 obj << +/D [997 0 R /XYZ 99.213 688.052 null] +>> endobj +1000 0 obj << +/D [997 0 R /XYZ 99.213 372.321 null] +>> endobj +996 0 obj << +/Font << /F71 445 0 R /F72 448 0 R /F85 483 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1003 0 obj << +/Length 2190 +/Filter /FlateDecode +>> +stream +xÚÛŽÛ¶ò}¿ÂH_ä"fu¡nMžMÚ¤HmÖA’ %ÚV#Yª.Ùú|ý™áeÙò.ŠVCr8wÎÅÞÂ…?o‘¦Â÷‚EìFÂ
ýEVݸ‹œüzã1ÆŠQVœÿ®o~ø%ö©H£`±Þ.‚4aìTDAºX矜Ûå—õo-iŒX±ðd +zDø8ðl¤Ÿ) ÓñÂØyzúöÃRºÎÇ+Ú™0Þ\Éè©tPD„¦×-¼_ÝÑzDÝ +›†|ÁœmA˜@¬¦Í®¯[¾SΈ£›¤ôœ÷‡Œwa[oÁNUC¤X0yÈ +8V=¤×ÍÒkôš®Þptµ•ñÝžbÄ
À)&箈|Ž—Zb`ê0È‹.:Jñ¼$©ËÆìT]ÇíBì éÇçõÝ´ +‹àF5t=AFÁä
móE½¦3T¿(ÄíÆßÜÿ]B¹µ.ºè½Bè©lïU âm1„Tf +câS—ŠzΚ +:º,Æ+™ +Ì‹{ç°¤8`SçG‚LüòѼÕ]!Ãø_Ä/¨8¶³†£ŒœÛ¡Å‡]G-³WxŽOëy/ˆ›‡¬rЄœ¸P4ü3ÕŠ~ÑLܽ¾# +|ˆ +¼žŒºÂÈíÇ‘öLþH¬Z +‚±@aú¼~#6*ûÊÂ!ç,›Œ7ºÐšg¾FíŠOÒ(¤åŽG3ÒÌÌ‚uZµÓ çš1:žßúL€pž°ZúH§+àœâ—•ÕìQB1cçd|e]:«JÉÝ@÷ؤèIWxnüä¤8ÁylR´(W&E,FLŠO1'ÅKî×&Å)ûÛÑ—Ãâ™Q±?Çvž!¯ÿ`šBzòÉŸ¢O(ó_¢¡yLF"(`(/ÙßJçœþǼùgendstream +endobj +1002 0 obj << +/Type /Page +/Contents 1003 0 R +/Resources 1001 0 R +/MediaBox [0 0 612 792] +/Parent 991 0 R +>> endobj +1004 0 obj << +/D [1002 0 R /XYZ 99.213 706.052 null] +>> endobj +1005 0 obj << +/D [1002 0 R /XYZ 99.213 601.213 null] +>> endobj +314 0 obj << +/D [1002 0 R /XYZ 99.213 486.186 null] +>> endobj +318 0 obj << +/D [1002 0 R /XYZ 99.213 329.1 null] +>> endobj +1006 0 obj << +/D [1002 0 R /XYZ 304.77 187.998 null] +>> endobj +1001 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1009 0 obj << +/Length 1536 +/Filter /FlateDecode +>> +stream +xÚíXKÛ6¾ûW>É@¬ð!ÉRÑ
›vƒ$›fôæ@K\[%e³ýõáP’½vÓ¦½XÃyÏpH~Xî1øã^š‚KoÅâ€EÂ˪ó¶ ùyÆÆÒ©,t~ZÏ?_ /
ÒXzë[tÆܤA,So¿÷ãÅ2b~`ù@ÇœùÏÖ/¬ù*HWh-À*J Ú=],… +)ÅÕ‰C$<vÒWÖÓûûÒùCšø—–óÚÒë…ä>LBî•å^Ûß×cv¹Û26.‚ÜClʧÙûÌË¡/f,iâÝÍž¦^5!eÙѺœÝÌ~}‘¶Vçö ”`·J¼¥€ã£}:ݯ0¾ñ* X:nWm&ï4°K?f*ÛéWM®K¨—1¿È/æ{Óä}Ö-Ü·âùbÉʪ}©+]wª+šúbþòí»9Y=Á6yœiyKi’ÿÛ²owWu§ÍgåBìšÞ´sÎ!Àc2eg¬®ëË/:ë;í·ÃUw1/êV›î
¥:ÿÏnú}Ëÿíú§OÜD0–«cWÐÚ=¤}O¨U¥/æÏ°Ç7ÅŸØç(M}hTlÎØàhÉaû¥8võxÚº'§'J<+R¾®K)ØÊWøIü¶¨·¥&Ö”®Š–4ŒÎšm
YåÄßÜ¿Û9³—o!óßÑÂæB +‡SÍij8ÿ"ýõnp=Œ#ÿ°xd;‘S‡+WÜž
:Òc£NË]š‡-ªú¶÷Iäo4}[m±ß5ÄP5}˜Ðä±Õ†Ÿ¤°´4zotEAßœ¨Í–t¥¾U_‘jÝW›ÁKsë¾›?tÖµ´ Ø1œƒ2¢;ÉÎ…Ú‰ºŒ›:ÓÁ™é|Š¶)Ƕ7¦SuGËnG™"‰ñR5@Ê•vÙc§vÆí`¥œ½rn)s¢3b +j')AÏîbÝš¦r2úŒƒ†tgFE8¾l¦S‡§*c_œP?ñk³‚Á’qèß4ÄìÔǼ3šV™2Žª›Î)8Ŷk´B\Ç®úlG”ÝøÞÃM5x£
RÕ9¦h?:ª¯kªsë»ãd¡å¹`œ£ñií^¹m†ùæÃJç9dðøEôÈp¶á"bp‰<¿zo•€‡ÖžóWƒ÷™L×X‡d‘³“,ŠCæñOâ¾Õ-IUíLì.\]M\b…þuïdªÜ6¦èv-qPžÃeiª¢ï<nw´hÀ÷p^¬ã’S}×,Ï4‚d3Uâ'‡9¯œËÏøƒ×—dÂ
&Š;ªUPÙ0C!ÌÐo;]׉ùÔœ÷¬©(Wîø6Îw_–pINÎálkêÆt†W”%q7Nóaº§õ
¹3wAÀ1æ¥ÃŽ²qGŠÝõ„«¢¥ï¶ir2¿m±úVmÖ^u°)uKwt9 @
rmîKžõ§^›{"©.´Ü83£oÁñ|ps¦2íöÇ·+l‘´0 +¤|:2¨4µ‡çD +c)ß±ðW°0ûn\/ÏAá3ýg(,Σ(ÌlàgB(ÈšÒâqœ&(Œë“ÛÛ4ÈHex;fÿxJ,fEs8˜Éï8øÿáàDáàDŽ8$Ï>¸‚Ѳuz‚A¦œÏ'Â`nœÙ‚;bøA0oŒ'<æ'>óO‡D!¿úÏšIåä5’Å€'ز(z(‚{;dé™HݲÄ%endstream +endobj +1008 0 obj << +/Type /Page +/Contents 1009 0 R +/Resources 1007 0 R +/MediaBox [0 0 612 792] +/Parent 991 0 R +>> endobj +1010 0 obj << +/D [1008 0 R /XYZ 99.213 706.052 null] +>> endobj +322 0 obj << +/D [1008 0 R /XYZ 99.213 480.785 null] +>> endobj +1011 0 obj << +/D [1008 0 R /XYZ 99.213 355.007 null] +>> endobj +1007 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1014 0 obj << +/Length 396 +/Filter /FlateDecode +>> +stream +xÚ}SËRƒ@¼ïWìŒû†=úˆ¦RZ>ÂÍÊÉÓ(!eåïØ
ÑD-ª w·§{èN^œZ‚K3Lš„ÑžÜDß8)9»Žµ`¤éœJk@Çu,ii:}.ÃI:êh1ظeÅÀ•DÑötFFçxçÁC‡Ó:üÔ×,(¹¯6a¤Y +Æ•Ûl²uˆQÏÜ*ÏjʪñOÜ4Uò=±©üv±Í—J÷ÜUÛz¯–/=?+§Ô«ÍÚ£mY®Ê…ïmÛül¶˜U½;*ž¶Å8
L&â +¬RÝ»nÞ³|Œ)‘ ¸ú÷8PN¿=éEZ3mŽö:uúØË|endstream +endobj +1013 0 obj << +/Type /Page +/Contents 1014 0 R +/Resources 1012 0 R +/MediaBox [0 0 612 792] +/Parent 991 0 R +>> endobj +1015 0 obj << +/D [1013 0 R /XYZ 99.213 706.052 null] +>> endobj +1012 0 obj << +/Font << /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1018 0 obj << +/Length 230 +/Filter /FlateDecode +>> +stream +xÚmMOÃ0†ïý>¶‡˜8ß¹Û$„¹!ÕX%¶Umwàß“nL0å[~ü¾¶ xzÞ£ –äZÀzWqøH•UE3¡„Fò&ÅWŠL…Š{` +·¡ºYZÁÑ áýä¢C²Âæµ¾Û¶ý‡† ÍkÛ¼…‡Ò£Ð:›{80cP$ÉŒßíÛ]·.øËóãÜ À£/™—h
ýðO‡©°ã±ïÃ7%íö埶ÝX¢!~ƶ¡zŒ˜E«E8o7í$*RWן ö‹”å/æ’é0î,’gÓö¯ÑéŠÿ¾)ä]endstream +endobj +1017 0 obj << +/Type /Page +/Contents 1018 0 R +/Resources 1016 0 R +/MediaBox [0 0 612 792] +/Parent 1020 0 R +>> endobj +1019 0 obj << +/D [1017 0 R /XYZ 99.213 706.052 null] +>> endobj +326 0 obj << +/D [1017 0 R /XYZ 99.213 688.052 null] +>> endobj +1016 0 obj << +/Font << /F71 445 0 R /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1023 0 obj << +/Length 267 +/Filter /FlateDecode +>> +stream +xÚ}Q;O1Þó+<æ†3Žó¡”GˆÒ,¨êD˜øû8iî@P¡H–/þ—ÏHŽœ‘…HÉ3¼Á›L®•éˆ±CƘ‹¢Î®"CÆ,”WpÄH,2ƒÍPö[½veÕPs¬ ˆÆYѬӛa¬Ï¥ýØúÒúeëŸfîì`:;±ã0zÒX¤/ÿ±Aöú܆‘S·»—jõm»_œ0ŠèrîìM3Z·zW±jYædæì<ïj.j»#ØK†+Ehs‚Oé ”c/]ìßïj£Ö³ÖqàÈ:µ† !¿ÍrúwWÉ¢3î䪬¬1YèˆúHŸ~¿Ì³CG¾e&§/$¿tendstream +endobj +1022 0 obj << +/Type /Page +/Contents 1023 0 R +/Resources 1021 0 R +/MediaBox [0 0 612 792] +/Parent 1020 0 R +>> endobj +1024 0 obj << +/D [1022 0 R /XYZ 99.213 706.052 null] +>> endobj +1021 0 obj << +/Font << /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1027 0 obj << +/Length 928 +/Filter /FlateDecode +>> +stream +xÚ…VKoÛ0¾÷Wøh³*Éòk·mÝÐaö>¨ŽÛsíÀVÖíßåGZ7C¦(’ùI¤"?”%“" rž1žÊ º=ãÁ
ì¼>ÞBÉ”‰2ye3N¥bŠ—A¼ˆð|svþ*ä,Ë’`s=¢¤EÁDž›í÷ðÅNïmÝG±LyXD?7ïÈG±¼Èчq–1 !Ñüm;XÝ4Úš®%ÝnIøTÛÃ~ + KTæä‚É$q +ö.Üq‹ÁÖûÁë½ê0˜èñ©Lô¬dãéÉÓƒ0ÄÊ|®Çt§¢ðxú ¬ß0·µcëìåfºùþJÃù1Q¬6NìMâ…
µÆ‚!R–¥bŠƒ¤ü2½'DÉDRœÄmNB‡á uÐ1 [3ØÞ\E»dþ¾iqºþÙæñú½Íë?7× +p¬ølëGÉ—ï}õ]u¸[;ÌM"X™¦!†%Ï U˜.rl•i¾¬±y±ÂæþŒùØQœpR®£ðFVF7¤Œõ»îî¢0vÊË.82r“œgøÝY»z~΋bƒÈ}Ò|Œ„ÃÈÅ('W¥üÆÜ´iô•wÐÓ.% +]WÕ+óçÁå‚++`NâWzÖ@™ß’ +hpMËoÑÛK’u_í&òr5pn¢‚fXli.·M§·Æ#HLû|™Ë€S¹Ä¹Ð4Ó@_ú4¦úËçÚÝÛ½X€
+¥Ã˜Ã"2¨©¥/¹‚pW_‘€ŒYdãÛ€ºº×çžÇ$x¦¼B.hiÌÍ>bžœ ¤G„£ +ë`õ€¬Â~ŽæÃJ]•F <÷.Ú^»lA&@V-Žó‘‘áfcðXþÒº1¿ðEðQ>GŠÿßÌžÖ®®%Ì"q0Mã32;6ÚÚ–«è +õ¢wîY¥»{3°¿MÃc/B‘0%ÔÉ8›<O ü*¦ HlZÞÿs=DúRcqˆendstream +endobj +1026 0 obj << +/Type /Page +/Contents 1027 0 R +/Resources 1025 0 R +/MediaBox [0 0 612 792] +/Parent 1020 0 R +>> endobj +1028 0 obj << +/D [1026 0 R /XYZ 99.213 706.052 null] +>> endobj +330 0 obj << +/D [1026 0 R /XYZ 99.213 688.052 null] +>> endobj +334 0 obj << +/D [1026 0 R /XYZ 99.213 477.043 null] +>> endobj +338 0 obj << +/D [1026 0 R /XYZ 99.213 383.023 null] +>> endobj +342 0 obj << +/D [1026 0 R /XYZ 99.213 242.328 null] +>> endobj +1025 0 obj << +/Font << /F71 445 0 R /F72 448 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1031 0 obj << +/Length 2173 +/Filter /FlateDecode +>> +stream +xÚYmsã¶þî_¡o¥fN(@ +«w›…6©ÈŒƒu +au±¸[ÿ’¼^þv÷]Ps¢p¨å„Ê4,Š£ß.W6M®Ãßø«’»ð÷müsœ{ÜŠLÇÙùred"p†LÞ_l¤œÈmÔý!¬xË{hÅ›~‚|Xí.4߇¿ÿa¦vëÿôß\,OE®sÞìöâ,`4íxôíxÑ4ù‰¯úWoïFs1šýùê—ßäb
ŽùîJ +]ä‹GhK¡Šb±½ÊR-ÇýæêöêÇq-³š5ç[“f"“ÅËÎå#Y—I®ýi·®ºå*5E2<TØÈ“uÝ]}¿Lerêv÷—¥1IOJ}{èÊŠÚ›¶áÉyâ;VõLJa:ÞÇÕý@ª[ÿi ¾fýÃþd÷"¹ýñÃ÷~Oª7ßÞð<H÷ñ¬tÄš®¿R™(²,\èxìW¨*ßÓ·h—©IwÔù{·ÌA義¨› µT +ää’§ö@*uÓÐÄ]U©5´4æ×QðˆÄÉÒái_õ$o7$â¾PºìÅgþ2JuÏ«ï÷M]ú9.°hËhoêûÎwOÔÙwíªrˆF4ÒúÉ +à,;ôÃŒ^½¡/_]&òÉŒ}Ðî*XM ¢¹»‡ª¯¦Žb¶ƒ”óÍOg"Õ@L…¯‡©¬&:ÑÄŠ*¡øÜ +NjRûâ¦Qçr×ò·»îdÛ_ÓÔÂuÃÚ%µ6ð5 ˆË¡1Â×Ú˜fAZ¶»Á×»žäì|C~¥ÆçCÕÕUV¶2ùy©[¤e'.M'ˆyQƒ›¸Á“¶m%› +øXy}è:Ѓض&D»Q‘ÉÔäü*þ& Î6RɦÃ7.þxÞÝ'wAö«÷¶¡ú½§ßÅ_"Ô| +Iu”e¯ßDp¿‹»Xx¸¹ø«[@>_!ònW’àÚPeÅimÉ\XÀÏïàK¦MÜò`lLo&\ÞÅ×7﵎u®ŠK£¡:Ùû|éBlc¤â|(Ù¿ˆÜ æQêw3Á×Þs†×)½õ3æ†×‡Ëò¯³¶Ì³©µCí“%×;ÚJ»PzŒû¦óûBµœ}—%LœìK«â£’£IŸáFÉ|øT¤ñšgåW\í8#´>ÃÃ@u‘#~‘ï8É’ÅÒ
U+n†gŒ›Pìü–´tÁ>Ϫ³úvGm,«¡*kìsèBÞº÷=÷ˆZÜXÿ9NrÏýR™k°|öâo(G•ËÿVäã"h4+Ï7Š?8_îô_ø.)endstream +endobj +1030 0 obj << +/Type /Page +/Contents 1031 0 R +/Resources 1029 0 R +/MediaBox [0 0 612 792] +/Parent 1020 0 R +>> endobj +1032 0 obj << +/D [1030 0 R /XYZ 99.213 706.052 null] +>> endobj +1033 0 obj << +/D [1030 0 R /XYZ 198.293 648.887 null] +>> endobj +346 0 obj << +/D [1030 0 R /XYZ 99.213 597.378 null] +>> endobj +350 0 obj << +/D [1030 0 R /XYZ 99.213 379.359 null] +>> endobj +354 0 obj << +/D [1030 0 R /XYZ 99.213 239.818 null] +>> endobj +1029 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1036 0 obj << +/Length 1705 +/Filter /FlateDecode +>> +stream +xÚ•XëoÓHÿž¿"ê—s$²Ù‡Ÿˆ^¯WZ´ÐœîNÀ7Ù¤~Ûiá¿¿™]ljÓ"„Æ»³¿y켶bÌáŸ' “B#2Èñ¢ññv^„å˜Z–içÏùhvÉqÂ’Pç+„ñC 0 U2ž/?yñdp™_ßÑ¡àÞËÉ—ùs<bI„§eÂb?ixît¬ó‰@9ÆNŽ™”‰å|7 +ügJï +eš_éúãäGü³jÄ„ïP/
Ò{ƒtñÚü÷Ê,ým~?š_k’¯Íï¥a|?”FàÛÈb_$ƒÃ•Û%¤·éü gTàløÌn\~}ÀEq ,Û9,äá“nöÜ<ÄàLÄÒr±á³$N:—÷„tçÚ¬’Lˆdk§@–Ñù¼‹×.¢B0@´~}úÂÇKˆì7#ÎT€õ’d\Œ| +‘ðŒ`IPK¯uÓÚ\÷ü Ñ–~…{ö´S‡xªê«]°6™ø^2ñCÂm†°ÎÕà凨ôTpŸ…q4†c‘¨Ï3èî‚ó!úÙ!ô‹,×NNÀÉCÌéáêù®¯8Þô}éŽ1æC f(‘tu̵#‘ÒÞDÖ_WD?è»·‘´€åňº0á,pf·Ï¿“ +{ýLJ€ÃÐ TÄ\±é¸Q¯-ÇÇ^ÿpÜÓû ìCZ·³Aïó¡;ú<Ú•½×±ÏÓ÷€¨‡O¨1ï«Öú+ÏÉ!}÷ƒc³.‰”‚îéï$‘ÎuAJ*¨& U¥èÛ&«©„óÀ–pX_UyNΧ¤ƒ¥ÆVa’ +n0 +Xàû‡qb™öxèâÄ`Ôp,(ÐÅ©Iºƒa#‚ÄT£§„;žt(¾½!T„Iº/þº'ÝzÖZ´5¹«_W˜™Hü3I”‡Ù‰§ý æßù1¾§þã>Üò+úÑà|hYЈ'߈öªÜ=4YPâøH0~DKÐÅ+ˆGlÇG›v5N~?Ðp_4Æþ3XÄ K@|Å11ÝÕÕ}¶Ä"ŠØ‹Æ©ã£ùé²ZÞ\Õ †v蔾(˜ÿp¾@FZRæ\·u†éº?@£ç³?õsœÛþÈ«EšßVM;kuÓ ++eFb¦äÌœºÁ}[ÈàæQ-iŸ„àx|tJs•\ë=‹f€¶€4³ûîãhö„“û˜—õÖaˆ¸A7vˆf÷—ðÎ`Ú\Wõ-$^KOIÇðK¨o³R¿nuñ(ªc裼’¯O„Ølˆ{žÛïÛ^üJ‹?w•÷'üº*t›f‡ ØTnœVï±à§ùÆ
¾vD¦§,dvNÁA–ü÷Ý[;ê4Jz³ +ðÂô°}ÞÀ¶}Øó1¸i¦Hóx0MbpqÝêüŽ(3¡UÒ2]ëGžÙ±b¾ðŸü»Ñ–e0f(x4Çú.4'÷ÂJúÈÛ½endstream +endobj +1035 0 obj << +/Type /Page +/Contents 1036 0 R +/Resources 1034 0 R +/MediaBox [0 0 612 792] +/Parent 1020 0 R +/Annots [ 1038 0 R ] +>> endobj +1038 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [411.485 534.214 433.403 545.118] +/Subtype /Link +/A << /S /GoTo /D (example.8.4.1) >> +>> endobj +1037 0 obj << +/D [1035 0 R /XYZ 99.213 706.052 null] +>> endobj +358 0 obj << +/D [1035 0 R /XYZ 99.213 600.815 null] +>> endobj +1039 0 obj << +/D [1035 0 R /XYZ 99.213 507.38 null] +>> endobj +362 0 obj << +/D [1035 0 R /XYZ 99.213 219.121 null] +>> endobj +1034 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F85 483 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1042 0 obj << +/Length 1824 +/Filter /FlateDecode +>> +stream +xÚXYoÛF~ׯ Œ<P@¸Þ“‡'Hs´ Ò4Õ‡"É-Q6JTHʉPô¿wf‰—lÈ{ÌÎÎÎ7'™GáyIB8^DCB÷æ« õ®`ç× ³% Z4¿Ì&§¯#î%$ …7[zBq"U|ŠÄ›->ù/¦_fo5YD’©"¤ +N-³×Ô–sZ5fT. +iÄQ"5… ¢LÄ#àˆ˜QÑ¢ +b²æÉÁžŽpŽBÃÐÒ¦MSå—SNýmpâZÀ 8$^((õPš]g†2VJ·!ˆ{T„ÓH +ý5àœÛÅA:Ÿg»X®G®K$¡ŒYUVƒùͳáU +ñc5¼›ÒüßG$½xmw‹rîìåXö¶[žŒŽp€!yÇߌ¡“#^É„¨„™£’¨ðg!‰ØxäæT(«SòO$FÎ9à4ß½t^æx1Òèó®=è]ýn(†°3Jx¬î‡»$œóî}gc dRåµ^4|vÂIDc„'w§G&D„ñhzÌ¢ƒì/MD—Yê+ÆCäRL `-e A…ÇÁ#el¤£ñ¾6XàSéY†Í2:.è4ŽYG´—Y=¯òö€>LIH8T±aa‚8ØúäáX‚ÓÃ3î+—¥>†¥!ÒÄ×ê¿ÅS‚vcűhÊ¿HçàìK×v·=¤”Ŧ4uqN¸Ìƒ
Öª¤•ä5]iþ_ÚyQ¦]íÈÄ_VåªÇm‘WÙ¼)+{K•D±l¹™2ågæœãiƒJ¯J™o«Ê$…½(d¨P(ðAC2‚grÙŽƒˆáhƒñ +endobj +1041 0 obj << +/Type /Page +/Contents 1042 0 R +/Resources 1040 0 R +/MediaBox [0 0 612 792] +/Parent 1020 0 R +/Annots [ 1044 0 R ] +>> endobj +1044 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [459.03 645.049 466.004 655.953] +/Subtype /Link +/A << /S /GoTo /D (chapter.3) >> +>> endobj +1043 0 obj << +/D [1041 0 R /XYZ 99.213 706.052 null] +>> endobj +366 0 obj << +/D [1041 0 R /XYZ 99.213 631.471 null] +>> endobj +1045 0 obj << +/D [1041 0 R /XYZ 246.477 547.817 null] +>> endobj +370 0 obj << +/D [1041 0 R /XYZ 99.213 209.931 null] +>> endobj +1040 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F103 753 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1048 0 obj << +/Length 1771 +/Filter /FlateDecode +>> +stream +xÚÍXKÛ6¾ûW¾T*.¢$Ù +&Eg†óø8$ó(ü˜§áLx1•Ü[®'Ô»™Ÿ'ÌQŽ$èÐü4Ÿ\¼Š¹§ˆŠ„7¿F6aÄ"‘PÞ|õÙOf¤>1ÿ²éGŒú/g_æoÌò˜¨WsE’0i¸îùHç3Á ׶rXD8WŽò퀑"B†-Ÿ âþ”iþ¹eú‡\A~ÜÊ1à6\ßNï§W¯Mó³úÝü4ÿnØ}mþßÂwCÉQ¶ï«äˆÈDºÙ_
+Ãïm»ŸF+ír˜\Í[çµî•$Ë€ëþš|þB½¸ùÍ„¡ïô)aJyëIÈ%ôb÷O>M~kyٹȳ«Æ"%°.N¼€Ã¢£hFUH¸€6ŠBUT°ÝnP¡òŽmðt[[]Ö™®žáV= †Š½€…D‹º${0¥~©«bW.õåt½ÿ€33æ›õdYl®³›é…eDïÇ$Ýõ.ɇV“ïáôUï/§·EUOí÷]šïô%°›f‹´Î×ÚM9vÁÈ/zfñ¸Tœ$lÌKŒQ"!È¥˜;’CÇ‘á1ä¼Ïù†¨¡AÕ泄úé"׳€C$D<™’þy]—ÙbÆ©¿«ueg‹kÛ‚Emäwjò6ø[3>É!Nx9BëµÞÔ}«„LD&^g?ÃM'`INWÿž+"¸‰’Ñ\ ì`ãµS¼^¶ç¨{¬@!È*)ðí!WbÖ1—„QpX#¦Â"`×æ}‹0Ü-
½Ó¨è˜WCb²«âK]-Ël[gÅfà2Xpó( +¢¯X¤Ð/¼î[ yt½õIßÒ˜ÊGq°õ-À+‹¢áH:éÖ¿h׺µ§Ýёܸ5”À<6Ú}Úêev
˜'\z›Îq³.;ô¥<wÓuaÛ…û΋t¥WЕ]ë·UVêe]”NJ©s +äxƒcxé=÷"€jS&ºÕcDIÙ¹½‰¼ÇÜ“ +×›ySô3(« "ãD4U½ßê_ÒÍ*×eSø‰ +VÖ¦bÌÞ¶@KKãtµSxnTZ›»$øD$Ç—I`iøñ˜wøñXøáTµè‰g¦qÌ*KhîïZoV•%6ÅÎ!ñ˜Ié;úeºM°,Ïl%ˆƒˆ ¸ä¥f›;Š> +‡@*±ç‹tùõr:‡`ÑólÝ,pï)ý\¶É&;›Ê€_ ¿w*[|=Je4ÝmZ5O¥voéÑKÆ“X +T>¾‡áLÒB_ø=бcâf÷ +endobj +1047 0 obj << +/Type /Page +/Contents 1048 0 R +/Resources 1046 0 R +/MediaBox [0 0 612 792] +/Parent 1051 0 R +>> endobj +1049 0 obj << +/D [1047 0 R /XYZ 99.213 706.052 null] +>> endobj +1050 0 obj << +/D [1047 0 R /XYZ 252.454 591.036 null] +>> endobj +374 0 obj << +/D [1047 0 R /XYZ 99.213 399.786 null] +>> endobj +378 0 obj << +/D [1047 0 R /XYZ 99.213 210.922 null] +>> endobj +1046 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F103 753 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1054 0 obj << +/Length 1962 +/Filter /FlateDecode +>> +stream +xÚÍY[o7~ׯ˜5ò ;4ïCM±Ù$mSd³m¬îKÛ‡±4Ž…ÕÅÑÈMƒÀÿ}?^æ¦Ñ(êZ( +5<sxx¾ï\H³„â%ÖÎD’QM¨âÉl5¢É{Ì|;bQ""iKæŸÓÑå7O,±Z$Ó›D(N¤Ê Ç-l2ÿ<~1ùuú½ËˆÍœTF˜Pêf¿›¤šŸûÏðÉÆSÿùÊ?yW¿Û,¡‰ÕÛf’*:&î
:~Ý[ˆeÄèJö×x×,.úÆ?xÍ€¶©¾öŸÿöŸxSd2Ê¿õò/û†NŒ0q±«ž-pšÈâì«z£|üSÜ:äG¯¦µ»k@ÑJ:gýü+Mæ +N)É >Õ#B¨ƒü¨„*çŒéÄÐq~½,&)‡û
‘Oáoàù|·Û.®'œŽïwEf77á{w[êZ;c”p<Nþj÷é®ø._Ï—ÅöëpA6JËbU¬wûHD¹LZêïÚ¸=åÄrûe˜„¶Dhs¦4<L™$VCî…(t…Q”ÞSŸ†A
%ˆÔhe- AU‰V ©htœ¾ïf€?…G¥01*;n¢†uL|Y”³íân·Ø¬{YM8x|VL[‡KŒ›Çc+Q™:Õ®(=ˆ-ò›4¶v\'*l'™ÑÞq. !mt=²Ò=ËÚWC*)ÑÒö®¸)¶1Âw›&Âý`¯Š~ð‡Á„Ýž:/Þúïïµ +öÚhô§ +'§ãm´%™F«x$Ldmtªd¶ÑêLm4ROszhÍ G¢<ÞEÃF™¤µŸþ¦qvÒ’ý.šfþ„.Z0Bq$J¥Í°S9ÐEã<kI™ÌN颱[ð§¥ô1]tPvÜDcB0¶L<ÚEWªÏc @FµæDÞ—±=KY¨±Å‚Î9\¶2Ó8 +ãƯ~ÏWwýF^RäH¦àJ¤)7±}%ÞµïÙ¢tÚïEö¾Ê +5Ö¿ r7©¬»vïf/È_Ž—8ƶW,o7®ÆËè‹°{ï°V¥‡^”³ºÞºWÿõéêòñ €± +%xèÁþo0Slj3ÓÉ
Ý~¹`™;Üks¬CÏ+Ùár-¨&èÏ¢„óš–ûÎRÜ%b›4jª•þìè©¢endstream +endobj +1053 0 obj << +/Type /Page +/Contents 1054 0 R +/Resources 1052 0 R +/MediaBox [0 0 612 792] +/Parent 1051 0 R +/Annots [ 1058 0 R ] +>> endobj +1058 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [406.22 395.281 428.137 406.185] +/Subtype /Link +/A << /S /GoTo /D (example.8.5.1) >> +>> endobj +1055 0 obj << +/D [1053 0 R /XYZ 99.213 706.052 null] +>> endobj +1056 0 obj << +/D [1053 0 R /XYZ 243.488 686.279 null] +>> endobj +1057 0 obj << +/D [1053 0 R /XYZ 252.454 530.081 null] +>> endobj +382 0 obj << +/D [1053 0 R /XYZ 99.213 439.633 null] +>> endobj +1059 0 obj << +/D [1053 0 R /XYZ 99.213 381.334 null] +>> endobj +1052 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F103 753 0 R /F97 676 0 R /F105 774 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1062 0 obj << +/Length 2342 +/Filter /FlateDecode +>> +stream +xÚÍZÝoÜ6÷_±ôa8ѤDRbÐç&N‘¢ùhì{Jó ke[Wíj#i“úŠûßo†C¾7Fn¬FÔhf8ó›á²Xpøc˜/‚EÈ5ãÊ_$›3¾¸ƒ'?Ÿ Çá9¯ÃóÓõÙù‹Ð_ft°¸¾E1Rû Æ0˜Åõúý2ZyŠ/™ýU
_>_}¸þž2âÛ¾a‘Œ@¾w±ÖëU €jz„f¾oç«‘ Ã%r<í/ߢNûë//-ýn,ô‡ýÏFRC&d#õ•ôÚJzñÒ^~¶Cÿ´¿ï쯛’/íïËøz¬Y‡àÛÐɾiÖLEÊ=ýÕʸ´ò^æÓØBÚ®PÂÙåõ!x‡ð*¦Á3ºgï?ðÅÂüËg‰ŸæL³ØœI_ºûüìêì·ƒ,z¦ôÖROL>\2Jx¬Y³žáó€…Q¸ðt(X¨I´5L
ºçzñe|“§+ÏïGL?]yÀí¢®Ëìfåóå¾N+zZÜÒµ¾OÉçàåc‚3ÿàöï×qWžLÒÇñ%àÑw¬ižnÒm=Œ +àÁùòº9Š°t^R£f=ÿÒWñ®9‡é +×0›°9¤»lbúQô„ +lû]`gà³ûxW71˜B‘À;Í´r'Nø ¼sﺧvŽÛë°ð?‰ŽFà…>-„ð÷ô ÒðÕ6DÍœ§´jpïsÁTt8æêîù…u*QëôwÎýmJw®ÕË8©÷qNôÕo¿QÕÐ}ÂÚŒàq¯eó¨°ÞGzWIºÞ—©ãDwO0.x%hX=»]ž%1å: .° 1q 5Ã…œÓ‰m ,AÉ>Gài¹,nþU£¢óª ¡fªîÕ¸"ÐSÓˆE.Œ +aÀ¡†^ÐV<¡Ë.œ‡pTÒNƒÝA7{ÒϺÆGVz°‰è•ûOÿ¸3„âØîim$´†[¡Ÿ¥ufCö¨®.¶@½²v*¢3ß‚FïSÜ +¨É©ÑFËý€A]úåŸñf—Ú +„ŒÃê@Î}ù…ºÙp{öQ%Šl–=¤–EæõtjgÃs\£ÂoqQOcuï²ubo
K¾Ól¯"3?û:0ÎR㮉p™ñƒíhLcUZÓ½Ícp™'5¥ +Çr\UOÌäÙ4¶`àÝ8Ýå‡×²Œ¾³¸ÏqtSÍ}•›D>¼¤Ãcš–¡jØ&t>
ƒy2êê¾Ú¥Ivûà>Q`kÙ¾„r`š¯|„{âê +
`/®´cÂC‹Ñ9×IÉB¡Ž+|ØòŽvB‹MÌ¿y¬ÍÐ'¿Ks0âSJw»;K¤nË–eÕEZg%ôEù@·˜xmŠ9~uú
T²/KlœIb–;Aûʦ5’±Ó‹›ÙÊwí„‚U›¾¾âçýHI9¡ÃÕb„öÆ~ùáÉw•EÝ×ÎqI>¿H’b¿ÙŸ›üÉù«Ï#å<ƒó|ð?z[ë}24ÈM°£{\@û>l#E»ŒÔÁ™_öä£L>ÿî¯=eÄ>¢–?\ìvsøG(`RÈ£{¼–eTf®Yt‚†j5ÞàJ&¹™Ðô_ž(ósendstream +endobj +1061 0 obj << +/Type /Page +/Contents 1062 0 R +/Resources 1060 0 R +/MediaBox [0 0 612 792] +/Parent 1051 0 R +/Annots [ 1065 0 R 1066 0 R 1067 0 R ] +>> endobj +1065 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [494.815 423.501 501.789 434.405] +/Subtype /Link +/A << /S /GoTo /D (chapter.3) >> +>> endobj +1066 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [136.09 324.871 158.008 335.775] +/Subtype /Link +/A << /S /GoTo /D (example.8.5.2) >> +>> endobj +1067 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [230.489 309.927 237.463 320.831] +/Subtype /Link +/A << /S /GoTo /D (chapter.3) >> +>> endobj +1063 0 obj << +/D [1061 0 R /XYZ 99.213 706.052 null] +>> endobj +1064 0 obj << +/D [1061 0 R /XYZ 246.477 686.279 null] +>> endobj +386 0 obj << +/D [1061 0 R /XYZ 99.213 467.722 null] +>> endobj +1068 0 obj << +/D [1061 0 R /XYZ 99.213 310.924 null] +>> endobj +1060 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F103 753 0 R /F97 676 0 R /F105 774 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1071 0 obj << +/Length 975 +/Filter /FlateDecode +>> +stream +xÚ¥VÛŽÛ6}÷WA$ ¢y/E M¶M‚ÜÚu‘‡$´ÍµÕj%E’wcý÷/²ÛEŠÄ!93œ9g84I0üH¢5¢„%„š¬ng8ÙÀί35ò¨’Ot~^Ìæ¿Hšh¤K7 +(â…? ¦“Åú]ú8û°xîÕ$ÒÒiID8§n÷i–š>òß7ð%é¯üÊïÛãq6Z«,/pŠœNŸD$RbÔ}å=^Ç3‰‡¾ð/bàmáÅgþûÚÁ’Iõ_yý'ç)ŠSñ°ë³X +{" V—¸-(GësrUqL0†$¤B8Pûcÿ±ziZÈ +˜þ0†4‰ ‘"p·ÈˆJËö‡C¡µ!'BÆB.ë•Ír¢y:l£`Ú¶*Wf(›:,tM3 xv€Þ0š©Úº¼Éh‘Þخ˨¼:Rã1û6žóømÆqZÖëÆYÞ÷ßg9ÅEú6S8µK?iÓÅÓÊegº=˜‘¨W¾¾ò˜RN:ä_öAeiû!HCÆ]oƒ` +šsãtGË[óWý:lIG’é÷Arxºqµ5õ&y4§€ÛTÍTUö +o̱›}kWÌ]ßå\&¹³Òÿ¯O瀑p9¸„ +endobj +1070 0 obj << +/Type /Page +/Contents 1071 0 R +/Resources 1069 0 R +/MediaBox [0 0 612 792] +/Parent 1051 0 R +>> endobj +1072 0 obj << +/D [1070 0 R /XYZ 99.213 706.052 null] +>> endobj +1069 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1075 0 obj << +/Length 1340 +/Filter /FlateDecode +>> +stream +xÚXÛŽÛ6}߯ð[e fyѵoI›¶)`Û¸@¦\‰¶ÕÊ’#JÙîßwÈ!eY²å¢(Dcêp†<3<-[QøÇVYF8«„Æ„F|•èjo~x`òˆ°,ûÊËMÄCÒlµyx³}øúû„8%q,VÛ¥)aI²Ú¿ßä©SízÃ#dë?¶?áœ$ibæÐÕ&Ž —þ«.ë=‚?þüþƒ<¡ýøã#ßÉNÂè <¢+¾ÊH†Ñ'A’˜YOÛƒšûMßð0^?¾Ã§¶Y³(øRJãÈ®éݪó¦u®v}weSëoLô‡·Û ·óöESv•BÙŒ0ÈàhŒE$ŽØàÇläçÑ4ca"]Œç1Ëá +éÇ>÷ª}AóhYçU_”kø3”«¶ó\BU©NÁFn°(’„ÄÙ"‰gÈMä…Á—¢9GÑ®ó‡
›Ï»};_\VÖ@ã~(½§?ͬlE Y´L×s›/‡¹KØB¼3cK=e£€ÿgU©3‡kvcæ´ÙË +N4Éh‹3b#.ezbYéæ–(ëþt‚±À×ô°„Vum©ÖÜ }ñŸä¾¬á¬Ãò:=9 +GY›Ðµ²Ö"€È“á¶b!aìî˜0!ë¨^o¥4xcärðã“Q1ši*!¾%™ã)ˆkN5s&·lÆCË&O1‹¯Hˆ0øíÃ{cˆ hòþ¨ê‡KO‰ç53-T»ë+êšÆYH-@~Þ#ånð<„H>Ið—×o€ž%Æ2ù.`RÑaDí®¹HI"¬hó¢Ý;Ä/£ÚöèÍ>«í©KÃJ:Á“˸““ä1w¢]:²G„w~fÐcdyÊ VžÅp$!™PYZGö%±¥#Ô¸ÐøkXNa= ;œCC”‰;ÄzôfŸmuêÒ¬PÌâ2"^Æë1ËÑ&Ž<±¯`ë©p-J‡<¼4=¹¬¿Z³4pã#IîªÁ8‚Ö»ŸM[ØŽÌ®Ág¯º;8cè#M¤Y¯ƒFUl2!‘$LÙE2±êÒ¶þrÆçËÐim.ä–\•¹uÿjÀ¡Q+U\¶N•ÔzÊ}Ã˪²RfÄèÌFý±à)4bòh•äÀ4Yáð¥ø +žA±¶_Œä +Uwh +ðî ;´ì +ŒaW`€f8ô|(óš’…ŸêÎX5o}ˆvDv›àÖ÷‚SᬸŽ8/Þ‹á +÷fòoú_AM¯B(½(–þ™cèÈ þ›_\ý$kfýy®´ãÁ·}bpš:ù1kÒövÍÜîdf¥BŒ¿$x§,ÇS5kË9|*Ps‚P×÷„È¡7#ø\&.ÝMwñ4vh.Èô2öTŒf9"ä%‚¾eQK„Ff$>ì×+\ÕYQ€‘ákl<#Ʋ10«ËcYIÿºñ0GwS+„aMÖE…ç>žËîp£èD𕉋T^É×õ6$„Ûg©ƒ=Cfü“tpbÇÉ4ÿÂ<Ò?ÙÎû–endstream +endobj +1074 0 obj << +/Type /Page +/Contents 1075 0 R +/Resources 1073 0 R +/MediaBox [0 0 612 792] +/Parent 1051 0 R +/Annots [ 1077 0 R 1078 0 R 1079 0 R ] +>> endobj +1077 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [449.35 235.584 456.324 246.488] +/Subtype /Link +/A << /S /GoTo /D (chapter.8) >> +>> endobj +1078 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [207.717 220.64 214.691 231.544] +/Subtype /Link +/A << /S /GoTo /D (chapter.3) >> +>> endobj +1079 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [228.006 151.898 249.924 162.802] +/Subtype /Link +/A << /S /GoTo /D (example.9.1.1) >> +>> endobj +1076 0 obj << +/D [1074 0 R /XYZ 99.213 706.052 null] +>> endobj +390 0 obj << +/D [1074 0 R /XYZ 99.213 688.052 null] +>> endobj +394 0 obj << +/D [1074 0 R /XYZ 99.213 302.248 null] +>> endobj +1080 0 obj << +/D [1074 0 R /XYZ 99.213 140.008 null] +>> endobj +1073 0 obj << +/Font << /F71 445 0 R /F72 448 0 R /F85 483 0 R /F103 753 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1083 0 obj << +/Length 1153 +/Filter /FlateDecode +>> +stream +xÚWYoÛF~ׯ ¦€p½'á4vÜÀ ìZy(’À å•Å‚"eq¢ÿ½³/“JìB +Îj#ÿiQyÌ‘ÀŽ:[\p"£QØ•3n]N„çP}Nh%ÑV°a´»"áýÍP•5¥n¹•Ëdõh€y&
._™Ó©qá?×›¸‘Õ:¿-
±™îÖÚ•4CHÆ Ó‚A¶ŽŽŽT…B²àÜìžÉBîÏ…pK»ÃÛÞ‡á+\s9ÜÑrÈ}NžÕr=ì®–k z§I5z[BÛ„v±¡óڹ꽹¯eñxšç ¦ž¾ºÅ&©>åðìOëç¯^ÛÍå{œÖ²l»tâ2Ä ÿá÷HŽÀ§JØ’¨püð©!A9âpf[úm¢<úendstream +endobj +1082 0 obj << +/Type /Page +/Contents 1083 0 R +/Resources 1081 0 R +/MediaBox [0 0 612 792] +/Parent 1051 0 R +>> endobj +1084 0 obj << +/D [1082 0 R /XYZ 99.213 706.052 null] +>> endobj +1081 0 obj << +/Font << /F72 448 0 R /F71 445 0 R /F97 676 0 R /F85 483 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1087 0 obj << +/Length 1495 +/Filter /FlateDecode +>> +stream +xÚµXÝoÓHÏ_áœYöËk['à(´¨…”ä$$àÁ$Nâñƒã´ôþú›ÙÇŽ]Jïtªdwç{~3»)ó(ü1/Ž g©"4àÞb;¢ÞvÞŒ˜å˜X–I‹çå|ôìuȽ˜ÄJx󪑊ƒš˜({óå'?OêýäŽVŒúgã/ó·Z<$qˆÒ<&h
¥>÷§Èé_jú½~~Ð+š~§ŸoÆJ®?Îõó¨¹qL)ÂB§|¦Ý¸ÖÏKý¼òE©Ø +¼°ÞôÔ2ATY®©Vu®Ÿ@JýWCzƒè¨WÆà¹`@õu+¹ãì;'þan¦6Hèkû¡ŒŽrÆWx^ô]¥„ +gb®ž»"pW?õú—ý{JR†.®ù¬apf'A ì>‰é*õÔ0 ˆºéiiQ$[éa&ÎÖë̼tŠFgóçM'PZ‰(ÿ>úô…zK舷#JDy·@SÂâØÛŽ$€ +íw>š®]fOyFj¨©.‰¤ñ@W1Œ0†›¶BŸTj»Š0 + »:äu¶ËS¨ +ô*©“¯É>Ý÷kO= +ªîÛEì +&ÿÞ\¡5Õq÷þœyµ×|S¼4êÐégŒ²B‡›¢ÃozÙBÇåëCZÁA ¨ D‹Âõ2T}YT¶DŸYàÁý9Ràí_aà«C±0Ç~}Gs¯Ëêý׿ Yé¢þLúºÖCÿ€ü©á|²KòuZ=/yîV¡eત—@”’ß®;p™áu濘ªírŠ¨í†ÿdÿ-Û=Ÿ0'´M~Àºk| =ìò4YcΖàú£ýÞ%ët–ý>
Œ]ylÚ°˜ÿëÖïMÑ·ônZ•ÐDõ]ÇÑ›$?`”Í¡ÔÁ (Šp9(qîbÁðXcê1‰9ìàNšþŸ–ižþk,õæ‚žÂ]QŠ´å…þ¦–}Çá ÚÃÁ¯ë ”¿*”¼°†!Î/˜}tÂ9\‹ŒÌÖ‰šß‰0Á$“?ý‡É‘¥÷ÿ¿ú¢F º¦âSCî'bßÒ?jƒSendstream +endobj +1086 0 obj << +/Type /Page +/Contents 1087 0 R +/Resources 1085 0 R +/MediaBox [0 0 612 792] +/Parent 1090 0 R +>> endobj +1088 0 obj << +/D [1086 0 R /XYZ 99.213 706.052 null] +>> endobj +398 0 obj << +/D [1086 0 R /XYZ 99.213 688.052 null] +>> endobj +402 0 obj << +/D [1086 0 R /XYZ 99.213 614.245 null] +>> endobj +406 0 obj << +/D [1086 0 R /XYZ 99.213 510.092 null] +>> endobj +1089 0 obj << +/D [1086 0 R /XYZ 99.213 429.287 null] +>> endobj +1085 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F103 753 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1093 0 obj << +/Length 1929 +/Filter /FlateDecode +>> +stream +xÚµXÝ“Û6÷_¡‡>Ȉᗾ¦¹v’Kšn¯É%Íö©íƒ,Ñ»ºØ’+ÉÍmÿú)Ë–v³—™›L,~ +yLEGW„nHƒHf,‡ˆ@áoÛÁ`Øfáp[H¥¡)Ê[šk·~ÍÐúÙÎìÍpÛV=
Š²4·Ù±faSìÍ¥°Òͽ)Sýa(³7“PôgJ³ð7Îe×48]±G9ƒéÖqŒx¡3^³=ï4ÌÎýØ{5oѲ™s2@;é“Ôj±*ÀY +n¬ëLh<1Nû.©Å¨©žºZHsÍÒÔ'2ºÇ˲„÷
Òßà°n.x_C1î"¢2àôYSÛP±‘.A¥N“3‡` ôqPdJÏ
¶Š“:·AC¶~÷Ýü0HH·Žq7"s^¡©\»hBeÑ›'@ +s¯%)‹Á°ÿÿ{ó0lA©`Iæ²Øõ-©ØwDli?`fô½©¬U±Ë°…Üi×2?ÕÍ
Mõä‚žF•éË®Þï?¼Ós¤¸¥ýÀàO7YbL|ZCQk»lt°5 +SÍ€²eI&Ö‘ààWMoºá ‰øåPA,¸ÁK³÷Ì ^3© +ÑOryûè)ðÁðØ›ž¨¶1Žp›—q&5> žÑ5|;7J§,“±=™Ë‰YûÄ{F®¾OÊ($R +€½[0*eJ{¾gäÖi +’[÷äÝ +¡]Šû™.h•óÌë¢0\ +ˆÃUœDg†c‡UM©Äe.,ÁprÿßÀLš‡-°vŸj,ŸnKAD?¢Z*Ζ®ÒfëmœdásÒ<;”d&ÇV‰°gᙈ/ö¢©Q'’ÕXÄx&_ X À‡èÜmŽ9î7¶)Œ ›ñÛQà +}±]Ë$Ü‚_l3›»!#Ü=Ôäc3 ]3ðþhº»ïQW;i‹Ê¿ÌŠ\Ó +ho[[ë´ïÚ#s/ÖZe
ú6»;‡Pà¶ã&Óë8Äâ–¾û¢)n±ùg,ÒS;<"
¢:ˆ¨SÒÛg ÌoLYØ.µc¶Á Â6&Z83Õk肼_*ZXy¸o;C3ð‚oˆ²ý#cþ¡!€KtDá/ƒšN¿Û…ó˜‚O÷´‡n-!‡…ç\¨‹Ð+êñ^.³ï‹ñÈsO]ô˜9ÜÖÃÓÓÛÜ>ÿ¡jBªûû +endobj +1092 0 obj << +/Type /Page +/Contents 1093 0 R +/Resources 1091 0 R +/MediaBox [0 0 612 792] +/Parent 1090 0 R +/Annots [ 1095 0 R ] +>> endobj +1095 0 obj << +/Type /Annot +/Border[0 0 0]/H/I/C[1 0 0] +/Rect [295.554 396.958 310 407.862] +/Subtype /Link +/A << /S /GoTo /D (section.3.4) >> +>> endobj +1094 0 obj << +/D [1092 0 R /XYZ 99.213 706.052 null] +>> endobj +410 0 obj << +/D [1092 0 R /XYZ 99.213 545.736 null] +>> endobj +414 0 obj << +/D [1092 0 R /XYZ 99.213 354.389 null] +>> endobj +418 0 obj << +/D [1092 0 R /XYZ 99.213 194.986 null] +>> endobj +1091 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1098 0 obj << +/Length 2690 +/Filter /FlateDecode +>> +stream +xÚ¥YK“Û¸¾Ï¯ÐÁªÊB€ ÈMù°Ùøµe{Ç;³IªÖ{ $JbL‘2Ïþúô (Q3ž$5U"Fw£ûC7FÎBø“³4Jê™
c5[í¯ÂÙf^_IG±p$‹Íßn¯þòʪY*ÒXÏn7È&Š°IE¬ÓÙíú÷ /LúU¾Ë0x9ÿãögZnEjqµJþ4*¸FÊൡß_iä-µ?ÐïëùBÛ(¸¥Îú=r‹c!g~Cb|¤ßwôûþ’,qœº?:i&l¥±IÕ5±zC¿ÐÖaüý_“ùF)H®%´¦¼c¡”§œ +˜ +mÌ™|h›kglÿ:å +ûÛ(9®cYá÷íTÔP„ÚoqKßøCPþ@~£ñ×ÞúEbFG¡…µ^¯Û›Ào+•0F»y8$I§4a#% 95ψK,Œ™G²ž£ÏKþ‰®^Þ~>D‚£ÐË¿\ýþG8[CDü| +&³;h‡B¦él)-ëúåÕÍÕÇÏÅ3^u)¨ŒŠD¦Ó¨«ª%ųØ&"ä¨zÖ~./ò9XüìÙ>û½O¡‘žùBI”bWG`æðv§h¢ ãÏûìpÈ×ܾé².ßçUÇÝ¢åo>—&øvÈW'ìjþ¶y £ÜÞ÷eWÊœ{M=W&¸kŸc×Ý.¯xb••¥cïömQm§ç¬ÃH$‘÷‡/}ÞÜ¿ª›wEÛMµRXí£`Fƒ›¾ÌV;'Õ5÷¸ì`Ñ?¢Ù,›FÑ ™–eĆ +J܃†î +[K7—9â&oA_n×Ë“pþPú2#CáÔ¦©÷<á¸ë`U7°øPWkT›™±±˜°Þœˆ£Yužã]AÁ(Mè qþ¾î¹Qå°-ªžè‡‡¥uìAvdÛœ»È£ÞˆÏx«_Xµ‰Á)BëÏhÀ6pÖÚ¤î¬q<aü®ÈjÚ²aàµ-™Æ²ö(
t»¢Y{º&Ûç]ÞÌÁ‡ð*±šÕ2VˆHmfQó)ËH‘xc+ÔË.ÓV‰%×O;أ銬ä>«Œ›Æ,Žn@»nÇòLaÉ‘ Ãtj6„5ÞÄYµf®ŸÂPmx/ÔC»X¸g¥ò›@ð_ +w (žŽg
±O)‡P"x?` îus=‡´íÑã÷#¡`½‘¡HÌ XO3!&]Œh'˜-C„ÜŠuè—e±bÀÞôÕª+êŠ{Õ®³-X‰åCc>k= +Û2àÂuó™‡
+P¬óÖ³j¹‚
¢Ð©Y9ˇКw»z=¸ƒÒ"•Œ•·—®i…N¢!EÿRBvx=γªÏ +3 +EÈ´¡øÃÝeÂЪìIpì±,ØIR´7VÙ\äÅvŽžL÷w»¦î·;î ¿;&ˆŽS¹äÛ:õ÷b•ëPj¤¥{g"¸bÈÆŽüù” dè¯Ppê¯EÝ·sL LḈ’D(Øs|lÛº«=[Âñ·Õ:ÿvy)¡€r¾NÑ Ô(Ûš[«]¾ú<²¤;
¼SzgFŒ,<†aò@ +µT$Ÿfgâù¾X¯Ëüq¦2”"–飶6à˜öÔÖÈþ]ö=‰,ÿõ!ñçl?~ÍŠ2[– iz%y”y4f~=ò=·@Ö£{ÄÂV=YŸz¨F*ÒÀ;ßCœ!ÆŒzÌìFƒ‹êøÜì×.Ay˜µµÂ†Éȵæà!aûpÝa’NxáòÃÁ‹ÓQ2Jq@x2W³ð8,rÓUÝ9ºÕ*oÛb àRÍGÌbŒq(ŸŒO +r x †– +wm~v +m&êEEæˆáØ}1ÑŽŸ.B—a‹lמUç/¡¯çeЊóAÂ…–<ÀšÇ +Ð/ÁÇpóý$m‘DæâÜÕIHÏ€±¡E!°áRë3&.ód§«èâ#†4Þƒ‹£jü²+gà7qÌ^ + +8Œ2Ćç99¯h÷YYúiVÐRDÁ·®É8Î÷òi&êÅç>ÉQñ%*4ŒÓ*QêowT¼'Ê? (gl¡G|âccó
JÇë3×bk©D2Î$jd 칇EI#‰W¹ 4—„÷®Eq_ápíösëýuÑ",U +endobj +1097 0 obj << +/Type /Page +/Contents 1098 0 R +/Resources 1096 0 R +/MediaBox [0 0 612 792] +/Parent 1090 0 R +>> endobj +1099 0 obj << +/D [1097 0 R /XYZ 99.213 706.052 null] +>> endobj +422 0 obj << +/D [1097 0 R /XYZ 99.213 565.949 null] +>> endobj +426 0 obj << +/D [1097 0 R /XYZ 99.213 204.098 null] +>> endobj +1096 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1102 0 obj << +/Length 1747 +/Filter /FlateDecode +>> +stream +xÚXÛnÛ8}÷WE€Ê@Ì”¨[ mÓ6EÓM[÷aÑím)±ZYruIÖXì¿ïCJ²¥dûP±Gäpx8sf82s(ü1'Ž gžÒ€PÁõvF[˜y=cFcaTçËÙÙ«;1‰ÏYÞ8£Dx>؉IàÅÎ2ù⾘]¾Õj!‰C¥æ{`T;™/îžÃ's¯µ¼Ôò…–?vkû-â{vu<_굂ºŸGqFB/2ºŸ´ÅKýù^ïñzl{p6¯_Ö?èÏwúój¼4ˆÍ‚smüzl–y$VëZ›z£?Aö(u_NÙQÔÙõcð‹Ç@ÛçÖö`L<!ŽðõξвvóìbÙ»£ƒ +õÙ—¯ÔI€og”xqä܃L ‹cg;ó¹ +ùÚuÿËO?ò+ôï¸uâpÓÚ’Õw;Çm7ðÏSsL|àÀ W¿¡êb «[æ!<Ó2[µõÙ4ü@£çéª]J”øuR-nÖµö‹šo문Eñ<)“Õh•™Rƒ¸kWy¶F›¶è¯e¥¢g
ýE…fí +à¿Ð¤xÿ)‡~¡þõ€‘ËJ³ø1ÄÑâÀÕçrýýÌêø§ÖÑ¿ö]™%‡{ƒ›Uý +endobj +1101 0 obj << +/Type /Page +/Contents 1102 0 R +/Resources 1100 0 R +/MediaBox [0 0 612 792] +/Parent 1090 0 R +>> endobj +1103 0 obj << +/D [1101 0 R /XYZ 99.213 706.052 null] +>> endobj +430 0 obj << +/D [1101 0 R /XYZ 99.213 532.897 null] +>> endobj +1104 0 obj << +/D [1101 0 R /XYZ 99.213 313.681 null] +>> endobj +1100 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1107 0 obj << +/Length 989 +/Filter /FlateDecode +>> +stream +xÚWQoÔ8~ϯÈÃIÝ•ˆÏv'Ñ $ +Q(×=ð6¦Í‘Ýl“¬ þûí8›lìP T5ëØßÌ7žù<IˆáøiŠ( ýs„#ê¯7ö¯aå¹G:DÐA‚ætåýù,¦~ŠRú«OÒ
ãܤˆ‡©¿Êß-ÒeáR×ÐŒ9Á‹'Ë«se£4–Ö¬¢Ø¤Ýk@ÑÅSu}¡®¯¤ÝâyoÖ³†quvg·E‰Y}«<=V×—êz©¼^¨ñ™_IÞÙªßzŸœâcrã·Þ»ØÏ!IçFašø_`ŒISã1Á(îîKïÊ{ÓûÒkÜ×V¶<G”!†Ói¢aÃDËxœ ¬óü]FíÓ¥àƒ0”2¦æÿhnË—Ù.xTWeyš?¯êlÛdëvIEµ}#ÿä/eLítù$>L†ŒKvì,†Øº +#¨+ÁJZåÅözP¨òÙ×l³+E3I&C4$~Ä9J)±+MC‚F+L` ªòšQÓCPˆs“¬x<Kn0v(hÏN8„È’#z±Þ·ýþÿÙåYÛ#“\lQ·ôÄ^.BÄB7—‹R´BÖ•²¤ƒù0jwêX—vD‰dWWù4p(ãCý³_ôàR/©@ÜZ-x,´ÎGð¨í‹\¢ÉAY3ا¢Y×Å®5‚kiwruSÜhîÕ·}q2r]"Ií泸…Ü‹]g[ù’:WwÝ6NtØ]¿-íÜ’rÆŠÂdV¥ŒK¥bU)u©tŽ¼Wé1»M¥cú‘JßìE}§‡ŸªZ^üOH)å6R¨¥Ð™r% +:~Lé½´9Àº´i Ö"Ï*Ê.‡[Ø&ìâîYU›ÝÉu¥k1Q0Ω"ä¥ ŸUÅ +̶÷²“¢<(Rš€©”£àÐ¥²:ÛÀƒ¡6&xgoý©ÿÇy^ÿÿëj³)Ú~ãàüèuÂRU +ïP1‰fÎ +endobj +1106 0 obj << +/Type /Page +/Contents 1107 0 R +/Resources 1105 0 R +/MediaBox [0 0 612 792] +/Parent 1090 0 R +>> endobj +1108 0 obj << +/D [1106 0 R /XYZ 99.213 706.052 null] +>> endobj +434 0 obj << +/D [1106 0 R /XYZ 99.213 631.429 null] +>> endobj +1109 0 obj << +/D [1106 0 R /XYZ 99.213 587.919 null] +>> endobj +1110 0 obj << +/D [1106 0 R /XYZ 99.213 463.914 null] +>> endobj +1111 0 obj << +/D [1106 0 R /XYZ 99.213 383.362 null] +>> endobj +1112 0 obj << +/D [1106 0 R /XYZ 99.213 228.091 null] +>> endobj +1113 0 obj << +/D [1106 0 R /XYZ 99.213 162.483 null] +>> endobj +1105 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +1116 0 obj << +/Length 693 +/Filter /FlateDecode +>> +stream +xÚU]oÚ0}ϯðC%iñìø+VµJ]GÛUíköÔõAŠÐÂGùØÚ¿kÇ$€Šì|î9××'E~iÊ"‘ á4 h+7uˆØAâÌ×,ø|¤±–e/ˆQ‚ãÀ£±de£§ð*zÎî,LaJaÊšÕÛ(–Ix #
{6ÎlܵñÏ*·–˜³m¶ŽbABl2HøËJ(V,uØGËøÝŽ?¬ÆÏž0Ø«3€½oÇ{;>ø°Q©]Â¥%ïù´”a)¶¨ž¥ºµ#ÄŒð[¯HÓŠ—kè£ùÜ'É–Û/Pc&ÄA}u³»6¶mºYuØ•ÔÍÍQ¿OÏÀwÁL§èÄSÑ4à‰€H¹ç"xúW¹&Q™Õä,Î O¥(N h¹ç>ß…'f©RLteÂTÔ
qÅ;„Ù÷Y1Ya§Ðé/åt¶z-‹øâu“/߯ç
—÷è7¤3Î×½å|´®ÍBç“Kü“¿»p¶) +7s©ôÜkgY´ä0SÕüvm÷UcÊ·‹ús³³îÛ`º(ò(NÀLpÔXjSYRßb<u8ÒŸÁOäóáf=™Ëú¦™eø2_–A/JI8ˆàýOfƒu>‚¿ÁÍe³
À4{•ùpmz×Ö:š`ÆäiÖ¨±ÞpÓÍÑŒóÑIÙ±í +endobj +1115 0 obj << +/Type /Page +/Contents 1116 0 R +/Resources 1114 0 R +/MediaBox [0 0 612 792] +/Parent 1090 0 R +>> endobj +1117 0 obj << +/D [1115 0 R /XYZ 99.213 706.052 null] +>> endobj +1118 0 obj << +/D [1115 0 R /XYZ 99.213 666.593 null] +>> endobj +1119 0 obj << +/D [1115 0 R /XYZ 99.213 570.952 null] +>> endobj +1114 0 obj << +/Font << /F72 448 0 R /F85 483 0 R /F71 445 0 R /F97 676 0 R >> +/ProcSet [ /PDF /Text ] +>> endobj +849 0 obj << +/Length1 744 +/Length2 1488 +/Length3 532 +/Length 2040 +/Filter /FlateDecode +>> +stream +xÚíR{8ÔÛF‘gj3¤F£´Ü:î3Cã¾cÆ¥Ä A.‘¦™óÓ\cÜ%„ ¥FF(Ñm"Ù•±kövi+¥¢ìBöVcWä’¶kÓ:óœöŸçüuž³ÖzÖ³¾ï{×»Þõ®e ããkF ±÷Bnl×gŽ³Î$2pæX,a`àÌ(\˜Ír¡p!;€³µµ +(\:Ä”qP)à˦Â7Á@^ÜÈPÄáA4sh0•öB0Y”äÎ +gë/iZlÔ×âÄÈDÃ%™F@&’Æf1 +G`¼Ø²Ó ™–ÿ†¬oÉÝb/ +s‘~Ѩ¿”)L˜‘ð +ýæ,W•MƒYÀo(%!û=²’p +¨~fiF?»½÷v¼°Š})ïo³Ê‰Ò·àÄŒm¥È£h¿þ`ÚX²j·"ogˆQ°¢Îw7#õPÁüÒ.DœÊÔi4!¡åXü9)Ö‰JH©ŽAÀCȹH¢ÑBª÷Þ•ª-m>o(ÁumO2ŠƒÑÎwšø˜Ÿ`. öyr„¬FZxLòSH¾m Þ¯Æ^Np"Ÿ¿ìÛH¨L=ðê@q|æ|¸–ûÇ +Â8kUÅ?+û½äŠRŠ—« +J‰Ç“45óõ/‰¶\íHJ´—ÖxÚÞŸgúíªµv¯-¡¸XVìÏèWÖ(¥Ë5öÙ 3W:¶¥r¬òí‰Ü~¥+áQª›X˹s_c¯cêñL>/ºâ¼'¿xT®èÜ<05‹Ü*|NŽTÉ߸BWS=i}âõ %
÷/wÅ.·¨Ä~ªºls}¸Äø¢XOéúÔÂŒfYôSÇ}n½==q3ùvûýîù÷‹£QÈÆ((å^uЋì +tC7é_]YTˆÚpûid™¼£…GwÝ¢ÃûЩ¢ð¤9ëø5q·nyÏt.[Ùâ"~7&84ü°§]"ÎnâY :m®¨ëÔõaïH‡æx…b^j}”)R¦1šÞ~$1¡R¡)xùêB¡Á`æljåWBº+?uõŸ4ÏÎ suÊX7]¸¶Û¯†v(ÿÍŒÕïn#&}Zû÷ ÂÚƒ?PÏ CkÝ•{µpMuŸ:ò›OWfšV³GV…Çî=É3¬Ø¤¼¡Õì“ÛlwÍ$áÂŒCÕ±ÿ^¤¢–iÞvÆ\uÇ/’ÀÅd&#I÷¤ [–«ÊuÃ:0Šš§ØlIâÁMO¤;»¬Þ .aN,ÇÍ'<\±3í«eí]›OÍq‹RFB-mRÂ…^ÔýÊ—[…‚ϼøžþ‡<Jƒ*òÚÝôÑ>A&n'‡«ÞzÝq½oºÞÏÐ'ªTQÄ^÷+ýZŸsh¦iOz¶&êm¼z½Ù~÷ÖÚ†ƒ{~> +|sn+W÷Þ9Qþ2.{g_VÄ%µ +ûúöú®†ù +¤]ft*iêKŽàù]zS„•ðËÖó>̨ŽDóÚ<Y`öÝo¤0KÑ
ŒŽÄÞÚ#ÛÖ%]:œ¶ÖÿÏÛçŸÛ Mö>ú¯PÕõñÜfˆïYXݶÏs›-mêVg°ghRBœ“.× X0JVºÕ X‚…³†)~wo +ªYè„ö?lˆÿüOP…Ãe3)œ}ˆ¿60endstream +endobj +850 0 obj << +/Type /Font +/Subtype /Type1 +/Encoding 1120 0 R +/FirstChar 57 +/LastChar 57 +/Widths 1121 0 R +/BaseFont /VONUWE+CMR10 +/FontDescriptor 848 0 R +>> endobj +848 0 obj << +/Ascent 694 +/CapHeight 683 +/Descent -194 +/FontName /VONUWE+CMR10 +/ItalicAngle 0 +/StemV 69 +/XHeight 431 +/FontBBox [-251 -250 1009 969] +/Flags 4 +/CharSet (/nine) +/FontFile 849 0 R +>> endobj +1121 0 obj +[500 ] +endobj +1120 0 obj << +/Type /Encoding +/Differences [ 0 /.notdef 57/nine 58/.notdef] +>> endobj +846 0 obj << +/Length1 774 +/Length2 652 +/Length3 532 +/Length 1207 +/Filter /FlateDecode +>> +stream +xÚíR}PeK‰Ug"ˆ
{Ѹ ËãøJ8ðÐ$cÙ}ïXÙÛ=÷öà.<¸I”oA?âC0Ð4¾ +G"2Ét2ôt`F@óuJÓZ'ü³þrÚýg¿çyŸß³ÏïõpV å“Ú¢"4("Uq¨ " âá¡`!Æ‘‚q0 (4 +6Ž}îÔÐœw vC±EÚ!º¨˜¶æ•.xìíy3¢kóú={·
-ì`‡?Ë]æc[–Ù=q=ø꓃ýó^óZ»Ëké"™µÎ~ÀÎE`øí—7ÿhË,;½ +¿sîÒšˆh>,ϳWýμ¤1ìþ’ NííõŽ¹&ï<}(m!ñç«Ê‚Žxã¨[ÎÒlß¿WÕÇó2§_8£³ôåÜ*<8¾Eé€t¸ŸèéÊ:=$.²„Ÿt[óF÷°_òÃEÝÞQ¥ÎÉ ßìã×Ú\Ú4²îØQ4û¯ëö‹j2&k±”½»ÝBF?Þ|¨}ÕÄ:A¡Yô~Ëž¸»Õí]óúÛ_GÞ=oU=Øïuvnžo¤¡ù~i[òŽ[bÁÕÛ+Ïö÷„W[´³æ»Vn‰IhE +•XØã¹…WóOéüŽ1£‰oçÿ<×nU5~´ª|ë—þVàStVY—÷ ñÊH¹ó¨e¾}zÙ¹eɬ*·)§§eÍCsçáÛ–¥é€þbÉáåéM鿾•X|ˆºábé¼—‘öR¦gJkîzŸýôx¼®þ•vñò•Zû#VçW<Þ}ž®®o¼êz”(q¹#êüá“ñ¥ã³ŽÏ}:àÓ°ñ²cWb™pâX蘆†“˾£vú^xtìëÎY_}ÓyûLu‚»rŽ£ß¿¶ðÚž[¥§/8œhø±îZå)Šöðì»ÇGªSÑÍrsHù@[’¡’©»W\Úè2[ª)ðóiÈO˜u)~u„ö„0/Ãnö‚>,¨Â®*&v|rÏû‘WsNÉ6˨ÀGJþåƒü/ðBàÄXŽÑbl +ò7 +ž©Òendstream +endobj +847 0 obj << +/Type /Font +/Subtype /Type1 +/Encoding 1122 0 R +/FirstChar 0 +/LastChar 1 +/Widths 1123 0 R +/BaseFont /TPOMFV+CMSY10 +/FontDescriptor 845 0 R +>> endobj +845 0 obj << +/Ascent 750 +/CapHeight 683 +/Descent -194 +/FontName /TPOMFV+CMSY10 +/ItalicAngle -14.035 +/StemV 85 +/XHeight 431 +/FontBBox [-29 -960 1116 775] +/Flags 4 +/CharSet (/minus/periodcentered) +/FontFile 846 0 R +>> endobj +1123 0 obj +[778 278 ] +endobj +1122 0 obj << +/Type /Encoding +/Differences [ 0 /minus/periodcentered 2/.notdef] +>> endobj +1124 0 obj << +/Type /Encoding +/Differences [ 0 /.notdef 1/dotaccent/fi/fl/fraction/hungarumlaut/Lslash/lslash/ogonek/ring 10/.notdef 11/breve/minus 13/.notdef 14/Zcaron/zcaron/caron/dotlessi/dotlessj/ff/ffi/ffl/notequal/infinity/lessequal/greaterequal/partialdiff/summation/product/pi/grave/quotesingle/space/exclam/quotedbl/numbersign/dollar/percent/ampersand/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/backslash/bracketright/asciicircum/underscore/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/braceleft/bar/braceright/asciitilde 127/.notdef 128/Euro/integral/quotesinglbase/florin/quotedblbase/ellipsis/dagger/daggerdbl/circumflex/perthousand/Scaron/guilsinglleft/OE/Omega/radical/approxequal 144/.notdef 147/quotedblleft/quotedblright/bullet/endash/emdash/tilde/trademark/scaron/guilsinglright/oe/Delta/lozenge/Ydieresis 160/.notdef 161/exclamdown/cent/sterling/currency/yen/brokenbar/section/dieresis/copyright/ordfeminine/guillemotleft/logicalnot/hyphen/registered/macron/degree/plusminus/twosuperior/threesuperior/acute/mu/paragraph/periodcentered/cedilla/onesuperior/ordmasculine/guillemotright/onequarter/onehalf/threequarters/questiondown/Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex/Idieresis/Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis/multiply/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn/germandbls/agrave/aacute/acircumflex/atilde/adieresis/aring/ae/ccedilla/egrave/eacute/ecircumflex/edieresis/igrave/iacute/icircumflex/idieresis/eth/ntilde/ograve/oacute/ocircumflex/otilde/odieresis/divide/oslash/ugrave/uacute/ucircumflex/udieresis/yacute/thorn/ydieresis] +>> endobj +773 0 obj << +/Length1 1630 +/Length2 8647 +/Length3 532 +/Length 9496 +/Filter /FlateDecode +>> +stream +xÚíxePœÝ²np—à‚»»B€àî2ƒÜÝA‚»‚»<¸&¸»·Kò½÷©ïî_çì_·îTÍÔ»úé~º{=½V½5´”*j,æS ,eá`e(ìLÞAÀŠ,ª@KgeS[ààA¥¥•rš@A°´ (К¤f +bïî²´‚4Tµ™˜˜ÿeùí0uÿòé²èž\€¶{; úLñ?TP+ ÀdH)«èÈ+Éä”4 +tƒþÎe +˜ƒœìmMÜŸs?“Ù;‚þ”áì[þ«f€#ÐÒÄÑÜèäôLóÌý{wþÕ'à¿uobooëþ'òÇëŸ5€ N@[VTÎçœfÐçÜ– 0*Ûïa‘[@ +3±Ùºÿ›À¿;jÿ*ö¿øþËCMž7Elù,'+û_f“,È
h®‚šY,LlŸ÷ì]lt´ÏÚþÙÖç vö¿aêV 3ðoxþ‚€`ó¿—ÿ,ןâÙ$Ôµ”Ô™þÝ
ûÇSåy êîö@À¥Ñz1ÿçâ7¤$Ä
àÁÂË`áäâðñqø98¼þMÆ?4ÿZ¿3:‚Ü + ûÚîÎ%ÆHš¼)™â“\EyɸaÑÛ´o×óòAÒþÝT5N‰ó”\fqÂT©h䉫]o
e˜}æÜqÑ +úฯîÛ?zÐ@Sˆëç95Z'³(çÝ(¸£¼‡ô—Ëž7á×úˆ²CHéW#Oõ¤Ö¢¦öÈô/à*tyÎܬxǨZÞWÈ#‰w1ù28ÕpnT‡Že ¹·çr8‚Ô¯lE¼¦ñ<Éü~L +PËϬ˜J‰½%l°ûDù³ÿ'GŽF>Ž~°ñÒטôÄò²´-Ýà* 5²¤AÞP÷קͣÛÑ›J|ºùÙWP¢øpl¸×š?» +¹áGÌmI2`p³;âïø?Ùy½0N•©¢@§h§°¥F'%Fè +(¢€# äܦ›Jƒ,ÃÄh~¡q´Å7D•ÓaÕWïy†I¯†S¡<{A‡¾s9e%zðÑogK&Ô™|íQÄc]á9g„HcE5Š×²á±*XV¬Œæ½Kêðâ㻇#rfwÈúYjÂøh´Ñ꿺õg©wh³µT1>u'9¿¬€³YOíSIõ¨³yŒÑ=û¯áYök®ºb,vÉçkOñ5ŸîJ®,ýàã©QéǨŒÜi˜x‘Í[ƒó¯%Ç:Óh!°é.¢Diø±oËÊc÷\ ©‘å¶ÍoíFÙ½eýà
m=bžM+uG†á˜Z]¬· •%éÄn` +ŽQ—™Â>"ß2¼ +¡©~º–»3ûl•
vâ+┌Wúlh§ÇŠÙAWè ðHÝÁåvâAÙ»X¾Á™('të8» Âõn‘7ÄRƒ$U®m™ò¬ïp»õ©¸Ï‡Ô)q?rÜÄ9k)vDÓú—]ïÄ=ÖlôE=¿ÌÙÕ®Æöö3*ÉÿjL—¹sNæhØ=Eœýé®C*ÂÊg£9³[ÑztåÑiew³4ƒ$Ñ—íüXºÎvîK¤4¥&j_iG›Åß“3Þ—· GG$p!#Ï6g¸ú˘f퀉ï¢äÍ…:
ùˆëÜgk
¹¶¬ÕM–œêZáãl.±XNŠN+ª³¯†¹fÃÌxeeê½jE¯CÉ2<»=)”£]ýÏȧÇ?„:Œ™c Ãñm²w(ùªE¬¿e{Í©í1jQŸ§L¢ôIÒu„;C'#óì6w<ÉXâ{ó2šŠ(÷¼ +à\)„º*„Sã*óaƒ}|»j;^³ ut³ÊšmN5Ê/9ÿW¢/Fmh¨8,³]á,Éd•i.6V‹RÔ뽟¤Wûzë5™Ð´olά¹!@ðm£Ž®M|
+¿ðÐÉ” +gôjeMõÞ +æqÊ¡ô^ßÕ秊é&Ý›=ÖM¨óDTRNÎÐý®S“ÙÎ[ºE£áö”â×Ù*êZ*ßžxÓã©i÷=m"/ÎÿŠš·jc+<—(,×7’N£<æÒÁgWZô°åH¨ÃO{¯ª¥¯æ~.MÄŸ,Ë"£U¢ËS:ÞTó/ò—"[„íÔú…¹¾YŸ6Àp`5qƒý‰¡_SCXK¸ì?΂©&1[_bѸ×ÙÞÝ%qØq^£µ¤$útyB>ÁD"¸ú Œmkݬ¹QÐfs^Ob¤?]|øhŬšžóZ7õÒFš[™ìÒ2‰ndÓ%èMpÜ1%d\7áÉÞêÛ˜mïYR•{Oê›oìäT¶d¢œƒK=øÂßÌ9ª´ WÜëû JÇ+ñÇ¥ŽVÂ-á÷µI1'¯ˆÂθä¦ë”a‹kü×Òy>2îc¬‚@UÈìó_€`…=|· „¦2[‘q«U€i*ÀÀ]¤Sé¦òR]ƒW + +mñw&£‘âbFÓM$¬‡ÇC¿váu¾•Ý2… +¢Ø½:{¤ÖO +’2·ÃÒ?v̽ +¼2#Oé,…iô+³µ6ë +w:^è8BG]XèÍ9ªÄfNB2iÿ¡€Y]ON£&·Ÿ8ÍK/X/6ž—¼ó&îZþýLüÌë~³õù‹E37Ä^2 µþ¹¸'ê'ßY\ ¼F´7ˆo¦–¤byçpsë¯>³FC
vﶄŽG›] +ÒdYê‹™iœ(ÝÇYm8ÎÖ£¸8{ÏTmåûÙǺI4üà@²ãçQ$âæh¤FѪ.äíÆ,\Ëg[‘²}ÂÙf¾* Í®Ç㊊%×hü_5C(IGmv}¨ºî +“â[†Æ3íMŸTØxÈüLgEéÏØá!a»¸c¯5Jt‚OÞ¶ùœädèè}~ÿ!«HäÝì»åœŒóH¼ÂI +$`¶RJÒl–ÐðZÏ[f ½=Ã2‰aüµÁÅF}y1Œx +ܽ²£è‘i‡±WŽíÔŸù?(áà–òíÂй‰ +~ÕX‘N®Ù£ºr¬?¦ÃÇOŽÕ¼äÖ"à¤3Â]4Ók'OhXí-$Ä Ñœ~ÓŠ#6R€AƺpwÆüEtJŽUÆãšÌØ<®È
iªg-Sj¤5@ˆ÷äüéœ}ð€X½—ÓdFÓl3ô§¾\û C¡/\\Y¥.@ËÞK¿m·¾I‹J’<¥¿_²i^9Ì5u¿O?%Ú½LkYnÛ{Æô¥§æ›bܶdfÊJ+,ÓM>ë‰`1gy2~äðëÞñ9Îù(¢¾Wä?~òáCàÎ1 ¿M4LÀWXÃüv“úú|!>+ÑϾâÁ/yhäˆã¶ˆ²Ø%ðF•z¨ù=³§3¯!ÿ×Ó‹Ýæô¾Ø
ÝÂ5mÚ¢vg™afÌÄ· +v&Þ +ß?¾#VÁP3a½o€ÓÈ̉è-„^ö‘n°òÏDÏq¯Wh¤2ËKkðòø`YJº›Æå,«ãŽP*;E¥H
ŸŒ©*²Ù¿ueä%¢KÉvÞG³a$â"…Éј á³ +°$ÍÓu·Q%/·õEtC\e¼ÛYûž”Þ}äø4®=©¨Zèð^Á§ç)1 +4¸¶Ô{V'‘€€b€«à0{' “7y¡`½ËÇ¥xó€ÎÀx¢‰hÑÏ 0(7JQlŠFFiÕìB.|¡ÎËz‚S2XŽôV%òu´Ôе«„QE<<^2„P識v$
àjwBÃ:‘æ÷(”øÈ V‘&ðª¾1Êö¦tŸõYúw"c…ž
+®‚ÍFŒyË÷æÝ>**=|nÅ÷MCŒ±pN»²Ù!*p¶zn•›ö͇ÌêÚ_æ´Œ¨µäNïQCçW{¢_8pù—ø¦Dë^÷õ²½P- +ì¥Ïfƒ—|éD 0j9qú•^zc+‹õ»TH¤.éǹöl$Å]‹ë¶=/qgQ-Ö”þ/KxºN¸ßV¤>»¡±¨È#ÿÑ€kø3sÒÏŸ-Ãu®W` +½ù]¸¤W˜Ðã€y©Œ¬ƒ#ª"›Ø¸r¹Å•Œz6˜”ç|¬Z·õH˜´‚þjcã¨þ;Q¡yêÊU†È¸§0Ê¢|”¥êmqsŒõ=ŠOöqs¿}œâé4ŽhÆKF¸jDWîzf¯^Ùml„‚šŸšµˆ§³'fEÉìW;’îl$ùO,ÒÄÊú»¦)Ä÷Bü!É!Q¥¹Iãþ䟂‹%Ø4&ªAÕýZçN®Yþü¥Øµõc›1K'¡ÖAqXÆaú]~¦ 옪¿ƒU|{P¯o=GDXÉ2\K‹ô+Ê33Úmr+§}Lõ¨¼l’±È+j™³ªKh®á6–GãÌ:€§
WaB|íÓM©µá–i"ˆíÇCÿ£±Ö!¹¥ÿœâvy·‡4gÕ¥áhÄÐÇÄ×õÁȶïÂdªxj™„oC|fV¨Oæ1 ¿ó!1ê'“Ë£íñ¾Û¥ÕåeÙ`‡½9R™îÛCµ2Îwyª9Dï]½'
ø9¨¡$8`§x™ºD¿;.ÅnÉQ/×”u_& +G³waœé+aéìê‹ž@Ùuzývð³GÖØÈß„-›Q+ˆ/)ÑÛF ç’‰R…@Èšaχgú0g~â=Õd‰íÉú‘Éä B•åÃ:·éAÚ&§‹Šzì%“_‹ï³¿z38‹¥+ϺØðÆÙ‰·n‰,|ë¼ÚjÖ9£(.çJ†ê&—QÙu6D-¨è(¼–@¢‰Â³¢Õ JC°<dû*VõHþ4„Yˆµ´owj,F#‰«v÷ Jõ |¦p¯ÑÀÇT²€I´¿ë^xmÊ8-\™eèêæf`·íT®*,Ø×ÝpðkÎm·ˆ«Jó¥AýéŸà¯¾ƒï|§MgøqŒŽÕ; +?ôi¼Ü¡â~#,õ0
•P›Â‡ãåî~'náŒéë–ðmÕª¨P¤Âõü =sÒÎL‰æe§Ôo@Ö¢\×Zÿ‡Jü²,‘m-²òZÞÓNvKI¡™!Æ¢1mØHó‡ªI½
—4C‘&ô|#šeXɃ&E%]ð9ÜÔ_÷ò½@XÂÙI‡Üu”êóq²ÅaÊ·BЦP'ê¬pˆûcgÚÂ6JÂZíŸz›äiN:O;o^pîþpÃÓšÄ5¢±mr¿[ñÀÉxð¿Ö#U8×Ò²v2ÄAž{dÊé«fž¼vlŠ8•`q™ÐUÏUÌàx[«_]›FÁº6ÑuØô®U²*ð²¡Ï5è¡_·=/UÇ’ÿôÇÐmð •SP_!P1®ú\¿,t:×£„ãzË.µìlÅo52ó§ÿu…Ó;É5¼ø‡kƒ>ñ,Êú躜ñ–¡Õ裳ȶ0†¸›Ëôkš"Ä<ÂBÈçƒ&ǶïPFL1Ê<³ÌÍì4}ÅÀã(z>¨Ú8àräóèaž9¥Þވ€‘šMˆ’š]t{ƒS“Láµt´ïË$Š,y€gp7áîÃxä<Ñì#Û¤÷DJR +gò±1¡>“%éb(?ÿ¤L®Z‘9ðÚÈý•Þ[³Ë@é|`ŘPgXzЯфӇÓå6‡JUŸ_-•Óqp‡dºG÷KT$}8`
Vìv¼‚¶=7ýÕ/áÔ˜“Ô>kqnª€ïžsåüÚ"äSÞ‰?§Û‘³ =ߣ¡c¦¡M;>"ÜCã¨LçvÎ[kE¿XxJžl|´†M¨Ä$ÕØ¥#½ÿ<|꿳iL@8·ÆàÉGÒÅ䎘¡·„Œ.Þ‚¯Í!¡SŽ˜}æýYÀƸ콤ª»þTÛ¶ª©J",ŸQ%¾ô¥Pã$žðÞ‹à‰FY+ö(Yéü{ŒQNÕ£IŽFÁÏ.½!ÎÀ‰"Ìið%ÏYA{ká©2»Û>û> +h“S4/g½ÓÌ^í!;'û¾Šz®w¿½›Ì¼*G*ZŒ3ÜN&yìÉ°“Nc§gg%Qi¹ƒq™<>nMïðòªeµ9·‘þ´=ÆÿÎÂB¤5:^ð"óvh2¨r?¯ÍFu‘¿îºŽýùAýÿÿO˜ÙM¡;GÔÿñol¹endstream +endobj +774 0 obj << +/Type /Font +/Subtype /Type1 +/Encoding 1124 0 R +/FirstChar 60 +/LastChar 121 +/Widths 1125 0 R +/BaseFont /ATLWNT+NimbusMonL-ReguObli +/FontDescriptor 772 0 R +>> endobj +772 0 obj << +/Ascent 625 +/CapHeight 557 +/Descent -147 +/FontName /ATLWNT+NimbusMonL-ReguObli +/ItalicAngle -12 +/StemV 43 +/XHeight 426 +/FontBBox [-61 -237 774 811] +/Flags 4 +/CharSet (/less/greater/A/C/D/H/I/L/M/T/a/b/c/d/e/h/i/k/l/m/n/o/p/q/r/s/t/u/v/y) +/FontFile 773 0 R +>> endobj +1125 0 obj +[600 0 600 0 0 600 0 600 600 0 0 0 600 600 0 0 600 600 0 0 0 0 0 0 600 0 0 0 0 0 0 0 0 0 0 0 0 600 600 600 600 600 0 0 600 600 0 600 600 600 600 600 600 600 600 600 600 600 600 0 0 600 ] +endobj +752 0 obj << +/Length1 1606 +/Length2 10827 +/Length3 532 +/Length 11682 +/Filter /FlateDecode +>> +stream +xÚíweP]“-Á%¸ûÁ-x° +ÛÅm¬ÁŒ¬L,_ +廙U\m߉ý[²6Æÿ±øCXØÆàÎø™ÀÈÆÅ
àäfps±zþÙþ†aýçZÖ +h©–¹‡ÒÏî¥ÿ᳘vöã¾Æá\a
á]-ëwö<ý<'VF°Qì's<±´wó뎟ìò!èkÝò7¡n'»¬h}Á’ˆ?w”:Ò#ZbG;àwÉNÆùh2WÝùÍ)42£;;òñ×þm$ßFÑ&š°äHk +úk¿t¦0–éøˆ¸H¢ƒÔ;ô¸‰a +–øÒG•·‚h©*Ã~ +ÈÝäë·ˆ_ŸdYýcïò¬Ã…¬¦œ-Y°¨FªüfXÜ.èè92c®4[UdçþŽT<¥Ç¸¨EûÛ^/¢\Ëbß™á•YU•¯ú~uHâš¡¼ åå`-[”öN̺#œ‡—Î¥˜âug`kÎWúíÒè÷%¶ðyíС"‡†×í@ÿ.}Í?ÊŲèÏ +´Úe/î.kuš×Å*J‡i’Ò°3µVlS*Ý°õåp™¢Ñ‹?¼6òâf<SvUvîN7úåð¼öÜõZSŠ_ë£gõ¦)Æø&<³TqË€˜Š„»-±–n]V˜Å^§ú e‡µU´…(G0}Ò¸Gêg*®ùˇ/oE¯ +²êã=€áÂmöT4ž¨5ÙåvcBkÖƒÆ7Eªe_æÉtxIí¼óâ¤ÝŸ“ƒAóó7Ãì„E^¿âžK }{ü.Ònn–´Â +!•F6uøP—¥ÅÕ*˜{]/ú!>¼’WY¾ü .Ô»9ï—öé2ÊL +ûðÇÎj4¢ú·h{Lw<¶eÌmYEU¡=G5ºáü]U¦G¾ªlhÜmµŽÎÄdÕàUõg\&Ê"ïþèìÛ¾<÷}×}o§øßœº3·âídcŸbx”ÏR'Ín\ü«iLãWdîvˆõ‡Ú‹«zG1%m%Ñž4`¨ª’K³¿•af®-÷d31a5šãWÍÄ>DúµÃN=fNT“™Õ1ƒº^ôèŸþ„ßGFàŽ%»ùY~R*µÊŸÌS˜•GSìbW›_°·›Q³¤eÈ%DVõÏ‚óxÓ÷ÜÔx¶eÛYBšÈLÝ"(¡G…†ñt>¥Çåt™DYCè•€})%UN¯„àÏòúWBžXž!+š<Zø+·½$WÕ䆚Úù!÷š:RƒÈ)úúwГŽ‚ŠÁ¡Ïôô˜É£xþì"ø&yf{jöGèG4Ÿt^"»SÙ›~¥°å¸É(cA׉šÎ«7Õ¦ÀÉ«ŠîXl°âR==rJvש/)<¨ž~SÍ>ì†vÈ+®[æjÌjd/HÉíÝ›S{êÒÂ&~xûAjòµG‚¤¾°ñdÙ”Xegƒ?ø|ç)d9õ+(ðù(¯n>K“è‹Î]B¹\î7•`Žð¦JJ¹ï˜ùÃÈÚÿŒÍ
û˜`0оpGKÂý ¶ôªa¸mq!6xùÉ‘üŠûÆ”XÞÛS t$”Ë-´!ÌR3i°V8gA PûŠ³×©Å¯ßlqü4ºm6Í×ä==g_ŽpÄÎ[ÔúÛ;„H| #„$yü™¿'"·» +J6$èÄ”mX%»¼áçòÜOJ
ƒµ"`Ëoód +|Фó›ëm¤Ë)Åu`®AÁ_ØvñNÆæö¤ÈþÈZ¹bœ»ùLòÛ¯Æ7®J‡Þ R†FygZËÆÚÕH[? ²Còå—@ + +m÷Q7ô6wúê4¥~ÔF¸ÕŸªÚ3`nؾQK–T&*) +.y戾h©¬8ö¼:XLžI:²Ô¯
BÑdHå¥^Dl– +;×éJÍ9¦Æ—rI“LÕ<QYU‚Ýqö—ØÏ
±3ŽŽmzóe\˜¥}µÌ#ýxËc—±~Ž~£•ðŠ&ÕJU…§oð³ìŒzÌ©¨õËZ©>ýnX¨•J|VvB®ˆz„nO‡pÃ=Ãxrãj!.v!Û“k~þê«ej°ëÀþr±%Úœ£Ñx·ñC·ºþªÁç›Ýp}e¬{•‹_ˆ‹—eÓM^5bëz¯NÆÂ"Öaãƒwî~#ò!H<_´· ±)¬Kˆ¬Sì°9/ð7]B6s+ÎjR·`9
¦n©º¤qþúðÉPõ‚_!1âa—oKR.i/
̩׻ÕØתœËLc‘¿ MÂã‡}Ææ|K¤Ì“ñaÚ÷6Õú?˜§šeø³= +ù€ÏEÖ¤˜d›b‡Á³M°jôîë~¢[w£ö•ùqB'g h—ñùž™€‘$6ö湡>Ó’2‘yp>T;˜DŸ+¥JX3êå´Äcþ ^už/ƒù—Bl¸ÑXqÄøªÕÒúšÆSâ¬7J~Û“>p>˜ôT;Ë<çß$ ,ÛÈuÖÎËð¢Ì¤2EWŒãxý.€œ„„±±ÃùÑù2Wá¯'®FlLŒ=,FˆÞ³Qĉ©®º˜Bî:ƒÈ¼×7») +…[ßÌy™NIVÒ0Ø{@Á>IÖs +÷„²O შ˜aç™0¿~“™çôø²’ifǯä]'Î&¨Ïií~¦=Ë‹o!ñ›ª <^»!•O7ï'îŽÝíÉø‘}² +d?™ÉÜ9÷°Î‰ÑK:q·‚mêËhEsŽêÞð솑ˆØifÕ¬æ:U¹Ë «¯‰_ÄŸ`Jøþ[¼<†éXhqyüÏlÌÊc6n´sCºñ°×v«m¤"v)?Û5«Ð‡Ê «xIíÂò¤Ä¯ìlÛÉ4צ,z;½=è)âL4Ù¤ö ÷a¥ ©þÁ39Œ8¼³ +Ÿqò=|è!zùQ³û>î´úÕZs‚•oO“ûŸ/Ã$æD{¿´ãs“ʱð•fkKŽ¼‘+hø^:ÙŒFGÇÜz“°i¿U¦pUw}rܳ>·a; Y˜‡4|‰/½âæe3Ýâ/¯¦Å¶5ž w‘ÄùcQ;hó`=ùчÙg°øǯA»ŽJöÉÌãAáÚà™'ü¤ªÒ«K ÈËutј²©Ò*¤ðIM‚_ñ5‰ ªä¤®[eèicÃZ :X}·1ÿ· mØZO`ÿ¹t´ÈŒ³³ÛÔµ³¢å0Ž5P÷7x=í²®@”+³¶ý*\M„ëfB±áD¾ÃDNØ)ŸOë½{åeTÇieÏ3q²"¿Ç²1ÙêÍ‚ÿå¢óðÓY<£êd=~ªL‡ŽR˜Ê):$NìGÜ‘@>JYò*F©&‡Ä©µª§[å¸hŠúV~ðHò
YC Ígºi9_ž‰ö¢î?*‡¸æ¶1ñ·ÒÂÜnF탃 ä‹i†Ì Þ„¹“›axŽ8Uò©Po«1£'Z‘0üáñ㛄ÄIÞqðà-fžNªIKÉW*¹QÌKu×Ö'v8R½ op–ß.fË.I9þe=®\‰9ßJå+4{Qœ Á@B2ÝÑ2!œ_H¡+>ÕCÓÑìþZk%:üÀSe,ë)1>tcoÆd9õX<k«4^ëAh¹µE”]´ºwb“Î#+¤QF’N̈n¸%ùO9ìÁ³SjÚ×ó‡©Eù¨Q§ÞOHcìðÈÊŸjÇ,j™0
©ù6NJݧfqj ÃåªN“„å&©!·H+è-Å,åhK+çn-'Ñ +,&€'‘³BBWÝ]ËFÈZžÛÊ.Qeë©Š78Kb‡Jj÷GŠ´öT
*÷+µúþø5pf*?•oÌð!”r4jHÔ§øGÍZKÉäýh¦NâÜôpxK;‚Å\ ·aûfâ?–°WNÄPpÜîÙÂ/ß…¥)^!¢™S‡îúf¥·Ç)yÇÞ\"«Z'ÚàÓ Šª.›òß½IK›e—ãBÃãéÆ{sJ¯Î¯(+©:äO'¡i×Ç–Û[\‡´w¤RÉ?Í_ÚéT–ï²óÙr°k”bK(<œÐ…v¼:Ôªhð‘÷c+Ë…´*µÊÐÚžzžuø}„<eþá™›z$šÝãdÔ
$ËŽÏL•¼.f_UD)l‚÷iÁªvʇLHŠÑžyýÌ#¢Aö©a¯Á–6aeS.2kÌ€vÝÉV…>ñf¾Yo2*Iš§€÷ûèøâYîýâGae—%žÍolúÿáRoïRRÏUë¾Ì%ó/´òsZ»¼ƒïwT¡¨‰ÚeÉO…‘AÝq\Ø¢šÎ9ö÷ÐÛË;G÷®±5[ +ñˆ7L°{Ìë÷=êO“£5ù•z‡9‚f±ë~ÊŠ1ÚýNÖ¬:nœº†»F±¸-ݧµ|^ªëeïj:[AJs¨‚×™†_ÓôqéjÃõ0R·`EÒw6÷´g2|
ó$ŽÄ°¡ù„|¾Å|àÑšQExæ»Ë‡!±sýø8{SKjh2ÎJ:®b%àðñSŸ(™µ +¤ó¨KKÕw-YòZ’Ø°jV%+âfJWF¤CRg+Ø"˜H!?œAlaª&‡È%¹ M~R€le½î܇[¼¼w^¿”+ãr &4`ðRÀºƒ‹êFé
0蹘Fek ÷ÊÐó›†Ãˆöî¶÷%A{2ﺎ±W™»îô®,°÷}]ËWýâ˜Ú/“ž1t$im>r¿3Ñz]]ÍV-潩,@ö$mUMŽ gŽÚlk²Â:[„ƒªõ¥Šá,1"s/À°„Q“ßêe8CÈd’ýQt€)¯s|øãžémÂþ{ÚŸ +…Õú~Ù¡ÚïÊ6šãÛ<)ß¾¶4øWªd‰î¹Ñ»÷ƒÇ?V0ýô™BC¾%VæŠsBµdnçnaS«#þrZò]ííÚ%~¤û+æN±ìD_ÃÈiƒJSûÍåŽLÇeþV´^5¼µà‹‰†èÁÄ3ψ3××>ÑEˆy–†Œä&¹À8ÂXØÅGƒW™ÓÒ˯'äK¿¹Ç’¤Õ»XY«üæû·Hã&Yøø9’¶š†®¢×@Ù‰¾öš¸[æN䌾CÒnU†
Î@ý«RîT{
÷æݽîIöÀI#Ž
<qÅ$ˆäĵ\WgRv™ÝhóôFÆøi¯À{Ö\q4ü½`–
•%ñ‚Ï:«M”†!í¹JCÔð†Íߦ³o•F 6=‘v?qNr¡h¤K ¥©d+½âz®wÌÛ£&!Ò)EÎA‡ì_ÐÔ|Û÷ØSD±iÍ%ü¼±ü¥Ííu<S²ú6üC<þ‚Å”+²ÀmðÌ™û`ÿaö[ôžëíÜóNˆù‚ý%*͇ÊO·ÅÅÝ¿!=J“®mԙɨk¾Ð%´7Ò³lÓ|ZŸ“»¿Ô éWïŽC>r=Õ8/÷¯4ªUÙÔ r\Zñ-2õ
YËu*|Þñš[&B³K`Ä°ÛÒÇW¡Ôž˜9Òü¼]Ó–GžÊó„dþñTŒuF`oD݈*lLQ㆛³~yÝá
ÍQ3oŠ ý,²ˆ¸ÇMßG *Å‘i +ÒŽõYRæ&‚˜M°VUÇ íî®}:øõæ-¦¥D·›‰¶Ùd?†[¾Å®•©g–Ë:Z8zßOÖÕ 0÷þ%Ì3…?+Um¬š¼â¡O¿Ågæ›4ђ㪙²ºoÜì1|q]£n4†ÛÒ²¡âÆZdcs>9¸œ9j¨ƒùx
Sá£Báé„—Wij¡ªLàðË~À+5kGòã¶Í–v5ÛŽ·É—Ö\‚PÊuUØ"øl<›uaðËÃ4VðA7Ÿ']feœãµŒ +ØŒ.™wÈÄFÄyÅ Ëú˜;1á¬A›<C½8òàhÝصMk«§ŒvgA*¡anö§»óõ±Ö7Y•ÚN+õ ý®ßÕ‡-ͱT*q—Wdš¨—«D€»ÿtáIu/]ò¢Yµ`6ûµ”m¤£^l-ˆà§»“°5pÊ^ ç'ÚÞ6z'x~Ó;ùÕÉåî†ãj1¿ŒÛ2º0#áÒÛól¬,:#Á§ì’l’'‰¿©‹ú“ +¶lÝÝ2¸„e †JÌo»Ýª6fwT 6—猟Kó¥ƒ@C4ÝÙ˜ÁiÒIéRx$^ßÓüQhwjAbSj½©¡$ù´â‰M Còg¤µ ÁpH4.Su¦#Ú*+ÔäjjUôAÖ.Ÿˆ·1êùðþðIL—<=Ù
#:–üp¼aOº½Â+ú%¤[
K1
¯´$æ€ðÀûˆf\³+/XLW”\Ê¡›Ÿ +GíögëOù¢içQ[{ƒàäó«"{ÊurÐÛ=•ÜÛ‰ë)m0¯#¶Yp*¤æîÊ6?ÛtDŽe&Ù/vÛIÔ!&»~\ "*ùf± +«-BüUx1Mö…RÀýOY*žŒÖ_¥ù@r'úÀQHD?å¯<ç=“Y¿NïšG\³m°±+*élÈn'd+b£:ÔZT¶QplI?šR(ŸãÖüÁ’â +ô‡Eiì%lÌ/8fp?F‚M«Pa"WïipÖsKêÖÅêf¡ä?Nì@±Õ llˆ@ppÙ; ~Êâcö€_ö¶5‹¥*“]õÄÑ’™b"oz«!rI%‡B1À±û³‰“&ÕçW‡k Õ,Ø ®&2-òÇW4œwòãþ +7c¯nœ¾î^Ä°ò;lÎÿ¾€ès3UG¶””ÿǪ±ŒÊ$”‡Z0~í™vÄe¬‰Žžax˜ë‹1š·ç¡f™Ä±ëÈLfе™G¹mqˆ8¶òøY!ÿü(’d"Ö +gÕ='ÉÕ¤ìÔ‰O*l˜›–A¾l|4ù0©sçÉWÑ:¸ëÜn·ƒQP²i|´žöpDZmô’³X›/3YðæøÍÕ6ÌÎY&²«~cú=fdPÅί½BžÈ”Cu‹WàõÙ¾a-± ïPúˆš†\Ò —(¼÷”[æ§ÞÐ*âvƒ‡x!9§1™ÆL‹–¿:ÜpêpEÔ•!ý`.øÊVcU +Kºx–%†ùw~ɺ×.P#Ý¡?…x+Š4)uëi¥Çkþ¨Ž3è ÉNÐĪ¢KÆ´w!0™wâY«^£³`$ÇG@Î]÷ nÛ>^çíxèû‡^‹±B¹ˆ–E²öKsafE£<Ý´`¸X”Òv‹IeQrtíø™~žˆí^Qç½Ö‡VÖ%À¦M®¼š‡à_±7u˜rÕ9›rÑGÀ](·BšÒš–¶—ʵsM—æî«ÀƒôÕÙÍ )‹¿´¶â™ôê`Z55Ù=ï…˜ŠoyÅ©9#-ý{"ÝQ×užWWš¨Ú鳕A,ÿÃñÿü?`؃m¬ì-ÿª€þendstream +endobj +753 0 obj << +/Type /Font +/Subtype /Type1 +/Encoding 1124 0 R +/FirstChar 60 +/LastChar 122 +/Widths 1126 0 R +/BaseFont /WKKNMB+NimbusMonL-Bold +/FontDescriptor 751 0 R +>> endobj +751 0 obj << +/Ascent 624 +/CapHeight 553 +/Descent -126 +/FontName /WKKNMB+NimbusMonL-Bold +/ItalicAngle 0 +/StemV 101 +/XHeight 439 +/FontBBox [-43 -278 681 871] +/Flags 4 +/CharSet (/less/greater/B/C/D/H/I/L/M/S/T/V/a/b/c/d/e/g/h/i/l/m/n/o/p/q/r/s/t/u/v/x/y/z) +/FontFile 752 0 R +>> endobj +1126 0 obj +[600 0 600 0 0 0 600 600 600 0 0 0 600 600 0 0 600 600 0 0 0 0 0 600 600 0 600 0 0 0 0 0 0 0 0 0 0 600 600 600 600 600 0 600 600 600 0 0 600 600 600 600 600 600 600 600 600 600 600 0 600 600 600 ] +endobj +704 0 obj << +/Length1 736 +/Length2 1155 +/Length3 532 +/Length 1704 +/Filter /FlateDecode +>> +stream +xÚíRkTSWUƒŠ‘GNt!ˆ!$„!<¢Ä(/)BšÜ$Wnî
7‰+JQ+‚#Š¨(‚ +ŒP; +Òb)RåQZ +X-Œ¥j58¬íúsæ׬9÷Ïù¾½Ï>ûîó¹R£Ö{ò¤ØC5žL:Ó„E\À¤3H®®!8$ÖÀ*Ö@þ€Éå2O+ÞÀôõgqýÙ\’+ÁTz–+4À=dÅ$‰xJ‡%bŤ$4$b¬Ç$0¤ÑÓA€hò„ˆ 5„ë )Äd),Ñ€ 9Œ’¼&ýP8oÛR꤃p5a +¸&W +~ê³IL_UPçRÆÀz˾ñ;s^ðêÂ’ç<Ž|J
M¼oÿµ§öÌE?¸e‡xUYÍpÌꔟÅ.£½±£úÇ·Xk1¿ˆŠUU×âiŸ´S9{6”×ô>oÍß1¸ù”ýàš¼nÍÃæ³=Oë9áNIG÷×-l½Ç<RYL[ËqS9–Î!²&qúßl»Ç(6VÎ…7Òµ$8šŠ +ýY_pÛUÚŽµ>bôò„“'3á ¸e]©oh¬Û«C®å7‹ªÀÜGd8}æõEå/üè"3‡§u‚³‰YUG¬X>áAÜÔñ}¦=Ý»b<Ö“õâºa#Nv¨³é=Vo–iJoB6]\¶ÞÍÓíz]fóÓ°Í%‹v9ýð»»<…ƒÍ•”ëí'îtFØKsYÏæY§ÐöÞM +*ò–B5úŠÙKüÔ¼¢ÜÚ4w°"ófx}版+d'oíXe5í¹±Qi±ü\†Úªðô›\J̹¤²äu‚ÇUÕSSO™Ni/TG-ª—? }bm(ydw$æ` åäîPg»§÷ÜjQά®eg#‘ãÎËç–dÇ„_ow“QËqßñ®j¿i[ +¶6ŒìL{õ,jáíó€y¦úÎ Ýé5cÛóEütiÁ}<™d˜øþ[‡ ÷ñüF¨‚âx¡¸ÄeÜBȽtµó8%Ãi¯ã,]¦{÷ý*°ÛñÄÍGðâ†íå½1ÌÃM—v ¥«v¿.âÉ6¬Y»YСMòõbÖÇÜ~{6znŸ7W'v› u[ô{þº¯yÐ2tîÓ½äƸHÿøŸ ×`J1žBúY2üendstream +endobj +705 0 obj << +/Type /Font +/Subtype /Type1 +/Encoding 1127 0 R +/FirstChar 53 +/LastChar 53 +/Widths 1128 0 R +/BaseFont /EBVRIU+CMR9 +/FontDescriptor 703 0 R +>> endobj +703 0 obj << +/Ascent 694 +/CapHeight 683 +/Descent -194 +/FontName /EBVRIU+CMR9 +/ItalicAngle 0 +/StemV 74 +/XHeight 431 +/FontBBox [-39 -250 1036 750] +/Flags 4 +/CharSet (/five) +/FontFile 704 0 R +>> endobj +1128 0 obj +[514 ] +endobj +1127 0 obj << +/Type /Encoding +/Differences [ 0 /.notdef 53/five 54/.notdef] +>> endobj +675 0 obj << +/Length1 1647 +/Length2 14109 +/Length3 532 +/Length 14987 +/Filter /FlateDecode +>> +stream +xÚíyePœíÒ&înÁÁÝ-8ƒ{pgpww.ÁÝ58îÜÝ œå}Ïw¾³uv÷Ï~߯ª™z¯î¾[ªž*r%ÕO¢¦vÆ I;°ó'V&> + +@+THÀ Ç÷$”\Œm,M +}bådâü‡ØÒIÒÒdªdélb03²y¿¼¿åê`S£%ô^ä¿ï÷݈…åß05Kkð_Õàü›þ{ïuû;fEe ¬ŽÃÿaÛþ¬ôÞÎjö ÀxÒT°3ýÏÃ_Tbbvî +ò³œË¤é=i%g´¥ú›0ä‹Çíürò»z€Dè^¿ð_Â!êÎë¹*K½rîNö[“Š‘nç½ÇÄ:5Üè¾p€¿0¬’^1mú”/Ñn4s¨ÿŽ¿¦D83eiuþ`Áwå í‰{™Ü”?ܨ{ +LÞiU{þ¹8³é'Ï·N’Tjô`8Ôψ8±M³„-&+ˆJÙjbi–°ÄE¯mL+IzñŸF°G
st*SbäÐØ»pæDóï… +Eå…œh?…5€Ìj +Ï]´¸õ~Xí×`^bÄ0ÿOj*}ޤȟŽrÏkÎ RÞ¸uHIWŸôÝ„<WoEžjéfùßà;¬\VöG°â§y(ds|¡ÍÙ×—Vý˽H±£x¹ÙIK0CzŸ†¥}˜ƒ!’¿€° É£U®à’Ñ—u³¶^˜ïþjØI…P™{cE™Û¤²;ØEdn)>ÞgÒ!ƒ©eÚØX¬‘©=¥9IýBËàÑ!œÇi$eõ¦µDôb2ÝÖÁf£Ø)6‚ Lu€·:·:¬âO Ó³ìé'†Mð³6Ê:±×T”ÿN×6Dm ã¦g)ª¾Ï°¾Z³mÉô®úÛ´à°°Ä*l××?[Ý_’eGúèW+È5\ê#€ô¨Žr†à¬Òßxɬ0]”^f%í°²&¶ÉØ<Üé´ˆ[wcÕæ?*Ü3>2a®çò¥ßêE;L”M«==@¡šÞ±H¸æ#èHú=.ôR°7çÅÔ±?îôre¥rêó³õ +ŒæðÒ¶´°ú•€¯óa,’—ÀT–Ž+™üxê‘ΓeQ¤®ž¶¥…lè‘sÊ)M?ó»M×AÄ‚ªSKÁßϾ¯íDxVQqXvX44*B„¿H·ôî×·Cã¼ Ñr>¥¾Éõçd›ºXËÔ£êNVÐÐf$6॑V²¼¥
Na.ßnžîòyÒm{@ÈV +á×2è‡QùœD’™FŒ@âFÄÅ£ó¼PM@ÿ¶|^|6)¶
&\Ÿâˆý]*>õûËÎNLfPÕ&~P=ÞårÔÂ2Û¯|ˆYÌ*]ûÅ…â$´/÷öÛ!Ñ+?XxäPTM~WÕÒäCî|`éEcÈшʞ¡Zšÿ +r—B\˜¨P†«ãýÌ/-ÄfV ‚né‹TzTÙ"&Þý‰ÃfÎF‰¦Ez¾fÇRªÂ¯Ë‰9J/áXÅÌ:–`M õ£‚K¿ãˆ©–¶½úéê;~T}††Ö\ORn:Z›V†³û’õe¯—~ÀXBŒÛˆš3Ú±>õ.ˆŒ*¶:)ë-ôpåÓBa @E™ðV°ÆäþuF;íÑ|tãEµX]]!cùûòCu‡ê’—H{¿Ê:ëGî¦ôŠ‡g-ä¥hQn‰*]Ò¼©ÛúŠëkã¶å×@ í#™6&¿~x빿¶_`IÕEûÇ÷¶nk^P)úM| ‹]Ÿ-jÈ¡¾¡¸t³#«¤!1ÀcíI]E–v¾”z¾,oNùæÿö0ÇPÂœghäƒkþÇ;¾&õ3“ìå/«YV »wäk1ôŸ–ïˆ?~‚—»þ°EG)GÐG?Á9_‹•ú9Õ4mÙ ^!.5I‰)ÐxùyŠDÃRJHù”ÊðÕo¼µé—„k×FýØ˜¼kÓ™Ýy*q(e§Ž +Ò“ITo±ÌíwÚŒñ7V +.ÓÛÕ™Ôt + +»î•&‚Ž?›«VSmK›(–“Y,£?bè¤~Úð”q5?ÅE.F?.,J%×Ï +Ù‰øC?Ýû+èR=ÄHO8wÖ3¬!uDîkfÀò ïÜ8Q2ÅžºÒÆ´‰Tù³BÅ7‰ØŒCÒ¡c°>—òq{žú·eŸ£üE:p!ñœµ\ƒý«)I¦ü²Käñ·"¤d?›Ü³Áì¦ìß0ÞùS4<±›1Î|<iÐeWG\4MißP¸‰áØÁ¹ì®î¥~îp4=;U4—ÓÅ3ÑŸ^âr5IÐSê┣b’ûïÂàDNSD?k¦XSîÎPJ-U±ËPè[ÇvóeA*b&^uзœ…ĈßÄ-#šG™ð=Žß] +܉Êä~
ðtîzå?%cŽ6#‘B‰›¸ÖKØÙùçJ05ù,cì"…Öæ–©.éoØ>šæäPdoÜû>Þ³-Gåî²úØÖÜ4æ–Nˆ]Ún'Qé:Ó?3½†6{'o81É«W¡»U~â}·k…ôãïU{æh]*ÁJÛL7¨ÈYm:xD8OØ€'m +3#èpÓZ•wÅŒ'…ÜqEzí/wÒUÐß„JAwj¡d›Žƒ«?:e5ó +Vð~/a´.e=8¶«òÒ õ•+ÓÑ—¼àà-À/¢“ˆ½wMË\¨MMË•F-”$ʃ£©JêëB©Š|±äu:ŽÚƒ?ÓR+Áj›tÚQõm%&!“æÓ8k¿À›¦i³hWÑs:—cµÛ€ÓÉÎÎHÞþŽR4}³î(l“ÄÑŸ: S6HæËu6ೄ}ص>Yª_fÉøt*Ú¡ßæü¬dV8nºû7㛨ƒ.X96' +¯ÆÅ“»-ñF)æ(B88<kò$öévc‹›m5“‡§a +3/è:Ñ4¶œ•j}ׯs°Är±~ƒŠSZC3o±ç:ÚQÚÔõŠYP.·.Ýúë +îÜja
*ßë?ú½üÞHpÛš·á½©ô»WA~§ù7Cw$×µœÕ›HâÖo;é´õÆï)§×,lhUϲ_Õ\6Ö“F³1âZ‰?-®ü’›ß¹Í×®ÃúÆñ§qwøñÐò®g?š²"^³=oÙÂæ–)yÙ;ûyÕÃi…Rl‡LÁ
ŸEÏÍБ»ø[ðÛ—€ìj&¶ë~&¥@¢|öæ%TþeçƒÓú3ö¬ªùëçƒÃú†y½ZŠWûا|çã·?J}½0†XC™†K›P«`겎û%EXœl>þ]®"‚@Ä$XÿR²iefžYvµÖT?Æý=hå¬FRUª‚Ú $ºgµ¸Î*ðÊU)§(’éo³|*×®~‡Úg +
ïìœíŒ‹/“ÂÖyj
ìü@xŠ–›áÊ KeD!4Á±0³r)S²YÃ¥.EuùÊqœëKCÈ"ë#¥à>œÔÑŠw3~*€µYP‹E¶n¸·kËCèsŠŽ·
Áí9i¥N¸D0”¾Ç¢Zð¡fhC« +þ¹˜ã”¿k‰TÁ0Äå +&dç:¢ÑA†…NèVÐloù¯Û¯¬ðÞ,¶í‡ÚÕ™Ä×¥r͵+´+Û.À¢ +˜ûOzBª‹{ÝOÉI©¥Ÿ||ÓÊO—ñetv=7¾+%~¤êd@Ú_öÑ°ap•¦‚3{cÿ£ÔB"7äÁ`Ìx/;Ä^sµÓIðµ%”â“[YM†{ÍŠ%}£L£÷H惆¸b"ÆU&Ê·ÇgGûhc„«s=j +Jn‚Ãûy©\’å²Ð׺fõS}¢H¨
ù««<²&sø‰M¾9u Ô˼£ËبoG’
˜ÊÔn\ÆrF†–fFof'—9xSš—:?Ñý6Ý”TÝ‘Š÷êCâÕajqËü<Ü“€¸—ÄÜ[+£ô®Ù0]LP£ž1"þoEâ;‡\ÓÔAº½†ÏÏ»}S³^„AÕ {†o®gzÏUüèÚ…¿èÑ %9zÙÚÒ8‚ZŽ®„àh¹yøü›ŠúHˆ!Ÿà/î…"eáEŸm¾nPXÈnðZSÚ«ÐG¶F!Š_‘x?8M¬M€„qÍ&„XåߌKeLÔ0bÉPEfXúª^¨Î¨d0œõÆ6Qp72Ø›'}5:OzâÐþßÇ=G³mqbªC59J©½`§•ÇÅ•ø©Î$C§UŠf6g9º•øXÍüÅnwXáÕôCçCtH.ÕSdçoÏséï +ˆÆKa ä'ºüÊÔ|x?À_ž+½>&´Œ³5Û)½Mùé˜Xê!ú6>`þ1Ù¡ø¼Â¬™<_Lîo|†Ý²†~µ©ùL1ÖQ¦ªå¥±ÄGb¸í°kL)ÊLlŸ=ìl—yª]DŸtÏeÜ´3åê\_§ßÙoÃY¹ÀÈ}…uIHÏè8Hái7“îPþŽ‹Ö¸ü¤Ïºiwò +_š-`çóŠ+ )¦Ò¡×¹Ë}|‹åä(œ<ÎCU+Ò;…ÊxÖ©›‚»
äµ™âƒVóYWàwr„oAƒp™3† +Èã¡zDbqpiæLn³sVV©¼ÐJ•GÕÂuÚ@C¨³ÑÒ6L8å¢L?ÙnXƶNwLÌíÄl´ßMÄEsg@róÚeQ‘Ô’a²®‰)ƒøe
†ž¶ß›ì³¨üæ(°C¥ÌŒJÌÖ¿â]ÚúŠa¡®žRªÚæî\Pn `LHb™à&¿Ek(x(.çóͦ:t±3Å +(À#ÛË¡W$#èÎ2ÈF³3†ïp¿aíò]O¤½’œGI:åT?zzPfL¨ôkÄž6?’Ó…Ùm0’¶ûX´½ÅŽ"º(b?uárOì¸ê¼Šã£tõ Ü’ 8QÉÅç»Ðõ]^X¹¥’Ù_Å`4G7¿CJáÖI½.S«úê¹ø“dÆÁ0Áû
#Y]n_Ož’¦Åõk‰öv&Ê8ë—uY¹˜ÉÝÓæ°_ãJÈÙO~e…¸ö&¬ÑB+Ø49±DV“:€~VÑßùÆŸ2´DžF|WH½ŽÙê]©p=Œº +˜UÉÏ•$ò·o?cõwcVAãèp]Ï_ +ûe„2&ŠÇþÆÆ…ÆeøC*bŽÎ>ð|h”Çô¯¦¦\¼!Øš´sÛÉ¥!“½¸ëÏUñ„RVÆ—î…æh.Éù”A)|D¢„Ì!ÇÃCéëÿä3#DÇ©3ûËc¢:÷W÷OÜå^t§ª»@wÕ½7çe“44¥à¥¸öðd>J +8<ßt6{ ó”V§ŠÕ±¿VïpÐ;Ã5NY“?®¸ÀðoÙèšÏ´Ê™ûXÊÂoÝцoÍ'"†ÁÖHh²qf[qcnÄEÀ!ØeG¬Ó¯MHÏ]g7â9¨Ÿ¹îò¼õ1àïo3€=Î^ö‘ÏíxSW=Nƒ\+Röî:0CfnÓ\ÞD'«¡H>ù9î‘æ‡+÷e½å<èyÅÃû¶©F@XŸ¡mX¹¤6…q÷4tæ¶ +Ÿ¶¹käו‹i k”f,iè«š9ç¼lY¥t¥3 ÔZþ$Ï&ÜBsç+Ùh‚ˆP$Þ¬±8–ËéÆ4Ãôx³ sfªí1Kí*#T^þ ×`å´j*oµó!3¡¨c‡æ³ˆP32•&ãú)\÷×9bÖµ^°Iѽ߸YPxþ¸-øÞÅ}ÖË`[çû1,é„8ó¤üe4êS'~´ˆ ¹Ir*<º×7ÀÚ¥«_ï«ø0‚%¿@Ú™$¹øF4Ž[DE”dY`jHPhLµj”E©ª¯Œfu¹VèdqÖhÜXßWÍѤҹãz*uä0f >k›«€lJ€£8‰½‘M"̓¯†×„y\å›MCIsMðãO«Tb ׶©¨ûºù5‰J<[¶Ë;ãÞà ¤ÖÍm• ®ÅêM¢]C6SuE"4³3mrÑ0½j:6“-¢ +ÿW”ôú½cÏt÷kÅg+9PªÛkŸmC¿éÆ2sqo÷•¡õš–ücºñ«ë®|è‹ü¹¦Þ§u¯ßÀrë󅤼o”ïïô`~ÑaúDA´£ûÙr¸>âuÛ#‹Y
,kÒ.»LÕ9õ60”b#ÑŽWQÿ!¦÷Qbü¢Jê*ãiBøVÇ*—‡=KU(ÉàŒý.äªÑu5{ða£D—fÙ¸¦|L²
ÞÜ:ÅR/ž¬õ[GØÏ1ÇUuä×åó*Q’ŶÑȾéCÃþËÍA,êÈÞæ ³à§hC \ÁRÁîP|Š%‘U7¿º*¯>n’ Ò׫V–~³?âWƒZVÒ”@П" αà¹$ùÓöL +Λi—Ç:No” +Ò"Å*LZUK¬§’2-Ç·èn{Ó`7 +èë 'pú…8[‹¡]°Sù€9»ômŽôÆ!ñ5>ª€ +½÷Fö`ûá^s9ãp~Eì.¹0þ1/Œ¢Óðç0•³L +Ú&JQ&.Êãà +O)¢–ØÜé{5õê)¥áªh®”}ÆÚøLqÐó>N±{ ‡áj”†eòäüMæ!‡‘P9¾‘Ÿ‘•y}àËñâ”®q¿)‰‘Ó½8´#´‹æ[·i®T +Žõ +äàÙnÂ9N™ 8S 5³yTØŸÚíÔÊ{d&÷"ÖõÁÃ¥_$-Ýöf,¼ZIý¸»„ÑQªfú·ˆŠ"8[9·Ë¦ÑeÐ…_äÙd¾Õ&ÝÎñý‚y¡w./éIi½j˜F,¦î†åŠî*Á›0ÿZÅÉV·¡8^wßs@j›
ÉHÖ:’Q<Jc.µð—3nŸošÖ”¯¥! ÐHÐa‹Fp§¨NºE•“9ÔÇüž¬ˆ¿AK²ík‚Bù¾<àì®$·Éw=g¬¾v½bÓ4îf?°(²I'(†æ+v—uÀkÅ¿:¿¢5©ËEBÄ`É@ñ‘Sߊº¶8ÌH–»+7KãAÂœ¼Ó¦Èˆ¾Mhàk›þ–g°ågɬ$$†ŠV¥ÌY’Z]¬q6‰r-J
× ÝÚw/jS¢züÌ4wܯK¬ç?t6m(«6`ɦ)D˜>[çô¹0ÎÆxŒ¬œbBG;½Mh{ùNØäZ€ÇÏÄpG¨½-XÙ“H°k“íõå¤ ¡9§¼S>ÃÿºŠâ©¦`(2{:ee³ëk
g`ZN'ë>€»Dayº yZÁŒ=ɬìí¿0»W
KÊáü¼;Æ1Ãaª¡ð*O¡Çˆb©TÚÐŽ~]IÚðó§^å~ÀŸ3hqa9u?¼cÔ›Uw$@€„¦<K¸Èœ?Eº {øÃJôíd^f~Ji“(|ï—[:çÈšmi?$eõun^r%ÕºŽL Ȩ{=rNZû¾Ðx+¾|ÇX2&ª·3›z•÷Œ¬¢CäÄÁâÆ<¶ÅßT6.õ>ÌÕ7ål÷ŠOœ[À‡û«Žü8)C¨ë¿z2I„Ïkº–!º¶4ëÒƒíDàä“”Ú¦68x†m¿gÚvàY¯•[Ò’]µ¥®‡íõdn߯X.š°wí&_§3¦ž:±«wkÐa¤KIû)–¾}…îôGcH±rb}µÝØ”J\?Ž§$T‹óÕIaCÁ/s¸Ò*Úþàïñ‹¼|ðWWᑧ'ß”{Ijõ¶qãóãï²9ˆÛJ
†¶b¶@uñ©»O6Ž©ô‘¹PKÇ~!í…Ôàu +Dç +£¹¥ß¨‹¯?^9¿)oŠJÜHW¬¾ +aJà“:âŵü0ÖÌ}nÝtû@³“¯€\B×EE6ûâ ˜®¸|2Èí`’jÁÐÔ]4—GÉÇ@v»¥ï|ÌwäŒÈ”~qù昚—´ÿcÔªÄÐþù¤ba«æ->}Ó\Þj èô›LÕð[ªË™N5´9ÃcÃe€^Õ·jÁ-må“ ßɱn¬ÑnËúš +Æöø—Ë…OâÉôvœ"aýs^…·Ð«ßpN/Uf3ÞZ¬ç~¦¯¹®ú%ØÊ0^§þhUêÁ_j¬ÊˆœaºÙ+kI k&ýšé
s¹Ûªžåt¯ÚhÀ[{ ™©Äžao¼`–!ì«YÈbèyÂ7F’QüÑ—d¶ªÚtB’CçŸ>¶Ä‡{•i&“‹¤°J7a´&B
eç¸ +â®å\Z-UZâºígÛ³8¡ˆÑŸŸã±•ƒ fñ¾äÇánQüŒ±8!3ÅïŠOì€iâÚ±“|ld4YQ@êNÀL‹×d¿“ÆÝSó)dÕ_ªtæÿJê,èG¬ÃE½'¼Ä¦”0¬[]Þ…ý3<Nø
窳ZXv<\fØÍ‹•w–CÄÃÿÏ-l>žœðE£ŒÇOÁ½½WG=!–œÑ +ë¢g4TÝÌrÎK¡ƒ8]uÈ°R‘½ŽY*þk^q•L.´“¶~Ó²\$¸î ˜é~imsC£e~Ñ,Î>'aïuj43ØŠA‘ðÅ’.|HîsÜôéÊ tŸj
=dª°B+Ìôs9þ +¡'5°vÃ2Û¦$f
†´¦¨¼<~PXÂW|:ž|ó
q:þœÒñ+yEŠTWo¥âö2žƒÌ•Q¯?…%´{pgœê¶ŒòÙ Ÿë¦=n-–fZY=c ÐLW6#ŽÕ‘îGàÌ(ׄ•€ B‘χùj;*+¯…×;$í#§RìNЀ—Àæî‚ )n6uQ¾$VêMµ +J
âºk;£w©Wš~ߦŽûúÛ.€3OŽóyu$@Û+œ¯mêˆ3N’²Úl]¶*³”õaóˆ +íã¯Zv +´„×DzZ +pôÁ2ˆ9$mÊfÕ
Ády$Øè Qõ£ŽcJuÙb-OVÅ&×Éáùù=F5¹ˆÕï.ºxe
éËKö–L$è½ð…‡4óæYnÔ¥¬[w ã £ØÚZÏ;θGƒˆ3¿b¡JÇ1}7g%]~Ƙá?ÿ†„¸•%©ïñÐÁQ䣓G^ª!9Åc¹~IQ"öP ˆþÙÖp°„£Ð—x0²G6ž¥~±ÕÐc´™¢û›@:åÖ6CÓõ™ßoí:)ùŠ«ªÑ64nËŠú÷YÌbüÖ§Æ>ˆ•5ó *Wîåâ&¹ê_–xK¯kz²s¶Ûµæ'WzR-u]Ì‚¦¯Brý;×CÔÏP5¨J¥ÖŸ¯¾@R¢… úÍùŸseè*ƽ-”ÛO‚ígÏ|‚ªç/™ +—§ñpÒçŒÇB€"Â9¢FØâ}³š5ôeoþkíú3´í´º_ÔZa >‰ñÏoF#™Ass]K.¤7‡Ñ1H©§5ÉÓAþ +X©Bóî´šJ=y>5ç{: +L-/"£gÌ?.iìq^M¬{N^ ¿ØãWºÊ–”bbîKw¨ü]\’3âà)µú&û”¨€b'œK g¬±^휾ÚýÅXùD‹L!.Q_¯w¥Å¤ÐÏ=Ïï6¥vB±Ã2ˆå§Y47†.• %™íÅïágº6·ßYiÆF8õƒ >æé*Ëlj*¹f{´þ®Œ#Žg‡Ÿ¨öLá6N +ÍyF¡Bã÷úÉ<ä› ^º€ –©¦‡JòÓ–pÂe’œK¿ÙZ-MP®íB·I/p4ÇfUZñ£íi}J§Êý"_l2Ü™#{e-.¼º'ƒÛ…›§ó+Â)·zÉ‚nƒ)ü€M=äœ=/Ɇµ +ÓÕšéû‘Þ Da±I¶Á‰Æ}äˆ
}A§ß»Ã¤¼ÄŒS1ž¾mEí%dÚoF‹Õ–íáûxŸ÷¨{Ðâí„u +Ý
ÒGÙ?·¢ØÍ›"ûŠÁ#ßK†Éfz™í3ýb&8g¥W?í+¹ËÚ®H¥u=æ$ʆãÔÐø˜·tyVN'fû$Ý‚ãÃÅ]‡@]îƒ{«ÉÈ<ïéÁ¡s¸5€õ*ÂägQU>ËèNù¡húÖüaÈåÄç”Ä[m·µ&›c&—Ñ'?‰Z:y˜¯›½[Ágçƒæ¼>z" M’ItCP…íâ—C³R]ýWéÆ€¼®Á´O¼p5u«7ÏFr´J/QžáÜt¬îz%Ÿ£pà{BäD%aøYÓ+¼™v
Ù^¨d–¾ +Ûëh܈.…ÅØhâKe˜x‚ç¬DIIÇÒ})TTÎÓEàËDjL!tí/cEá9‚Œ‚–oðo·âæý¢÷zTäÄÀCþ(ûQåß'$Y¾^®“OWíÃ~DRÓ!³ž8`Eì9Å-ß(ËLe¼h®´£°á®.>QŸR¯¡ßp’y>ÿã¾2ëSF 9W‹g¶ŠÖn$MÊÏ1›Ç5Uˆ ’
‡<ôï»,òLÖ£–9i0cCÅá‹pìÒæ}ÚÎD.´_JO*u]§o‚+á¶ÍŒëâvïBŸÂºhƒØDô”•[ì±þ‰ÛÓŽí&su˜žt¸vå:^Z¶ØâªÀ-Úüñ@(ñ}º(~O–C?–Á¼ðoM_U¿<ÏîxR€V3#—påˆSˆU|_ù¢”wÑBò®Cƒæ ’=ð6ÃI'9ÜÐf9¹f#ÕÜ-]*”2ƪ•'<k[/ÜÄoÊŇF~ܸ°¥H0_ÈøJH¥g%K”œV¢“7' O‹~YùÀƒÌ ! +ªèEþçÿ“×?ü.R×#ÏÙ7|cÔc€|ñž¨73¥CÕ–Äç;®äí×?#w§«² U€\{kÜÕbóba +^¶ò.nó<_‘8>#ÚÔp‡„Ù>žÜå
fß•ê!Ë¥N†ÿ`/Ö{6>¨f¾åGÁÑë¥D¦u|÷uSw]Ä«Œ¬TµH¸l–X¿mˆã¸ÆÍq?B
Í™¼xÉðð¡%#‡¾âÓD"z`y¹ßE sªâ™•nfÑ~r£ðØޚ͒H\BTÝœØAã#.Fñ_ü#waUš7ƒh%¯>!œìïÀ½ž0¯°ÿô€F¬õf +ïZB÷©ß.Qpfgf«ž_fJzršWoá›d[Ç]„PvƒEM.9HxârŠÔ¿S…¯Y‹rTžaNt`9Œ`åuŠÿv+á–÷É_dù/~þ?Áÿ&6 #Gg;[#Gk¤ÿª‘î2endstream +endobj +676 0 obj << +/Type /Font +/Subtype /Type1 +/Encoding 1124 0 R +/FirstChar 2 +/LastChar 148 +/Widths 1129 0 R +/BaseFont /OQNJZM+NimbusRomNo9L-ReguItal +/FontDescriptor 674 0 R +>> endobj +674 0 obj << +/Ascent 668 +/CapHeight 668 +/Descent -193 +/FontName /OQNJZM+NimbusRomNo9L-ReguItal +/ItalicAngle -15.5 +/StemV 78 +/XHeight 441 +/FontBBox [-169 -270 1010 924] +/Flags 4 +/CharSet (/fi/quoteright/parenleft/parenright/plus/comma/period/one/colon/A/B/C/D/E/F/H/I/L/M/N/O/P/Q/R/S/T/U/W/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/quotedblleft/quotedblright) +/FontFile 675 0 R +>> endobj +1129 0 obj +[500 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 333 333 333 0 675 250 0 250 0 0 500 0 0 0 0 0 0 0 0 333 0 0 0 0 0 0 611 611 667 722 611 611 0 722 333 0 0 556 833 667 722 611 722 611 500 556 722 0 833 0 0 0 0 0 0 0 0 0 500 500 444 500 444 278 500 500 278 278 444 278 722 500 500 500 500 389 389 278 500 444 667 444 444 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 556 556 ] +endobj +482 0 obj << +/Length1 1612 +/Length2 18281 +/Length3 532 +/Length 19200 +/Filter /FlateDecode +>> +stream +xÚ¬·eT]]Ó%ŠCpw9œàÜÝÝpp8@pwwÜ‚» îîîÁ]ož÷íî¯Ç{ûþéûýØcìUUkÖ¬šµÖØ›Š\YQÄÜÁ(é`ïÂÈÊÄÂPÙ™º‚ìåU–®€¿FND**1g ‰ÈÁ^ÜÄÈКÄf +G2ÑÎîŒpñ@çÿÙí‡?6å½#ª¯YZ]NzfMÁÉ)uÒÑÃ=MÿðïÁî+ØÞ="†œ¸OT|n0(ÔQþN9DBº^tpÈDWmïdZ]^†yT”„”‘vòÏÊ–ù+ömò¯É°mBüI +ÎÒ-{•Ú¸—3Qü"I<Ó¹«®T`<þFâñ _×S6”A“Yðu¤ƒwP—´¿Àá^5v?éŸ{QsgOªt”Ô}k†Û£baüáZ‰*Öâ¦òÔ_•³¾–~·Ké¿ŸÓ2h©>•7Ï5p®ÉÂWf:i +;Æ9nä*0i¸ï—j¼ÔU•þ\tªâÑî{åö‹ˆ2¡Ìáž5J[©*ý–¹¡D££Y~Vý!y¨«¨ÕŠh>ø»¥/Or8eügT\ÓÙì‘0½“¾ÃL÷
{ÌôŠã +Ä©Ù ÿ93=^y>¥ˆvá +?çX›½›§AThOTqKÄþŽL‚‘˜z$'2•ÈhW9áodÉó™jÓ¿ÏQS˜ÐlàÁÃàúÓ‚ëCÒŒ¤VßHîGeé$ß”8â¥W{tiuNÍôg²{ŒVóbuƇ÷ËA‚µèà1-+rZ¬ïØΑ4Ÿ·§è/9(&«W"ÇÉö·&³šãfn?Þy,·ª¬czS>¾èvʘ<I`8w€Ä6˜!2B—fÙ1z´„´Ç3Q‘ g³.¬±˜Öb™SO-pàØ +F*Ὸ’Gö_¹¥ZÚv[F½0¦^02éÚDWó%DClYäñjP†¯'ï^dæ´ p©ô²}ÊWE. _ê¡V¦¬úÂᓶ#ص܎‡»ì‹3÷’2»÷p~ÀSƒnvÚüÉ UÝ1“‹¶¹Ò˜æ©s7çA²¨ùmVÊàвA +xJÃt^åµ…n×4™gýT²3Š ++¿Ì*”Ô\ZµúØ™‰Hjüjä'øLÒÕ°¢…àuhF?8s(÷ÔW©÷øú´ ±‘ðÝæŒ+É똼ª…møX^(‰†šå³wwƒ½¾!ûFÛc +Œ6eÌ_@ÚNVfÂ8›íæqѱH¼á«žNc“†,€àvº£ ó$æµLÑP/•¸¾óñXòßf?~^PÀMj~zùL¼Ô+&™ã±Àÿ'!iJ–/üU~u²ÛؼŒ[üšM3ÎÔãªìiʶç.(ÕͲ±ÖŒ?`“§£Gš¸}4:L¬Ø³O!Ç›BI¦9ø©“BUš7À‰[
¸¤©BäGØúK";ZøýÄz†{‡hLN¶ß¹Ç¡ô›Úò÷³s5ÿÉ® 7„æÊÁ\¨šX<£§í’®ÕÎbIƒ‚‰4þÕV;y¶ÿp5â*,ÀYù£§…<TCtÁŠ.ÀGÒNßv}ÿKPÀ +š_ý,dÅÿ|vþÖU³RŽñõ,G&|UFk”Önàyád|Öþ@Lþ„Ç/\›³û²&3sÁUÁkòÂÆ*±çÅíOF9ìô>ÝDâ/âó‘ü“n¥hv½J@}G}óùÝõ2¯CÑtlèþU*´@s›£ƒ ÐC +ÅR¥^ù·O9GÞGÆ205#¤{¡¥Ø¬”ä~w>éÉóǬýˆÜòö7*ùÝì<PóC,ìB+É
ö°A‹¤»ñAÕ¹r +ýv;DvŽkLî|9È*ÐìlB MÕWžÆdq>6L\ÀaäÖýEöPs$°QŠs£C›µü ¦Âò§Š1´Þó`?Rqôb1¹7Þˆ¾Ñòú-{ÁùYe=UÓoVæ÷ŒÛ;ÿÙopå—òÑ•íÈNóOôBg«˜sfâ{ýWÂŽ”«µ„>ZÒñö¦¼ ²¬=ůtcEo†ÕidÅ<´¡Ž +Ì.IþçdÔj5(¾Ò²á?¤ßð>P=áu5”r.ó +i _?@E](pPdI +èö¤ø3]‹cBØîÓmu¼—üà;IìÄfúòô{›¾–]5»Í\KUô£ÒñxQy6ÛE÷£ž(¶أ&oÌá +Èo õ̶´ÅA[ÄÊ+‚›——Rª»_Üb_e&bó„¥ý¼›7GB=mÒõŽîªù–JÒðê)þ¶aòÙ±¢Ê_.)âb²ë~EïÝ¢Âuëñ‹ßô'ÜŽœ…“ÑOïEš"\¡a9—¨Fø¢DÎQ—§ÓlÚcÅÈ +Wg¦½aÊúW$ÑN™¾•:¿jõ›Õ5=»ªLÄ^¯ƒb ¡×¾5p7ņC +ãý9ô,¤ŠÀR¸{‚O³'¹qo׫UÖÑŸ^H-£}¶(ÍøfÁøH¦{ÿýh +˃EŠ1ç•~ LôÃR"sÀãeò/ÄÊ47Fùq×¼Ô.‰bà_»å,ËI•þC Ü!£ +¹6=ÃX!òò<¥5ÁP¦¦Ó©ÄzW(eÞJyÿÂ)¨bC|yÄê ©Õ”\gÞ(úÌp‡î:÷¥1òâ¥Ä"Ëß{Ø/!3JøÊcØ8ËCÜI‰Q“g@x$¨—KÔì×.’ét»ÒñÛÕ#F‘Ì‘üIw“Óu |Ù€|_£À•®„ÞÒ_¬÷yIŠv’ æ‘Q‰.#F·©ÅŠñ—”ï¸bÍL?Ó›àüÜ#ë¶:FЯzŒ ^yµB’WÈÕ¨=G4«)ı_Cžƒd_3Ù6€vhµ›!Þ¤aáÔQIàzðÑÿôjû$íC¡¦üD#Ô%TWé, ²ŒP4ð™!â´ó ג£ûÃdfàÚI&;Æ!«%ĸ—;V‘d€0µá¤øÜ°X”D¯nžy'¡š:IÇO˜#Ž°¨…ù&kÆÏÞq´€Ú®èÒ‹Ïbh´«jh"—TfÜ_ü¹%'1[§ ¹‘²¢Œ‚™êX·[HXÇ;»Ð&ir9m—™H]éÈ1®æYô¿ÁzamÔœ–ÆDR®¶$A +°¹òhòl*ÅGA¾…ùýúqønà1öµ²L)KËXÙ#Æ?õžw¬J8ës.I +1¤×`Xr +úLßȯq†ØoâN2D‡«3uhÁòuQž$Gc·fya¤é<ö<&‰ß•§¢ªw’@UîãM™Iêû21ò¡‰blª=Ž[çŸÁ¼xë$Ì!l!»|¬û=8¤œ"ø½BTˆæ>|˜PÕ@eEK3ògT£‹ùæZI‘ïi~}tzâ#Ƕ‘ÝßÜDÈI/4sníŒ2ý~²Áþ!À&çʧñ¥JM² +Ćv$þäJ¥øm‰‚ìssÿ“äÞ@Ü%#B›x«o›þrx‘Š.ÁžT@íû¼Ì©¯ñìdÙ.²q"CvµxŽÆÒ,õÍWHsÁ´)þu#aGŠm²ö“ý)qÔ ×²í›šs”„b0®¼Ç0ñW]Rc±w7ÊZƒ„[´×¤þrxûp’¶³î¶WybvÑ›‰úNf®-î;;|ÏK\YaÆR3Ý’¯ÔSÜé
d„üb=~6 ×¼$u¥1’`D@מIø¡º•_3Añ{6…VwÍ[¡ÖYðOÆ®ÒI‚CRˆjq”v»oßjêènDF¥8,açC'£4ï‡5±d²ðo]¹ò‡tŸ;åòF©Äx¨úæÚä€þzð+iL‡Ì⬲î‹}„šÓ×ÇÏÓkÅ<ãënå)ÒúGó“w—`&(WIT<…§ü\¸Bzf¸ð”Œïß^Báw•\ÑÕ¡œ¥r— ,á¿Éa
;ÕÖ™{Èõbĩѻԟ:–ÒÔ‹uˆÒ¾Œáá\ì<õ‘déËßwˆ\Z°´uXá|ô¤„ˆïS»£×ì[n柵Æ>Ë1ÂAÙå-bĬқVNàLGÈs×}UCã¢ÊÎhXƒeÑ+ozJ
Ø,í6‡ƒÜrc©†¤iÜVE®çÖö˜²•Š&0nCÝÜR;z_/¥[†!ÉÓø»›Ì‹äì}’’`Â~Þ³Ò%!ù¬fìVëK„ýÎw#mãr";gXD¨íÄ‹8;¼ß†eš!UnBs`³¸.Ö€Û1Buc®²Âú¾É“Gæn;fð‡î°U¦áµ¬(R•¹r¬™uš‚ÎRü§úÌL³¬û/H‚"!eɢrw*+ÍÙdÛ>£©èòLË÷ácÃ'¹ä +y2ö¸»…ï¢È¸h˜}Ì?ÝÍƆ–¸+:P±´³ïNփ̻—ìÅ9 +ÔgÎW™¯n‰ÎÜî ´»ôýæ ¼hÕìE‘Ž›íLÞÙOJúŸ&"ñ~d²çfäuåöñ:2úGV$¼É br +¦mGÞyˆ¬?l +öwzà.NZ•›NËb]¢ì¥B˜!Vîi3z¯Z1³o¡Ó1”˜àË%ScbnìFc+
äêëüq¼/÷)•Ûz/â=‘-.ÅG$“»øŒ‹ªªÂAÀrÉn?×Ägó#h…éD·â›Ü÷ÉD“_¹.“ȃ»tÌæµy—D‘,ËÂ’¾´·Õš
6r›ä¶¡‡ +‘)̤hXÂ.‚ŒIŒ¬s¿…Y;¼gµÅf²ËÆ%»».¯ŒÄc!ÔÖïc†Q½Q6òí?‰OÒÁÏê²³Qý^Öɳýt׶¢Á©ˆ‡$7Îàjºêlw¼¸¼ß«&ó¨«ÅÒ‚2_ß®yúÏBpE"Äù£’N‘£&Ô€ÛÒã¦8‰ÛBÂe4ÖÏúØÈ•û'HPô¹NDg#¾8þ|t“60û&wZ¬
‘ýIx»´RõzDsn[Q]‡»ËóâäUg9‹É1€u†}53TL¬ÌÊ
›íPhsrQá‹ìK䯫Ú2I3R–ºvëŠúWj;Z{Ä9nfë¬J·¢·ˆ÷òÍÄñÏÐOŽ{f˜:bÙM¥#Ét¶8õË&<¬quîÆn!ç9 "±µ’&›#}>{ +…M!Nð”Ä¿˜ù€½Ãî´¥¤Ð–ïµ<]B83¯'øÕâ"ÚÒÜ2¡K¡ÜTŸ¥y±Óüd$¦È81Óç;j©ŽñR[Bð,#zCø°~,E7yŸ•åÏÊÄh~ß„£í¯cíÛˆeùÐ…ãÌp/§”㡪tWÆ<üЮ.`ÿ⫼æÇA>’ØÁ1V,âù#¨
?“˜Øâ B{È,µŽô¦-᯷¸ÇçB™ÕÈL¯„ü¸®©yÙŒLïHÖî|öjÆ›®¦1+Ä«y)"Àmz3ñy‚U¬±CyÑ<úÊôɶ,ا˜²
w9*ª‡GM Òù`›uËŠ\/k΃/=ÆC®]‘B’ßVÛ¿d£âWi=ØñM +¸ +3j÷§Î.|?Ñ? F?*Y‡Ò¾xI1Y&»•ZàÁsþÕqÄì_ýKWt‡K°¬›^‹ÊrXX¢™øPöª1ö6côªlRÚ€Æf󛻣4E…^žìótXj åö'Ëvõ(7ÜÁq}F¼T:ÌŃäScŒ$ò-Žíú¹W>O_¼k_Ãi¶|Xèý?Bò¥ùLÀ6àï S5¼|Œ’JÆÎS¢ýÙ½óšôɃù^CÃ39».ÌdÜúµú×¾°ä†;ÆÜ{i}Ñ +‰däÁ¢³¯ùÚ¿œ÷>)e×ÖåÏp[)¨¬9—J±÷H¢À±{
®ÝÌä^d.ú„„Ï teÒcYš°ÆZÞIŠ¤µÈX¿jÜ.Fkãx¥‹ÔÎ û¾À*Ó¦â!øÈ`ó¹ÿRª¸7q*‰De¯±¸¡]6µö9?f|”¿KõtÓÛNÉ°e>Âæóðp†E(ojL‹JˆhJ 'k€
qbç¯ùF&«+5†ÿÀ41o+@ÿýÏ‘Ïén*†)Ë{PœvNp彑°gCµkÇn¨‹¥©åÌ7'(_m³iÍYØ*¤/ú†
çñ]q×_öœÕÉ•'\Œ,À‡
Ï–´ßË‘Ñi$¹m,-…¸tANðßÊöR27MC¦æ—ŇL% w.U
"Ê×ÇÓño×íÞÎŽ· M¯ #æûYsS¡Ã¥{ë²Ó&¸h¾†nȾ™ôr •bLÀ%¾»MÎ@m“uI9!_”C&Oä«ÕhpYÜßo†@ó,XY"†?<9î’Uå?¦
Ñ3v•£n´0°zqd‡¿ +±‘ž##çÑ…ëˆ^žsû:s¼Ç#vÛéÏW§†Ã2"Q^ñÞRÜÅØÐ +ƒ7ÙºïX|aÿ„uŠŒd¬‚¡ ueêÊ^î®fâÕ(eÒvõ±·š¡‰‚^ùõžÛwÒÜsh¶,/[ˆ° +ÄÌ÷ aß}Ù)„›Áó¢4[†îyV¾-Ú +÷ÙXȳä¢H€Ê+¨JµõÕwöðJ#³I˜™¾Ð <†:¬& +Rð*Ë…ëÉ£¯UE#ó`°Ó‚î7ÆZùÛyÞËQ†{ë“/íŒgn
‘ZCy”Ÿ¶Ø,|H£e½njàÄéÚ§rQÎÎ-\rqçå:Œ¦_6í>[›(JiòÀ{Ý "1]C>P1=%ªð`ûÔ%‘é¤$:ȶ(M| h&Ä“õ¬6_Þ`B ³°ùrBËTu0ÝRëé5N*ñ
‰§.Üä{Û†Kˆ/ò_¿ÅÛ¡˜17sÎQ–ÎT*
LZ¿²Gt=׬˜3WŠúŽqƒHrM°ÄÝæÉ£Ÿ¢jùw©_Ë +ÞïW}…„±¦À£×ˆWæH”^î&ôºçyɈbN|gøÑWSÅL¥eZuÝðýÉÖp†é槾a;èýoÜ<(›/‰Ñ°=hy7+2Õ}¡ë–ð-½J¡.ÀÔ°þ,;e»º3^>~w÷<Ét]ÅâB}¨œé—º¸1Ö¦œ +k?d²Œl§„qCÙ…ãõ‘E¯@ær8²)1R°03esñT +@oÖ™/Uå}=ƒü¯|ˆŸùH„L`X¼0Î\û`ÊàÁWb[âË1ªr˜›¾|þÌãLüXZb½®»kò‘OK +"†él&ŸŸ\cYËV¿ÓIhpóŽNøRØe/ä¸åâqÑÚLBª·»Eš¾šØŽ~ áczþ +˜ÞÐ]ÐÌ’!c:±b†d6œ”•ñ‘rb`¾p³)PÇ}MæuUÅ·[qq£9Sß57Œ(¿‹ø4Ü».÷¥Ï_”»Q/œ¥ZÍy$³ä×xß''ëõó-žˆîJ-ã¨W9Ms
N{د+B‹éR1¶O•¨Ì0?SÇ¡J^3©o•>Ùd‰¸~¿vèá"Z|ª¦'“¦Sšê.‚Ùë¿Ë$ñõ ü²}qH•Û]§odJM>’ÓêµÈùƒfµ‡î=Z¦Ät¼Êb¶Ü©rÆ¡Îú1ø®P÷K)M9•Ë¬ý%ü§@m +£{ê©ÚØ&–pLƯ-ÇW¸Äˆ†:Ûá:à ˜$Âæ~Q[¹ÔS+ܽ"GŽUõŽ¸EQ×Ü’¸»`ĉíXGû¸Âs„þxŒ×z¨i-‘˳vPÁå”H‚æh¼ V³íAô¦ +4½§LpŸ÷³‡óÏž†A§ –ò]_Ösõ(M)ƒÒÇ_“–’Ñx`·|wFE6ÂÂÕÜÑè)8ÂŒá6ð=DoŠP¼ÆÎÌèõº#YHCfÜ¥þ6ªì4h@®zÎSêUÇÇœ¡R±£·I7t ¼`0¼t«t˜~Œ‡ÖGLÕ©o*‰q„íÙ× ç?‡:ßáÿ„ñ'f̬Qm¢ê™g¢SÖfv0w{. DaýHËí[ä™tÛ5ô…vYã@“Áµ
ÕéîÚéç¬×ÌÚ9*Ù`Ñ8$”qBÓ;3Í,eÊhùw¦ÏƾîçkrQxƒ-Ìò-3‹„_IAË'Øâbõc[â‹/ø5éๅuûÆÛÕHRñ:Uÿy£F‡ÛŽ’9w醬ýäÞm—,)$Õ|›s¡ÅŠ_Zñ#-ÛïÌ÷g#nBÃ)à(› +P +@-±Ÿµõô©O ö…bz¨Þ®lç~RÚ—F-{\)ú©ø4J8ÙMôyú÷Uöz7Ñê^¦¯MÊ2†êRüɨ‘ž-=7‰£¬s«üÅâFD?}Ì? #!Ú /7ˆÑn±fLÌÜ‘RÁX¦%çæ³'óU#ßbAï54Jçéé’M§9“]9iÊ&)šØÂñ9ô +(ÚoݤF¤7—~ñ‡h»ÚˆžÜ#aÔ÷âÿêðâ•X\_BÕ)ÄxÀ<}|–[E7á
E²‰ÒÀbↀ‚ÇjêòîLš‚+C»¸:Ý;D¶1Æ‘±éô¡[ƒ&˜nvÇ–øãµx.I©óÿŒ¥Z;^ÔõÈ‚ÅŽ¨Áûñ>ºŸ¯ƒh_Ú;™›éNwÔWk"%‰è„'ïyB5EÚz>` ì¨nÊXæØFšx›pœžçIJ¥LOܨxm*q`Ò´²oJ‰±…yVú³myP‚ÅÛ<7jp²ÚŠÍZ×IZE ^ÂïYgì9›ì¿¼(Ú-\jòR‡âºžÙ/wœ_~Öþ\µšö‘xg¬vÏ°nÈÂ5ñðùŸÍ]}+ ˜Ð„°˜–ÅS:ðÛ¢•®£.Ž!o)_ ®–×Qǃç9˜E= H®¨N +ø¹~µäº¯*$@ÿÁmOª 1TE€×ø½XÁì¹s»´°{²OÝ”Üåce$»«›ï¤’ à9›ÖÁùVÍrB“ÎKµ¬ys'1ÿêþ3Xb‰%>¤|îw`s$çDL"RBÈ»§³
Í:8q1r熉«, VO*ÊŠa¥d¯Óƒí¡Yt²'Þf.äbhC|±'pðÇ×ËÖjóo*ød?Vb! +á¿õÕñ•„A°ùR@v\Ž¦2uOág3¾ìÏ. 0_/L¯œ{»ÅM°aDW*s¦PA;ЋⓠۡiŸ¾Ì3íO\±~D6â˜Ô +Ö'›Ø²8™)Š_M‹äaÐVn›rùÉøQ}ôÐ'‚èòÝ‘2È.XqM©At!¾‰>-uú烙ÌîûÕeúí2(•E«~“z+wº-R;õ §2zŲŒ!¾¬á(wgÐPÏqÉÔWˆÉÁ§,©ëÁ³ÎŠñž( +eBúãÑÍtîšõ
HÅŒ0D¢wm{© +*izBf|‹¤p|G{¾œ4s¸Ò…+ÐH¡M1bx&½~_åš_bR‘ÜÍÃgGRýhç…êã(¸«¬Fý=LG9œ;þD™/’0fJf–ç‚q~(‚€w¬÷±,sê¶jvìeøæ0ß9è-&úpTéþž¶üüƒkŠš¾%õ^Ÿ÷C ë +ú¾Cé±ØEÓ’÷yû©^Üx«+ê’‡j
ôÖ〯ZcÿïâÊ‚×>E^£ì«4&ŽGŸl«ÉŠÍÜ)Ÿ¢á‰½‹ÚgÊ»CSÚsôð;îxi°•úl´[9o6'ûÚѺxÄoª°vK'Æz\áX··\RI(câ>ùo¨ø%À÷8o
áÚÞ—ªóÁj°°½²ßá·?æ0CZ § +ìb(W +#“UÚ:·³ü“WÃ-†Œ‹„Ä?^_Ð{›KZU£e„§æø˜[hÊLßšoجŽã@‚ª½ë£a•_:•‹±uâXˆ¬}5Ë32À¼|FtÍÉÌ}m„w¥™ÞËhŒgwÔ¿&è•„íHÚ탵;í*Ý““fkM‹l}¿Óvô—îtthîNATÂKhðÞ½>?ˆºÖÇ•¸XR:ıw–¼k˜sl‹¼ O®©U1|är)½²û.€{<Ò73jýCg¢ÄgzÖ© mX¸9ʯó‡‹ÑšÁÜ#w¼ÁÙ¹!Ü¢ØóØRW"7Ñ‚Èæâ¦Hží-‘Ÿ Í2à<s±œAŠQ6Ã[»ØOR_ThBÚíD9 H<csUaìt®˜I(›n»·†è€1)£N&) Ž[tÎR‰go7Ö5óØûæ6tâõÞE²Û¯u[BcN!ëñ'€ú@_/:RìúéY®ë‹ÒݦÝí¯ˆ·k"'
oÖ +1ÙŸÀ#š(¯a%y¦¾º·ºì£÷úH{‡0w¤d(áî±[LQ7²þ½SÍÙg©¤ÛýêfǸ„fáÈqÈ—3HfwY#à¥H@'à“i&BÜ‹¦Øgë!î;Ý€‰žÕS•ٞ„#QÓXb–g¨ÒéöAZ¦ô•™¾*…îTòµ9ºœŸh3‰Ô*ô~·øœYM¶–~žø—Ĭ¦Ñ¯ãÅIu? +ÑÎÏ)SsÕÃ݈®) +· +²x@6´˜àÛŽDjÃ&]1tèa–LA +\Lt +}q¼9ŽS "@CV‡´Î7¶®GÂ×Rj\=ñTýAZõu˜«]Ú÷.1柚ǘ„ø„j×?2Üȱ’øв´ß*XÑI™Î_^jã…´%íÿg’®wQ3ÐÖ,mÅyî0aP±Pa²@"6°ó'W+Só¹ŒÆ5PÕõÊѼßÈÑ£ÑY}È[…µÝO¾$WËú“,’HÕ¦©Âó&yðæ’î¿HtoPç»Ë^öçG¼Æ‡²t„Þ‚±"p6(š3R².ž/Á¤<Þ®Ñ×ó-\Ó
Üɤ#;)0WR'ðù=^:žÓff0¡ +mÐW±Ô@C‡·9¥B|!“N)6[RpœDÕìÖL@1´÷É¥öÍe/D`×I¨)Ö#R$ßô
TãýiR§¬Pog½„nR$!à d774Æ\Ml߃î2-²ÙþWã'¹FϹÏTs,»ß‹{nåǬäíå>”ÈÞŒì[Ä‹ø³M¡{†ÊÇNüË8—Vì +!qqÓ¬ÍKKÓxDBKåG JiPŒÇF*P£õ-Åkˆ_¢®©™I¨Ê© BæþeÙrîz9-ÎÇ|k3;«ˆšÃß°àÞuÎׯ‡NpáŠ<(Õ½®1¹Æ°yúmî±~ºA÷•KÄ‹)¶î¶ü–´J8Ú‹’D$Ð=+.ü}¸ä×ócù¦q‰®ì¶:‹µéC]µ¯?›»éVR1,†us@UŒð +Ù6öåœà[¯éžRI¿Ïé~jŽ¸gXØœÆæ³H·)š_îR¤œÃ”xŽ½Ä¨ùAùɈd&b—«oóF„»{q¿ÆkŽ)ú—’ßOTÔy0‚ùt–S³„ï€N} +-,«M¯ò:Á²KÙY ÏmÖм,¨wRtŽÉÜ»§k®®0º¨ì6ç3)c3ñ¾ ×´×`—¶6O?\ +ź
9¢˜Úì‰*œ¥`ˆ—!:¯ümQýûÌ[«Žù’ÅgxlþLñmOÞ¡8Œ%]Á2&‹˜þŽ¼Ò˜âÌM}@¦7^y,XÑØ]}6¼6¶Öq/ýÛ5©mFs·pîÄÈYd’aÜSLÐR\Ê ôpú¥Þ^Õ‡íþ±Bk¤å÷
BÏ.aò}ˆœ£Ä˜AÑ«âmPÇ,¿:6ÀtïZ$ˆDW—´ì&8.0œ–Ä‹OŽj@Ô + ߣ¸$rÄ#j¸¨—ô-Ž7¯¡áGºÉ,M‹9fÙdá |m¯©îq…%RãZ·N÷pž”É9[œ¶A–iøÅÏ”»?7t…)…±H#Ò%ÁåϤ_ÜD²˜þL¿Ý20z¨énŽ=팘(Î"°‰U#ót+‘ÁP-áoý%iS'ÎNf³ûÙQ½ò°Z§¥OÈsÊ(>=!,à²bÑéO +• à„1¨Wsçç…¦´K¢—à¤^A…î!™mÖÝ¡PŸgFöió+·tæé£óu#c"Ï£ÃPùÖ-19lµXÇÝÅUútT0ªÕRC«xêŸír€¤ÅñÏ&ÆqŒƒ=?;]æ¥ +¥—jÒÓN;Þm[Õ¤ØIÎ ÿú²Æ~Ÿ`;ÇÁác9:¼Ì¯ig—¤´2©ß6±^d·ÿ¬å˜¾™€9Ä%çO;ݺ_Î}Pî‘}e,žO%5)K˜Ò&ê%é Ýtñgãä“Ñ«™Ì=H•÷æ~ÐÈ)—ü($sJ™’%®UKKT ,®† +6í ‹)yƤ´àJÉ£7›ªgDçS!lLÁ¦$Š¿*¼†ô=ûýç,9âNüÙ⾿r:÷ðKXò7K<‚Òsµ#%fÐ{í +‚Î2ïêÅ#z£p®rµ +ûzA–å[ʈÒå›ùÚ›2‹©A±:®ò1} @¤v·ÐŽ-py¥)¦®dàI¢X~ÈíN£™bÓ÷Z¦•£“aj£¬guM²VÚ}ت4Jœ4Û¤·–òÐgþ¨a<å†æo+apPWte`Nù-ãT…´¤.nøÔsṃ¡^³Î@(˜,}!'å󴱕ü”Ô›õ&OÊ›õœ3¶þ’[¶˜iípô» +ù{Áðé´6È^§rJ²Âœû‡’ϯÒÅ +ÐÒåã6KubLÊóÔ2©‰,â•ã¥¶¿û#rF7í9¢!Ñ·ëž_²Ãk}WÄžk'ĺ·%F/«‰8ßÄ“ÓÜV.nLª½GŠ£e:«á
›¬¦þ6<pw€»F©çÇö™ÆûÖ(Z/î³èØ™bÁ0 3pÌ÷«²F# +!ò1ëéMºYËE©<s«†p¶1ÒŒÚÞ;
n:QñÄ|af€Õ +ÃÇ%6õá…|Ë{ó +tíæ:GöËnË“ØIâÓ-ÔkÚ.·G€—-ãCl.SVqÖpô„„WL…UNö†¦–h +Û®åÙ¢Æ9eðMc†-R!]ÆvÐzùhG“!µ\”©°ûU9àpTˆÐT g£F^±†Ï¿^>å”:°Z’R´Yý
‚4ówóeO›Ò²¤ºý©Jbº$Ó6×ZF®aIÕB„ËõNœe£†žrB9ôg|“#>¤¥Ö©!H¬¾Ð
ö +ŒFuBüR—´C,{ül6ÐgïRuõØz¶È\Ó!‹·Å~—Î_ÌgØ’eŸ$}ÚtT´-íüÞ + óê0ÉIR2a¯Ft¦ô÷ãÙ"tz>¨eRA#ðÓØ‘2ê¢ão¢AšÇ99l‹µàùÔÌ5»úîÅœ6Ü[óÀOoò«fʨÎ|l†/_Þí|¢eŠ|´Õ¹ï ^Gÿ[Ž>ôÆñÁ‘9mÈÎÛwš_.¡¬i
€JÛƒ{=úìuKáƒãò®U°×¾âMÂf½›GS/ÔC +·Ûý¾“†nÝ(È|Ó?Óˆ‚QX”tÎ}"óm!ã7EûlÔÇÔ"…ùr ÚlnÕöS´ÐO+²c•ƒ£‡ø-ш¬öc°1%ëûxó±=¹ktç§à-ŸÌÅòïˆÝ"^ß³;hÓ¸uR·½ 3ŽÙI¦ÃéëÕ +uÅ¢Ü=[@Q®ôýѹ´ù¼ŒÂ&JüšåVŒy½Ö1 +ïê‰õ‘_+¦òp;1;š´¦À(ø¹óóº¾Yà‹B•O®Ë3„CÓÒȼÌ3‚‘‹9€ “0úõÇT¨•üªé«Œ'&ß“1–éÓ°ösÑÚµ³¾¶kœÐîÞP˜È×KÑC:Ç(ˆãDU4”+RF9~O—”Êa +T‹Y$—Òê1÷µÌïµ</qªØ¤)9˜áQënú’Ín¨®XË|TmºƒBA¡02¬p¹~¢áê0#A†°‡ßºàð@Gr¥{T*
.5]å9R¿{Ÿ–9:&¥©ÒÕvFgü@ﶡù;ÉJX>UÓ^ŸÆC«¯ +·Ù”æƾµE—gKô± ¼u:D‹€¼ÐCd¤ø!Ì‘,_wµŽo ÞýwÈ%„yUŸ/9Â^êÎÁ'zäŸi¢Þ!ùœæéAÍž¯!>Mï'Úg¦w +5ʱVÿèÖÒ_äƇ¸ÞÛüMhÕ˜®¶—ºN.<]ÞÓJìZ}‹m%7®¬®¹½“±zBO‚Àÿ u¿”õ¦\ÎÐIe9¿*r=ãÆûï-yg‰ò¼È×DLÀî„(-‘ª0£’ø5m½ N±U)MHœèêF§pÖ›¯)4KN³:-¿°Ø,·™u~U£cÞiÎØÃÏ„a“¥-ã÷ŽÍ¹þÕ /¨ù"Ÿc'µR³‹¼ví7À¥#v€3x*ü@àv¾nOZ¾°a&:¹¯¸Zœh!GÊqv†=æ)_ +q~²of¸Ù“¢`ô+ôÉrÉK:$æÂcOia°”1–ðiî÷.ÇÒ§r,ZÅ)ꄆåà¼Ðvß…¦Ðçm¤È‡°¶¤&÷ O·è}^û}‰*–zhP ˜ž³òÂíé8»Œ=éë0ò\SϬ¥ÝF|U‹P\q"iÈó +0ão`9M!Kx»¡8RÒf‡8¬_M*Ù)´nú1û‰1ÃD~eô²'±Õ¸'"â’ñ[š0êÝ룣Vï=Að«R5îáN•,¦†š Wúig£ÁNe˜Üħ|ÝT‡äKQÈÔBO0®×J±¿/ÆéC£ø9ûÅ’_+#w€•¤UïÌ£·‘W.¶Gçü™!ΆÂ0…+…b+·Oç÷º‡pÞAx“(cø(Û(e.7_)*Wçù˜ºÂ]ò|¾¯[REú©5õ]eŸ¾aa¾2Їô·F¼xç¹"ɉÑ[ôº({ ª\;UFÂÖ!ÿÏ +endobj +483 0 obj << +/Type /Font +/Subtype /Type1 +/Encoding 1124 0 R +/FirstChar 7 +/LastChar 125 +/Widths 1130 0 R +/BaseFont /IFUJHU+NimbusMonL-Regu +/FontDescriptor 481 0 R +>> endobj +481 0 obj << +/Ascent 625 +/CapHeight 557 +/Descent -147 +/FontName /IFUJHU+NimbusMonL-Regu +/ItalicAngle 0 +/StemV 41 +/XHeight 426 +/FontBBox [-12 -237 650 811] +/Flags 4 +/CharSet (/lslash/ogonek/exclam/quotedbl/numbersign/dollar/quoteright/parenleft/parenright/asterisk/comma/hyphen/period/slash/zero/one/two/three/four/five/eight/nine/colon/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/bracketleft/bracketright/underscore/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/braceleft/bar/braceright) +/FontFile 482 0 R +>> endobj +1130 0 obj +[600 600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 600 600 600 600 0 0 600 600 600 600 0 600 600 600 600 600 600 600 600 600 600 0 0 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 0 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 0 600 0 600 0 600 0 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 ] +endobj +447 0 obj << +/Length1 1630 +/Length2 20284 +/Length3 532 +/Length 21188 +/Filter /FlateDecode +>> +stream +xÚ¬¸ctem·&§â¤bïضY±mï$;¶m[•ŠŠmÛ»bUl~õ¼oŸszœ¯ûOwÿXc¬{⚸æ=Ç‹‚DI•AÄÌÞ(aoçÂÀÂÈÌP +€˜½ƒ§ÈÂÒ@®¢ICGGÿ_’L +™íœ4 +¿ºdoÊb•ñã»K-vîð¤¸n/äp¨CiƒZQA@µ}FÄO¥Ñ[M(cã4ïG›çâ©Ãû¾íÁh/–
UOð"ŸÀ—Œ¦¯ +_„£ØÏ㪄€ÍÕ‚‚äa~'ó –ODbNw’KÝòÏÝ“ˆ-þ! hÞ¤õ¯®cÜà
+Ì Ö"(t‚n>ý¸êÍn†4ðgÈÚǦ)¤+tÐŽñóW§ ÅzâW9øÒþ×+¹A¤=–ïD ®ó´MÈ!?ØÁ”±‡ÍJƒfÕ¥×ﮓëúè$8ú´\$¶´´ÎYê²E]a4Óý«6JÇX‘\¸×KxóPßÀî9¤áðð÷õ‹*1ŸudŒ®}‚#²>ü +`J+yø›/%»¹ÀÚ5¸-Yxpà.ƒCªËú©8ŽahsÁÎQ’¼y²:WÇQ$^oëë΂é{ÕÚ›Æ4ó +mõVá3q¥¦‚ꎪŒ©JÖO…¯à7#]²ÝÏ‘Ïví—Nõc¦ÉÒV¥2ȬN˜ná?BšGvÙû´š…«Ç›nÊÁ¨}Ù¶YBìƒWá<;Hãh±‡æ´_µ±ý¾þ¹fEѶó’ãJ¦ùÙ`ñLw–™™Î‡¸¿ÂÏnioho¡/mîQ<›ea}†Õ£"¨Á§m‡@n$J S<|Á ¹…ËšòÌ¢‡KI¼rd³ÍÐCäD`ÒÑrk~fÔFÛÜn Ä.'¿ˆ-™XKÁµõ[ †2FVtFªabÐi± +¥Óz[É”s6LféÅ_ÝøÕ¿w=ƒ´H¨gOßÅÒBÎé%A¤æ¯3±„rû`Ò¤ÞYõa·Ôô¥4ò3‚S}%T¼rø[„‹j¯%ÐŽ8QZW2Ö9÷–?<òF•õòt8‚)±üàâù™šá+áLuB"ÇÇþWhÄšY]H%Ñ‚ógÏ7ѨÂA{Æá\‚o^lBû… ͼn'õª8ŽÖ‹Æ÷ø„“hÊI%o¤s::W|yYÛ%ÖæˆwÔqÙž”GéæÚµŸfú p ZqD8`™‰wO«ëx7y¦]•«lL²ëøm²Šóß…«]ZWÉ÷ã|.kUóståo3{퇱'ó`¾ˆÒ2'NAQÖø„Eœ%¤¡2þ~Å <›'hjrœÇ3¡ž„LJ4èN¿±FÐÂþ1O;¦tΛñ-@¹Ì…Æg©çˆe¨=0˜ØîìUƒzLýþzÙžbšƒØ}ßqn,ˆð§‰ÿ2‹6´»á3›þ|lh>þµº›kƒoþv8ºìÊG’˜Ëwåݾ_.cUoÌÉT_‹v>øý8Ø4NÐu0„ð¨¸w¿Ä‹› +¬2#ÔŒ8˜è˜ÕwœÐQæë7•¨!T3ìàIr’|n8ÏÞgÒ’Š‡Ô‰pÞ?¸ ß²ç™lÎØ‘ßS\1 +âN½‘x0Sàœv{ÇEFE´¯HÐظÈÖ=úûÃ-ô1÷†ÙÒïOM G`5¹v¾ŠE‡ÎÙ:æÛ‚V¬ÀšYC¯Ðl\ÅN3ýÁÃþ\:vÔ_<~ÆÝUq²®Ä.ž;XÂ7u]ÔŠ¢ùF̸h £ŠéùØ;ŸÛM¡PŽªÂª¶¹”ÜjŽÐ…]_/tªYƒYç„,:¼¥êàÒë8„ˆ&3$÷élŽ—¹#IÌ, +W#ë!þ1N®ªj}m§z€Û‹ÍÚ¥&ú;Kwo_$U +Ö5høò_0×6î¦[îˆü™Fò:T%áãv(Ë2²zŸ§ÛV³íám0½IàŸÀi!„žŠÉnµµÙð˜¦{j}n‘óÇxx¢ý2Xò5^Å…Ü× +ónvˆ)xûø>3v^κHÉwC"ûˆ¨¥É´€&OŒÁÒCôûm®¥™…òê%)êa@ãJÉZ¥kôò‹o¹%ÔAOè\9}^\$iþ‚Õ¨ê]×8×ãGÀü¼‡8£L¼CW¨BQ_ä)ˆÎZ+ê,¯âÜÜ‘«ÔÂx¢àõsºfï$V=a] S¼áyá +w|—9,2º^‡”ñ·¯Ôˆ;Í)Y°´?šB·5Ús‘PmWH˜XËI™¯^8ëкðX¨LÌ)\°µQX'^w%¦ÿ†ÁèE˜ÜÁ4ô.¼*Lý½^ÓdÒ“|ì×4Ò/ú·|òsâi¹ét‡üxè)ç€éÎÚ*òNÏ«²†¢âÙõ—dQGsÜ)ÎàíÒò%KA/ÈÍF¶÷ÏHŽ›+¨û¸-3ÉŸÓŠkøÎr®CXƒ0)ÔSê)FZŠþû? ŸÒv å>?õ‹œ¢ï¦ #*øRÄZ÷§ì +q=¿ù*&ĦeÒ³ƒ(þxˆ¸IY0Þy³<ÜÓ‰pR +‚U~^©‚¿ÜÜ–i»ÓóªöCÑ å^!RdÕ(ðo|wËUø·q~-úó³Â6ªEõLª"³Àúí±–q¡ßdx’>îBŽÜ/àß»dµÏËJ×v^ #÷íq½ö£"3™ƒd©!¼‰–¹ª>+QÆS7ÿ²›ïh¶uñç÷ù¤Ì:2·E 2 Nf0«£¦É¸#Ž¥Æ#^Å—Ö ËÎ<üÕhÓsŸ*‘M˜¡Žµ0R¯·¾GOkÃðH‰>ŽžRŒu|MîÁt‡j²û—?=æâÛZ*ý™£$wZ²iM›5å
ùÈsþ}GÚX®|£#ߥý¾zž{–ÙÞÂN©5ÅÐ v¼;`樂e¿¤9\µGæ=àà7^vÄß—TÏeš Ð/"òªM¿ÿÒ.ÔG¿ƒŽ\Õö!’º]‰C*Ú¾“j·1‡Tð,ŒcG1–dv`〪?¢¢ýJ¤_`a €¦ÆhD‹Zþˬn +}£ðWh+û÷ÌóeípózÌD02Ť7ÎÙ.)Æ“I¶`SLóàTÊb!Š/8°ÑéË|„&ù¿Ë]ù>n„¼muášV@I#$˜æ +Z¢þÀ¡Ì×õñIÇÇKk÷Þ~ÓM+)gz‚¹žà#°« +Z- oåפÝ&í®f™#KÇűǸ4õß?©ûˆWÄ_áôu[°èNH)ÃÁM¢¿¸à"þíÇcM5=t¥.é¡å?²5u‹]v¹=JϤÙ,‚ÏÛŸ¶w4ÅŠg+,¦¤Q©–êdÆÑmÚCòJßýÙûD;¥-Ï^Oqè{€`é6°´°0î³Ó.Øí*‹*?Ó똉:µša€K¿nðp”CVS +ßÔèuËxì‚ÙX…94ð»sÎK7è•7v,|kké&ŠÉü +ê'ô4:M£†å:³Þ¼û)Õˆ¼ +‡ŽÙ¢·Ó&¼‹
÷ð7{<`1bàÑéÖ»£èI +Ñ"²óE¿c<Þ;²&›jçM¾óר$0¹=êÛ4ÚÎÃó##}»‘è!³`“ +D×¢yF‹—ý +>iÓd‘CæKŒû~p<Ï{ém_;ŒžŠ*ƒgg7œ£¾ð—jÀvù¶½lƒ8O|-"}ìg…uÖñ¯˜}¼£§øäræÇ•ú¬Jþóò/õêÀ›/œÕ·#Íc¦áËÞ¯Ó
À(Z«.šó¨ºK/\ÕÈ…g§›/K¦ ¥±`obtîºøé•
šºÁC…ü‰Êj4Oa@hÌÀ$/3Mp_íö]©#S§2Ó+ƒ¢ž‹.ý?¥²*£g¥„ZÓˆbK‘&Mì,<a~{»YÊj£ù
w +Ë]°(Û<‡µ*Zk-bؘIçOˆ›œJÕ„*+O±9û1ò&ô<ªŠ_®Õ—™ˆê•‚<ÍBá&{óõ.4éÖ0Vº¶g¨Äò
£’y(FëÏ»Š:"ÿ¼ì.Ó³ôâó.SÏ 2ÁH´—ÁÎö=:vUeûG)ì$"§´Ù!Ciqæ,ø¸µA-ª£ˆŽSØžvbýË3ÿ:SåZU\¯ÎYâ hr»ŽÞiHÞá}îÿÊ^ÙÓThRTCçuþFõ‰~Ù;ò:˜îõJÈ›…íz„ì¯yj§O·N¦Á¯o·Ä„FaÒB…Û÷C +ŸNYi˜%c
ˆCóP̯Wò’“¸R㘛,¥”…Œ½Äú>(
‚]à?]3¦e¬ +ê4À½ðøð> +PÛ+Ødä“œ'…P•œ‚±š,wRæÀQTæm[ÏçÙmW‹–âøaL»/ÇJÙˆ¸gEY®QM2l9 áñ#_«7Vβ1©ùb%¡<ƒ î ]2J“xy>M´ X•Í„påOáÆZ +ζ<Oh¥ÉMŽHÏÊü—tÁã +L„ÔféBùÌø.ˆÙ/ÊCŠõí¦”lgïFHŠÀÃß%Ê.ëj)@´)&rG6ÓÏ¢¿¨Û&YŸoøYYàOœ7£J"ÓI[.åúÇô²Å!~ùN,Á;1ËW|¥¤eu_6ÐqŸÁceÙ>“õz†»ŽÊ†„#âH¬`r7r•›.d¿ tÒpB²Ùox7 +÷Õâpµº)'Ô§8Ö”°“¥¯ûGS᮶?8p½r—-ÿ¦…^‚Šà›ùGíÈÚ5JYÃf‚Z"*3SºYØc +àæÊëV>]tëtÄ>b÷$ÀE¯þš”ŒHI›½7>ˆ
|y°†¤jè2ýäºmJiM¨IVZ!&åìÃIx*ªÀ>é:®sÇ¥ïa[P&»lö†¾È›I»ý@cæA&ê%¶ÕÕ-2iŠ-täÔÝŒâ¹}Ó|ÇÇWzŠG•¶lå7c[áðUª(àÌ! +ÄDÄÍ%¦©…<°NhmñšøE7o:âŽqpî½åd1”šÛ£K]¶¤'¸s~d=x¬—ÆÓaëá ¢Úƒ3†Cßê,½¼p3ì7j ÍH¥–,«Ï^’)¢³4ðoE¨¨§=ÇÕ[É$BS6Rö\Î<]˜ÍJ,(P¸KWü~¦”É®A7V»çª£QàNƒP
ø!…å?”ïkÀÐ)¯—øq«Âñ$ÜP ™1$÷ûß²Îo2hk‰ûn_²Ë´ƒTÛ‹ÊŸ;[Ûﯶ:ÿ§9À•ÒÐýÞ¶§ÿ;}ÇöŠF{LûŠ†õ¸âÌUd+§ß@ãw>¬{ª¬µÛ¹aŠ)mÅù7˜ï“S?kz„ªt“§‘2{;»Õ¨!–›‘¯³Œ¢dv(ýR¿)Å3ZÊÙ£SÕIˆvâ¸.…”iU¥B·àrÄ•p +“`â‘`ûÏËž¢G¦í=ÕRvð=»Hñ‰j1" +ÅÕ1äPC´ý‰ÔÂÞ=Œ7qáÉÐÞ‹Fqjéå(Å@ŽÉŸ–¸üŸI¸Ãe`±À)PÆ1zûs +kºˆÂñ¥³*@òÃãI]n„\e7^A<ßJùµ…IvöÌÅu/mT«"2~ǘNòíª«Ýò×^¤UŽa'a‡¶ÚYQ¯ rX¹ežXpèŸt5¡ÆÛI]þnIÚx¼”r¼`þÙŸÐ0j¼ª“Xì²Ô}$·ýÃk7Èh™v2¦‡KS˜ÂÏþårCô›ª™îËB<p³Á²Ø¥k-,¡L‘¢ñl#]Åy|Z¿õŒ©K(sˆ:—°ò·´œ—¦qß›¹Î™ÐÅ’'ŸÅIÑt\-åÓ;`Ümr¡l̽$‡ÇÝ{S#GB *Åð0&<ºâX%x(Ññîá[ +£ÿß’å7œFÐ’sRGtïG“:wU”²»MR2qáö&·:_*ôk6Êý•£ÇɤMµ}pà“V‰Ê|§Ëù8°>•ÎŒÿuõùˆÑç&ÇG*‡Pm"áf«<ñHÌ¢ÝT·Zú½b"ʶ´¹#ᨧÞ*1ŸÙ/òM(«Ìüôò}¼èÁÜ>öÝBðƒbuú²{1±ÝÀøŠÓ©ÛbF‘œLŸƒ4áwpAu]hTÈ+)hk + ÅÍú6¶8üfB]°9ÆÆSOßØìu™!›ß÷PN‘›Ô²/#ãIýúÃq›yÔ\³Â„•qÝ:¼ÐͽàH;€r½QýãÉžd}ê6F!˜×&~þmÚBóÏe$-=ÄUõ㤤_ZÏ`8Ã|Ö‹å±›¨½›¯o®,}¹qÂ2*—Œ5»Ö/`j~”ñiaA”E9Éø—·ÖÃÆ•™ø»ÈŽ5ëêÜ÷´HýW—xÅþ…“Aéß’tph›k-¤Ö„á¹J¸7DÄ‹EÆPš[•´/¼ÝA +GÎÄR@'ŽO+êãsk
…û•Þ‹QËu¬˜î*–E}w-Csj?WíÌåäFìèôAXʧVîÄEŸ1œ2¤É5ÁU^ëP.ˆ-¶É(ºzVrýõ¾Ýaƒ¿Ì1§ÿwýµâ{ʦ×ÍþÙôò‰LNºÃŸ¾>—ÚÁS¾úUŸ`r¦´9OÍ1xpÈ|y†Ö4¡Z}zrµÆàƒ¦áx§m-VàH ÿUwîíХݻÌvú¥…ÚåÊk šÅÝÖúÍ0ŒWÐåcUt>ú%~é’aóÇžqÁu4ãW'0Y·B˜·ÞŠ`Èe=ýcÓaböhîe¼,~ž+A½ +g”åyiÿãF5𮶨pÌIh”á6k•‚™0˜Út&Ó§ß&)…E÷$@&tøˆ+ +þU¶y4$‘éu˜»}Ê19åDÌ]¸$E_½ÊXýG¯R´Á6ƒŒÙ ãÄ÷¥a[Ÿ‚j#“ÎZê•WüȘj(9Êa}³¾~ŧÆ'42>৥`͘x|/G›ˆÁ”š—ÐVÒ‚£F¼Ô3”jø9ÊXìÒßE'åÉ_ÈÅñÀÓ8êRróÌÕ'D„ìÍG"† ÐÜBÊMÐÛ1Æ©DíWFŽò‡=š9MzÙëÓù[ë›Êèk"ÿŠæ¦ê5á$Ô~[Ö¡ùõ16ÊÅRW•6ÂìMU™,Vëý`+bƒ]ø&5SÆŒ[@á¥ío"?@ßA”wâxOi„6)‚G &ã¦rŠ€åA\«MîÛoBå¨À\a+Öàë ;韟Ê/ؾ÷7è
ó¾Â&O.æ7ÓW¹ømoŽâ}°ßD$v.TŸÃd±vÓÑla¤["‹žÄøPsmµyJä‘å_D
bý#&u«Ï¬Šéz2×ä4}Æân•Q2"Ò½ƒ#y\ñö ReÕW‘ˆ +2–0 +Ž˜;4!.6c©=%[h™qì7”½Ónz‚ôâ +‚nŸ*Ö|l¯§¾z’µxÍA¡"º&¾&©CfO .ó5¬b
—{³s’dsvÔ9fŒ!—b~D{胋‘Ê—¥v[<æ ÉF‡ +xiI^PÈjIeÃyóó"Š‡Ò¸K˜ð-Ý«Ä—ƒ”‰Ù–RhŽ©f>ÒÕ輫˜ßÎÖ¾P¾Ji¤ÉMëPý¥ðj•Fãom‰ØÍ…¯lþ¹tdF V樂¢²Ídô,'ÊÚu£›l˜Ûà=Û)3q?„Ö´1Gà¸ä;`«©d¶8Y_¬5tsïÂÚÑåα$ªýsÊXv÷‹êÈÊ@ƒÉ†14BƒîL°9¹±Kè¼TÖ-:ri
©>eÆnn¶‰I9ø Ø‚rpî”Z¾j9w0¥ÕwµeÚ|µ8–Ë)yÔ§aH£[Pµbfl¥¦ðTß9üã‘Ûûûß!&Öôjy:XÓ[\{2M$:ß›ñ +i¦‡ÈeÈ\UÛ‰Y_VCRk +µN4Q<³º;ù}Ãûîñ³j3~Ñ·uf,.F?‹aÙ›—WT=ÏwOoYѸg§N1 y~Fdˆo5Λp÷Ñ¿½¶EJ‘Ó"+«ÜîQ~ÎÂ>ãÊú‰³ºù¬ 10jXO¢ïMàØòBÉš¹ÔŠü’öV*Žô´ÝqýÍ[>·÷Jk68ªxòÕìÓÃ!4>¾ðG¨?Z!íp å-Ö¿jyë8`6NœÙÜ`CBûêF'†Üº<~ÑïË +õÍ1D“£ßê¾4kQº‚Tg†y¨”Ä{Q©>zDiXYÈÔpVZßGà ¢l¹xÿS¿ÒdìD…°2"æ¶Å: Î+¥¹hf?Iˆu”aF—‰Á÷¨È2#ÚAŠ„Ø\=;þˆ:žïI·:vG$E+ý¤ª»¢nº“_'ø§µO3WkB¼ßrÚyvûÍ’Gœ´4Žò.sºE“¬°L–²úL¯eABÉ +n +Y¹Úôî™(i ºå*¬´|·iôÝù†wo†öžÜá§ñvã¢:ro–+X¿*¶”AË>L2o+^©“)Hœ +¨~Ñ~…y^,èÜ; ÉLþ]pž§^Ì‚ÏšCQÜ—H +%ª·Ï~ˆX”z}çÞµöN%Î +bk}ŒåRsh.˜²/³Ðè¿yœ +&Kû=Ë>ª0 +7ÙéèhxaýáÏnèèå‰ÚÑ[éN Z +ôÖ¬Lç;¬2/õÊUžN÷R_Ø+[Á&›¸&çZ:FŽ}|Û̘2búÙȧ;¹-L+>&™àá¡Fi‡—ë΢,ÇxÚïǹ9ô“–*.3ÂÖÁÀ6°‘q8bä_:wþw(\6B”0¦Í÷1çRêºûÆÈ,’ +êÁ˜9b2ÊòHœÕ„¿ù#»O›*ü·°V']—ELÁ•)*Äè eß[±|ÛºÀ ×nèÖrŠŠ%î£êóUɳξá UÍ3Z^‡¦ãæWyuRjÏnž¬YªÍ^ÉTâ0˜—дeM¦M5C¬ö(:¨eÕ;Géß’Ö!7]#‘j¯(ø³Éˆ`ZÁ~Áõp$útÐö˜;1@3ÑàŸÚs‰´ ×3ŒI¤Ym®n(mÿ™-nxó0P}”_åꔥ$ú#Ãì,o,é(ÜîËßÅÞ8úÒûP½Ç”¯]n/Õu~%xHÜÁ±ûµ"G'˜·ˆg +Ž>ÑÜCr®€V%´PПºâ”(CÎŒáÕ‰zéß“KÚœ´ëýéOš‰KÕãinsÈ„ƒ–‹¦¬‘Ó)˜†Õ×óz¸³q†¨¨\áp¨mU É~Ô"™•Ë‰éZ!IèŒgÅ~ +Ò}W_!ØT¿I©!Aei?%ÂËø
†»¦¼aT3›19 +K0§$ÓÄêÓŸùþžˆ¼§’¬Åý1íɆ† t:ù±¡¯>[å¼; &ð¸ßð9TDrÊ-…šHUKø6ZAÔ,ø}f°Ú†\¡Îþ9bïJ®6u“hI6®O^Ik|1cÐÏû¬`³ýþjñ43›\ŠÈšnÔð˜:yÚîùýá°²À7@Í™¯DÒÊ àdØf¯ßà‘ÉlõJTù†éˆ–þ;áåq~Fc¾LÏ$øå›YÖí[[ïU>6Ó„Rpª°²èÚ9´œSkÙ•°5<_IF·ÙÀas0íFl˜LHÆ„Úç°ÃF7úAê9˜)5ÌLŸtMfãAÚ +œ~ó|±z£›¤£–Ÿëbc7Ž¨J7-/–däâ+ì‘Ö¼”«‘½qÑûBuéÊÌÎÒIV¨fkçÑ9D;9öyšg]9¦"ËŠ•|A¸kºšÐ]W¾]2ÐYL÷È#0ÞÏ_—¸3ßa¼þ<ÌQ:Z“ÁƒãÜj÷Y2% “F·U*©c nËæná“õøñ%éR¦–bOÞ1yås)ž&:d¸M¦¨“ü°ÑŠÞ6½üBˆãúHà¶_þÆÒÁˆ.`.Y/íQY×_¸€ÝÜ*dûlÚä"a7ÑMßBN´UÚûÇs-ñ]ENd^QtNÔÎäÎùôn9\éÐ +';vaoš¹ãLoÎ\ñpìà„T{¸à0Ðî°ÜÜv[nüEi”7®‘GJ>@yσœþ«C v‰‡Xzo09J°ýK4êÔQNóá¹Ú§8‡«žÁàŒA“…p˜Pr·ûñÿkg®ÿáp +Ž+u_šÁonúÕ‰¦¾}ËUüðÆšÊÏÃœ‡lGuÚ?Mh¤yþ$ÉZÿû‡BýóÓî£Üç +ûò`&Y–ŠÿÓ/( šQr×,¼”(qbé9ôq …—õÒþ>jb=é=u ßS£zšOwÚѦS³&7üÀõèî´OŒ«ûvñ¦‰Pê‡où7ŒÅíO•ÂÙ Ï;Xâl۫纔(0C W
Aç¤#òçD§E7‹Pž•TAŽÂóŸ™”><£¨Q¼øç{¸žs
ÿ3ˆn>Cw‰šzôMÐköË<ç~첚Û|¸¶·m¸æùÕÝèê‰Õ˜¢ïùk>ªÝ!÷‡oR«øóHhj²BòãÞatjŽ4Kå6ç|•XSØ|˜M¯ +Æ;Ôöµ{¹9
™t©ÞsKºùr‡y@¬l†8ee©öÌEj.Hþ½¢üôW$WÀ6¤s{Œ)œ[Ȥ޹Yì7ÇÏßxR߶¬Ë>C”ª}.Éý÷œe&‰zLfl}²$Àsz#pŸU%r˜ïq`\?†=¨±¨Øô_“F!„ݯ٫ÍXFÈ r¥.f<mÊ“ÅÅôé™8j,þšßÿí¥p‰nàW.‰íEa,ÞY9©ÚÛ¼c·x‹_Lñ#æú Wä“ÉßLé¯a«‡)xEÝ!ÏèÓ³$ú…|ÍSÔóFðK´JS§Y†[›ˆbzub4n¡ð®ÍŸ‹æ°…!:Ó\#B6¥Ím +éZq^q«Õ€@·ìÇÞ›ü”YïJcšf:åâJóÛÙ¬Äá#hV¾¡ œk2^ÿýS|D::³St¡ß›éC’ö’ñ)Nó—ÆÀÉØï Í0RÐêÄ5|“=Tpæ¶kû¯?ßOõOââ5F¯zµ14jسs@¬öµ¨Ýòm¯
M;u(f‚Åÿ^1ìPcÃa£ùÚfßa¶‘mHg¢ìJ 6VᓬZ<‘«|ë#Ù=–_§äïB’0ÎÈA6ì¯S‚&ÚÈaœv ™ºr·½à´ÆÇNæíêQ¢b +Cî ì³ìîϹ+Á +a™Ç£:FH5ò‘ÓÊ?5REªhŠ¼ƒÎ>§—$à•™ç[@3oK}ñÆ-Y³ýz{ÛÁ ¾
Åzpv`Õ€O@?æ’ÒÃzKÃJ›ÂÐHRK5¶èÚNâfã®dnzþ¦ì:q¿ñv_Ãy eD”ÔvåꙡoÖÁþÓØkù,¯‘B60 +D ÀV!ÂÅ.ûÖFN$УVSèœYŸµ?>G{Sê¯]Ch¾&$èðFÌ”ßj¿gUuÐHœÖ³-Úþ‹ùãÀ…Prg΢=e9èõWÙàÛ¬7z®À‹½\¥pŒ´]›NbVžþEAb§a-º‘ªø
õ×ãiá,ê}Ç®mú-@kõÐ`2a}`9-¨(f¢s—½m·(QOôš¿ÿâtCº+¿qÿjâòæqv–ÃIR°4ÃK—§îýç™éò–TQ]ÖúZh}/ +šŒR8†;±Œ;n‘gn#D1ZA8Íþ,|D kik¸Xü +$[ÍPQ4T«í,|´æ–Rö€†ÐßØŽ,Š4b³ª2ŸL›_ãC•zOëÄ9íD¾ˆTÌÈ’[…héð¹w6§jšLÄ7<¬]64±]è›éÌ‚Zæî4Õz‚ÚÜhÝ;Í+Õ>$Ó|_5UåR¬ôkXàÊaƒò^ÒIFJ(Z+-1WG…`íúÆÈaœ:} Æ—ULσŒÂq¡XN…WxiQ–3+}r/ˆ“\RˆL5ãá¶y@è/Îs2Ò Rø
RÅÁXæÉóå%µêäU÷–æY¬?ˆ®*¿ÍTC/Å +z¦ýyO½™ë
‚Êþ@;åm·O–Û¬—zÌx˺ÉîþÎ +~#1Eû5œ~ÒÍ<¯h²_ž¼Å'R{ßäDÜ#1”’ä|oM´H.Œ]ÇMV5lg²cM¾pÍ`ý/¢R† +endobj +448 0 obj << +/Type /Font +/Subtype /Type1 +/Encoding 1124 0 R +/FirstChar 2 +/LastChar 180 +/Widths 1131 0 R +/BaseFont /AWVPMQ+NimbusRomNo9L-Regu +/FontDescriptor 446 0 R +>> endobj +446 0 obj << +/Ascent 678 +/CapHeight 651 +/Descent -216 +/FontName /AWVPMQ+NimbusRomNo9L-Regu +/ItalicAngle 0 +/StemV 85 +/XHeight 450 +/FontBBox [-168 -281 1000 924] +/Flags 4 +/CharSet (/fi/fl/exclam/quotedbl/percent/ampersand/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/less/equal/greater/question/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/bracketright/underscore/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/braceleft/braceright/quotedblleft/quotedblright/bullet/endash/acute) +/FontFile 447 0 R +>> endobj +1131 0 obj +[556 556 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 333 408 0 0 833 778 333 333 333 500 564 250 333 250 278 500 500 500 500 500 500 500 500 500 500 278 278 564 564 564 444 0 722 667 667 722 611 556 722 722 333 389 722 611 889 722 722 556 722 667 556 611 722 722 944 722 722 611 333 0 333 0 500 0 444 500 444 500 444 333 500 500 278 278 500 278 778 500 500 500 500 333 389 278 500 500 722 500 500 444 480 0 480 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 444 444 350 500 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 333 ] +endobj +444 0 obj << +/Length1 1626 +/Length2 16965 +/Length3 532 +/Length 17892 +/Filter /FlateDecode +>> +stream +xÚ¬·ctf]·&ÛvîضmVœŠqǶŠíTl;WlÛN*¶Õõ¼oŸ>=Î×ߟîóc±×ä5ç5çÚcS(«2ˆ˜Ú%íí\X™yŠ–¶Æ®Î*ö¶Šö<ò +@SKÀ_9…˜ÐÈÅÒÞNÜÈÈКÄ& +\,€ +@-¥¨ÚŒl +t²±´þåõ_-0°03ÿš…¥‰µÝ?pü[´3ý¯ðÿRõ/ðLÊZ"bJštÿ‡Ëõ_†Ê‡ÀEÍÓá/¶ÿYŠ‚½éÿ:üFTÔÞàÍÀÂÉ
``cgù»{ñp2ûþRþ+ËžŒ\œ,= +jì{üÓìðT¾×†26Mñ~¶y.ž:|ÈÒŽüƲ¡êI^æø’Ñô nRvpÑ1é—"fœiF{_-ÈoC|çdÖ8Üø¦¢_òE8ÕÁæsõD@æV€Aþè€äg’Ö‡Ù‰Ò‚VWxzF™ôçé‘j`txh°ç²÷ +ññ&sw8´“ +¾¾ßˆ&2?dÌ`ª7Éœµ–§£tf1£·L-W»ìlÓŒÏïø©,ã‘Ñj³HnTµ4EO2Âá/¾L`'ƒRc;„Wû,'Å €²•æ온¬["ÉÐä¡0L¶ï_9Ä#1ÉÁ‘%rZ4<Ã9¿SêƒUz6Ø›º³“’Bö©ñ'¬q`JZˆ¼[ª0ê!¿J/¬N2 ò
g‹û‘;Ô‡ìµP‹'¡(€’j@\ϲ~#€Á<ȤèìêX…VÎÒð¤~YyÞÏ¢j2ó8Î}+Ï5^Ýž›žø½IT}ânÎÛæ“»_8ó«ïo þ¤žÅ™ÿ·Õ©E5p0B‰'ŸJ}¿:âp½2p8²pißöOM™?2çIbÙ9¤g€YBè5dØË$EÄPõ›ºóôaj“F·y1;”-¦úÊdÕµ5Пã¦3Ú•¼)
QÉbÊ£e]B¿è~îì3Ïp’ò®›¨mG +ÐAC'+«R&M )«û«(øŒeíè|¥¤U?Ì°þF€GŒíá´Ð‘»ÛCщxµouã"Oí»çvjìEÕîc\w”©Ž`å„ûQ@6auAáÀZ]ª|ãäùĆÑO:®¿Úb§L#Ñ]nQ?Üù›Œôjɲ¦!zwfü7ˆÁ‹ùL· jä+ú¡´‚sŽ´¯ŒïéÊ£Qˆ—^вoIæ´™†J"·õa,žt¼Z´ {zI±ò@I¬ÿ´˜¥|w5¨îTè7ýµýIi¾¼;*±ãü‡ÈëqûöÚ‘[Nˆ¯óîn)ú"¸©JB $è—Eê9½Ïý×CIöB¯ÝâRNÝïô‘T¦0߇]«Ð駩ưä¥H½M¹£°Ñ˜0·y°\&ZF/ö¡—sx@,Òw*IÀ¹ÔCÅÿŒiw×_€ôÙîu\'h19 +Ýö¶µ5úÔŽ@~èi,´¸Â)eãëWZ£–Qƒ¾ë û¿æ6í/¡ËÚç7 +Ñ8TuŒEx¬²h(H
fÙ:Ç ‚ü‰´ºúƒ˜·—úÓæ„’}Šáç4Ýb0˜¾ÊˆÝœŸnuÀðV[¿Ip!>wŸ1”SŒ3¬ÌÖfkNÂ:É(Õ4‘ì+ïëŽçX…%qÙ/(æ$\05ý®;-=Fãp= Óƒ#~ŸcÖô_Ûfy¥"Nìíá°›9lØ睊Óì¥2C"p£Ðf·šélþÒ3cn†ÖÏ¢ø>(Àé,GÌï`Üÿp Y#twðSœH‘TÑHLÆÁ$ýÊ•ÎÝ|Çð}äé·L8ÈŒ•©Ÿÿl\÷¢Ø–îÇ +YªOÁV¨±Ûï›Å®ÔÉô<CÇÉ—_gax1±'©¢àGM"ZtõcTBvs ¯nè8
XÁÆGp +Ã)<2°x>2mÕ=ÒÞJÿ&Žw;×Aõîôa;_®Ò«¶ðôC$BX’LŠÊïéŸ`qK‡ÜMC·Vã/âv-Pµ$óä3û‚/„ÀE>7i¥?¤ +ªôâÈ“a†Y¢#™—ܲ2yȇžÙ +¤JÖÉR à¿´hm”ÓΕ‡Õ»éŽ|µºº®iË„Ú3pb·q8æùËçÇï'ú
†’„ƒWý ?ueˆ6ÙlrýÃÍçï)Ä´±}M¢ø…Å™×"ÃÁËgšY—óåÈy?ÁÓ¥ +1·B¨#:„Rl·Û[¡öÞZò0™èÈüÍÎðÍŽTs]üê˧hçÝqÉ °w‘î]BŽ03lP»FäVO)ÈÝMLJ›rcÒ+"^÷+˜£d¬7ÙU°‹ñ‰FÎô#Ð__ÃÜ|ÇfÍHž-ôJ¶»åÞà]%Wª<l㌄öþsWýA¶ùÉÏxœÂèÚèBägÖpÝ_šÚ)ì@sF¥œeÿÀ9ø±›ë™¾ +”¦ÑL2Ú-ª°¯Ô
hýŽÒHÄÈýP_[¿²ÑÐ^Äi–žá¢ÆÇÞ-6²Y˜£xèÁÑ.¦&Ü;”WmÒi¤
åÝ›êŽê«_ZôˆñDFÙÿ¨<]= ›˜ŸÌqV•QÍÀ¹du6´ån& f¢å¼F·öv¿š—r.}npvôë9ŠhÞ;}Y¨&œ¢Þ6ñÃ[/ï}"-FLÒ”¹ÐnÔó£u¾ãÍOeü(ð3t‚˜7¡ðÎS›i&fUˆHÍ¡¹BDÕªßBäÜ-ÄQïB >Uõ-·fk1e²OîjË°dµùã2g䮡´9[Ýâç×cHZ½X‹=›k!åù”7ÚÄ*oÿ’±X';òÝg²VÓ/"® Ù<{ån‚9z0Uç =Xubãz«ó¬f¨ù…nµsfÇ3 ¢¢DT®•PÛR‹ÜWöoÞ|U\[úàæ ö®_-Ë|² )á”Úpi¼”ƒêÚ}rpHj£Ï‡AjDöòóÒ|ëòš³²3ÛeÊvŒN¾zÿ£/ú¤ŸB8Ð +q3ù»éOGpªŽ&'«¸úMspð‚Î~ÌDù¼}Õmã ¹4ˆR2ãÀh¬û´'á¹GwáRÁêæ_ݸ²%ÒZ?c~ŒJ+ºtµ=¥ó£=ÙšJž"§È.Ï°»””1Ôêãu=Oñ©É7±6‘P¹åé2P·²ñöVº&†úÑáÁ¿hD‡y“bŒÇí¶-ҩͺd»Î‘9 +gÛÉwب·¸—§[M°µ£ )Gk|B°æ"ëæâåþìÈs%ôJ/ê¸cþ–ºx?5Έö.‚Ó6¾”pÚóà$UÝx§rpýñÐû¼¥Xÿå›}(©æ“%Cÿˆà¼×ó¬{—ºZë!`è—®‚sºÉju?M"²ˆ´‹®ãˆ'Žv›S¦[w*ÃI¤&{Äléx×a9±‡c+\|BP™ìqýlÈ$ž0%4·™¥U¹å2ï§yÝ…ÐÕµ“²éß ›6jìRà“µ1ͱG§FW£šo¶†‘Ð¥W.¶!„~VrîC°Ì‘˜ú£7k~ÙØ|ç¿· "‰§"ÿ³“¼$CWcÒœ¾Ç:hZºF \Õ}~˜rt#q—vGÉx”‹Á>|¶±©=ÈcmÍMEM3徦±Ò/‘y•¤‘œ³spîöÉì +:!°ê5çÉ:×Ê÷ׂCÖœC¿„ËÇ`y]P0Õ0n—UQÓ°+ÙvŽWñ—! +ØØp¬d¿¬s®:5 ²øàU¯^hqahØ“}e§IÏ5ë°Ñ°÷
é?ã’¸ÖGÅ q9t(ögš(hP‹™Œä¬èÍíÞØG`&€¤ážy׆^HbÅ5]bÇüéÜP+ŒEij© ˆñ•‚¿´“Õ#|^2§-Ö“
ºY`®O‰›Š ÎElë‡VêxU÷òYrR‡×<MJô½1»5 +–,jeS“Ô𵉹2|'éóIsz‡ÍfR*+ÛueèÈÒµ)ƒ®¦AÁƒ¥0èàU±äãŽùΉ~J»–ªv÷÷¤Qø04G±B¤þ†Ïv0v6§|8!>ýð€‘ÉH©ÌÓ´ßroƒŸ¼(ÁM`°SmT6ñ"°v
G;Zˆ·ßĬ.ÂƉVT¾Œ~X
ăŽ{²Šà2ö2Çclé$8|¬úÕg÷-ääd:B˜§»$ðöÿÊÓöZ¶¶ò~Þ Bq‘s¢…E‘â˜IÅ´¸×Šƒs5VçPƒð À !ÎU0SÑΛ:’ˆõuk(´ÔÀ°¤0a<v’Ÿd›ôŽÔº˜d—`xò +úèx0•³"OºYkºK S,z]S«š¾ø +¡ŠB–71ê1nxuWQéÓúùzÔt˜‡œƒEªh"„õ +‹×þü5`J£s¾.m±x›š +[‰Yó¸Ç¬PÙÈ¥¦jïÝÀòN̺R}‹]%QyÍeF^a›z$mGáu÷ÝÌóÖYØ64˜. +¨œ©dð¾¶NÊÛúâ»·C¬¤N³o7<"(Ûñ$(¦ÔÅ!OgH[V˜*Ö¼ã×n(óüÝK™óДQ%ªŽ*gcrÜO/ŠÕ4Z…ë ;€gv(sÁÖD°wèm9®Em—JïÝdl]x1†ï%!ô/ÇY|[·y+’ +â`PÝÕßrÈrð\˜#EךAdP^yÎä±[1ăú*\‰ú6#+èý#Ÿƒlú7Ã"¢úoMýZÅ|5æãxã7¾Bã†ZKÿÀØ|ð7§rÈh[‚²¬Cú“ò“÷l9ÛEÍmkrÚÈ6ï`QûªÀC6{Ë)PR(ãqûÑ-ƒCÁ`w²ãóëCGÑ5´pß4~aªÞC¥€jÞšé,N°`¶³z<ßÂçÒg½š\e²ŠbוÄö*tiZ‘`0U¤Fÿ¤¡?G3wY¼øÄŸ#Õ¤ÛP˜ìØ"\‹Û¸LÚœ|èºâ€{Z;ÀÐá‰ô´X¤øöë€öÜ°N”Èû<=ê‘Á€à‘ùÈ£ÁòŒZV”¬î¨™Â¾Ìû¤#{“íÉI¯´„:©|O³P¥9O8¼ 2ôÄûuO'Éb˸0Q7×!Zì<HYÃa›=Åê§ü¬î½U>èt¼3&[˜¨×„„õ´“ÙS¶“¸V·³ÓKKl¶¡ße&Î2‹N"Ùžo]ýñO]Eþ¼ŸÈ2ópË'•2NÊÜC)Œb§ÄÎ;‘þBQƒ‡Áèàâ>Éà{àÈ“A~07sÄ—Ê„K—Ã`añ4£„üŸîà#«À®zÀ„«Ïb˜§4;P +GPf¼Ç89˜HÉFXî$Ý5xzn7vó׶²Qùž(WÏJ|ü>ã*öÄìÔ þ»Z2èÚáæW¼¯%â±7øfóZ¬"a&w¶ébL'o»Ý”›2ÁWàã„Ršã€ƒó‡€%ˆJ?Ž^p¬†ìÏü?³¶ŒËÕÌÊGQL:áÉlEÄcd£ÜB#…Ø'ÉåŽîqó‹à{‹Á=Zi@›ØŽß!,ú¡eŽŠ- ™ÙPk5n]{lgÊ´sa¡¾K¾q‘+n +ÄÀhûäšÍ$<Õq¶¸JŠíc×(ö7UxŽêõ+ð—J5ˆZ¡o"ldAÊñ„@^‡7Góè8-s@„\ê4øw>‰+uujNkÄ!úªÏB[é?zë-Þ]$°«?Yõ\É8bò¬Õü¹¡^Ä&âÐE_œÒÁV¼>±È{.—>9ùÝ—Ág`}!ã7sp°=ßú™}à{Hà˜Û¡Í+¶GJ/¬(à{³ ©ýAo±[eõ£ìa´,¦ÂT°á³f_´òrXÌlå/a;ÙhXåL>s=˜?ýöRnΗÈJàÀG‹.iBÙ‚oe¼)³%BK÷¶XJclü{ûc_Q*tšôb”V©]¥“[ƒ–tl%³^.’g½’lFx›T‚‰ÎÒnÒóšÎ*ö{íNPÞïE…*ÚÛz¢»´8sfô\3RJ +Ù¥ä&°V9ÇìÚa»{~4[êZ]ïœy=ûnWûgç¤35²1±ÌuapÝáÒýÙrµ¼“Jò×´jlôÆ·ø%È…Ek‚ò|̲7õ*¦šyž +j +¦[»§¹uÙ5{ËÑ¢?¿Ð’nÏ(9AîsqÉå Lfî~?Y$+AqÖx•#j6IÛS[¸,M¶ƒ–Õº¶5ÍyÁwf¿Ôþ>J¿Všb4³NÏWò; P›èæøÚ+7ý¸cÏä”î$‰F°â‹p5Wo•ñ½¯Ï BþÃl}«Ná>à‡.pγó‹Nò]2¶bïZAŽT³¦¦xì.z,øN묬*ÿ“¼-ý”ƃéå'8âO¸NöÖ¼Ý"ùyqcFD‚•Yë’øu…]Ÿq§ {còœ½+~®µ
WyDÌx~¼Ó¼F}¥ö׶6̼Eù/6…]çÉ|¥á
¿KõÝ•Xú.)tðë‚ú~êuêF!|”dmšª‘xd… Pˆtá¹=·õå«WÃË+dÂÄ~Ê°Áu¹¢ +opó×Û +#èàï
L¤øbSþ%${ljÜ\i Çú$êþ®é¨/cï œoõ~¹HcM"”ûÕŠ°_×mð]0gRž]ÝÓX=õ¬ùw·I()EÙùïýetÙ!9S„ððÙ3zÂ"ÛEN2J~š‘³æ6Ê!ËmhΘ’M«ã¨-³Žp¢pšZƦUhÇ9ÁÎÆ
2’ß#ØoõqV·;áèxÝ]*ÊU˜D¸
>p·î«'fˆ fsÄd "Ñ&¹UCH$økÖ{xMT·g/ÚgnŠ½ÛŒœîl6™Ý_P¤™\ÆâÅÊ`ÖÞžŠ^.ÍÎDgA§˜O&ßz<šò{dЕǺã¦g Mʳ‰™¨WÏ{0`^Â|2é÷äÕ˜7³Ê¦&mÕÊSfŸJ4Aa…¶&"ìxî÷|y{ ºG‘@„×túqAÊY]ƒ/´ãô–®(¾¢ô¦uT¤‘úÅË JA“‚UÜÍ ÚE¿ÜýH0 üÌ~ÎÎû0ì+˜m" +ù4IÌŠÒÍgÀÛœ!ŸÄ×ÄkG ĺM=Lam~ýÊA%£Õv¯`ûµìò«€cõõJ|E°ôÀü=–uËíWÿörFGªÒid¦>å[tÊþ¶n¯¡ŒÒ~KæýÚ}8¥¥5\ñÔìðe9o·*}Ò/
ü? +µYûåXOuœWÖÈnŽ)qƒåà¸KmØxWšÂ$ŒÂ‘†A7NX$Õ@ƒ®¥E¹ŽKÛ`ª"ç#MëØzÛ%Ür‘=šÔ°q«>ez¤®›l_`9/“DE¾Ê;+–TCDÌó`‰«fGû¦r=wvíË.ÃiŽe†ÛF„ü¸±B62Æ0 g…F÷¼ ó Ž9t¼ÅÜõÓ +»ÊŒBœp~‘ºÒÄPc½Wö‚ èè{“£È –É¢ +,쉤ž(_&J½³~;XHA›£{žjIê½)ÚµÇ\ô,öeŸ’2$Ér±Žà)3²¯N6M¦æW:V4®w]T‚YNccF€6 +ö¶Ú®c}Ÿø8º»?³
k,*„wöÑZcüT»Uñ®~ÜtÎöY͇ +‰ÓCjæÅï!6¥Ù¢ù ]ˆÅÒº^Ã6Y3ÊLôSiî +„@¾ŽÎŒjû»¿ÃžuÒl¿n¨‚«R6ÍVe‹N1*²ÏG6âáÄm{2q¬ZMY£2…àÙ÷?LvÔdR¹$Gg.¶×š£ÎµÈü«Õߧµt½±>Åâ>Àð²C°+à•i¡Ðáäù< Ã)ÜÝF7é¹6F
~ªf—?êÓ¨±\`u]íY§ú`MuW^£²õͧ›-åéÙ ýtJ +Åû^‹3Å^ÜbAê•ìï;lÕS³1þ£2â:ù–-©«®ŸaˆÈ±‘ÐÛ_Ui[,Ð&A<Û¤Y-œovLÉ /jýô^yµâzÁýŒx‚‚b’>?W%4YI`cí3ï[›‹œVÍe˜ãl¸¥¬Õ´wÙ¼bå¿›íqvF¿lÐ7cSWn±”è]„ñW|ô}Úåo‹¼…àVßF¿t¬¨¼o»±€þÄ€›ÙÈámú.ru +g¬Ÿ¨«¶†àSºÕªtvD»û<•‡8ãùœ‘Ë™-Ëm +ʉrÁ¼wÂM¥ÐÍú]ÜjF^̵+@@s4u
‡µœR
úDÁR=Žß*vý–ÙX”à®áBørÈ]ƒÈi#]”Éû^Š¡Lc[gA7¯X™8!§À'‘@¾Z…ìÖk0ïdÊdR’w„kH¦S)ÊßW §†ˆØׇºOùm0z•‰îªf̧\æpl Ÿë;“칇–ÿyÏãd,Fò#7ñùQæâ-W2œ;Wjk³(Ÿ¬á‹d·<MH¢+Ü]’eÊ팧‡X#Fõ¦ìuàö`0û#Ø”*\ñ‡/&Ó×'hwÑðä:I@W}Wé<;ZÆ€ÉßU±+ûšilc ÙG¦üÁ9i²ãý"nV÷¸c ™6öõ«••¸vÑ`HXSÉPAµoóv+U&[žø£ÎŠÊ²c +Ho™-Îx•…q5b„yñw!:»¹ÞsÅù‘ƒO Sïáð~s +ô7ã:ݹ/ò5¼}~A¿+Žh¹W†
Šg÷2(²ÍT–“ÆëöŽWÕ/2r53äQ=%S>v,gýƤè%ÛÛüÊè
õ'8úâwwtLåE(w.À'X˜²‰G6u©‹Š:¤¤ò¯ ª,%mx=e$fçªP€2]?XÍÒѵßÕ÷ ›N¦NÂ7ž^ˆ–bud¡ÉÕvE..v“!»GÊZºh(YNwîöØJ?ˆÅ4ú÷n(hÑŽ/œ¾O3Bj¡¸ø~%ùZ‡ÉKC5;_ýN±È£l_åËé.ðÅ__Z©¦¤™@²¡Å¦øŠÝÕYìs´ôƒ¤œ8oÓ5}Èf¢!xÙø‹\‹wœç‰ +?-À¬Æ]ó€pXZE][BÈ4BLVCùM“¬SÊ9h•……:¨ØDO겡šWÖ#ýa7£|öþñÏwíKq¿ðó`åÈ®šÑ^`-XR…Uf¶ëDâ6ùLZ +Qs#ª%v=ž57I.UàT6dSäêCkõÓ\ZÃ,¯Ïê–ô·âM£†Á@¹ïB7cm
w¡rì1,ô¢|Ù™›L¦[V¥W¶™…Vh%©Ö‘HÉ ¶ÀA½š-K*f¶ð †\¥F±åŠÎ#ÈtlVó)œF¬‰ê½F‹ž;¯ú‘-÷œ¾=Û-… òs¦"&Yº-ÐS8Àëh…ÿ*ãѾ.<49†, v6šuçGªÓ»l¥~?/Ô<š£snj{‰rÀE¢…ܲéq?Wz1iúzòç¶Æ1vCðÞGµ(°Ä5’$æÛ”3y)Áͽ?ï—a§@74$éÁ]—È—l¼, öˆºn©±5ØÙ(–%ûGc¸|F ùòhûë‘ÆÍÖg˜IYXQ8GÏêà玬jœ3Ãü€?§âr
ËÚëB¹ë<f=ˆ=ë‡ü–7i0åEÒft8ÇXŠá„ã
Ù¥C ¥ûþM¦\Rœ)6À¿¾¥×”jÔÃÏ›$anû +9t?ëëÊïåJrÒ0“%©“3B[M_²î"móÏ4§ +êºeNŒeña°8U9¥úÓÀ¦õ!³=l¥ôt|q‹|
&yEi¿¢ÊÍñœiK'–a– +‰ÿm`Ýñ%4¥d+s‰_—P\‚ƒbØÛXáÂ4ÿÿm‚ù{³Xxß?8ëq蟖ÅÅÃæ³Õ´ÏVétÝŠvÊ4zôþŸcVÄÎKÚ_NtâPÅÔ:Ëç9Î<‹Ë4ø9"¤}Ë(pM[uú +{à–Œ«ñï÷Kä’ìyW˜½öÒÑœ–Y®7®³(Úaà#µ·5ƒïú¾ýOÏÛÏêÜ”1ÿÃ'W>9㯷ïÛκÎÚÌÜÞp#Dl¦ëÕŠC5]ï;®fJ.]ù•ñ.{Úµ/geÎ5öï«mà^Ñ·uÂÁœÓ»>Äqí¸½/ãïäCœ“$ZÏ{3„û=õœXÖlNóu󌟄è5rÜe˜úú–ë«xÖø-î\_Ö}¼ëm¾µÀ`ö³öû½W6(9®žÍmkšù{ÓTù¸Ô§Ì¾”ÌLß3ó|®²ƒoÍk5ö
ç˜_ö8ݳ<Ýóõº÷öYW*:ä?˾z»Qií)ë«Û¿‰X1»R±<öÉì?pgïØ¥±o*׊Ž{ê+Ÿù_Ðܸ-ÆmEÐî;Ÿ]÷ö…ŸXͦõUð•ðɳ»R^).?Þ—÷‚fè÷0“Î)ûßï\rðqÆN9A±V/Ejx¬}4Ç%÷sÛßLmüÖA“çä(ºÅ.,HH¾ù\¸ÐƒiÛ¦ÂKÒ7¿Þ¿ûÇ[•’ÛÆäØ[†G-ûµé»å ÍfÖï+9ydîüÚ¾cŸ£ó4}‡s~Ïœ„¬ç>IntsL]•“dËôáq™Wá”Å[V.e±<üfòÞLÏí?|÷Ý_Xzš~Uqp/(úþãЃõºŸökÄÙÖyÏx9àrÒªîçNÛÒª"Ê?.Û)÷¢4M«µºØz›××Hiv¶\méñ)ëe|ÆœÓ&¿ÏlÚÞ:«ÎY1Súà1‡èö$á,_>8®:,ÖrY@(ó;C›ó¹ö禷ç?r?ô ;©ÉÑâãÅt!y=#¯e?Å.oˆ\|⟾ö§ˆ¹Î™{2W°&,ˆï{$Ãç³{ݯ—__ìì]°ÕìDßÍ#GïĹ¹%¾_Zñ-ædaÙ¯‚ÀØ=÷&Åís“y8a®¡®è‹Zq1/€iîvBX†£Òˆ©†¶'ž~bã˜éj4EýÙ†–kµo½iöÚ–·BýSƒüŽÕË'êwÓpxiš½òͶ«…?böþ¬œ®>Aõ‡Ûi“ÄÒÝÁçg†VI±ÿñiõgΉu®ÝkÛ6±ß§®Ô€BÀ5jÀ°0 9'5±¨$?7±(› +endobj +445 0 obj << +/Type /Font +/Subtype /Type1 +/Encoding 1124 0 R +/FirstChar 2 +/LastChar 150 +/Widths 1132 0 R +/BaseFont /PXACOW+NimbusRomNo9L-Medi +/FontDescriptor 443 0 R +>> endobj +443 0 obj << +/Ascent 690 +/CapHeight 690 +/Descent -209 +/FontName /PXACOW+NimbusRomNo9L-Medi +/ItalicAngle 0 +/StemV 140 +/XHeight 461 +/FontBBox [-168 -341 1000 960] +/Flags 4 +/CharSet (/fi/exclam/ampersand/quoteright/parenleft/parenright/plus/comma/hyphen/period/zero/one/two/three/four/five/six/seven/eight/nine/colon/question/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/bracketleft/bracketright/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/endash) +/FontFile 444 0 R +>> endobj +1132 0 obj +[556 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 333 0 0 0 0 833 333 333 333 0 570 250 333 250 0 500 500 500 500 500 500 500 500 500 500 333 0 0 0 0 500 0 722 667 722 722 667 611 778 778 389 500 778 667 944 722 778 611 778 722 556 667 722 722 1000 722 0 0 333 0 333 0 0 0 500 556 444 556 444 333 500 556 278 333 556 278 833 556 500 556 556 444 389 333 556 500 722 500 500 444 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 500 ] +endobj +449 0 obj << +/Type /Pages +/Count 6 +/Parent 1133 0 R +/Kids [438 0 R 451 0 R 469 0 R 499 0 R 526 0 R 552 0 R] +>> endobj +585 0 obj << +/Type /Pages +/Count 6 +/Parent 1133 0 R +/Kids [579 0 R 587 0 R 591 0 R 595 0 R 602 0 R 605 0 R] +>> endobj +610 0 obj << +/Type /Pages +/Count 6 +/Parent 1133 0 R +/Kids [608 0 R 613 0 R 670 0 R 678 0 R 682 0 R 685 0 R] +>> endobj +690 0 obj << +/Type /Pages +/Count 6 +/Parent 1133 0 R +/Kids [688 0 R 692 0 R 700 0 R 709 0 R 722 0 R 735 0 R] +>> endobj +746 0 obj << +/Type /Pages +/Count 6 +/Parent 1133 0 R +/Kids [739 0 R 748 0 R 757 0 R 761 0 R 767 0 R 781 0 R] +>> endobj +796 0 obj << +/Type /Pages +/Count 6 +/Parent 1133 0 R +/Kids [790 0 R 798 0 R 804 0 R 812 0 R 816 0 R 822 0 R] +>> endobj +835 0 obj << +/Type /Pages +/Count 6 +/Parent 1134 0 R +/Kids [830 0 R 837 0 R 842 0 R 853 0 R 861 0 R 867 0 R] +>> endobj +880 0 obj << +/Type /Pages +/Count 6 +/Parent 1134 0 R +/Kids [874 0 R 882 0 R 887 0 R 891 0 R 895 0 R 903 0 R] +>> endobj +912 0 obj << +/Type /Pages +/Count 6 +/Parent 1134 0 R +/Kids [908 0 R 914 0 R 919 0 R 927 0 R 936 0 R 944 0 R] +>> endobj +961 0 obj << +/Type /Pages +/Count 6 +/Parent 1134 0 R +/Kids [952 0 R 963 0 R 969 0 R 974 0 R 979 0 R 983 0 R] +>> endobj +991 0 obj << +/Type /Pages +/Count 6 +/Parent 1134 0 R +/Kids [988 0 R 993 0 R 997 0 R 1002 0 R 1008 0 R 1013 0 R] +>> endobj +1020 0 obj << +/Type /Pages +/Count 6 +/Parent 1134 0 R +/Kids [1017 0 R 1022 0 R 1026 0 R 1030 0 R 1035 0 R 1041 0 R] +>> endobj +1051 0 obj << +/Type /Pages +/Count 6 +/Parent 1135 0 R +/Kids [1047 0 R 1053 0 R 1061 0 R 1070 0 R 1074 0 R 1082 0 R] +>> endobj +1090 0 obj << +/Type /Pages +/Count 6 +/Parent 1135 0 R +/Kids [1086 0 R 1092 0 R 1097 0 R 1101 0 R 1106 0 R 1115 0 R] +>> endobj +1133 0 obj << +/Type /Pages +/Count 36 +/Parent 1136 0 R +/Kids [449 0 R 585 0 R 610 0 R 690 0 R 746 0 R 796 0 R] +>> endobj +1134 0 obj << +/Type /Pages +/Count 36 +/Parent 1136 0 R +/Kids [835 0 R 880 0 R 912 0 R 961 0 R 991 0 R 1020 0 R] +>> endobj +1135 0 obj << +/Type /Pages +/Count 12 +/Parent 1136 0 R +/Kids [1051 0 R 1090 0 R] +>> endobj +1136 0 obj << +/Type /Pages +/Count 84 +/Kids [1133 0 R 1134 0 R 1135 0 R] +>> endobj +1137 0 obj << +/Type /Outlines +/First 7 0 R +/Last 391 0 R +/Count 12 +>> endobj +435 0 obj << +/Title 436 0 R +/A 433 0 R +/Parent 391 0 R +/Prev 407 0 R +>> endobj +431 0 obj << +/Title 432 0 R +/A 429 0 R +/Parent 407 0 R +/Prev 427 0 R +>> endobj +427 0 obj << +/Title 428 0 R +/A 425 0 R +/Parent 407 0 R +/Prev 423 0 R +/Next 431 0 R +>> endobj +423 0 obj << +/Title 424 0 R +/A 421 0 R +/Parent 407 0 R +/Prev 419 0 R +/Next 427 0 R +>> endobj +419 0 obj << +/Title 420 0 R +/A 417 0 R +/Parent 407 0 R +/Prev 415 0 R +/Next 423 0 R +>> endobj +415 0 obj << +/Title 416 0 R +/A 413 0 R +/Parent 407 0 R +/Prev 411 0 R +/Next 419 0 R +>> endobj +411 0 obj << +/Title 412 0 R +/A 409 0 R +/Parent 407 0 R +/Next 415 0 R +>> endobj +407 0 obj << +/Title 408 0 R +/A 405 0 R +/Parent 391 0 R +/Prev 395 0 R +/Next 435 0 R +/First 411 0 R +/Last 431 0 R +/Count -6 +>> endobj +403 0 obj << +/Title 404 0 R +/A 401 0 R +/Parent 395 0 R +/Prev 399 0 R +>> endobj +399 0 obj << +/Title 400 0 R +/A 397 0 R +/Parent 395 0 R +/Next 403 0 R +>> endobj +395 0 obj << +/Title 396 0 R +/A 393 0 R +/Parent 391 0 R +/Next 407 0 R +/First 399 0 R +/Last 403 0 R +/Count -2 +>> endobj +391 0 obj << +/Title 392 0 R +/A 389 0 R +/Parent 1137 0 R +/Prev 331 0 R +/First 395 0 R +/Last 435 0 R +/Count -3 +>> endobj +387 0 obj << +/Title 388 0 R +/A 385 0 R +/Parent 363 0 R +/Prev 383 0 R +>> endobj +383 0 obj << +/Title 384 0 R +/A 381 0 R +/Parent 363 0 R +/Prev 379 0 R +/Next 387 0 R +>> endobj +379 0 obj << +/Title 380 0 R +/A 377 0 R +/Parent 363 0 R +/Prev 375 0 R +/Next 383 0 R +>> endobj +375 0 obj << +/Title 376 0 R +/A 373 0 R +/Parent 363 0 R +/Prev 371 0 R +/Next 379 0 R +>> endobj +371 0 obj << +/Title 372 0 R +/A 369 0 R +/Parent 363 0 R +/Prev 367 0 R +/Next 375 0 R +>> endobj +367 0 obj << +/Title 368 0 R +/A 365 0 R +/Parent 363 0 R +/Next 371 0 R +>> endobj +363 0 obj << +/Title 364 0 R +/A 361 0 R +/Parent 331 0 R +/Prev 359 0 R +/First 367 0 R +/Last 387 0 R +/Count -6 +>> endobj +359 0 obj << +/Title 360 0 R +/A 357 0 R +/Parent 331 0 R +/Prev 351 0 R +/Next 363 0 R +>> endobj +355 0 obj << +/Title 356 0 R +/A 353 0 R +/Parent 351 0 R +>> endobj +351 0 obj << +/Title 352 0 R +/A 349 0 R +/Parent 331 0 R +/Prev 339 0 R +/Next 359 0 R +/First 355 0 R +/Last 355 0 R +/Count -1 +>> endobj +347 0 obj << +/Title 348 0 R +/A 345 0 R +/Parent 339 0 R +/Prev 343 0 R +>> endobj +343 0 obj << +/Title 344 0 R +/A 341 0 R +/Parent 339 0 R +/Next 347 0 R +>> endobj +339 0 obj << +/Title 340 0 R +/A 337 0 R +/Parent 331 0 R +/Prev 335 0 R +/Next 351 0 R +/First 343 0 R +/Last 347 0 R +/Count -2 +>> endobj +335 0 obj << +/Title 336 0 R +/A 333 0 R +/Parent 331 0 R +/Next 339 0 R +>> endobj +331 0 obj << +/Title 332 0 R +/A 329 0 R +/Parent 1137 0 R +/Prev 327 0 R +/Next 391 0 R +/First 335 0 R +/Last 363 0 R +/Count -5 +>> endobj +327 0 obj << +/Title 328 0 R +/A 325 0 R +/Parent 1137 0 R +/Prev 311 0 R +/Next 331 0 R +>> endobj +323 0 obj << +/Title 324 0 R +/A 321 0 R +/Parent 315 0 R +/Prev 319 0 R +>> endobj +319 0 obj << +/Title 320 0 R +/A 317 0 R +/Parent 315 0 R +/Next 323 0 R +>> endobj +315 0 obj << +/Title 316 0 R +/A 313 0 R +/Parent 311 0 R +/First 319 0 R +/Last 323 0 R +/Count -2 +>> endobj +311 0 obj << +/Title 312 0 R +/A 309 0 R +/Parent 1137 0 R +/Prev 203 0 R +/Next 327 0 R +/First 315 0 R +/Last 315 0 R +/Count -1 +>> endobj +307 0 obj << +/Title 308 0 R +/A 305 0 R +/Parent 203 0 R +/Prev 299 0 R +>> endobj +303 0 obj << +/Title 304 0 R +/A 301 0 R +/Parent 299 0 R +>> endobj +299 0 obj << +/Title 300 0 R +/A 297 0 R +/Parent 203 0 R +/Prev 295 0 R +/Next 307 0 R +/First 303 0 R +/Last 303 0 R +/Count -1 +>> endobj +295 0 obj << +/Title 296 0 R +/A 293 0 R +/Parent 203 0 R +/Prev 291 0 R +/Next 299 0 R +>> endobj +291 0 obj << +/Title 292 0 R +/A 289 0 R +/Parent 203 0 R +/Prev 287 0 R +/Next 295 0 R +>> endobj +287 0 obj << +/Title 288 0 R +/A 285 0 R +/Parent 203 0 R +/Prev 283 0 R +/Next 291 0 R +>> endobj +283 0 obj << +/Title 284 0 R +/A 281 0 R +/Parent 203 0 R +/Prev 279 0 R +/Next 287 0 R +>> endobj +279 0 obj << +/Title 280 0 R +/A 277 0 R +/Parent 203 0 R +/Prev 275 0 R +/Next 283 0 R +>> endobj +275 0 obj << +/Title 276 0 R +/A 273 0 R +/Parent 203 0 R +/Prev 271 0 R +/Next 279 0 R +>> endobj +271 0 obj << +/Title 272 0 R +/A 269 0 R +/Parent 203 0 R +/Prev 227 0 R +/Next 275 0 R +>> endobj +267 0 obj << +/Title 268 0 R +/A 265 0 R +/Parent 227 0 R +/Prev 263 0 R +>> endobj +263 0 obj << +/Title 264 0 R +/A 261 0 R +/Parent 227 0 R +/Prev 259 0 R +/Next 267 0 R +>> endobj +259 0 obj << +/Title 260 0 R +/A 257 0 R +/Parent 227 0 R +/Prev 255 0 R +/Next 263 0 R +>> endobj +255 0 obj << +/Title 256 0 R +/A 253 0 R +/Parent 227 0 R +/Prev 251 0 R +/Next 259 0 R +>> endobj +251 0 obj << +/Title 252 0 R +/A 249 0 R +/Parent 227 0 R +/Prev 247 0 R +/Next 255 0 R +>> endobj +247 0 obj << +/Title 248 0 R +/A 245 0 R +/Parent 227 0 R +/Prev 243 0 R +/Next 251 0 R +>> endobj +243 0 obj << +/Title 244 0 R +/A 241 0 R +/Parent 227 0 R +/Prev 239 0 R +/Next 247 0 R +>> endobj +239 0 obj << +/Title 240 0 R +/A 237 0 R +/Parent 227 0 R +/Prev 235 0 R +/Next 243 0 R +>> endobj +235 0 obj << +/Title 236 0 R +/A 233 0 R +/Parent 227 0 R +/Prev 231 0 R +/Next 239 0 R +>> endobj +231 0 obj << +/Title 232 0 R +/A 229 0 R +/Parent 227 0 R +/Next 235 0 R +>> endobj +227 0 obj << +/Title 228 0 R +/A 225 0 R +/Parent 203 0 R +/Prev 211 0 R +/Next 271 0 R +/First 231 0 R +/Last 267 0 R +/Count -10 +>> endobj +223 0 obj << +/Title 224 0 R +/A 221 0 R +/Parent 211 0 R +/Prev 219 0 R +>> endobj +219 0 obj << +/Title 220 0 R +/A 217 0 R +/Parent 211 0 R +/Prev 215 0 R +/Next 223 0 R +>> endobj +215 0 obj << +/Title 216 0 R +/A 213 0 R +/Parent 211 0 R +/Next 219 0 R +>> endobj +211 0 obj << +/Title 212 0 R +/A 209 0 R +/Parent 203 0 R +/Prev 207 0 R +/Next 227 0 R +/First 215 0 R +/Last 223 0 R +/Count -3 +>> endobj +207 0 obj << +/Title 208 0 R +/A 205 0 R +/Parent 203 0 R +/Next 211 0 R +>> endobj +203 0 obj << +/Title 204 0 R +/A 201 0 R +/Parent 1137 0 R +/Prev 135 0 R +/Next 311 0 R +/First 207 0 R +/Last 307 0 R +/Count -12 +>> endobj +199 0 obj << +/Title 200 0 R +/A 197 0 R +/Parent 135 0 R +/Prev 195 0 R +>> endobj +195 0 obj << +/Title 196 0 R +/A 193 0 R +/Parent 135 0 R +/Prev 191 0 R +/Next 199 0 R +>> endobj +191 0 obj << +/Title 192 0 R +/A 189 0 R +/Parent 135 0 R +/Prev 147 0 R +/Next 195 0 R +>> endobj +187 0 obj << +/Title 188 0 R +/A 185 0 R +/Parent 147 0 R +/Prev 183 0 R +>> endobj +183 0 obj << +/Title 184 0 R +/A 181 0 R +/Parent 147 0 R +/Prev 179 0 R +/Next 187 0 R +>> endobj +179 0 obj << +/Title 180 0 R +/A 177 0 R +/Parent 147 0 R +/Prev 175 0 R +/Next 183 0 R +>> endobj +175 0 obj << +/Title 176 0 R +/A 173 0 R +/Parent 147 0 R +/Prev 171 0 R +/Next 179 0 R +>> endobj +171 0 obj << +/Title 172 0 R +/A 169 0 R +/Parent 147 0 R +/Prev 167 0 R +/Next 175 0 R +>> endobj +167 0 obj << +/Title 168 0 R +/A 165 0 R +/Parent 147 0 R +/Prev 163 0 R +/Next 171 0 R +>> endobj +163 0 obj << +/Title 164 0 R +/A 161 0 R +/Parent 147 0 R +/Prev 159 0 R +/Next 167 0 R +>> endobj +159 0 obj << +/Title 160 0 R +/A 157 0 R +/Parent 147 0 R +/Prev 155 0 R +/Next 163 0 R +>> endobj +155 0 obj << +/Title 156 0 R +/A 153 0 R +/Parent 147 0 R +/Prev 151 0 R +/Next 159 0 R +>> endobj +151 0 obj << +/Title 152 0 R +/A 149 0 R +/Parent 147 0 R +/Next 155 0 R +>> endobj +147 0 obj << +/Title 148 0 R +/A 145 0 R +/Parent 135 0 R +/Prev 139 0 R +/Next 191 0 R +/First 151 0 R +/Last 187 0 R +/Count -10 +>> endobj +143 0 obj << +/Title 144 0 R +/A 141 0 R +/Parent 139 0 R +>> endobj +139 0 obj << +/Title 140 0 R +/A 137 0 R +/Parent 135 0 R +/Next 147 0 R +/First 143 0 R +/Last 143 0 R +/Count -1 +>> endobj +135 0 obj << +/Title 136 0 R +/A 133 0 R +/Parent 1137 0 R +/Prev 59 0 R +/Next 203 0 R +/First 139 0 R +/Last 199 0 R +/Count -5 +>> endobj +131 0 obj << +/Title 132 0 R +/A 129 0 R +/Parent 99 0 R +/Prev 127 0 R +>> endobj +127 0 obj << +/Title 128 0 R +/A 125 0 R +/Parent 99 0 R +/Prev 123 0 R +/Next 131 0 R +>> endobj +123 0 obj << +/Title 124 0 R +/A 121 0 R +/Parent 99 0 R +/Prev 119 0 R +/Next 127 0 R +>> endobj +119 0 obj << +/Title 120 0 R +/A 117 0 R +/Parent 99 0 R +/Prev 115 0 R +/Next 123 0 R +>> endobj +115 0 obj << +/Title 116 0 R +/A 113 0 R +/Parent 99 0 R +/Prev 111 0 R +/Next 119 0 R +>> endobj +111 0 obj << +/Title 112 0 R +/A 109 0 R +/Parent 99 0 R +/Prev 107 0 R +/Next 115 0 R +>> endobj +107 0 obj << +/Title 108 0 R +/A 105 0 R +/Parent 99 0 R +/Prev 103 0 R +/Next 111 0 R +>> endobj +103 0 obj << +/Title 104 0 R +/A 101 0 R +/Parent 99 0 R +/Next 107 0 R +>> endobj +99 0 obj << +/Title 100 0 R +/A 97 0 R +/Parent 59 0 R +/Prev 83 0 R +/First 103 0 R +/Last 131 0 R +/Count -8 +>> endobj +95 0 obj << +/Title 96 0 R +/A 93 0 R +/Parent 83 0 R +/Prev 91 0 R +>> endobj +91 0 obj << +/Title 92 0 R +/A 89 0 R +/Parent 83 0 R +/Prev 87 0 R +/Next 95 0 R +>> endobj +87 0 obj << +/Title 88 0 R +/A 85 0 R +/Parent 83 0 R +/Next 91 0 R +>> endobj +83 0 obj << +/Title 84 0 R +/A 81 0 R +/Parent 59 0 R +/Prev 71 0 R +/Next 99 0 R +/First 87 0 R +/Last 95 0 R +/Count -3 +>> endobj +79 0 obj << +/Title 80 0 R +/A 77 0 R +/Parent 71 0 R +/Prev 75 0 R +>> endobj +75 0 obj << +/Title 76 0 R +/A 73 0 R +/Parent 71 0 R +/Next 79 0 R +>> endobj +71 0 obj << +/Title 72 0 R +/A 69 0 R +/Parent 59 0 R +/Prev 67 0 R +/Next 83 0 R +/First 75 0 R +/Last 79 0 R +/Count -2 +>> endobj +67 0 obj << +/Title 68 0 R +/A 65 0 R +/Parent 59 0 R +/Prev 63 0 R +/Next 71 0 R +>> endobj +63 0 obj << +/Title 64 0 R +/A 61 0 R +/Parent 59 0 R +/Next 67 0 R +>> endobj +59 0 obj << +/Title 60 0 R +/A 57 0 R +/Parent 1137 0 R +/Prev 39 0 R +/Next 135 0 R +/First 63 0 R +/Last 99 0 R +/Count -5 +>> endobj +55 0 obj << +/Title 56 0 R +/A 53 0 R +/Parent 39 0 R +/Prev 51 0 R +>> endobj +51 0 obj << +/Title 52 0 R +/A 49 0 R +/Parent 39 0 R +/Prev 47 0 R +/Next 55 0 R +>> endobj +47 0 obj << +/Title 48 0 R +/A 45 0 R +/Parent 39 0 R +/Prev 43 0 R +/Next 51 0 R +>> endobj +43 0 obj << +/Title 44 0 R +/A 41 0 R +/Parent 39 0 R +/Next 47 0 R +>> endobj +39 0 obj << +/Title 40 0 R +/A 37 0 R +/Parent 1137 0 R +/Prev 19 0 R +/Next 59 0 R +/First 43 0 R +/Last 55 0 R +/Count -4 +>> endobj +35 0 obj << +/Title 36 0 R +/A 33 0 R +/Parent 19 0 R +/Prev 31 0 R +>> endobj +31 0 obj << +/Title 32 0 R +/A 29 0 R +/Parent 19 0 R +/Prev 27 0 R +/Next 35 0 R +>> endobj +27 0 obj << +/Title 28 0 R +/A 25 0 R +/Parent 19 0 R +/Prev 23 0 R +/Next 31 0 R +>> endobj +23 0 obj << +/Title 24 0 R +/A 21 0 R +/Parent 19 0 R +/Next 27 0 R +>> endobj +19 0 obj << +/Title 20 0 R +/A 17 0 R +/Parent 1137 0 R +/Prev 15 0 R +/Next 39 0 R +/First 23 0 R +/Last 35 0 R +/Count -4 +>> endobj +15 0 obj << +/Title 16 0 R +/A 13 0 R +/Parent 1137 0 R +/Prev 11 0 R +/Next 19 0 R +>> endobj +11 0 obj << +/Title 12 0 R +/A 9 0 R +/Parent 1137 0 R +/Prev 7 0 R +/Next 15 0 R +>> endobj +7 0 obj << +/Title 8 0 R +/A 5 0 R +/Parent 1137 0 R +/Next 11 0 R +>> endobj +1138 0 obj << +/Names [(Doc-Start) 442 0 R (Item.1) 598 0 R (Item.2) 599 0 R (Item.3) 600 0 R (chapter*.2) 453 0 R (chapter*.3) 10 0 R (chapter*.4) 14 0 R (chapter.1) 18 0 R (chapter.2) 38 0 R (chapter.3) 58 0 R (chapter.4) 134 0 R (chapter.5) 202 0 R (chapter.6) 310 0 R (chapter.7) 326 0 R (chapter.8) 330 0 R (chapter.9) 390 0 R (example.2.3.1) 673 0 R (example.3.2.1) 696 0 R (example.3.2.2) 698 0 R (example.3.3.1) 720 0 R (example.3.3.2) 727 0 R (example.3.4.1) 743 0 R (example.3.4.2) 745 0 R (example.3.4.3) 755 0 R (example.3.5.1) 771 0 R (example.3.5.2) 779 0 R (example.3.5.3) 785 0 R (example.3.5.4) 793 0 R (example.3.5.5) 801 0 R (example.3.5.6) 802 0 R (example.3.5.7) 808 0 R (example.4.1.1) 820 0 R (example.4.1.2) 827 0 R (example.4.3.1) 856 0 R (example.4.3.2) 857 0 R (example.4.3.3) 858 0 R (example.4.3.4) 859 0 R (example.4.4.1) 865 0 R (example.4.5.1) 871 0 R (example.5.0.2) 879 0 R (example.5.10.1) 972 0 R (example.5.11.1) 977 0 R (example.5.12.1) 986 0 R (example.5.3.1) 899 0 R (example.5.4.1) 906 0 R (example.5.4.2) 911 0 R (example.5.4.3) 917 0 R (example.5.5.1) 923 0 R (example.5.5.2) 925 0 R (example.5.6.1) 931 0 R (example.5.6.2) 932 0 R (example.5.7.1) 934 0 R (example.5.7.2) 941 0 R (example.5.8.1) 948 0 R (example.5.9.1) 956 0 R (example.5.9.2) 960 0 R (example.6.0.2) 1000 0 R (example.6.0.3) 1005 0 R (example.6.1.1) 1006 0 R (example.6.1.2) 1011 0 R (example.8.4.1) 1039 0 R (example.8.5.1) 1059 0 R (example.8.5.2) 1068 0 R (example.9.1.1) 1080 0 R (example.9.2.1) 1089 0 R (example.9.2.2) 1104 0 R (example.9.3.1) 1109 0 R (example.9.3.2) 1110 0 R (example.9.3.3) 1111 0 R (example.9.3.4) 1112 0 R (example.9.3.5) 1113 0 R (example.9.3.6) 1118 0 R (example.9.3.7) 1119 0 R (figure.2.1) 615 0 R (page.1) 441 0 R (page.10) 694 0 R (page.11) 702 0 R (page.12) 711 0 R (page.13) 724 0 R (page.14) 737 0 R (page.15) 741 0 R (page.16) 750 0 R (page.17) 759 0 R (page.18) 763 0 R (page.19) 769 0 R (page.2) 471 0 R (page.20) 783 0 R (page.21) 792 0 R (page.22) 800 0 R (page.23) 806 0 R (page.24) 814 0 R (page.25) 818 0 R (page.26) 824 0 R (page.27) 832 0 R (page.28) 839 0 R (page.29) 844 0 R (page.3) 501 0 R (page.30) 855 0 R (page.31) 863 0 R (page.32) 869 0 R (page.33) 876 0 R (page.34) 884 0 R (page.35) 889 0 R (page.36) 893 0 R (page.37) 897 0 R (page.38) 905 0 R (page.39) 910 0 R (page.4) 528 0 R (page.40) 916 0 R (page.41) 921 0 R (page.42) 929 0 R (page.43) 938 0 R (page.44) 946 0 R (page.45) 954 0 R (page.46) 965 0 R (page.47) 971 0 R (page.48) 976 0 R (page.49) 981 0 R (page.5) 554 0 R (page.50) 985 0 R (page.51) 990 0 R (page.52) 995 0 R (page.53) 999 0 R (page.54) 1004 0 R (page.55) 1010 0 R (page.56) 1015 0 R (page.57) 1019 0 R (page.58) 1024 0 R (page.59) 1028 0 R (page.6) 581 0 R (page.60) 1032 0 R (page.61) 1037 0 R (page.62) 1043 0 R (page.63) 1049 0 R (page.64) 1055 0 R (page.65) 1063 0 R (page.66) 1072 0 R (page.67) 1076 0 R (page.68) 1084 0 R (page.69) 1088 0 R (page.7) 589 0 R (page.70) 1094 0 R (page.71) 1099 0 R (page.72) 1103 0 R (page.73) 1108 0 R (page.74) 1117 0 R (page.8) 593 0 R (page.9) 597 0 R (section*.1) 6 0 R (section*.5) 833 0 R (section*.6) 834 0 R (section.1.1) 22 0 R (section.1.2) 26 0 R (section.1.3) 30 0 R (section.1.4) 34 0 R (section.2.1) 42 0 R (section.2.2) 46 0 R (section.2.3) 50 0 R (section.2.4) 54 0 R (section.3.1) 62 0 R (section.3.2) 66 0 R (section.3.3) 70 0 R (section.3.4) 82 0 R (section.3.5) 98 0 R (section.4.1) 138 0 R (section.4.2) 146 0 R (section.4.3) 190 0 R (section.4.4) 194 0 R (section.4.5) 198 0 R (section.5.1) 206 0 R (section.5.10) 294 0 R (section.5.11) 298 0 R (section.5.12) 306 0 R (section.5.2) 210 0 R (section.5.3) 226 0 R (section.5.4) 270 0 R (section.5.5) 274 0 R (section.5.6) 278 0 R (section.5.7) 282 0 R (section.5.8) 286 0 R (section.5.9) 290 0 R (section.6.1) 314 0 R (section.8.1) 334 0 R (section.8.2) 338 0 R (section.8.3) 350 0 R (section.8.4) 358 0 R (section.8.5) 362 0 R (section.9.1) 394 0 R (section.9.2) 406 0 R (section.9.3) 434 0 R (subsection.3.3.1) 74 0 R (subsection.3.3.2) 78 0 R (subsection.3.4.1) 86 0 R (subsection.3.4.2) 90 0 R (subsection.3.4.3) 94 0 R (subsection.3.5.1) 102 0 R (subsection.3.5.2) 106 0 R (subsection.3.5.3) 110 0 R (subsection.3.5.4) 114 0 R (subsection.3.5.5) 118 0 R (subsection.3.5.6) 122 0 R (subsection.3.5.7) 126 0 R (subsection.3.5.8) 130 0 R (subsection.4.1.1) 142 0 R (subsection.4.2.1) 150 0 R (subsection.4.2.10) 186 0 R (subsection.4.2.2) 154 0 R (subsection.4.2.3) 158 0 R (subsection.4.2.4) 162 0 R (subsection.4.2.5) 166 0 R (subsection.4.2.6) 170 0 R (subsection.4.2.7) 174 0 R (subsection.4.2.8) 178 0 R (subsection.4.2.9) 182 0 R (subsection.5.11.1) 302 0 R (subsection.5.2.1) 214 0 R (subsection.5.2.2) 218 0 R (subsection.5.2.3) 222 0 R (subsection.5.3.1) 230 0 R (subsection.5.3.10) 266 0 R (subsection.5.3.2) 234 0 R (subsection.5.3.3) 238 0 R (subsection.5.3.4) 242 0 R (subsection.5.3.5) 246 0 R (subsection.5.3.6) 250 0 R (subsection.5.3.7) 254 0 R (subsection.5.3.8) 258 0 R (subsection.5.3.9) 262 0 R (subsection.6.1.1) 318 0 R (subsection.6.1.2) 322 0 R (subsection.8.2.1) 342 0 R (subsection.8.2.2) 346 0 R (subsection.8.3.1) 354 0 R (subsection.8.5.1) 366 0 R (subsection.8.5.2) 370 0 R (subsection.8.5.3) 374 0 R (subsection.8.5.4) 378 0 R (subsection.8.5.5) 382 0 R (subsection.8.5.6) 386 0 R (subsection.9.1.1) 398 0 R (subsection.9.1.2) 402 0 R (subsection.9.2.1) 410 0 R (subsection.9.2.2) 414 0 R (subsection.9.2.3) 418 0 R (subsection.9.2.4) 422 0 R (subsection.9.2.5) 426 0 R (subsection.9.2.6) 430 0 R (table.3.1) 733 0 R (table.3.2) 764 0 R (table.4.1) 840 0 R (table.8.1) 1033 0 R (table.8.2) 1045 0 R (table.8.3) 1050 0 R (table.8.4) 1056 0 R (table.8.5) 1057 0 R (table.8.6) 1064 0 R] +/Limits [(Doc-Start) (table.8.6)] +>> endobj +1139 0 obj << +/Kids [1138 0 R] +>> endobj +1140 0 obj << +/Dests 1139 0 R +>> endobj +1141 0 obj << +/Type /Catalog +/Pages 1136 0 R +/Outlines 1137 0 R +/Names 1140 0 R +/PageMode /UseOutlines +/OpenAction 437 0 R +>> endobj +1142 0 obj << +/Author()/Title()/Subject()/Creator(LaTeX with hyperref package)/Producer(pdfeTeX-1.21a)/Keywords() +/CreationDate (D:20060414153230+10'00') +/PTEX.Fullbanner (This is pdfTeX, Version 3.141592-1.20b (MiKTeX 2.4.1986)) +>> endobj +xref +0 1143 +0000000001 65535 f +0000000002 00000 f +0000000003 00000 f +0000000004 00000 f +0000000000 00000 f +0000000009 00000 n +0000014321 00000 n +0000822345 00000 n +0000000055 00000 n +0000000081 00000 n +0000040268 00000 n +0000822258 00000 n +0000000127 00000 n +0000000158 00000 n +0000042595 00000 n +0000822169 00000 n +0000000205 00000 n +0000000231 00000 n +0000044236 00000 n +0000822043 00000 n +0000000277 00000 n +0000000308 00000 n +0000044296 00000 n +0000821969 00000 n +0000000356 00000 n +0000000383 00000 n +0000044356 00000 n +0000821882 00000 n +0000000431 00000 n +0000000469 00000 n +0000045550 00000 n +0000821795 00000 n +0000000517 00000 n +0000000543 00000 n +0000045609 00000 n +0000821721 00000 n +0000000591 00000 n +0000000620 00000 n +0000047006 00000 n +0000821595 00000 n +0000000666 00000 n +0000000700 00000 n +0000047066 00000 n +0000821521 00000 n +0000000748 00000 n +0000000779 00000 n +0000047126 00000 n +0000821434 00000 n +0000000827 00000 n +0000000862 00000 n +0000047186 00000 n +0000821347 00000 n +0000000910 00000 n +0000000946 00000 n +0000548061 00000 n +0000821273 00000 n +0000000994 00000 n +0000001054 00000 n +0000551323 00000 n +0000821146 00000 n +0000001100 00000 n +0000001141 00000 n +0000551383 00000 n +0000821072 00000 n +0000001189 00000 n +0000001220 00000 n +0000553432 00000 n +0000820985 00000 n +0000001268 00000 n +0000001332 00000 n +0000560408 00000 n +0000820861 00000 n +0000001380 00000 n +0000001416 00000 n +0000564037 00000 n +0000820787 00000 n +0000001469 00000 n +0000001503 00000 n +0000568303 00000 n +0000820713 00000 n +0000001556 00000 n +0000001592 00000 n +0000568363 00000 n +0000820589 00000 n +0000001640 00000 n +0000001666 00000 n +0000568423 00000 n +0000820515 00000 n +0000001719 00000 n +0000001758 00000 n +0000568543 00000 n +0000820428 00000 n +0000001811 00000 n +0000001849 00000 n +0000570729 00000 n +0000820354 00000 n +0000001902 00000 n +0000001935 00000 n +0000574883 00000 n +0000820240 00000 n +0000001983 00000 n +0000002036 00000 n +0000578142 00000 n +0000820162 00000 n +0000002090 00000 n +0000002122 00000 n +0000578203 00000 n +0000820070 00000 n +0000002176 00000 n +0000002218 00000 n +0000578325 00000 n +0000819978 00000 n +0000002272 00000 n +0000002317 00000 n +0000581470 00000 n +0000819886 00000 n +0000002371 00000 n +0000002410 00000 n +0000584542 00000 n +0000819794 00000 n +0000002464 00000 n +0000002505 00000 n +0000584664 00000 n +0000819702 00000 n +0000002559 00000 n +0000002598 00000 n +0000589128 00000 n +0000819610 00000 n +0000002652 00000 n +0000002692 00000 n +0000589250 00000 n +0000819532 00000 n +0000002746 00000 n +0000002783 00000 n +0000592341 00000 n +0000819400 00000 n +0000002830 00000 n +0000002886 00000 n +0000592402 00000 n +0000819282 00000 n +0000002935 00000 n +0000002968 00000 n +0000597099 00000 n +0000819217 00000 n +0000003022 00000 n +0000003067 00000 n +0000597281 00000 n +0000819084 00000 n +0000003116 00000 n +0000003156 00000 n +0000597342 00000 n +0000819005 00000 n +0000003210 00000 n +0000003248 00000 n +0000597403 00000 n +0000818912 00000 n +0000003302 00000 n +0000003341 00000 n +0000600075 00000 n +0000818819 00000 n +0000003395 00000 n +0000003431 00000 n +0000600198 00000 n +0000818726 00000 n +0000003485 00000 n +0000003521 00000 n +0000600259 00000 n +0000818633 00000 n +0000003575 00000 n +0000003609 00000 n +0000600320 00000 n +0000818540 00000 n +0000003663 00000 n +0000003702 00000 n +0000602701 00000 n +0000818447 00000 n +0000003756 00000 n +0000003790 00000 n +0000602762 00000 n +0000818354 00000 n +0000003844 00000 n +0000003883 00000 n +0000602823 00000 n +0000818261 00000 n +0000003937 00000 n +0000003972 00000 n +0000602884 00000 n +0000818182 00000 n +0000004027 00000 n +0000004068 00000 n +0000602945 00000 n +0000818089 00000 n +0000004117 00000 n +0000004158 00000 n +0000607373 00000 n +0000817996 00000 n +0000004207 00000 n +0000004251 00000 n +0000609491 00000 n +0000817917 00000 n +0000004300 00000 n +0000004341 00000 n +0000611475 00000 n +0000817783 00000 n +0000004388 00000 n +0000004419 00000 n +0000614215 00000 n +0000817704 00000 n +0000004468 00000 n +0000004508 00000 n +0000614276 00000 n +0000817572 00000 n +0000004557 00000 n +0000004599 00000 n +0000616189 00000 n +0000817493 00000 n +0000004653 00000 n +0000004685 00000 n +0000616250 00000 n +0000817400 00000 n +0000004739 00000 n +0000004774 00000 n +0000616311 00000 n +0000817321 00000 n +0000004828 00000 n +0000004865 00000 n +0000616372 00000 n +0000817188 00000 n +0000004914 00000 n +0000004951 00000 n +0000616433 00000 n +0000817109 00000 n +0000005005 00000 n +0000005043 00000 n +0000616494 00000 n +0000817016 00000 n +0000005097 00000 n +0000005133 00000 n +0000618748 00000 n +0000816923 00000 n +0000005187 00000 n +0000005228 00000 n +0000618809 00000 n +0000816830 00000 n +0000005282 00000 n +0000005318 00000 n +0000618870 00000 n +0000816737 00000 n +0000005372 00000 n +0000005406 00000 n +0000618931 00000 n +0000816644 00000 n +0000005460 00000 n +0000005503 00000 n +0000622138 00000 n +0000816551 00000 n +0000005557 00000 n +0000005596 00000 n +0000622260 00000 n +0000816458 00000 n +0000005650 00000 n +0000005686 00000 n +0000624480 00000 n +0000816365 00000 n +0000005740 00000 n +0000005778 00000 n +0000624541 00000 n +0000816286 00000 n +0000005833 00000 n +0000005874 00000 n +0000624602 00000 n +0000816193 00000 n +0000005923 00000 n +0000005963 00000 n +0000629814 00000 n +0000816100 00000 n +0000006012 00000 n +0000006052 00000 n +0000632326 00000 n +0000816007 00000 n +0000006101 00000 n +0000006172 00000 n +0000632507 00000 n +0000815914 00000 n +0000006221 00000 n +0000006261 00000 n +0000635756 00000 n +0000815821 00000 n +0000006310 00000 n +0000006348 00000 n +0000638424 00000 n +0000815728 00000 n +0000006397 00000 n +0000006445 00000 n +0000644623 00000 n +0000815635 00000 n +0000006495 00000 n +0000006544 00000 n +0000648227 00000 n +0000815503 00000 n +0000006594 00000 n +0000006655 00000 n +0000651360 00000 n +0000815438 00000 n +0000006710 00000 n +0000006752 00000 n +0000651421 00000 n +0000815359 00000 n +0000006802 00000 n +0000006878 00000 n +0000657034 00000 n +0000815226 00000 n +0000006925 00000 n +0000006957 00000 n +0000659780 00000 n +0000815122 00000 n +0000007006 00000 n +0000007046 00000 n +0000659842 00000 n +0000815043 00000 n +0000007100 00000 n +0000007151 00000 n +0000661872 00000 n +0000814964 00000 n +0000007205 00000 n +0000007235 00000 n +0000663327 00000 n +0000814870 00000 n +0000007282 00000 n +0000007313 00000 n +0000665263 00000 n +0000814737 00000 n +0000007360 00000 n +0000007402 00000 n +0000665325 00000 n +0000814658 00000 n +0000007451 00000 n +0000007483 00000 n +0000665387 00000 n +0000814526 00000 n +0000007532 00000 n +0000007585 00000 n +0000665449 00000 n +0000814447 00000 n +0000007639 00000 n +0000007681 00000 n +0000668094 00000 n +0000814368 00000 n +0000007735 00000 n +0000007773 00000 n +0000668156 00000 n +0000814236 00000 n +0000007822 00000 n +0000007876 00000 n +0000668218 00000 n +0000814171 00000 n +0000007930 00000 n +0000007968 00000 n +0000670522 00000 n +0000814078 00000 n +0000008017 00000 n +0000008084 00000 n +0000670646 00000 n +0000813960 00000 n +0000008133 00000 n +0000008186 00000 n +0000673077 00000 n +0000813881 00000 n +0000008240 00000 n +0000008283 00000 n +0000673203 00000 n +0000813788 00000 n +0000008337 00000 n +0000008390 00000 n +0000675473 00000 n +0000813695 00000 n +0000008444 00000 n +0000008489 00000 n +0000675535 00000 n +0000813602 00000 n +0000008543 00000 n +0000008599 00000 n +0000678237 00000 n +0000813509 00000 n +0000008653 00000 n +0000008712 00000 n +0000681669 00000 n +0000813430 00000 n +0000008766 00000 n +0000008806 00000 n +0000685368 00000 n +0000813311 00000 n +0000008853 00000 n +0000008900 00000 n +0000685430 00000 n +0000813193 00000 n +0000008949 00000 n +0000008999 00000 n +0000688948 00000 n +0000813114 00000 n +0000009053 00000 n +0000009091 00000 n +0000689010 00000 n +0000813035 00000 n +0000009145 00000 n +0000009204 00000 n +0000689072 00000 n +0000812903 00000 n +0000009253 00000 n +0000009335 00000 n +0000691684 00000 n +0000812824 00000 n +0000009389 00000 n +0000009431 00000 n +0000691746 00000 n +0000812731 00000 n +0000009485 00000 n +0000009519 00000 n +0000691808 00000 n +0000812638 00000 n +0000009573 00000 n +0000009605 00000 n +0000694919 00000 n +0000812545 00000 n +0000009659 00000 n +0000009696 00000 n +0000694981 00000 n +0000812452 00000 n +0000009750 00000 n +0000009781 00000 n +0000697149 00000 n +0000812373 00000 n +0000009835 00000 n +0000009866 00000 n +0000698635 00000 n +0000812294 00000 n +0000009915 00000 n +0000009950 00000 n +0000010457 00000 n +0000010691 00000 n +0000010003 00000 n +0000010569 00000 n +0000010630 00000 n +0000809174 00000 n +0000790985 00000 n +0000808999 00000 n +0000789797 00000 n +0000768312 00000 n +0000789622 00000 n +0000810128 00000 n +0000014441 00000 n +0000011954 00000 n +0000010776 00000 n +0000014380 00000 n +0000012190 00000 n +0000012342 00000 n +0000012494 00000 n +0000012646 00000 n +0000012797 00000 n +0000012950 00000 n +0000013102 00000 n +0000013255 00000 n +0000013408 00000 n +0000013559 00000 n +0000013712 00000 n +0000013865 00000 n +0000014018 00000 n +0000014170 00000 n +0000020430 00000 n +0000016460 00000 n +0000014526 00000 n +0000020369 00000 n +0000016768 00000 n +0000016921 00000 n +0000017073 00000 n +0000017225 00000 n +0000017383 00000 n +0000017542 00000 n +0000017695 00000 n +0000017854 00000 n +0000018013 00000 n +0000767316 00000 n +0000747822 00000 n +0000767144 00000 n +0000018172 00000 n +0000018325 00000 n +0000018484 00000 n +0000018643 00000 n +0000018802 00000 n +0000018960 00000 n +0000019119 00000 n +0000019278 00000 n +0000019436 00000 n +0000019594 00000 n +0000019745 00000 n +0000019898 00000 n +0000020057 00000 n +0000020210 00000 n +0000026241 00000 n +0000022263 00000 n +0000020528 00000 n +0000026180 00000 n +0000022571 00000 n +0000022730 00000 n +0000022889 00000 n +0000023048 00000 n +0000023206 00000 n +0000023365 00000 n +0000023524 00000 n +0000023683 00000 n +0000023842 00000 n +0000024002 00000 n +0000024155 00000 n +0000024308 00000 n +0000024460 00000 n +0000024611 00000 n +0000024763 00000 n +0000024916 00000 n +0000025075 00000 n +0000025233 00000 n +0000025392 00000 n +0000025545 00000 n +0000025704 00000 n +0000025862 00000 n +0000026021 00000 n +0000032069 00000 n +0000028295 00000 n +0000026339 00000 n +0000032008 00000 n +0000028595 00000 n +0000028754 00000 n +0000028913 00000 n +0000029072 00000 n +0000029231 00000 n +0000029390 00000 n +0000029550 00000 n +0000029703 00000 n +0000029852 00000 n +0000030005 00000 n +0000030158 00000 n +0000030311 00000 n +0000030463 00000 n +0000030617 00000 n +0000030771 00000 n +0000030930 00000 n +0000031084 00000 n +0000031235 00000 n +0000031388 00000 n +0000031547 00000 n +0000031706 00000 n +0000031857 00000 n +0000038143 00000 n +0000034173 00000 n +0000032167 00000 n +0000038082 00000 n +0000034481 00000 n +0000034634 00000 n +0000034786 00000 n +0000034945 00000 n +0000035103 00000 n +0000035256 00000 n +0000035415 00000 n +0000035568 00000 n +0000035721 00000 n +0000035879 00000 n +0000036038 00000 n +0000036196 00000 n +0000036355 00000 n +0000036514 00000 n +0000036672 00000 n +0000036823 00000 n +0000036976 00000 n +0000037134 00000 n +0000037293 00000 n +0000037446 00000 n +0000037605 00000 n +0000037764 00000 n +0000037923 00000 n +0000039419 00000 n +0000038742 00000 n +0000038241 00000 n +0000039358 00000 n +0000038890 00000 n +0000039049 00000 n +0000039207 00000 n +0000810246 00000 n +0000040328 00000 n +0000040095 00000 n +0000039491 00000 n +0000040207 00000 n +0000040792 00000 n +0000040619 00000 n +0000040413 00000 n +0000040731 00000 n +0000042838 00000 n +0000042422 00000 n +0000040864 00000 n +0000042534 00000 n +0000042655 00000 n +0000042716 00000 n +0000042777 00000 n +0000044416 00000 n +0000044124 00000 n +0000042936 00000 n +0000045669 00000 n +0000045438 00000 n +0000044501 00000 n +0000047246 00000 n +0000046894 00000 n +0000045754 00000 n +0000810364 00000 n +0000049262 00000 n +0000543136 00000 n +0000049130 00000 n +0000047331 00000 n +0000543074 00000 n +0000542922 00000 n +0000054568 00000 n +0000054724 00000 n +0000054804 00000 n +0000054867 00000 n +0000055016 00000 n +0000055168 00000 n +0000055292 00000 n +0000055394 00000 n +0000055542 00000 n +0000055644 00000 n +0000056423 00000 n +0000056653 00000 n +0000056681 00000 n +0000057145 00000 n +0000057173 00000 n +0000057480 00000 n +0000057589 00000 n +0000057691 00000 n +0000057800 00000 n +0000059132 00000 n +0000070800 00000 n +0000086345 00000 n +0000103237 00000 n +0000128703 00000 n +0000154033 00000 n +0000176640 00000 n +0000198895 00000 n +0000220491 00000 n +0000242655 00000 n +0000255677 00000 n +0000259248 00000 n +0000275832 00000 n +0000294632 00000 n +0000311816 00000 n +0000333679 00000 n +0000357285 00000 n +0000380181 00000 n +0000400968 00000 n +0000426793 00000 n +0000438198 00000 n +0000451061 00000 n +0000469964 00000 n +0000494403 00000 n +0000506602 00000 n +0000506983 00000 n +0000507148 00000 n +0000507239 00000 n +0000507315 00000 n +0000507530 00000 n +0000507606 00000 n +0000507825 00000 n +0000523967 00000 n +0000545265 00000 n +0000544915 00000 n +0000543249 00000 n +0000545047 00000 n +0000545203 00000 n +0000747002 00000 n +0000731714 00000 n +0000746823 00000 n +0000548121 00000 n +0000547778 00000 n +0000545376 00000 n +0000547910 00000 n +0000549372 00000 n +0000549260 00000 n +0000548219 00000 n +0000549914 00000 n +0000549802 00000 n +0000549444 00000 n +0000551443 00000 n +0000551211 00000 n +0000549986 00000 n +0000810482 00000 n +0000553553 00000 n +0000552921 00000 n +0000551528 00000 n +0000553371 00000 n +0000553061 00000 n +0000553492 00000 n +0000553216 00000 n +0000556373 00000 n +0000556433 00000 n +0000555864 00000 n +0000553664 00000 n +0000556312 00000 n +0000731404 00000 n +0000729419 00000 n +0000731243 00000 n +0000556004 00000 n +0000556156 00000 n +0000560527 00000 n +0000558911 00000 n +0000556557 00000 n +0000560347 00000 n +0000559099 00000 n +0000559255 00000 n +0000559410 00000 n +0000559564 00000 n +0000559720 00000 n +0000559876 00000 n +0000560031 00000 n +0000560189 00000 n +0000560467 00000 n +0000564097 00000 n +0000562660 00000 n +0000560638 00000 n +0000563915 00000 n +0000562840 00000 n +0000562995 00000 n +0000563976 00000 n +0000563151 00000 n +0000563306 00000 n +0000563458 00000 n +0000563609 00000 n +0000563761 00000 n +0000565726 00000 n +0000565788 00000 n +0000565553 00000 n +0000564208 00000 n +0000565665 00000 n +0000568663 00000 n +0000567790 00000 n +0000565886 00000 n +0000568242 00000 n +0000567930 00000 n +0000568482 00000 n +0000568086 00000 n +0000568603 00000 n +0000810600 00000 n +0000570789 00000 n +0000570384 00000 n +0000568774 00000 n +0000570668 00000 n +0000728922 00000 n +0000716945 00000 n +0000728749 00000 n +0000570516 00000 n +0000572394 00000 n +0000572455 00000 n +0000572221 00000 n +0000570901 00000 n +0000572333 00000 n +0000574943 00000 n +0000574469 00000 n +0000572553 00000 n +0000574760 00000 n +0000574821 00000 n +0000574601 00000 n +0000578447 00000 n +0000577145 00000 n +0000575041 00000 n +0000578081 00000 n +0000577309 00000 n +0000578264 00000 n +0000716461 00000 n +0000706667 00000 n +0000716284 00000 n +0000577465 00000 n +0000577621 00000 n +0000577773 00000 n +0000577927 00000 n +0000578386 00000 n +0000581593 00000 n +0000580634 00000 n +0000578586 00000 n +0000581409 00000 n +0000580790 00000 n +0000581531 00000 n +0000580946 00000 n +0000581101 00000 n +0000581257 00000 n +0000584724 00000 n +0000584033 00000 n +0000581732 00000 n +0000584481 00000 n +0000584603 00000 n +0000584173 00000 n +0000584329 00000 n +0000810718 00000 n +0000586665 00000 n +0000586370 00000 n +0000584863 00000 n +0000586482 00000 n +0000586543 00000 n +0000586604 00000 n +0000589311 00000 n +0000588456 00000 n +0000586790 00000 n +0000589067 00000 n +0000588604 00000 n +0000589189 00000 n +0000588760 00000 n +0000588915 00000 n +0000590197 00000 n +0000590024 00000 n +0000589450 00000 n +0000590136 00000 n +0000592524 00000 n +0000591993 00000 n +0000590282 00000 n +0000592280 00000 n +0000592125 00000 n +0000592463 00000 n +0000595203 00000 n +0000594465 00000 n +0000592622 00000 n +0000595081 00000 n +0000594613 00000 n +0000594769 00000 n +0000595142 00000 n +0000594925 00000 n +0000597463 00000 n +0000596926 00000 n +0000595328 00000 n +0000597038 00000 n +0000597160 00000 n +0000597221 00000 n +0000810836 00000 n +0000600381 00000 n +0000599902 00000 n +0000597575 00000 n +0000600014 00000 n +0000600136 00000 n +0000603006 00000 n +0000602356 00000 n +0000600493 00000 n +0000602640 00000 n +0000704436 00000 n +0000702949 00000 n +0000704275 00000 n +0000702637 00000 n +0000700315 00000 n +0000702475 00000 n +0000602488 00000 n +0000604886 00000 n +0000604469 00000 n +0000603143 00000 n +0000604581 00000 n +0000604642 00000 n +0000604703 00000 n +0000604764 00000 n +0000604825 00000 n +0000607495 00000 n +0000607024 00000 n +0000605011 00000 n +0000607312 00000 n +0000607156 00000 n +0000607434 00000 n +0000609613 00000 n +0000608978 00000 n +0000607620 00000 n +0000609430 00000 n +0000609118 00000 n +0000609552 00000 n +0000609274 00000 n +0000611597 00000 n +0000610967 00000 n +0000609738 00000 n +0000611414 00000 n +0000611107 00000 n +0000611259 00000 n +0000611536 00000 n +0000810954 00000 n +0000614337 00000 n +0000613866 00000 n +0000611722 00000 n +0000614154 00000 n +0000613998 00000 n +0000616554 00000 n +0000616016 00000 n +0000614449 00000 n +0000616128 00000 n +0000618992 00000 n +0000618575 00000 n +0000616666 00000 n +0000618687 00000 n +0000622321 00000 n +0000621460 00000 n +0000619104 00000 n +0000622077 00000 n +0000621608 00000 n +0000622199 00000 n +0000621764 00000 n +0000621919 00000 n +0000624724 00000 n +0000624307 00000 n +0000622485 00000 n +0000624419 00000 n +0000624663 00000 n +0000626375 00000 n +0000626142 00000 n +0000624863 00000 n +0000626254 00000 n +0000626315 00000 n +0000811072 00000 n +0000627755 00000 n +0000627521 00000 n +0000626460 00000 n +0000627633 00000 n +0000627694 00000 n +0000629997 00000 n +0000629301 00000 n +0000627880 00000 n +0000629753 00000 n +0000629441 00000 n +0000629875 00000 n +0000629597 00000 n +0000629936 00000 n +0000632629 00000 n +0000631814 00000 n +0000630108 00000 n +0000632265 00000 n +0000631954 00000 n +0000632387 00000 n +0000632447 00000 n +0000632110 00000 n +0000632568 00000 n +0000635817 00000 n +0000635020 00000 n +0000632740 00000 n +0000635634 00000 n +0000635168 00000 n +0000635323 00000 n +0000635695 00000 n +0000635479 00000 n +0000638485 00000 n +0000637688 00000 n +0000635928 00000 n +0000638302 00000 n +0000637836 00000 n +0000638363 00000 n +0000637992 00000 n +0000638148 00000 n +0000641508 00000 n +0000640551 00000 n +0000638596 00000 n +0000641326 00000 n +0000640707 00000 n +0000641387 00000 n +0000640862 00000 n +0000641014 00000 n +0000641170 00000 n +0000641448 00000 n +0000811190 00000 n +0000644684 00000 n +0000644110 00000 n +0000641619 00000 n +0000644562 00000 n +0000644250 00000 n +0000644406 00000 n +0000646325 00000 n +0000646091 00000 n +0000644795 00000 n +0000646203 00000 n +0000646264 00000 n +0000648349 00000 n +0000648054 00000 n +0000646436 00000 n +0000648166 00000 n +0000648288 00000 n +0000651482 00000 n +0000651187 00000 n +0000648460 00000 n +0000651299 00000 n +0000653545 00000 n +0000653311 00000 n +0000651580 00000 n +0000653423 00000 n +0000653484 00000 n +0000654694 00000 n +0000654521 00000 n +0000653669 00000 n +0000654633 00000 n +0000811308 00000 n +0000655315 00000 n +0000655142 00000 n +0000654792 00000 n +0000655254 00000 n +0000657157 00000 n +0000656861 00000 n +0000655387 00000 n +0000656973 00000 n +0000657095 00000 n +0000659965 00000 n +0000659539 00000 n +0000657268 00000 n +0000659654 00000 n +0000659717 00000 n +0000659902 00000 n +0000661997 00000 n +0000661694 00000 n +0000660077 00000 n +0000661809 00000 n +0000661934 00000 n +0000662764 00000 n +0000662586 00000 n +0000662109 00000 n +0000662701 00000 n +0000663389 00000 n +0000663148 00000 n +0000662837 00000 n +0000663264 00000 n +0000811429 00000 n +0000664002 00000 n +0000663823 00000 n +0000663475 00000 n +0000663939 00000 n +0000665511 00000 n +0000665084 00000 n +0000664075 00000 n +0000665200 00000 n +0000668280 00000 n +0000667851 00000 n +0000665597 00000 n +0000667967 00000 n +0000668030 00000 n +0000670708 00000 n +0000670165 00000 n +0000668379 00000 n +0000670459 00000 n +0000670302 00000 n +0000670584 00000 n +0000673265 00000 n +0000672725 00000 n +0000670820 00000 n +0000673014 00000 n +0000672862 00000 n +0000673139 00000 n +0000675597 00000 n +0000675230 00000 n +0000673378 00000 n +0000675346 00000 n +0000675409 00000 n +0000811554 00000 n +0000678362 00000 n +0000677753 00000 n +0000675710 00000 n +0000678046 00000 n +0000678109 00000 n +0000678173 00000 n +0000677890 00000 n +0000678299 00000 n +0000681794 00000 n +0000680925 00000 n +0000678502 00000 n +0000681542 00000 n +0000681605 00000 n +0000681080 00000 n +0000681233 00000 n +0000681389 00000 n +0000681731 00000 n +0000683169 00000 n +0000682990 00000 n +0000681934 00000 n +0000683106 00000 n +0000685555 00000 n +0000684689 00000 n +0000683268 00000 n +0000685305 00000 n +0000684844 00000 n +0000684996 00000 n +0000685148 00000 n +0000685492 00000 n +0000687081 00000 n +0000686902 00000 n +0000685668 00000 n +0000687018 00000 n +0000689197 00000 n +0000688769 00000 n +0000687193 00000 n +0000688885 00000 n +0000689134 00000 n +0000811679 00000 n +0000691870 00000 n +0000691333 00000 n +0000689323 00000 n +0000691621 00000 n +0000691470 00000 n +0000695043 00000 n +0000694740 00000 n +0000691969 00000 n +0000694856 00000 n +0000697274 00000 n +0000696970 00000 n +0000695142 00000 n +0000697086 00000 n +0000697211 00000 n +0000699012 00000 n +0000698456 00000 n +0000697386 00000 n +0000698572 00000 n +0000698697 00000 n +0000698760 00000 n +0000698823 00000 n +0000698886 00000 n +0000698949 00000 n +0000700203 00000 n +0000699898 00000 n +0000699124 00000 n +0000700014 00000 n +0000700077 00000 n +0000700140 00000 n +0000702863 00000 n +0000702838 00000 n +0000704688 00000 n +0000704659 00000 n +0000704778 00000 n +0000716740 00000 n +0000729204 00000 n +0000731628 00000 n +0000731603 00000 n +0000747397 00000 n +0000767883 00000 n +0000790420 00000 n +0000809658 00000 n +0000811804 00000 n +0000811924 00000 n +0000812045 00000 n +0000812135 00000 n +0000812217 00000 n +0000822418 00000 n +0000828179 00000 n +0000828220 00000 n +0000828260 00000 n +0000828394 00000 n +trailer +<< +/Size 1143 +/Root 1141 0 R +/Info 1142 0 R +/ID [<BE4289FB29A57A8BBA94EE46DDDC77F4> <BE4289FB29A57A8BBA94EE46DDDC77F4>] +>> +startxref +828634 +%%EOF diff --git a/docs/sqlmap/sqlmap_tut.pdf b/docs/sqlmap/sqlmap_tut.pdf Binary files differnew file mode 100644 index 00000000..36675fab --- /dev/null +++ b/docs/sqlmap/sqlmap_tut.pdf diff --git a/framework/Collections/TPagedList.php b/framework/Collections/TPagedList.php index e441e67c..9637f499 100644 --- a/framework/Collections/TPagedList.php +++ b/framework/Collections/TPagedList.php @@ -68,7 +68,7 @@ class TPagedList extends TList /**
* @var integer current page index
*/
- private $_currentPageIndex=0;
+ private $_currentPageIndex=-1;
/**
* @var integer user-assigned number of items in data source
*/
diff --git a/framework/DataAccess/SQLMap/Configuration/TConfigDeserialize.php b/framework/DataAccess/SQLMap/Configuration/TConfigDeserialize.php new file mode 100644 index 00000000..250133b9 --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TConfigDeserialize.php @@ -0,0 +1,171 @@ +<?php
+
+class TConfigDeserialize
+{
+ private $_properties;
+
+ public function __construct($properties)
+ {
+ $this->_properties = $properties;
+ }
+
+ public function loadConfiguration($object, $node, $file)
+ {
+ foreach($node->attributes() as $k=>$v)
+ {
+ if($object->canSetProperty($k))
+ $object->{'set'.$k}($this->replaceProperties((string)($v)));
+ else
+ throw new TUndefinedAttributeException($k,$node,$object,$file);
+ }
+ }
+
+ public function replaceProperties($string)
+ {
+ foreach($this->_properties as $find => $replace)
+ $string = str_replace('${'.$find.'}', $replace, $string);
+ return $string;
+ }
+
+ public function parameterMap($node, $sqlMap, $file)
+ {
+ $parameterMap = new TParameterMap;
+ $this->loadConfiguration($parameterMap, $node, $file);
+ foreach($node->parameter as $parameter)
+ {
+ $property = $this->parameterProperty($parameter, $sqlMap, $file);
+ $property->initialize($sqlMap);
+ $parameterMap->addParameterProperty($property);
+ }
+ return $parameterMap;
+ }
+
+ public function parameterProperty($node, $sqlMap, $file)
+ {
+ $property = new TParameterProperty;
+ $this->loadConfiguration($property, $node, $file);
+ return $property;
+ }
+
+ public function select($node, $sqlMap, $file)
+ {
+ $select = new TSqlMapSelect;
+ $this->loadConfiguration($select, $node, $file);
+ $select->initialize($sqlMap);
+ return $select;
+ }
+
+ public function update($node, $sqlMap, $file)
+ {
+ $update = new TSqlMapUpdate;
+ $this->loadConfiguration($update, $node, $file);
+ $update->initialize($sqlMap);
+ return $update;
+ }
+
+ public function delete($node, $sqlMap, $file)
+ {
+ $delete = new TSqlMapDelete;
+ $this->loadConfiguration($delete, $node, $file);
+ $delete->initialize($sqlMap);
+ return $delete;
+ }
+
+
+ public function insert($node, $sqlMap, $file)
+ {
+ $insert = new TSqlMapInsert;
+ $this->loadConfiguration($insert, $node, $file);
+ if(isset($node->selectKey))
+ {
+ $selectKey = new TSqlMapSelectKey;
+ $this->loadConfiguration($selectKey, $node->selectKey, $file);
+ $type = $selectKey->getType();
+ $selectKey->setType(strtolower($type) == 'post' ? 'post' : 'pre');
+ $insert->setSelectKey($selectKey);
+ }
+ if(isset($node->generate))
+ {
+ var_dump("add generate");
+ }
+
+ $insert->initialize($sqlMap);
+ return $insert;
+ }
+
+ public function statement($node, $sqlMap, $file)
+ {
+ $statement = new TSqlMapStatement;
+ $this->loadConfiguration($statement, $node, $file);
+ $statement->initialize($sqlMap);
+ return $statement;
+ }
+
+ public function resultMap($node, $sqlMap, $file)
+ {
+ $resultMap = new TResultMap;
+ $this->loadConfiguration($resultMap, $node, $file);
+ foreach($node->result as $result)
+ {
+ $property = $this->resultProperty($result, $sqlMap, $file);
+ $property->initialize($sqlMap, $resultMap);
+ $resultMap->addResultProperty($property);
+ }
+ $discriminator = null;
+ if(isset($node->discriminator))
+ {
+ $discriminator = new TDiscriminator;
+ $this->loadConfiguration($discriminator, $node->discriminator, $file);
+ $discriminator->initMapping($sqlMap, $resultMap);
+ }
+ foreach($node->subMap as $subMapNode)
+ {
+ if(is_null($discriminator))
+ throw new TSqlMapConfigurationException(
+ 'sqlmap_undefined_discriminator', $resultMap->getID(), $file);
+ $subMap = new TSubMap;
+ $this->loadConfiguration($subMap, $subMapNode, $file);
+ $discriminator->add($subMap);
+ }
+ if(!is_null($discriminator))
+ $resultMap->setDiscriminator($discriminator);
+
+ return $resultMap;
+ }
+
+ public function resultProperty($node, $sqlMap, $file)
+ {
+ $resultProperty = new TResultProperty;
+ $this->loadConfiguration($resultProperty, $node, $file);
+ return $resultProperty;
+ }
+
+ public function cacheModel($node, $sqlMap, $file)
+ {
+ $cacheModel = new TSqlMapCacheModel;
+ $this->loadConfiguration($cacheModel, $node, $file);
+ if(isset($node->flushInterval))
+ {
+ $interval = $node->flushInterval;
+ $span = 0; //span in seconds
+ if(isset($interval['hours']))
+ $span += intval($interval['hours'])*60*60;
+ if(isset($interval['minutes']))
+ $span += intval($interval['minutes'])*60;
+ if(isset($interval['seconds']))
+ $span += intval($interval['seconds']);
+ if($span > 0)
+ $cacheModel->setFlushInterval($span);
+ }
+ if(isset($node->property))
+ {
+ foreach($node->property as $property)
+ $cacheModel->addProperty((string)$property['name'],
+ (string)$property['value']);
+ }
+ return $cacheModel;
+ }
+}
+
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TDiscriminator.php b/framework/DataAccess/SQLMap/Configuration/TDiscriminator.php new file mode 100644 index 00000000..0ee34e3e --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TDiscriminator.php @@ -0,0 +1,86 @@ +<?php
+
+class TDiscriminator extends TComponent
+{
+ private $_column='';
+ private $_type='';
+ private $_typeHandler=null;
+ private $_dbType='';
+ private $_columnIndex='';
+ private $_nullValue='';
+ private $_mapping='';
+ private $_resultMaps=array();
+ private $_subMaps=array();
+
+ public function getColumn(){ return $this->_column; }
+ public function setColumn($value){ $this->_column = $value; }
+
+ public function getType(){ return $this->_type; }
+ public function setType($value){ $this->_type = $value; }
+
+ public function getTypeHandler(){ return $this->_typeHandler; }
+ public function setTypeHandler($value){ $this->_typeHandler = $value; }
+
+ public function getDbType(){ return $this->_dbType; }
+ public function setDbType($value){ $this->_dbType = $value; }
+
+ public function getColumnIndex(){ return $this->_columnIndex; }
+ public function setColumnIndex($value){ $this->_columnIndex = $value; }
+
+ public function getNullValue(){ return $this->_nullValue; }
+ public function setNullValue($value){ $this->_nullValue = $value; }
+
+ public function getMapping(){ return $this->_mapping; }
+
+ public function getResultMaps(){ return $this->_resultMaps; }
+ public function setResultMaps($value){ $this->_resultMaps = $value; }
+
+ public function add($subMap)
+ {
+ $this->_subMaps[] = $subMap;
+ }
+
+ public function getSubMap($value)
+ {
+ if(isset($this->_resultMaps[$value]))
+ return $this->_resultMaps[$value];
+ else
+ return null;
+ }
+
+ public function initMapping($sqlMap, $resultMap)
+ {
+ $this->_mapping = new TResultProperty;
+ $this->_mapping->setColumn($this->getColumn());
+ $this->_mapping->setColumnIndex($this->getColumnIndex());
+ $this->_mapping->setType($this->getType());
+ $this->_mapping->setTypeHandler($this->getTypeHandler());
+ $this->_mapping->setDbType($this->getDbType());
+ $this->_mapping->setNullValue($this->getNullValue());
+ $this->_mapping->initialize($sqlMap, $resultMap);
+ }
+
+ public function initialize($sqlMap)
+ {
+ foreach($this->_subMaps as $subMap)
+ {
+ $this->_resultMaps[$subMap->getValue()] =
+ $sqlMap->getResultMap($subMap->getResultMapping());
+ }
+ }
+}
+
+
+class TSubMap extends TComponent
+{
+ private $_value='';
+ private $_resultMapping='';
+
+ public function getValue(){ return $this->_value; }
+ public function setValue($value){ $this->_value = $value; }
+
+ public function getResultMapping(){ return $this->_resultMapping; }
+ public function setResultMapping($value){ $this->_resultMapping = $value; }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TDomSqlMapBuilder.php b/framework/DataAccess/SQLMap/Configuration/TDomSqlMapBuilder.php new file mode 100644 index 00000000..d3059dd7 --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TDomSqlMapBuilder.php @@ -0,0 +1,481 @@ +<?php
+
+class TDomSqlMapBuilder
+{
+ const DEFAULT_CONFIG_FILE = 'sqlmap.xml';
+
+ private $_document;
+
+ private $_sqlMapper;
+
+ private $_configFile;
+
+ private $_properties;
+
+ private $_deserialize;
+
+ private $_useNamespaces = false;
+
+ private $_FlushOnExecuteStatements=array();
+
+ public function __construct($cachedir='./cache')
+ {
+ $this->_properties = new TMap;
+ $this->_deserialize = new TConfigDeserialize($this->_properties);
+ }
+
+ public function configure($resource=null)
+ {
+ if($resource instanceof SimpleXMLElement)
+ return $this->build($resource);
+
+ if(!is_string($resource))
+ $resource = self::DEFAULT_CONFIG_FILE;
+
+ $this->_configFile = $resource;
+ if(!is_file($resource))
+ throw new TSqlMapConfigurationException(
+ 'sqlmap_unable_to_find_config', $resource);
+
+ return $this->build($this->getConfigAsXmlDocument($resource));
+ }
+
+ protected function getConfigAsXmlDocument($file)
+ {
+ return simplexml_load_file($file);
+ }
+
+ public function build(SimpleXMLElement $document)
+ {
+ $this->_document = $document;
+ $this->initialize($document);
+ return $this->_sqlMapper;
+ }
+
+ protected function initialize($document)
+ {
+ $this->_sqlMapper = new TSqlMapper(new TTypeHandlerFactory);
+
+ if(isset($document->properties))
+ {
+ $this->loadGlobalProperties($document->properties);
+ }
+
+ if(isset($document->settings) && isset($document->settings->setting))
+ $this->configureSettings($document->settings);
+
+ //load database provider
+ if(isset($document->provider) && isset($document->provider->datasource))
+ {
+ $this->loadProvider($document->provider,
+ $document->provider->datasource, $document, $this->_configFile);
+ }
+ else
+ {
+ throw new TSqlMapConfigurationException(
+ 'sqlmap_unable_to_find_db_config', $this->_configFile);
+ }
+
+ if(isset($document->sqlMaps) && isset($document->sqlMaps->sqlMap))
+ $this->loadSqlMappingFiles($document->sqlMaps);
+
+ if($this->_sqlMapper->getIsCacheModelsEnabled())
+ $this->attachCacheModel();
+ }
+
+ protected function configureSettings($node)
+ {
+ foreach($node->setting as $setting)
+ {
+ if(isset($setting['useStatementNamespaces']))
+ {
+ $this->_useNamespaces =
+ TPropertyValue::ensureBoolean(
+ (string)$setting['useStatementNamespaces']);
+ }
+
+ if(isset($setting['cacheModelsEnabled']))
+ {
+ $this->_sqlMapper->setCacheModelsEnabled(
+ TPropertyValue::ensureBoolean(
+ (string)$setting['cacheModelsEnabled']));
+ }
+ }
+ }
+
+ /**
+ * Attach CacheModel to statement and register trigger statements for
+ * cache models
+ */
+ protected function attachCacheModel()
+ {
+ foreach($this->_sqlMapper->getStatements() as $mappedStatement)
+ {
+ if(strlen($model = $mappedStatement->getStatement()->getCacheModel()) > 0)
+ {
+ $cache = $this->_sqlMapper->getCache($model);
+ //var_dump($model);
+ $mappedStatement->getStatement()->setCache($cache);
+ }
+ }
+
+ foreach($this->_FlushOnExecuteStatements as $cacheID => $statementIDs)
+ {
+ if(count($statementIDs) > 0)
+ {
+ foreach($statementIDs as $statementID)
+ {
+ $cacheModel = $this->_sqlMapper->getCache($cacheID);
+ $statement = $this->_sqlMapper->getMappedStatement($statementID);
+ $cacheModel->registerTriggerStatement($statement);
+ }
+ }
+ }
+ }
+
+ protected function loadGlobalProperties($node)
+ {
+ if(isset($node['resource']))
+ $this->loadPropertyResource($node);
+
+ foreach($node->children() as $child)
+ {
+ if(isset($child['resource']))
+ $this->loadPropertyResource($child);
+ $this->_properties[(string)$child['key']] = (string)$child['value'];
+ }
+ }
+
+ protected function loadPropertyResource($node)
+ {
+ $resource = $this->getResourceFromPath((string)$node['resource']);
+ $properties = $this->getConfigAsXmlDocument($resource);
+ $this->loadGlobalProperties($properties);
+ }
+
+ protected function loadProvider($providerNode, $node, $document, $file)
+ {
+ //$id = (string)$node['id'];
+ $class = (string)$providerNode['class'];
+ if(strlen($class) > 0 && class_exists($class,false))
+ {
+ $provider = new $class;
+ $this->_deserialize->loadConfiguration($provider, $node,$file);
+ $this->_sqlMapper->setDataProvider($provider);
+ }
+ else
+ {
+ throw new TSqlMapConfigurationException(
+ 'sqlmap_unable_find_provider_class', $file);
+ }
+ //var_dump($node);
+ }
+/*
+ protected function loadTypeHandlers()
+ {
+ }
+*/
+ protected function loadSqlMappingFiles($sqlmappings)
+ {
+ foreach($sqlmappings->sqlMap as $node)
+ {
+ $resource = $this->getResourceFromPath((string)$node['resource']);
+ $sqlmap = $this->getConfigAsXmlDocument($resource);
+ $this->configureSqlMap($sqlmap,$resource);
+ }
+
+ $this->resolveResultMapping();
+ }
+
+ protected function getResourceFromPath($resource)
+ {
+ $basedir = dirname($this->_configFile);
+ $file = realpath($basedir.'/'.$resource);
+ if(!is_string($file) || !is_file($file))
+ $file = realpath($resource);
+ if(is_string($file) && is_file($file))
+ return $file;
+ else
+ throw new TSqlMapConfigurationException(
+ 'sqlmap_unable_to_find_resource', $resource);
+ }
+
+ protected function configureSqlMap($document,$file)
+ {
+ // if(isset($document->typeAlias))
+ // foreach($document->typeAlias as $node)
+ // TTypeAliasDeSerializer::Deserialize($node, $this->_sqlMapper);
+ if(isset($document->resultMap))
+ foreach($document->resultMap as $node)
+ $this->loadResultMap($node,$document,$file);
+ if(isset($document->parameterMap))
+ foreach($document->parameterMap as $node)
+ $this->loadParameterMap($node, $document, $file);
+ if(isset($document->statement))
+ foreach($document->statement as $node)
+ $this->loadStatementTag($node, $document,$file);
+ if(isset($document->select))
+ foreach($document->select as $node)
+ $this->loadSelectTag($node, $document, $file);
+ if(isset($document->insert))
+ foreach($document->insert as $node)
+ $this->loadInsertTag($node, $document, $file);
+ if(isset($document->update))
+ foreach($document->update as $node)
+ $this->loadUpdateTag($node, $document, $file);
+ if(isset($document->delete))
+ foreach($document->delete as $node)
+ $this->loadDeleteTag($node, $document, $file);
+/* if(isset($document->procedure))
+ foreach($document->procedure as $node)
+ $this->loadProcedureTag($node);
+*/
+ if($this->_sqlMapper->getIsCacheModelsEnabled())
+ {
+ if(isset($document->cacheModel))
+ foreach($document->cacheModel as $node)
+ $this->loadCacheModel($node, $document, $file);
+ }
+ }
+
+ protected function loadCacheModel($node, $document, $file)
+ {
+ $cacheModel = $this->_deserialize->cacheModel($node, $this->_sqlMapper, $file);
+ if(isset($node->flushOnExecute))
+ {
+ foreach($node->flushOnExecute as $flush)
+ {
+ $id = $cacheModel->getID();
+ if(!isset($this->_FlushOnExecuteStatements[$id]))
+ $this->_FlushOnExecuteStatements[$id] = array();
+
+ $this->_FlushOnExecuteStatements[$id][] = (string)$flush['statement'];
+ }
+ }
+ //var_dump($cacheModel);
+ $cacheModel->initialize($this->_sqlMapper);
+ $this->_sqlMapper->addCache($cacheModel);
+ }
+
+ protected function loadUpdateTag($node, $document, $file)
+ {
+ $update = $this->_deserialize->insert($node, $this->_sqlMapper, $file);
+ if(!is_null($update->getGenerate()))
+ {
+ var_dump('generate update');
+ }
+ else
+ {
+ $this->processSqlStatement($update, $document, $node, $file);
+ }
+ $mappedStatement = new TUpdateMappedStatement($this->_sqlMapper, $update);
+ $this->_sqlMapper->addMappedStatement($mappedStatement);
+ }
+
+ protected function loadDeleteTag($node, $document, $file)
+ {
+ $delete = $this->_deserialize->delete($node, $this->_sqlMapper, $file);
+ if(!is_null($delete->getGenerate()))
+ {
+ var_dump('generate delete');
+ }
+ else
+ {
+ $this->processSqlStatement($delete, $document, $node, $file);
+ }
+ $mappedStatement = new TDeleteMappedStatement($this->_sqlMapper, $delete);
+ $this->_sqlMapper->addMappedStatement($mappedStatement);
+ }
+
+
+ protected function loadParameterMap($node, $document, $file)
+ {
+ $id = (string)$node['id'];
+ if($this->_sqlMapper->getParameterMaps()->contains($id))
+ return;
+ $parameterMap = $this->_deserialize->parameterMap($node, $this->_sqlMapper, $file);
+ $extendMap = $parameterMap->getExtends();
+ if(strlen($extendMap) > 0)
+ {
+ if($this->_sqlMapper->getParameterMaps()->contains($extendMap) == false)
+ {
+ $nodes = $document->xpath("//parameterMap[@id='{$extendMap}']");
+ if(isset($nodes[0]))
+ $this->loadParameterMap($nodes[0],$document,$file);
+ else
+ throw new TSqlMapConfigurationException(
+ 'sqlmap_unable_to_find_parent_parameter_map', $extendMap, $file);
+ }
+ $superMap = $this->_sqlMapper->getParameterMap($extendMap);
+ $index = 0;
+ foreach($superMap->getPropertyNames() as $propertyName)
+ {
+ $parameterMap->insertParameterProperty($index++,
+ $superMap->getProperty($propertyName));
+ }
+ }
+ $this->_sqlMapper->addParameterMap($parameterMap);
+ }
+
+ protected function loadInsertTag($node, $document, $file)
+ {
+ $insert = $this->_deserialize->insert($node, $this->_sqlMapper, $file);
+ if(!is_null($insert->getGenerate()))
+ {
+ var_dump("generate insert");
+ }
+ else
+ {
+ $this->processSqlStatement($insert, $document, $node, $file);
+ }
+
+ $mappedStatement = new TInsertMappedStatement($this->_sqlMapper, $insert);
+ $this->_sqlMapper->addMappedStatement($mappedStatement);
+ if(!is_null($insert->getSelectKey()))
+ {
+ $selectKey = $insert->getSelectKey();
+ $selectKey->setID($insert->getID());
+ $selectKey->initialize($this->_sqlMapper);
+ $selectKey->setID($insert->getID().'.SelectKey');
+ $this->processSqlStatement($selectKey,
+ $document, $node->selectKey, $file);
+ $mappedStatement = new TMappedStatement($this->_sqlMapper, $selectKey);
+ $this->_sqlMapper->addMappedStatement($mappedStatement);
+ }
+ }
+
+ protected function processSqlStatement($statement, $document, $node, $file)
+ {
+ $commandText = (string)$node;
+ if(strlen($extend = $statement->getExtends()) > 0)
+ {
+ $superNodes = $document->xpath("//*[@id='{$extend}']");
+ if(isset($superNodes[0]))
+ $commandText = (string)$superNodes[0] . $commandText;
+ else
+ throw TSqlMapConfigurationException(
+ 'sqlmap_unable_to_find_parent_sql', $extend, $file);
+ }
+
+ //$sql = new TStaticSql();
+ //$sql->buildPreparedStatement($statement, (string)$node);
+ $commandText = $this->_deserialize->replaceProperties($commandText);
+ $this->applyInlineParameterMap($statement, $commandText, $node, $file);
+ //$statement->setSql($sql);
+ }
+
+ protected function applyInlineParameterMap($statement, $sqlStatement, $node, $file)
+ {
+ if($statement->parameterMap() == null)
+ {
+ $scope['statement'] = $statement->getID();
+ $scope['file'] = $file;
+
+ // Build a Parametermap with the inline parameters.
+ // if they exist. Then delete inline infos from sqltext.
+ $parameterParser = new TInlineParameterMapParser;
+ $sqlText = $parameterParser->parseInlineParameterMap(
+ $this->_sqlMapper, $statement, $sqlStatement, $scope);
+ if(count($sqlText['parameters']) > 0)
+ {
+ $map = new TParameterMap();
+ $map->setID($statement->getID().'-InLineParameterMap');
+ $statement->setInlineParameterMap($map);
+ foreach($sqlText['parameters'] as $property)
+ $map->addParameterProperty($property);
+ }
+ $sqlStatement = $sqlText['sql'];
+ }
+ $sql = new TStaticSql();
+ $sql->buildPreparedStatement($statement, $sqlStatement);
+ $statement->setSql($sql);
+ }
+
+ protected function resolveResultMapping()
+ {
+ $maps = $this->_sqlMapper->getResultMaps();
+ foreach($maps as $entry)
+ {
+ foreach($entry->getColumns() as $item)
+ {
+ $resultMap = $item->getResultMapping();
+ if(strlen($resultMap) > 0)
+ {
+ if($maps->contains($resultMap))
+ $item->setNestedResultMap($maps[$resultMap]);
+ else
+ throw new TSqlMapConfigurationException(
+ 'sqlmap_unable_to_find_result_mapping',
+ $resultMap, $this->_configFile, $entry->getID());
+ }
+ }
+ if(!is_null($entry->getDiscriminator()))
+ {
+ $entry->getDiscriminator()->initialize($this->_sqlMapper);
+ }
+ }
+ }
+
+ protected function loadSelectTag($node, $document, $file)
+ {
+ $select = $this->_deserialize->select($node, $this->_sqlMapper, $file);
+ if(!is_null($select->getGenerate()))
+ {
+ var_dump("generate select");
+ }
+ else
+ {
+ $this->processSqlStatement($select, $document, $node, $file);
+ /*$sql = new TStaticSql();
+ $sql->buildPreparedStatement($select, (string)$node);
+ $select->setSql($sql);*/
+ }
+
+ $mappedStatement = new TMappedStatement($this->_sqlMapper, $select);
+ if($this->_sqlMapper->getIsCacheModelsEnabled() &&
+ strlen($select->getCacheModel()) > 0)
+ {
+ $mappedStatement = new TCachingStatement($mappedStatement);
+ }
+
+ $this->_sqlMapper->addMappedStatement($mappedStatement);
+ }
+
+ protected function loadResultMap($node,$document,$file)
+ {
+ $resultMap = $this->_deserialize->resultMap($node, $this->_sqlMapper,$file);
+ $extendMap = $resultMap->getExtends();
+ if(strlen($extendMap) > 0)
+ {
+ if(!$this->_sqlMapper->getResultMaps()->contains($extendMap))
+ {
+ $nodes = $document->xpath("//resultMap[@id='{$extendMap}']");
+ if(isset($nodes[0]))
+ $this->loadResultMap($nodes[0],$document,$file);
+ else
+ throw new TSqlMapConfigurationException(
+ 'sqlmap_unable_to_find_parent_result_map', $extendMap, $file);
+ }
+ $superMap = $this->_sqlMapper->getResultMap($extendMap);
+ $resultMap->getColumns()->mergeWith($superMap->getColumns());
+ }
+ if(!$this->_sqlMapper->getResultMaps()->contains($resultMap->getID()))
+ $this->_sqlMapper->addResultMap($resultMap);
+ }
+
+
+ protected function loadStatementTag($node, $document, $file)
+ {
+ $statement = $this->_deserialize->statement($node, $this->_sqlMapper, $file);
+
+ /*$sql = new TStaticSql();
+ $sql->buildPreparedStatement($statement, (string)$node);
+ $statement->setSql($sql);*/
+ $this->processSqlStatement($statement, $document, $node, $file);
+
+ $mappedStatement = new TMappedStatement($this->_sqlMapper, $statement);
+ $this->_sqlMapper->addMappedStatement($mappedStatement);
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TInlineParameterMapParser.php b/framework/DataAccess/SQLMap/Configuration/TInlineParameterMapParser.php new file mode 100644 index 00000000..8b1ecf04 --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TInlineParameterMapParser.php @@ -0,0 +1,50 @@ +<?php
+
+class TInlineParameterMapParser
+{
+ private $PARAMETER_TOKEN_REGEXP = '/#(#?[^#]+#?)#/';
+
+ public function parseInlineParameterMap($sqlMap, $statement, $sqlText, $scope)
+ {
+ $parameterClass = !is_null($statement)
+ ? $statement->getParameterClass() : null;
+ $matches = array();
+ $mappings = array();
+ preg_match_all($this->PARAMETER_TOKEN_REGEXP, $sqlText, $matches);
+
+ for($i = 0, $k=count($matches[1]); $i<$k; $i++)
+ {
+ $mappings[] = $this->parseMapping($matches[1][$i],
+ $parameterClass, $sqlMap, $scope);
+ $sqlText = str_replace($matches[0][$i], '?', $sqlText);
+ }
+ return array('sql'=>$sqlText, 'parameters'=>$mappings);
+ }
+
+ /**
+ * Parse inline parameter with syntax as
+ * #propertyName,type=string,dbype=Varchar,nullValue=N/A,handler=string#
+ */
+ protected function parseMapping($token, $parameterClass, $sqlMap, $scope)
+ {
+ $mapping = new TParameterProperty;
+ $properties = explode(',', $token);
+ $mapping->setProperty(trim(array_shift($properties)));
+ //var_dump($properties);
+ foreach($properties as $property)
+ {
+ $prop = explode('=',$property);
+ $name = trim($prop[0]); $value=trim($prop[1]);
+ if($mapping->canSetProperty($name))
+ $mapping->{'set'.$name}($value);
+ else
+ throw new TSqlMapUndefinedException(
+ 'sqlmap_undefined_property_inline_map',
+ $name, $scope['statement'], $scope['file']);
+ }
+ $mapping->initialize($sqlMap);
+ return $mapping;
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TParameterMap.php b/framework/DataAccess/SQLMap/Configuration/TParameterMap.php new file mode 100644 index 00000000..8e1c757d --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TParameterMap.php @@ -0,0 +1,95 @@ +<?php
+
+class TParameterMap extends TComponent
+{
+ private $_ID='';
+ private $_extend='';
+ private $_properties;
+ private $_propertyMap;
+ private $_extendMap;
+
+ public function __construct()
+ {
+ $this->_properties = new TList;
+ $this->_propertyMap = new TMap;
+ }
+
+ public function getProperties(){ return $this->_properties; }
+
+ public function getID(){ return $this->_ID; }
+ public function setID($value){ $this->_ID = $value; }
+
+ public function getExtends(){ return $this->_extend; }
+ public function setExtends($value){ $this->_extend = $value; }
+
+ public function getProperty($index)
+ {
+ if(is_string($index))
+ return $this->_propertyMap->itemAt($index);
+ else if(is_int($index))
+ return $this->_properties->itemAt($index);
+ else
+ throw new TDataMapperException(
+ 'sqlmap_index_must_be_string_or_int', $index);
+ }
+
+ public function addParameterProperty(TParameterProperty $property)
+ {
+ $this->_propertyMap->add($property->getProperty(), $property);
+ $this->_properties->add($property);
+ }
+
+ public function insertParameterProperty($index, TParameterProperty $property)
+ {
+ $this->_propertyMap->add($property->getProperty(), $property);
+ $this->_properties->insertAt($index, $property);
+ }
+
+ public function getPropertyNames()
+ {
+ return $this->_propertyMap->getKeys();
+ }
+
+ public function getParameter($mapping, $parameterValue, $statement)
+ {
+ $value = $parameterValue;
+ $typeHandler = $mapping->getTypeHandler();
+ try
+ {
+ $value = TPropertyAccess::get($parameterValue, $mapping->getProperty());
+ }
+ catch (TInvalidPropertyException $e)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_unable_to_get_property_for_parameter',$this->getID(),
+ $mapping->getProperty(), get_class($parameterValue),
+ $e->getMessage(), $statement->getID());
+ }
+
+ if(!is_null($typeHandler))
+ {
+ try
+ {
+ $value = $typeHandler->getParameter($value);
+ }
+ catch (Exception $e)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_error_in_parameter_from_handler',$this->getID(),
+ $value, get_class($typeHandler), $e->getMessage());
+ }
+ }
+
+ if(!is_null($nullValue = $mapping->getNullValue()))
+ {
+ if($nullValue === $value)
+ $value = null;
+ }
+
+ if(!is_null($type = $mapping->getType()))
+ $value = TTypeHandlerFactory::convertToType($type, $value);
+
+ return $value;
+ }
+}
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TParameterProperty.php b/framework/DataAccess/SQLMap/Configuration/TParameterProperty.php new file mode 100644 index 00000000..2ff55f73 --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TParameterProperty.php @@ -0,0 +1,68 @@ +<?php
+
+class TParameterProperty extends TComponent
+{
+ private $_typeHandler=null;
+ private $_type=null;
+ private $_column='';
+ private $_dbType='';
+ private $_property='';
+ private $_nullValue=null;
+ private $_typeHandlerFactory;
+
+ private $_size;
+ private $_precision;
+ private $_scale;
+ private $_direction;
+
+ public function getTypeHandler()
+ {
+ if(is_null($this->_typeHandlerFactory)) return null;
+ if(!is_null($this->_typeHandler))
+ return $this->_typeHandlerFactory->getTypeHandler($this->_typeHandler);
+ else if(!is_null($this->getType()))
+ return $this->_typeHandlerFactory->getTypeHandler($this->getType());
+ else
+ return null;
+ }
+ public function setTypeHandler($value){ $this->_typeHandler = $value; }
+
+ public function getType(){ return $this->_type; }
+ public function setType($value){ $this->_type = $value; }
+
+ public function getColumn(){ return $this->_column; }
+ public function setColumn($value){ $this->_column = $value; }
+
+ public function getDbType(){ return $this->_dbType; }
+ public function setDbType($value){ $this->_dbType = $value; }
+
+ public function getProperty(){ return $this->_property; }
+ public function setProperty($value){ $this->_property = $value; }
+
+ public function getNullValue(){ return $this->_nullValue; }
+ public function setNullValue($value){ $this->_nullValue = $value; }
+
+ public function getSize(){ return $this->_size; }
+ public function setSize($value){ $this->_size = $value; }
+
+ public function getPrecision(){ return $this->_precision; }
+ public function setPrecision($value){ $this->_precision = $value; }
+
+ public function getScale(){ return $this->_scale; }
+ public function setScale($value){ $this->_scale = $value; }
+
+
+ public function getDirection(){ return $this->_direction; }
+ public function setDirection($value){ $this->_direction = $value; }
+
+ public function initialize($sqlMap)
+ {
+ $this->_typeHandlerFactory = $sqlMap->getTypeHandlerFactory();
+ // $type = !is_null($this->_typeHandler) ? $this->_typeHandler: $this->_type;
+ // $this->setTypeHandler($sqlMap->getTypeHandlerFactory()->getTypeHandler($type));
+ // if(!is_null($type))
+ // var_dump($sqlMap->getTypeHandlerFactory());
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TResultMap.php b/framework/DataAccess/SQLMap/Configuration/TResultMap.php new file mode 100644 index 00000000..0f09a1ba --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TResultMap.php @@ -0,0 +1,63 @@ +<?php
+
+class TResultMap extends TComponent
+{
+ private $_ID='';
+ private $_className='';
+ private $_columns='';
+ private $_extendMap='';
+ private $_groupBy='';
+ private $_discriminator=null;
+
+ public function __construct()
+ {
+ $this->_columns = new TMap;
+ }
+
+ public function getID(){ return $this->_ID; }
+ public function setID($value){ $this->_ID = $value; }
+
+ public function getClass(){ return $this->_className; }
+ public function setClass($value){ $this->_className = $value; }
+
+ public function getColumns(){ return $this->_columns; }
+ public function setColumns($value){ $this->_columns = $value; }
+
+ public function getExtends(){ return $this->_extendMap; }
+ public function setExtends($value){ $this->_extendMap = $value; }
+
+ public function getGroupBy(){ return $this->_groupBy; }
+ public function setGroupBy($value){ $this->_groupBy = $value; }
+
+ public function getDiscriminator(){ return $this->_discriminator; }
+ public function setDiscriminator($value){ $this->_discriminator = $value; }
+
+ public function addResultProperty(TResultProperty $property)
+ {
+ $this->_columns->add($property->getProperty(), $property);
+ }
+
+ public function createInstanceOfResult()
+ {
+ return TTypeHandlerFactory::createInstanceOf($this->getClass());
+ }
+
+ public function resolveSubMap($row)
+ {
+ $subMap = $this;
+ if(!is_null($disc = $this->getDiscriminator()))
+ {
+ $mapping = $disc->getMapping();
+ $dataValue = $mapping->getDatabaseValue($row);
+ $subMap = $disc->getSubMap((string)$dataValue);
+
+ if(is_null($subMap))
+ $subMap = $this;
+ else if($subMap !== $this)
+ $subMap = $subMap->resolveSubMap($row);
+ }
+ return $subMap;
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TResultProperty.php b/framework/DataAccess/SQLMap/Configuration/TResultProperty.php new file mode 100644 index 00000000..9cc0df3a --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TResultProperty.php @@ -0,0 +1,164 @@ +<?php
+
+class TResultProperty extends TComponent
+{
+ private $_nullValue=null;
+ private $_propertyName='';
+ private $_columnName='';
+ private $_columnIndex=-1;
+ private $_nestedResultMapName='';
+ private $_nestedResultMap=null;
+ private $_valueType=null;
+ private $_typeHandler=null;
+ private $_isLazyLoad=false;
+ private $_select='';
+ private $_dbType='';
+ private $_typeHandlerFactory;
+ private $_hostResultMapID='inplicit internal mapping';
+
+ const LIST_TYPE = 0;
+ const ARRAY_TYPE = 1;
+ const OBJECT_TYPE = 2;
+
+ public function getNullValue(){ return $this->_nullValue; }
+ public function setNullValue($value){ $this->_nullValue = $value; }
+
+ public function getProperty(){ return $this->_propertyName; }
+ public function setProperty($value){ $this->_propertyName = $value; }
+
+ public function getColumn(){ return $this->_columnName; }
+ public function setColumn($value){ $this->_columnName = $value; }
+
+ public function getColumnIndex(){ return $this->_columnIndex; }
+ public function setColumnIndex($value){ $this->_columnIndex = TPropertyValue::ensureInteger($value,-1); }
+
+ public function getResultMapping(){ return $this->_nestedResultMapName; }
+ public function setResultMapping($value){ $this->_nestedResultMapName = $value; }
+
+ public function getNestedResultMap(){ return $this->_nestedResultMap; }
+ public function setNestedResultMap($value){ $this->_nestedResultMap = $value; }
+
+ public function getType(){ return $this->_valueType; }
+ public function setType($value) { $this->_valueType = $value; }
+
+ public function getTypeHandler()
+ {
+ if(is_null($this->_typeHandlerFactory)) return null;
+ if(!is_null($this->_typeHandler))
+ return $this->_typeHandlerFactory->getTypeHandler($this->_typeHandler);
+ else if(!is_null($this->getType()))
+ return $this->_typeHandlerFactory->getTypeHandler($this->getType());
+ else
+ return null;
+ }
+ public function setTypeHandler($value) { $this->_typeHandler = $value; }
+
+ public function getSelect(){ return $this->_select; }
+ public function setSelect($value){ $this->_select = $value; }
+
+ public function getLazyLoad(){ return $this->_isLazyLoad; }
+ public function setLazyLoad($value){ $this->_isLazyLoad = TPropertyValue::ensureBoolean($value,false); }
+
+ public function getDbType(){ return $this->_dbType; }
+ public function setDbType($value){ $this->_dbType = $value; }
+
+ public function initialize($sqlMap, $resultMap=null)
+ {
+ $this->_typeHandlerFactory = $sqlMap->getTypeHandlerFactory();
+ if(!is_null($resultMap))
+ $this->_hostResultMapID = $resultMap->getID();
+// $type = !is_null($this->_typeHandler) ? $this->_typeHandler: $this->_valueType;
+// $this->setTypeHandler($sqlMap->getTypeHandlerFactory()->getTypeHandler($type));
+ }
+
+ public function getDatabaseValue($row,$forceType=true)
+ {
+ $value = null;
+ if($this->_columnIndex > 0 && isset($row[$this->_columnIndex]))
+ $value = $this->getTypedValue($row[$this->_columnIndex], $forceType);
+ else if(isset($row[$this->_columnName]))
+ $value = $this->getTypedValue($row[$this->_columnName],$forceType);
+ if(is_null($value) && !is_null($this->_nullValue))
+ $value = $this->getTypedValue($this->_nullValue,$forceType);
+ return $value;
+ }
+
+ public function getOrdinalValue($row)
+ {
+ return $this->getDatabaseValue($row,false);
+ }
+
+ private function getTypedValue($value, $forceType=true)
+ {
+ if(!$forceType) return $value;
+ if(is_null($this->getTypeHandler()))
+ {
+ return TTypeHandlerFactory::convertToType($this->_valueType, $value);
+ }
+ else
+ {
+ try
+ {
+ return $this->getTypeHandler()->getResult($value);
+ }
+ catch (Exception $e)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_error_in_result_property_from_handler',$this->_hostResultMapID,
+ $value, get_class($this->getTypeHandler()), $e->getMessage());
+ }
+ }
+ }
+
+
+ public function getPropertyType($type=null)
+ {
+ if(is_null($type))
+ $type = $this->getType();
+ if(class_exists($type, false)) //NO force autoloading
+ {
+ $class = new ReflectionClass($type);
+ if($class->isSubclassOf('TList'))
+ return self::LIST_TYPE;
+ if($class->inmplementsInterface('ArrayAccess'))
+ return self::ARRAY_TYPE;
+ }
+ if(strtolower($type) == 'array')
+ return self::ARRAY_TYPE;
+ return self::OBJECT_TYPE;
+ }
+
+ public function isListType($target)
+ {
+ if(is_null($this->getType()))
+ {
+ $prop = TPropertyAccess::get($target,$this->getProperty());
+ return $prop instanceof TList;
+ }
+ return $this->getPropertyType() == self::LIST_TYPE;
+ }
+
+ public function isArrayType($target)
+ {
+ if(is_null($this->getType()))
+ {
+ $prop = TPropertyAccess::get($target,$this->getProperty());
+ if(is_object($prop))
+ return $prop instanceof ArrayAccess;
+ return is_array($prop);
+ }
+ return $this->getPropertyType() == self::ARRAY_TYPE;
+ }
+
+ public function isObjectType($target)
+ {
+ if(is_null($this->getType()))
+ {
+ $prop = TPropertyAccess::get($target,$this->getProperty());
+ return is_object($prop);
+ }
+ return $this->getPropertyType() == self::OBJECT_TYPE;
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TSqlMapCacheModel.php b/framework/DataAccess/SQLMap/Configuration/TSqlMapCacheModel.php new file mode 100644 index 00000000..6c945155 --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TSqlMapCacheModel.php @@ -0,0 +1,131 @@ +<?php
+
+class TSqlMapCacheModel extends TComponent
+{
+ private $_cache;
+ private $_flushInterval = -1;
+ private $_hits = 0;
+ private $_requests = 0;
+ private $_id;
+ private $_lastFlush;
+ private $_implementation;
+ private $_properties = array();
+
+
+ public function getID(){ return $this->_id; }
+ public function setID($value){ $this->_id = $value; }
+
+ public function getImplementation(){ return $this->_implementation; }
+ public function setImplementation($value){ $this->_implementation = $value; }
+
+ public function getFlushInterval(){ return $this->_flushInterval; }
+ public function setFlushInterval($value){ $this->_flushInterval = $value; }
+
+ public function initialize($sqlMap)
+ {
+ $implementation = $this->getImplementationClass(
+ $sqlMap->getTypeHandlerFactory());
+ $this->_cache = new $implementation;
+ $this->_cache->configure($this->_properties);
+ }
+
+ protected function getImplementationClass($typeFactory)
+ {
+ switch(strtolower($this->_implementation))
+ {
+ case 'fifo': return 'TSqlMapFifoCache';
+ case 'lru' : return 'TSqlMapLruCache';
+ }
+
+ if(class_exists($this->_implementation, false))
+ $class = $this->_implementation;
+ else
+ $class = $typeFactory->getTypeHandler($this->_implementation);
+ if(!is_null($class))
+ return $class;
+ else
+ throw new TSqlMapConfigurationException(
+ 'sqlmap_unable_to_find_implemenation', $this->_implementation);
+ }
+
+ public function addProperty($name, $value)
+ {
+ $this->_properties[strtolower($name)] = $value;
+ }
+
+ public function registerTriggerStatement($mappedStatement)
+ {
+ $mappedStatement->attachEventHandler('OnExecuteQuery',
+ array($this, 'flushHandler'));
+ }
+
+ protected function flushHandler($sender, $param)
+ {
+ $this->flush();
+ }
+
+ public function flush()
+ {
+ var_dump("flush!");
+ $this->_cache->flush();
+ }
+
+ public function get($key)
+ {
+ if($key instanceof TSqlMapCacheKey)
+ $key = $key->getHash();
+
+ //if flush ?
+ $value = $this->_cache->get($key);
+ $this->_requests++;
+ if(!is_null($value))
+ $this->_hits++;
+ return $value;
+ }
+
+ public function set($key, $value)
+ {
+ if($key instanceof TSqlMapCacheKey)
+ $key = $key->getHash();
+
+ if(!is_null($value))
+ $this->_cache->set($key, $value);
+ }
+
+ public function getHitRatio()
+ {
+ if($this->_requests != 0)
+ return $this->_hits / $this->_requests;
+ else
+ return 0;
+ }
+}
+
+
+class TSqlMapCacheKey
+{
+ private $_key;
+
+ public function __construct($object)
+ {
+ $this->_key = $this->generateKey(serialize($object));
+ }
+
+ protected function generateKey($string)
+ {
+ return sprintf('%x',crc32($string));
+ }
+
+ public function getHash()
+ {
+ return $this->_key;
+ }
+
+ public function __toString()
+ {
+ return $this->getHash();
+ }
+}
+
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TSqlMapDelete.php b/framework/DataAccess/SQLMap/Configuration/TSqlMapDelete.php new file mode 100644 index 00000000..9ca3843a --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TSqlMapDelete.php @@ -0,0 +1,7 @@ +<?php
+
+class TSqlMapDelete extends TSqlMapUpdate
+{
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TSqlMapInsert.php b/framework/DataAccess/SQLMap/Configuration/TSqlMapInsert.php new file mode 100644 index 00000000..c8d798c5 --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TSqlMapInsert.php @@ -0,0 +1,15 @@ +<?php
+
+class TSqlMapInsert extends TSqlMapStatement
+{
+ private $_selectKey=null;
+ private $_generate=null;
+
+ public function getSelectKey(){ return $this->_selectKey; }
+ public function setSelectKey($value){ $this->_selectKey = $value; }
+
+ public function getGenerate(){ return $this->_generate; }
+ public function setGenerate($value){ $this->_generate = $value; }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TSqlMapSelect.php b/framework/DataAccess/SQLMap/Configuration/TSqlMapSelect.php new file mode 100644 index 00000000..b33bc27e --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TSqlMapSelect.php @@ -0,0 +1,11 @@ +<?php
+
+class TSqlMapSelect extends TSqlMapStatement
+{
+ private $_generate;
+
+ public function getGenerate(){ return $this->_generate; }
+ public function setGenerate($value){ $this->_generate = $value; }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TSqlMapSelectKey.php b/framework/DataAccess/SQLMap/Configuration/TSqlMapSelectKey.php new file mode 100644 index 00000000..b73d4ef0 --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TSqlMapSelectKey.php @@ -0,0 +1,26 @@ +<?php
+
+class TSqlMapSelectKey extends TSqlMapStatement
+{
+ private $_type = 'post';
+ private $_property = '';
+
+ public function getType(){ return $this->_type; }
+ public function setType($value){ $this->_type = $value; }
+
+ public function getProperty(){ return $this->_property; }
+ public function setProperty($value){ $this->_property = $value; }
+
+ public function setExtends($value)
+ {
+ throw new TSqlMapConfigurationException(
+ 'sqlmap_can_not_extend_select_key');
+ }
+
+ public function getIsAfter()
+ {
+ return $this->_type == 'post';
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TSqlMapStatement.php b/framework/DataAccess/SQLMap/Configuration/TSqlMapStatement.php new file mode 100644 index 00000000..228b37d6 --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TSqlMapStatement.php @@ -0,0 +1,109 @@ +<?php
+
+class TSqlMapStatement extends TComponent
+{
+ private $_ID='';
+ private $_parameterMapName='';
+ private $_parameterMap;
+ private $_parameterClassName='';
+// private $_parameterClass;
+ private $_resultMapName='';
+ private $_resultMap;
+ private $_resultClassName='';
+// private $_resultClass;
+ private $_cacheModelName='';
+ private $_remapResults=false;
+ private $_SQL='';
+ private $_listClass='';
+ private $_typeHandler;
+ private $_extendStatement='';
+ private $_cache;
+
+ public function getID(){ return $this->_ID; }
+ public function setID($value){ $this->_ID = $value; }
+
+ public function getParameterMap(){ return $this->_parameterMapName; }
+ public function setParameterMap($value){ $this->_parameterMapName = $value; }
+
+ public function getParameterClass(){ return $this->_parameterClassName; }
+ public function setParameterClass($value){ $this->_parameterClassName = $value; }
+
+ public function getResultMap(){ return $this->_resultMapName; }
+ public function setResultMap($value){ $this->_resultMapName = $value; }
+
+ public function getResultClass(){ return $this->_resultClassName; }
+ public function setResultClass($value){ $this->_resultClassName = $value; }
+
+ public function getCacheModel(){ return $this->_cacheModelName; }
+ public function setCacheModel($value){ $this->_cacheModelName = $value; }
+
+ public function getCache(){ return $this->_cache; }
+ public function setCache($value){ $this->_cache = $value; }
+
+ public function getRemapResults(){ return $this->_remapResults; }
+ public function setRemapResults($value){ $this->_remapResults = TPropertyValue::ensureBoolean($value,false); }
+
+ public function getSQL(){ return $this->_SQL; }
+ public function setSQL($value){ $this->_SQL = $value; }
+
+ public function getListClass(){ return $this->_listClass; }
+ public function setListClass($value){ $this->_listClass = $value; }
+
+ public function getExtends(){ return $this->_extendStatement; }
+ public function setExtends($value){ $this->_extendStatement = $value; }
+
+ public function resultMap(){ return $this->_resultMap; }
+ public function parameterMap(){ return $this->_parameterMap; }
+
+ public function setInlineParameterMap($map)
+ {
+ $this->_parameterMap = $map;
+ }
+// public function parameterClass(){ return $this->_parameterClass; }
+// public function resultClass(){ return $this->_resultClass; }
+
+ public function initialize($sqlMap)
+ {
+ $this->_typeHandler = $sqlMap->getTypeHandlerFactory();
+ if(strlen($this->_resultMapName) > 0)
+ $this->_resultMap = $sqlMap->getResultMap($this->_resultMapName);
+ if(strlen($this->_parameterMapName) > 0)
+ $this->_parameterMap = $sqlMap->getParameterMap($this->_parameterMapName);
+ }
+
+
+ public function createInstanceOfListClass()
+ {
+ if(strlen($type = $this->getListClass()) > 0)
+ return $this->createInstanceOf($type);
+ return array(); //new TList;
+ }
+
+ protected function createInstanceOf($type)
+ {
+ $handler = $this->_typeHandler->getTypeHandler($type);
+
+ try
+ {
+ if(!is_null($handler))
+ return $handler->createNewInstance();
+ else
+ return TTypeHandlerFactory::createInstanceOf($type);
+ }
+ catch (TDataMapperException $e)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_unable_to_create_new_instance',
+ $type, get_class($handler), $this->getID());
+ }
+
+ }
+
+ public function createInstanceOfResultClass()
+ {
+ if(strlen($type= $this->getResultClass()) > 0)
+ return $this->createInstanceOf($type);
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Configuration/TSqlMapUpdate.php b/framework/DataAccess/SQLMap/Configuration/TSqlMapUpdate.php new file mode 100644 index 00000000..26d00e39 --- /dev/null +++ b/framework/DataAccess/SQLMap/Configuration/TSqlMapUpdate.php @@ -0,0 +1,11 @@ +<?php
+
+class TSqlMapUpdate extends TSqlMapStatement
+{
+ private $_generate;
+
+ public function getGenerate(){ return $this->_generate; }
+ public function setGenerate($value){ $this->_generate = $value; }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/DataMapper/TDataMapperException.php b/framework/DataAccess/SQLMap/DataMapper/TDataMapperException.php new file mode 100644 index 00000000..7cb74244 --- /dev/null +++ b/framework/DataAccess/SQLMap/DataMapper/TDataMapperException.php @@ -0,0 +1,65 @@ +<?php
+
+class TDataMapperException extends TException
+{
+ /**
+ * @return string path to the error message file
+ */
+ protected function getErrorMessageFile()
+ {
+ $lang=Prado::getPreferredLanguage();
+ $msgFile=Prado::getFrameworkPath().'/DataAccess/SQLMap/DataMapper/messages-'.$lang.'.txt';
+ if(!is_file($msgFile))
+ $msgFile=Prado::getFrameworkPath().'/DataAccess/SQLMap/DataMapper/messages.txt';
+ return $msgFile;
+ }
+}
+
+class TSqlMapConfigurationException extends TDataMapperException
+{
+
+}
+
+class TUndefinedAttributeException extends TSqlMapConfigurationException
+{
+ public function __construct($attr, $node, $object, $file)
+ {
+ parent::__construct(
+ 'sqlmap_undefined_attribute', get_class($object), $attr,
+ htmlentities($node->asXml()),$file);
+ }
+}
+
+class TSqlMapExecutionException extends TDataMapperException
+{
+}
+
+class TSqlMapQueryExecutionException extends TSqlMapExecutionException
+{
+ protected $parent;
+ public function __construct($statement, $exception)
+ {
+ $this->parent = $exception;
+ parent::__construct('sqlmap_query_execution_error',
+ $statement->getID(), $exception->getMessage());
+ }
+}
+
+class TSqlMapUndefinedException extends TDataMapperException
+{
+
+}
+
+class TSqlMapDuplicateException extends TDataMapperException
+{
+}
+
+class TSqlMapConnectionException extends TDataMapperException
+{
+}
+
+class TInvalidPropertyException extends TDataMapperException
+{
+
+}
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/DataMapper/TLazyLoadList.php b/framework/DataAccess/SQLMap/DataMapper/TLazyLoadList.php new file mode 100644 index 00000000..465dcaac --- /dev/null +++ b/framework/DataAccess/SQLMap/DataMapper/TLazyLoadList.php @@ -0,0 +1,87 @@ +<?php
+
+class TLazyLoadList implements IInterceptor
+{
+ private $_param;
+ private $_target;
+ private $_propertyName='';
+ private $_sqlMap;
+ private $_statementName='';
+ private $_loaded=false;
+ private $_innerList;
+
+ protected function __construct($mappedStatement, $param, $target, $propertyName)
+ {
+ $this->_param = $param;
+ $this->_target = $target;
+ $this->_statementName = $mappedStatement->getID();
+ $this->_sqlMap = $mappedStatement->getSqlMap();
+ $this->_propertyName = $propertyName;
+ }
+
+ public static function newInstance($mappedStatement, $param, $target, $propertyName)
+ {
+ $handler = new self($mappedStatement, $param, $target, $propertyName);
+ $statement = $mappedStatement->getStatement();
+ $list = $statement->createInstanceOfListClass();
+ if(!is_object($list))
+ throw new TSqlMapExecutionException('sqlmap_invalid_lazyload_list',
+ $statement->getID());
+ return new TObjectProxy($handler, $list);
+ }
+
+ public function intercept($method, $arguments)
+ {
+ return call_user_func_array(array($this->_innerList, $method), $arguments);
+ }
+
+ protected function fetchListData()
+ {
+
+ if($this->_loaded == false)
+ {
+ $this->_innerList = $this->_sqlMap->queryForList(
+ $this->_statementName, $this->_param);
+ $this->_loaded = true;
+ //replace the target property with real list
+ TPropertyAccess::set($this->_target,
+ $this->_propertyName, $this->_innerList);
+ }
+ }
+
+ public function hasMethod($method)
+ {
+ $this->fetchListData();
+ if(is_object($this->_innerList))
+ return in_array($method, get_class_methods($this->_innerList));
+ return false;
+ }
+}
+
+interface IInterceptor
+{
+ public function intercept($method, $params);
+ public function hasMethod($method);
+}
+
+class TObjectProxy
+{
+ private $_object;
+ private $_handler;
+
+ public function __construct(IInterceptor $handler, $object)
+ {
+ $this->_handler = $handler;
+ $this->_object = $object;
+ }
+
+ public function __call($method,$params)
+ {
+ if($this->_handler->hasMethod($method))
+ return $this->_handler->intercept($method, $params);
+ else
+ return call_user_func_array(array($this->_object, $method), $params);
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/DataMapper/TPropertyAccess.php b/framework/DataAccess/SQLMap/DataMapper/TPropertyAccess.php new file mode 100644 index 00000000..8680601e --- /dev/null +++ b/framework/DataAccess/SQLMap/DataMapper/TPropertyAccess.php @@ -0,0 +1,91 @@ +<?php
+
+class TPropertyAccess
+{
+ private $_obj;
+ private $_performance=false;
+
+ public function __construct($obj,$performance=false)
+ {
+ $this->_obj = $obj;
+ $this->_performance=$performance;
+ }
+
+ public function __get($name)
+ {
+ return self::get($this->_obj,$name,$this->_performance);
+ }
+
+ public function __set($name,$value)
+ {
+ self::set($this->_obj,$name,$value,$this->_performance);
+ }
+
+ /**
+ * Evaluates the data value at the specified field.
+ * - If the data is an array, then the field is treated as an array index
+ * and the corresponding element value is returned;
+ * - If the data is a TMap or TList object, then the field is treated as a key
+ * into the map or list, and the corresponding value is returned.
+ * - If the data is an object, the field is treated as a property or subproperty
+ * defined with getter methods. For example, if the object has a method called
+ * getMyValue(), then field 'MyValue' will retrive the result of this method call.
+ * If getMyValue() returns an object which contains a method getMySubValue(),
+ * then field 'MyValue.MySubValue' will return that method call result.
+ * @param mixed data containing the field value, can be an array, TMap, TList or object.
+ * @param mixed field value
+ * @return mixed value at the specified field
+ * @throw TInvalidDataValueException if field or data is invalid
+ */
+ public static function get($object,$path)
+ {
+ if(!is_array($object) && !is_object($object))
+ return $object;
+ $properties = explode('.', $path);
+ foreach($properties as $prop)
+ {
+ if(is_array($object) || $object instanceof ArrayAccess)
+ {
+ if(isset($object[$prop]))
+ $object = $object[$prop];
+ else
+ throw new TInvalidPropertyException('sqlmap_invalid_property',$path);
+ }
+ else if(is_object($object))
+ {
+ $getter = 'get'.$prop;
+ if(is_callable(array($object,$getter)))
+ $object = $object->{$getter}();
+ else if(in_array($prop, array_keys(get_object_vars($object))))
+ $object = $object->{$prop};
+ else
+ throw new TInvalidPropertyException('sqlmap_invalid_property',$path);
+ }
+ else
+ throw new TInvalidPropertyException('sqlmap_invalid_property',$path);
+ }
+ return $object;
+ }
+
+
+ public static function set($object, $path, $value)
+ {
+ $properties = explode('.', $path);
+ $prop = array_pop($properties);
+ if(count($properties) > 0)
+ $object = self::get($object, implode('.',$properties));
+ if(is_object($object))
+ {
+ $setter = 'set'.$prop;
+ if(is_callable(array($object, $setter)))
+ $object->{$setter}($value);
+ else
+ $object->{$prop} = $value;
+ }
+ else
+ throw new TInvalidPropertyException('sqlmap_invalid_property_type',$path);
+ }
+
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/DataMapper/TSqlMapCache.php b/framework/DataAccess/SQLMap/DataMapper/TSqlMapCache.php new file mode 100644 index 00000000..8571d46d --- /dev/null +++ b/framework/DataAccess/SQLMap/DataMapper/TSqlMapCache.php @@ -0,0 +1,165 @@ +<?php
+/**
+ * TSqlMapCache class file contains FIFO and LRU cache implementations.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright © 2005 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $Date: $
+ * @package System.DataAccess.SQLMap
+ */
+
+interface ISqLMapCache
+{
+ public function remove($key);
+
+ public function flush();
+
+ public function get($key);
+
+ public function set($key, $value);
+
+ public function configure($properties);
+}
+
+/**
+ * Allow different implementation of caching strategy. See <tt>TSqlMapFifoCache</tt>
+ * for a first-in-first-out implementation. See <tt>TSqlMapLruCache</tt> for
+ * a least-recently-used cache implementation.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $Date: $
+ * @package System.DataAccess.SQLMap
+ * @since 3.0
+ */
+abstract class TSqlMapCache implements ISqlMapCache
+{
+ protected $_keyList;
+ protected $_cache;
+ protected $_cacheSize = 100;
+
+ /**
+ * Create a new cache with limited cache size.
+ * @param integer maxium number of items to cache.
+ */
+ public function __construct($cacheSize=100)
+ {
+ $this->_cache = new TMap;
+ $this->_cacheSize = intval($cacheSize);
+ $this->_keyList = new TList;
+ }
+
+ /**
+ * Configures the Cache Size.
+ * @param array list of properties
+ */
+ public function configure($properties)
+ {
+ if(isset($properties['size']))
+ $this->_cacheSize = intval($properties['size']);
+ }
+
+ /**
+ * @return object the object removed if exists, null otherwise.
+ */
+ public function remove($key)
+ {
+ $object = $this->get($key);
+ $this->_cache->remove($key);
+ $this->_keyList->remove($key);
+ return $object;
+ }
+
+ /**
+ * Clears the cache.
+ */
+ public function flush()
+ {
+ $this->_keyList->clear();
+ $this->_cache->clear();
+ }
+
+}
+
+/**
+ * First-in-First-out cache implementation, removes
+ * object that was first added when the cache is full.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $Date: $
+ * @package System.DataAccess.SQLMap
+ * @since 3.0
+ */
+class TSqlMapFifoCache extends TSqlMapCache
+{
+ /**
+ * @return mixed Gets a cached object with the specified key.
+ */
+ public function get($key)
+ {
+ return $this->_cache->itemAt($key);
+ }
+
+ /**
+ * Adds an item with the specified key and value into cached data.
+ * @param string cache key
+ * @param mixed value to cache.
+ */
+ public function set($key, $value)
+ {
+ $this->_cache->add($key, $value);
+ $this->_keyList->add($key);
+ if($this->_keyList->getCount() > $this->_cacheSize)
+ {
+ $oldestKey = $this->_keyList->removeAt(0);
+ $this->_cache->remove($oldestKey);
+ }
+ }
+}
+
+/**
+ * Least recently used cache implementation, removes
+ * object that was accessed last when the cache is full.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $Date: $
+ * @package System.DataAccess.SQLMap
+ * @since 3.0
+ */
+class TSqlMapLruCache extends TSqlMapCache
+{
+ /**
+ * @return mixed Gets a cached object with the specified key.
+ */
+ public function get($key)
+ {
+ if($this->_keyList->contains($key))
+ {
+ $this->_keyList->remove($key);
+ $this->_keyList->add($key);
+ return $this->_cache->itemAt($key);
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Adds an item with the specified key and value into cached data.
+ * @param string cache key
+ * @param mixed value to cache.
+ */
+ public function set($key, $value)
+ {
+ $this->_cache->add($key, $value);
+ $this->_keyList->add($key);
+ if($this->_keyList->getCount() > $this->_cacheSize)
+ {
+ $oldestKey = $this->_keyList->removeAt(0);
+ $this->_cache->remove($oldestKey);
+ }
+ }
+}
+
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/DataMapper/TSqlMapPagedList.php b/framework/DataAccess/SQLMap/DataMapper/TSqlMapPagedList.php new file mode 100644 index 00000000..cded4b32 --- /dev/null +++ b/framework/DataAccess/SQLMap/DataMapper/TSqlMapPagedList.php @@ -0,0 +1,156 @@ +<?php
+
+/**
+ * TSQLMapPagedList
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TSqlMapPagedList extends TPagedList
+{
+ private $_statement;
+ private $_parameter;
+ private $_prevPageList;
+ private $_nextPageList;
+ private $_delegate=null;
+
+ public function __construct(IMappedStatement $statement,
+ $parameter, $pageSize, $delegate=null)
+ {
+ parent::__construct();
+ parent::setCustomPaging(true);
+ $this->initialize($statement,$parameter, $pageSize);
+ $this->_delegate=$delegate;
+ }
+
+ protected function initialize($statement, $parameter, $pageSize)
+ {
+ $this->_statement = $statement;
+ $this->_parameter = $parameter;
+ $this->setPageSize($pageSize);
+ $this->attachEventHandler('OnFetchData', array($this, 'fetchDataFromStatement'));
+ $this->gotoPage(0);
+ }
+
+ public function setCustomPaging($value)
+ {
+ throw new TDataMapperException('sqlmap_must_enable_custom_paging');
+ }
+
+ protected function fetchDataFromStatement($sender, $param)
+ {
+ $limit = $this->getOffsetAndLimit($param);
+ $connection = $this->_statement->getSqlMap()->openConnection();
+ $data = $this->_statement->executeQueryForList($connection,
+ $this->_parameter, null, $limit[0], $limit[1], $this->_delegate);
+ $this->populateData($param, $data);
+ }
+
+ public function nextPage()
+ {
+ if($this->getIsNextPageAvailable())
+ return parent::nextPage();
+ else
+ return false;
+ }
+
+ public function previousPage()
+ {
+ if($this->getIsPreviousPageAvailable())
+ return parent::previousPage();
+ else
+ return false;
+ }
+
+ protected function populateData($param, $data)
+ {
+ $total = $data instanceof TList ? $data->getCount() : count($data);
+ $pageSize = $this->getPageSize();
+ if($total < 1)
+ {
+ $param->setData($data);
+ $this->_prevPageList = null;
+ $this->_nextPageList = null;
+ return;
+ }
+
+ if($param->getNewPageIndex() < 1)
+ {
+ $this->_prevPageList = null;
+ if($total <= $pageSize)
+ {
+ $param->setData($data);
+ $this->_nextPageList = null;
+ }
+ else
+ {
+ $param->setData($this->sublist($data, 0, $pageSize));
+ $this->_nextPageList = $this->sublist($data, $pageSize,$total);
+ }
+ }
+ else
+ {
+ if($total <= $pageSize)
+ {
+ $this->_prevPageList = $this->sublist($data, 0, $total);
+ $param->setData(array());
+ $this->_nextPageList = null;
+ }
+ else if($total <= $pageSize*2)
+ {
+ $this->_prevPageList = $this->sublist($data, 0, $pageSize);
+ $param->setData($this->sublist($data, $pageSize, $total));
+ $this->_nextPageList = null;
+ }
+ else
+ {
+ $this->_prevPageList = $this->sublist($data, 0, $pageSize);
+ $param->setData($this->sublist($data, $pageSize, $pageSize*2));
+ $this->_nextPageList = $this->sublist($data, $pageSize*2, $total);
+ }
+ }
+ }
+
+ protected function sublist($data, $from, $to)
+ {
+ $array = array();
+ for($i = $from; $i<$to; $i++)
+ $array[] = $data[$i];
+ return $array;
+ }
+
+ protected function getOffsetAndLimit($param)
+ {
+ $index = $param->getNewPageIndex();
+ $pageSize = $this->getPageSize();
+ if($index < 1)
+ return array($index, $pageSize*2);
+ else
+ return array(($index-1)*$pageSize, $pageSize*3);
+ }
+
+ public function getIsNextPageAvailable()
+ {
+ return !is_null($this->_nextPageList);
+ }
+
+ public function getIsPreviousPageAvailable()
+ {
+ return !is_null($this->_prevPageList);
+ }
+
+ public function getIsLastPage()
+ {
+ return is_null($this->_nextPageList)
+ || $this->_nextPageList->getCount() < 1;
+ }
+
+ public function getIsMiddlePage()
+ {
+ return !($this->getIsFirstPage() || $this->getIsLastPage());
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/DataMapper/TTypeHandlerFactory.php b/framework/DataAccess/SQLMap/DataMapper/TTypeHandlerFactory.php new file mode 100644 index 00000000..fcadea28 --- /dev/null +++ b/framework/DataAccess/SQLMap/DataMapper/TTypeHandlerFactory.php @@ -0,0 +1,134 @@ +<?php
+
+class TTypeHandlerFactory
+{
+ private $_typeHandlerMap;
+
+ const NullDbType = '__NULL__';
+
+ public function __construct()
+ {
+ $this->_typeHandlerMap = new TMap;
+ }
+
+ public function getTypeHandler($type, $dbType=null)
+ {
+ $dbTypeHandlerMap = $this->_typeHandlerMap[$type];
+ $handler = null;
+ if(!is_null($dbTypeHandlerMap))
+ {
+ if(is_null($dbType))
+ $handler = $dbTypeHandlerMap[self::NullDbType];
+ else
+ {
+ $handler = $dbTypeHandlerMap[$dbType];
+ if(is_null($handler))
+ $handler = $dbTypeHandlerMap[self::NullDbType];
+ }
+ }
+ return $handler;
+ }
+
+ public function register($type, $handler, $dbType=null)
+ {
+ $map = $this->_typeHandlerMap[$type];
+ if(is_null($map))
+ {
+ $map = new TMap;
+ $this->_typeHandlerMap->add($type, $map);
+ }
+ if(is_null($dbType))
+ $map->add(self::NullDbType, $handler);
+ else
+ $map->add($dbType, $handler);
+ }
+
+ public static function createInstanceOf($type)
+ {
+ if(strlen($type) > 0)
+ {
+ switch(strtolower($type))
+ {
+ case 'string': return '';
+ case 'array': return array();
+ case 'float': case 'double': case 'decimal': return 0.0;
+ case 'integer': case 'int': return 0;
+ case 'bool': case 'boolean': return false;
+ }
+
+ if(class_exists($type, false)) //NO auto loading
+ return new $type;
+ else
+ throw new TDataMapperException('sqlmap_unable_to_find_class', $type);
+ }
+ return null;
+ }
+
+ public static function convertToType($type, $value)
+ {
+ switch(strtolower($type))
+ {
+ case 'integer': case 'int':
+ $type = 'integer'; break;
+ case 'float': case 'double': case 'decimal':
+ $type = 'float'; break;
+ case 'boolean': case 'bool':
+ $type = 'boolean'; break;
+ case 'string' :
+ $type = 'string'; break;
+ default:
+ return $value;
+ }
+ settype($value, $type);
+ return $value;
+ }
+}
+
+/**
+ * A simple interface for implementing custom type handlers.
+ *
+ * Using this interface, you can implement a type handler that
+ * will perform customized processing before parameters are set
+ * on and after values are retrieved from the database.
+ * Using a custom type handler you can extend
+ * the framework to handle types that are not supported, or
+ * handle supported types in a different way. For example,
+ * you might use a custom type handler to implement proprietary
+ * BLOB support (e.g. Oracle), or you might use it to handle
+ * booleans using "Y" and "N" instead of the more typical 0/1.
+ */
+interface ITypeHandlerCallback
+{
+ /**
+ * Performs processing on a value before it is used to set
+ * the parameter of a IDbCommand.
+ * @param object The interface for setting the value.
+ * @param object The value to be set.
+ */
+ public function getParameter($object);
+
+
+ /**
+ * Performs processing on a value before after it has been retrieved
+ * from a database
+ * @param object The interface for getting the value.
+ * @return mixed The processed value.
+ */
+ public function getResult($string);
+
+
+ /**
+ * Casts the string representation of a value into a type recognized by
+ * this type handler. This method is used to translate nullValue values
+ * into types that can be appropriately compared. If your custom type handler
+ * cannot support nullValues, or if there is no reasonable string representation
+ * for this type (e.g. File type), you can simply return the String representation
+ * as it was passed in. It is not recommended to return null, unless null was passed
+ * in.
+ * @param string nullValue.
+ * @return mixed
+ */
+ public function createNewInstance();
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/DataMapper/messages.txt b/framework/DataAccess/SQLMap/DataMapper/messages.txt new file mode 100644 index 00000000..79c80ad5 --- /dev/null +++ b/framework/DataAccess/SQLMap/DataMapper/messages.txt @@ -0,0 +1,61 @@ +component_property_undefined = Component property '{0}.{1}' is not defined.
+component_property_readonly = Component property '{0}.{1}' is read-only.
+component_event_undefined = Component event '{0}.{1}' is not defined.
+component_eventhandler_invalid = Component event '{0}.{1}' is attached with an invalid event handler.
+component_expression_invalid = Component '{0}' is evaluating an invalid expression '{1}' : {2}.
+component_statements_invalid = Component '{0}' is evaluating invalid PHP statements '{1}' : {2}.
+
+propertyvalue_enumvalue_invalid = Value '{0}' is a not valid enumeration value ({1}).
+
+list_index_invalid = Index '{0}' is out of range.
+list_item_inexistent = The item cannot be found in the list.
+list_data_not_iterable = Data must be either an array or an object implementing Traversable interface.
+list_readonly = {0} is read-only.
+
+map_addition_disallowed = The new item cannot be added to the map.
+map_item_unremovable = The item cannot be removed from the map.
+map_data_not_iterable = Data must be either an array or an object implementing Traversable interface.
+map_readonly = {0} is read-only.
+
+sqlmap_contains_no_statement = Unable to find SQLMap statement '{0}'.
+sqlmap_already_contains_statement = Duplicate SQLMap statement found, '{0}' already exists.
+sqlmap_contains_no_result_map = Unable to find SQLMap result map '{0}'.
+sqlmap_already_contains_result_map = Duplicate SQLMap result map found, '{0}' already exists.
+sqlmap_contains_no_parameter_map = Unable to find SQLMap parameter map '{0}'.
+sqlmap_already_contains_parameter_map = Duplicate SQLMap parameter map found, '{0}' already exists.
+sqlmap_connection_already_exists = SqlMap could not invoke OpenConnection(). A connection is already started. Call CloseConnection first.
+sqlmap_unable_to_close_null_connection = SqlMap could not invoke CloseConnection(). No connection was started. Call OpenConnection() first.
+sqlmap_undefined_attribute = {0} attribute '{1}' is not defined for {2} in file {3}.
+sqlmap_unable_find_provider_class = Unable to find a database provider in SQLMap configuration file {0}.
+sqlmap_unable_to_find_parent_parameter_map = Unable to find parent parameter map extension '{0}' in file {1}.
+sqlmap_unable_to_find_parent_sql = Unable to find parent sql statement extension '{0}' in file {1}.
+sqlmap_unable_to_find_result_mapping = Unable to resolve SQLMap result mapping '{0}' in Result Map '{2}' using configuration file {1}.
+sqlmap_unable_to_find_parent_result_map = Unable to find parent SQLMap result map '{0}' in file {1}.
+sqlmap_undefined_property_inline_map = Invalid attribute '{0}' for inline parameter in statement '{1}' in file {2}.
+sqlmap_index_must_be_string_or_int = Invalid index '{0}', must be an integes or string to get a SQLMap parameter map property.
+sqlmap_undefined_input_property = Undefined array index '{0}' in retrieving property in SQLMap parameter map '{1}'.
+sqlmap_unable_to_find_class = Unable to find result class '{0}' in TResultMap::createInstanceOfResult().
+sqlmap_can_not_instantiate = Type handler '{0}' can not create new objects.
+sqlmap_cannot_execute_query_for_map = SQLMap statement class {0} can not query for map.
+sqlmap_cannot_execute_update = SQLMap statement class {0} can not execute update query.
+sqlmap_cannot_execute_insert = SQLMap statement class {0} can not execute insert.
+sqlmap_cannot_execute_query_for_list = SQLMap statement class {0} can not query for list.
+sqlmap_cannot_execute_query_for_object = SQLMap statement class {0} can not query for object
+sqlmap_execution_error_no_record = No record set found in executing statement '{0}': '{1}'.
+sqlmap_unable_to_create_new_instance = Unable to create a new instance of '{0}' using type hander '{1}' for SQLMap statement with ID '{2}'.
+sqlmap_invalid_property = Invalid property getter path '{0}'.
+sqlmap_invalid_property_type = Invalid object type, must be 'Object', unable to set property in path '{0}'.
+sqlmap_unable_to_get_property_for_parameter = Unable to find property '{1}' in object '{2}' for parameter map '{0}' while executing statement '{4}': '{3}'.
+sqlmap_error_in_parameter_from_handler = For parameter map '{0}', error in getting parameter from type handler '{2}' with value '{1}': '{3}'.
+sqlmap_error_in_result_property_from_handler = For result map '{0}', error in getting result from type handler '{2}', with value '{1}': '{3}'.=======
+sqlmap_unable_to_find_implemenation = Unable to find SQLMap cache implementation named '{0}'.
+sqlmap_cache_model_already_exists = This SQLMap already contains cache model '{0}'.
+sqlmap_unable_to_find_cache_model = Unable to find cache model '{0}' in this SQLMap.
+sqlmap_unable_to_find_db_config = Unable to find database connection settings <provider> and <datasource> in configuration file '{0}'.
+sqlmap_unable_to_find_config = Unable to find SQLMap configuration file '{0}'.
+sqlmap_unable_to_find_groupby = Unable to find data in result set with column '{0}' in result map with ID '{1}'.
+sqlmap_invalid_lazyload_list = Invalid type to lazy load, must specify a valid ListClass in statement '{0}'.
+sqlmap_unable_to_find_resource = 'Unable to find SQLMap configuration file '{0}'.
+sqlmap_query_execution_error = Error in executing SQLMap statement '{0}' : '{1}'.
+sqlmap_undefined_discriminator = The discriminator is null, but somehow a subMap was reached in ResultMap '{0}' in file '{1}'.
+sqlmap_invalid_delegate = Invalid callback row delegate '{1}' in mapped statement '{0}'.
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Statements/IMappedStatement.php b/framework/DataAccess/SQLMap/Statements/IMappedStatement.php new file mode 100644 index 00000000..6a4d76db --- /dev/null +++ b/framework/DataAccess/SQLMap/Statements/IMappedStatement.php @@ -0,0 +1,71 @@ +<?php
+
+interface IMappedStatement
+{
+ /**
+ * @return string Name used to identify the MappedStatement amongst the others.
+ */
+ public function getID();
+
+ /**
+ * @return TSqlMapStatement The SQL statment used by this TMappedStatement.
+ */
+ public function getStatement();
+
+ /**
+ * @return TSqlMap The TSqlMap used by this TMappedStatement
+ */
+ public function getSqlMap();
+
+ /**
+ * Executes the SQL and retuns all rows selected in a map that is keyed on
+ * the property named in the <tt>$keyProperty</tt> parameter. The value at
+ * each key will be the value of the property specified in the
+ * <tt>$valueProperty</tt> parameter. If <tt>$valueProperty</tt> is
+ * <tt>null</tt>, the entire result object will be entered.
+ * @param IDbConnection database connection to execute the query
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param string The property of the result object to be used as the key.
+ * @param string The property of the result object to be used as the value (or null)
+ * @return TMap A map of object containing the rows keyed by <tt>$keyProperty</tt>.
+ */
+ public function executeQueryForMap($connection, $parameter,
+ $keyProperty, $valueProperty=null);
+
+
+ /**
+ * Execute an update statement. Also used for delete statement. Return the
+ * number of row effected.
+ * @param IDbConnection database connection to execute the query
+ * @param mixed The object used to set the parameters in the SQL.
+ * @return integer The number of row effected.
+ */
+ public function executeUpdate($connection, $parameter);
+
+
+ /**
+ * Executes the SQL and retuns a subset of the rows selected.
+ * @param IDbConnection database connection to execute the query
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param TList A list to populate the result with.
+ * @param integer The number of rows to skip over.
+ * @param integer The maximum number of rows to return.
+ * @return TList A TList of result objects.
+ */
+ public function executeQueryForList($connection,
+ $parameter, $result, $skip=-1, $max=-1);
+
+
+ /**
+ * Executes an SQL statement that returns a single row as an object
+ * of the type of the <tt>$result</tt> passed in as a parameter.
+ * @param IDbConnection database connection to execute the query
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param object The result object.
+ * @return object result.
+ */
+ public function executeQueryForObject($connection,
+ $parameter, $result);
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Statements/TCachingStatement.php b/framework/DataAccess/SQLMap/Statements/TCachingStatement.php new file mode 100644 index 00000000..8e4e2a3d --- /dev/null +++ b/framework/DataAccess/SQLMap/Statements/TCachingStatement.php @@ -0,0 +1,102 @@ +<?php
+
+/**
+ *
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $Date: $
+ * @package System.Web.UI.WebControls
+ * @since 3.0
+ */
+class TCachingStatement implements IMappedStatement
+{
+ private $_mappedStatement;
+
+ public function __construct(TMappedStatement $statement)
+ {
+ $this->_mappedStatement = $statement;
+ }
+
+ public function getID()
+ {
+ return $this->_mappedStatement->getID();
+ }
+
+ public function getStatement()
+ {
+ return $this->_mappedStatement->getStatement();
+ }
+
+ public function getSqlMap()
+ {
+ return $this->_mappedStatement->getSqlMap();
+ }
+
+ public function executeQueryForMap($connection, $parameter,
+ $keyProperty, $valueProperty=null, $delegate=null)
+ {
+ $sql = $this->createCommand($connection, $parameter);
+ $key = $this->getCacheKey(array($sql, $keyProperty, $valueProperty));
+ $map = $this->getStatement()->getCache()->get($key);
+ if(is_null($map))
+ {
+ $map = $this->_mappedStatement->runQueryForMap(
+ $connection, $parameter, $sql, $keyProperty, $valueProperty, $delegate);
+ $this->getStatement()->getCache()->set($key, $map);
+ }
+ return $map;
+ }
+
+ public function executeUpdate($connection, $parameter)
+ {
+ return $this->_mappedStatement->executeUpdate($connection, $parameter);
+ }
+
+ public function executeInsert($connection, $parameter)
+ {
+ return $this->executeInsert($connection, $parameter);
+ }
+
+ public function executeQueryForList($connection, $parameter,
+ $result, $skip=-1, $max=-1, $delegate=null)
+ {
+ $sql = $this->createCommand($connection, $parameter);
+ $key = $this->getCacheKey(array($sql, $skip, $max));
+ $list = $this->getStatement()->getCache()->get($key);
+ if(is_null($list))
+ {
+ $list = $this->_mappedStatement->runQueryForList(
+ $connection, $parameter, $sql, $result, $skip, $max, $delegate);
+ $this->getStatement()->getCache()->set($key, $list);
+ }
+ return $list;
+ }
+
+ public function executeQueryForObject($connection, $parameter, $result)
+ {
+ $sql = $this->createCommand($connection, $parameter);
+ $key = $this->getCacheKey($sql);
+ $object = $this->getStatement()->getCache()->get($key);
+ if(is_null($object))
+ {
+ $object = $this->_mappedStatement->runQueryForObject(
+ $connection, $sql, $result);
+ $this->getStatement()->getCache()->set($key, $object);
+ }
+ return $object;
+ }
+
+ protected function getCacheKey($object)
+ {
+ $cacheKey = new TSqlMapCacheKey($object);
+ return $cacheKey->getHash();
+ }
+
+ protected function createCommand($connection, $parameter)
+ {
+ return $this->_mappedStatement->getCommand()->create(
+ $connection, $this->getStatement(), $parameter);
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Statements/TDeleteMappedStatement.php b/framework/DataAccess/SQLMap/Statements/TDeleteMappedStatement.php new file mode 100644 index 00000000..9a1c8fae --- /dev/null +++ b/framework/DataAccess/SQLMap/Statements/TDeleteMappedStatement.php @@ -0,0 +1,7 @@ +<?php
+
+class TDeleteMappedStatement extends TUpdateMappedStatement
+{
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Statements/TInsertMappedStatement.php b/framework/DataAccess/SQLMap/Statements/TInsertMappedStatement.php new file mode 100644 index 00000000..7a444c89 --- /dev/null +++ b/framework/DataAccess/SQLMap/Statements/TInsertMappedStatement.php @@ -0,0 +1,32 @@ +<?php
+
+class TInsertMappedStatement extends TMappedStatement
+{
+ public function executeQueryForMap($connection, $parameter,
+ $keyProperty, $valueProperty=null)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_cannot_execute_query_for_map', get_class($this));
+ }
+
+ public function executeUpdate($connection, $parameter)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_cannot_execute_update', get_class($this));
+ }
+
+ public function executeQueryForList($connection, $parameter, $result,
+ $skip=-1, $max=-1)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_cannot_execute_query_for_list', get_class($this));
+ }
+
+ public function executeQueryForObject($connection, $parameter, $result)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_cannot_execute_query_for_object', get_class($this));
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Statements/TMappedStatement.php b/framework/DataAccess/SQLMap/Statements/TMappedStatement.php new file mode 100644 index 00000000..483a315a --- /dev/null +++ b/framework/DataAccess/SQLMap/Statements/TMappedStatement.php @@ -0,0 +1,988 @@ +<?php
+/**
+ * TMappedStatement and related classes.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright © 2005 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $Date: $
+ * @package System.DataAccess.SQLMap.Statements
+ */
+
+/**
+ * TMappedStatement class executes SQL mapped statements. Mapped Statements can
+ * hold any SQL statement and use Parameter Maps and Result Maps for input and output.
+ *
+ * This class is usualy instantiated during SQLMap configuration by TSqlDomBuilder.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $Date: $
+ * @package System.DataAccess.SQLMap.Statements
+ * @since 3.0
+ */
+class TMappedStatement extends TComponent implements IMappedStatement
+{
+ /**
+ * @var TSqlMapStatement current SQL statement.
+ */
+ private $_statement;
+
+ /**
+ * @var TPreparedCommand SQL command prepareer
+ */
+ private $_command;
+
+ /**
+ * @var TSqlMapper sqlmap used by this mapper.
+ */
+ private $_sqlMap;
+
+ /**
+ * @var TPostSelectBinding[] post select statement queue.
+ */
+ private $_selectQueque=array();
+
+ /**
+ * @var boolean true when data is mapped to a particular row.
+ */
+ private $_IsRowDataFound = false;
+
+ /**
+ * @var array group by result data cache.
+ */
+ private $_groupBy=array();
+
+ /**
+ * @var Post select is to query for list.
+ */
+ const QUERY_FOR_LIST = 0;
+
+ /**
+ * @var Post select is to query for list.
+ */
+ const QUERY_FOR_ARRAY = 1;
+
+ /**
+ * @var Post select is to query for object.
+ */
+ const QUERY_FOR_OBJECT = 2;
+
+ /**
+ * @return string Name used to identify the TMappedStatement amongst the others.
+ * This the name of the SQL statement by default.
+ */
+ public function getID()
+ {
+ return $this->_statement->ID;
+ }
+
+ /**
+ * @return TSqlMapStatement The SQL statment used by this MappedStatement
+ */
+ public function getStatement()
+ {
+ return $this->_statement;
+ }
+
+ /**
+ * @return TSqlMapper The SqlMap used by this MappedStatement
+ */
+ public function getSqlMap()
+ {
+ return $this->_sqlMap;
+ }
+
+ /**
+ * @return TPreparedCommand command to prepare SQL statements.
+ */
+ public function getCommand()
+ {
+ return $this->_command;
+ }
+
+ /**
+ * @return TResultMapGroupBy[] list of results obtained from group by result maps.
+ */
+ protected function getGroupByResults()
+ {
+ return $this->_groupBy;
+ }
+
+ /**
+ * Empty the group by results cache.
+ */
+ protected function clearGroupByResults()
+ {
+ $this->_groupBy = array();
+ }
+
+ /**
+ * Creates a new mapped statement.
+ * @param TSqlMapper an sqlmap.
+ * @param TSqlMapStatement An SQL statement.
+ */
+ public function __construct(TSqlMapper $sqlMap, TSqlMapStatement $statement)
+ {
+ $this->_sqlMap = $sqlMap;
+ $this->_statement = $statement;
+ $this->_command = new TPreparedCommand();
+ }
+
+ /**
+ * Execute SQL Query.
+ * @param IDbConnection database connection
+ * @param array SQL statement and parameters.
+ * @return mixed record set if applicable.
+ * @throws TSqlMapExecutionException if execution error or false record set.
+ * @throws TSqlMapQueryExecutionException if any execution error
+ */
+ protected function executeSQLQuery($connection, $sql)
+ {
+ try
+ {
+ if(!($recordSet = $connection->execute($sql['sql'],$sql['parameters'])))
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_execution_error_no_record', $this->getID(),
+ $connection->ErrorMsg());
+ }
+ return $recordSet;
+ }
+ catch (Exception $e)
+ {
+ throw new TSqlMapQueryExecutionException($this->getStatement(), $e);
+ }
+ }
+
+ /**
+ * Execute SQL Query with limits.
+ * @param IDbConnection database connection
+ * @param array SQL statement and parameters.
+ * @return mixed record set if applicable.
+ * @throws TSqlMapExecutionException if execution error or false record set.
+ * @throws TSqlMapQueryExecutionException if any execution error
+ */
+ protected function executeSQLQueryLimit($connection, $sql, $max, $skip)
+ {
+ try
+ {
+ $recordSet = $connection->selectLimit($sql['sql'],$max,$skip,$sql['parameters']);
+ if(!$recordSet)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_execution_error_query_for_list',
+ $connection->ErrorMsg());
+ }
+ return $recordSet;
+ }
+ catch (Exception $e)
+ {
+ throw new TSqlMapQueryExecutionException($this->getStatement(), $e);
+ }
+ }
+
+ /**
+ * Executes the SQL and retuns a List of result objects.
+ * @param IDbConnection database connection
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param object result collection object.
+ * @param integer The number of rows to skip over.
+ * @param integer The maximum number of rows to return.
+ * @return array a list of result objects
+ * @param callback row delegate handler
+ * @see executeQueryForList()
+ */
+ public function executeQueryForList($connection, $parameter, $result, $skip=-1, $max=-1, $delegate=null)
+ {
+ $sql = $this->_command->create($connection, $this->_statement, $parameter);
+ return $this->runQueryForList($connection, $parameter, $sql, $result, $skip, $max, $delegate);
+ }
+
+ /**
+ * Executes the SQL and retuns a List of result objects.
+ *
+ * This method should only be called by internal developers, consider using
+ * <tt>executeQueryForList()</tt> first.
+ *
+ * @param IDbConnection database connection
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param array SQL string and subsititution parameters.
+ * @param object result collection object.
+ * @param integer The number of rows to skip over.
+ * @param integer The maximum number of rows to return.
+ * @param callback row delegate handler
+ * @return array a list of result objects
+ * @see executeQueryForList()
+ */
+ public function runQueryForList($connection, $parameter, $sql, $result, $skip=-1, $max=-1, $delegate=null)
+ {
+ $list = $result instanceof ArrayAccess ? $result :
+ $this->_statement->createInstanceOfListClass();
+ $recordSet = $this->executeSQLQueryLimit($connection, $sql, $max, $skip);
+
+ if(!is_null($delegate))
+ {
+ while($row = $recordSet->fetchRow())
+ {
+ $obj = $this->applyResultMap($row);
+ $param = new TResultSetListItemParameter($obj, $parameter, $list);
+ $this->raiseRowDelegate($delegate, $param);
+ }
+ }
+ else
+ {
+ while($row = $recordSet->fetchRow())
+ {
+ $obj = $this->applyResultMap($row);
+ if(!is_null($obj))
+ $list[] = $obj;
+ }
+ }
+
+ //get the groupings
+ foreach($this->getGroupbyResults() as $group)
+ $list[] = $group->updateProperties();
+ $this->clearGroupByResults();
+
+ $this->executePostSelect($connection);
+ $this->onExecuteQuery($sql);
+ return $list;
+ }
+
+ /**
+ * Executes the SQL and retuns all rows selected in a map that is keyed on
+ * the property named in the keyProperty parameter. The value at each key
+ * will be the value of the property specified in the valueProperty parameter.
+ * If valueProperty is null, the entire result object will be entered.
+ * @param IDbConnection database connection
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param string The property of the result object to be used as the key.
+ * @param string The property of the result object to be used as the value (or null).
+ * @param callback row delegate handler
+ * @return array An array of object containing the rows keyed by keyProperty.
+ */
+ public function executeQueryForMap($connection, $parameter, $keyProperty, $valueProperty=null, $delegate=null)
+ {
+ $sql = $this->_command->create($connection, $this->_statement, $parameter);
+ return $this->runQueryForMap($connection, $parameter, $sql, $keyProperty, $valueProperty, $delegate);
+ }
+
+ /**
+ * Executes the SQL and retuns all rows selected in a map that is keyed on
+ * the property named in the keyProperty parameter. The value at each key
+ * will be the value of the property specified in the valueProperty parameter.
+ * If valueProperty is null, the entire result object will be entered.
+ *
+ * This method should only be called by internal developers, consider using
+ * <tt>executeQueryForMap()</tt> first.
+ *
+ * @param IDbConnection database connection
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param array SQL string and subsititution parameters.
+ * @param string The property of the result object to be used as the key.
+ * @param string The property of the result object to be used as the value (or null).
+ * @param callback row delegate, a callback function
+ * @return array An array of object containing the rows keyed by keyProperty.
+ * @see executeQueryForMap()
+ */
+ public function runQueryForMap($connection, $parameter, $sql, $keyProperty, $valueProperty=null, $delegate=null)
+ {
+ $map = array();
+ $recordSet = $this->executeSQLQuery($connection, $sql);
+ if(!is_null($delegate))
+ {
+ while($row = $recordSet->fetchRow())
+ {
+ $obj = $this->applyResultMap($row);
+ $key = TPropertyAccess::get($obj, $keyProperty);
+ $value = is_null($valueProperty) ? $obj :
+ TPropertyAccess::get($obj, $valueProperty);
+ $param = new TResultSetMapItemParameter($key, $value, $parameter, $map);
+ $this->raiseRowDelegate($delegate, $param);
+ }
+ }
+ else
+ {
+ while($row = $recordSet->fetchRow())
+ {
+ $obj = $this->applyResultMap($row);
+ $key = TPropertyAccess::get($obj, $keyProperty);
+ $map[$key] = is_null($valueProperty) ? $obj :
+ TPropertyAccess::get($obj, $valueProperty);
+ }
+ }
+ $this->onExecuteQuery($sql);
+ return $map;
+ }
+
+ /**
+ * Raises delegate handler.
+ * This method is invoked for each new list item. It is the responsibility
+ * of the handler to add the item to the list.
+ * @param object event parameter
+ */
+ protected function raiseRowDelegate($handler, $param)
+ {
+ if(is_string($handler))
+ {
+ call_user_func($handler,$this,$param);
+ }
+ else if(is_callable($handler,true))
+ {
+ // an array: 0 - object, 1 - method name/path
+ list($object,$method)=$handler;
+ if(is_string($object)) // static method call
+ call_user_func($handler,$this,$param);
+ else
+ {
+ if(($pos=strrpos($method,'.'))!==false)
+ {
+ $object=$this->getSubProperty(substr($method,0,$pos));
+ $method=substr($method,$pos+1);
+ }
+ $object->$method($this,$param);
+ }
+ }
+ else
+ throw new TInvalidDataValueException('sqlmap_invalid_delegate', $this->getID(), $handler);
+ }
+
+ /**
+ * Executes an SQL statement that returns a single row as an object of the
+ * type of the <tt>$result</tt> passed in as a parameter.
+ * @param IDbConnection database connection
+ * @param mixed The parameter data (object, arrary, primitive) used to set the parameters in the SQL
+ * @param mixed The result object.
+ * @return ${return}
+ */
+ public function executeQueryForObject($connection, $parameter, $result)
+ {
+ $sql = $this->_command->create($connection, $this->_statement, $parameter);
+ return $this->runQueryForObject($connection, $sql, $result);
+ }
+
+ /**
+ * Executes an SQL statement that returns a single row as an object of the
+ * type of the <tt>$result</tt> passed in as a parameter.
+ *
+ * This method should only be called by internal developers, consider using
+ * <tt>executeQueryForObject()</tt> first.
+ *
+ * @param IDbConnection database connection
+ * @param array SQL string and subsititution parameters.
+ * @param object The result object.
+ * @return object the object.
+ * @see executeQueryForObject()
+ */
+ public function runQueryForObject($connection, $sql, $result)
+ {
+ $recordSet = $this->executeSQLQuery($connection, $sql);
+ $object = $this->applyResultMap($recordSet->fetchRow(), $result);
+
+ //get group by result
+ foreach($this->getGroupbyResults() as $group)
+ $object = $group->updateProperties();
+ $this->clearGroupByResults();
+
+ $this->executePostSelect($connection);
+ $this->onExecuteQuery($sql);
+ return $object;
+ }
+
+ /**
+ * Execute an insert statement. Fill the parameter object with the ouput
+ * parameters if any, also could return the insert generated key.
+ * @param IDbConnection database connection
+ * @param mixed The parameter object used to fill the statement.
+ * @return string the insert generated key.
+ */
+ public function executeInsert($connection, $parameter)
+ {
+ $generatedKey = $this->getPreGeneratedSelectKey($connection, $parameter);
+
+ $sql = $this->_command->create($connection, $this->_statement, $parameter);
+
+ $this->executeSQLQuery($connection, $sql);
+
+ if(is_null($generatedKey))
+ $generatedKey = $this->getPostGeneratedSelectKey($connection, $parameter);
+
+ $this->executePostSelect($connection);
+ $this->onExecuteQuery($sql);
+ return $generatedKey;
+ }
+
+ /**
+ * Gets the insert generated ID before executing an insert statement.
+ * @param IDbConnection database connection
+ * @param mixed insert statement parameter.
+ * @return string new insert ID if pre-select key statement was executed, null otherwise.
+ */
+ protected function getPreGeneratedSelectKey($connection, $parameter)
+ {
+ if($this->_statement instanceof TSqlMapInsert)
+ {
+ $selectKey = $this->_statement->getSelectKey();
+ if(!is_null($selectKey) && !$selectKey->getIsAfter())
+ return $this->executeSelectKey($connection, $parameter, $selectKey);
+ }
+ }
+
+ /**
+ * Gets the inserted row ID after executing an insert statement.
+ * @param IDbConnection database connection
+ * @param mixed insert statement parameter.
+ * @return string last insert ID, null otherwise.
+ */
+ protected function getPostGeneratedSelectKey($connection, $parameter)
+ {
+ if($this->_statement instanceof TSqlMapInsert)
+ {
+ $selectKey = $this->_statement->getSelectKey();
+ if(!is_null($selectKey) && $selectKey->getIsAfter())
+ return $this->executeSelectKey($connection, $parameter, $selectKey);
+ }
+ }
+
+ /**
+ * Execute the select key statement, used to obtain last insert ID.
+ * @param IDbConnection database connection
+ * @param mixed insert statement parameter
+ * @param TSqlMapSelectKey select key statement
+ * @return string last insert ID.
+ */
+ protected function executeSelectKey($connection, $parameter, $selectKey)
+ {
+ $mappedStatement = $this->sqlMap->getMappedStatement($selectKey->getID());
+ $generatedKey = $mappedStatement->executeQueryForObject(
+ $connection, $parameter, null);
+ if(strlen($prop = $selectKey->getProperty()) > 0)
+ TPropertyAccess::set($parameter, $prop, $generatedKey);
+ return $generatedKey;
+ }
+
+ /**
+ * Execute an update statement. Also used for delete statement.
+ * Return the number of rows effected.
+ * @param IDbConnection database connection
+ * @param mixed The object used to set the parameters in the SQL.
+ * @return integer The number of rows effected.
+ */
+ public function executeUpdate($connection, $parameter)
+ {
+ $sql = $this->_command->create($connection, $this->_statement, $parameter);
+ $this->executeSQLQuery($connection, $sql);
+ $this->executePostSelect($connection);
+ $this->onExecuteQuery($sql);
+ return $connection->Affected_Rows();
+ }
+
+ /**
+ * Process 'select' result properties
+ * @param IDbConnection database connection
+ */
+ protected function executePostSelect($connection)
+ {
+
+ while(count($this->_selectQueque))
+ {
+ $postSelect = array_shift($this->_selectQueque);
+ $method = $postSelect->getMethod();
+ $statement = $postSelect->getStatement();
+ $property = $postSelect->getResultProperty()->getProperty();
+ $keys = $postSelect->getKeys();
+ $resultObject = $postSelect->getResultObject();
+
+ if($method == self::QUERY_FOR_LIST || $method == self::QUERY_FOR_ARRAY)
+ {
+ $values = $statement->executeQueryForList($connection, $keys, null);
+
+ if($method == self::QUERY_FOR_ARRAY)
+ $values = $values->toArray();
+ TPropertyAccess::set($resultObject, $property, $values);
+ }
+ else if($method == self::QUERY_FOR_OBJECT)
+ {
+ $value = $statement->executeQueryForObject($connection, $keys, null);
+ TPropertyAccess::set($resultObject, $property, $value);
+ }
+ }
+ }
+
+ /**
+ * Raise the execute query event.
+ * @param array prepared SQL statement and subsititution parameters
+ */
+ public function onExecuteQuery($sql)
+ {
+ $this->raiseEvent('OnExecuteQuery', $this, $sql);
+ }
+
+ /**
+ * Apply result mapping.
+ * @param array a result set row retrieved from the database
+ * @param object the result object, will create if necessary.
+ * @return object the result filled with data, null if not filled.
+ */
+ protected function applyResultMap($row, $resultObject=null)
+ {
+ if($row === false) return null;
+
+ $resultMapName = $this->_statement->getResultMap();
+ $resultClass = $this->_statement->getResultClass();
+
+ if($this->_sqlMap->getResultMaps()->contains($resultMapName))
+ return $this->fillResultMap($resultMapName, $row, $resultObject);
+ else if(strlen($resultClass) > 0)
+ return $this->fillResultClass($resultClass, $row, $resultObject);
+ else
+ return $this->fillDefaultResultMap(null, $row, $resultObject);
+ }
+
+ /**
+ * Fill the result using ResultClass, will creates new result object if required.
+ * @param string result object class name
+ * @param array a result set row retrieved from the database
+ * @param object the result object, will create if necessary.
+ * @return object result object filled with data
+ */
+ protected function fillResultClass($resultClass, $row, $resultObject)
+ {
+ if(is_null($resultObject))
+ $resultObject = $this->_statement->createInstanceOfResultClass();
+
+ if($resultObject instanceOf ArrayAccess)
+ return $this->fillResultArrayList($row, $resultObject);
+ else if(is_object($resultObject))
+ return $this->fillResultObjectProperty($row, $resultObject);
+ else
+ return $this->fillDefaultResultMap(null, $row, $resultObject);
+ }
+
+ /**
+ * Apply the result to a TList or an array.
+ * @param array a result set row retrieved from the database
+ * @param object result object, array or list
+ * @return object result filled with data.
+ */
+ protected function fillResultArrayList($row, $resultObject)
+ {
+ if($resultObject instanceof TList)
+ foreach($row as $v)
+ $resultObject[] = $v;
+ else
+ foreach($row as $k => $v)
+ $resultObject[$k] = $v;
+ return $resultObject;
+ }
+
+ /**
+ * Apply the result to an object.
+ * @param array a result set row retrieved from the database
+ * @param object result object, array or list
+ * @return object result filled with data.
+ */
+ protected function fillResultObjectProperty($row, $resultObject)
+ {
+ $index = 0;
+ foreach($row as $k=>$v)
+ {
+ $property = new TResultProperty;
+ $property->initialize($this->_sqlMap);
+ if(is_string($k) && strlen($k) > 0)
+ $property->setColumn($k);
+ $property->setColumnIndex(++$index);
+ $type = gettype(TPropertyAccess::get($resultObject,$k));
+ $property->setType($type);
+ $value = $property->getDatabaseValue($row);
+ TPropertyAccess::set($resultObject, $k,$value);
+ }
+ return $resultObject;
+ }
+
+ /**
+ * Fills the result object according to result mappings.
+ * @param string result map name.
+ * @param array a result set row retrieved from the database
+ * @param object result object to fill, will create new instances if required.
+ * @return object result object filled with data.
+ */
+ protected function fillResultMap($resultMapName, $row, $resultObject)
+ {
+ $resultMap = $this->_sqlMap->getResultMap($resultMapName);
+ $resultMap = $resultMap->resolveSubMap($row);
+
+ if(is_null($resultObject))
+ $resultObject = $resultMap->createInstanceOfResult();
+
+ if(is_object($resultObject))
+ {
+ if(strlen($resultMap->getGroupBy()) > 0)
+ return $this->addResultMapGroupBy($resultMap, $row, $resultObject);
+ else
+ foreach($resultMap->getColumns() as $property)
+ $this->setObjectProperty($resultMap, $property, $row, $resultObject);
+ }
+ else
+ {
+ $resultObject = $this->fillDefaultResultMap($resultMap, $row, $resultObject);
+ }
+ return $resultObject;
+ }
+
+ /**
+ * ResultMap with GroupBy property. Save results temporarily, retrieve it later using
+ * <tt>TMappedStatement::getGroupByResults()</tt> and <tt>TResultMapGroupBy::updateProperties()</tt>
+ * @param TResultMap result mapping details.
+ * @param array a result set row retrieved from the database
+ * @param object the result object
+ * @return null, always returns null, use getGroupByResults() to obtain the result object list.
+ * @see getGroupByResults()
+ * @see runQueryForList()
+ */
+ protected function addResultMapGroupBy($resultMap, $row, $resultObject)
+ {
+ $group = $this->getResultMapGroupKey($resultMap, $row, $resultObject);
+ foreach($resultMap->getColumns() as $property)
+ {
+ $this->setObjectProperty($resultMap, $property, $row, $resultObject);
+ if(strlen($property->getResultMapping()) > 0)
+ {
+ $key = $property->getProperty();
+ $value = TPropertyAccess::get($resultObject, $key);
+ $this->_groupBy[$group]->addValue($key, $value);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the result 'group by' groupping key for each row.
+ * @param TResultMap result mapping details.
+ * @param array a result set row retrieved from the database
+ * @param object the result object
+ * @return string groupping key.
+ * @throws TSqlMapExecutionException if unable to find grouping key.
+ */
+ protected function getResultMapGroupKey($resultMap, $row, $resultObject)
+ {
+ $groupBy = $resultMap->getGroupBy();
+ if(isset($row[$groupBy]))
+ {
+ $group = $row[$groupBy];
+ if(!isset($this->_groupBy[$group]))
+ $this->_groupBy[$group] = new TResultMapGroupBy($group, $resultObject);
+ return $group;
+ }
+ else
+ {
+ throw new TSqlMapExecutionException('sqlmap_unable_to_find_groupby',
+ $groupBy, $resultMap->getID());
+ }
+ }
+
+ /**
+ * Fill the result map using default settings. If <tt>$resultMap</tt> is null
+ * the result object returned will be guessed from <tt>$resultObject</tt>.
+ * @param TResultMap result mapping details.
+ * @param array a result set row retrieved from the database
+ * @param object the result object
+ * @return mixed the result object filled with data.
+ */
+ protected function fillDefaultResultMap($resultMap, $row, $resultObject)
+ {
+ if(is_null($resultObject))
+ $resultObject='';
+
+ if(!is_null($resultMap))
+ $result = $this->fillArrayResultMap($resultMap, $row, $resultObject);
+ else
+ $result = $row;
+
+ //if scalar result types
+ if(count($result) == 1 && ($type = gettype($resultObject))!= 'array')
+ return $this->getScalarResult($result, $type);
+ else
+ return $result;
+ }
+
+ /**
+ * Retrieve the result map as an array.
+ * @param TResultMap result mapping details.
+ * @param array a result set row retrieved from the database
+ * @param object the result object
+ * @return array array list of result objects.
+ */
+ protected function fillArrayResultMap($resultMap, $row, $resultObject)
+ {
+ $result = array();
+ foreach($resultMap->getColumns() as $column)
+ {
+ if(is_null($column->getType())
+ && !is_null($resultObject) && !is_object($resultObject))
+ $column->setType(gettype($resultObject));
+ $result[$column->getProperty()] = $column->getDatabaseValue($row);
+ }
+ return $result;
+ }
+
+ /**
+ * Converts the first array value to scalar value of given type.
+ * @param array list of results
+ * @param string scalar type.
+ * @return mixed scalar value.
+ */
+ protected function getScalarResult($result, $type)
+ {
+ $scalar = array_shift($result);
+ settype($scalar, $type);
+ return $scalar;
+ }
+
+ /**
+ * Set a property of the result object with appropriate value.
+ * @param TResultMap result mapping details.
+ * @param TResultProperty the result property to fill.
+ * @param array a result set row retrieved from the database
+ * @param object the result object
+ */
+ protected function setObjectProperty($resultMap, $property, $row, $resultObject)
+ {
+ $select = $property->getSelect();
+ $key = $property->getProperty();
+ $nested = $property->getNestedResultMap();
+
+ if(strlen($select) == 0 && is_null($nested))
+ {
+ $value = $property->getDatabaseValue($row);
+ TPropertyAccess::set($resultObject, $key, $value);
+ $this->_IsRowDataFound = $this->_IsRowDataFound || ($value != null);
+ }
+ else if(!is_null($nested))
+ {
+ $obj = $nested->createInstanceOfResult();
+ if($this->fillPropertyWithResultMap($nested, $row, $obj) == false)
+ $obj = null;
+ TPropertyAccess::set($resultObject, $key, $obj);
+ }
+ else //'select' ResultProperty
+ {
+ $this->enquequePostSelect($select, $resultMap, $property, $row, $resultObject);
+ }
+ }
+
+ /**
+ * Add nested result property to post select queue.
+ * @param string post select statement ID
+ * @param TResultMap current result mapping details.
+ * @param TResultProperty current result property.
+ * @param array a result set row retrieved from the database
+ * @param object the result object
+ */
+ protected function enquequePostSelect($select, $resultMap, $property, $row, $resultObject)
+ {
+ $statement = $this->_sqlMap->getMappedStatement($select);
+ $key = $this->getPostSelectKeys($resultMap, $property, $row);
+ $postSelect = new TPostSelectBinding;
+ $postSelect->setStatement($statement);
+ $postSelect->setResultObject($resultObject);
+ $postSelect->setResultProperty($property);
+ $postSelect->setKeys($key);
+
+ if($property->isListType($resultObject))
+ {
+ $values = null;
+ if($property->getLazyLoad())
+ {
+ $values = TLazyLoadList::newInstance($statement, $key,
+ $resultObject, $property->getProperty());
+ TPropertyAccess::set($resultObject, $property->getProperty(), $values);
+ }
+ else
+ $postSelect->setMethod(self::QUERY_FOR_LIST);
+ }
+ else if($property->isArrayType($resultObject))
+ $postSelect->setMethod(self::QUERY_FOR_ARRAY);
+ else
+ $postSelect->setMethod(self::QUERY_FOR_OBJECT);
+
+ if(!$property->getLazyLoad())
+ array_push($this->_selectQueque, $postSelect);
+ }
+
+ /**
+ * Finds in the post select property the SQL statement primary selection keys.
+ * @param TResultMap result mapping details
+ * @param TResultProperty result property
+ * @param array current row data.
+ * @return array list of primary key values.
+ */
+ protected function getPostSelectKeys($resultMap, $property,$row)
+ {
+ $value = $property->getColumn();
+ if(is_int(strpos($value.',',0)) || is_int(strpos($value, '=',0)))
+ {
+ $keys = array();
+ foreach(explode(',', $value) as $entry)
+ {
+ $pair =explode('=',$entry);
+ $keys[trim($pair[0])] = $row[trim($pair[1])];
+ }
+ return $keys;
+ }
+ else
+ {
+ return $property->getOrdinalValue($row);
+ }
+ }
+
+ /**
+ * Fills the property with result mapping results.
+ * @param TResultMap nested result mapping details.
+ * @param array a result set row retrieved from the database
+ * @param object the result object
+ * @return boolean true if the data was found, false otherwise.
+ */
+ protected function fillPropertyWithResultMap($resultMap, $row, $resultObject)
+ {
+ $dataFound = false;
+ foreach($resultMap->getColumns() as $property)
+ {
+ $this->_IsRowDataFound = false;
+ $this->setObjectProperty($resultMap, $property, $row, $resultObject);
+ $dataFound = $dataFound || $this->_IsRowDataFound;
+ }
+ $this->_IsRowDataFound = $dataFound;
+ return $dataFound;
+ }
+}
+
+class TPostSelectBinding
+{
+ private $_statement=null;
+ private $_property=null;
+ private $_resultObject=null;
+ private $_keys=null;
+ private $_method=TMappedStatement::QUERY_FOR_LIST;
+
+ public function getStatement(){ return $this->_statement; }
+ public function setStatement($value){ $this->_statement = $value; }
+
+ public function getResultProperty(){ return $this->_property; }
+ public function setResultProperty($value){ $this->_property = $value; }
+
+ public function getResultObject(){ return $this->_resultObject; }
+ public function setResultObject($value){ $this->_resultObject = $value; }
+
+ public function getKeys(){ return $this->_keys; }
+ public function setKeys($value){ $this->_keys = $value; }
+
+ public function getMethod(){ return $this->_method; }
+ public function setMethod($value){ $this->_method = $value; }
+}
+
+class TResultMapGroupBy
+{
+ private $_ID='';
+ private $_object='';
+ private $_values = array();
+
+ public function __construct($id, $object)
+ {
+ $this->setID($id);
+ $this->setObject($object);
+ }
+
+ public function getID(){ return $this->_ID; }
+ public function setID($value){ $this->_ID = $value; }
+
+ public function getObject(){ return $this->_object; }
+ public function setObject($value){ $this->_object = $value; }
+
+ public function addValue($property, $value)
+ {
+ $this->_values[$property][] = $value;
+ }
+
+ public function getProperties()
+ {
+ return array_keys($this->_values);
+ }
+
+ public function updateProperties()
+ {
+ foreach($this->_values as $property => $value)
+ {
+ TPropertyAccess::set($this->getObject(), $property, $value);
+ }
+ return $this->getObject();
+ }
+}
+
+class TResultSetListItemParameter extends TComponent
+{
+ private $_resultObject;
+ private $_parameterObject;
+ private $_list;
+
+ public function __construct($result, $parameter, &$list)
+ {
+ $this->_resultObject = $result;
+ $this->_parameterObject = $parameter;
+ $this->_list = &$list;
+ }
+
+ public function getResult()
+ {
+ return $this->_resultObject;
+ }
+
+ public function getParameter()
+ {
+ return $this->_parameterObject;
+ }
+
+ public function &getList()
+ {
+ return $this->_list;
+ }
+}
+
+class TResultSetMapItemParameter extends TComponent
+{
+ private $_key;
+ private $_value;
+ private $_parameterObject;
+ private $_map;
+
+ public function __construct($key, $value, $parameter, &$map)
+ {
+ $this->_key = $key;
+ $this->_value = $value;
+ $this->_parameterObject = $parameter;
+ $this->_map = &$map;
+ }
+
+ public function getKey()
+ {
+ return $this->_key;
+ }
+
+ public function getValue()
+ {
+ return $this->_value;
+ }
+
+ public function getParameter()
+ {
+ return $this->_parameterObject;
+ }
+
+ public function &getMap()
+ {
+ return $this->_map;
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Statements/TPreparedCommand.php b/framework/DataAccess/SQLMap/Statements/TPreparedCommand.php new file mode 100644 index 00000000..1c0e5ad7 --- /dev/null +++ b/framework/DataAccess/SQLMap/Statements/TPreparedCommand.php @@ -0,0 +1,60 @@ +<?php
+
+class TPreparedCommand
+{
+
+ public function create($connection, $statement, $parameterObject)
+ {
+ $prepared = $statement->getSQL()->getPreparedStatement();
+ $parameters = $this->applyParameterMap($connection,
+ $prepared, $statement, $parameterObject);
+ return array('sql'=>$prepared->getPreparedSql(),
+ 'parameters'=>$parameters);
+ }
+
+ protected function applyParameterMap($connection,
+ $prepared, $statement, $parameterObject)
+ {
+ $properties = $prepared->getParameterNames();
+ $parameters = $prepared->getParameterValues();
+ $values = array();
+ for($i = 0, $k=$properties->getCount(); $i<$k; $i++)
+ {
+ $property = $statement->parameterMap()->getProperty($i);
+ $values[] = $statement->parameterMap()->getParameter(
+ $property, $parameterObject, $statement);
+ }
+ return count($values) > 0 ? $values : false;
+ }
+
+/* protected function applyParameterClass($connection, $statement, $parameter)
+ {
+ $type=$statement->getParameterClass();
+ if(strlen($type) < 1) return;
+ $prepared = $statement->getSql()->getPreparedStatement();
+ $names = $prepared->getParameterNames();
+ $values = $prepared->getParameterValues();
+ switch (strtolower($type))
+ {
+ case 'integer':
+ case 'int':
+ $values[$names[0]] = $connection->quote(intval($parameter));
+ break;
+ case 'array':
+ foreach($names as $name)
+ {
+ $key = substr(substr($name,0,-1),1);
+ if(isset($parameter[$key]))
+ $values->add($name,$connection->quote($parameter[$key]));
+ else
+ throw new TDataMapperException('unable_to_find_parameter', $key);
+ }
+ break;
+ default:
+ var_dump("todo for other parameter classes");
+ }
+ }
+*/
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Statements/TPreparedStatement.php b/framework/DataAccess/SQLMap/Statements/TPreparedStatement.php new file mode 100644 index 00000000..e534f532 --- /dev/null +++ b/framework/DataAccess/SQLMap/Statements/TPreparedStatement.php @@ -0,0 +1,26 @@ +<?php
+
+class TPreparedStatement extends TComponent
+{
+ private $_sqlString='';
+ private $_parameterNames;
+ private $_parameterValues;
+
+ public function __construct()
+ {
+ $this->_parameterNames=new TList;
+ $this->_parameterValues=new TMap;
+ }
+
+ public function getPreparedSql(){ return $this->_sqlString; }
+ public function setPreparedSql($value){ $this->_sqlString = $value; }
+
+ public function getParameterNames(){ return $this->_parameterNames; }
+ public function setParameterNames($value){ $this->_parameterNames = $value; }
+
+ public function getParameterValues(){ return $this->_parameterValues; }
+ public function setParameterValues($value){ $this->_parameterValues = $value; }
+
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Statements/TPreparedStatementFactory.php b/framework/DataAccess/SQLMap/Statements/TPreparedStatementFactory.php new file mode 100644 index 00000000..41059934 --- /dev/null +++ b/framework/DataAccess/SQLMap/Statements/TPreparedStatementFactory.php @@ -0,0 +1,47 @@ +<?php
+
+class TPreparedStatementFactory
+{
+ private $_statement;
+ private $_preparedStatement;
+ private $_parameterPrefix = 'param';
+ private $_commandText;
+
+ public function __construct($statement, $sqlString)
+ {
+ $this->_statement = $statement;
+ $this->_commandText = $sqlString;
+ // $this->_statement = new TPreparedStatement();
+// $this->_statement->setSqlString($sqlString);
+ }
+
+ public function prepare()
+ {
+ //$this->createParametersFromTextCommand();
+ //return $this->_statement;
+ $this->_preparedStatement = new TPreparedStatement();
+ $this->_preparedStatement->setPreparedSql($this->_commandText);
+ if(!is_null($this->_statement->parameterMap()))
+ {
+ $this->createParametersForTextCommand();
+ //$this->evaluateParameterMap();
+ }
+ //var_dump($this->_preparedStatement);
+ return $this->_preparedStatement;
+ }
+
+ protected function createParametersForTextCommand()
+ {
+ /*$matches = array();
+ $string = $this->_statement->getSqlString();
+ preg_match_all('/#([a-zA-Z0-9._]+)#/', $string, $matches);
+ $this->_statement->getParameterNames()->copyFrom($matches[0]);*/
+ //var_dump($this->_statement);
+ foreach($this->_statement->ParameterMap()->getProperties() as $prop)
+ {
+ $this->_preparedStatement->getParameterNames()->add($prop->getProperty());
+ }
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Statements/TSelectMappedStatement.php b/framework/DataAccess/SQLMap/Statements/TSelectMappedStatement.php new file mode 100644 index 00000000..1171e28f --- /dev/null +++ b/framework/DataAccess/SQLMap/Statements/TSelectMappedStatement.php @@ -0,0 +1,19 @@ +<?php
+
+class TSelectMappedStatement extends TMappedStatement
+{
+ public function executeInsert($connection, $parameter)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_cannot_execute_insert', get_class($this));
+ }
+
+ public function executeUpdate($connection, $parameter)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_cannot_execute_update', get_class($this));
+ }
+
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Statements/TStaticSql.php b/framework/DataAccess/SQLMap/Statements/TStaticSql.php new file mode 100644 index 00000000..bf6e4a18 --- /dev/null +++ b/framework/DataAccess/SQLMap/Statements/TStaticSql.php @@ -0,0 +1,19 @@ +<?php
+
+class TStaticSql extends TComponent
+{
+ private $_preparedStatement;
+
+ public function buildPreparedStatement($statement, $sqlString)
+ {
+ $factory = new TPreparedStatementFactory($statement, $sqlString);
+ $this->_preparedStatement = $factory->prepare();
+ }
+
+ public function getPreparedStatement()
+ {
+ return $this->_preparedStatement;
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/Statements/TUpdateMappedStatement.php b/framework/DataAccess/SQLMap/Statements/TUpdateMappedStatement.php new file mode 100644 index 00000000..cf16ee52 --- /dev/null +++ b/framework/DataAccess/SQLMap/Statements/TUpdateMappedStatement.php @@ -0,0 +1,32 @@ +<?php
+
+class TUpdateMappedStatement extends TMappedStatement
+{
+ public function executeInsert($connection, $parameter)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_cannot_execute_insert', get_class($this));
+ }
+
+ public function executeQueryForMap($connection, $parameter, $keyProperty,
+ $valueProperty=null)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_cannot_execute_query_for_map', get_class($this));
+ }
+
+ public function executeQueryForList($connection, $parameter, $result,
+ $skip=-1, $max=-1)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_cannot_execute_query_for_list', get_class($this));
+ }
+
+ public function executeQueryForObject($connection, $parameter, $result)
+ {
+ throw new TSqlMapExecutionException(
+ 'sqlmap_cannot_execute_query_for_object', get_class($this));
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/TMapper.php b/framework/DataAccess/SQLMap/TMapper.php new file mode 100644 index 00000000..4427c012 --- /dev/null +++ b/framework/DataAccess/SQLMap/TMapper.php @@ -0,0 +1,60 @@ +<?php
+
+require_once(dirname(__FILE__).'/TSqlMapClient.php');
+
+/**
+ * A singleton class to access the default SqlMapper.
+ *
+ * Usage: Call configure() once, then use instance() to obtain a TSqlMapper
+ * instance.
+ * <code>
+ * TMapper::configure($configFile);
+ * $object = TMapper::instance()->queryForObject('statementName');
+ * </code>
+ *
+ * If your configuration file is named 'sqlmap.config' you may skip the
+ * configure() call.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $Date: $
+ * @package System.DataAccess.SQLMap
+ * @since 3.0
+ */
+class TMapper
+{
+ /**
+ * Data mapper singleton
+ * @var TSqlMapper
+ */
+ private static $_mapper;
+
+ /**
+ * Configure the data mapper singleton instance.
+ * @param string configuration file
+ * @param boolean true to load configuration from cache.
+ * @return TSqlMapper data mapper instance.
+ */
+ public static function configure($configFile,$loadCachedConfig=false)
+ {
+ if(is_null(self::$_mapper))
+ {
+ $sqlmap = new TSQLMapClient;
+ self::$_mapper = $sqlmap->configure($configFile,$loadCachedConfig);
+ }
+ return self::$_mapper;
+ }
+
+ /**
+ * Gets the data mapper singleton instance. Default configuration file is
+ * 'sqlmap.config'.
+ * @return TSqlMapper singleton instance.
+ */
+ public static function instance()
+ {
+ if(is_null(self::$_mapper))
+ self::configure('sqlmap.xml');
+ return self::$_mapper;
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/TSqlMapClient.php b/framework/DataAccess/SQLMap/TSqlMapClient.php new file mode 100644 index 00000000..7662d83e --- /dev/null +++ b/framework/DataAccess/SQLMap/TSqlMapClient.php @@ -0,0 +1,81 @@ +<?php
+
+require_once(dirname(__FILE__).'/TSqlMapper.php');
+
+/**
+ * A DataMapper client class that can load a cached SqlMapper. Give the configuration
+ * file, it looks for a .cache file containing serialized TSqlMapClient instance to
+ * load. Usage:
+ *
+ * <code>
+ * $client = new TSqlMapClient;
+ * $sqlmap = $client->configure($configFile, true); //load from cache.
+ * $products = $sqlMap->queryForList('statementName');
+ * </code>
+ *
+ * To save the TSqlMapper instance to cache for later usage, call
+ * cacheConfiguration().
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $Date: $
+ * @package System.DataAccess.SQLMap
+ * @since 3.0
+ */
+class TSqlMapClient
+{
+ private $_mapper;
+ private $_cache;
+
+ public function configure($configFile, $loadFromCache=false)
+ {
+ if(is_null($this->_mapper))
+ $this->initMapper($configFile, $loadFromCache);
+ return $this->_mapper;
+ }
+
+ public function getInstance()
+ {
+ return $this->_mapper;
+ }
+
+ public function cacheConfiguration()
+ {
+ if(!is_null($this->_mapper) && $this->_cache !== false)
+ {
+ if(!is_file($this->_cache))
+ {
+ var_dump('saving cache to file', $this->_cache);
+ file_put_contents($this->_cache,serialize($this->_mapper));
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected function initMapper($file=null,$loadFromCache=false)
+ {
+ $this->_cache = $this->getCacheFile($file);
+ if($loadFromCache && $this->_cache !== false && is_file($this->_cache))
+ {
+ var_dump('loading from cache: '.$this->_cache);
+ $this->_mapper = unserialize(file_get_contents($this->_cache));
+ }
+ else
+ {
+// var_dump('build from *.xml');
+ $builder = new TDomSqlMapBuilder();
+ $this->_mapper = $builder->configure($file);
+ }
+ }
+
+ protected function getCacheFile($file)
+ {
+ $path = realpath($file);
+ if($path !== false)
+ return substr($path,0, strrpos($path,'.')).'.cache';
+ else
+ return false;
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/SQLMap/TSqlMapper.php b/framework/DataAccess/SQLMap/TSqlMapper.php new file mode 100644 index 00000000..602b83ea --- /dev/null +++ b/framework/DataAccess/SQLMap/TSqlMapper.php @@ -0,0 +1,543 @@ +<?php
+
+Prado::using('System.DataAccess.SQLMap.DataMapper.*');
+Prado::using('System.DataAccess.SQLMap.Configuration.*');
+Prado::using('System.DataAccess.SQLMap.Statements.*');
+Prado::using('System.DataAccess.SQLMap.DataMapper.TTypeHandlerFactory');
+Prado::using('System.DataAccess.SQLMap.DataMapper.TSqlMapCache');
+Prado::using('System.DataAccess.SQLMap.DataMapper.TDataMapperException');
+Prado::using('System.DataAccess.TAdodbProvider');
+
+/**
+ * DataMapper client, a facade to provide access the rest of the DataMapper
+ * framework. It provides three core functions:
+ *
+ * # execute an update query (including insert and delete).
+ * # execute a select query for a single object
+ * # execute a select query for a list of objects
+ *
+ * Do not create this class explicitly, use TDomSqlMapBuilder to obtain
+ * an instance by parsing through the xml configurations. Example:
+ * <code>
+ * $builder = new TDomSqlMapBuilder();
+ * $mapper = $builder->configure($configFile);
+ * </code>
+ *
+ * Otherwise use convient classes TMapper or TSqlMap to obtain singleton
+ * instances.
+ *
+ * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
+ * @version $Revision: $ $Date: $
+ * @package System.DataAccess.SQLMap
+ * @since 3.0
+ */
+class TSqlMapper extends TComponent
+{
+ private $_connection;
+ private $_mappedStatements;
+ private $_provider;
+ private $_resultMaps;
+ private $_parameterMaps;
+ private $_typeHandlerFactory;
+ private $_cacheModelsEnabled = false;
+ private $_cacheMaps;
+
+ /**
+ * Create a new SqlMap.
+ * @param TTypeHandlerFactory
+ */
+ public function __construct($typeHandlerFactory=null)
+ {
+ $this->_mappedStatements = new TMap;
+ $this->_resultMaps = new TMap;
+ $this->_parameterMaps = new TMap;
+ $this->_typeHandlerFactory = $typeHandlerFactory;
+ $this->_cacheMaps = new TMap;
+ }
+
+ /**
+ * Cleanup work before serializing.
+ * This is a PHP defined magic method.
+ * @return array the names of instance-variables to serialize.
+ */
+ public function __sleep()
+ {
+ if(!is_null($this->_connection) && !$this->_connection->getIsClosed())
+ $this->closeConnection();
+ $this->_connection = null;
+ return array_keys(get_object_vars($this));
+ }
+
+ /**
+ * This method will be automatically called when unserialization happens.
+ * This is a PHP defined magic method.
+ */
+ public function __wake()
+ {
+
+ }
+
+ /**
+ * Set the falg to tell us if cache models were enabled or not.
+ * This should only be called during configuration parsing.
+ * It does not disable the cache after the configuration phase.
+ * @param boolean enables cache.
+ */
+ public function setCacheModelsEnabled($value)
+ {
+ $this->_cacheModelsEnabled = $value;
+ }
+
+ /**
+ * @return boolean true if cache models were enabled when this SqlMap was
+ * built.
+ */
+ public function getIsCacheModelsEnabled()
+ {
+ return $this->_cacheModelsEnabled;
+ }
+
+ /**
+ * @return TTypeHandlerFactory The TypeHandlerFactory
+ */
+ public function getTypeHandlerFactory()
+ {
+ return $this->_typeHandlerFactory;
+ }
+
+ /**
+ * @return TMap mapped statements collection.
+ */
+ public function getStatements()
+ {
+ return $this->_mappedStatements;
+ }
+
+ /**
+ * @return TMap result maps collection.
+ */
+ public function getResultMaps()
+ {
+ return $this->_resultMaps;
+ }
+
+ /**
+ * Adds a named cache.
+ * @param TSqlMapCacheModel the cache to add.
+ * @throws TSqlMapConfigurationException
+ */
+ public function addCache(TSqlMapCacheModel $cacheModel)
+ {
+ if($this->_cacheMaps->contains($cacheModel->getID()))
+ throw new TSqlMapConfigurationException(
+ 'sqlmap_cache_model_already_exists', $cacheModel->getID());
+ else
+ $this->_cacheMaps->add($cacheModel->getID(), $cacheModel);
+ }
+
+ /**
+ * Gets a cache by name
+ * @param string the name of the cache to get.
+ * @return TSqlMapCacheModel the cache object.
+ * @throws TSqlMapConfigurationException
+ */
+ public function getCache($name)
+ {
+ if(!$this->_cacheMaps->contains($name))
+ throw new TSqlMapConfigurationException(
+ 'sqlmap_unable_to_find_cache_model', $name);
+ return $this->_cacheMaps[$name];
+ }
+
+ /**
+ * Flushes all cached objects that belong to this SqlMap
+ */
+ public function flushCaches()
+ {
+ foreach($this->_cacheMaps as $cache)
+ $cache->flush();
+ }
+
+ /**
+ * @return TMap parameter maps collection.
+ */
+ public function getParameterMaps()
+ {
+ return $this->_parameterMaps;
+ }
+
+ /**
+ * Gets a MappedStatement by name.
+ * @param string The name of the statement.
+ * @return IMappedStatement The MappedStatement
+ * @throws TSqlMapUndefinedException
+ */
+ public function getMappedStatement($name)
+ {
+ if($this->_mappedStatements->contains($name) == false)
+ throw new TSqlMapUndefinedException(
+ 'sqlmap_contains_no_statement', $name);
+ return $this->_mappedStatements[$name];
+ }
+
+ /**
+ * Adds a (named) MappedStatement.
+ * @param string The key name
+ * @param IMappedStatement The statement to add
+ * @throws TSqlMapDuplicateException
+ */
+ public function addMappedStatement(IMappedStatement $statement)
+ {
+ $key = $statement->getID();
+ if($this->_mappedStatements->contains($key) == true)
+ throw new TSqlMapDuplicateException(
+ 'sqlmap_already_contains_statement', $key);
+ $this->_mappedStatements->add($key, $statement);
+ }
+
+ /**
+ * Gets a named result map
+ * @param string result name.
+ * @return TResultMap the result map.
+ * @throws TSqlMapUndefinedException
+ */
+ public function getResultMap($name)
+ {
+ if($this->_resultMaps->contains($name) == false)
+ throw new TSqlMapUndefinedException(
+ 'sqlmap_contains_no_result_map', $name);
+ return $this->_resultMaps[$name];
+ }
+
+ /**
+ * @param TResultMap add a new result map to this SQLMap
+ * @throws TSqlMapDuplicateException
+ */
+ public function addResultMap(TResultMap $result)
+ {
+ $key = $result->getID();
+ if($this->_resultMaps->contains($key) == true)
+ throw new TSqlMapDuplicateException(
+ 'sqlmap_already_contains_result_map', $key);
+ $this->_resultMaps->add($key, $result);
+ }
+
+ /**
+ * @param string parameter map ID name.
+ * @return TParameterMap the parameter with given ID.
+ * @throws TSqlMapUndefinedException
+ */
+ public function getParameterMap($name)
+ {
+ if($this->_parameterMaps->contains($name) == false)
+ throw new TSqlMapUndefinedException(
+ 'sqlmap_contains_no_parameter_map', $name);
+ return $this->_parameterMaps[$name];
+ }
+
+ /**
+ * @param TParameterMap add a new parameter map to this SQLMap.
+ * @throws TSqlMapDuplicateException
+ */
+ public function addParameterMap(TParameterMap $parameter)
+ {
+ $key = $parameter->getID();
+ if($this->_parameterMaps->contains($key) == true)
+ throw new TSqlMapDuplicateException(
+ 'sqlmap_already_contains_parameter_map', $key);
+ $this->_parameterMaps->add($key, $parameter);
+ }
+
+ /**
+ * @param TDatabaseProvider changes the database provider.
+ */
+ public function setDataProvider($provider)
+ {
+ $this->_provider = $provider;
+ }
+
+ /**
+ * @return TDatabaseProvider database provider.
+ */
+ public function getDataProvider()
+ {
+ return $this->_provider;
+ }
+
+ /**
+ * Get the current connection, opens the connection if necessary.
+ * @return TDbConnection database connection.
+ */
+ protected function getConnection()
+ {
+ if(is_null($this->_connection))
+ $this->_connection = $this->getDataProvider()->getConnection();
+ $this->_connection->open();
+ return $this->_connection;
+ }
+
+ /**
+ * Open a connection, on the specified connection string if provided.
+ * @param string The connection DSN string
+ * @return TDbConnection database connection.
+ */
+ public function openConnection($connectionString=null)
+ {
+ if(!is_null($connectionString))
+ {
+ if(!is_null($this->_connection))
+ throw new TSqlMapConnectionException(
+ 'sqlmap_connection_already_exists');
+ $this->getDataProvider()->setConnectionString($connectionString);
+ }
+ return $this->getConnection();
+ }
+
+ /**
+ * Close the current database connection.
+ */
+ public function closeConnection()
+ {
+ if(is_null($this->_connection))
+ throw new TSqlMapConnectionException(
+ 'sqlmap_unable_to_close_null_connection');
+ $this->_connection->close();
+ }
+
+ /**
+ * Executes a Sql SELECT statement that returns that returns data
+ * to populate a single object instance.
+ *
+ * The parameter object is generally used to supply the input
+ * data for the WHERE clause parameter(s) of the SELECT statement.
+ *
+ * @param string The name of the sql statement to execute.
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param mixed An object of the type to be returned.
+ * @return object A single result object populated with the result set data.
+ */
+ public function queryForObject($statementName, $parameter=null, $result=null)
+ {
+ $statement = $this->getMappedStatement($statementName);
+ $connection = $this->getConnection();
+ return $statement->executeQueryForObject($connection,
+ $parameter, $result);
+ }
+
+ /**
+ * Executes a Sql SELECT statement that returns data to populate a number
+ * of result objects.
+ *
+ * The parameter object is generally used to supply the input
+ * data for the WHERE clause parameter(s) of the SELECT statement.
+ *
+ * @param string The name of the sql statement to execute.
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param TList An Ilist object used to hold the objects,
+ * pass in null if want to return a list instead.
+ * @param int The number of rows to skip over.
+ * @param int The maximum number of rows to return.
+ * @return TList A List of result objects.
+ */
+ public function queryForList($statementName, $parameter=null,
+ $result=null, $skip=-1, $max=-1)
+ {
+ $statement = $this->getMappedStatement($statementName);
+ $connection = $this->getConnection();
+ return $statement->executeQueryForList($connection,
+ $parameter, $result, $skip, $max);
+ }
+
+ /**
+ * Runs a query for list with a custom object that gets a chance to deal
+ * with each row as it is processed.
+ *
+ * Example: $sqlmap->queryWithRowDelegate('getAccounts', array($this, 'rowHandler'));
+ *
+ * @param string The name of the sql statement to execute.
+ * @param callback Row delegate handler, a valid callback required.
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param TList An Ilist object used to hold the objects,
+ * pass in null if want to return a list instead.
+ * @param int The number of rows to skip over.
+ * @param int The maximum number of rows to return.
+ * @return TList A List of result objects.
+ */
+ public function queryWithRowDelegate($statementName, $delegate, $parameter=null,
+ $result=null, $skip=-1, $max=-1)
+ {
+ $statement = $this->getMappedStatement($statementName);
+ $connection = $this->getConnection();
+ return $statement->executeQueryForList($connection,
+ $parameter, $result, $skip, $max, $delegate);
+ }
+
+ /**
+ * Executes the SQL and retuns a subset of the results in a dynamic
+ * TPagedList that can be used to automatically scroll through results
+ * from a database table.
+ * @param string The name of the sql statement to execute.
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param integer The maximum number of objects to store in each page.
+ * @return TPagedList A PaginatedList of beans containing the rows.
+ */
+ public function queryForPagedList($statementName, $parameter=null, $pageSize=10)
+ {
+ $statement = $this->getMappedStatement($statementName);
+ return new TSqlMapPagedList($statement, $parameter, $pageSize);
+ }
+
+ /**
+ * Executes the SQL and retuns a subset of the results in a dynamic
+ * TPagedList that can be used to automatically scroll through results
+ * from a database table.
+ *
+ * Runs paged list query with row delegate
+ * Example: $sqlmap->queryForPagedListWithRowDelegate('getAccounts', array($this, 'rowHandler'));
+ *
+ * @param string The name of the sql statement to execute.
+ * @param callback Row delegate handler, a valid callback required.
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param integer The maximum number of objects to store in each page.
+ * @return TPagedList A PaginatedList of beans containing the rows.
+ */
+ public function queryForPagedListWithRowDelegate($statementName,
+ $delegate, $parameter=null, $pageSize=10)
+ {
+ $statement = $this->getMappedStatement($statementName);
+ return new TSqlMapPagedList($statement, $parameter, $pageSize, $delegate);
+ }
+
+
+ /**
+ * Executes the SQL and retuns all rows selected in a map that is keyed on
+ * the property named in the keyProperty parameter. The value at each key
+ * will be the value of the property specified in the valueProperty
+ * parameter. If valueProperty is null, the entire result object will be
+ * entered.
+ * @param string The name of the sql statement to execute.
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param string The property of the result object to be used as the key.
+ * @param string The property of the result object to be used as the value.
+ * @return TMap Array object containing the rows keyed by keyProperty.
+ */
+ public function queryForMap($statementName, $parameter=null,
+ $keyProperty=null, $valueProperty=null)
+ {
+ $statement = $this->getMappedStatement($statementName);
+ $connection = $this->getConnection();
+ return $statement->executeQueryForMap($connection,
+ $parameter, $keyProperty, $valueProperty);
+ }
+
+ /**
+ * Runs a query with a custom object that gets a chance to deal
+ * with each row as it is processed.
+ *
+ * Example: $sqlmap->queryForMapWithRowDelegate('getAccounts', array($this, 'rowHandler'));
+ *
+ * @param string The name of the sql statement to execute.
+ * @param callback Row delegate handler, a valid callback required.
+ * @param mixed The object used to set the parameters in the SQL.
+ * @param string The property of the result object to be used as the key.
+ * @param string The property of the result object to be used as the value.
+ * @return TMap Array object containing the rows keyed by keyProperty.
+ */
+ public function queryForMapWithRowDelegate($statementName,
+ $delegate, $parameter=null, $keyProperty=null, $valueProperty=null)
+ {
+ $statement = $this->getMappedStatement($statementName);
+ $connection = $this->getConnection();
+ return $statement->executeQueryForMap($connection,
+ $parameter, $keyProperty, $valueProperty, $delegate);
+ }
+
+ /**
+ * Executes a Sql INSERT statement.
+ *
+ * Insert is a bit different from other update methods, as it provides
+ * facilities for returning the primary key of the newly inserted row
+ * (rather than the effected rows),
+ *
+ * The parameter object is generally used to supply the input data for the
+ * INSERT values.
+ *
+ * @param string The name of the statement to execute.
+ * @param string The parameter object.
+ * @return mixed The primary key of the newly inserted row.
+ * This might be automatically generated by the RDBMS,
+ * or selected from a sequence table or other source.
+ */
+ public function insert($statementName, $parameter=null)
+ {
+ $statement = $this->getMappedStatement($statementName);
+ $connection = $this->getConnection();
+ $generatedKey = $statement->executeInsert($connection, $parameter);
+ return $generatedKey;
+ }
+
+ /**
+ * Executes a Sql UPDATE statement.
+ *
+ * Update can also be used for any other update statement type, such as
+ * inserts and deletes. Update returns the number of rows effected.
+ *
+ * The parameter object is generally used to supply the input data for the
+ * UPDATE values as well as the WHERE clause parameter(s).
+ *
+ * @param string The name of the statement to execute.
+ * @param mixed The parameter object.
+ * @return integer The number of rows effected.
+ */
+ public function update($statementName, $parameter=null)
+ {
+ $statement = $this->getMappedStatement($statementName);
+ $connection = $this->getConnection();
+ return $statement->executeUpdate($connection, $parameter);
+ }
+
+ /**
+ * Executes a Sql DELETE statement. Delete returns the number of rows effected.
+ * @param string The name of the statement to execute.
+ * @param mixed The parameter object.
+ * @return integer The number of rows effected.
+ */
+ public function delete($statementName, $parameter=null)
+ {
+ return $this->update($statementName, $parameter);
+ }
+
+
+ /**
+ * Begins a database transaction on the currect session.
+ * Some databases will always return false if transaction support is not
+ * available
+ * @return boolean true if successful, false otherwise.
+ */
+ public function beginTransaction()
+ {
+ return $this->getConnection()->beginTransaction();
+ }
+
+ /**
+ * End a transaction successfully. If the database does not support
+ * transactions, will return true also as data is always committed.
+ * @return boolean true if successful, false otherwise.
+ */
+ public function commitTransaction()
+ {
+ return $this->getConnection()->commit();
+ }
+
+ /**
+ * End a transaction, rollback all changes. If the database does not
+ * support transactions, will return false as data is never rollbacked.
+ * @return boolean true if successful, false otherwise.
+ */
+ public function rollbackTransaction()
+ {
+ return $this->getConnection()->rollback();
+ }
+}
+
+?>
\ No newline at end of file diff --git a/framework/DataAccess/TSQLMap.php b/framework/DataAccess/TSQLMap.php index bc9ba1fb..27973047 100644 --- a/framework/DataAccess/TSQLMap.php +++ b/framework/DataAccess/TSQLMap.php @@ -2,48 +2,54 @@ class TSQLMap extends TModule
{
- private $_SQLMapLibrary='';
private $_configFile;
private $_sqlmap;
- private $_provider;
+ private $_enableCache=false;
/**
* File extension of external configuration file
*/
- const CONFIG_FILE_EXT='.config';
-
- public function getSQLMapLibrary()
+ const CONFIG_FILE_EXT='.xml';
+
+ /**
+ * Saves the current sqlmap instance to cache.
+ * @return boolean true if sqlmap was cached, false otherwise.
+ */
+ protected function cacheSqlMap()
{
- if(strlen($this->_SQLMapLibrary) < 1)
- return dirname(__FILE__).'/SQLMap';
- else
- return $this->_SQLMapLibrary;
+ if($this->getEnableConfigCache())
+ {
+ $cache = $this->getApplication()->getCache();
+ if(!is_null($cache))
+ return $cache->add($this->getID(), $this->_sqlmap);
+ }
+ return false;
}
- public function setSQLMapLibrary($path)
+ /**
+ * Loads sqlmap data mapper instance from cache.
+ * @return boolean true if load was successful, false otherwise.
+ */
+ protected function loadSqlMapCache()
{
- $this->_SQLMapLibrary = Prado::getPathOfNamespace($path);
+ if($this->getEnableConfigCache())
+ {
+ $cache = $this->getApplication()->getCache();
+ Prado::using('System.DataAccess.SQLMap.TSqlMapper');
+ if(!is_null($cache))
+ $this->_sqlmap = $cache->get($this->getID());
+ return $this->_sqlmap instanceof TSqlMapper;
+ }
+ return false;
}
-
+
/**
- * @return string external configuration file. Defaults to null.
+ * @return string sqlmap configuration file.
*/
public function getConfigFile()
{
return $this->_configFile;
- }
-
- public function init($xml)
- {
- $config = $xml->getElementByTagName('provider');
- $class = $config->getAttribute('class');
- $provider = Prado::createComponent($class);
- $datasource = $config->getElementByTagName('datasource');
- $properties = $datasource->getAttributes();
- foreach($properties as $name=>$value)
- $provider->setSubproperty($name,$value);
- $this->_provider = $provider;
- }
+ }
/**
* @param string external configuration file in namespace format. The file
@@ -52,26 +58,68 @@ class TSQLMap extends TModule */
public function setConfigFile($value)
{
- if(($this->_configFile=Prado::getPathOfNamespace(
- $value,self::CONFIG_FILE_EXT))===null)
+ $file = Prado::getPathOfNamespace($value,self::CONFIG_FILE_EXT);
+ if(is_null($file))
throw new TConfigurationException('sqlmap_configfile_invalid',$value);
+ else
+ $this->_configFile = $file;
+ }
+
+ /**
+ * Set true to cache sqlmap instances.
+ * @param boolean true to cache sqlmap instance.
+ */
+ public function setEnableConfigCache($value)
+ {
+ $this->_enableCache = TPropertyValue::ensureBoolean($value, false);
+ }
+
+ /**
+ * @return boolean true if configuration should be cached, false otherwise.
+ */
+ public function getEnableConfigCache()
+ {
+ return $this->_enableCache;
}
+ /**
+ * Configure the data mapper using sqlmap configuration file.
+ * If cache is enabled, the data mapper instance is cached.
+ * @param string sqlmap configuration file.
+ * @return TSqlMapper sqlmap instance.
+ */
protected function configure($configFile)
{
- include($this->getSQLMapLibrary().'/TSqlMapper.php');
+ Prado::using('System.DataAccess.SQLMap.TSqlMapper');
$builder = new TDomSqlMapBuilder();
$this->_sqlmap = $builder->configure($configFile);
- if(!is_null($this->_provider))
- $this->_sqlmap->setDataProvider($this->_provider);
+ $this->cacheSqlMap();
+ return $this->_sqlmap;
}
+ /**
+ * Initialize the sqlmap if necessary, returns the TSqlMapper instance.
+ * @return TSqlMapper data mapper for this module.
+ */
public function getClient()
{
- if(is_null($this->_sqlmap))
+ if(is_null($this->_sqlmap) && !$this->loadSqlMapCache())
$this->configure($this->getConfigFile());
return $this->_sqlmap;
}
+
+ /**
+ * This magic method allows this TSQLMap module to be treated like
+ * TSqlMapper instance.
+ * @param string calling method name
+ * @param array calling parameters
+ * @return mixed data obtained from TSqlMapper method call.
+ */
+ public function __call($method, $params)
+ {
+ $client = $this->getClient();
+ return call_user_func_array(array($client,$method),$params);
+ }
}
?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb-csvlib.inc.php b/framework/DataAccess/adodb/adodb-csvlib.inc.php new file mode 100644 index 00000000..6653fffb --- /dev/null +++ b/framework/DataAccess/adodb/adodb-csvlib.inc.php @@ -0,0 +1,312 @@ +<?php + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +global $ADODB_INCLUDED_CSV; +$ADODB_INCLUDED_CSV = 1; + +/* + + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. See License.txt. + Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Library for CSV serialization. This is used by the csv/proxy driver and is the + CacheExecute() serialization format. + + ==== NOTE ==== + Format documented at http://php.weblogs.com/ADODB_CSV + ============== +*/ + + /** + * convert a recordset into special format + * + * @param rs the recordset + * + * @return the CSV formated data + */ + function _rs2serialize(&$rs,$conn=false,$sql='') + { + $max = ($rs) ? $rs->FieldCount() : 0; + + if ($sql) $sql = urlencode($sql); + // metadata setup + + if ($max <= 0 || $rs->dataProvider == 'empty') { // is insert/update/delete + if (is_object($conn)) { + $sql .= ','.$conn->Affected_Rows(); + $sql .= ','.$conn->Insert_ID(); + } else + $sql .= ',,'; + + $text = "====-1,0,$sql\n"; + return $text; + } + $tt = ($rs->timeCreated) ? $rs->timeCreated : time(); + + ## changed format from ====0 to ====1 + $line = "====1,$tt,$sql\n"; + + if ($rs->databaseType == 'array') { + $rows =& $rs->_array; + } else { + $rows = array(); + while (!$rs->EOF) { + $rows[] = $rs->fields; + $rs->MoveNext(); + } + } + + for($i=0; $i < $max; $i++) { + $o =& $rs->FetchField($i); + $flds[] = $o; + } + + $savefetch = isset($rs->adodbFetchMode) ? $rs->adodbFetchMode : $rs->fetchMode; + $class = $rs->connection->arrayClass; + $rs2 = new $class(); + $rs2->sql = $rs->sql; + $rs2->oldProvider = $rs->dataProvider; + $rs2->InitArrayFields($rows,$flds); + $rs2->fetchMode = $savefetch; + return $line.serialize($rs2); + } + + +/** +* Open CSV file and convert it into Data. +* +* @param url file/ftp/http url +* @param err returns the error message +* @param timeout dispose if recordset has been alive for $timeout secs +* +* @return recordset, or false if error occured. If no +* error occurred in sql INSERT/UPDATE/DELETE, +* empty recordset is returned +*/ + function &csv2rs($url,&$err,$timeout=0, $rsclass='ADORecordSet_array') + { + $false = false; + $err = false; + $fp = @fopen($url,'rb'); + if (!$fp) { + $err = $url.' file/URL not found'; + return $false; + } + @flock($fp, LOCK_SH); + $arr = array(); + $ttl = 0; + + if ($meta = fgetcsv($fp, 32000, ",")) { + // check if error message + if (strncmp($meta[0],'****',4) === 0) { + $err = trim(substr($meta[0],4,1024)); + fclose($fp); + return $false; + } + // check for meta data + // $meta[0] is -1 means return an empty recordset + // $meta[1] contains a time + + if (strncmp($meta[0], '====',4) === 0) { + + if ($meta[0] == "====-1") { + if (sizeof($meta) < 5) { + $err = "Corrupt first line for format -1"; + fclose($fp); + return $false; + } + fclose($fp); + + if ($timeout > 0) { + $err = " Illegal Timeout $timeout "; + return $false; + } + + $rs = new $rsclass($val=true); + $rs->fields = array(); + $rs->timeCreated = $meta[1]; + $rs->EOF = true; + $rs->_numOfFields = 0; + $rs->sql = urldecode($meta[2]); + $rs->affectedrows = (integer)$meta[3]; + $rs->insertid = $meta[4]; + return $rs; + } + # Under high volume loads, we want only 1 thread/process to _write_file + # so that we don't have 50 processes queueing to write the same data. + # We use probabilistic timeout, ahead of time. + # + # -4 sec before timeout, give processes 1/32 chance of timing out + # -2 sec before timeout, give processes 1/16 chance of timing out + # -1 sec after timeout give processes 1/4 chance of timing out + # +0 sec after timeout, give processes 100% chance of timing out + if (sizeof($meta) > 1) { + if($timeout >0){ + $tdiff = (integer)( $meta[1]+$timeout - time()); + if ($tdiff <= 2) { + switch($tdiff) { + case 4: + case 3: + if ((rand() & 31) == 0) { + fclose($fp); + $err = "Timeout 3"; + return $false; + } + break; + case 2: + if ((rand() & 15) == 0) { + fclose($fp); + $err = "Timeout 2"; + return $false; + } + break; + case 1: + if ((rand() & 3) == 0) { + fclose($fp); + $err = "Timeout 1"; + return $false; + } + break; + default: + fclose($fp); + $err = "Timeout 0"; + return $false; + } // switch + + } // if check flush cache + }// (timeout>0) + $ttl = $meta[1]; + } + //================================================ + // new cache format - use serialize extensively... + if ($meta[0] === '====1') { + // slurp in the data + $MAXSIZE = 128000; + + $text = fread($fp,$MAXSIZE); + if (strlen($text)) { + while ($txt = fread($fp,$MAXSIZE)) { + $text .= $txt; + } + } + fclose($fp); + $rs = unserialize($text); + if (is_object($rs)) $rs->timeCreated = $ttl; + else { + $err = "Unable to unserialize recordset"; + //echo htmlspecialchars($text),' !--END--!<p>'; + } + return $rs; + } + + $meta = false; + $meta = fgetcsv($fp, 32000, ","); + if (!$meta) { + fclose($fp); + $err = "Unexpected EOF 1"; + return $false; + } + } + + // Get Column definitions + $flds = array(); + foreach($meta as $o) { + $o2 = explode(':',$o); + if (sizeof($o2)!=3) { + $arr[] = $meta; + $flds = false; + break; + } + $fld = new ADOFieldObject(); + $fld->name = urldecode($o2[0]); + $fld->type = $o2[1]; + $fld->max_length = $o2[2]; + $flds[] = $fld; + } + } else { + fclose($fp); + $err = "Recordset had unexpected EOF 2"; + return $false; + } + + // slurp in the data + $MAXSIZE = 128000; + + $text = ''; + while ($txt = fread($fp,$MAXSIZE)) { + $text .= $txt; + } + + fclose($fp); + @$arr = unserialize($text); + //var_dump($arr); + if (!is_array($arr)) { + $err = "Recordset had unexpected EOF (in serialized recordset)"; + if (get_magic_quotes_runtime()) $err .= ". Magic Quotes Runtime should be disabled!"; + return $false; + } + $rs = new $rsclass(); + $rs->timeCreated = $ttl; + $rs->InitArrayFields($arr,$flds); + return $rs; + } + + + /** + * Save a file $filename and its $contents (normally for caching) with file locking + */ + function adodb_write_file($filename, $contents,$debug=false) + { + # http://www.php.net/bugs.php?id=9203 Bug that flock fails on Windows + # So to simulate locking, we assume that rename is an atomic operation. + # First we delete $filename, then we create a $tempfile write to it and + # rename to the desired $filename. If the rename works, then we successfully + # modified the file exclusively. + # What a stupid need - having to simulate locking. + # Risks: + # 1. $tempfile name is not unique -- very very low + # 2. unlink($filename) fails -- ok, rename will fail + # 3. adodb reads stale file because unlink fails -- ok, $rs timeout occurs + # 4. another process creates $filename between unlink() and rename() -- ok, rename() fails and cache updated + if (strncmp(PHP_OS,'WIN',3) === 0) { + // skip the decimal place + $mtime = substr(str_replace(' ','_',microtime()),2); + // getmypid() actually returns 0 on Win98 - never mind! + $tmpname = $filename.uniqid($mtime).getmypid(); + if (!($fd = @fopen($tmpname,'a'))) return false; + $ok = ftruncate($fd,0); + if (!fwrite($fd,$contents)) $ok = false; + fclose($fd); + chmod($tmpname,0644); + // the tricky moment + @unlink($filename); + if (!@rename($tmpname,$filename)) { + unlink($tmpname); + $ok = false; + } + if (!$ok) { + if ($debug) ADOConnection::outp( " Rename $tmpname ".($ok? 'ok' : 'failed')); + } + return $ok; + } + if (!($fd = @fopen($filename, 'a'))) return false; + if (flock($fd, LOCK_EX) && ftruncate($fd, 0)) { + $ok = fwrite( $fd, $contents ); + fclose($fd); + chmod($filename,0644); + }else { + fclose($fd); + if ($debug)ADOConnection::outp( " Failed acquiring lock for $filename<br>\n"); + $ok = false; + } + + return $ok; + } +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb-datadict.inc.php b/framework/DataAccess/adodb/adodb-datadict.inc.php new file mode 100644 index 00000000..27996aa5 --- /dev/null +++ b/framework/DataAccess/adodb/adodb-datadict.inc.php @@ -0,0 +1,784 @@ +<?php + +/** + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + + Set tabs to 4 for best viewing. + + DOCUMENTATION: + + See adodb/tests/test-datadict.php for docs and examples. +*/ + +/* + Test script for parser +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +function Lens_ParseTest() +{ +$str = "`zcol ACOL` NUMBER(32,2) DEFAULT 'The \"cow\" (and Jim''s dog) jumps over the moon' PRIMARY, INTI INT AUTO DEFAULT 0, zcol2\"afs ds"; +print "<p>$str</p>"; +$a= Lens_ParseArgs($str); +print "<pre>"; +print_r($a); +print "</pre>"; +} + + +if (!function_exists('ctype_alnum')) { + function ctype_alnum($text) { + return preg_match('/^[a-z0-9]*$/i', $text); + } +} + +//Lens_ParseTest(); + +/** + Parse arguments, treat "text" (text) and 'text' as quotation marks. + To escape, use "" or '' or )) + + Will read in "abc def" sans quotes, as: abc def + Same with 'abc def'. + However if `abc def`, then will read in as `abc def` + + @param endstmtchar Character that indicates end of statement + @param tokenchars Include the following characters in tokens apart from A-Z and 0-9 + @returns 2 dimensional array containing parsed tokens. +*/ +function Lens_ParseArgs($args,$endstmtchar=',',$tokenchars='_.-') +{ + $pos = 0; + $intoken = false; + $stmtno = 0; + $endquote = false; + $tokens = array(); + $tokens[$stmtno] = array(); + $max = strlen($args); + $quoted = false; + $tokarr = array(); + + while ($pos < $max) { + $ch = substr($args,$pos,1); + switch($ch) { + case ' ': + case "\t": + case "\n": + case "\r": + if (!$quoted) { + if ($intoken) { + $intoken = false; + $tokens[$stmtno][] = implode('',$tokarr); + } + break; + } + + $tokarr[] = $ch; + break; + + case '`': + if ($intoken) $tokarr[] = $ch; + case '(': + case ')': + case '"': + case "'": + + if ($intoken) { + if (empty($endquote)) { + $tokens[$stmtno][] = implode('',$tokarr); + if ($ch == '(') $endquote = ')'; + else $endquote = $ch; + $quoted = true; + $intoken = true; + $tokarr = array(); + } else if ($endquote == $ch) { + $ch2 = substr($args,$pos+1,1); + if ($ch2 == $endquote) { + $pos += 1; + $tokarr[] = $ch2; + } else { + $quoted = false; + $intoken = false; + $tokens[$stmtno][] = implode('',$tokarr); + $endquote = ''; + } + } else + $tokarr[] = $ch; + + }else { + + if ($ch == '(') $endquote = ')'; + else $endquote = $ch; + $quoted = true; + $intoken = true; + $tokarr = array(); + if ($ch == '`') $tokarr[] = '`'; + } + break; + + default: + + if (!$intoken) { + if ($ch == $endstmtchar) { + $stmtno += 1; + $tokens[$stmtno] = array(); + break; + } + + $intoken = true; + $quoted = false; + $endquote = false; + $tokarr = array(); + + } + + if ($quoted) $tokarr[] = $ch; + else if (ctype_alnum($ch) || strpos($tokenchars,$ch) !== false) $tokarr[] = $ch; + else { + if ($ch == $endstmtchar) { + $tokens[$stmtno][] = implode('',$tokarr); + $stmtno += 1; + $tokens[$stmtno] = array(); + $intoken = false; + $tokarr = array(); + break; + } + $tokens[$stmtno][] = implode('',$tokarr); + $tokens[$stmtno][] = $ch; + $intoken = false; + } + } + $pos += 1; + } + if ($intoken) $tokens[$stmtno][] = implode('',$tokarr); + + return $tokens; +} + + +class ADODB_DataDict { + var $connection; + var $debug = false; + var $dropTable = 'DROP TABLE %s'; + var $renameTable = 'RENAME TABLE %s TO %s'; + var $dropIndex = 'DROP INDEX %s'; + var $addCol = ' ADD'; + var $alterCol = ' ALTER COLUMN'; + var $dropCol = ' DROP COLUMN'; + var $renameColumn = 'ALTER TABLE %s RENAME COLUMN %s TO %s'; // table, old-column, new-column, column-definitions (not used by default) + var $nameRegex = '\w'; + var $nameRegexBrackets = 'a-zA-Z0-9_\(\)'; + var $schema = false; + var $serverInfo = array(); + var $autoIncrement = false; + var $dataProvider; + var $invalidResizeTypes4 = array('CLOB','BLOB','TEXT','DATE','TIME'); // for changetablesql + var $blobSize = 100; /// any varchar/char field this size or greater is treated as a blob + /// in other words, we use a text area for editting. + + function GetCommentSQL($table,$col) + { + return false; + } + + function SetCommentSQL($table,$col,$cmt) + { + return false; + } + + function MetaTables() + { + if (!$this->connection->IsConnected()) return array(); + return $this->connection->MetaTables(); + } + + function MetaColumns($tab, $upper=true, $schema=false) + { + if (!$this->connection->IsConnected()) return array(); + return $this->connection->MetaColumns($this->TableName($tab), $upper, $schema); + } + + function MetaPrimaryKeys($tab,$owner=false,$intkey=false) + { + if (!$this->connection->IsConnected()) return array(); + return $this->connection->MetaPrimaryKeys($this->TableName($tab), $owner, $intkey); + } + + function MetaIndexes($table, $primary = false, $owner = false) + { + if (!$this->connection->IsConnected()) return array(); + return $this->connection->MetaIndexes($this->TableName($table), $primary, $owner); + } + + function MetaType($t,$len=-1,$fieldobj=false) + { + return ADORecordSet::MetaType($t,$len,$fieldobj); + } + + function NameQuote($name = NULL,$allowBrackets=false) + { + if (!is_string($name)) { + return FALSE; + } + + $name = trim($name); + + if ( !is_object($this->connection) ) { + return $name; + } + + $quote = $this->connection->nameQuote; + + // if name is of the form `name`, quote it + if ( preg_match('/^`(.+)`$/', $name, $matches) ) { + return $quote . $matches[1] . $quote; + } + + // if name contains special characters, quote it + $regex = ($allowBrackets) ? $this->nameRegexBrackets : $this->nameRegex; + + if ( !preg_match('/^[' . $regex . ']+$/', $name) ) { + return $quote . $name . $quote; + } + + return $name; + } + + function TableName($name) + { + if ( $this->schema ) { + return $this->NameQuote($this->schema) .'.'. $this->NameQuote($name); + } + return $this->NameQuote($name); + } + + // Executes the sql array returned by GetTableSQL and GetIndexSQL + function ExecuteSQLArray($sql, $continueOnError = true) + { + $rez = 2; + $conn = &$this->connection; + $saved = $conn->debug; + foreach($sql as $line) { + + if ($this->debug) $conn->debug = true; + $ok = $conn->Execute($line); + $conn->debug = $saved; + if (!$ok) { + if ($this->debug) ADOConnection::outp($conn->ErrorMsg()); + if (!$continueOnError) return 0; + $rez = 1; + } + } + return $rez; + } + + /* + Returns the actual type given a character code. + + C: varchar + X: CLOB (character large object) or largest varchar size if CLOB is not supported + C2: Multibyte varchar + X2: Multibyte CLOB + + B: BLOB (binary large object) + + D: Date + T: Date-time + L: Integer field suitable for storing booleans (0 or 1) + I: Integer + F: Floating point number + N: Numeric or decimal number + */ + + function ActualType($meta) + { + return $meta; + } + + function CreateDatabase($dbname,$options=false) + { + $options = $this->_Options($options); + $sql = array(); + + $s = 'CREATE DATABASE ' . $this->NameQuote($dbname); + if (isset($options[$this->upperName])) + $s .= ' '.$options[$this->upperName]; + + $sql[] = $s; + return $sql; + } + + /* + Generates the SQL to create index. Returns an array of sql strings. + */ + function CreateIndexSQL($idxname, $tabname, $flds, $idxoptions = false) + { + if (!is_array($flds)) { + $flds = explode(',',$flds); + } + + foreach($flds as $key => $fld) { + # some indexes can use partial fields, eg. index first 32 chars of "name" with NAME(32) + $flds[$key] = $this->NameQuote($fld,$allowBrackets=true); + } + + return $this->_IndexSQL($this->NameQuote($idxname), $this->TableName($tabname), $flds, $this->_Options($idxoptions)); + } + + function DropIndexSQL ($idxname, $tabname = NULL) + { + return array(sprintf($this->dropIndex, $this->NameQuote($idxname), $this->TableName($tabname))); + } + + function SetSchema($schema) + { + $this->schema = $schema; + } + + function AddColumnSQL($tabname, $flds) + { + $tabname = $this->TableName ($tabname); + $sql = array(); + list($lines,$pkey) = $this->_GenFields($flds); + $alter = 'ALTER TABLE ' . $tabname . $this->addCol . ' '; + foreach($lines as $v) { + $sql[] = $alter . $v; + } + return $sql; + } + + /** + * Change the definition of one column + * + * As some DBM's can't do that on there own, you need to supply the complete defintion of the new table, + * to allow, recreating the table and copying the content over to the new table + * @param string $tabname table-name + * @param string $flds column-name and type for the changed column + * @param string $tableflds='' complete defintion of the new table, eg. for postgres, default '' + * @param array/string $tableoptions='' options for the new table see CreateTableSQL, default '' + * @return array with SQL strings + */ + function AlterColumnSQL($tabname, $flds, $tableflds='',$tableoptions='') + { + $tabname = $this->TableName ($tabname); + $sql = array(); + list($lines,$pkey) = $this->_GenFields($flds); + $alter = 'ALTER TABLE ' . $tabname . $this->alterCol . ' '; + foreach($lines as $v) { + $sql[] = $alter . $v; + } + return $sql; + } + + /** + * Rename one column + * + * Some DBM's can only do this together with changeing the type of the column (even if that stays the same, eg. mysql) + * @param string $tabname table-name + * @param string $oldcolumn column-name to be renamed + * @param string $newcolumn new column-name + * @param string $flds='' complete column-defintion-string like for AddColumnSQL, only used by mysql atm., default='' + * @return array with SQL strings + */ + function RenameColumnSQL($tabname,$oldcolumn,$newcolumn,$flds='') + { + $tabname = $this->TableName ($tabname); + if ($flds) { + list($lines,$pkey) = $this->_GenFields($flds); + list(,$first) = each($lines); + list(,$column_def) = split("[\t ]+",$first,2); + } + return array(sprintf($this->renameColumn,$tabname,$this->NameQuote($oldcolumn),$this->NameQuote($newcolumn),$column_def)); + } + + /** + * Drop one column + * + * Some DBM's can't do that on there own, you need to supply the complete defintion of the new table, + * to allow, recreating the table and copying the content over to the new table + * @param string $tabname table-name + * @param string $flds column-name and type for the changed column + * @param string $tableflds='' complete defintion of the new table, eg. for postgres, default '' + * @param array/string $tableoptions='' options for the new table see CreateTableSQL, default '' + * @return array with SQL strings + */ + function DropColumnSQL($tabname, $flds, $tableflds='',$tableoptions='') + { + $tabname = $this->TableName ($tabname); + if (!is_array($flds)) $flds = explode(',',$flds); + $sql = array(); + $alter = 'ALTER TABLE ' . $tabname . $this->dropCol . ' '; + foreach($flds as $v) { + $sql[] = $alter . $this->NameQuote($v); + } + return $sql; + } + + function DropTableSQL($tabname) + { + return array (sprintf($this->dropTable, $this->TableName($tabname))); + } + + function RenameTableSQL($tabname,$newname) + { + return array (sprintf($this->renameTable, $this->TableName($tabname),$this->TableName($newname))); + } + + /* + Generate the SQL to create table. Returns an array of sql strings. + */ + function CreateTableSQL($tabname, $flds, $tableoptions=false) + { + if (!$tableoptions) $tableoptions = array(); + + list($lines,$pkey) = $this->_GenFields($flds, true); + + $taboptions = $this->_Options($tableoptions); + $tabname = $this->TableName ($tabname); + $sql = $this->_TableSQL($tabname,$lines,$pkey,$taboptions); + + $tsql = $this->_Triggers($tabname,$taboptions); + foreach($tsql as $s) $sql[] = $s; + + return $sql; + } + + function _GenFields($flds,$widespacing=false) + { + if (is_string($flds)) { + $padding = ' '; + $txt = $flds.$padding; + $flds = array(); + $flds0 = Lens_ParseArgs($txt,','); + $hasparam = false; + foreach($flds0 as $f0) { + $f1 = array(); + foreach($f0 as $token) { + switch (strtoupper($token)) { + case 'CONSTRAINT': + case 'DEFAULT': + $hasparam = $token; + break; + default: + if ($hasparam) $f1[$hasparam] = $token; + else $f1[] = $token; + $hasparam = false; + break; + } + } + $flds[] = $f1; + + } + } + $this->autoIncrement = false; + $lines = array(); + $pkey = array(); + foreach($flds as $fld) { + $fld = _array_change_key_case($fld); + + $fname = false; + $fdefault = false; + $fautoinc = false; + $ftype = false; + $fsize = false; + $fprec = false; + $fprimary = false; + $fnoquote = false; + $fdefts = false; + $fdefdate = false; + $fconstraint = false; + $fnotnull = false; + $funsigned = false; + + //----------------- + // Parse attributes + foreach($fld as $attr => $v) { + if ($attr == 2 && is_numeric($v)) $attr = 'SIZE'; + else if (is_numeric($attr) && $attr > 1 && !is_numeric($v)) $attr = strtoupper($v); + + switch($attr) { + case '0': + case 'NAME': $fname = $v; break; + case '1': + case 'TYPE': $ty = $v; $ftype = $this->ActualType(strtoupper($v)); break; + + case 'SIZE': + $dotat = strpos($v,'.'); if ($dotat === false) $dotat = strpos($v,','); + if ($dotat === false) $fsize = $v; + else { + $fsize = substr($v,0,$dotat); + $fprec = substr($v,$dotat+1); + } + break; + case 'UNSIGNED': $funsigned = true; break; + case 'AUTOINCREMENT': + case 'AUTO': $fautoinc = true; $fnotnull = true; break; + case 'KEY': + case 'PRIMARY': $fprimary = $v; $fnotnull = true; break; + case 'DEF': + case 'DEFAULT': $fdefault = $v; break; + case 'NOTNULL': $fnotnull = $v; break; + case 'NOQUOTE': $fnoquote = $v; break; + case 'DEFDATE': $fdefdate = $v; break; + case 'DEFTIMESTAMP': $fdefts = $v; break; + case 'CONSTRAINT': $fconstraint = $v; break; + } //switch + } // foreach $fld + + //-------------------- + // VALIDATE FIELD INFO + if (!strlen($fname)) { + if ($this->debug) ADOConnection::outp("Undefined NAME"); + return false; + } + + $fid = strtoupper(preg_replace('/^`(.+)`$/', '$1', $fname)); + $fname = $this->NameQuote($fname); + + if (!strlen($ftype)) { + if ($this->debug) ADOConnection::outp("Undefined TYPE for field '$fname'"); + return false; + } else { + $ftype = strtoupper($ftype); + } + + $ftype = $this->_GetSize($ftype, $ty, $fsize, $fprec); + + if ($ty == 'X' || $ty == 'X2' || $ty == 'B') $fnotnull = false; // some blob types do not accept nulls + + if ($fprimary) $pkey[] = $fname; + + // some databases do not allow blobs to have defaults + if ($ty == 'X') $fdefault = false; + + //-------------------- + // CONSTRUCT FIELD SQL + if ($fdefts) { + if (substr($this->connection->databaseType,0,5) == 'mysql') { + $ftype = 'TIMESTAMP'; + } else { + $fdefault = $this->connection->sysTimeStamp; + } + } else if ($fdefdate) { + if (substr($this->connection->databaseType,0,5) == 'mysql') { + $ftype = 'TIMESTAMP'; + } else { + $fdefault = $this->connection->sysDate; + } + } else if ($fdefault !== false && !$fnoquote) + if ($ty == 'C' or $ty == 'X' or + ( substr($fdefault,0,1) != "'" && !is_numeric($fdefault))) + if (strlen($fdefault) != 1 && substr($fdefault,0,1) == ' ' && substr($fdefault,strlen($fdefault)-1) == ' ') + $fdefault = trim($fdefault); + else if (strtolower($fdefault) != 'null') + $fdefault = $this->connection->qstr($fdefault); + $suffix = $this->_CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint,$funsigned); + + if ($widespacing) $fname = str_pad($fname,24); + $lines[$fid] = $fname.' '.$ftype.$suffix; + + if ($fautoinc) $this->autoIncrement = true; + } // foreach $flds + + return array($lines,$pkey); + } + /* + GENERATE THE SIZE PART OF THE DATATYPE + $ftype is the actual type + $ty is the type defined originally in the DDL + */ + function _GetSize($ftype, $ty, $fsize, $fprec) + { + if (strlen($fsize) && $ty != 'X' && $ty != 'B' && strpos($ftype,'(') === false) { + $ftype .= "(".$fsize; + if (strlen($fprec)) $ftype .= ",".$fprec; + $ftype .= ')'; + } + return $ftype; + } + + + // return string must begin with space + function _CreateSuffix($fname,$ftype,$fnotnull,$fdefault,$fautoinc,$fconstraint) + { + $suffix = ''; + if (strlen($fdefault)) $suffix .= " DEFAULT $fdefault"; + if ($fnotnull) $suffix .= ' NOT NULL'; + if ($fconstraint) $suffix .= ' '.$fconstraint; + return $suffix; + } + + function _IndexSQL($idxname, $tabname, $flds, $idxoptions) + { + $sql = array(); + + if ( isset($idxoptions['REPLACE']) || isset($idxoptions['DROP']) ) { + $sql[] = sprintf ($this->dropIndex, $idxname); + if ( isset($idxoptions['DROP']) ) + return $sql; + } + + if ( empty ($flds) ) { + return $sql; + } + + $unique = isset($idxoptions['UNIQUE']) ? ' UNIQUE' : ''; + + $s = 'CREATE' . $unique . ' INDEX ' . $idxname . ' ON ' . $tabname . ' '; + + if ( isset($idxoptions[$this->upperName]) ) + $s .= $idxoptions[$this->upperName]; + + if ( is_array($flds) ) + $flds = implode(', ',$flds); + $s .= '(' . $flds . ')'; + $sql[] = $s; + + return $sql; + } + + function _DropAutoIncrement($tabname) + { + return false; + } + + function _TableSQL($tabname,$lines,$pkey,$tableoptions) + { + $sql = array(); + + if (isset($tableoptions['REPLACE']) || isset ($tableoptions['DROP'])) { + $sql[] = sprintf($this->dropTable,$tabname); + if ($this->autoIncrement) { + $sInc = $this->_DropAutoIncrement($tabname); + if ($sInc) $sql[] = $sInc; + } + if ( isset ($tableoptions['DROP']) ) { + return $sql; + } + } + $s = "CREATE TABLE $tabname (\n"; + $s .= implode(",\n", $lines); + if (sizeof($pkey)>0) { + $s .= ",\n PRIMARY KEY ("; + $s .= implode(", ",$pkey).")"; + } + if (isset($tableoptions['CONSTRAINTS'])) + $s .= "\n".$tableoptions['CONSTRAINTS']; + + if (isset($tableoptions[$this->upperName.'_CONSTRAINTS'])) + $s .= "\n".$tableoptions[$this->upperName.'_CONSTRAINTS']; + + $s .= "\n)"; + if (isset($tableoptions[$this->upperName])) $s .= $tableoptions[$this->upperName]; + $sql[] = $s; + + return $sql; + } + + /* + GENERATE TRIGGERS IF NEEDED + used when table has auto-incrementing field that is emulated using triggers + */ + function _Triggers($tabname,$taboptions) + { + return array(); + } + + /* + Sanitize options, so that array elements with no keys are promoted to keys + */ + function _Options($opts) + { + if (!is_array($opts)) return array(); + $newopts = array(); + foreach($opts as $k => $v) { + if (is_numeric($k)) $newopts[strtoupper($v)] = $v; + else $newopts[strtoupper($k)] = $v; + } + return $newopts; + } + + /* + "Florian Buzin [ easywe ]" <florian.buzin#easywe.de> + + This function changes/adds new fields to your table. You don't + have to know if the col is new or not. It will check on its own. + */ + function ChangeTableSQL($tablename, $flds, $tableoptions = false) + { + global $ADODB_FETCH_MODE; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; + if ($this->connection->fetchMode !== false) $savem = $this->connection->SetFetchMode(false); + + // check table exists + $save_handler = $this->connection->raiseErrorFn; + $this->connection->raiseErrorFn = ''; + $cols = $this->MetaColumns($tablename); + $this->connection->raiseErrorFn = $save_handler; + + if (isset($savem)) $this->connection->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if ( empty($cols)) { + return $this->CreateTableSQL($tablename, $flds, $tableoptions); + } + + if (is_array($flds)) { + // Cycle through the update fields, comparing + // existing fields to fields to update. + // if the Metatype and size is exactly the + // same, ignore - by Mark Newham + $holdflds = array(); + foreach($flds as $k=>$v) { + if ( isset($cols[$k]) && is_object($cols[$k]) ) { + // If already not allowing nulls, then don't change + $obj = $cols[$k]; + if (isset($obj->not_null) && $obj->not_null) + $v = str_replace('NOT NULL','',$v); + + $c = $cols[$k]; + $ml = $c->max_length; + $mt = $this->MetaType($c->type,$ml); + if ($ml == -1) $ml = ''; + if ($mt == 'X') $ml = $v['SIZE']; + if (($mt != $v['TYPE']) || $ml != $v['SIZE']) { + $holdflds[$k] = $v; + } + } else { + $holdflds[$k] = $v; + } + } + $flds = $holdflds; + } + + + // already exists, alter table instead + list($lines,$pkey) = $this->_GenFields($flds); + $alter = 'ALTER TABLE ' . $this->TableName($tablename); + $sql = array(); + + foreach ( $lines as $id => $v ) { + if ( isset($cols[$id]) && is_object($cols[$id]) ) { + + $flds = Lens_ParseArgs($v,','); + + // We are trying to change the size of the field, if not allowed, simply ignore the request. + if ($flds && in_array(strtoupper(substr($flds[0][1],0,4)),$this->invalidResizeTypes4)) continue; + + $sql[] = $alter . $this->alterCol . ' ' . $v; + } else { + $sql[] = $alter . $this->addCol . ' ' . $v; + } + } + + return $sql; + } +} // class +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb-error.inc.php b/framework/DataAccess/adodb/adodb-error.inc.php new file mode 100644 index 00000000..9b7c4812 --- /dev/null +++ b/framework/DataAccess/adodb/adodb-error.inc.php @@ -0,0 +1,258 @@ +<?php +/** + * @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + * Released under both BSD license and Lesser GPL library license. + * Whenever there is any discrepancy between the two licenses, + * the BSD license will take precedence. + * + * Set tabs to 4 for best viewing. + * + * The following code is adapted from the PEAR DB error handling code. + * Portions (c)1997-2002 The PHP Group. + */ + + +if (!defined("DB_ERROR")) define("DB_ERROR",-1); + +if (!defined("DB_ERROR_SYNTAX")) { + define("DB_ERROR_SYNTAX", -2); + define("DB_ERROR_CONSTRAINT", -3); + define("DB_ERROR_NOT_FOUND", -4); + define("DB_ERROR_ALREADY_EXISTS", -5); + define("DB_ERROR_UNSUPPORTED", -6); + define("DB_ERROR_MISMATCH", -7); + define("DB_ERROR_INVALID", -8); + define("DB_ERROR_NOT_CAPABLE", -9); + define("DB_ERROR_TRUNCATED", -10); + define("DB_ERROR_INVALID_NUMBER", -11); + define("DB_ERROR_INVALID_DATE", -12); + define("DB_ERROR_DIVZERO", -13); + define("DB_ERROR_NODBSELECTED", -14); + define("DB_ERROR_CANNOT_CREATE", -15); + define("DB_ERROR_CANNOT_DELETE", -16); + define("DB_ERROR_CANNOT_DROP", -17); + define("DB_ERROR_NOSUCHTABLE", -18); + define("DB_ERROR_NOSUCHFIELD", -19); + define("DB_ERROR_NEED_MORE_DATA", -20); + define("DB_ERROR_NOT_LOCKED", -21); + define("DB_ERROR_VALUE_COUNT_ON_ROW", -22); + define("DB_ERROR_INVALID_DSN", -23); + define("DB_ERROR_CONNECT_FAILED", -24); + define("DB_ERROR_EXTENSION_NOT_FOUND",-25); + define("DB_ERROR_NOSUCHDB", -25); + define("DB_ERROR_ACCESS_VIOLATION", -26); +} + +function adodb_errormsg($value) +{ +global $ADODB_LANG,$ADODB_LANG_ARRAY; + + if (empty($ADODB_LANG)) $ADODB_LANG = 'en'; + if (isset($ADODB_LANG_ARRAY['LANG']) && $ADODB_LANG_ARRAY['LANG'] == $ADODB_LANG) ; + else { + include_once(ADODB_DIR."/lang/adodb-$ADODB_LANG.inc.php"); + } + return isset($ADODB_LANG_ARRAY[$value]) ? $ADODB_LANG_ARRAY[$value] : $ADODB_LANG_ARRAY[DB_ERROR]; +} + +function adodb_error($provider,$dbType,$errno) +{ + //var_dump($errno); + if (is_numeric($errno) && $errno == 0) return 0; + switch($provider) { + case 'mysql': $map = adodb_error_mysql(); break; + + case 'oracle': + case 'oci8': $map = adodb_error_oci8(); break; + + case 'ibase': $map = adodb_error_ibase(); break; + + case 'odbc': $map = adodb_error_odbc(); break; + + case 'mssql': + case 'sybase': $map = adodb_error_mssql(); break; + + case 'informix': $map = adodb_error_ifx(); break; + + case 'postgres': return adodb_error_pg($errno); break; + + case 'sqlite': return $map = adodb_error_sqlite(); break; + default: + return DB_ERROR; + } + //print_r($map); + //var_dump($errno); + if (isset($map[$errno])) return $map[$errno]; + return DB_ERROR; +} + +//************************************************************************************** + +function adodb_error_pg($errormsg) +{ + if (is_numeric($errormsg)) return (integer) $errormsg; + static $error_regexps = array( + '/(Table does not exist\.|Relation [\"\'].*[\"\'] does not exist|sequence does not exist|class ".+" not found)$/' => DB_ERROR_NOSUCHTABLE, + '/Relation [\"\'].*[\"\'] already exists|Cannot insert a duplicate key into (a )?unique index.*/' => DB_ERROR_ALREADY_EXISTS, + '/divide by zero$/' => DB_ERROR_DIVZERO, + '/pg_atoi: error in .*: can\'t parse /' => DB_ERROR_INVALID_NUMBER, + '/ttribute [\"\'].*[\"\'] not found|Relation [\"\'].*[\"\'] does not have attribute [\"\'].*[\"\']/' => DB_ERROR_NOSUCHFIELD, + '/parser: parse error at or near \"/' => DB_ERROR_SYNTAX, + '/referential integrity violation/' => DB_ERROR_CONSTRAINT, + '/Relation [\"\'].*[\"\'] already exists|Cannot insert a duplicate key into (a )?unique index.*|duplicate key violates unique constraint/' + => DB_ERROR_ALREADY_EXISTS + ); + reset($error_regexps); + while (list($regexp,$code) = each($error_regexps)) { + if (preg_match($regexp, $errormsg)) { + return $code; + } + } + // Fall back to DB_ERROR if there was no mapping. + return DB_ERROR; +} + +function adodb_error_odbc() +{ +static $MAP = array( + '01004' => DB_ERROR_TRUNCATED, + '07001' => DB_ERROR_MISMATCH, + '21S01' => DB_ERROR_MISMATCH, + '21S02' => DB_ERROR_MISMATCH, + '22003' => DB_ERROR_INVALID_NUMBER, + '22008' => DB_ERROR_INVALID_DATE, + '22012' => DB_ERROR_DIVZERO, + '23000' => DB_ERROR_CONSTRAINT, + '24000' => DB_ERROR_INVALID, + '34000' => DB_ERROR_INVALID, + '37000' => DB_ERROR_SYNTAX, + '42000' => DB_ERROR_SYNTAX, + 'IM001' => DB_ERROR_UNSUPPORTED, + 'S0000' => DB_ERROR_NOSUCHTABLE, + 'S0001' => DB_ERROR_NOT_FOUND, + 'S0002' => DB_ERROR_NOSUCHTABLE, + 'S0011' => DB_ERROR_ALREADY_EXISTS, + 'S0012' => DB_ERROR_NOT_FOUND, + 'S0021' => DB_ERROR_ALREADY_EXISTS, + 'S0022' => DB_ERROR_NOT_FOUND, + 'S1000' => DB_ERROR_NOSUCHTABLE, + 'S1009' => DB_ERROR_INVALID, + 'S1090' => DB_ERROR_INVALID, + 'S1C00' => DB_ERROR_NOT_CAPABLE + ); + return $MAP; +} + +function adodb_error_ibase() +{ +static $MAP = array( + -104 => DB_ERROR_SYNTAX, + -150 => DB_ERROR_ACCESS_VIOLATION, + -151 => DB_ERROR_ACCESS_VIOLATION, + -155 => DB_ERROR_NOSUCHTABLE, + -157 => DB_ERROR_NOSUCHFIELD, + -158 => DB_ERROR_VALUE_COUNT_ON_ROW, + -170 => DB_ERROR_MISMATCH, + -171 => DB_ERROR_MISMATCH, + -172 => DB_ERROR_INVALID, + -204 => DB_ERROR_INVALID, + -205 => DB_ERROR_NOSUCHFIELD, + -206 => DB_ERROR_NOSUCHFIELD, + -208 => DB_ERROR_INVALID, + -219 => DB_ERROR_NOSUCHTABLE, + -297 => DB_ERROR_CONSTRAINT, + -530 => DB_ERROR_CONSTRAINT, + -803 => DB_ERROR_CONSTRAINT, + -551 => DB_ERROR_ACCESS_VIOLATION, + -552 => DB_ERROR_ACCESS_VIOLATION, + -922 => DB_ERROR_NOSUCHDB, + -923 => DB_ERROR_CONNECT_FAILED, + -924 => DB_ERROR_CONNECT_FAILED + ); + + return $MAP; +} + +function adodb_error_ifx() +{ +static $MAP = array( + '-201' => DB_ERROR_SYNTAX, + '-206' => DB_ERROR_NOSUCHTABLE, + '-217' => DB_ERROR_NOSUCHFIELD, + '-329' => DB_ERROR_NODBSELECTED, + '-1204' => DB_ERROR_INVALID_DATE, + '-1205' => DB_ERROR_INVALID_DATE, + '-1206' => DB_ERROR_INVALID_DATE, + '-1209' => DB_ERROR_INVALID_DATE, + '-1210' => DB_ERROR_INVALID_DATE, + '-1212' => DB_ERROR_INVALID_DATE + ); + + return $MAP; +} + +function adodb_error_oci8() +{ +static $MAP = array( + 1 => DB_ERROR_ALREADY_EXISTS, + 900 => DB_ERROR_SYNTAX, + 904 => DB_ERROR_NOSUCHFIELD, + 923 => DB_ERROR_SYNTAX, + 942 => DB_ERROR_NOSUCHTABLE, + 955 => DB_ERROR_ALREADY_EXISTS, + 1476 => DB_ERROR_DIVZERO, + 1722 => DB_ERROR_INVALID_NUMBER, + 2289 => DB_ERROR_NOSUCHTABLE, + 2291 => DB_ERROR_CONSTRAINT, + 2449 => DB_ERROR_CONSTRAINT + ); + + return $MAP; +} + +function adodb_error_mssql() +{ +static $MAP = array( + 208 => DB_ERROR_NOSUCHTABLE, + 2601 => DB_ERROR_ALREADY_EXISTS + ); + + return $MAP; +} + +function adodb_error_sqlite() +{ +static $MAP = array( + 1 => DB_ERROR_SYNTAX + ); + + return $MAP; +} + +function adodb_error_mysql() +{ +static $MAP = array( + 1004 => DB_ERROR_CANNOT_CREATE, + 1005 => DB_ERROR_CANNOT_CREATE, + 1006 => DB_ERROR_CANNOT_CREATE, + 1007 => DB_ERROR_ALREADY_EXISTS, + 1008 => DB_ERROR_CANNOT_DROP, + 1045 => DB_ERROR_ACCESS_VIOLATION, + 1046 => DB_ERROR_NODBSELECTED, + 1049 => DB_ERROR_NOSUCHDB, + 1050 => DB_ERROR_ALREADY_EXISTS, + 1051 => DB_ERROR_NOSUCHTABLE, + 1054 => DB_ERROR_NOSUCHFIELD, + 1062 => DB_ERROR_ALREADY_EXISTS, + 1064 => DB_ERROR_SYNTAX, + 1100 => DB_ERROR_NOT_LOCKED, + 1136 => DB_ERROR_VALUE_COUNT_ON_ROW, + 1146 => DB_ERROR_NOSUCHTABLE, + 1048 => DB_ERROR_CONSTRAINT, + 2002 => DB_ERROR_CONNECT_FAILED, + 2005 => DB_ERROR_CONNECT_FAILED + ); + + return $MAP; +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb-errorhandler.inc.php b/framework/DataAccess/adodb/adodb-errorhandler.inc.php new file mode 100644 index 00000000..c4e79885 --- /dev/null +++ b/framework/DataAccess/adodb/adodb-errorhandler.inc.php @@ -0,0 +1,79 @@ +<?php +/** + * @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + * Released under both BSD license and Lesser GPL library license. + * Whenever there is any discrepancy between the two licenses, + * the BSD license will take precedence. + * + * Set tabs to 4 for best viewing. + * + * Latest version is available at http://php.weblogs.com + * +*/ + + +// added Claudio Bustos clbustos#entelchile.net +if (!defined('ADODB_ERROR_HANDLER_TYPE')) define('ADODB_ERROR_HANDLER_TYPE',E_USER_ERROR); + +if (!defined('ADODB_ERROR_HANDLER')) define('ADODB_ERROR_HANDLER','ADODB_Error_Handler'); + +/** +* Default Error Handler. This will be called with the following params +* +* @param $dbms the RDBMS you are connecting to +* @param $fn the name of the calling function (in uppercase) +* @param $errno the native error number from the database +* @param $errmsg the native error msg from the database +* @param $p1 $fn specific parameter - see below +* @param $p2 $fn specific parameter - see below +* @param $thisConn $current connection object - can be false if no connection object created +*/ +function ADODB_Error_Handler($dbms, $fn, $errno, $errmsg, $p1, $p2, &$thisConnection) +{ + if (error_reporting() == 0) return; // obey @ protocol + switch($fn) { + case 'EXECUTE': + $sql = $p1; + $inputparams = $p2; + + $s = "$dbms error: [$errno: $errmsg] in $fn(\"$sql\")\n"; + break; + + case 'PCONNECT': + case 'CONNECT': + $host = $p1; + $database = $p2; + + $s = "$dbms error: [$errno: $errmsg] in $fn($host, '****', '****', $database)\n"; + break; + default: + $s = "$dbms error: [$errno: $errmsg] in $fn($p1, $p2)\n"; + break; + } + /* + * Log connection error somewhere + * 0 message is sent to PHP's system logger, using the Operating System's system + * logging mechanism or a file, depending on what the error_log configuration + * directive is set to. + * 1 message is sent by email to the address in the destination parameter. + * This is the only message type where the fourth parameter, extra_headers is used. + * This message type uses the same internal function as mail() does. + * 2 message is sent through the PHP debugging connection. + * This option is only available if remote debugging has been enabled. + * In this case, the destination parameter specifies the host name or IP address + * and optionally, port number, of the socket receiving the debug information. + * 3 message is appended to the file destination + */ + if (defined('ADODB_ERROR_LOG_TYPE')) { + $t = date('Y-m-d H:i:s'); + if (defined('ADODB_ERROR_LOG_DEST')) + error_log("($t) $s", ADODB_ERROR_LOG_TYPE, ADODB_ERROR_LOG_DEST); + else + error_log("($t) $s", ADODB_ERROR_LOG_TYPE); + } + + + //print "<p>$s</p>"; + trigger_error($s,ADODB_ERROR_HANDLER_TYPE); +} +?> diff --git a/framework/DataAccess/adodb/adodb-errorpear.inc.php b/framework/DataAccess/adodb/adodb-errorpear.inc.php new file mode 100644 index 00000000..433fd86d --- /dev/null +++ b/framework/DataAccess/adodb/adodb-errorpear.inc.php @@ -0,0 +1,88 @@ +<?php +/** + * @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + * Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + * + * Set tabs to 4 for best viewing. + * + * Latest version is available at http://php.weblogs.com + * +*/ +include_once('PEAR.php'); + +if (!defined('ADODB_ERROR_HANDLER')) define('ADODB_ERROR_HANDLER','ADODB_Error_PEAR'); + +/* +* Enabled the following if you want to terminate scripts when an error occurs +*/ +//PEAR::setErrorHandling (PEAR_ERROR_DIE); + +/* +* Name of the PEAR_Error derived class to call. +*/ +if (!defined('ADODB_PEAR_ERROR_CLASS')) define('ADODB_PEAR_ERROR_CLASS','PEAR_Error'); + +/* +* Store the last PEAR_Error object here +*/ +global $ADODB_Last_PEAR_Error; $ADODB_Last_PEAR_Error = false; + + /** +* Error Handler with PEAR support. This will be called with the following params +* +* @param $dbms the RDBMS you are connecting to +* @param $fn the name of the calling function (in uppercase) +* @param $errno the native error number from the database +* @param $errmsg the native error msg from the database +* @param $p1 $fn specific parameter - see below +* @param $P2 $fn specific parameter - see below + */ +function ADODB_Error_PEAR($dbms, $fn, $errno, $errmsg, $p1=false, $p2=false) +{ +global $ADODB_Last_PEAR_Error; + + if (error_reporting() == 0) return; // obey @ protocol + switch($fn) { + case 'EXECUTE': + $sql = $p1; + $inputparams = $p2; + + $s = "$dbms error: [$errno: $errmsg] in $fn(\"$sql\")"; + break; + + case 'PCONNECT': + case 'CONNECT': + $host = $p1; + $database = $p2; + + $s = "$dbms error: [$errno: $errmsg] in $fn('$host', ?, ?, '$database')"; + break; + + default: + $s = "$dbms error: [$errno: $errmsg] in $fn($p1, $p2)"; + break; + } + + $class = ADODB_PEAR_ERROR_CLASS; + $ADODB_Last_PEAR_Error = new $class($s, $errno, + $GLOBALS['_PEAR_default_error_mode'], + $GLOBALS['_PEAR_default_error_options'], + $errmsg); + + //print "<p>!$s</p>"; +} + +/** +* Returns last PEAR_Error object. This error might be for an error that +* occured several sql statements ago. +*/ +function &ADODB_PEAR_Error() +{ +global $ADODB_Last_PEAR_Error; + + return $ADODB_Last_PEAR_Error; +} + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb-exceptions.inc.php b/framework/DataAccess/adodb/adodb-exceptions.inc.php new file mode 100644 index 00000000..53ec96e3 --- /dev/null +++ b/framework/DataAccess/adodb/adodb-exceptions.inc.php @@ -0,0 +1,80 @@ +<?php + +/** + * @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + * Released under both BSD license and Lesser GPL library license. + * Whenever there is any discrepancy between the two licenses, + * the BSD license will take precedence. + * + * Set tabs to 4 for best viewing. + * + * Latest version is available at http://php.weblogs.com + * + * Exception-handling code using PHP5 exceptions (try-catch-throw). + */ + + +if (!defined('ADODB_ERROR_HANDLER_TYPE')) define('ADODB_ERROR_HANDLER_TYPE',E_USER_ERROR); +define('ADODB_ERROR_HANDLER','adodb_throw'); + +class ADODB_Exception extends Exception { +public $dbms; +public $fn; +public $sql = ''; +public $params = ''; +public $host = ''; +public $database = ''; + + function __construct($dbms, $fn, $errno, $errmsg, $p1, $p2, $thisConnection) + { + switch($fn) { + case 'EXECUTE': + $this->sql = $p1; + $this->params = $p2; + $s = "$dbms error: [$errno: $errmsg] in $fn(\"$p1\")\n"; + break; + + case 'PCONNECT': + case 'CONNECT': + $user = $thisConnection->user; + $s = "$dbms error: [$errno: $errmsg] in $fn($p1, '$user', '****', $p2)\n"; + break; + default: + $s = "$dbms error: [$errno: $errmsg] in $fn($p1, $p2)\n"; + break; + } + + $this->dbms = $dbms; + $this->host = $thisConnection->host; + $this->database = $thisConnection->database; + $this->fn = $fn; + $this->msg = $errmsg; + + if (!is_numeric($errno)) $errno = -1; + parent::__construct($s,$errno); + } +} + +/** +* Default Error Handler. This will be called with the following params +* +* @param $dbms the RDBMS you are connecting to +* @param $fn the name of the calling function (in uppercase) +* @param $errno the native error number from the database +* @param $errmsg the native error msg from the database +* @param $p1 $fn specific parameter - see below +* @param $P2 $fn specific parameter - see below +*/ + +function adodb_throw($dbms, $fn, $errno, $errmsg, $p1, $p2, $thisConnection) +{ +global $ADODB_EXCEPTION; + + if (error_reporting() == 0) return; // obey @ protocol + if (is_string($ADODB_EXCEPTION)) $errfn = $ADODB_EXCEPTION; + else $errfn = 'ADODB_EXCEPTION'; + throw new $errfn($dbms, $fn, $errno, $errmsg, $p1, $p2, $thisConnection); +} + + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb-iterator.inc.php b/framework/DataAccess/adodb/adodb-iterator.inc.php new file mode 100644 index 00000000..926626c3 --- /dev/null +++ b/framework/DataAccess/adodb/adodb-iterator.inc.php @@ -0,0 +1,84 @@ +<?php + +/* + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + + Set tabs to 4. + + Declares the ADODB Base Class for PHP5 "ADODB_BASE_RS", and supports iteration with + the ADODB_Iterator class. + + $rs = $db->Execute("select * from adoxyz"); + foreach($rs as $k => $v) { + echo $k; print_r($v); echo "<br>"; + } + + + Iterator code based on http://cvs.php.net/cvs.php/php-src/ext/spl/examples/cachingiterator.inc?login=2 + */ + + + class ADODB_Iterator implements Iterator { + + private $rs; + + function __construct($rs) + { + $this->rs = $rs; + } + function rewind() + { + $this->rs->MoveFirst(); + } + + function valid() + { + return !$this->rs->EOF; + } + + function key() + { + return $this->rs->_currentRow; + } + + function current() + { + return $this->rs->fields; + } + + function next() + { + $this->rs->MoveNext(); + } + + function __call($func, $params) + { + return call_user_func_array(array($this->rs, $func), $params); + } + + + function hasMore() + { + return !$this->rs->EOF; + } + +} + + +class ADODB_BASE_RS implements IteratorAggregate { + function getIterator() { + return new ADODB_Iterator($this); + } + + /* this is experimental - i don't really know what to return... */ + function __toString() + { + include_once(ADODB_DIR.'/toexport.inc.php'); + return _adodb_export($this,',',',',false,true); + } +} + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb-lib.inc.php b/framework/DataAccess/adodb/adodb-lib.inc.php new file mode 100644 index 00000000..2a4348db --- /dev/null +++ b/framework/DataAccess/adodb/adodb-lib.inc.php @@ -0,0 +1,1021 @@ +<?php + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +global $ADODB_INCLUDED_LIB; +$ADODB_INCLUDED_LIB = 1; + +/* + @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim\@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. See License.txt. + Set tabs to 4 for best viewing. + + Less commonly used functions are placed here to reduce size of adodb.inc.php. +*/ + + +// Force key to upper. +// See also http://www.php.net/manual/en/function.array-change-key-case.php +function _array_change_key_case($an_array) +{ + if (is_array($an_array)) { + $new_array = array(); + foreach($an_array as $key=>$value) + $new_array[strtoupper($key)] = $value; + + return $new_array; + } + + return $an_array; +} + +function _adodb_replace(&$zthis, $table, $fieldArray, $keyCol, $autoQuote, $has_autoinc) +{ + if (count($fieldArray) == 0) return 0; + $first = true; + $uSet = ''; + + if (!is_array($keyCol)) { + $keyCol = array($keyCol); + } + foreach($fieldArray as $k => $v) { + if ($autoQuote && !is_numeric($v) and strncmp($v,"'",1) !== 0 and strcasecmp($v,'null')!=0) { + $v = $zthis->qstr($v); + $fieldArray[$k] = $v; + } + if (in_array($k,$keyCol)) continue; // skip UPDATE if is key + + if ($first) { + $first = false; + $uSet = "$k=$v"; + } else + $uSet .= ",$k=$v"; + } + + $where = false; + foreach ($keyCol as $v) { + if (isset($fieldArray[$v])) { + if ($where) $where .= ' and '.$v.'='.$fieldArray[$v]; + else $where = $v.'='.$fieldArray[$v]; + } + } + + if ($uSet && $where) { + $update = "UPDATE $table SET $uSet WHERE $where"; + + $rs = $zthis->Execute($update); + + + if ($rs) { + if ($zthis->poorAffectedRows) { + /* + The Select count(*) wipes out any errors that the update would have returned. + http://phplens.com/lens/lensforum/msgs.php?id=5696 + */ + if ($zthis->ErrorNo()<>0) return 0; + + # affected_rows == 0 if update field values identical to old values + # for mysql - which is silly. + + $cnt = $zthis->GetOne("select count(*) from $table where $where"); + if ($cnt > 0) return 1; // record already exists + } else { + if (($zthis->Affected_Rows()>0)) return 1; + } + } else + return 0; + } + + // print "<p>Error=".$this->ErrorNo().'<p>'; + $first = true; + foreach($fieldArray as $k => $v) { + if ($has_autoinc && in_array($k,$keyCol)) continue; // skip autoinc col + + if ($first) { + $first = false; + $iCols = "$k"; + $iVals = "$v"; + } else { + $iCols .= ",$k"; + $iVals .= ",$v"; + } + } + $insert = "INSERT INTO $table ($iCols) VALUES ($iVals)"; + $rs = $zthis->Execute($insert); + return ($rs) ? 2 : 0; +} + +// Requires $ADODB_FETCH_MODE = ADODB_FETCH_NUM +function _adodb_getmenu(&$zthis, $name,$defstr='',$blank1stItem=true,$multiple=false, + $size=0, $selectAttr='',$compareFields0=true) +{ + $hasvalue = false; + + if ($multiple or is_array($defstr)) { + if ($size==0) $size=5; + $attr = ' multiple size="'.$size.'"'; + if (!strpos($name,'[]')) $name .= '[]'; + } else if ($size) $attr = ' size="'.$size.'"'; + else $attr =''; + + $s = '<select name="'.$name.'"'.$attr.' '.$selectAttr.'>'; + if ($blank1stItem) + if (is_string($blank1stItem)) { + $barr = explode(':',$blank1stItem); + if (sizeof($barr) == 1) $barr[] = ''; + $s .= "\n<option value=\"".$barr[0]."\">".$barr[1]."</option>"; + } else $s .= "\n<option></option>"; + + if ($zthis->FieldCount() > 1) $hasvalue=true; + else $compareFields0 = true; + + $value = ''; + $optgroup = null; + $firstgroup = true; + $fieldsize = $zthis->FieldCount(); + while(!$zthis->EOF) { + $zval = rtrim(reset($zthis->fields)); + + if ($blank1stItem && $zval=="") { + $zthis->MoveNext(); + continue; + } + + if ($fieldsize > 1) { + if (isset($zthis->fields[1])) + $zval2 = rtrim($zthis->fields[1]); + else + $zval2 = rtrim(next($zthis->fields)); + } + $selected = ($compareFields0) ? $zval : $zval2; + + $group = ''; + if ($fieldsize > 2) { + $group = rtrim($zthis->fields[2]); + } + + if ($optgroup != $group) { + $optgroup = $group; + if ($firstgroup) { + $firstgroup = false; + $s .="\n<optgroup label='". htmlspecialchars($group) ."'>"; + } else { + $s .="\n</optgroup>"; + $s .="\n<optgroup label='". htmlspecialchars($group) ."'>"; + } + } + + if ($hasvalue) + $value = " value='".htmlspecialchars($zval2)."'"; + + if (is_array($defstr)) { + + if (in_array($selected,$defstr)) + $s .= "\n<option selected='selected'$value>".htmlspecialchars($zval).'</option>'; + else + $s .= "\n<option".$value.'>'.htmlspecialchars($zval).'</option>'; + } + else { + if (strcasecmp($selected,$defstr)==0) + $s .= "\n<option selected='selected'$value>".htmlspecialchars($zval).'</option>'; + else + $s .= "\n<option".$value.'>'.htmlspecialchars($zval).'</option>'; + } + $zthis->MoveNext(); + } // while + + // closing last optgroup + if($optgroup != null) { + $s .= "\n</optgroup>"; + } + return $s ."\n</select>\n"; +} + +// Requires $ADODB_FETCH_MODE = ADODB_FETCH_NUM +function _adodb_getmenu_gp(&$zthis, $name,$defstr='',$blank1stItem=true,$multiple=false, + $size=0, $selectAttr='',$compareFields0=true) +{ + $hasvalue = false; + + if ($multiple or is_array($defstr)) { + if ($size==0) $size=5; + $attr = ' multiple size="'.$size.'"'; + if (!strpos($name,'[]')) $name .= '[]'; + } else if ($size) $attr = ' size="'.$size.'"'; + else $attr =''; + + $s = '<select name="'.$name.'"'.$attr.' '.$selectAttr.'>'; + if ($blank1stItem) + if (is_string($blank1stItem)) { + $barr = explode(':',$blank1stItem); + if (sizeof($barr) == 1) $barr[] = ''; + $s .= "\n<option value=\"".$barr[0]."\">".$barr[1]."</option>"; + } else $s .= "\n<option></option>"; + + if ($zthis->FieldCount() > 1) $hasvalue=true; + else $compareFields0 = true; + + $value = ''; + $optgroup = null; + $firstgroup = true; + $fieldsize = sizeof($zthis->fields); + while(!$zthis->EOF) { + $zval = rtrim(reset($zthis->fields)); + + if ($blank1stItem && $zval=="") { + $zthis->MoveNext(); + continue; + } + + if ($fieldsize > 1) { + if (isset($zthis->fields[1])) + $zval2 = rtrim($zthis->fields[1]); + else + $zval2 = rtrim(next($zthis->fields)); + } + $selected = ($compareFields0) ? $zval : $zval2; + + $group = ''; + if (isset($zthis->fields[2])) { + $group = rtrim($zthis->fields[2]); + } + + if ($optgroup != $group) { + $optgroup = $group; + if ($firstgroup) { + $firstgroup = false; + $s .="\n<optgroup label='". htmlspecialchars($group) ."'>"; + } else { + $s .="\n</optgroup>"; + $s .="\n<optgroup label='". htmlspecialchars($group) ."'>"; + } + } + + if ($hasvalue) + $value = " value='".htmlspecialchars($zval2)."'"; + + if (is_array($defstr)) { + + if (in_array($selected,$defstr)) + $s .= "\n<option selected='selected'$value>".htmlspecialchars($zval).'</option>'; + else + $s .= "\n<option".$value.'>'.htmlspecialchars($zval).'</option>'; + } + else { + if (strcasecmp($selected,$defstr)==0) + $s .= "\n<option selected='selected'$value>".htmlspecialchars($zval).'</option>'; + else + $s .= "\n<option".$value.'>'.htmlspecialchars($zval).'</option>'; + } + $zthis->MoveNext(); + } // while + + // closing last optgroup + if($optgroup != null) { + $s .= "\n</optgroup>"; + } + return $s ."\n</select>\n"; +} + + +/* + Count the number of records this sql statement will return by using + query rewriting heuristics... + + Does not work with UNIONs, except with postgresql and oracle. + + Usage: + + $conn->Connect(...); + $cnt = _adodb_getcount($conn, $sql); + +*/ +function _adodb_getcount(&$zthis, $sql,$inputarr=false,$secs2cache=0) +{ + $qryRecs = 0; + + if (preg_match("/^\s*SELECT\s+DISTINCT/is", $sql) || + preg_match('/\s+GROUP\s+BY\s+/is',$sql) || + preg_match('/\s+UNION\s+/is',$sql)) { + // ok, has SELECT DISTINCT or GROUP BY so see if we can use a table alias + // but this is only supported by oracle and postgresql... + if ($zthis->dataProvider == 'oci8') { + + $rewritesql = preg_replace('/(\sORDER\s+BY\s.*)/is','',$sql); + + // Allow Oracle hints to be used for query optimization, Chris Wrye + if (preg_match('#/\\*+.*?\\*\\/#', $sql, $hint)) { + $rewritesql = "SELECT ".$hint[0]." COUNT(*) FROM (".$rewritesql.")"; + } else + $rewritesql = "SELECT COUNT(*) FROM (".$rewritesql.")"; + + } else if (strncmp($zthis->databaseType,'postgres',8) == 0) { + + $info = $zthis->ServerInfo(); + if (substr($info['version'],0,3) >= 7.1) { // good till version 999 + $rewritesql = preg_replace('/(\sORDER\s+BY\s[^)]*)/is','',$sql); + $rewritesql = "SELECT COUNT(*) FROM ($rewritesql) _ADODB_ALIAS_"; + } + } + } else { + // now replace SELECT ... FROM with SELECT COUNT(*) FROM + $rewritesql = preg_replace( + '/^\s*SELECT\s.*\s+FROM\s/Uis','SELECT COUNT(*) FROM ',$sql); + + // fix by alexander zhukov, alex#unipack.ru, because count(*) and 'order by' fails + // with mssql, access and postgresql. Also a good speedup optimization - skips sorting! + // also see http://phplens.com/lens/lensforum/msgs.php?id=12752 + if (preg_match('/\sORDER\s+BY\s*\(/i',$rewritesql)) + $rewritesql = preg_replace('/(\sORDER\s+BY\s.*)/is','',$rewritesql); + else + $rewritesql = preg_replace('/(\sORDER\s+BY\s[^)]*)/is','',$rewritesql); + + } + + if (isset($rewritesql) && $rewritesql != $sql) { + if ($secs2cache) { + // we only use half the time of secs2cache because the count can quickly + // become inaccurate if new records are added + $qryRecs = $zthis->CacheGetOne($secs2cache/2,$rewritesql,$inputarr); + + } else { + $qryRecs = $zthis->GetOne($rewritesql,$inputarr); + } + if ($qryRecs !== false) return $qryRecs; + } + //-------------------------------------------- + // query rewrite failed - so try slower way... + + + // strip off unneeded ORDER BY if no UNION + if (preg_match('/\s*UNION\s*/is', $sql)) $rewritesql = $sql; + else $rewritesql = preg_replace('/(\sORDER\s+BY\s.*)/is','',$sql); + + $rstest = &$zthis->Execute($rewritesql,$inputarr); + if (!$rstest) $rstest = $zthis->Execute($sql,$inputarr); + + if ($rstest) { + $qryRecs = $rstest->RecordCount(); + if ($qryRecs == -1) { + global $ADODB_EXTENSION; + // some databases will return -1 on MoveLast() - change to MoveNext() + if ($ADODB_EXTENSION) { + while(!$rstest->EOF) { + adodb_movenext($rstest); + } + } else { + while(!$rstest->EOF) { + $rstest->MoveNext(); + } + } + $qryRecs = $rstest->_currentRow; + } + $rstest->Close(); + if ($qryRecs == -1) return 0; + } + + return $qryRecs; +} + +/* + Code originally from "Cornel G" <conyg@fx.ro> + + This code might not work with SQL that has UNION in it + + Also if you are using CachePageExecute(), there is a strong possibility that + data will get out of synch. use CachePageExecute() only with tables that + rarely change. +*/ +function &_adodb_pageexecute_all_rows(&$zthis, $sql, $nrows, $page, + $inputarr=false, $secs2cache=0) +{ + $atfirstpage = false; + $atlastpage = false; + $lastpageno=1; + + // If an invalid nrows is supplied, + // we assume a default value of 10 rows per page + if (!isset($nrows) || $nrows <= 0) $nrows = 10; + + $qryRecs = false; //count records for no offset + + $qryRecs = _adodb_getcount($zthis,$sql,$inputarr,$secs2cache); + $lastpageno = (int) ceil($qryRecs / $nrows); + $zthis->_maxRecordCount = $qryRecs; + + + + // ***** Here we check whether $page is the last page or + // whether we are trying to retrieve + // a page number greater than the last page number. + if ($page >= $lastpageno) { + $page = $lastpageno; + $atlastpage = true; + } + + // If page number <= 1, then we are at the first page + if (empty($page) || $page <= 1) { + $page = 1; + $atfirstpage = true; + } + + // We get the data we want + $offset = $nrows * ($page-1); + if ($secs2cache > 0) + $rsreturn = &$zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $offset, $inputarr); + else + $rsreturn = &$zthis->SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache); + + + // Before returning the RecordSet, we set the pagination properties we need + if ($rsreturn) { + $rsreturn->_maxRecordCount = $qryRecs; + $rsreturn->rowsPerPage = $nrows; + $rsreturn->AbsolutePage($page); + $rsreturn->AtFirstPage($atfirstpage); + $rsreturn->AtLastPage($atlastpage); + $rsreturn->LastPageNo($lastpageno); + } + return $rsreturn; +} + +// Iván Oliva version +function &_adodb_pageexecute_no_last_page(&$zthis, $sql, $nrows, $page, $inputarr=false, $secs2cache=0) +{ + + $atfirstpage = false; + $atlastpage = false; + + if (!isset($page) || $page <= 1) { // If page number <= 1, then we are at the first page + $page = 1; + $atfirstpage = true; + } + if ($nrows <= 0) $nrows = 10; // If an invalid nrows is supplied, we assume a default value of 10 rows per page + + // ***** Here we check whether $page is the last page or whether we are trying to retrieve a page number greater than + // the last page number. + $pagecounter = $page + 1; + $pagecounteroffset = ($pagecounter * $nrows) - $nrows; + if ($secs2cache>0) $rstest = &$zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $pagecounteroffset, $inputarr); + else $rstest = &$zthis->SelectLimit($sql, $nrows, $pagecounteroffset, $inputarr, $secs2cache); + if ($rstest) { + while ($rstest && $rstest->EOF && $pagecounter>0) { + $atlastpage = true; + $pagecounter--; + $pagecounteroffset = $nrows * ($pagecounter - 1); + $rstest->Close(); + if ($secs2cache>0) $rstest = &$zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $pagecounteroffset, $inputarr); + else $rstest = &$zthis->SelectLimit($sql, $nrows, $pagecounteroffset, $inputarr, $secs2cache); + } + if ($rstest) $rstest->Close(); + } + if ($atlastpage) { // If we are at the last page or beyond it, we are going to retrieve it + $page = $pagecounter; + if ($page == 1) $atfirstpage = true; // We have to do this again in case the last page is the same as the first + //... page, that is, the recordset has only 1 page. + } + + // We get the data we want + $offset = $nrows * ($page-1); + if ($secs2cache > 0) $rsreturn = &$zthis->CacheSelectLimit($secs2cache, $sql, $nrows, $offset, $inputarr); + else $rsreturn = &$zthis->SelectLimit($sql, $nrows, $offset, $inputarr, $secs2cache); + + // Before returning the RecordSet, we set the pagination properties we need + if ($rsreturn) { + $rsreturn->rowsPerPage = $nrows; + $rsreturn->AbsolutePage($page); + $rsreturn->AtFirstPage($atfirstpage); + $rsreturn->AtLastPage($atlastpage); + } + return $rsreturn; +} + +function _adodb_getupdatesql(&$zthis,&$rs, $arrFields,$forceUpdate=false,$magicq=false,$force=2) +{ + if (!$rs) { + printf(ADODB_BAD_RS,'GetUpdateSQL'); + return false; + } + + $fieldUpdatedCount = 0; + $arrFields = _array_change_key_case($arrFields); + + $hasnumeric = isset($rs->fields[0]); + $setFields = ''; + + // Loop through all of the fields in the recordset + for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++) { + // Get the field from the recordset + $field = $rs->FetchField($i); + + // If the recordset field is one + // of the fields passed in then process. + $upperfname = strtoupper($field->name); + if (adodb_key_exists($upperfname,$arrFields,$force)) { + + // If the existing field value in the recordset + // is different from the value passed in then + // go ahead and append the field name and new value to + // the update query. + + if ($hasnumeric) $val = $rs->fields[$i]; + else if (isset($rs->fields[$upperfname])) $val = $rs->fields[$upperfname]; + else if (isset($rs->fields[$field->name])) $val = $rs->fields[$field->name]; + else if (isset($rs->fields[strtolower($upperfname)])) $val = $rs->fields[strtolower($upperfname)]; + else $val = ''; + + + if ($forceUpdate || strcmp($val, $arrFields[$upperfname])) { + // Set the counter for the number of fields that will be updated. + $fieldUpdatedCount++; + + // Based on the datatype of the field + // Format the value properly for the database + $type = $rs->MetaType($field->type); + + + if ($type == 'null') { + $type = 'C'; + } + + if (strpos($upperfname,' ') !== false) + $fnameq = $zthis->nameQuote.$upperfname.$zthis->nameQuote; + else + $fnameq = $upperfname; + + + // is_null requires php 4.0.4 + //********************************************************// + if (is_null($arrFields[$upperfname]) + || (empty($arrFields[$upperfname]) && strlen($arrFields[$upperfname]) == 0) + || $arrFields[$upperfname] === 'null' + ) + { + switch ($force) { + + //case 0: + // //Ignore empty values. This is allready handled in "adodb_key_exists" function. + //break; + + case 1: + //Set null + $setFields .= $field->name . " = null, "; + break; + + case 2: + //Set empty + $arrFields[$upperfname] = ""; + $setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq,$arrFields, $magicq); + break; + default: + case 3: + //Set the value that was given in array, so you can give both null and empty values + if (is_null($arrFields[$upperfname]) || $arrFields[$upperfname] === 'null') { + $setFields .= $field->name . " = null, "; + } else { + $setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq,$arrFields, $magicq); + } + break; + } + //********************************************************// + } else { + //we do this so each driver can customize the sql for + //DB specific column types. + //Oracle needs BLOB types to be handled with a returning clause + //postgres has special needs as well + $setFields .= _adodb_column_sql($zthis, 'U', $type, $upperfname, $fnameq, + $arrFields, $magicq); + } + } + } + } + + // If there were any modified fields then build the rest of the update query. + if ($fieldUpdatedCount > 0 || $forceUpdate) { + // Get the table name from the existing query. + if (!empty($rs->tableName)) $tableName = $rs->tableName; + else { + preg_match("/FROM\s+".ADODB_TABLE_REGEX."/is", $rs->sql, $tableName); + $tableName = $tableName[1]; + } + // Get the full where clause excluding the word "WHERE" from + // the existing query. + preg_match('/\sWHERE\s(.*)/is', $rs->sql, $whereClause); + + $discard = false; + // not a good hack, improvements? + if ($whereClause) { + #var_dump($whereClause); + if (preg_match('/\s(ORDER\s.*)/is', $whereClause[1], $discard)); + else if (preg_match('/\s(LIMIT\s.*)/is', $whereClause[1], $discard)); + else if (preg_match('/\s(FOR UPDATE.*)/is', $whereClause[1], $discard)); + else preg_match('/\s.*(\) WHERE .*)/is', $whereClause[1], $discard); # see http://sourceforge.net/tracker/index.php?func=detail&aid=1379638&group_id=42718&atid=433976 + } else + $whereClause = array(false,false); + + if ($discard) + $whereClause[1] = substr($whereClause[1], 0, strlen($whereClause[1]) - strlen($discard[1])); + + $sql = 'UPDATE '.$tableName.' SET '.substr($setFields, 0, -2); + if (strlen($whereClause[1]) > 0) + $sql .= ' WHERE '.$whereClause[1]; + + return $sql; + + } else { + return false; + } +} + +function adodb_key_exists($key, &$arr,$force=2) +{ + if ($force<=0) { + // the following is the old behaviour where null or empty fields are ignored + return (!empty($arr[$key])) || (isset($arr[$key]) && strlen($arr[$key])>0); + } + + if (isset($arr[$key])) return true; + ## null check below + if (ADODB_PHPVER >= 0x4010) return array_key_exists($key,$arr); + return false; +} + +/** + * There is a special case of this function for the oci8 driver. + * The proper way to handle an insert w/ a blob in oracle requires + * a returning clause with bind variables and a descriptor blob. + * + * + */ +function _adodb_getinsertsql(&$zthis,&$rs,$arrFields,$magicq=false,$force=2) +{ +static $cacheRS = false; +static $cacheSig = 0; +static $cacheCols; + + $tableName = ''; + $values = ''; + $fields = ''; + $recordSet = null; + $arrFields = _array_change_key_case($arrFields); + $fieldInsertedCount = 0; + + if (is_string($rs)) { + //ok we have a table name + //try and get the column info ourself. + $tableName = $rs; + + //we need an object for the recordSet + //because we have to call MetaType. + //php can't do a $rsclass::MetaType() + $rsclass = $zthis->rsPrefix.$zthis->databaseType; + $recordSet = new $rsclass(-1,$zthis->fetchMode); + $recordSet->connection = &$zthis; + + if (is_string($cacheRS) && $cacheRS == $rs) { + $columns =& $cacheCols; + } else { + $columns = $zthis->MetaColumns( $tableName ); + $cacheRS = $tableName; + $cacheCols = $columns; + } + } else if (is_subclass_of($rs, 'adorecordset')) { + if (isset($rs->insertSig) && is_integer($cacheRS) && $cacheRS == $rs->insertSig) { + $columns =& $cacheCols; + } else { + for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++) + $columns[] = $rs->FetchField($i); + $cacheRS = $cacheSig; + $cacheCols = $columns; + $rs->insertSig = $cacheSig++; + } + $recordSet =& $rs; + + } else { + printf(ADODB_BAD_RS,'GetInsertSQL'); + return false; + } + + // Loop through all of the fields in the recordset + foreach( $columns as $field ) { + $upperfname = strtoupper($field->name); + if (adodb_key_exists($upperfname,$arrFields,$force)) { + $bad = false; + if (strpos($upperfname,' ') !== false) + $fnameq = $zthis->nameQuote.$upperfname.$zthis->nameQuote; + else + $fnameq = $upperfname; + + $type = $recordSet->MetaType($field->type); + + /********************************************************/ + if (is_null($arrFields[$upperfname]) + || (empty($arrFields[$upperfname]) && strlen($arrFields[$upperfname]) == 0) + || $arrFields[$upperfname] === 'null' + ) + { + switch ($force) { + + case 0: // we must always set null if missing + $bad = true; + break; + + case 1: + $values .= "null, "; + break; + + case 2: + //Set empty + $arrFields[$upperfname] = ""; + $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq,$arrFields, $magicq); + break; + + default: + case 3: + //Set the value that was given in array, so you can give both null and empty values + if (is_null($arrFields[$upperfname]) || $arrFields[$upperfname] === 'null') { + $values .= "null, "; + } else { + $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, $arrFields, $magicq); + } + break; + } // switch + + /*********************************************************/ + } else { + //we do this so each driver can customize the sql for + //DB specific column types. + //Oracle needs BLOB types to be handled with a returning clause + //postgres has special needs as well + $values .= _adodb_column_sql($zthis, 'I', $type, $upperfname, $fnameq, + $arrFields, $magicq); + } + + if ($bad) continue; + // Set the counter for the number of fields that will be inserted. + $fieldInsertedCount++; + + + // Get the name of the fields to insert + $fields .= $fnameq . ", "; + } + } + + + // If there were any inserted fields then build the rest of the insert query. + if ($fieldInsertedCount <= 0) return false; + + // Get the table name from the existing query. + if (!$tableName) { + if (!empty($rs->tableName)) $tableName = $rs->tableName; + else if (preg_match("/FROM\s+".ADODB_TABLE_REGEX."/is", $rs->sql, $tableName)) + $tableName = $tableName[1]; + else + return false; + } + + // Strip off the comma and space on the end of both the fields + // and their values. + $fields = substr($fields, 0, -2); + $values = substr($values, 0, -2); + + // Append the fields and their values to the insert query. + return 'INSERT INTO '.$tableName.' ( '.$fields.' ) VALUES ( '.$values.' )'; +} + + +/** + * This private method is used to help construct + * the update/sql which is generated by GetInsertSQL and GetUpdateSQL. + * It handles the string construction of 1 column -> sql string based on + * the column type. We want to do 'safe' handling of BLOBs + * + * @param string the type of sql we are trying to create + * 'I' or 'U'. + * @param string column data type from the db::MetaType() method + * @param string the column name + * @param array the column value + * + * @return string + * + */ +function _adodb_column_sql_oci8(&$zthis,$action, $type, $fname, $fnameq, $arrFields, $magicq) +{ + $sql = ''; + + // Based on the datatype of the field + // Format the value properly for the database + switch($type) { + case 'B': + //in order to handle Blobs correctly, we need + //to do some magic for Oracle + + //we need to create a new descriptor to handle + //this properly + if (!empty($zthis->hasReturningInto)) { + if ($action == 'I') { + $sql = 'empty_blob(), '; + } else { + $sql = $fnameq. '=empty_blob(), '; + } + //add the variable to the returning clause array + //so the user can build this later in + //case they want to add more to it + $zthis->_returningArray[$fname] = ':xx'.$fname.'xx'; + } else if (empty($arrFields[$fname])){ + if ($action == 'I') { + $sql = 'empty_blob(), '; + } else { + $sql = $fnameq. '=empty_blob(), '; + } + } else { + //this is to maintain compatibility + //with older adodb versions. + $sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, $magicq,false); + } + break; + + case "X": + //we need to do some more magic here for long variables + //to handle these correctly in oracle. + + //create a safe bind var name + //to avoid conflicts w/ dupes. + if (!empty($zthis->hasReturningInto)) { + if ($action == 'I') { + $sql = ':xx'.$fname.'xx, '; + } else { + $sql = $fnameq.'=:xx'.$fname.'xx, '; + } + //add the variable to the returning clause array + //so the user can build this later in + //case they want to add more to it + $zthis->_returningArray[$fname] = ':xx'.$fname.'xx'; + } else { + //this is to maintain compatibility + //with older adodb versions. + $sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, $magicq,false); + } + break; + + default: + $sql = _adodb_column_sql($zthis, $action, $type, $fname, $fnameq, $arrFields, $magicq,false); + break; + } + + return $sql; +} + +function _adodb_column_sql(&$zthis, $action, $type, $fname, $fnameq, $arrFields, $magicq, $recurse=true) +{ + + if ($recurse) { + switch($zthis->dataProvider) { + case 'postgres': + if ($type == 'L') $type = 'C'; + break; + case 'oci8': + return _adodb_column_sql_oci8($zthis, $action, $type, $fname, $fnameq, $arrFields, $magicq); + + } + } + + switch($type) { + case "C": + case "X": + case 'B': + $val = $zthis->qstr($arrFields[$fname],$magicq); + break; + + case "D": + $val = $zthis->DBDate($arrFields[$fname]); + break; + + case "T": + $val = $zthis->DBTimeStamp($arrFields[$fname]); + break; + + default: + $val = $arrFields[$fname]; + if (empty($val)) $val = '0'; + break; + } + + if ($action == 'I') return $val . ", "; + + + return $fnameq . "=" . $val . ", "; + +} + + + +function _adodb_debug_execute(&$zthis, $sql, $inputarr) +{ + $ss = ''; + if ($inputarr) { + foreach($inputarr as $kk=>$vv) { + if (is_string($vv) && strlen($vv)>64) $vv = substr($vv,0,64).'...'; + $ss .= "($kk=>'$vv') "; + } + $ss = "[ $ss ]"; + } + $sqlTxt = is_array($sql) ? $sql[0] : $sql; + /*str_replace(', ','##1#__^LF',is_array($sql) ? $sql[0] : $sql); + $sqlTxt = str_replace(',',', ',$sqlTxt); + $sqlTxt = str_replace('##1#__^LF', ', ' ,$sqlTxt); + */ + // check if running from browser or command-line + $inBrowser = isset($_SERVER['HTTP_USER_AGENT']); + + $dbt = $zthis->databaseType; + if (isset($zthis->dsnType)) $dbt .= '-'.$zthis->dsnType; + if ($inBrowser) { + if ($ss) { + $ss = '<code>'.htmlspecialchars($ss).'</code>'; + } + if ($zthis->debug === -1) + ADOConnection::outp( "<br />\n($dbt): ".htmlspecialchars($sqlTxt)." $ss\n<br />\n",false); + else + ADOConnection::outp( "<hr />\n($dbt): ".htmlspecialchars($sqlTxt)." $ss\n<hr />\n",false); + } else { + ADOConnection::outp("-----\n($dbt): ".$sqlTxt."\n-----\n",false); + } + + $qID = $zthis->_query($sql,$inputarr); + + /* + Alexios Fakios notes that ErrorMsg() must be called before ErrorNo() for mssql + because ErrorNo() calls Execute('SELECT @ERROR'), causing recursion + */ + if ($zthis->databaseType == 'mssql') { + // ErrorNo is a slow function call in mssql, and not reliable in PHP 4.0.6 + if($emsg = $zthis->ErrorMsg()) { + if ($err = $zthis->ErrorNo()) ADOConnection::outp($err.': '.$emsg); + } + } else if (!$qID) { + ADOConnection::outp($zthis->ErrorNo() .': '. $zthis->ErrorMsg()); + } + + if ($zthis->debug === 99) _adodb_backtrace(true,9999,2); + return $qID; +} + +# pretty print the debug_backtrace function +function _adodb_backtrace($printOrArr=true,$levels=9999,$skippy=0) +{ + if (!function_exists('debug_backtrace')) return ''; + + $html = (isset($_SERVER['HTTP_USER_AGENT'])); + $fmt = ($html) ? "</font><font color=#808080 size=-1> %% line %4d, file: <a href=\"file:/%s\">%s</a></font>" : "%% line %4d, file: %s"; + + $MAXSTRLEN = 128; + + $s = ($html) ? '<pre align=left>' : ''; + + if (is_array($printOrArr)) $traceArr = $printOrArr; + else $traceArr = debug_backtrace(); + array_shift($traceArr); + array_shift($traceArr); + $tabs = sizeof($traceArr)-2; + + foreach ($traceArr as $arr) { + if ($skippy) {$skippy -= 1; continue;} + $levels -= 1; + if ($levels < 0) break; + + $args = array(); + for ($i=0; $i < $tabs; $i++) $s .= ($html) ? ' ' : "\t"; + $tabs -= 1; + if ($html) $s .= '<font face="Courier New,Courier">'; + if (isset($arr['class'])) $s .= $arr['class'].'.'; + if (isset($arr['args'])) + foreach($arr['args'] as $v) { + if (is_null($v)) $args[] = 'null'; + else if (is_array($v)) $args[] = 'Array['.sizeof($v).']'; + else if (is_object($v)) $args[] = 'Object:'.get_class($v); + else if (is_bool($v)) $args[] = $v ? 'true' : 'false'; + else { + $v = (string) @$v; + $str = htmlspecialchars(substr($v,0,$MAXSTRLEN)); + if (strlen($v) > $MAXSTRLEN) $str .= '...'; + $args[] = $str; + } + } + $s .= $arr['function'].'('.implode(', ',$args).')'; + + + $s .= @sprintf($fmt, $arr['line'],$arr['file'],basename($arr['file'])); + + $s .= "\n"; + } + if ($html) $s .= '</pre>'; + if ($printOrArr) print $s; + + return $s; +} + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb-pager.inc.php b/framework/DataAccess/adodb/adodb-pager.inc.php new file mode 100644 index 00000000..04bea98e --- /dev/null +++ b/framework/DataAccess/adodb/adodb-pager.inc.php @@ -0,0 +1,290 @@ +<?php + +/* + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 4 for best viewing. + + This class provides recordset pagination with + First/Prev/Next/Last links. + + Feel free to modify this class for your own use as + it is very basic. To learn how to use it, see the + example in adodb/tests/testpaging.php. + + "Pablo Costa" <pablo@cbsp.com.br> implemented Render_PageLinks(). + + Please note, this class is entirely unsupported, + and no free support requests except for bug reports + will be entertained by the author. + +*/ +class ADODB_Pager { + var $id; // unique id for pager (defaults to 'adodb') + var $db; // ADODB connection object + var $sql; // sql used + var $rs; // recordset generated + var $curr_page; // current page number before Render() called, calculated in constructor + var $rows; // number of rows per page + var $linksPerPage=10; // number of links per page in navigation bar + var $showPageLinks; + + var $gridAttributes = 'width=100% border=1 bgcolor=white'; + + // Localize text strings here + var $first = '<code>|<</code>'; + var $prev = '<code><<</code>'; + var $next = '<code>>></code>'; + var $last = '<code>>|</code>'; + var $moreLinks = '...'; + var $startLinks = '...'; + var $gridHeader = false; + var $htmlSpecialChars = true; + var $page = 'Page'; + var $linkSelectedColor = 'red'; + var $cache = 0; #secs to cache with CachePageExecute() + + //---------------------------------------------- + // constructor + // + // $db adodb connection object + // $sql sql statement + // $id optional id to identify which pager, + // if you have multiple on 1 page. + // $id should be only be [a-z0-9]* + // + function ADODB_Pager(&$db,$sql,$id = 'adodb', $showPageLinks = false) + { + global $PHP_SELF; + + $curr_page = $id.'_curr_page'; + if (empty($PHP_SELF)) $PHP_SELF = htmlspecialchars($_SERVER['PHP_SELF']); // htmlspecialchars() to prevent XSS attacks + + $this->sql = $sql; + $this->id = $id; + $this->db = $db; + $this->showPageLinks = $showPageLinks; + + $next_page = $id.'_next_page'; + + if (isset($_GET[$next_page])) { + $_SESSION[$curr_page] = (integer) $_GET[$next_page]; + } + if (empty($_SESSION[$curr_page])) $_SESSION[$curr_page] = 1; ## at first page + + $this->curr_page = $_SESSION[$curr_page]; + + } + + //--------------------------- + // Display link to first page + function Render_First($anchor=true) + { + global $PHP_SELF; + if ($anchor) { + ?> + <a href="<?php echo $PHP_SELF,'?',$this->id;?>_next_page=1"><?php echo $this->first;?></a> + <?php + } else { + print "$this->first "; + } + } + + //-------------------------- + // Display link to next page + function render_next($anchor=true) + { + global $PHP_SELF; + + if ($anchor) { + ?> + <a href="<?php echo $PHP_SELF,'?',$this->id,'_next_page=',$this->rs->AbsolutePage() + 1 ?>"><?php echo $this->next;?></a> + <?php + } else { + print "$this->next "; + } + } + + //------------------ + // Link to last page + // + // for better performance with large recordsets, you can set + // $this->db->pageExecuteCountRows = false, which disables + // last page counting. + function render_last($anchor=true) + { + global $PHP_SELF; + + if (!$this->db->pageExecuteCountRows) return; + + if ($anchor) { + ?> + <a href="<?php echo $PHP_SELF,'?',$this->id,'_next_page=',$this->rs->LastPageNo() ?>"><?php echo $this->last;?></a> + <?php + } else { + print "$this->last "; + } + } + + //--------------------------------------------------- + // original code by "Pablo Costa" <pablo@cbsp.com.br> + function render_pagelinks() + { + global $PHP_SELF; + $pages = $this->rs->LastPageNo(); + $linksperpage = $this->linksPerPage ? $this->linksPerPage : $pages; + for($i=1; $i <= $pages; $i+=$linksperpage) + { + if($this->rs->AbsolutePage() >= $i) + { + $start = $i; + } + } + $numbers = ''; + $end = $start+$linksperpage-1; + $link = $this->id . "_next_page"; + if($end > $pages) $end = $pages; + + + if ($this->startLinks && $start > 1) { + $pos = $start - 1; + $numbers .= "<a href=$PHP_SELF?$link=$pos>$this->startLinks</a> "; + } + + for($i=$start; $i <= $end; $i++) { + if ($this->rs->AbsolutePage() == $i) + $numbers .= "<font color=$this->linkSelectedColor><b>$i</b></font> "; + else + $numbers .= "<a href=$PHP_SELF?$link=$i>$i</a> "; + + } + if ($this->moreLinks && $end < $pages) + $numbers .= "<a href=$PHP_SELF?$link=$i>$this->moreLinks</a> "; + print $numbers . ' '; + } + // Link to previous page + function render_prev($anchor=true) + { + global $PHP_SELF; + if ($anchor) { + ?> + <a href="<?php echo $PHP_SELF,'?',$this->id,'_next_page=',$this->rs->AbsolutePage() - 1 ?>"><?php echo $this->prev;?></a> + <?php + } else { + print "$this->prev "; + } + } + + //-------------------------------------------------------- + // Simply rendering of grid. You should override this for + // better control over the format of the grid + // + // We use output buffering to keep code clean and readable. + function RenderGrid() + { + global $gSQLBlockRows; // used by rs2html to indicate how many rows to display + include_once(ADODB_DIR.'/tohtml.inc.php'); + ob_start(); + $gSQLBlockRows = $this->rows; + rs2html($this->rs,$this->gridAttributes,$this->gridHeader,$this->htmlSpecialChars); + $s = ob_get_contents(); + ob_end_clean(); + return $s; + } + + //------------------------------------------------------- + // Navigation bar + // + // we use output buffering to keep the code easy to read. + function RenderNav() + { + ob_start(); + if (!$this->rs->AtFirstPage()) { + $this->Render_First(); + $this->Render_Prev(); + } else { + $this->Render_First(false); + $this->Render_Prev(false); + } + if ($this->showPageLinks){ + $this->Render_PageLinks(); + } + if (!$this->rs->AtLastPage()) { + $this->Render_Next(); + $this->Render_Last(); + } else { + $this->Render_Next(false); + $this->Render_Last(false); + } + $s = ob_get_contents(); + ob_end_clean(); + return $s; + } + + //------------------- + // This is the footer + function RenderPageCount() + { + if (!$this->db->pageExecuteCountRows) return ''; + $lastPage = $this->rs->LastPageNo(); + if ($lastPage == -1) $lastPage = 1; // check for empty rs. + if ($this->curr_page > $lastPage) $this->curr_page = 1; + return "<font size=-1>$this->page ".$this->curr_page."/".$lastPage."</font>"; + } + + //----------------------------------- + // Call this class to draw everything. + function Render($rows=10) + { + global $ADODB_COUNTRECS; + + $this->rows = $rows; + + if ($this->db->dataProvider == 'informix') $this->db->cursorType = IFX_SCROLL; + + $savec = $ADODB_COUNTRECS; + if ($this->db->pageExecuteCountRows) $ADODB_COUNTRECS = true; + if ($this->cache) + $rs = &$this->db->CachePageExecute($this->cache,$this->sql,$rows,$this->curr_page); + else + $rs = &$this->db->PageExecute($this->sql,$rows,$this->curr_page); + $ADODB_COUNTRECS = $savec; + + $this->rs = &$rs; + if (!$rs) { + print "<h3>Query failed: $this->sql</h3>"; + return; + } + + if (!$rs->EOF && (!$rs->AtFirstPage() || !$rs->AtLastPage())) + $header = $this->RenderNav(); + else + $header = " "; + + $grid = $this->RenderGrid(); + $footer = $this->RenderPageCount(); + + $this->RenderLayout($header,$grid,$footer); + + $rs->Close(); + $this->rs = false; + } + + //------------------------------------------------------ + // override this to control overall layout and formating + function RenderLayout($header,$grid,$footer,$attributes='border=1 bgcolor=beige') + { + echo "<table ".$attributes."><tr><td>", + $header, + "</td></tr><tr><td>", + $grid, + "</td></tr><tr><td>", + $footer, + "</td></tr></table>"; + } +} + + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb-pear.inc.php b/framework/DataAccess/adodb/adodb-pear.inc.php new file mode 100644 index 00000000..8ab6d83a --- /dev/null +++ b/framework/DataAccess/adodb/adodb-pear.inc.php @@ -0,0 +1,374 @@ +<?php +/** + * @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + * Released under both BSD license and Lesser GPL library license. + * Whenever there is any discrepancy between the two licenses, + * the BSD license will take precedence. + * + * Set tabs to 4 for best viewing. + * + * PEAR DB Emulation Layer for ADODB. + * + * The following code is modelled on PEAR DB code by Stig Bakken <ssb@fast.no> | + * and Tomas V.V.Cox <cox@idecnet.com>. Portions (c)1997-2002 The PHP Group. + */ + + /* + We support: + + DB_Common + --------- + query - returns PEAR_Error on error + limitQuery - return PEAR_Error on error + prepare - does not return PEAR_Error on error + execute - does not return PEAR_Error on error + setFetchMode - supports ASSOC and ORDERED + errorNative + quote + nextID + disconnect + + getOne + getAssoc + getRow + getCol + getAll + + DB_Result + --------- + numRows - returns -1 if not supported + numCols + fetchInto - does not support passing of fetchmode + fetchRows - does not support passing of fetchmode + free + */ + +define('ADODB_PEAR',dirname(__FILE__)); +include_once "PEAR.php"; +include_once ADODB_PEAR."/adodb-errorpear.inc.php"; +include_once ADODB_PEAR."/adodb.inc.php"; + +if (!defined('DB_OK')) { +define("DB_OK", 1); +define("DB_ERROR",-1); + +// autoExecute constants +define('DB_AUTOQUERY_INSERT', 1); +define('DB_AUTOQUERY_UPDATE', 2); + +/** + * This is a special constant that tells DB the user hasn't specified + * any particular get mode, so the default should be used. + */ + +define('DB_FETCHMODE_DEFAULT', 0); + +/** + * Column data indexed by numbers, ordered from 0 and up + */ + +define('DB_FETCHMODE_ORDERED', 1); + +/** + * Column data indexed by column names + */ + +define('DB_FETCHMODE_ASSOC', 2); + +/* for compatibility */ + +define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED); +define('DB_GETMODE_ASSOC', DB_FETCHMODE_ASSOC); + +/** + * these are constants for the tableInfo-function + * they are bitwised or'ed. so if there are more constants to be defined + * in the future, adjust DB_TABLEINFO_FULL accordingly + */ + +define('DB_TABLEINFO_ORDER', 1); +define('DB_TABLEINFO_ORDERTABLE', 2); +define('DB_TABLEINFO_FULL', 3); +} + +/** + * The main "DB" class is simply a container class with some static + * methods for creating DB objects as well as some utility functions + * common to all parts of DB. + * + */ + +class DB +{ + /** + * Create a new DB object for the specified database type + * + * @param $type string database type, for example "mysql" + * + * @return object a newly created DB object, or a DB error code on + * error + */ + + function &factory($type) + { + include_once(ADODB_DIR."/drivers/adodb-$type.inc.php"); + $obj = &NewADOConnection($type); + if (!is_object($obj)) $obj =& new PEAR_Error('Unknown Database Driver: '.$dsninfo['phptype'],-1); + return $obj; + } + + /** + * Create a new DB object and connect to the specified database + * + * @param $dsn mixed "data source name", see the DB::parseDSN + * method for a description of the dsn format. Can also be + * specified as an array of the format returned by DB::parseDSN. + * + * @param $options mixed if boolean (or scalar), tells whether + * this connection should be persistent (for backends that support + * this). This parameter can also be an array of options, see + * DB_common::setOption for more information on connection + * options. + * + * @return object a newly created DB connection object, or a DB + * error object on error + * + * @see DB::parseDSN + * @see DB::isError + */ + function &connect($dsn, $options = false) + { + if (is_array($dsn)) { + $dsninfo = $dsn; + } else { + $dsninfo = DB::parseDSN($dsn); + } + switch ($dsninfo["phptype"]) { + case 'pgsql': $type = 'postgres7'; break; + case 'ifx': $type = 'informix9'; break; + default: $type = $dsninfo["phptype"]; break; + } + + if (is_array($options) && isset($options["debug"]) && + $options["debug"] >= 2) { + // expose php errors with sufficient debug level + @include_once("adodb-$type.inc.php"); + } else { + @include_once("adodb-$type.inc.php"); + } + + @$obj =& NewADOConnection($type); + if (!is_object($obj)) { + $obj =& new PEAR_Error('Unknown Database Driver: '.$dsninfo['phptype'],-1); + return $obj; + } + if (is_array($options)) { + foreach($options as $k => $v) { + switch(strtolower($k)) { + case 'persist': + case 'persistent': $persist = $v; break; + #ibase + case 'dialect': $obj->dialect = $v; break; + case 'charset': $obj->charset = $v; break; + case 'buffers': $obj->buffers = $v; break; + #ado + case 'charpage': $obj->charPage = $v; break; + #mysql + case 'clientflags': $obj->clientFlags = $v; break; + } + } + } else { + $persist = false; + } + + if (isset($dsninfo['socket'])) $dsninfo['hostspec'] .= ':'.$dsninfo['socket']; + else if (isset($dsninfo['port'])) $dsninfo['hostspec'] .= ':'.$dsninfo['port']; + + if($persist) $ok = $obj->PConnect($dsninfo['hostspec'], $dsninfo['username'],$dsninfo['password'],$dsninfo['database']); + else $ok = $obj->Connect($dsninfo['hostspec'], $dsninfo['username'],$dsninfo['password'],$dsninfo['database']); + + if (!$ok) $obj = ADODB_PEAR_Error(); + return $obj; + } + + /** + * Return the DB API version + * + * @return int the DB API version number + */ + function apiVersion() + { + return 2; + } + + /** + * Tell whether a result code from a DB method is an error + * + * @param $value int result code + * + * @return bool whether $value is an error + */ + function isError($value) + { + if (!is_object($value)) return false; + $class = get_class($value); + return $class == 'pear_error' || is_subclass_of($value, 'pear_error') || + $class == 'db_error' || is_subclass_of($value, 'db_error'); + } + + + /** + * Tell whether a result code from a DB method is a warning. + * Warnings differ from errors in that they are generated by DB, + * and are not fatal. + * + * @param $value mixed result value + * + * @return bool whether $value is a warning + */ + function isWarning($value) + { + return false; + /* + return is_object($value) && + (get_class( $value ) == "db_warning" || + is_subclass_of($value, "db_warning"));*/ + } + + /** + * Parse a data source name + * + * @param $dsn string Data Source Name to be parsed + * + * @return array an associative array with the following keys: + * + * phptype: Database backend used in PHP (mysql, odbc etc.) + * dbsyntax: Database used with regards to SQL syntax etc. + * protocol: Communication protocol to use (tcp, unix etc.) + * hostspec: Host specification (hostname[:port]) + * database: Database to use on the DBMS server + * username: User name for login + * password: Password for login + * + * The format of the supplied DSN is in its fullest form: + * + * phptype(dbsyntax)://username:password@protocol+hostspec/database + * + * Most variations are allowed: + * + * phptype://username:password@protocol+hostspec:110//usr/db_file.db + * phptype://username:password@hostspec/database_name + * phptype://username:password@hostspec + * phptype://username@hostspec + * phptype://hostspec/database + * phptype://hostspec + * phptype(dbsyntax) + * phptype + * + * @author Tomas V.V.Cox <cox@idecnet.com> + */ + function parseDSN($dsn) + { + if (is_array($dsn)) { + return $dsn; + } + + $parsed = array( + 'phptype' => false, + 'dbsyntax' => false, + 'protocol' => false, + 'hostspec' => false, + 'database' => false, + 'username' => false, + 'password' => false + ); + + // Find phptype and dbsyntax + if (($pos = strpos($dsn, '://')) !== false) { + $str = substr($dsn, 0, $pos); + $dsn = substr($dsn, $pos + 3); + } else { + $str = $dsn; + $dsn = NULL; + } + + // Get phptype and dbsyntax + // $str => phptype(dbsyntax) + if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) { + $parsed['phptype'] = $arr[1]; + $parsed['dbsyntax'] = (empty($arr[2])) ? $arr[1] : $arr[2]; + } else { + $parsed['phptype'] = $str; + $parsed['dbsyntax'] = $str; + } + + if (empty($dsn)) { + return $parsed; + } + + // Get (if found): username and password + // $dsn => username:password@protocol+hostspec/database + if (($at = strpos($dsn,'@')) !== false) { + $str = substr($dsn, 0, $at); + $dsn = substr($dsn, $at + 1); + if (($pos = strpos($str, ':')) !== false) { + $parsed['username'] = urldecode(substr($str, 0, $pos)); + $parsed['password'] = urldecode(substr($str, $pos + 1)); + } else { + $parsed['username'] = urldecode($str); + } + } + + // Find protocol and hostspec + // $dsn => protocol+hostspec/database + if (($pos=strpos($dsn, '/')) !== false) { + $str = substr($dsn, 0, $pos); + $dsn = substr($dsn, $pos + 1); + } else { + $str = $dsn; + $dsn = NULL; + } + + // Get protocol + hostspec + // $str => protocol+hostspec + if (($pos=strpos($str, '+')) !== false) { + $parsed['protocol'] = substr($str, 0, $pos); + $parsed['hostspec'] = urldecode(substr($str, $pos + 1)); + } else { + $parsed['hostspec'] = urldecode($str); + } + + // Get dabase if any + // $dsn => database + if (!empty($dsn)) { + $parsed['database'] = $dsn; + } + + return $parsed; + } + + /** + * Load a PHP database extension if it is not loaded already. + * + * @access public + * + * @param $name the base name of the extension (without the .so or + * .dll suffix) + * + * @return bool true if the extension was already or successfully + * loaded, false if it could not be loaded + */ + function assertExtension($name) + { + if (!extension_loaded($name)) { + $dlext = (strncmp(PHP_OS,'WIN',3) === 0) ? '.dll' : '.so'; + @dl($name . $dlext); + } + if (!extension_loaded($name)) { + return false; + } + return true; + } +} + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb-perf.inc.php b/framework/DataAccess/adodb/adodb-perf.inc.php new file mode 100644 index 00000000..cc95e254 --- /dev/null +++ b/framework/DataAccess/adodb/adodb-perf.inc.php @@ -0,0 +1,1053 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. See License.txt. + Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Library for basic performance monitoring and tuning. + + My apologies if you see code mixed with presentation. The presentation suits + my needs. If you want to separate code from presentation, be my guest. Patches + are welcome. + +*/ + +if (!defined('ADODB_DIR')) include_once(dirname(__FILE__).'/adodb.inc.php'); +include_once(ADODB_DIR.'/tohtml.inc.php'); + +define( 'ADODB_OPT_HIGH', 2); +define( 'ADODB_OPT_LOW', 1); + +// returns in K the memory of current process, or 0 if not known +function adodb_getmem() +{ + if (function_exists('memory_get_usage')) + return (integer) ((memory_get_usage()+512)/1024); + + $pid = getmypid(); + + if ( strncmp(strtoupper(PHP_OS),'WIN',3)==0) { + $output = array(); + + exec('tasklist /FI "PID eq ' . $pid. '" /FO LIST', $output); + return substr($output[5], strpos($output[5], ':') + 1); + } + + /* Hopefully UNIX */ + exec("ps --pid $pid --no-headers -o%mem,size", $output); + if (sizeof($output) == 0) return 0; + + $memarr = explode(' ',$output[0]); + if (sizeof($memarr)>=2) return (integer) $memarr[1]; + + return 0; +} + +// avoids localization problems where , is used instead of . +function adodb_round($n,$prec) +{ + return number_format($n, $prec, '.', ''); +} + +/* return microtime value as a float */ +function adodb_microtime() +{ + $t = microtime(); + $t = explode(' ',$t); + return (float)$t[1]+ (float)$t[0]; +} + +/* sql code timing */ +function& adodb_log_sql(&$conn,$sql,$inputarr) +{ + + $perf_table = adodb_perf::table(); + $conn->fnExecute = false; + $t0 = microtime(); + $rs =& $conn->Execute($sql,$inputarr); + $t1 = microtime(); + + if (!empty($conn->_logsql)) { + $conn->_logsql = false; // disable logsql error simulation + $dbT = $conn->databaseType; + + $a0 = split(' ',$t0); + $a0 = (float)$a0[1]+(float)$a0[0]; + + $a1 = split(' ',$t1); + $a1 = (float)$a1[1]+(float)$a1[0]; + + $time = $a1 - $a0; + + if (!$rs) { + $errM = $conn->ErrorMsg(); + $errN = $conn->ErrorNo(); + $conn->lastInsID = 0; + $tracer = substr('ERROR: '.htmlspecialchars($errM),0,250); + } else { + $tracer = ''; + $errM = ''; + $errN = 0; + $dbg = $conn->debug; + $conn->debug = false; + if (!is_object($rs) || $rs->dataProvider == 'empty') + $conn->_affected = $conn->affected_rows(true); + $conn->lastInsID = @$conn->Insert_ID(); + $conn->debug = $dbg; + } + if (isset($_SERVER['HTTP_HOST'])) { + $tracer .= '<br>'.$_SERVER['HTTP_HOST']; + if (isset($_SERVER['PHP_SELF'])) $tracer .= $_SERVER['PHP_SELF']; + } else + if (isset($_SERVER['PHP_SELF'])) $tracer .= '<br>'.$_SERVER['PHP_SELF']; + //$tracer .= (string) adodb_backtrace(false); + + $tracer = (string) substr($tracer,0,500); + + if (is_array($inputarr)) { + if (is_array(reset($inputarr))) $params = 'Array sizeof='.sizeof($inputarr); + else { + // Quote string parameters so we can see them in the + // performance stats. This helps spot disabled indexes. + $xar_params = $inputarr; + foreach ($xar_params as $xar_param_key => $xar_param) { + if (gettype($xar_param) == 'string') + $xar_params[$xar_param_key] = '"' . $xar_param . '"'; + } + $params = implode(', ', $xar_params); + if (strlen($params) >= 3000) $params = substr($params, 0, 3000); + } + } else { + $params = ''; + } + + if (is_array($sql)) $sql = $sql[0]; + $arr = array('b'=>strlen($sql).'.'.crc32($sql), + 'c'=>substr($sql,0,3900), 'd'=>$params,'e'=>$tracer,'f'=>adodb_round($time,6)); + //var_dump($arr); + $saved = $conn->debug; + $conn->debug = 0; + + $d = $conn->sysTimeStamp; + if (empty($d)) $d = date("'Y-m-d H:i:s'"); + if ($conn->dataProvider == 'oci8' && $dbT != 'oci8po') { + $isql = "insert into $perf_table values($d,:b,:c,:d,:e,:f)"; + } else if ($dbT == 'odbc_mssql' || $dbT == 'informix' || $dbT == 'odbtp') { + $timer = $arr['f']; + if ($dbT == 'informix') $sql2 = substr($sql2,0,230); + + $sql1 = $conn->qstr($arr['b']); + $sql2 = $conn->qstr($arr['c']); + $params = $conn->qstr($arr['d']); + $tracer = $conn->qstr($arr['e']); + + $isql = "insert into $perf_table (created,sql0,sql1,params,tracer,timer) values($d,$sql1,$sql2,$params,$tracer,$timer)"; + if ($dbT == 'informix') $isql = str_replace(chr(10),' ',$isql); + $arr = false; + } else { + $isql = "insert into $perf_table (created,sql0,sql1,params,tracer,timer) values( $d,?,?,?,?,?)"; + } + + $ok = $conn->Execute($isql,$arr); + $conn->debug = $saved; + + if ($ok) { + $conn->_logsql = true; + } else { + $err2 = $conn->ErrorMsg(); + $conn->_logsql = true; // enable logsql error simulation + $perf =& NewPerfMonitor($conn); + if ($perf) { + if ($perf->CreateLogTable()) $ok = $conn->Execute($isql,$arr); + } else { + $ok = $conn->Execute("create table $perf_table ( + created varchar(50), + sql0 varchar(250), + sql1 varchar(4000), + params varchar(3000), + tracer varchar(500), + timer decimal(16,6))"); + } + if (!$ok) { + ADOConnection::outp( "<p><b>LOGSQL Insert Failed</b>: $isql<br>$err2</p>"); + $conn->_logsql = false; + } + } + $conn->_errorMsg = $errM; + $conn->_errorCode = $errN; + } + $conn->fnExecute = 'adodb_log_sql'; + return $rs; +} + + +/* +The settings data structure is an associative array that database parameter per element. + +Each database parameter element in the array is itself an array consisting of: + +0: category code, used to group related db parameters +1: either + a. sql string to retrieve value, eg. "select value from v\$parameter where name='db_block_size'", + b. array holding sql string and field to look for, e.g. array('show variables','table_cache'), + c. a string prefixed by =, then a PHP method of the class is invoked, + e.g. to invoke $this->GetIndexValue(), set this array element to '=GetIndexValue', +2: description of the database parameter +*/ + +class adodb_perf { + var $conn; + var $color = '#F0F0F0'; + var $table = '<table border=1 bgcolor=white>'; + var $titles = '<tr><td><b>Parameter</b></td><td><b>Value</b></td><td><b>Description</b></td></tr>'; + var $warnRatio = 90; + var $tablesSQL = false; + var $cliFormat = "%32s => %s \r\n"; + var $sql1 = 'sql1'; // used for casting sql1 to text for mssql + var $explain = true; + var $helpurl = "<a href=http://phplens.com/adodb/reference.functions.fnexecute.and.fncacheexecute.properties.html#logsql>LogSQL help</a>"; + var $createTableSQL = false; + var $maxLength = 2000; + + // Sets the tablename to be used + function table($newtable = false) + { + static $_table; + + if (!empty($newtable)) $_table = $newtable; + if (empty($_table)) $_table = 'adodb_logsql'; + return $_table; + } + + // returns array with info to calculate CPU Load + function _CPULoad() + { +/* + +cpu 524152 2662 2515228 336057010 +cpu0 264339 1408 1257951 168025827 +cpu1 259813 1254 1257277 168031181 +page 622307 25475680 +swap 24 1891 +intr 890153570 868093576 6 0 4 4 0 6 1 2 0 0 0 124 0 8098760 2 13961053 0 0 0 0 0 0 0 0 0 0 0 0 0 16 16 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +disk_io: (3,0):(3144904,54369,610378,3090535,50936192) (3,1):(3630212,54097,633016,3576115,50951320) +ctxt 66155838 +btime 1062315585 +processes 69293 + +*/ + // Algorithm is taken from + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/example__obtaining_raw_performance_data.asp + if (strncmp(PHP_OS,'WIN',3)==0) { + if (PHP_VERSION == '5.0.0') return false; + if (PHP_VERSION == '5.0.1') return false; + if (PHP_VERSION == '5.0.2') return false; + if (PHP_VERSION == '5.0.3') return false; + if (PHP_VERSION == '4.3.10') return false; # see http://bugs.php.net/bug.php?id=31737 + + @$c = new COM("WinMgmts:{impersonationLevel=impersonate}!Win32_PerfRawData_PerfOS_Processor.Name='_Total'"); + if (!$c) return false; + + $info[0] = $c->PercentProcessorTime; + $info[1] = 0; + $info[2] = 0; + $info[3] = $c->TimeStamp_Sys100NS; + //print_r($info); + return $info; + } + + // Algorithm - Steve Blinch (BlitzAffe Online, http://www.blitzaffe.com) + $statfile = '/proc/stat'; + if (!file_exists($statfile)) return false; + + $fd = fopen($statfile,"r"); + if (!$fd) return false; + + $statinfo = explode("\n",fgets($fd, 1024)); + fclose($fd); + foreach($statinfo as $line) { + $info = explode(" ",$line); + if($info[0]=="cpu") { + array_shift($info); // pop off "cpu" + if(!$info[0]) array_shift($info); // pop off blank space (if any) + return $info; + } + } + + return false; + + } + + /* NOT IMPLEMENTED */ + function MemInfo() + { + /* + + total: used: free: shared: buffers: cached: +Mem: 1055289344 917299200 137990144 0 165437440 599773184 +Swap: 2146775040 11055104 2135719936 +MemTotal: 1030556 kB +MemFree: 134756 kB +MemShared: 0 kB +Buffers: 161560 kB +Cached: 581384 kB +SwapCached: 4332 kB +Active: 494468 kB +Inact_dirty: 322856 kB +Inact_clean: 24256 kB +Inact_target: 168316 kB +HighTotal: 131064 kB +HighFree: 1024 kB +LowTotal: 899492 kB +LowFree: 133732 kB +SwapTotal: 2096460 kB +SwapFree: 2085664 kB +Committed_AS: 348732 kB + */ + } + + + /* + Remember that this is client load, not db server load! + */ + var $_lastLoad; + function CPULoad() + { + $info = $this->_CPULoad(); + if (!$info) return false; + + if (empty($this->_lastLoad)) { + sleep(1); + $this->_lastLoad = $info; + $info = $this->_CPULoad(); + } + + $last = $this->_lastLoad; + $this->_lastLoad = $info; + + $d_user = $info[0] - $last[0]; + $d_nice = $info[1] - $last[1]; + $d_system = $info[2] - $last[2]; + $d_idle = $info[3] - $last[3]; + + //printf("Delta - User: %f Nice: %f System: %f Idle: %f<br>",$d_user,$d_nice,$d_system,$d_idle); + + if (strncmp(PHP_OS,'WIN',3)==0) { + if ($d_idle < 1) $d_idle = 1; + return 100*(1-$d_user/$d_idle); + }else { + $total=$d_user+$d_nice+$d_system+$d_idle; + if ($total<1) $total=1; + return 100*($d_user+$d_nice+$d_system)/$total; + } + } + + function Tracer($sql) + { + $perf_table = adodb_perf::table(); + $saveE = $this->conn->fnExecute; + $this->conn->fnExecute = false; + + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false); + + $sqlq = $this->conn->qstr($sql); + $arr = $this->conn->GetArray( +"select count(*),tracer + from $perf_table where sql1=$sqlq + group by tracer + order by 1 desc"); + $s = ''; + if ($arr) { + $s .= '<h3>Scripts Affected</h3>'; + foreach($arr as $k) { + $s .= sprintf("%4d",$k[0]).' '.strip_tags($k[1]).'<br>'; + } + } + + if (isset($savem)) $this->conn->SetFetchMode($savem); + $ADODB_CACHE_MODE = $save; + $this->conn->fnExecute = $saveE; + return $s; + } + + /* + Explain Plan for $sql. + If only a snippet of the $sql is passed in, then $partial will hold the crc32 of the + actual sql. + */ + function Explain($sql,$partial=false) + { + return false; + } + + function InvalidSQL($numsql = 10) + { + + if (isset($_GET['sql'])) return; + $s = '<h3>Invalid SQL</h3>'; + $saveE = $this->conn->fnExecute; + $this->conn->fnExecute = false; + $perf_table = adodb_perf::table(); + $rs =& $this->conn->SelectLimit("select distinct count(*),sql1,tracer as error_msg from $perf_table where tracer like 'ERROR:%' group by sql1,tracer order by 1 desc",$numsql);//,$numsql); + $this->conn->fnExecute = $saveE; + if ($rs) { + $s .= rs2html($rs,false,false,false,false); + } else + return "<p>$this->helpurl. ".$this->conn->ErrorMsg()."</p>"; + + return $s; + } + + + /* + This script identifies the longest running SQL + */ + function _SuspiciousSQL($numsql = 10) + { + global $ADODB_FETCH_MODE; + + $perf_table = adodb_perf::table(); + $saveE = $this->conn->fnExecute; + $this->conn->fnExecute = false; + + if (isset($_GET['exps']) && isset($_GET['sql'])) { + $partial = !empty($_GET['part']); + echo "<a name=explain></a>".$this->Explain($_GET['sql'],$partial)."\n"; + } + + if (isset($_GET['sql'])) return; + $sql1 = $this->sql1; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false); + //$this->conn->debug=1; + $rs =& $this->conn->SelectLimit( + "select avg(timer) as avg_timer,$sql1,count(*),max(timer) as max_timer,min(timer) as min_timer + from $perf_table + where {$this->conn->upperCase}({$this->conn->substr}(sql0,1,5)) not in ('DROP ','INSER','COMMI','CREAT') + and (tracer is null or tracer not like 'ERROR:%') + group by sql1 + order by 1 desc",$numsql); + if (isset($savem)) $this->conn->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + $this->conn->fnExecute = $saveE; + + if (!$rs) return "<p>$this->helpurl. ".$this->conn->ErrorMsg()."</p>"; + $s = "<h3>Suspicious SQL</h3> +<font size=1>The following SQL have high average execution times</font><br> +<table border=1 bgcolor=white><tr><td><b>Avg Time</b><td><b>Count</b><td><b>SQL</b><td><b>Max</b><td><b>Min</b></tr>\n"; + $max = $this->maxLength; + while (!$rs->EOF) { + $sql = $rs->fields[1]; + $raw = urlencode($sql); + if (strlen($raw)>$max-100) { + $sql2 = substr($sql,0,$max-500); + $raw = urlencode($sql2).'&part='.crc32($sql); + } + $prefix = "<a target=sql".rand()." href=\"?hidem=1&exps=1&sql=".$raw."&x#explain\">"; + $suffix = "</a>"; + if ($this->explain == false || strlen($prefix)>$max) { + $suffix = ' ... <i>String too long for GET parameter: '.strlen($prefix).'</i>'; + $prefix = ''; + } + $s .= "<tr><td>".adodb_round($rs->fields[0],6)."<td align=right>".$rs->fields[2]."<td><font size=-1>".$prefix.htmlspecialchars($sql).$suffix."</font>". + "<td>".$rs->fields[3]."<td>".$rs->fields[4]."</tr>"; + $rs->MoveNext(); + } + return $s."</table>"; + + } + + function CheckMemory() + { + return ''; + } + + + function SuspiciousSQL($numsql=10) + { + return adodb_perf::_SuspiciousSQL($numsql); + } + + function ExpensiveSQL($numsql=10) + { + return adodb_perf::_ExpensiveSQL($numsql); + } + + + /* + This reports the percentage of load on the instance due to the most + expensive few SQL statements. Tuning these statements can often + make huge improvements in overall system performance. + */ + function _ExpensiveSQL($numsql = 10) + { + global $ADODB_FETCH_MODE; + + $perf_table = adodb_perf::table(); + $saveE = $this->conn->fnExecute; + $this->conn->fnExecute = false; + + if (isset($_GET['expe']) && isset($_GET['sql'])) { + $partial = !empty($_GET['part']); + echo "<a name=explain></a>".$this->Explain($_GET['sql'],$partial)."\n"; + } + + if (isset($_GET['sql'])) return; + + $sql1 = $this->sql1; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false); + + $rs =& $this->conn->SelectLimit( + "select sum(timer) as total,$sql1,count(*),max(timer) as max_timer,min(timer) as min_timer + from $perf_table + where {$this->conn->upperCase}({$this->conn->substr}(sql0,1,5)) not in ('DROP ','INSER','COMMI','CREAT') + and (tracer is null or tracer not like 'ERROR:%') + group by sql1 + having count(*)>1 + order by 1 desc",$numsql); + if (isset($savem)) $this->conn->SetFetchMode($savem); + $this->conn->fnExecute = $saveE; + $ADODB_FETCH_MODE = $save; + if (!$rs) return "<p>$this->helpurl. ".$this->conn->ErrorMsg()."</p>"; + $s = "<h3>Expensive SQL</h3> +<font size=1>Tuning the following SQL could reduce the server load substantially</font><br> +<table border=1 bgcolor=white><tr><td><b>Load</b><td><b>Count</b><td><b>SQL</b><td><b>Max</b><td><b>Min</b></tr>\n"; + $max = $this->maxLength; + while (!$rs->EOF) { + $sql = $rs->fields[1]; + $raw = urlencode($sql); + if (strlen($raw)>$max-100) { + $sql2 = substr($sql,0,$max-500); + $raw = urlencode($sql2).'&part='.crc32($sql); + } + $prefix = "<a target=sqle".rand()." href=\"?hidem=1&expe=1&sql=".$raw."&x#explain\">"; + $suffix = "</a>"; + if($this->explain == false || strlen($prefix>$max)) { + $prefix = ''; + $suffix = ''; + } + $s .= "<tr><td>".adodb_round($rs->fields[0],6)."<td align=right>".$rs->fields[2]."<td><font size=-1>".$prefix.htmlspecialchars($sql).$suffix."</font>". + "<td>".$rs->fields[3]."<td>".$rs->fields[4]."</tr>"; + $rs->MoveNext(); + } + return $s."</table>"; + } + + /* + Raw function to return parameter value from $settings. + */ + function DBParameter($param) + { + if (empty($this->settings[$param])) return false; + $sql = $this->settings[$param][1]; + return $this->_DBParameter($sql); + } + + /* + Raw function returning array of poll paramters + */ + function &PollParameters() + { + $arr[0] = (float)$this->DBParameter('data cache hit ratio'); + $arr[1] = (float)$this->DBParameter('data reads'); + $arr[2] = (float)$this->DBParameter('data writes'); + $arr[3] = (integer) $this->DBParameter('current connections'); + return $arr; + } + + /* + Low-level Get Database Parameter + */ + function _DBParameter($sql) + { + $savelog = $this->conn->LogSQL(false); + if (is_array($sql)) { + global $ADODB_FETCH_MODE; + + $sql1 = $sql[0]; + $key = $sql[1]; + if (sizeof($sql)>2) $pos = $sql[2]; + else $pos = 1; + if (sizeof($sql)>3) $coef = $sql[3]; + else $coef = false; + $ret = false; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->conn->fetchMode !== false) $savem = $this->conn->SetFetchMode(false); + + $rs = $this->conn->Execute($sql1); + + if (isset($savem)) $this->conn->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + if ($rs) { + while (!$rs->EOF) { + $keyf = reset($rs->fields); + if (trim($keyf) == $key) { + $ret = $rs->fields[$pos]; + if ($coef) $ret *= $coef; + break; + } + $rs->MoveNext(); + } + $rs->Close(); + } + $this->conn->LogSQL($savelog); + return $ret; + } else { + if (strncmp($sql,'=',1) == 0) { + $fn = substr($sql,1); + return $this->$fn(); + } + $sql = str_replace('$DATABASE',$this->conn->database,$sql); + $ret = $this->conn->GetOne($sql); + $this->conn->LogSQL($savelog); + + return $ret; + } + } + + /* + Warn if cache ratio falls below threshold. Displayed in "Description" column. + */ + function WarnCacheRatio($val) + { + if ($val < $this->warnRatio) + return '<font color=red><b>Cache ratio should be at least '.$this->warnRatio.'%</b></font>'; + else return ''; + } + + /***********************************************************************************************/ + // HIGH LEVEL UI FUNCTIONS + /***********************************************************************************************/ + + + function UI($pollsecs=5) + { + + $perf_table = adodb_perf::table(); + $conn = $this->conn; + + $app = $conn->host; + if ($conn->host && $conn->database) $app .= ', db='; + $app .= $conn->database; + + if ($app) $app .= ', '; + $savelog = $this->conn->LogSQL(false); + $info = $conn->ServerInfo(); + if (isset($_GET['clearsql'])) { + $this->conn->Execute("delete from $perf_table"); + } + $this->conn->LogSQL($savelog); + + // magic quotes + + if (isset($_GET['sql']) && get_magic_quotes_gpc()) { + $_GET['sql'] = $_GET['sql'] = str_replace(array("\\'",'\"'),array("'",'"'),$_GET['sql']); + } + + if (!isset($_SESSION['ADODB_PERF_SQL'])) $nsql = $_SESSION['ADODB_PERF_SQL'] = 10; + else $nsql = $_SESSION['ADODB_PERF_SQL']; + + $app .= $info['description']; + + + if (isset($_GET['do'])) $do = $_GET['do']; + else if (isset($_POST['do'])) $do = $_POST['do']; + else if (isset($_GET['sql'])) $do = 'viewsql'; + else $do = 'stats'; + + if (isset($_GET['nsql'])) { + if ($_GET['nsql'] > 0) $nsql = $_SESSION['ADODB_PERF_SQL'] = (integer) $_GET['nsql']; + } + echo "<title>ADOdb Performance Monitor on $app</title><body bgcolor=white>"; + if ($do == 'viewsql') $form = "<td><form># SQL:<input type=hidden value=viewsql name=do> <input type=text size=4 name=nsql value=$nsql><input type=submit value=Go></td></form>"; + else $form = "<td> </td>"; + + $allowsql = !defined('ADODB_PERF_NO_RUN_SQL'); + + if (empty($_GET['hidem'])) + echo "<table border=1 width=100% bgcolor=lightyellow><tr><td colspan=2> + <b><a href=http://adodb.sourceforge.net/?perf=1>ADOdb</a> Performance Monitor</b> <font size=1>for $app</font></tr><tr><td> + <a href=?do=stats><b>Performance Stats</b></a> <a href=?do=viewsql><b>View SQL</b></a> + <a href=?do=tables><b>View Tables</b></a> <a href=?do=poll><b>Poll Stats</b></a>", + $allowsql ? ' <a href=?do=dosql><b>Run SQL</b></a>' : '', + "$form", + "</tr></table>"; + + + switch ($do) { + default: + case 'stats': + echo $this->HealthCheck(); + //$this->conn->debug=1; + echo $this->CheckMemory(); + break; + case 'poll': + echo "<iframe width=720 height=80% + src=\"{$_SERVER['PHP_SELF']}?do=poll2&hidem=1\"></iframe>"; + break; + case 'poll2': + echo "<pre>"; + $this->Poll($pollsecs); + break; + + case 'dosql': + if (!$allowsql) break; + + $this->DoSQLForm(); + break; + case 'viewsql': + if (empty($_GET['hidem'])) + echo " <a href=\"?do=viewsql&clearsql=1\">Clear SQL Log</a><br>"; + echo($this->SuspiciousSQL($nsql)); + echo($this->ExpensiveSQL($nsql)); + echo($this->InvalidSQL($nsql)); + break; + case 'tables': + echo $this->Tables(); break; + } + global $ADODB_vers; + echo "<p><div align=center><font size=1>$ADODB_vers Sponsored by <a href=http://phplens.com/>phpLens</a></font></div>"; + } + + /* + Runs in infinite loop, returning real-time statistics + */ + function Poll($secs=5) + { + $this->conn->fnExecute = false; + //$this->conn->debug=1; + if ($secs <= 1) $secs = 1; + echo "Accumulating statistics, every $secs seconds...\n";flush(); + $arro =& $this->PollParameters(); + $cnt = 0; + set_time_limit(0); + sleep($secs); + while (1) { + + $arr =& $this->PollParameters(); + + $hits = sprintf('%2.2f',$arr[0]); + $reads = sprintf('%12.4f',($arr[1]-$arro[1])/$secs); + $writes = sprintf('%12.4f',($arr[2]-$arro[2])/$secs); + $sess = sprintf('%5d',$arr[3]); + + $load = $this->CPULoad(); + if ($load !== false) { + $oslabel = 'WS-CPU%'; + $osval = sprintf(" %2.1f ",(float) $load); + }else { + $oslabel = ''; + $osval = ''; + } + if ($cnt % 10 == 0) echo " Time ".$oslabel." Hit% Sess Reads/s Writes/s\n"; + $cnt += 1; + echo date('H:i:s').' '.$osval."$hits $sess $reads $writes\n"; + flush(); + + if (connection_aborted()) return; + + sleep($secs); + $arro = $arr; + } + } + + /* + Returns basic health check in a command line interface + */ + function HealthCheckCLI() + { + return $this->HealthCheck(true); + } + + + /* + Returns basic health check as HTML + */ + function HealthCheck($cli=false) + { + $saveE = $this->conn->fnExecute; + $this->conn->fnExecute = false; + if ($cli) $html = ''; + else $html = $this->table.'<tr><td colspan=3><h3>'.$this->conn->databaseType.'</h3></td></tr>'.$this->titles; + + $oldc = false; + $bgc = ''; + foreach($this->settings as $name => $arr) { + if ($arr === false) break; + + if (!is_string($name)) { + if ($cli) $html .= " -- $arr -- \n"; + else $html .= "<tr bgcolor=$this->color><td colspan=3><i>$arr</i> </td></tr>"; + continue; + } + + if (!is_array($arr)) break; + $category = $arr[0]; + $how = $arr[1]; + if (sizeof($arr)>2) $desc = $arr[2]; + else $desc = ' '; + + + if ($category == 'HIDE') continue; + + $val = $this->_DBParameter($how); + + if ($desc && strncmp($desc,"=",1) === 0) { + $fn = substr($desc,1); + $desc = $this->$fn($val); + } + + if ($val === false) { + $m = $this->conn->ErrorMsg(); + $val = "Error: $m"; + } else { + if (is_numeric($val) && $val >= 256*1024) { + if ($val % (1024*1024) == 0) { + $val /= (1024*1024); + $val .= 'M'; + } else if ($val % 1024 == 0) { + $val /= 1024; + $val .= 'K'; + } + //$val = htmlspecialchars($val); + } + } + if ($category != $oldc) { + $oldc = $category; + //$bgc = ($bgc == ' bgcolor='.$this->color) ? ' bgcolor=white' : ' bgcolor='.$this->color; + } + if (strlen($desc)==0) $desc = ' '; + if (strlen($val)==0) $val = ' '; + if ($cli) { + $html .= str_replace(' ','',sprintf($this->cliFormat,strip_tags($name),strip_tags($val),strip_tags($desc))); + + }else { + $html .= "<tr$bgc><td>".$name.'</td><td>'.$val.'</td><td>'.$desc."</td></tr>\n"; + } + } + + if (!$cli) $html .= "</table>\n"; + $this->conn->fnExecute = $saveE; + + return $html; + } + + function Tables($orderby='1') + { + if (!$this->tablesSQL) return false; + + $savelog = $this->conn->LogSQL(false); + $rs = $this->conn->Execute($this->tablesSQL.' order by '.$orderby); + $this->conn->LogSQL($savelog); + $html = rs2html($rs,false,false,false,false); + return $html; + } + + + function CreateLogTable() + { + if (!$this->createTableSQL) return false; + + $savelog = $this->conn->LogSQL(false); + $ok = $this->conn->Execute($this->createTableSQL); + $this->conn->LogSQL($savelog); + return ($ok) ? true : false; + } + + function DoSQLForm() + { + + + $PHP_SELF = $_SERVER['PHP_SELF']; + $sql = isset($_REQUEST['sql']) ? $_REQUEST['sql'] : ''; + + if (isset($_SESSION['phplens_sqlrows'])) $rows = $_SESSION['phplens_sqlrows']; + else $rows = 3; + + if (isset($_REQUEST['SMALLER'])) { + $rows /= 2; + if ($rows < 3) $rows = 3; + $_SESSION['phplens_sqlrows'] = $rows; + } + if (isset($_REQUEST['BIGGER'])) { + $rows *= 2; + $_SESSION['phplens_sqlrows'] = $rows; + } + +?> + +<form method="POST" action="<?php echo $PHP_SELF ?>"> +<table><tr> +<td> Form size: <input type="submit" value=" < " name="SMALLER"><input type="submit" value=" > > " name="BIGGER"> +</td> +<td align=right> +<input type="submit" value=" Run SQL Below " name="RUN"><input type=hidden name=do value=dosql> +</td></tr> + <tr> + <td colspan=2><textarea rows=<?php print $rows; ?> name="sql" cols="80"><?php print htmlspecialchars($sql) ?></textarea> + </td> + </tr> + </table> +</form> + +<?php + if (!isset($_REQUEST['sql'])) return; + + $sql = $this->undomq(trim($sql)); + if (substr($sql,strlen($sql)-1) === ';') { + $print = true; + $sqla = $this->SplitSQL($sql); + } else { + $print = false; + $sqla = array($sql); + } + foreach($sqla as $sqls) { + + if (!$sqls) continue; + + if ($print) { + print "<p>".htmlspecialchars($sqls)."</p>"; + flush(); + } + $savelog = $this->conn->LogSQL(false); + $rs = $this->conn->Execute($sqls); + $this->conn->LogSQL($savelog); + if ($rs && is_object($rs) && !$rs->EOF) { + rs2html($rs); + while ($rs->NextRecordSet()) { + print "<table width=98% bgcolor=#C0C0FF><tr><td> </td></tr></table>"; + rs2html($rs); + } + } else { + $e1 = (integer) $this->conn->ErrorNo(); + $e2 = $this->conn->ErrorMsg(); + if (($e1) || ($e2)) { + if (empty($e1)) $e1 = '-1'; // postgresql fix + print ' '.$e1.': '.$e2; + } else { + print "<p>No Recordset returned<br></p>"; + } + } + } // foreach + } + + function SplitSQL($sql) + { + $arr = explode(';',$sql); + return $arr; + } + + function undomq($m) + { + if (get_magic_quotes_gpc()) { + // undo the damage + $m = str_replace('\\\\','\\',$m); + $m = str_replace('\"','"',$m); + $m = str_replace('\\\'','\'',$m); + } + return $m; +} + + + /************************************************************************/ + + /** + * Reorganise multiple table-indices/statistics/.. + * OptimizeMode could be given by last Parameter + * + * @example + * <pre> + * optimizeTables( 'tableA'); + * </pre> + * <pre> + * optimizeTables( 'tableA', 'tableB', 'tableC'); + * </pre> + * <pre> + * optimizeTables( 'tableA', 'tableB', ADODB_OPT_LOW); + * </pre> + * + * @param string table name of the table to optimize + * @param int mode optimization-mode + * <code>ADODB_OPT_HIGH</code> for full optimization + * <code>ADODB_OPT_LOW</code> for CPU-less optimization + * Default is LOW <code>ADODB_OPT_LOW</code> + * @author Markus Staab + * @return Returns <code>true</code> on success and <code>false</code> on error + */ + function OptimizeTables() + { + $args = func_get_args(); + $numArgs = func_num_args(); + + if ( $numArgs == 0) return false; + + $mode = ADODB_OPT_LOW; + $lastArg = $args[ $numArgs - 1]; + if ( !is_string($lastArg)) { + $mode = $lastArg; + unset( $args[ $numArgs - 1]); + } + + foreach( $args as $table) { + $this->optimizeTable( $table, $mode); + } + } + + /** + * Reorganise the table-indices/statistics/.. depending on the given mode. + * Default Implementation throws an error. + * + * @param string table name of the table to optimize + * @param int mode optimization-mode + * <code>ADODB_OPT_HIGH</code> for full optimization + * <code>ADODB_OPT_LOW</code> for CPU-less optimization + * Default is LOW <code>ADODB_OPT_LOW</code> + * @author Markus Staab + * @return Returns <code>true</code> on success and <code>false</code> on error + */ + function OptimizeTable( $table, $mode = ADODB_OPT_LOW) + { + ADOConnection::outp( sprintf( "<p>%s: '%s' not implemented for driver '%s'</p>", __CLASS__, __FUNCTION__, $this->conn->databaseType)); + return false; + } + + /** + * Reorganise current database. + * Default implementation loops over all <code>MetaTables()</code> and + * optimize each using <code>optmizeTable()</code> + * + * @author Markus Staab + * @return Returns <code>true</code> on success and <code>false</code> on error + */ + function optimizeDatabase() + { + $conn = $this->conn; + if ( !$conn) return false; + + $tables = $conn->MetaTables( 'TABLES'); + if ( !$tables ) return false; + + foreach( $tables as $table) { + if ( !$this->optimizeTable( $table)) { + return false; + } + } + + return true; + } + // end hack +} + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb-php4.inc.php b/framework/DataAccess/adodb/adodb-php4.inc.php new file mode 100644 index 00000000..459aa681 --- /dev/null +++ b/framework/DataAccess/adodb/adodb-php4.inc.php @@ -0,0 +1,16 @@ +<?php + +/* + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + + Set tabs to 4. +*/ + + +class ADODB_BASE_RS { +} + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb-time.inc.php b/framework/DataAccess/adodb/adodb-time.inc.php new file mode 100644 index 00000000..43947545 --- /dev/null +++ b/framework/DataAccess/adodb/adodb-time.inc.php @@ -0,0 +1,1327 @@ +<?php +/** +ADOdb Date Library, part of the ADOdb abstraction library +Download: http://phplens.com/phpeverywhere/ + +PHP native date functions use integer timestamps for computations. +Because of this, dates are restricted to the years 1901-2038 on Unix +and 1970-2038 on Windows due to integer overflow for dates beyond +those years. This library overcomes these limitations by replacing the +native function's signed integers (normally 32-bits) with PHP floating +point numbers (normally 64-bits). + +Dates from 100 A.D. to 3000 A.D. and later +have been tested. The minimum is 100 A.D. as <100 will invoke the +2 => 4 digit year conversion. The maximum is billions of years in the +future, but this is a theoretical limit as the computation of that year +would take too long with the current implementation of adodb_mktime(). + +This library replaces native functions as follows: + +<pre> + getdate() with adodb_getdate() + date() with adodb_date() + gmdate() with adodb_gmdate() + mktime() with adodb_mktime() + gmmktime() with adodb_gmmktime() + strftime() with adodb_strftime() + strftime() with adodb_gmstrftime() +</pre> + +The parameters are identical, except that adodb_date() accepts a subset +of date()'s field formats. Mktime() will convert from local time to GMT, +and date() will convert from GMT to local time, but daylight savings is +not handled currently. + +This library is independant of the rest of ADOdb, and can be used +as standalone code. + +PERFORMANCE + +For high speed, this library uses the native date functions where +possible, and only switches to PHP code when the dates fall outside +the 32-bit signed integer range. + +GREGORIAN CORRECTION + +Pope Gregory shortened October of A.D. 1582 by ten days. Thursday, +October 4, 1582 (Julian) was followed immediately by Friday, October 15, +1582 (Gregorian). + +Since 0.06, we handle this correctly, so: + +adodb_mktime(0,0,0,10,15,1582) - adodb_mktime(0,0,0,10,4,1582) + == 24 * 3600 (1 day) + +============================================================================= + +COPYRIGHT + +(c) 2003-2005 John Lim and released under BSD-style license except for code by +jackbbs, which includes adodb_mktime, adodb_get_gmt_diff, adodb_is_leap_year +and originally found at http://www.php.net/manual/en/function.mktime.php + +============================================================================= + +BUG REPORTS + +These should be posted to the ADOdb forums at + + http://phplens.com/lens/lensforum/topics.php?id=4 + +============================================================================= + +FUNCTION DESCRIPTIONS + + +** FUNCTION adodb_getdate($date=false) + +Returns an array containing date information, as getdate(), but supports +dates greater than 1901 to 2038. The local date/time format is derived from a +heuristic the first time adodb_getdate is called. + + +** FUNCTION adodb_date($fmt, $timestamp = false) + +Convert a timestamp to a formatted local date. If $timestamp is not defined, the +current timestamp is used. Unlike the function date(), it supports dates +outside the 1901 to 2038 range. + +The format fields that adodb_date supports: + +<pre> + a - "am" or "pm" + A - "AM" or "PM" + d - day of the month, 2 digits with leading zeros; i.e. "01" to "31" + D - day of the week, textual, 3 letters; e.g. "Fri" + F - month, textual, long; e.g. "January" + g - hour, 12-hour format without leading zeros; i.e. "1" to "12" + G - hour, 24-hour format without leading zeros; i.e. "0" to "23" + h - hour, 12-hour format; i.e. "01" to "12" + H - hour, 24-hour format; i.e. "00" to "23" + i - minutes; i.e. "00" to "59" + j - day of the month without leading zeros; i.e. "1" to "31" + l (lowercase 'L') - day of the week, textual, long; e.g. "Friday" + L - boolean for whether it is a leap year; i.e. "0" or "1" + m - month; i.e. "01" to "12" + M - month, textual, 3 letters; e.g. "Jan" + n - month without leading zeros; i.e. "1" to "12" + O - Difference to Greenwich time in hours; e.g. "+0200" + Q - Quarter, as in 1, 2, 3, 4 + r - RFC 2822 formatted date; e.g. "Thu, 21 Dec 2000 16:01:07 +0200" + s - seconds; i.e. "00" to "59" + S - English ordinal suffix for the day of the month, 2 characters; + i.e. "st", "nd", "rd" or "th" + t - number of days in the given month; i.e. "28" to "31" + T - Timezone setting of this machine; e.g. "EST" or "MDT" + U - seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) + w - day of the week, numeric, i.e. "0" (Sunday) to "6" (Saturday) + Y - year, 4 digits; e.g. "1999" + y - year, 2 digits; e.g. "99" + z - day of the year; i.e. "0" to "365" + Z - timezone offset in seconds (i.e. "-43200" to "43200"). + The offset for timezones west of UTC is always negative, + and for those east of UTC is always positive. +</pre> + +Unsupported: +<pre> + B - Swatch Internet time + I (capital i) - "1" if Daylight Savings Time, "0" otherwise. + W - ISO-8601 week number of year, weeks starting on Monday + +</pre> + + +** FUNCTION adodb_date2($fmt, $isoDateString = false) +Same as adodb_date, but 2nd parameter accepts iso date, eg. + + adodb_date2('d-M-Y H:i','2003-12-25 13:01:34'); + + +** FUNCTION adodb_gmdate($fmt, $timestamp = false) + +Convert a timestamp to a formatted GMT date. If $timestamp is not defined, the +current timestamp is used. Unlike the function date(), it supports dates +outside the 1901 to 2038 range. + + +** FUNCTION adodb_mktime($hr, $min, $sec[, $month, $day, $year]) + +Converts a local date to a unix timestamp. Unlike the function mktime(), it supports +dates outside the 1901 to 2038 range. All parameters are optional. + + +** FUNCTION adodb_gmmktime($hr, $min, $sec [, $month, $day, $year]) + +Converts a gmt date to a unix timestamp. Unlike the function gmmktime(), it supports +dates outside the 1901 to 2038 range. Differs from gmmktime() in that all parameters +are currently compulsory. + +** FUNCTION adodb_gmstrftime($fmt, $timestamp = false) +Convert a timestamp to a formatted GMT date. + +** FUNCTION adodb_strftime($fmt, $timestamp = false) + +Convert a timestamp to a formatted local date. Internally converts $fmt into +adodb_date format, then echo result. + +For best results, you can define the local date format yourself. Define a global +variable $ADODB_DATE_LOCALE which is an array, 1st element is date format using +adodb_date syntax, and 2nd element is the time format, also in adodb_date syntax. + + eg. $ADODB_DATE_LOCALE = array('d/m/Y','H:i:s'); + + Supported format codes: + +<pre> + %a - abbreviated weekday name according to the current locale + %A - full weekday name according to the current locale + %b - abbreviated month name according to the current locale + %B - full month name according to the current locale + %c - preferred date and time representation for the current locale + %d - day of the month as a decimal number (range 01 to 31) + %D - same as %m/%d/%y + %e - day of the month as a decimal number, a single digit is preceded by a space (range ' 1' to '31') + %h - same as %b + %H - hour as a decimal number using a 24-hour clock (range 00 to 23) + %I - hour as a decimal number using a 12-hour clock (range 01 to 12) + %m - month as a decimal number (range 01 to 12) + %M - minute as a decimal number + %n - newline character + %p - either `am' or `pm' according to the given time value, or the corresponding strings for the current locale + %r - time in a.m. and p.m. notation + %R - time in 24 hour notation + %S - second as a decimal number + %t - tab character + %T - current time, equal to %H:%M:%S + %x - preferred date representation for the current locale without the time + %X - preferred time representation for the current locale without the date + %y - year as a decimal number without a century (range 00 to 99) + %Y - year as a decimal number including the century + %Z - time zone or name or abbreviation + %% - a literal `%' character +</pre> + + Unsupported codes: +<pre> + %C - century number (the year divided by 100 and truncated to an integer, range 00 to 99) + %g - like %G, but without the century. + %G - The 4-digit year corresponding to the ISO week number (see %V). + This has the same format and value as %Y, except that if the ISO week number belongs + to the previous or next year, that year is used instead. + %j - day of the year as a decimal number (range 001 to 366) + %u - weekday as a decimal number [1,7], with 1 representing Monday + %U - week number of the current year as a decimal number, starting + with the first Sunday as the first day of the first week + %V - The ISO 8601:1988 week number of the current year as a decimal number, + range 01 to 53, where week 1 is the first week that has at least 4 days in the + current year, and with Monday as the first day of the week. (Use %G or %g for + the year component that corresponds to the week number for the specified timestamp.) + %w - day of the week as a decimal, Sunday being 0 + %W - week number of the current year as a decimal number, starting with the + first Monday as the first day of the first week +</pre> + +============================================================================= + +NOTES + +Useful url for generating test timestamps: + http://www.4webhelp.net/us/timestamp.php + +Possible future optimizations include + +a. Using an algorithm similar to Plauger's in "The Standard C Library" +(page 428, xttotm.c _Ttotm() function). Plauger's algorithm will not +work outside 32-bit signed range, so i decided not to implement it. + +b. Implement daylight savings, which looks awfully complicated, see + http://webexhibits.org/daylightsaving/ + + +CHANGELOG +- 10 Feb 2006 0.23 +PHP5 compat: when we detect PHP5, the RFC2822 format for gmt 0000hrs is changed from -0000 to +0000. + In PHP4, we will still use -0000 for 100% compat with PHP4. + +- 08 Sept 2005 0.22 +In adodb_date2(), $is_gmt not supported properly. Fixed. + +- 18 July 2005 0.21 +In PHP 4.3.11, the 'r' format has changed. Leading 0 in day is added. Changed for compat. +Added support for negative months in adodb_mktime(). + +- 24 Feb 2005 0.20 +Added limited strftime/gmstrftime support. x10 improvement in performance of adodb_date(). + +- 21 Dec 2004 0.17 +In adodb_getdate(), the timestamp was accidentally converted to gmt when $is_gmt is false. +Also adodb_mktime(0,0,0) did not work properly. Both fixed thx Mauro. + +- 17 Nov 2004 0.16 +Removed intval typecast in adodb_mktime() for secs, allowing: + adodb_mktime(0,0,0 + 2236672153,1,1,1934); +Suggested by Ryan. + +- 18 July 2004 0.15 +All params in adodb_mktime were formerly compulsory. Now only the hour, min, secs is compulsory. +This brings it more in line with mktime (still not identical). + +- 23 June 2004 0.14 + +Allow you to define your own daylights savings function, adodb_daylight_sv. +If the function is defined (somewhere in an include), then you can correct for daylights savings. + +In this example, we apply daylights savings in June or July, adding one hour. This is extremely +unrealistic as it does not take into account time-zone, geographic location, current year. + +function adodb_daylight_sv(&$arr, $is_gmt) +{ + if ($is_gmt) return; + $m = $arr['mon']; + if ($m == 6 || $m == 7) $arr['hours'] += 1; +} + +This is only called by adodb_date() and not by adodb_mktime(). + +The format of $arr is +Array ( + [seconds] => 0 + [minutes] => 0 + [hours] => 0 + [mday] => 1 # day of month, eg 1st day of the month + [mon] => 2 # month (eg. Feb) + [year] => 2102 + [yday] => 31 # days in current year + [leap] => # true if leap year + [ndays] => 28 # no of days in current month + ) + + +- 28 Apr 2004 0.13 +Fixed adodb_date to properly support $is_gmt. Thx to Dimitar Angelov. + +- 20 Mar 2004 0.12 +Fixed month calculation error in adodb_date. 2102-June-01 appeared as 2102-May-32. + +- 26 Oct 2003 0.11 +Because of daylight savings problems (some systems apply daylight savings to +January!!!), changed adodb_get_gmt_diff() to ignore daylight savings. + +- 9 Aug 2003 0.10 +Fixed bug with dates after 2038. +See http://phplens.com/lens/lensforum/msgs.php?id=6980 + +- 1 July 2003 0.09 +Added support for Q (Quarter). +Added adodb_date2(), which accepts ISO date in 2nd param + +- 3 March 2003 0.08 +Added support for 'S' adodb_date() format char. Added constant ADODB_ALLOW_NEGATIVE_TS +if you want PHP to handle negative timestamps between 1901 to 1969. + +- 27 Feb 2003 0.07 +All negative numbers handled by adodb now because of RH 7.3+ problems. +See http://bugs.php.net/bug.php?id=20048&edit=2 + +- 4 Feb 2003 0.06 +Fixed a typo, 1852 changed to 1582! This means that pre-1852 dates +are now correctly handled. + +- 29 Jan 2003 0.05 + +Leap year checking differs under Julian calendar (pre 1582). Also +leap year code optimized by checking for most common case first. + +We also handle month overflow correctly in mktime (eg month set to 13). + +Day overflow for less than one month's days is supported. + +- 28 Jan 2003 0.04 + +Gregorian correction handled. In PHP5, we might throw an error if +mktime uses invalid dates around 5-14 Oct 1582. Released with ADOdb 3.10. +Added limbo 5-14 Oct 1582 check, when we set to 15 Oct 1582. + +- 27 Jan 2003 0.03 + +Fixed some more month problems due to gmt issues. Added constant ADODB_DATE_VERSION. +Fixed calculation of days since start of year for <1970. + +- 27 Jan 2003 0.02 + +Changed _adodb_getdate() to inline leap year checking for better performance. +Fixed problem with time-zones west of GMT +0000. + +- 24 Jan 2003 0.01 + +First implementation. +*/ + + +/* Initialization */ + +/* + Version Number +*/ +define('ADODB_DATE_VERSION',0.23); + +/* + This code was originally for windows. But apparently this problem happens + also with Linux, RH 7.3 and later! + + glibc-2.2.5-34 and greater has been changed to return -1 for dates < + 1970. This used to work. The problem exists with RedHat 7.3 and 8.0 + echo (mktime(0, 0, 0, 1, 1, 1960)); // prints -1 + + References: + http://bugs.php.net/bug.php?id=20048&edit=2 + http://lists.debian.org/debian-glibc/2002/debian-glibc-200205/msg00010.html +*/ + +if (!defined('ADODB_ALLOW_NEGATIVE_TS')) define('ADODB_NO_NEGATIVE_TS',1); + +function adodb_date_test_date($y1,$m,$d=13) +{ + $t = adodb_mktime(0,0,0,$m,$d,$y1); + $rez = adodb_date('Y-n-j H:i:s',$t); + if ("$y1-$m-$d 00:00:00" != $rez) { + print "<b>$y1 error, expected=$y1-$m-$d 00:00:00, adodb=$rez</b><br>"; + return false; + } + return true; +} + +function adodb_date_test_strftime($fmt) +{ + $s1 = strftime($fmt); + $s2 = adodb_strftime($fmt); + + if ($s1 == $s2) return true; + + echo "error for $fmt, strftime=$s1, $adodb=$s2<br>"; + return false; +} + +/** + Test Suite +*/ +function adodb_date_test() +{ + + error_reporting(E_ALL); + print "<h4>Testing adodb_date and adodb_mktime. version=".ADODB_DATE_VERSION.' PHP='.PHP_VERSION."</h4>"; + @set_time_limit(0); + $fail = false; + + // This flag disables calling of PHP native functions, so we can properly test the code + if (!defined('ADODB_TEST_DATES')) define('ADODB_TEST_DATES',1); + + adodb_date_test_strftime('%Y %m %x %X'); + adodb_date_test_strftime("%A %d %B %Y"); + adodb_date_test_strftime("%H %M S"); + + $t = adodb_mktime(0,0,0); + if (!(adodb_date('Y-m-d') == date('Y-m-d'))) print 'Error in '.adodb_mktime(0,0,0).'<br>'; + + $t = adodb_mktime(0,0,0,6,1,2102); + if (!(adodb_date('Y-m-d',$t) == '2102-06-01')) print 'Error in '.adodb_date('Y-m-d',$t).'<br>'; + + $t = adodb_mktime(0,0,0,2,1,2102); + if (!(adodb_date('Y-m-d',$t) == '2102-02-01')) print 'Error in '.adodb_date('Y-m-d',$t).'<br>'; + + + print "<p>Testing gregorian <=> julian conversion<p>"; + $t = adodb_mktime(0,0,0,10,11,1492); + //http://www.holidayorigins.com/html/columbus_day.html - Friday check + if (!(adodb_date('D Y-m-d',$t) == 'Fri 1492-10-11')) print 'Error in Columbus landing<br>'; + + $t = adodb_mktime(0,0,0,2,29,1500); + if (!(adodb_date('Y-m-d',$t) == '1500-02-29')) print 'Error in julian leap years<br>'; + + $t = adodb_mktime(0,0,0,2,29,1700); + if (!(adodb_date('Y-m-d',$t) == '1700-03-01')) print 'Error in gregorian leap years<br>'; + + print adodb_mktime(0,0,0,10,4,1582).' '; + print adodb_mktime(0,0,0,10,15,1582); + $diff = (adodb_mktime(0,0,0,10,15,1582) - adodb_mktime(0,0,0,10,4,1582)); + if ($diff != 3600*24) print " <b>Error in gregorian correction = ".($diff/3600/24)." days </b><br>"; + + print " 15 Oct 1582, Fri=".(adodb_dow(1582,10,15) == 5 ? 'Fri' : '<b>Error</b>')."<br>"; + print " 4 Oct 1582, Thu=".(adodb_dow(1582,10,4) == 4 ? 'Thu' : '<b>Error</b>')."<br>"; + + print "<p>Testing overflow<p>"; + + $t = adodb_mktime(0,0,0,3,33,1965); + if (!(adodb_date('Y-m-d',$t) == '1965-04-02')) print 'Error in day overflow 1 <br>'; + $t = adodb_mktime(0,0,0,4,33,1971); + if (!(adodb_date('Y-m-d',$t) == '1971-05-03')) print 'Error in day overflow 2 <br>'; + $t = adodb_mktime(0,0,0,1,60,1965); + if (!(adodb_date('Y-m-d',$t) == '1965-03-01')) print 'Error in day overflow 3 '.adodb_date('Y-m-d',$t).' <br>'; + $t = adodb_mktime(0,0,0,12,32,1965); + if (!(adodb_date('Y-m-d',$t) == '1966-01-01')) print 'Error in day overflow 4 '.adodb_date('Y-m-d',$t).' <br>'; + $t = adodb_mktime(0,0,0,12,63,1965); + if (!(adodb_date('Y-m-d',$t) == '1966-02-01')) print 'Error in day overflow 5 '.adodb_date('Y-m-d',$t).' <br>'; + $t = adodb_mktime(0,0,0,13,3,1965); + if (!(adodb_date('Y-m-d',$t) == '1966-01-03')) print 'Error in mth overflow 1 <br>'; + + print "Testing 2-digit => 4-digit year conversion<p>"; + if (adodb_year_digit_check(00) != 2000) print "Err 2-digit 2000<br>"; + if (adodb_year_digit_check(10) != 2010) print "Err 2-digit 2010<br>"; + if (adodb_year_digit_check(20) != 2020) print "Err 2-digit 2020<br>"; + if (adodb_year_digit_check(30) != 2030) print "Err 2-digit 2030<br>"; + if (adodb_year_digit_check(40) != 1940) print "Err 2-digit 1940<br>"; + if (adodb_year_digit_check(50) != 1950) print "Err 2-digit 1950<br>"; + if (adodb_year_digit_check(90) != 1990) print "Err 2-digit 1990<br>"; + + // Test string formating + print "<p>Testing date formating</p>"; + $fmt = '\d\a\t\e T Y-m-d H:i:s a A d D F g G h H i j l L m M n O \R\F\C2822 r s t U w y Y z Z 2003'; + $s1 = date($fmt,0); + $s2 = adodb_date($fmt,0); + if ($s1 != $s2) { + print " date() 0 failed<br>$s1<br>$s2<br>"; + } + flush(); + for ($i=100; --$i > 0; ) { + + $ts = 3600.0*((rand()%60000)+(rand()%60000))+(rand()%60000); + $s1 = date($fmt,$ts); + $s2 = adodb_date($fmt,$ts); + //print "$s1 <br>$s2 <p>"; + $pos = strcmp($s1,$s2); + + if (($s1) != ($s2)) { + for ($j=0,$k=strlen($s1); $j < $k; $j++) { + if ($s1[$j] != $s2[$j]) { + print substr($s1,$j).' '; + break; + } + } + print "<b>Error date(): $ts<br><pre> + \"$s1\" (date len=".strlen($s1).") + \"$s2\" (adodb_date len=".strlen($s2).")</b></pre><br>"; + $fail = true; + } + + $a1 = getdate($ts); + $a2 = adodb_getdate($ts); + $rez = array_diff($a1,$a2); + if (sizeof($rez)>0) { + print "<b>Error getdate() $ts</b><br>"; + print_r($a1); + print "<br>"; + print_r($a2); + print "<p>"; + $fail = true; + } + } + + // Test generation of dates outside 1901-2038 + print "<p>Testing random dates between 100 and 4000</p>"; + adodb_date_test_date(100,1); + for ($i=100; --$i >= 0;) { + $y1 = 100+rand(0,1970-100); + $m = rand(1,12); + adodb_date_test_date($y1,$m); + + $y1 = 3000-rand(0,3000-1970); + adodb_date_test_date($y1,$m); + } + print '<p>'; + $start = 1960+rand(0,10); + $yrs = 12; + $i = 365.25*86400*($start-1970); + $offset = 36000+rand(10000,60000); + $max = 365*$yrs*86400; + $lastyear = 0; + + // we generate a timestamp, convert it to a date, and convert it back to a timestamp + // and check if the roundtrip broke the original timestamp value. + print "Testing $start to ".($start+$yrs).", or $max seconds, offset=$offset: "; + $cnt = 0; + for ($max += $i; $i < $max; $i += $offset) { + $ret = adodb_date('m,d,Y,H,i,s',$i); + $arr = explode(',',$ret); + if ($lastyear != $arr[2]) { + $lastyear = $arr[2]; + print " $lastyear "; + flush(); + } + $newi = adodb_mktime($arr[3],$arr[4],$arr[5],$arr[0],$arr[1],$arr[2]); + if ($i != $newi) { + print "Error at $i, adodb_mktime returned $newi ($ret)"; + $fail = true; + break; + } + $cnt += 1; + } + echo "Tested $cnt dates<br>"; + if (!$fail) print "<p>Passed !</p>"; + else print "<p><b>Failed</b> :-(</p>"; +} + +/** + Returns day of week, 0 = Sunday,... 6=Saturday. + Algorithm from PEAR::Date_Calc +*/ +function adodb_dow($year, $month, $day) +{ +/* +Pope Gregory removed 10 days - October 5 to October 14 - from the year 1582 and +proclaimed that from that time onwards 3 days would be dropped from the calendar +every 400 years. + +Thursday, October 4, 1582 (Julian) was followed immediately by Friday, October 15, 1582 (Gregorian). +*/ + if ($year <= 1582) { + if ($year < 1582 || + ($year == 1582 && ($month < 10 || ($month == 10 && $day < 15)))) $greg_correction = 3; + else + $greg_correction = 0; + } else + $greg_correction = 0; + + if($month > 2) + $month -= 2; + else { + $month += 10; + $year--; + } + + $day = floor((13 * $month - 1) / 5) + + $day + ($year % 100) + + floor(($year % 100) / 4) + + floor(($year / 100) / 4) - 2 * + floor($year / 100) + 77 + $greg_correction; + + return $day - 7 * floor($day / 7); +} + + +/** + Checks for leap year, returns true if it is. No 2-digit year check. Also + handles julian calendar correctly. +*/ +function _adodb_is_leap_year($year) +{ + if ($year % 4 != 0) return false; + + if ($year % 400 == 0) { + return true; + // if gregorian calendar (>1582), century not-divisible by 400 is not leap + } else if ($year > 1582 && $year % 100 == 0 ) { + return false; + } + + return true; +} + + +/** + checks for leap year, returns true if it is. Has 2-digit year check +*/ +function adodb_is_leap_year($year) +{ + return _adodb_is_leap_year(adodb_year_digit_check($year)); +} + +/** + Fix 2-digit years. Works for any century. + Assumes that if 2-digit is more than 30 years in future, then previous century. +*/ +function adodb_year_digit_check($y) +{ + if ($y < 100) { + + $yr = (integer) date("Y"); + $century = (integer) ($yr /100); + + if ($yr%100 > 50) { + $c1 = $century + 1; + $c0 = $century; + } else { + $c1 = $century; + $c0 = $century - 1; + } + $c1 *= 100; + // if 2-digit year is less than 30 years in future, set it to this century + // otherwise if more than 30 years in future, then we set 2-digit year to the prev century. + if (($y + $c1) < $yr+30) $y = $y + $c1; + else $y = $y + $c0*100; + } + return $y; +} + +/** + get local time zone offset from GMT +*/ +function adodb_get_gmt_diff() +{ +static $TZ; + if (isset($TZ)) return $TZ; + + $TZ = mktime(0,0,0,1,2,1970,0) - gmmktime(0,0,0,1,2,1970,0); + return $TZ; +} + +/** + Returns an array with date info. +*/ +function adodb_getdate($d=false,$fast=false) +{ + if ($d === false) return getdate(); + if (!defined('ADODB_TEST_DATES')) { + if ((abs($d) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range + if (!defined('ADODB_NO_NEGATIVE_TS') || $d >= 0) // if windows, must be +ve integer + return @getdate($d); + } + } + return _adodb_getdate($d); +} + +/* +// generate $YRS table for _adodb_getdate() +function adodb_date_gentable($out=true) +{ + + for ($i=1970; $i >= 1600; $i-=10) { + $s = adodb_gmmktime(0,0,0,1,1,$i); + echo "$i => $s,<br>"; + } +} +adodb_date_gentable(); + +for ($i=1970; $i > 1500; $i--) { + +echo "<hr />$i "; + adodb_date_test_date($i,1,1); +} + +*/ + + +$_month_table_normal = array("",31,28,31,30,31,30,31,31,30,31,30,31); +$_month_table_leaf = array("",31,29,31,30,31,30,31,31,30,31,30,31); + +function adodb_validdate($y,$m,$d) +{ +global $_month_table_normal,$_month_table_leaf; + + if (_adodb_is_leap_year($y)) $marr =& $_month_table_leaf; + else $marr =& $_month_table_normal; + + if ($m > 12 || $m < 1) return false; + + if ($d > 31 || $d < 1) return false; + + if ($marr[$m] < $d) return false; + + if ($y < 1000 && $y > 3000) return false; + + return true; +} + +/** + Low-level function that returns the getdate() array. We have a special + $fast flag, which if set to true, will return fewer array values, + and is much faster as it does not calculate dow, etc. +*/ +function _adodb_getdate($origd=false,$fast=false,$is_gmt=false) +{ +static $YRS; +global $_month_table_normal,$_month_table_leaf; + + $d = $origd - ($is_gmt ? 0 : adodb_get_gmt_diff()); + + $_day_power = 86400; + $_hour_power = 3600; + $_min_power = 60; + + if ($d < -12219321600) $d -= 86400*10; // if 15 Oct 1582 or earlier, gregorian correction + + $_month_table_normal = array("",31,28,31,30,31,30,31,31,30,31,30,31); + $_month_table_leaf = array("",31,29,31,30,31,30,31,31,30,31,30,31); + + $d366 = $_day_power * 366; + $d365 = $_day_power * 365; + + if ($d < 0) { + + if (empty($YRS)) $YRS = array( + 1970 => 0, + 1960 => -315619200, + 1950 => -631152000, + 1940 => -946771200, + 1930 => -1262304000, + 1920 => -1577923200, + 1910 => -1893456000, + 1900 => -2208988800, + 1890 => -2524521600, + 1880 => -2840140800, + 1870 => -3155673600, + 1860 => -3471292800, + 1850 => -3786825600, + 1840 => -4102444800, + 1830 => -4417977600, + 1820 => -4733596800, + 1810 => -5049129600, + 1800 => -5364662400, + 1790 => -5680195200, + 1780 => -5995814400, + 1770 => -6311347200, + 1760 => -6626966400, + 1750 => -6942499200, + 1740 => -7258118400, + 1730 => -7573651200, + 1720 => -7889270400, + 1710 => -8204803200, + 1700 => -8520336000, + 1690 => -8835868800, + 1680 => -9151488000, + 1670 => -9467020800, + 1660 => -9782640000, + 1650 => -10098172800, + 1640 => -10413792000, + 1630 => -10729324800, + 1620 => -11044944000, + 1610 => -11360476800, + 1600 => -11676096000); + + if ($is_gmt) $origd = $d; + // The valid range of a 32bit signed timestamp is typically from + // Fri, 13 Dec 1901 20:45:54 GMT to Tue, 19 Jan 2038 03:14:07 GMT + // + + # old algorithm iterates through all years. new algorithm does it in + # 10 year blocks + + /* + # old algo + for ($a = 1970 ; --$a >= 0;) { + $lastd = $d; + + if ($leaf = _adodb_is_leap_year($a)) $d += $d366; + else $d += $d365; + + if ($d >= 0) { + $year = $a; + break; + } + } + */ + + $lastsecs = 0; + $lastyear = 1970; + foreach($YRS as $year => $secs) { + if ($d >= $secs) { + $a = $lastyear; + break; + } + $lastsecs = $secs; + $lastyear = $year; + } + + $d -= $lastsecs; + if (!isset($a)) $a = $lastyear; + + //echo ' yr=',$a,' ', $d,'.'; + + for (; --$a >= 0;) { + $lastd = $d; + + if ($leaf = _adodb_is_leap_year($a)) $d += $d366; + else $d += $d365; + + if ($d >= 0) { + $year = $a; + break; + } + } + /**/ + + $secsInYear = 86400 * ($leaf ? 366 : 365) + $lastd; + + $d = $lastd; + $mtab = ($leaf) ? $_month_table_leaf : $_month_table_normal; + for ($a = 13 ; --$a > 0;) { + $lastd = $d; + $d += $mtab[$a] * $_day_power; + if ($d >= 0) { + $month = $a; + $ndays = $mtab[$a]; + break; + } + } + + $d = $lastd; + $day = $ndays + ceil(($d+1) / ($_day_power)); + + $d += ($ndays - $day+1)* $_day_power; + $hour = floor($d/$_hour_power); + + } else { + for ($a = 1970 ;; $a++) { + $lastd = $d; + + if ($leaf = _adodb_is_leap_year($a)) $d -= $d366; + else $d -= $d365; + if ($d < 0) { + $year = $a; + break; + } + } + $secsInYear = $lastd; + $d = $lastd; + $mtab = ($leaf) ? $_month_table_leaf : $_month_table_normal; + for ($a = 1 ; $a <= 12; $a++) { + $lastd = $d; + $d -= $mtab[$a] * $_day_power; + if ($d < 0) { + $month = $a; + $ndays = $mtab[$a]; + break; + } + } + $d = $lastd; + $day = ceil(($d+1) / $_day_power); + $d = $d - ($day-1) * $_day_power; + $hour = floor($d /$_hour_power); + } + + $d -= $hour * $_hour_power; + $min = floor($d/$_min_power); + $secs = $d - $min * $_min_power; + if ($fast) { + return array( + 'seconds' => $secs, + 'minutes' => $min, + 'hours' => $hour, + 'mday' => $day, + 'mon' => $month, + 'year' => $year, + 'yday' => floor($secsInYear/$_day_power), + 'leap' => $leaf, + 'ndays' => $ndays + ); + } + + + $dow = adodb_dow($year,$month,$day); + + return array( + 'seconds' => $secs, + 'minutes' => $min, + 'hours' => $hour, + 'mday' => $day, + 'wday' => $dow, + 'mon' => $month, + 'year' => $year, + 'yday' => floor($secsInYear/$_day_power), + 'weekday' => gmdate('l',$_day_power*(3+$dow)), + 'month' => gmdate('F',mktime(0,0,0,$month,2,1971)), + 0 => $origd + ); +} + +function adodb_gmdate($fmt,$d=false) +{ + return adodb_date($fmt,$d,true); +} + +// accepts unix timestamp and iso date format in $d +function adodb_date2($fmt, $d=false, $is_gmt=false) +{ + if ($d !== false) { + if (!preg_match( + "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ -]?(([0-9]{1,2}):?([0-9]{1,2}):?([0-9\.]{1,4}))?|", + ($d), $rr)) return adodb_date($fmt,false,$is_gmt); + + if ($rr[1] <= 100 && $rr[2]<= 1) return adodb_date($fmt,false,$is_gmt); + + // h-m-s-MM-DD-YY + if (!isset($rr[5])) $d = adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1],false,$is_gmt); + else $d = @adodb_mktime($rr[5],$rr[6],$rr[7],$rr[2],$rr[3],$rr[1],false,$is_gmt); + } + + return adodb_date($fmt,$d,$is_gmt); +} + + +/** + Return formatted date based on timestamp $d +*/ +function adodb_date($fmt,$d=false,$is_gmt=false) +{ +static $daylight; + + if ($d === false) return ($is_gmt)? @gmdate($fmt): @date($fmt); + if (!defined('ADODB_TEST_DATES')) { + if ((abs($d) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range + if (!defined('ADODB_NO_NEGATIVE_TS') || $d >= 0) // if windows, must be +ve integer + return ($is_gmt)? @gmdate($fmt,$d): @date($fmt,$d); + + } + } + $_day_power = 86400; + + $arr = _adodb_getdate($d,true,$is_gmt); + + if (!isset($daylight)) $daylight = function_exists('adodb_daylight_sv'); + if ($daylight) adodb_daylight_sv($arr, $is_gmt); + + $year = $arr['year']; + $month = $arr['mon']; + $day = $arr['mday']; + $hour = $arr['hours']; + $min = $arr['minutes']; + $secs = $arr['seconds']; + + $max = strlen($fmt); + $dates = ''; + + $isphp5 = PHP_VERSION >= 5; + + /* + at this point, we have the following integer vars to manipulate: + $year, $month, $day, $hour, $min, $secs + */ + for ($i=0; $i < $max; $i++) { + switch($fmt[$i]) { + case 'T': $dates .= date('T');break; + // YEAR + case 'L': $dates .= $arr['leap'] ? '1' : '0'; break; + case 'r': // Thu, 21 Dec 2000 16:01:07 +0200 + + // 4.3.11 uses '04 Jun 2004' + // 4.3.8 uses ' 4 Jun 2004' + $dates .= gmdate('D',$_day_power*(3+adodb_dow($year,$month,$day))).', ' + . ($day<10?'0'.$day:$day) . ' '.date('M',mktime(0,0,0,$month,2,1971)).' '.$year.' '; + + if ($hour < 10) $dates .= '0'.$hour; else $dates .= $hour; + + if ($min < 10) $dates .= ':0'.$min; else $dates .= ':'.$min; + + if ($secs < 10) $dates .= ':0'.$secs; else $dates .= ':'.$secs; + + $gmt = adodb_get_gmt_diff(); + if ($isphp5) + $dates .= sprintf(' %s%04d',($gmt<=0)?'+':'-',abs($gmt)/36); + else + $dates .= sprintf(' %s%04d',($gmt<0)?'+':'-',abs($gmt)/36); + break; + + case 'Y': $dates .= $year; break; + case 'y': $dates .= substr($year,strlen($year)-2,2); break; + // MONTH + case 'm': if ($month<10) $dates .= '0'.$month; else $dates .= $month; break; + case 'Q': $dates .= ($month+3)>>2; break; + case 'n': $dates .= $month; break; + case 'M': $dates .= date('M',mktime(0,0,0,$month,2,1971)); break; + case 'F': $dates .= date('F',mktime(0,0,0,$month,2,1971)); break; + // DAY + case 't': $dates .= $arr['ndays']; break; + case 'z': $dates .= $arr['yday']; break; + case 'w': $dates .= adodb_dow($year,$month,$day); break; + case 'l': $dates .= gmdate('l',$_day_power*(3+adodb_dow($year,$month,$day))); break; + case 'D': $dates .= gmdate('D',$_day_power*(3+adodb_dow($year,$month,$day))); break; + case 'j': $dates .= $day; break; + case 'd': if ($day<10) $dates .= '0'.$day; else $dates .= $day; break; + case 'S': + $d10 = $day % 10; + if ($d10 == 1) $dates .= 'st'; + else if ($d10 == 2 && $day != 12) $dates .= 'nd'; + else if ($d10 == 3) $dates .= 'rd'; + else $dates .= 'th'; + break; + + // HOUR + case 'Z': + $dates .= ($is_gmt) ? 0 : -adodb_get_gmt_diff(); break; + case 'O': + $gmt = ($is_gmt) ? 0 : adodb_get_gmt_diff(); + + if ($isphp5) + $dates .= sprintf('%s%04d',($gmt<=0)?'+':'-',abs($gmt)/36); + else + $dates .= sprintf('%s%04d',($gmt<0)?'+':'-',abs($gmt)/36); + break; + + case 'H': + if ($hour < 10) $dates .= '0'.$hour; + else $dates .= $hour; + break; + case 'h': + if ($hour > 12) $hh = $hour - 12; + else { + if ($hour == 0) $hh = '12'; + else $hh = $hour; + } + + if ($hh < 10) $dates .= '0'.$hh; + else $dates .= $hh; + break; + + case 'G': + $dates .= $hour; + break; + + case 'g': + if ($hour > 12) $hh = $hour - 12; + else { + if ($hour == 0) $hh = '12'; + else $hh = $hour; + } + $dates .= $hh; + break; + // MINUTES + case 'i': if ($min < 10) $dates .= '0'.$min; else $dates .= $min; break; + // SECONDS + case 'U': $dates .= $d; break; + case 's': if ($secs < 10) $dates .= '0'.$secs; else $dates .= $secs; break; + // AM/PM + // Note 00:00 to 11:59 is AM, while 12:00 to 23:59 is PM + case 'a': + if ($hour>=12) $dates .= 'pm'; + else $dates .= 'am'; + break; + case 'A': + if ($hour>=12) $dates .= 'PM'; + else $dates .= 'AM'; + break; + default: + $dates .= $fmt[$i]; break; + // ESCAPE + case "\\": + $i++; + if ($i < $max) $dates .= $fmt[$i]; + break; + } + } + return $dates; +} + +/** + Returns a timestamp given a GMT/UTC time. + Note that $is_dst is not implemented and is ignored. +*/ +function adodb_gmmktime($hr,$min,$sec,$mon=false,$day=false,$year=false,$is_dst=false) +{ + return adodb_mktime($hr,$min,$sec,$mon,$day,$year,$is_dst,true); +} + +/** + Return a timestamp given a local time. Originally by jackbbs. + Note that $is_dst is not implemented and is ignored. + + Not a very fast algorithm - O(n) operation. Could be optimized to O(1). +*/ +function adodb_mktime($hr,$min,$sec,$mon=false,$day=false,$year=false,$is_dst=false,$is_gmt=false) +{ + if (!defined('ADODB_TEST_DATES')) { + + if ($mon === false) { + return $is_gmt? @gmmktime($hr,$min,$sec): @mktime($hr,$min,$sec); + } + + // for windows, we don't check 1970 because with timezone differences, + // 1 Jan 1970 could generate negative timestamp, which is illegal + if (1971 < $year && $year < 2038 + || !defined('ADODB_NO_NEGATIVE_TS') && (1901 < $year && $year < 2038) + ) { + return $is_gmt ? + @gmmktime($hr,$min,$sec,$mon,$day,$year): + @mktime($hr,$min,$sec,$mon,$day,$year); + } + } + + $gmt_different = ($is_gmt) ? 0 : adodb_get_gmt_diff(); + + /* + # disabled because some people place large values in $sec. + # however we need it for $mon because we use an array... + $hr = intval($hr); + $min = intval($min); + $sec = intval($sec); + */ + $mon = intval($mon); + $day = intval($day); + $year = intval($year); + + + $year = adodb_year_digit_check($year); + + if ($mon > 12) { + $y = floor($mon / 12); + $year += $y; + $mon -= $y*12; + } else if ($mon < 1) { + $y = ceil((1-$mon) / 12); + $year -= $y; + $mon += $y*12; + } + + $_day_power = 86400; + $_hour_power = 3600; + $_min_power = 60; + + $_month_table_normal = array("",31,28,31,30,31,30,31,31,30,31,30,31); + $_month_table_leaf = array("",31,29,31,30,31,30,31,31,30,31,30,31); + + $_total_date = 0; + if ($year >= 1970) { + for ($a = 1970 ; $a <= $year; $a++) { + $leaf = _adodb_is_leap_year($a); + if ($leaf == true) { + $loop_table = $_month_table_leaf; + $_add_date = 366; + } else { + $loop_table = $_month_table_normal; + $_add_date = 365; + } + if ($a < $year) { + $_total_date += $_add_date; + } else { + for($b=1;$b<$mon;$b++) { + $_total_date += $loop_table[$b]; + } + } + } + $_total_date +=$day-1; + $ret = $_total_date * $_day_power + $hr * $_hour_power + $min * $_min_power + $sec + $gmt_different; + + } else { + for ($a = 1969 ; $a >= $year; $a--) { + $leaf = _adodb_is_leap_year($a); + if ($leaf == true) { + $loop_table = $_month_table_leaf; + $_add_date = 366; + } else { + $loop_table = $_month_table_normal; + $_add_date = 365; + } + if ($a > $year) { $_total_date += $_add_date; + } else { + for($b=12;$b>$mon;$b--) { + $_total_date += $loop_table[$b]; + } + } + } + $_total_date += $loop_table[$mon] - $day; + + $_day_time = $hr * $_hour_power + $min * $_min_power + $sec; + $_day_time = $_day_power - $_day_time; + $ret = -( $_total_date * $_day_power + $_day_time - $gmt_different); + if ($ret < -12220185600) $ret += 10*86400; // if earlier than 5 Oct 1582 - gregorian correction + else if ($ret < -12219321600) $ret = -12219321600; // if in limbo, reset to 15 Oct 1582. + } + //print " dmy=$day/$mon/$year $hr:$min:$sec => " .$ret; + return $ret; +} + +function adodb_gmstrftime($fmt, $ts=false) +{ + return adodb_strftime($fmt,$ts,true); +} + +// hack - convert to adodb_date +function adodb_strftime($fmt, $ts=false,$is_gmt=false) +{ +global $ADODB_DATE_LOCALE; + + if (!defined('ADODB_TEST_DATES')) { + if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range + if (!defined('ADODB_NO_NEGATIVE_TS') || $ts >= 0) // if windows, must be +ve integer + return ($is_gmt)? @gmstrftime($fmt,$ts): @strftime($fmt,$ts); + + } + } + + if (empty($ADODB_DATE_LOCALE)) { + $tstr = strtoupper(gmstrftime('%c',31366800)); // 30 Dec 1970, 1 am + $sep = substr($tstr,2,1); + $hasAM = strrpos($tstr,'M') !== false; + + $ADODB_DATE_LOCALE = array(); + $ADODB_DATE_LOCALE[] = strncmp($tstr,'30',2) == 0 ? 'd'.$sep.'m'.$sep.'y' : 'm'.$sep.'d'.$sep.'y'; + $ADODB_DATE_LOCALE[] = ($hasAM) ? 'h:i:s a' : 'H:i:s'; + + } + $inpct = false; + $fmtdate = ''; + for ($i=0,$max = strlen($fmt); $i < $max; $i++) { + $ch = $fmt[$i]; + if ($ch == '%') { + if ($inpct) { + $fmtdate .= '%'; + $inpct = false; + } else + $inpct = true; + } else if ($inpct) { + + $inpct = false; + switch($ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'E': + case 'O': + /* ignore format modifiers */ + $inpct = true; + break; + + case 'a': $fmtdate .= 'D'; break; + case 'A': $fmtdate .= 'l'; break; + case 'h': + case 'b': $fmtdate .= 'M'; break; + case 'B': $fmtdate .= 'F'; break; + case 'c': $fmtdate .= $ADODB_DATE_LOCALE[0].$ADODB_DATE_LOCALE[1]; break; + case 'C': $fmtdate .= '\C?'; break; // century + case 'd': $fmtdate .= 'd'; break; + case 'D': $fmtdate .= 'm/d/y'; break; + case 'e': $fmtdate .= 'j'; break; + case 'g': $fmtdate .= '\g?'; break; //? + case 'G': $fmtdate .= '\G?'; break; //? + case 'H': $fmtdate .= 'H'; break; + case 'I': $fmtdate .= 'h'; break; + case 'j': $fmtdate .= '?z'; $parsej = true; break; // wrong as j=1-based, z=0-basd + case 'm': $fmtdate .= 'm'; break; + case 'M': $fmtdate .= 'i'; break; + case 'n': $fmtdate .= "\n"; break; + case 'p': $fmtdate .= 'a'; break; + case 'r': $fmtdate .= 'h:i:s a'; break; + case 'R': $fmtdate .= 'H:i:s'; break; + case 'S': $fmtdate .= 's'; break; + case 't': $fmtdate .= "\t"; break; + case 'T': $fmtdate .= 'H:i:s'; break; + case 'u': $fmtdate .= '?u'; $parseu = true; break; // wrong strftime=1-based, date=0-based + case 'U': $fmtdate .= '?U'; $parseU = true; break;// wrong strftime=1-based, date=0-based + case 'x': $fmtdate .= $ADODB_DATE_LOCALE[0]; break; + case 'X': $fmtdate .= $ADODB_DATE_LOCALE[1]; break; + case 'w': $fmtdate .= '?w'; $parseu = true; break; // wrong strftime=1-based, date=0-based + case 'W': $fmtdate .= '?W'; $parseU = true; break;// wrong strftime=1-based, date=0-based + case 'y': $fmtdate .= 'y'; break; + case 'Y': $fmtdate .= 'Y'; break; + case 'Z': $fmtdate .= 'T'; break; + } + } else if (('A' <= ($ch) && ($ch) <= 'Z' ) || ('a' <= ($ch) && ($ch) <= 'z' )) + $fmtdate .= "\\".$ch; + else + $fmtdate .= $ch; + } + //echo "fmt=",$fmtdate,"<br>"; + if ($ts === false) $ts = time(); + $ret = adodb_date($fmtdate, $ts, $is_gmt); + return $ret; +} + + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb-xmlschema.inc.php b/framework/DataAccess/adodb/adodb-xmlschema.inc.php new file mode 100644 index 00000000..388e3d8a --- /dev/null +++ b/framework/DataAccess/adodb/adodb-xmlschema.inc.php @@ -0,0 +1,2221 @@ +<?php +// Copyright (c) 2004 ars Cognita Inc., all rights reserved +/* ****************************************************************************** + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +*******************************************************************************/ +/** + * xmlschema is a class that allows the user to quickly and easily + * build a database on any ADOdb-supported platform using a simple + * XML schema. + * + * Last Editor: $Author: jlim $ + * @author Richard Tango-Lowy & Dan Cech + * @version $Revision: 1.12 $ + * + * @package axmls + * @tutorial getting_started.pkg + */ + +function _file_get_contents($file) +{ + if (function_exists('file_get_contents')) return file_get_contents($file); + + $f = fopen($file,'r'); + if (!$f) return ''; + $t = ''; + + while ($s = fread($f,100000)) $t .= $s; + fclose($f); + return $t; +} + + +/** +* Debug on or off +*/ +if( !defined( 'XMLS_DEBUG' ) ) { + define( 'XMLS_DEBUG', FALSE ); +} + +/** +* Default prefix key +*/ +if( !defined( 'XMLS_PREFIX' ) ) { + define( 'XMLS_PREFIX', '%%P' ); +} + +/** +* Maximum length allowed for object prefix +*/ +if( !defined( 'XMLS_PREFIX_MAXLEN' ) ) { + define( 'XMLS_PREFIX_MAXLEN', 10 ); +} + +/** +* Execute SQL inline as it is generated +*/ +if( !defined( 'XMLS_EXECUTE_INLINE' ) ) { + define( 'XMLS_EXECUTE_INLINE', FALSE ); +} + +/** +* Continue SQL Execution if an error occurs? +*/ +if( !defined( 'XMLS_CONTINUE_ON_ERROR' ) ) { + define( 'XMLS_CONTINUE_ON_ERROR', FALSE ); +} + +/** +* Current Schema Version +*/ +if( !defined( 'XMLS_SCHEMA_VERSION' ) ) { + define( 'XMLS_SCHEMA_VERSION', '0.2' ); +} + +/** +* Default Schema Version. Used for Schemas without an explicit version set. +*/ +if( !defined( 'XMLS_DEFAULT_SCHEMA_VERSION' ) ) { + define( 'XMLS_DEFAULT_SCHEMA_VERSION', '0.1' ); +} + +/** +* Default Schema Version. Used for Schemas without an explicit version set. +*/ +if( !defined( 'XMLS_DEFAULT_UPGRADE_METHOD' ) ) { + define( 'XMLS_DEFAULT_UPGRADE_METHOD', 'ALTER' ); +} + +/** +* Include the main ADODB library +*/ +if( !defined( '_ADODB_LAYER' ) ) { + require( 'adodb.inc.php' ); + require( 'adodb-datadict.inc.php' ); +} + +/** +* Abstract DB Object. This class provides basic methods for database objects, such +* as tables and indexes. +* +* @package axmls +* @access private +*/ +class dbObject { + + /** + * var object Parent + */ + var $parent; + + /** + * var string current element + */ + var $currentElement; + + /** + * NOP + */ + function dbObject( &$parent, $attributes = NULL ) { + $this->parent =& $parent; + } + + /** + * XML Callback to process start elements + * + * @access private + */ + function _tag_open( &$parser, $tag, $attributes ) { + + } + + /** + * XML Callback to process CDATA elements + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + + } + + function create() { + return array(); + } + + /** + * Destroys the object + */ + function destroy() { + unset( $this ); + } + + /** + * Checks whether the specified RDBMS is supported by the current + * database object or its ranking ancestor. + * + * @param string $platform RDBMS platform name (from ADODB platform list). + * @return boolean TRUE if RDBMS is supported; otherwise returns FALSE. + */ + function supportedPlatform( $platform = NULL ) { + return is_object( $this->parent ) ? $this->parent->supportedPlatform( $platform ) : TRUE; + } + + /** + * Returns the prefix set by the ranking ancestor of the database object. + * + * @param string $name Prefix string. + * @return string Prefix. + */ + function prefix( $name = '' ) { + return is_object( $this->parent ) ? $this->parent->prefix( $name ) : $name; + } + + /** + * Extracts a field ID from the specified field. + * + * @param string $field Field. + * @return string Field ID. + */ + function FieldID( $field ) { + return strtoupper( preg_replace( '/^`(.+)`$/', '$1', $field ) ); + } +} + +/** +* Creates a table object in ADOdb's datadict format +* +* This class stores information about a database table. As charactaristics +* of the table are loaded from the external source, methods and properties +* of this class are used to build up the table description in ADOdb's +* datadict format. +* +* @package axmls +* @access private +*/ +class dbTable extends dbObject { + + /** + * @var string Table name + */ + var $name; + + /** + * @var array Field specifier: Meta-information about each field + */ + var $fields = array(); + + /** + * @var array List of table indexes. + */ + var $indexes = array(); + + /** + * @var array Table options: Table-level options + */ + var $opts = array(); + + /** + * @var string Field index: Keeps track of which field is currently being processed + */ + var $current_field; + + /** + * @var boolean Mark table for destruction + * @access private + */ + var $drop_table; + + /** + * @var boolean Mark field for destruction (not yet implemented) + * @access private + */ + var $drop_field = array(); + + /** + * Iniitializes a new table object. + * + * @param string $prefix DB Object prefix + * @param array $attributes Array of table attributes. + */ + function dbTable( &$parent, $attributes = NULL ) { + $this->parent =& $parent; + $this->name = $this->prefix($attributes['NAME']); + } + + /** + * XML Callback to process start elements. Elements currently + * processed are: INDEX, DROP, FIELD, KEY, NOTNULL, AUTOINCREMENT & DEFAULT. + * + * @access private + */ + function _tag_open( &$parser, $tag, $attributes ) { + $this->currentElement = strtoupper( $tag ); + + switch( $this->currentElement ) { + case 'INDEX': + if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { + xml_set_object( $parser, $this->addIndex( $attributes ) ); + } + break; + case 'DATA': + if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { + xml_set_object( $parser, $this->addData( $attributes ) ); + } + break; + case 'DROP': + $this->drop(); + break; + case 'FIELD': + // Add a field + $fieldName = $attributes['NAME']; + $fieldType = $attributes['TYPE']; + $fieldSize = isset( $attributes['SIZE'] ) ? $attributes['SIZE'] : NULL; + $fieldOpts = isset( $attributes['OPTS'] ) ? $attributes['OPTS'] : NULL; + + $this->addField( $fieldName, $fieldType, $fieldSize, $fieldOpts ); + break; + case 'KEY': + case 'NOTNULL': + case 'AUTOINCREMENT': + // Add a field option + $this->addFieldOpt( $this->current_field, $this->currentElement ); + break; + case 'DEFAULT': + // Add a field option to the table object + + // Work around ADOdb datadict issue that misinterprets empty strings. + if( $attributes['VALUE'] == '' ) { + $attributes['VALUE'] = " '' "; + } + + $this->addFieldOpt( $this->current_field, $this->currentElement, $attributes['VALUE'] ); + break; + case 'DEFDATE': + case 'DEFTIMESTAMP': + // Add a field option to the table object + $this->addFieldOpt( $this->current_field, $this->currentElement ); + break; + default: + // print_r( array( $tag, $attributes ) ); + } + } + + /** + * XML Callback to process CDATA elements + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + switch( $this->currentElement ) { + // Table constraint + case 'CONSTRAINT': + if( isset( $this->current_field ) ) { + $this->addFieldOpt( $this->current_field, $this->currentElement, $cdata ); + } else { + $this->addTableOpt( $cdata ); + } + break; + // Table option + case 'OPT': + $this->addTableOpt( $cdata ); + break; + default: + + } + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + $this->currentElement = ''; + + switch( strtoupper( $tag ) ) { + case 'TABLE': + $this->parent->addSQL( $this->create( $this->parent ) ); + xml_set_object( $parser, $this->parent ); + $this->destroy(); + break; + case 'FIELD': + unset($this->current_field); + break; + + } + } + + /** + * Adds an index to a table object + * + * @param array $attributes Index attributes + * @return object dbIndex object + */ + function &addIndex( $attributes ) { + $name = strtoupper( $attributes['NAME'] ); + $this->indexes[$name] =& new dbIndex( $this, $attributes ); + return $this->indexes[$name]; + } + + /** + * Adds data to a table object + * + * @param array $attributes Data attributes + * @return object dbData object + */ + function &addData( $attributes ) { + if( !isset( $this->data ) ) { + $this->data =& new dbData( $this, $attributes ); + } + return $this->data; + } + + /** + * Adds a field to a table object + * + * $name is the name of the table to which the field should be added. + * $type is an ADODB datadict field type. The following field types + * are supported as of ADODB 3.40: + * - C: varchar + * - X: CLOB (character large object) or largest varchar size + * if CLOB is not supported + * - C2: Multibyte varchar + * - X2: Multibyte CLOB + * - B: BLOB (binary large object) + * - D: Date (some databases do not support this, and we return a datetime type) + * - T: Datetime or Timestamp + * - L: Integer field suitable for storing booleans (0 or 1) + * - I: Integer (mapped to I4) + * - I1: 1-byte integer + * - I2: 2-byte integer + * - I4: 4-byte integer + * - I8: 8-byte integer + * - F: Floating point number + * - N: Numeric or decimal number + * + * @param string $name Name of the table to which the field will be added. + * @param string $type ADODB datadict field type. + * @param string $size Field size + * @param array $opts Field options array + * @return array Field specifier array + */ + function addField( $name, $type, $size = NULL, $opts = NULL ) { + $field_id = $this->FieldID( $name ); + + // Set the field index so we know where we are + $this->current_field = $field_id; + + // Set the field name (required) + $this->fields[$field_id]['NAME'] = $name; + + // Set the field type (required) + $this->fields[$field_id]['TYPE'] = $type; + + // Set the field size (optional) + if( isset( $size ) ) { + $this->fields[$field_id]['SIZE'] = $size; + } + + // Set the field options + if( isset( $opts ) ) { + $this->fields[$field_id]['OPTS'][] = $opts; + } + } + + /** + * Adds a field option to the current field specifier + * + * This method adds a field option allowed by the ADOdb datadict + * and appends it to the given field. + * + * @param string $field Field name + * @param string $opt ADOdb field option + * @param mixed $value Field option value + * @return array Field specifier array + */ + function addFieldOpt( $field, $opt, $value = NULL ) { + if( !isset( $value ) ) { + $this->fields[$this->FieldID( $field )]['OPTS'][] = $opt; + // Add the option and value + } else { + $this->fields[$this->FieldID( $field )]['OPTS'][] = array( $opt => $value ); + } + } + + /** + * Adds an option to the table + * + * This method takes a comma-separated list of table-level options + * and appends them to the table object. + * + * @param string $opt Table option + * @return array Options + */ + function addTableOpt( $opt ) { + $this->opts[] = $opt; + + return $this->opts; + } + + /** + * Generates the SQL that will create the table in the database + * + * @param object $xmls adoSchema object + * @return array Array containing table creation SQL + */ + function create( &$xmls ) { + $sql = array(); + + // drop any existing indexes + if( is_array( $legacy_indexes = $xmls->dict->MetaIndexes( $this->name ) ) ) { + foreach( $legacy_indexes as $index => $index_details ) { + $sql[] = $xmls->dict->DropIndexSQL( $index, $this->name ); + } + } + + // remove fields to be dropped from table object + foreach( $this->drop_field as $field ) { + unset( $this->fields[$field] ); + } + + // if table exists + if( is_array( $legacy_fields = $xmls->dict->MetaColumns( $this->name ) ) ) { + // drop table + if( $this->drop_table ) { + $sql[] = $xmls->dict->DropTableSQL( $this->name ); + + return $sql; + } + + // drop any existing fields not in schema + foreach( $legacy_fields as $field_id => $field ) { + if( !isset( $this->fields[$field_id] ) ) { + $sql[] = $xmls->dict->DropColumnSQL( $this->name, '`'.$field->name.'`' ); + } + } + // if table doesn't exist + } else { + if( $this->drop_table ) { + return $sql; + } + + $legacy_fields = array(); + } + + // Loop through the field specifier array, building the associative array for the field options + $fldarray = array(); + + foreach( $this->fields as $field_id => $finfo ) { + // Set an empty size if it isn't supplied + if( !isset( $finfo['SIZE'] ) ) { + $finfo['SIZE'] = ''; + } + + // Initialize the field array with the type and size + $fldarray[$field_id] = array( + 'NAME' => $finfo['NAME'], + 'TYPE' => $finfo['TYPE'], + 'SIZE' => $finfo['SIZE'] + ); + + // Loop through the options array and add the field options. + if( isset( $finfo['OPTS'] ) ) { + foreach( $finfo['OPTS'] as $opt ) { + // Option has an argument. + if( is_array( $opt ) ) { + $key = key( $opt ); + $value = $opt[key( $opt )]; + @$fldarray[$field_id][$key] .= $value; + // Option doesn't have arguments + } else { + $fldarray[$field_id][$opt] = $opt; + } + } + } + } + + if( empty( $legacy_fields ) ) { + // Create the new table + $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts ); + logMsg( end( $sql ), 'Generated CreateTableSQL' ); + } else { + // Upgrade an existing table + logMsg( "Upgrading {$this->name} using '{$xmls->upgrade}'" ); + switch( $xmls->upgrade ) { + // Use ChangeTableSQL + case 'ALTER': + logMsg( 'Generated ChangeTableSQL (ALTERing table)' ); + $sql[] = $xmls->dict->ChangeTableSQL( $this->name, $fldarray, $this->opts ); + break; + case 'REPLACE': + logMsg( 'Doing upgrade REPLACE (testing)' ); + $sql[] = $xmls->dict->DropTableSQL( $this->name ); + $sql[] = $xmls->dict->CreateTableSQL( $this->name, $fldarray, $this->opts ); + break; + // ignore table + default: + return array(); + } + } + + foreach( $this->indexes as $index ) { + $sql[] = $index->create( $xmls ); + } + + if( isset( $this->data ) ) { + $sql[] = $this->data->create( $xmls ); + } + + return $sql; + } + + /** + * Marks a field or table for destruction + */ + function drop() { + if( isset( $this->current_field ) ) { + // Drop the current field + logMsg( "Dropping field '{$this->current_field}' from table '{$this->name}'" ); + // $this->drop_field[$this->current_field] = $xmls->dict->DropColumnSQL( $this->name, $this->current_field ); + $this->drop_field[$this->current_field] = $this->current_field; + } else { + // Drop the current table + logMsg( "Dropping table '{$this->name}'" ); + // $this->drop_table = $xmls->dict->DropTableSQL( $this->name ); + $this->drop_table = TRUE; + } + } +} + +/** +* Creates an index object in ADOdb's datadict format +* +* This class stores information about a database index. As charactaristics +* of the index are loaded from the external source, methods and properties +* of this class are used to build up the index description in ADOdb's +* datadict format. +* +* @package axmls +* @access private +*/ +class dbIndex extends dbObject { + + /** + * @var string Index name + */ + var $name; + + /** + * @var array Index options: Index-level options + */ + var $opts = array(); + + /** + * @var array Indexed fields: Table columns included in this index + */ + var $columns = array(); + + /** + * @var boolean Mark index for destruction + * @access private + */ + var $drop = FALSE; + + /** + * Initializes the new dbIndex object. + * + * @param object $parent Parent object + * @param array $attributes Attributes + * + * @internal + */ + function dbIndex( &$parent, $attributes = NULL ) { + $this->parent =& $parent; + + $this->name = $this->prefix ($attributes['NAME']); + } + + /** + * XML Callback to process start elements + * + * Processes XML opening tags. + * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH. + * + * @access private + */ + function _tag_open( &$parser, $tag, $attributes ) { + $this->currentElement = strtoupper( $tag ); + + switch( $this->currentElement ) { + case 'DROP': + $this->drop(); + break; + case 'CLUSTERED': + case 'BITMAP': + case 'UNIQUE': + case 'FULLTEXT': + case 'HASH': + // Add index Option + $this->addIndexOpt( $this->currentElement ); + break; + default: + // print_r( array( $tag, $attributes ) ); + } + } + + /** + * XML Callback to process CDATA elements + * + * Processes XML cdata. + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + switch( $this->currentElement ) { + // Index field name + case 'COL': + $this->addField( $cdata ); + break; + default: + + } + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + $this->currentElement = ''; + + switch( strtoupper( $tag ) ) { + case 'INDEX': + xml_set_object( $parser, $this->parent ); + break; + } + } + + /** + * Adds a field to the index + * + * @param string $name Field name + * @return string Field list + */ + function addField( $name ) { + $this->columns[$this->FieldID( $name )] = $name; + + // Return the field list + return $this->columns; + } + + /** + * Adds options to the index + * + * @param string $opt Comma-separated list of index options. + * @return string Option list + */ + function addIndexOpt( $opt ) { + $this->opts[] = $opt; + + // Return the options list + return $this->opts; + } + + /** + * Generates the SQL that will create the index in the database + * + * @param object $xmls adoSchema object + * @return array Array containing index creation SQL + */ + function create( &$xmls ) { + if( $this->drop ) { + return NULL; + } + + // eliminate any columns that aren't in the table + foreach( $this->columns as $id => $col ) { + if( !isset( $this->parent->fields[$id] ) ) { + unset( $this->columns[$id] ); + } + } + + return $xmls->dict->CreateIndexSQL( $this->name, $this->parent->name, $this->columns, $this->opts ); + } + + /** + * Marks an index for destruction + */ + function drop() { + $this->drop = TRUE; + } +} + +/** +* Creates a data object in ADOdb's datadict format +* +* This class stores information about table data. +* +* @package axmls +* @access private +*/ +class dbData extends dbObject { + + var $data = array(); + + var $row; + + /** + * Initializes the new dbIndex object. + * + * @param object $parent Parent object + * @param array $attributes Attributes + * + * @internal + */ + function dbData( &$parent, $attributes = NULL ) { + $this->parent =& $parent; + } + + /** + * XML Callback to process start elements + * + * Processes XML opening tags. + * Elements currently processed are: DROP, CLUSTERED, BITMAP, UNIQUE, FULLTEXT & HASH. + * + * @access private + */ + function _tag_open( &$parser, $tag, $attributes ) { + $this->currentElement = strtoupper( $tag ); + + switch( $this->currentElement ) { + case 'ROW': + $this->row = count( $this->data ); + $this->data[$this->row] = array(); + break; + case 'F': + $this->addField($attributes); + default: + // print_r( array( $tag, $attributes ) ); + } + } + + /** + * XML Callback to process CDATA elements + * + * Processes XML cdata. + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + switch( $this->currentElement ) { + // Index field name + case 'F': + $this->addData( $cdata ); + break; + default: + + } + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + $this->currentElement = ''; + + switch( strtoupper( $tag ) ) { + case 'DATA': + xml_set_object( $parser, $this->parent ); + break; + } + } + + /** + * Adds a field to the index + * + * @param string $name Field name + * @return string Field list + */ + function addField( $attributes ) { + if( isset( $attributes['NAME'] ) ) { + $name = $attributes['NAME']; + } else { + $name = count($this->data[$this->row]); + } + + // Set the field index so we know where we are + $this->current_field = $this->FieldID( $name ); + } + + /** + * Adds options to the index + * + * @param string $opt Comma-separated list of index options. + * @return string Option list + */ + function addData( $cdata ) { + if( !isset( $this->data[$this->row] ) ) { + $this->data[$this->row] = array(); + } + + if( !isset( $this->data[$this->row][$this->current_field] ) ) { + $this->data[$this->row][$this->current_field] = ''; + } + + $this->data[$this->row][$this->current_field] .= $cdata; + } + + /** + * Generates the SQL that will create the index in the database + * + * @param object $xmls adoSchema object + * @return array Array containing index creation SQL + */ + function create( &$xmls ) { + $table = $xmls->dict->TableName($this->parent->name); + $table_field_count = count($this->parent->fields); + $sql = array(); + + // eliminate any columns that aren't in the table + foreach( $this->data as $row ) { + $table_fields = $this->parent->fields; + $fields = array(); + + foreach( $row as $field_id => $field_data ) { + if( !array_key_exists( $field_id, $table_fields ) ) { + if( is_numeric( $field_id ) ) { + $field_id = reset( array_keys( $table_fields ) ); + } else { + continue; + } + } + + $name = $table_fields[$field_id]['NAME']; + + switch( $table_fields[$field_id]['TYPE'] ) { + case 'C': + case 'C2': + case 'X': + case 'X2': + $fields[$name] = $xmls->db->qstr( $field_data ); + break; + case 'I': + case 'I1': + case 'I2': + case 'I4': + case 'I8': + $fields[$name] = intval($field_data); + break; + default: + $fields[$name] = $field_data; + } + + unset($table_fields[$field_id]); + } + + // check that at least 1 column is specified + if( empty( $fields ) ) { + continue; + } + + // check that no required columns are missing + if( count( $fields ) < $table_field_count ) { + foreach( $table_fields as $field ) { + if (isset( $field['OPTS'] )) + if( ( in_array( 'NOTNULL', $field['OPTS'] ) || in_array( 'KEY', $field['OPTS'] ) ) && !in_array( 'AUTOINCREMENT', $field['OPTS'] ) ) { + continue(2); + } + } + } + + $sql[] = 'INSERT INTO '. $table .' ('. implode( ',', array_keys( $fields ) ) .') VALUES ('. implode( ',', $fields ) .')'; + } + + return $sql; + } +} + +/** +* Creates the SQL to execute a list of provided SQL queries +* +* @package axmls +* @access private +*/ +class dbQuerySet extends dbObject { + + /** + * @var array List of SQL queries + */ + var $queries = array(); + + /** + * @var string String used to build of a query line by line + */ + var $query; + + /** + * @var string Query prefix key + */ + var $prefixKey = ''; + + /** + * @var boolean Auto prefix enable (TRUE) + */ + var $prefixMethod = 'AUTO'; + + /** + * Initializes the query set. + * + * @param object $parent Parent object + * @param array $attributes Attributes + */ + function dbQuerySet( &$parent, $attributes = NULL ) { + $this->parent =& $parent; + + // Overrides the manual prefix key + if( isset( $attributes['KEY'] ) ) { + $this->prefixKey = $attributes['KEY']; + } + + $prefixMethod = isset( $attributes['PREFIXMETHOD'] ) ? strtoupper( trim( $attributes['PREFIXMETHOD'] ) ) : ''; + + // Enables or disables automatic prefix prepending + switch( $prefixMethod ) { + case 'AUTO': + $this->prefixMethod = 'AUTO'; + break; + case 'MANUAL': + $this->prefixMethod = 'MANUAL'; + break; + case 'NONE': + $this->prefixMethod = 'NONE'; + break; + } + } + + /** + * XML Callback to process start elements. Elements currently + * processed are: QUERY. + * + * @access private + */ + function _tag_open( &$parser, $tag, $attributes ) { + $this->currentElement = strtoupper( $tag ); + + switch( $this->currentElement ) { + case 'QUERY': + // Create a new query in a SQL queryset. + // Ignore this query set if a platform is specified and it's different than the + // current connection platform. + if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { + $this->newQuery(); + } else { + $this->discardQuery(); + } + break; + default: + // print_r( array( $tag, $attributes ) ); + } + } + + /** + * XML Callback to process CDATA elements + */ + function _tag_cdata( &$parser, $cdata ) { + switch( $this->currentElement ) { + // Line of queryset SQL data + case 'QUERY': + $this->buildQuery( $cdata ); + break; + default: + + } + } + + /** + * XML Callback to process end elements + * + * @access private + */ + function _tag_close( &$parser, $tag ) { + $this->currentElement = ''; + + switch( strtoupper( $tag ) ) { + case 'QUERY': + // Add the finished query to the open query set. + $this->addQuery(); + break; + case 'SQL': + $this->parent->addSQL( $this->create( $this->parent ) ); + xml_set_object( $parser, $this->parent ); + $this->destroy(); + break; + default: + + } + } + + /** + * Re-initializes the query. + * + * @return boolean TRUE + */ + function newQuery() { + $this->query = ''; + + return TRUE; + } + + /** + * Discards the existing query. + * + * @return boolean TRUE + */ + function discardQuery() { + unset( $this->query ); + + return TRUE; + } + + /** + * Appends a line to a query that is being built line by line + * + * @param string $data Line of SQL data or NULL to initialize a new query + * @return string SQL query string. + */ + function buildQuery( $sql = NULL ) { + if( !isset( $this->query ) OR empty( $sql ) ) { + return FALSE; + } + + $this->query .= $sql; + + return $this->query; + } + + /** + * Adds a completed query to the query list + * + * @return string SQL of added query + */ + function addQuery() { + if( !isset( $this->query ) ) { + return FALSE; + } + + $this->queries[] = $return = trim($this->query); + + unset( $this->query ); + + return $return; + } + + /** + * Creates and returns the current query set + * + * @param object $xmls adoSchema object + * @return array Query set + */ + function create( &$xmls ) { + foreach( $this->queries as $id => $query ) { + switch( $this->prefixMethod ) { + case 'AUTO': + // Enable auto prefix replacement + + // Process object prefix. + // Evaluate SQL statements to prepend prefix to objects + $query = $this->prefixQuery( '/^\s*((?is)INSERT\s+(INTO\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); + $query = $this->prefixQuery( '/^\s*((?is)UPDATE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); + $query = $this->prefixQuery( '/^\s*((?is)DELETE\s+(FROM\s+)?)((\w+\s*,?\s*)+)(\s.*$)/', $query, $xmls->objectPrefix ); + + // SELECT statements aren't working yet + #$data = preg_replace( '/(?ias)(^\s*SELECT\s+.*\s+FROM)\s+(\W\s*,?\s*)+((?i)\s+WHERE.*$)/', "\1 $prefix\2 \3", $data ); + + case 'MANUAL': + // If prefixKey is set and has a value then we use it to override the default constant XMLS_PREFIX. + // If prefixKey is not set, we use the default constant XMLS_PREFIX + if( isset( $this->prefixKey ) AND( $this->prefixKey !== '' ) ) { + // Enable prefix override + $query = str_replace( $this->prefixKey, $xmls->objectPrefix, $query ); + } else { + // Use default replacement + $query = str_replace( XMLS_PREFIX , $xmls->objectPrefix, $query ); + } + } + + $this->queries[$id] = trim( $query ); + } + + // Return the query set array + return $this->queries; + } + + /** + * Rebuilds the query with the prefix attached to any objects + * + * @param string $regex Regex used to add prefix + * @param string $query SQL query string + * @param string $prefix Prefix to be appended to tables, indices, etc. + * @return string Prefixed SQL query string. + */ + function prefixQuery( $regex, $query, $prefix = NULL ) { + if( !isset( $prefix ) ) { + return $query; + } + + if( preg_match( $regex, $query, $match ) ) { + $preamble = $match[1]; + $postamble = $match[5]; + $objectList = explode( ',', $match[3] ); + // $prefix = $prefix . '_'; + + $prefixedList = ''; + + foreach( $objectList as $object ) { + if( $prefixedList !== '' ) { + $prefixedList .= ', '; + } + + $prefixedList .= $prefix . trim( $object ); + } + + $query = $preamble . ' ' . $prefixedList . ' ' . $postamble; + } + + return $query; + } +} + +/** +* Loads and parses an XML file, creating an array of "ready-to-run" SQL statements +* +* This class is used to load and parse the XML file, to create an array of SQL statements +* that can be used to build a database, and to build the database using the SQL array. +* +* @tutorial getting_started.pkg +* +* @author Richard Tango-Lowy & Dan Cech +* @version $Revision: 1.12 $ +* +* @package axmls +*/ +class adoSchema { + + /** + * @var array Array containing SQL queries to generate all objects + * @access private + */ + var $sqlArray; + + /** + * @var object ADOdb connection object + * @access private + */ + var $db; + + /** + * @var object ADOdb Data Dictionary + * @access private + */ + var $dict; + + /** + * @var string Current XML element + * @access private + */ + var $currentElement = ''; + + /** + * @var string If set (to 'ALTER' or 'REPLACE'), upgrade an existing database + * @access private + */ + var $upgrade = ''; + + /** + * @var string Optional object prefix + * @access private + */ + var $objectPrefix = ''; + + /** + * @var long Original Magic Quotes Runtime value + * @access private + */ + var $mgq; + + /** + * @var long System debug + * @access private + */ + var $debug; + + /** + * @var string Regular expression to find schema version + * @access private + */ + var $versionRegex = '/<schema.*?( version="([^"]*)")?.*?>/'; + + /** + * @var string Current schema version + * @access private + */ + var $schemaVersion; + + /** + * @var int Success of last Schema execution + */ + var $success; + + /** + * @var bool Execute SQL inline as it is generated + */ + var $executeInline; + + /** + * @var bool Continue SQL execution if errors occur + */ + var $continueOnError; + + /** + * Creates an adoSchema object + * + * Creating an adoSchema object is the first step in processing an XML schema. + * The only parameter is an ADOdb database connection object, which must already + * have been created. + * + * @param object $db ADOdb database connection object. + */ + function adoSchema( &$db ) { + // Initialize the environment + $this->mgq = get_magic_quotes_runtime(); + set_magic_quotes_runtime(0); + + $this->db =& $db; + $this->debug = $this->db->debug; + $this->dict = NewDataDictionary( $this->db ); + $this->sqlArray = array(); + $this->schemaVersion = XMLS_SCHEMA_VERSION; + $this->executeInline( XMLS_EXECUTE_INLINE ); + $this->continueOnError( XMLS_CONTINUE_ON_ERROR ); + $this->setUpgradeMethod(); + } + + /** + * Sets the method to be used for upgrading an existing database + * + * Use this method to specify how existing database objects should be upgraded. + * The method option can be set to ALTER, REPLACE, BEST, or NONE. ALTER attempts to + * alter each database object directly, REPLACE attempts to rebuild each object + * from scratch, BEST attempts to determine the best upgrade method for each + * object, and NONE disables upgrading. + * + * This method is not yet used by AXMLS, but exists for backward compatibility. + * The ALTER method is automatically assumed when the adoSchema object is + * instantiated; other upgrade methods are not currently supported. + * + * @param string $method Upgrade method (ALTER|REPLACE|BEST|NONE) + * @returns string Upgrade method used + */ + function SetUpgradeMethod( $method = '' ) { + if( !is_string( $method ) ) { + return FALSE; + } + + $method = strtoupper( $method ); + + // Handle the upgrade methods + switch( $method ) { + case 'ALTER': + $this->upgrade = $method; + break; + case 'REPLACE': + $this->upgrade = $method; + break; + case 'BEST': + $this->upgrade = 'ALTER'; + break; + case 'NONE': + $this->upgrade = 'NONE'; + break; + default: + // Use default if no legitimate method is passed. + $this->upgrade = XMLS_DEFAULT_UPGRADE_METHOD; + } + + return $this->upgrade; + } + + /** + * Enables/disables inline SQL execution. + * + * Call this method to enable or disable inline execution of the schema. If the mode is set to TRUE (inline execution), + * AXMLS applies the SQL to the database immediately as each schema entity is parsed. If the mode + * is set to FALSE (post execution), AXMLS parses the entire schema and you will need to call adoSchema::ExecuteSchema() + * to apply the schema to the database. + * + * @param bool $mode execute + * @return bool current execution mode + * + * @see ParseSchema(), ExecuteSchema() + */ + function ExecuteInline( $mode = NULL ) { + if( is_bool( $mode ) ) { + $this->executeInline = $mode; + } + + return $this->executeInline; + } + + /** + * Enables/disables SQL continue on error. + * + * Call this method to enable or disable continuation of SQL execution if an error occurs. + * If the mode is set to TRUE (continue), AXMLS will continue to apply SQL to the database, even if an error occurs. + * If the mode is set to FALSE (halt), AXMLS will halt execution of generated sql if an error occurs, though parsing + * of the schema will continue. + * + * @param bool $mode execute + * @return bool current continueOnError mode + * + * @see addSQL(), ExecuteSchema() + */ + function ContinueOnError( $mode = NULL ) { + if( is_bool( $mode ) ) { + $this->continueOnError = $mode; + } + + return $this->continueOnError; + } + + /** + * Loads an XML schema from a file and converts it to SQL. + * + * Call this method to load the specified schema (see the DTD for the proper format) from + * the filesystem and generate the SQL necessary to create the database described. + * @see ParseSchemaString() + * + * @param string $file Name of XML schema file. + * @param bool $returnSchema Return schema rather than parsing. + * @return array Array of SQL queries, ready to execute + */ + function ParseSchema( $filename, $returnSchema = FALSE ) { + return $this->ParseSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema ); + } + + /** + * Loads an XML schema from a file and converts it to SQL. + * + * Call this method to load the specified schema from a file (see the DTD for the proper format) + * and generate the SQL necessary to create the database described by the schema. + * + * @param string $file Name of XML schema file. + * @param bool $returnSchema Return schema rather than parsing. + * @return array Array of SQL queries, ready to execute. + * + * @deprecated Replaced by adoSchema::ParseSchema() and adoSchema::ParseSchemaString() + * @see ParseSchema(), ParseSchemaString() + */ + function ParseSchemaFile( $filename, $returnSchema = FALSE ) { + // Open the file + if( !($fp = fopen( $filename, 'r' )) ) { + // die( 'Unable to open file' ); + return FALSE; + } + + // do version detection here + if( $this->SchemaFileVersion( $filename ) != $this->schemaVersion ) { + return FALSE; + } + + if ( $returnSchema ) + { + $xmlstring = ''; + while( $data = fread( $fp, 100000 ) ) { + $xmlstring .= $data; + } + return $xmlstring; + } + + $this->success = 2; + + $xmlParser = $this->create_parser(); + + // Process the file + while( $data = fread( $fp, 4096 ) ) { + if( !xml_parse( $xmlParser, $data, feof( $fp ) ) ) { + die( sprintf( + "XML error: %s at line %d", + xml_error_string( xml_get_error_code( $xmlParser) ), + xml_get_current_line_number( $xmlParser) + ) ); + } + } + + xml_parser_free( $xmlParser ); + + return $this->sqlArray; + } + + /** + * Converts an XML schema string to SQL. + * + * Call this method to parse a string containing an XML schema (see the DTD for the proper format) + * and generate the SQL necessary to create the database described by the schema. + * @see ParseSchema() + * + * @param string $xmlstring XML schema string. + * @param bool $returnSchema Return schema rather than parsing. + * @return array Array of SQL queries, ready to execute. + */ + function ParseSchemaString( $xmlstring, $returnSchema = FALSE ) { + if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) { + return FALSE; + } + + // do version detection here + if( $this->SchemaStringVersion( $xmlstring ) != $this->schemaVersion ) { + return FALSE; + } + + if ( $returnSchema ) + { + return $xmlstring; + } + + $this->success = 2; + + $xmlParser = $this->create_parser(); + + if( !xml_parse( $xmlParser, $xmlstring, TRUE ) ) { + die( sprintf( + "XML error: %s at line %d", + xml_error_string( xml_get_error_code( $xmlParser) ), + xml_get_current_line_number( $xmlParser) + ) ); + } + + xml_parser_free( $xmlParser ); + + return $this->sqlArray; + } + + /** + * Loads an XML schema from a file and converts it to uninstallation SQL. + * + * Call this method to load the specified schema (see the DTD for the proper format) from + * the filesystem and generate the SQL necessary to remove the database described. + * @see RemoveSchemaString() + * + * @param string $file Name of XML schema file. + * @param bool $returnSchema Return schema rather than parsing. + * @return array Array of SQL queries, ready to execute + */ + function RemoveSchema( $filename, $returnSchema = FALSE ) { + return $this->RemoveSchemaString( $this->ConvertSchemaFile( $filename ), $returnSchema ); + } + + /** + * Converts an XML schema string to uninstallation SQL. + * + * Call this method to parse a string containing an XML schema (see the DTD for the proper format) + * and generate the SQL necessary to uninstall the database described by the schema. + * @see RemoveSchema() + * + * @param string $schema XML schema string. + * @param bool $returnSchema Return schema rather than parsing. + * @return array Array of SQL queries, ready to execute. + */ + function RemoveSchemaString( $schema, $returnSchema = FALSE ) { + + // grab current version + if( !( $version = $this->SchemaStringVersion( $schema ) ) ) { + return FALSE; + } + + return $this->ParseSchemaString( $this->TransformSchema( $schema, 'remove-' . $version), $returnSchema ); + } + + /** + * Applies the current XML schema to the database (post execution). + * + * Call this method to apply the current schema (generally created by calling + * ParseSchema() or ParseSchemaString() ) to the database (creating the tables, indexes, + * and executing other SQL specified in the schema) after parsing. + * @see ParseSchema(), ParseSchemaString(), ExecuteInline() + * + * @param array $sqlArray Array of SQL statements that will be applied rather than + * the current schema. + * @param boolean $continueOnErr Continue to apply the schema even if an error occurs. + * @returns integer 0 if failure, 1 if errors, 2 if successful. + */ + function ExecuteSchema( $sqlArray = NULL, $continueOnErr = NULL ) { + if( !is_bool( $continueOnErr ) ) { + $continueOnErr = $this->ContinueOnError(); + } + + if( !isset( $sqlArray ) ) { + $sqlArray = $this->sqlArray; + } + + if( !is_array( $sqlArray ) ) { + $this->success = 0; + } else { + $this->success = $this->dict->ExecuteSQLArray( $sqlArray, $continueOnErr ); + } + + return $this->success; + } + + /** + * Returns the current SQL array. + * + * Call this method to fetch the array of SQL queries resulting from + * ParseSchema() or ParseSchemaString(). + * + * @param string $format Format: HTML, TEXT, or NONE (PHP array) + * @return array Array of SQL statements or FALSE if an error occurs + */ + function PrintSQL( $format = 'NONE' ) { + $sqlArray = null; + return $this->getSQL( $format, $sqlArray ); + } + + /** + * Saves the current SQL array to the local filesystem as a list of SQL queries. + * + * Call this method to save the array of SQL queries (generally resulting from a + * parsed XML schema) to the filesystem. + * + * @param string $filename Path and name where the file should be saved. + * @return boolean TRUE if save is successful, else FALSE. + */ + function SaveSQL( $filename = './schema.sql' ) { + + if( !isset( $sqlArray ) ) { + $sqlArray = $this->sqlArray; + } + if( !isset( $sqlArray ) ) { + return FALSE; + } + + $fp = fopen( $filename, "w" ); + + foreach( $sqlArray as $key => $query ) { + fwrite( $fp, $query . ";\n" ); + } + fclose( $fp ); + } + + /** + * Create an xml parser + * + * @return object PHP XML parser object + * + * @access private + */ + function &create_parser() { + // Create the parser + $xmlParser = xml_parser_create(); + xml_set_object( $xmlParser, $this ); + + // Initialize the XML callback functions + xml_set_element_handler( $xmlParser, '_tag_open', '_tag_close' ); + xml_set_character_data_handler( $xmlParser, '_tag_cdata' ); + + return $xmlParser; + } + + /** + * XML Callback to process start elements + * + * @access private + */ + function _tag_open( &$parser, $tag, $attributes ) { + switch( strtoupper( $tag ) ) { + case 'TABLE': + $this->obj = new dbTable( $this, $attributes ); + xml_set_object( $parser, $this->obj ); + break; + case 'SQL': + if( !isset( $attributes['PLATFORM'] ) OR $this->supportedPlatform( $attributes['PLATFORM'] ) ) { + $this->obj = new dbQuerySet( $this, $attributes ); + xml_set_object( $parser, $this->obj ); + } + break; + default: + // print_r( array( $tag, $attributes ) ); + } + + } + + /** + * XML Callback to process CDATA elements + * + * @access private + */ + function _tag_cdata( &$parser, $cdata ) { + } + + /** + * XML Callback to process end elements + * + * @access private + * @internal + */ + function _tag_close( &$parser, $tag ) { + + } + + /** + * Converts an XML schema string to the specified DTD version. + * + * Call this method to convert a string containing an XML schema to a different AXMLS + * DTD version. For instance, to convert a schema created for an pre-1.0 version for + * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version + * parameter is specified, the schema will be converted to the current DTD version. + * If the newFile parameter is provided, the converted schema will be written to the specified + * file. + * @see ConvertSchemaFile() + * + * @param string $schema String containing XML schema that will be converted. + * @param string $newVersion DTD version to convert to. + * @param string $newFile File name of (converted) output file. + * @return string Converted XML schema or FALSE if an error occurs. + */ + function ConvertSchemaString( $schema, $newVersion = NULL, $newFile = NULL ) { + + // grab current version + if( !( $version = $this->SchemaStringVersion( $schema ) ) ) { + return FALSE; + } + + if( !isset ($newVersion) ) { + $newVersion = $this->schemaVersion; + } + + if( $version == $newVersion ) { + $result = $schema; + } else { + $result = $this->TransformSchema( $schema, 'convert-' . $version . '-' . $newVersion); + } + + if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) { + fwrite( $fp, $result ); + fclose( $fp ); + } + + return $result; + } + + // compat for pre-4.3 - jlim + function _file_get_contents($path) + { + if (function_exists('file_get_contents')) return file_get_contents($path); + return join('',file($path)); + } + + /** + * Converts an XML schema file to the specified DTD version. + * + * Call this method to convert the specified XML schema file to a different AXMLS + * DTD version. For instance, to convert a schema created for an pre-1.0 version for + * AXMLS (DTD version 0.1) to a newer version of the DTD (e.g. 0.2). If no DTD version + * parameter is specified, the schema will be converted to the current DTD version. + * If the newFile parameter is provided, the converted schema will be written to the specified + * file. + * @see ConvertSchemaString() + * + * @param string $filename Name of XML schema file that will be converted. + * @param string $newVersion DTD version to convert to. + * @param string $newFile File name of (converted) output file. + * @return string Converted XML schema or FALSE if an error occurs. + */ + function ConvertSchemaFile( $filename, $newVersion = NULL, $newFile = NULL ) { + + // grab current version + if( !( $version = $this->SchemaFileVersion( $filename ) ) ) { + return FALSE; + } + + if( !isset ($newVersion) ) { + $newVersion = $this->schemaVersion; + } + + if( $version == $newVersion ) { + $result = _file_get_contents( $filename ); + + // remove unicode BOM if present + if( substr( $result, 0, 3 ) == sprintf( '%c%c%c', 239, 187, 191 ) ) { + $result = substr( $result, 3 ); + } + } else { + $result = $this->TransformSchema( $filename, 'convert-' . $version . '-' . $newVersion, 'file' ); + } + + if( is_string( $result ) AND is_string( $newFile ) AND ( $fp = fopen( $newFile, 'w' ) ) ) { + fwrite( $fp, $result ); + fclose( $fp ); + } + + return $result; + } + + function TransformSchema( $schema, $xsl, $schematype='string' ) + { + // Fail if XSLT extension is not available + if( ! function_exists( 'xslt_create' ) ) { + return FALSE; + } + + $xsl_file = dirname( __FILE__ ) . '/xsl/' . $xsl . '.xsl'; + + // look for xsl + if( !is_readable( $xsl_file ) ) { + return FALSE; + } + + switch( $schematype ) + { + case 'file': + if( !is_readable( $schema ) ) { + return FALSE; + } + + $schema = _file_get_contents( $schema ); + break; + case 'string': + default: + if( !is_string( $schema ) ) { + return FALSE; + } + } + + $arguments = array ( + '/_xml' => $schema, + '/_xsl' => _file_get_contents( $xsl_file ) + ); + + // create an XSLT processor + $xh = xslt_create (); + + // set error handler + xslt_set_error_handler ($xh, array (&$this, 'xslt_error_handler')); + + // process the schema + $result = xslt_process ($xh, 'arg:/_xml', 'arg:/_xsl', NULL, $arguments); + + xslt_free ($xh); + + return $result; + } + + /** + * Processes XSLT transformation errors + * + * @param object $parser XML parser object + * @param integer $errno Error number + * @param integer $level Error level + * @param array $fields Error information fields + * + * @access private + */ + function xslt_error_handler( $parser, $errno, $level, $fields ) { + if( is_array( $fields ) ) { + $msg = array( + 'Message Type' => ucfirst( $fields['msgtype'] ), + 'Message Code' => $fields['code'], + 'Message' => $fields['msg'], + 'Error Number' => $errno, + 'Level' => $level + ); + + switch( $fields['URI'] ) { + case 'arg:/_xml': + $msg['Input'] = 'XML'; + break; + case 'arg:/_xsl': + $msg['Input'] = 'XSL'; + break; + default: + $msg['Input'] = $fields['URI']; + } + + $msg['Line'] = $fields['line']; + } else { + $msg = array( + 'Message Type' => 'Error', + 'Error Number' => $errno, + 'Level' => $level, + 'Fields' => var_export( $fields, TRUE ) + ); + } + + $error_details = $msg['Message Type'] . ' in XSLT Transformation' . "\n" + . '<table>' . "\n"; + + foreach( $msg as $label => $details ) { + $error_details .= '<tr><td><b>' . $label . ': </b></td><td>' . htmlentities( $details ) . '</td></tr>' . "\n"; + } + + $error_details .= '</table>'; + + trigger_error( $error_details, E_USER_ERROR ); + } + + /** + * Returns the AXMLS Schema Version of the requested XML schema file. + * + * Call this method to obtain the AXMLS DTD version of the requested XML schema file. + * @see SchemaStringVersion() + * + * @param string $filename AXMLS schema file + * @return string Schema version number or FALSE on error + */ + function SchemaFileVersion( $filename ) { + // Open the file + if( !($fp = fopen( $filename, 'r' )) ) { + // die( 'Unable to open file' ); + return FALSE; + } + + // Process the file + while( $data = fread( $fp, 4096 ) ) { + if( preg_match( $this->versionRegex, $data, $matches ) ) { + return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION; + } + } + + return FALSE; + } + + /** + * Returns the AXMLS Schema Version of the provided XML schema string. + * + * Call this method to obtain the AXMLS DTD version of the provided XML schema string. + * @see SchemaFileVersion() + * + * @param string $xmlstring XML schema string + * @return string Schema version number or FALSE on error + */ + function SchemaStringVersion( $xmlstring ) { + if( !is_string( $xmlstring ) OR empty( $xmlstring ) ) { + return FALSE; + } + + if( preg_match( $this->versionRegex, $xmlstring, $matches ) ) { + return !empty( $matches[2] ) ? $matches[2] : XMLS_DEFAULT_SCHEMA_VERSION; + } + + return FALSE; + } + + /** + * Extracts an XML schema from an existing database. + * + * Call this method to create an XML schema string from an existing database. + * If the data parameter is set to TRUE, AXMLS will include the data from the database + * in the schema. + * + * @param boolean $data Include data in schema dump + * @return string Generated XML schema + */ + function ExtractSchema( $data = FALSE ) { + $old_mode = $this->db->SetFetchMode( ADODB_FETCH_NUM ); + + $schema = '<?xml version="1.0"?>' . "\n" + . '<schema version="' . $this->schemaVersion . '">' . "\n"; + + if( is_array( $tables = $this->db->MetaTables( 'TABLES' ) ) ) { + foreach( $tables as $table ) { + $schema .= ' <table name="' . $table . '">' . "\n"; + + // grab details from database + $rs = $this->db->Execute( 'SELECT * FROM ' . $table . ' WHERE 1=1' ); + $fields = $this->db->MetaColumns( $table ); + $indexes = $this->db->MetaIndexes( $table ); + + if( is_array( $fields ) ) { + foreach( $fields as $details ) { + $extra = ''; + $content = array(); + + if( $details->max_length > 0 ) { + $extra .= ' size="' . $details->max_length . '"'; + } + + if( $details->primary_key ) { + $content[] = '<KEY/>'; + } elseif( $details->not_null ) { + $content[] = '<NOTNULL/>'; + } + + if( $details->has_default ) { + $content[] = '<DEFAULT value="' . $details->default_value . '"/>'; + } + + if( $details->auto_increment ) { + $content[] = '<AUTOINCREMENT/>'; + } + + // this stops the creation of 'R' columns, + // AUTOINCREMENT is used to create auto columns + $details->primary_key = 0; + $type = $rs->MetaType( $details ); + + $schema .= ' <field name="' . $details->name . '" type="' . $type . '"' . $extra . '>'; + + if( !empty( $content ) ) { + $schema .= "\n " . implode( "\n ", $content ) . "\n "; + } + + $schema .= '</field>' . "\n"; + } + } + + if( is_array( $indexes ) ) { + foreach( $indexes as $index => $details ) { + $schema .= ' <index name="' . $index . '">' . "\n"; + + if( $details['unique'] ) { + $schema .= ' <UNIQUE/>' . "\n"; + } + + foreach( $details['columns'] as $column ) { + $schema .= ' <col>' . $column . '</col>' . "\n"; + } + + $schema .= ' </index>' . "\n"; + } + } + + if( $data ) { + $rs = $this->db->Execute( 'SELECT * FROM ' . $table ); + + if( is_object( $rs ) ) { + $schema .= ' <data>' . "\n"; + + while( $row = $rs->FetchRow() ) { + foreach( $row as $key => $val ) { + $row[$key] = htmlentities($val); + } + + $schema .= ' <row><f>' . implode( '</f><f>', $row ) . '</f></row>' . "\n"; + } + + $schema .= ' </data>' . "\n"; + } + } + + $schema .= ' </table>' . "\n"; + } + } + + $this->db->SetFetchMode( $old_mode ); + + $schema .= '</schema>'; + return $schema; + } + + /** + * Sets a prefix for database objects + * + * Call this method to set a standard prefix that will be prepended to all database tables + * and indices when the schema is parsed. Calling setPrefix with no arguments clears the prefix. + * + * @param string $prefix Prefix that will be prepended. + * @param boolean $underscore If TRUE, automatically append an underscore character to the prefix. + * @return boolean TRUE if successful, else FALSE + */ + function SetPrefix( $prefix = '', $underscore = TRUE ) { + switch( TRUE ) { + // clear prefix + case empty( $prefix ): + logMsg( 'Cleared prefix' ); + $this->objectPrefix = ''; + return TRUE; + // prefix too long + case strlen( $prefix ) > XMLS_PREFIX_MAXLEN: + // prefix contains invalid characters + case !preg_match( '/^[a-z][a-z0-9_]+$/i', $prefix ): + logMsg( 'Invalid prefix: ' . $prefix ); + return FALSE; + } + + if( $underscore AND substr( $prefix, -1 ) != '_' ) { + $prefix .= '_'; + } + + // prefix valid + logMsg( 'Set prefix: ' . $prefix ); + $this->objectPrefix = $prefix; + return TRUE; + } + + /** + * Returns an object name with the current prefix prepended. + * + * @param string $name Name + * @return string Prefixed name + * + * @access private + */ + function prefix( $name = '' ) { + // if prefix is set + if( !empty( $this->objectPrefix ) ) { + // Prepend the object prefix to the table name + // prepend after quote if used + return preg_replace( '/^(`?)(.+)$/', '$1' . $this->objectPrefix . '$2', $name ); + } + + // No prefix set. Use name provided. + return $name; + } + + /** + * Checks if element references a specific platform + * + * @param string $platform Requested platform + * @returns boolean TRUE if platform check succeeds + * + * @access private + */ + function supportedPlatform( $platform = NULL ) { + $regex = '/^(\w*\|)*' . $this->db->databaseType . '(\|\w*)*$/'; + + if( !isset( $platform ) OR preg_match( $regex, $platform ) ) { + logMsg( "Platform $platform is supported" ); + return TRUE; + } else { + logMsg( "Platform $platform is NOT supported" ); + return FALSE; + } + } + + /** + * Clears the array of generated SQL. + * + * @access private + */ + function clearSQL() { + $this->sqlArray = array(); + } + + /** + * Adds SQL into the SQL array. + * + * @param mixed $sql SQL to Add + * @return boolean TRUE if successful, else FALSE. + * + * @access private + */ + function addSQL( $sql = NULL ) { + if( is_array( $sql ) ) { + foreach( $sql as $line ) { + $this->addSQL( $line ); + } + + return TRUE; + } + + if( is_string( $sql ) ) { + $this->sqlArray[] = $sql; + + // if executeInline is enabled, and either no errors have occurred or continueOnError is enabled, execute SQL. + if( $this->ExecuteInline() && ( $this->success == 2 || $this->ContinueOnError() ) ) { + $saved = $this->db->debug; + $this->db->debug = $this->debug; + $ok = $this->db->Execute( $sql ); + $this->db->debug = $saved; + + if( !$ok ) { + if( $this->debug ) { + ADOConnection::outp( $this->db->ErrorMsg() ); + } + + $this->success = 1; + } + } + + return TRUE; + } + + return FALSE; + } + + /** + * Gets the SQL array in the specified format. + * + * @param string $format Format + * @return mixed SQL + * + * @access private + */ + function getSQL( $format = NULL, $sqlArray = NULL ) { + if( !is_array( $sqlArray ) ) { + $sqlArray = $this->sqlArray; + } + + if( !is_array( $sqlArray ) ) { + return FALSE; + } + + switch( strtolower( $format ) ) { + case 'string': + case 'text': + return !empty( $sqlArray ) ? implode( ";\n\n", $sqlArray ) . ';' : ''; + case'html': + return !empty( $sqlArray ) ? nl2br( htmlentities( implode( ";\n\n", $sqlArray ) . ';' ) ) : ''; + } + + return $this->sqlArray; + } + + /** + * Destroys an adoSchema object. + * + * Call this method to clean up after an adoSchema object that is no longer in use. + * @deprecated adoSchema now cleans up automatically. + */ + function Destroy() { + set_magic_quotes_runtime( $this->mgq ); + unset( $this ); + } +} + +/** +* Message logging function +* +* @access private +*/ +function logMsg( $msg, $title = NULL, $force = FALSE ) { + if( XMLS_DEBUG or $force ) { + echo '<pre>'; + + if( isset( $title ) ) { + echo '<h3>' . htmlentities( $title ) . '</h3>'; + } + + if( is_object( $this ) ) { + echo '[' . get_class( $this ) . '] '; + } + + print_r( $msg ); + + echo '</pre>'; + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/adodb.inc.php b/framework/DataAccess/adodb/adodb.inc.php new file mode 100644 index 00000000..23b91b73 --- /dev/null +++ b/framework/DataAccess/adodb/adodb.inc.php @@ -0,0 +1,3961 @@ +<?php +/* + * Set tabs to 4 for best viewing. + * + * Latest version is available at http://adodb.sourceforge.net + * + * This is the main include file for ADOdb. + * Database specific drivers are stored in the adodb/drivers/adodb-*.inc.php + * + * The ADOdb files are formatted so that doxygen can be used to generate documentation. + * Doxygen is a documentation generation tool and can be downloaded from http://doxygen.org/ + */ + +/** + \mainpage + + @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim#natsoft.com.my). All rights reserved. + + Released under both BSD license and Lesser GPL library license. You can choose which license + you prefer. + + PHP's database access functions are not standardised. This creates a need for a database + class library to hide the differences between the different database API's (encapsulate + the differences) so we can easily switch databases. + + We currently support MySQL, Oracle, Microsoft SQL Server, Sybase, Sybase SQL Anywhere, DB2, + Informix, PostgreSQL, FrontBase, Interbase (Firebird and Borland variants), Foxpro, Access, + ADO, SAP DB, SQLite and ODBC. We have had successful reports of connecting to Progress and + other databases via ODBC. + + Latest Download at http://adodb.sourceforge.net/ + + */ + + if (!defined('_ADODB_LAYER')) { + define('_ADODB_LAYER',1); + + //============================================================================================== + // CONSTANT DEFINITIONS + //============================================================================================== + + + /** + * Set ADODB_DIR to the directory where this file resides... + * This constant was formerly called $ADODB_RootPath + */ + if (!defined('ADODB_DIR')) define('ADODB_DIR',dirname(__FILE__)); + + //============================================================================================== + // GLOBAL VARIABLES + //============================================================================================== + + GLOBAL + $ADODB_vers, // database version + $ADODB_COUNTRECS, // count number of records returned - slows down query + $ADODB_CACHE_DIR, // directory to cache recordsets + $ADODB_EXTENSION, // ADODB extension installed + $ADODB_COMPAT_FETCH, // If $ADODB_COUNTRECS and this is true, $rs->fields is available on EOF + $ADODB_FETCH_MODE; // DEFAULT, NUM, ASSOC or BOTH. Default follows native driver default... + + //============================================================================================== + // GLOBAL SETUP + //============================================================================================== + + $ADODB_EXTENSION = defined('ADODB_EXTENSION'); + + //********************************************************// + /* + Controls $ADODB_FORCE_TYPE mode. Default is ADODB_FORCE_VALUE (3). + Used in GetUpdateSql and GetInsertSql functions. Thx to Niko, nuko#mbnet.fi + + 0 = ignore empty fields. All empty fields in array are ignored. + 1 = force null. All empty, php null and string 'null' fields are changed to sql NULL values. + 2 = force empty. All empty, php null and string 'null' fields are changed to sql empty '' or 0 values. + 3 = force value. Value is left as it is. Php null and string 'null' are set to sql NULL values and empty fields '' are set to empty '' sql values. + */ + define('ADODB_FORCE_IGNORE',0); + define('ADODB_FORCE_NULL',1); + define('ADODB_FORCE_EMPTY',2); + define('ADODB_FORCE_VALUE',3); + //********************************************************// + + + if (!$ADODB_EXTENSION || ADODB_EXTENSION < 4.0) { + + define('ADODB_BAD_RS','<p>Bad $rs in %s. Connection or SQL invalid. Try using $connection->debug=true;</p>'); + + // allow [ ] @ ` " and . in table names + define('ADODB_TABLE_REGEX','([]0-9a-z_\:\"\`\.\@\[-]*)'); + + // prefetching used by oracle + if (!defined('ADODB_PREFETCH_ROWS')) define('ADODB_PREFETCH_ROWS',10); + + + /* + Controls ADODB_FETCH_ASSOC field-name case. Default is 2, use native case-names. + This currently works only with mssql, odbc, oci8po and ibase derived drivers. + + 0 = assoc lowercase field names. $rs->fields['orderid'] + 1 = assoc uppercase field names. $rs->fields['ORDERID'] + 2 = use native-case field names. $rs->fields['OrderID'] + */ + + define('ADODB_FETCH_DEFAULT',0); + define('ADODB_FETCH_NUM',1); + define('ADODB_FETCH_ASSOC',2); + define('ADODB_FETCH_BOTH',3); + + if (!defined('TIMESTAMP_FIRST_YEAR')) define('TIMESTAMP_FIRST_YEAR',100); + + // PHP's version scheme makes converting to numbers difficult - workaround + $_adodb_ver = (float) PHP_VERSION; + if ($_adodb_ver >= 5.0) { + define('ADODB_PHPVER',0x5000); + } else if ($_adodb_ver > 4.299999) { # 4.3 + define('ADODB_PHPVER',0x4300); + } else if ($_adodb_ver > 4.199999) { # 4.2 + define('ADODB_PHPVER',0x4200); + } else if (strnatcmp(PHP_VERSION,'4.0.5')>=0) { + define('ADODB_PHPVER',0x4050); + } else { + define('ADODB_PHPVER',0x4000); + } + } + + //if (!defined('ADODB_ASSOC_CASE')) define('ADODB_ASSOC_CASE',2); + + + /** + Accepts $src and $dest arrays, replacing string $data + */ + function ADODB_str_replace($src, $dest, $data) + { + if (ADODB_PHPVER >= 0x4050) return str_replace($src,$dest,$data); + + $s = reset($src); + $d = reset($dest); + while ($s !== false) { + $data = str_replace($s,$d,$data); + $s = next($src); + $d = next($dest); + } + return $data; + } + + function ADODB_Setup() + { + GLOBAL + $ADODB_vers, // database version + $ADODB_COUNTRECS, // count number of records returned - slows down query + $ADODB_CACHE_DIR, // directory to cache recordsets + $ADODB_FETCH_MODE, + $ADODB_FORCE_TYPE; + + $ADODB_FETCH_MODE = ADODB_FETCH_DEFAULT; + $ADODB_FORCE_TYPE = ADODB_FORCE_VALUE; + + + if (!isset($ADODB_CACHE_DIR)) { + $ADODB_CACHE_DIR = '/tmp'; //(isset($_ENV['TMP'])) ? $_ENV['TMP'] : '/tmp'; + } else { + // do not accept url based paths, eg. http:/ or ftp:/ + if (strpos($ADODB_CACHE_DIR,'://') !== false) + die("Illegal path http:// or ftp://"); + } + + + // Initialize random number generator for randomizing cache flushes + srand(((double)microtime())*1000000); + + /** + * ADODB version as a string. + */ + $ADODB_vers = 'V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim#natsoft.com.my). All rights reserved. Released BSD & LGPL.'; + + /** + * Determines whether recordset->RecordCount() is used. + * Set to false for highest performance -- RecordCount() will always return -1 then + * for databases that provide "virtual" recordcounts... + */ + if (!isset($ADODB_COUNTRECS)) $ADODB_COUNTRECS = true; + } + + + //============================================================================================== + // CHANGE NOTHING BELOW UNLESS YOU ARE DESIGNING ADODB + //============================================================================================== + + ADODB_Setup(); + + //============================================================================================== + // CLASS ADOFieldObject + //============================================================================================== + /** + * Helper class for FetchFields -- holds info on a column + */ + class ADOFieldObject { + public $name = ''; + public $max_length=0; + public $type=""; +/* + // additional fields by dannym... (danny_milo@yahoo.com) + public $not_null = false; + // actually, this has already been built-in in the postgres, fbsql AND mysql module? ^-^ + // so we can as well make not_null standard (leaving it at "false" does not harm anyways) + + public $has_default = false; // this one I have done only in mysql and postgres for now ... + // others to come (dannym) + public $default_value; // default, if any, and supported. Check has_default first. +*/ + } + + + + function ADODB_TransMonitor($dbms, $fn, $errno, $errmsg, $p1, $p2, &$thisConnection) + { + //print "Errorno ($fn errno=$errno m=$errmsg) "; + $thisConnection->_transOK = false; + if ($thisConnection->_oldRaiseFn) { + $fn = $thisConnection->_oldRaiseFn; + $fn($dbms, $fn, $errno, $errmsg, $p1, $p2,$thisConnection); + } + } + + //============================================================================================== + // CLASS ADOConnection + //============================================================================================== + + /** + * Connection object. For connecting to databases, and executing queries. + */ + class ADOConnection { + // + // PUBLIC VARS + // + public $dataProvider = 'native'; + public $databaseType = ''; /// RDBMS currently in use, eg. odbc, mysql, mssql + public $database = ''; /// Name of database to be used. + public $host = ''; /// The hostname of the database server + public $user = ''; /// The username which is used to connect to the database server. + public $password = ''; /// Password for the username. For security, we no longer store it. + public $debug = false; /// if set to true will output sql statements + public $maxblobsize = 262144; /// maximum size of blobs or large text fields (262144 = 256K)-- some db's die otherwise like foxpro + public $concat_operator = '+'; /// default concat operator -- change to || for Oracle/Interbase + public $substr = 'substr'; /// substring operator + public $length = 'length'; /// string length ofperator + public $random = 'rand()'; /// random function + public $upperCase = 'upper'; /// uppercase function + public $fmtDate = "'Y-m-d'"; /// used by DBDate() as the default date format used by the database + public $fmtTimeStamp = "'Y-m-d, h:i:s A'"; /// used by DBTimeStamp as the default timestamp fmt. + public $true = '1'; /// string that represents TRUE for a database + public $false = '0'; /// string that represents FALSE for a database + public $replaceQuote = "\\'"; /// string to use to replace quotes + public $nameQuote = '"'; /// string to use to quote identifiers and names + public $charSet=false; /// character set to use - only for interbase, postgres and oci8 + public $metaDatabasesSQL = ''; + public $metaTablesSQL = ''; + public $uniqueOrderBy = false; /// All order by columns have to be unique + public $emptyDate = ' '; + public $emptyTimeStamp = ' '; + public $lastInsID = false; + //-- + public $hasInsertID = false; /// supports autoincrement ID? + public $hasAffectedRows = false; /// supports affected rows for update/delete? + public $hasTop = false; /// support mssql/access SELECT TOP 10 * FROM TABLE + public $hasLimit = false; /// support pgsql/mysql SELECT * FROM TABLE LIMIT 10 + public $readOnly = false; /// this is a readonly database - used by phpLens + public $hasMoveFirst = false; /// has ability to run MoveFirst(), scrolling backwards + public $hasGenID = false; /// can generate sequences using GenID(); + public $hasTransactions = true; /// has transactions + //-- + public $genID = 0; /// sequence id used by GenID(); + public $raiseErrorFn = false; /// error function to call + public $isoDates = false; /// accepts dates in ISO format + public $cacheSecs = 3600; /// cache for 1 hour + public $sysDate = false; /// name of function that returns the current date + public $sysTimeStamp = false; /// name of function that returns the current timestamp + public $arrayClass = 'ADORecordSet_array'; /// name of class used to generate array recordsets, which are pre-downloaded recordsets + + public $noNullStrings = false; /// oracle specific stuff - if true ensures that '' is converted to ' ' + public $numCacheHits = 0; + public $numCacheMisses = 0; + public $pageExecuteCountRows = true; + public $uniqueSort = false; /// indicates that all fields in order by must be unique + public $leftOuter = false; /// operator to use for left outer join in WHERE clause + public $rightOuter = false; /// operator to use for right outer join in WHERE clause + public $ansiOuter = false; /// whether ansi outer join syntax supported + public $autoRollback = false; // autoRollback on PConnect(). + public $poorAffectedRows = false; // affectedRows not working or unreliable + + public $fnExecute = false; + public $fnCacheExecute = false; + public $blobEncodeType = false; // false=not required, 'I'=encode to integer, 'C'=encode to char + public $rsPrefix = "ADORecordSet_"; + + public $autoCommit = true; /// do not modify this yourself - actually private + public $transOff = 0; /// temporarily disable transactions + public $transCnt = 0; /// count of nested transactions + + public $fetchMode=false; + // + // PRIVATE VARS + // + public $_oldRaiseFn = false; + public $_transOK = null; + public $_connectionID = false; /// The returned link identifier whenever a successful database connection is made. + public $_errorMsg = false; /// A variable which was used to keep the returned last error message. The value will + /// then returned by the errorMsg() function + public $_errorCode = false; /// Last error code, not guaranteed to be used - only by oci8 + public $_queryID = false; /// This variable keeps the last created result link identifier + + public $_isPersistentConnection = false; /// A boolean variable to state whether its a persistent connection or normal connection. */ + public $_bindInputArray = false; /// set to true if ADOConnection.Execute() permits binding of array parameters. + public $_evalAll = false; + public $_affected = false; + public $_logsql = false; + + + /** + * Constructor + */ + function ADOConnection() + { + die('Virtual Class -- cannot instantiate'); + } + + function Version() + { + global $ADODB_vers; + + return (float) substr($ADODB_vers,1); + } + + /** + Get server version info... + + @returns An array with 2 elements: $arr['string'] is the description string, + and $arr[version] is the version (also a string). + */ + function ServerInfo() + { + return array('description' => '', 'version' => ''); + } + + function IsConnected() + { + return !empty($this->_connectionID); + } + + function _findvers($str) + { + if (preg_match('/([0-9]+\.([0-9\.])+)/',$str, $arr)) return $arr[1]; + else return ''; + } + + /** + * All error messages go through this bottleneck function. + * You can define your own handler by defining the function name in ADODB_OUTP. + */ + function outp($msg,$newline=true) + { + global $ADODB_FLUSH,$ADODB_OUTP; + + if (defined('ADODB_OUTP')) { + $fn = ADODB_OUTP; + $fn($msg,$newline); + return; + } else if (isset($ADODB_OUTP)) { + $fn = $ADODB_OUTP; + $fn($msg,$newline); + return; + } + + if ($newline) $msg .= "<br>\n"; + + if (isset($_SERVER['HTTP_USER_AGENT']) || !$newline) echo $msg; + else echo strip_tags($msg); + + + if (!empty($ADODB_FLUSH) && ob_get_length() !== false) flush(); // do not flush if output buffering enabled - useless - thx to Jesse Mullan + + } + + function Time() + { + $rs =& $this->_Execute("select $this->sysTimeStamp"); + if ($rs && !$rs->EOF) return $this->UnixTimeStamp(reset($rs->fields)); + + return false; + } + + /** + * Connect to database + * + * @param [argHostname] Host to connect to + * @param [argUsername] Userid to login + * @param [argPassword] Associated password + * @param [argDatabaseName] database + * @param [forceNew] force new connection + * + * @return true or false + */ + function Connect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "", $forceNew = false) + { + if ($argHostname != "") $this->host = $argHostname; + if ($argUsername != "") $this->user = $argUsername; + if ($argPassword != "") $this->password = $argPassword; // not stored for security reasons + if ($argDatabaseName != "") $this->database = $argDatabaseName; + + $this->_isPersistentConnection = false; + if ($forceNew) { + if ($rez=$this->_nconnect($this->host, $this->user, $this->password, $this->database)) return true; + } else { + if ($rez=$this->_connect($this->host, $this->user, $this->password, $this->database)) return true; + } + if (isset($rez)) { + $err = $this->ErrorMsg(); + if (empty($err)) $err = "Connection error to server '$argHostname' with user '$argUsername'"; + $ret = false; + } else { + $err = "Missing extension for ".$this->dataProvider; + $ret = 0; + } + if ($fn = $this->raiseErrorFn) + $fn($this->databaseType,'CONNECT',$this->ErrorNo(),$err,$this->host,$this->database,$this); + + + $this->_connectionID = false; + if ($this->debug) ADOConnection::outp( $this->host.': '.$err); + return $ret; + } + + function _nconnect($argHostname, $argUsername, $argPassword, $argDatabaseName) + { + return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabaseName); + } + + + /** + * Always force a new connection to database - currently only works with oracle + * + * @param [argHostname] Host to connect to + * @param [argUsername] Userid to login + * @param [argPassword] Associated password + * @param [argDatabaseName] database + * + * @return true or false + */ + function NConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") + { + return $this->Connect($argHostname, $argUsername, $argPassword, $argDatabaseName, true); + } + + /** + * Establish persistent connect to database + * + * @param [argHostname] Host to connect to + * @param [argUsername] Userid to login + * @param [argPassword] Associated password + * @param [argDatabaseName] database + * + * @return return true or false + */ + function PConnect($argHostname = "", $argUsername = "", $argPassword = "", $argDatabaseName = "") + { + if (defined('ADODB_NEVER_PERSIST')) + return $this->Connect($argHostname,$argUsername,$argPassword,$argDatabaseName); + + if ($argHostname != "") $this->host = $argHostname; + if ($argUsername != "") $this->user = $argUsername; + if ($argPassword != "") $this->password = $argPassword; + if ($argDatabaseName != "") $this->database = $argDatabaseName; + + $this->_isPersistentConnection = true; + if ($rez = $this->_pconnect($this->host, $this->user, $this->password, $this->database)) return true; + if (isset($rez)) { + $err = $this->ErrorMsg(); + if (empty($err)) $err = "Connection error to server '$argHostname' with user '$argUsername'"; + $ret = false; + } else { + $err = "Missing extension for ".$this->dataProvider; + $ret = 0; + } + if ($fn = $this->raiseErrorFn) { + $fn($this->databaseType,'PCONNECT',$this->ErrorNo(),$err,$this->host,$this->database,$this); + } + + $this->_connectionID = false; + if ($this->debug) ADOConnection::outp( $this->host.': '.$err); + return $ret; + } + + // Format date column in sql string given an input format that understands Y M D + function SQLDate($fmt, $col=false) + { + if (!$col) $col = $this->sysDate; + return $col; // child class implement + } + + /** + * Should prepare the sql statement and return the stmt resource. + * For databases that do not support this, we return the $sql. To ensure + * compatibility with databases that do not support prepare: + * + * $stmt = $db->Prepare("insert into table (id, name) values (?,?)"); + * $db->Execute($stmt,array(1,'Jill')) or die('insert failed'); + * $db->Execute($stmt,array(2,'Joe')) or die('insert failed'); + * + * @param sql SQL to send to database + * + * @return return FALSE, or the prepared statement, or the original sql if + * if the database does not support prepare. + * + */ + function Prepare($sql) + { + return $sql; + } + + /** + * Some databases, eg. mssql require a different function for preparing + * stored procedures. So we cannot use Prepare(). + * + * Should prepare the stored procedure and return the stmt resource. + * For databases that do not support this, we return the $sql. To ensure + * compatibility with databases that do not support prepare: + * + * @param sql SQL to send to database + * + * @return return FALSE, or the prepared statement, or the original sql if + * if the database does not support prepare. + * + */ + function PrepareSP($sql,$param=true) + { + return $this->Prepare($sql,$param); + } + + /** + * PEAR DB Compat + */ + function Quote($s) + { + return $this->qstr($s,false); + } + + /** + Requested by "Karsten Dambekalns" <k.dambekalns@fishfarm.de> + */ + function QMagic($s) + { + return $this->qstr($s,get_magic_quotes_gpc()); + } + + function q(&$s) + { + $s = $this->qstr($s,false); + } + + /** + * PEAR DB Compat - do not use internally. + */ + function ErrorNative() + { + return $this->ErrorNo(); + } + + + /** + * PEAR DB Compat - do not use internally. + */ + function nextId($seq_name) + { + return $this->GenID($seq_name); + } + + /** + * Lock a row, will escalate and lock the table if row locking not supported + * will normally free the lock at the end of the transaction + * + * @param $table name of table to lock + * @param $where where clause to use, eg: "WHERE row=12". If left empty, will escalate to table lock + */ + function RowLock($table,$where) + { + return false; + } + + function CommitLock($table) + { + return $this->CommitTrans(); + } + + function RollbackLock($table) + { + return $this->RollbackTrans(); + } + + /** + * PEAR DB Compat - do not use internally. + * + * The fetch modes for NUMERIC and ASSOC for PEAR DB and ADODB are identical + * for easy porting :-) + * + * @param mode The fetchmode ADODB_FETCH_ASSOC or ADODB_FETCH_NUM + * @returns The previous fetch mode + */ + function SetFetchMode($mode) + { + $old = $this->fetchMode; + $this->fetchMode = $mode; + + if ($old === false) { + global $ADODB_FETCH_MODE; + return $ADODB_FETCH_MODE; + } + return $old; + } + + + /** + * PEAR DB Compat - do not use internally. + */ + function &Query($sql, $inputarr=false) + { + $rs = &$this->Execute($sql, $inputarr); + if (!$rs && defined('ADODB_PEAR')) return ADODB_PEAR_Error(); + return $rs; + } + + + /** + * PEAR DB Compat - do not use internally + */ + function &LimitQuery($sql, $offset, $count, $params=false) + { + $rs = &$this->SelectLimit($sql, $count, $offset, $params); + if (!$rs && defined('ADODB_PEAR')) return ADODB_PEAR_Error(); + return $rs; + } + + + /** + * PEAR DB Compat - do not use internally + */ + function Disconnect() + { + return $this->Close(); + } + + /* + Returns placeholder for parameter, eg. + $DB->Param('a') + + will return ':a' for Oracle, and '?' for most other databases... + + For databases that require positioned params, eg $1, $2, $3 for postgresql, + pass in Param(false) before setting the first parameter. + */ + function Param($name,$type='C') + { + return '?'; + } + + /* + InParameter and OutParameter are self-documenting versions of Parameter(). + */ + function InParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false) + { + return $this->Parameter($stmt,$var,$name,false,$maxLen,$type); + } + + /* + */ + function OutParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false) + { + return $this->Parameter($stmt,$var,$name,true,$maxLen,$type); + + } + + /* + Usage in oracle + $stmt = $db->Prepare('select * from table where id =:myid and group=:group'); + $db->Parameter($stmt,$id,'myid'); + $db->Parameter($stmt,$group,'group',64); + $db->Execute(); + + @param $stmt Statement returned by Prepare() or PrepareSP(). + @param $var PHP variable to bind to + @param $name Name of stored procedure variable name to bind to. + @param [$isOutput] Indicates direction of parameter 0/false=IN 1=OUT 2= IN/OUT. This is ignored in oci8. + @param [$maxLen] Holds an maximum length of the variable. + @param [$type] The data type of $var. Legal values depend on driver. + + */ + function Parameter(&$stmt,&$var,$name,$isOutput=false,$maxLen=4000,$type=false) + { + return false; + } + + /** + Improved method of initiating a transaction. Used together with CompleteTrans(). + Advantages include: + + a. StartTrans/CompleteTrans is nestable, unlike BeginTrans/CommitTrans/RollbackTrans. + Only the outermost block is treated as a transaction.<br> + b. CompleteTrans auto-detects SQL errors, and will rollback on errors, commit otherwise.<br> + c. All BeginTrans/CommitTrans/RollbackTrans inside a StartTrans/CompleteTrans block + are disabled, making it backward compatible. + */ + function StartTrans($errfn = 'ADODB_TransMonitor') + { + if ($this->transOff > 0) { + $this->transOff += 1; + return; + } + + $this->_oldRaiseFn = $this->raiseErrorFn; + $this->raiseErrorFn = $errfn; + $this->_transOK = true; + + if ($this->debug && $this->transCnt > 0) ADOConnection::outp("Bad Transaction: StartTrans called within BeginTrans"); + $this->BeginTrans(); + $this->transOff = 1; + } + + + /** + Used together with StartTrans() to end a transaction. Monitors connection + for sql errors, and will commit or rollback as appropriate. + + @autoComplete if true, monitor sql errors and commit and rollback as appropriate, + and if set to false force rollback even if no SQL error detected. + @returns true on commit, false on rollback. + */ + function CompleteTrans($autoComplete = true) + { + if ($this->transOff > 1) { + $this->transOff -= 1; + return true; + } + $this->raiseErrorFn = $this->_oldRaiseFn; + + $this->transOff = 0; + if ($this->_transOK && $autoComplete) { + if (!$this->CommitTrans()) { + $this->_transOK = false; + if ($this->debug) ADOConnection::outp("Smart Commit failed"); + } else + if ($this->debug) ADOConnection::outp("Smart Commit occurred"); + } else { + $this->_transOK = false; + $this->RollbackTrans(); + if ($this->debug) ADOCOnnection::outp("Smart Rollback occurred"); + } + + return $this->_transOK; + } + + /* + At the end of a StartTrans/CompleteTrans block, perform a rollback. + */ + function FailTrans() + { + if ($this->debug) + if ($this->transOff == 0) { + ADOConnection::outp("FailTrans outside StartTrans/CompleteTrans"); + } else { + ADOConnection::outp("FailTrans was called"); + adodb_backtrace(); + } + $this->_transOK = false; + } + + /** + Check if transaction has failed, only for Smart Transactions. + */ + function HasFailedTrans() + { + if ($this->transOff > 0) return $this->_transOK == false; + return false; + } + + /** + * Execute SQL + * + * @param sql SQL statement to execute, or possibly an array holding prepared statement ($sql[0] will hold sql text) + * @param [inputarr] holds the input data to bind to. Null elements will be set to null. + * @return RecordSet or false + */ + function &Execute($sql,$inputarr=false) + { + if ($this->fnExecute) { + $fn = $this->fnExecute; + $ret =& $fn($this,$sql,$inputarr); + if (isset($ret)) return $ret; + } + if ($inputarr) { + if (!is_array($inputarr)) $inputarr = array($inputarr); + + $element0 = reset($inputarr); + # is_object check because oci8 descriptors can be passed in + $array_2d = is_array($element0) && !is_object(reset($element0)); + + if (!is_array($sql) && !$this->_bindInputArray) { + $sqlarr = explode('?',$sql); + + if (!$array_2d) $inputarr = array($inputarr); + foreach($inputarr as $arr) { + $sql = ''; $i = 0; + foreach($arr as $v) { + $sql .= $sqlarr[$i]; + // from Ron Baldwin <ron.baldwin#sourceprose.com> + // Only quote string types + $typ = gettype($v); + if ($typ == 'string') + $sql .= $this->qstr($v); + else if ($typ == 'double') + $sql .= str_replace(',','.',$v); // locales fix so 1.1 does not get converted to 1,1 + else if ($typ == 'boolean') + $sql .= $v ? $this->true : $this->false; + else if ($v === null) + $sql .= 'NULL'; + else + $sql .= $v; + $i += 1; + } + if (isset($sqlarr[$i])) { + $sql .= $sqlarr[$i]; + if ($i+1 != sizeof($sqlarr)) ADOConnection::outp( "Input Array does not match ?: ".htmlspecialchars($sql)); + } else if ($i != sizeof($sqlarr)) + ADOConnection::outp( "Input array does not match ?: ".htmlspecialchars($sql)); + + $ret =& $this->_Execute($sql); + if (!$ret) return $ret; + } + } else { + if ($array_2d) { + if (is_string($sql)) + $stmt = $this->Prepare($sql); + else + $stmt = $sql; + + foreach($inputarr as $arr) { + $ret =& $this->_Execute($stmt,$arr); + if (!$ret) return $ret; + } + } else { + $ret =& $this->_Execute($sql,$inputarr); + } + } + } else { + $ret =& $this->_Execute($sql,false); + } + + return $ret; + } + + + function &_Execute($sql,$inputarr=false) + { + if ($this->debug) { + global $ADODB_INCLUDED_LIB; + if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php'); + $this->_queryID = _adodb_debug_execute($this, $sql,$inputarr); + } else { + $this->_queryID = @$this->_query($sql,$inputarr); + } + + /************************ + // OK, query executed + *************************/ + + if ($this->_queryID === false) { // error handling if query fails + if ($this->debug == 99) adodb_backtrace(true,5); + $fn = $this->raiseErrorFn; + if ($fn) { + $fn($this->databaseType,'EXECUTE',$this->ErrorNo(),$this->ErrorMsg(),$sql,$inputarr,$this); + } + $false = false; + return $false; + } + + if ($this->_queryID === true) { // return simplified recordset for inserts/updates/deletes with lower overhead + $rs = new ADORecordSet_empty(); + return $rs; + } + + // return real recordset from select statement + $rsclass = $this->rsPrefix.$this->databaseType; + $rs = new $rsclass($this->_queryID,$this->fetchMode); + $rs->connection = &$this; // Pablo suggestion + $rs->Init(); + if (is_array($sql)) $rs->sql = $sql[0]; + else $rs->sql = $sql; + if ($rs->_numOfRows <= 0) { + global $ADODB_COUNTRECS; + if ($ADODB_COUNTRECS) { + if (!$rs->EOF) { + $rs = &$this->_rs2rs($rs,-1,-1,!is_array($sql)); + $rs->_queryID = $this->_queryID; + } else + $rs->_numOfRows = 0; + } + } + return $rs; + } + + function CreateSequence($seqname='adodbseq',$startID=1) + { + if (empty($this->_genSeqSQL)) return false; + return $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID)); + } + + function DropSequence($seqname='adodbseq') + { + if (empty($this->_dropSeqSQL)) return false; + return $this->Execute(sprintf($this->_dropSeqSQL,$seqname)); + } + + /** + * Generates a sequence id and stores it in $this->genID; + * GenID is only available if $this->hasGenID = true; + * + * @param seqname name of sequence to use + * @param startID if sequence does not exist, start at this ID + * @return 0 if not supported, otherwise a sequence id + */ + function GenID($seqname='adodbseq',$startID=1) + { + if (!$this->hasGenID) { + return 0; // formerly returns false pre 1.60 + } + + $getnext = sprintf($this->_genIDSQL,$seqname); + + $holdtransOK = $this->_transOK; + + $save_handler = $this->raiseErrorFn; + $this->raiseErrorFn = ''; + @($rs = $this->Execute($getnext)); + $this->raiseErrorFn = $save_handler; + + if (!$rs) { + $this->_transOK = $holdtransOK; //if the status was ok before reset + $createseq = $this->Execute(sprintf($this->_genSeqSQL,$seqname,$startID)); + $rs = $this->Execute($getnext); + } + if ($rs && !$rs->EOF) $this->genID = reset($rs->fields); + else $this->genID = 0; // false + + if ($rs) $rs->Close(); + + return $this->genID; + } + + /** + * @param $table string name of the table, not needed by all databases (eg. mysql), default '' + * @param $column string name of the column, not needed by all databases (eg. mysql), default '' + * @return the last inserted ID. Not all databases support this. + */ + function Insert_ID($table='',$column='') + { + if ($this->_logsql && $this->lastInsID) return $this->lastInsID; + if ($this->hasInsertID) return $this->_insertid($table,$column); + if ($this->debug) { + ADOConnection::outp( '<p>Insert_ID error</p>'); + adodb_backtrace(); + } + return false; + } + + + /** + * Portable Insert ID. Pablo Roca <pabloroca#mvps.org> + * + * @return the last inserted ID. All databases support this. But aware possible + * problems in multiuser environments. Heavy test this before deploying. + */ + function PO_Insert_ID($table="", $id="") + { + if ($this->hasInsertID){ + return $this->Insert_ID($table,$id); + } else { + return $this->GetOne("SELECT MAX($id) FROM $table"); + } + } + + /** + * @return # rows affected by UPDATE/DELETE + */ + function Affected_Rows() + { + if ($this->hasAffectedRows) { + if ($this->fnExecute === 'adodb_log_sql') { + if ($this->_logsql && $this->_affected !== false) return $this->_affected; + } + $val = $this->_affectedrows(); + return ($val < 0) ? false : $val; + } + + if ($this->debug) ADOConnection::outp( '<p>Affected_Rows error</p>',false); + return false; + } + + + /** + * @return the last error message + */ + function ErrorMsg() + { + if ($this->_errorMsg) return '!! '.strtoupper($this->dataProvider.' '.$this->databaseType).': '.$this->_errorMsg; + else return ''; + } + + + /** + * @return the last error number. Normally 0 means no error. + */ + function ErrorNo() + { + return ($this->_errorMsg) ? -1 : 0; + } + + function MetaError($err=false) + { + include_once(ADODB_DIR."/adodb-error.inc.php"); + if ($err === false) $err = $this->ErrorNo(); + return adodb_error($this->dataProvider,$this->databaseType,$err); + } + + function MetaErrorMsg($errno) + { + include_once(ADODB_DIR."/adodb-error.inc.php"); + return adodb_errormsg($errno); + } + + /** + * @returns an array with the primary key columns in it. + */ + function MetaPrimaryKeys($table, $owner=false) + { + // owner not used in base class - see oci8 + $p = array(); + $objs =& $this->MetaColumns($table); + if ($objs) { + foreach($objs as $v) { + if (!empty($v->primary_key)) + $p[] = $v->name; + } + } + if (sizeof($p)) return $p; + if (function_exists('ADODB_VIEW_PRIMARYKEYS')) + return ADODB_VIEW_PRIMARYKEYS($this->databaseType, $this->database, $table, $owner); + return false; + } + + /** + * @returns assoc array where keys are tables, and values are foreign keys + */ + function MetaForeignKeys($table, $owner=false, $upper=false) + { + return false; + } + /** + * Choose a database to connect to. Many databases do not support this. + * + * @param dbName is the name of the database to select + * @return true or false + */ + function SelectDB($dbName) + {return false;} + + + /** + * Will select, getting rows from $offset (1-based), for $nrows. + * This simulates the MySQL "select * from table limit $offset,$nrows" , and + * the PostgreSQL "select * from table limit $nrows offset $offset". Note that + * MySQL and PostgreSQL parameter ordering is the opposite of the other. + * eg. + * SelectLimit('select * from table',3); will return rows 1 to 3 (1-based) + * SelectLimit('select * from table',3,2); will return rows 3 to 5 (1-based) + * + * Uses SELECT TOP for Microsoft databases (when $this->hasTop is set) + * BUG: Currently SelectLimit fails with $sql with LIMIT or TOP clause already set + * + * @param sql + * @param [offset] is the row to start calculations from (1-based) + * @param [nrows] is the number of rows to get + * @param [inputarr] array of bind variables + * @param [secs2cache] is a private parameter only used by jlim + * @return the recordset ($rs->databaseType == 'array') + */ + function &SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) + { + if ($this->hasTop && $nrows > 0) { + // suggested by Reinhard Balling. Access requires top after distinct + // Informix requires first before distinct - F Riosa + $ismssql = (strpos($this->databaseType,'mssql') !== false); + if ($ismssql) $isaccess = false; + else $isaccess = (strpos($this->databaseType,'access') !== false); + + if ($offset <= 0) { + + // access includes ties in result + if ($isaccess) { + $sql = preg_replace( + '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.((integer)$nrows).' ',$sql); + + if ($secs2cache>0) { + $ret =& $this->CacheExecute($secs2cache, $sql,$inputarr); + } else { + $ret =& $this->Execute($sql,$inputarr); + } + return $ret; // PHP5 fix + } else if ($ismssql){ + $sql = preg_replace( + '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.((integer)$nrows).' ',$sql); + } else { + $sql = preg_replace( + '/(^\s*select\s)/i','\\1 '.$this->hasTop.' '.((integer)$nrows).' ',$sql); + } + } else { + $nn = $nrows + $offset; + if ($isaccess || $ismssql) { + $sql = preg_replace( + '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop.' '.$nn.' ',$sql); + } else { + $sql = preg_replace( + '/(^\s*select\s)/i','\\1 '.$this->hasTop.' '.$nn.' ',$sql); + } + } + } + + // if $offset>0, we want to skip rows, and $ADODB_COUNTRECS is set, we buffer rows + // 0 to offset-1 which will be discarded anyway. So we disable $ADODB_COUNTRECS. + global $ADODB_COUNTRECS; + + $savec = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; + + if ($offset>0){ + if ($secs2cache>0) $rs = &$this->CacheExecute($secs2cache,$sql,$inputarr); + else $rs = &$this->Execute($sql,$inputarr); + } else { + if ($secs2cache>0) $rs = &$this->CacheExecute($secs2cache,$sql,$inputarr); + else $rs = &$this->Execute($sql,$inputarr); + } + $ADODB_COUNTRECS = $savec; + if ($rs && !$rs->EOF) { + $rs =& $this->_rs2rs($rs,$nrows,$offset); + } + //print_r($rs); + return $rs; + } + + /** + * Create serializable recordset. Breaks rs link to connection. + * + * @param rs the recordset to serialize + */ + function &SerializableRS(&$rs) + { + $rs2 =& $this->_rs2rs($rs); + $ignore = false; + $rs2->connection =& $ignore; + + return $rs2; + } + + /** + * Convert database recordset to an array recordset + * input recordset's cursor should be at beginning, and + * old $rs will be closed. + * + * @param rs the recordset to copy + * @param [nrows] number of rows to retrieve (optional) + * @param [offset] offset by number of rows (optional) + * @return the new recordset + */ + function &_rs2rs(&$rs,$nrows=-1,$offset=-1,$close=true) + { + if (! $rs) { + $false = false; + return $false; + } + $dbtype = $rs->databaseType; + if (!$dbtype) { + $rs = &$rs; // required to prevent crashing in 4.2.1, but does not happen in 4.3.1 -- why ? + return $rs; + } + if (($dbtype == 'array' || $dbtype == 'csv') && $nrows == -1 && $offset == -1) { + $rs->MoveFirst(); + $rs = &$rs; // required to prevent crashing in 4.2.1, but does not happen in 4.3.1-- why ? + return $rs; + } + $flds = array(); + for ($i=0, $max=$rs->FieldCount(); $i < $max; $i++) { + $flds[] = $rs->FetchField($i); + } + + $arr =& $rs->GetArrayLimit($nrows,$offset); + //print_r($arr); + if ($close) $rs->Close(); + + $arrayClass = $this->arrayClass; + + $rs2 = new $arrayClass(); + $rs2->connection = &$this; + $rs2->sql = $rs->sql; + $rs2->dataProvider = $this->dataProvider; + $rs2->InitArrayFields($arr,$flds); + $rs2->fetchMode = isset($rs->adodbFetchMode) ? $rs->adodbFetchMode : $rs->fetchMode; + return $rs2; + } + + /* + * Return all rows. Compat with PEAR DB + */ + function &GetAll($sql, $inputarr=false) + { + $arr =& $this->GetArray($sql,$inputarr); + return $arr; + } + + function &GetAssoc($sql, $inputarr=false,$force_array = false, $first2cols = false) + { + $rs =& $this->Execute($sql, $inputarr); + if (!$rs) { + $false = false; + return $false; + } + $arr =& $rs->GetAssoc($force_array,$first2cols); + return $arr; + } + + function &CacheGetAssoc($secs2cache, $sql=false, $inputarr=false,$force_array = false, $first2cols = false) + { + if (!is_numeric($secs2cache)) { + $first2cols = $force_array; + $force_array = $inputarr; + } + $rs =& $this->CacheExecute($secs2cache, $sql, $inputarr); + if (!$rs) { + $false = false; + return $false; + } + $arr =& $rs->GetAssoc($force_array,$first2cols); + return $arr; + } + + /** + * Return first element of first row of sql statement. Recordset is disposed + * for you. + * + * @param sql SQL statement + * @param [inputarr] input bind array + */ + function GetOne($sql,$inputarr=false) + { + global $ADODB_COUNTRECS; + $crecs = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; + + $ret = false; + $rs = &$this->Execute($sql,$inputarr); + if ($rs) { + if (!$rs->EOF) $ret = reset($rs->fields); + $rs->Close(); + } + $ADODB_COUNTRECS = $crecs; + return $ret; + } + + function CacheGetOne($secs2cache,$sql=false,$inputarr=false) + { + $ret = false; + $rs = &$this->CacheExecute($secs2cache,$sql,$inputarr); + if ($rs) { + if (!$rs->EOF) $ret = reset($rs->fields); + $rs->Close(); + } + + return $ret; + } + + function GetCol($sql, $inputarr = false, $trim = false) + { + $rv = false; + $rs = &$this->Execute($sql, $inputarr); + if ($rs) { + $rv = array(); + if ($trim) { + while (!$rs->EOF) { + $rv[] = trim(reset($rs->fields)); + $rs->MoveNext(); + } + } else { + while (!$rs->EOF) { + $rv[] = reset($rs->fields); + $rs->MoveNext(); + } + } + $rs->Close(); + } + return $rv; + } + + function CacheGetCol($secs, $sql = false, $inputarr = false,$trim=false) + { + $rv = false; + $rs = &$this->CacheExecute($secs, $sql, $inputarr); + if ($rs) { + if ($trim) { + while (!$rs->EOF) { + $rv[] = trim(reset($rs->fields)); + $rs->MoveNext(); + } + } else { + while (!$rs->EOF) { + $rv[] = reset($rs->fields); + $rs->MoveNext(); + } + } + $rs->Close(); + } + return $rv; + } + + /* + Calculate the offset of a date for a particular database and generate + appropriate SQL. Useful for calculating future/past dates and storing + in a database. + + If dayFraction=1.5 means 1.5 days from now, 1.0/24 for 1 hour. + */ + function OffsetDate($dayFraction,$date=false) + { + if (!$date) $date = $this->sysDate; + return '('.$date.'+'.$dayFraction.')'; + } + + + /** + * + * @param sql SQL statement + * @param [inputarr] input bind array + */ + function &GetArray($sql,$inputarr=false) + { + global $ADODB_COUNTRECS; + + $savec = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; + $rs =& $this->Execute($sql,$inputarr); + $ADODB_COUNTRECS = $savec; + if (!$rs) + if (defined('ADODB_PEAR')) { + $cls = ADODB_PEAR_Error(); + return $cls; + } else { + $false = false; + return $false; + } + $arr =& $rs->GetArray(); + $rs->Close(); + return $arr; + } + + function &CacheGetAll($secs2cache,$sql=false,$inputarr=false) + { + return $this->CacheGetArray($secs2cache,$sql,$inputarr); + } + + function &CacheGetArray($secs2cache,$sql=false,$inputarr=false) + { + global $ADODB_COUNTRECS; + + $savec = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; + $rs =& $this->CacheExecute($secs2cache,$sql,$inputarr); + $ADODB_COUNTRECS = $savec; + + if (!$rs) + if (defined('ADODB_PEAR')) { + $cls = ADODB_PEAR_Error(); + return $cls; + } else { + $false = false; + return $false; + } + $arr =& $rs->GetArray(); + $rs->Close(); + return $arr; + } + + + + /** + * Return one row of sql statement. Recordset is disposed for you. + * + * @param sql SQL statement + * @param [inputarr] input bind array + */ + function &GetRow($sql,$inputarr=false) + { + global $ADODB_COUNTRECS; + $crecs = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = false; + + $rs =& $this->Execute($sql,$inputarr); + + $ADODB_COUNTRECS = $crecs; + if ($rs) { + if (!$rs->EOF) $arr = $rs->fields; + else $arr = array(); + $rs->Close(); + return $arr; + } + + $false = false; + return $false; + } + + function &CacheGetRow($secs2cache,$sql=false,$inputarr=false) + { + $rs =& $this->CacheExecute($secs2cache,$sql,$inputarr); + if ($rs) { + $arr = false; + if (!$rs->EOF) $arr = $rs->fields; + $rs->Close(); + return $arr; + } + $false = false; + return $false; + } + + /** + * Insert or replace a single record. Note: this is not the same as MySQL's replace. + * ADOdb's Replace() uses update-insert semantics, not insert-delete-duplicates of MySQL. + * Also note that no table locking is done currently, so it is possible that the + * record be inserted twice by two programs... + * + * $this->Replace('products', array('prodname' =>"'Nails'","price" => 3.99), 'prodname'); + * + * $table table name + * $fieldArray associative array of data (you must quote strings yourself). + * $keyCol the primary key field name or if compound key, array of field names + * autoQuote set to true to use a hueristic to quote strings. Works with nulls and numbers + * but does not work with dates nor SQL functions. + * has_autoinc the primary key is an auto-inc field, so skip in insert. + * + * Currently blob replace not supported + * + * returns 0 = fail, 1 = update, 2 = insert + */ + + function Replace($table, $fieldArray, $keyCol, $autoQuote=false, $has_autoinc=false) + { + global $ADODB_INCLUDED_LIB; + if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php'); + + return _adodb_replace($this, $table, $fieldArray, $keyCol, $autoQuote, $has_autoinc); + } + + + /** + * Will select, getting rows from $offset (1-based), for $nrows. + * This simulates the MySQL "select * from table limit $offset,$nrows" , and + * the PostgreSQL "select * from table limit $nrows offset $offset". Note that + * MySQL and PostgreSQL parameter ordering is the opposite of the other. + * eg. + * CacheSelectLimit(15,'select * from table',3); will return rows 1 to 3 (1-based) + * CacheSelectLimit(15,'select * from table',3,2); will return rows 3 to 5 (1-based) + * + * BUG: Currently CacheSelectLimit fails with $sql with LIMIT or TOP clause already set + * + * @param [secs2cache] seconds to cache data, set to 0 to force query. This is optional + * @param sql + * @param [offset] is the row to start calculations from (1-based) + * @param [nrows] is the number of rows to get + * @param [inputarr] array of bind variables + * @return the recordset ($rs->databaseType == 'array') + */ + function &CacheSelectLimit($secs2cache,$sql,$nrows=-1,$offset=-1,$inputarr=false) + { + if (!is_numeric($secs2cache)) { + if ($sql === false) $sql = -1; + if ($offset == -1) $offset = false; + // sql, nrows, offset,inputarr + $rs =& $this->SelectLimit($secs2cache,$sql,$nrows,$offset,$this->cacheSecs); + } else { + if ($sql === false) ADOConnection::outp( "Warning: \$sql missing from CacheSelectLimit()"); + $rs =& $this->SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); + } + return $rs; + } + + + /** + * Flush cached recordsets that match a particular $sql statement. + * If $sql == false, then we purge all files in the cache. + */ + + /** + * Flush cached recordsets that match a particular $sql statement. + * If $sql == false, then we purge all files in the cache. + */ + function CacheFlush($sql=false,$inputarr=false) + { + global $ADODB_CACHE_DIR; + + if (strlen($ADODB_CACHE_DIR) > 1 && !$sql) { + /*if (strncmp(PHP_OS,'WIN',3) === 0) + $dir = str_replace('/', '\\', $ADODB_CACHE_DIR); + else */ + $dir = $ADODB_CACHE_DIR; + + if ($this->debug) { + ADOConnection::outp( "CacheFlush: $dir<br><pre>\n", $this->_dirFlush($dir),"</pre>"); + } else { + $this->_dirFlush($dir); + } + return; + } + + global $ADODB_INCLUDED_CSV; + if (empty($ADODB_INCLUDED_CSV)) include_once(ADODB_DIR.'/adodb-csvlib.inc.php'); + + $f = $this->_gencachename($sql.serialize($inputarr),false); + adodb_write_file($f,''); // is adodb_write_file needed? + if (!@unlink($f)) { + if ($this->debug) ADOConnection::outp( "CacheFlush: failed for $f"); + } + } + + /** + * Private function to erase all of the files and subdirectories in a directory. + * + * Just specify the directory, and tell it if you want to delete the directory or just clear it out. + * Note: $kill_top_level is used internally in the function to flush subdirectories. + */ + function _dirFlush($dir, $kill_top_level = false) { + if(!$dh = @opendir($dir)) return; + + while (($obj = readdir($dh))) { + if($obj=='.' || $obj=='..') + continue; + + if (!@unlink($dir.'/'.$obj)) + $this->_dirFlush($dir.'/'.$obj, true); + } + if ($kill_top_level === true) + @rmdir($dir); + return true; + } + + + function xCacheFlush($sql=false,$inputarr=false) + { + global $ADODB_CACHE_DIR; + + if (strlen($ADODB_CACHE_DIR) > 1 && !$sql) { + if (strncmp(PHP_OS,'WIN',3) === 0) { + $cmd = 'del /s '.str_replace('/','\\',$ADODB_CACHE_DIR).'\adodb_*.cache'; + } else { + //$cmd = 'find "'.$ADODB_CACHE_DIR.'" -type f -maxdepth 1 -print0 | xargs -0 rm -f'; + $cmd = 'rm -rf '.$ADODB_CACHE_DIR.'/[0-9a-f][0-9a-f]/'; + // old version 'rm -f `find '.$ADODB_CACHE_DIR.' -name adodb_*.cache`'; + } + if ($this->debug) { + ADOConnection::outp( "CacheFlush: $cmd<br><pre>\n", system($cmd),"</pre>"); + } else { + exec($cmd); + } + return; + } + + global $ADODB_INCLUDED_CSV; + if (empty($ADODB_INCLUDED_CSV)) include_once(ADODB_DIR.'/adodb-csvlib.inc.php'); + + $f = $this->_gencachename($sql.serialize($inputarr),false); + adodb_write_file($f,''); // is adodb_write_file needed? + if (!@unlink($f)) { + if ($this->debug) ADOConnection::outp( "CacheFlush: failed for $f"); + } + } + + /** + * Private function to generate filename for caching. + * Filename is generated based on: + * + * - sql statement + * - database type (oci8, ibase, ifx, etc) + * - database name + * - userid + * - setFetchMode (adodb 4.23) + * + * When not in safe mode, we create 256 sub-directories in the cache directory ($ADODB_CACHE_DIR). + * Assuming that we can have 50,000 files per directory with good performance, + * then we can scale to 12.8 million unique cached recordsets. Wow! + */ + function _gencachename($sql,$createdir) + { + global $ADODB_CACHE_DIR; + static $notSafeMode; + + if ($this->fetchMode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } else { + $mode = $this->fetchMode; + } + $m = md5($sql.$this->databaseType.$this->database.$this->user.$mode); + + if (!isset($notSafeMode)) $notSafeMode = !ini_get('safe_mode'); + $dir = ($notSafeMode) ? $ADODB_CACHE_DIR.'/'.substr($m,0,2) : $ADODB_CACHE_DIR; + + if ($createdir && $notSafeMode && !file_exists($dir)) { + $oldu = umask(0); + if (!mkdir($dir,0771)) + if ($this->debug) ADOConnection::outp( "Unable to mkdir $dir for $sql"); + umask($oldu); + } + return $dir.'/adodb_'.$m.'.cache'; + } + + + /** + * Execute SQL, caching recordsets. + * + * @param [secs2cache] seconds to cache data, set to 0 to force query. + * This is an optional parameter. + * @param sql SQL statement to execute + * @param [inputarr] holds the input data to bind to + * @return RecordSet or false + */ + function &CacheExecute($secs2cache,$sql=false,$inputarr=false) + { + + + if (!is_numeric($secs2cache)) { + $inputarr = $sql; + $sql = $secs2cache; + $secs2cache = $this->cacheSecs; + } + + if (is_array($sql)) { + $sqlparam = $sql; + $sql = $sql[0]; + } else + $sqlparam = $sql; + + global $ADODB_INCLUDED_CSV; + if (empty($ADODB_INCLUDED_CSV)) include_once(ADODB_DIR.'/adodb-csvlib.inc.php'); + + $md5file = $this->_gencachename($sql.serialize($inputarr),true); + $err = ''; + + if ($secs2cache > 0){ + $rs = &csv2rs($md5file,$err,$secs2cache,$this->arrayClass); + $this->numCacheHits += 1; + } else { + $err='Timeout 1'; + $rs = false; + $this->numCacheMisses += 1; + } + if (!$rs) { + // no cached rs found + if ($this->debug) { + if (get_magic_quotes_runtime()) { + ADOConnection::outp("Please disable magic_quotes_runtime - it corrupts cache files :("); + } + if ($this->debug !== -1) ADOConnection::outp( " $md5file cache failure: $err (see sql below)"); + } + + $rs = &$this->Execute($sqlparam,$inputarr); + + if ($rs) { + $eof = $rs->EOF; + $rs = &$this->_rs2rs($rs); // read entire recordset into memory immediately + $txt = _rs2serialize($rs,false,$sql); // serialize + + if (!adodb_write_file($md5file,$txt,$this->debug)) { + if ($fn = $this->raiseErrorFn) { + $fn($this->databaseType,'CacheExecute',-32000,"Cache write error",$md5file,$sql,$this); + } + if ($this->debug) ADOConnection::outp( " Cache write error"); + } + if ($rs->EOF && !$eof) { + $rs->MoveFirst(); + //$rs = &csv2rs($md5file,$err); + $rs->connection = &$this; // Pablo suggestion + } + + } else + @unlink($md5file); + } else { + $this->_errorMsg = ''; + $this->_errorCode = 0; + + if ($this->fnCacheExecute) { + $fn = $this->fnCacheExecute; + $fn($this, $secs2cache, $sql, $inputarr); + } + // ok, set cached object found + $rs->connection = &$this; // Pablo suggestion + if ($this->debug){ + + $inBrowser = isset($_SERVER['HTTP_USER_AGENT']); + $ttl = $rs->timeCreated + $secs2cache - time(); + $s = is_array($sql) ? $sql[0] : $sql; + if ($inBrowser) $s = '<i>'.htmlspecialchars($s).'</i>'; + + ADOConnection::outp( " $md5file reloaded, ttl=$ttl [ $s ]"); + } + } + return $rs; + } + + + /* + Similar to PEAR DB's autoExecute(), except that + $mode can be 'INSERT' or 'UPDATE' or DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE + If $mode == 'UPDATE', then $where is compulsory as a safety measure. + + $forceUpdate means that even if the data has not changed, perform update. + */ + function& AutoExecute($table, $fields_values, $mode = 'INSERT', $where = FALSE, $forceUpdate=true, $magicq=false) + { + $sql = 'SELECT * FROM '.$table; + if ($where!==FALSE) $sql .= ' WHERE '.$where; + else if ($mode == 'UPDATE' || $mode == 2 /* DB_AUTOQUERY_UPDATE */) { + ADOConnection::outp('AutoExecute: Illegal mode=UPDATE with empty WHERE clause'); + return false; + } + + $rs =& $this->SelectLimit($sql,1); + if (!$rs) return false; // table does not exist + $rs->tableName = $table; + + switch((string) $mode) { + case 'UPDATE': + case '2': + $sql = $this->GetUpdateSQL($rs, $fields_values, $forceUpdate, $magicq); + break; + case 'INSERT': + case '1': + $sql = $this->GetInsertSQL($rs, $fields_values, $magicq); + break; + default: + ADOConnection::outp("AutoExecute: Unknown mode=$mode"); + return false; + } + $ret = false; + if ($sql) $ret = $this->Execute($sql); + if ($ret) $ret = true; + return $ret; + } + + + /** + * Generates an Update Query based on an existing recordset. + * $arrFields is an associative array of fields with the value + * that should be assigned. + * + * Note: This function should only be used on a recordset + * that is run against a single table and sql should only + * be a simple select stmt with no groupby/orderby/limit + * + * "Jonathan Younger" <jyounger@unilab.com> + */ + function GetUpdateSQL(&$rs, $arrFields,$forceUpdate=false,$magicq=false,$force=null) + { + global $ADODB_INCLUDED_LIB; + + //********************************************************// + //This is here to maintain compatibility + //with older adodb versions. Sets force type to force nulls if $forcenulls is set. + if (!isset($force)) { + global $ADODB_FORCE_TYPE; + $force = $ADODB_FORCE_TYPE; + } + //********************************************************// + + if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php'); + return _adodb_getupdatesql($this,$rs,$arrFields,$forceUpdate,$magicq,$force); + } + + /** + * Generates an Insert Query based on an existing recordset. + * $arrFields is an associative array of fields with the value + * that should be assigned. + * + * Note: This function should only be used on a recordset + * that is run against a single table. + */ + function GetInsertSQL(&$rs, $arrFields,$magicq=false,$force=null) + { + global $ADODB_INCLUDED_LIB; + if (!isset($force)) { + global $ADODB_FORCE_TYPE; + $force = $ADODB_FORCE_TYPE; + + } + if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php'); + return _adodb_getinsertsql($this,$rs,$arrFields,$magicq,$force); + } + + + /** + * Update a blob column, given a where clause. There are more sophisticated + * blob handling functions that we could have implemented, but all require + * a very complex API. Instead we have chosen something that is extremely + * simple to understand and use. + * + * Note: $blobtype supports 'BLOB' and 'CLOB', default is BLOB of course. + * + * Usage to update a $blobvalue which has a primary key blob_id=1 into a + * field blobtable.blobcolumn: + * + * UpdateBlob('blobtable', 'blobcolumn', $blobvalue, 'blob_id=1'); + * + * Insert example: + * + * $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)'); + * $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1'); + */ + + function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') + { + return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false; + } + + /** + * Usage: + * UpdateBlob('TABLE', 'COLUMN', '/path/to/file', 'ID=1'); + * + * $blobtype supports 'BLOB' and 'CLOB' + * + * $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)'); + * $conn->UpdateBlob('blobtable','blobcol',$blobpath,'id=1'); + */ + function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB') + { + $fd = fopen($path,'rb'); + if ($fd === false) return false; + $val = fread($fd,filesize($path)); + fclose($fd); + return $this->UpdateBlob($table,$column,$val,$where,$blobtype); + } + + function BlobDecode($blob) + { + return $blob; + } + + function BlobEncode($blob) + { + return $blob; + } + + function SetCharSet($charset) + { + return false; + } + + function IfNull( $field, $ifNull ) + { + return " CASE WHEN $field is null THEN $ifNull ELSE $field END "; + } + + function LogSQL($enable=true) + { + include_once(ADODB_DIR.'/adodb-perf.inc.php'); + + if ($enable) $this->fnExecute = 'adodb_log_sql'; + else $this->fnExecute = false; + + $old = $this->_logsql; + $this->_logsql = $enable; + if ($enable && !$old) $this->_affected = false; + return $old; + } + + function GetCharSet() + { + return false; + } + + /** + * Usage: + * UpdateClob('TABLE', 'COLUMN', $var, 'ID=1', 'CLOB'); + * + * $conn->Execute('INSERT INTO clobtable (id, clobcol) VALUES (1, null)'); + * $conn->UpdateClob('clobtable','clobcol',$clob,'id=1'); + */ + function UpdateClob($table,$column,$val,$where) + { + return $this->UpdateBlob($table,$column,$val,$where,'CLOB'); + } + + // not the fastest implementation - quick and dirty - jlim + // for best performance, use the actual $rs->MetaType(). + function MetaType($t,$len=-1,$fieldobj=false) + { + + if (empty($this->_metars)) { + $rsclass = $this->rsPrefix.$this->databaseType; + $this->_metars = new $rsclass(false,$this->fetchMode); + $this->_metars->connection =& $this; + } + return $this->_metars->MetaType($t,$len,$fieldobj); + } + + + /** + * Change the SQL connection locale to a specified locale. + * This is used to get the date formats written depending on the client locale. + */ + function SetDateLocale($locale = 'En') + { + $this->locale = $locale; + switch (strtoupper($locale)) + { + case 'EN': + $this->fmtDate="'Y-m-d'"; + $this->fmtTimeStamp = "'Y-m-d H:i:s'"; + break; + + case 'US': + $this->fmtDate = "'m-d-Y'"; + $this->fmtTimeStamp = "'m-d-Y H:i:s'"; + break; + + case 'NL': + case 'FR': + case 'RO': + case 'IT': + $this->fmtDate="'d-m-Y'"; + $this->fmtTimeStamp = "'d-m-Y H:i:s'"; + break; + + case 'GE': + $this->fmtDate="'d.m.Y'"; + $this->fmtTimeStamp = "'d.m.Y H:i:s'"; + break; + + default: + $this->fmtDate="'Y-m-d'"; + $this->fmtTimeStamp = "'Y-m-d H:i:s'"; + break; + } + } + + + /** + * Close Connection + */ + function Close() + { + $rez = $this->_close(); + $this->_connectionID = false; + return $rez; + } + + /** + * Begin a Transaction. Must be followed by CommitTrans() or RollbackTrans(). + * + * @return true if succeeded or false if database does not support transactions + */ + function BeginTrans() {return false;} + + + /** + * If database does not support transactions, always return true as data always commited + * + * @param $ok set to false to rollback transaction, true to commit + * + * @return true/false. + */ + function CommitTrans($ok=true) + { return true;} + + + /** + * If database does not support transactions, rollbacks always fail, so return false + * + * @return true/false. + */ + function RollbackTrans() + { return false;} + + + /** + * return the databases that the driver can connect to. + * Some databases will return an empty array. + * + * @return an array of database names. + */ + function MetaDatabases() + { + global $ADODB_FETCH_MODE; + + if ($this->metaDatabasesSQL) { + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + + $arr = $this->GetCol($this->metaDatabasesSQL); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + return $arr; + } + + return false; + } + + /** + * @param ttype can either be 'VIEW' or 'TABLE' or false. + * If false, both views and tables are returned. + * "VIEW" returns only views + * "TABLE" returns only tables + * @param showSchema returns the schema/user with the table name, eg. USER.TABLE + * @param mask is the input mask - only supported by oci8 and postgresql + * + * @return array of tables for current database. + */ + function &MetaTables($ttype=false,$showSchema=false,$mask=false) + { + global $ADODB_FETCH_MODE; + + + $false = false; + if ($mask) { + return $false; + } + if ($this->metaTablesSQL) { + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + + $rs = $this->Execute($this->metaTablesSQL); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if ($rs === false) return $false; + $arr =& $rs->GetArray(); + $arr2 = array(); + + if ($hast = ($ttype && isset($arr[0][1]))) { + $showt = strncmp($ttype,'T',1); + } + + for ($i=0; $i < sizeof($arr); $i++) { + if ($hast) { + if ($showt == 0) { + if (strncmp($arr[$i][1],'T',1) == 0) $arr2[] = trim($arr[$i][0]); + } else { + if (strncmp($arr[$i][1],'V',1) == 0) $arr2[] = trim($arr[$i][0]); + } + } else + $arr2[] = trim($arr[$i][0]); + } + $rs->Close(); + return $arr2; + } + return $false; + } + + + function _findschema(&$table,&$schema) + { + if (!$schema && ($at = strpos($table,'.')) !== false) { + $schema = substr($table,0,$at); + $table = substr($table,$at+1); + } + } + + /** + * List columns in a database as an array of ADOFieldObjects. + * See top of file for definition of object. + * + * @param $table table name to query + * @param $normalize makes table name case-insensitive (required by some databases) + * @schema is optional database schema to use - not supported by all databases. + * + * @return array of ADOFieldObjects for current table. + */ + function &MetaColumns($table,$normalize=true) + { + global $ADODB_FETCH_MODE; + + $false = false; + + if (!empty($this->metaColumnsSQL)) { + + $schema = false; + $this->_findschema($table,$schema); + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + $rs = $this->Execute(sprintf($this->metaColumnsSQL,($normalize)?strtoupper($table):$table)); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + if ($rs === false || $rs->EOF) return $false; + + $retarr = array(); + while (!$rs->EOF) { //print_r($rs->fields); + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[0]; + $fld->type = $rs->fields[1]; + if (isset($rs->fields[3]) && $rs->fields[3]) { + if ($rs->fields[3]>0) $fld->max_length = $rs->fields[3]; + $fld->scale = $rs->fields[4]; + if ($fld->scale>0) $fld->max_length += 1; + } else + $fld->max_length = $rs->fields[2]; + + if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld; + else $retarr[strtoupper($fld->name)] = $fld; + $rs->MoveNext(); + } + $rs->Close(); + return $retarr; + } + return $false; + } + + /** + * List indexes on a table as an array. + * @param table table name to query + * @param primary true to only show primary keys. Not actually used for most databases + * + * @return array of indexes on current table. Each element represents an index, and is itself an associative array. + + Array ( + [name_of_index] => Array + ( + [unique] => true or false + [columns] => Array + ( + [0] => firstname + [1] => lastname + ) + ) + */ + function &MetaIndexes($table, $primary = false, $owner = false) + { + $false = false; + return $false; + } + + /** + * List columns names in a table as an array. + * @param table table name to query + * + * @return array of column names for current table. + */ + function &MetaColumnNames($table, $numIndexes=false) + { + $objarr =& $this->MetaColumns($table); + if (!is_array($objarr)) { + $false = false; + return $false; + } + $arr = array(); + if ($numIndexes) { + $i = 0; + foreach($objarr as $v) $arr[$i++] = $v->name; + } else + foreach($objarr as $v) $arr[strtoupper($v->name)] = $v->name; + + return $arr; + } + + /** + * Different SQL databases used different methods to combine strings together. + * This function provides a wrapper. + * + * param s variable number of string parameters + * + * Usage: $db->Concat($str1,$str2); + * + * @return concatenated string + */ + function Concat() + { + $arr = func_get_args(); + return implode($this->concat_operator, $arr); + } + + + /** + * Converts a date "d" to a string that the database can understand. + * + * @param d a date in Unix date time format. + * + * @return date string in database date format + */ + function DBDate($d) + { + if (empty($d) && $d !== 0) return 'null'; + + if (is_string($d) && !is_numeric($d)) { + if ($d === 'null' || strncmp($d,"'",1) === 0) return $d; + if ($this->isoDates) return "'$d'"; + $d = ADOConnection::UnixDate($d); + } + + return adodb_date($this->fmtDate,$d); + } + + + /** + * Converts a timestamp "ts" to a string that the database can understand. + * + * @param ts a timestamp in Unix date time format. + * + * @return timestamp string in database timestamp format + */ + function DBTimeStamp($ts) + { + if (empty($ts) && $ts !== 0) return 'null'; + + # strlen(14) allows YYYYMMDDHHMMSS format + if (!is_string($ts) || (is_numeric($ts) && strlen($ts)<14)) + return adodb_date($this->fmtTimeStamp,$ts); + + if ($ts === 'null') return $ts; + if ($this->isoDates && strlen($ts) !== 14) return "'$ts'"; + + $ts = ADOConnection::UnixTimeStamp($ts); + return adodb_date($this->fmtTimeStamp,$ts); + } + + /** + * Also in ADORecordSet. + * @param $v is a date string in YYYY-MM-DD format + * + * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format + */ + function UnixDate($v) + { + if (is_object($v)) { + // odbtp support + //( [year] => 2004 [month] => 9 [day] => 4 [hour] => 12 [minute] => 44 [second] => 8 [fraction] => 0 ) + return adodb_mktime($v->hour,$v->minute,$v->second,$v->month,$v->day, $v->year); + } + + if (is_numeric($v) && strlen($v) !== 8) return $v; + if (!preg_match( "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})|", + ($v), $rr)) return false; + + if ($rr[1] <= TIMESTAMP_FIRST_YEAR) return 0; + // h-m-s-MM-DD-YY + return @adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]); + } + + + /** + * Also in ADORecordSet. + * @param $v is a timestamp string in YYYY-MM-DD HH-NN-SS format + * + * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format + */ + function UnixTimeStamp($v) + { + if (is_object($v)) { + // odbtp support + //( [year] => 2004 [month] => 9 [day] => 4 [hour] => 12 [minute] => 44 [second] => 8 [fraction] => 0 ) + return adodb_mktime($v->hour,$v->minute,$v->second,$v->month,$v->day, $v->year); + } + + if (!preg_match( + "|^([0-9]{4})[-/\.]?([0-9]{1,2})[-/\.]?([0-9]{1,2})[ ,-]*(([0-9]{1,2}):?([0-9]{1,2}):?([0-9\.]{1,4}))?|", + ($v), $rr)) return false; + + if ($rr[1] <= TIMESTAMP_FIRST_YEAR && $rr[2]<= 1) return 0; + + // h-m-s-MM-DD-YY + if (!isset($rr[5])) return adodb_mktime(0,0,0,$rr[2],$rr[3],$rr[1]); + return @adodb_mktime($rr[5],$rr[6],$rr[7],$rr[2],$rr[3],$rr[1]); + } + + /** + * Also in ADORecordSet. + * + * Format database date based on user defined format. + * + * @param v is the character date in YYYY-MM-DD format, returned by database + * @param fmt is the format to apply to it, using date() + * + * @return a date formated as user desires + */ + + function UserDate($v,$fmt='Y-m-d',$gmt=false) + { + $tt = $this->UnixDate($v); + + // $tt == -1 if pre TIMESTAMP_FIRST_YEAR + if (($tt === false || $tt == -1) && $v != false) return $v; + else if ($tt == 0) return $this->emptyDate; + else if ($tt == -1) { // pre-TIMESTAMP_FIRST_YEAR + } + + return ($gmt) ? adodb_gmdate($fmt,$tt) : adodb_date($fmt,$tt); + + } + + /** + * + * @param v is the character timestamp in YYYY-MM-DD hh:mm:ss format + * @param fmt is the format to apply to it, using date() + * + * @return a timestamp formated as user desires + */ + function UserTimeStamp($v,$fmt='Y-m-d H:i:s',$gmt=false) + { + if (!isset($v)) return $this->emptyTimeStamp; + # strlen(14) allows YYYYMMDDHHMMSS format + if (is_numeric($v) && strlen($v)<14) return ($gmt) ? adodb_gmdate($fmt,$v) : adodb_date($fmt,$v); + $tt = $this->UnixTimeStamp($v); + // $tt == -1 if pre TIMESTAMP_FIRST_YEAR + if (($tt === false || $tt == -1) && $v != false) return $v; + if ($tt == 0) return $this->emptyTimeStamp; + return ($gmt) ? adodb_gmdate($fmt,$tt) : adodb_date($fmt,$tt); + } + + function escape($s,$magic_quotes=false) + { + return $this->addq($s,$magic_quotes); + } + + /** + * Quotes a string, without prefixing nor appending quotes. + */ + function addq($s,$magic_quotes=false) + { + if (!$magic_quotes) { + + if ($this->replaceQuote[0] == '\\'){ + // only since php 4.0.5 + $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s); + //$s = str_replace("\0","\\\0", str_replace('\\','\\\\',$s)); + } + return str_replace("'",$this->replaceQuote,$s); + } + + // undo magic quotes for " + $s = str_replace('\\"','"',$s); + + if ($this->replaceQuote == "\\'") // ' already quoted, no need to change anything + return $s; + else {// change \' to '' for sybase/mssql + $s = str_replace('\\\\','\\',$s); + return str_replace("\\'",$this->replaceQuote,$s); + } + } + + /** + * Correctly quotes a string so that all strings are escaped. We prefix and append + * to the string single-quotes. + * An example is $db->qstr("Don't bother",magic_quotes_runtime()); + * + * @param s the string to quote + * @param [magic_quotes] if $s is GET/POST var, set to get_magic_quotes_gpc(). + * This undoes the stupidity of magic quotes for GPC. + * + * @return quoted string to be sent back to database + */ + function qstr($s,$magic_quotes=false) + { + if (!$magic_quotes) { + + if ($this->replaceQuote[0] == '\\'){ + // only since php 4.0.5 + $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s); + //$s = str_replace("\0","\\\0", str_replace('\\','\\\\',$s)); + } + return "'".str_replace("'",$this->replaceQuote,$s)."'"; + } + + // undo magic quotes for " + $s = str_replace('\\"','"',$s); + + if ($this->replaceQuote == "\\'") // ' already quoted, no need to change anything + return "'$s'"; + else {// change \' to '' for sybase/mssql + $s = str_replace('\\\\','\\',$s); + return "'".str_replace("\\'",$this->replaceQuote,$s)."'"; + } + } + + + /** + * Will select the supplied $page number from a recordset, given that it is paginated in pages of + * $nrows rows per page. It also saves two boolean values saying if the given page is the first + * and/or last one of the recordset. Added by Iv�n Oliva to provide recordset pagination. + * + * See readme.htm#ex8 for an example of usage. + * + * @param sql + * @param nrows is the number of rows per page to get + * @param page is the page number to get (1-based) + * @param [inputarr] array of bind variables + * @param [secs2cache] is a private parameter only used by jlim + * @return the recordset ($rs->databaseType == 'array') + * + * NOTE: phpLens uses a different algorithm and does not use PageExecute(). + * + */ + function &PageExecute($sql, $nrows, $page, $inputarr=false, $secs2cache=0) + { + global $ADODB_INCLUDED_LIB; + if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php'); + if ($this->pageExecuteCountRows) $rs =& _adodb_pageexecute_all_rows($this, $sql, $nrows, $page, $inputarr, $secs2cache); + else $rs =& _adodb_pageexecute_no_last_page($this, $sql, $nrows, $page, $inputarr, $secs2cache); + return $rs; + } + + + /** + * Will select the supplied $page number from a recordset, given that it is paginated in pages of + * $nrows rows per page. It also saves two boolean values saying if the given page is the first + * and/or last one of the recordset. Added by Iv�n Oliva to provide recordset pagination. + * + * @param secs2cache seconds to cache data, set to 0 to force query + * @param sql + * @param nrows is the number of rows per page to get + * @param page is the page number to get (1-based) + * @param [inputarr] array of bind variables + * @return the recordset ($rs->databaseType == 'array') + */ + function &CachePageExecute($secs2cache, $sql, $nrows, $page,$inputarr=false) + { + /*switch($this->dataProvider) { + case 'postgres': + case 'mysql': + break; + default: $secs2cache = 0; break; + }*/ + $rs =& $this->PageExecute($sql,$nrows,$page,$inputarr,$secs2cache); + return $rs; + } + +} // end class ADOConnection + + + + //============================================================================================== + // CLASS ADOFetchObj + //============================================================================================== + + /** + * Internal placeholder for record objects. Used by ADORecordSet->FetchObj(). + */ + class ADOFetchObj { + }; + + //============================================================================================== + // CLASS ADORecordSet_empty + //============================================================================================== + + /** + * Lightweight recordset when there are no records to be returned + */ + class ADORecordSet_empty + { + public $dataProvider = 'empty'; + public $databaseType = false; + public $EOF = true; + public $_numOfRows = 0; + public $fields = false; + public $connection = false; + function RowCount() {return 0;} + function RecordCount() {return 0;} + function PO_RecordCount(){return 0;} + function Close(){return true;} + function FetchRow() {return false;} + function FieldCount(){ return 0;} + function Init() {} + } + + //============================================================================================== + // DATE AND TIME FUNCTIONS + //============================================================================================== + include_once(ADODB_DIR.'/adodb-time.inc.php'); + + //============================================================================================== + // CLASS ADORecordSet + //============================================================================================== + + if (PHP_VERSION < 5) include_once(ADODB_DIR.'/adodb-php4.inc.php'); + else include_once(ADODB_DIR.'/adodb-iterator.inc.php'); + /** + * RecordSet class that represents the dataset returned by the database. + * To keep memory overhead low, this class holds only the current row in memory. + * No prefetching of data is done, so the RecordCount() can return -1 ( which + * means recordcount not known). + */ + class ADORecordSet extends ADODB_BASE_RS { + /* + * public variables + */ + public $dataProvider = "native"; + public $fields = false; /// holds the current row data + public $blobSize = 100; /// any varchar/char field this size or greater is treated as a blob + /// in other words, we use a text area for editing. + public $canSeek = false; /// indicates that seek is supported + public $sql; /// sql text + public $EOF = false; /// Indicates that the current record position is after the last record in a Recordset object. + + public $emptyTimeStamp = ' '; /// what to display when $time==0 + public $emptyDate = ' '; /// what to display when $time==0 + public $debug = false; + public $timeCreated=0; /// datetime in Unix format rs created -- for cached recordsets + + public $bind = false; /// used by Fields() to hold array - should be private? + public $fetchMode; /// default fetch mode + public $connection = false; /// the parent connection + /* + * private variables + */ + public $_numOfRows = -1; /** number of rows, or -1 */ + public $_numOfFields = -1; /** number of fields in recordset */ + public $_queryID = -1; /** This variable keeps the result link identifier. */ + public $_currentRow = -1; /** This variable keeps the current row in the Recordset. */ + public $_closed = false; /** has recordset been closed */ + public $_inited = false; /** Init() should only be called once */ + public $_obj; /** Used by FetchObj */ + public $_names; /** Used by FetchObj */ + + public $_currentPage = -1; /** Added by Iv�n Oliva to implement recordset pagination */ + public $_atFirstPage = false; /** Added by Iv�n Oliva to implement recordset pagination */ + public $_atLastPage = false; /** Added by Iv�n Oliva to implement recordset pagination */ + public $_lastPageNo = -1; + public $_maxRecordCount = 0; + public $datetime = false; + + /** + * Constructor + * + * @param queryID this is the queryID returned by ADOConnection->_query() + * + */ + function ADORecordSet($queryID) + { + $this->_queryID = $queryID; + } + + + + function Init() + { + if ($this->_inited) return; + $this->_inited = true; + if ($this->_queryID) @$this->_initrs(); + else { + $this->_numOfRows = 0; + $this->_numOfFields = 0; + } + if ($this->_numOfRows != 0 && $this->_numOfFields && $this->_currentRow == -1) { + + $this->_currentRow = 0; + if ($this->EOF = ($this->_fetch() === false)) { + $this->_numOfRows = 0; // _numOfRows could be -1 + } + } else { + $this->EOF = true; + } + } + + + /** + * Generate a SELECT tag string from a recordset, and return the string. + * If the recordset has 2 cols, we treat the 1st col as the containing + * the text to display to the user, and 2nd col as the return value. Default + * strings are compared with the FIRST column. + * + * @param name name of SELECT tag + * @param [defstr] the value to hilite. Use an array for multiple hilites for listbox. + * @param [blank1stItem] true to leave the 1st item in list empty + * @param [multiple] true for listbox, false for popup + * @param [size] #rows to show for listbox. not used by popup + * @param [selectAttr] additional attributes to defined for SELECT tag. + * useful for holding javascript onChange='...' handlers. + & @param [compareFields0] when we have 2 cols in recordset, we compare the defstr with + * column 0 (1st col) if this is true. This is not documented. + * + * @return HTML + * + * changes by glen.davies@cce.ac.nz to support multiple hilited items + */ + function GetMenu($name,$defstr='',$blank1stItem=true,$multiple=false, + $size=0, $selectAttr='',$compareFields0=true) + { + global $ADODB_INCLUDED_LIB; + if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php'); + return _adodb_getmenu($this, $name,$defstr,$blank1stItem,$multiple, + $size, $selectAttr,$compareFields0); + } + + + + /** + * Generate a SELECT tag string from a recordset, and return the string. + * If the recordset has 2 cols, we treat the 1st col as the containing + * the text to display to the user, and 2nd col as the return value. Default + * strings are compared with the SECOND column. + * + */ + function GetMenu2($name,$defstr='',$blank1stItem=true,$multiple=false,$size=0, $selectAttr='') + { + return $this->GetMenu($name,$defstr,$blank1stItem,$multiple, + $size, $selectAttr,false); + } + + /* + Grouped Menu + */ + function GetMenu3($name,$defstr='',$blank1stItem=true,$multiple=false, + $size=0, $selectAttr='') + { + global $ADODB_INCLUDED_LIB; + if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php'); + return _adodb_getmenu_gp($this, $name,$defstr,$blank1stItem,$multiple, + $size, $selectAttr,false); + } + + /** + * return recordset as a 2-dimensional array. + * + * @param [nRows] is the number of rows to return. -1 means every row. + * + * @return an array indexed by the rows (0-based) from the recordset + */ + function &GetArray($nRows = -1) + { + global $ADODB_EXTENSION; if ($ADODB_EXTENSION) { + $results = adodb_getall($this,$nRows); + return $results; + } + $results = array(); + $cnt = 0; + while (!$this->EOF && $nRows != $cnt) { + $results[] = $this->fields; + $this->MoveNext(); + $cnt++; + } + return $results; + } + + function &GetAll($nRows = -1) + { + $arr =& $this->GetArray($nRows); + return $arr; + } + + /* + * Some databases allow multiple recordsets to be returned. This function + * will return true if there is a next recordset, or false if no more. + */ + function NextRecordSet() + { + return false; + } + + /** + * return recordset as a 2-dimensional array. + * Helper function for ADOConnection->SelectLimit() + * + * @param offset is the row to start calculations from (1-based) + * @param [nrows] is the number of rows to return + * + * @return an array indexed by the rows (0-based) from the recordset + */ + function &GetArrayLimit($nrows,$offset=-1) + { + if ($offset <= 0) { + $arr =& $this->GetArray($nrows); + return $arr; + } + + $this->Move($offset); + + $results = array(); + $cnt = 0; + while (!$this->EOF && $nrows != $cnt) { + $results[$cnt++] = $this->fields; + $this->MoveNext(); + } + + return $results; + } + + + /** + * Synonym for GetArray() for compatibility with ADO. + * + * @param [nRows] is the number of rows to return. -1 means every row. + * + * @return an array indexed by the rows (0-based) from the recordset + */ + function &GetRows($nRows = -1) + { + $arr =& $this->GetArray($nRows); + return $arr; + } + + /** + * return whole recordset as a 2-dimensional associative array if there are more than 2 columns. + * The first column is treated as the key and is not included in the array. + * If there is only 2 columns, it will return a 1 dimensional array of key-value pairs unless + * $force_array == true. + * + * @param [force_array] has only meaning if we have 2 data columns. If false, a 1 dimensional + * array is returned, otherwise a 2 dimensional array is returned. If this sounds confusing, + * read the source. + * + * @param [first2cols] means if there are more than 2 cols, ignore the remaining cols and + * instead of returning array[col0] => array(remaining cols), return array[col0] => col1 + * + * @return an associative array indexed by the first column of the array, + * or false if the data has less than 2 cols. + */ + function &GetAssoc($force_array = false, $first2cols = false) + { + global $ADODB_EXTENSION; + + $cols = $this->_numOfFields; + if ($cols < 2) { + $false = false; + return $false; + } + $numIndex = isset($this->fields[0]); + $results = array(); + + if (!$first2cols && ($cols > 2 || $force_array)) { + if ($ADODB_EXTENSION) { + if ($numIndex) { + while (!$this->EOF) { + $results[trim($this->fields[0])] = array_slice($this->fields, 1); + adodb_movenext($this); + } + } else { + while (!$this->EOF) { + $results[trim(reset($this->fields))] = array_slice($this->fields, 1); + adodb_movenext($this); + } + } + } else { + if ($numIndex) { + while (!$this->EOF) { + $results[trim($this->fields[0])] = array_slice($this->fields, 1); + $this->MoveNext(); + } + } else { + while (!$this->EOF) { + $results[trim(reset($this->fields))] = array_slice($this->fields, 1); + $this->MoveNext(); + } + } + } + } else { + if ($ADODB_EXTENSION) { + // return scalar values + if ($numIndex) { + while (!$this->EOF) { + // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string + $results[trim(($this->fields[0]))] = $this->fields[1]; + adodb_movenext($this); + } + } else { + while (!$this->EOF) { + // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string + $v1 = trim(reset($this->fields)); + $v2 = ''.next($this->fields); + $results[$v1] = $v2; + adodb_movenext($this); + } + } + } else { + if ($numIndex) { + while (!$this->EOF) { + // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string + $results[trim(($this->fields[0]))] = $this->fields[1]; + $this->MoveNext(); + } + } else { + while (!$this->EOF) { + // some bug in mssql PHP 4.02 -- doesn't handle references properly so we FORCE creating a new string + $v1 = trim(reset($this->fields)); + $v2 = ''.next($this->fields); + $results[$v1] = $v2; + $this->MoveNext(); + } + } + } + } + + $ref =& $results; # workaround accelerator incompat with PHP 4.4 :( + return $ref; + } + + + /** + * + * @param v is the character timestamp in YYYY-MM-DD hh:mm:ss format + * @param fmt is the format to apply to it, using date() + * + * @return a timestamp formated as user desires + */ + function UserTimeStamp($v,$fmt='Y-m-d H:i:s') + { + if (is_numeric($v) && strlen($v)<14) return adodb_date($fmt,$v); + $tt = $this->UnixTimeStamp($v); + // $tt == -1 if pre TIMESTAMP_FIRST_YEAR + if (($tt === false || $tt == -1) && $v != false) return $v; + if ($tt === 0) return $this->emptyTimeStamp; + return adodb_date($fmt,$tt); + } + + + /** + * @param v is the character date in YYYY-MM-DD format, returned by database + * @param fmt is the format to apply to it, using date() + * + * @return a date formated as user desires + */ + function UserDate($v,$fmt='Y-m-d') + { + $tt = $this->UnixDate($v); + // $tt == -1 if pre TIMESTAMP_FIRST_YEAR + if (($tt === false || $tt == -1) && $v != false) return $v; + else if ($tt == 0) return $this->emptyDate; + else if ($tt == -1) { // pre-TIMESTAMP_FIRST_YEAR + } + return adodb_date($fmt,$tt); + } + + + /** + * @param $v is a date string in YYYY-MM-DD format + * + * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format + */ + function UnixDate($v) + { + return ADOConnection::UnixDate($v); + } + + + /** + * @param $v is a timestamp string in YYYY-MM-DD HH-NN-SS format + * + * @return date in unix timestamp format, or 0 if before TIMESTAMP_FIRST_YEAR, or false if invalid date format + */ + function UnixTimeStamp($v) + { + return ADOConnection::UnixTimeStamp($v); + } + + + /** + * PEAR DB Compat - do not use internally + */ + function Free() + { + return $this->Close(); + } + + + /** + * PEAR DB compat, number of rows + */ + function NumRows() + { + return $this->_numOfRows; + } + + + /** + * PEAR DB compat, number of cols + */ + function NumCols() + { + return $this->_numOfFields; + } + + /** + * Fetch a row, returning false if no more rows. + * This is PEAR DB compat mode. + * + * @return false or array containing the current record + */ + function &FetchRow() + { + if ($this->EOF) { + $false = false; + return $false; + } + $arr = $this->fields; + $this->_currentRow++; + if (!$this->_fetch()) $this->EOF = true; + return $arr; + } + + + /** + * Fetch a row, returning PEAR_Error if no more rows. + * This is PEAR DB compat mode. + * + * @return DB_OK or error object + */ + function FetchInto(&$arr) + { + if ($this->EOF) return (defined('PEAR_ERROR_RETURN')) ? new PEAR_Error('EOF',-1): false; + $arr = $this->fields; + $this->MoveNext(); + return 1; // DB_OK + } + + + /** + * Move to the first row in the recordset. Many databases do NOT support this. + * + * @return true or false + */ + function MoveFirst() + { + if ($this->_currentRow == 0) return true; + return $this->Move(0); + } + + + /** + * Move to the last row in the recordset. + * + * @return true or false + */ + function MoveLast() + { + if ($this->_numOfRows >= 0) return $this->Move($this->_numOfRows-1); + if ($this->EOF) return false; + while (!$this->EOF) { + $f = $this->fields; + $this->MoveNext(); + } + $this->fields = $f; + $this->EOF = false; + return true; + } + + + /** + * Move to next record in the recordset. + * + * @return true if there still rows available, or false if there are no more rows (EOF). + */ + function MoveNext() + { + if (!$this->EOF) { + $this->_currentRow++; + if ($this->_fetch()) return true; + } + $this->EOF = true; + /* -- tested error handling when scrolling cursor -- seems useless. + $conn = $this->connection; + if ($conn && $conn->raiseErrorFn && ($errno = $conn->ErrorNo())) { + $fn = $conn->raiseErrorFn; + $fn($conn->databaseType,'MOVENEXT',$errno,$conn->ErrorMsg().' ('.$this->sql.')',$conn->host,$conn->database); + } + */ + return false; + } + + + /** + * Random access to a specific row in the recordset. Some databases do not support + * access to previous rows in the databases (no scrolling backwards). + * + * @param rowNumber is the row to move to (0-based) + * + * @return true if there still rows available, or false if there are no more rows (EOF). + */ + function Move($rowNumber = 0) + { + $this->EOF = false; + if ($rowNumber == $this->_currentRow) return true; + if ($rowNumber >= $this->_numOfRows) + if ($this->_numOfRows != -1) $rowNumber = $this->_numOfRows-2; + + if ($this->canSeek) { + + if ($this->_seek($rowNumber)) { + $this->_currentRow = $rowNumber; + if ($this->_fetch()) { + return true; + } + } else { + $this->EOF = true; + return false; + } + } else { + if ($rowNumber < $this->_currentRow) return false; + global $ADODB_EXTENSION; + if ($ADODB_EXTENSION) { + while (!$this->EOF && $this->_currentRow < $rowNumber) { + adodb_movenext($this); + } + } else { + + while (! $this->EOF && $this->_currentRow < $rowNumber) { + $this->_currentRow++; + + if (!$this->_fetch()) $this->EOF = true; + } + } + return !($this->EOF); + } + + $this->fields = false; + $this->EOF = true; + return false; + } + + + /** + * Get the value of a field in the current row by column name. + * Will not work if ADODB_FETCH_MODE is set to ADODB_FETCH_NUM. + * + * @param colname is the field to access + * + * @return the value of $colname column + */ + function Fields($colname) + { + return $this->fields[$colname]; + } + + function GetAssocKeys($upper=true) + { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + if ($upper === 2) $this->bind[$o->name] = $i; + else $this->bind[($upper) ? strtoupper($o->name) : strtolower($o->name)] = $i; + } + } + + /** + * Use associative array to get fields array for databases that do not support + * associative arrays. Submitted by Paolo S. Asioli paolo.asioli#libero.it + * + * If you don't want uppercase cols, set $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC + * before you execute your SQL statement, and access $rs->fields['col'] directly. + * + * $upper 0 = lowercase, 1 = uppercase, 2 = whatever is returned by FetchField + */ + function &GetRowAssoc($upper=1) + { + $record = array(); + // if (!$this->fields) return $record; + + if (!$this->bind) { + $this->GetAssocKeys($upper); + } + + foreach($this->bind as $k => $v) { + $record[$k] = $this->fields[$v]; + } + + return $record; + } + + + /** + * Clean up recordset + * + * @return true or false + */ + function Close() + { + // free connection object - this seems to globally free the object + // and not merely the reference, so don't do this... + // $this->connection = false; + if (!$this->_closed) { + $this->_closed = true; + return $this->_close(); + } else + return true; + } + + /** + * synonyms RecordCount and RowCount + * + * @return the number of rows or -1 if this is not supported + */ + function RecordCount() {return $this->_numOfRows;} + + + /* + * If we are using PageExecute(), this will return the maximum possible rows + * that can be returned when paging a recordset. + */ + function MaxRecordCount() + { + return ($this->_maxRecordCount) ? $this->_maxRecordCount : $this->RecordCount(); + } + + /** + * synonyms RecordCount and RowCount + * + * @return the number of rows or -1 if this is not supported + */ + function RowCount() {return $this->_numOfRows;} + + + /** + * Portable RecordCount. Pablo Roca <pabloroca@mvps.org> + * + * @return the number of records from a previous SELECT. All databases support this. + * + * But aware possible problems in multiuser environments. For better speed the table + * must be indexed by the condition. Heavy test this before deploying. + */ + function PO_RecordCount($table="", $condition="") { + + $lnumrows = $this->_numOfRows; + // the database doesn't support native recordcount, so we do a workaround + if ($lnumrows == -1 && $this->connection) { + IF ($table) { + if ($condition) $condition = " WHERE " . $condition; + $resultrows = &$this->connection->Execute("SELECT COUNT(*) FROM $table $condition"); + if ($resultrows) $lnumrows = reset($resultrows->fields); + } + } + return $lnumrows; + } + + /** + * @return the current row in the recordset. If at EOF, will return the last row. 0-based. + */ + function CurrentRow() {return $this->_currentRow;} + + /** + * synonym for CurrentRow -- for ADO compat + * + * @return the current row in the recordset. If at EOF, will return the last row. 0-based. + */ + function AbsolutePosition() {return $this->_currentRow;} + + /** + * @return the number of columns in the recordset. Some databases will set this to 0 + * if no records are returned, others will return the number of columns in the query. + */ + function FieldCount() {return $this->_numOfFields;} + + + /** + * Get the ADOFieldObject of a specific column. + * + * @param fieldoffset is the column position to access(0-based). + * + * @return the ADOFieldObject for that column, or false. + */ + function &FetchField($fieldoffset = -1) + { + // must be defined by child class + } + + /** + * Get the ADOFieldObjects of all columns in an array. + * + */ + function& FieldTypesArray() + { + $arr = array(); + for ($i=0, $max=$this->_numOfFields; $i < $max; $i++) + $arr[] = $this->FetchField($i); + return $arr; + } + + /** + * Return the fields array of the current row as an object for convenience. + * The default case is lowercase field names. + * + * @return the object with the properties set to the fields of the current row + */ + function &FetchObj() + { + $o =& $this->FetchObject(false); + return $o; + } + + /** + * Return the fields array of the current row as an object for convenience. + * The default case is uppercase. + * + * @param $isupper to set the object property names to uppercase + * + * @return the object with the properties set to the fields of the current row + */ + function &FetchObject($isupper=true) + { + if (empty($this->_obj)) { + $this->_obj = new ADOFetchObj(); + $this->_names = array(); + for ($i=0; $i <$this->_numOfFields; $i++) { + $f = $this->FetchField($i); + $this->_names[] = $f->name; + } + } + $i = 0; + if (PHP_VERSION >= 5) $o = clone($this->_obj); + else $o = $this->_obj; + + for ($i=0; $i <$this->_numOfFields; $i++) { + $name = $this->_names[$i]; + if ($isupper) $n = strtoupper($name); + else $n = $name; + + $o->$n = $this->Fields($name); + } + return $o; + } + + /** + * Return the fields array of the current row as an object for convenience. + * The default is lower-case field names. + * + * @return the object with the properties set to the fields of the current row, + * or false if EOF + * + * Fixed bug reported by tim@orotech.net + */ + function &FetchNextObj() + { + $o =& $this->FetchNextObject(false); + return $o; + } + + + /** + * Return the fields array of the current row as an object for convenience. + * The default is upper case field names. + * + * @param $isupper to set the object property names to uppercase + * + * @return the object with the properties set to the fields of the current row, + * or false if EOF + * + * Fixed bug reported by tim@orotech.net + */ + function &FetchNextObject($isupper=true) + { + $o = false; + if ($this->_numOfRows != 0 && !$this->EOF) { + $o = $this->FetchObject($isupper); + $this->_currentRow++; + if ($this->_fetch()) return $o; + } + $this->EOF = true; + return $o; + } + + /** + * Get the metatype of the column. This is used for formatting. This is because + * many databases use different names for the same type, so we transform the original + * type to our standardised version which uses 1 character codes: + * + * @param t is the type passed in. Normally is ADOFieldObject->type. + * @param len is the maximum length of that field. This is because we treat character + * fields bigger than a certain size as a 'B' (blob). + * @param fieldobj is the field object returned by the database driver. Can hold + * additional info (eg. primary_key for mysql). + * + * @return the general type of the data: + * C for character < 250 chars + * X for teXt (>= 250 chars) + * B for Binary + * N for numeric or floating point + * D for date + * T for timestamp + * L for logical/Boolean + * I for integer + * R for autoincrement counter/integer + * + * + */ + function MetaType($t,$len=-1,$fieldobj=false) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + // changed in 2.32 to hashing instead of switch stmt for speed... + static $typeMap = array( + 'VARCHAR' => 'C', + 'VARCHAR2' => 'C', + 'CHAR' => 'C', + 'C' => 'C', + 'STRING' => 'C', + 'NCHAR' => 'C', + 'NVARCHAR' => 'C', + 'VARYING' => 'C', + 'BPCHAR' => 'C', + 'CHARACTER' => 'C', + 'INTERVAL' => 'C', # Postgres + ## + 'LONGCHAR' => 'X', + 'TEXT' => 'X', + 'NTEXT' => 'X', + 'M' => 'X', + 'X' => 'X', + 'CLOB' => 'X', + 'NCLOB' => 'X', + 'LVARCHAR' => 'X', + ## + 'BLOB' => 'B', + 'IMAGE' => 'B', + 'BINARY' => 'B', + 'VARBINARY' => 'B', + 'LONGBINARY' => 'B', + 'B' => 'B', + ## + 'YEAR' => 'D', // mysql + 'DATE' => 'D', + 'D' => 'D', + ## + 'TIME' => 'T', + 'TIMESTAMP' => 'T', + 'DATETIME' => 'T', + 'TIMESTAMPTZ' => 'T', + 'T' => 'T', + ## + 'BOOL' => 'L', + 'BOOLEAN' => 'L', + 'BIT' => 'L', + 'L' => 'L', + ## + 'COUNTER' => 'R', + 'R' => 'R', + 'SERIAL' => 'R', // ifx + 'INT IDENTITY' => 'R', + ## + 'INT' => 'I', + 'INT2' => 'I', + 'INT4' => 'I', + 'INT8' => 'I', + 'INTEGER' => 'I', + 'INTEGER UNSIGNED' => 'I', + 'SHORT' => 'I', + 'TINYINT' => 'I', + 'SMALLINT' => 'I', + 'I' => 'I', + ## + 'LONG' => 'N', // interbase is numeric, oci8 is blob + 'BIGINT' => 'N', // this is bigger than PHP 32-bit integers + 'DECIMAL' => 'N', + 'DEC' => 'N', + 'REAL' => 'N', + 'DOUBLE' => 'N', + 'DOUBLE PRECISION' => 'N', + 'SMALLFLOAT' => 'N', + 'FLOAT' => 'N', + 'NUMBER' => 'N', + 'NUM' => 'N', + 'NUMERIC' => 'N', + 'MONEY' => 'N', + + ## informix 9.2 + 'SQLINT' => 'I', + 'SQLSERIAL' => 'I', + 'SQLSMINT' => 'I', + 'SQLSMFLOAT' => 'N', + 'SQLFLOAT' => 'N', + 'SQLMONEY' => 'N', + 'SQLDECIMAL' => 'N', + 'SQLDATE' => 'D', + 'SQLVCHAR' => 'C', + 'SQLCHAR' => 'C', + 'SQLDTIME' => 'T', + 'SQLINTERVAL' => 'N', + 'SQLBYTES' => 'B', + 'SQLTEXT' => 'X' + ); + + $tmap = false; + $t = strtoupper($t); + $tmap = (isset($typeMap[$t])) ? $typeMap[$t] : 'N'; + switch ($tmap) { + case 'C': + + // is the char field is too long, return as text field... + if ($this->blobSize >= 0) { + if ($len > $this->blobSize) return 'X'; + } else if ($len > 250) { + return 'X'; + } + return 'C'; + + case 'I': + if (!empty($fieldobj->primary_key)) return 'R'; + return 'I'; + + case false: + return 'N'; + + case 'B': + if (isset($fieldobj->binary)) + return ($fieldobj->binary) ? 'B' : 'X'; + return 'B'; + + case 'D': + if (!empty($this->connection) && !empty($this->connection->datetime)) return 'T'; + return 'D'; + + default: + if ($t == 'LONG' && $this->dataProvider == 'oci8') return 'B'; + return $tmap; + } + } + + function _close() {} + + /** + * set/returns the current recordset page when paginating + */ + function AbsolutePage($page=-1) + { + if ($page != -1) $this->_currentPage = $page; + return $this->_currentPage; + } + + /** + * set/returns the status of the atFirstPage flag when paginating + */ + function AtFirstPage($status=false) + { + if ($status != false) $this->_atFirstPage = $status; + return $this->_atFirstPage; + } + + function LastPageNo($page = false) + { + if ($page != false) $this->_lastPageNo = $page; + return $this->_lastPageNo; + } + + /** + * set/returns the status of the atLastPage flag when paginating + */ + function AtLastPage($status=false) + { + if ($status != false) $this->_atLastPage = $status; + return $this->_atLastPage; + } + +} // end class ADORecordSet + + //============================================================================================== + // CLASS ADORecordSet_array + //============================================================================================== + + /** + * This class encapsulates the concept of a recordset created in memory + * as an array. This is useful for the creation of cached recordsets. + * + * Note that the constructor is different from the standard ADORecordSet + */ + + class ADORecordSet_array extends ADORecordSet + { + public $databaseType = 'array'; + + public $_array; // holds the 2-dimensional data array + public $_types; // the array of types of each column (C B I L M) + public $_colnames; // names of each column in array + public $_skiprow1; // skip 1st row because it holds column names + public $_fieldarr; // holds array of field objects + public $canSeek = true; + public $affectedrows = false; + public $insertid = false; + public $sql = ''; + public $compat = false; + /** + * Constructor + * + */ + function ADORecordSet_array($fakeid=1) + { + global $ADODB_FETCH_MODE,$ADODB_COMPAT_FETCH; + + // fetch() on EOF does not delete $this->fields + $this->compat = !empty($ADODB_COMPAT_FETCH); + $this->ADORecordSet($fakeid); // fake queryID + $this->fetchMode = $ADODB_FETCH_MODE; + } + + + /** + * Setup the array. + * + * @param array is a 2-dimensional array holding the data. + * The first row should hold the column names + * unless paramter $colnames is used. + * @param typearr holds an array of types. These are the same types + * used in MetaTypes (C,B,L,I,N). + * @param [colnames] array of column names. If set, then the first row of + * $array should not hold the column names. + */ + function InitArray($array,$typearr,$colnames=false) + { + $this->_array = $array; + $this->_types = $typearr; + if ($colnames) { + $this->_skiprow1 = false; + $this->_colnames = $colnames; + } else { + $this->_skiprow1 = true; + $this->_colnames = $array[0]; + } + $this->Init(); + } + /** + * Setup the Array and datatype file objects + * + * @param array is a 2-dimensional array holding the data. + * The first row should hold the column names + * unless paramter $colnames is used. + * @param fieldarr holds an array of ADOFieldObject's. + */ + function InitArrayFields(&$array,&$fieldarr) + { + $this->_array =& $array; + $this->_skiprow1= false; + if ($fieldarr) { + $this->_fieldobjects =& $fieldarr; + } + $this->Init(); + } + + function &GetArray($nRows=-1) + { + if ($nRows == -1 && $this->_currentRow <= 0 && !$this->_skiprow1) { + return $this->_array; + } else { + $arr =& ADORecordSet::GetArray($nRows); + return $arr; + } + } + + function _initrs() + { + $this->_numOfRows = sizeof($this->_array); + if ($this->_skiprow1) $this->_numOfRows -= 1; + + $this->_numOfFields =(isset($this->_fieldobjects)) ? + sizeof($this->_fieldobjects):sizeof($this->_types); + } + + /* Use associative array to get fields array */ + function Fields($colname) + { + $mode = isset($this->adodbFetchMode) ? $this->adodbFetchMode : $this->fetchMode; + + if ($mode & ADODB_FETCH_ASSOC) { + if (!isset($this->fields[$colname])) $colname = strtolower($colname); + return $this->fields[$colname]; + } + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + function &FetchField($fieldOffset = -1) + { + if (isset($this->_fieldobjects)) { + return $this->_fieldobjects[$fieldOffset]; + } + $o = new ADOFieldObject(); + $o->name = $this->_colnames[$fieldOffset]; + $o->type = $this->_types[$fieldOffset]; + $o->max_length = -1; // length not known + + return $o; + } + + function _seek($row) + { + if (sizeof($this->_array) && 0 <= $row && $row < $this->_numOfRows) { + $this->_currentRow = $row; + if ($this->_skiprow1) $row += 1; + $this->fields = $this->_array[$row]; + return true; + } + return false; + } + + function MoveNext() + { + if (!$this->EOF) { + $this->_currentRow++; + + $pos = $this->_currentRow; + + if ($this->_numOfRows <= $pos) { + if (!$this->compat) $this->fields = false; + } else { + if ($this->_skiprow1) $pos += 1; + $this->fields = $this->_array[$pos]; + return true; + } + $this->EOF = true; + } + + return false; + } + + function _fetch() + { + $pos = $this->_currentRow; + + if ($this->_numOfRows <= $pos) { + if (!$this->compat) $this->fields = false; + return false; + } + if ($this->_skiprow1) $pos += 1; + $this->fields = $this->_array[$pos]; + return true; + } + + function _close() + { + return true; + } + + } // ADORecordSet_array + + //============================================================================================== + // HELPER FUNCTIONS + //============================================================================================== + + /** + * Synonym for ADOLoadCode. Private function. Do not use. + * + * @deprecated + */ + function ADOLoadDB($dbType) + { + return ADOLoadCode($dbType); + } + + /** + * Load the code for a specific database driver. Private function. Do not use. + */ + function ADOLoadCode($dbType) + { + global $ADODB_LASTDB; + + if (!$dbType) return false; + $db = strtolower($dbType); + switch ($db) { + case 'ado': + if (PHP_VERSION >= 5) $db = 'ado5'; + $class = 'ado'; + break; + case 'ifx': + case 'maxsql': $class = $db = 'mysqlt'; break; + case 'postgres': + case 'postgres8': + case 'pgsql': $class = $db = 'postgres7'; break; + default: + $class = $db; break; + } + + $file = ADODB_DIR."/drivers/adodb-".$db.".inc.php"; + @include_once($file); + $ADODB_LASTDB = $class; + if (class_exists("ADODB_" . $class)) return $class; + + //ADOConnection::outp(adodb_pr(get_declared_classes(),true)); + if (!file_exists($file)) ADOConnection::outp("Missing file: $file"); + else ADOConnection::outp("Syntax error in file: $file"); + return false; + } + + /** + * synonym for ADONewConnection for people like me who cannot remember the correct name + */ + function &NewADOConnection($db='') + { + $tmp =& ADONewConnection($db); + return $tmp; + } + + /** + * Instantiate a new Connection class for a specific database driver. + * + * @param [db] is the database Connection object to create. If undefined, + * use the last database driver that was loaded by ADOLoadCode(). + * + * @return the freshly created instance of the Connection class. + */ + function &ADONewConnection($db='') + { + GLOBAL $ADODB_NEWCONNECTION, $ADODB_LASTDB; + + if (!defined('ADODB_ASSOC_CASE')) define('ADODB_ASSOC_CASE',2); + $errorfn = (defined('ADODB_ERROR_HANDLER')) ? ADODB_ERROR_HANDLER : false; + $false = false; + if ($at = strpos($db,'://')) { + $origdsn = $db; + if (PHP_VERSION < 5) $dsna = @parse_url($db); + else { + $fakedsn = 'fake'.substr($db,$at); + $dsna = @parse_url($fakedsn); + $dsna['scheme'] = substr($db,0,$at); + + if (strncmp($db,'pdo',3) == 0) { + $sch = explode('_',$dsna['scheme']); + if (sizeof($sch)>1) { + $dsna['host'] = isset($dsna['host']) ? rawurldecode($dsna['host']) : ''; + $dsna['host'] = rawurlencode($sch[1].':host='.rawurldecode($dsna['host'])); + $dsna['scheme'] = 'pdo'; + } + } + } + + if (!$dsna) { + // special handling of oracle, which might not have host + $db = str_replace('@/','@adodb-fakehost/',$db); + $dsna = parse_url($db); + if (!$dsna) return $false; + $dsna['host'] = ''; + } + $db = @$dsna['scheme']; + if (!$db) return $false; + $dsna['host'] = isset($dsna['host']) ? rawurldecode($dsna['host']) : ''; + $dsna['user'] = isset($dsna['user']) ? rawurldecode($dsna['user']) : ''; + $dsna['pass'] = isset($dsna['pass']) ? rawurldecode($dsna['pass']) : ''; + $dsna['path'] = isset($dsna['path']) ? rawurldecode(substr($dsna['path'],1)) : ''; # strip off initial / + + if (isset($dsna['query'])) { + $opt1 = explode('&',$dsna['query']); + foreach($opt1 as $k => $v) { + $arr = explode('=',$v); + $opt[$arr[0]] = isset($arr[1]) ? rawurldecode($arr[1]) : 1; + } + } else $opt = array(); + } + /* + * phptype: Database backend used in PHP (mysql, odbc etc.) + * dbsyntax: Database used with regards to SQL syntax etc. + * protocol: Communication protocol to use (tcp, unix etc.) + * hostspec: Host specification (hostname[:port]) + * database: Database to use on the DBMS server + * username: User name for login + * password: Password for login + */ + if (!empty($ADODB_NEWCONNECTION)) { + $obj = $ADODB_NEWCONNECTION($db); + + } else { + + if (!isset($ADODB_LASTDB)) $ADODB_LASTDB = ''; + if (empty($db)) $db = $ADODB_LASTDB; + + if ($db != $ADODB_LASTDB) $db = ADOLoadCode($db); + + if (!$db) { + if (isset($origdsn)) $db = $origdsn; + if ($errorfn) { + // raise an error + $ignore = false; + $errorfn('ADONewConnection', 'ADONewConnection', -998, + "could not load the database driver for '$db'", + $db,false,$ignore); + } else + ADOConnection::outp( "<p>ADONewConnection: Unable to load database driver '$db'</p>",false); + + return $false; + } + + $cls = 'ADODB_'.$db; + if (!class_exists($cls)) { + adodb_backtrace(); + return $false; + } + + $obj = new $cls(); + } + + # constructor should not fail + if ($obj) { + if ($errorfn) $obj->raiseErrorFn = $errorfn; + if (isset($dsna)) { + if (isset($dsna['port'])) $obj->port = $dsna['port']; + foreach($opt as $k => $v) { + switch(strtolower($k)) { + case 'new': + $nconnect = true; $persist = true; break; + case 'persist': + case 'persistent': $persist = $v; break; + case 'debug': $obj->debug = (integer) $v; break; + #ibase + case 'role': $obj->role = $v; break; + case 'dialect': $obj->dialect = (integer) $v; break; + case 'charset': $obj->charset = $v; $obj->charSet=$v; break; + case 'buffers': $obj->buffers = $v; break; + case 'fetchmode': $obj->SetFetchMode($v); break; + #ado + case 'charpage': $obj->charPage = $v; break; + #mysql, mysqli + case 'clientflags': $obj->clientFlags = $v; break; + #mysql, mysqli, postgres + case 'port': $obj->port = $v; break; + #mysqli + case 'socket': $obj->socket = $v; break; + #oci8 + case 'nls_date_format': $obj->NLS_DATE_FORMAT = $v; break; + } + } + if (empty($persist)) + $ok = $obj->Connect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']); + else if (empty($nconnect)) + $ok = $obj->PConnect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']); + else + $ok = $obj->NConnect($dsna['host'], $dsna['user'], $dsna['pass'], $dsna['path']); + + if (!$ok) return $false; + } + } + return $obj; + } + + + + // $perf == true means called by NewPerfMonitor(), otherwise for data dictionary + function _adodb_getdriver($provider,$drivername,$perf=false) + { + switch ($provider) { + case 'odbtp': if (strncmp('odbtp_',$drivername,6)==0) return substr($drivername,6); + case 'odbc' : if (strncmp('odbc_',$drivername,5)==0) return substr($drivername,5); + case 'ado' : if (strncmp('ado_',$drivername,4)==0) return substr($drivername,4); + case 'native': break; + default: + return $provider; + } + + switch($drivername) { + case 'mysqlt': + case 'mysqli': + $drivername='mysql'; + break; + case 'postgres7': + case 'postgres8': + $drivername = 'postgres'; + break; + case 'firebird15': $drivername = 'firebird'; break; + case 'oracle': $drivername = 'oci8'; break; + case 'access': if ($perf) $drivername = ''; break; + case 'db2' : break; + case 'odbc_db2': $drivername = 'db2'; break; + case 'sapdb' : break; + default: + $drivername = 'generic'; + break; + } + return $drivername; + } + + function &NewPerfMonitor(&$conn) + { + $false = false; + $drivername = _adodb_getdriver($conn->dataProvider,$conn->databaseType,true); + if (!$drivername || $drivername == 'generic') return $false; + include_once(ADODB_DIR.'/adodb-perf.inc.php'); + @include_once(ADODB_DIR."/perf/perf-$drivername.inc.php"); + $class = "Perf_$drivername"; + if (!class_exists($class)) return $false; + $perf = new $class($conn); + + return $perf; + } + + function &NewDataDictionary(&$conn,$drivername=false) + { + $false = false; + if (!$drivername) $drivername = _adodb_getdriver($conn->dataProvider,$conn->databaseType); + + include_once(ADODB_DIR.'/adodb-lib.inc.php'); + include_once(ADODB_DIR.'/adodb-datadict.inc.php'); + $path = ADODB_DIR."/datadict/datadict-$drivername.inc.php"; + + if (!file_exists($path)) { + ADOConnection::outp("Database driver '$path' not available"); + return $false; + } + include_once($path); + $class = "ADODB2_$drivername"; + $dict = new $class(); + $dict->dataProvider = $conn->dataProvider; + $dict->connection = &$conn; + $dict->upperName = strtoupper($drivername); + $dict->quote = $conn->nameQuote; + if (!empty($conn->_connectionID)) + $dict->serverInfo = $conn->ServerInfo(); + + return $dict; + } + + + + /* + Perform a print_r, with pre tags for better formatting. + */ + function adodb_pr($var,$as_string=false) + { + if ($as_string) ob_start(); + + if (isset($_SERVER['HTTP_USER_AGENT'])) { + echo " <pre>\n";print_r($var);echo "</pre>\n"; + } else + print_r($var); + + if ($as_string) { + $s = ob_get_contents(); + ob_end_clean(); + return $s; + } + } + + /* + Perform a stack-crawl and pretty print it. + + @param printOrArr Pass in a boolean to indicate print, or an $exception->trace array (assumes that print is true then). + @param levels Number of levels to display + */ + function adodb_backtrace($printOrArr=true,$levels=9999) + { + global $ADODB_INCLUDED_LIB; + if (empty($ADODB_INCLUDED_LIB)) include_once(ADODB_DIR.'/adodb-lib.inc.php'); + return _adodb_backtrace($printOrArr,$levels); + } + + +} +?> diff --git a/framework/DataAccess/adodb/drivers/adodb-access.inc.php b/framework/DataAccess/adodb/drivers/adodb-access.inc.php new file mode 100644 index 00000000..0437956f --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-access.inc.php @@ -0,0 +1,86 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. See License.txt. + Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Microsoft Access data driver. Requires ODBC. Works only on MS Windows. +*/ +if (!defined('_ADODB_ODBC_LAYER')) { + if (!defined('ADODB_DIR')) die(); + + include(ADODB_DIR."/drivers/adodb-odbc.inc.php"); +} + if (!defined('_ADODB_ACCESS')) { + define('_ADODB_ACCESS',1); + +class ADODB_access extends ADODB_odbc { + var $databaseType = 'access'; + var $hasTop = 'top'; // support mssql SELECT TOP 10 * FROM TABLE + var $fmtDate = "#Y-m-d#"; + var $fmtTimeStamp = "#Y-m-d h:i:sA#"; // note not comma + var $_bindInputArray = false; // strangely enough, setting to true does not work reliably + var $sysDate = "FORMAT(NOW,'yyyy-mm-dd')"; + var $sysTimeStamp = 'NOW'; + var $hasTransactions = false; + + function ADODB_access() + { + global $ADODB_EXTENSION; + + $ADODB_EXTENSION = false; + $this->ADODB_odbc(); + } + + function Time() + { + return time(); + } + + function BeginTrans() { return false;} + + function IfNull( $field, $ifNull ) + { + return " IIF(IsNull($field), $ifNull, $field) "; // if Access + } +/* + function &MetaTables() + { + global $ADODB_FETCH_MODE; + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $qid = odbc_tables($this->_connectionID); + $rs = new ADORecordSet_odbc($qid); + $ADODB_FETCH_MODE = $savem; + if (!$rs) return false; + + $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change; + + $arr = &$rs->GetArray(); + //print_pre($arr); + $arr2 = array(); + for ($i=0; $i < sizeof($arr); $i++) { + if ($arr[$i][2] && $arr[$i][3] != 'SYSTEM TABLE') + $arr2[] = $arr[$i][2]; + } + return $arr2; + }*/ +} + + +class ADORecordSet_access extends ADORecordSet_odbc { + + var $databaseType = "access"; + + function ADORecordSet_access($id,$mode=false) + { + return $this->ADORecordSet_odbc($id,$mode); + } +}// class +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-ado.inc.php b/framework/DataAccess/adodb/drivers/adodb-ado.inc.php new file mode 100644 index 00000000..b9863a48 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-ado.inc.php @@ -0,0 +1,631 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Microsoft ADO data driver. Requires ADO. Works only on MS Windows. +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +define("_ADODB_ADO_LAYER", 1 ); +/*-------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------*/ + + +class ADODB_ado extends ADOConnection { + var $databaseType = "ado"; + var $_bindInputArray = false; + var $fmtDate = "'Y-m-d'"; + var $fmtTimeStamp = "'Y-m-d, h:i:sA'"; + var $replaceQuote = "''"; // string to use to replace quotes + var $dataProvider = "ado"; + var $hasAffectedRows = true; + var $adoParameterType = 201; // 201 = long varchar, 203=long wide varchar, 205 = long varbinary + var $_affectedRows = false; + var $_thisTransactions; + var $_cursor_type = 3; // 3=adOpenStatic,0=adOpenForwardOnly,1=adOpenKeyset,2=adOpenDynamic + var $_cursor_location = 3; // 2=adUseServer, 3 = adUseClient; + var $_lock_type = -1; + var $_execute_option = -1; + var $poorAffectedRows = true; + var $charPage; + + function ADODB_ado() + { + $this->_affectedRows = new VARIANT; + } + + function ServerInfo() + { + if (!empty($this->_connectionID)) $desc = $this->_connectionID->provider; + return array('description' => $desc, 'version' => ''); + } + + function _affectedrows() + { + if (PHP_VERSION >= 5) return $this->_affectedRows; + + return $this->_affectedRows->value; + } + + // you can also pass a connection string like this: + // + // $DB->Connect('USER ID=sa;PASSWORD=pwd;SERVER=mangrove;DATABASE=ai',false,false,'SQLOLEDB'); + function _connect($argHostname, $argUsername, $argPassword, $argProvider= 'MSDASQL') + { + $u = 'UID'; + $p = 'PWD'; + + if (!empty($this->charPage)) + $dbc = new COM('ADODB.Connection',null,$this->charPage); + else + $dbc = new COM('ADODB.Connection'); + + if (! $dbc) return false; + + /* special support if provider is mssql or access */ + if ($argProvider=='mssql') { + $u = 'User Id'; //User parameter name for OLEDB + $p = 'Password'; + $argProvider = "SQLOLEDB"; // SQL Server Provider + + // not yet + //if ($argDatabasename) $argHostname .= ";Initial Catalog=$argDatabasename"; + + //use trusted conection for SQL if username not specified + if (!$argUsername) $argHostname .= ";Trusted_Connection=Yes"; + } else if ($argProvider=='access') + $argProvider = "Microsoft.Jet.OLEDB.4.0"; // Microsoft Jet Provider + + if ($argProvider) $dbc->Provider = $argProvider; + + if ($argUsername) $argHostname .= ";$u=$argUsername"; + if ($argPassword)$argHostname .= ";$p=$argPassword"; + + if ($this->debug) ADOConnection::outp( "Host=".$argHostname."<BR>\n version=$dbc->version"); + // @ added below for php 4.0.1 and earlier + @$dbc->Open((string) $argHostname); + + $this->_connectionID = $dbc; + + $dbc->CursorLocation = $this->_cursor_location; + return $dbc->State > 0; + } + + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argProvider='MSDASQL') + { + return $this->_connect($argHostname,$argUsername,$argPassword,$argProvider); + } + +/* + adSchemaCatalogs = 1, + adSchemaCharacterSets = 2, + adSchemaCollations = 3, + adSchemaColumns = 4, + adSchemaCheckConstraints = 5, + adSchemaConstraintColumnUsage = 6, + adSchemaConstraintTableUsage = 7, + adSchemaKeyColumnUsage = 8, + adSchemaReferentialContraints = 9, + adSchemaTableConstraints = 10, + adSchemaColumnsDomainUsage = 11, + adSchemaIndexes = 12, + adSchemaColumnPrivileges = 13, + adSchemaTablePrivileges = 14, + adSchemaUsagePrivileges = 15, + adSchemaProcedures = 16, + adSchemaSchemata = 17, + adSchemaSQLLanguages = 18, + adSchemaStatistics = 19, + adSchemaTables = 20, + adSchemaTranslations = 21, + adSchemaProviderTypes = 22, + adSchemaViews = 23, + adSchemaViewColumnUsage = 24, + adSchemaViewTableUsage = 25, + adSchemaProcedureParameters = 26, + adSchemaForeignKeys = 27, + adSchemaPrimaryKeys = 28, + adSchemaProcedureColumns = 29, + adSchemaDBInfoKeywords = 30, + adSchemaDBInfoLiterals = 31, + adSchemaCubes = 32, + adSchemaDimensions = 33, + adSchemaHierarchies = 34, + adSchemaLevels = 35, + adSchemaMeasures = 36, + adSchemaProperties = 37, + adSchemaMembers = 38 + +*/ + + function &MetaTables() + { + $arr= array(); + $dbc = $this->_connectionID; + + $adors=@$dbc->OpenSchema(20);//tables + if ($adors){ + $f = $adors->Fields(2);//table/view name + $t = $adors->Fields(3);//table type + while (!$adors->EOF){ + $tt=substr($t->value,0,6); + if ($tt!='SYSTEM' && $tt !='ACCESS') + $arr[]=$f->value; + //print $f->value . ' ' . $t->value.'<br>'; + $adors->MoveNext(); + } + $adors->Close(); + } + + return $arr; + } + + function &MetaColumns($table) + { + $table = strtoupper($table); + $arr = array(); + $dbc = $this->_connectionID; + + $adors=@$dbc->OpenSchema(4);//tables + + if ($adors){ + $t = $adors->Fields(2);//table/view name + while (!$adors->EOF){ + + + if (strtoupper($t->Value) == $table) { + + $fld = new ADOFieldObject(); + $c = $adors->Fields(3); + $fld->name = $c->Value; + $fld->type = 'CHAR'; // cannot discover type in ADO! + $fld->max_length = -1; + $arr[strtoupper($fld->name)]=$fld; + } + + $adors->MoveNext(); + } + $adors->Close(); + } + $false = false; + return empty($arr) ? $false : $arr; + } + + + + + /* returns queryID or false */ + function &_query($sql,$inputarr=false) + { + + $dbc = $this->_connectionID; + $false = false; + + // return rs + if ($inputarr) { + + if (!empty($this->charPage)) + $oCmd = new COM('ADODB.Command',null,$this->charPage); + else + $oCmd = new COM('ADODB.Command'); + $oCmd->ActiveConnection = $dbc; + $oCmd->CommandText = $sql; + $oCmd->CommandType = 1; + + foreach($inputarr as $val) { + // name, type, direction 1 = input, len, + $this->adoParameterType = 130; + $p = $oCmd->CreateParameter('name',$this->adoParameterType,1,strlen($val),$val); + //print $p->Type.' '.$p->value; + $oCmd->Parameters->Append($p); + } + $p = false; + $rs = $oCmd->Execute(); + $e = $dbc->Errors; + if ($dbc->Errors->Count > 0) return $false; + return $rs; + } + + $rs = @$dbc->Execute($sql,$this->_affectedRows, $this->_execute_option); + + if ($dbc->Errors->Count > 0) return $false; + if (! $rs) return $false; + + if ($rs->State == 0) { + $true = true; + return $true; // 0 = adStateClosed means no records returned + } + return $rs; + } + + + function BeginTrans() + { + if ($this->transOff) return true; + + if (isset($this->_thisTransactions)) + if (!$this->_thisTransactions) return false; + else { + $o = $this->_connectionID->Properties("Transaction DDL"); + $this->_thisTransactions = $o ? true : false; + if (!$o) return false; + } + @$this->_connectionID->BeginTrans(); + $this->transCnt += 1; + return true; + } + function CommitTrans($ok=true) + { + if (!$ok) return $this->RollbackTrans(); + if ($this->transOff) return true; + + @$this->_connectionID->CommitTrans(); + if ($this->transCnt) @$this->transCnt -= 1; + return true; + } + function RollbackTrans() { + if ($this->transOff) return true; + @$this->_connectionID->RollbackTrans(); + if ($this->transCnt) @$this->transCnt -= 1; + return true; + } + + /* Returns: the last error message from previous database operation */ + + function ErrorMsg() + { + $errc = $this->_connectionID->Errors; + if ($errc->Count == 0) return ''; + $err = $errc->Item($errc->Count-1); + return $err->Description; + } + + function ErrorNo() + { + $errc = $this->_connectionID->Errors; + if ($errc->Count == 0) return 0; + $err = $errc->Item($errc->Count-1); + return $err->NativeError; + } + + // returns true or false + function _close() + { + if ($this->_connectionID) $this->_connectionID->Close(); + $this->_connectionID = false; + return true; + } + + +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordSet_ado extends ADORecordSet { + + var $bind = false; + var $databaseType = "ado"; + var $dataProvider = "ado"; + var $_tarr = false; // caches the types + var $_flds; // and field objects + var $canSeek = true; + var $hideErrors = true; + + function ADORecordSet_ado($id,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + $this->fetchMode = $mode; + return $this->ADORecordSet($id,$mode); + } + + + // returns the field object + function &FetchField($fieldOffset = -1) { + $off=$fieldOffset+1; // offsets begin at 1 + + $o= new ADOFieldObject(); + $rs = $this->_queryID; + $f = $rs->Fields($fieldOffset); + $o->name = $f->Name; + $t = $f->Type; + $o->type = $this->MetaType($t); + $o->max_length = $f->DefinedSize; + $o->ado_type = $t; + + //print "off=$off name=$o->name type=$o->type len=$o->max_length<br>"; + return $o; + } + + /* Use associative array to get fields array */ + function Fields($colname) + { + if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname]; + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + + function _initrs() + { + $rs = $this->_queryID; + $this->_numOfRows = $rs->RecordCount; + + $f = $rs->Fields; + $this->_numOfFields = $f->Count; + } + + + // should only be used to move forward as we normally use forward-only cursors + function _seek($row) + { + $rs = $this->_queryID; + // absoluteposition doesn't work -- my maths is wrong ? + // $rs->AbsolutePosition->$row-2; + // return true; + if ($this->_currentRow > $row) return false; + @$rs->Move((integer)$row - $this->_currentRow-1); //adBookmarkFirst + return true; + } + +/* + OLEDB types + + enum DBTYPEENUM + { DBTYPE_EMPTY = 0, + DBTYPE_NULL = 1, + DBTYPE_I2 = 2, + DBTYPE_I4 = 3, + DBTYPE_R4 = 4, + DBTYPE_R8 = 5, + DBTYPE_CY = 6, + DBTYPE_DATE = 7, + DBTYPE_BSTR = 8, + DBTYPE_IDISPATCH = 9, + DBTYPE_ERROR = 10, + DBTYPE_BOOL = 11, + DBTYPE_VARIANT = 12, + DBTYPE_IUNKNOWN = 13, + DBTYPE_DECIMAL = 14, + DBTYPE_UI1 = 17, + DBTYPE_ARRAY = 0x2000, + DBTYPE_BYREF = 0x4000, + DBTYPE_I1 = 16, + DBTYPE_UI2 = 18, + DBTYPE_UI4 = 19, + DBTYPE_I8 = 20, + DBTYPE_UI8 = 21, + DBTYPE_GUID = 72, + DBTYPE_VECTOR = 0x1000, + DBTYPE_RESERVED = 0x8000, + DBTYPE_BYTES = 128, + DBTYPE_STR = 129, + DBTYPE_WSTR = 130, + DBTYPE_NUMERIC = 131, + DBTYPE_UDT = 132, + DBTYPE_DBDATE = 133, + DBTYPE_DBTIME = 134, + DBTYPE_DBTIMESTAMP = 135 + + ADO Types + + adEmpty = 0, + adTinyInt = 16, + adSmallInt = 2, + adInteger = 3, + adBigInt = 20, + adUnsignedTinyInt = 17, + adUnsignedSmallInt = 18, + adUnsignedInt = 19, + adUnsignedBigInt = 21, + adSingle = 4, + adDouble = 5, + adCurrency = 6, + adDecimal = 14, + adNumeric = 131, + adBoolean = 11, + adError = 10, + adUserDefined = 132, + adVariant = 12, + adIDispatch = 9, + adIUnknown = 13, + adGUID = 72, + adDate = 7, + adDBDate = 133, + adDBTime = 134, + adDBTimeStamp = 135, + adBSTR = 8, + adChar = 129, + adVarChar = 200, + adLongVarChar = 201, + adWChar = 130, + adVarWChar = 202, + adLongVarWChar = 203, + adBinary = 128, + adVarBinary = 204, + adLongVarBinary = 205, + adChapter = 136, + adFileTime = 64, + adDBFileTime = 137, + adPropVariant = 138, + adVarNumeric = 139 +*/ + function MetaType($t,$len=-1,$fieldobj=false) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + + if (!is_numeric($t)) return $t; + + switch ($t) { + case 0: + case 12: // variant + case 8: // bstr + case 129: //char + case 130: //wc + case 200: // varc + case 202:// varWC + case 128: // bin + case 204: // varBin + case 72: // guid + if ($len <= $this->blobSize) return 'C'; + + case 201: + case 203: + return 'X'; + case 128: + case 204: + case 205: + return 'B'; + case 7: + case 133: return 'D'; + + case 134: + case 135: return 'T'; + + case 11: return 'L'; + + case 16:// adTinyInt = 16, + case 2://adSmallInt = 2, + case 3://adInteger = 3, + case 4://adBigInt = 20, + case 17://adUnsignedTinyInt = 17, + case 18://adUnsignedSmallInt = 18, + case 19://adUnsignedInt = 19, + case 20://adUnsignedBigInt = 21, + return 'I'; + default: return 'N'; + } + } + + // time stamp not supported yet + function _fetch() + { + $rs = $this->_queryID; + if (!$rs or $rs->EOF) { + $this->fields = false; + return false; + } + $this->fields = array(); + + if (!$this->_tarr) { + $tarr = array(); + $flds = array(); + for ($i=0,$max = $this->_numOfFields; $i < $max; $i++) { + $f = $rs->Fields($i); + $flds[] = $f; + $tarr[] = $f->Type; + } + // bind types and flds only once + $this->_tarr = $tarr; + $this->_flds = $flds; + } + $t = reset($this->_tarr); + $f = reset($this->_flds); + + if ($this->hideErrors) $olde = error_reporting(E_ERROR|E_CORE_ERROR);// sometimes $f->value be null + for ($i=0,$max = $this->_numOfFields; $i < $max; $i++) { + //echo "<p>",$t,' ';var_dump($f->value); echo '</p>'; + switch($t) { + case 135: // timestamp + if (!strlen((string)$f->value)) $this->fields[] = false; + else { + if (!is_numeric($f->value)) # $val = variant_date_to_timestamp($f->value); + // VT_DATE stores dates as (float) fractional days since 1899/12/30 00:00:00 + $val=(float) variant_cast($f->value,VT_R8)*3600*24-2209161600; + else + $val = $f->value; + $this->fields[] = adodb_date('Y-m-d H:i:s',$val); + } + break; + case 133:// A date value (yyyymmdd) + if ($val = $f->value) { + $this->fields[] = substr($val,0,4).'-'.substr($val,4,2).'-'.substr($val,6,2); + } else + $this->fields[] = false; + break; + case 7: // adDate + if (!strlen((string)$f->value)) $this->fields[] = false; + else { + if (!is_numeric($f->value)) $val = variant_date_to_timestamp($f->value); + else $val = $f->value; + + if (($val % 86400) == 0) $this->fields[] = adodb_date('Y-m-d',$val); + else $this->fields[] = adodb_date('Y-m-d H:i:s',$val); + } + break; + case 1: // null + $this->fields[] = false; + break; + case 6: // currency is not supported properly; + ADOConnection::outp( '<b>'.$f->Name.': currency type not supported by PHP</b>'); + $this->fields[] = (float) $f->value; + break; + default: + $this->fields[] = $f->value; + break; + } + //print " $f->value $t, "; + $f = next($this->_flds); + $t = next($this->_tarr); + } // for + if ($this->hideErrors) error_reporting($olde); + @$rs->MoveNext(); // @ needed for some versions of PHP! + + if ($this->fetchMode & ADODB_FETCH_ASSOC) { + $this->fields = &$this->GetRowAssoc(ADODB_ASSOC_CASE); + } + return true; + } + + function NextRecordSet() + { + $rs = $this->_queryID; + $this->_queryID = $rs->NextRecordSet(); + //$this->_queryID = $this->_QueryId->NextRecordSet(); + if ($this->_queryID == null) return false; + + $this->_currentRow = -1; + $this->_currentPage = -1; + $this->bind = false; + $this->fields = false; + $this->_flds = false; + $this->_tarr = false; + + $this->_inited = false; + $this->Init(); + return true; + } + + function _close() { + $this->_flds = false; + @$this->_queryID->Close();// by Pete Dishman (peterd@telephonetics.co.uk) + $this->_queryID = false; + } + +} + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-ado5.inc.php b/framework/DataAccess/adodb/drivers/adodb-ado5.inc.php new file mode 100644 index 00000000..5fa26fc1 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-ado5.inc.php @@ -0,0 +1,639 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Microsoft ADO data driver. Requires ADO. Works only on MS Windows. PHP5 compat version. +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +define("_ADODB_ADO_LAYER", 1 ); +/*-------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------*/ + + +class ADODB_ado extends ADOConnection { + var $databaseType = "ado"; + var $_bindInputArray = false; + var $fmtDate = "'Y-m-d'"; + var $fmtTimeStamp = "'Y-m-d, h:i:sA'"; + var $replaceQuote = "''"; // string to use to replace quotes + var $dataProvider = "ado"; + var $hasAffectedRows = true; + var $adoParameterType = 201; // 201 = long varchar, 203=long wide varchar, 205 = long varbinary + var $_affectedRows = false; + var $_thisTransactions; + var $_cursor_type = 3; // 3=adOpenStatic,0=adOpenForwardOnly,1=adOpenKeyset,2=adOpenDynamic + var $_cursor_location = 3; // 2=adUseServer, 3 = adUseClient; + var $_lock_type = -1; + var $_execute_option = -1; + var $poorAffectedRows = true; + var $charPage; + + function ADODB_ado() + { + $this->_affectedRows = new VARIANT; + } + + function ServerInfo() + { + if (!empty($this->_connectionID)) $desc = $this->_connectionID->provider; + return array('description' => $desc, 'version' => ''); + } + + function _affectedrows() + { + if (PHP_VERSION >= 5) return $this->_affectedRows; + + return $this->_affectedRows->value; + } + + // you can also pass a connection string like this: + // + // $DB->Connect('USER ID=sa;PASSWORD=pwd;SERVER=mangrove;DATABASE=ai',false,false,'SQLOLEDB'); + function _connect($argHostname, $argUsername, $argPassword, $argProvider= 'MSDASQL') + { + try { + $u = 'UID'; + $p = 'PWD'; + + if (!empty($this->charPage)) + $dbc = new COM('ADODB.Connection',null,$this->charPage); + else + $dbc = new COM('ADODB.Connection'); + + if (! $dbc) return false; + + /* special support if provider is mssql or access */ + if ($argProvider=='mssql') { + $u = 'User Id'; //User parameter name for OLEDB + $p = 'Password'; + $argProvider = "SQLOLEDB"; // SQL Server Provider + + // not yet + //if ($argDatabasename) $argHostname .= ";Initial Catalog=$argDatabasename"; + + //use trusted conection for SQL if username not specified + if (!$argUsername) $argHostname .= ";Trusted_Connection=Yes"; + } else if ($argProvider=='access') + $argProvider = "Microsoft.Jet.OLEDB.4.0"; // Microsoft Jet Provider + + if ($argProvider) $dbc->Provider = $argProvider; + + if ($argUsername) $argHostname .= ";$u=$argUsername"; + if ($argPassword)$argHostname .= ";$p=$argPassword"; + + if ($this->debug) ADOConnection::outp( "Host=".$argHostname."<BR>\n version=$dbc->version"); + // @ added below for php 4.0.1 and earlier + @$dbc->Open((string) $argHostname); + + $this->_connectionID = $dbc; + + $dbc->CursorLocation = $this->_cursor_location; + return $dbc->State > 0; + } catch (exception $e) { + } + + return false; + } + + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argProvider='MSDASQL') + { + return $this->_connect($argHostname,$argUsername,$argPassword,$argProvider); + } + +/* + adSchemaCatalogs = 1, + adSchemaCharacterSets = 2, + adSchemaCollations = 3, + adSchemaColumns = 4, + adSchemaCheckConstraints = 5, + adSchemaConstraintColumnUsage = 6, + adSchemaConstraintTableUsage = 7, + adSchemaKeyColumnUsage = 8, + adSchemaReferentialContraints = 9, + adSchemaTableConstraints = 10, + adSchemaColumnsDomainUsage = 11, + adSchemaIndexes = 12, + adSchemaColumnPrivileges = 13, + adSchemaTablePrivileges = 14, + adSchemaUsagePrivileges = 15, + adSchemaProcedures = 16, + adSchemaSchemata = 17, + adSchemaSQLLanguages = 18, + adSchemaStatistics = 19, + adSchemaTables = 20, + adSchemaTranslations = 21, + adSchemaProviderTypes = 22, + adSchemaViews = 23, + adSchemaViewColumnUsage = 24, + adSchemaViewTableUsage = 25, + adSchemaProcedureParameters = 26, + adSchemaForeignKeys = 27, + adSchemaPrimaryKeys = 28, + adSchemaProcedureColumns = 29, + adSchemaDBInfoKeywords = 30, + adSchemaDBInfoLiterals = 31, + adSchemaCubes = 32, + adSchemaDimensions = 33, + adSchemaHierarchies = 34, + adSchemaLevels = 35, + adSchemaMeasures = 36, + adSchemaProperties = 37, + adSchemaMembers = 38 + +*/ + + function &MetaTables() + { + $arr= array(); + $dbc = $this->_connectionID; + + $adors=@$dbc->OpenSchema(20);//tables + if ($adors){ + $f = $adors->Fields(2);//table/view name + $t = $adors->Fields(3);//table type + while (!$adors->EOF){ + $tt=substr($t->value,0,6); + if ($tt!='SYSTEM' && $tt !='ACCESS') + $arr[]=$f->value; + //print $f->value . ' ' . $t->value.'<br>'; + $adors->MoveNext(); + } + $adors->Close(); + } + + return $arr; + } + + function &MetaColumns($table) + { + $table = strtoupper($table); + $arr= array(); + $dbc = $this->_connectionID; + + $adors=@$dbc->OpenSchema(4);//tables + + if ($adors){ + $t = $adors->Fields(2);//table/view name + while (!$adors->EOF){ + + + if (strtoupper($t->Value) == $table) { + + $fld = new ADOFieldObject(); + $c = $adors->Fields(3); + $fld->name = $c->Value; + $fld->type = 'CHAR'; // cannot discover type in ADO! + $fld->max_length = -1; + $arr[strtoupper($fld->name)]=$fld; + } + + $adors->MoveNext(); + } + $adors->Close(); + } + + return $arr; + } + + + + + /* returns queryID or false */ + function &_query($sql,$inputarr=false) + { + try { // In PHP5, all COM errors are exceptions, so to maintain old behaviour... + + $dbc = $this->_connectionID; + + // return rs + if ($inputarr) { + + if (!empty($this->charPage)) + $oCmd = new COM('ADODB.Command',null,$this->charPage); + else + $oCmd = new COM('ADODB.Command'); + $oCmd->ActiveConnection = $dbc; + $oCmd->CommandText = $sql; + $oCmd->CommandType = 1; + + foreach($inputarr as $val) { + // name, type, direction 1 = input, len, + $this->adoParameterType = 130; + $p = $oCmd->CreateParameter('name',$this->adoParameterType,1,strlen($val),$val); + //print $p->Type.' '.$p->value; + $oCmd->Parameters->Append($p); + } + $p = false; + $rs = $oCmd->Execute(); + $e = $dbc->Errors; + if ($dbc->Errors->Count > 0) return false; + return $rs; + } + + $rs = @$dbc->Execute($sql,$this->_affectedRows, $this->_execute_option); + + if ($dbc->Errors->Count > 0) return false; + if (! $rs) return false; + + if ($rs->State == 0) return true; // 0 = adStateClosed means no records returned + return $rs; + + } catch (exception $e) { + + } + return false; + } + + + function BeginTrans() + { + if ($this->transOff) return true; + + if (isset($this->_thisTransactions)) + if (!$this->_thisTransactions) return false; + else { + $o = $this->_connectionID->Properties("Transaction DDL"); + $this->_thisTransactions = $o ? true : false; + if (!$o) return false; + } + @$this->_connectionID->BeginTrans(); + $this->transCnt += 1; + return true; + } + function CommitTrans($ok=true) + { + if (!$ok) return $this->RollbackTrans(); + if ($this->transOff) return true; + + @$this->_connectionID->CommitTrans(); + if ($this->transCnt) @$this->transCnt -= 1; + return true; + } + function RollbackTrans() { + if ($this->transOff) return true; + @$this->_connectionID->RollbackTrans(); + if ($this->transCnt) @$this->transCnt -= 1; + return true; + } + + /* Returns: the last error message from previous database operation */ + + function ErrorMsg() + { + $errc = $this->_connectionID->Errors; + if ($errc->Count == 0) return ''; + $err = $errc->Item($errc->Count-1); + return $err->Description; + } + + function ErrorNo() + { + $errc = $this->_connectionID->Errors; + if ($errc->Count == 0) return 0; + $err = $errc->Item($errc->Count-1); + return $err->NativeError; + } + + // returns true or false + function _close() + { + if ($this->_connectionID) $this->_connectionID->Close(); + $this->_connectionID = false; + return true; + } + + +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordSet_ado extends ADORecordSet { + + var $bind = false; + var $databaseType = "ado"; + var $dataProvider = "ado"; + var $_tarr = false; // caches the types + var $_flds; // and field objects + var $canSeek = true; + var $hideErrors = true; + + function ADORecordSet_ado($id,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + $this->fetchMode = $mode; + return $this->ADORecordSet($id,$mode); + } + + + // returns the field object + function &FetchField($fieldOffset = -1) { + $off=$fieldOffset+1; // offsets begin at 1 + + $o= new ADOFieldObject(); + $rs = $this->_queryID; + $f = $rs->Fields($fieldOffset); + $o->name = $f->Name; + $t = $f->Type; + $o->type = $this->MetaType($t); + $o->max_length = $f->DefinedSize; + $o->ado_type = $t; + + + //print "off=$off name=$o->name type=$o->type len=$o->max_length<br>"; + return $o; + } + + /* Use associative array to get fields array */ + function Fields($colname) + { + if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname]; + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + + function _initrs() + { + $rs = $this->_queryID; + $this->_numOfRows = $rs->RecordCount; + + $f = $rs->Fields; + $this->_numOfFields = $f->Count; + } + + + // should only be used to move forward as we normally use forward-only cursors + function _seek($row) + { + $rs = $this->_queryID; + // absoluteposition doesn't work -- my maths is wrong ? + // $rs->AbsolutePosition->$row-2; + // return true; + if ($this->_currentRow > $row) return false; + @$rs->Move((integer)$row - $this->_currentRow-1); //adBookmarkFirst + return true; + } + +/* + OLEDB types + + enum DBTYPEENUM + { DBTYPE_EMPTY = 0, + DBTYPE_NULL = 1, + DBTYPE_I2 = 2, + DBTYPE_I4 = 3, + DBTYPE_R4 = 4, + DBTYPE_R8 = 5, + DBTYPE_CY = 6, + DBTYPE_DATE = 7, + DBTYPE_BSTR = 8, + DBTYPE_IDISPATCH = 9, + DBTYPE_ERROR = 10, + DBTYPE_BOOL = 11, + DBTYPE_VARIANT = 12, + DBTYPE_IUNKNOWN = 13, + DBTYPE_DECIMAL = 14, + DBTYPE_UI1 = 17, + DBTYPE_ARRAY = 0x2000, + DBTYPE_BYREF = 0x4000, + DBTYPE_I1 = 16, + DBTYPE_UI2 = 18, + DBTYPE_UI4 = 19, + DBTYPE_I8 = 20, + DBTYPE_UI8 = 21, + DBTYPE_GUID = 72, + DBTYPE_VECTOR = 0x1000, + DBTYPE_RESERVED = 0x8000, + DBTYPE_BYTES = 128, + DBTYPE_STR = 129, + DBTYPE_WSTR = 130, + DBTYPE_NUMERIC = 131, + DBTYPE_UDT = 132, + DBTYPE_DBDATE = 133, + DBTYPE_DBTIME = 134, + DBTYPE_DBTIMESTAMP = 135 + + ADO Types + + adEmpty = 0, + adTinyInt = 16, + adSmallInt = 2, + adInteger = 3, + adBigInt = 20, + adUnsignedTinyInt = 17, + adUnsignedSmallInt = 18, + adUnsignedInt = 19, + adUnsignedBigInt = 21, + adSingle = 4, + adDouble = 5, + adCurrency = 6, + adDecimal = 14, + adNumeric = 131, + adBoolean = 11, + adError = 10, + adUserDefined = 132, + adVariant = 12, + adIDispatch = 9, + adIUnknown = 13, + adGUID = 72, + adDate = 7, + adDBDate = 133, + adDBTime = 134, + adDBTimeStamp = 135, + adBSTR = 8, + adChar = 129, + adVarChar = 200, + adLongVarChar = 201, + adWChar = 130, + adVarWChar = 202, + adLongVarWChar = 203, + adBinary = 128, + adVarBinary = 204, + adLongVarBinary = 205, + adChapter = 136, + adFileTime = 64, + adDBFileTime = 137, + adPropVariant = 138, + adVarNumeric = 139 +*/ + function MetaType($t,$len=-1,$fieldobj=false) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + + if (!is_numeric($t)) return $t; + + switch ($t) { + case 0: + case 12: // variant + case 8: // bstr + case 129: //char + case 130: //wc + case 200: // varc + case 202:// varWC + case 128: // bin + case 204: // varBin + case 72: // guid + if ($len <= $this->blobSize) return 'C'; + + case 201: + case 203: + return 'X'; + case 128: + case 204: + case 205: + return 'B'; + case 7: + case 133: return 'D'; + + case 134: + case 135: return 'T'; + + case 11: return 'L'; + + case 16:// adTinyInt = 16, + case 2://adSmallInt = 2, + case 3://adInteger = 3, + case 4://adBigInt = 20, + case 17://adUnsignedTinyInt = 17, + case 18://adUnsignedSmallInt = 18, + case 19://adUnsignedInt = 19, + case 20://adUnsignedBigInt = 21, + return 'I'; + default: return 'N'; + } + } + + // time stamp not supported yet + function _fetch() + { + $rs = $this->_queryID; + if (!$rs or $rs->EOF) { + $this->fields = false; + return false; + } + $this->fields = array(); + + if (!$this->_tarr) { + $tarr = array(); + $flds = array(); + for ($i=0,$max = $this->_numOfFields; $i < $max; $i++) { + $f = $rs->Fields($i); + $flds[] = $f; + $tarr[] = $f->Type; + } + // bind types and flds only once + $this->_tarr = $tarr; + $this->_flds = $flds; + } + $t = reset($this->_tarr); + $f = reset($this->_flds); + + if ($this->hideErrors) $olde = error_reporting(E_ERROR|E_CORE_ERROR);// sometimes $f->value be null + for ($i=0,$max = $this->_numOfFields; $i < $max; $i++) { + //echo "<p>",$t,' ';var_dump($f->value); echo '</p>'; + switch($t) { + case 135: // timestamp + if (!strlen((string)$f->value)) $this->fields[] = false; + else { + if (!is_numeric($f->value)) # $val = variant_date_to_timestamp($f->value); + // VT_DATE stores dates as (float) fractional days since 1899/12/30 00:00:00 + $val= (float) variant_cast($f->value,VT_R8)*3600*24-2209161600; + else + $val = $f->value; + $this->fields[] = adodb_date('Y-m-d H:i:s',$val); + } + break; + case 133:// A date value (yyyymmdd) + if ($val = $f->value) { + $this->fields[] = substr($val,0,4).'-'.substr($val,4,2).'-'.substr($val,6,2); + } else + $this->fields[] = false; + break; + case 7: // adDate + if (!strlen((string)$f->value)) $this->fields[] = false; + else { + if (!is_numeric($f->value)) $val = variant_date_to_timestamp($f->value); + else $val = $f->value; + + if (($val % 86400) == 0) $this->fields[] = adodb_date('Y-m-d',$val); + else $this->fields[] = adodb_date('Y-m-d H:i:s',$val); + } + break; + case 1: // null + $this->fields[] = false; + break; + case 6: // currency is not supported properly; + ADOConnection::outp( '<b>'.$f->Name.': currency type not supported by PHP</b>'); + $this->fields[] = (float) $f->value; + break; + default: + $this->fields[] = $f->value; + break; + } + //print " $f->value $t, "; + $f = next($this->_flds); + $t = next($this->_tarr); + } // for + if ($this->hideErrors) error_reporting($olde); + @$rs->MoveNext(); // @ needed for some versions of PHP! + + if ($this->fetchMode & ADODB_FETCH_ASSOC) { + $this->fields = &$this->GetRowAssoc(ADODB_ASSOC_CASE); + } + return true; + } + + function NextRecordSet() + { + $rs = $this->_queryID; + $this->_queryID = $rs->NextRecordSet(); + //$this->_queryID = $this->_QueryId->NextRecordSet(); + if ($this->_queryID == null) return false; + + $this->_currentRow = -1; + $this->_currentPage = -1; + $this->bind = false; + $this->fields = false; + $this->_flds = false; + $this->_tarr = false; + + $this->_inited = false; + $this->Init(); + return true; + } + + function _close() { + $this->_flds = false; + @$this->_queryID->Close();// by Pete Dishman (peterd@telephonetics.co.uk) + $this->_queryID = false; + } + +} + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-ado_access.inc.php b/framework/DataAccess/adodb/drivers/adodb-ado_access.inc.php new file mode 100644 index 00000000..b4bee3dd --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-ado_access.inc.php @@ -0,0 +1,54 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. +Released under both BSD license and Lesser GPL library license. +Whenever there is any discrepancy between the two licenses, +the BSD license will take precedence. See License.txt. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Microsoft Access ADO data driver. Requires ADO and ODBC. Works only on MS Windows. +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (!defined('_ADODB_ADO_LAYER')) { + if (PHP_VERSION >= 5) include(ADODB_DIR."/drivers/adodb-ado5.inc.php"); + else include(ADODB_DIR."/drivers/adodb-ado.inc.php"); +} + +class ADODB_ado_access extends ADODB_ado { + var $databaseType = 'ado_access'; + var $hasTop = 'top'; // support mssql SELECT TOP 10 * FROM TABLE + var $fmtDate = "#Y-m-d#"; + var $fmtTimeStamp = "#Y-m-d h:i:sA#";// note no comma + var $sysDate = "FORMAT(NOW,'yyyy-mm-dd')"; + var $sysTimeStamp = 'NOW'; + var $hasTransactions = false; + + function ADODB_ado_access() + { + $this->ADODB_ado(); + } + + function BeginTrans() { return false;} + + function CommitTrans() { return false;} + + function RollbackTrans() { return false;} + +} + + +class ADORecordSet_ado_access extends ADORecordSet_ado { + + var $databaseType = "ado_access"; + + function ADORecordSet_ado_access($id,$mode=false) + { + return $this->ADORecordSet_ado($id,$mode); + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-ado_mssql.inc.php b/framework/DataAccess/adodb/drivers/adodb-ado_mssql.inc.php new file mode 100644 index 00000000..151b13fb --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-ado_mssql.inc.php @@ -0,0 +1,98 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Microsoft SQL Server ADO data driver. Requires ADO and MSSQL client. + Works only on MS Windows. + + It is normally better to use the mssql driver directly because it is much faster. + This file is only a technology demonstration and for test purposes. +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (!defined('_ADODB_ADO_LAYER')) { + if (PHP_VERSION >= 5) include(ADODB_DIR."/drivers/adodb-ado5.inc.php"); + else include(ADODB_DIR."/drivers/adodb-ado.inc.php"); +} + + +class ADODB_ado_mssql extends ADODB_ado { + var $databaseType = 'ado_mssql'; + var $hasTop = 'top'; + var $hasInsertID = true; + var $sysDate = 'convert(datetime,convert(char,GetDate(),102),102)'; + var $sysTimeStamp = 'GetDate()'; + var $leftOuter = '*='; + var $rightOuter = '=*'; + var $ansiOuter = true; // for mssql7 or later + var $substr = "substring"; + var $length = 'len'; + + //var $_inTransaction = 1; // always open recordsets, so no transaction problems. + + function ADODB_ado_mssql() + { + $this->ADODB_ado(); + } + + function _insertid() + { + return $this->GetOne('select @@identity'); + } + + function _affectedrows() + { + return $this->GetOne('select @@rowcount'); + } + + function MetaColumns($table) + { + $table = strtoupper($table); + $arr= array(); + $dbc = $this->_connectionID; + + $osoptions = array(); + $osoptions[0] = null; + $osoptions[1] = null; + $osoptions[2] = $table; + $osoptions[3] = null; + + $adors=@$dbc->OpenSchema(4, $osoptions);//tables + + if ($adors){ + while (!$adors->EOF){ + $fld = new ADOFieldObject(); + $c = $adors->Fields(3); + $fld->name = $c->Value; + $fld->type = 'CHAR'; // cannot discover type in ADO! + $fld->max_length = -1; + $arr[strtoupper($fld->name)]=$fld; + + $adors->MoveNext(); + } + $adors->Close(); + } + $false = false; + return empty($arr) ? $false : $arr; + } + + } // end class + + class ADORecordSet_ado_mssql extends ADORecordSet_ado { + + var $databaseType = 'ado_mssql'; + + function ADORecordSet_ado_mssql($id,$mode=false) + { + return $this->ADORecordSet_ado($id,$mode); + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-borland_ibase.inc.php b/framework/DataAccess/adodb/drivers/adodb-borland_ibase.inc.php new file mode 100644 index 00000000..ba9e6c18 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-borland_ibase.inc.php @@ -0,0 +1,91 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Support Borland Interbase 6.5 and later + +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +include_once(ADODB_DIR."/drivers/adodb-ibase.inc.php"); + +class ADODB_borland_ibase extends ADODB_ibase { + var $databaseType = "borland_ibase"; + + function ADODB_borland_ibase() + { + $this->ADODB_ibase(); + } + + function BeginTrans() + { + if ($this->transOff) return true; + $this->transCnt += 1; + $this->autoCommit = false; + $this->_transactionID = ibase_trans($this->ibasetrans, $this->_connectionID); + return $this->_transactionID; + } + + function ServerInfo() + { + $arr['dialect'] = $this->dialect; + switch($arr['dialect']) { + case '': + case '1': $s = 'Interbase 6.5, Dialect 1'; break; + case '2': $s = 'Interbase 6.5, Dialect 2'; break; + default: + case '3': $s = 'Interbase 6.5, Dialect 3'; break; + } + $arr['version'] = '6.5'; + $arr['description'] = $s; + return $arr; + } + + // Note that Interbase 6.5 uses ROWS instead - don't you love forking wars! + // SELECT col1, col2 FROM table ROWS 5 -- get 5 rows + // SELECT col1, col2 FROM TABLE ORDER BY col1 ROWS 3 TO 7 -- first 5 skip 2 + // Firebird uses + // SELECT FIRST 5 SKIP 2 col1, col2 FROM TABLE + function &SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0) + { + if ($nrows > 0) { + if ($offset <= 0) $str = " ROWS $nrows "; + else { + $a = $offset+1; + $b = $offset+$nrows; + $str = " ROWS $a TO $b"; + } + } else { + // ok, skip + $a = $offset + 1; + $str = " ROWS $a TO 999999999"; // 999 million + } + $sql .= $str; + + return ($secs2cache) ? + $this->CacheExecute($secs2cache,$sql,$inputarr) + : + $this->Execute($sql,$inputarr); + } + +}; + + +class ADORecordSet_borland_ibase extends ADORecordSet_ibase { + + var $databaseType = "borland_ibase"; + + function ADORecordSet_borland_ibase($id,$mode=false) + { + $this->ADORecordSet_ibase($id,$mode); + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-csv.inc.php b/framework/DataAccess/adodb/drivers/adodb-csv.inc.php new file mode 100644 index 00000000..6f1d04d6 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-csv.inc.php @@ -0,0 +1,207 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 4. + + Currently unsupported: MetaDatabases, MetaTables and MetaColumns, and also inputarr in Execute. + Native types have been converted to MetaTypes. + Transactions not supported yet. + + Limitation of url length. For IIS, see MaxClientRequestBuffer registry value. + + http://support.microsoft.com/default.aspx?scid=kb;en-us;260694 +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (! defined("_ADODB_CSV_LAYER")) { + define("_ADODB_CSV_LAYER", 1 ); + +include_once(ADODB_DIR.'/adodb-csvlib.inc.php'); + +class ADODB_csv extends ADOConnection { + var $databaseType = 'csv'; + var $databaseProvider = 'csv'; + var $hasInsertID = true; + var $hasAffectedRows = true; + var $fmtTimeStamp = "'Y-m-d H:i:s'"; + var $_affectedrows=0; + var $_insertid=0; + var $_url; + var $replaceQuote = "''"; // string to use to replace quotes + var $hasTransactions = false; + var $_errorNo = false; + + function ADODB_csv() + { + } + + function _insertid() + { + return $this->_insertid; + } + + function _affectedrows() + { + return $this->_affectedrows; + } + + function &MetaDatabases() + { + return false; + } + + + // returns true or false + function _connect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + if (strtolower(substr($argHostname,0,7)) !== 'http://') return false; + $this->_url = $argHostname; + return true; + } + + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + if (strtolower(substr($argHostname,0,7)) !== 'http://') return false; + $this->_url = $argHostname; + return true; + } + + function &MetaColumns($table) + { + return false; + } + + + // parameters use PostgreSQL convention, not MySQL + function &SelectLimit($sql,$nrows=-1,$offset=-1) + { + global $ADODB_FETCH_MODE; + + $url = $this->_url.'?sql='.urlencode($sql)."&nrows=$nrows&fetch=". + (($this->fetchMode !== false)?$this->fetchMode : $ADODB_FETCH_MODE). + "&offset=$offset"; + $err = false; + $rs = csv2rs($url,$err,false); + + if ($this->debug) print "$url<br><i>$err</i><br>"; + + $at = strpos($err,'::::'); + if ($at === false) { + $this->_errorMsg = $err; + $this->_errorNo = (integer)$err; + } else { + $this->_errorMsg = substr($err,$at+4,1024); + $this->_errorNo = -9999; + } + if ($this->_errorNo) + if ($fn = $this->raiseErrorFn) { + $fn($this->databaseType,'EXECUTE',$this->ErrorNo(),$this->ErrorMsg(),$sql,''); + } + + if (is_object($rs)) { + + $rs->databaseType='csv'; + $rs->fetchMode = ($this->fetchMode !== false) ? $this->fetchMode : $ADODB_FETCH_MODE; + $rs->connection = &$this; + } + return $rs; + } + + // returns queryID or false + function &_Execute($sql,$inputarr=false) + { + global $ADODB_FETCH_MODE; + + if (!$this->_bindInputArray && $inputarr) { + $sqlarr = explode('?',$sql); + $sql = ''; + $i = 0; + foreach($inputarr as $v) { + + $sql .= $sqlarr[$i]; + if (gettype($v) == 'string') + $sql .= $this->qstr($v); + else if ($v === null) + $sql .= 'NULL'; + else + $sql .= $v; + $i += 1; + + } + $sql .= $sqlarr[$i]; + if ($i+1 != sizeof($sqlarr)) + print "Input Array does not match ?: ".htmlspecialchars($sql); + $inputarr = false; + } + + $url = $this->_url.'?sql='.urlencode($sql)."&fetch=". + (($this->fetchMode !== false)?$this->fetchMode : $ADODB_FETCH_MODE); + $err = false; + + + $rs = csv2rs($url,$err,false); + if ($this->debug) print urldecode($url)."<br><i>$err</i><br>"; + $at = strpos($err,'::::'); + if ($at === false) { + $this->_errorMsg = $err; + $this->_errorNo = (integer)$err; + } else { + $this->_errorMsg = substr($err,$at+4,1024); + $this->_errorNo = -9999; + } + + if ($this->_errorNo) + if ($fn = $this->raiseErrorFn) { + $fn($this->databaseType,'EXECUTE',$this->ErrorNo(),$this->ErrorMsg(),$sql,$inputarr); + } + if (is_object($rs)) { + $rs->fetchMode = ($this->fetchMode !== false) ? $this->fetchMode : $ADODB_FETCH_MODE; + + $this->_affectedrows = $rs->affectedrows; + $this->_insertid = $rs->insertid; + $rs->databaseType='csv'; + $rs->connection = &$this; + } + return $rs; + } + + /* Returns: the last error message from previous database operation */ + function ErrorMsg() + { + return $this->_errorMsg; + } + + /* Returns: the last error number from previous database operation */ + function ErrorNo() + { + return $this->_errorNo; + } + + // returns true or false + function _close() + { + return true; + } +} // class + +class ADORecordset_csv extends ADORecordset { + function ADORecordset_csv($id,$mode=false) + { + $this->ADORecordset($id,$mode); + } + + function _close() + { + return true; + } +} + +} // define + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-db2.inc.php b/framework/DataAccess/adodb/drivers/adodb-db2.inc.php new file mode 100644 index 00000000..dc732ac3 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-db2.inc.php @@ -0,0 +1,687 @@ +<?php +/* + V4.72 21 Feb 2006 (c) 2006 John Lim (jlim@natsoft.com.my). All rights reserved. + +This is a version of the ADODB driver for DB2. It uses the 'ibm_db2' PECL extension for PHP + (http://pecl.php.net/package/ibm_db2), which in turn requires DB2 V8.2.2. + + Tested with PHP 5.1.1 and Apache 2.0.55 on Windows XP SP2. + + This file was ported from "adodb-odbc.inc.php" by Larry Menard, "larry.menard@rogers.com". + I ripped out what I believed to be a lot of redundant or obsolete code, but there are + probably still some remnants of the ODBC support in this file; I'm relying on reviewers + of this code to point out any other things that can be removed. +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + + define("_ADODB_DB2_LAYER", 2 ); + +/*-------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------*/ + + +class ADODB_db2 extends ADOConnection { + var $databaseType = "db2"; + var $fmtDate = "'Y-m-d'"; + var $fmtTimeStamp = "'Y-m-d, h:i:sA'"; + var $replaceQuote = "''"; // string to use to replace quotes + var $dataProvider = "db2"; + var $hasAffectedRows = true; + + var $binmode = DB2_BINARY; + + var $useFetchArray = false; // setting this to true will make array elements in FETCH_ASSOC mode case-sensitive + // breaking backward-compat + var $_bindInputArray = false; + var $_genSeqSQL = "create table %s (id integer)"; + var $_autocommit = true; + var $_haserrorfunctions = true; + var $_lastAffectedRows = 0; + var $uCaseTables = true; // for meta* functions, uppercase table names + + function ADODB_db2() + { + $this->_haserrorfunctions = ADODB_PHPVER >= 0x4050; + } + + // returns true or false + function _connect($argDSN, $argUsername, $argPassword, $argDatabasename) + { + global $php_errormsg; + + if (!function_exists('db2_connect')) { + ADOConnection::outp("Warning: The old ODBC based DB2 driver has been renamed 'odbc_db2'. This ADOdb driver calls PHP's native db2 extension."); + return null; + } + // This needs to be set before the connect(). + // Replaces the odbc_binmode() call that was in Execute() + ini_set('ibm_db2.binmode', $this->binmode); + + if ($argDatabasename) { + $this->_connectionID = db2_connect($argDatabasename,$argUsername,$argPassword); + } else { + $this->_connectionID = db2_connect($argDSN,$argUsername,$argPassword); + } + if (isset($php_errormsg)) $php_errormsg = ''; + + // For db2_connect(), there is an optional 4th arg. If present, it must be + // an array of valid options. So far, we don't use them. + + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; + if (isset($this->connectStmt)) $this->Execute($this->connectStmt); + + return $this->_connectionID != false; + } + + // returns true or false + function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename) + { + global $php_errormsg; + + if (!function_exists('db2_connect')) return null; + + // This needs to be set before the connect(). + // Replaces the odbc_binmode() call that was in Execute() + ini_set('ibm_db2.binmode', $this->binmode); + + if (isset($php_errormsg)) $php_errormsg = ''; + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; + + if ($argDatabasename) { + $this->_connectionID = db2_pconnect($argDatabasename,$argUsername,$argPassword); + } else { + $this->_connectionID = db2_pconnect($argDSN,$argUsername,$argPassword); + } + if (isset($php_errormsg)) $php_errormsg = ''; + + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; + if ($this->_connectionID && $this->autoRollback) @db2_rollback($this->_connectionID); + if (isset($this->connectStmt)) $this->Execute($this->connectStmt); + + return $this->_connectionID != false; + } + + + function ServerInfo() + { + + if (!empty($this->host) && ADODB_PHPVER >= 0x4300) { + $dsn = strtoupper($this->host); + $first = true; + $found = false; + + if (!function_exists('db2_data_source')) return false; + + while(true) { + + $rez = @db2_data_source($this->_connectionID, + $first ? SQL_FETCH_FIRST : SQL_FETCH_NEXT); + $first = false; + if (!is_array($rez)) break; + if (strtoupper($rez['server']) == $dsn) { + $found = true; + break; + } + } + if (!$found) return ADOConnection::ServerInfo(); + if (!isset($rez['version'])) $rez['version'] = ''; + return $rez; + } else { + return ADOConnection::ServerInfo(); + } + } + + + function CreateSequence($seqname='adodbseq',$start=1) + { + if (empty($this->_genSeqSQL)) return false; + $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname)); + if (!$ok) return false; + $start -= 1; + return $this->Execute("insert into $seqname values($start)"); + } + + var $_dropSeqSQL = 'drop table %s'; + function DropSequence($seqname) + { + if (empty($this->_dropSeqSQL)) return false; + return $this->Execute(sprintf($this->_dropSeqSQL,$seqname)); + } + + /* + This algorithm is not very efficient, but works even if table locking + is not available. + + Will return false if unable to generate an ID after $MAXLOOPS attempts. + */ + function GenID($seq='adodbseq',$start=1) + { + // if you have to modify the parameter below, your database is overloaded, + // or you need to implement generation of id's yourself! + $MAXLOOPS = 100; + while (--$MAXLOOPS>=0) { + $num = $this->GetOne("select id from $seq"); + if ($num === false) { + $this->Execute(sprintf($this->_genSeqSQL ,$seq)); + $start -= 1; + $num = '0'; + $ok = $this->Execute("insert into $seq values($start)"); + if (!$ok) return false; + } + $this->Execute("update $seq set id=id+1 where id=$num"); + + if ($this->affected_rows() > 0) { + $num += 1; + $this->genID = $num; + return $num; + } + } + if ($fn = $this->raiseErrorFn) { + $fn($this->databaseType,'GENID',-32000,"Unable to generate unique id after $MAXLOOPS attempts",$seq,$num); + } + return false; + } + + + function ErrorMsg() + { + if ($this->_haserrorfunctions) { + if ($this->_errorMsg !== false) return $this->_errorMsg; + if (empty($this->_connectionID)) return @db2_errormsg(); + return @db2_errormsg($this->_connectionID); + } else return ADOConnection::ErrorMsg(); + } + + function ErrorNo() + { + + if ($this->_haserrorfunctions) { + if ($this->_errorCode !== false) { + // bug in 4.0.6, error number can be corrupted string (should be 6 digits) + return (strlen($this->_errorCode)<=2) ? 0 : $this->_errorCode; + } + + if (empty($this->_connectionID)) $e = @db2_error(); + else $e = @db2_error($this->_connectionID); + + // bug in 4.0.6, error number can be corrupted string (should be 6 digits) + // so we check and patch + if (strlen($e)<=2) return 0; + return $e; + } else return ADOConnection::ErrorNo(); + } + + + + function BeginTrans() + { + if (!$this->hasTransactions) return false; + if ($this->transOff) return true; + $this->transCnt += 1; + $this->_autocommit = false; + return db2_autocommit($this->_connectionID,false); + } + + function CommitTrans($ok=true) + { + if ($this->transOff) return true; + if (!$ok) return $this->RollbackTrans(); + if ($this->transCnt) $this->transCnt -= 1; + $this->_autocommit = true; + $ret = db2_commit($this->_connectionID); + db2_autocommit($this->_connectionID,true); + return $ret; + } + + function RollbackTrans() + { + if ($this->transOff) return true; + if ($this->transCnt) $this->transCnt -= 1; + $this->_autocommit = true; + $ret = db2_rollback($this->_connectionID); + db2_autocommit($this->_connectionID,true); + return $ret; + } + + function MetaPrimaryKeys($table) + { + global $ADODB_FETCH_MODE; + + if ($this->uCaseTables) $table = strtoupper($table); + $schema = ''; + $this->_findschema($table,$schema); + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $qid = @db2_primarykeys($this->_connectionID,'',$schema,$table); + + if (!$qid) { + $ADODB_FETCH_MODE = $savem; + return false; + } + $rs = new ADORecordSet_db2($qid); + $ADODB_FETCH_MODE = $savem; + + if (!$rs) return false; + + $arr =& $rs->GetArray(); + $rs->Close(); + $arr2 = array(); + for ($i=0; $i < sizeof($arr); $i++) { + if ($arr[$i][3]) $arr2[] = $arr[$i][3]; + } + return $arr2; + } + + + + function &MetaTables($ttype=false) + { + global $ADODB_FETCH_MODE; + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $qid = db2_tables($this->_connectionID); + + $rs = new ADORecordSet_db2($qid); + + $ADODB_FETCH_MODE = $savem; + if (!$rs) { + $false = false; + return $false; + } + + $arr =& $rs->GetArray(); + + $rs->Close(); + $arr2 = array(); + + if ($ttype) { + $isview = strncmp($ttype,'V',1) === 0; + } + for ($i=0; $i < sizeof($arr); $i++) { + if (!$arr[$i][2]) continue; + $type = $arr[$i][3]; + if ($ttype) { + if ($isview) { + if (strncmp($type,'V',1) === 0) $arr2[] = $arr[$i][2]; + } else if (strncmp($type,'SYS',3) !== 0) $arr2[] = $arr[$i][2]; + } else if (strncmp($type,'SYS',3) !== 0) $arr2[] = $arr[$i][2]; + } + return $arr2; + } + +/* +See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/db2/htm/db2datetime_data_type_changes.asp +/ SQL data type codes / +#define SQL_UNKNOWN_TYPE 0 +#define SQL_CHAR 1 +#define SQL_NUMERIC 2 +#define SQL_DECIMAL 3 +#define SQL_INTEGER 4 +#define SQL_SMALLINT 5 +#define SQL_FLOAT 6 +#define SQL_REAL 7 +#define SQL_DOUBLE 8 +#if (DB2VER >= 0x0300) +#define SQL_DATETIME 9 +#endif +#define SQL_VARCHAR 12 + + +/ One-parameter shortcuts for date/time data types / +#if (DB2VER >= 0x0300) +#define SQL_TYPE_DATE 91 +#define SQL_TYPE_TIME 92 +#define SQL_TYPE_TIMESTAMP 93 + +#define SQL_UNICODE (-95) +#define SQL_UNICODE_VARCHAR (-96) +#define SQL_UNICODE_LONGVARCHAR (-97) +*/ + function DB2Types($t) + { + switch ((integer)$t) { + case 1: + case 12: + case 0: + case -95: + case -96: + return 'C'; + case -97: + case -1: //text + return 'X'; + case -4: //image + return 'B'; + + case 9: + case 91: + return 'D'; + + case 10: + case 11: + case 92: + case 93: + return 'T'; + + case 4: + case 5: + case -6: + return 'I'; + + case -11: // uniqidentifier + return 'R'; + case -7: //bit + return 'L'; + + default: + return 'N'; + } + } + + function &MetaColumns($table) + { + global $ADODB_FETCH_MODE; + + $false = false; + if ($this->uCaseTables) $table = strtoupper($table); + $schema = ''; + $this->_findschema($table,$schema); + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + + $colname = "%"; + $qid = db2_columns($this->_connectionID, "", $schema, $table, $colname); + if (empty($qid)) return $false; + + $rs =& new ADORecordSet_db2($qid); + $ADODB_FETCH_MODE = $savem; + + if (!$rs) return $false; + $rs->_fetch(); + + $retarr = array(); + + /* + $rs->fields indices + 0 TABLE_QUALIFIER + 1 TABLE_SCHEM + 2 TABLE_NAME + 3 COLUMN_NAME + 4 DATA_TYPE + 5 TYPE_NAME + 6 PRECISION + 7 LENGTH + 8 SCALE + 9 RADIX + 10 NULLABLE + 11 REMARKS + */ + while (!$rs->EOF) { + if (strtoupper(trim($rs->fields[2])) == $table && (!$schema || strtoupper($rs->fields[1]) == $schema)) { + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[3]; + $fld->type = $this->DB2Types($rs->fields[4]); + + // ref: http://msdn.microsoft.com/library/default.asp?url=/archive/en-us/dnaraccgen/html/msdn_odk.asp + // access uses precision to store length for char/varchar + if ($fld->type == 'C' or $fld->type == 'X') { + if ($rs->fields[4] <= -95) // UNICODE + $fld->max_length = $rs->fields[7]/2; + else + $fld->max_length = $rs->fields[7]; + } else + $fld->max_length = $rs->fields[7]; + $fld->not_null = !empty($rs->fields[10]); + $fld->scale = $rs->fields[8]; + $retarr[strtoupper($fld->name)] = $fld; + } else if (sizeof($retarr)>0) + break; + $rs->MoveNext(); + } + $rs->Close(); //-- crashes 4.03pl1 -- why? + + if (empty($retarr)) $retarr = false; + return $retarr; + } + + function Prepare($sql) + { + if (! $this->_bindInputArray) return $sql; // no binding + $stmt = db2_prepare($this->_connectionID,$sql); + if (!$stmt) { + // we don't know whether db2 driver is parsing prepared stmts, so just return sql + return $sql; + } + return array($sql,$stmt,false); + } + + /* returns queryID or false */ + function _query($sql,$inputarr=false) + { + GLOBAL $php_errormsg; + if (isset($php_errormsg)) $php_errormsg = ''; + $this->_error = ''; + + if ($inputarr) { + if (is_array($sql)) { + $stmtid = $sql[1]; + } else { + $stmtid = db2_prepare($this->_connectionID,$sql); + + if ($stmtid == false) { + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; + return false; + } + } + + if (! db2_execute($stmtid,$inputarr)) { + if ($this->_haserrorfunctions) { + $this->_errorMsg = db2_errormsg(); + $this->_errorCode = db2_error(); + } + return false; + } + + } else if (is_array($sql)) { + $stmtid = $sql[1]; + if (!db2_execute($stmtid)) { + if ($this->_haserrorfunctions) { + $this->_errorMsg = db2_errormsg(); + $this->_errorCode = db2_error(); + } + return false; + } + } else + $stmtid = db2_exec($this->_connectionID,$sql); + + $this->_lastAffectedRows = 0; + if ($stmtid) { + if (@db2_num_fields($stmtid) == 0) { + $this->_lastAffectedRows = db2_num_rows($stmtid); + $stmtid = true; + } else { + $this->_lastAffectedRows = 0; + } + + if ($this->_haserrorfunctions) { + $this->_errorMsg = ''; + $this->_errorCode = 0; + } else + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; + } else { + if ($this->_haserrorfunctions) { + $this->_errorMsg = db2_stmt_errormsg(); + $this->_errorCode = db2_stmt_error(); + } else + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; + } + return $stmtid; + } + + /* + Insert a null into the blob field of the table first. + Then use UpdateBlob to store the blob. + + Usage: + + $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)'); + $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1'); + */ + function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') + { + return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false; + } + + // returns true or false + function _close() + { + $ret = @db2_close($this->_connectionID); + $this->_connectionID = false; + return $ret; + } + + function _affectedrows() + { + return $this->_lastAffectedRows; + } + +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordSet_db2 extends ADORecordSet { + + var $bind = false; + var $databaseType = "db2"; + var $dataProvider = "db2"; + var $useFetchArray; + + function ADORecordSet_db2($id,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + $this->fetchMode = $mode; + + $this->_queryID = $id; + } + + + // returns the field object + function &FetchField($fieldOffset = -1) + { + + $off=$fieldOffset+1; // offsets begin at 1 + + $o= new ADOFieldObject(); + $o->name = @db2_field_name($this->_queryID,$off); + $o->type = @db2_field_type($this->_queryID,$off); + $o->max_length = db2_field_width($this->_queryID,$off); + if (ADODB_ASSOC_CASE == 0) $o->name = strtolower($o->name); + else if (ADODB_ASSOC_CASE == 1) $o->name = strtoupper($o->name); + return $o; + } + + /* Use associative array to get fields array */ + function Fields($colname) + { + if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname]; + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + + function _initrs() + { + global $ADODB_COUNTRECS; + $this->_numOfRows = ($ADODB_COUNTRECS) ? @db2_num_rows($this->_queryID) : -1; + $this->_numOfFields = @db2_num_fields($this->_queryID); + // some silly drivers such as db2 as/400 and intersystems cache return _numOfRows = 0 + if ($this->_numOfRows == 0) $this->_numOfRows = -1; + } + + function _seek($row) + { + return false; + } + + // speed up SelectLimit() by switching to ADODB_FETCH_NUM as ADODB_FETCH_ASSOC is emulated + function &GetArrayLimit($nrows,$offset=-1) + { + if ($offset <= 0) { + $rs =& $this->GetArray($nrows); + return $rs; + } + $savem = $this->fetchMode; + $this->fetchMode = ADODB_FETCH_NUM; + $this->Move($offset); + $this->fetchMode = $savem; + + if ($this->fetchMode & ADODB_FETCH_ASSOC) { + $this->fields =& $this->GetRowAssoc(ADODB_ASSOC_CASE); + } + + $results = array(); + $cnt = 0; + while (!$this->EOF && $nrows != $cnt) { + $results[$cnt++] = $this->fields; + $this->MoveNext(); + } + + return $results; + } + + + function MoveNext() + { + if ($this->_numOfRows != 0 && !$this->EOF) { + $this->_currentRow++; + + $this->fields = @db2_fetch_array($this->_queryID); + if ($this->fields) { + if ($this->fetchMode & ADODB_FETCH_ASSOC) { + $this->fields =& $this->GetRowAssoc(ADODB_ASSOC_CASE); + } + return true; + } + } + $this->fields = false; + $this->EOF = true; + return false; + } + + function _fetch() + { + + $this->fields = db2_fetch_array($this->_queryID); + if ($this->fields) { + if ($this->fetchMode & ADODB_FETCH_ASSOC) { + $this->fields =& $this->GetRowAssoc(ADODB_ASSOC_CASE); + } + return true; + } + $this->fields = false; + return false; + } + + function _close() + { + return @db2_free_result($this->_queryID); + } + +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-fbsql.inc.php b/framework/DataAccess/adodb/drivers/adodb-fbsql.inc.php new file mode 100644 index 00000000..e085ae2c --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-fbsql.inc.php @@ -0,0 +1,266 @@ +<?php +/* + @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Contribution by Frank M. Kromann <frank@frontbase.com>. + Set tabs to 8. +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (! defined("_ADODB_FBSQL_LAYER")) { + define("_ADODB_FBSQL_LAYER", 1 ); + +class ADODB_fbsql extends ADOConnection { + var $databaseType = 'fbsql'; + var $hasInsertID = true; + var $hasAffectedRows = true; + var $metaTablesSQL = "SHOW TABLES"; + var $metaColumnsSQL = "SHOW COLUMNS FROM %s"; + var $fmtTimeStamp = "'Y-m-d H:i:s'"; + var $hasLimit = false; + + function ADODB_fbsql() + { + } + + function _insertid() + { + return fbsql_insert_id($this->_connectionID); + } + + function _affectedrows() + { + return fbsql_affected_rows($this->_connectionID); + } + + function &MetaDatabases() + { + $qid = fbsql_list_dbs($this->_connectionID); + $arr = array(); + $i = 0; + $max = fbsql_num_rows($qid); + while ($i < $max) { + $arr[] = fbsql_tablename($qid,$i); + $i += 1; + } + return $arr; + } + + // returns concatenated string + function Concat() + { + $s = ""; + $arr = func_get_args(); + $first = true; + + $s = implode(',',$arr); + if (sizeof($arr) > 0) return "CONCAT($s)"; + else return ''; + } + + // returns true or false + function _connect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + $this->_connectionID = fbsql_connect($argHostname,$argUsername,$argPassword); + if ($this->_connectionID === false) return false; + if ($argDatabasename) return $this->SelectDB($argDatabasename); + return true; + } + + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + $this->_connectionID = fbsql_pconnect($argHostname,$argUsername,$argPassword); + if ($this->_connectionID === false) return false; + if ($argDatabasename) return $this->SelectDB($argDatabasename); + return true; + } + + function &MetaColumns($table) + { + if ($this->metaColumnsSQL) { + + $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table)); + + if ($rs === false) return false; + + $retarr = array(); + while (!$rs->EOF){ + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[0]; + $fld->type = $rs->fields[1]; + + // split type into type(length): + if (preg_match("/^(.+)\((\d+)\)$/", $fld->type, $query_array)) { + $fld->type = $query_array[1]; + $fld->max_length = $query_array[2]; + } else { + $fld->max_length = -1; + } + $fld->not_null = ($rs->fields[2] != 'YES'); + $fld->primary_key = ($rs->fields[3] == 'PRI'); + $fld->auto_increment = (strpos($rs->fields[5], 'auto_increment') !== false); + $fld->binary = (strpos($fld->type,'blob') !== false); + + $retarr[strtoupper($fld->name)] = $fld; + $rs->MoveNext(); + } + $rs->Close(); + return $retarr; + } + return false; + } + + // returns true or false + function SelectDB($dbName) + { + $this->database = $dbName; + if ($this->_connectionID) { + return @fbsql_select_db($dbName,$this->_connectionID); + } + else return false; + } + + + // returns queryID or false + function _query($sql,$inputarr) + { + return fbsql_query("$sql;",$this->_connectionID); + } + + /* Returns: the last error message from previous database operation */ + function ErrorMsg() + { + $this->_errorMsg = @fbsql_error($this->_connectionID); + return $this->_errorMsg; + } + + /* Returns: the last error number from previous database operation */ + function ErrorNo() + { + return @fbsql_errno($this->_connectionID); + } + + // returns true or false + function _close() + { + return @fbsql_close($this->_connectionID); + } + +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordSet_fbsql extends ADORecordSet{ + + var $databaseType = "fbsql"; + var $canSeek = true; + + function ADORecordSet_fbsql($queryID,$mode=false) + { + if (!$mode) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + switch ($mode) { + case ADODB_FETCH_NUM: $this->fetchMode = FBSQL_NUM; break; + case ADODB_FETCH_ASSOC: $this->fetchMode = FBSQL_ASSOC; break; + case ADODB_FETCH_BOTH: + default: + $this->fetchMode = FBSQL_BOTH; break; + } + return $this->ADORecordSet($queryID); + } + + function _initrs() + { + GLOBAL $ADODB_COUNTRECS; + $this->_numOfRows = ($ADODB_COUNTRECS) ? @fbsql_num_rows($this->_queryID):-1; + $this->_numOfFields = @fbsql_num_fields($this->_queryID); + } + + + + function &FetchField($fieldOffset = -1) { + if ($fieldOffset != -1) { + $o = @fbsql_fetch_field($this->_queryID, $fieldOffset); + //$o->max_length = -1; // fbsql returns the max length less spaces -- so it is unrealiable + $f = @fbsql_field_flags($this->_queryID,$fieldOffset); + $o->binary = (strpos($f,'binary')!== false); + } + else if ($fieldOffset == -1) { /* The $fieldOffset argument is not provided thus its -1 */ + $o = @fbsql_fetch_field($this->_queryID);// fbsql returns the max length less spaces -- so it is unrealiable + //$o->max_length = -1; + } + + return $o; + } + + function _seek($row) + { + return @fbsql_data_seek($this->_queryID,$row); + } + + function _fetch($ignore_fields=false) + { + $this->fields = @fbsql_fetch_array($this->_queryID,$this->fetchMode); + return ($this->fields == true); + } + + function _close() { + return @fbsql_free_result($this->_queryID); + } + + function MetaType($t,$len=-1,$fieldobj=false) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + $len = -1; // fbsql max_length is not accurate + switch (strtoupper($t)) { + case 'CHARACTER': + case 'CHARACTER VARYING': + case 'BLOB': + case 'CLOB': + case 'BIT': + case 'BIT VARYING': + if ($len <= $this->blobSize) return 'C'; + + // so we have to check whether binary... + case 'IMAGE': + case 'LONGBLOB': + case 'BLOB': + case 'MEDIUMBLOB': + return !empty($fieldobj->binary) ? 'B' : 'X'; + + case 'DATE': return 'D'; + + case 'TIME': + case 'TIME WITH TIME ZONE': + case 'TIMESTAMP': + case 'TIMESTAMP WITH TIME ZONE': return 'T'; + + case 'PRIMARY_KEY': + return 'R'; + case 'INTEGER': + case 'SMALLINT': + case 'BOOLEAN': + + if (!empty($fieldobj->primary_key)) return 'R'; + else return 'I'; + + default: return 'N'; + } + } + +} //class +} // defined +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-firebird.inc.php b/framework/DataAccess/adodb/drivers/adodb-firebird.inc.php new file mode 100644 index 00000000..fccd11f9 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-firebird.inc.php @@ -0,0 +1,77 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +include_once(ADODB_DIR."/drivers/adodb-ibase.inc.php"); + +class ADODB_firebird extends ADODB_ibase { + var $databaseType = "firebird"; + var $dialect = 3; + + var $sysTimeStamp = "cast('NOW' as timestamp)"; + + function ADODB_firebird() + { + $this->ADODB_ibase(); + } + + function ServerInfo() + { + $arr['dialect'] = $this->dialect; + switch($arr['dialect']) { + case '': + case '1': $s = 'Firebird Dialect 1'; break; + case '2': $s = 'Firebird Dialect 2'; break; + default: + case '3': $s = 'Firebird Dialect 3'; break; + } + $arr['version'] = ADOConnection::_findvers($s); + $arr['description'] = $s; + return $arr; + } + + // Note that Interbase 6.5 uses this ROWS instead - don't you love forking wars! + // SELECT col1, col2 FROM table ROWS 5 -- get 5 rows + // SELECT col1, col2 FROM TABLE ORDER BY col1 ROWS 3 TO 7 -- first 5 skip 2 + function &SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false, $secs=0) + { + $nrows = (integer) $nrows; + $offset = (integer) $offset; + $str = 'SELECT '; + if ($nrows >= 0) $str .= "FIRST $nrows "; + $str .=($offset>=0) ? "SKIP $offset " : ''; + + $sql = preg_replace('/^[ \t]*select/i',$str,$sql); + if ($secs) + $rs =& $this->CacheExecute($secs,$sql,$inputarr); + else + $rs =& $this->Execute($sql,$inputarr); + + return $rs; + } + + +}; + + +class ADORecordSet_firebird extends ADORecordSet_ibase { + + var $databaseType = "firebird"; + + function ADORecordSet_firebird($id,$mode=false) + { + $this->ADORecordSet_ibase($id,$mode); + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-ibase.inc.php b/framework/DataAccess/adodb/drivers/adodb-ibase.inc.php new file mode 100644 index 00000000..8a42540a --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-ibase.inc.php @@ -0,0 +1,861 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + + Latest version is available at http://adodb.sourceforge.net + + Interbase data driver. Requires interbase client. Works on Windows and Unix. + + 3 Jan 2002 -- suggestions by Hans-Peter Oeri <kampfcaspar75@oeri.ch> + changed transaction handling and added experimental blob stuff + + Docs to interbase at the website + http://www.synectics.co.za/php3/tutorial/IB_PHP3_API.html + + To use gen_id(), see + http://www.volny.cz/iprenosil/interbase/ip_ib_code.htm#_code_creategen + + $rs = $conn->Execute('select gen_id(adodb,1) from rdb$database'); + $id = $rs->fields[0]; + $conn->Execute("insert into table (id, col1,...) values ($id, $val1,...)"); +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +class ADODB_ibase extends ADOConnection { + var $databaseType = "ibase"; + var $dataProvider = "ibase"; + var $replaceQuote = "''"; // string to use to replace quotes + var $ibase_datefmt = '%Y-%m-%d'; // For hours,mins,secs change to '%Y-%m-%d %H:%M:%S'; + var $fmtDate = "'Y-m-d'"; + var $ibase_timestampfmt = "%Y-%m-%d %H:%M:%S"; + var $ibase_timefmt = "%H:%M:%S"; + var $fmtTimeStamp = "'Y-m-d, H:i:s'"; + var $concat_operator='||'; + var $_transactionID; + var $metaTablesSQL = "select rdb\$relation_name from rdb\$relations where rdb\$relation_name not like 'RDB\$%'"; + //OPN STUFF start + var $metaColumnsSQL = "select a.rdb\$field_name, a.rdb\$null_flag, a.rdb\$default_source, b.rdb\$field_length, b.rdb\$field_scale, b.rdb\$field_sub_type, b.rdb\$field_precision, b.rdb\$field_type from rdb\$relation_fields a, rdb\$fields b where a.rdb\$field_source = b.rdb\$field_name and a.rdb\$relation_name = '%s' order by a.rdb\$field_position asc"; + //OPN STUFF end + var $ibasetrans; + var $hasGenID = true; + var $_bindInputArray = true; + var $buffers = 0; + var $dialect = 1; + var $sysDate = "cast('TODAY' as timestamp)"; + var $sysTimeStamp = "cast('NOW' as timestamp)"; + var $ansiOuter = true; + var $hasAffectedRows = false; + var $poorAffectedRows = true; + var $blobEncodeType = 'C'; + var $role = false; + + function ADODB_ibase() + { + if (defined('IBASE_DEFAULT')) $this->ibasetrans = IBASE_DEFAULT; + } + + + // returns true or false + function _connect($argHostname, $argUsername, $argPassword, $argDatabasename,$persist=false) + { + if (!function_exists('ibase_pconnect')) return null; + if ($argDatabasename) $argHostname .= ':'.$argDatabasename; + $fn = ($persist) ? 'ibase_pconnect':'ibase_connect'; + if ($this->role) + $this->_connectionID = $fn($argHostname,$argUsername,$argPassword, + $this->charSet,$this->buffers,$this->dialect,$this->role); + else + $this->_connectionID = $fn($argHostname,$argUsername,$argPassword, + $this->charSet,$this->buffers,$this->dialect); + + if ($this->dialect != 1) { // http://www.ibphoenix.com/ibp_60_del_id_ds.html + $this->replaceQuote = "''"; + } + if ($this->_connectionID === false) { + $this->_handleerror(); + return false; + } + + // PHP5 change. + if (function_exists('ibase_timefmt')) { + ibase_timefmt($this->ibase_datefmt,IBASE_DATE ); + if ($this->dialect == 1) ibase_timefmt($this->ibase_datefmt,IBASE_TIMESTAMP ); + else ibase_timefmt($this->ibase_timestampfmt,IBASE_TIMESTAMP ); + ibase_timefmt($this->ibase_timefmt,IBASE_TIME ); + + } else { + ini_set("ibase.timestampformat", $this->ibase_timestampfmt); + ini_set("ibase.dateformat", $this->ibase_datefmt); + ini_set("ibase.timeformat", $this->ibase_timefmt); + } + return true; + } + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename,true); + } + + + function MetaPrimaryKeys($table,$owner_notused=false,$internalKey=false) + { + if ($internalKey) return array('RDB$DB_KEY'); + + $table = strtoupper($table); + + $sql = 'SELECT S.RDB$FIELD_NAME AFIELDNAME + FROM RDB$INDICES I JOIN RDB$INDEX_SEGMENTS S ON I.RDB$INDEX_NAME=S.RDB$INDEX_NAME + WHERE I.RDB$RELATION_NAME=\''.$table.'\' and I.RDB$INDEX_NAME like \'RDB$PRIMARY%\' + ORDER BY I.RDB$INDEX_NAME,S.RDB$FIELD_POSITION'; + + $a = $this->GetCol($sql,false,true); + if ($a && sizeof($a)>0) return $a; + return false; + } + + function ServerInfo() + { + $arr['dialect'] = $this->dialect; + switch($arr['dialect']) { + case '': + case '1': $s = 'Interbase 5.5 or earlier'; break; + case '2': $s = 'Interbase 5.6'; break; + default: + case '3': $s = 'Interbase 6.0'; break; + } + $arr['version'] = ADOConnection::_findvers($s); + $arr['description'] = $s; + return $arr; + } + + function BeginTrans() + { + if ($this->transOff) return true; + $this->transCnt += 1; + $this->autoCommit = false; + $this->_transactionID = $this->_connectionID;//ibase_trans($this->ibasetrans, $this->_connectionID); + return $this->_transactionID; + } + + function CommitTrans($ok=true) + { + if (!$ok) return $this->RollbackTrans(); + if ($this->transOff) return true; + if ($this->transCnt) $this->transCnt -= 1; + $ret = false; + $this->autoCommit = true; + if ($this->_transactionID) { + //print ' commit '; + $ret = ibase_commit($this->_transactionID); + } + $this->_transactionID = false; + return $ret; + } + + // there are some compat problems with ADODB_COUNTRECS=false and $this->_logsql currently. + // it appears that ibase extension cannot support multiple concurrent queryid's + function &_Execute($sql,$inputarr=false) + { + global $ADODB_COUNTRECS; + + if ($this->_logsql) { + $savecrecs = $ADODB_COUNTRECS; + $ADODB_COUNTRECS = true; // force countrecs + $ret =& ADOConnection::_Execute($sql,$inputarr); + $ADODB_COUNTRECS = $savecrecs; + } else { + $ret =& ADOConnection::_Execute($sql,$inputarr); + } + return $ret; + } + + function RollbackTrans() + { + if ($this->transOff) return true; + if ($this->transCnt) $this->transCnt -= 1; + $ret = false; + $this->autoCommit = true; + if ($this->_transactionID) + $ret = ibase_rollback($this->_transactionID); + $this->_transactionID = false; + + return $ret; + } + + function &MetaIndexes ($table, $primary = FALSE, $owner=false) + { + // save old fetch mode + global $ADODB_FETCH_MODE; + $false = false; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== FALSE) { + $savem = $this->SetFetchMode(FALSE); + } + $table = strtoupper($table); + $sql = "SELECT * FROM RDB\$INDICES WHERE RDB\$RELATION_NAME = '".$table."'"; + if (!$primary) { + $sql .= " AND RDB\$INDEX_NAME NOT LIKE 'RDB\$%'"; + } else { + $sql .= " AND RDB\$INDEX_NAME NOT LIKE 'RDB\$FOREIGN%'"; + } + // get index details + $rs = $this->Execute($sql); + if (!is_object($rs)) { + // restore fetchmode + if (isset($savem)) { + $this->SetFetchMode($savem); + } + $ADODB_FETCH_MODE = $save; + return $false; + } + + $indexes = array(); + while ($row = $rs->FetchRow()) { + $index = $row[0]; + if (!isset($indexes[$index])) { + if (is_null($row[3])) {$row[3] = 0;} + $indexes[$index] = array( + 'unique' => ($row[3] == 1), + 'columns' => array() + ); + } + $sql = "SELECT * FROM RDB\$INDEX_SEGMENTS WHERE RDB\$INDEX_NAME = '".$index."' ORDER BY RDB\$FIELD_POSITION ASC"; + $rs1 = $this->Execute($sql); + while ($row1 = $rs1->FetchRow()) { + $indexes[$index]['columns'][$row1[2]] = $row1[1]; + } + } + // restore fetchmode + if (isset($savem)) { + $this->SetFetchMode($savem); + } + $ADODB_FETCH_MODE = $save; + + return $indexes; + } + + + // See http://community.borland.com/article/0,1410,25844,00.html + function RowLock($tables,$where,$col) + { + if ($this->autoCommit) $this->BeginTrans(); + $this->Execute("UPDATE $table SET $col=$col WHERE $where "); // is this correct - jlim? + return 1; + } + + + function CreateSequence($seqname,$startID=1) + { + $ok = $this->Execute(("INSERT INTO RDB\$GENERATORS (RDB\$GENERATOR_NAME) VALUES (UPPER('$seqname'))" )); + if (!$ok) return false; + return $this->Execute("SET GENERATOR $seqname TO ".($startID-1).';'); + } + + function DropSequence($seqname) + { + $seqname = strtoupper($seqname); + $this->Execute("delete from RDB\$GENERATORS where RDB\$GENERATOR_NAME='$seqname'"); + } + + function GenID($seqname='adodbseq',$startID=1) + { + $getnext = ("SELECT Gen_ID($seqname,1) FROM RDB\$DATABASE"); + $rs = @$this->Execute($getnext); + if (!$rs) { + $this->Execute(("INSERT INTO RDB\$GENERATORS (RDB\$GENERATOR_NAME) VALUES (UPPER('$seqname'))" )); + $this->Execute("SET GENERATOR $seqname TO ".($startID-1).';'); + $rs = $this->Execute($getnext); + } + if ($rs && !$rs->EOF) $this->genID = (integer) reset($rs->fields); + else $this->genID = 0; // false + + if ($rs) $rs->Close(); + + return $this->genID; + } + + function SelectDB($dbName) + { + return false; + } + + function _handleerror() + { + $this->_errorMsg = ibase_errmsg(); + } + + function ErrorNo() + { + if (preg_match('/error code = ([\-0-9]*)/i', $this->_errorMsg,$arr)) return (integer) $arr[1]; + else return 0; + } + + function ErrorMsg() + { + return $this->_errorMsg; + } + + function Prepare($sql) + { + $stmt = ibase_prepare($this->_connectionID,$sql); + if (!$stmt) return false; + return array($sql,$stmt); + } + + // returns query ID if successful, otherwise false + // there have been reports of problems with nested queries - the code is probably not re-entrant? + function _query($sql,$iarr=false) + { + + if (!$this->autoCommit && $this->_transactionID) { + $conn = $this->_transactionID; + $docommit = false; + } else { + $conn = $this->_connectionID; + $docommit = true; + } + if (is_array($sql)) { + $fn = 'ibase_execute'; + $sql = $sql[1]; + if (is_array($iarr)) { + if (ADODB_PHPVER >= 0x4050) { // actually 4.0.4 + if ( !isset($iarr[0]) ) $iarr[0] = ''; // PHP5 compat hack + $fnarr =& array_merge( array($sql) , $iarr); + $ret = call_user_func_array($fn,$fnarr); + } else { + switch(sizeof($iarr)) { + case 1: $ret = $fn($sql,$iarr[0]); break; + case 2: $ret = $fn($sql,$iarr[0],$iarr[1]); break; + case 3: $ret = $fn($sql,$iarr[0],$iarr[1],$iarr[2]); break; + case 4: $ret = $fn($sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3]); break; + case 5: $ret = $fn($sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4]); break; + case 6: $ret = $fn($sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4],$iarr[5]); break; + case 7: $ret = $fn($sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4],$iarr[5],$iarr[6]); break; + default: ADOConnection::outp( "Too many parameters to ibase query $sql"); + case 8: $ret = $fn($sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4],$iarr[5],$iarr[6],$iarr[7]); break; + } + } + } else $ret = $fn($sql); + } else { + $fn = 'ibase_query'; + + if (is_array($iarr)) { + if (ADODB_PHPVER >= 0x4050) { // actually 4.0.4 + if (sizeof($iarr) == 0) $iarr[0] = ''; // PHP5 compat hack + $fnarr =& array_merge( array($conn,$sql) , $iarr); + $ret = call_user_func_array($fn,$fnarr); + } else { + switch(sizeof($iarr)) { + case 1: $ret = $fn($conn,$sql,$iarr[0]); break; + case 2: $ret = $fn($conn,$sql,$iarr[0],$iarr[1]); break; + case 3: $ret = $fn($conn,$sql,$iarr[0],$iarr[1],$iarr[2]); break; + case 4: $ret = $fn($conn,$sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3]); break; + case 5: $ret = $fn($conn,$sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4]); break; + case 6: $ret = $fn($conn,$sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4],$iarr[5]); break; + case 7: $ret = $fn($conn,$sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4],$iarr[5],$iarr[6]); break; + default: ADOConnection::outp( "Too many parameters to ibase query $sql"); + case 8: $ret = $fn($conn,$sql,$iarr[0],$iarr[1],$iarr[2],$iarr[3],$iarr[4],$iarr[5],$iarr[6],$iarr[7]); break; + } + } + } else $ret = $fn($conn,$sql); + } + if ($docommit && $ret === true) ibase_commit($this->_connectionID); + + $this->_handleerror(); + return $ret; + } + + // returns true or false + function _close() + { + if (!$this->autoCommit) @ibase_rollback($this->_connectionID); + return @ibase_close($this->_connectionID); + } + + //OPN STUFF start + function _ConvertFieldType(&$fld, $ftype, $flen, $fscale, $fsubtype, $fprecision, $dialect3) + { + $fscale = abs($fscale); + $fld->max_length = $flen; + $fld->scale = null; + switch($ftype){ + case 7: + case 8: + if ($dialect3) { + switch($fsubtype){ + case 0: + $fld->type = ($ftype == 7 ? 'smallint' : 'integer'); + break; + case 1: + $fld->type = 'numeric'; + $fld->max_length = $fprecision; + $fld->scale = $fscale; + break; + case 2: + $fld->type = 'decimal'; + $fld->max_length = $fprecision; + $fld->scale = $fscale; + break; + } // switch + } else { + if ($fscale !=0) { + $fld->type = 'decimal'; + $fld->scale = $fscale; + $fld->max_length = ($ftype == 7 ? 4 : 9); + } else { + $fld->type = ($ftype == 7 ? 'smallint' : 'integer'); + } + } + break; + case 16: + if ($dialect3) { + switch($fsubtype){ + case 0: + $fld->type = 'decimal'; + $fld->max_length = 18; + $fld->scale = 0; + break; + case 1: + $fld->type = 'numeric'; + $fld->max_length = $fprecision; + $fld->scale = $fscale; + break; + case 2: + $fld->type = 'decimal'; + $fld->max_length = $fprecision; + $fld->scale = $fscale; + break; + } // switch + } + break; + case 10: + $fld->type = 'float'; + break; + case 14: + $fld->type = 'char'; + break; + case 27: + if ($fscale !=0) { + $fld->type = 'decimal'; + $fld->max_length = 15; + $fld->scale = 5; + } else { + $fld->type = 'double'; + } + break; + case 35: + if ($dialect3) { + $fld->type = 'timestamp'; + } else { + $fld->type = 'date'; + } + break; + case 12: + $fld->type = 'date'; + break; + case 13: + $fld->type = 'time'; + break; + case 37: + $fld->type = 'varchar'; + break; + case 40: + $fld->type = 'cstring'; + break; + case 261: + $fld->type = 'blob'; + $fld->max_length = -1; + break; + } // switch + } + //OPN STUFF end + // returns array of ADOFieldObjects for current table + function &MetaColumns($table) + { + global $ADODB_FETCH_MODE; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + + $rs = $this->Execute(sprintf($this->metaColumnsSQL,strtoupper($table))); + + $ADODB_FETCH_MODE = $save; + $false = false; + if ($rs === false) { + return $false; + } + + $retarr = array(); + //OPN STUFF start + $dialect3 = ($this->dialect==3 ? true : false); + //OPN STUFF end + while (!$rs->EOF) { //print_r($rs->fields); + $fld = new ADOFieldObject(); + $fld->name = trim($rs->fields[0]); + //OPN STUFF start + $this->_ConvertFieldType($fld, $rs->fields[7], $rs->fields[3], $rs->fields[4], $rs->fields[5], $rs->fields[6], $dialect3); + if (isset($rs->fields[1]) && $rs->fields[1]) { + $fld->not_null = true; + } + if (isset($rs->fields[2])) { + + $fld->has_default = true; + $d = substr($rs->fields[2],strlen('default ')); + switch ($fld->type) + { + case 'smallint': + case 'integer': $fld->default_value = (int) $d; break; + case 'char': + case 'blob': + case 'text': + case 'varchar': $fld->default_value = (string) substr($d,1,strlen($d)-2); break; + case 'double': + case 'float': $fld->default_value = (float) $d; break; + default: $fld->default_value = $d; break; + } + // case 35:$tt = 'TIMESTAMP'; break; + } + if ((isset($rs->fields[5])) && ($fld->type == 'blob')) { + $fld->sub_type = $rs->fields[5]; + } else { + $fld->sub_type = null; + } + //OPN STUFF end + if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld; + else $retarr[strtoupper($fld->name)] = $fld; + + $rs->MoveNext(); + } + $rs->Close(); + if ( empty($retarr)) return $false; + else return $retarr; + } + + function BlobEncode( $blob ) + { + $blobid = ibase_blob_create( $this->_connectionID); + ibase_blob_add( $blobid, $blob ); + return ibase_blob_close( $blobid ); + } + + // since we auto-decode all blob's since 2.42, + // BlobDecode should not do any transforms + function BlobDecode($blob) + { + return $blob; + } + + + + + // old blobdecode function + // still used to auto-decode all blob's + function _BlobDecode( $blob ) + { + $blobid = ibase_blob_open( $blob ); + $realblob = ibase_blob_get( $blobid,$this->maxblobsize); // 2nd param is max size of blob -- Kevin Boillet <kevinboillet@yahoo.fr> + while($string = ibase_blob_get($blobid, 8192)){ + $realblob .= $string; + } + ibase_blob_close( $blobid ); + + return( $realblob ); + } + + function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB') + { + $fd = fopen($path,'rb'); + if ($fd === false) return false; + $blob_id = ibase_blob_create($this->_connectionID); + + /* fill with data */ + + while ($val = fread($fd,32768)){ + ibase_blob_add($blob_id, $val); + } + + /* close and get $blob_id_str for inserting into table */ + $blob_id_str = ibase_blob_close($blob_id); + + fclose($fd); + return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false; + } + + /* + Insert a null into the blob field of the table first. + Then use UpdateBlob to store the blob. + + Usage: + + $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)'); + $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1'); + */ + function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') + { + $blob_id = ibase_blob_create($this->_connectionID); + + // ibase_blob_add($blob_id, $val); + + // replacement that solves the problem by which only the first modulus 64K / + // of $val are stored at the blob field //////////////////////////////////// + // Thx Abel Berenstein aberenstein#afip.gov.ar + $len = strlen($val); + $chunk_size = 32768; + $tail_size = $len % $chunk_size; + $n_chunks = ($len - $tail_size) / $chunk_size; + + for ($n = 0; $n < $n_chunks; $n++) { + $start = $n * $chunk_size; + $data = substr($val, $start, $chunk_size); + ibase_blob_add($blob_id, $data); + } + + if ($tail_size) { + $start = $n_chunks * $chunk_size; + $data = substr($val, $start, $tail_size); + ibase_blob_add($blob_id, $data); + } + // end replacement ///////////////////////////////////////////////////////// + + $blob_id_str = ibase_blob_close($blob_id); + + return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false; + + } + + + function OldUpdateBlob($table,$column,$val,$where,$blobtype='BLOB') + { + $blob_id = ibase_blob_create($this->_connectionID); + ibase_blob_add($blob_id, $val); + $blob_id_str = ibase_blob_close($blob_id); + return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blob_id_str)) != false; + } + + // Format date column in sql string given an input format that understands Y M D + // Only since Interbase 6.0 - uses EXTRACT + // problem - does not zero-fill the day and month yet + function SQLDate($fmt, $col=false) + { + if (!$col) $col = $this->sysDate; + $s = ''; + + $len = strlen($fmt); + for ($i=0; $i < $len; $i++) { + if ($s) $s .= '||'; + $ch = $fmt[$i]; + switch($ch) { + case 'Y': + case 'y': + $s .= "extract(year from $col)"; + break; + case 'M': + case 'm': + $s .= "extract(month from $col)"; + break; + case 'Q': + case 'q': + $s .= "cast(((extract(month from $col)+2) / 3) as integer)"; + break; + case 'D': + case 'd': + $s .= "(extract(day from $col))"; + break; + case 'H': + case 'h': + $s .= "(extract(hour from $col))"; + break; + case 'I': + case 'i': + $s .= "(extract(minute from $col))"; + break; + case 'S': + case 's': + $s .= "CAST((extract(second from $col)) AS INTEGER)"; + break; + + default: + if ($ch == '\\') { + $i++; + $ch = substr($fmt,$i,1); + } + $s .= $this->qstr($ch); + break; + } + } + return $s; + } +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordset_ibase extends ADORecordSet +{ + + var $databaseType = "ibase"; + var $bind=false; + var $_cacheType; + + function ADORecordset_ibase($id,$mode=false) + { + global $ADODB_FETCH_MODE; + + $this->fetchMode = ($mode === false) ? $ADODB_FETCH_MODE : $mode; + $this->ADORecordSet($id); + } + + /* Returns: an object containing field information. + Get column information in the Recordset object. fetchField() can be used in order to obtain information about + fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by + fetchField() is retrieved. */ + + function &FetchField($fieldOffset = -1) + { + $fld = new ADOFieldObject; + $ibf = ibase_field_info($this->_queryID,$fieldOffset); + switch (ADODB_ASSOC_CASE) { + case 2: // the default + $fld->name = ($ibf['alias']); + if (empty($fld->name)) $fld->name = ($ibf['name']); + break; + case 0: + $fld->name = strtoupper($ibf['alias']); + if (empty($fld->name)) $fld->name = strtoupper($ibf['name']); + break; + case 1: + $fld->name = strtolower($ibf['alias']); + if (empty($fld->name)) $fld->name = strtolower($ibf['name']); + break; + } + + $fld->type = $ibf['type']; + $fld->max_length = $ibf['length']; + + /* This needs to be populated from the metadata */ + $fld->not_null = false; + $fld->has_default = false; + $fld->default_value = 'null'; + return $fld; + } + + function _initrs() + { + $this->_numOfRows = -1; + $this->_numOfFields = @ibase_num_fields($this->_queryID); + + // cache types for blob decode check + for ($i=0, $max = $this->_numOfFields; $i < $max; $i++) { + $f1 = $this->FetchField($i); + $this->_cacheType[] = $f1->type; + } + } + + function _seek($row) + { + return false; + } + + function _fetch() + { + $f = @ibase_fetch_row($this->_queryID); + if ($f === false) { + $this->fields = false; + return false; + } + // OPN stuff start - optimized + // fix missing nulls and decode blobs automatically + + global $ADODB_ANSI_PADDING_OFF; + //$ADODB_ANSI_PADDING_OFF=1; + $rtrim = !empty($ADODB_ANSI_PADDING_OFF); + + for ($i=0, $max = $this->_numOfFields; $i < $max; $i++) { + if ($this->_cacheType[$i]=="BLOB") { + if (isset($f[$i])) { + $f[$i] = $this->connection->_BlobDecode($f[$i]); + } else { + $f[$i] = null; + } + } else { + if (!isset($f[$i])) { + $f[$i] = null; + } else if ($rtrim && is_string($f[$i])) { + $f[$i] = rtrim($f[$i]); + } + } + } + // OPN stuff end + + $this->fields = $f; + if ($this->fetchMode == ADODB_FETCH_ASSOC) { + $this->fields = &$this->GetRowAssoc(ADODB_ASSOC_CASE); + } else if ($this->fetchMode == ADODB_FETCH_BOTH) { + $this->fields =& array_merge($this->fields,$this->GetRowAssoc(ADODB_ASSOC_CASE)); + } + return true; + } + + /* Use associative array to get fields array */ + function Fields($colname) + { + if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname]; + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + + return $this->fields[$this->bind[strtoupper($colname)]]; + + } + + + function _close() + { + return @ibase_free_result($this->_queryID); + } + + function MetaType($t,$len=-1,$fieldobj=false) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + switch (strtoupper($t)) { + case 'CHAR': + return 'C'; + + case 'TEXT': + case 'VARCHAR': + case 'VARYING': + if ($len <= $this->blobSize) return 'C'; + return 'X'; + case 'BLOB': + return 'B'; + + case 'TIMESTAMP': + case 'DATE': return 'D'; + case 'TIME': return 'T'; + //case 'T': return 'T'; + + //case 'L': return 'L'; + case 'INT': + case 'SHORT': + case 'INTEGER': return 'I'; + default: return 'N'; + } + } + +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-informix.inc.php b/framework/DataAccess/adodb/drivers/adodb-informix.inc.php new file mode 100644 index 00000000..108d3e35 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-informix.inc.php @@ -0,0 +1,35 @@ +<?php +/** +* @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. +* Released under both BSD license and Lesser GPL library license. +* Whenever there is any discrepancy between the two licenses, +* the BSD license will take precedence. +* +* Set tabs to 4 for best viewing. +* +* Latest version is available at http://php.weblogs.com +* +* Informix 9 driver that supports SELECT FIRST +* +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +include_once(ADODB_DIR.'/drivers/adodb-informix72.inc.php'); + +class ADODB_informix extends ADODB_informix72 { + var $databaseType = "informix"; + var $hasTop = 'FIRST'; + var $ansiOuter = true; +} + +class ADORecordset_informix extends ADORecordset_informix72 { + var $databaseType = "informix"; + + function ADORecordset_informix($id,$mode=false) + { + $this->ADORecordset_informix72($id,$mode); + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-informix72.inc.php b/framework/DataAccess/adodb/drivers/adodb-informix72.inc.php new file mode 100644 index 00000000..39c29c84 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-informix72.inc.php @@ -0,0 +1,475 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim. All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Informix port by Mitchell T. Young (mitch@youngfamily.org) + + Further mods by "Samuel CARRIERE" <samuel_carriere@hotmail.com> + +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (!defined('IFX_SCROLL')) define('IFX_SCROLL',1); + +class ADODB_informix72 extends ADOConnection { + var $databaseType = "informix72"; + var $dataProvider = "informix"; + var $replaceQuote = "''"; // string to use to replace quotes + var $fmtDate = "'Y-m-d'"; + var $fmtTimeStamp = "'Y-m-d H:i:s'"; + var $hasInsertID = true; + var $hasAffectedRows = true; + var $substr = 'substr'; + var $metaTablesSQL="select tabname,tabtype from systables where tabtype in ('T','V') and owner!='informix'"; //Don't get informix tables and pseudo-tables + + + var $metaColumnsSQL = + "select c.colname, c.coltype, c.collength, d.default,c.colno + from syscolumns c, systables t,outer sysdefaults d + where c.tabid=t.tabid and d.tabid=t.tabid and d.colno=c.colno + and tabname='%s' order by c.colno"; + + var $metaPrimaryKeySQL = + "select part1,part2,part3,part4,part5,part6,part7,part8 from + systables t,sysconstraints s,sysindexes i where t.tabname='%s' + and s.tabid=t.tabid and s.constrtype='P' + and i.idxname=s.idxname"; + + var $concat_operator = '||'; + + var $lastQuery = false; + var $has_insertid = true; + + var $_autocommit = true; + var $_bindInputArray = true; // set to true if ADOConnection.Execute() permits binding of array parameters. + var $sysDate = 'TODAY'; + var $sysTimeStamp = 'CURRENT'; + var $cursorType = IFX_SCROLL; // IFX_SCROLL or IFX_HOLD or 0 + + function ADODB_informix72() + { + // alternatively, use older method: + //putenv("DBDATE=Y4MD-"); + + // force ISO date format + putenv('GL_DATE=%Y-%m-%d'); + + if (function_exists('ifx_byteasvarchar')) { + ifx_byteasvarchar(1); // Mode "0" will return a blob id, and mode "1" will return a varchar with text content. + ifx_textasvarchar(1); // Mode "0" will return a blob id, and mode "1" will return a varchar with text content. + ifx_blobinfile_mode(0); // Mode "0" means save Byte-Blobs in memory, and mode "1" means save Byte-Blobs in a file. + } + } + + function ServerInfo() + { + if (isset($this->version)) return $this->version; + + $arr['description'] = $this->GetOne("select DBINFO('version','full') from systables where tabid = 1"); + $arr['version'] = $this->GetOne("select DBINFO('version','major') || DBINFO('version','minor') from systables where tabid = 1"); + $this->version = $arr; + return $arr; + } + + + + function _insertid() + { + $sqlca =ifx_getsqlca($this->lastQuery); + return @$sqlca["sqlerrd1"]; + } + + function _affectedrows() + { + if ($this->lastQuery) { + return @ifx_affected_rows ($this->lastQuery); + } + return 0; + } + + function BeginTrans() + { + if ($this->transOff) return true; + $this->transCnt += 1; + $this->Execute('BEGIN'); + $this->_autocommit = false; + return true; + } + + function CommitTrans($ok=true) + { + if (!$ok) return $this->RollbackTrans(); + if ($this->transOff) return true; + if ($this->transCnt) $this->transCnt -= 1; + $this->Execute('COMMIT'); + $this->_autocommit = true; + return true; + } + + function RollbackTrans() + { + if ($this->transOff) return true; + if ($this->transCnt) $this->transCnt -= 1; + $this->Execute('ROLLBACK'); + $this->_autocommit = true; + return true; + } + + function RowLock($tables,$where,$flds='1 as ignore') + { + if ($this->_autocommit) $this->BeginTrans(); + return $this->GetOne("select $flds from $tables where $where for update"); + } + + /* Returns: the last error message from previous database operation + Note: This function is NOT available for Microsoft SQL Server. */ + + function ErrorMsg() + { + if (!empty($this->_logsql)) return $this->_errorMsg; + $this->_errorMsg = ifx_errormsg(); + return $this->_errorMsg; + } + + function ErrorNo() + { + preg_match("/.*SQLCODE=([^\]]*)/",ifx_error(),$parse); + if (is_array($parse) && isset($parse[1])) return (int)$parse[1]; + return 0; + } + + + function &MetaColumns($table) + { + global $ADODB_FETCH_MODE; + + $false = false; + if (!empty($this->metaColumnsSQL)) { + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table)); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + if ($rs === false) return $false; + $rspkey = $this->Execute(sprintf($this->metaPrimaryKeySQL,$table)); //Added to get primary key colno items + + $retarr = array(); + while (!$rs->EOF) { //print_r($rs->fields); + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[0]; +/* //!eos. + $rs->fields[1] is not the correct adodb type + $rs->fields[2] is not correct max_length, because can include not-null bit + + $fld->type = $rs->fields[1]; + $fld->primary_key=$rspkey->fields && array_search($rs->fields[4],$rspkey->fields); //Added to set primary key flag + $fld->max_length = $rs->fields[2];*/ + $pr=ifx_props($rs->fields[1],$rs->fields[2]); //!eos + $fld->type = $pr[0] ;//!eos + $fld->primary_key=$rspkey->fields && array_search($rs->fields[4],$rspkey->fields); + $fld->max_length = $pr[1]; //!eos + $fld->precision = $pr[2] ;//!eos + $fld->not_null = $pr[3]=="N"; //!eos + + if (trim($rs->fields[3]) != "AAAAAA 0") { + $fld->has_default = 1; + $fld->default_value = $rs->fields[3]; + } else { + $fld->has_default = 0; + } + + $retarr[strtolower($fld->name)] = $fld; + $rs->MoveNext(); + } + + $rs->Close(); + $rspKey->Close(); //!eos + return $retarr; + } + + return $false; + } + + function &xMetaColumns($table) + { + return ADOConnection::MetaColumns($table,false); + } + + function MetaForeignKeys($table, $owner=false, $upper=false) //!Eos + { + $sql = " + select tr.tabname,updrule,delrule, + i.part1 o1,i2.part1 d1,i.part2 o2,i2.part2 d2,i.part3 o3,i2.part3 d3,i.part4 o4,i2.part4 d4, + i.part5 o5,i2.part5 d5,i.part6 o6,i2.part6 d6,i.part7 o7,i2.part7 d7,i.part8 o8,i2.part8 d8 + from systables t,sysconstraints s,sysindexes i, + sysreferences r,systables tr,sysconstraints s2,sysindexes i2 + where t.tabname='$table' + and s.tabid=t.tabid and s.constrtype='R' and r.constrid=s.constrid + and i.idxname=s.idxname and tr.tabid=r.ptabid + and s2.constrid=r.primary and i2.idxname=s2.idxname"; + + $rs = $this->Execute($sql); + if (!$rs || $rs->EOF) return false; + $arr =& $rs->GetArray(); + $a = array(); + foreach($arr as $v) { + $coldest=$this->metaColumnNames($v["tabname"]); + $colorig=$this->metaColumnNames($table); + $colnames=array(); + for($i=1;$i<=8 && $v["o$i"] ;$i++) { + $colnames[]=$coldest[$v["d$i"]-1]."=".$colorig[$v["o$i"]-1]; + } + if($upper) + $a[strtoupper($v["tabname"])] = $colnames; + else + $a[$v["tabname"]] = $colnames; + } + return $a; + } + + function UpdateBlob($table, $column, $val, $where, $blobtype = 'BLOB') + { + $type = ($blobtype == 'TEXT') ? 1 : 0; + $blobid = ifx_create_blob($type,0,$val); + return $this->Execute("UPDATE $table SET $column=(?) WHERE $where",array($blobid)); + } + + function BlobDecode($blobid) + { + return function_exists('ifx_byteasvarchar') ? $blobid : @ifx_get_blob($blobid); + } + + // returns true or false + function _connect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + if (!function_exists('ifx_connect')) return null; + + $dbs = $argDatabasename . "@" . $argHostname; + if ($argHostname) putenv("INFORMIXSERVER=$argHostname"); + putenv("INFORMIXSERVER=".trim($argHostname)); + $this->_connectionID = ifx_connect($dbs,$argUsername,$argPassword); + if ($this->_connectionID === false) return false; + #if ($argDatabasename) return $this->SelectDB($argDatabasename); + return true; + } + + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + if (!function_exists('ifx_connect')) return null; + + $dbs = $argDatabasename . "@" . $argHostname; + putenv("INFORMIXSERVER=".trim($argHostname)); + $this->_connectionID = ifx_pconnect($dbs,$argUsername,$argPassword); + if ($this->_connectionID === false) return false; + #if ($argDatabasename) return $this->SelectDB($argDatabasename); + return true; + } +/* + // ifx_do does not accept bind parameters - weird ??? + function Prepare($sql) + { + $stmt = ifx_prepare($sql); + if (!$stmt) return $sql; + else return array($sql,$stmt); + } +*/ + // returns query ID if successful, otherwise false + function _query($sql,$inputarr) + { + global $ADODB_COUNTRECS; + + // String parameters have to be converted using ifx_create_char + if ($inputarr) { + foreach($inputarr as $v) { + if (gettype($v) == 'string') { + $tab[] = ifx_create_char($v); + } + else { + $tab[] = $v; + } + } + } + + // In case of select statement, we use a scroll cursor in order + // to be able to call "move", or "movefirst" statements + if (!$ADODB_COUNTRECS && preg_match("/^\s*select/is", $sql)) { + if ($inputarr) { + $this->lastQuery = ifx_query($sql,$this->_connectionID, $this->cursorType, $tab); + } + else { + $this->lastQuery = ifx_query($sql,$this->_connectionID, $this->cursorType); + } + } + else { + if ($inputarr) { + $this->lastQuery = ifx_query($sql,$this->_connectionID, $tab); + } + else { + $this->lastQuery = ifx_query($sql,$this->_connectionID); + } + } + + // Following line have been commented because autocommit mode is + // not supported by informix SE 7.2 + + //if ($this->_autocommit) ifx_query('COMMIT',$this->_connectionID); + + return $this->lastQuery; + } + + // returns true or false + function _close() + { + $this->lastQuery = false; + return ifx_close($this->_connectionID); + } +} + + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordset_informix72 extends ADORecordSet { + + var $databaseType = "informix72"; + var $canSeek = true; + var $_fieldprops = false; + + function ADORecordset_informix72($id,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + $this->fetchMode = $mode; + return $this->ADORecordSet($id); + } + + + + /* Returns: an object containing field information. + Get column information in the Recordset object. fetchField() can be used in order to obtain information about + fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by + fetchField() is retrieved. */ + function &FetchField($fieldOffset = -1) + { + if (empty($this->_fieldprops)) { + $fp = ifx_fieldproperties($this->_queryID); + foreach($fp as $k => $v) { + $o = new ADOFieldObject; + $o->name = $k; + $arr = split(';',$v); //"SQLTYPE;length;precision;scale;ISNULLABLE" + $o->type = $arr[0]; + $o->max_length = $arr[1]; + $this->_fieldprops[] = $o; + $o->not_null = $arr[4]=="N"; + } + } + $ret = $this->_fieldprops[$fieldOffset]; + return $ret; + } + + function _initrs() + { + $this->_numOfRows = -1; // ifx_affected_rows not reliable, only returns estimate -- ($ADODB_COUNTRECS)? ifx_affected_rows($this->_queryID):-1; + $this->_numOfFields = ifx_num_fields($this->_queryID); + } + + function _seek($row) + { + return @ifx_fetch_row($this->_queryID, (int) $row); + } + + function MoveLast() + { + $this->fields = @ifx_fetch_row($this->_queryID, "LAST"); + if ($this->fields) $this->EOF = false; + $this->_currentRow = -1; + + if ($this->fetchMode == ADODB_FETCH_NUM) { + foreach($this->fields as $v) { + $arr[] = $v; + } + $this->fields = $arr; + } + + return true; + } + + function MoveFirst() + { + $this->fields = @ifx_fetch_row($this->_queryID, "FIRST"); + if ($this->fields) $this->EOF = false; + $this->_currentRow = 0; + + if ($this->fetchMode == ADODB_FETCH_NUM) { + foreach($this->fields as $v) { + $arr[] = $v; + } + $this->fields = $arr; + } + + return true; + } + + function _fetch($ignore_fields=false) + { + + $this->fields = @ifx_fetch_row($this->_queryID); + + if (!is_array($this->fields)) return false; + + if ($this->fetchMode == ADODB_FETCH_NUM) { + foreach($this->fields as $v) { + $arr[] = $v; + } + $this->fields = $arr; + } + return true; + } + + /* close() only needs to be called if you are worried about using too much memory while your script + is running. All associated result memory for the specified result identifier will automatically be freed. */ + function _close() + { + return ifx_free_result($this->_queryID); + } + +} +/** !Eos +* Auxiliar function to Parse coltype,collength. Used by Metacolumns +* return: array ($mtype,$length,$precision,$nullable) (similar to ifx_fieldpropierties) +*/ +function ifx_props($coltype,$collength){ + $itype=fmod($coltype+1,256); + $nullable=floor(($coltype+1) /256) ?"N":"Y"; + $mtype=substr(" CIIFFNNDN TBXCC ",$itype,1); + switch ($itype){ + case 2: + $length=4; + case 6: + case 9: + case 14: + $length=floor($collength/256); + $precision=fmod($collength,256); + break; + default: + $precision=0; + $length=$collength; + } + return array($mtype,$length,$precision,$nullable); +} + + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-ldap.inc.php b/framework/DataAccess/adodb/drivers/adodb-ldap.inc.php new file mode 100644 index 00000000..fd1c55b5 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-ldap.inc.php @@ -0,0 +1,406 @@ +<?php +/* + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim#natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 8. + + Revision 1: (02/25/2005) Updated codebase to include the _inject_bind_options function. This allows + users to access the options in the ldap_set_option function appropriately. Most importantly + LDAP Version 3 is now supported. See the examples for more information. Also fixed some minor + bugs that surfaced when PHP error levels were set high. + + Joshua Eldridge (joshuae74#hotmail.com) +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (!defined('LDAP_ASSOC')) { + define('LDAP_ASSOC',ADODB_FETCH_ASSOC); + define('LDAP_NUM',ADODB_FETCH_NUM); + define('LDAP_BOTH',ADODB_FETCH_BOTH); +} + +class ADODB_ldap extends ADOConnection { + var $databaseType = 'ldap'; + var $dataProvider = 'ldap'; + + # Connection information + var $username = false; + var $password = false; + + # Used during searches + var $filter; + var $dn; + var $version; + var $port = 389; + + # Options configuration information + var $LDAP_CONNECT_OPTIONS; + + function ADODB_ldap() + { + } + + // returns true or false + + function _connect( $host, $username, $password, $ldapbase) + { + global $LDAP_CONNECT_OPTIONS; + + if ( !function_exists( 'ldap_connect' ) ) return null; + + $conn_info = array( $host,$this->port); + + if ( strstr( $host, ':' ) ) { + $conn_info = split( ':', $host ); + } + + $this->_connectionID = ldap_connect( $conn_info[0], $conn_info[1] ); + if (!$this->_connectionID) { + $e = 'Could not connect to ' . $conn_info[0]; + $this->_errorMsg = $e; + if ($this->debug) ADOConnection::outp($e); + return false; + } + if( count( $LDAP_CONNECT_OPTIONS ) > 0 ) { + $this->_inject_bind_options( $LDAP_CONNECT_OPTIONS ); + } + + if ($username) { + $bind = ldap_bind( $this->_connectionID, $username, $password ); + } else { + $username = 'anonymous'; + $bind = ldap_bind( $this->_connectionID ); + } + + if (!$bind) { + $e = 'Could not bind to ' . $conn_info[0] . " as ".$username; + $this->_errorMsg = $e; + if ($this->debug) ADOConnection::outp($e); + return false; + } + $this->_errorMsg = ''; + $this->database = $ldapbase; + return $this->_connectionID; + } + +/* + Valid Domain Values for LDAP Options: + + LDAP_OPT_DEREF (integer) + LDAP_OPT_SIZELIMIT (integer) + LDAP_OPT_TIMELIMIT (integer) + LDAP_OPT_PROTOCOL_VERSION (integer) + LDAP_OPT_ERROR_NUMBER (integer) + LDAP_OPT_REFERRALS (boolean) + LDAP_OPT_RESTART (boolean) + LDAP_OPT_HOST_NAME (string) + LDAP_OPT_ERROR_STRING (string) + LDAP_OPT_MATCHED_DN (string) + LDAP_OPT_SERVER_CONTROLS (array) + LDAP_OPT_CLIENT_CONTROLS (array) + + Make sure to set this BEFORE calling Connect() + + Example: + + $LDAP_CONNECT_OPTIONS = Array( + Array ( + "OPTION_NAME"=>LDAP_OPT_DEREF, + "OPTION_VALUE"=>2 + ), + Array ( + "OPTION_NAME"=>LDAP_OPT_SIZELIMIT, + "OPTION_VALUE"=>100 + ), + Array ( + "OPTION_NAME"=>LDAP_OPT_TIMELIMIT, + "OPTION_VALUE"=>30 + ), + Array ( + "OPTION_NAME"=>LDAP_OPT_PROTOCOL_VERSION, + "OPTION_VALUE"=>3 + ), + Array ( + "OPTION_NAME"=>LDAP_OPT_ERROR_NUMBER, + "OPTION_VALUE"=>13 + ), + Array ( + "OPTION_NAME"=>LDAP_OPT_REFERRALS, + "OPTION_VALUE"=>FALSE + ), + Array ( + "OPTION_NAME"=>LDAP_OPT_RESTART, + "OPTION_VALUE"=>FALSE + ) + ); +*/ + + function _inject_bind_options( $options ) { + foreach( $options as $option ) { + ldap_set_option( $this->_connectionID, $option["OPTION_NAME"], $option["OPTION_VALUE"] ) + or die( "Unable to set server option: " . $option["OPTION_NAME"] ); + } + } + + /* returns _queryID or false */ + function _query($sql,$inputarr) + { + $rs = ldap_search( $this->_connectionID, $this->database, $sql ); + $this->_errorMsg = ($rs) ? '' : 'Search error on '.$sql; + return $rs; + } + + /* closes the LDAP connection */ + function _close() + { + @ldap_close( $this->_connectionID ); + $this->_connectionID = false; + } + + function SelectDB($db) { + $this->database = $db; + return true; + } // SelectDB + + function ServerInfo() + { + if( !empty( $this->version ) ) return $this->version; + $version = array(); + /* + Determines how aliases are handled during search. + LDAP_DEREF_NEVER (0x00) + LDAP_DEREF_SEARCHING (0x01) + LDAP_DEREF_FINDING (0x02) + LDAP_DEREF_ALWAYS (0x03) + The LDAP_DEREF_SEARCHING value means aliases are dereferenced during the search but + not when locating the base object of the search. The LDAP_DEREF_FINDING value means + aliases are dereferenced when locating the base object but not during the search. + Default: LDAP_DEREF_NEVER + */ + ldap_get_option( $this->_connectionID, LDAP_OPT_DEREF, $version['LDAP_OPT_DEREF'] ) ; + switch ( $version['LDAP_OPT_DEREF'] ) { + case 0: + $version['LDAP_OPT_DEREF'] = 'LDAP_DEREF_NEVER'; + case 1: + $version['LDAP_OPT_DEREF'] = 'LDAP_DEREF_SEARCHING'; + case 2: + $version['LDAP_OPT_DEREF'] = 'LDAP_DEREF_FINDING'; + case 3: + $version['LDAP_OPT_DEREF'] = 'LDAP_DEREF_ALWAYS'; + } + + /* + A limit on the number of entries to return from a search. + LDAP_NO_LIMIT (0) means no limit. + Default: LDAP_NO_LIMIT + */ + ldap_get_option( $this->_connectionID, LDAP_OPT_SIZELIMIT, $version['LDAP_OPT_SIZELIMIT'] ); + if ( $version['LDAP_OPT_SIZELIMIT'] == 0 ) { + $version['LDAP_OPT_SIZELIMIT'] = 'LDAP_NO_LIMIT'; + } + + /* + A limit on the number of seconds to spend on a search. + LDAP_NO_LIMIT (0) means no limit. + Default: LDAP_NO_LIMIT + */ + ldap_get_option( $this->_connectionID, LDAP_OPT_TIMELIMIT, $version['LDAP_OPT_TIMELIMIT'] ); + if ( $version['LDAP_OPT_TIMELIMIT'] == 0 ) { + $version['LDAP_OPT_TIMELIMIT'] = 'LDAP_NO_LIMIT'; + } + + /* + Determines whether the LDAP library automatically follows referrals returned by LDAP servers or not. + LDAP_OPT_ON + LDAP_OPT_OFF + Default: ON + */ + ldap_get_option( $this->_connectionID, LDAP_OPT_REFERRALS, $version['LDAP_OPT_REFERRALS'] ); + if ( $version['LDAP_OPT_REFERRALS'] == 0 ) { + $version['LDAP_OPT_REFERRALS'] = 'LDAP_OPT_OFF'; + } else { + $version['LDAP_OPT_REFERRALS'] = 'LDAP_OPT_ON'; + + } + /* + Determines whether LDAP I/O operations are automatically restarted if they abort prematurely. + LDAP_OPT_ON + LDAP_OPT_OFF + Default: OFF + */ + ldap_get_option( $this->_connectionID, LDAP_OPT_RESTART, $version['LDAP_OPT_RESTART'] ); + if ( $version['LDAP_OPT_RESTART'] == 0 ) { + $version['LDAP_OPT_RESTART'] = 'LDAP_OPT_OFF'; + } else { + $version['LDAP_OPT_RESTART'] = 'LDAP_OPT_ON'; + + } + /* + This option indicates the version of the LDAP protocol used when communicating with the primary LDAP server. + LDAP_VERSION2 (2) + LDAP_VERSION3 (3) + Default: LDAP_VERSION2 (2) + */ + ldap_get_option( $this->_connectionID, LDAP_OPT_PROTOCOL_VERSION, $version['LDAP_OPT_PROTOCOL_VERSION'] ); + if ( $version['LDAP_OPT_PROTOCOL_VERSION'] == 2 ) { + $version['LDAP_OPT_PROTOCOL_VERSION'] = 'LDAP_VERSION2'; + } else { + $version['LDAP_OPT_PROTOCOL_VERSION'] = 'LDAP_VERSION3'; + + } + /* The host name (or list of hosts) for the primary LDAP server. */ + ldap_get_option( $this->_connectionID, LDAP_OPT_HOST_NAME, $version['LDAP_OPT_HOST_NAME'] ); + ldap_get_option( $this->_connectionID, LDAP_OPT_ERROR_NUMBER, $version['LDAP_OPT_ERROR_NUMBER'] ); + ldap_get_option( $this->_connectionID, LDAP_OPT_ERROR_STRING, $version['LDAP_OPT_ERROR_STRING'] ); + ldap_get_option( $this->_connectionID, LDAP_OPT_MATCHED_DN, $version['LDAP_OPT_MATCHED_DN'] ); + + return $this->version = $version; + + } +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordSet_ldap extends ADORecordSet{ + + var $databaseType = "ldap"; + var $canSeek = false; + var $_entryID; /* keeps track of the entry resource identifier */ + + function ADORecordSet_ldap($queryID,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + switch ($mode) + { + case ADODB_FETCH_NUM: + $this->fetchMode = LDAP_NUM; + break; + case ADODB_FETCH_ASSOC: + $this->fetchMode = LDAP_ASSOC; + break; + case ADODB_FETCH_DEFAULT: + case ADODB_FETCH_BOTH: + default: + $this->fetchMode = LDAP_BOTH; + break; + } + + $this->ADORecordSet($queryID); + } + + function _initrs() + { + /* + This could be teaked to respect the $COUNTRECS directive from ADODB + It's currently being used in the _fetch() function and the + GetAssoc() function + */ + $this->_numOfRows = ldap_count_entries( $this->connection->_connectionID, $this->_queryID ); + + } + + /* + Return whole recordset as a multi-dimensional associative array + */ + function &GetAssoc($force_array = false, $first2cols = false) + { + $records = $this->_numOfRows; + $results = array(); + for ( $i=0; $i < $records; $i++ ) { + foreach ( $this->fields as $k=>$v ) { + if ( is_array( $v ) ) { + if ( $v['count'] == 1 ) { + $results[$i][$k] = $v[0]; + } else { + array_shift( $v ); + $results[$i][$k] = $v; + } + } + } + } + + return $results; + } + + function &GetRowAssoc() + { + $results = array(); + foreach ( $this->fields as $k=>$v ) { + if ( is_array( $v ) ) { + if ( $v['count'] == 1 ) { + $results[$k] = $v[0]; + } else { + array_shift( $v ); + $results[$k] = $v; + } + } + } + + return $results; + } + + function GetRowNums() + { + $results = array(); + foreach ( $this->fields as $k=>$v ) { + static $i = 0; + if (is_array( $v )) { + if ( $v['count'] == 1 ) { + $results[$i] = $v[0]; + } else { + array_shift( $v ); + $results[$i] = $v; + } + $i++; + } + } + return $results; + } + + function _fetch() + { + if ( $this->_currentRow >= $this->_numOfRows && $this->_numOfRows >= 0 ) + return false; + + if ( $this->_currentRow == 0 ) { + $this->_entryID = ldap_first_entry( $this->connection->_connectionID, $this->_queryID ); + } else { + $this->_entryID = ldap_next_entry( $this->connection->_connectionID, $this->_entryID ); + } + + $this->fields = ldap_get_attributes( $this->connection->_connectionID, $this->_entryID ); + $this->_numOfFields = $this->fields['count']; + switch ( $this->fetchMode ) { + + case LDAP_ASSOC: + $this->fields = $this->GetRowAssoc(); + break; + + case LDAP_NUM: + $this->fields = array_merge($this->GetRowNums(),$this->GetRowAssoc()); + break; + + case LDAP_BOTH: + default: + $this->fields = $this->GetRowNums(); + break; + } + return ( is_array( $this->fields ) ); + } + + function _close() { + @ldap_free_result( $this->_queryID ); + $this->_queryID = false; + } + +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-mssql.inc.php b/framework/DataAccess/adodb/drivers/adodb-mssql.inc.php new file mode 100644 index 00000000..ef4b2649 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-mssql.inc.php @@ -0,0 +1,1024 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Native mssql driver. Requires mssql client. Works on Windows. + To configure for Unix, see + http://phpbuilder.com/columns/alberto20000919.php3 + +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +//---------------------------------------------------------------- +// MSSQL returns dates with the format Oct 13 2002 or 13 Oct 2002 +// and this causes tons of problems because localized versions of +// MSSQL will return the dates in dmy or mdy order; and also the +// month strings depends on what language has been configured. The +// following two variables allow you to control the localization +// settings - Ugh. +// +// MORE LOCALIZATION INFO +// ---------------------- +// To configure datetime, look for and modify sqlcommn.loc, +// typically found in c:\mssql\install +// Also read : +// http://support.microsoft.com/default.aspx?scid=kb;EN-US;q220918 +// Alternatively use: +// CONVERT(char(12),datecol,120) +//---------------------------------------------------------------- + + +// has datetime converstion to YYYY-MM-DD format, and also mssql_fetch_assoc +if (ADODB_PHPVER >= 0x4300) { +// docs say 4.2.0, but testing shows only since 4.3.0 does it work! + ini_set('mssql.datetimeconvert',0); +} else { +global $ADODB_mssql_mths; // array, months must be upper-case + + + $ADODB_mssql_date_order = 'mdy'; + $ADODB_mssql_mths = array( + 'JAN'=>1,'FEB'=>2,'MAR'=>3,'APR'=>4,'MAY'=>5,'JUN'=>6, + 'JUL'=>7,'AUG'=>8,'SEP'=>9,'OCT'=>10,'NOV'=>11,'DEC'=>12); +} + +//--------------------------------------------------------------------------- +// Call this to autoset $ADODB_mssql_date_order at the beginning of your code, +// just after you connect to the database. Supports mdy and dmy only. +// Not required for PHP 4.2.0 and above. +function AutoDetect_MSSQL_Date_Order($conn) +{ +global $ADODB_mssql_date_order; + $adate = $conn->GetOne('select getdate()'); + if ($adate) { + $anum = (int) $adate; + if ($anum > 0) { + if ($anum > 31) { + //ADOConnection::outp( "MSSQL: YYYY-MM-DD date format not supported currently"); + } else + $ADODB_mssql_date_order = 'dmy'; + } else + $ADODB_mssql_date_order = 'mdy'; + } +} + +class ADODB_mssql extends ADOConnection { + var $databaseType = "mssql"; + var $dataProvider = "mssql"; + var $replaceQuote = "''"; // string to use to replace quotes + var $fmtDate = "'Y-m-d'"; + var $fmtTimeStamp = "'Y-m-d H:i:s'"; + var $hasInsertID = true; + var $substr = "substring"; + var $length = 'len'; + var $hasAffectedRows = true; + var $metaDatabasesSQL = "select name from sysdatabases where name <> 'master'"; + var $metaTablesSQL="select name,case when type='U' then 'T' else 'V' end from sysobjects where (type='U' or type='V') and (name not in ('sysallocations','syscolumns','syscomments','sysdepends','sysfilegroups','sysfiles','sysfiles1','sysforeignkeys','sysfulltextcatalogs','sysindexes','sysindexkeys','sysmembers','sysobjects','syspermissions','sysprotects','sysreferences','systypes','sysusers','sysalternates','sysconstraints','syssegments','REFERENTIAL_CONSTRAINTS','CHECK_CONSTRAINTS','CONSTRAINT_TABLE_USAGE','CONSTRAINT_COLUMN_USAGE','VIEWS','VIEW_TABLE_USAGE','VIEW_COLUMN_USAGE','SCHEMATA','TABLES','TABLE_CONSTRAINTS','TABLE_PRIVILEGES','COLUMNS','COLUMN_DOMAIN_USAGE','COLUMN_PRIVILEGES','DOMAINS','DOMAIN_CONSTRAINTS','KEY_COLUMN_USAGE','dtproperties'))"; + var $metaColumnsSQL = # xtype==61 is datetime +"select c.name,t.name,c.length, + (case when c.xusertype=61 then 0 else c.xprec end), + (case when c.xusertype=61 then 0 else c.xscale end) + from syscolumns c join systypes t on t.xusertype=c.xusertype join sysobjects o on o.id=c.id where o.name='%s'"; + var $hasTop = 'top'; // support mssql SELECT TOP 10 * FROM TABLE + var $hasGenID = true; + var $sysDate = 'convert(datetime,convert(char,GetDate(),102),102)'; + var $sysTimeStamp = 'GetDate()'; + var $_has_mssql_init; + var $maxParameterLen = 4000; + var $arrayClass = 'ADORecordSet_array_mssql'; + var $uniqueSort = true; + var $leftOuter = '*='; + var $rightOuter = '=*'; + var $ansiOuter = true; // for mssql7 or later + var $poorAffectedRows = true; + var $identitySQL = 'select @@IDENTITY'; // 'select SCOPE_IDENTITY'; # for mssql 2000 + var $uniqueOrderBy = true; + var $_bindInputArray = true; + + function ADODB_mssql() + { + $this->_has_mssql_init = (strnatcmp(PHP_VERSION,'4.1.0')>=0); + } + + function ServerInfo() + { + global $ADODB_FETCH_MODE; + + $stmt = $this->PrepareSP('sp_server_info'); + $val = 2; + if ($this->fetchMode === false) { + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + } else + $savem = $this->SetFetchMode(ADODB_FETCH_NUM); + + + $this->Parameter($stmt,$val,'attribute_id'); + $row = $this->GetRow($stmt); + + //$row = $this->GetRow("execute sp_server_info 2"); + + + if ($this->fetchMode === false) { + $ADODB_FETCH_MODE = $savem; + } else + $this->SetFetchMode($savem); + + $arr['description'] = $row[2]; + $arr['version'] = ADOConnection::_findvers($arr['description']); + return $arr; + } + + function IfNull( $field, $ifNull ) + { + return " ISNULL($field, $ifNull) "; // if MS SQL Server + } + + function _insertid() + { + // SCOPE_IDENTITY() + // Returns the last IDENTITY value inserted into an IDENTITY column in + // the same scope. A scope is a module -- a stored procedure, trigger, + // function, or batch. Thus, two statements are in the same scope if + // they are in the same stored procedure, function, or batch. + return $this->GetOne($this->identitySQL); + } + + function _affectedrows() + { + return $this->GetOne('select @@rowcount'); + } + + var $_dropSeqSQL = "drop table %s"; + + function CreateSequence($seq='adodbseq',$start=1) + { + + $this->Execute('BEGIN TRANSACTION adodbseq'); + $start -= 1; + $this->Execute("create table $seq (id float(53))"); + $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)"); + if (!$ok) { + $this->Execute('ROLLBACK TRANSACTION adodbseq'); + return false; + } + $this->Execute('COMMIT TRANSACTION adodbseq'); + return true; + } + + function GenID($seq='adodbseq',$start=1) + { + //$this->debug=1; + $this->Execute('BEGIN TRANSACTION adodbseq'); + $ok = $this->Execute("update $seq with (tablock,holdlock) set id = id + 1"); + if (!$ok) { + $this->Execute("create table $seq (id float(53))"); + $ok = $this->Execute("insert into $seq with (tablock,holdlock) values($start)"); + if (!$ok) { + $this->Execute('ROLLBACK TRANSACTION adodbseq'); + return false; + } + $this->Execute('COMMIT TRANSACTION adodbseq'); + return $start; + } + $num = $this->GetOne("select id from $seq"); + $this->Execute('COMMIT TRANSACTION adodbseq'); + return $num; + + // in old implementation, pre 1.90, we returned GUID... + //return $this->GetOne("SELECT CONVERT(varchar(255), NEWID()) AS 'Char'"); + } + + + function &SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) + { + if ($nrows > 0 && $offset <= 0) { + $sql = preg_replace( + '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop." $nrows ",$sql); + $rs =& $this->Execute($sql,$inputarr); + } else + $rs =& ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); + + return $rs; + } + + + // Format date column in sql string given an input format that understands Y M D + function SQLDate($fmt, $col=false) + { + if (!$col) $col = $this->sysTimeStamp; + $s = ''; + + $len = strlen($fmt); + for ($i=0; $i < $len; $i++) { + if ($s) $s .= '+'; + $ch = $fmt[$i]; + switch($ch) { + case 'Y': + case 'y': + $s .= "datename(yyyy,$col)"; + break; + case 'M': + $s .= "convert(char(3),$col,0)"; + break; + case 'm': + $s .= "replace(str(month($col),2),' ','0')"; + break; + case 'Q': + case 'q': + $s .= "datename(quarter,$col)"; + break; + case 'D': + case 'd': + $s .= "replace(str(day($col),2),' ','0')"; + break; + case 'h': + $s .= "substring(convert(char(14),$col,0),13,2)"; + break; + + case 'H': + $s .= "replace(str(datepart(hh,$col),2),' ','0')"; + break; + + case 'i': + $s .= "replace(str(datepart(mi,$col),2),' ','0')"; + break; + case 's': + $s .= "replace(str(datepart(ss,$col),2),' ','0')"; + break; + case 'a': + case 'A': + $s .= "substring(convert(char(19),$col,0),18,2)"; + break; + + default: + if ($ch == '\\') { + $i++; + $ch = substr($fmt,$i,1); + } + $s .= $this->qstr($ch); + break; + } + } + return $s; + } + + + function BeginTrans() + { + if ($this->transOff) return true; + $this->transCnt += 1; + $this->Execute('BEGIN TRAN'); + return true; + } + + function CommitTrans($ok=true) + { + if ($this->transOff) return true; + if (!$ok) return $this->RollbackTrans(); + if ($this->transCnt) $this->transCnt -= 1; + $this->Execute('COMMIT TRAN'); + return true; + } + function RollbackTrans() + { + if ($this->transOff) return true; + if ($this->transCnt) $this->transCnt -= 1; + $this->Execute('ROLLBACK TRAN'); + return true; + } + + /* + Usage: + + $this->BeginTrans(); + $this->RowLock('table1,table2','table1.id=33 and table2.id=table1.id'); # lock row 33 for both tables + + # some operation on both tables table1 and table2 + + $this->CommitTrans(); + + See http://www.swynk.com/friends/achigrik/SQL70Locks.asp + */ + function RowLock($tables,$where,$flds='top 1 null as ignore') + { + if (!$this->transCnt) $this->BeginTrans(); + return $this->GetOne("select $flds from $tables with (ROWLOCK,HOLDLOCK) where $where"); + } + + + function &MetaIndexes($table,$primary=false) + { + $table = $this->qstr($table); + + $sql = "SELECT i.name AS ind_name, C.name AS col_name, USER_NAME(O.uid) AS Owner, c.colid, k.Keyno, + CASE WHEN I.indid BETWEEN 1 AND 254 AND (I.status & 2048 = 2048 OR I.Status = 16402 AND O.XType = 'V') THEN 1 ELSE 0 END AS IsPK, + CASE WHEN I.status & 2 = 2 THEN 1 ELSE 0 END AS IsUnique + FROM dbo.sysobjects o INNER JOIN dbo.sysindexes I ON o.id = i.id + INNER JOIN dbo.sysindexkeys K ON I.id = K.id AND I.Indid = K.Indid + INNER JOIN dbo.syscolumns c ON K.id = C.id AND K.colid = C.Colid + WHERE LEFT(i.name, 8) <> '_WA_Sys_' AND o.status >= 0 AND O.Name LIKE $table + ORDER BY O.name, I.Name, K.keyno"; + + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== FALSE) { + $savem = $this->SetFetchMode(FALSE); + } + + $rs = $this->Execute($sql); + if (isset($savem)) { + $this->SetFetchMode($savem); + } + $ADODB_FETCH_MODE = $save; + + if (!is_object($rs)) { + return FALSE; + } + + $indexes = array(); + while ($row = $rs->FetchRow()) { + if (!$primary && $row[5]) continue; + + $indexes[$row[0]]['unique'] = $row[6]; + $indexes[$row[0]]['columns'][] = $row[1]; + } + return $indexes; + } + + function MetaForeignKeys($table, $owner=false, $upper=false) + { + global $ADODB_FETCH_MODE; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $table = $this->qstr(strtoupper($table)); + + $sql = +"select object_name(constid) as constraint_name, + col_name(fkeyid, fkey) as column_name, + object_name(rkeyid) as referenced_table_name, + col_name(rkeyid, rkey) as referenced_column_name +from sysforeignkeys +where upper(object_name(fkeyid)) = $table +order by constraint_name, referenced_table_name, keyno"; + + $constraints =& $this->GetArray($sql); + + $ADODB_FETCH_MODE = $save; + + $arr = false; + foreach($constraints as $constr) { + //print_r($constr); + $arr[$constr[0]][$constr[2]][] = $constr[1].'='.$constr[3]; + } + if (!$arr) return false; + + $arr2 = false; + + foreach($arr as $k => $v) { + foreach($v as $a => $b) { + if ($upper) $a = strtoupper($a); + $arr2[$a] = $b; + } + } + return $arr2; + } + + //From: Fernando Moreira <FMoreira@imediata.pt> + function MetaDatabases() + { + if(@mssql_select_db("master")) { + $qry=$this->metaDatabasesSQL; + if($rs=@mssql_query($qry)){ + $tmpAr=$ar=array(); + while($tmpAr=@mssql_fetch_row($rs)) + $ar[]=$tmpAr[0]; + @mssql_select_db($this->database); + if(sizeof($ar)) + return($ar); + else + return(false); + } else { + @mssql_select_db($this->database); + return(false); + } + } + return(false); + } + + // "Stein-Aksel Basma" <basma@accelero.no> + // tested with MSSQL 2000 + function &MetaPrimaryKeys($table) + { + global $ADODB_FETCH_MODE; + + $schema = ''; + $this->_findschema($table,$schema); + if (!$schema) $schema = $this->database; + if ($schema) $schema = "and k.table_catalog like '$schema%'"; + + $sql = "select distinct k.column_name,ordinal_position from information_schema.key_column_usage k, + information_schema.table_constraints tc + where tc.constraint_name = k.constraint_name and tc.constraint_type = + 'PRIMARY KEY' and k.table_name = '$table' $schema order by ordinal_position "; + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $a = $this->GetCol($sql); + $ADODB_FETCH_MODE = $savem; + + if ($a && sizeof($a)>0) return $a; + $false = false; + return $false; + } + + + function &MetaTables($ttype=false,$showSchema=false,$mask=false) + { + if ($mask) { + $save = $this->metaTablesSQL; + $mask = $this->qstr(($mask)); + $this->metaTablesSQL .= " AND name like $mask"; + } + $ret =& ADOConnection::MetaTables($ttype,$showSchema); + + if ($mask) { + $this->metaTablesSQL = $save; + } + return $ret; + } + + function SelectDB($dbName) + { + $this->database = $dbName; + $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions + if ($this->_connectionID) { + return @mssql_select_db($dbName); + } + else return false; + } + + function ErrorMsg() + { + if (empty($this->_errorMsg)){ + $this->_errorMsg = mssql_get_last_message(); + } + return $this->_errorMsg; + } + + function ErrorNo() + { + if ($this->_logsql && $this->_errorCode !== false) return $this->_errorCode; + if (empty($this->_errorMsg)) { + $this->_errorMsg = mssql_get_last_message(); + } + $id = @mssql_query("select @@ERROR",$this->_connectionID); + if (!$id) return false; + $arr = mssql_fetch_array($id); + @mssql_free_result($id); + if (is_array($arr)) return $arr[0]; + else return -1; + } + + // returns true or false + function _connect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + if (!function_exists('mssql_pconnect')) return null; + $this->_connectionID = mssql_connect($argHostname,$argUsername,$argPassword); + if ($this->_connectionID === false) return false; + if ($argDatabasename) return $this->SelectDB($argDatabasename); + return true; + } + + + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + if (!function_exists('mssql_pconnect')) return null; + $this->_connectionID = mssql_pconnect($argHostname,$argUsername,$argPassword); + if ($this->_connectionID === false) return false; + + // persistent connections can forget to rollback on crash, so we do it here. + if ($this->autoRollback) { + $cnt = $this->GetOne('select @@TRANCOUNT'); + while (--$cnt >= 0) $this->Execute('ROLLBACK TRAN'); + } + if ($argDatabasename) return $this->SelectDB($argDatabasename); + return true; + } + + function Prepare($sql) + { + $sqlarr = explode('?',$sql); + if (sizeof($sqlarr) <= 1) return $sql; + $sql2 = $sqlarr[0]; + for ($i = 1, $max = sizeof($sqlarr); $i < $max; $i++) { + $sql2 .= '@P'.($i-1) . $sqlarr[$i]; + } + return array($sql,$this->qstr($sql2),$max); + } + + function PrepareSP($sql) + { + if (!$this->_has_mssql_init) { + ADOConnection::outp( "PrepareSP: mssql_init only available since PHP 4.1.0"); + return $sql; + } + $stmt = mssql_init($sql,$this->_connectionID); + if (!$stmt) return $sql; + return array($sql,$stmt); + } + + // returns concatenated string + // MSSQL requires integers to be cast as strings + // automatically cast every datatype to VARCHAR(255) + // @author David Rogers (introspectshun) + function Concat() + { + $s = ""; + $arr = func_get_args(); + + // Split single record on commas, if possible + if (sizeof($arr) == 1) { + foreach ($arr as $arg) { + $args = explode(',', $arg); + } + $arr = $args; + } + + array_walk($arr, create_function('&$v', '$v = "CAST(" . $v . " AS VARCHAR(255))";')); + $s = implode('+',$arr); + if (sizeof($arr) > 0) return "$s"; + + return ''; + } + + /* + Usage: + $stmt = $db->PrepareSP('SP_RUNSOMETHING'); -- takes 2 params, @myid and @group + + # note that the parameter does not have @ in front! + $db->Parameter($stmt,$id,'myid'); + $db->Parameter($stmt,$group,'group',false,64); + $db->Execute($stmt); + + @param $stmt Statement returned by Prepare() or PrepareSP(). + @param $var PHP variable to bind to. Can set to null (for isNull support). + @param $name Name of stored procedure variable name to bind to. + @param [$isOutput] Indicates direction of parameter 0/false=IN 1=OUT 2= IN/OUT. This is ignored in oci8. + @param [$maxLen] Holds an maximum length of the variable. + @param [$type] The data type of $var. Legal values depend on driver. + + See mssql_bind documentation at php.net. + */ + function Parameter(&$stmt, &$var, $name, $isOutput=false, $maxLen=4000, $type=false) + { + if (!$this->_has_mssql_init) { + ADOConnection::outp( "Parameter: mssql_bind only available since PHP 4.1.0"); + return false; + } + + $isNull = is_null($var); // php 4.0.4 and above... + + if ($type === false) + switch(gettype($var)) { + default: + case 'string': $type = SQLCHAR; break; + case 'double': $type = SQLFLT8; break; + case 'integer': $type = SQLINT4; break; + case 'boolean': $type = SQLINT1; break; # SQLBIT not supported in 4.1.0 + } + + if ($this->debug) { + $prefix = ($isOutput) ? 'Out' : 'In'; + $ztype = (empty($type)) ? 'false' : $type; + ADOConnection::outp( "{$prefix}Parameter(\$stmt, \$php_var='$var', \$name='$name', \$maxLen=$maxLen, \$type=$ztype);"); + } + /* + See http://phplens.com/lens/lensforum/msgs.php?id=7231 + + RETVAL is HARD CODED into php_mssql extension: + The return value (a long integer value) is treated like a special OUTPUT parameter, + called "RETVAL" (without the @). See the example at mssql_execute to + see how it works. - type: one of this new supported PHP constants. + SQLTEXT, SQLVARCHAR,SQLCHAR, SQLINT1,SQLINT2, SQLINT4, SQLBIT,SQLFLT8 + */ + if ($name !== 'RETVAL') $name = '@'.$name; + return mssql_bind($stmt[1], $name, $var, $type, $isOutput, $isNull, $maxLen); + } + + /* + Unfortunately, it appears that mssql cannot handle varbinary > 255 chars + So all your blobs must be of type "image". + + Remember to set in php.ini the following... + + ; Valid range 0 - 2147483647. Default = 4096. + mssql.textlimit = 0 ; zero to pass through + + ; Valid range 0 - 2147483647. Default = 4096. + mssql.textsize = 0 ; zero to pass through + */ + function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') + { + + if (strtoupper($blobtype) == 'CLOB') { + $sql = "UPDATE $table SET $column='" . $val . "' WHERE $where"; + return $this->Execute($sql) != false; + } + $sql = "UPDATE $table SET $column=0x".bin2hex($val)." WHERE $where"; + return $this->Execute($sql) != false; + } + + // returns query ID if successful, otherwise false + function _query($sql,$inputarr) + { + $this->_errorMsg = false; + if (is_array($inputarr)) { + + # bind input params with sp_executesql: + # see http://www.quest-pipelines.com/newsletter-v3/0402_F.htm + # works only with sql server 7 and newer + if (!is_array($sql)) $sql = $this->Prepare($sql); + $params = ''; + $decl = ''; + $i = 0; + foreach($inputarr as $v) { + if ($decl) { + $decl .= ', '; + $params .= ', '; + } + if (is_string($v)) { + $len = strlen($v); + if ($len == 0) $len = 1; + + if ($len > 4000 ) { + // NVARCHAR is max 4000 chars. Let's use NTEXT + $decl .= "@P$i NTEXT"; + } else { + $decl .= "@P$i NVARCHAR($len)"; + } + + $params .= "@P$i=N". (strncmp($v,"'",1)==0? $v : $this->qstr($v)); + } else if (is_integer($v)) { + $decl .= "@P$i INT"; + $params .= "@P$i=".$v; + } else if (is_float($v)) { + $decl .= "@P$i FLOAT"; + $params .= "@P$i=".$v; + } else if (is_bool($v)) { + $decl .= "@P$i INT"; # Used INT just in case BIT in not supported on the user's MSSQL version. It will cast appropriately. + $params .= "@P$i=".(($v)?'1':'0'); # True == 1 in MSSQL BIT fields and acceptable for storing logical true in an int field + } else { + $decl .= "@P$i CHAR"; # Used char because a type is required even when the value is to be NULL. + $params .= "@P$i=NULL"; + } + $i += 1; + } + $decl = $this->qstr($decl); + if ($this->debug) ADOConnection::outp("<font size=-1>sp_executesql N{$sql[1]},N$decl,$params</font>"); + $rez = mssql_query("sp_executesql N{$sql[1]},N$decl,$params"); + + } else if (is_array($sql)) { + # PrepareSP() + $rez = mssql_execute($sql[1]); + + } else { + $rez = mssql_query($sql,$this->_connectionID); + } + return $rez; + } + + // returns true or false + function _close() + { + if ($this->transCnt) $this->RollbackTrans(); + $rez = @mssql_close($this->_connectionID); + $this->_connectionID = false; + return $rez; + } + + // mssql uses a default date like Dec 30 2000 12:00AM + function UnixDate($v) + { + return ADORecordSet_array_mssql::UnixDate($v); + } + + function UnixTimeStamp($v) + { + return ADORecordSet_array_mssql::UnixTimeStamp($v); + } +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordset_mssql extends ADORecordSet { + + var $databaseType = "mssql"; + var $canSeek = true; + var $hasFetchAssoc; // see http://phplens.com/lens/lensforum/msgs.php?id=6083 + // _mths works only in non-localised system + + function ADORecordset_mssql($id,$mode=false) + { + // freedts check... + $this->hasFetchAssoc = function_exists('mssql_fetch_assoc'); + + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + + } + $this->fetchMode = $mode; + return $this->ADORecordSet($id,$mode); + } + + + function _initrs() + { + GLOBAL $ADODB_COUNTRECS; + $this->_numOfRows = ($ADODB_COUNTRECS)? @mssql_num_rows($this->_queryID):-1; + $this->_numOfFields = @mssql_num_fields($this->_queryID); + } + + + //Contributed by "Sven Axelsson" <sven.axelsson@bokochwebb.se> + // get next resultset - requires PHP 4.0.5 or later + function NextRecordSet() + { + if (!mssql_next_result($this->_queryID)) return false; + $this->_inited = false; + $this->bind = false; + $this->_currentRow = -1; + $this->Init(); + return true; + } + + /* Use associative array to get fields array */ + function Fields($colname) + { + if ($this->fetchMode != ADODB_FETCH_NUM) return $this->fields[$colname]; + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + /* Returns: an object containing field information. + Get column information in the Recordset object. fetchField() can be used in order to obtain information about + fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by + fetchField() is retrieved. */ + + function &FetchField($fieldOffset = -1) + { + if ($fieldOffset != -1) { + $f = @mssql_fetch_field($this->_queryID, $fieldOffset); + } + else if ($fieldOffset == -1) { /* The $fieldOffset argument is not provided thus its -1 */ + $f = @mssql_fetch_field($this->_queryID); + } + $false = false; + if (empty($f)) return $false; + return $f; + } + + function _seek($row) + { + return @mssql_data_seek($this->_queryID, $row); + } + + // speedup + function MoveNext() + { + if ($this->EOF) return false; + + $this->_currentRow++; + + if ($this->fetchMode & ADODB_FETCH_ASSOC) { + if ($this->fetchMode & ADODB_FETCH_NUM) { + //ADODB_FETCH_BOTH mode + $this->fields = @mssql_fetch_array($this->_queryID); + } + else { + if ($this->hasFetchAssoc) {// only for PHP 4.2.0 or later + $this->fields = @mssql_fetch_assoc($this->_queryID); + } else { + $flds = @mssql_fetch_array($this->_queryID); + if (is_array($flds)) { + $fassoc = array(); + foreach($flds as $k => $v) { + if (is_numeric($k)) continue; + $fassoc[$k] = $v; + } + $this->fields = $fassoc; + } else + $this->fields = false; + } + } + + if (is_array($this->fields)) { + if (ADODB_ASSOC_CASE == 0) { + foreach($this->fields as $k=>$v) { + $this->fields[strtolower($k)] = $v; + } + } else if (ADODB_ASSOC_CASE == 1) { + foreach($this->fields as $k=>$v) { + $this->fields[strtoupper($k)] = $v; + } + } + } + } else { + $this->fields = @mssql_fetch_row($this->_queryID); + } + if ($this->fields) return true; + $this->EOF = true; + + return false; + } + + + // INSERT UPDATE DELETE returns false even if no error occurs in 4.0.4 + // also the date format has been changed from YYYY-mm-dd to dd MMM YYYY in 4.0.4. Idiot! + function _fetch($ignore_fields=false) + { + if ($this->fetchMode & ADODB_FETCH_ASSOC) { + if ($this->fetchMode & ADODB_FETCH_NUM) { + //ADODB_FETCH_BOTH mode + $this->fields = @mssql_fetch_array($this->_queryID); + } else { + if ($this->hasFetchAssoc) // only for PHP 4.2.0 or later + $this->fields = @mssql_fetch_assoc($this->_queryID); + else { + $this->fields = @mssql_fetch_array($this->_queryID); + if (@is_array($$this->fields)) { + $fassoc = array(); + foreach($$this->fields as $k => $v) { + if (is_integer($k)) continue; + $fassoc[$k] = $v; + } + $this->fields = $fassoc; + } + } + } + + if (!$this->fields) { + } else if (ADODB_ASSOC_CASE == 0) { + foreach($this->fields as $k=>$v) { + $this->fields[strtolower($k)] = $v; + } + } else if (ADODB_ASSOC_CASE == 1) { + foreach($this->fields as $k=>$v) { + $this->fields[strtoupper($k)] = $v; + } + } + } else { + $this->fields = @mssql_fetch_row($this->_queryID); + } + return $this->fields; + } + + /* close() only needs to be called if you are worried about using too much memory while your script + is running. All associated result memory for the specified result identifier will automatically be freed. */ + + function _close() + { + $rez = mssql_free_result($this->_queryID); + $this->_queryID = false; + return $rez; + } + // mssql uses a default date like Dec 30 2000 12:00AM + function UnixDate($v) + { + return ADORecordSet_array_mssql::UnixDate($v); + } + + function UnixTimeStamp($v) + { + return ADORecordSet_array_mssql::UnixTimeStamp($v); + } + +} + + +class ADORecordSet_array_mssql extends ADORecordSet_array { + function ADORecordSet_array_mssql($id=-1,$mode=false) + { + $this->ADORecordSet_array($id,$mode); + } + + // mssql uses a default date like Dec 30 2000 12:00AM + function UnixDate($v) + { + + if (is_numeric(substr($v,0,1)) && ADODB_PHPVER >= 0x4200) return parent::UnixDate($v); + + global $ADODB_mssql_mths,$ADODB_mssql_date_order; + + //Dec 30 2000 12:00AM + if ($ADODB_mssql_date_order == 'dmy') { + if (!preg_match( "|^([0-9]{1,2})[-/\. ]+([A-Za-z]{3})[-/\. ]+([0-9]{4})|" ,$v, $rr)) { + return parent::UnixDate($v); + } + if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0; + + $theday = $rr[1]; + $themth = substr(strtoupper($rr[2]),0,3); + } else { + if (!preg_match( "|^([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4})|" ,$v, $rr)) { + return parent::UnixDate($v); + } + if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0; + + $theday = $rr[2]; + $themth = substr(strtoupper($rr[1]),0,3); + } + $themth = $ADODB_mssql_mths[$themth]; + if ($themth <= 0) return false; + // h-m-s-MM-DD-YY + return mktime(0,0,0,$themth,$theday,$rr[3]); + } + + function UnixTimeStamp($v) + { + + if (is_numeric(substr($v,0,1)) && ADODB_PHPVER >= 0x4200) return parent::UnixTimeStamp($v); + + global $ADODB_mssql_mths,$ADODB_mssql_date_order; + + //Dec 30 2000 12:00AM + if ($ADODB_mssql_date_order == 'dmy') { + if (!preg_match( "|^([0-9]{1,2})[-/\. ]+([A-Za-z]{3})[-/\. ]+([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})|" + ,$v, $rr)) return parent::UnixTimeStamp($v); + if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0; + + $theday = $rr[1]; + $themth = substr(strtoupper($rr[2]),0,3); + } else { + if (!preg_match( "|^([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})|" + ,$v, $rr)) return parent::UnixTimeStamp($v); + if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0; + + $theday = $rr[2]; + $themth = substr(strtoupper($rr[1]),0,3); + } + + $themth = $ADODB_mssql_mths[$themth]; + if ($themth <= 0) return false; + + switch (strtoupper($rr[6])) { + case 'P': + if ($rr[4]<12) $rr[4] += 12; + break; + case 'A': + if ($rr[4]==12) $rr[4] = 0; + break; + default: + break; + } + // h-m-s-MM-DD-YY + return mktime($rr[4],$rr[5],0,$themth,$theday,$rr[3]); + } +} + +/* +Code Example 1: + +select object_name(constid) as constraint_name, + object_name(fkeyid) as table_name, + col_name(fkeyid, fkey) as column_name, + object_name(rkeyid) as referenced_table_name, + col_name(rkeyid, rkey) as referenced_column_name +from sysforeignkeys +where object_name(fkeyid) = x +order by constraint_name, table_name, referenced_table_name, keyno + +Code Example 2: +select constraint_name, + column_name, + ordinal_position +from information_schema.key_column_usage +where constraint_catalog = db_name() +and table_name = x +order by constraint_name, ordinal_position + +http://www.databasejournal.com/scripts/article.php/1440551 +*/ + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-mssqlpo.inc.php b/framework/DataAccess/adodb/drivers/adodb-mssqlpo.inc.php new file mode 100644 index 00000000..93d3dd1c --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-mssqlpo.inc.php @@ -0,0 +1,62 @@ +<?php +/** +* @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. +* Released under both BSD license and Lesser GPL library license. +* Whenever there is any discrepancy between the two licenses, +* the BSD license will take precedence. +* +* Set tabs to 4 for best viewing. +* +* Latest version is available at http://php.weblogs.com +* +* Portable MSSQL Driver that supports || instead of + +* +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + + +/* + The big difference between mssqlpo and it's parent mssql is that mssqlpo supports + the more standard || string concatenation operator. +*/ + +include_once(ADODB_DIR.'/drivers/adodb-mssql.inc.php'); + +class ADODB_mssqlpo extends ADODB_mssql { + var $databaseType = "mssqlpo"; + var $concat_operator = '||'; + + function ADODB_mssqlpo() + { + ADODB_mssql::ADODB_mssql(); + } + + function PrepareSP($sql) + { + if (!$this->_has_mssql_init) { + ADOConnection::outp( "PrepareSP: mssql_init only available since PHP 4.1.0"); + return $sql; + } + if (is_string($sql)) $sql = str_replace('||','+',$sql); + $stmt = mssql_init($sql,$this->_connectionID); + if (!$stmt) return $sql; + return array($sql,$stmt); + } + + function _query($sql,$inputarr) + { + if (is_string($sql)) $sql = str_replace('||','+',$sql); + return ADODB_mssql::_query($sql,$inputarr); + } +} + +class ADORecordset_mssqlpo extends ADORecordset_mssql { + var $databaseType = "mssqlpo"; + function ADORecordset_mssqlpo($id,$mode=false) + { + $this->ADORecordset_mssql($id,$mode); + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-mysql.inc.php b/framework/DataAccess/adodb/drivers/adodb-mysql.inc.php new file mode 100644 index 00000000..34e9c17e --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-mysql.inc.php @@ -0,0 +1,775 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 8. + + MySQL code that does not support transactions. Use mysqlt if you need transactions. + Requires mysql client. Works on Windows and Unix. + + 28 Feb 2001: MetaColumns bug fix - suggested by Freek Dijkstra (phpeverywhere@macfreek.com) +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (! defined("_ADODB_MYSQL_LAYER")) { + define("_ADODB_MYSQL_LAYER", 1 ); + +class ADODB_mysql extends ADOConnection { + var $databaseType = 'mysql'; + var $dataProvider = 'mysql'; + var $hasInsertID = true; + var $hasAffectedRows = true; + var $metaTablesSQL = "SHOW TABLES"; + var $metaColumnsSQL = "SHOW COLUMNS FROM %s"; + var $fmtTimeStamp = "'Y-m-d H:i:s'"; + var $hasLimit = true; + var $hasMoveFirst = true; + var $hasGenID = true; + var $isoDates = true; // accepts dates in ISO format + var $sysDate = 'CURDATE()'; + var $sysTimeStamp = 'NOW()'; + var $hasTransactions = false; + var $forceNewConnect = false; + var $poorAffectedRows = true; + var $clientFlags = 0; + var $substr = "substring"; + var $nameQuote = '`'; /// string to use to quote identifiers and names + + function ADODB_mysql() + { + if (defined('ADODB_EXTENSION')) $this->rsPrefix .= 'ext_'; + } + + function ServerInfo() + { + $arr['description'] = ADOConnection::GetOne("select version()"); + $arr['version'] = ADOConnection::_findvers($arr['description']); + return $arr; + } + + function IfNull( $field, $ifNull ) + { + return " IFNULL($field, $ifNull) "; // if MySQL + } + + + function &MetaTables($ttype=false,$showSchema=false,$mask=false) + { + $save = $this->metaTablesSQL; + if ($showSchema && is_string($showSchema)) { + $this->metaTablesSQL .= " from $showSchema"; + } + + if ($mask) { + $mask = $this->qstr($mask); + $this->metaTablesSQL .= " like $mask"; + } + $ret =& ADOConnection::MetaTables($ttype,$showSchema); + + $this->metaTablesSQL = $save; + return $ret; + } + + + function &MetaIndexes ($table, $primary = FALSE, $owner=false) + { + // save old fetch mode + global $ADODB_FETCH_MODE; + + $false = false; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== FALSE) { + $savem = $this->SetFetchMode(FALSE); + } + + // get index details + $rs = $this->Execute(sprintf('SHOW INDEX FROM %s',$table)); + + // restore fetchmode + if (isset($savem)) { + $this->SetFetchMode($savem); + } + $ADODB_FETCH_MODE = $save; + + if (!is_object($rs)) { + return $false; + } + + $indexes = array (); + + // parse index data into array + while ($row = $rs->FetchRow()) { + if ($primary == FALSE AND $row[2] == 'PRIMARY') { + continue; + } + + if (!isset($indexes[$row[2]])) { + $indexes[$row[2]] = array( + 'unique' => ($row[1] == 0), + 'columns' => array() + ); + } + + $indexes[$row[2]]['columns'][$row[3] - 1] = $row[4]; + } + + // sort columns by order in the index + foreach ( array_keys ($indexes) as $index ) + { + ksort ($indexes[$index]['columns']); + } + + return $indexes; + } + + + // if magic quotes disabled, use mysql_real_escape_string() + function qstr($s,$magic_quotes=false) + { + if (!$magic_quotes) { + + if (ADODB_PHPVER >= 0x4300) { + if (is_resource($this->_connectionID)) + return "'".mysql_real_escape_string($s,$this->_connectionID)."'"; + } + if ($this->replaceQuote[0] == '\\'){ + $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s); + } + return "'".str_replace("'",$this->replaceQuote,$s)."'"; + } + + // undo magic quotes for " + $s = str_replace('\\"','"',$s); + return "'$s'"; + } + + function _insertid() + { + return ADOConnection::GetOne('SELECT LAST_INSERT_ID()'); + //return mysql_insert_id($this->_connectionID); + } + + function GetOne($sql,$inputarr=false) + { + if (strncasecmp($sql,'sele',4) == 0) { + $rs =& $this->SelectLimit($sql,1,-1,$inputarr); + if ($rs) { + $rs->Close(); + if ($rs->EOF) return false; + return reset($rs->fields); + } + } else { + return ADOConnection::GetOne($sql,$inputarr); + } + return false; + } + + function BeginTrans() + { + if ($this->debug) ADOConnection::outp("Transactions not supported in 'mysql' driver. Use 'mysqlt' or 'mysqli' driver"); + } + + function _affectedrows() + { + return mysql_affected_rows($this->_connectionID); + } + + // See http://www.mysql.com/doc/M/i/Miscellaneous_functions.html + // Reference on Last_Insert_ID on the recommended way to simulate sequences + var $_genIDSQL = "update %s set id=LAST_INSERT_ID(id+1);"; + var $_genSeqSQL = "create table %s (id int not null)"; + var $_genSeq2SQL = "insert into %s values (%s)"; + var $_dropSeqSQL = "drop table %s"; + + function CreateSequence($seqname='adodbseq',$startID=1) + { + if (empty($this->_genSeqSQL)) return false; + $u = strtoupper($seqname); + + $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname)); + if (!$ok) return false; + return $this->Execute(sprintf($this->_genSeq2SQL,$seqname,$startID-1)); + } + + + function GenID($seqname='adodbseq',$startID=1) + { + // post-nuke sets hasGenID to false + if (!$this->hasGenID) return false; + + $savelog = $this->_logsql; + $this->_logsql = false; + $getnext = sprintf($this->_genIDSQL,$seqname); + $holdtransOK = $this->_transOK; // save the current status + $rs = @$this->Execute($getnext); + if (!$rs) { + if ($holdtransOK) $this->_transOK = true; //if the status was ok before reset + $u = strtoupper($seqname); + $this->Execute(sprintf($this->_genSeqSQL,$seqname)); + $this->Execute(sprintf($this->_genSeq2SQL,$seqname,$startID-1)); + $rs = $this->Execute($getnext); + } + $this->genID = mysql_insert_id($this->_connectionID); + + if ($rs) $rs->Close(); + + $this->_logsql = $savelog; + return $this->genID; + } + + function &MetaDatabases() + { + $qid = mysql_list_dbs($this->_connectionID); + $arr = array(); + $i = 0; + $max = mysql_num_rows($qid); + while ($i < $max) { + $db = mysql_tablename($qid,$i); + if ($db != 'mysql') $arr[] = $db; + $i += 1; + } + return $arr; + } + + + // Format date column in sql string given an input format that understands Y M D + function SQLDate($fmt, $col=false) + { + if (!$col) $col = $this->sysTimeStamp; + $s = 'DATE_FORMAT('.$col.",'"; + $concat = false; + $len = strlen($fmt); + for ($i=0; $i < $len; $i++) { + $ch = $fmt[$i]; + switch($ch) { + + default: + if ($ch == '\\') { + $i++; + $ch = substr($fmt,$i,1); + } + /** FALL THROUGH */ + case '-': + case '/': + $s .= $ch; + break; + + case 'Y': + case 'y': + $s .= '%Y'; + break; + case 'M': + $s .= '%b'; + break; + + case 'm': + $s .= '%m'; + break; + case 'D': + case 'd': + $s .= '%d'; + break; + + case 'Q': + case 'q': + $s .= "'),Quarter($col)"; + + if ($len > $i+1) $s .= ",DATE_FORMAT($col,'"; + else $s .= ",('"; + $concat = true; + break; + + case 'H': + $s .= '%H'; + break; + + case 'h': + $s .= '%I'; + break; + + case 'i': + $s .= '%i'; + break; + + case 's': + $s .= '%s'; + break; + + case 'a': + case 'A': + $s .= '%p'; + break; + + case 'w': + $s .= '%w'; + break; + + case 'W': + $s .= '%U'; + break; + + case 'l': + $s .= '%W'; + break; + } + } + $s.="')"; + if ($concat) $s = "CONCAT($s)"; + return $s; + } + + + // returns concatenated string + // much easier to run "mysqld --ansi" or "mysqld --sql-mode=PIPES_AS_CONCAT" and use || operator + function Concat() + { + $s = ""; + $arr = func_get_args(); + + // suggestion by andrew005@mnogo.ru + $s = implode(',',$arr); + if (strlen($s) > 0) return "CONCAT($s)"; + else return ''; + } + + function OffsetDate($dayFraction,$date=false) + { + if (!$date) $date = $this->sysDate; + $fraction = $dayFraction * 24 * 3600; + return "from_unixtime(unix_timestamp($date)+$fraction)"; + } + + // returns true or false + function _connect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + if (!empty($this->port)) $argHostname .= ":".$this->port; + + if (ADODB_PHPVER >= 0x4300) + $this->_connectionID = mysql_connect($argHostname,$argUsername,$argPassword, + $this->forceNewConnect,$this->clientFlags); + else if (ADODB_PHPVER >= 0x4200) + $this->_connectionID = mysql_connect($argHostname,$argUsername,$argPassword, + $this->forceNewConnect); + else + $this->_connectionID = mysql_connect($argHostname,$argUsername,$argPassword); + + if ($this->_connectionID === false) return false; + if ($argDatabasename) return $this->SelectDB($argDatabasename); + return true; + } + + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + if (!empty($this->port)) $argHostname .= ":".$this->port; + + if (ADODB_PHPVER >= 0x4300) + $this->_connectionID = mysql_pconnect($argHostname,$argUsername,$argPassword,$this->clientFlags); + else + $this->_connectionID = mysql_pconnect($argHostname,$argUsername,$argPassword); + if ($this->_connectionID === false) return false; + if ($this->autoRollback) $this->RollbackTrans(); + if ($argDatabasename) return $this->SelectDB($argDatabasename); + return true; + } + + function _nconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + $this->forceNewConnect = true; + return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename); + } + + function &MetaColumns($table) + { + $this->_findschema($table,$schema); + if ($schema) { + $dbName = $this->database; + $this->SelectDB($schema); + } + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table)); + + if ($schema) { + $this->SelectDB($dbName); + } + + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + if (!is_object($rs)) { + $false = false; + return $false; + } + + $retarr = array(); + while (!$rs->EOF){ + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[0]; + $type = $rs->fields[1]; + + // split type into type(length): + $fld->scale = null; + if (preg_match("/^(.+)\((\d+),(\d+)/", $type, $query_array)) { + $fld->type = $query_array[1]; + $fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1; + $fld->scale = is_numeric($query_array[3]) ? $query_array[3] : -1; + } elseif (preg_match("/^(.+)\((\d+)/", $type, $query_array)) { + $fld->type = $query_array[1]; + $fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1; + } elseif (preg_match("/^(enum)\((.*)\)$/i", $type, $query_array)) { + $fld->type = $query_array[1]; + $arr = explode(",",$query_array[2]); + $fld->enums = $arr; + $zlen = max(array_map("strlen",$arr)) - 2; // PHP >= 4.0.6 + $fld->max_length = ($zlen > 0) ? $zlen : 1; + } else { + $fld->type = $type; + $fld->max_length = -1; + } + $fld->not_null = ($rs->fields[2] != 'YES'); + $fld->primary_key = ($rs->fields[3] == 'PRI'); + $fld->auto_increment = (strpos($rs->fields[5], 'auto_increment') !== false); + $fld->binary = (strpos($type,'blob') !== false); + $fld->unsigned = (strpos($type,'unsigned') !== false); + + if (!$fld->binary) { + $d = $rs->fields[4]; + if ($d != '' && $d != 'NULL') { + $fld->has_default = true; + $fld->default_value = $d; + } else { + $fld->has_default = false; + } + } + + if ($save == ADODB_FETCH_NUM) { + $retarr[] = $fld; + } else { + $retarr[strtoupper($fld->name)] = $fld; + } + $rs->MoveNext(); + } + + $rs->Close(); + return $retarr; + } + + // returns true or false + function SelectDB($dbName) + { + $this->database = $dbName; + $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions + if ($this->_connectionID) { + return @mysql_select_db($dbName,$this->_connectionID); + } + else return false; + } + + // parameters use PostgreSQL convention, not MySQL + function &SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs=0) + { + $offsetStr =($offset>=0) ? ((integer)$offset)."," : ''; + // jason judge, see http://phplens.com/lens/lensforum/msgs.php?id=9220 + if ($nrows < 0) $nrows = '18446744073709551615'; + + if ($secs) + $rs =& $this->CacheExecute($secs,$sql." LIMIT $offsetStr".((integer)$nrows),$inputarr); + else + $rs =& $this->Execute($sql." LIMIT $offsetStr".((integer)$nrows),$inputarr); + return $rs; + } + + // returns queryID or false + function _query($sql,$inputarr) + { + //global $ADODB_COUNTRECS; + //if($ADODB_COUNTRECS) + return mysql_query($sql,$this->_connectionID); + //else return @mysql_unbuffered_query($sql,$this->_connectionID); // requires PHP >= 4.0.6 + } + + /* Returns: the last error message from previous database operation */ + function ErrorMsg() + { + + if ($this->_logsql) return $this->_errorMsg; + if (empty($this->_connectionID)) $this->_errorMsg = @mysql_error(); + else $this->_errorMsg = @mysql_error($this->_connectionID); + return $this->_errorMsg; + } + + /* Returns: the last error number from previous database operation */ + function ErrorNo() + { + if ($this->_logsql) return $this->_errorCode; + if (empty($this->_connectionID)) return @mysql_errno(); + else return @mysql_errno($this->_connectionID); + } + + // returns true or false + function _close() + { + @mysql_close($this->_connectionID); + $this->_connectionID = false; + } + + + /* + * Maximum size of C field + */ + function CharMax() + { + return 255; + } + + /* + * Maximum size of X field + */ + function TextMax() + { + return 4294967295; + } + + // "Innox - Juan Carlos Gonzalez" <jgonzalez#innox.com.mx> + function MetaForeignKeys( $table, $owner = FALSE, $upper = FALSE, $associative = FALSE ) + { + if ( !empty($owner) ) { + $table = "$owner.$table"; + } + $a_create_table = $this->getRow(sprintf('SHOW CREATE TABLE %s', $table)); + if ($associative) $create_sql = $a_create_table["Create Table"]; + else $create_sql = $a_create_table[1]; + + $matches = array(); + + if (!preg_match_all("/FOREIGN KEY \(`(.*?)`\) REFERENCES `(.*?)` \(`(.*?)`\)/", $create_sql, $matches)) return false; + $foreign_keys = array(); + $num_keys = count($matches[0]); + for ( $i = 0; $i < $num_keys; $i ++ ) { + $my_field = explode('`, `', $matches[1][$i]); + $ref_table = $matches[2][$i]; + $ref_field = explode('`, `', $matches[3][$i]); + + if ( $upper ) { + $ref_table = strtoupper($ref_table); + } + + $foreign_keys[$ref_table] = array(); + $num_fields = count($my_field); + for ( $j = 0; $j < $num_fields; $j ++ ) { + if ( $associative ) { + $foreign_keys[$ref_table][$ref_field[$j]] = $my_field[$j]; + } else { + $foreign_keys[$ref_table][] = "{$my_field[$j]}={$ref_field[$j]}"; + } + } + } + + return $foreign_keys; + } + + +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + + +class ADORecordSet_mysql extends ADORecordSet{ + + var $databaseType = "mysql"; + var $canSeek = true; + + function ADORecordSet_mysql($queryID,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + switch ($mode) + { + case ADODB_FETCH_NUM: $this->fetchMode = MYSQL_NUM; break; + case ADODB_FETCH_ASSOC:$this->fetchMode = MYSQL_ASSOC; break; + case ADODB_FETCH_DEFAULT: + case ADODB_FETCH_BOTH: + default: + $this->fetchMode = MYSQL_BOTH; break; + } + $this->adodbFetchMode = $mode; + $this->ADORecordSet($queryID); + } + + function _initrs() + { + //GLOBAL $ADODB_COUNTRECS; + // $this->_numOfRows = ($ADODB_COUNTRECS) ? @mysql_num_rows($this->_queryID):-1; + $this->_numOfRows = @mysql_num_rows($this->_queryID); + $this->_numOfFields = @mysql_num_fields($this->_queryID); + } + + function &FetchField($fieldOffset = -1) + { + if ($fieldOffset != -1) { + $o = @mysql_fetch_field($this->_queryID, $fieldOffset); + $f = @mysql_field_flags($this->_queryID,$fieldOffset); + $o->max_length = @mysql_field_len($this->_queryID,$fieldOffset); // suggested by: Jim Nicholson (jnich@att.com) + //$o->max_length = -1; // mysql returns the max length less spaces -- so it is unrealiable + $o->binary = (strpos($f,'binary')!== false); + } + else if ($fieldOffset == -1) { /* The $fieldOffset argument is not provided thus its -1 */ + $o = @mysql_fetch_field($this->_queryID); + $o->max_length = @mysql_field_len($this->_queryID); // suggested by: Jim Nicholson (jnich@att.com) + //$o->max_length = -1; // mysql returns the max length less spaces -- so it is unrealiable + } + + return $o; + } + + function &GetRowAssoc($upper=true) + { + if ($this->fetchMode == MYSQL_ASSOC && !$upper) return $this->fields; + $row =& ADORecordSet::GetRowAssoc($upper); + return $row; + } + + /* Use associative array to get fields array */ + function Fields($colname) + { + // added @ by "Michael William Miller" <mille562@pilot.msu.edu> + if ($this->fetchMode != MYSQL_NUM) return @$this->fields[$colname]; + + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + function _seek($row) + { + if ($this->_numOfRows == 0) return false; + return @mysql_data_seek($this->_queryID,$row); + } + + function MoveNext() + { + //return adodb_movenext($this); + //if (defined('ADODB_EXTENSION')) return adodb_movenext($this); + if (@$this->fields = mysql_fetch_array($this->_queryID,$this->fetchMode)) { + $this->_currentRow += 1; + return true; + } + if (!$this->EOF) { + $this->_currentRow += 1; + $this->EOF = true; + } + return false; + } + + function _fetch() + { + $this->fields = @mysql_fetch_array($this->_queryID,$this->fetchMode); + return is_array($this->fields); + } + + function _close() { + @mysql_free_result($this->_queryID); + $this->_queryID = false; + } + + function MetaType($t,$len=-1,$fieldobj=false) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + + $len = -1; // mysql max_length is not accurate + switch (strtoupper($t)) { + case 'STRING': + case 'CHAR': + case 'VARCHAR': + case 'TINYBLOB': + case 'TINYTEXT': + case 'ENUM': + case 'SET': + if ($len <= $this->blobSize) return 'C'; + + case 'TEXT': + case 'LONGTEXT': + case 'MEDIUMTEXT': + return 'X'; + + // php_mysql extension always returns 'blob' even if 'text' + // so we have to check whether binary... + case 'IMAGE': + case 'LONGBLOB': + case 'BLOB': + case 'MEDIUMBLOB': + return !empty($fieldobj->binary) ? 'B' : 'X'; + + case 'YEAR': + case 'DATE': return 'D'; + + case 'TIME': + case 'DATETIME': + case 'TIMESTAMP': return 'T'; + + case 'INT': + case 'INTEGER': + case 'BIGINT': + case 'TINYINT': + case 'MEDIUMINT': + case 'SMALLINT': + + if (!empty($fieldobj->primary_key)) return 'R'; + else return 'I'; + + default: return 'N'; + } + } + +} + +class ADORecordSet_ext_mysql extends ADORecordSet_mysql { + function ADORecordSet_ext_mysql($queryID,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + switch ($mode) + { + case ADODB_FETCH_NUM: $this->fetchMode = MYSQL_NUM; break; + case ADODB_FETCH_ASSOC:$this->fetchMode = MYSQL_ASSOC; break; + case ADODB_FETCH_DEFAULT: + case ADODB_FETCH_BOTH: + default: + $this->fetchMode = MYSQL_BOTH; break; + } + $this->adodbFetchMode = $mode; + $this->ADORecordSet($queryID); + } + + function MoveNext() + { + return @adodb_movenext($this); + } +} + + +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-mysqli.inc.php b/framework/DataAccess/adodb/drivers/adodb-mysqli.inc.php new file mode 100644 index 00000000..ed649d23 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-mysqli.inc.php @@ -0,0 +1,986 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 8. + + MySQL code that does not support transactions. Use mysqlt if you need transactions. + Requires mysql client. Works on Windows and Unix. + +21 October 2003: MySQLi extension implementation by Arjen de Rijke (a.de.rijke@xs4all.nl) +Based on adodb 3.40 +*/ + +// security - hide paths +//if (!defined('ADODB_DIR')) die(); + +if (! defined("_ADODB_MYSQLI_LAYER")) { + define("_ADODB_MYSQLI_LAYER", 1 ); + + // PHP5 compat... + if (! defined("MYSQLI_BINARY_FLAG")) define("MYSQLI_BINARY_FLAG", 128); + if (!defined('MYSQLI_READ_DEFAULT_GROUP')) define('MYSQLI_READ_DEFAULT_GROUP',1); + + // disable adodb extension - currently incompatible. + global $ADODB_EXTENSION; $ADODB_EXTENSION = false; + +class ADODB_mysqli extends ADOConnection { + var $databaseType = 'mysqli'; + var $dataProvider = 'native'; + var $hasInsertID = true; + var $hasAffectedRows = true; + var $metaTablesSQL = "SHOW TABLES"; + var $metaColumnsSQL = "SHOW COLUMNS FROM %s"; + var $fmtTimeStamp = "'Y-m-d H:i:s'"; + var $hasLimit = true; + var $hasMoveFirst = true; + var $hasGenID = true; + var $isoDates = true; // accepts dates in ISO format + var $sysDate = 'CURDATE()'; + var $sysTimeStamp = 'NOW()'; + var $hasTransactions = true; + var $forceNewConnect = false; + var $poorAffectedRows = true; + var $clientFlags = 0; + var $substr = "substring"; + var $port = false; + var $socket = false; + var $_bindInputArray = false; + var $nameQuote = '`'; /// string to use to quote identifiers and names + var $optionFlags = array(array(MYSQLI_READ_DEFAULT_GROUP,0)); + + function ADODB_mysqli() + { + // if(!extension_loaded("mysqli")) + ;//trigger_error("You must have the mysqli extension installed.", E_USER_ERROR); + + } + + + // returns true or false + // To add: parameter int $port, + // parameter string $socket + function _connect($argHostname = NULL, + $argUsername = NULL, + $argPassword = NULL, + $argDatabasename = NULL, $persist=false) + { + if(!extension_loaded("mysqli")) { + return null; + } + $this->_connectionID = @mysqli_init(); + + if (is_null($this->_connectionID)) { + // mysqli_init only fails if insufficient memory + if ($this->debug) + ADOConnection::outp("mysqli_init() failed : " . $this->ErrorMsg()); + return false; + } + /* + I suggest a simple fix which would enable adodb and mysqli driver to + read connection options from the standard mysql configuration file + /etc/my.cnf - "Bastien Duclaux" <bduclaux#yahoo.com> + */ + foreach($this->optionFlags as $arr) { + mysqli_options($this->_connectionID,$arr[0],$arr[1]); + } + + #if (!empty($this->port)) $argHostname .= ":".$this->port; + $ok = mysqli_real_connect($this->_connectionID, + $argHostname, + $argUsername, + $argPassword, + $argDatabasename, + $this->port, + $this->socket, + $this->clientFlags); + + if ($ok) { + if ($argDatabasename) return $this->SelectDB($argDatabasename); + return true; + } else { + if ($this->debug) + ADOConnection::outp("Could't connect : " . $this->ErrorMsg()); + return false; + } + } + + // returns true or false + // How to force a persistent connection + function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename, true); + + } + + // When is this used? Close old connection first? + // In _connect(), check $this->forceNewConnect? + function _nconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + $this->forceNewConnect = true; + return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename); + } + + function IfNull( $field, $ifNull ) + { + return " IFNULL($field, $ifNull) "; // if MySQL + } + + function ServerInfo() + { + $arr['description'] = $this->GetOne("select version()"); + $arr['version'] = ADOConnection::_findvers($arr['description']); + return $arr; + } + + + function BeginTrans() + { + if ($this->transOff) return true; + $this->transCnt += 1; + $this->Execute('SET AUTOCOMMIT=0'); + $this->Execute('BEGIN'); + return true; + } + + function CommitTrans($ok=true) + { + if ($this->transOff) return true; + if (!$ok) return $this->RollbackTrans(); + + if ($this->transCnt) $this->transCnt -= 1; + $this->Execute('COMMIT'); + $this->Execute('SET AUTOCOMMIT=1'); + return true; + } + + function RollbackTrans() + { + if ($this->transOff) return true; + if ($this->transCnt) $this->transCnt -= 1; + $this->Execute('ROLLBACK'); + $this->Execute('SET AUTOCOMMIT=1'); + return true; + } + + function RowLock($tables,$where='',$flds='1 as adodb_ignore') + { + if ($this->transCnt==0) $this->BeginTrans(); + if ($where) $where = ' where '.$where; + $rs =& $this->Execute("select $flds from $tables $where for update"); + return !empty($rs); + } + + // if magic quotes disabled, use mysql_real_escape_string() + // From readme.htm: + // Quotes a string to be sent to the database. The $magic_quotes_enabled + // parameter may look funny, but the idea is if you are quoting a + // string extracted from a POST/GET variable, then + // pass get_magic_quotes_gpc() as the second parameter. This will + // ensure that the variable is not quoted twice, once by qstr and once + // by the magic_quotes_gpc. + // + //Eg. $s = $db->qstr(_GET['name'],get_magic_quotes_gpc()); + function qstr($s, $magic_quotes = false) + { + if (!$magic_quotes) { + if (PHP_VERSION >= 5) + return "'" . mysqli_real_escape_string($this->_connectionID, $s) . "'"; + + if ($this->replaceQuote[0] == '\\') + $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\0"),$s); + return "'".str_replace("'",$this->replaceQuote,$s)."'"; + } + // undo magic quotes for " + $s = str_replace('\\"','"',$s); + return "'$s'"; + } + + function _insertid() + { + $result = @mysqli_insert_id($this->_connectionID); + if ($result == -1){ + if ($this->debug) ADOConnection::outp("mysqli_insert_id() failed : " . $this->ErrorMsg()); + } + return $result; + } + + // Only works for INSERT, UPDATE and DELETE query's + function _affectedrows() + { + $result = @mysqli_affected_rows($this->_connectionID); + if ($result == -1) { + if ($this->debug) ADOConnection::outp("mysqli_affected_rows() failed : " . $this->ErrorMsg()); + } + return $result; + } + + // See http://www.mysql.com/doc/M/i/Miscellaneous_functions.html + // Reference on Last_Insert_ID on the recommended way to simulate sequences + var $_genIDSQL = "update %s set id=LAST_INSERT_ID(id+1);"; + var $_genSeqSQL = "create table %s (id int not null)"; + var $_genSeq2SQL = "insert into %s values (%s)"; + var $_dropSeqSQL = "drop table %s"; + + function CreateSequence($seqname='adodbseq',$startID=1) + { + if (empty($this->_genSeqSQL)) return false; + $u = strtoupper($seqname); + + $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname)); + if (!$ok) return false; + return $this->Execute(sprintf($this->_genSeq2SQL,$seqname,$startID-1)); + } + + function GenID($seqname='adodbseq',$startID=1) + { + // post-nuke sets hasGenID to false + if (!$this->hasGenID) return false; + + $getnext = sprintf($this->_genIDSQL,$seqname); + $holdtransOK = $this->_transOK; // save the current status + $rs = @$this->Execute($getnext); + if (!$rs) { + if ($holdtransOK) $this->_transOK = true; //if the status was ok before reset + $u = strtoupper($seqname); + $this->Execute(sprintf($this->_genSeqSQL,$seqname)); + $this->Execute(sprintf($this->_genSeq2SQL,$seqname,$startID-1)); + $rs = $this->Execute($getnext); + } + $this->genID = mysqli_insert_id($this->_connectionID); + + if ($rs) $rs->Close(); + + return $this->genID; + } + + function &MetaDatabases() + { + $query = "SHOW DATABASES"; + $ret =& $this->Execute($query); + if ($ret && is_object($ret)){ + $arr = array(); + while (!$ret->EOF){ + $db = $ret->Fields('Database'); + if ($db != 'mysql') $arr[] = $db; + $ret->MoveNext(); + } + return $arr; + } + return $ret; + } + + + function &MetaIndexes ($table, $primary = FALSE) + { + // save old fetch mode + global $ADODB_FETCH_MODE; + + $false = false; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== FALSE) { + $savem = $this->SetFetchMode(FALSE); + } + + // get index details + $rs = $this->Execute(sprintf('SHOW INDEXES FROM %s',$table)); + + // restore fetchmode + if (isset($savem)) { + $this->SetFetchMode($savem); + } + $ADODB_FETCH_MODE = $save; + + if (!is_object($rs)) { + return $false; + } + + $indexes = array (); + + // parse index data into array + while ($row = $rs->FetchRow()) { + if ($primary == FALSE AND $row[2] == 'PRIMARY') { + continue; + } + + if (!isset($indexes[$row[2]])) { + $indexes[$row[2]] = array( + 'unique' => ($row[1] == 0), + 'columns' => array() + ); + } + + $indexes[$row[2]]['columns'][$row[3] - 1] = $row[4]; + } + + // sort columns by order in the index + foreach ( array_keys ($indexes) as $index ) + { + ksort ($indexes[$index]['columns']); + } + + return $indexes; + } + + + // Format date column in sql string given an input format that understands Y M D + function SQLDate($fmt, $col=false) + { + if (!$col) $col = $this->sysTimeStamp; + $s = 'DATE_FORMAT('.$col.",'"; + $concat = false; + $len = strlen($fmt); + for ($i=0; $i < $len; $i++) { + $ch = $fmt[$i]; + switch($ch) { + case 'Y': + case 'y': + $s .= '%Y'; + break; + case 'Q': + case 'q': + $s .= "'),Quarter($col)"; + + if ($len > $i+1) $s .= ",DATE_FORMAT($col,'"; + else $s .= ",('"; + $concat = true; + break; + case 'M': + $s .= '%b'; + break; + + case 'm': + $s .= '%m'; + break; + case 'D': + case 'd': + $s .= '%d'; + break; + + case 'H': + $s .= '%H'; + break; + + case 'h': + $s .= '%I'; + break; + + case 'i': + $s .= '%i'; + break; + + case 's': + $s .= '%s'; + break; + + case 'a': + case 'A': + $s .= '%p'; + break; + + case 'w': + $s .= '%w'; + break; + + case 'l': + $s .= '%W'; + break; + + default: + + if ($ch == '\\') { + $i++; + $ch = substr($fmt,$i,1); + } + $s .= $ch; + break; + } + } + $s.="')"; + if ($concat) $s = "CONCAT($s)"; + return $s; + } + + // returns concatenated string + // much easier to run "mysqld --ansi" or "mysqld --sql-mode=PIPES_AS_CONCAT" and use || operator + function Concat() + { + $s = ""; + $arr = func_get_args(); + + // suggestion by andrew005@mnogo.ru + $s = implode(',',$arr); + if (strlen($s) > 0) return "CONCAT($s)"; + else return ''; + } + + // dayFraction is a day in floating point + function OffsetDate($dayFraction,$date=false) + { + if (!$date) + $date = $this->sysDate; + return "from_unixtime(unix_timestamp($date)+($dayFraction)*24*3600)"; + } + + function &MetaTables($ttype=false,$showSchema=false,$mask=false) + { + $save = $this->metaTablesSQL; + if ($showSchema && is_string($showSchema)) { + $this->metaTablesSQL .= " from $showSchema"; + } + + if ($mask) { + $mask = $this->qstr($mask); + $this->metaTablesSQL .= " like $mask"; + } + $ret =& ADOConnection::MetaTables($ttype,$showSchema); + + $this->metaTablesSQL = $save; + return $ret; + } + + // "Innox - Juan Carlos Gonzalez" <jgonzalez#innox.com.mx> + function MetaForeignKeys( $table, $owner = FALSE, $upper = FALSE, $associative = FALSE ) + { + if ( !empty($owner) ) { + $table = "$owner.$table"; + } + $a_create_table = $this->getRow(sprintf('SHOW CREATE TABLE %s', $table)); + if ($associative) $create_sql = $a_create_table["Create Table"]; + else $create_sql = $a_create_table[1]; + + $matches = array(); + + if (!preg_match_all("/FOREIGN KEY \(`(.*?)`\) REFERENCES `(.*?)` \(`(.*?)`\)/", $create_sql, $matches)) return false; + $foreign_keys = array(); + $num_keys = count($matches[0]); + for ( $i = 0; $i < $num_keys; $i ++ ) { + $my_field = explode('`, `', $matches[1][$i]); + $ref_table = $matches[2][$i]; + $ref_field = explode('`, `', $matches[3][$i]); + + if ( $upper ) { + $ref_table = strtoupper($ref_table); + } + + $foreign_keys[$ref_table] = array(); + $num_fields = count($my_field); + for ( $j = 0; $j < $num_fields; $j ++ ) { + if ( $associative ) { + $foreign_keys[$ref_table][$ref_field[$j]] = $my_field[$j]; + } else { + $foreign_keys[$ref_table][] = "{$my_field[$j]}={$ref_field[$j]}"; + } + } + } + + return $foreign_keys; + } + + function &MetaColumns($table) + { + $false = false; + if (!$this->metaColumnsSQL) + return $false; + + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== false) + $savem = $this->SetFetchMode(false); + $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table)); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + if (!is_object($rs)) + return $false; + + $retarr = array(); + while (!$rs->EOF) { + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[0]; + $type = $rs->fields[1]; + + // split type into type(length): + $fld->scale = null; + if (preg_match("/^(.+)\((\d+),(\d+)/", $type, $query_array)) { + $fld->type = $query_array[1]; + $fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1; + $fld->scale = is_numeric($query_array[3]) ? $query_array[3] : -1; + } elseif (preg_match("/^(.+)\((\d+)/", $type, $query_array)) { + $fld->type = $query_array[1]; + $fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1; + } elseif (preg_match("/^(enum)\((.*)\)$/i", $type, $query_array)) { + $fld->type = $query_array[1]; + $fld->max_length = max(array_map("strlen",explode(",",$query_array[2]))) - 2; // PHP >= 4.0.6 + $fld->max_length = ($fld->max_length == 0 ? 1 : $fld->max_length); + } else { + $fld->type = $type; + $fld->max_length = -1; + } + $fld->not_null = ($rs->fields[2] != 'YES'); + $fld->primary_key = ($rs->fields[3] == 'PRI'); + $fld->auto_increment = (strpos($rs->fields[5], 'auto_increment') !== false); + $fld->binary = (strpos($type,'blob') !== false); + $fld->unsigned = (strpos($type,'unsigned') !== false); + + if (!$fld->binary) { + $d = $rs->fields[4]; + if ($d != '' && $d != 'NULL') { + $fld->has_default = true; + $fld->default_value = $d; + } else { + $fld->has_default = false; + } + } + + if ($save == ADODB_FETCH_NUM) { + $retarr[] = $fld; + } else { + $retarr[strtoupper($fld->name)] = $fld; + } + $rs->MoveNext(); + } + + $rs->Close(); + return $retarr; + } + + // returns true or false + function SelectDB($dbName) + { +// $this->_connectionID = $this->mysqli_resolve_link($this->_connectionID); + $this->database = $dbName; + $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions + + if ($this->_connectionID) { + $result = @mysqli_select_db($this->_connectionID, $dbName); + if (!$result) { + ADOConnection::outp("Select of database " . $dbName . " failed. " . $this->ErrorMsg()); + } + return $result; + } + return false; + } + + // parameters use PostgreSQL convention, not MySQL + function &SelectLimit($sql, + $nrows = -1, + $offset = -1, + $inputarr = false, + $arg3 = false, + $secs = 0) + { + $offsetStr = ($offset >= 0) ? "$offset," : ''; + if ($nrows < 0) $nrows = '18446744073709551615'; + + if ($secs) + $rs =& $this->CacheExecute($secs, $sql . " LIMIT $offsetStr$nrows" , $inputarr , $arg3); + else + $rs =& $this->Execute($sql . " LIMIT $offsetStr$nrows" , $inputarr , $arg3); + + return $rs; + } + + + function Prepare($sql) + { + return $sql; + + $stmt = $this->_connectionID->prepare($sql); + if (!$stmt) { + echo $this->ErrorMsg(); + return $sql; + } + return array($sql,$stmt); + } + + + // returns queryID or false + function _query($sql, $inputarr) + { + global $ADODB_COUNTRECS; + + if (is_array($sql)) { + $stmt = $sql[1]; + $a = ''; + foreach($inputarr as $k => $v) { + if (is_string($v)) $a .= 's'; + else if (is_integer($v)) $a .= 'i'; + else $a .= 'd'; + } + + $fnarr = array_merge( array($stmt,$a) , $inputarr); + $ret = call_user_func_array('mysqli_stmt_bind_param',$fnarr); + + $ret = mysqli_stmt_execute($stmt); + return $ret; + } + if (!$mysql_res = mysqli_query($this->_connectionID, $sql, ($ADODB_COUNTRECS) ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT)) { + if ($this->debug) ADOConnection::outp("Query: " . $sql . " failed. " . $this->ErrorMsg()); + return false; + } + + return $mysql_res; + } + + /* Returns: the last error message from previous database operation */ + function ErrorMsg() + { + if (empty($this->_connectionID)) + $this->_errorMsg = @mysqli_connect_error(); + else + $this->_errorMsg = @mysqli_error($this->_connectionID); + return $this->_errorMsg; + } + + /* Returns: the last error number from previous database operation */ + function ErrorNo() + { + if (empty($this->_connectionID)) + return @mysqli_connect_errno(); + else + return @mysqli_errno($this->_connectionID); + } + + // returns true or false + function _close() + { + @mysqli_close($this->_connectionID); + $this->_connectionID = false; + } + + /* + * Maximum size of C field + */ + function CharMax() + { + return 255; + } + + /* + * Maximum size of X field + */ + function TextMax() + { + return 4294967295; + } + + + + // this is a set of functions for managing client encoding - very important if the encodings + // of your database and your output target (i.e. HTML) don't match + // for instance, you may have UTF8 database and server it on-site as latin1 etc. + // GetCharSet - get the name of the character set the client is using now + // Under Windows, the functions should work with MySQL 4.1.11 and above, the set of charsets supported + // depends on compile flags of mysql distribution + + function GetCharSet() + { + //we will use ADO's builtin property charSet + if (!is_callable($this->_connectionID,'character_set_name')) + return false; + + $this->charSet = @$this->_connectionID->character_set_name(); + if (!$this->charSet) { + return false; + } else { + return $this->charSet; + } + } + + // SetCharSet - switch the client encoding + function SetCharSet($charset_name) + { + if (!is_callable($this->_connectionID,'set_charset')) + return false; + + if ($this->charSet !== $charset_name) { + $if = @$this->_connectionID->set_charset($charset_name); + if ($if == "0" & $this->GetCharSet() == $charset_name) { + return true; + } else return false; + } else return true; + } + + + + +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordSet_mysqli extends ADORecordSet{ + + var $databaseType = "mysqli"; + var $canSeek = true; + + function ADORecordSet_mysqli($queryID, $mode = false) + { + if ($mode === false) + { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + + switch ($mode) + { + case ADODB_FETCH_NUM: + $this->fetchMode = MYSQLI_NUM; + break; + case ADODB_FETCH_ASSOC: + $this->fetchMode = MYSQLI_ASSOC; + break; + case ADODB_FETCH_DEFAULT: + case ADODB_FETCH_BOTH: + default: + $this->fetchMode = MYSQLI_BOTH; + break; + } + $this->adodbFetchMode = $mode; + $this->ADORecordSet($queryID); + } + + function _initrs() + { + global $ADODB_COUNTRECS; + + $this->_numOfRows = $ADODB_COUNTRECS ? @mysqli_num_rows($this->_queryID) : -1; + $this->_numOfFields = @mysqli_num_fields($this->_queryID); + } + +/* +1 = MYSQLI_NOT_NULL_FLAG +2 = MYSQLI_PRI_KEY_FLAG +4 = MYSQLI_UNIQUE_KEY_FLAG +8 = MYSQLI_MULTIPLE_KEY_FLAG +16 = MYSQLI_BLOB_FLAG +32 = MYSQLI_UNSIGNED_FLAG +64 = MYSQLI_ZEROFILL_FLAG +128 = MYSQLI_BINARY_FLAG +256 = MYSQLI_ENUM_FLAG +512 = MYSQLI_AUTO_INCREMENT_FLAG +1024 = MYSQLI_TIMESTAMP_FLAG +2048 = MYSQLI_SET_FLAG +32768 = MYSQLI_NUM_FLAG +16384 = MYSQLI_PART_KEY_FLAG +32768 = MYSQLI_GROUP_FLAG +65536 = MYSQLI_UNIQUE_FLAG +131072 = MYSQLI_BINCMP_FLAG +*/ + + function &FetchField($fieldOffset = -1) + { + $fieldnr = $fieldOffset; + if ($fieldOffset != -1) { + $fieldOffset = mysqli_field_seek($this->_queryID, $fieldnr); + } + $o = mysqli_fetch_field($this->_queryID); + /* Properties of an ADOFieldObject as set by MetaColumns */ + $o->primary_key = $o->flags & MYSQLI_PRI_KEY_FLAG; + $o->not_null = $o->flags & MYSQLI_NOT_NULL_FLAG; + $o->auto_increment = $o->flags & MYSQLI_AUTO_INCREMENT_FLAG; + $o->binary = $o->flags & MYSQLI_BINARY_FLAG; + // $o->blob = $o->flags & MYSQLI_BLOB_FLAG; /* not returned by MetaColumns */ + $o->unsigned = $o->flags & MYSQLI_UNSIGNED_FLAG; + + return $o; + } + + function &GetRowAssoc($upper = true) + { + if ($this->fetchMode == MYSQLI_ASSOC && !$upper) + return $this->fields; + $row =& ADORecordSet::GetRowAssoc($upper); + return $row; + } + + /* Use associative array to get fields array */ + function Fields($colname) + { + if ($this->fetchMode != MYSQLI_NUM) + return @$this->fields[$colname]; + + if (!$this->bind) { + $this->bind = array(); + for ($i = 0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + function _seek($row) + { + if ($this->_numOfRows == 0) + return false; + + if ($row < 0) + return false; + + mysqli_data_seek($this->_queryID, $row); + $this->EOF = false; + return true; + } + + // 10% speedup to move MoveNext to child class + // This is the only implementation that works now (23-10-2003). + // Other functions return no or the wrong results. + function MoveNext() + { + if ($this->EOF) return false; + $this->_currentRow++; + $this->fields = @mysqli_fetch_array($this->_queryID,$this->fetchMode); + + if (is_array($this->fields)) return true; + $this->EOF = true; + return false; + } + + function _fetch() + { + $this->fields = mysqli_fetch_array($this->_queryID,$this->fetchMode); + return is_array($this->fields); + } + + function _close() + { + mysqli_free_result($this->_queryID); + $this->_queryID = false; + } + +/* + +0 = MYSQLI_TYPE_DECIMAL +1 = MYSQLI_TYPE_CHAR +1 = MYSQLI_TYPE_TINY +2 = MYSQLI_TYPE_SHORT +3 = MYSQLI_TYPE_LONG +4 = MYSQLI_TYPE_FLOAT +5 = MYSQLI_TYPE_DOUBLE +6 = MYSQLI_TYPE_NULL +7 = MYSQLI_TYPE_TIMESTAMP +8 = MYSQLI_TYPE_LONGLONG +9 = MYSQLI_TYPE_INT24 +10 = MYSQLI_TYPE_DATE +11 = MYSQLI_TYPE_TIME +12 = MYSQLI_TYPE_DATETIME +13 = MYSQLI_TYPE_YEAR +14 = MYSQLI_TYPE_NEWDATE +247 = MYSQLI_TYPE_ENUM +248 = MYSQLI_TYPE_SET +249 = MYSQLI_TYPE_TINY_BLOB +250 = MYSQLI_TYPE_MEDIUM_BLOB +251 = MYSQLI_TYPE_LONG_BLOB +252 = MYSQLI_TYPE_BLOB +253 = MYSQLI_TYPE_VAR_STRING +254 = MYSQLI_TYPE_STRING +255 = MYSQLI_TYPE_GEOMETRY +*/ + + function MetaType($t, $len = -1, $fieldobj = false) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + + + $len = -1; // mysql max_length is not accurate + switch (strtoupper($t)) { + case 'STRING': + case 'CHAR': + case 'VARCHAR': + case 'TINYBLOB': + case 'TINYTEXT': + case 'ENUM': + case 'SET': + + case MYSQLI_TYPE_TINY_BLOB : + case MYSQLI_TYPE_CHAR : + case MYSQLI_TYPE_STRING : + case MYSQLI_TYPE_ENUM : + case MYSQLI_TYPE_SET : + case 253 : + if ($len <= $this->blobSize) return 'C'; + + case 'TEXT': + case 'LONGTEXT': + case 'MEDIUMTEXT': + return 'X'; + + + // php_mysql extension always returns 'blob' even if 'text' + // so we have to check whether binary... + case 'IMAGE': + case 'LONGBLOB': + case 'BLOB': + case 'MEDIUMBLOB': + + case MYSQLI_TYPE_BLOB : + case MYSQLI_TYPE_LONG_BLOB : + case MYSQLI_TYPE_MEDIUM_BLOB : + + return !empty($fieldobj->binary) ? 'B' : 'X'; + case 'YEAR': + case 'DATE': + case MYSQLI_TYPE_DATE : + case MYSQLI_TYPE_YEAR : + + return 'D'; + + case 'TIME': + case 'DATETIME': + case 'TIMESTAMP': + + case MYSQLI_TYPE_DATETIME : + case MYSQLI_TYPE_NEWDATE : + case MYSQLI_TYPE_TIME : + case MYSQLI_TYPE_TIMESTAMP : + + return 'T'; + + case 'INT': + case 'INTEGER': + case 'BIGINT': + case 'TINYINT': + case 'MEDIUMINT': + case 'SMALLINT': + + case MYSQLI_TYPE_INT24 : + case MYSQLI_TYPE_LONG : + case MYSQLI_TYPE_LONGLONG : + case MYSQLI_TYPE_SHORT : + case MYSQLI_TYPE_TINY : + + if (!empty($fieldobj->primary_key)) return 'R'; + + return 'I'; + + + // Added floating-point types + // Maybe not necessery. + case 'FLOAT': + case 'DOUBLE': + // case 'DOUBLE PRECISION': + case 'DECIMAL': + case 'DEC': + case 'FIXED': + default: + //if (!is_numeric($t)) echo "<p>--- Error in type matching $t -----</p>"; + return 'N'; + } + } // function + + +} // rs class + +} + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-mysqlt.inc.php b/framework/DataAccess/adodb/drivers/adodb-mysqlt.inc.php new file mode 100644 index 00000000..38b3e98d --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-mysqlt.inc.php @@ -0,0 +1,138 @@ +<?php + +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 8. + + MySQL code that supports transactions. For MySQL 3.23 or later. + Code from James Poon <jpoon88@yahoo.com> + + Requires mysql client. Works on Windows and Unix. +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +include_once(ADODB_DIR."/drivers/adodb-mysql.inc.php"); + + +class ADODB_mysqlt extends ADODB_mysql { + var $databaseType = 'mysqlt'; + var $ansiOuter = true; // for Version 3.23.17 or later + var $hasTransactions = true; + var $autoRollback = true; // apparently mysql does not autorollback properly + + function ADODB_mysqlt() + { + global $ADODB_EXTENSION; if ($ADODB_EXTENSION) $this->rsPrefix .= 'ext_'; + } + + function BeginTrans() + { + if ($this->transOff) return true; + $this->transCnt += 1; + $this->Execute('SET AUTOCOMMIT=0'); + $this->Execute('BEGIN'); + return true; + } + + function CommitTrans($ok=true) + { + if ($this->transOff) return true; + if (!$ok) return $this->RollbackTrans(); + + if ($this->transCnt) $this->transCnt -= 1; + $this->Execute('COMMIT'); + $this->Execute('SET AUTOCOMMIT=1'); + return true; + } + + function RollbackTrans() + { + if ($this->transOff) return true; + if ($this->transCnt) $this->transCnt -= 1; + $this->Execute('ROLLBACK'); + $this->Execute('SET AUTOCOMMIT=1'); + return true; + } + + function RowLock($tables,$where='',$flds='1 as adodb_ignore') + { + if ($this->transCnt==0) $this->BeginTrans(); + if ($where) $where = ' where '.$where; + $rs =& $this->Execute("select $flds from $tables $where for update"); + return !empty($rs); + } + +} + +class ADORecordSet_mysqlt extends ADORecordSet_mysql{ + var $databaseType = "mysqlt"; + + function ADORecordSet_mysqlt($queryID,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + + switch ($mode) + { + case ADODB_FETCH_NUM: $this->fetchMode = MYSQL_NUM; break; + case ADODB_FETCH_ASSOC:$this->fetchMode = MYSQL_ASSOC; break; + + case ADODB_FETCH_DEFAULT: + case ADODB_FETCH_BOTH: + default: $this->fetchMode = MYSQL_BOTH; break; + } + + $this->adodbFetchMode = $mode; + $this->ADORecordSet($queryID); + } + + function MoveNext() + { + if (@$this->fields = mysql_fetch_array($this->_queryID,$this->fetchMode)) { + $this->_currentRow += 1; + return true; + } + if (!$this->EOF) { + $this->_currentRow += 1; + $this->EOF = true; + } + return false; + } +} + +class ADORecordSet_ext_mysqlt extends ADORecordSet_mysqlt { + + function ADORecordSet_ext_mysqlt($queryID,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + switch ($mode) + { + case ADODB_FETCH_NUM: $this->fetchMode = MYSQL_NUM; break; + case ADODB_FETCH_ASSOC:$this->fetchMode = MYSQL_ASSOC; break; + + case ADODB_FETCH_DEFAULT: + case ADODB_FETCH_BOTH: + default: + $this->fetchMode = MYSQL_BOTH; break; + } + $this->adodbFetchMode = $mode; + $this->ADORecordSet($queryID); + } + + function MoveNext() + { + return adodb_movenext($this); + } +} + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-netezza.inc.php b/framework/DataAccess/adodb/drivers/adodb-netezza.inc.php new file mode 100644 index 00000000..c99e6f32 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-netezza.inc.php @@ -0,0 +1,170 @@ +<?php +/* + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim#natsoft.com.my). All rights reserved. + + First cut at the Netezza Driver by Josh Eldridge joshuae74#hotmail.com + Based on the previous postgres drivers. + http://www.netezza.com/ + Major Additions/Changes: + MetaDatabasesSQL, MetaTablesSQL, MetaColumnsSQL + Note: You have to have admin privileges to access the system tables + Removed non-working keys code (Netezza has no concept of keys) + Fixed the way data types and lengths are returned in MetaColumns() + as well as added the default lengths for certain types + Updated public variables for Netezza + Still need to remove blob functions, as Netezza doesn't suppport blob +*/ +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +include_once(ADODB_DIR.'/drivers/adodb-postgres64.inc.php'); + +class ADODB_netezza extends ADODB_postgres64 { + var $databaseType = 'netezza'; + var $dataProvider = 'netezza'; + var $hasInsertID = false; + var $_resultid = false; + var $concat_operator='||'; + var $random = 'random'; + var $metaDatabasesSQL = "select objname from _v_object_data where objtype='database' order by 1"; + var $metaTablesSQL = "select objname from _v_object_data where objtype='table' order by 1"; + var $isoDates = true; // accepts dates in ISO format + var $sysDate = "CURRENT_DATE"; + var $sysTimeStamp = "CURRENT_TIMESTAMP"; + var $blobEncodeType = 'C'; + var $metaColumnsSQL = "SELECT attname, atttype FROM _v_relation_column_def WHERE name = '%s' AND attnum > 0 ORDER BY attnum"; + var $metaColumnsSQL1 = "SELECT attname, atttype FROM _v_relation_column_def WHERE name = '%s' AND attnum > 0 ORDER BY attnum"; + // netezza doesn't have keys. it does have distributions, so maybe this is + // something that can be pulled from the system tables + var $metaKeySQL = ""; + var $hasAffectedRows = true; + var $hasLimit = true; + var $true = 't'; // string that represents TRUE for a database + var $false = 'f'; // string that represents FALSE for a database + var $fmtDate = "'Y-m-d'"; // used by DBDate() as the default date format used by the database + var $fmtTimeStamp = "'Y-m-d G:i:s'"; // used by DBTimeStamp as the default timestamp fmt. + var $ansiOuter = true; + var $autoRollback = true; // apparently pgsql does not autorollback properly before 4.3.4 + // http://bugs.php.net/bug.php?id=25404 + + + function ADODB_netezza() + { + + } + + function &MetaColumns($table,$upper=true) + { + + // Changed this function to support Netezza which has no concept of keys + // could posisbly work on other things from the system table later. + + global $ADODB_FETCH_MODE; + + $table = strtolower($table); + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + + $rs =& $this->Execute(sprintf($this->metaColumnsSQL,$table,$table)); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if ($rs === false) return false; + + $retarr = array(); + while (!$rs->EOF) { + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[0]; + + // since we're returning type and length as one string, + // split them out here. + + if ($first = strstr($rs->fields[1], "(")) { + $fld->max_length = trim($first, "()"); + } else { + $fld->max_length = -1; + } + + if ($first = strpos($rs->fields[1], "(")) { + $fld->type = substr($rs->fields[1], 0, $first); + } else { + $fld->type = $rs->fields[1]; + } + + switch ($fld->type) { + case "byteint": + case "boolean": + $fld->max_length = 1; + break; + case "smallint": + $fld->max_length = 2; + break; + case "integer": + case "numeric": + case "date": + $fld->max_length = 4; + break; + case "bigint": + case "time": + case "timestamp": + $fld->max_length = 8; + break; + case "timetz": + case "time with time zone": + $fld->max_length = 12; + break; + } + + if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld; + else $retarr[($upper) ? strtoupper($fld->name) : $fld->name] = $fld; + + $rs->MoveNext(); + } + $rs->Close(); + return $retarr; + + } + + +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordSet_netezza extends ADORecordSet_postgres64 +{ + var $databaseType = "netezza"; + var $canSeek = true; + + function ADORecordSet_netezza($queryID,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + switch ($mode) + { + case ADODB_FETCH_NUM: $this->fetchMode = PGSQL_NUM; break; + case ADODB_FETCH_ASSOC:$this->fetchMode = PGSQL_ASSOC; break; + + case ADODB_FETCH_DEFAULT: + case ADODB_FETCH_BOTH: + default: $this->fetchMode = PGSQL_BOTH; break; + } + $this->adodbFetchMode = $mode; + $this->ADORecordSet($queryID); + } + + // _initrs modified to disable blob handling + function _initrs() + { + global $ADODB_COUNTRECS; + $this->_numOfRows = ($ADODB_COUNTRECS)? @pg_numrows($this->_queryID):-1; + $this->_numOfFields = @pg_numfields($this->_queryID); + } + +} +?> diff --git a/framework/DataAccess/adodb/drivers/adodb-oci8.inc.php b/framework/DataAccess/adodb/drivers/adodb-oci8.inc.php new file mode 100644 index 00000000..a499a675 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-oci8.inc.php @@ -0,0 +1,1484 @@ +<?php +/* + + version V4.72 21 Feb 2006 (c) 2000-2006 John Lim. All rights reserved. + + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + + Latest version is available at http://adodb.sourceforge.net + + Code contributed by George Fourlanos <fou@infomap.gr> + + 13 Nov 2000 jlim - removed all ora_* references. +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +/* +NLS_Date_Format +Allows you to use a date format other than the Oracle Lite default. When a literal +character string appears where a date value is expected, the Oracle Lite database +tests the string to see if it matches the formats of Oracle, SQL-92, or the value +specified for this parameter in the POLITE.INI file. Setting this parameter also +defines the default format used in the TO_CHAR or TO_DATE functions when no +other format string is supplied. + +For Oracle the default is dd-mon-yy or dd-mon-yyyy, and for SQL-92 the default is +yy-mm-dd or yyyy-mm-dd. + +Using 'RR' in the format forces two-digit years less than or equal to 49 to be +interpreted as years in the 21st century (2000–2049), and years over 50 as years in +the 20th century (1950–1999). Setting the RR format as the default for all two-digit +year entries allows you to become year-2000 compliant. For example: +NLS_DATE_FORMAT='RR-MM-DD' + +You can also modify the date format using the ALTER SESSION command. +*/ + +# define the LOB descriptor type for the given type +# returns false if no LOB descriptor +function oci_lob_desc($type) { + switch ($type) { + case OCI_B_BFILE: $result = OCI_D_FILE; break; + case OCI_B_CFILEE: $result = OCI_D_FILE; break; + case OCI_B_CLOB: $result = OCI_D_LOB; break; + case OCI_B_BLOB: $result = OCI_D_LOB; break; + case OCI_B_ROWID: $result = OCI_D_ROWID; break; + default: $result = false; break; + } + return $result; +} + +class ADODB_oci8 extends ADOConnection { + var $databaseType = 'oci8'; + var $dataProvider = 'oci8'; + var $replaceQuote = "''"; // string to use to replace quotes + var $concat_operator='||'; + var $sysDate = "TRUNC(SYSDATE)"; + var $sysTimeStamp = 'SYSDATE'; + var $metaDatabasesSQL = "SELECT USERNAME FROM ALL_USERS WHERE USERNAME NOT IN ('SYS','SYSTEM','DBSNMP','OUTLN') ORDER BY 1"; + var $_stmt; + var $_commit = OCI_COMMIT_ON_SUCCESS; + var $_initdate = true; // init date to YYYY-MM-DD + var $metaTablesSQL = "select table_name,table_type from cat where table_type in ('TABLE','VIEW')"; + var $metaColumnsSQL = "select cname,coltype,width, SCALE, PRECISION, NULLS, DEFAULTVAL from col where tname='%s' order by colno"; //changed by smondino@users.sourceforge. net + var $_bindInputArray = true; + var $hasGenID = true; + var $_genIDSQL = "SELECT (%s.nextval) FROM DUAL"; + var $_genSeqSQL = "CREATE SEQUENCE %s START WITH %s"; + var $_dropSeqSQL = "DROP SEQUENCE %s"; + var $hasAffectedRows = true; + var $random = "abs(mod(DBMS_RANDOM.RANDOM,10000001)/10000000)"; + var $noNullStrings = false; + var $connectSID = false; + var $_bind = false; + var $_hasOCIFetchStatement = false; + var $_getarray = false; // currently not working + var $leftOuter = ''; // oracle wierdness, $col = $value (+) for LEFT OUTER, $col (+)= $value for RIGHT OUTER + var $session_sharing_force_blob = false; // alter session on updateblob if set to true + var $firstrows = true; // enable first rows optimization on SelectLimit() + var $selectOffsetAlg1 = 100; // when to use 1st algorithm of selectlimit. + var $NLS_DATE_FORMAT = 'YYYY-MM-DD'; // To include time, use 'RRRR-MM-DD HH24:MI:SS' + var $useDBDateFormatForTextInput=false; + var $datetime = false; // MetaType('DATE') returns 'D' (datetime==false) or 'T' (datetime == true) + var $_refLOBs = array(); + + // var $ansiOuter = true; // if oracle9 + + function ADODB_oci8() + { + $this->_hasOCIFetchStatement = ADODB_PHPVER >= 0x4200; + if (defined('ADODB_EXTENSION')) $this->rsPrefix .= 'ext_'; + } + + /* Function &MetaColumns($table) added by smondino@users.sourceforge.net*/ + function &MetaColumns($table) + { + global $ADODB_FETCH_MODE; + + $false = false; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + + $rs = $this->Execute(sprintf($this->metaColumnsSQL,strtoupper($table))); + + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + if (!$rs) { + return $false; + } + $retarr = array(); + while (!$rs->EOF) { //print_r($rs->fields); + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[0]; + $fld->type = $rs->fields[1]; + $fld->max_length = $rs->fields[2]; + $fld->scale = $rs->fields[3]; + if ($rs->fields[1] == 'NUMBER' && $rs->fields[3] == 0) { + $fld->type ='INT'; + $fld->max_length = $rs->fields[4]; + } + $fld->not_null = (strncmp($rs->fields[5], 'NOT',3) === 0); + $fld->binary = (strpos($fld->type,'BLOB') !== false); + $fld->default_value = $rs->fields[6]; + + if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld; + else $retarr[strtoupper($fld->name)] = $fld; + $rs->MoveNext(); + } + $rs->Close(); + if (empty($retarr)) + return $false; + else + return $retarr; + } + + function Time() + { + $rs =& $this->Execute("select TO_CHAR($this->sysTimeStamp,'YYYY-MM-DD HH24:MI:SS') from dual"); + if ($rs && !$rs->EOF) return $this->UnixTimeStamp(reset($rs->fields)); + + return false; + } + +/* + + Multiple modes of connection are supported: + + a. Local Database + $conn->Connect(false,'scott','tiger'); + + b. From tnsnames.ora + $conn->Connect(false,'scott','tiger',$tnsname); + $conn->Connect($tnsname,'scott','tiger'); + + c. Server + service name + $conn->Connect($serveraddress,'scott,'tiger',$service_name); + + d. Server + SID + $conn->connectSID = true; + $conn->Connect($serveraddress,'scott,'tiger',$SID); + + +Example TNSName: +--------------- +NATSOFT.DOMAIN = + (DESCRIPTION = + (ADDRESS_LIST = + (ADDRESS = (PROTOCOL = TCP)(HOST = kermit)(PORT = 1523)) + ) + (CONNECT_DATA = + (SERVICE_NAME = natsoft.domain) + ) + ) + + There are 3 connection modes, 0 = non-persistent, 1 = persistent, 2 = force new connection + +*/ + function _connect($argHostname, $argUsername, $argPassword, $argDatabasename,$mode=0) + { + if (!function_exists('OCIPLogon')) return null; + + + $this->_errorMsg = false; + $this->_errorCode = false; + + if($argHostname) { // added by Jorma Tuomainen <jorma.tuomainen@ppoy.fi> + if (empty($argDatabasename)) $argDatabasename = $argHostname; + else { + if(strpos($argHostname,":")) { + $argHostinfo=explode(":",$argHostname); + $argHostname=$argHostinfo[0]; + $argHostport=$argHostinfo[1]; + } else { + $argHostport = empty($this->port)? "1521" : $this->port; + } + + if ($this->connectSID) { + $argDatabasename="(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=".$argHostname + .")(PORT=$argHostport))(CONNECT_DATA=(SID=$argDatabasename)))"; + } else + $argDatabasename="(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=".$argHostname + .")(PORT=$argHostport))(CONNECT_DATA=(SERVICE_NAME=$argDatabasename)))"; + } + } + + //if ($argHostname) print "<p>Connect: 1st argument should be left blank for $this->databaseType</p>"; + if ($mode==1) { + $this->_connectionID = ($this->charSet) ? + OCIPLogon($argUsername,$argPassword, $argDatabasename) + : + OCIPLogon($argUsername,$argPassword, $argDatabasename, $this->charSet) + ; + if ($this->_connectionID && $this->autoRollback) OCIrollback($this->_connectionID); + } else if ($mode==2) { + $this->_connectionID = ($this->charSet) ? + OCINLogon($argUsername,$argPassword, $argDatabasename) + : + OCINLogon($argUsername,$argPassword, $argDatabasename, $this->charSet); + + } else { + $this->_connectionID = ($this->charSet) ? + OCILogon($argUsername,$argPassword, $argDatabasename) + : + OCILogon($argUsername,$argPassword, $argDatabasename,$this->charSet); + } + if (!$this->_connectionID) return false; + if ($this->_initdate) { + $this->Execute("ALTER SESSION SET NLS_DATE_FORMAT='".$this->NLS_DATE_FORMAT."'"); + } + + // looks like: + // Oracle8i Enterprise Edition Release 8.1.7.0.0 - Production With the Partitioning option JServer Release 8.1.7.0.0 - Production + // $vers = OCIServerVersion($this->_connectionID); + // if (strpos($vers,'8i') !== false) $this->ansiOuter = true; + return true; + } + + function ServerInfo() + { + $arr['compat'] = $this->GetOne('select value from sys.database_compatible_level'); + $arr['description'] = @OCIServerVersion($this->_connectionID); + $arr['version'] = ADOConnection::_findvers($arr['description']); + return $arr; + } + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename,1); + } + + // returns true or false + function _nconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename,2); + } + + function _affectedrows() + { + if (is_resource($this->_stmt)) return @OCIRowCount($this->_stmt); + return 0; + } + + function IfNull( $field, $ifNull ) + { + return " NVL($field, $ifNull) "; // if Oracle + } + + // format and return date string in database date format + function DBDate($d) + { + if (empty($d) && $d !== 0) return 'null'; + + if (is_string($d)) $d = ADORecordSet::UnixDate($d); + return "TO_DATE(".adodb_date($this->fmtDate,$d).",'".$this->NLS_DATE_FORMAT."')"; + } + + + // format and return date string in database timestamp format + function DBTimeStamp($ts) + { + if (empty($ts) && $ts !== 0) return 'null'; + if (is_string($ts)) $ts = ADORecordSet::UnixTimeStamp($ts); + return 'TO_DATE('.adodb_date($this->fmtTimeStamp,$ts).",'RRRR-MM-DD, HH:MI:SS AM')"; + } + + function RowLock($tables,$where,$flds='1 as ignore') + { + if ($this->autoCommit) $this->BeginTrans(); + return $this->GetOne("select $flds from $tables where $where for update"); + } + + function &MetaTables($ttype=false,$showSchema=false,$mask=false) + { + if ($mask) { + $save = $this->metaTablesSQL; + $mask = $this->qstr(strtoupper($mask)); + $this->metaTablesSQL .= " AND upper(table_name) like $mask"; + } + $ret =& ADOConnection::MetaTables($ttype,$showSchema); + + if ($mask) { + $this->metaTablesSQL = $save; + } + return $ret; + } + + // Mark Newnham + function &MetaIndexes ($table, $primary = FALSE, $owner=false) + { + // save old fetch mode + global $ADODB_FETCH_MODE; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + + if ($this->fetchMode !== FALSE) { + $savem = $this->SetFetchMode(FALSE); + } + + // get index details + $table = strtoupper($table); + + // get Primary index + $primary_key = ''; + + $false = false; + $rs = $this->Execute(sprintf("SELECT * FROM ALL_CONSTRAINTS WHERE UPPER(TABLE_NAME)='%s' AND CONSTRAINT_TYPE='P'",$table)); + if ($row = $rs->FetchRow()) + $primary_key = $row[1]; //constraint_name + + if ($primary==TRUE && $primary_key=='') { + if (isset($savem)) + $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + return $false; //There is no primary key + } + + $rs = $this->Execute(sprintf("SELECT ALL_INDEXES.INDEX_NAME, ALL_INDEXES.UNIQUENESS, ALL_IND_COLUMNS.COLUMN_POSITION, ALL_IND_COLUMNS.COLUMN_NAME FROM ALL_INDEXES,ALL_IND_COLUMNS WHERE UPPER(ALL_INDEXES.TABLE_NAME)='%s' AND ALL_IND_COLUMNS.INDEX_NAME=ALL_INDEXES.INDEX_NAME",$table)); + + + if (!is_object($rs)) { + if (isset($savem)) + $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + return $false; + } + + $indexes = array (); + // parse index data into array + + while ($row = $rs->FetchRow()) { + if ($primary && $row[0] != $primary_key) continue; + if (!isset($indexes[$row[0]])) { + $indexes[$row[0]] = array( + 'unique' => ($row[1] == 'UNIQUE'), + 'columns' => array() + ); + } + $indexes[$row[0]]['columns'][$row[2] - 1] = $row[3]; + } + + // sort columns by order in the index + foreach ( array_keys ($indexes) as $index ) { + ksort ($indexes[$index]['columns']); + } + + if (isset($savem)) { + $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + } + return $indexes; + } + + function BeginTrans() + { + if ($this->transOff) return true; + $this->transCnt += 1; + $this->autoCommit = false; + $this->_commit = OCI_DEFAULT; + return true; + } + + function CommitTrans($ok=true) + { + if ($this->transOff) return true; + if (!$ok) return $this->RollbackTrans(); + + if ($this->transCnt) $this->transCnt -= 1; + $ret = OCIcommit($this->_connectionID); + $this->_commit = OCI_COMMIT_ON_SUCCESS; + $this->autoCommit = true; + return $ret; + } + + function RollbackTrans() + { + if ($this->transOff) return true; + if ($this->transCnt) $this->transCnt -= 1; + $ret = OCIrollback($this->_connectionID); + $this->_commit = OCI_COMMIT_ON_SUCCESS; + $this->autoCommit = true; + return $ret; + } + + + function SelectDB($dbName) + { + return false; + } + + function ErrorMsg() + { + if ($this->_errorMsg !== false) return $this->_errorMsg; + + if (is_resource($this->_stmt)) $arr = @OCIerror($this->_stmt); + if (empty($arr)) { + $arr = @OCIerror($this->_connectionID); + if ($arr === false) $arr = @OCIError(); + if ($arr === false) return ''; + } + $this->_errorMsg = $arr['message']; + $this->_errorCode = $arr['code']; + return $this->_errorMsg; + } + + function ErrorNo() + { + if ($this->_errorCode !== false) return $this->_errorCode; + + if (is_resource($this->_stmt)) $arr = @OCIError($this->_stmt); + if (empty($arr)) { + $arr = @OCIError($this->_connectionID); + if ($arr == false) $arr = @OCIError(); + if ($arr == false) return ''; + } + + $this->_errorMsg = $arr['message']; + $this->_errorCode = $arr['code']; + + return $arr['code']; + } + + // Format date column in sql string given an input format that understands Y M D + function SQLDate($fmt, $col=false) + { + if (!$col) $col = $this->sysTimeStamp; + $s = 'TO_CHAR('.$col.",'"; + + $len = strlen($fmt); + for ($i=0; $i < $len; $i++) { + $ch = $fmt[$i]; + switch($ch) { + case 'Y': + case 'y': + $s .= 'YYYY'; + break; + case 'Q': + case 'q': + $s .= 'Q'; + break; + + case 'M': + $s .= 'Mon'; + break; + + case 'm': + $s .= 'MM'; + break; + case 'D': + case 'd': + $s .= 'DD'; + break; + + case 'H': + $s.= 'HH24'; + break; + + case 'h': + $s .= 'HH'; + break; + + case 'i': + $s .= 'MI'; + break; + + case 's': + $s .= 'SS'; + break; + + case 'a': + case 'A': + $s .= 'AM'; + break; + + case 'w': + $s .= 'D'; + break; + + case 'l': + $s .= 'DAY'; + break; + + case 'W': + $s .= 'WW'; + break; + + default: + // handle escape characters... + if ($ch == '\\') { + $i++; + $ch = substr($fmt,$i,1); + } + if (strpos('-/.:;, ',$ch) !== false) $s .= $ch; + else $s .= '"'.$ch.'"'; + + } + } + return $s. "')"; + } + + + /* + This algorithm makes use of + + a. FIRST_ROWS hint + The FIRST_ROWS hint explicitly chooses the approach to optimize response time, + that is, minimum resource usage to return the first row. Results will be returned + as soon as they are identified. + + b. Uses rownum tricks to obtain only the required rows from a given offset. + As this uses complicated sql statements, we only use this if the $offset >= 100. + This idea by Tomas V V Cox. + + This implementation does not appear to work with oracle 8.0.5 or earlier. Comment + out this function then, and the slower SelectLimit() in the base class will be used. + */ + function &SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) + { + // seems that oracle only supports 1 hint comment in 8i + if ($this->firstrows) { + if (strpos($sql,'/*+') !== false) + $sql = str_replace('/*+ ','/*+FIRST_ROWS ',$sql); + else + $sql = preg_replace('/^[ \t\n]*select/i','SELECT /*+FIRST_ROWS*/',$sql); + } + + if ($offset < $this->selectOffsetAlg1) { + if ($nrows > 0) { + if ($offset > 0) $nrows += $offset; + //$inputarr['adodb_rownum'] = $nrows; + if ($this->databaseType == 'oci8po') { + $sql = "select * from (".$sql.") where rownum <= ?"; + } else { + $sql = "select * from (".$sql.") where rownum <= :adodb_offset"; + } + $inputarr['adodb_offset'] = $nrows; + $nrows = -1; + } + // note that $nrows = 0 still has to work ==> no rows returned + + $rs =& ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); + return $rs; + + } else { + // Algorithm by Tomas V V Cox, from PEAR DB oci8.php + + // Let Oracle return the name of the columns + $q_fields = "SELECT * FROM (".$sql.") WHERE NULL = NULL"; + + $false = false; + if (! $stmt_arr = $this->Prepare($q_fields)) { + return $false; + } + $stmt = $stmt_arr[1]; + + if (is_array($inputarr)) { + foreach($inputarr as $k => $v) { + if (is_array($v)) { + if (sizeof($v) == 2) // suggested by g.giunta@libero. + OCIBindByName($stmt,":$k",$inputarr[$k][0],$v[1]); + else + OCIBindByName($stmt,":$k",$inputarr[$k][0],$v[1],$v[2]); + } else { + $len = -1; + if ($v === ' ') $len = 1; + if (isset($bindarr)) { // is prepared sql, so no need to ocibindbyname again + $bindarr[$k] = $v; + } else { // dynamic sql, so rebind every time + OCIBindByName($stmt,":$k",$inputarr[$k],$len); + } + } + } + } + + if (!OCIExecute($stmt, OCI_DEFAULT)) { + OCIFreeStatement($stmt); + return $false; + } + + $ncols = OCINumCols($stmt); + for ( $i = 1; $i <= $ncols; $i++ ) { + $cols[] = '"'.OCIColumnName($stmt, $i).'"'; + } + $result = false; + + OCIFreeStatement($stmt); + $fields = implode(',', $cols); + $nrows += $offset; + $offset += 1; // in Oracle rownum starts at 1 + + if ($this->databaseType == 'oci8po') { + $sql = "SELECT $fields FROM". + "(SELECT rownum as adodb_rownum, $fields FROM". + " ($sql) WHERE rownum <= ?". + ") WHERE adodb_rownum >= ?"; + } else { + $sql = "SELECT $fields FROM". + "(SELECT rownum as adodb_rownum, $fields FROM". + " ($sql) WHERE rownum <= :adodb_nrows". + ") WHERE adodb_rownum >= :adodb_offset"; + } + $inputarr['adodb_nrows'] = $nrows; + $inputarr['adodb_offset'] = $offset; + + if ($secs2cache>0) $rs =& $this->CacheExecute($secs2cache, $sql,$inputarr); + else $rs =& $this->Execute($sql,$inputarr); + return $rs; + } + + } + + /** + * Usage: + * Store BLOBs and CLOBs + * + * Example: to store $var in a blob + * + * $conn->Execute('insert into TABLE (id,ablob) values(12,empty_blob())'); + * $conn->UpdateBlob('TABLE', 'ablob', $varHoldingBlob, 'ID=12', 'BLOB'); + * + * $blobtype supports 'BLOB' and 'CLOB', but you need to change to 'empty_clob()'. + * + * to get length of LOB: + * select DBMS_LOB.GETLENGTH(ablob) from TABLE + * + * If you are using CURSOR_SHARING = force, it appears this will case a segfault + * under oracle 8.1.7.0. Run: + * $db->Execute('ALTER SESSION SET CURSOR_SHARING=EXACT'); + * before UpdateBlob() then... + */ + + function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') + { + + //if (strlen($val) < 4000) return $this->Execute("UPDATE $table SET $column=:blob WHERE $where",array('blob'=>$val)) != false; + + switch(strtoupper($blobtype)) { + default: ADOConnection::outp("<b>UpdateBlob</b>: Unknown blobtype=$blobtype"); return false; + case 'BLOB': $type = OCI_B_BLOB; break; + case 'CLOB': $type = OCI_B_CLOB; break; + } + + if ($this->databaseType == 'oci8po') + $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO ?"; + else + $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO :blob"; + + $desc = OCINewDescriptor($this->_connectionID, OCI_D_LOB); + $arr['blob'] = array($desc,-1,$type); + if ($this->session_sharing_force_blob) $this->Execute('ALTER SESSION SET CURSOR_SHARING=EXACT'); + $commit = $this->autoCommit; + if ($commit) $this->BeginTrans(); + $rs = $this->_Execute($sql,$arr); + if ($rez = !empty($rs)) $desc->save($val); + $desc->free(); + if ($commit) $this->CommitTrans(); + if ($this->session_sharing_force_blob) $this->Execute('ALTER SESSION SET CURSOR_SHARING=FORCE'); + + if ($rez) $rs->Close(); + return $rez; + } + + /** + * Usage: store file pointed to by $var in a blob + */ + function UpdateBlobFile($table,$column,$val,$where,$blobtype='BLOB') + { + switch(strtoupper($blobtype)) { + default: ADOConnection::outp( "<b>UpdateBlob</b>: Unknown blobtype=$blobtype"); return false; + case 'BLOB': $type = OCI_B_BLOB; break; + case 'CLOB': $type = OCI_B_CLOB; break; + } + + if ($this->databaseType == 'oci8po') + $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO ?"; + else + $sql = "UPDATE $table set $column=EMPTY_{$blobtype}() WHERE $where RETURNING $column INTO :blob"; + + $desc = OCINewDescriptor($this->_connectionID, OCI_D_LOB); + $arr['blob'] = array($desc,-1,$type); + + $this->BeginTrans(); + $rs = ADODB_oci8::Execute($sql,$arr); + if ($rez = !empty($rs)) $desc->savefile($val); + $desc->free(); + $this->CommitTrans(); + + if ($rez) $rs->Close(); + return $rez; + } + + /** + * Execute SQL + * + * @param sql SQL statement to execute, or possibly an array holding prepared statement ($sql[0] will hold sql text) + * @param [inputarr] holds the input data to bind to. Null elements will be set to null. + * @return RecordSet or false + */ + function &Execute($sql,$inputarr=false) + { + if ($this->fnExecute) { + $fn = $this->fnExecute; + $ret =& $fn($this,$sql,$inputarr); + if (isset($ret)) return $ret; + } + if ($inputarr) { + #if (!is_array($inputarr)) $inputarr = array($inputarr); + + $element0 = reset($inputarr); + + # is_object check because oci8 descriptors can be passed in + if (is_array($element0) && !is_object(reset($element0))) { + if (is_string($sql)) + $stmt = $this->Prepare($sql); + else + $stmt = $sql; + + foreach($inputarr as $arr) { + $ret =& $this->_Execute($stmt,$arr); + if (!$ret) return $ret; + } + } else { + $ret =& $this->_Execute($sql,$inputarr); + } + + } else { + $ret =& $this->_Execute($sql,false); + } + + return $ret; + } + + /* + Example of usage: + + $stmt = $this->Prepare('insert into emp (empno, ename) values (:empno, :ename)'); + */ + function Prepare($sql,$cursor=false) + { + static $BINDNUM = 0; + + $stmt = OCIParse($this->_connectionID,$sql); + + if (!$stmt) return false; + + $BINDNUM += 1; + + $sttype = @OCIStatementType($stmt); + if ($sttype == 'BEGIN' || $sttype == 'DECLARE') { + return array($sql,$stmt,0,$BINDNUM, ($cursor) ? OCINewCursor($this->_connectionID) : false); + } + return array($sql,$stmt,0,$BINDNUM); + } + + /* + Call an oracle stored procedure and returns a cursor variable as a recordset. + Concept by Robert Tuttle robert@ud.com + + Example: + Note: we return a cursor variable in :RS2 + $rs = $db->ExecuteCursor("BEGIN adodb.open_tab(:RS2); END;",'RS2'); + + $rs = $db->ExecuteCursor( + "BEGIN :RS2 = adodb.getdata(:VAR1); END;", + 'RS2', + array('VAR1' => 'Mr Bean')); + + */ + function &ExecuteCursor($sql,$cursorName='rs',$params=false) + { + if (is_array($sql)) $stmt = $sql; + else $stmt = ADODB_oci8::Prepare($sql,true); # true to allocate OCINewCursor + + if (is_array($stmt) && sizeof($stmt) >= 5) { + $hasref = true; + $ignoreCur = false; + $this->Parameter($stmt, $ignoreCur, $cursorName, false, -1, OCI_B_CURSOR); + if ($params) { + foreach($params as $k => $v) { + $this->Parameter($stmt,$params[$k], $k); + } + } + } else + $hasref = false; + + $rs =& $this->Execute($stmt); + if ($rs) { + if ($rs->databaseType == 'array') OCIFreeCursor($stmt[4]); + else if ($hasref) $rs->_refcursor = $stmt[4]; + } + return $rs; + } + + /* + Bind a variable -- very, very fast for executing repeated statements in oracle. + Better than using + for ($i = 0; $i < $max; $i++) { + $p1 = ?; $p2 = ?; $p3 = ?; + $this->Execute("insert into table (col0, col1, col2) values (:0, :1, :2)", + array($p1,$p2,$p3)); + } + + Usage: + $stmt = $DB->Prepare("insert into table (col0, col1, col2) values (:0, :1, :2)"); + $DB->Bind($stmt, $p1); + $DB->Bind($stmt, $p2); + $DB->Bind($stmt, $p3); + for ($i = 0; $i < $max; $i++) { + $p1 = ?; $p2 = ?; $p3 = ?; + $DB->Execute($stmt); + } + + Some timings: + ** Test table has 3 cols, and 1 index. Test to insert 1000 records + Time 0.6081s (1644.60 inserts/sec) with direct OCIParse/OCIExecute + Time 0.6341s (1577.16 inserts/sec) with ADOdb Prepare/Bind/Execute + Time 1.5533s ( 643.77 inserts/sec) with pure SQL using Execute + + Now if PHP only had batch/bulk updating like Java or PL/SQL... + + Note that the order of parameters differs from OCIBindByName, + because we default the names to :0, :1, :2 + */ + function Bind(&$stmt,&$var,$size=4000,$type=false,$name=false,$isOutput=false) + { + + if (!is_array($stmt)) return false; + + if (($type == OCI_B_CURSOR) && sizeof($stmt) >= 5) { + return OCIBindByName($stmt[1],":".$name,$stmt[4],$size,$type); + } + + if ($name == false) { + if ($type !== false) $rez = OCIBindByName($stmt[1],":".$stmt[2],$var,$size,$type); + else $rez = OCIBindByName($stmt[1],":".$stmt[2],$var,$size); // +1 byte for null terminator + $stmt[2] += 1; + } else if (oci_lob_desc($type)) { + if ($this->debug) { + ADOConnection::outp("<b>Bind</b>: name = $name"); + } + //we have to create a new Descriptor here + $numlob = count($this->_refLOBs); + $this->_refLOBs[$numlob]['LOB'] = OCINewDescriptor($this->_connectionID, oci_lob_desc($type)); + $this->_refLOBs[$numlob]['TYPE'] = $isOutput; + + $tmp = &$this->_refLOBs[$numlob]['LOB']; + $rez = OCIBindByName($stmt[1], ":".$name, $tmp, -1, $type); + if ($this->debug) { + ADOConnection::outp("<b>Bind</b>: descriptor has been allocated, var (".$name.") binded"); + } + + // if type is input then write data to lob now + if ($isOutput == false) { + $var = $this->BlobEncode($var); + $tmp->WriteTemporary($var); + $this->_refLOBs[$numlob]['VAR'] = &$var; + if ($this->debug) { + ADOConnection::outp("<b>Bind</b>: LOB has been written to temp"); + } + } else { + $this->_refLOBs[$numlob]['VAR'] = &$var; + } + $rez = $tmp; + } else { + if ($this->debug) + ADOConnection::outp("<b>Bind</b>: name = $name"); + + if ($type !== false) $rez = OCIBindByName($stmt[1],":".$name,$var,$size,$type); + else $rez = OCIBindByName($stmt[1],":".$name,$var,$size); // +1 byte for null terminator + } + + return $rez; + } + + function Param($name,$type=false) + { + return ':'.$name; + } + + /* + Usage: + $stmt = $db->Prepare('select * from table where id =:myid and group=:group'); + $db->Parameter($stmt,$id,'myid'); + $db->Parameter($stmt,$group,'group'); + $db->Execute($stmt); + + @param $stmt Statement returned by Prepare() or PrepareSP(). + @param $var PHP variable to bind to + @param $name Name of stored procedure variable name to bind to. + @param [$isOutput] Indicates direction of parameter 0/false=IN 1=OUT 2= IN/OUT. This is ignored in oci8. + @param [$maxLen] Holds an maximum length of the variable. + @param [$type] The data type of $var. Legal values depend on driver. + + See OCIBindByName documentation at php.net. + */ + function Parameter(&$stmt,&$var,$name,$isOutput=false,$maxLen=4000,$type=false) + { + if ($this->debug) { + $prefix = ($isOutput) ? 'Out' : 'In'; + $ztype = (empty($type)) ? 'false' : $type; + ADOConnection::outp( "{$prefix}Parameter(\$stmt, \$php_var='$var', \$name='$name', \$maxLen=$maxLen, \$type=$ztype);"); + } + return $this->Bind($stmt,$var,$maxLen,$type,$name,$isOutput); + } + + /* + returns query ID if successful, otherwise false + this version supports: + + 1. $db->execute('select * from table'); + + 2. $db->prepare('insert into table (a,b,c) values (:0,:1,:2)'); + $db->execute($prepared_statement, array(1,2,3)); + + 3. $db->execute('insert into table (a,b,c) values (:a,:b,:c)',array('a'=>1,'b'=>2,'c'=>3)); + + 4. $db->prepare('insert into table (a,b,c) values (:0,:1,:2)'); + $db->bind($stmt,1); $db->bind($stmt,2); $db->bind($stmt,3); + $db->execute($stmt); + */ + function _query($sql,$inputarr) + { + if (is_array($sql)) { // is prepared sql + $stmt = $sql[1]; + + // we try to bind to permanent array, so that OCIBindByName is persistent + // and carried out once only - note that max array element size is 4000 chars + if (is_array($inputarr)) { + $bindpos = $sql[3]; + if (isset($this->_bind[$bindpos])) { + // all tied up already + $bindarr = &$this->_bind[$bindpos]; + } else { + // one statement to bind them all + $bindarr = array(); + foreach($inputarr as $k => $v) { + $bindarr[$k] = $v; + OCIBindByName($stmt,":$k",$bindarr[$k],is_string($v) && strlen($v)>4000 ? -1 : 4000); + } + $this->_bind[$bindpos] = &$bindarr; + } + } + } else { + $stmt=OCIParse($this->_connectionID,$sql); + } + + $this->_stmt = $stmt; + if (!$stmt) return false; + + if (defined('ADODB_PREFETCH_ROWS')) @OCISetPrefetch($stmt,ADODB_PREFETCH_ROWS); + + if (is_array($inputarr)) { + foreach($inputarr as $k => $v) { + if (is_array($v)) { + if (sizeof($v) == 2) // suggested by g.giunta@libero. + OCIBindByName($stmt,":$k",$inputarr[$k][0],$v[1]); + else + OCIBindByName($stmt,":$k",$inputarr[$k][0],$v[1],$v[2]); + + if ($this->debug==99) echo "name=:$k",' var='.$inputarr[$k][0],' len='.$v[1],' type='.$v[2],'<br>'; + } else { + $len = -1; + if ($v === ' ') $len = 1; + if (isset($bindarr)) { // is prepared sql, so no need to ocibindbyname again + $bindarr[$k] = $v; + } else { // dynamic sql, so rebind every time + OCIBindByName($stmt,":$k",$inputarr[$k],$len); + } + } + } + } + + $this->_errorMsg = false; + $this->_errorCode = false; + if (OCIExecute($stmt,$this->_commit)) { +//OCIInternalDebug(1); + if (count($this -> _refLOBs) > 0) { + + foreach ($this -> _refLOBs as $key => $value) { + if ($this -> _refLOBs[$key]['TYPE'] == true) { + $tmp = $this -> _refLOBs[$key]['LOB'] -> load(); + if ($this -> debug) { + ADOConnection::outp("<b>OUT LOB</b>: LOB has been loaded. <br>"); + } + //$_GLOBALS[$this -> _refLOBs[$key]['VAR']] = $tmp; + $this -> _refLOBs[$key]['VAR'] = $tmp; + } else { + $this->_refLOBs[$key]['LOB']->save($this->_refLOBs[$key]['VAR']); + $this -> _refLOBs[$key]['LOB']->free(); + unset($this -> _refLOBs[$key]); + if ($this->debug) { + ADOConnection::outp("<b>IN LOB</b>: LOB has been saved. <br>"); + } + } + } + } + + switch (@OCIStatementType($stmt)) { + case "SELECT": + return $stmt; + + case 'DECLARE': + case "BEGIN": + if (is_array($sql) && !empty($sql[4])) { + $cursor = $sql[4]; + if (is_resource($cursor)) { + $ok = OCIExecute($cursor); + return $cursor; + } + return $stmt; + } else { + if (is_resource($stmt)) { + OCIFreeStatement($stmt); + return true; + } + return $stmt; + } + break; + default : + // ociclose -- no because it could be used in a LOB? + return true; + } + } + return false; + } + + // returns true or false + function _close() + { + if (!$this->_connectionID) return; + + if (!$this->autoCommit) OCIRollback($this->_connectionID); + if (count($this->_refLOBs) > 0) { + foreach ($this ->_refLOBs as $key => $value) { + $this->_refLOBs[$key]['LOB']->free(); + unset($this->_refLOBs[$key]); + } + } + OCILogoff($this->_connectionID); + + $this->_stmt = false; + $this->_connectionID = false; + } + + function MetaPrimaryKeys($table, $owner=false,$internalKey=false) + { + if ($internalKey) return array('ROWID'); + + // tested with oracle 8.1.7 + $table = strtoupper($table); + if ($owner) { + $owner_clause = "AND ((a.OWNER = b.OWNER) AND (a.OWNER = UPPER('$owner')))"; + $ptab = 'ALL_'; + } else { + $owner_clause = ''; + $ptab = 'USER_'; + } + $sql = " +SELECT /*+ RULE */ distinct b.column_name + FROM {$ptab}CONSTRAINTS a + , {$ptab}CONS_COLUMNS b + WHERE ( UPPER(b.table_name) = ('$table')) + AND (UPPER(a.table_name) = ('$table') and a.constraint_type = 'P') + $owner_clause + AND (a.constraint_name = b.constraint_name)"; + + $rs = $this->Execute($sql); + if ($rs && !$rs->EOF) { + $arr =& $rs->GetArray(); + $a = array(); + foreach($arr as $v) { + $a[] = reset($v); + } + return $a; + } + else return false; + } + + // http://gis.mit.edu/classes/11.521/sqlnotes/referential_integrity.html + function MetaForeignKeys($table, $owner=false) + { + global $ADODB_FETCH_MODE; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $table = $this->qstr(strtoupper($table)); + if (!$owner) { + $owner = $this->user; + $tabp = 'user_'; + } else + $tabp = 'all_'; + + $owner = ' and owner='.$this->qstr(strtoupper($owner)); + + $sql = +"select constraint_name,r_owner,r_constraint_name + from {$tabp}constraints + where constraint_type = 'R' and table_name = $table $owner"; + + $constraints =& $this->GetArray($sql); + $arr = false; + foreach($constraints as $constr) { + $cons = $this->qstr($constr[0]); + $rowner = $this->qstr($constr[1]); + $rcons = $this->qstr($constr[2]); + $cols = $this->GetArray("select column_name from {$tabp}cons_columns where constraint_name=$cons $owner order by position"); + $tabcol = $this->GetArray("select table_name,column_name from {$tabp}cons_columns where owner=$rowner and constraint_name=$rcons order by position"); + + if ($cols && $tabcol) + for ($i=0, $max=sizeof($cols); $i < $max; $i++) { + $arr[$tabcol[$i][0]] = $cols[$i][0].'='.$tabcol[$i][1]; + } + } + $ADODB_FETCH_MODE = $save; + + return $arr; + } + + + function CharMax() + { + return 4000; + } + + function TextMax() + { + return 4000; + } + + /** + * Quotes a string. + * An example is $db->qstr("Don't bother",magic_quotes_runtime()); + * + * @param s the string to quote + * @param [magic_quotes] if $s is GET/POST var, set to get_magic_quotes_gpc(). + * This undoes the stupidity of magic quotes for GPC. + * + * @return quoted string to be sent back to database + */ + function qstr($s,$magic_quotes=false) + { + //$nofixquotes=false; + + if ($this->noNullStrings && strlen($s)==0)$s = ' '; + if (!$magic_quotes) { + if ($this->replaceQuote[0] == '\\'){ + $s = str_replace('\\','\\\\',$s); + } + return "'".str_replace("'",$this->replaceQuote,$s)."'"; + } + + // undo magic quotes for " + $s = str_replace('\\"','"',$s); + + $s = str_replace('\\\\','\\',$s); + return "'".str_replace("\\'",$this->replaceQuote,$s)."'"; + + } + +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordset_oci8 extends ADORecordSet { + + var $databaseType = 'oci8'; + var $bind=false; + var $_fieldobjs; + + //var $_arr = false; + + function ADORecordset_oci8($queryID,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + switch ($mode) + { + case ADODB_FETCH_ASSOC:$this->fetchMode = OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS; break; + case ADODB_FETCH_DEFAULT: + case ADODB_FETCH_BOTH:$this->fetchMode = OCI_NUM+OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS; break; + case ADODB_FETCH_NUM: + default: + $this->fetchMode = OCI_NUM+OCI_RETURN_NULLS+OCI_RETURN_LOBS; break; + } + + $this->adodbFetchMode = $mode; + $this->_queryID = $queryID; + } + + + function Init() + { + if ($this->_inited) return; + + $this->_inited = true; + if ($this->_queryID) { + + $this->_currentRow = 0; + @$this->_initrs(); + $this->EOF = !$this->_fetch(); + + /* + // based on idea by Gaetano Giunta to detect unusual oracle errors + // see http://phplens.com/lens/lensforum/msgs.php?id=6771 + $err = OCIError($this->_queryID); + if ($err && $this->connection->debug) ADOConnection::outp($err); + */ + + if (!is_array($this->fields)) { + $this->_numOfRows = 0; + $this->fields = array(); + } + } else { + $this->fields = array(); + $this->_numOfRows = 0; + $this->_numOfFields = 0; + $this->EOF = true; + } + } + + function _initrs() + { + $this->_numOfRows = -1; + $this->_numOfFields = OCInumcols($this->_queryID); + if ($this->_numOfFields>0) { + $this->_fieldobjs = array(); + $max = $this->_numOfFields; + for ($i=0;$i<$max; $i++) $this->_fieldobjs[] = $this->_FetchField($i); + } + } + + /* Returns: an object containing field information. + Get column information in the Recordset object. fetchField() can be used in order to obtain information about + fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by + fetchField() is retrieved. */ + + function &_FetchField($fieldOffset = -1) + { + $fld = new ADOFieldObject; + $fieldOffset += 1; + $fld->name =OCIcolumnname($this->_queryID, $fieldOffset); + $fld->type = OCIcolumntype($this->_queryID, $fieldOffset); + $fld->max_length = OCIcolumnsize($this->_queryID, $fieldOffset); + if ($fld->type == 'NUMBER') { + $p = OCIColumnPrecision($this->_queryID, $fieldOffset); + $sc = OCIColumnScale($this->_queryID, $fieldOffset); + if ($p != 0 && $sc == 0) $fld->type = 'INT'; + //echo " $this->name ($p.$sc) "; + } + return $fld; + } + + /* For some reason, OCIcolumnname fails when called after _initrs() so we cache it */ + function &FetchField($fieldOffset = -1) + { + return $this->_fieldobjs[$fieldOffset]; + } + + + /* + // 10% speedup to move MoveNext to child class + function _MoveNext() + { + //global $ADODB_EXTENSION;if ($ADODB_EXTENSION) return @adodb_movenext($this); + + if ($this->EOF) return false; + + $this->_currentRow++; + if(@OCIfetchinto($this->_queryID,$this->fields,$this->fetchMode)) + return true; + $this->EOF = true; + + return false; + } */ + + + function MoveNext() + { + if (@OCIfetchinto($this->_queryID,$this->fields,$this->fetchMode)) { + $this->_currentRow += 1; + return true; + } + if (!$this->EOF) { + $this->_currentRow += 1; + $this->EOF = true; + } + return false; + } + + /* + # does not work as first record is retrieved in _initrs(), so is not included in GetArray() + function &GetArray($nRows = -1) + { + global $ADODB_OCI8_GETARRAY; + + if (true || !empty($ADODB_OCI8_GETARRAY)) { + # does not support $ADODB_ANSI_PADDING_OFF + + //OCI_RETURN_NULLS and OCI_RETURN_LOBS is set by OCIfetchstatement + switch($this->adodbFetchMode) { + case ADODB_FETCH_NUM: + + $ncols = @OCIfetchstatement($this->_queryID, $results, 0, $nRows, OCI_FETCHSTATEMENT_BY_ROW+OCI_NUM); + $results = array_merge(array($this->fields),$results); + return $results; + + case ADODB_FETCH_ASSOC: + if (ADODB_ASSOC_CASE != 2 || $this->databaseType != 'oci8') break; + + $ncols = @OCIfetchstatement($this->_queryID, $assoc, 0, $nRows, OCI_FETCHSTATEMENT_BY_ROW); + $results =& array_merge(array($this->fields),$assoc); + return $results; + + default: + break; + } + } + + $results =& ADORecordSet::GetArray($nRows); + return $results; + + } */ + + /* Optimize SelectLimit() by using OCIFetch() instead of OCIFetchInto() */ + function &GetArrayLimit($nrows,$offset=-1) + { + if ($offset <= 0) { + $arr =& $this->GetArray($nrows); + return $arr; + } + for ($i=1; $i < $offset; $i++) + if (!@OCIFetch($this->_queryID)) return array(); + + if (!@OCIfetchinto($this->_queryID,$this->fields,$this->fetchMode)) return array(); + $results = array(); + $cnt = 0; + while (!$this->EOF && $nrows != $cnt) { + $results[$cnt++] = $this->fields; + $this->MoveNext(); + } + + return $results; + } + + + /* Use associative array to get fields array */ + function Fields($colname) + { + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + + + function _seek($row) + { + return false; + } + + function _fetch() + { + return @OCIfetchinto($this->_queryID,$this->fields,$this->fetchMode); + } + + /* close() only needs to be called if you are worried about using too much memory while your script + is running. All associated result memory for the specified result identifier will automatically be freed. */ + + function _close() + { + if ($this->connection->_stmt === $this->_queryID) $this->connection->_stmt = false; + if (!empty($this->_refcursor)) { + OCIFreeCursor($this->_refcursor); + $this->_refcursor = false; + } + @OCIFreeStatement($this->_queryID); + $this->_queryID = false; + + } + + function MetaType($t,$len=-1) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + switch (strtoupper($t)) { + case 'VARCHAR': + case 'VARCHAR2': + case 'CHAR': + case 'VARBINARY': + case 'BINARY': + case 'NCHAR': + case 'NVARCHAR': + case 'NVARCHAR2': + if (isset($this) && $len <= $this->blobSize) return 'C'; + + case 'NCLOB': + case 'LONG': + case 'LONG VARCHAR': + case 'CLOB': + return 'X'; + + case 'LONG RAW': + case 'LONG VARBINARY': + case 'BLOB': + return 'B'; + + case 'DATE': + return ($this->connection->datetime) ? 'T' : 'D'; + + + case 'TIMESTAMP': return 'T'; + + case 'INT': + case 'SMALLINT': + case 'INTEGER': + return 'I'; + + default: return 'N'; + } + } +} + +class ADORecordSet_ext_oci8 extends ADORecordSet_oci8 { + function ADORecordSet_ext_oci8($queryID,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + switch ($mode) + { + case ADODB_FETCH_ASSOC:$this->fetchMode = OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS; break; + case ADODB_FETCH_DEFAULT: + case ADODB_FETCH_BOTH:$this->fetchMode = OCI_NUM+OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS; break; + case ADODB_FETCH_NUM: + default: $this->fetchMode = OCI_NUM+OCI_RETURN_NULLS+OCI_RETURN_LOBS; break; + } + $this->adodbFetchMode = $mode; + $this->_queryID = $queryID; + } + + function MoveNext() + { + return adodb_movenext($this); + } +} +?> diff --git a/framework/DataAccess/adodb/drivers/adodb-oci805.inc.php b/framework/DataAccess/adodb/drivers/adodb-oci805.inc.php new file mode 100644 index 00000000..7fb3c1eb --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-oci805.inc.php @@ -0,0 +1,59 @@ +<?php +/** + * @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + * Released under both BSD license and Lesser GPL library license. + * Whenever there is any discrepancy between the two licenses, + * the BSD license will take precedence. + * + * Set tabs to 4 for best viewing. + * + * Latest version is available at http://php.weblogs.com + * + * Oracle 8.0.5 driver +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +include_once(ADODB_DIR.'/drivers/adodb-oci8.inc.php'); + +class ADODB_oci805 extends ADODB_oci8 { + var $databaseType = "oci805"; + var $connectSID = true; + + function ADODB_oci805() + { + $this->ADODB_oci8(); + } + + function &SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) + { + // seems that oracle only supports 1 hint comment in 8i + if (strpos($sql,'/*+') !== false) + $sql = str_replace('/*+ ','/*+FIRST_ROWS ',$sql); + else + $sql = preg_replace('/^[ \t\n]*select/i','SELECT /*+FIRST_ROWS*/',$sql); + + /* + The following is only available from 8.1.5 because order by in inline views not + available before then... + http://www.jlcomp.demon.co.uk/faq/top_sql.html + if ($nrows > 0) { + if ($offset > 0) $nrows += $offset; + $sql = "select * from ($sql) where rownum <= $nrows"; + $nrows = -1; + } + */ + + return ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); + } +} + +class ADORecordset_oci805 extends ADORecordset_oci8 { + var $databaseType = "oci805"; + function ADORecordset_oci805($id,$mode=false) + { + $this->ADORecordset_oci8($id,$mode); + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-oci8po.inc.php b/framework/DataAccess/adodb/drivers/adodb-oci8po.inc.php new file mode 100644 index 00000000..b9db112b --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-oci8po.inc.php @@ -0,0 +1,217 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim. All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + + Latest version is available at http://adodb.sourceforge.net + + Portable version of oci8 driver, to make it more similar to other database drivers. + The main differences are + + 1. that the OCI_ASSOC names are in lowercase instead of uppercase. + 2. bind variables are mapped using ? instead of :<bindvar> + + Should some emulation of RecordCount() be implemented? + +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +include_once(ADODB_DIR.'/drivers/adodb-oci8.inc.php'); + +class ADODB_oci8po extends ADODB_oci8 { + var $databaseType = 'oci8po'; + var $dataProvider = 'oci8'; + var $metaColumnsSQL = "select lower(cname),coltype,width, SCALE, PRECISION, NULLS, DEFAULTVAL from col where tname='%s' order by colno"; //changed by smondino@users.sourceforge. net + var $metaTablesSQL = "select lower(table_name),table_type from cat where table_type in ('TABLE','VIEW')"; + + function ADODB_oci8po() + { + $this->_hasOCIFetchStatement = ADODB_PHPVER >= 0x4200; + # oci8po does not support adodb extension: adodb_movenext() + } + + function Param($name) + { + return '?'; + } + + function Prepare($sql,$cursor=false) + { + $sqlarr = explode('?',$sql); + $sql = $sqlarr[0]; + for ($i = 1, $max = sizeof($sqlarr); $i < $max; $i++) { + $sql .= ':'.($i-1) . $sqlarr[$i]; + } + return ADODB_oci8::Prepare($sql,$cursor); + } + + // emulate handling of parameters ? ?, replacing with :bind0 :bind1 + function _query($sql,$inputarr) + { + if (is_array($inputarr)) { + $i = 0; + if (is_array($sql)) { + foreach($inputarr as $v) { + $arr['bind'.$i++] = $v; + } + } else { + $sqlarr = explode('?',$sql); + $sql = $sqlarr[0]; + foreach($inputarr as $k => $v) { + $sql .= ":$k" . $sqlarr[++$i]; + } + } + } + return ADODB_oci8::_query($sql,$inputarr); + } +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordset_oci8po extends ADORecordset_oci8 { + + var $databaseType = 'oci8po'; + + function ADORecordset_oci8po($queryID,$mode=false) + { + $this->ADORecordset_oci8($queryID,$mode); + } + + function Fields($colname) + { + if ($this->fetchMode & OCI_ASSOC) return $this->fields[$colname]; + + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + // lowercase field names... + function &_FetchField($fieldOffset = -1) + { + $fld = new ADOFieldObject; + $fieldOffset += 1; + $fld->name = strtolower(OCIcolumnname($this->_queryID, $fieldOffset)); + $fld->type = OCIcolumntype($this->_queryID, $fieldOffset); + $fld->max_length = OCIcolumnsize($this->_queryID, $fieldOffset); + if ($fld->type == 'NUMBER') { + //$p = OCIColumnPrecision($this->_queryID, $fieldOffset); + $sc = OCIColumnScale($this->_queryID, $fieldOffset); + if ($sc == 0) $fld->type = 'INT'; + } + return $fld; + } + /* + function MoveNext() + { + if (@OCIfetchinto($this->_queryID,$this->fields,$this->fetchMode)) { + $this->_currentRow += 1; + return true; + } + if (!$this->EOF) { + $this->_currentRow += 1; + $this->EOF = true; + } + return false; + }*/ + + // 10% speedup to move MoveNext to child class + function MoveNext() + { + if(@OCIfetchinto($this->_queryID,$this->fields,$this->fetchMode)) { + global $ADODB_ANSI_PADDING_OFF; + $this->_currentRow++; + + if ($this->fetchMode & OCI_ASSOC) $this->_updatefields(); + if (!empty($ADODB_ANSI_PADDING_OFF)) { + foreach($this->fields as $k => $v) { + if (is_string($v)) $this->fields[$k] = rtrim($v); + } + } + return true; + } + if (!$this->EOF) { + $this->EOF = true; + $this->_currentRow++; + } + return false; + } + + /* Optimize SelectLimit() by using OCIFetch() instead of OCIFetchInto() */ + function &GetArrayLimit($nrows,$offset=-1) + { + if ($offset <= 0) { + $arr = $this->GetArray($nrows); + return $arr; + } + for ($i=1; $i < $offset; $i++) + if (!@OCIFetch($this->_queryID)) { + $arr = array(); + return $arr; + } + if (!@OCIfetchinto($this->_queryID,$this->fields,$this->fetchMode)) { + $arr = array(); + return $arr; + } + if ($this->fetchMode & OCI_ASSOC) $this->_updatefields(); + $results = array(); + $cnt = 0; + while (!$this->EOF && $nrows != $cnt) { + $results[$cnt++] = $this->fields; + $this->MoveNext(); + } + + return $results; + } + + // Create associative array + function _updatefields() + { + if (ADODB_ASSOC_CASE == 2) return; // native + + $arr = array(); + $lowercase = (ADODB_ASSOC_CASE == 0); + + foreach($this->fields as $k => $v) { + if (is_integer($k)) $arr[$k] = $v; + else { + if ($lowercase) + $arr[strtolower($k)] = $v; + else + $arr[strtoupper($k)] = $v; + } + } + $this->fields = $arr; + } + + function _fetch() + { + $ret = @OCIfetchinto($this->_queryID,$this->fields,$this->fetchMode); + if ($ret) { + global $ADODB_ANSI_PADDING_OFF; + + if ($this->fetchMode & OCI_ASSOC) $this->_updatefields(); + if (!empty($ADODB_ANSI_PADDING_OFF)) { + foreach($this->fields as $k => $v) { + if (is_string($v)) $this->fields[$k] = rtrim($v); + } + } + } + return $ret; + } + +} + + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-odbc.inc.php b/framework/DataAccess/adodb/drivers/adodb-odbc.inc.php new file mode 100644 index 00000000..55350b8d --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-odbc.inc.php @@ -0,0 +1,738 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim#natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Requires ODBC. Works on Windows and Unix. +*/ +// security - hide paths +if (!defined('ADODB_DIR')) die(); + + define("_ADODB_ODBC_LAYER", 2 ); + +/*-------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------*/ + + +class ADODB_odbc extends ADOConnection { + var $databaseType = "odbc"; + var $fmtDate = "'Y-m-d'"; + var $fmtTimeStamp = "'Y-m-d, h:i:sA'"; + var $replaceQuote = "''"; // string to use to replace quotes + var $dataProvider = "odbc"; + var $hasAffectedRows = true; + var $binmode = ODBC_BINMODE_RETURN; + var $useFetchArray = false; // setting this to true will make array elements in FETCH_ASSOC mode case-sensitive + // breaking backward-compat + //var $longreadlen = 8000; // default number of chars to return for a Blob/Long field + var $_bindInputArray = false; + var $curmode = SQL_CUR_USE_DRIVER; // See sqlext.h, SQL_CUR_DEFAULT == SQL_CUR_USE_DRIVER == 2L + var $_genSeqSQL = "create table %s (id integer)"; + var $_autocommit = true; + var $_haserrorfunctions = true; + var $_has_stupid_odbc_fetch_api_change = true; + var $_lastAffectedRows = 0; + var $uCaseTables = true; // for meta* functions, uppercase table names + + function ADODB_odbc() + { + $this->_haserrorfunctions = ADODB_PHPVER >= 0x4050; + $this->_has_stupid_odbc_fetch_api_change = ADODB_PHPVER >= 0x4200; + } + + // returns true or false + function _connect($argDSN, $argUsername, $argPassword, $argDatabasename) + { + global $php_errormsg; + + if (!function_exists('odbc_connect')) return null; + + if ($this->debug && $argDatabasename && $this->databaseType != 'vfp') { + ADOConnection::outp("For odbc Connect(), $argDatabasename is not used. Place dsn in 1st parameter."); + } + if (isset($php_errormsg)) $php_errormsg = ''; + if ($this->curmode === false) $this->_connectionID = odbc_connect($argDSN,$argUsername,$argPassword); + else $this->_connectionID = odbc_connect($argDSN,$argUsername,$argPassword,$this->curmode); + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; + if (isset($this->connectStmt)) $this->Execute($this->connectStmt); + + return $this->_connectionID != false; + } + + // returns true or false + function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename) + { + global $php_errormsg; + + if (!function_exists('odbc_connect')) return null; + + if (isset($php_errormsg)) $php_errormsg = ''; + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; + if ($this->debug && $argDatabasename) { + ADOConnection::outp("For odbc PConnect(), $argDatabasename is not used. Place dsn in 1st parameter."); + } + // print "dsn=$argDSN u=$argUsername p=$argPassword<br>"; flush(); + if ($this->curmode === false) $this->_connectionID = odbc_connect($argDSN,$argUsername,$argPassword); + else $this->_connectionID = odbc_pconnect($argDSN,$argUsername,$argPassword,$this->curmode); + + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; + if ($this->_connectionID && $this->autoRollback) @odbc_rollback($this->_connectionID); + if (isset($this->connectStmt)) $this->Execute($this->connectStmt); + + return $this->_connectionID != false; + } + + + function ServerInfo() + { + + if (!empty($this->host) && ADODB_PHPVER >= 0x4300) { + $dsn = strtoupper($this->host); + $first = true; + $found = false; + + if (!function_exists('odbc_data_source')) return false; + + while(true) { + + $rez = @odbc_data_source($this->_connectionID, + $first ? SQL_FETCH_FIRST : SQL_FETCH_NEXT); + $first = false; + if (!is_array($rez)) break; + if (strtoupper($rez['server']) == $dsn) { + $found = true; + break; + } + } + if (!$found) return ADOConnection::ServerInfo(); + if (!isset($rez['version'])) $rez['version'] = ''; + return $rez; + } else { + return ADOConnection::ServerInfo(); + } + } + + + function CreateSequence($seqname='adodbseq',$start=1) + { + if (empty($this->_genSeqSQL)) return false; + $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname)); + if (!$ok) return false; + $start -= 1; + return $this->Execute("insert into $seqname values($start)"); + } + + var $_dropSeqSQL = 'drop table %s'; + function DropSequence($seqname) + { + if (empty($this->_dropSeqSQL)) return false; + return $this->Execute(sprintf($this->_dropSeqSQL,$seqname)); + } + + /* + This algorithm is not very efficient, but works even if table locking + is not available. + + Will return false if unable to generate an ID after $MAXLOOPS attempts. + */ + function GenID($seq='adodbseq',$start=1) + { + // if you have to modify the parameter below, your database is overloaded, + // or you need to implement generation of id's yourself! + $MAXLOOPS = 100; + //$this->debug=1; + while (--$MAXLOOPS>=0) { + $num = $this->GetOne("select id from $seq"); + if ($num === false) { + $this->Execute(sprintf($this->_genSeqSQL ,$seq)); + $start -= 1; + $num = '0'; + $ok = $this->Execute("insert into $seq values($start)"); + if (!$ok) return false; + } + $this->Execute("update $seq set id=id+1 where id=$num"); + + if ($this->affected_rows() > 0) { + $num += 1; + $this->genID = $num; + return $num; + } + } + if ($fn = $this->raiseErrorFn) { + $fn($this->databaseType,'GENID',-32000,"Unable to generate unique id after $MAXLOOPS attempts",$seq,$num); + } + return false; + } + + + function ErrorMsg() + { + if ($this->_haserrorfunctions) { + if ($this->_errorMsg !== false) return $this->_errorMsg; + if (empty($this->_connectionID)) return @odbc_errormsg(); + return @odbc_errormsg($this->_connectionID); + } else return ADOConnection::ErrorMsg(); + } + + function ErrorNo() + { + + if ($this->_haserrorfunctions) { + if ($this->_errorCode !== false) { + // bug in 4.0.6, error number can be corrupted string (should be 6 digits) + return (strlen($this->_errorCode)<=2) ? 0 : $this->_errorCode; + } + + if (empty($this->_connectionID)) $e = @odbc_error(); + else $e = @odbc_error($this->_connectionID); + + // bug in 4.0.6, error number can be corrupted string (should be 6 digits) + // so we check and patch + if (strlen($e)<=2) return 0; + return $e; + } else return ADOConnection::ErrorNo(); + } + + + + function BeginTrans() + { + if (!$this->hasTransactions) return false; + if ($this->transOff) return true; + $this->transCnt += 1; + $this->_autocommit = false; + return odbc_autocommit($this->_connectionID,false); + } + + function CommitTrans($ok=true) + { + if ($this->transOff) return true; + if (!$ok) return $this->RollbackTrans(); + if ($this->transCnt) $this->transCnt -= 1; + $this->_autocommit = true; + $ret = odbc_commit($this->_connectionID); + odbc_autocommit($this->_connectionID,true); + return $ret; + } + + function RollbackTrans() + { + if ($this->transOff) return true; + if ($this->transCnt) $this->transCnt -= 1; + $this->_autocommit = true; + $ret = odbc_rollback($this->_connectionID); + odbc_autocommit($this->_connectionID,true); + return $ret; + } + + function MetaPrimaryKeys($table) + { + global $ADODB_FETCH_MODE; + + if ($this->uCaseTables) $table = strtoupper($table); + $schema = ''; + $this->_findschema($table,$schema); + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $qid = @odbc_primarykeys($this->_connectionID,'',$schema,$table); + + if (!$qid) { + $ADODB_FETCH_MODE = $savem; + return false; + } + $rs = new ADORecordSet_odbc($qid); + $ADODB_FETCH_MODE = $savem; + + if (!$rs) return false; + $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change; + + $arr =& $rs->GetArray(); + $rs->Close(); + //print_r($arr); + $arr2 = array(); + for ($i=0; $i < sizeof($arr); $i++) { + if ($arr[$i][3]) $arr2[] = $arr[$i][3]; + } + return $arr2; + } + + + + function &MetaTables($ttype=false) + { + global $ADODB_FETCH_MODE; + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $qid = odbc_tables($this->_connectionID); + + $rs = new ADORecordSet_odbc($qid); + + $ADODB_FETCH_MODE = $savem; + if (!$rs) { + $false = false; + return $false; + } + $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change; + + $arr =& $rs->GetArray(); + //print_r($arr); + + $rs->Close(); + $arr2 = array(); + + if ($ttype) { + $isview = strncmp($ttype,'V',1) === 0; + } + for ($i=0; $i < sizeof($arr); $i++) { + if (!$arr[$i][2]) continue; + $type = $arr[$i][3]; + if ($ttype) { + if ($isview) { + if (strncmp($type,'V',1) === 0) $arr2[] = $arr[$i][2]; + } else if (strncmp($type,'SYS',3) !== 0) $arr2[] = $arr[$i][2]; + } else if (strncmp($type,'SYS',3) !== 0) $arr2[] = $arr[$i][2]; + } + return $arr2; + } + +/* +See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/htm/odbcdatetime_data_type_changes.asp +/ SQL data type codes / +#define SQL_UNKNOWN_TYPE 0 +#define SQL_CHAR 1 +#define SQL_NUMERIC 2 +#define SQL_DECIMAL 3 +#define SQL_INTEGER 4 +#define SQL_SMALLINT 5 +#define SQL_FLOAT 6 +#define SQL_REAL 7 +#define SQL_DOUBLE 8 +#if (ODBCVER >= 0x0300) +#define SQL_DATETIME 9 +#endif +#define SQL_VARCHAR 12 + + +/ One-parameter shortcuts for date/time data types / +#if (ODBCVER >= 0x0300) +#define SQL_TYPE_DATE 91 +#define SQL_TYPE_TIME 92 +#define SQL_TYPE_TIMESTAMP 93 + +#define SQL_UNICODE (-95) +#define SQL_UNICODE_VARCHAR (-96) +#define SQL_UNICODE_LONGVARCHAR (-97) +*/ + function ODBCTypes($t) + { + switch ((integer)$t) { + case 1: + case 12: + case 0: + case -95: + case -96: + return 'C'; + case -97: + case -1: //text + return 'X'; + case -4: //image + return 'B'; + + case 9: + case 91: + return 'D'; + + case 10: + case 11: + case 92: + case 93: + return 'T'; + + case 4: + case 5: + case -6: + return 'I'; + + case -11: // uniqidentifier + return 'R'; + case -7: //bit + return 'L'; + + default: + return 'N'; + } + } + + function &MetaColumns($table) + { + global $ADODB_FETCH_MODE; + + $false = false; + if ($this->uCaseTables) $table = strtoupper($table); + $schema = ''; + $this->_findschema($table,$schema); + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + + /*if (false) { // after testing, confirmed that the following does not work becoz of a bug + $qid2 = odbc_tables($this->_connectionID); + $rs = new ADORecordSet_odbc($qid2); + $ADODB_FETCH_MODE = $savem; + if (!$rs) return false; + $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change; + $rs->_fetch(); + + while (!$rs->EOF) { + if ($table == strtoupper($rs->fields[2])) { + $q = $rs->fields[0]; + $o = $rs->fields[1]; + break; + } + $rs->MoveNext(); + } + $rs->Close(); + + $qid = odbc_columns($this->_connectionID,$q,$o,strtoupper($table),'%'); + } */ + + switch ($this->databaseType) { + case 'access': + case 'vfp': + $qid = odbc_columns($this->_connectionID);#,'%','',strtoupper($table),'%'); + break; + + + case 'db2': + $colname = "%"; + $qid = odbc_columns($this->_connectionID, "", $schema, $table, $colname); + break; + + default: + $qid = @odbc_columns($this->_connectionID,'%','%',strtoupper($table),'%'); + if (empty($qid)) $qid = odbc_columns($this->_connectionID); + break; + } + if (empty($qid)) return $false; + + $rs =& new ADORecordSet_odbc($qid); + $ADODB_FETCH_MODE = $savem; + + if (!$rs) return $false; + $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change; + $rs->_fetch(); + + $retarr = array(); + + /* + $rs->fields indices + 0 TABLE_QUALIFIER + 1 TABLE_SCHEM + 2 TABLE_NAME + 3 COLUMN_NAME + 4 DATA_TYPE + 5 TYPE_NAME + 6 PRECISION + 7 LENGTH + 8 SCALE + 9 RADIX + 10 NULLABLE + 11 REMARKS + */ + while (!$rs->EOF) { + // adodb_pr($rs->fields); + if (strtoupper(trim($rs->fields[2])) == $table && (!$schema || strtoupper($rs->fields[1]) == $schema)) { + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[3]; + $fld->type = $this->ODBCTypes($rs->fields[4]); + + // ref: http://msdn.microsoft.com/library/default.asp?url=/archive/en-us/dnaraccgen/html/msdn_odk.asp + // access uses precision to store length for char/varchar + if ($fld->type == 'C' or $fld->type == 'X') { + if ($this->databaseType == 'access') + $fld->max_length = $rs->fields[6]; + else if ($rs->fields[4] <= -95) // UNICODE + $fld->max_length = $rs->fields[7]/2; + else + $fld->max_length = $rs->fields[7]; + } else + $fld->max_length = $rs->fields[7]; + $fld->not_null = !empty($rs->fields[10]); + $fld->scale = $rs->fields[8]; + $retarr[strtoupper($fld->name)] = $fld; + } else if (sizeof($retarr)>0) + break; + $rs->MoveNext(); + } + $rs->Close(); //-- crashes 4.03pl1 -- why? + + if (empty($retarr)) $retarr = false; + return $retarr; + } + + function Prepare($sql) + { + if (! $this->_bindInputArray) return $sql; // no binding + $stmt = odbc_prepare($this->_connectionID,$sql); + if (!$stmt) { + // we don't know whether odbc driver is parsing prepared stmts, so just return sql + return $sql; + } + return array($sql,$stmt,false); + } + + /* returns queryID or false */ + function _query($sql,$inputarr=false) + { + GLOBAL $php_errormsg; + if (isset($php_errormsg)) $php_errormsg = ''; + $this->_error = ''; + + if ($inputarr) { + if (is_array($sql)) { + $stmtid = $sql[1]; + } else { + $stmtid = odbc_prepare($this->_connectionID,$sql); + + if ($stmtid == false) { + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; + return false; + } + } + + if (! odbc_execute($stmtid,$inputarr)) { + //@odbc_free_result($stmtid); + if ($this->_haserrorfunctions) { + $this->_errorMsg = odbc_errormsg(); + $this->_errorCode = odbc_error(); + } + return false; + } + + } else if (is_array($sql)) { + $stmtid = $sql[1]; + if (!odbc_execute($stmtid)) { + //@odbc_free_result($stmtid); + if ($this->_haserrorfunctions) { + $this->_errorMsg = odbc_errormsg(); + $this->_errorCode = odbc_error(); + } + return false; + } + } else + $stmtid = odbc_exec($this->_connectionID,$sql); + + $this->_lastAffectedRows = 0; + if ($stmtid) { + if (@odbc_num_fields($stmtid) == 0) { + $this->_lastAffectedRows = odbc_num_rows($stmtid); + $stmtid = true; + } else { + $this->_lastAffectedRows = 0; + odbc_binmode($stmtid,$this->binmode); + odbc_longreadlen($stmtid,$this->maxblobsize); + } + + if ($this->_haserrorfunctions) { + $this->_errorMsg = ''; + $this->_errorCode = 0; + } else + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; + } else { + if ($this->_haserrorfunctions) { + $this->_errorMsg = odbc_errormsg(); + $this->_errorCode = odbc_error(); + } else + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : ''; + } + return $stmtid; + } + + /* + Insert a null into the blob field of the table first. + Then use UpdateBlob to store the blob. + + Usage: + + $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)'); + $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1'); + */ + function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') + { + return $this->Execute("UPDATE $table SET $column=? WHERE $where",array($val)) != false; + } + + // returns true or false + function _close() + { + $ret = @odbc_close($this->_connectionID); + $this->_connectionID = false; + return $ret; + } + + function _affectedrows() + { + return $this->_lastAffectedRows; + } + +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordSet_odbc extends ADORecordSet { + + var $bind = false; + var $databaseType = "odbc"; + var $dataProvider = "odbc"; + var $useFetchArray; + var $_has_stupid_odbc_fetch_api_change; + + function ADORecordSet_odbc($id,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + $this->fetchMode = $mode; + + $this->_queryID = $id; + + // the following is required for mysql odbc driver in 4.3.1 -- why? + $this->EOF = false; + $this->_currentRow = -1; + //$this->ADORecordSet($id); + } + + + // returns the field object + function &FetchField($fieldOffset = -1) + { + + $off=$fieldOffset+1; // offsets begin at 1 + + $o= new ADOFieldObject(); + $o->name = @odbc_field_name($this->_queryID,$off); + $o->type = @odbc_field_type($this->_queryID,$off); + $o->max_length = @odbc_field_len($this->_queryID,$off); + if (ADODB_ASSOC_CASE == 0) $o->name = strtolower($o->name); + else if (ADODB_ASSOC_CASE == 1) $o->name = strtoupper($o->name); + return $o; + } + + /* Use associative array to get fields array */ + function Fields($colname) + { + if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname]; + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + + function _initrs() + { + global $ADODB_COUNTRECS; + $this->_numOfRows = ($ADODB_COUNTRECS) ? @odbc_num_rows($this->_queryID) : -1; + $this->_numOfFields = @odbc_num_fields($this->_queryID); + // some silly drivers such as db2 as/400 and intersystems cache return _numOfRows = 0 + if ($this->_numOfRows == 0) $this->_numOfRows = -1; + //$this->useFetchArray = $this->connection->useFetchArray; + $this->_has_stupid_odbc_fetch_api_change = ADODB_PHPVER >= 0x4200; + } + + function _seek($row) + { + return false; + } + + // speed up SelectLimit() by switching to ADODB_FETCH_NUM as ADODB_FETCH_ASSOC is emulated + function &GetArrayLimit($nrows,$offset=-1) + { + if ($offset <= 0) { + $rs =& $this->GetArray($nrows); + return $rs; + } + $savem = $this->fetchMode; + $this->fetchMode = ADODB_FETCH_NUM; + $this->Move($offset); + $this->fetchMode = $savem; + + if ($this->fetchMode & ADODB_FETCH_ASSOC) { + $this->fields =& $this->GetRowAssoc(ADODB_ASSOC_CASE); + } + + $results = array(); + $cnt = 0; + while (!$this->EOF && $nrows != $cnt) { + $results[$cnt++] = $this->fields; + $this->MoveNext(); + } + + return $results; + } + + + function MoveNext() + { + if ($this->_numOfRows != 0 && !$this->EOF) { + $this->_currentRow++; + + if ($this->_has_stupid_odbc_fetch_api_change) + $rez = @odbc_fetch_into($this->_queryID,$this->fields); + else { + $row = 0; + $rez = @odbc_fetch_into($this->_queryID,$row,$this->fields); + } + if ($rez) { + if ($this->fetchMode & ADODB_FETCH_ASSOC) { + $this->fields =& $this->GetRowAssoc(ADODB_ASSOC_CASE); + } + return true; + } + } + $this->fields = false; + $this->EOF = true; + return false; + } + + function _fetch() + { + + if ($this->_has_stupid_odbc_fetch_api_change) + $rez = @odbc_fetch_into($this->_queryID,$this->fields); + else { + $row = 0; + $rez = @odbc_fetch_into($this->_queryID,$row,$this->fields); + } + if ($rez) { + if ($this->fetchMode & ADODB_FETCH_ASSOC) { + $this->fields =& $this->GetRowAssoc(ADODB_ASSOC_CASE); + } + return true; + } + $this->fields = false; + return false; + } + + function _close() + { + return @odbc_free_result($this->_queryID); + } + +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-odbc_db2.inc.php b/framework/DataAccess/adodb/drivers/adodb-odbc_db2.inc.php new file mode 100644 index 00000000..55f3a7b9 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-odbc_db2.inc.php @@ -0,0 +1,362 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + DB2 data driver. Requires ODBC. + +From phpdb list: + +Hi Andrew, + +thanks a lot for your help. Today we discovered what +our real problem was: + +After "playing" a little bit with the php-scripts that try +to connect to the IBM DB2, we set the optional parameter +Cursortype when calling odbc_pconnect(....). + +And the exciting thing: When we set the cursor type +to SQL_CUR_USE_ODBC Cursor Type, then +the whole query speed up from 1 till 10 seconds +to 0.2 till 0.3 seconds for 100 records. Amazing!!! + +Therfore, PHP is just almost fast as calling the DB2 +from Servlets using JDBC (don't take too much care +about the speed at whole: the database was on a +completely other location, so the whole connection +was made over a slow network connection). + +I hope this helps when other encounter the same +problem when trying to connect to DB2 from +PHP. + +Kind regards, +Christian Szardenings + +2 Oct 2001 +Mark Newnham has discovered that the SQL_CUR_USE_ODBC is not supported by +IBM's DB2 ODBC driver, so this must be a 3rd party ODBC driver. + +From the IBM CLI Reference: + +SQL_ATTR_ODBC_CURSORS (DB2 CLI v5) +This connection attribute is defined by ODBC, but is not supported by DB2 +CLI. Any attempt to set or get this attribute will result in an SQLSTATE of +HYC00 (Driver not capable). + +A 32-bit option specifying how the Driver Manager uses the ODBC cursor +library. + +So I guess this means the message [above] was related to using a 3rd party +odbc driver. + +Setting SQL_CUR_USE_ODBC +======================== +To set SQL_CUR_USE_ODBC for drivers that require it, do this: + +$db = NewADOConnection('db2'); +$db->curMode = SQL_CUR_USE_ODBC; +$db->Connect($dsn, $userid, $pwd); + + + +USING CLI INTERFACE +=================== + +I have had reports that the $host and $database params have to be reversed in +Connect() when using the CLI interface. From Halmai Csongor csongor.halmai#nexum.hu: + +> The symptom is that if I change the database engine from postgres or any other to DB2 then the following +> connection command becomes wrong despite being described this version to be correct in the docs. +> +> $connection_object->Connect( $DATABASE_HOST, $DATABASE_AUTH_USER_NAME, $DATABASE_AUTH_PASSWORD, $DATABASE_NAME ) +> +> In case of DB2 I had to swap the first and last arguments in order to connect properly. + + +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (!defined('_ADODB_ODBC_LAYER')) { + include(ADODB_DIR."/drivers/adodb-odbc.inc.php"); +} +if (!defined('ADODB_DB2')){ +define('ADODB_DB2',1); + +class ADODB_DB2 extends ADODB_odbc { + var $databaseType = "db2"; + var $concat_operator = '||'; + var $sysDate = 'CURRENT_DATE'; + var $sysTimeStamp = 'CURRENT TIMESTAMP'; + // The complete string representation of a timestamp has the form + // yyyy-mm-dd-hh.mm.ss.nnnnnn. + var $fmtTimeStamp = "'Y-m-d-H.i.s'"; + var $ansiOuter = true; + var $identitySQL = 'values IDENTITY_VAL_LOCAL()'; + var $_bindInputArray = true; + var $hasInsertID = true; + + function ADODB_DB2() + { + if (strncmp(PHP_OS,'WIN',3) === 0) $this->curmode = SQL_CUR_USE_ODBC; + $this->ADODB_odbc(); + } + + function IfNull( $field, $ifNull ) + { + return " COALESCE($field, $ifNull) "; // if DB2 UDB + } + + function ServerInfo() + { + //odbc_setoption($this->_connectionID,1,101 /*SQL_ATTR_ACCESS_MODE*/, 1 /*SQL_MODE_READ_ONLY*/); + $vers = $this->GetOne('select versionnumber from sysibm.sysversions'); + //odbc_setoption($this->_connectionID,1,101, 0 /*SQL_MODE_READ_WRITE*/); + return array('description'=>'DB2 ODBC driver', 'version'=>$vers); + } + + function _insertid() + { + return $this->GetOne($this->identitySQL); + } + + function RowLock($tables,$where,$flds='1 as ignore') + { + if ($this->_autocommit) $this->BeginTrans(); + return $this->GetOne("select $flds from $tables where $where for update"); + } + + function &MetaTables($ttype=false,$showSchema=false, $qtable="%", $qschema="%") + { + global $ADODB_FETCH_MODE; + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $qid = odbc_tables($this->_connectionID, "", $qschema, $qtable, ""); + + $rs = new ADORecordSet_odbc($qid); + + $ADODB_FETCH_MODE = $savem; + if (!$rs) { + $false = false; + return $false; + } + $rs->_has_stupid_odbc_fetch_api_change = $this->_has_stupid_odbc_fetch_api_change; + + $arr =& $rs->GetArray(); + //print_r($arr); + + $rs->Close(); + $arr2 = array(); + + if ($ttype) { + $isview = strncmp($ttype,'V',1) === 0; + } + for ($i=0; $i < sizeof($arr); $i++) { + + if (!$arr[$i][2]) continue; + if (strncmp($arr[$i][1],'SYS',3) === 0) continue; + + $type = $arr[$i][3]; + + if ($showSchema) $arr[$i][2] = $arr[$i][1].'.'.$arr[$i][2]; + + if ($ttype) { + if ($isview) { + if (strncmp($type,'V',1) === 0) $arr2[] = $arr[$i][2]; + } else if (strncmp($type,'T',1) === 0) $arr2[] = $arr[$i][2]; + } else if (strncmp($type,'S',1) !== 0) $arr2[] = $arr[$i][2]; + } + return $arr2; + } + + function &MetaIndexes ($table, $primary = FALSE, $owner=false) + { + // save old fetch mode + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== FALSE) { + $savem = $this->SetFetchMode(FALSE); + } + $false = false; + // get index details + $table = strtoupper($table); + $SQL="SELECT NAME, UNIQUERULE, COLNAMES FROM SYSIBM.SYSINDEXES WHERE TBNAME='$table'"; + if ($primary) + $SQL.= " AND UNIQUERULE='P'"; + $rs = $this->Execute($SQL); + if (!is_object($rs)) { + if (isset($savem)) + $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + return $false; + } + $indexes = array (); + // parse index data into array + while ($row = $rs->FetchRow()) { + $indexes[$row[0]] = array( + 'unique' => ($row[1] == 'U' || $row[1] == 'P'), + 'columns' => array() + ); + $cols = ltrim($row[2],'+'); + $indexes[$row[0]]['columns'] = explode('+', $cols); + } + if (isset($savem)) { + $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + } + return $indexes; + } + + // Format date column in sql string given an input format that understands Y M D + function SQLDate($fmt, $col=false) + { + // use right() and replace() ? + if (!$col) $col = $this->sysDate; + $s = ''; + + $len = strlen($fmt); + for ($i=0; $i < $len; $i++) { + if ($s) $s .= '||'; + $ch = $fmt[$i]; + switch($ch) { + case 'Y': + case 'y': + $s .= "char(year($col))"; + break; + case 'M': + $s .= "substr(monthname($col),1,3)"; + break; + case 'm': + $s .= "right(digits(month($col)),2)"; + break; + case 'D': + case 'd': + $s .= "right(digits(day($col)),2)"; + break; + case 'H': + case 'h': + if ($col != $this->sysDate) $s .= "right(digits(hour($col)),2)"; + else $s .= "''"; + break; + case 'i': + case 'I': + if ($col != $this->sysDate) + $s .= "right(digits(minute($col)),2)"; + else $s .= "''"; + break; + case 'S': + case 's': + if ($col != $this->sysDate) + $s .= "right(digits(second($col)),2)"; + else $s .= "''"; + break; + default: + if ($ch == '\\') { + $i++; + $ch = substr($fmt,$i,1); + } + $s .= $this->qstr($ch); + } + } + return $s; + } + + + function &SelectLimit($sql,$nrows=-1,$offset=-1,$inputArr=false) + { + $nrows = (integer) $nrows; + if ($offset <= 0) { + // could also use " OPTIMIZE FOR $nrows ROWS " + if ($nrows >= 0) $sql .= " FETCH FIRST $nrows ROWS ONLY "; + $rs =& $this->Execute($sql,$inputArr); + } else { + if ($offset > 0 && $nrows < 0); + else { + $nrows += $offset; + $sql .= " FETCH FIRST $nrows ROWS ONLY "; + } + $rs =& ADOConnection::SelectLimit($sql,-1,$offset,$inputArr); + } + + return $rs; + } + +}; + + +class ADORecordSet_db2 extends ADORecordSet_odbc { + + var $databaseType = "db2"; + + function ADORecordSet_db2($id,$mode=false) + { + $this->ADORecordSet_odbc($id,$mode); + } + + function MetaType($t,$len=-1,$fieldobj=false) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + + switch (strtoupper($t)) { + case 'VARCHAR': + case 'CHAR': + case 'CHARACTER': + case 'C': + if ($len <= $this->blobSize) return 'C'; + + case 'LONGCHAR': + case 'TEXT': + case 'CLOB': + case 'DBCLOB': // double-byte + case 'X': + return 'X'; + + case 'BLOB': + case 'GRAPHIC': + case 'VARGRAPHIC': + return 'B'; + + case 'DATE': + case 'D': + return 'D'; + + case 'TIME': + case 'TIMESTAMP': + case 'T': + return 'T'; + + //case 'BOOLEAN': + //case 'BIT': + // return 'L'; + + //case 'COUNTER': + // return 'R'; + + case 'INT': + case 'INTEGER': + case 'BIGINT': + case 'SMALLINT': + case 'I': + return 'I'; + + default: return 'N'; + } + } +} + +} //define +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-odbc_mssql.inc.php b/framework/DataAccess/adodb/drivers/adodb-odbc_mssql.inc.php new file mode 100644 index 00000000..6a0fac29 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-odbc_mssql.inc.php @@ -0,0 +1,254 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + MSSQL support via ODBC. Requires ODBC. Works on Windows and Unix. + For Unix configuration, see http://phpbuilder.com/columns/alberto20000919.php3 +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (!defined('_ADODB_ODBC_LAYER')) { + include(ADODB_DIR."/drivers/adodb-odbc.inc.php"); +} + + +class ADODB_odbc_mssql extends ADODB_odbc { + var $databaseType = 'odbc_mssql'; + var $fmtDate = "'Y-m-d'"; + var $fmtTimeStamp = "'Y-m-d H:i:s'"; + var $_bindInputArray = true; + var $metaTablesSQL="select name,case when type='U' then 'T' else 'V' end from sysobjects where (type='U' or type='V') and (name not in ('sysallocations','syscolumns','syscomments','sysdepends','sysfilegroups','sysfiles','sysfiles1','sysforeignkeys','sysfulltextcatalogs','sysindexes','sysindexkeys','sysmembers','sysobjects','syspermissions','sysprotects','sysreferences','systypes','sysusers','sysalternates','sysconstraints','syssegments','REFERENTIAL_CONSTRAINTS','CHECK_CONSTRAINTS','CONSTRAINT_TABLE_USAGE','CONSTRAINT_COLUMN_USAGE','VIEWS','VIEW_TABLE_USAGE','VIEW_COLUMN_USAGE','SCHEMATA','TABLES','TABLE_CONSTRAINTS','TABLE_PRIVILEGES','COLUMNS','COLUMN_DOMAIN_USAGE','COLUMN_PRIVILEGES','DOMAINS','DOMAIN_CONSTRAINTS','KEY_COLUMN_USAGE'))"; + var $metaColumnsSQL = "select c.name,t.name,c.length from syscolumns c join systypes t on t.xusertype=c.xusertype join sysobjects o on o.id=c.id where o.name='%s'"; + var $hasTop = 'top'; // support mssql/interbase SELECT TOP 10 * FROM TABLE + var $sysDate = 'GetDate()'; + var $sysTimeStamp = 'GetDate()'; + var $leftOuter = '*='; + var $rightOuter = '=*'; + var $substr = 'substring'; + var $length = 'len'; + var $ansiOuter = true; // for mssql7 or later + var $identitySQL = 'select @@IDENTITY'; // 'select SCOPE_IDENTITY'; # for mssql 2000 + var $hasInsertID = true; + var $connectStmt = 'SET CONCAT_NULL_YIELDS_NULL OFF'; # When SET CONCAT_NULL_YIELDS_NULL is ON, + # concatenating a null value with a string yields a NULL result + + function ADODB_odbc_mssql() + { + $this->ADODB_odbc(); + //$this->curmode = SQL_CUR_USE_ODBC; + } + + // crashes php... + function ServerInfo() + { + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $row = $this->GetRow("execute sp_server_info 2"); + $ADODB_FETCH_MODE = $save; + if (!is_array($row)) return false; + $arr['description'] = $row[2]; + $arr['version'] = ADOConnection::_findvers($arr['description']); + return $arr; + } + + function IfNull( $field, $ifNull ) + { + return " ISNULL($field, $ifNull) "; // if MS SQL Server + } + + function _insertid() + { + // SCOPE_IDENTITY() + // Returns the last IDENTITY value inserted into an IDENTITY column in + // the same scope. A scope is a module -- a stored procedure, trigger, + // function, or batch. Thus, two statements are in the same scope if + // they are in the same stored procedure, function, or batch. + return $this->GetOne($this->identitySQL); + } + + + function MetaForeignKeys($table, $owner=false, $upper=false) + { + global $ADODB_FETCH_MODE; + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $table = $this->qstr(strtoupper($table)); + + $sql = +"select object_name(constid) as constraint_name, + col_name(fkeyid, fkey) as column_name, + object_name(rkeyid) as referenced_table_name, + col_name(rkeyid, rkey) as referenced_column_name +from sysforeignkeys +where upper(object_name(fkeyid)) = $table +order by constraint_name, referenced_table_name, keyno"; + + $constraints =& $this->GetArray($sql); + + $ADODB_FETCH_MODE = $save; + + $arr = false; + foreach($constraints as $constr) { + //print_r($constr); + $arr[$constr[0]][$constr[2]][] = $constr[1].'='.$constr[3]; + } + if (!$arr) return false; + + $arr2 = false; + + foreach($arr as $k => $v) { + foreach($v as $a => $b) { + if ($upper) $a = strtoupper($a); + $arr2[$a] = $b; + } + } + return $arr2; + } + + function &MetaTables($ttype=false,$showSchema=false,$mask=false) + { + if ($mask) {$this->debug=1; + $save = $this->metaTablesSQL; + $mask = $this->qstr($mask); + $this->metaTablesSQL .= " AND name like $mask"; + } + $ret =& ADOConnection::MetaTables($ttype,$showSchema); + + if ($mask) { + $this->metaTablesSQL = $save; + } + return $ret; + } + + function &MetaColumns($table) + { + $arr = ADOConnection::MetaColumns($table); + return $arr; + } + + function _query($sql,$inputarr) + { + if (is_string($sql)) $sql = str_replace('||','+',$sql); + return ADODB_odbc::_query($sql,$inputarr); + } + + // "Stein-Aksel Basma" <basma@accelero.no> + // tested with MSSQL 2000 + function &MetaPrimaryKeys($table) + { + global $ADODB_FETCH_MODE; + + $schema = ''; + $this->_findschema($table,$schema); + //if (!$schema) $schema = $this->database; + if ($schema) $schema = "and k.table_catalog like '$schema%'"; + + $sql = "select distinct k.column_name,ordinal_position from information_schema.key_column_usage k, + information_schema.table_constraints tc + where tc.constraint_name = k.constraint_name and tc.constraint_type = + 'PRIMARY KEY' and k.table_name = '$table' $schema order by ordinal_position "; + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; + $a = $this->GetCol($sql); + $ADODB_FETCH_MODE = $savem; + + if ($a && sizeof($a)>0) return $a; + $false = false; + return $false; + } + + function &SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) + { + if ($nrows > 0 && $offset <= 0) { + $sql = preg_replace( + '/(^\s*select\s+(distinctrow|distinct)?)/i','\\1 '.$this->hasTop." $nrows ",$sql); + $rs =& $this->Execute($sql,$inputarr); + } else + $rs =& ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); + + return $rs; + } + + // Format date column in sql string given an input format that understands Y M D + function SQLDate($fmt, $col=false) + { + if (!$col) $col = $this->sysTimeStamp; + $s = ''; + + $len = strlen($fmt); + for ($i=0; $i < $len; $i++) { + if ($s) $s .= '+'; + $ch = $fmt[$i]; + switch($ch) { + case 'Y': + case 'y': + $s .= "datename(yyyy,$col)"; + break; + case 'M': + $s .= "convert(char(3),$col,0)"; + break; + case 'm': + $s .= "replace(str(month($col),2),' ','0')"; + break; + case 'Q': + case 'q': + $s .= "datename(quarter,$col)"; + break; + case 'D': + case 'd': + $s .= "replace(str(day($col),2),' ','0')"; + break; + case 'h': + $s .= "substring(convert(char(14),$col,0),13,2)"; + break; + + case 'H': + $s .= "replace(str(datepart(hh,$col),2),' ','0')"; + break; + + case 'i': + $s .= "replace(str(datepart(mi,$col),2),' ','0')"; + break; + case 's': + $s .= "replace(str(datepart(ss,$col),2),' ','0')"; + break; + case 'a': + case 'A': + $s .= "substring(convert(char(19),$col,0),18,2)"; + break; + + default: + if ($ch == '\\') { + $i++; + $ch = substr($fmt,$i,1); + } + $s .= $this->qstr($ch); + break; + } + } + return $s; + } + +} + +class ADORecordSet_odbc_mssql extends ADORecordSet_odbc { + + var $databaseType = 'odbc_mssql'; + + function ADORecordSet_odbc_mssql($id,$mode=false) + { + return $this->ADORecordSet_odbc($id,$mode); + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-odbc_oracle.inc.php b/framework/DataAccess/adodb/drivers/adodb-odbc_oracle.inc.php new file mode 100644 index 00000000..2ab27c3e --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-odbc_oracle.inc.php @@ -0,0 +1,115 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Oracle support via ODBC. Requires ODBC. Works on Windows. +*/ +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (!defined('_ADODB_ODBC_LAYER')) { + include(ADODB_DIR."/drivers/adodb-odbc.inc.php"); +} + + +class ADODB_odbc_oracle extends ADODB_odbc { + var $databaseType = 'odbc_oracle'; + var $replaceQuote = "''"; // string to use to replace quotes + var $concat_operator='||'; + var $fmtDate = "'Y-m-d 00:00:00'"; + var $fmtTimeStamp = "'Y-m-d h:i:sA'"; + var $metaTablesSQL = 'select table_name from cat'; + var $metaColumnsSQL = "select cname,coltype,width from col where tname='%s' order by colno"; + var $sysDate = "TRUNC(SYSDATE)"; + var $sysTimeStamp = 'SYSDATE'; + + //var $_bindInputArray = false; + + function ADODB_odbc_oracle() + { + $this->ADODB_odbc(); + } + + function &MetaTables() + { + $false = false; + $rs = $this->Execute($this->metaTablesSQL); + if ($rs === false) return $false; + $arr = $rs->GetArray(); + $arr2 = array(); + for ($i=0; $i < sizeof($arr); $i++) { + $arr2[] = $arr[$i][0]; + } + $rs->Close(); + return $arr2; + } + + function &MetaColumns($table) + { + global $ADODB_FETCH_MODE; + + $rs = $this->Execute(sprintf($this->metaColumnsSQL,strtoupper($table))); + if ($rs === false) { + $false = false; + return $false; + } + $retarr = array(); + while (!$rs->EOF) { //print_r($rs->fields); + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[0]; + $fld->type = $rs->fields[1]; + $fld->max_length = $rs->fields[2]; + + + if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld; + else $retarr[strtoupper($fld->name)] = $fld; + + $rs->MoveNext(); + } + $rs->Close(); + return $retarr; + } + + // returns true or false + function _connect($argDSN, $argUsername, $argPassword, $argDatabasename) + { + global $php_errormsg; + + $php_errormsg = ''; + $this->_connectionID = odbc_connect($argDSN,$argUsername,$argPassword,SQL_CUR_USE_ODBC ); + $this->_errorMsg = $php_errormsg; + + $this->Execute("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'"); + //if ($this->_connectionID) odbc_autocommit($this->_connectionID,true); + return $this->_connectionID != false; + } + // returns true or false + function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename) + { + global $php_errormsg; + $php_errormsg = ''; + $this->_connectionID = odbc_pconnect($argDSN,$argUsername,$argPassword,SQL_CUR_USE_ODBC ); + $this->_errorMsg = $php_errormsg; + + $this->Execute("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'"); + //if ($this->_connectionID) odbc_autocommit($this->_connectionID,true); + return $this->_connectionID != false; + } +} + +class ADORecordSet_odbc_oracle extends ADORecordSet_odbc { + + var $databaseType = 'odbc_oracle'; + + function ADORecordSet_odbc_oracle($id,$mode=false) + { + return $this->ADORecordSet_odbc($id,$mode); + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-odbtp.inc.php b/framework/DataAccess/adodb/drivers/adodb-odbtp.inc.php new file mode 100644 index 00000000..caba96c2 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-odbtp.inc.php @@ -0,0 +1,732 @@ +<?php +/* + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. See License.txt. + Set tabs to 4 for best viewing. + Latest version is available at http://adodb.sourceforge.net +*/ +// Code contributed by "stefan bogdan" <sbogdan#rsb.ro> + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +define("_ADODB_ODBTP_LAYER", 2 ); + +class ADODB_odbtp extends ADOConnection{ + var $databaseType = "odbtp"; + var $dataProvider = "odbtp"; + var $fmtDate = "'Y-m-d'"; + var $fmtTimeStamp = "'Y-m-d, h:i:sA'"; + var $replaceQuote = "''"; // string to use to replace quotes + var $odbc_driver = 0; + var $hasAffectedRows = true; + var $hasInsertID = false; + var $hasGenID = true; + var $hasMoveFirst = true; + + var $_genSeqSQL = "create table %s (seq_name char(30) not null unique , seq_value integer not null)"; + var $_dropSeqSQL = "delete from adodb_seq where seq_name = '%s'"; + var $_bindInputArray = false; + var $_useUnicodeSQL = false; + var $_canPrepareSP = false; + var $_dontPoolDBC = true; + + function ADODB_odbtp() + { + } + + function ServerInfo() + { + return array('description' => @odbtp_get_attr( ODB_ATTR_DBMSNAME, $this->_connectionID), + 'version' => @odbtp_get_attr( ODB_ATTR_DBMSVER, $this->_connectionID)); + } + + function ErrorMsg() + { + if (empty($this->_connectionID)) return @odbtp_last_error(); + return @odbtp_last_error($this->_connectionID); + } + + function ErrorNo() + { + if (empty($this->_connectionID)) return @odbtp_last_error_state(); + return @odbtp_last_error_state($this->_connectionID); + } + + function _insertid() + { + // SCOPE_IDENTITY() + // Returns the last IDENTITY value inserted into an IDENTITY column in + // the same scope. A scope is a module -- a stored procedure, trigger, + // function, or batch. Thus, two statements are in the same scope if + // they are in the same stored procedure, function, or batch. + return $this->GetOne($this->identitySQL); + } + + function _affectedrows() + { + if ($this->_queryID) { + return @odbtp_affected_rows ($this->_queryID); + } else + return 0; + } + + function CreateSequence($seqname='adodbseq',$start=1) + { + //verify existence + $num = $this->GetOne("select seq_value from adodb_seq"); + $seqtab='adodb_seq'; + if( $this->odbc_driver == ODB_DRIVER_FOXPRO ) { + $path = @odbtp_get_attr( ODB_ATTR_DATABASENAME, $this->_connectionID ); + //if using vfp dbc file + if( !strcasecmp(strrchr($path, '.'), '.dbc') ) + $path = substr($path,0,strrpos($path,'\/')); + $seqtab = $path . '/' . $seqtab; + } + if($num == false) { + if (empty($this->_genSeqSQL)) return false; + $ok = $this->Execute(sprintf($this->_genSeqSQL ,$seqtab)); + } + $num = $this->GetOne("select seq_value from adodb_seq where seq_name='$seqname'"); + if ($num) { + return false; + } + $start -= 1; + return $this->Execute("insert into adodb_seq values('$seqname',$start)"); + } + + function DropSequence($seqname) + { + if (empty($this->_dropSeqSQL)) return false; + return $this->Execute(sprintf($this->_dropSeqSQL,$seqname)); + } + + function GenID($seq='adodbseq',$start=1) + { + $seqtab='adodb_seq'; + if( $this->odbc_driver == ODB_DRIVER_FOXPRO) { + $path = @odbtp_get_attr( ODB_ATTR_DATABASENAME, $this->_connectionID ); + //if using vfp dbc file + if( !strcasecmp(strrchr($path, '.'), '.dbc') ) + $path = substr($path,0,strrpos($path,'\/')); + $seqtab = $path . '/' . $seqtab; + } + $MAXLOOPS = 100; + while (--$MAXLOOPS>=0) { + $num = $this->GetOne("select seq_value from adodb_seq where seq_name='$seq'"); + if ($num === false) { + //verify if abodb_seq table exist + $ok = $this->GetOne("select seq_value from adodb_seq "); + if(!$ok) { + //creating the sequence table adodb_seq + $this->Execute(sprintf($this->_genSeqSQL ,$seqtab)); + } + $start -= 1; + $num = '0'; + $ok = $this->Execute("insert into adodb_seq values('$seq',$start)"); + if (!$ok) return false; + } + $ok = $this->Execute("update adodb_seq set seq_value=seq_value+1 where seq_name='$seq'"); + if($ok) { + $num += 1; + $this->genID = $num; + return $num; + } + } + if ($fn = $this->raiseErrorFn) { + $fn($this->databaseType,'GENID',-32000,"Unable to generate unique id after $MAXLOOPS attempts",$seq,$num); + } + return false; + } + + //example for $UserOrDSN + //for visual fox : DRIVER={Microsoft Visual FoxPro Driver};SOURCETYPE=DBF;SOURCEDB=c:\YourDbfFileDir;EXCLUSIVE=NO; + //for visual fox dbc: DRIVER={Microsoft Visual FoxPro Driver};SOURCETYPE=DBC;SOURCEDB=c:\YourDbcFileDir\mydb.dbc;EXCLUSIVE=NO; + //for access : DRIVER={Microsoft Access Driver (*.mdb)};DBQ=c:\path_to_access_db\base_test.mdb;UID=root;PWD=; + //for mssql : DRIVER={SQL Server};SERVER=myserver;UID=myuid;PWD=mypwd;DATABASE=OdbtpTest; + //if uid & pwd can be separate + function _connect($HostOrInterface, $UserOrDSN='', $argPassword='', $argDatabase='') + { + $this->_connectionID = @odbtp_connect($HostOrInterface,$UserOrDSN,$argPassword,$argDatabase); + if ($this->_connectionID === false) { + $this->_errorMsg = $this->ErrorMsg() ; + return false; + } + if ($this->_dontPoolDBC) { + if (function_exists('odbtp_dont_pool_dbc')) + @odbtp_dont_pool_dbc($this->_connectionID); + } + else { + $this->_dontPoolDBC = true; + } + $this->odbc_driver = @odbtp_get_attr(ODB_ATTR_DRIVER, $this->_connectionID); + $dbms = strtolower(@odbtp_get_attr(ODB_ATTR_DBMSNAME, $this->_connectionID)); + $this->odbc_name = $dbms; + + // Account for inconsistent DBMS names + if( $this->odbc_driver == ODB_DRIVER_ORACLE ) + $dbms = 'oracle'; + else if( $this->odbc_driver == ODB_DRIVER_SYBASE ) + $dbms = 'sybase'; + + // Set DBMS specific attributes + switch( $dbms ) { + case 'microsoft sql server': + $this->databaseType = 'odbtp_mssql'; + $this->fmtDate = "'Y-m-d'"; + $this->fmtTimeStamp = "'Y-m-d h:i:sA'"; + $this->sysDate = 'convert(datetime,convert(char,GetDate(),102),102)'; + $this->sysTimeStamp = 'GetDate()'; + $this->ansiOuter = true; + $this->leftOuter = '*='; + $this->rightOuter = '=*'; + $this->hasTop = 'top'; + $this->hasInsertID = true; + $this->hasTransactions = true; + $this->_bindInputArray = true; + $this->_canSelectDb = true; + $this->substr = "substring"; + $this->length = 'len'; + $this->identitySQL = 'select @@IDENTITY'; + $this->metaDatabasesSQL = "select name from master..sysdatabases where name <> 'master'"; + $this->_canPrepareSP = true; + break; + case 'access': + $this->databaseType = 'odbtp_access'; + $this->fmtDate = "#Y-m-d#"; + $this->fmtTimeStamp = "#Y-m-d h:i:sA#"; + $this->sysDate = "FORMAT(NOW,'yyyy-mm-dd')"; + $this->sysTimeStamp = 'NOW'; + $this->hasTop = 'top'; + $this->hasTransactions = false; + $this->_canPrepareSP = true; // For MS Access only. + break; + case 'visual foxpro': + $this->databaseType = 'odbtp_vfp'; + $this->fmtDate = "{^Y-m-d}"; + $this->fmtTimeStamp = "{^Y-m-d, h:i:sA}"; + $this->sysDate = 'date()'; + $this->sysTimeStamp = 'datetime()'; + $this->ansiOuter = true; + $this->hasTop = 'top'; + $this->hasTransactions = false; + $this->replaceQuote = "'+chr(39)+'"; + $this->true = '.T.'; + $this->false = '.F.'; + break; + case 'oracle': + $this->databaseType = 'odbtp_oci8'; + $this->fmtDate = "'Y-m-d 00:00:00'"; + $this->fmtTimeStamp = "'Y-m-d h:i:sA'"; + $this->sysDate = 'TRUNC(SYSDATE)'; + $this->sysTimeStamp = 'SYSDATE'; + $this->hasTransactions = true; + $this->_bindInputArray = true; + $this->concat_operator = '||'; + break; + case 'sybase': + $this->databaseType = 'odbtp_sybase'; + $this->fmtDate = "'Y-m-d'"; + $this->fmtTimeStamp = "'Y-m-d H:i:s'"; + $this->sysDate = 'GetDate()'; + $this->sysTimeStamp = 'GetDate()'; + $this->leftOuter = '*='; + $this->rightOuter = '=*'; + $this->hasInsertID = true; + $this->hasTransactions = true; + $this->identitySQL = 'select @@IDENTITY'; + break; + default: + $this->databaseType = 'odbtp'; + if( @odbtp_get_attr(ODB_ATTR_TXNCAPABLE, $this->_connectionID) ) + $this->hasTransactions = true; + else + $this->hasTransactions = false; + } + @odbtp_set_attr(ODB_ATTR_FULLCOLINFO, TRUE, $this->_connectionID ); + + if ($this->_useUnicodeSQL ) + @odbtp_set_attr(ODB_ATTR_UNICODESQL, TRUE, $this->_connectionID); + + return true; + } + + function _pconnect($HostOrInterface, $UserOrDSN='', $argPassword='', $argDatabase='') + { + $this->_dontPoolDBC = false; + return $this->_connect($HostOrInterface, $UserOrDSN, $argPassword, $argDatabase); + } + + function SelectDB($dbName) + { + if (!@odbtp_select_db($dbName, $this->_connectionID)) { + return false; + } + $this->database = $dbName; + $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions + return true; + } + + function &MetaTables($ttype='',$showSchema=false,$mask=false) + { + global $ADODB_FETCH_MODE; + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== false) $savefm = $this->SetFetchMode(false); + + $arr =& $this->GetArray("||SQLTables||||$ttype"); + + if (isset($savefm)) $this->SetFetchMode($savefm); + $ADODB_FETCH_MODE = $savem; + + $arr2 = array(); + for ($i=0; $i < sizeof($arr); $i++) { + if ($arr[$i][3] == 'SYSTEM TABLE' ) continue; + if ($arr[$i][2]) + $arr2[] = $showSchema ? $arr[$i][1].'.'.$arr[$i][2] : $arr[$i][2]; + } + return $arr2; + } + + function &MetaColumns($table,$upper=true) + { + global $ADODB_FETCH_MODE; + + $schema = false; + $this->_findschema($table,$schema); + if ($upper) $table = strtoupper($table); + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== false) $savefm = $this->SetFetchMode(false); + + $rs = $this->Execute( "||SQLColumns||$schema|$table" ); + + if (isset($savefm)) $this->SetFetchMode($savefm); + $ADODB_FETCH_MODE = $savem; + + if (!$rs || $rs->EOF) { + $false = false; + return $false; + } + $retarr = array(); + while (!$rs->EOF) { + //print_r($rs->fields); + if (strtoupper($rs->fields[2]) == $table) { + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[3]; + $fld->type = $rs->fields[5]; + $fld->max_length = $rs->fields[6]; + $fld->not_null = !empty($rs->fields[9]); + $fld->scale = $rs->fields[7]; + if (!is_null($rs->fields[12])) { + $fld->has_default = true; + $fld->default_value = $rs->fields[12]; + } + $retarr[strtoupper($fld->name)] = $fld; + } else if (!empty($retarr)) + break; + $rs->MoveNext(); + } + $rs->Close(); + + return $retarr; + } + + function &MetaPrimaryKeys($table, $owner='') + { + global $ADODB_FETCH_MODE; + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $arr =& $this->GetArray("||SQLPrimaryKeys||$owner|$table"); + $ADODB_FETCH_MODE = $savem; + + //print_r($arr); + $arr2 = array(); + for ($i=0; $i < sizeof($arr); $i++) { + if ($arr[$i][3]) $arr2[] = $arr[$i][3]; + } + return $arr2; + } + + function &MetaForeignKeys($table, $owner='', $upper=false) + { + global $ADODB_FETCH_MODE; + + $savem = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + $constraints =& $this->GetArray("||SQLForeignKeys|||||$owner|$table"); + $ADODB_FETCH_MODE = $savem; + + $arr = false; + foreach($constraints as $constr) { + //print_r($constr); + $arr[$constr[11]][$constr[2]][] = $constr[7].'='.$constr[3]; + } + if (!$arr) { + $false = false; + return $false; + } + + $arr2 = array(); + + foreach($arr as $k => $v) { + foreach($v as $a => $b) { + if ($upper) $a = strtoupper($a); + $arr2[$a] = $b; + } + } + return $arr2; + } + + function BeginTrans() + { + if (!$this->hasTransactions) return false; + if ($this->transOff) return true; + $this->transCnt += 1; + $this->autoCommit = false; + if (defined('ODB_TXN_DEFAULT')) + $txn = ODB_TXN_DEFAULT; + else + $txn = ODB_TXN_READUNCOMMITTED; + $rs = @odbtp_set_attr(ODB_ATTR_TRANSACTIONS,$txn,$this->_connectionID); + if(!$rs) return false; + return true; + } + + function CommitTrans($ok=true) + { + if ($this->transOff) return true; + if (!$ok) return $this->RollbackTrans(); + if ($this->transCnt) $this->transCnt -= 1; + $this->autoCommit = true; + if( ($ret = @odbtp_commit($this->_connectionID)) ) + $ret = @odbtp_set_attr(ODB_ATTR_TRANSACTIONS, ODB_TXN_NONE, $this->_connectionID);//set transaction off + return $ret; + } + + function RollbackTrans() + { + if ($this->transOff) return true; + if ($this->transCnt) $this->transCnt -= 1; + $this->autoCommit = true; + if( ($ret = @odbtp_rollback($this->_connectionID)) ) + $ret = @odbtp_set_attr(ODB_ATTR_TRANSACTIONS, ODB_TXN_NONE, $this->_connectionID);//set transaction off + return $ret; + } + + function &SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) + { + // TOP requires ORDER BY for Visual FoxPro + if( $this->odbc_driver == ODB_DRIVER_FOXPRO ) { + if (!preg_match('/ORDER[ \t\r\n]+BY/is',$sql)) $sql .= ' ORDER BY 1'; + } + $ret =& ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); + return $ret; + } + + function Prepare($sql) + { + if (! $this->_bindInputArray) return $sql; // no binding + $stmt = @odbtp_prepare($sql,$this->_connectionID); + if (!$stmt) { + // print "Prepare Error for ($sql) ".$this->ErrorMsg()."<br>"; + return $sql; + } + return array($sql,$stmt,false); + } + + function PrepareSP($sql) + { + if (!$this->_canPrepareSP) return $sql; // Can't prepare procedures + + $stmt = @odbtp_prepare_proc($sql,$this->_connectionID); + if (!$stmt) return false; + return array($sql,$stmt); + } + + /* + Usage: + $stmt = $db->PrepareSP('SP_RUNSOMETHING'); -- takes 2 params, @myid and @group + + # note that the parameter does not have @ in front! + $db->Parameter($stmt,$id,'myid'); + $db->Parameter($stmt,$group,'group',false,64); + $db->Parameter($stmt,$group,'photo',false,100000,ODB_BINARY); + $db->Execute($stmt); + + @param $stmt Statement returned by Prepare() or PrepareSP(). + @param $var PHP variable to bind to. Can set to null (for isNull support). + @param $name Name of stored procedure variable name to bind to. + @param [$isOutput] Indicates direction of parameter 0/false=IN 1=OUT 2= IN/OUT. This is ignored in odbtp. + @param [$maxLen] Holds an maximum length of the variable. + @param [$type] The data type of $var. Legal values depend on driver. + + See odbtp_attach_param documentation at http://odbtp.sourceforge.net. + */ + function Parameter(&$stmt, &$var, $name, $isOutput=false, $maxLen=0, $type=0) + { + if ( $this->odbc_driver == ODB_DRIVER_JET ) { + $name = '['.$name.']'; + if( !$type && $this->_useUnicodeSQL + && @odbtp_param_bindtype($stmt[1], $name) == ODB_CHAR ) + { + $type = ODB_WCHAR; + } + } + else { + $name = '@'.$name; + } + return @odbtp_attach_param($stmt[1], $name, $var, $type, $maxLen); + } + + /* + Insert a null into the blob field of the table first. + Then use UpdateBlob to store the blob. + + Usage: + + $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)'); + $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1'); + */ + + function UpdateBlob($table,$column,$val,$where,$blobtype='image') + { + $sql = "UPDATE $table SET $column = ? WHERE $where"; + if( !($stmt = @odbtp_prepare($sql, $this->_connectionID)) ) + return false; + if( !@odbtp_input( $stmt, 1, ODB_BINARY, 1000000, $blobtype ) ) + return false; + if( !@odbtp_set( $stmt, 1, $val ) ) + return false; + return @odbtp_execute( $stmt ) != false; + } + + function IfNull( $field, $ifNull ) + { + switch( $this->odbc_driver ) { + case ODB_DRIVER_MSSQL: + return " ISNULL($field, $ifNull) "; + case ODB_DRIVER_JET: + return " IIF(IsNull($field), $ifNull, $field) "; + } + return " CASE WHEN $field is null THEN $ifNull ELSE $field END "; + } + + function _query($sql,$inputarr=false) + { + global $php_errormsg; + + if ($inputarr) { + if (is_array($sql)) { + $stmtid = $sql[1]; + } else { + $stmtid = @odbtp_prepare($sql,$this->_connectionID); + if ($stmtid == false) { + $this->_errorMsg = $php_errormsg; + return false; + } + } + $num_params = @odbtp_num_params( $stmtid ); + for( $param = 1; $param <= $num_params; $param++ ) { + @odbtp_input( $stmtid, $param ); + @odbtp_set( $stmtid, $param, $inputarr[$param-1] ); + } + if (!@odbtp_execute($stmtid) ) { + return false; + } + } else if (is_array($sql)) { + $stmtid = $sql[1]; + if (!@odbtp_execute($stmtid)) { + return false; + } + } else { + $stmtid = @odbtp_query($sql,$this->_connectionID); + } + $this->_lastAffectedRows = 0; + if ($stmtid) { + $this->_lastAffectedRows = @odbtp_affected_rows($stmtid); + } + return $stmtid; + } + + function _close() + { + $ret = @odbtp_close($this->_connectionID); + $this->_connectionID = false; + return $ret; + } +} + +class ADORecordSet_odbtp extends ADORecordSet { + + var $databaseType = 'odbtp'; + var $canSeek = true; + + function ADORecordSet_odbtp($queryID,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + $this->fetchMode = $mode; + $this->ADORecordSet($queryID); + } + + function _initrs() + { + $this->_numOfFields = @odbtp_num_fields($this->_queryID); + if (!($this->_numOfRows = @odbtp_num_rows($this->_queryID))) + $this->_numOfRows = -1; + + if (!$this->connection->_useUnicodeSQL) return; + + if ($this->connection->odbc_driver == ODB_DRIVER_JET) { + if (!@odbtp_get_attr(ODB_ATTR_MAPCHARTOWCHAR, + $this->connection->_connectionID)) + { + for ($f = 0; $f < $this->_numOfFields; $f++) { + if (@odbtp_field_bindtype($this->_queryID, $f) == ODB_CHAR) + @odbtp_bind_field($this->_queryID, $f, ODB_WCHAR); + } + } + } + } + + function &FetchField($fieldOffset = 0) + { + $off=$fieldOffset; // offsets begin at 0 + $o= new ADOFieldObject(); + $o->name = @odbtp_field_name($this->_queryID,$off); + $o->type = @odbtp_field_type($this->_queryID,$off); + $o->max_length = @odbtp_field_length($this->_queryID,$off); + if (ADODB_ASSOC_CASE == 0) $o->name = strtolower($o->name); + else if (ADODB_ASSOC_CASE == 1) $o->name = strtoupper($o->name); + return $o; + } + + function _seek($row) + { + return @odbtp_data_seek($this->_queryID, $row); + } + + function fields($colname) + { + if ($this->fetchMode & ADODB_FETCH_ASSOC) return $this->fields[$colname]; + + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $name = @odbtp_field_name( $this->_queryID, $i ); + $this->bind[strtoupper($name)] = $i; + } + } + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + function _fetch_odbtp($type=0) + { + switch ($this->fetchMode) { + case ADODB_FETCH_NUM: + $this->fields = @odbtp_fetch_row($this->_queryID, $type); + break; + case ADODB_FETCH_ASSOC: + $this->fields = @odbtp_fetch_assoc($this->_queryID, $type); + break; + default: + $this->fields = @odbtp_fetch_array($this->_queryID, $type); + } + return is_array($this->fields); + } + + function _fetch() + { + return $this->_fetch_odbtp(); + } + + function MoveFirst() + { + if (!$this->_fetch_odbtp(ODB_FETCH_FIRST)) return false; + $this->EOF = false; + $this->_currentRow = 0; + return true; + } + + function MoveLast() + { + if (!$this->_fetch_odbtp(ODB_FETCH_LAST)) return false; + $this->EOF = false; + $this->_currentRow = $this->_numOfRows - 1; + return true; + } + + function NextRecordSet() + { + if (!@odbtp_next_result($this->_queryID)) return false; + $this->_inited = false; + $this->bind = false; + $this->_currentRow = -1; + $this->Init(); + return true; + } + + function _close() + { + return @odbtp_free_query($this->_queryID); + } +} + +class ADORecordSet_odbtp_mssql extends ADORecordSet_odbtp { + + var $databaseType = 'odbtp_mssql'; + + function ADORecordSet_odbtp_mssql($id,$mode=false) + { + return $this->ADORecordSet_odbtp($id,$mode); + } +} + +class ADORecordSet_odbtp_access extends ADORecordSet_odbtp { + + var $databaseType = 'odbtp_access'; + + function ADORecordSet_odbtp_access($id,$mode=false) + { + return $this->ADORecordSet_odbtp($id,$mode); + } +} + +class ADORecordSet_odbtp_vfp extends ADORecordSet_odbtp { + + var $databaseType = 'odbtp_vfp'; + + function ADORecordSet_odbtp_vfp($id,$mode=false) + { + return $this->ADORecordSet_odbtp($id,$mode); + } +} + +class ADORecordSet_odbtp_oci8 extends ADORecordSet_odbtp { + + var $databaseType = 'odbtp_oci8'; + + function ADORecordSet_odbtp_oci8($id,$mode=false) + { + return $this->ADORecordSet_odbtp($id,$mode); + } +} + +class ADORecordSet_odbtp_sybase extends ADORecordSet_odbtp { + + var $databaseType = 'odbtp_sybase'; + + function ADORecordSet_odbtp_sybase($id,$mode=false) + { + return $this->ADORecordSet_odbtp($id,$mode); + } +} +?> diff --git a/framework/DataAccess/adodb/drivers/adodb-odbtp_unicode.inc.php b/framework/DataAccess/adodb/drivers/adodb-odbtp_unicode.inc.php new file mode 100644 index 00000000..1219de6d --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-odbtp_unicode.inc.php @@ -0,0 +1,39 @@ +<?php +/* + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. See License.txt. + Set tabs to 4 for best viewing. + Latest version is available at http://adodb.sourceforge.net +*/ + +// Code contributed by "Robert Twitty" <rtwitty#neutron.ushmm.org> + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +/* + Because the ODBTP server sends and reads UNICODE text data using UTF-8 + encoding, the following HTML meta tag must be included within the HTML + head section of every HTML form and script page: + + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + + Also, all SQL query strings must be submitted as UTF-8 encoded text. +*/ + +if (!defined('_ADODB_ODBTP_LAYER')) { + include(ADODB_DIR."/drivers/adodb-odbtp.inc.php"); +} + +class ADODB_odbtp_unicode extends ADODB_odbtp { + var $databaseType = 'odbtp'; + var $_useUnicodeSQL = true; + + function ADODB_odbtp_unicode() + { + $this->ADODB_odbtp(); + } +} +?> diff --git a/framework/DataAccess/adodb/drivers/adodb-oracle.inc.php b/framework/DataAccess/adodb/drivers/adodb-oracle.inc.php new file mode 100644 index 00000000..99e76482 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-oracle.inc.php @@ -0,0 +1,320 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + + Latest version is available at http://adodb.sourceforge.net + + Oracle data driver. Requires Oracle client. Works on Windows and Unix and Oracle 7. + + If you are using Oracle 8 or later, use the oci8 driver which is much better and more reliable. +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +class ADODB_oracle extends ADOConnection { + var $databaseType = "oracle"; + var $replaceQuote = "''"; // string to use to replace quotes + var $concat_operator='||'; + var $_curs; + var $_initdate = true; // init date to YYYY-MM-DD + var $metaTablesSQL = 'select table_name from cat'; + var $metaColumnsSQL = "select cname,coltype,width from col where tname='%s' order by colno"; + var $sysDate = "TO_DATE(TO_CHAR(SYSDATE,'YYYY-MM-DD'),'YYYY-MM-DD')"; + var $sysTimeStamp = 'SYSDATE'; + var $connectSID = true; + + function ADODB_oracle() + { + } + + // format and return date string in database date format + function DBDate($d) + { + if (is_string($d)) $d = ADORecordSet::UnixDate($d); + return 'TO_DATE('.adodb_date($this->fmtDate,$d).",'YYYY-MM-DD')"; + } + + // format and return date string in database timestamp format + function DBTimeStamp($ts) + { + + if (is_string($ts)) $d = ADORecordSet::UnixTimeStamp($ts); + return 'TO_DATE('.adodb_date($this->fmtTimeStamp,$ts).",'RRRR-MM-DD, HH:MI:SS AM')"; + } + + + function BeginTrans() + { + $this->autoCommit = false; + ora_commitoff($this->_connectionID); + return true; + } + + + function CommitTrans($ok=true) + { + if (!$ok) return $this->RollbackTrans(); + $ret = ora_commit($this->_connectionID); + ora_commiton($this->_connectionID); + return $ret; + } + + + function RollbackTrans() + { + $ret = ora_rollback($this->_connectionID); + ora_commiton($this->_connectionID); + return $ret; + } + + + /* there seems to be a bug in the oracle extension -- always returns ORA-00000 - no error */ + function ErrorMsg() + { + if ($this->_errorMsg !== false) return $this->_errorMsg; + + if (is_resource($this->_curs)) $this->_errorMsg = @ora_error($this->_curs); + if (empty($this->_errorMsg)) $this->_errorMsg = @ora_error($this->_connectionID); + return $this->_errorMsg; + } + + + function ErrorNo() + { + if ($this->_errorCode !== false) return $this->_errorCode; + + if (is_resource($this->_curs)) $this->_errorCode = @ora_errorcode($this->_curs); + if (empty($this->_errorCode)) $this->_errorCode = @ora_errorcode($this->_connectionID); + return $this->_errorCode; + } + + + + // returns true or false + function _connect($argHostname, $argUsername, $argPassword, $argDatabasename, $mode=0) + { + if (!function_exists('ora_plogon')) return null; + + // <G. Giunta 2003/03/03/> Reset error messages before connecting + $this->_errorMsg = false; + $this->_errorCode = false; + + // G. Giunta 2003/08/13 - This looks danegrously suspicious: why should we want to set + // the oracle home to the host name of remote DB? +// if ($argHostname) putenv("ORACLE_HOME=$argHostname"); + + if($argHostname) { // code copied from version submitted for oci8 by Jorma Tuomainen <jorma.tuomainen@ppoy.fi> + if (empty($argDatabasename)) $argDatabasename = $argHostname; + else { + if(strpos($argHostname,":")) { + $argHostinfo=explode(":",$argHostname); + $argHostname=$argHostinfo[0]; + $argHostport=$argHostinfo[1]; + } else { + $argHostport="1521"; + } + + + if ($this->connectSID) { + $argDatabasename="(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=".$argHostname + .")(PORT=$argHostport))(CONNECT_DATA=(SID=$argDatabasename)))"; + } else + $argDatabasename="(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=".$argHostname + .")(PORT=$argHostport))(CONNECT_DATA=(SERVICE_NAME=$argDatabasename)))"; + } + + } + + if ($argDatabasename) $argUsername .= "@$argDatabasename"; + + //if ($argHostname) print "<p>Connect: 1st argument should be left blank for $this->databaseType</p>"; + if ($mode = 1) + $this->_connectionID = ora_plogon($argUsername,$argPassword); + else + $this->_connectionID = ora_logon($argUsername,$argPassword); + if ($this->_connectionID === false) return false; + if ($this->autoCommit) ora_commiton($this->_connectionID); + if ($this->_initdate) { + $rs = $this->_query("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD'"); + if ($rs) ora_close($rs); + } + + return true; + } + + + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabasename, 1); + } + + + // returns query ID if successful, otherwise false + function _query($sql,$inputarr=false) + { + // <G. Giunta 2003/03/03/> Reset error messages before executing + $this->_errorMsg = false; + $this->_errorCode = false; + + $curs = ora_open($this->_connectionID); + + if ($curs === false) return false; + $this->_curs = $curs; + if (!ora_parse($curs,$sql)) return false; + if (ora_exec($curs)) return $curs; + // <G. Giunta 2004/03/03> before we close the cursor, we have to store the error message + // that we can obtain ONLY from the cursor (and not from the connection) + $this->_errorCode = @ora_errorcode($curs); + $this->_errorMsg = @ora_error($curs); + // </G. Giunta 2004/03/03> + @ora_close($curs); + return false; + } + + + // returns true or false + function _close() + { + return @ora_logoff($this->_connectionID); + } + + + +} + + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordset_oracle extends ADORecordSet { + + var $databaseType = "oracle"; + var $bind = false; + + function ADORecordset_oracle($queryID,$mode=false) + { + + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + $this->fetchMode = $mode; + + $this->_queryID = $queryID; + + $this->_inited = true; + $this->fields = array(); + if ($queryID) { + $this->_currentRow = 0; + $this->EOF = !$this->_fetch(); + @$this->_initrs(); + } else { + $this->_numOfRows = 0; + $this->_numOfFields = 0; + $this->EOF = true; + } + + return $this->_queryID; + } + + + + /* Returns: an object containing field information. + Get column information in the Recordset object. fetchField() can be used in order to obtain information about + fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by + fetchField() is retrieved. */ + + function &FetchField($fieldOffset = -1) + { + $fld = new ADOFieldObject; + $fld->name = ora_columnname($this->_queryID, $fieldOffset); + $fld->type = ora_columntype($this->_queryID, $fieldOffset); + $fld->max_length = ora_columnsize($this->_queryID, $fieldOffset); + return $fld; + } + + /* Use associative array to get fields array */ + function Fields($colname) + { + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + function _initrs() + { + $this->_numOfRows = -1; + $this->_numOfFields = @ora_numcols($this->_queryID); + } + + + function _seek($row) + { + return false; + } + + function _fetch($ignore_fields=false) { +// should remove call by reference, but ora_fetch_into requires it in 4.0.3pl1 + if ($this->fetchMode & ADODB_FETCH_ASSOC) + return @ora_fetch_into($this->_queryID,&$this->fields,ORA_FETCHINTO_NULLS|ORA_FETCHINTO_ASSOC); + else + return @ora_fetch_into($this->_queryID,&$this->fields,ORA_FETCHINTO_NULLS); + } + + /* close() only needs to be called if you are worried about using too much memory while your script + is running. All associated result memory for the specified result identifier will automatically be freed. */ + + function _close() +{ + return @ora_close($this->_queryID); + } + + function MetaType($t,$len=-1) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + + switch (strtoupper($t)) { + case 'VARCHAR': + case 'VARCHAR2': + case 'CHAR': + case 'VARBINARY': + case 'BINARY': + if ($len <= $this->blobSize) return 'C'; + case 'LONG': + case 'LONG VARCHAR': + case 'CLOB': + return 'X'; + case 'LONG RAW': + case 'LONG VARBINARY': + case 'BLOB': + return 'B'; + + case 'DATE': return 'D'; + + //case 'T': return 'T'; + + case 'BIT': return 'L'; + case 'INT': + case 'SMALLINT': + case 'INTEGER': return 'I'; + default: return 'N'; + } + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-pdo.inc.php b/framework/DataAccess/adodb/drivers/adodb-pdo.inc.php new file mode 100644 index 00000000..4d2d2e72 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-pdo.inc.php @@ -0,0 +1,562 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim#natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Requires ODBC. Works on Windows and Unix. + + Problems: + Where is float/decimal type in pdo_param_type + LOB handling for CLOB/BLOB differs significantly +*/ +// security - hide paths +if (!defined('ADODB_DIR')) die(); + + +/* +enum pdo_param_type { +PDO::PARAM_NULL, 0 + +/* int as in long (the php native int type). + * If you mark a column as an int, PDO expects get_col to return + * a pointer to a long +PDO::PARAM_INT, 1 + +/* get_col ptr should point to start of the string buffer +PDO::PARAM_STR, 2 + +/* get_col: when len is 0 ptr should point to a php_stream *, + * otherwise it should behave like a string. Indicate a NULL field + * value by setting the ptr to NULL +PDO::PARAM_LOB, 3 + +/* get_col: will expect the ptr to point to a new PDOStatement object handle, + * but this isn't wired up yet +PDO::PARAM_STMT, 4 /* hierarchical result set + +/* get_col ptr should point to a zend_bool +PDO::PARAM_BOOL, 5 + + +/* magic flag to denote a parameter as being input/output +PDO::PARAM_INPUT_OUTPUT = 0x80000000 +}; +*/ + +function adodb_pdo_type($t) +{ + switch($t) { + case 2: return 'VARCHAR'; + case 3: return 'BLOB'; + default: return 'NUMERIC'; + } +} + +/*-------------------------------------------------------------------------------------- +--------------------------------------------------------------------------------------*/ + +//////////////////////////////////////////////// + + + +class ADODB_pdo_base extends ADODB_pdo { + + var $sysDate = "'?'"; + var $sysTimeStamp = "'?'"; + + + function _init($parentDriver) + { + $parentDriver->_bindInputArray = true; + #$parentDriver->_connectionID->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true); + } + + function ServerInfo() + { + return ADOConnection::ServerInfo(); + } + + function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0) + { + $ret = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); + return $ret; + } + + function MetaTables() + { + return false; + } + + function MetaColumns() + { + return false; + } +} + + +class ADODB_pdo extends ADOConnection { + var $databaseType = "pdo"; + var $dataProvider = "pdo"; + var $fmtDate = "'Y-m-d'"; + var $fmtTimeStamp = "'Y-m-d, h:i:sA'"; + var $replaceQuote = "''"; // string to use to replace quotes + var $hasAffectedRows = true; + var $_bindInputArray = true; + var $_genSeqSQL = "create table %s (id integer)"; + var $_autocommit = true; + var $_haserrorfunctions = true; + var $_lastAffectedRows = 0; + + var $_errormsg = false; + var $_errorno = false; + + var $dsnType = ''; + var $stmt = false; + + function ADODB_pdo() + { + } + + function _UpdatePDO() + { + $d = &$this->_driver; + $this->fmtDate = $d->fmtDate; + $this->fmtTimeStamp = $d->fmtTimeStamp; + $this->replaceQuote = $d->replaceQuote; + $this->sysDate = $d->sysDate; + $this->sysTimeStamp = $d->sysTimeStamp; + $this->random = $d->random; + $this->concat_operator = $d->concat_operator; + $this->nameQuote = $d->nameQuote; + + $d->_init($this); + } + + function Time() + { + if (!empty($this->_driver->_hasdual)) $sql = "select $this->sysTimeStamp from dual"; + else $sql = "select $this->sysTimeStamp"; + + $rs =& $this->_Execute($sql); + if ($rs && !$rs->EOF) return $this->UnixTimeStamp(reset($rs->fields)); + + return false; + } + + // returns true or false + function _connect($argDSN, $argUsername, $argPassword, $argDatabasename, $persist=false) + { + $at = strpos($argDSN,':'); + $this->dsnType = substr($argDSN,0,$at); + + if ($argDatabasename) { + $argDSN .= ';dbname='.$argDatabasename; + } + try { + $this->_connectionID = new PDO($argDSN, $argUsername, $argPassword); + } catch (Exception $e) { + $this->_connectionID = false; + $this->_errorno = -1; + //var_dump($e); + $this->_errormsg = 'Connection attempt failed: '.$e->getMessage(); + return false; + } + + if ($this->_connectionID) { + switch(ADODB_ASSOC_CASE){ + case 0: $m = PDO::CASE_LOWER; break; + case 1: $m = PDO::CASE_UPPER; break; + default: + case 2: $m = PDO::CASE_NATURAL; break; + } + + //$this->_connectionID->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_SILENT ); + $this->_connectionID->setAttribute(PDO::ATTR_CASE,$m); + + $class = 'ADODB_pdo_'.$this->dsnType; + //$this->_connectionID->setAttribute(PDO::ATTR_AUTOCOMMIT,true); + switch($this->dsnType) { + case 'oci': + case 'mysql': + case 'pgsql': + case 'mssql': + include_once(ADODB_DIR.'/drivers/adodb-pdo_'.$this->dsnType.'.inc.php'); + break; + } + if (class_exists($class)) + $this->_driver = new $class(); + else + $this->_driver = new ADODB_pdo_base(); + + $this->_driver->_connectionID = $this->_connectionID; + $this->_UpdatePDO(); + return true; + } + $this->_driver = new ADODB_pdo_base(); + return false; + } + + // returns true or false + function _pconnect($argDSN, $argUsername, $argPassword, $argDatabasename) + { + return $this->_connect($argDSN, $argUsername, $argPassword, $argDatabasename, true); + } + + /*------------------------------------------------------------------------------*/ + + + function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0) + { + $save = $this->_driver->fetchMode; + $this->_driver->fetchMode = $this->fetchMode; + $this->_driver->debug = $this->debug; + $ret = $this->_driver->SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); + $this->_driver->fetchMode = $save; + return $ret; + } + + + function ServerInfo() + { + return $this->_driver->ServerInfo(); + } + + function MetaTables($ttype=false,$showSchema=false,$mask=false) + { + return $this->_driver->MetaTables($ttype,$showSchema,$mask); + } + + function MetaColumns($table,$normalize=true) + { + return $this->_driver->MetaColumns($table,$normalize); + } + + function InParameter(&$stmt,&$var,$name,$maxLen=4000,$type=false) + { + $obj = $stmt[1]; + if ($type) $obj->bindParam($name,$var,$type,$maxLen); + else $obj->bindParam($name, $var); + } + + + function ErrorMsg() + { + if ($this->_errormsg !== false) return $this->_errormsg; + if (!empty($this->_stmt)) $arr = $this->_stmt->errorInfo(); + else if (!empty($this->_connectionID)) $arr = $this->_connectionID->errorInfo(); + else return 'No Connection Established'; + + + if ($arr) { + if (sizeof($arr)<2) return ''; + if ((integer)$arr[1]) return $arr[2]; + else return ''; + } else return '-1'; + } + + + function ErrorNo() + { + if ($this->_errorno !== false) return $this->_errorno; + if (!empty($this->_stmt)) $err = $this->_stmt->errorCode(); + else if (!empty($this->_connectionID)) { + $arr = $this->_connectionID->errorInfo(); + if (isset($arr[0])) $err = $arr[0]; + else $err = -1; + } else + return 0; + + if ($err == '00000') return 0; // allows empty check + return $err; + } + + function BeginTrans() + { + if (!$this->hasTransactions) return false; + if ($this->transOff) return true; + $this->transCnt += 1; + $this->_autocommit = false; + $this->_connectionID->setAttribute(PDO::ATTR_AUTOCOMMIT,false); + return $this->_connectionID->beginTransaction(); + } + + function CommitTrans($ok=true) + { + if (!$this->hasTransactions) return false; + if ($this->transOff) return true; + if (!$ok) return $this->RollbackTrans(); + if ($this->transCnt) $this->transCnt -= 1; + $this->_autocommit = true; + + $ret = $this->_connectionID->commit(); + $this->_connectionID->setAttribute(PDO::ATTR_AUTOCOMMIT,true); + return $ret; + } + + function RollbackTrans() + { + if (!$this->hasTransactions) return false; + if ($this->transOff) return true; + if ($this->transCnt) $this->transCnt -= 1; + $this->_autocommit = true; + + $ret = $this->_connectionID->rollback(); + $this->_connectionID->setAttribute(PDO::ATTR_AUTOCOMMIT,true); + return $ret; + } + + function Prepare($sql) + { + $this->_stmt = $this->_connectionID->prepare($sql); + if ($this->_stmt) return array($sql,$this->_stmt); + + return false; + } + + function PrepareStmt($sql) + { + $stmt = $this->_connectionID->prepare($sql); + if (!$stmt) return false; + $obj = new ADOPDOStatement($stmt,$this); + return $obj; + } + + /* returns queryID or false */ + function _query($sql,$inputarr=false) + { + if (is_array($sql)) { + $stmt = $sql[1]; + } else { + $stmt = $this->_connectionID->prepare($sql); + } + + if ($stmt) { + $this->_driver->debug = $this->debug; + if ($inputarr) $ok = $stmt->execute($inputarr); + else $ok = $stmt->execute(); + } + + + $this->_errormsg = false; + $this->_errorno = false; + + if ($ok) { + $this->_stmt = $stmt; + return $stmt; + } + + if ($stmt) { + + $arr = $stmt->errorinfo(); + if ((integer)$arr[1]) { + $this->_errormsg = $arr[2]; + $this->_errorno = $arr[1]; + } + + } else { + $this->_errormsg = false; + $this->_errorno = false; + } + return false; + } + + // returns true or false + function _close() + { + $this->_stmt = false; + return true; + } + + function _affectedrows() + { + return ($this->_stmt) ? $this->_stmt->rowCount() : 0; + } + + function _insertid() + { + return ($this->_connectionID) ? $this->_connectionID->lastInsertId() : 0; + } +} + +class ADOPDOStatement { + + var $databaseType = "pdo"; + var $dataProvider = "pdo"; + var $_stmt; + var $_connectionID; + + function ADOPDOStatement($stmt,$connection) + { + $this->_stmt = $stmt; + $this->_connectionID = $connection; + } + + function Execute($inputArr=false) + { + $savestmt = $this->_connectionID->_stmt; + $rs = $this->_connectionID->Execute(array(false,$this->_stmt),$inputArr); + $this->_connectionID->_stmt = $savestmt; + return $rs; + } + + function InParameter(&$var,$name,$maxLen=4000,$type=false) + { + + if ($type) $this->_stmt->bindParam($name,$var,$type,$maxLen); + else $this->_stmt->bindParam($name, $var); + } + + function Affected_Rows() + { + return ($this->_stmt) ? $this->_stmt->rowCount() : 0; + } + + function ErrorMsg() + { + if ($this->_stmt) $arr = $this->_stmt->errorInfo(); + else $arr = $this->_connectionID->errorInfo(); + + if (is_array($arr)) { + if ((integer) $arr[0] && isset($arr[2])) return $arr[2]; + else return ''; + } else return '-1'; + } + + function NumCols() + { + return ($this->_stmt) ? $this->_stmt->columnCount() : 0; + } + + function ErrorNo() + { + if ($this->_stmt) return $this->_stmt->errorCode(); + else return $this->_connectionID->errorInfo(); + } +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordSet_pdo extends ADORecordSet { + + var $bind = false; + var $databaseType = "pdo"; + var $dataProvider = "pdo"; + + function ADORecordSet_pdo($id,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + $this->adodbFetchMode = $mode; + switch($mode) { + case ADODB_FETCH_NUM: $mode = PDO::FETCH_NUM; break; + case ADODB_FETCH_ASSOC: $mode = PDO::FETCH_ASSOC; break; + + case ADODB_FETCH_BOTH: + default: $mode = PDO::FETCH_BOTH; break; + } + $this->fetchMode = $mode; + + $this->_queryID = $id; + $this->ADORecordSet($id); + } + + + function Init() + { + if ($this->_inited) return; + $this->_inited = true; + if ($this->_queryID) @$this->_initrs(); + else { + $this->_numOfRows = 0; + $this->_numOfFields = 0; + } + if ($this->_numOfRows != 0 && $this->_currentRow == -1) { + $this->_currentRow = 0; + if ($this->EOF = ($this->_fetch() === false)) { + $this->_numOfRows = 0; // _numOfRows could be -1 + } + } else { + $this->EOF = true; + } + } + + function _initrs() + { + global $ADODB_COUNTRECS; + + $this->_numOfRows = ($ADODB_COUNTRECS) ? @$this->_queryID->rowCount() : -1; + if (!$this->_numOfRows) $this->_numOfRows = -1; + $this->_numOfFields = $this->_queryID->columnCount(); + } + + // returns the field object + function &FetchField($fieldOffset = -1) + { + $off=$fieldOffset+1; // offsets begin at 1 + + $o= new ADOFieldObject(); + $arr = @$this->_queryID->getColumnMeta($fieldOffset); + if (!$arr) { + $o->name = 'bad getColumnMeta()'; + $o->max_length = -1; + $o->type = 'VARCHAR'; + $o->precision = 0; + # $false = false; + return $o; + } + //adodb_pr($arr); + $o->name = $arr['name']; + if (isset($arr['native_type'])) $o->type = $arr['native_type']; + else $o->type = adodb_pdo_type($arr['pdo_type']); + $o->max_length = $arr['len']; + $o->precision = $arr['precision']; + + if (ADODB_ASSOC_CASE == 0) $o->name = strtolower($o->name); + else if (ADODB_ASSOC_CASE == 1) $o->name = strtoupper($o->name); + return $o; + } + + function _seek($row) + { + return false; + } + + function _fetch() + { + if (!$this->_queryID) return false; + + $this->fields = $this->_queryID->fetch($this->fetchMode); + return !empty($this->fields); + } + + function _close() + { + $this->_queryID = false; + } + + function Fields($colname) + { + if ($this->adodbFetchMode != ADODB_FETCH_NUM) return @$this->fields[$colname]; + + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + return $this->fields[$this->bind[strtoupper($colname)]]; + } + +} + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-pdo_mssql.inc.php b/framework/DataAccess/adodb/drivers/adodb-pdo_mssql.inc.php new file mode 100644 index 00000000..08c93149 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-pdo_mssql.inc.php @@ -0,0 +1,50 @@ +<?php + + +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 8. + +*/ + +class ADODB_pdo_mssql extends ADODB_pdo { + + var $hasTop = 'top'; + var $sysDate = 'convert(datetime,convert(char,GetDate(),102),102)'; + var $sysTimeStamp = 'GetDate()'; + + + function _init($parentDriver) + { + + $parentDriver->hasTransactions = false; ## <<< BUG IN PDO mssql driver + $parentDriver->_bindInputArray = false; + $parentDriver->hasInsertID = true; + } + + function ServerInfo() + { + return ADOConnection::ServerInfo(); + } + + function SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0) + { + $ret = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); + return $ret; + } + + function MetaTables() + { + return false; + } + + function MetaColumns() + { + return false; + } + +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-pdo_mysql.inc.php b/framework/DataAccess/adodb/drivers/adodb-pdo_mysql.inc.php new file mode 100644 index 00000000..6177fb60 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-pdo_mysql.inc.php @@ -0,0 +1,146 @@ +<?php + + +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 8. + +*/ + +class ADODB_pdo_mysql extends ADODB_pdo { + var $metaTablesSQL = "SHOW TABLES"; + var $metaColumnsSQL = "SHOW COLUMNS FROM %s"; + var $_bindInputArray = false; + var $sysDate = 'CURDATE()'; + var $sysTimeStamp = 'NOW()'; + + function _init($parentDriver) + { + + $parentDriver->hasTransactions = false; + $parentDriver->_bindInputArray = true; + $parentDriver->hasInsertID = true; + $parentDriver->_connectionID->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true); + } + + function ServerInfo() + { + $arr['description'] = ADOConnection::GetOne("select version()"); + $arr['version'] = ADOConnection::_findvers($arr['description']); + return $arr; + } + + function &MetaTables($ttype=false,$showSchema=false,$mask=false) + { + $save = $this->metaTablesSQL; + if ($showSchema && is_string($showSchema)) { + $this->metaTablesSQL .= " from $showSchema"; + } + + if ($mask) { + $mask = $this->qstr($mask); + $this->metaTablesSQL .= " like $mask"; + } + $ret =& ADOConnection::MetaTables($ttype,$showSchema); + + $this->metaTablesSQL = $save; + return $ret; + } + + function &MetaColumns($table) + { + $this->_findschema($table,$schema); + if ($schema) { + $dbName = $this->database; + $this->SelectDB($schema); + } + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table)); + + if ($schema) { + $this->SelectDB($dbName); + } + + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + if (!is_object($rs)) { + $false = false; + return $false; + } + + $retarr = array(); + while (!$rs->EOF){ + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[0]; + $type = $rs->fields[1]; + + // split type into type(length): + $fld->scale = null; + if (preg_match("/^(.+)\((\d+),(\d+)/", $type, $query_array)) { + $fld->type = $query_array[1]; + $fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1; + $fld->scale = is_numeric($query_array[3]) ? $query_array[3] : -1; + } elseif (preg_match("/^(.+)\((\d+)/", $type, $query_array)) { + $fld->type = $query_array[1]; + $fld->max_length = is_numeric($query_array[2]) ? $query_array[2] : -1; + } elseif (preg_match("/^(enum)\((.*)\)$/i", $type, $query_array)) { + $fld->type = $query_array[1]; + $arr = explode(",",$query_array[2]); + $fld->enums = $arr; + $zlen = max(array_map("strlen",$arr)) - 2; // PHP >= 4.0.6 + $fld->max_length = ($zlen > 0) ? $zlen : 1; + } else { + $fld->type = $type; + $fld->max_length = -1; + } + $fld->not_null = ($rs->fields[2] != 'YES'); + $fld->primary_key = ($rs->fields[3] == 'PRI'); + $fld->auto_increment = (strpos($rs->fields[5], 'auto_increment') !== false); + $fld->binary = (strpos($type,'blob') !== false); + $fld->unsigned = (strpos($type,'unsigned') !== false); + + if (!$fld->binary) { + $d = $rs->fields[4]; + if ($d != '' && $d != 'NULL') { + $fld->has_default = true; + $fld->default_value = $d; + } else { + $fld->has_default = false; + } + } + + if ($save == ADODB_FETCH_NUM) { + $retarr[] = $fld; + } else { + $retarr[strtoupper($fld->name)] = $fld; + } + $rs->MoveNext(); + } + + $rs->Close(); + return $retarr; + } + + + // parameters use PostgreSQL convention, not MySQL + function &SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs=0) + { + $offsetStr =($offset>=0) ? "$offset," : ''; + // jason judge, see http://phplens.com/lens/lensforum/msgs.php?id=9220 + if ($nrows < 0) $nrows = '18446744073709551615'; + + if ($secs) + $rs =& $this->CacheExecute($secs,$sql." LIMIT $offsetStr$nrows",$inputarr); + else + $rs =& $this->Execute($sql." LIMIT $offsetStr$nrows",$inputarr); + return $rs; + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-pdo_oci.inc.php b/framework/DataAccess/adodb/drivers/adodb-pdo_oci.inc.php new file mode 100644 index 00000000..0dd2aab0 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-pdo_oci.inc.php @@ -0,0 +1,93 @@ +<?php + + +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 8. + +*/ + +class ADODB_pdo_oci extends ADODB_pdo_base { + + var $concat_operator='||'; + var $sysDate = "TRUNC(SYSDATE)"; + var $sysTimeStamp = 'SYSDATE'; + var $NLS_DATE_FORMAT = 'YYYY-MM-DD'; // To include time, use 'RRRR-MM-DD HH24:MI:SS' + var $random = "abs(mod(DBMS_RANDOM.RANDOM,10000001)/10000000)"; + var $metaTablesSQL = "select table_name,table_type from cat where table_type in ('TABLE','VIEW')"; + var $metaColumnsSQL = "select cname,coltype,width, SCALE, PRECISION, NULLS, DEFAULTVAL from col where tname='%s' order by colno"; + + var $_initdate = true; + var $_hasdual = true; + + function _init($parentDriver) + { + $parentDriver->_bindInputArray = true; + + if ($this->_initdate) { + $parentDriver->Execute("ALTER SESSION SET NLS_DATE_FORMAT='".$this->NLS_DATE_FORMAT."'"); + } + } + + function &MetaTables($ttype=false,$showSchema=false,$mask=false) + { + if ($mask) { + $save = $this->metaTablesSQL; + $mask = $this->qstr(strtoupper($mask)); + $this->metaTablesSQL .= " AND table_name like $mask"; + } + $ret =& ADOConnection::MetaTables($ttype,$showSchema); + + if ($mask) { + $this->metaTablesSQL = $save; + } + return $ret; + } + + function &MetaColumns($table) + { + global $ADODB_FETCH_MODE; + + $false = false; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + + $rs = $this->Execute(sprintf($this->metaColumnsSQL,strtoupper($table))); + + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + if (!$rs) { + return $false; + } + $retarr = array(); + while (!$rs->EOF) { //print_r($rs->fields); + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[0]; + $fld->type = $rs->fields[1]; + $fld->max_length = $rs->fields[2]; + $fld->scale = $rs->fields[3]; + if ($rs->fields[1] == 'NUMBER' && $rs->fields[3] == 0) { + $fld->type ='INT'; + $fld->max_length = $rs->fields[4]; + } + $fld->not_null = (strncmp($rs->fields[5], 'NOT',3) === 0); + $fld->binary = (strpos($fld->type,'BLOB') !== false); + $fld->default_value = $rs->fields[6]; + + if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld; + else $retarr[strtoupper($fld->name)] = $fld; + $rs->MoveNext(); + } + $rs->Close(); + if (empty($retarr)) + return $false; + else + return $retarr; + } +} + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-pdo_pgsql.inc.php b/framework/DataAccess/adodb/drivers/adodb-pdo_pgsql.inc.php new file mode 100644 index 00000000..b4cceecc --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-pdo_pgsql.inc.php @@ -0,0 +1,229 @@ +<?php + +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 8. + +*/ + +class ADODB_pdo_pgsql extends ADODB_pdo { + var $metaDatabasesSQL = "select datname from pg_database where datname not in ('template0','template1') order by 1"; + var $metaTablesSQL = "select tablename,'T' from pg_tables where tablename not like 'pg\_%' + and tablename not in ('sql_features', 'sql_implementation_info', 'sql_languages', + 'sql_packages', 'sql_sizing', 'sql_sizing_profiles') + union + select viewname,'V' from pg_views where viewname not like 'pg\_%'"; + //"select tablename from pg_tables where tablename not like 'pg_%' order by 1"; + var $isoDates = true; // accepts dates in ISO format + var $sysDate = "CURRENT_DATE"; + var $sysTimeStamp = "CURRENT_TIMESTAMP"; + var $blobEncodeType = 'C'; + var $metaColumnsSQL = "SELECT a.attname,t.typname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,a.attnum + FROM pg_class c, pg_attribute a,pg_type t + WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s')) and a.attname not like '....%%' +AND a.attnum > 0 AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum"; + + // used when schema defined + var $metaColumnsSQL1 = "SELECT a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, a.attnum +FROM pg_class c, pg_attribute a, pg_type t, pg_namespace n +WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s')) + and c.relnamespace=n.oid and n.nspname='%s' + and a.attname not like '....%%' AND a.attnum > 0 + AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum"; + + // get primary key etc -- from Freek Dijkstra + var $metaKeySQL = "SELECT ic.relname AS index_name, a.attname AS column_name,i.indisunique AS unique_key, i.indisprimary AS primary_key + FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a WHERE bc.oid = i.indrelid AND ic.oid = i.indexrelid AND (i.indkey[0] = a.attnum OR i.indkey[1] = a.attnum OR i.indkey[2] = a.attnum OR i.indkey[3] = a.attnum OR i.indkey[4] = a.attnum OR i.indkey[5] = a.attnum OR i.indkey[6] = a.attnum OR i.indkey[7] = a.attnum) AND a.attrelid = bc.oid AND bc.relname = '%s'"; + + var $hasAffectedRows = true; + var $hasLimit = false; // set to true for pgsql 7 only. support pgsql/mysql SELECT * FROM TABLE LIMIT 10 + // below suggested by Freek Dijkstra + var $true = 't'; // string that represents TRUE for a database + var $false = 'f'; // string that represents FALSE for a database + var $fmtDate = "'Y-m-d'"; // used by DBDate() as the default date format used by the database + var $fmtTimeStamp = "'Y-m-d G:i:s'"; // used by DBTimeStamp as the default timestamp fmt. + var $hasMoveFirst = true; + var $hasGenID = true; + var $_genIDSQL = "SELECT NEXTVAL('%s')"; + var $_genSeqSQL = "CREATE SEQUENCE %s START %s"; + var $_dropSeqSQL = "DROP SEQUENCE %s"; + var $metaDefaultsSQL = "SELECT d.adnum as num, d.adsrc as def from pg_attrdef d, pg_class c where d.adrelid=c.oid and c.relname='%s' order by d.adnum"; + var $random = 'random()'; /// random function + var $concat_operator='||'; + + function _init($parentDriver) + { + + $parentDriver->hasTransactions = false; ## <<< BUG IN PDO pgsql driver + $parentDriver->hasInsertID = true; + } + + function ServerInfo() + { + $arr['description'] = ADOConnection::GetOne("select version()"); + $arr['version'] = ADOConnection::_findvers($arr['description']); + return $arr; + } + + function &SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0) + { + $offsetStr = ($offset >= 0) ? " OFFSET $offset" : ''; + $limitStr = ($nrows >= 0) ? " LIMIT $nrows" : ''; + if ($secs2cache) + $rs =& $this->CacheExecute($secs2cache,$sql."$limitStr$offsetStr",$inputarr); + else + $rs =& $this->Execute($sql."$limitStr$offsetStr",$inputarr); + + return $rs; + } + + function &MetaTables($ttype=false,$showSchema=false,$mask=false) + { + $info = $this->ServerInfo(); + if ($info['version'] >= 7.3) { + $this->metaTablesSQL = "select tablename,'T' from pg_tables where tablename not like 'pg\_%' + and schemaname not in ( 'pg_catalog','information_schema') + union + select viewname,'V' from pg_views where viewname not like 'pg\_%' and schemaname not in ( 'pg_catalog','information_schema') "; + } + if ($mask) { + $save = $this->metaTablesSQL; + $mask = $this->qstr(strtolower($mask)); + if ($info['version']>=7.3) + $this->metaTablesSQL = " +select tablename,'T' from pg_tables where tablename like $mask and schemaname not in ( 'pg_catalog','information_schema') + union +select viewname,'V' from pg_views where viewname like $mask and schemaname not in ( 'pg_catalog','information_schema') "; + else + $this->metaTablesSQL = " +select tablename,'T' from pg_tables where tablename like $mask + union +select viewname,'V' from pg_views where viewname like $mask"; + } + $ret =& ADOConnection::MetaTables($ttype,$showSchema); + + if ($mask) { + $this->metaTablesSQL = $save; + } + return $ret; + } + + function &MetaColumns($table,$normalize=true) + { + global $ADODB_FETCH_MODE; + + $schema = false; + $this->_findschema($table,$schema); + + if ($normalize) $table = strtolower($table); + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + + if ($schema) $rs =& $this->Execute(sprintf($this->metaColumnsSQL1,$table,$table,$schema)); + else $rs =& $this->Execute(sprintf($this->metaColumnsSQL,$table,$table)); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if ($rs === false) { + $false = false; + return $false; + } + if (!empty($this->metaKeySQL)) { + // If we want the primary keys, we have to issue a separate query + // Of course, a modified version of the metaColumnsSQL query using a + // LEFT JOIN would have been much more elegant, but postgres does + // not support OUTER JOINS. So here is the clumsy way. + + $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; + + $rskey = $this->Execute(sprintf($this->metaKeySQL,($table))); + // fetch all result in once for performance. + $keys =& $rskey->GetArray(); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + $rskey->Close(); + unset($rskey); + } + + $rsdefa = array(); + if (!empty($this->metaDefaultsSQL)) { + $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; + $sql = sprintf($this->metaDefaultsSQL, ($table)); + $rsdef = $this->Execute($sql); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if ($rsdef) { + while (!$rsdef->EOF) { + $num = $rsdef->fields['num']; + $s = $rsdef->fields['def']; + if (strpos($s,'::')===false && substr($s, 0, 1) == "'") { /* quoted strings hack... for now... fixme */ + $s = substr($s, 1); + $s = substr($s, 0, strlen($s) - 1); + } + + $rsdefa[$num] = $s; + $rsdef->MoveNext(); + } + } else { + ADOConnection::outp( "==> SQL => " . $sql); + } + unset($rsdef); + } + + $retarr = array(); + while (!$rs->EOF) { + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[0]; + $fld->type = $rs->fields[1]; + $fld->max_length = $rs->fields[2]; + if ($fld->max_length <= 0) $fld->max_length = $rs->fields[3]-4; + if ($fld->max_length <= 0) $fld->max_length = -1; + if ($fld->type == 'numeric') { + $fld->scale = $fld->max_length & 0xFFFF; + $fld->max_length >>= 16; + } + // dannym + // 5 hasdefault; 6 num-of-column + $fld->has_default = ($rs->fields[5] == 't'); + if ($fld->has_default) { + $fld->default_value = $rsdefa[$rs->fields[6]]; + } + + //Freek + if ($rs->fields[4] == $this->true) { + $fld->not_null = true; + } + + // Freek + if (is_array($keys)) { + foreach($keys as $key) { + if ($fld->name == $key['column_name'] AND $key['primary_key'] == $this->true) + $fld->primary_key = true; + if ($fld->name == $key['column_name'] AND $key['unique_key'] == $this->true) + $fld->unique = true; // What name is more compatible? + } + } + + if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld; + else $retarr[($normalize) ? strtoupper($fld->name) : $fld->name] = $fld; + + $rs->MoveNext(); + } + $rs->Close(); + if (empty($retarr)) { + $false = false; + return $false; + } else return $retarr; + + } + +} + +?> diff --git a/framework/DataAccess/adodb/drivers/adodb-postgres.inc.php b/framework/DataAccess/adodb/drivers/adodb-postgres.inc.php new file mode 100644 index 00000000..6064fe1e --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-postgres.inc.php @@ -0,0 +1,14 @@ +<?php +/* + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 4. + + NOTE: Since 3.31, this file is no longer used, and the "postgres" driver is + remapped to "postgres7". Maintaining multiple postgres drivers is no easy + job, so hopefully this will ensure greater consistency and fewer bugs. +*/ + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-postgres64.inc.php b/framework/DataAccess/adodb/drivers/adodb-postgres64.inc.php new file mode 100644 index 00000000..491b674c --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-postgres64.inc.php @@ -0,0 +1,1047 @@ +<?php +/* + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 8. + + Original version derived from Alberto Cerezal (acerezalp@dbnet.es) - DBNet Informatica & Comunicaciones. + 08 Nov 2000 jlim - Minor corrections, removing mysql stuff + 09 Nov 2000 jlim - added insertid support suggested by "Christopher Kings-Lynne" <chriskl@familyhealth.com.au> + jlim - changed concat operator to || and data types to MetaType to match documented pgsql types + see http://www.postgresql.org/devel-corner/docs/postgres/datatype.htm + 22 Nov 2000 jlim - added changes to FetchField() and MetaTables() contributed by "raser" <raser@mail.zen.com.tw> + 27 Nov 2000 jlim - added changes to _connect/_pconnect from ideas by "Lennie" <leen@wirehub.nl> + 15 Dec 2000 jlim - added changes suggested by Additional code changes by "Eric G. Werk" egw@netguide.dk. + 31 Jan 2002 jlim - finally installed postgresql. testing + 01 Mar 2001 jlim - Freek Dijkstra changes, also support for text type + + See http://www.varlena.com/varlena/GeneralBits/47.php + + -- What indexes are on my table? + select * from pg_indexes where tablename = 'tablename'; + + -- What triggers are on my table? + select c.relname as "Table", t.tgname as "Trigger Name", + t.tgconstrname as "Constraint Name", t.tgenabled as "Enabled", + t.tgisconstraint as "Is Constraint", cc.relname as "Referenced Table", + p.proname as "Function Name" + from pg_trigger t, pg_class c, pg_class cc, pg_proc p + where t.tgfoid = p.oid and t.tgrelid = c.oid + and t.tgconstrrelid = cc.oid + and c.relname = 'tablename'; + + -- What constraints are on my table? + select r.relname as "Table", c.conname as "Constraint Name", + contype as "Constraint Type", conkey as "Key Columns", + confkey as "Foreign Columns", consrc as "Source" + from pg_class r, pg_constraint c + where r.oid = c.conrelid + and relname = 'tablename'; + +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +function adodb_addslashes($s) +{ + $len = strlen($s); + if ($len == 0) return "''"; + if (strncmp($s,"'",1) === 0 && substr($s,$len-1) == "'") return $s; // already quoted + + return "'".addslashes($s)."'"; +} + +class ADODB_postgres64 extends ADOConnection{ + var $databaseType = 'postgres64'; + var $dataProvider = 'postgres'; + var $hasInsertID = true; + var $_resultid = false; + var $concat_operator='||'; + var $metaDatabasesSQL = "select datname from pg_database where datname not in ('template0','template1') order by 1"; + var $metaTablesSQL = "select tablename,'T' from pg_tables where tablename not like 'pg\_%' + and tablename not in ('sql_features', 'sql_implementation_info', 'sql_languages', + 'sql_packages', 'sql_sizing', 'sql_sizing_profiles') + union + select viewname,'V' from pg_views where viewname not like 'pg\_%'"; + //"select tablename from pg_tables where tablename not like 'pg_%' order by 1"; + var $isoDates = true; // accepts dates in ISO format + var $sysDate = "CURRENT_DATE"; + var $sysTimeStamp = "CURRENT_TIMESTAMP"; + var $blobEncodeType = 'C'; + var $metaColumnsSQL = "SELECT a.attname,t.typname,a.attlen,a.atttypmod,a.attnotnull,a.atthasdef,a.attnum + FROM pg_class c, pg_attribute a,pg_type t + WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s')) and a.attname not like '....%%' +AND a.attnum > 0 AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum"; + + // used when schema defined + var $metaColumnsSQL1 = "SELECT a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, a.attnum +FROM pg_class c, pg_attribute a, pg_type t, pg_namespace n +WHERE relkind in ('r','v') AND (c.relname='%s' or c.relname = lower('%s')) + and c.relnamespace=n.oid and n.nspname='%s' + and a.attname not like '....%%' AND a.attnum > 0 + AND a.atttypid = t.oid AND a.attrelid = c.oid ORDER BY a.attnum"; + + // get primary key etc -- from Freek Dijkstra + var $metaKeySQL = "SELECT ic.relname AS index_name, a.attname AS column_name,i.indisunique AS unique_key, i.indisprimary AS primary_key + FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a WHERE bc.oid = i.indrelid AND ic.oid = i.indexrelid AND (i.indkey[0] = a.attnum OR i.indkey[1] = a.attnum OR i.indkey[2] = a.attnum OR i.indkey[3] = a.attnum OR i.indkey[4] = a.attnum OR i.indkey[5] = a.attnum OR i.indkey[6] = a.attnum OR i.indkey[7] = a.attnum) AND a.attrelid = bc.oid AND bc.relname = '%s'"; + + var $hasAffectedRows = true; + var $hasLimit = false; // set to true for pgsql 7 only. support pgsql/mysql SELECT * FROM TABLE LIMIT 10 + // below suggested by Freek Dijkstra + var $true = 'TRUE'; // string that represents TRUE for a database + var $false = 'FALSE'; // string that represents FALSE for a database + var $fmtDate = "'Y-m-d'"; // used by DBDate() as the default date format used by the database + var $fmtTimeStamp = "'Y-m-d H:i:s'"; // used by DBTimeStamp as the default timestamp fmt. + var $hasMoveFirst = true; + var $hasGenID = true; + var $_genIDSQL = "SELECT NEXTVAL('%s')"; + var $_genSeqSQL = "CREATE SEQUENCE %s START %s"; + var $_dropSeqSQL = "DROP SEQUENCE %s"; + var $metaDefaultsSQL = "SELECT d.adnum as num, d.adsrc as def from pg_attrdef d, pg_class c where d.adrelid=c.oid and c.relname='%s' order by d.adnum"; + var $random = 'random()'; /// random function + var $autoRollback = true; // apparently pgsql does not autorollback properly before php 4.3.4 + // http://bugs.php.net/bug.php?id=25404 + + var $_bindInputArray = false; // requires postgresql 7.3+ and ability to modify database + var $disableBlobs = false; // set to true to disable blob checking, resulting in 2-5% improvement in performance. + + // The last (fmtTimeStamp is not entirely correct: + // PostgreSQL also has support for time zones, + // and writes these time in this format: "2001-03-01 18:59:26+02". + // There is no code for the "+02" time zone information, so I just left that out. + // I'm not familiar enough with both ADODB as well as Postgres + // to know what the concequences are. The other values are correct (wheren't in 0.94) + // -- Freek Dijkstra + + function ADODB_postgres64() + { + // changes the metaColumnsSQL, adds columns: attnum[6] + } + + function ServerInfo() + { + if (isset($this->version)) return $this->version; + + $arr['description'] = $this->GetOne("select version()"); + $arr['version'] = ADOConnection::_findvers($arr['description']); + $this->version = $arr; + return $arr; + } + + function IfNull( $field, $ifNull ) + { + return " coalesce($field, $ifNull) "; + } + + // get the last id - never tested + function pg_insert_id($tablename,$fieldname) + { + $result=pg_exec($this->_connectionID, "SELECT last_value FROM ${tablename}_${fieldname}_seq"); + if ($result) { + $arr = @pg_fetch_row($result,0); + pg_freeresult($result); + if (isset($arr[0])) return $arr[0]; + } + return false; + } + +/* Warning from http://www.php.net/manual/function.pg-getlastoid.php: +Using a OID as a unique identifier is not generally wise. +Unless you are very careful, you might end up with a tuple having +a different OID if a database must be reloaded. */ + function _insertid($table,$column) + { + if (!is_resource($this->_resultid) || get_resource_type($this->_resultid) !== 'pgsql result') return false; + $oid = pg_getlastoid($this->_resultid); + // to really return the id, we need the table and column-name, else we can only return the oid != id + return empty($table) || empty($column) ? $oid : $this->GetOne("SELECT $column FROM $table WHERE oid=".(int)$oid); + } + +// I get this error with PHP before 4.0.6 - jlim +// Warning: This compilation does not support pg_cmdtuples() in d:/inetpub/wwwroot/php/adodb/adodb-postgres.inc.php on line 44 + function _affectedrows() + { + if (!is_resource($this->_resultid) || get_resource_type($this->_resultid) !== 'pgsql result') return false; + return pg_cmdtuples($this->_resultid); + } + + + // returns true/false + function BeginTrans() + { + if ($this->transOff) return true; + $this->transCnt += 1; + return @pg_Exec($this->_connectionID, "begin"); + } + + function RowLock($tables,$where,$flds='1 as ignore') + { + if (!$this->transCnt) $this->BeginTrans(); + return $this->GetOne("select $flds from $tables where $where for update"); + } + + // returns true/false. + function CommitTrans($ok=true) + { + if ($this->transOff) return true; + if (!$ok) return $this->RollbackTrans(); + + $this->transCnt -= 1; + return @pg_Exec($this->_connectionID, "commit"); + } + + // returns true/false + function RollbackTrans() + { + if ($this->transOff) return true; + $this->transCnt -= 1; + return @pg_Exec($this->_connectionID, "rollback"); + } + + function &MetaTables($ttype=false,$showSchema=false,$mask=false) + { + $info = $this->ServerInfo(); + if ($info['version'] >= 7.3) { + $this->metaTablesSQL = "select tablename,'T' from pg_tables where tablename not like 'pg\_%' + and schemaname not in ( 'pg_catalog','information_schema') + union + select viewname,'V' from pg_views where viewname not like 'pg\_%' and schemaname not in ( 'pg_catalog','information_schema') "; + } + if ($mask) { + $save = $this->metaTablesSQL; + $mask = $this->qstr(strtolower($mask)); + if ($info['version']>=7.3) + $this->metaTablesSQL = " +select tablename,'T' from pg_tables where tablename like $mask and schemaname not in ( 'pg_catalog','information_schema') + union +select viewname,'V' from pg_views where viewname like $mask and schemaname not in ( 'pg_catalog','information_schema') "; + else + $this->metaTablesSQL = " +select tablename,'T' from pg_tables where tablename like $mask + union +select viewname,'V' from pg_views where viewname like $mask"; + } + $ret =& ADOConnection::MetaTables($ttype,$showSchema); + + if ($mask) { + $this->metaTablesSQL = $save; + } + return $ret; + } + + + // if magic quotes disabled, use pg_escape_string() + function qstr($s,$magic_quotes=false) + { + if (!$magic_quotes) { + if (ADODB_PHPVER >= 0x4200) { + return "'".pg_escape_string($s)."'"; + } + if ($this->replaceQuote[0] == '\\'){ + $s = adodb_str_replace(array('\\',"\0"),array('\\\\',"\\\\000"),$s); + } + return "'".str_replace("'",$this->replaceQuote,$s)."'"; + } + + // undo magic quotes for " + $s = str_replace('\\"','"',$s); + return "'$s'"; + } + + + + // Format date column in sql string given an input format that understands Y M D + function SQLDate($fmt, $col=false) + { + if (!$col) $col = $this->sysTimeStamp; + $s = 'TO_CHAR('.$col.",'"; + + $len = strlen($fmt); + for ($i=0; $i < $len; $i++) { + $ch = $fmt[$i]; + switch($ch) { + case 'Y': + case 'y': + $s .= 'YYYY'; + break; + case 'Q': + case 'q': + $s .= 'Q'; + break; + + case 'M': + $s .= 'Mon'; + break; + + case 'm': + $s .= 'MM'; + break; + case 'D': + case 'd': + $s .= 'DD'; + break; + + case 'H': + $s.= 'HH24'; + break; + + case 'h': + $s .= 'HH'; + break; + + case 'i': + $s .= 'MI'; + break; + + case 's': + $s .= 'SS'; + break; + + case 'a': + case 'A': + $s .= 'AM'; + break; + + case 'w': + $s .= 'D'; + break; + + case 'l': + $s .= 'DAY'; + break; + + case 'W': + $s .= 'WW'; + break; + + default: + // handle escape characters... + if ($ch == '\\') { + $i++; + $ch = substr($fmt,$i,1); + } + if (strpos('-/.:;, ',$ch) !== false) $s .= $ch; + else $s .= '"'.$ch.'"'; + + } + } + return $s. "')"; + } + + + + /* + * Load a Large Object from a file + * - the procedure stores the object id in the table and imports the object using + * postgres proprietary blob handling routines + * + * contributed by Mattia Rossi mattia@technologist.com + * modified for safe mode by juraj chlebec + */ + function UpdateBlobFile($table,$column,$path,$where,$blobtype='BLOB') + { + pg_exec ($this->_connectionID, "begin"); + + $fd = fopen($path,'r'); + $contents = fread($fd,filesize($path)); + fclose($fd); + + $oid = pg_lo_create($this->_connectionID); + $handle = pg_lo_open($this->_connectionID, $oid, 'w'); + pg_lo_write($handle, $contents); + pg_lo_close($handle); + + // $oid = pg_lo_import ($path); + pg_exec($this->_connectionID, "commit"); + $rs = ADOConnection::UpdateBlob($table,$column,$oid,$where,$blobtype); + $rez = !empty($rs); + return $rez; + } + + /* + * Deletes/Unlinks a Blob from the database, otherwise it + * will be left behind + * + * Returns TRUE on success or FALSE on failure. + * + * contributed by Todd Rogers todd#windfox.net + */ + function BlobDelete( $blob ) + { + pg_exec ($this->_connectionID, "begin"); + $result = @pg_lo_unlink($blob); + pg_exec ($this->_connectionID, "commit"); + return( $result ); + } + + /* + Hueristic - not guaranteed to work. + */ + function GuessOID($oid) + { + if (strlen($oid)>16) return false; + return is_numeric($oid); + } + + /* + * If an OID is detected, then we use pg_lo_* to open the oid file and read the + * real blob from the db using the oid supplied as a parameter. If you are storing + * blobs using bytea, we autodetect and process it so this function is not needed. + * + * contributed by Mattia Rossi mattia@technologist.com + * + * see http://www.postgresql.org/idocs/index.php?largeobjects.html + * + * Since adodb 4.54, this returns the blob, instead of sending it to stdout. Also + * added maxsize parameter, which defaults to $db->maxblobsize if not defined. + */ + function BlobDecode($blob,$maxsize=false,$hastrans=true) + { + if (!$this->GuessOID($blob)) return $blob; + + if ($hastrans) @pg_exec($this->_connectionID,"begin"); + $fd = @pg_lo_open($this->_connectionID,$blob,"r"); + if ($fd === false) { + if ($hastrans) @pg_exec($this->_connectionID,"commit"); + return $blob; + } + if (!$maxsize) $maxsize = $this->maxblobsize; + $realblob = @pg_loread($fd,$maxsize); + @pg_loclose($fd); + if ($hastrans) @pg_exec($this->_connectionID,"commit"); + return $realblob; + } + + /* + See http://www.postgresql.org/idocs/index.php?datatype-binary.html + + NOTE: SQL string literals (input strings) must be preceded with two backslashes + due to the fact that they must pass through two parsers in the PostgreSQL + backend. + */ + function BlobEncode($blob) + { + if (ADODB_PHPVER >= 0x4200) return pg_escape_bytea($blob); + + /*92=backslash, 0=null, 39=single-quote*/ + $badch = array(chr(92),chr(0),chr(39)); # \ null ' + $fixch = array('\\\\134','\\\\000','\\\\047'); + return adodb_str_replace($badch,$fixch,$blob); + + // note that there is a pg_escape_bytea function only for php 4.2.0 or later + } + + // assumes bytea for blob, and varchar for clob + function UpdateBlob($table,$column,$val,$where,$blobtype='BLOB') + { + + if ($blobtype == 'CLOB') { + return $this->Execute("UPDATE $table SET $column=" . $this->qstr($val) . " WHERE $where"); + } + // do not use bind params which uses qstr(), as blobencode() already quotes data + return $this->Execute("UPDATE $table SET $column='".$this->BlobEncode($val)."'::bytea WHERE $where"); + } + + function OffsetDate($dayFraction,$date=false) + { + if (!$date) $date = $this->sysDate; + else if (strncmp($date,"'",1) == 0) { + $len = strlen($date); + if (10 <= $len && $len <= 12) $date = 'date '.$date; + else $date = 'timestamp '.$date; + } + return "($date+interval'$dayFraction days')"; + } + + + // for schema support, pass in the $table param "$schema.$tabname". + // converts field names to lowercase, $upper is ignored + // see http://phplens.com/lens/lensforum/msgs.php?id=14018 for more info + function &MetaColumns($table,$normalize=true) + { + global $ADODB_FETCH_MODE; + + $schema = false; + $false = false; + $this->_findschema($table,$schema); + + if ($normalize) $table = strtolower($table); + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + + if ($schema) $rs =& $this->Execute(sprintf($this->metaColumnsSQL1,$table,$table,$schema)); + else $rs =& $this->Execute(sprintf($this->metaColumnsSQL,$table,$table)); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if ($rs === false) { + return $false; + } + if (!empty($this->metaKeySQL)) { + // If we want the primary keys, we have to issue a separate query + // Of course, a modified version of the metaColumnsSQL query using a + // LEFT JOIN would have been much more elegant, but postgres does + // not support OUTER JOINS. So here is the clumsy way. + + $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; + + $rskey = $this->Execute(sprintf($this->metaKeySQL,($table))); + // fetch all result in once for performance. + $keys =& $rskey->GetArray(); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + $rskey->Close(); + unset($rskey); + } + + $rsdefa = array(); + if (!empty($this->metaDefaultsSQL)) { + $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; + $sql = sprintf($this->metaDefaultsSQL, ($table)); + $rsdef = $this->Execute($sql); + if (isset($savem)) $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + + if ($rsdef) { + while (!$rsdef->EOF) { + $num = $rsdef->fields['num']; + $s = $rsdef->fields['def']; + if (strpos($s,'::')===false && substr($s, 0, 1) == "'") { /* quoted strings hack... for now... fixme */ + $s = substr($s, 1); + $s = substr($s, 0, strlen($s) - 1); + } + + $rsdefa[$num] = $s; + $rsdef->MoveNext(); + } + } else { + ADOConnection::outp( "==> SQL => " . $sql); + } + unset($rsdef); + } + + $retarr = array(); + while (!$rs->EOF) { + $fld = new ADOFieldObject(); + $fld->name = $rs->fields[0]; + $fld->type = $rs->fields[1]; + $fld->max_length = $rs->fields[2]; + if ($fld->max_length <= 0) $fld->max_length = $rs->fields[3]-4; + if ($fld->max_length <= 0) $fld->max_length = -1; + if ($fld->type == 'numeric') { + $fld->scale = $fld->max_length & 0xFFFF; + $fld->max_length >>= 16; + } + // dannym + // 5 hasdefault; 6 num-of-column + $fld->has_default = ($rs->fields[5] == 't'); + if ($fld->has_default) { + $fld->default_value = $rsdefa[$rs->fields[6]]; + } + + //Freek + $fld->not_null = $rs->fields[4] == 't'; + + + // Freek + if (is_array($keys)) { + foreach($keys as $key) { + if ($fld->name == $key['column_name'] AND $key['primary_key'] == 't') + $fld->primary_key = true; + if ($fld->name == $key['column_name'] AND $key['unique_key'] == 't') + $fld->unique = true; // What name is more compatible? + } + } + + if ($ADODB_FETCH_MODE == ADODB_FETCH_NUM) $retarr[] = $fld; + else $retarr[($normalize) ? strtoupper($fld->name) : $fld->name] = $fld; + + $rs->MoveNext(); + } + $rs->Close(); + if (empty($retarr)) + return $false; + else + return $retarr; + + } + + function &MetaIndexes ($table, $primary = FALSE) + { + global $ADODB_FETCH_MODE; + + $schema = false; + $this->_findschema($table,$schema); + + if ($schema) { // requires pgsql 7.3+ - pg_namespace used. + $sql = ' +SELECT c.relname as "Name", i.indisunique as "Unique", i.indkey as "Columns" +FROM pg_catalog.pg_class c +JOIN pg_catalog.pg_index i ON i.indexrelid=c.oid +JOIN pg_catalog.pg_class c2 ON c2.oid=i.indrelid + ,pg_namespace n +WHERE (c2.relname=\'%s\' or c2.relname=lower(\'%s\')) and c.relnamespace=c2.relnamespace and c.relnamespace=n.oid and n.nspname=\'%s\''; + } else { + $sql = ' +SELECT c.relname as "Name", i.indisunique as "Unique", i.indkey as "Columns" +FROM pg_catalog.pg_class c +JOIN pg_catalog.pg_index i ON i.indexrelid=c.oid +JOIN pg_catalog.pg_class c2 ON c2.oid=i.indrelid +WHERE (c2.relname=\'%s\' or c2.relname=lower(\'%s\'))'; + } + + if ($primary == FALSE) { + $sql .= ' AND i.indisprimary=false;'; + } + + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== FALSE) { + $savem = $this->SetFetchMode(FALSE); + } + + $rs = $this->Execute(sprintf($sql,$table,$table,$schema)); + if (isset($savem)) { + $this->SetFetchMode($savem); + } + $ADODB_FETCH_MODE = $save; + + if (!is_object($rs)) { + $false = false; + return $false; + } + + $col_names = $this->MetaColumnNames($table,true); + $indexes = array(); + while ($row = $rs->FetchRow()) { + $columns = array(); + foreach (explode(' ', $row[2]) as $col) { + $columns[] = $col_names[$col - 1]; + } + + $indexes[$row[0]] = array( + 'unique' => ($row[1] == 't'), + 'columns' => $columns + ); + } + return $indexes; + } + + // returns true or false + // + // examples: + // $db->Connect("host=host1 user=user1 password=secret port=4341"); + // $db->Connect('host1','user1','secret'); + function _connect($str,$user='',$pwd='',$db='',$ctype=0) + { + + if (!function_exists('pg_connect')) return null; + + $this->_errorMsg = false; + + if ($user || $pwd || $db) { + $user = adodb_addslashes($user); + $pwd = adodb_addslashes($pwd); + if (strlen($db) == 0) $db = 'template1'; + $db = adodb_addslashes($db); + if ($str) { + $host = split(":", $str); + if ($host[0]) $str = "host=".adodb_addslashes($host[0]); + else $str = 'host=localhost'; + if (isset($host[1])) $str .= " port=$host[1]"; + else if (!empty($this->port)) $str .= " port=".$this->port; + } + if ($user) $str .= " user=".$user; + if ($pwd) $str .= " password=".$pwd; + if ($db) $str .= " dbname=".$db; + } + + //if ($user) $linea = "user=$user host=$linea password=$pwd dbname=$db port=5432"; + + if ($ctype === 1) { // persistent + $this->_connectionID = pg_pconnect($str); + } else { + if ($ctype === -1) { // nconnect, we trick pgsql ext by changing the connection str + static $ncnt; + + if (empty($ncnt)) $ncnt = 1; + else $ncnt += 1; + + $str .= str_repeat(' ',$ncnt); + } + $this->_connectionID = pg_connect($str); + } + if ($this->_connectionID === false) return false; + $this->Execute("set datestyle='ISO'"); + return true; + } + + function _nconnect($argHostname, $argUsername, $argPassword, $argDatabaseName) + { + return $this->_connect($argHostname, $argUsername, $argPassword, $argDatabaseName,-1); + } + + // returns true or false + // + // examples: + // $db->PConnect("host=host1 user=user1 password=secret port=4341"); + // $db->PConnect('host1','user1','secret'); + function _pconnect($str,$user='',$pwd='',$db='') + { + return $this->_connect($str,$user,$pwd,$db,1); + } + + + // returns queryID or false + function _query($sql,$inputarr) + { + + if ($inputarr) { + /* + It appears that PREPARE/EXECUTE is slower for many queries. + + For query executed 1000 times: + "select id,firstname,lastname from adoxyz + where firstname not like ? and lastname not like ? and id = ?" + + with plan = 1.51861286163 secs + no plan = 1.26903700829 secs + + + + */ + $plan = 'P'.md5($sql); + + $execp = ''; + foreach($inputarr as $v) { + if ($execp) $execp .= ','; + if (is_string($v)) { + if (strncmp($v,"'",1) !== 0) $execp .= $this->qstr($v); + } else { + $execp .= $v; + } + } + + if ($execp) $exsql = "EXECUTE $plan ($execp)"; + else $exsql = "EXECUTE $plan"; + + $rez = @pg_exec($this->_connectionID,$exsql); + if (!$rez) { + # Perhaps plan does not exist? Prepare/compile plan. + $params = ''; + foreach($inputarr as $v) { + if ($params) $params .= ','; + if (is_string($v)) { + $params .= 'VARCHAR'; + } else if (is_integer($v)) { + $params .= 'INTEGER'; + } else { + $params .= "REAL"; + } + } + $sqlarr = explode('?',$sql); + //print_r($sqlarr); + $sql = ''; + $i = 1; + foreach($sqlarr as $v) { + $sql .= $v.' $'.$i; + $i++; + } + $s = "PREPARE $plan ($params) AS ".substr($sql,0,strlen($sql)-2); + //adodb_pr($s); + pg_exec($this->_connectionID,$s); + echo $this->ErrorMsg(); + } + + $rez = pg_exec($this->_connectionID,$exsql); + } else { + $this->_errorMsg = false; + //adodb_backtrace(); + $rez = pg_exec($this->_connectionID,$sql); + } + // check if no data returned, then no need to create real recordset + if ($rez && pg_numfields($rez) <= 0) { + if (is_resource($this->_resultid) && get_resource_type($this->_resultid) === 'pgsql result') { + pg_freeresult($this->_resultid); + } + $this->_resultid = $rez; + return true; + } + + return $rez; + } + + function _errconnect() + { + if (defined('DB_ERROR_CONNECT_FAILED')) return DB_ERROR_CONNECT_FAILED; + else return 'Database connection failed'; + } + + /* Returns: the last error message from previous database operation */ + function ErrorMsg() + { + if ($this->_errorMsg !== false) return $this->_errorMsg; + if (ADODB_PHPVER >= 0x4300) { + if (!empty($this->_resultid)) { + $this->_errorMsg = @pg_result_error($this->_resultid); + if ($this->_errorMsg) return $this->_errorMsg; + } + + if (!empty($this->_connectionID)) { + $this->_errorMsg = @pg_last_error($this->_connectionID); + } else $this->_errorMsg = $this->_errconnect(); + } else { + if (empty($this->_connectionID)) $this->_errconnect(); + else $this->_errorMsg = @pg_errormessage($this->_connectionID); + } + return $this->_errorMsg; + } + + function ErrorNo() + { + $e = $this->ErrorMsg(); + if (strlen($e)) { + return ADOConnection::MetaError($e); + } + return 0; + } + + // returns true or false + function _close() + { + if ($this->transCnt) $this->RollbackTrans(); + if ($this->_resultid) { + @pg_freeresult($this->_resultid); + $this->_resultid = false; + } + @pg_close($this->_connectionID); + $this->_connectionID = false; + return true; + } + + + /* + * Maximum size of C field + */ + function CharMax() + { + return 1000000000; // should be 1 Gb? + } + + /* + * Maximum size of X field + */ + function TextMax() + { + return 1000000000; // should be 1 Gb? + } + + +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordSet_postgres64 extends ADORecordSet{ + var $_blobArr; + var $databaseType = "postgres64"; + var $canSeek = true; + function ADORecordSet_postgres64($queryID,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + switch ($mode) + { + case ADODB_FETCH_NUM: $this->fetchMode = PGSQL_NUM; break; + case ADODB_FETCH_ASSOC:$this->fetchMode = PGSQL_ASSOC; break; + + case ADODB_FETCH_DEFAULT: + case ADODB_FETCH_BOTH: + default: $this->fetchMode = PGSQL_BOTH; break; + } + $this->adodbFetchMode = $mode; + $this->ADORecordSet($queryID); + } + + function &GetRowAssoc($upper=true) + { + if ($this->fetchMode == PGSQL_ASSOC && !$upper) return $this->fields; + $row =& ADORecordSet::GetRowAssoc($upper); + return $row; + } + + function _initrs() + { + global $ADODB_COUNTRECS; + $qid = $this->_queryID; + $this->_numOfRows = ($ADODB_COUNTRECS)? @pg_numrows($qid):-1; + $this->_numOfFields = @pg_numfields($qid); + + // cache types for blob decode check + // apparently pg_fieldtype actually performs an sql query on the database to get the type. + if (empty($this->connection->noBlobs)) + for ($i=0, $max = $this->_numOfFields; $i < $max; $i++) { + if (pg_fieldtype($qid,$i) == 'bytea') { + $this->_blobArr[$i] = pg_fieldname($qid,$i); + } + } + } + + /* Use associative array to get fields array */ + function Fields($colname) + { + if ($this->fetchMode != PGSQL_NUM) return @$this->fields[$colname]; + + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + function &FetchField($off = 0) + { + // offsets begin at 0 + + $o= new ADOFieldObject(); + $o->name = @pg_fieldname($this->_queryID,$off); + $o->type = @pg_fieldtype($this->_queryID,$off); + $o->max_length = @pg_fieldsize($this->_queryID,$off); + return $o; + } + + function _seek($row) + { + return @pg_fetch_row($this->_queryID,$row); + } + + function _decode($blob) + { + eval('$realblob="'.adodb_str_replace(array('"','$'),array('\"','\$'),$blob).'";'); + return $realblob; + } + + function _fixblobs() + { + if ($this->fetchMode == PGSQL_NUM || $this->fetchMode == PGSQL_BOTH) { + foreach($this->_blobArr as $k => $v) { + $this->fields[$k] = ADORecordSet_postgres64::_decode($this->fields[$k]); + } + } + if ($this->fetchMode == PGSQL_ASSOC || $this->fetchMode == PGSQL_BOTH) { + foreach($this->_blobArr as $k => $v) { + $this->fields[$v] = ADORecordSet_postgres64::_decode($this->fields[$v]); + } + } + } + + // 10% speedup to move MoveNext to child class + function MoveNext() + { + if (!$this->EOF) { + $this->_currentRow++; + if ($this->_numOfRows < 0 || $this->_numOfRows > $this->_currentRow) { + $this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode); + if (is_array($this->fields) && $this->fields) { + if (isset($this->_blobArr)) $this->_fixblobs(); + return true; + } + } + $this->fields = false; + $this->EOF = true; + } + return false; + } + + function _fetch() + { + + if ($this->_currentRow >= $this->_numOfRows && $this->_numOfRows >= 0) + return false; + + $this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode); + + if ($this->fields && isset($this->_blobArr)) $this->_fixblobs(); + + return (is_array($this->fields)); + } + + function _close() + { + return @pg_freeresult($this->_queryID); + } + + function MetaType($t,$len=-1,$fieldobj=false) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + switch (strtoupper($t)) { + case 'MONEY': // stupid, postgres expects money to be a string + case 'INTERVAL': + case 'CHAR': + case 'CHARACTER': + case 'VARCHAR': + case 'NAME': + case 'BPCHAR': + case '_VARCHAR': + case 'INET': + if ($len <= $this->blobSize) return 'C'; + + case 'TEXT': + return 'X'; + + case 'IMAGE': // user defined type + case 'BLOB': // user defined type + case 'BIT': // This is a bit string, not a single bit, so don't return 'L' + case 'VARBIT': + case 'BYTEA': + return 'B'; + + case 'BOOL': + case 'BOOLEAN': + return 'L'; + + case 'DATE': + return 'D'; + + case 'TIME': + case 'DATETIME': + case 'TIMESTAMP': + case 'TIMESTAMPTZ': + return 'T'; + + case 'SMALLINT': + case 'BIGINT': + case 'INTEGER': + case 'INT8': + case 'INT4': + case 'INT2': + if (isset($fieldobj) && + empty($fieldobj->primary_key) && empty($fieldobj->unique)) return 'I'; + + case 'OID': + case 'SERIAL': + return 'R'; + + default: + return 'N'; + } + } + +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-postgres7.inc.php b/framework/DataAccess/adodb/drivers/adodb-postgres7.inc.php new file mode 100644 index 00000000..92baac01 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-postgres7.inc.php @@ -0,0 +1,262 @@ +<?php +/* + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim#natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 4. + + Postgres7 support. + 28 Feb 2001: Currently indicate that we support LIMIT + 01 Dec 2001: dannym added support for default values +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +include_once(ADODB_DIR."/drivers/adodb-postgres64.inc.php"); + +class ADODB_postgres7 extends ADODB_postgres64 { + var $databaseType = 'postgres7'; + var $hasLimit = true; // set to true for pgsql 6.5+ only. support pgsql/mysql SELECT * FROM TABLE LIMIT 10 + var $ansiOuter = true; + var $charSet = true; //set to true for Postgres 7 and above - PG client supports encodings + + function ADODB_postgres7() + { + $this->ADODB_postgres64(); + if (ADODB_ASSOC_CASE !== 2) { + $this->rsPrefix .= 'assoc_'; + } + $this->_bindInputArray = PHP_VERSION >= 5.1; + } + + + // the following should be compat with postgresql 7.2, + // which makes obsolete the LIMIT limit,offset syntax + function &SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0) + { + $offsetStr = ($offset >= 0) ? " OFFSET ".((integer)$offset) : ''; + $limitStr = ($nrows >= 0) ? " LIMIT ".((integer)$nrows) : ''; + if ($secs2cache) + $rs =& $this->CacheExecute($secs2cache,$sql."$limitStr$offsetStr",$inputarr); + else + $rs =& $this->Execute($sql."$limitStr$offsetStr",$inputarr); + + return $rs; + } + /* + function Prepare($sql) + { + $info = $this->ServerInfo(); + if ($info['version']>=7.3) { + return array($sql,false); + } + return $sql; + } + */ + + // from Edward Jaramilla, improved version - works on pg 7.4 + function MetaForeignKeys($table, $owner=false, $upper=false) + { + $sql = 'SELECT t.tgargs as args + FROM + pg_trigger t,pg_class c,pg_proc p + WHERE + t.tgenabled AND + t.tgrelid = c.oid AND + t.tgfoid = p.oid AND + p.proname = \'RI_FKey_check_ins\' AND + c.relname = \''.strtolower($table).'\' + ORDER BY + t.tgrelid'; + + $rs =& $this->Execute($sql); + + if ($rs && !$rs->EOF) { + $arr =& $rs->GetArray(); + $a = array(); + foreach($arr as $v) + { + $data = explode(chr(0), $v['args']); + if ($upper) { + $a[strtoupper($data[2])][] = strtoupper($data[4].'='.$data[5]); + } else { + $a[$data[2]][] = $data[4].'='.$data[5]; + } + } + return $a; + } + return false; + } + + function _query($sql,$inputarr) + { + if (! $this->_bindInputArray) { + // We don't have native support for parameterized queries, so let's emulate it at the parent + return ADODB_postgres64::_query($sql, $inputarr); + } + // -- added Cristiano da Cunha Duarte + if ($inputarr) { + $sqlarr = explode('?',trim($sql)); + $sql = ''; + $i = 1; + $last = sizeof($sqlarr)-1; + foreach($sqlarr as $v) { + if ($last < $i) $sql .= $v; + else $sql .= $v.' $'.$i; + $i++; + } + + $rez = pg_query_params($this->_connectionID,$sql, $inputarr); + } else { + $rez = pg_query($this->_connectionID,$sql); + } + // check if no data returned, then no need to create real recordset + if ($rez && pg_numfields($rez) <= 0) { + if (is_resource($this->_resultid) && get_resource_type($this->_resultid) === 'pgsql result') { + pg_freeresult($this->_resultid); + } + $this->_resultid = $rez; + return true; + } + return $rez; + } + + // this is a set of functions for managing client encoding - very important if the encodings + // of your database and your output target (i.e. HTML) don't match + //for instance, you may have UNICODE database and server it on-site as WIN1251 etc. + // GetCharSet - get the name of the character set the client is using now + // the functions should work with Postgres 7.0 and above, the set of charsets supported + // depends on compile flags of postgres distribution - if no charsets were compiled into the server + // it will return 'SQL_ANSI' always + function GetCharSet() + { + //we will use ADO's builtin property charSet + $this->charSet = @pg_client_encoding($this->_connectionID); + if (!$this->charSet) { + return false; + } else { + return $this->charSet; + } + } + + // SetCharSet - switch the client encoding + function SetCharSet($charset_name) + { + $this->GetCharSet(); + if ($this->charSet !== $charset_name) { + $if = pg_set_client_encoding($this->_connectionID, $charset_name); + if ($if == "0" & $this->GetCharSet() == $charset_name) { + return true; + } else return false; + } else return true; + } + +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordSet_postgres7 extends ADORecordSet_postgres64{ + + var $databaseType = "postgres7"; + + + function ADORecordSet_postgres7($queryID,$mode=false) + { + $this->ADORecordSet_postgres64($queryID,$mode); + } + + // 10% speedup to move MoveNext to child class + function MoveNext() + { + if (!$this->EOF) { + $this->_currentRow++; + if ($this->_numOfRows < 0 || $this->_numOfRows > $this->_currentRow) { + $this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode); + + if (is_array($this->fields)) { + if ($this->fields && isset($this->_blobArr)) $this->_fixblobs(); + return true; + } + } + $this->fields = false; + $this->EOF = true; + } + return false; + } + +} + +class ADORecordSet_assoc_postgres7 extends ADORecordSet_postgres64{ + + var $databaseType = "postgres7"; + + + function ADORecordSet_assoc_postgres7($queryID,$mode=false) + { + $this->ADORecordSet_postgres64($queryID,$mode); + } + + function _fetch() + { + if ($this->_currentRow >= $this->_numOfRows && $this->_numOfRows >= 0) + return false; + + $this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode); + + if ($this->fields) { + if (isset($this->_blobArr)) $this->_fixblobs(); + $this->_updatefields(); + } + + return (is_array($this->fields)); + } + + // Create associative array + function _updatefields() + { + if (ADODB_ASSOC_CASE == 2) return; // native + + $arr = array(); + $lowercase = (ADODB_ASSOC_CASE == 0); + + foreach($this->fields as $k => $v) { + if (is_integer($k)) $arr[$k] = $v; + else { + if ($lowercase) + $arr[strtolower($k)] = $v; + else + $arr[strtoupper($k)] = $v; + } + } + $this->fields = $arr; + } + + function MoveNext() + { + if (!$this->EOF) { + $this->_currentRow++; + if ($this->_numOfRows < 0 || $this->_numOfRows > $this->_currentRow) { + $this->fields = @pg_fetch_array($this->_queryID,$this->_currentRow,$this->fetchMode); + + if (is_array($this->fields)) { + if ($this->fields) { + if (isset($this->_blobArr)) $this->_fixblobs(); + + $this->_updatefields(); + } + return true; + } + } + + + $this->fields = false; + $this->EOF = true; + } + return false; + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-postgres8.inc.php b/framework/DataAccess/adodb/drivers/adodb-postgres8.inc.php new file mode 100644 index 00000000..4278a3ae --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-postgres8.inc.php @@ -0,0 +1,12 @@ +<?php +/* + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 4. + + NOTE: The "postgres8" driver is remapped to "postgres7". +*/ + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-proxy.inc.php b/framework/DataAccess/adodb/drivers/adodb-proxy.inc.php new file mode 100644 index 00000000..7304cf7d --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-proxy.inc.php @@ -0,0 +1,33 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 4. + + Synonym for csv driver. +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (! defined("_ADODB_PROXY_LAYER")) { + define("_ADODB_PROXY_LAYER", 1 ); + include(ADODB_DIR."/drivers/adodb-csv.inc.php"); + + class ADODB_proxy extends ADODB_csv { + var $databaseType = 'proxy'; + var $databaseProvider = 'csv'; + } + class ADORecordset_proxy extends ADORecordset_csv { + var $databaseType = "proxy"; + + function ADORecordset_proxy($id,$mode=false) + { + $this->ADORecordset($id,$mode); + } + }; +} // define + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-sapdb.inc.php b/framework/DataAccess/adodb/drivers/adodb-sapdb.inc.php new file mode 100644 index 00000000..dba84dbc --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-sapdb.inc.php @@ -0,0 +1,184 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + SAPDB data driver. Requires ODBC. + +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (!defined('_ADODB_ODBC_LAYER')) { + include(ADODB_DIR."/drivers/adodb-odbc.inc.php"); +} +if (!defined('ADODB_SAPDB')){ +define('ADODB_SAPDB',1); + +class ADODB_SAPDB extends ADODB_odbc { + var $databaseType = "sapdb"; + var $concat_operator = '||'; + var $sysDate = 'DATE'; + var $sysTimeStamp = 'TIMESTAMP'; + var $fmtDate = "'Y-m-d'"; /// used by DBDate() as the default date format used by the database + var $fmtTimeStamp = "'Y-m-d H:i:s'"; /// used by DBTimeStamp as the default timestamp fmt. + var $hasInsertId = true; + var $_bindInputArray = true; + + function ADODB_SAPDB() + { + //if (strncmp(PHP_OS,'WIN',3) === 0) $this->curmode = SQL_CUR_USE_ODBC; + $this->ADODB_odbc(); + } + + function ServerInfo() + { + $info = ADODB_odbc::ServerInfo(); + if (!$info['version'] && preg_match('/([0-9.]+)/',$info['description'],$matches)) { + $info['version'] = $matches[1]; + } + return $info; + } + + function MetaPrimaryKeys($table) + { + $table = $this->Quote(strtoupper($table)); + + return $this->GetCol("SELECT columnname FROM COLUMNS WHERE tablename=$table AND mode='KEY' ORDER BY pos"); + } + + function &MetaIndexes ($table, $primary = FALSE) + { + $table = $this->Quote(strtoupper($table)); + + $sql = "SELECT INDEXNAME,TYPE,COLUMNNAME FROM INDEXCOLUMNS ". + " WHERE TABLENAME=$table". + " ORDER BY INDEXNAME,COLUMNNO"; + + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== FALSE) { + $savem = $this->SetFetchMode(FALSE); + } + + $rs = $this->Execute($sql); + if (isset($savem)) { + $this->SetFetchMode($savem); + } + $ADODB_FETCH_MODE = $save; + + if (!is_object($rs)) { + return FALSE; + } + + $indexes = array(); + while ($row = $rs->FetchRow()) { + $indexes[$row[0]]['unique'] = $row[1] == 'UNIQUE'; + $indexes[$row[0]]['columns'][] = $row[2]; + } + if ($primary) { + $indexes['SYSPRIMARYKEYINDEX'] = array( + 'unique' => True, // by definition + 'columns' => $this->GetCol("SELECT columnname FROM COLUMNS WHERE tablename=$table AND mode='KEY' ORDER BY pos"), + ); + } + return $indexes; + } + + function &MetaColumns ($table) + { + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== FALSE) { + $savem = $this->SetFetchMode(FALSE); + } + $table = $this->Quote(strtoupper($table)); + + $retarr = array(); + foreach($this->GetAll("SELECT COLUMNNAME,DATATYPE,LEN,DEC,NULLABLE,MODE,\"DEFAULT\",CASE WHEN \"DEFAULT\" IS NULL THEN 0 ELSE 1 END AS HAS_DEFAULT FROM COLUMNS WHERE tablename=$table ORDER BY pos") as $column) + { + $fld = new ADOFieldObject(); + $fld->name = $column[0]; + $fld->type = $column[1]; + $fld->max_length = $fld->type == 'LONG' ? 2147483647 : $column[2]; + $fld->scale = $column[3]; + $fld->not_null = $column[4] == 'NO'; + $fld->primary_key = $column[5] == 'KEY'; + if ($fld->has_default = $column[7]) { + if ($fld->primary_key && $column[6] == 'DEFAULT SERIAL (1)') { + $fld->auto_increment = true; + $fld->has_default = false; + } else { + $fld->default_value = $column[6]; + switch($fld->type) { + case 'VARCHAR': + case 'CHARACTER': + case 'LONG': + $fld->default_value = $column[6]; + break; + default: + $fld->default_value = trim($column[6]); + break; + } + } + } + $retarr[$fld->name] = $fld; + } + if (isset($savem)) { + $this->SetFetchMode($savem); + } + $ADODB_FETCH_MODE = $save; + + return $retarr; + } + + function MetaColumnNames($table) + { + $table = $this->Quote(strtoupper($table)); + + return $this->GetCol("SELECT columnname FROM COLUMNS WHERE tablename=$table ORDER BY pos"); + } + + // unlike it seems, this depends on the db-session and works in a multiuser environment + function _insertid($table,$column) + { + return empty($table) ? False : $this->GetOne("SELECT $table.CURRVAL FROM DUAL"); + } + + /* + SelectLimit implementation problems: + + The following will return random 10 rows as order by performed after "WHERE rowno<10" + which is not ideal... + + select * from table where rowno < 10 order by 1 + + This means that we have to use the adoconnection base class SelectLimit when + there is an "order by". + + See http://listserv.sap.com/pipermail/sapdb.general/2002-January/010405.html + */ + +}; + + +class ADORecordSet_sapdb extends ADORecordSet_odbc { + + var $databaseType = "sapdb"; + + function ADORecordSet_sapdb($id,$mode=false) + { + $this->ADORecordSet_odbc($id,$mode); + } +} + +} //define +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-sqlanywhere.inc.php b/framework/DataAccess/adodb/drivers/adodb-sqlanywhere.inc.php new file mode 100644 index 00000000..7b064ff5 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-sqlanywhere.inc.php @@ -0,0 +1,169 @@ +<?php +/* +version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights +reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + 21.02.2002 - Wade Johnson wade@wadejohnson.de + Extended ODBC class for Sybase SQLAnywhere. + 1) Added support to retrieve the last row insert ID on tables with + primary key column using autoincrement function. + + 2) Added blob support. Usage: + a) create blob variable on db server: + + $dbconn->create_blobvar($blobVarName); + + b) load blob var from file. $filename must be complete path + + $dbcon->load_blobvar_from_file($blobVarName, $filename); + + c) Use the $blobVarName in SQL insert or update statement in the values + clause: + + $recordSet = $dbconn->Execute('INSERT INTO tabname (idcol, blobcol) ' + . + 'VALUES (\'test\', ' . $blobVarName . ')'); + + instead of loading blob from a file, you can also load from + an unformatted (raw) blob variable: + $dbcon->load_blobvar_from_var($blobVarName, $varName); + + d) drop blob variable on db server to free up resources: + $dbconn->drop_blobvar($blobVarName); + + Sybase_SQLAnywhere data driver. Requires ODBC. + +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (!defined('_ADODB_ODBC_LAYER')) { + include(ADODB_DIR."/drivers/adodb-odbc.inc.php"); +} + +if (!defined('ADODB_SYBASE_SQLANYWHERE')){ + + define('ADODB_SYBASE_SQLANYWHERE',1); + + class ADODB_sqlanywhere extends ADODB_odbc { + var $databaseType = "sqlanywhere"; + var $hasInsertID = true; + + function ADODB_sqlanywhere() + { + $this->ADODB_odbc(); + } + + function _insertid() { + return $this->GetOne('select @@identity'); + } + + function create_blobvar($blobVarName) { + $this->Execute("create variable $blobVarName long binary"); + return; + } + + function drop_blobvar($blobVarName) { + $this->Execute("drop variable $blobVarName"); + return; + } + + function load_blobvar_from_file($blobVarName, $filename) { + $chunk_size = 1000; + + $fd = fopen ($filename, "rb"); + + $integer_chunks = (integer)filesize($filename) / $chunk_size; + $modulus = filesize($filename) % $chunk_size; + if ($modulus != 0){ + $integer_chunks += 1; + } + + for($loop=1;$loop<=$integer_chunks;$loop++){ + $contents = fread ($fd, $chunk_size); + $contents = bin2hex($contents); + + $hexstring = ''; + + for($loop2=0;$loop2<strlen($contents);$loop2+=2){ + $hexstring .= '\x' . substr($contents,$loop2,2); + } + + $hexstring = $this->qstr($hexstring); + + $this->Execute("set $blobVarName = $blobVarName || " . $hexstring); + } + + fclose ($fd); + return; + } + + function load_blobvar_from_var($blobVarName, &$varName) { + $chunk_size = 1000; + + $integer_chunks = (integer)strlen($varName) / $chunk_size; + $modulus = strlen($varName) % $chunk_size; + if ($modulus != 0){ + $integer_chunks += 1; + } + + for($loop=1;$loop<=$integer_chunks;$loop++){ + $contents = substr ($varName, (($loop - 1) * $chunk_size), $chunk_size); + $contents = bin2hex($contents); + + $hexstring = ''; + + for($loop2=0;$loop2<strlen($contents);$loop2+=2){ + $hexstring .= '\x' . substr($contents,$loop2,2); + } + + $hexstring = $this->qstr($hexstring); + + $this->Execute("set $blobVarName = $blobVarName || " . $hexstring); + } + + return; + } + + /* + Insert a null into the blob field of the table first. + Then use UpdateBlob to store the blob. + + Usage: + + $conn->Execute('INSERT INTO blobtable (id, blobcol) VALUES (1, null)'); + $conn->UpdateBlob('blobtable','blobcol',$blob,'id=1'); + */ + function UpdateBlob($table,$column,&$val,$where,$blobtype='BLOB') + { + $blobVarName = 'hold_blob'; + $this->create_blobvar($blobVarName); + $this->load_blobvar_from_var($blobVarName, $val); + $this->Execute("UPDATE $table SET $column=$blobVarName WHERE $where"); + $this->drop_blobvar($blobVarName); + return true; + } + }; //class + + class ADORecordSet_sqlanywhere extends ADORecordSet_odbc { + + var $databaseType = "sqlanywhere"; + + function ADORecordSet_sqlanywhere($id,$mode=false) + { + $this->ADORecordSet_odbc($id,$mode); + } + + + }; //class + + +} //define +?> diff --git a/framework/DataAccess/adodb/drivers/adodb-sqlite.inc.php b/framework/DataAccess/adodb/drivers/adodb-sqlite.inc.php new file mode 100644 index 00000000..51d983af --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-sqlite.inc.php @@ -0,0 +1,398 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + + Latest version is available at http://adodb.sourceforge.net + + SQLite info: http://www.hwaci.com/sw/sqlite/ + + Install Instructions: + ==================== + 1. Place this in adodb/drivers + 2. Rename the file, remove the .txt prefix. +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +class ADODB_sqlite extends ADOConnection { + var $databaseType = "sqlite"; + var $replaceQuote = "''"; // string to use to replace quotes + var $concat_operator='||'; + var $_errorNo = 0; + var $hasLimit = true; + var $hasInsertID = true; /// supports autoincrement ID? + var $hasAffectedRows = true; /// supports affected rows for update/delete? + var $metaTablesSQL = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"; + var $sysDate = "adodb_date('Y-m-d')"; + var $sysTimeStamp = "adodb_date('Y-m-d H:i:s')"; + var $fmtTimeStamp = "'Y-m-d H:i:s'"; + + function ADODB_sqlite() + { + } + +/* + function __get($name) + { + switch($name) { + case 'sysDate': return "'".date($this->fmtDate)."'"; + case 'sysTimeStamp' : return "'".date($this->sysTimeStamp)."'"; + } + }*/ + + function ServerInfo() + { + $arr['version'] = sqlite_libversion(); + $arr['description'] = 'SQLite '; + $arr['encoding'] = sqlite_libencoding(); + return $arr; + } + + function BeginTrans() + { + if ($this->transOff) return true; + $ret = $this->Execute("BEGIN TRANSACTION"); + $this->transCnt += 1; + return true; + } + + function CommitTrans($ok=true) + { + if ($this->transOff) return true; + if (!$ok) return $this->RollbackTrans(); + $ret = $this->Execute("COMMIT"); + if ($this->transCnt>0)$this->transCnt -= 1; + return !empty($ret); + } + + function RollbackTrans() + { + if ($this->transOff) return true; + $ret = $this->Execute("ROLLBACK"); + if ($this->transCnt>0)$this->transCnt -= 1; + return !empty($ret); + } + + // mark newnham + function &MetaColumns($tab) + { + global $ADODB_FETCH_MODE; + $false = false; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; + if ($this->fetchMode !== false) $savem = $this->SetFetchMode(false); + $rs = $this->Execute("PRAGMA table_info('$tab')"); + if (isset($savem)) $this->SetFetchMode($savem); + if (!$rs) { + $ADODB_FETCH_MODE = $save; + return $false; + } + $arr = array(); + while ($r = $rs->FetchRow()) { + $type = explode('(',$r['type']); + $size = ''; + if (sizeof($type)==2) + $size = trim($type[1],')'); + $fn = strtoupper($r['name']); + $fld = new ADOFieldObject; + $fld->name = $r['name']; + $fld->type = $type[0]; + $fld->max_length = $size; + $fld->not_null = $r['notnull']; + $fld->default_value = $r['dflt_value']; + $fld->scale = 0; + if ($save == ADODB_FETCH_NUM) $arr[] = $fld; + else $arr[strtoupper($fld->name)] = $fld; + } + $rs->Close(); + $ADODB_FETCH_MODE = $save; + return $arr; + } + + function _init($parentDriver) + { + + $parentDriver->hasTransactions = false; + $parentDriver->hasInsertID = true; + } + + function _insertid() + { + return sqlite_last_insert_rowid($this->_connectionID); + } + + function _affectedrows() + { + return sqlite_changes($this->_connectionID); + } + + function ErrorMsg() + { + if ($this->_logsql) return $this->_errorMsg; + return ($this->_errorNo) ? sqlite_error_string($this->_errorNo) : ''; + } + + function ErrorNo() + { + return $this->_errorNo; + } + + function SQLDate($fmt, $col=false) + { + $fmt = $this->qstr($fmt); + return ($col) ? "adodb_date2($fmt,$col)" : "adodb_date($fmt)"; + } + + + function _createFunctions() + { + @sqlite_create_function($this->_connectionID, 'adodb_date', 'adodb_date', 1); + @sqlite_create_function($this->_connectionID, 'adodb_date2', 'adodb_date2', 2); + } + + + // returns true or false + function _connect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + if (!function_exists('sqlite_open')) return null; + if (empty($argHostname) && $argDatabasename) $argHostname = $argDatabasename; + + $this->_connectionID = sqlite_open($argHostname); + if ($this->_connectionID === false) return false; + $this->_createFunctions(); + return true; + } + + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + if (!function_exists('sqlite_open')) return null; + if (empty($argHostname) && $argDatabasename) $argHostname = $argDatabasename; + + $this->_connectionID = sqlite_popen($argHostname); + if ($this->_connectionID === false) return false; + $this->_createFunctions(); + return true; + } + + // returns query ID if successful, otherwise false + function _query($sql,$inputarr=false) + { + $rez = sqlite_query($sql,$this->_connectionID); + if (!$rez) { + $this->_errorNo = sqlite_last_error($this->_connectionID); + } + + return $rez; + } + + function &SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0) + { + $offsetStr = ($offset >= 0) ? " OFFSET $offset" : ''; + $limitStr = ($nrows >= 0) ? " LIMIT $nrows" : ($offset >= 0 ? ' LIMIT 999999999' : ''); + if ($secs2cache) + $rs =& $this->CacheExecute($secs2cache,$sql."$limitStr$offsetStr",$inputarr); + else + $rs =& $this->Execute($sql."$limitStr$offsetStr",$inputarr); + + return $rs; + } + + /* + This algorithm is not very efficient, but works even if table locking + is not available. + + Will return false if unable to generate an ID after $MAXLOOPS attempts. + */ + var $_genSeqSQL = "create table %s (id integer)"; + + function GenID($seq='adodbseq',$start=1) + { + // if you have to modify the parameter below, your database is overloaded, + // or you need to implement generation of id's yourself! + $MAXLOOPS = 100; + //$this->debug=1; + while (--$MAXLOOPS>=0) { + @($num = $this->GetOne("select id from $seq")); + if ($num === false) { + $this->Execute(sprintf($this->_genSeqSQL ,$seq)); + $start -= 1; + $num = '0'; + $ok = $this->Execute("insert into $seq values($start)"); + if (!$ok) return false; + } + $this->Execute("update $seq set id=id+1 where id=$num"); + + if ($this->affected_rows() > 0) { + $num += 1; + $this->genID = $num; + return $num; + } + } + if ($fn = $this->raiseErrorFn) { + $fn($this->databaseType,'GENID',-32000,"Unable to generate unique id after $MAXLOOPS attempts",$seq,$num); + } + return false; + } + + function CreateSequence($seqname='adodbseq',$start=1) + { + if (empty($this->_genSeqSQL)) return false; + $ok = $this->Execute(sprintf($this->_genSeqSQL,$seqname)); + if (!$ok) return false; + $start -= 1; + return $this->Execute("insert into $seqname values($start)"); + } + + var $_dropSeqSQL = 'drop table %s'; + function DropSequence($seqname) + { + if (empty($this->_dropSeqSQL)) return false; + return $this->Execute(sprintf($this->_dropSeqSQL,$seqname)); + } + + // returns true or false + function _close() + { + return @sqlite_close($this->_connectionID); + } + + function &MetaIndexes($table, $primary = FALSE, $owner=false) + { + $false = false; + // save old fetch mode + global $ADODB_FETCH_MODE; + $save = $ADODB_FETCH_MODE; + $ADODB_FETCH_MODE = ADODB_FETCH_NUM; + if ($this->fetchMode !== FALSE) { + $savem = $this->SetFetchMode(FALSE); + } + $SQL=sprintf("SELECT name,sql FROM sqlite_master WHERE type='index' AND tbl_name='%s'", strtolower($table)); + $rs = $this->Execute($SQL); + if (!is_object($rs)) { + if (isset($savem)) + $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + return $false; + } + + $indexes = array (); + while ($row = $rs->FetchRow()) { + if ($primary && preg_match("/primary/i",$row[1]) == 0) continue; + if (!isset($indexes[$row[0]])) { + + $indexes[$row[0]] = array( + 'unique' => preg_match("/unique/i",$row[1]), + 'columns' => array()); + } + /** + * There must be a more elegant way of doing this, + * the index elements appear in the SQL statement + * in cols[1] between parentheses + * e.g CREATE UNIQUE INDEX ware_0 ON warehouse (org,warehouse) + */ + $cols = explode("(",$row[1]); + $cols = explode(")",$cols[1]); + array_pop($cols); + $indexes[$row[0]]['columns'] = $cols; + } + if (isset($savem)) { + $this->SetFetchMode($savem); + $ADODB_FETCH_MODE = $save; + } + return $indexes; + } + +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordset_sqlite extends ADORecordSet { + + var $databaseType = "sqlite"; + var $bind = false; + + function ADORecordset_sqlite($queryID,$mode=false) + { + + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + switch($mode) { + case ADODB_FETCH_NUM: $this->fetchMode = SQLITE_NUM; break; + case ADODB_FETCH_ASSOC: $this->fetchMode = SQLITE_ASSOC; break; + default: $this->fetchMode = SQLITE_BOTH; break; + } + $this->adodbFetchMode = $mode; + + $this->_queryID = $queryID; + + $this->_inited = true; + $this->fields = array(); + if ($queryID) { + $this->_currentRow = 0; + $this->EOF = !$this->_fetch(); + @$this->_initrs(); + } else { + $this->_numOfRows = 0; + $this->_numOfFields = 0; + $this->EOF = true; + } + + return $this->_queryID; + } + + + function &FetchField($fieldOffset = -1) + { + $fld = new ADOFieldObject; + $fld->name = sqlite_field_name($this->_queryID, $fieldOffset); + $fld->type = 'VARCHAR'; + $fld->max_length = -1; + return $fld; + } + + function _initrs() + { + $this->_numOfRows = @sqlite_num_rows($this->_queryID); + $this->_numOfFields = @sqlite_num_fields($this->_queryID); + } + + function Fields($colname) + { + if ($this->fetchMode != SQLITE_NUM) return $this->fields[$colname]; + if (!$this->bind) { + $this->bind = array(); + for ($i=0; $i < $this->_numOfFields; $i++) { + $o = $this->FetchField($i); + $this->bind[strtoupper($o->name)] = $i; + } + } + + return $this->fields[$this->bind[strtoupper($colname)]]; + } + + function _seek($row) + { + return sqlite_seek($this->_queryID, $row); + } + + function _fetch($ignore_fields=false) + { + $this->fields = @sqlite_fetch_array($this->_queryID,$this->fetchMode); + return !empty($this->fields); + } + + function _close() + { + } + +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-sqlitepo.inc.php b/framework/DataAccess/adodb/drivers/adodb-sqlitepo.inc.php new file mode 100644 index 00000000..da622e1e --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-sqlitepo.inc.php @@ -0,0 +1,62 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + + Portable version of sqlite driver, to make it more similar to other database drivers. + The main differences are + + 1. When selecting (joining) multiple tables, in assoc mode the table + names are included in the assoc keys in the "sqlite" driver. + + In "sqlitepo" driver, the table names are stripped from the returned column names. + When this results in a conflict, the first field get preference. + + Contributed by Herman Kuiper herman#ozuzo.net +*/ + +if (!defined('ADODB_DIR')) die(); + +include_once(ADODB_DIR.'/drivers/adodb-sqlite.inc.php'); + +class ADODB_sqlitepo extends ADODB_sqlite { + var $databaseType = 'sqlitepo'; + + function ADODB_sqlitepo() + { + $this->ADODB_sqlite(); + } +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ + +class ADORecordset_sqlitepo extends ADORecordset_sqlite { + + var $databaseType = 'sqlitepo'; + + function ADORecordset_sqlitepo($queryID,$mode=false) + { + $this->ADORecordset_sqlite($queryID,$mode); + } + + // Modified to strip table names from returned fields + function _fetch($ignore_fields=false) + { + $this->fields = array(); + $fields = @sqlite_fetch_array($this->_queryID,$this->fetchMode); + if(is_array($fields)) + foreach($fields as $n => $v) + { + if(($p = strpos($n, ".")) !== false) + $n = substr($n, $p+1); + $this->fields[$n] = $v; + } + + return !empty($this->fields); + } +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-sybase.inc.php b/framework/DataAccess/adodb/drivers/adodb-sybase.inc.php new file mode 100644 index 00000000..29a98316 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-sybase.inc.php @@ -0,0 +1,418 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim. All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Sybase driver contributed by Toni (toni.tunkkari@finebyte.com) + + - MSSQL date patch applied. + + Date patch by Toni 15 Feb 2002 +*/ + + // security - hide paths +if (!defined('ADODB_DIR')) die(); + +class ADODB_sybase extends ADOConnection { + var $databaseType = "sybase"; + var $dataProvider = 'sybase'; + var $replaceQuote = "''"; // string to use to replace quotes + var $fmtDate = "'Y-m-d'"; + var $fmtTimeStamp = "'Y-m-d H:i:s'"; + var $hasInsertID = true; + var $hasAffectedRows = true; + var $metaTablesSQL="select name from sysobjects where type='U' or type='V'"; + // see http://sybooks.sybase.com/onlinebooks/group-aw/awg0800e/dbrfen8/@ebt-link;pt=5981;uf=0?target=0;window=new;showtoc=true;book=dbrfen8 + var $metaColumnsSQL = "SELECT c.column_name, c.column_type, c.width FROM syscolumn c, systable t WHERE t.table_name='%s' AND c.table_id=t.table_id AND t.table_type='BASE'"; + /* + "select c.name,t.name,c.length from + syscolumns c join systypes t on t.xusertype=c.xusertype join sysobjects o on o.id=c.id + where o.name='%s'"; + */ + var $concat_operator = '+'; + var $arrayClass = 'ADORecordSet_array_sybase'; + var $sysDate = 'GetDate()'; + var $leftOuter = '*='; + var $rightOuter = '=*'; + + function ADODB_sybase() + { + } + + // might require begintrans -- committrans + function _insertid() + { + return $this->GetOne('select @@identity'); + } + // might require begintrans -- committrans + function _affectedrows() + { + return $this->GetOne('select @@rowcount'); + } + + + function BeginTrans() + { + + if ($this->transOff) return true; + $this->transCnt += 1; + + $this->Execute('BEGIN TRAN'); + return true; + } + + function CommitTrans($ok=true) + { + if ($this->transOff) return true; + + if (!$ok) return $this->RollbackTrans(); + + $this->transCnt -= 1; + $this->Execute('COMMIT TRAN'); + return true; + } + + function RollbackTrans() + { + if ($this->transOff) return true; + $this->transCnt -= 1; + $this->Execute('ROLLBACK TRAN'); + return true; + } + + // http://www.isug.com/Sybase_FAQ/ASE/section6.1.html#6.1.4 + function RowLock($tables,$where,$flds='top 1 null as ignore') + { + if (!$this->_hastrans) $this->BeginTrans(); + $tables = str_replace(',',' HOLDLOCK,',$tables); + return $this->GetOne("select $flds from $tables HOLDLOCK where $where"); + + } + + function SelectDB($dbName) + { + $this->database = $dbName; + $this->databaseName = $dbName; # obsolete, retained for compat with older adodb versions + if ($this->_connectionID) { + return @sybase_select_db($dbName); + } + else return false; + } + + /* Returns: the last error message from previous database operation + Note: This function is NOT available for Microsoft SQL Server. */ + + + function ErrorMsg() + { + if ($this->_logsql) return $this->_errorMsg; + if (function_exists('sybase_get_last_message')) + $this->_errorMsg = sybase_get_last_message(); + else + $this->_errorMsg = isset($php_errormsg) ? $php_errormsg : 'SYBASE error messages not supported on this platform'; + return $this->_errorMsg; + } + + // returns true or false + function _connect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + if (!function_exists('sybase_connect')) return null; + + $this->_connectionID = sybase_connect($argHostname,$argUsername,$argPassword); + if ($this->_connectionID === false) return false; + if ($argDatabasename) return $this->SelectDB($argDatabasename); + return true; + } + // returns true or false + function _pconnect($argHostname, $argUsername, $argPassword, $argDatabasename) + { + if (!function_exists('sybase_connect')) return null; + + $this->_connectionID = sybase_pconnect($argHostname,$argUsername,$argPassword); + if ($this->_connectionID === false) return false; + if ($argDatabasename) return $this->SelectDB($argDatabasename); + return true; + } + + // returns query ID if successful, otherwise false + function _query($sql,$inputarr) + { + global $ADODB_COUNTRECS; + + if ($ADODB_COUNTRECS == false && ADODB_PHPVER >= 0x4300) + return sybase_unbuffered_query($sql,$this->_connectionID); + else + return sybase_query($sql,$this->_connectionID); + } + + // See http://www.isug.com/Sybase_FAQ/ASE/section6.2.html#6.2.12 + function &SelectLimit($sql,$nrows=-1,$offset=-1,$inputarr=false,$secs2cache=0) + { + if ($secs2cache > 0) {// we do not cache rowcount, so we have to load entire recordset + $rs =& ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); + return $rs; + } + + $nrows = (integer) $nrows; + $offset = (integer) $offset; + + $cnt = ($nrows >= 0) ? $nrows : 999999999; + if ($offset > 0 && $cnt) $cnt += $offset; + + $this->Execute("set rowcount $cnt"); + $rs =& ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,0); + $this->Execute("set rowcount 0"); + + return $rs; + } + + // returns true or false + function _close() + { + return @sybase_close($this->_connectionID); + } + + function UnixDate($v) + { + return ADORecordSet_array_sybase::UnixDate($v); + } + + function UnixTimeStamp($v) + { + return ADORecordSet_array_sybase::UnixTimeStamp($v); + } + + + + # Added 2003-10-05 by Chris Phillipson + # Used ASA SQL Reference Manual -- http://sybooks.sybase.com/onlinebooks/group-aw/awg0800e/dbrfen8/@ebt-link;pt=16756?target=%25N%15_12018_START_RESTART_N%25 + # to convert similar Microsoft SQL*Server (mssql) API into Sybase compatible version + // Format date column in sql string given an input format that understands Y M D + function SQLDate($fmt, $col=false) + { + if (!$col) $col = $this->sysTimeStamp; + $s = ''; + + $len = strlen($fmt); + for ($i=0; $i < $len; $i++) { + if ($s) $s .= '+'; + $ch = $fmt[$i]; + switch($ch) { + case 'Y': + case 'y': + $s .= "datename(yy,$col)"; + break; + case 'M': + $s .= "convert(char(3),$col,0)"; + break; + case 'm': + $s .= "replace(str(month($col),2),' ','0')"; + break; + case 'Q': + case 'q': + $s .= "datename(qq,$col)"; + break; + case 'D': + case 'd': + $s .= "replace(str(datepart(dd,$col),2),' ','0')"; + break; + case 'h': + $s .= "substring(convert(char(14),$col,0),13,2)"; + break; + + case 'H': + $s .= "replace(str(datepart(hh,$col),2),' ','0')"; + break; + + case 'i': + $s .= "replace(str(datepart(mi,$col),2),' ','0')"; + break; + case 's': + $s .= "replace(str(datepart(ss,$col),2),' ','0')"; + break; + case 'a': + case 'A': + $s .= "substring(convert(char(19),$col,0),18,2)"; + break; + + default: + if ($ch == '\\') { + $i++; + $ch = substr($fmt,$i,1); + } + $s .= $this->qstr($ch); + break; + } + } + return $s; + } + + # Added 2003-10-07 by Chris Phillipson + # Used ASA SQL Reference Manual -- http://sybooks.sybase.com/onlinebooks/group-aw/awg0800e/dbrfen8/@ebt-link;pt=5981;uf=0?target=0;window=new;showtoc=true;book=dbrfen8 + # to convert similar Microsoft SQL*Server (mssql) API into Sybase compatible version + function MetaPrimaryKeys($table) + { + $sql = "SELECT c.column_name " . + "FROM syscolumn c, systable t " . + "WHERE t.table_name='$table' AND c.table_id=t.table_id " . + "AND t.table_type='BASE' " . + "AND c.pkey = 'Y' " . + "ORDER BY c.column_id"; + + $a = $this->GetCol($sql); + if ($a && sizeof($a)>0) return $a; + return false; + } +} + +/*-------------------------------------------------------------------------------------- + Class Name: Recordset +--------------------------------------------------------------------------------------*/ +global $ADODB_sybase_mths; +$ADODB_sybase_mths = array( + 'JAN'=>1,'FEB'=>2,'MAR'=>3,'APR'=>4,'MAY'=>5,'JUN'=>6, + 'JUL'=>7,'AUG'=>8,'SEP'=>9,'OCT'=>10,'NOV'=>11,'DEC'=>12); + +class ADORecordset_sybase extends ADORecordSet { + + var $databaseType = "sybase"; + var $canSeek = true; + // _mths works only in non-localised system + var $_mths = array('JAN'=>1,'FEB'=>2,'MAR'=>3,'APR'=>4,'MAY'=>5,'JUN'=>6,'JUL'=>7,'AUG'=>8,'SEP'=>9,'OCT'=>10,'NOV'=>11,'DEC'=>12); + + function ADORecordset_sybase($id,$mode=false) + { + if ($mode === false) { + global $ADODB_FETCH_MODE; + $mode = $ADODB_FETCH_MODE; + } + if (!$mode) $this->fetchMode = ADODB_FETCH_ASSOC; + else $this->fetchMode = $mode; + $this->ADORecordSet($id,$mode); + } + + /* Returns: an object containing field information. + Get column information in the Recordset object. fetchField() can be used in order to obtain information about + fields in a certain query result. If the field offset isn't specified, the next field that wasn't yet retrieved by + fetchField() is retrieved. */ + function &FetchField($fieldOffset = -1) + { + if ($fieldOffset != -1) { + $o = @sybase_fetch_field($this->_queryID, $fieldOffset); + } + else if ($fieldOffset == -1) { /* The $fieldOffset argument is not provided thus its -1 */ + $o = @sybase_fetch_field($this->_queryID); + } + // older versions of PHP did not support type, only numeric + if ($o && !isset($o->type)) $o->type = ($o->numeric) ? 'float' : 'varchar'; + return $o; + } + + function _initrs() + { + global $ADODB_COUNTRECS; + $this->_numOfRows = ($ADODB_COUNTRECS)? @sybase_num_rows($this->_queryID):-1; + $this->_numOfFields = @sybase_num_fields($this->_queryID); + } + + function _seek($row) + { + return @sybase_data_seek($this->_queryID, $row); + } + + function _fetch($ignore_fields=false) + { + if ($this->fetchMode == ADODB_FETCH_NUM) { + $this->fields = @sybase_fetch_row($this->_queryID); + } else if ($this->fetchMode == ADODB_FETCH_ASSOC) { + $this->fields = @sybase_fetch_row($this->_queryID); + if (is_array($this->fields)) { + $this->fields = $this->GetRowAssoc(ADODB_ASSOC_CASE); + return true; + } + return false; + } else { + $this->fields = @sybase_fetch_array($this->_queryID); + } + if ( is_array($this->fields)) { + return true; + } + + return false; + } + + /* close() only needs to be called if you are worried about using too much memory while your script + is running. All associated result memory for the specified result identifier will automatically be freed. */ + function _close() { + return @sybase_free_result($this->_queryID); + } + + // sybase/mssql uses a default date like Dec 30 2000 12:00AM + function UnixDate($v) + { + return ADORecordSet_array_sybase::UnixDate($v); + } + + function UnixTimeStamp($v) + { + return ADORecordSet_array_sybase::UnixTimeStamp($v); + } +} + +class ADORecordSet_array_sybase extends ADORecordSet_array { + function ADORecordSet_array_sybase($id=-1) + { + $this->ADORecordSet_array($id); + } + + // sybase/mssql uses a default date like Dec 30 2000 12:00AM + function UnixDate($v) + { + global $ADODB_sybase_mths; + + //Dec 30 2000 12:00AM + if (!ereg( "([A-Za-z]{3})[-/\. ]+([0-9]{1,2})[-/\. ]+([0-9]{4})" + ,$v, $rr)) return parent::UnixDate($v); + + if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0; + + $themth = substr(strtoupper($rr[1]),0,3); + $themth = $ADODB_sybase_mths[$themth]; + if ($themth <= 0) return false; + // h-m-s-MM-DD-YY + return mktime(0,0,0,$themth,$rr[2],$rr[3]); + } + + function UnixTimeStamp($v) + { + global $ADODB_sybase_mths; + //11.02.2001 Toni Tunkkari toni.tunkkari@finebyte.com + //Changed [0-9] to [0-9 ] in day conversion + if (!ereg( "([A-Za-z]{3})[-/\. ]([0-9 ]{1,2})[-/\. ]([0-9]{4}) +([0-9]{1,2}):([0-9]{1,2}) *([apAP]{0,1})" + ,$v, $rr)) return parent::UnixTimeStamp($v); + if ($rr[3] <= TIMESTAMP_FIRST_YEAR) return 0; + + $themth = substr(strtoupper($rr[1]),0,3); + $themth = $ADODB_sybase_mths[$themth]; + if ($themth <= 0) return false; + + switch (strtoupper($rr[6])) { + case 'P': + if ($rr[4]<12) $rr[4] += 12; + break; + case 'A': + if ($rr[4]==12) $rr[4] = 0; + break; + default: + break; + } + // h-m-s-MM-DD-YY + return mktime($rr[4],$rr[5],0,$themth,$rr[2],$rr[3]); + } +} +?> diff --git a/framework/DataAccess/adodb/drivers/adodb-sybase_ase.inc.php b/framework/DataAccess/adodb/drivers/adodb-sybase_ase.inc.php new file mode 100644 index 00000000..f19e5b1b --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-sybase_ase.inc.php @@ -0,0 +1,115 @@ +<?php +/* + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + + Set tabs to 4. + + Contributed by Interakt Online. Thx Cristian MARIN cristic#interaktonline.com +*/ +class ADODB_sybase_ase extends ADODB_sybase { + var $databaseType = "sybase_ase"; + + var $metaTablesSQL="SELECT sysobjects.name FROM sysobjects, sysusers WHERE sysobjects.type='U' AND sysobjects.uid = sysusers.uid"; + var $metaColumnsSQL = "SELECT syscolumns.name AS field_name, systypes.name AS type, systypes.length AS width FROM sysobjects, syscolumns, systypes WHERE sysobjects.name='%s' AND syscolumns.id = sysobjects.id AND systypes.type=syscolumns.type"; + var $metaDatabasesSQL ="SELECT a.name FROM master.dbo.sysdatabases a, master.dbo.syslogins b WHERE a.suid = b.suid and a.name like '%' and a.name != 'tempdb' and a.status3 != 256 order by 1"; + + function ADODB_sybase_ase() + { + } + + // split the Views, Tables and procedures. + function &MetaTables($ttype=false,$showSchema=false,$mask=false) + { + $false = false; + if ($this->metaTablesSQL) { + // complicated state saving by the need for backward compat + + if ($ttype == 'VIEWS'){ + $sql = str_replace('U', 'V', $this->metaTablesSQL); + }elseif (false === $ttype){ + $sql = str_replace('U',"U' OR type='V", $this->metaTablesSQL); + }else{ // TABLES OR ANY OTHER + $sql = $this->metaTablesSQL; + } + $rs = $this->Execute($sql); + + if ($rs === false || !method_exists($rs, 'GetArray')){ + return $false; + } + $arr =& $rs->GetArray(); + + $arr2 = array(); + foreach($arr as $key=>$value){ + $arr2[] = trim($value['name']); + } + return $arr2; + } + return $false; + } + + function MetaDatabases() + { + $arr = array(); + if ($this->metaDatabasesSQL!='') { + $rs = $this->Execute($this->metaDatabasesSQL); + if ($rs && !$rs->EOF){ + while (!$rs->EOF){ + $arr[] = $rs->Fields('name'); + $rs->MoveNext(); + } + return $arr; + } + } + return false; + } + + // fix a bug which prevent the metaColumns query to be executed for Sybase ASE + function &MetaColumns($table,$upper=false) + { + $false = false; + if (!empty($this->metaColumnsSQL)) { + + $rs = $this->Execute(sprintf($this->metaColumnsSQL,$table)); + if ($rs === false) return $false; + + $retarr = array(); + while (!$rs->EOF) { + $fld =& new ADOFieldObject(); + $fld->name = $rs->Fields('field_name'); + $fld->type = $rs->Fields('type'); + $fld->max_length = $rs->Fields('width'); + $retarr[strtoupper($fld->name)] = $fld; + $rs->MoveNext(); + } + $rs->Close(); + return $retarr; + } + return $false; + } + + function getProcedureList($schema) + { + return false; + } + + function ErrorMsg() + { + if (!function_exists('sybase_connect')){ + return 'Your PHP doesn\'t contain the Sybase connection module!'; + } + return parent::ErrorMsg(); + } +} + +class adorecordset_sybase_ase extends ADORecordset_sybase { +var $databaseType = "sybase_ase"; +function ADORecordset_sybase_ase($id,$mode=false) + { + $this->ADORecordSet_sybase($id,$mode); + } + +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/drivers/adodb-vfp.inc.php b/framework/DataAccess/adodb/drivers/adodb-vfp.inc.php new file mode 100644 index 00000000..b9a632f2 --- /dev/null +++ b/framework/DataAccess/adodb/drivers/adodb-vfp.inc.php @@ -0,0 +1,107 @@ +<?php +/* +V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. +Set tabs to 4 for best viewing. + + Latest version is available at http://adodb.sourceforge.net + + Microsoft Visual FoxPro data driver. Requires ODBC. Works only on MS Windows. +*/ + +// security - hide paths +if (!defined('ADODB_DIR')) die(); + +if (!defined('_ADODB_ODBC_LAYER')) { + include(ADODB_DIR."/drivers/adodb-odbc.inc.php"); +} +if (!defined('ADODB_VFP')){ +define('ADODB_VFP',1); +class ADODB_vfp extends ADODB_odbc { + var $databaseType = "vfp"; + var $fmtDate = "{^Y-m-d}"; + var $fmtTimeStamp = "{^Y-m-d, h:i:sA}"; + var $replaceQuote = "'+chr(39)+'" ; + var $true = '.T.'; + var $false = '.F.'; + var $hasTop = 'top'; // support mssql SELECT TOP 10 * FROM TABLE + var $_bindInputArray = false; // strangely enough, setting to true does not work reliably + var $sysTimeStamp = 'datetime()'; + var $sysDate = 'date()'; + var $ansiOuter = true; + var $hasTransactions = false; + var $curmode = false ; // See sqlext.h, SQL_CUR_DEFAULT == SQL_CUR_USE_DRIVER == 2L + + function ADODB_vfp() + { + $this->ADODB_odbc(); + } + + function Time() + { + return time(); + } + + function BeginTrans() { return false;} + + // quote string to be sent back to database + function qstr($s,$nofixquotes=false) + { + if (!$nofixquotes) return "'".str_replace("\r\n","'+chr(13)+'",str_replace("'",$this->replaceQuote,$s))."'"; + return "'".$s."'"; + } + + + // TOP requires ORDER BY for VFP + function &SelectLimit($sql,$nrows=-1,$offset=-1, $inputarr=false,$secs2cache=0) + { + $this->hasTop = preg_match('/ORDER[ \t\r\n]+BY/is',$sql) ? 'top' : false; + $ret = ADOConnection::SelectLimit($sql,$nrows,$offset,$inputarr,$secs2cache); + return $ret; + } + + + +}; + + +class ADORecordSet_vfp extends ADORecordSet_odbc { + + var $databaseType = "vfp"; + + + function ADORecordSet_vfp($id,$mode=false) + { + return $this->ADORecordSet_odbc($id,$mode); + } + + function MetaType($t,$len=-1) + { + if (is_object($t)) { + $fieldobj = $t; + $t = $fieldobj->type; + $len = $fieldobj->max_length; + } + switch (strtoupper($t)) { + case 'C': + if ($len <= $this->blobSize) return 'C'; + case 'M': + return 'X'; + + case 'D': return 'D'; + + case 'T': return 'T'; + + case 'L': return 'L'; + + case 'I': return 'I'; + + default: return 'N'; + } + } +} + +} //define +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-ar.inc.php b/framework/DataAccess/adodb/lang/adodb-ar.inc.php new file mode 100644 index 00000000..dd0f830f --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-ar.inc.php @@ -0,0 +1,34 @@ +<?php +// by "El-Shamaa, Khaled" <k.el-shamaa#cgiar.org> +$ADODB_LANG_ARRAY = array ( + 'LANG' => 'ar', + DB_ERROR => 'ÎØà ÛíÑ ãÍÏÏ', + DB_ERROR_ALREADY_EXISTS => 'ãæÌæÏ ãÓÈÞÇ', + DB_ERROR_CANNOT_CREATE => 'áÇ íãßä ÅäÔÇÁ', + DB_ERROR_CANNOT_DELETE => 'áÇ íãßä ÍÐÝ', + DB_ERROR_CANNOT_DROP => 'áÇ íãßä ÍÐÝ', + DB_ERROR_CONSTRAINT => 'ÚãáíÉ ÅÏÎÇá ããäæÚÉ', + DB_ERROR_DIVZERO => 'ÚãáíÉ ÇáÊÞÓíã Úáì ÕÝÑ', + DB_ERROR_INVALID => 'ÛíÑ ÕÍíÍ', + DB_ERROR_INVALID_DATE => 'ÕíÛÉ æÞÊ Ãæ ÊÇÑíÎ ÛíÑ ÕÍíÍÉ', + DB_ERROR_INVALID_NUMBER => 'ÕíÛÉ ÑÞã ÛíÑ ÕÍíÍÉ', + DB_ERROR_MISMATCH => 'ÛíÑ ãÊØÇÈÞ', + DB_ERROR_NODBSELECTED => 'áã íÊã ÅÎÊíÇÑ ÞÇÚÏÉ ÇáÈíÇäÇÊ ÈÚÏ', + DB_ERROR_NOSUCHFIELD => 'áíÓ åäÇáß ÍÞá ÈåÐÇ ÇáÇÓã', + DB_ERROR_NOSUCHTABLE => 'áíÓ åäÇáß ÌÏæá ÈåÐÇ ÇáÇÓã', + DB_ERROR_NOT_CAPABLE => 'ÞÇÚÏÉ ÇáÈíÇäÇÊ ÇáãÑÊÈØ ÈåÇ ÛíÑ ÞÇÏÑÉ', + DB_ERROR_NOT_FOUND => 'áã íÊã ÅíÌÇÏå', + DB_ERROR_NOT_LOCKED => 'ÛíÑ ãÞÝæá', + DB_ERROR_SYNTAX => 'ÎØà Ýí ÇáÕíÛÉ', + DB_ERROR_UNSUPPORTED => 'ÛíÑ ãÏÚæã', + DB_ERROR_VALUE_COUNT_ON_ROW => 'ÚÏÏ ÇáÞíã Ýí ÇáÓÌá', + DB_ERROR_INVALID_DSN => 'DSN ÛíÑ ÕÍíÍ', + DB_ERROR_CONNECT_FAILED => 'ÝÔá ÚãáíÉ ÇáÅÊÕÇá', + 0 => 'áíÓ åäÇáß ÃÎØÇÁ', // DB_OK + DB_ERROR_NEED_MORE_DATA => 'ÇáÈíÇäÇÊ ÇáãÒæÏÉ ÛíÑ ßÇÝíÉ', + DB_ERROR_EXTENSION_NOT_FOUND=> 'áã íÊã ÅíÌÇÏ ÇáÅÖÇÝÉ ÇáãÊÚáÞÉ', + DB_ERROR_NOSUCHDB => 'áíÓ åäÇáß ÞÇÚÏÉ ÈíÇäÇÊ ÈåÐÇ ÇáÇÓã', + DB_ERROR_ACCESS_VIOLATION => 'ÓãÇÍíÇÊ ÛíÑ ßÇÝíÉ' +); +?> + diff --git a/framework/DataAccess/adodb/lang/adodb-bg.inc.php b/framework/DataAccess/adodb/lang/adodb-bg.inc.php new file mode 100644 index 00000000..4fb1dc07 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-bg.inc.php @@ -0,0 +1,38 @@ +<?php +/* + Bulgarian language, v1.0, 25.03.2004, encoding by Windows-1251 charset + contributed by Valentin Sheiretsky <valio#valio.eu.org> +*/ + +$ADODB_LANG_ARRAY = array ( + 'LANG' => 'bg', + DB_ERROR => 'íåèçâåñòíà ãðåøêà', + DB_ERROR_ALREADY_EXISTS => 'âå÷å ñúùåñòâóâà', + DB_ERROR_CANNOT_CREATE => 'íå ìîæå äà áúäå ñúçäàäåíà', + DB_ERROR_CANNOT_DELETE => 'íå ìîæå äà áúäå èçòðèòà', + DB_ERROR_CANNOT_DROP => 'íå ìîæå äà áúäå óíèùîæåíà', + DB_ERROR_CONSTRAINT => 'íàðóøåíî óñëîâèå', + DB_ERROR_DIVZERO => 'äåëåíèå íà íóëà', + DB_ERROR_INVALID => 'íåïðàâèëíî', + DB_ERROR_INVALID_DATE => 'íåêîðåêòíà äàòà èëè ÷àñ', + DB_ERROR_INVALID_NUMBER => 'íåâàëèäåí íîìåð', + DB_ERROR_MISMATCH => 'ïîãðåøíà óïîòðåáà', + DB_ERROR_NODBSELECTED => 'íå å èçáðàíà áàçà äàííè', + DB_ERROR_NOSUCHFIELD => 'íåñúùåñòâóâàùî ïîëå', + DB_ERROR_NOSUCHTABLE => 'íåñúùåñòâóâàùà òàáëèöà', + DB_ERROR_NOT_CAPABLE => 'DB backend not capable', + DB_ERROR_NOT_FOUND => 'íå å íàìåðåíà', + DB_ERROR_NOT_LOCKED => 'íå å çàêëþ÷åíà', + DB_ERROR_SYNTAX => 'ãðåøåí ñèíòàêñèñ', + DB_ERROR_UNSUPPORTED => 'íå ñå ïîääúðæà', + DB_ERROR_VALUE_COUNT_ON_ROW => 'íåêîðåêòåí áðîé êîëîíè â ðåäà', + DB_ERROR_INVALID_DSN => 'íåâàëèäåí DSN', + DB_ERROR_CONNECT_FAILED => 'âðúçêàòà íå ìîæå äà áúäå îñúùåñòâåíà', + 0 => 'íÿìà ãðåøêè', // DB_OK + DB_ERROR_NEED_MORE_DATA => 'ïðåäîñòàâåíèòå äàííè ñà íåäîñòàòú÷íè', + DB_ERROR_EXTENSION_NOT_FOUND=> 'ðàçøèðåíèåòî íå å íàìåðåíî', + DB_ERROR_NOSUCHDB => 'íåñúùåñòâóâàùà áàçà äàííè', + DB_ERROR_ACCESS_VIOLATION => 'íÿìàòå äîñòàòú÷íî ïðàâà' +); +?> +
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-bgutf8.inc.php b/framework/DataAccess/adodb/lang/adodb-bgutf8.inc.php new file mode 100644 index 00000000..6f3a4173 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-bgutf8.inc.php @@ -0,0 +1,38 @@ +<?php +/* + Bulgarian language, v1.0, 25.03.2004, encoding by UTF-8 charset + contributed by Valentin Sheiretsky <valio#valio.eu.org> +*/ + +$ADODB_LANG_ARRAY = array ( + 'LANG' => 'bgutf8', + DB_ERROR => 'неизвеÑтна грешка', + DB_ERROR_ALREADY_EXISTS => 'вече ÑъщеÑтвува', + DB_ERROR_CANNOT_CREATE => 'не може да бъде Ñъздадена', + DB_ERROR_CANNOT_DELETE => 'не може да бъде изтрита', + DB_ERROR_CANNOT_DROP => 'не може да бъде унищожена', + DB_ERROR_CONSTRAINT => 'нарушено уÑловие', + DB_ERROR_DIVZERO => 'деление на нула', + DB_ERROR_INVALID => 'неправилно', + DB_ERROR_INVALID_DATE => 'некоректна дата или чаÑ', + DB_ERROR_INVALID_NUMBER => 'невалиден номер', + DB_ERROR_MISMATCH => 'погрешна употреба', + DB_ERROR_NODBSELECTED => 'не е избрана база данни', + DB_ERROR_NOSUCHFIELD => 'неÑъщеÑтвуващо поле', + DB_ERROR_NOSUCHTABLE => 'неÑъщеÑтвуваща таблица', + DB_ERROR_NOT_CAPABLE => 'DB backend not capable', + DB_ERROR_NOT_FOUND => 'не е намерена', + DB_ERROR_NOT_LOCKED => 'не е заключена', + DB_ERROR_SYNTAX => 'грешен ÑинтакÑиÑ', + DB_ERROR_UNSUPPORTED => 'не Ñе поддържа', + DB_ERROR_VALUE_COUNT_ON_ROW => 'некоректен брой колони в реда', + DB_ERROR_INVALID_DSN => 'невалиден DSN', + DB_ERROR_CONNECT_FAILED => 'връзката не може да бъде оÑъщеÑтвена', + 0 => 'нÑма грешки', // DB_OK + DB_ERROR_NEED_MORE_DATA => 'предоÑтавените данни Ñа недоÑтатъчни', + DB_ERROR_EXTENSION_NOT_FOUND=> 'разширението не е намерено', + DB_ERROR_NOSUCHDB => 'неÑъщеÑтвуваща база данни', + DB_ERROR_ACCESS_VIOLATION => 'нÑмате доÑтатъчно права' +); +?> +
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-ca.inc.php b/framework/DataAccess/adodb/lang/adodb-ca.inc.php new file mode 100644 index 00000000..3e6a4496 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-ca.inc.php @@ -0,0 +1,35 @@ +<?php
+// Catalan language
+// contributed by "Josep Lladonosa" jlladono#pie.xtec.es
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'ca',
+ DB_ERROR => 'error desconegut',
+ DB_ERROR_ALREADY_EXISTS => 'ja existeix',
+ DB_ERROR_CANNOT_CREATE => 'no es pot crear',
+ DB_ERROR_CANNOT_DELETE => 'no es pot esborrar',
+ DB_ERROR_CANNOT_DROP => 'no es pot eliminar',
+ DB_ERROR_CONSTRAINT => 'violació de constraint',
+ DB_ERROR_DIVZERO => 'divisió per zero',
+ DB_ERROR_INVALID => 'no és vàlid',
+ DB_ERROR_INVALID_DATE => 'la data o l\'hora no són vàlides',
+ DB_ERROR_INVALID_NUMBER => 'el nombre no és vàlid',
+ DB_ERROR_MISMATCH => 'no hi ha coincidència',
+ DB_ERROR_NODBSELECTED => 'cap base de dades seleccionada',
+ DB_ERROR_NOSUCHFIELD => 'camp inexistent',
+ DB_ERROR_NOSUCHTABLE => 'taula inexistent',
+ DB_ERROR_NOT_CAPABLE => 'l\'execució secundària de DB no pot',
+ DB_ERROR_NOT_FOUND => 'no trobat',
+ DB_ERROR_NOT_LOCKED => 'no blocat',
+ DB_ERROR_SYNTAX => 'error de sintaxi',
+ DB_ERROR_UNSUPPORTED => 'no suportat',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'el nombre de columnes no coincideix amb el nombre de valors en la fila',
+ DB_ERROR_INVALID_DSN => 'el DSN no és vàlid',
+ DB_ERROR_CONNECT_FAILED => 'connexió fallida',
+ 0 => 'cap error', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'les dades subministrades són insuficients',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'extensió no trobada',
+ DB_ERROR_NOSUCHDB => 'base de dades inexistent',
+ DB_ERROR_ACCESS_VIOLATION => 'permisos insuficients'
+);
+?>
+
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-cn.inc.php b/framework/DataAccess/adodb/lang/adodb-cn.inc.php new file mode 100644 index 00000000..eb8c7de5 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-cn.inc.php @@ -0,0 +1,35 @@ +<?php
+// Chinese language file contributed by "Cuiyan (cysoft)" cysoft#php.net.
+// Encode by GB2312
+// Simplified Chinese
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'cn',
+ DB_ERROR => 'δ֪´íÎó',
+ DB_ERROR_ALREADY_EXISTS => 'ÒѾ´æÔÚ',
+ DB_ERROR_CANNOT_CREATE => '²»ÄÜ´´½¨',
+ DB_ERROR_CANNOT_DELETE => '²»ÄÜɾ³ý',
+ DB_ERROR_CANNOT_DROP => '²»ÄܶªÆú',
+ DB_ERROR_CONSTRAINT => 'Ô¼ÊøÏÞÖÆ',
+ DB_ERROR_DIVZERO => '±»0³ý',
+ DB_ERROR_INVALID => 'ÎÞЧ',
+ DB_ERROR_INVALID_DATE => 'ÎÞЧµÄÈÕÆÚ»òÕßʱ¼ä',
+ DB_ERROR_INVALID_NUMBER => 'ÎÞЧµÄÊý×Ö',
+ DB_ERROR_MISMATCH => '²»Æ¥Åä',
+ DB_ERROR_NODBSELECTED => 'ûÓÐÊý¾Ý¿â±»Ñ¡Ôñ',
+ DB_ERROR_NOSUCHFIELD => 'ûÓÐÏàÓ¦µÄ×Ö¶Î',
+ DB_ERROR_NOSUCHTABLE => 'ûÓÐÏàÓ¦µÄ±í',
+ DB_ERROR_NOT_CAPABLE => 'Êý¾Ý¿âºǫ́²»¼æÈÝ',
+ DB_ERROR_NOT_FOUND => 'ûÓз¢ÏÖ',
+ DB_ERROR_NOT_LOCKED => 'ûÓб»Ëø¶¨',
+ DB_ERROR_SYNTAX => 'Óï·¨´íÎó',
+ DB_ERROR_UNSUPPORTED => '²»Ö§³Ö',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'ÔÚÐÐÉÏÀÛ¼ÆÖµ',
+ DB_ERROR_INVALID_DSN => 'ÎÞЧµÄÊý¾ÝÔ´ (DSN)',
+ DB_ERROR_CONNECT_FAILED => 'Á¬½Óʧ°Ü',
+ 0 => 'ûÓдíÎó', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'ÌṩµÄÊý¾Ý²»ÄÜ·ûºÏÒªÇó',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'À©Õ¹Ã»Óб»·¢ÏÖ',
+ DB_ERROR_NOSUCHDB => 'ûÓÐÏàÓ¦µÄÊý¾Ý¿â',
+ DB_ERROR_ACCESS_VIOLATION => 'ûÓкÏÊʵÄȨÏÞ'
+);
+?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-cz.inc.php b/framework/DataAccess/adodb/lang/adodb-cz.inc.php new file mode 100644 index 00000000..2424c244 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-cz.inc.php @@ -0,0 +1,40 @@ +<?php
+
+# Czech language, encoding by ISO 8859-2 charset (Iso Latin-2)
+# For convert to MS Windows use shell command:
+# iconv -f ISO_8859-2 -t CP1250 < adodb-cz.inc.php
+# For convert to ASCII use shell command:
+# unaccent ISO_8859-2 < adodb-cz.inc.php
+# v1.0, 19.06.2003 Kamil Jakubovic <jake@host.sk>
+
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'cz',
+ DB_ERROR => 'neznámá chyba',
+ DB_ERROR_ALREADY_EXISTS => 'ji? existuje',
+ DB_ERROR_CANNOT_CREATE => 'nelze vytvo?it',
+ DB_ERROR_CANNOT_DELETE => 'nelze smazat',
+ DB_ERROR_CANNOT_DROP => 'nelze odstranit',
+ DB_ERROR_CONSTRAINT => 'poru?ení omezující podmínky',
+ DB_ERROR_DIVZERO => 'd?lení nulou',
+ DB_ERROR_INVALID => 'neplatné',
+ DB_ERROR_INVALID_DATE => 'neplatné datum nebo ?as',
+ DB_ERROR_INVALID_NUMBER => 'neplatné ?íslo',
+ DB_ERROR_MISMATCH => 'nesouhlasí',
+ DB_ERROR_NODBSELECTED => '?ádná databáze není vybrána',
+ DB_ERROR_NOSUCHFIELD => 'pole nenalezeno',
+ DB_ERROR_NOSUCHTABLE => 'tabulka nenalezena',
+ DB_ERROR_NOT_CAPABLE => 'nepodporováno',
+ DB_ERROR_NOT_FOUND => 'nenalezeno',
+ DB_ERROR_NOT_LOCKED => 'nezam?eno',
+ DB_ERROR_SYNTAX => 'syntaktická chyba',
+ DB_ERROR_UNSUPPORTED => 'nepodporováno',
+ DB_ERROR_VALUE_COUNT_ON_ROW => '',
+ DB_ERROR_INVALID_DSN => 'neplatné DSN',
+ DB_ERROR_CONNECT_FAILED => 'p?ipojení selhalo',
+ 0 => 'bez chyb', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'málo zdrojových dat',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'roz?í?ení nenalezeno',
+ DB_ERROR_NOSUCHDB => 'databáze neexistuje',
+ DB_ERROR_ACCESS_VIOLATION => 'nedostate?ná práva'
+);
+?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-da.inc.php b/framework/DataAccess/adodb/lang/adodb-da.inc.php new file mode 100644 index 00000000..ca0e72d6 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-da.inc.php @@ -0,0 +1,33 @@ +<?php +// Arne Eckmann bananstat#users.sourceforge.net +$ADODB_LANG_ARRAY = array ( + 'LANG' => 'da', + DB_ERROR => 'ukendt fejl', + DB_ERROR_ALREADY_EXISTS => 'eksisterer allerede', + DB_ERROR_CANNOT_CREATE => 'kan ikke oprette', + DB_ERROR_CANNOT_DELETE => 'kan ikke slette', + DB_ERROR_CANNOT_DROP => 'kan ikke droppe', + DB_ERROR_CONSTRAINT => 'begrænsning krænket', + DB_ERROR_DIVZERO => 'division med nul', + DB_ERROR_INVALID => 'ugyldig', + DB_ERROR_INVALID_DATE => 'ugyldig dato eller klokkeslet', + DB_ERROR_INVALID_NUMBER => 'ugyldigt tal', + DB_ERROR_MISMATCH => 'mismatch', + DB_ERROR_NODBSELECTED => 'ingen database valgt', + DB_ERROR_NOSUCHFIELD => 'felt findes ikke', + DB_ERROR_NOSUCHTABLE => 'tabel findes ikke', + DB_ERROR_NOT_CAPABLE => 'DB backend opgav', + DB_ERROR_NOT_FOUND => 'ikke fundet', + DB_ERROR_NOT_LOCKED => 'ikke låst', + DB_ERROR_SYNTAX => 'syntaksfejl', + DB_ERROR_UNSUPPORTED => 'ikke understøttet', + DB_ERROR_VALUE_COUNT_ON_ROW => 'resulterende antal felter svarer ikke til forespørgslens antal felter', + DB_ERROR_INVALID_DSN => 'ugyldig DSN', + DB_ERROR_CONNECT_FAILED => 'tilslutning mislykkedes', + 0 => 'ingen fejl', // DB_OK + DB_ERROR_NEED_MORE_DATA => 'utilstrækkelige data angivet', + DB_ERROR_EXTENSION_NOT_FOUND=> 'udvidelse ikke fundet', + DB_ERROR_NOSUCHDB => 'database ikke fundet', + DB_ERROR_ACCESS_VIOLATION => 'utilstrækkelige rettigheder' +); +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-de.inc.php b/framework/DataAccess/adodb/lang/adodb-de.inc.php new file mode 100644 index 00000000..244cb2f6 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-de.inc.php @@ -0,0 +1,33 @@ +<?php
+// contributed by "Heinz Hombergs" <opn@hhombergs.de>
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'de',
+ DB_ERROR => 'Unbekannter Fehler',
+ DB_ERROR_ALREADY_EXISTS => 'existiert bereits',
+ DB_ERROR_CANNOT_CREATE => 'kann nicht erstellen',
+ DB_ERROR_CANNOT_DELETE => 'kann nicht löschen',
+ DB_ERROR_CANNOT_DROP => 'Tabelle oder Index konnte nicht gelöscht werden',
+ DB_ERROR_CONSTRAINT => 'Constraint Verletzung',
+ DB_ERROR_DIVZERO => 'Division durch Null',
+ DB_ERROR_INVALID => 'ung¨ltig',
+ DB_ERROR_INVALID_DATE => 'ung¨ltiges Datum oder Zeit',
+ DB_ERROR_INVALID_NUMBER => 'ung¨ltige Zahl',
+ DB_ERROR_MISMATCH => 'Unverträglichkeit',
+ DB_ERROR_NODBSELECTED => 'keine Dantebank ausgewählt',
+ DB_ERROR_NOSUCHFIELD => 'Feld nicht vorhanden',
+ DB_ERROR_NOSUCHTABLE => 'Tabelle nicht vorhanden',
+ DB_ERROR_NOT_CAPABLE => 'Funktion nicht installiert',
+ DB_ERROR_NOT_FOUND => 'nicht gefunden',
+ DB_ERROR_NOT_LOCKED => 'nicht gesperrt',
+ DB_ERROR_SYNTAX => 'Syntaxfehler',
+ DB_ERROR_UNSUPPORTED => 'nicht Unterst¨tzt',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'Anzahl der zur¨ckgelieferten Felder entspricht nicht der Anzahl der Felder in der Abfrage',
+ DB_ERROR_INVALID_DSN => 'ung¨ltiger DSN',
+ DB_ERROR_CONNECT_FAILED => 'Verbindung konnte nicht hergestellt werden',
+ 0 => 'kein Fehler', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'Nicht gen¨gend Daten geliefert',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'erweiterung nicht gefunden',
+ DB_ERROR_NOSUCHDB => 'keine Datenbank',
+ DB_ERROR_ACCESS_VIOLATION => 'ungen¨gende Rechte'
+);
+?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-en.inc.php b/framework/DataAccess/adodb/lang/adodb-en.inc.php new file mode 100644 index 00000000..ed1b8f19 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-en.inc.php @@ -0,0 +1,34 @@ +<?php + +$ADODB_LANG_ARRAY = array ( + 'LANG' => 'en', + DB_ERROR => 'unknown error', + DB_ERROR_ALREADY_EXISTS => 'already exists', + DB_ERROR_CANNOT_CREATE => 'can not create', + DB_ERROR_CANNOT_DELETE => 'can not delete', + DB_ERROR_CANNOT_DROP => 'can not drop', + DB_ERROR_CONSTRAINT => 'constraint violation', + DB_ERROR_DIVZERO => 'division by zero', + DB_ERROR_INVALID => 'invalid', + DB_ERROR_INVALID_DATE => 'invalid date or time', + DB_ERROR_INVALID_NUMBER => 'invalid number', + DB_ERROR_MISMATCH => 'mismatch', + DB_ERROR_NODBSELECTED => 'no database selected', + DB_ERROR_NOSUCHFIELD => 'no such field', + DB_ERROR_NOSUCHTABLE => 'no such table', + DB_ERROR_NOT_CAPABLE => 'DB backend not capable', + DB_ERROR_NOT_FOUND => 'not found', + DB_ERROR_NOT_LOCKED => 'not locked', + DB_ERROR_SYNTAX => 'syntax error', + DB_ERROR_UNSUPPORTED => 'not supported', + DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row', + DB_ERROR_INVALID_DSN => 'invalid DSN', + DB_ERROR_CONNECT_FAILED => 'connect failed', + 0 => 'no error', // DB_OK + DB_ERROR_NEED_MORE_DATA => 'insufficient data supplied', + DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found', + DB_ERROR_NOSUCHDB => 'no such database', + DB_ERROR_ACCESS_VIOLATION => 'insufficient permissions' +); +?> +
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-es.inc.php b/framework/DataAccess/adodb/lang/adodb-es.inc.php new file mode 100644 index 00000000..1e0afbb4 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-es.inc.php @@ -0,0 +1,33 @@ +<?php +// contributed by "Horacio Degiorgi" <horaciod@codigophp.com> +$ADODB_LANG_ARRAY = array ( + 'LANG' => 'es', + DB_ERROR => 'error desconocido', + DB_ERROR_ALREADY_EXISTS => 'ya existe', + DB_ERROR_CANNOT_CREATE => 'imposible crear', + DB_ERROR_CANNOT_DELETE => 'imposible borrar', + DB_ERROR_CANNOT_DROP => 'imposible hacer drop', + DB_ERROR_CONSTRAINT => 'violacion de constraint', + DB_ERROR_DIVZERO => 'division por cero', + DB_ERROR_INVALID => 'invalido', + DB_ERROR_INVALID_DATE => 'fecha u hora invalida', + DB_ERROR_INVALID_NUMBER => 'numero invalido', + DB_ERROR_MISMATCH => 'error', + DB_ERROR_NODBSELECTED => 'no hay base de datos seleccionada', + DB_ERROR_NOSUCHFIELD => 'campo invalido', + DB_ERROR_NOSUCHTABLE => 'tabla no existe', + DB_ERROR_NOT_CAPABLE => 'capacidad invalida para esta DB', + DB_ERROR_NOT_FOUND => 'no encontrado', + DB_ERROR_NOT_LOCKED => 'no bloqueado', + DB_ERROR_SYNTAX => 'error de sintaxis', + DB_ERROR_UNSUPPORTED => 'no soportado', + DB_ERROR_VALUE_COUNT_ON_ROW => 'la cantidad de columnas no corresponden a la cantidad de valores', + DB_ERROR_INVALID_DSN => 'DSN invalido', + DB_ERROR_CONNECT_FAILED => 'fallo la conexion', + 0 => 'sin error', // DB_OK + DB_ERROR_NEED_MORE_DATA => 'insuficientes datos', + DB_ERROR_EXTENSION_NOT_FOUND=> 'extension no encontrada', + DB_ERROR_NOSUCHDB => 'base de datos no encontrada', + DB_ERROR_ACCESS_VIOLATION => 'permisos insuficientes' +); +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-esperanto.inc.php b/framework/DataAccess/adodb/lang/adodb-esperanto.inc.php new file mode 100644 index 00000000..16ca00e2 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-esperanto.inc.php @@ -0,0 +1,35 @@ +<?php +// Vivu Esperanto cxiam! +// Traduko fare de Antono Vasiljev (anders[#]brainactive.org) + +$ADODB_LANG_ARRAY = array ( + 'LANG' => 'eo', + DB_ERROR => 'nekonata eraro', + DB_ERROR_ALREADY_EXISTS => 'jam ekzistas', + DB_ERROR_CANNOT_CREATE => 'maleblas krei', + DB_ERROR_CANNOT_DELETE => 'maleblas elimini', + DB_ERROR_CANNOT_DROP => 'maleblas elimini (drop)', + DB_ERROR_CONSTRAINT => 'rompo de kondicxoj de provo', + DB_ERROR_DIVZERO => 'divido per 0 (nul)', + DB_ERROR_INVALID => 'malregule', + DB_ERROR_INVALID_DATE => 'malregula dato kaj tempo', + DB_ERROR_INVALID_NUMBER => 'malregula nombro', + DB_ERROR_MISMATCH => 'eraro', + DB_ERROR_NODBSELECTED => 'datumbazo ne elektita', + DB_ERROR_NOSUCHFIELD => 'ne ekzistas kampo', + DB_ERROR_NOSUCHTABLE => 'ne ekzistas tabelo', + DB_ERROR_NOT_CAPABLE => 'DBMS ne povas', + DB_ERROR_NOT_FOUND => 'ne trovita', + DB_ERROR_NOT_LOCKED => 'ne blokita', + DB_ERROR_SYNTAX => 'sintaksa eraro', + DB_ERROR_UNSUPPORTED => 'ne apogata', + DB_ERROR_VALUE_COUNT_ON_ROW => 'nombrilo de valoroj en linio', + DB_ERROR_INVALID_DSN => 'malregula DSN-o', + DB_ERROR_CONNECT_FAILED => 'konekto malsukcesa', + 0 => 'cxio bone', // DB_OK + DB_ERROR_NEED_MORE_DATA => 'ne suficxe da datumo', + DB_ERROR_EXTENSION_NOT_FOUND=> 'etendo ne trovita', + DB_ERROR_NOSUCHDB => 'datumbazo ne ekzistas', + DB_ERROR_ACCESS_VIOLATION => 'ne suficxe da rajto por atingo' +); +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-fr.inc.php b/framework/DataAccess/adodb/lang/adodb-fr.inc.php new file mode 100644 index 00000000..066a2a5e --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-fr.inc.php @@ -0,0 +1,33 @@ +<?php
+
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'fr',
+ DB_ERROR => 'erreur inconnue',
+ DB_ERROR_ALREADY_EXISTS => 'existe déjà',
+ DB_ERROR_CANNOT_CREATE => 'crétion impossible',
+ DB_ERROR_CANNOT_DELETE => 'effacement impossible',
+ DB_ERROR_CANNOT_DROP => 'suppression impossible',
+ DB_ERROR_CONSTRAINT => 'violation de contrainte',
+ DB_ERROR_DIVZERO => 'division par zéro',
+ DB_ERROR_INVALID => 'invalide',
+ DB_ERROR_INVALID_DATE => 'date ou heure invalide',
+ DB_ERROR_INVALID_NUMBER => 'nombre invalide',
+ DB_ERROR_MISMATCH => 'erreur de concordance',
+ DB_ERROR_NODBSELECTED => 'pas de base de donnéessélectionnée',
+ DB_ERROR_NOSUCHFIELD => 'nom de colonne invalide',
+ DB_ERROR_NOSUCHTABLE => 'table ou vue inexistante',
+ DB_ERROR_NOT_CAPABLE => 'fonction optionnelle non installée',
+ DB_ERROR_NOT_FOUND => 'pas trouvé',
+ DB_ERROR_NOT_LOCKED => 'non verrouillé',
+ DB_ERROR_SYNTAX => 'erreur de syntaxe',
+ DB_ERROR_UNSUPPORTED => 'non supporté',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'valeur insérée trop grande pour colonne',
+ DB_ERROR_INVALID_DSN => 'DSN invalide',
+ DB_ERROR_CONNECT_FAILED => 'échec à la connexion',
+ 0 => "pas d'erreur", // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'données fournies insuffisantes',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'extension non trouvée',
+ DB_ERROR_NOSUCHDB => 'base de données inconnue',
+ DB_ERROR_ACCESS_VIOLATION => 'droits insuffisants'
+);
+?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-hu.inc.php b/framework/DataAccess/adodb/lang/adodb-hu.inc.php new file mode 100644 index 00000000..d6f0ef82 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-hu.inc.php @@ -0,0 +1,34 @@ +<?php +# Hungarian language, encoding by ISO 8859-2 charset (Iso Latin-2) +# Halászvári Gábor <g.halaszvari#portmax.hu> +$ADODB_LANG_ARRAY = array ( + 'LANG' => 'hu', + DB_ERROR => 'ismeretlen hiba', + DB_ERROR_ALREADY_EXISTS => 'már létezik', + DB_ERROR_CANNOT_CREATE => 'nem sikerült létrehozni', + DB_ERROR_CANNOT_DELETE => 'nem sikerült törölni', + DB_ERROR_CANNOT_DROP => 'nem sikerült eldobni', + DB_ERROR_CONSTRAINT => 'szabályok megszegése', + DB_ERROR_DIVZERO => 'osztás nullával', + DB_ERROR_INVALID => 'érvénytelen', + DB_ERROR_INVALID_DATE => 'érvénytelen dátum vagy idõ', + DB_ERROR_INVALID_NUMBER => 'érvénytelen szám', + DB_ERROR_MISMATCH => 'nem megfelelõ', + DB_ERROR_NODBSELECTED => 'nincs kiválasztott adatbázis', + DB_ERROR_NOSUCHFIELD => 'nincs ilyen mezõ', + DB_ERROR_NOSUCHTABLE => 'nincs ilyen tábla', + DB_ERROR_NOT_CAPABLE => 'DB backend nem támogatja', + DB_ERROR_NOT_FOUND => 'nem található', + DB_ERROR_NOT_LOCKED => 'nincs lezárva', + DB_ERROR_SYNTAX => 'szintaktikai hiba', + DB_ERROR_UNSUPPORTED => 'nem támogatott', + DB_ERROR_VALUE_COUNT_ON_ROW => 'soron végzett érték számlálás', + DB_ERROR_INVALID_DSN => 'hibás DSN', + DB_ERROR_CONNECT_FAILED => 'sikertelen csatlakozás', + 0 => 'nincs hiba', // DB_OK + DB_ERROR_NEED_MORE_DATA => 'túl kevés az adat', + DB_ERROR_EXTENSION_NOT_FOUND=> 'bõvítmény nem található', + DB_ERROR_NOSUCHDB => 'nincs ilyen adatbázis', + DB_ERROR_ACCESS_VIOLATION => 'nincs jogosultság' +); +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-it.inc.php b/framework/DataAccess/adodb/lang/adodb-it.inc.php new file mode 100644 index 00000000..20c5b93b --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-it.inc.php @@ -0,0 +1,34 @@ +<?php
+// Italian language file contributed by Tiraboschi Massimiliano aka TiMax
+// www.maxdev.com timax@maxdev.com
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'it',
+ DB_ERROR => 'errore sconosciuto',
+ DB_ERROR_ALREADY_EXISTS => 'esiste già',
+ DB_ERROR_CANNOT_CREATE => 'non posso creare',
+ DB_ERROR_CANNOT_DELETE => 'non posso cancellare',
+ DB_ERROR_CANNOT_DROP => 'non posso eliminare',
+ DB_ERROR_CONSTRAINT => 'violazione constraint',
+ DB_ERROR_DIVZERO => 'divisione per zero',
+ DB_ERROR_INVALID => 'non valido',
+ DB_ERROR_INVALID_DATE => 'data od ora non valida',
+ DB_ERROR_INVALID_NUMBER => 'numero non valido',
+ DB_ERROR_MISMATCH => 'diversi',
+ DB_ERROR_NODBSELECTED => 'nessun database selezionato',
+ DB_ERROR_NOSUCHFIELD => 'nessun campo trovato',
+ DB_ERROR_NOSUCHTABLE => 'nessuna tabella trovata',
+ DB_ERROR_NOT_CAPABLE => 'DB backend non abilitato',
+ DB_ERROR_NOT_FOUND => 'non trovato',
+ DB_ERROR_NOT_LOCKED => 'non bloccato',
+ DB_ERROR_SYNTAX => 'errore di sintassi',
+ DB_ERROR_UNSUPPORTED => 'non supportato',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'valore inserito troppo grande per una colonna',
+ DB_ERROR_INVALID_DSN => 'DSN non valido',
+ DB_ERROR_CONNECT_FAILED => 'connessione fallita',
+ 0 => 'nessun errore', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'dati inseriti insufficienti',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'estensione non trovata',
+ DB_ERROR_NOSUCHDB => 'database non trovato',
+ DB_ERROR_ACCESS_VIOLATION => 'permessi insufficienti'
+);
+?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-nl.inc.php b/framework/DataAccess/adodb/lang/adodb-nl.inc.php new file mode 100644 index 00000000..abe77b52 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-nl.inc.php @@ -0,0 +1,33 @@ +<?php +// Translated by Pim Koeman (pim#wittenborg-university.com) +$ADODB_LANG_ARRAY = array ( + 'LANG' => 'nl', + DB_ERROR => 'onbekende fout', + DB_ERROR_ALREADY_EXISTS => 'bestaat al', + DB_ERROR_CANNOT_CREATE => 'kan niet aanmaken', + DB_ERROR_CANNOT_DELETE => 'kan niet wissen', + DB_ERROR_CANNOT_DROP => 'kan niet verwijderen', + DB_ERROR_CONSTRAINT => 'constraint overtreding', + DB_ERROR_DIVZERO => 'poging tot delen door nul', + DB_ERROR_INVALID => 'ongeldig', + DB_ERROR_INVALID_DATE => 'ongeldige datum of tijd', + DB_ERROR_INVALID_NUMBER => 'ongeldig nummer', + DB_ERROR_MISMATCH => 'is incorrect', + DB_ERROR_NODBSELECTED => 'geen database geselecteerd', + DB_ERROR_NOSUCHFIELD => 'onbekend veld', + DB_ERROR_NOSUCHTABLE => 'onbekende tabel', + DB_ERROR_NOT_CAPABLE => 'database systeem is niet tot uitvoer in staat', + DB_ERROR_NOT_FOUND => 'niet gevonden', + DB_ERROR_NOT_LOCKED => 'niet vergrendeld', + DB_ERROR_SYNTAX => 'syntaxis fout', + DB_ERROR_UNSUPPORTED => 'niet ondersteund', + DB_ERROR_VALUE_COUNT_ON_ROW => 'waarde telling op rij', + DB_ERROR_INVALID_DSN => 'ongeldige DSN', + DB_ERROR_CONNECT_FAILED => 'connectie mislukt', + 0 => 'geen fout', // DB_OK + DB_ERROR_NEED_MORE_DATA => 'onvoldoende data gegeven', + DB_ERROR_EXTENSION_NOT_FOUND=> 'extensie niet gevonden', + DB_ERROR_NOSUCHDB => 'onbekende database', + DB_ERROR_ACCESS_VIOLATION => 'onvoldoende rechten' +); +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-pl.inc.php b/framework/DataAccess/adodb/lang/adodb-pl.inc.php new file mode 100644 index 00000000..aee5bfdf --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-pl.inc.php @@ -0,0 +1,36 @@ +<?php + +// Contributed by Grzegorz Pacan <gp#dione.cc> + +$ADODB_LANG_ARRAY = array ( + 'LANG' => 'pl', + DB_ERROR => 'niezidentyfikowany b³±d', + DB_ERROR_ALREADY_EXISTS => 'ju¿ istniej±', + DB_ERROR_CANNOT_CREATE => 'nie mo¿na stworzyæ', + DB_ERROR_CANNOT_DELETE => 'nie mo¿na usun±æ', + DB_ERROR_CANNOT_DROP => 'nie mo¿na porzuciæ', + DB_ERROR_CONSTRAINT => 'pogwa³cenie uprawnieñ', + DB_ERROR_DIVZERO => 'dzielenie przez zero', + DB_ERROR_INVALID => 'b³êdny', + DB_ERROR_INVALID_DATE => 'b³êdna godzina lub data', + DB_ERROR_INVALID_NUMBER => 'b³êdny numer', + DB_ERROR_MISMATCH => 'niedopasowanie', + DB_ERROR_NODBSELECTED => 'baza danych nie zosta³a wybrana', + DB_ERROR_NOSUCHFIELD => 'nie znaleziono pola', + DB_ERROR_NOSUCHTABLE => 'nie znaleziono tabeli', + DB_ERROR_NOT_CAPABLE => 'nie zdolny', + DB_ERROR_NOT_FOUND => 'nie znaleziono', + DB_ERROR_NOT_LOCKED => 'nie zakmniêty', + DB_ERROR_SYNTAX => 'b³±d sk³adni', + DB_ERROR_UNSUPPORTED => 'nie obs³uguje', + DB_ERROR_VALUE_COUNT_ON_ROW => 'warto¶æ liczona w szeregu', + DB_ERROR_INVALID_DSN => 'b³êdny DSN', + DB_ERROR_CONNECT_FAILED => 'po³±czenie nie zosta³o zrealizowane', + 0 => 'brak b³êdów', // DB_OK + DB_ERROR_NEED_MORE_DATA => 'niedostateczna ilo¶æ informacji', + DB_ERROR_EXTENSION_NOT_FOUND=> 'nie znaleziono rozszerzenia', + DB_ERROR_NOSUCHDB => 'nie znaleziono bazy', + DB_ERROR_ACCESS_VIOLATION => 'niedostateczne uprawnienia' +); +?> +
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-pt-br.inc.php b/framework/DataAccess/adodb/lang/adodb-pt-br.inc.php new file mode 100644 index 00000000..3424099a --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-pt-br.inc.php @@ -0,0 +1,35 @@ +<?php
+// contributed by "Levi Fukumori" levi _AT_ fukumori _DOT_ com _DOT_ br
+// portugese (brazilian)
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'pt-br',
+ DB_ERROR => 'erro desconhecido',
+ DB_ERROR_ALREADY_EXISTS => 'já existe',
+ DB_ERROR_CANNOT_CREATE => 'impossível criar',
+ DB_ERROR_CANNOT_DELETE => 'impossível excluír',
+ DB_ERROR_CANNOT_DROP => 'impossível remover',
+ DB_ERROR_CONSTRAINT => 'violação do confinamente',
+ DB_ERROR_DIVZERO => 'divisão por zero',
+ DB_ERROR_INVALID => 'inválido',
+ DB_ERROR_INVALID_DATE => 'data ou hora inválida',
+ DB_ERROR_INVALID_NUMBER => 'número inválido',
+ DB_ERROR_MISMATCH => 'erro',
+ DB_ERROR_NODBSELECTED => 'nenhum banco de dados selecionado',
+ DB_ERROR_NOSUCHFIELD => 'campo inválido',
+ DB_ERROR_NOSUCHTABLE => 'tabela inexistente',
+ DB_ERROR_NOT_CAPABLE => 'capacidade inválida para este BD',
+ DB_ERROR_NOT_FOUND => 'não encontrado',
+ DB_ERROR_NOT_LOCKED => 'não bloqueado',
+ DB_ERROR_SYNTAX => 'erro de sintaxe',
+ DB_ERROR_UNSUPPORTED =>
+'não suportado',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'a quantidade de colunas não corresponde ao de valores',
+ DB_ERROR_INVALID_DSN => 'DSN inválido',
+ DB_ERROR_CONNECT_FAILED => 'falha na conexão',
+ 0 => 'sem erro', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'dados insuficientes',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'extensão não encontrada',
+ DB_ERROR_NOSUCHDB => 'banco de dados não encontrado',
+ DB_ERROR_ACCESS_VIOLATION => 'permissão insuficiente'
+);
+?>
diff --git a/framework/DataAccess/adodb/lang/adodb-ro.inc.php b/framework/DataAccess/adodb/lang/adodb-ro.inc.php new file mode 100644 index 00000000..7c9aa522 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-ro.inc.php @@ -0,0 +1,36 @@ +<?php + +/* Romanian - by "bogdan stefan" <sbogdan#rsb.ro> */ + +$ADODB_LANG_ARRAY = array ( + 'LANG' => 'ro', + DB_ERROR => 'eroare necunoscuta', + DB_ERROR_ALREADY_EXISTS => 'deja exista', + DB_ERROR_CANNOT_CREATE => 'nu se poate creea', + DB_ERROR_CANNOT_DELETE => 'nu se poate sterge', + DB_ERROR_CANNOT_DROP => 'nu se poate executa drop', + DB_ERROR_CONSTRAINT => 'violare de constrain', + DB_ERROR_DIVZERO => 'se divide la zero', + DB_ERROR_INVALID => 'invalid', + DB_ERROR_INVALID_DATE => 'data sau timp invalide', + DB_ERROR_INVALID_NUMBER => 'numar invalid', + DB_ERROR_MISMATCH => 'nepotrivire-mismatch', + DB_ERROR_NODBSELECTED => 'nu exista baza de date selectata', + DB_ERROR_NOSUCHFIELD => 'camp inexistent', + DB_ERROR_NOSUCHTABLE => 'tabela inexistenta', + DB_ERROR_NOT_CAPABLE => 'functie optionala neinstalata', + DB_ERROR_NOT_FOUND => 'negasit', + DB_ERROR_NOT_LOCKED => 'neblocat', + DB_ERROR_SYNTAX => 'eroare de sintaxa', + DB_ERROR_UNSUPPORTED => 'nu e suportat', + DB_ERROR_VALUE_COUNT_ON_ROW => 'valoare prea mare pentru coloana', + DB_ERROR_INVALID_DSN => 'DSN invalid', + DB_ERROR_CONNECT_FAILED => 'conectare esuata', + 0 => 'fara eroare', // DB_OK + DB_ERROR_NEED_MORE_DATA => 'data introduse insuficiente', + DB_ERROR_EXTENSION_NOT_FOUND=> 'extensie negasita', + DB_ERROR_NOSUCHDB => 'nu exista baza de date', + DB_ERROR_ACCESS_VIOLATION => 'permisiuni insuficiente' +); +?> + diff --git a/framework/DataAccess/adodb/lang/adodb-ru1251.inc.php b/framework/DataAccess/adodb/lang/adodb-ru1251.inc.php new file mode 100644 index 00000000..3a20538a --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-ru1251.inc.php @@ -0,0 +1,35 @@ +<?php
+
+// Russian language file contributed by "Cyrill Malevanov" cyrill#malevanov.spb.ru.
+
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'ru1251',
+ DB_ERROR => 'íåèçâåñòíàÿ îøèáêà',
+ DB_ERROR_ALREADY_EXISTS => 'óæå ñóùåñòâóåò',
+ DB_ERROR_CANNOT_CREATE => 'íåâîçìîæíî ñîçäàòü',
+ DB_ERROR_CANNOT_DELETE => 'íåâîçìîæíî óäàëèòü',
+ DB_ERROR_CANNOT_DROP => 'íåâîçìîæíî óäàëèòü (drop)',
+ DB_ERROR_CONSTRAINT => 'íàðóøåíèå óñëîâèé ïðîâåðêè',
+ DB_ERROR_DIVZERO => 'äåëåíèå íà 0',
+ DB_ERROR_INVALID => 'íåïðàâèëüíî',
+ DB_ERROR_INVALID_DATE => 'íåêîððåêòíàÿ äàòà èëè âðåìÿ',
+ DB_ERROR_INVALID_NUMBER => 'íåêîððåêòíîå ÷èñëî',
+ DB_ERROR_MISMATCH => 'îøèáêà',
+ DB_ERROR_NODBSELECTED => 'ÁÄ íå âûáðàíà',
+ DB_ERROR_NOSUCHFIELD => 'íå ñóùåñòâóåò ïîëå',
+ DB_ERROR_NOSUCHTABLE => 'íå ñóùåñòâóåò òàáëèöà',
+ DB_ERROR_NOT_CAPABLE => 'ÑÓÁÄ íå â ñîñòîÿíèè',
+ DB_ERROR_NOT_FOUND => 'íå íàéäåíî',
+ DB_ERROR_NOT_LOCKED => 'íå çàáëîêèðîâàíî',
+ DB_ERROR_SYNTAX => 'ñèíòàêñè÷åñêàÿ îøèáêà',
+ DB_ERROR_UNSUPPORTED => 'íå ïîääåðæèâàåòñÿ',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'ñ÷åò÷èê çíà÷åíèé â ñòðîêå',
+ DB_ERROR_INVALID_DSN => 'íåïðàâèëüíàÿ DSN',
+ DB_ERROR_CONNECT_FAILED => 'ñîåäèíåíèå íåóñïåøíî',
+ 0 => 'íåò îøèáêè', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'ïðåäîñòàâëåíî íåäîñòàòî÷íî äàííûõ',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'ðàñøèðåíèå íå íàéäåíî',
+ DB_ERROR_NOSUCHDB => 'íå ñóùåñòâóåò ÁÄ',
+ DB_ERROR_ACCESS_VIOLATION => 'íåäîñòàòî÷íî ïðàâ äîñòóïà'
+);
+?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-sv.inc.php b/framework/DataAccess/adodb/lang/adodb-sv.inc.php new file mode 100644 index 00000000..a9fd6981 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-sv.inc.php @@ -0,0 +1,33 @@ +<?php
+// Christian Tiberg" christian@commsoft.nu
+$ADODB_LANG_ARRAY = array (
+ 'LANG' => 'en',
+ DB_ERROR => 'Okänt fel',
+ DB_ERROR_ALREADY_EXISTS => 'finns redan',
+ DB_ERROR_CANNOT_CREATE => 'kan inte skapa',
+ DB_ERROR_CANNOT_DELETE => 'kan inte ta bort',
+ DB_ERROR_CANNOT_DROP => 'kan inte släppa',
+ DB_ERROR_CONSTRAINT => 'begränsning kränkt',
+ DB_ERROR_DIVZERO => 'division med noll',
+ DB_ERROR_INVALID => 'ogiltig',
+ DB_ERROR_INVALID_DATE => 'ogiltigt datum eller tid',
+ DB_ERROR_INVALID_NUMBER => 'ogiltigt tal',
+ DB_ERROR_MISMATCH => 'felaktig matchning',
+ DB_ERROR_NODBSELECTED => 'ingen databas vald',
+ DB_ERROR_NOSUCHFIELD => 'inget sådant fält',
+ DB_ERROR_NOSUCHTABLE => 'ingen sådan tabell',
+ DB_ERROR_NOT_CAPABLE => 'DB backend klarar det inte',
+ DB_ERROR_NOT_FOUND => 'finns inte',
+ DB_ERROR_NOT_LOCKED => 'inte låst',
+ DB_ERROR_SYNTAX => 'syntaxfel',
+ DB_ERROR_UNSUPPORTED => 'stöds ej',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'värde räknat på rad',
+ DB_ERROR_INVALID_DSN => 'ogiltig DSN',
+ DB_ERROR_CONNECT_FAILED => 'anslutning misslyckades',
+ 0 => 'inget fel', // DB_OK
+ DB_ERROR_NEED_MORE_DATA => 'otillräckligt med data angivet',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'utökning hittades ej',
+ DB_ERROR_NOSUCHDB => 'ingen sådan databas',
+ DB_ERROR_ACCESS_VIOLATION => 'otillräckliga rättigheter'
+);
+?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/lang/adodb-uk1251.inc.php b/framework/DataAccess/adodb/lang/adodb-uk1251.inc.php new file mode 100644 index 00000000..9fa32ed5 --- /dev/null +++ b/framework/DataAccess/adodb/lang/adodb-uk1251.inc.php @@ -0,0 +1,35 @@ +<?php + +// Ukrainian language file contributed by Alex Rootoff rootoff{AT}pisem.net. + +$ADODB_LANG_ARRAY = array ( + 'LANG' => 'uk1251', + DB_ERROR => 'íåâ³äîìà ïîìèëêà', + DB_ERROR_ALREADY_EXISTS => 'âæå ³ñíóº', + DB_ERROR_CANNOT_CREATE => 'íåìîæëèâî ñòâîðèòè', + DB_ERROR_CANNOT_DELETE => 'íåìîæëèâî âèäàëèòè', + DB_ERROR_CANNOT_DROP => 'íåìîæëèâî çíèùèòè (drop)', + DB_ERROR_CONSTRAINT => 'ïîðóøåííÿ óìîâ ïåðåâ³ðêè', + DB_ERROR_DIVZERO => 'ä³ëåííÿ íà 0', + DB_ERROR_INVALID => 'íåïðàâèëüíî', + DB_ERROR_INVALID_DATE => 'íåïðàâèëüíà äàòà ÷è ÷àñ', + DB_ERROR_INVALID_NUMBER => 'íåïðàâèëüíå ÷èñëî', + DB_ERROR_MISMATCH => 'ïîìèëêà', + DB_ERROR_NODBSELECTED => 'íå âèáðàíî ÁÄ', + DB_ERROR_NOSUCHFIELD => 'íå ³ñíóº ïîëå', + DB_ERROR_NOSUCHTABLE => 'íå ³ñíóº òàáëèöÿ', + DB_ERROR_NOT_CAPABLE => 'ÑÓÁÄ íå â ñòàí³', + DB_ERROR_NOT_FOUND => 'íå çíàéäåíî', + DB_ERROR_NOT_LOCKED => 'íå çàáëîêîâàíî', + DB_ERROR_SYNTAX => 'ñèíòàêñè÷íà ïîìèëêà', + DB_ERROR_UNSUPPORTED => 'íå ï³äòðèìóºòüñÿ', + DB_ERROR_VALUE_COUNT_ON_ROW => 'ðàõ³âíèê çíà÷åíü â ñòð³÷ö³', + DB_ERROR_INVALID_DSN => 'íåïðàâèëüíà DSN', + DB_ERROR_CONNECT_FAILED => 'ç\'ºäíàííÿ íåóñï³øíå', + 0 => 'âñå ãàðàçä', // DB_OK + DB_ERROR_NEED_MORE_DATA => 'íàäàíî íåäîñòàòíüî äàíèõ', + DB_ERROR_EXTENSION_NOT_FOUND=> 'ðîçøèðåííÿ íå çíàéäåíî', + DB_ERROR_NOSUCHDB => 'íå ³ñíóº ÁÄ', + DB_ERROR_ACCESS_VIOLATION => 'íåäîñòàòíüî ïðàâ äîñòóïà' +); +?> diff --git a/framework/DataAccess/adodb/license.txt b/framework/DataAccess/adodb/license.txt new file mode 100644 index 00000000..2353871c --- /dev/null +++ b/framework/DataAccess/adodb/license.txt @@ -0,0 +1,182 @@ +ADOdb is dual licensed using BSD and LGPL.
+
+In plain English, you do not need to distribute your application in source code form, nor do you need to distribute ADOdb source code, provided you follow the rest of terms of the BSD license.
+
+For more info about ADOdb, visit http://adodb.sourceforge.net/
+
+BSD Style-License
+=================
+
+Copyright (c) 2000, 2001, 2002, 2003, 2004 John Lim
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this list
+of conditions and the following disclaimer in the documentation and/or other materials
+provided with the distribution.
+
+Neither the name of the John Lim nor the names of its contributors may be used to
+endorse or promote products derived from this software without specific prior written
+permission.
+
+DISCLAIMER:
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+JOHN LIM OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+==========================================================
+GNU LESSER GENERAL PUBLIC LICENSE
+Version 2.1, February 1999
+
+Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+
+Preamble
+The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users.
+
+This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below.
+
+When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things.
+
+To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it.
+
+For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights.
+
+We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library.
+
+To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others.
+
+Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license.
+
+Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs.
+
+When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library.
+
+We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances.
+
+For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License.
+
+In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system.
+
+Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library.
+
+The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run.
+
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you".
+
+A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables.
+
+The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".)
+
+"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library.
+
+Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does.
+
+1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library.
+
+You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
+
+
+a) The modified work must itself be a software library.
+b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change.
+c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License.
+d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful.
+(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
+
+3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices.
+
+Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy.
+
+This option is useful when you wish to copy part of the code of the Library into a program that is not a library.
+
+4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange.
+
+If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code.
+
+5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License.
+
+However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables.
+
+When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law.
+
+If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.)
+
+Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself.
+
+6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications.
+
+You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things:
+
+
+a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.)
+b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with.
+c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution.
+d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place.
+e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy.
+For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
+
+It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute.
+
+7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things:
+
+
+a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above.
+b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
+8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
+
+9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it.
+
+10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License.
+
+11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
+
+12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
+
+13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation.
+
+14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
+
+NO WARRANTY
+
+15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+
+END OF TERMS AND CONDITIONS
\ No newline at end of file diff --git a/framework/DataAccess/adodb/pivottable.inc.php b/framework/DataAccess/adodb/pivottable.inc.php new file mode 100644 index 00000000..4fa1875b --- /dev/null +++ b/framework/DataAccess/adodb/pivottable.inc.php @@ -0,0 +1,185 @@ +<?php +/** + * @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + * Released under both BSD license and Lesser GPL library license. + * Whenever there is any discrepancy between the two licenses, + * the BSD license will take precedence. + * + * Set tabs to 4 for best viewing. + * + * Latest version is available at http://php.weblogs.com + * + * Requires PHP4.01pl2 or later because it uses include_once +*/ + +/* + * Concept from daniel.lucazeau@ajornet.com. + * + * @param db Adodb database connection + * @param tables List of tables to join + * @rowfields List of fields to display on each row + * @colfield Pivot field to slice and display in columns, if we want to calculate + * ranges, we pass in an array (see example2) + * @where Where clause. Optional. + * @aggfield This is the field to sum. Optional. + * Since 2.3.1, if you can use your own aggregate function + * instead of SUM, eg. $aggfield = 'fieldname'; $aggfn = 'AVG'; + * @sumlabel Prefix to display in sum columns. Optional. + * @aggfn Aggregate function to use (could be AVG, SUM, COUNT) + * @showcount Show count of records + * + * @returns Sql generated + */ + + function PivotTableSQL(&$db,$tables,$rowfields,$colfield, $where=false, + $aggfield = false,$sumlabel='Sum ',$aggfn ='SUM', $showcount = true) + { + if ($aggfield) $hidecnt = true; + else $hidecnt = false; + + $iif = strpos($db->databaseType,'access') !== false; + // note - vfp still doesn' work even with IIF enabled || $db->databaseType == 'vfp'; + + //$hidecnt = false; + + if ($where) $where = "\nWHERE $where"; + if (!is_array($colfield)) $colarr = $db->GetCol("select distinct $colfield from $tables $where order by 1"); + if (!$aggfield) $hidecnt = false; + + $sel = "$rowfields, "; + if (is_array($colfield)) { + foreach ($colfield as $k => $v) { + $k = trim($k); + if (!$hidecnt) { + $sel .= $iif ? + "\n\t$aggfn(IIF($v,1,0)) AS \"$k\", " + : + "\n\t$aggfn(CASE WHEN $v THEN 1 ELSE 0 END) AS \"$k\", "; + } + if ($aggfield) { + $sel .= $iif ? + "\n\t$aggfn(IIF($v,$aggfield,0)) AS \"$sumlabel$k\", " + : + "\n\t$aggfn(CASE WHEN $v THEN $aggfield ELSE 0 END) AS \"$sumlabel$k\", "; + } + } + } else { + foreach ($colarr as $v) { + if (!is_numeric($v)) $vq = $db->qstr($v); + else $vq = $v; + $v = trim($v); + if (strlen($v) == 0 ) $v = 'null'; + if (!$hidecnt) { + $sel .= $iif ? + "\n\t$aggfn(IIF($colfield=$vq,1,0)) AS \"$v\", " + : + "\n\t$aggfn(CASE WHEN $colfield=$vq THEN 1 ELSE 0 END) AS \"$v\", "; + } + if ($aggfield) { + if ($hidecnt) $label = $v; + else $label = "{$v}_$aggfield"; + $sel .= $iif ? + "\n\t$aggfn(IIF($colfield=$vq,$aggfield,0)) AS \"$label\", " + : + "\n\t$aggfn(CASE WHEN $colfield=$vq THEN $aggfield ELSE 0 END) AS \"$label\", "; + } + } + } + if ($aggfield && $aggfield != '1'){ + $agg = "$aggfn($aggfield)"; + $sel .= "\n\t$agg as \"$sumlabel$aggfield\", "; + } + + if ($showcount) + $sel .= "\n\tSUM(1) as Total"; + else + $sel = substr($sel,0,strlen($sel)-2); + + $sql = "SELECT $sel \nFROM $tables $where \nGROUP BY $rowfields"; + return $sql; + } + +/* EXAMPLES USING MS NORTHWIND DATABASE */ +if (0) { + +# example1 +# +# Query the main "product" table +# Set the rows to CompanyName and QuantityPerUnit +# and the columns to the Categories +# and define the joins to link to lookup tables +# "categories" and "suppliers" +# + + $sql = PivotTableSQL( + $gDB, # adodb connection + 'products p ,categories c ,suppliers s', # tables + 'CompanyName,QuantityPerUnit', # row fields + 'CategoryName', # column fields + 'p.CategoryID = c.CategoryID and s.SupplierID= p.SupplierID' # joins/where +); + print "<pre>$sql"; + $rs = $gDB->Execute($sql); + rs2html($rs); + +/* +Generated SQL: + +SELECT CompanyName,QuantityPerUnit, + SUM(CASE WHEN CategoryName='Beverages' THEN 1 ELSE 0 END) AS "Beverages", + SUM(CASE WHEN CategoryName='Condiments' THEN 1 ELSE 0 END) AS "Condiments", + SUM(CASE WHEN CategoryName='Confections' THEN 1 ELSE 0 END) AS "Confections", + SUM(CASE WHEN CategoryName='Dairy Products' THEN 1 ELSE 0 END) AS "Dairy Products", + SUM(CASE WHEN CategoryName='Grains/Cereals' THEN 1 ELSE 0 END) AS "Grains/Cereals", + SUM(CASE WHEN CategoryName='Meat/Poultry' THEN 1 ELSE 0 END) AS "Meat/Poultry", + SUM(CASE WHEN CategoryName='Produce' THEN 1 ELSE 0 END) AS "Produce", + SUM(CASE WHEN CategoryName='Seafood' THEN 1 ELSE 0 END) AS "Seafood", + SUM(1) as Total +FROM products p ,categories c ,suppliers s WHERE p.CategoryID = c.CategoryID and s.SupplierID= p.SupplierID +GROUP BY CompanyName,QuantityPerUnit +*/ +//===================================================================== + +# example2 +# +# Query the main "product" table +# Set the rows to CompanyName and QuantityPerUnit +# and the columns to the UnitsInStock for diiferent ranges +# and define the joins to link to lookup tables +# "categories" and "suppliers" +# + $sql = PivotTableSQL( + $gDB, # adodb connection + 'products p ,categories c ,suppliers s', # tables + 'CompanyName,QuantityPerUnit', # row fields + # column ranges +array( +' 0 ' => 'UnitsInStock <= 0', +"1 to 5" => '0 < UnitsInStock and UnitsInStock <= 5', +"6 to 10" => '5 < UnitsInStock and UnitsInStock <= 10', +"11 to 15" => '10 < UnitsInStock and UnitsInStock <= 15', +"16+" =>'15 < UnitsInStock' +), + ' p.CategoryID = c.CategoryID and s.SupplierID= p.SupplierID', # joins/where + 'UnitsInStock', # sum this field + 'Sum' # sum label prefix +); + print "<pre>$sql"; + $rs = $gDB->Execute($sql); + rs2html($rs); + /* + Generated SQL: + +SELECT CompanyName,QuantityPerUnit, + SUM(CASE WHEN UnitsInStock <= 0 THEN UnitsInStock ELSE 0 END) AS "Sum 0 ", + SUM(CASE WHEN 0 < UnitsInStock and UnitsInStock <= 5 THEN UnitsInStock ELSE 0 END) AS "Sum 1 to 5", + SUM(CASE WHEN 5 < UnitsInStock and UnitsInStock <= 10 THEN UnitsInStock ELSE 0 END) AS "Sum 6 to 10", + SUM(CASE WHEN 10 < UnitsInStock and UnitsInStock <= 15 THEN UnitsInStock ELSE 0 END) AS "Sum 11 to 15", + SUM(CASE WHEN 15 < UnitsInStock THEN UnitsInStock ELSE 0 END) AS "Sum 16+", + SUM(UnitsInStock) AS "Sum UnitsInStock", + SUM(1) as Total +FROM products p ,categories c ,suppliers s WHERE p.CategoryID = c.CategoryID and s.SupplierID= p.SupplierID +GROUP BY CompanyName,QuantityPerUnit + */ +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/readme.txt b/framework/DataAccess/adodb/readme.txt new file mode 100644 index 00000000..97bdd9ea --- /dev/null +++ b/framework/DataAccess/adodb/readme.txt @@ -0,0 +1,62 @@ +>> ADODB Library for PHP4
+
+(c) 2000-2004 John Lim (jlim@natsoft.com.my)
+
+Released under both BSD and GNU Lesser GPL library license.
+This means you can use it in proprietary products.
+
+
+>> Introduction
+
+PHP's database access functions are not standardised. This creates a
+need for a database class library to hide the differences between the
+different databases (encapsulate the differences) so we can easily
+switch databases.
+
+We currently support MySQL, Interbase, Sybase, PostgreSQL, Oracle,
+Microsoft SQL server, Foxpro ODBC, Access ODBC, Informix, DB2,
+Sybase SQL Anywhere, generic ODBC and Microsoft's ADO.
+
+We hope more people will contribute drivers to support other databases.
+
+
+>> Documentation and Examples
+
+Refer to the adodb/docs directory for full documentation and examples.
+There is also a tutorial tute.htm that contrasts ADODB code with
+mysql code.
+
+
+>>> Files
+Adodb.inc.php is the main file. You need to include only this file.
+
+Adodb-*.inc.php are the database specific driver code.
+
+Test.php contains a list of test commands to exercise the class library.
+
+Adodb-session.php is the PHP4 session handling code.
+
+Testdatabases.inc.php contains the list of databases to apply the tests on.
+
+Benchmark.php is a simple benchmark to test the throughput of a simple SELECT
+statement for databases described in testdatabases.inc.php. The benchmark
+tables are created in test.php.
+
+readme.htm is the main documentation.
+
+tute.htm is the tutorial.
+
+
+>> More Info
+
+For more information, including installation see readme.htm
+or visit
+ http://adodb.sourceforge.net/
+
+
+>> Feature Requests and Bug Reports
+
+Email to jlim@natsoft.com.my
+
+
+
\ No newline at end of file diff --git a/framework/DataAccess/adodb/rsfilter.inc.php b/framework/DataAccess/adodb/rsfilter.inc.php new file mode 100644 index 00000000..eb4cd776 --- /dev/null +++ b/framework/DataAccess/adodb/rsfilter.inc.php @@ -0,0 +1,61 @@ +<?php +/** + * @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + * Released under both BSD license and Lesser GPL library license. + * Whenever there is any discrepancy between the two licenses, + * the BSD license will take precedence. + * + * Set tabs to 4 for best viewing. + * + * Latest version is available at http://php.weblogs.com + * + * Requires PHP4.01pl2 or later because it uses include_once +*/ + +/* + Filter all fields and all rows in a recordset and returns the + processed recordset. We scroll to the beginning of the new recordset + after processing. + + We pass a recordset and function name to RSFilter($rs,'rowfunc'); + and the function will be called multiple times, once + for each row in the recordset. The function will be passed + an array containing one row repeatedly. + + Example: + + // ucwords() every element in the recordset + function do_ucwords(&$arr,$rs) + { + foreach($arr as $k => $v) { + $arr[$k] = ucwords($v); + } + } + $rs = RSFilter($rs,'do_ucwords'); + */ +function &RSFilter($rs,$fn) +{ + if ($rs->databaseType != 'array') { + if (!$rs->connection) return false; + + $rs = &$rs->connection->_rs2rs($rs); + } + $rows = $rs->RecordCount(); + for ($i=0; $i < $rows; $i++) { + if (is_array ($fn)) { + $obj = $fn[0]; + $method = $fn[1]; + $obj->$method ($rs->_array[$i],$rs); + } else { + $fn($rs->_array[$i],$rs); + } + + } + if (!$rs->EOF) { + $rs->_currentRow = 0; + $rs->fields = $rs->_array[0]; + } + + return $rs; +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/server.php b/framework/DataAccess/adodb/server.php new file mode 100644 index 00000000..48968dd9 --- /dev/null +++ b/framework/DataAccess/adodb/server.php @@ -0,0 +1,100 @@ +<?php + +/** + * @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + * Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + */ + +/* Documentation on usage is at http://php.weblogs.com/adodb_csv + * + * Legal query string parameters: + * + * sql = holds sql string + * nrows = number of rows to return + * offset = skip offset rows of data + * fetch = $ADODB_FETCH_MODE + * + * example: + * + * http://localhost/php/server.php?select+*+from+table&nrows=10&offset=2 + */ + + +/* + * Define the IP address you want to accept requests from + * as a security measure. If blank we accept anyone promisciously! + */ +$ACCEPTIP = '127.0.0.1'; + +/* + * Connection parameters + */ +$driver = 'mysql'; +$host = 'localhost'; // DSN for odbc +$uid = 'root'; +$pwd = 'garbase-it-is'; +$database = 'test'; + +/*============================ DO NOT MODIFY BELOW HERE =================================*/ +// $sep must match csv2rs() in adodb.inc.php +$sep = ' :::: '; + +include('./adodb.inc.php'); +include_once(ADODB_DIR.'/adodb-csvlib.inc.php'); + +function err($s) +{ + die('**** '.$s.' '); +} + +// undo stupid magic quotes +function undomq(&$m) +{ + if (get_magic_quotes_gpc()) { + // undo the damage + $m = str_replace('\\\\','\\',$m); + $m = str_replace('\"','"',$m); + $m = str_replace('\\\'','\'',$m); + + } + return $m; +} + +///////////////////////////////////////// DEFINITIONS + + +$remote = $_SERVER["REMOTE_ADDR"]; + + +if (!empty($ACCEPTIP)) + if ($remote != '127.0.0.1' && $remote != $ACCEPTIP) + err("Unauthorised client: '$remote'"); + + +if (empty($_REQUEST['sql'])) err('No SQL'); + + +$conn = &ADONewConnection($driver); + +if (!$conn->Connect($host,$uid,$pwd,$database)) err($conn->ErrorNo(). $sep . $conn->ErrorMsg()); +$sql = undomq($_REQUEST['sql']); + +if (isset($_REQUEST['fetch'])) + $ADODB_FETCH_MODE = $_REQUEST['fetch']; + +if (isset($_REQUEST['nrows'])) { + $nrows = $_REQUEST['nrows']; + $offset = isset($_REQUEST['offset']) ? $_REQUEST['offset'] : -1; + $rs = $conn->SelectLimit($sql,$nrows,$offset); +} else + $rs = $conn->Execute($sql); +if ($rs){ + //$rs->timeToLive = 1; + echo _rs2serialize($rs,$conn,$sql); + $rs->Close(); +} else + err($conn->ErrorNo(). $sep .$conn->ErrorMsg()); + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/toexport.inc.php b/framework/DataAccess/adodb/toexport.inc.php new file mode 100644 index 00000000..f53b9aa2 --- /dev/null +++ b/framework/DataAccess/adodb/toexport.inc.php @@ -0,0 +1,133 @@ +<?php + +/** + * @version V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + * Released under both BSD license and Lesser GPL library license. + * Whenever there is any discrepancy between the two licenses, + * the BSD license will take precedence. + * + * Code to export recordsets in several formats: + * + * AS VARIABLE + * $s = rs2csv($rs); # comma-separated values + * $s = rs2tab($rs); # tab delimited + * + * TO A FILE + * $f = fopen($path,'w'); + * rs2csvfile($rs,$f); + * fclose($f); + * + * TO STDOUT + * rs2csvout($rs); + */ + +// returns a recordset as a csv string +function rs2csv(&$rs,$addtitles=true) +{ + return _adodb_export($rs,',',',',false,$addtitles); +} + +// writes recordset to csv file +function rs2csvfile(&$rs,$fp,$addtitles=true) +{ + _adodb_export($rs,',',',',$fp,$addtitles); +} + +// write recordset as csv string to stdout +function rs2csvout(&$rs,$addtitles=true) +{ + $fp = fopen('php://stdout','wb'); + _adodb_export($rs,',',',',true,$addtitles); + fclose($fp); +} + +function rs2tab(&$rs,$addtitles=true) +{ + return _adodb_export($rs,"\t",',',false,$addtitles); +} + +// to file pointer +function rs2tabfile(&$rs,$fp,$addtitles=true) +{ + _adodb_export($rs,"\t",',',$fp,$addtitles); +} + +// to stdout +function rs2tabout(&$rs,$addtitles=true) +{ + $fp = fopen('php://stdout','wb'); + _adodb_export($rs,"\t",' ',true,$addtitles); + if ($fp) fclose($fp); +} + +function _adodb_export(&$rs,$sep,$sepreplace,$fp=false,$addtitles=true,$quote = '"',$escquote = '"',$replaceNewLine = ' ') +{ + if (!$rs) return ''; + //---------- + // CONSTANTS + $NEWLINE = "\r\n"; + $BUFLINES = 100; + $escquotequote = $escquote.$quote; + $s = ''; + + if ($addtitles) { + $fieldTypes = $rs->FieldTypesArray(); + reset($fieldTypes); + while(list(,$o) = each($fieldTypes)) { + + $v = $o->name; + if ($escquote) $v = str_replace($quote,$escquotequote,$v); + $v = strip_tags(str_replace("\n", $replaceNewLine, str_replace("\r\n",$replaceNewLine,str_replace($sep,$sepreplace,$v)))); + $elements[] = $v; + + } + $s .= implode($sep, $elements).$NEWLINE; + } + $hasNumIndex = isset($rs->fields[0]); + + $line = 0; + $max = $rs->FieldCount(); + + while (!$rs->EOF) { + $elements = array(); + $i = 0; + + if ($hasNumIndex) { + for ($j=0; $j < $max; $j++) { + $v = $rs->fields[$j]; + if (!is_object($v)) $v = trim($v); + else $v = 'Object'; + if ($escquote) $v = str_replace($quote,$escquotequote,$v); + $v = strip_tags(str_replace("\n", $replaceNewLine, str_replace("\r\n",$replaceNewLine,str_replace($sep,$sepreplace,$v)))); + + if (strpos($v,$sep) !== false || strpos($v,$quote) !== false) $elements[] = "$quote$v$quote"; + else $elements[] = $v; + } + } else { // ASSOCIATIVE ARRAY + foreach($rs->fields as $v) { + if ($escquote) $v = str_replace($quote,$escquotequote,trim($v)); + $v = strip_tags(str_replace("\n", $replaceNewLine, str_replace("\r\n",$replaceNewLine,str_replace($sep,$sepreplace,$v)))); + + if (strpos($v,$sep) !== false || strpos($v,$quote) !== false) $elements[] = "$quote$v$quote"; + else $elements[] = $v; + } + } + $s .= implode($sep, $elements).$NEWLINE; + $rs->MoveNext(); + $line += 1; + if ($fp && ($line % $BUFLINES) == 0) { + if ($fp === true) echo $s; + else fwrite($fp,$s); + $s = ''; + } + } + + if ($fp) { + if ($fp === true) echo $s; + else fwrite($fp,$s); + $s = ''; + } + + return $s; +} +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/tohtml.inc.php b/framework/DataAccess/adodb/tohtml.inc.php new file mode 100644 index 00000000..b7dca463 --- /dev/null +++ b/framework/DataAccess/adodb/tohtml.inc.php @@ -0,0 +1,195 @@ +<?php +/* + V4.72 21 Feb 2006 (c) 2000-2006 John Lim (jlim@natsoft.com.my). All rights reserved. + Released under both BSD license and Lesser GPL library license. + Whenever there is any discrepancy between the two licenses, + the BSD license will take precedence. + + Some pretty-printing by Chris Oxenreider <oxenreid@state.net> +*/ + +// specific code for tohtml +GLOBAL $gSQLMaxRows,$gSQLBlockRows,$ADODB_ROUND; + +$ADODB_ROUND=4; // rounding +$gSQLMaxRows = 1000; // max no of rows to download +$gSQLBlockRows=20; // max no of rows per table block + +// RecordSet to HTML Table +//------------------------------------------------------------ +// Convert a recordset to a html table. Multiple tables are generated +// if the number of rows is > $gSQLBlockRows. This is because +// web browsers normally require the whole table to be downloaded +// before it can be rendered, so we break the output into several +// smaller faster rendering tables. +// +// $rs: the recordset +// $ztabhtml: the table tag attributes (optional) +// $zheaderarray: contains the replacement strings for the headers (optional) +// +// USAGE: +// include('adodb.inc.php'); +// $db = ADONewConnection('mysql'); +// $db->Connect('mysql','userid','password','database'); +// $rs = $db->Execute('select col1,col2,col3 from table'); +// rs2html($rs, 'BORDER=2', array('Title1', 'Title2', 'Title3')); +// $rs->Close(); +// +// RETURNS: number of rows displayed + + +function rs2html(&$rs,$ztabhtml=false,$zheaderarray=false,$htmlspecialchars=true,$echo = true) +{ +$s ='';$rows=0;$docnt = false; +GLOBAL $gSQLMaxRows,$gSQLBlockRows,$ADODB_ROUND; + + if (!$rs) { + printf(ADODB_BAD_RS,'rs2html'); + return false; + } + + if (! $ztabhtml) $ztabhtml = "BORDER='1' WIDTH='98%'"; + //else $docnt = true; + $typearr = array(); + $ncols = $rs->FieldCount(); + $hdr = "<TABLE COLS=$ncols $ztabhtml><tr>\n\n"; + for ($i=0; $i < $ncols; $i++) { + $field = $rs->FetchField($i); + if ($field) { + if ($zheaderarray) $fname = $zheaderarray[$i]; + else $fname = htmlspecialchars($field->name); + $typearr[$i] = $rs->MetaType($field->type,$field->max_length); + //print " $field->name $field->type $typearr[$i] "; + } else { + $fname = 'Field '.($i+1); + $typearr[$i] = 'C'; + } + if (strlen($fname)==0) $fname = ' '; + $hdr .= "<TH>$fname</TH>"; + } + $hdr .= "\n</tr>"; + if ($echo) print $hdr."\n\n"; + else $html = $hdr; + + // smart algorithm - handles ADODB_FETCH_MODE's correctly by probing... + $numoffset = isset($rs->fields[0]) ||isset($rs->fields[1]) || isset($rs->fields[2]); + while (!$rs->EOF) { + + $s .= "<TR valign=top>\n"; + + for ($i=0; $i < $ncols; $i++) { + if ($i===0) $v=($numoffset) ? $rs->fields[0] : reset($rs->fields); + else $v = ($numoffset) ? $rs->fields[$i] : next($rs->fields); + + $type = $typearr[$i]; + switch($type) { + case 'D': + if (empty($v)) $s .= "<TD> </TD>\n"; + else if (!strpos($v,':')) { + $s .= " <TD>".$rs->UserDate($v,"D d, M Y") ." </TD>\n"; + } + break; + case 'T': + if (empty($v)) $s .= "<TD> </TD>\n"; + else $s .= " <TD>".$rs->UserTimeStamp($v,"D d, M Y, h:i:s") ." </TD>\n"; + break; + + case 'N': + if (abs($v) - round($v,0) < 0.00000001) + $v = round($v); + else + $v = round($v,$ADODB_ROUND); + case 'I': + $s .= " <TD align=right>".stripslashes((trim($v))) ." </TD>\n"; + + break; + /* + case 'B': + if (substr($v,8,2)=="BM" ) $v = substr($v,8); + $mtime = substr(str_replace(' ','_',microtime()),2); + $tmpname = "tmp/".uniqid($mtime).getmypid(); + $fd = @fopen($tmpname,'a'); + @ftruncate($fd,0); + @fwrite($fd,$v); + @fclose($fd); + if (!function_exists ("mime_content_type")) { + function mime_content_type ($file) { + return exec("file -bi ".escapeshellarg($file)); + } + } + $t = mime_content_type($tmpname); + $s .= (substr($t,0,5)=="image") ? " <td><img src='$tmpname' alt='$t'></td>\\n" : " <td><a + href='$tmpname'>$t</a></td>\\n"; + break; + */ + + default: + if ($htmlspecialchars) $v = htmlspecialchars(trim($v)); + $v = trim($v); + if (strlen($v) == 0) $v = ' '; + $s .= " <TD>". str_replace("\n",'<br>',stripslashes($v)) ."</TD>\n"; + + } + } // for + $s .= "</TR>\n\n"; + + $rows += 1; + if ($rows >= $gSQLMaxRows) { + $rows = "<p>Truncated at $gSQLMaxRows</p>"; + break; + } // switch + + $rs->MoveNext(); + + // additional EOF check to prevent a widow header + if (!$rs->EOF && $rows % $gSQLBlockRows == 0) { + + //if (connection_aborted()) break;// not needed as PHP aborts script, unlike ASP + if ($echo) print $s . "</TABLE>\n\n"; + else $html .= $s ."</TABLE>\n\n"; + $s = $hdr; + } + } // while + + if ($echo) print $s."</TABLE>\n\n"; + else $html .= $s."</TABLE>\n\n"; + + if ($docnt) if ($echo) print "<H2>".$rows." Rows</H2>"; + + return ($echo) ? $rows : $html; + } + +// pass in 2 dimensional array +function arr2html(&$arr,$ztabhtml='',$zheaderarray='') +{ + if (!$ztabhtml) $ztabhtml = 'BORDER=1'; + + $s = "<TABLE $ztabhtml>";//';print_r($arr); + + if ($zheaderarray) { + $s .= '<TR>'; + for ($i=0; $i<sizeof($zheaderarray); $i++) { + $s .= " <TH>{$zheaderarray[$i]}</TH>\n"; + } + $s .= "\n</TR>"; + } + + for ($i=0; $i<sizeof($arr); $i++) { + $s .= '<TR>'; + $a = &$arr[$i]; + if (is_array($a)) + for ($j=0; $j<sizeof($a); $j++) { + $val = $a[$j]; + if (empty($val)) $val = ' '; + $s .= " <TD>$val</TD>\n"; + } + else if ($a) { + $s .= ' <TD>'.$a."</TD>\n"; + } else $s .= " <TD> </TD>\n"; + $s .= "\n</TR>\n"; + } + $s .= '</TABLE>'; + print $s; +} + +?>
\ No newline at end of file diff --git a/framework/DataAccess/adodb/xmlschema.dtd b/framework/DataAccess/adodb/xmlschema.dtd new file mode 100644 index 00000000..4a055da4 --- /dev/null +++ b/framework/DataAccess/adodb/xmlschema.dtd @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<!DOCTYPE adodb_schema [ +<!ELEMENT schema (table*, sql*)> +<!ATTLIST schema version CDATA #REQUIRED> +<!ELEMENT table ((field+|DROP), CONSTRAINT*, descr?, index*, data*)> +<!ELEMENT field ((NOTNULL|KEY|PRIMARY)?, (AUTO|AUTOINCREMENT)?, (DEFAULT|DEFDATE|DEFTIMESTAMP)?, +NOQUOTE?, CONSTRAINT*, descr?)> +<!ELEMENT data (row+)> +<!ELEMENT row (f+)> +<!ELEMENT f (#CDATA)> +<!ELEMENT descr (#CDATA)> +<!ELEMENT NOTNULL EMPTY> +<!ELEMENT KEY EMPTY> +<!ELEMENT PRIMARY EMPTY> +<!ELEMENT AUTO EMPTY> +<!ELEMENT AUTOINCREMENT EMPTY> +<!ELEMENT DEFAULT EMPTY> +<!ELEMENT DEFDATE EMPTY> +<!ELEMENT DEFTIMESTAMP EMPTY> +<!ELEMENT NOQUOTE EMPTY> +<!ELEMENT DROP EMPTY> +<!ELEMENT CONSTRAINT (#CDATA)> +<!ATTLIST table name CDATA #REQUIRED platform CDATA #IMPLIED version CDATA #IMPLIED> +<!ATTLIST field name CDATA #REQUIRED type (C|C2|X|X2|B|D|T|L|I|F|N) #REQUIRED size CDATA #IMPLIED> +<!ATTLIST data platform CDATA #IMPLIED> +<!ATTLIST f name CDATA #IMPLIED> +<!ATTLIST DEFAULT VALUE CDATA #REQUIRED> +<!ELEMENT index ((col+|DROP), CLUSTERED?, BITMAP?, UNIQUE?, FULLTEXT?, HASH?, descr?)> +<!ELEMENT col (#CDATA)> +<!ELEMENT CLUSTERED EMPTY> +<!ELEMENT BITMAP EMPTY> +<!ELEMENT UNIQUE EMPTY> +<!ELEMENT FULLTEXT EMPTY> +<!ELEMENT HASH EMPTY> +<!ATTLIST index name CDATA #REQUIRED platform CDATA #IMPLIED> +<!ELEMENT sql (query+, descr?)> +<!ELEMENT query (#CDATA)> +<!ATTLIST sql name CDATA #IMPLIED platform CDATA #IMPLIED, key CDATA, prefixmethod (AUTO|MANUAL|NONE) > +] >
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/scorer.php b/tests/UnitTests/simpletest/scorer.php index de14883c..7861493c 100644 --- a/tests/UnitTests/simpletest/scorer.php +++ b/tests/UnitTests/simpletest/scorer.php @@ -371,7 +371,7 @@ * @access public
* @static
*/
- function inCli() {
+ static function inCli() {
return php_sapi_name() == 'cli';
}
}
diff --git a/tests/UnitTests/simpletest/test/acceptance_test.php b/tests/UnitTests/simpletest/test/acceptance_test.php deleted file mode 100644 index 7cb73afb..00000000 --- a/tests/UnitTests/simpletest/test/acceptance_test.php +++ /dev/null @@ -1,1106 +0,0 @@ -<?php
- // $Id: acceptance_test.php,v 1.42 2005/02/22 02:17:06 lastcraft Exp $
- require_once(dirname(__FILE__) . '/../options.php');
- require_once(dirname(__FILE__) . '/../browser.php');
- require_once(dirname(__FILE__) . '/../web_tester.php');
- require_once(dirname(__FILE__) . '/../unit_tester.php');
-
- class TestOfLiveBrowser extends UnitTestCase {
-
- function testGet() {
- $browser = &new SimpleBrowser();
- $browser->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
-
- $this->assertTrue($browser->get('http://www.lastcraft.com/test/network_confirm.php'));
- $this->assertWantedPattern('/target for the SimpleTest/', $browser->getContent());
- $this->assertWantedPattern('/Request method.*?<dd>GET<\/dd>/', $browser->getContent());
- $this->assertEqual($browser->getTitle(), 'Simple test target file');
- $this->assertEqual($browser->getResponseCode(), 200);
- $this->assertEqual($browser->getMimeType(), 'text/html');
- }
-
- function testPost() {
- $browser = &new SimpleBrowser();
- $browser->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- $this->assertTrue($browser->post('http://www.lastcraft.com/test/network_confirm.php'));
- $this->assertWantedPattern('/target for the SimpleTest/', $browser->getContent());
- $this->assertWantedPattern('/Request method.*?<dd>POST<\/dd>/', $browser->getContent());
- }
-
- function testAbsoluteLinkFollowing() {
- $browser = &new SimpleBrowser();
- $browser->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- $browser->get('http://www.lastcraft.com/test/link_confirm.php');
- $this->assertTrue($browser->clickLink('Absolute'));
- $this->assertWantedPattern('/target for the SimpleTest/', $browser->getContent());
- }
-
- function testRelativeLinkFollowing() {
- $browser = &new SimpleBrowser();
- $browser->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- $browser->get('http://www.lastcraft.com/test/link_confirm.php');
- $this->assertTrue($browser->clickLink('Relative'));
- $this->assertWantedPattern('/target for the SimpleTest/', $browser->getContent());
- }
-
- function testIdLinkFollowing() {
- $browser = &new SimpleBrowser();
- $browser->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- $browser->get('http://www.lastcraft.com/test/link_confirm.php');
- $this->assertTrue($browser->clickLinkById(1));
- $this->assertWantedPattern('/target for the SimpleTest/', $browser->getContent());
- }
-
- function testCookieReading() {
- $browser = &new SimpleBrowser();
- $browser->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- $browser->get('http://www.lastcraft.com/test/set_cookies.php');
- $this->assertEqual($browser->getCurrentCookieValue('session_cookie'), 'A');
- $this->assertEqual($browser->getCurrentCookieValue('short_cookie'), 'B');
- $this->assertEqual($browser->getCurrentCookieValue('day_cookie'), 'C');
- }
-
- function testSimpleSubmit() {
- $browser = &new SimpleBrowser();
- $browser->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- $browser->get('http://www.lastcraft.com/test/form.html');
- $this->assertTrue($browser->clickSubmit('Go!'));
- $this->assertWantedPattern('/Request method.*?<dd>POST<\/dd>/', $browser->getContent());
- $this->assertWantedPattern('/go=\[Go!\]/', $browser->getContent());
- }
- }
-
- class TestOfLiveFetching extends WebTestCase {
-
- function setUp() {
- $this->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- }
-
- function testGet() {
- $this->assertTrue($this->get('http://www.lastcraft.com/test/network_confirm.php'));
- $this->assertTrue($this->getUrl() == 'http://www.lastcraft.com/test/network_confirm.php');
- $this->assertWantedPattern('/target for the SimpleTest/');
- $this->assertWantedPattern('/Request method.*?<dd>GET<\/dd>/');
- $this->assertTitle('Simple test target file');
- $this->assertResponse(200);
- $this->assertMime('text/html');
- }
-
- function testSlowGet() {
- $this->assertTrue($this->get('http://www.lastcraft.com/test/slow_page.php'));
- }
-
- function testTimedOutGet() {
- $this->setConnectionTimeout(1);
- $this->assertFalse($this->get('http://www.lastcraft.com/test/slow_page.php'));
- }
-
- function testPost() {
- $this->assertTrue($this->post('http://www.lastcraft.com/test/network_confirm.php'));
- $this->assertWantedText('target for the SimpleTest');
- $this->assertWantedPattern('/Request method.*?<dd>POST<\/dd>/');
- }
-
- function testGetWithData() {
- $this->get('http://www.lastcraft.com/test/network_confirm.php', array("a" => "aaa"));
- $this->assertWantedPattern('/Request method.*?<dd>GET<\/dd>/');
- $this->assertWantedText('a=[aaa]');
- }
-
- function testPostWithData() {
- $this->post('http://www.lastcraft.com/test/network_confirm.php', array("a" => "aaa"));
- $this->assertWantedPattern('/Request method.*?<dd>POST<\/dd>/');
- $this->assertWantedText('a=[aaa]');
- }
-
- function testRelativeGet() {
- $this->get('http://www.lastcraft.com/test/link_confirm.php');
- $this->assertTrue($this->get('network_confirm.php'));
- $this->assertWantedText('target for the SimpleTest');
- }
-
- function testRelativePost() {
- $this->post('http://www.lastcraft.com/test/link_confirm.php');
- $this->assertTrue($this->post('network_confirm.php'));
- $this->assertWantedText('target for the SimpleTest');
- }
-
- function testAbsoluteLinkFollowing() {
- $this->get('http://www.lastcraft.com/test/link_confirm.php');
- $this->assertLink('Absolute');
- $this->assertTrue($this->clickLink('Absolute'));
- $this->assertWantedText('target for the SimpleTest');
- }
-
- function testRelativeLinkFollowing() {
- $this->get('http://www.lastcraft.com/test/link_confirm.php');
- $this->assertTrue($this->clickLink('Relative'));
- $this->assertWantedText('target for the SimpleTest');
- }
-
- function testLinkIdFollowing() {
- $this->get('http://www.lastcraft.com/test/link_confirm.php');
- $this->assertLinkById(1);
- $this->assertTrue($this->clickLinkById(1));
- $this->assertWantedText('target for the SimpleTest');
- }
-
- function testAbsoluteUrlBehavesAbsolutely() {
- $this->get('http://www.lastcraft.com/test/link_confirm.php');
- $this->get('http://www.lastcraft.com');
- $this->assertWantedText('No guarantee of quality is given or even intended');
- }
- }
-
- class TestOfLivePageLinkingWithMinimalLinks extends WebTestCase {
-
- function setUp() {
- $this->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- }
-
- function testClickToExplicitelyNamedSelfReturns() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/a_page.php');
- $this->assertTrue($this->getUrl() == 'http://www.lastcraft.com/test/front_controller_style/a_page.php');
- $this->assertTitle('Simple test page with links');
- $this->clickLink('Self');
- $this->assertTitle('Simple test page with links');
- }
-
- function testClickToMissingPageReturnsToSamePage() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/a_page.php');
- $this->clickLink('No page');
- $this->assertTitle('Simple test page with links');
- $this->assertWantedText('[action=no_page]');
- }
-
- function testClickToBareActionReturnsToSamePage() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/a_page.php');
- $this->clickLink('Bare action');
- $this->assertTitle('Simple test page with links');
- $this->assertWantedText('[action=]');
- }
-
- function testClickToSingleQuestionMarkReturnsToSamePage() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/a_page.php');
- $this->clickLink('Empty query');
- $this->assertTitle('Simple test page with links');
- }
-
- function testClickToEmptyStringReturnsToSamePage() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/a_page.php');
- $this->clickLink('Empty link');
- $this->assertTitle('Simple test page with links');
- }
-
- function testClickToSingleDotGoesToCurrentDirectory() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/a_page.php');
- $this->clickLink('Current directory');
- $this->assertTitle('Simple test front controller');
- }
-
- function testClickBackADirectoryLevel() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/');
- $this->clickLink('Down one');
- $this->assertWantedText('Index of /test');
- }
- }
-
- class TestOfLiveFrontControllerEmulation extends WebTestCase {
-
- function setUp() {
- $this->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- }
-
- function testJumpToNamedPage() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/');
- $this->assertWantedText('Simple test front controller');
- $this->clickLink('Index');
- $this->assertResponse(200);
- $this->assertWantedText('[action=index]');
- }
-
- function testJumpToUnnamedPage() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/');
- $this->clickLink('No page');
- $this->assertResponse(200);
- $this->assertWantedText('Simple test front controller');
- $this->assertWantedText('[action=no_page]');
- }
-
- function testJumpToUnnamedPageWithBareParameter() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/');
- $this->clickLink('Bare action');
- $this->assertResponse(200);
- $this->assertWantedText('Simple test front controller');
- $this->assertWantedText('[action=]');
- }
-
- function testJumpToUnnamedPageWithEmptyQuery() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/');
- $this->clickLink('Empty query');
- $this->assertResponse(200);
- $this->assertWantedPattern('/Simple test front controller/');
- $this->assertWantedPattern('/raw get data.*?\[\].*?get data/si');
- }
-
- function testJumpToUnnamedPageWithEmptyLink() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/');
- $this->clickLink('Empty link');
- $this->assertResponse(200);
- $this->assertWantedPattern('/Simple test front controller/');
- $this->assertWantedPattern('/raw get data.*?\[\].*?get data/si');
- }
-
- function testJumpBackADirectoryLevel() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/');
- $this->clickLink('Down one');
- $this->assertWantedText('Index of /test');
- }
-
- function testSubmitToNamedPage() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/');
- $this->assertWantedText('Simple test front controller');
- $this->clickSubmit('Index');
- $this->assertResponse(200);
- $this->assertWantedText('[action=Index]');
- }
-
- function testSubmitToSameDirectory() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/index.php');
- $this->clickSubmit('Same directory');
- $this->assertResponse(200);
- $this->assertWantedText('[action=Same+directory]');
- }
-
- function testSubmitToEmptyAction() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/index.php');
- $this->clickSubmit('Empty action');
- $this->assertResponse(200);
- $this->assertWantedText('[action=Empty+action]');
- }
-
- function testSubmitToNoAction() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/index.php');
- $this->clickSubmit('No action');
- $this->assertResponse(200);
- $this->assertWantedText('[action=No+action]');
- }
-
- function testSubmitBackADirectoryLevel() {
- $this->get('http://www.lastcraft.com/test/front_controller_style/');
- $this->clickSubmit('Down one');
- $this->assertWantedText('Index of /test');
- }
- }
-
- class TestOfLiveHeaders extends WebTestCase {
-
- function setUp() {
- $this->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- }
-
- function testConfirmingHeaderExistence() {
- $this->get('http://www.lastcraft.com/');
- $this->assertHeader('content-type');
- $this->assertHeader('content-type', 'text/html');
- $this->assertHeaderPattern('content-type', '/HTML/i');
- $this->assertNoUnwantedHeader('WWW-Authenticate');
- }
- }
-
- class TestOfLiveRedirects extends WebTestCase {
-
- function setUp() {
- $this->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- }
-
- function testNoRedirects() {
- $this->setMaximumRedirects(0);
- $this->get('http://www.lastcraft.com/test/redirect.php');
- $this->assertTitle('Redirection test');
- }
-
- function testRedirects() {
- $this->setMaximumRedirects(1);
- $this->get('http://www.lastcraft.com/test/redirect.php');
- $this->assertTitle('Simple test target file');
- }
-
- function testRedirectLosesGetData() {
- $this->get('http://www.lastcraft.com/test/redirect.php', array('a' => 'aaa'));
- $this->assertNoUnwantedText('a=[aaa]');
- }
-
- function testRedirectKeepsExtraRequestDataOfItsOwn() {
- $this->get('http://www.lastcraft.com/test/redirect.php');
- $this->assertWantedText('r=[rrr]');
- }
-
- function testRedirectLosesPostData() {
- $this->post('http://www.lastcraft.com/test/redirect.php', array('a' => 'aaa'));
- $this->assertTitle('Simple test target file');
- $this->assertNoUnwantedText('a=[aaa]');
- }
-
- function testRedirectWithBaseUrlChange() {
- $this->get('http://www.lastcraft.com/test/base_change_redirect.php');
- $this->assertTitle('Simple test target file in folder');
- $this->get('http://www.lastcraft.com/test/path/base_change_redirect.php');
- $this->assertTitle('Simple test target file');
- }
-
- function testRedirectWithDoubleBaseUrlChange() {
- $this->get('http://www.lastcraft.com/test/double_base_change_redirect.php');
- $this->assertTitle('Simple test target file');
- }
- }
-
- class TestOfLiveCookies extends WebTestCase {
-
- function setUp() {
- $this->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- }
-
- function testCookieSetting() {
- $this->setCookie('a', 'Test cookie a', 'www.lastcraft.com');
- $this->setCookie('b', 'Test cookie b', 'www.lastcraft.com', 'test');
- $this->get('http://www.lastcraft.com/test/network_confirm.php');
- $this->assertWantedPattern('/Test cookie a/');
- $this->assertWantedPattern('/Test cookie b/');
- $this->assertCookie('a');
- $this->assertCookie('b', 'Test cookie b');
- $this->assertTrue($this->getCookie('a') == 'Test cookie a');
- $this->assertTrue($this->getCookie('b') == 'Test cookie b');
- }
-
- function testCookieReading() {
- $this->get('http://www.lastcraft.com/test/set_cookies.php');
- $this->assertCookie('session_cookie', 'A');
- $this->assertCookie('short_cookie', 'B');
- $this->assertCookie('day_cookie', 'C');
- }
-
- function testTemporaryCookieExpiry() {
- $this->get('http://www.lastcraft.com/test/set_cookies.php');
- $this->restart();
- $this->assertNoCookie('session_cookie');
- $this->assertCookie('day_cookie', 'C');
- }
-
- function testTimedCookieExpiry() {
- $this->get('http://www.lastcraft.com/test/set_cookies.php');
- $this->ageCookies(3600);
- $this->restart(time() + 60); // Includes a 60 sec. clock drift margin.
- $this->assertNoCookie('session_cookie');
- $this->assertNoCookie('hour_cookie');
- $this->assertCookie('day_cookie', 'C');
- }
-
- function testOfClockOverDrift() {
- $this->get('http://www.lastcraft.com/test/set_cookies.php');
- $this->restart(time() + 160); // Allows sixty second drift.
- $this->assertNoCookie(
- 'short_cookie',
- '%s->Please check your computer clock setting if you are not using NTP');
- }
-
- function testOfClockUnderDrift() {
- $this->get('http://www.lastcraft.com/test/set_cookies.php');
- $this->restart(time() + 40); // Allows sixty second drift.
- $this->assertCookie(
- 'short_cookie',
- 'B',
- '%s->Please check your computer clock setting if you are not using NTP');
- }
-
- function testCookiePath() {
- $this->get('http://www.lastcraft.com/test/set_cookies.php');
- $this->assertNoCookie("path_cookie", "D");
- $this->get('./path/show_cookies.php');
- $this->assertWantedPattern('/path_cookie/');
- $this->assertCookie("path_cookie", "D");
- }
- }
-
- class TestOfLiveForms extends WebTestCase {
-
- function setUp() {
- $this->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- }
-
- function testSimpleSubmit() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->assertTrue($this->clickSubmit('Go!'));
- $this->assertWantedPattern('/Request method.*?<dd>POST<\/dd>/');
- $this->assertWantedText('go=[Go!]');
- }
-
- function testDefaultFormValues() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->assertField('a', '');
- $this->assertField('b', 'Default text');
- $this->assertField('c', '');
- $this->assertField('d', 'd1');
- $this->assertField('e', false);
- $this->assertField('f', 'on');
- $this->assertField('g', 'g3');
- $this->assertField('h', 2);
- $this->assertField('go', 'Go!');
- $this->assertTrue($this->clickSubmit('Go!'));
- $this->assertWantedText('go=[Go!]');
- $this->assertWantedText('a=[]');
- $this->assertWantedText('b=[Default text]');
- $this->assertWantedText('c=[]');
- $this->assertWantedText('d=[d1]');
- $this->assertNoUnwantedText('e=[');
- $this->assertWantedText('f=[on]');
- $this->assertWantedText('g=[g3]');
- }
-
- function testFormSubmissionByLabel() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->setField('a', 'aaa');
- $this->setField('b', 'bbb');
- $this->setField('c', 'ccc');
- $this->setField('d', 'D2');
- $this->setField('e', 'on');
- $this->setField('f', false);
- $this->setField('g', 'g2');
- $this->setField('h', 1);
- $this->assertTrue($this->clickSubmit('Go!'));
- $this->assertWantedText('a=[aaa]');
- $this->assertWantedText('b=[bbb]');
- $this->assertWantedText('c=[ccc]');
- $this->assertWantedText('d=[d2]');
- $this->assertWantedText('e=[on]');
- $this->assertNoUnwantedText('f=[');
- $this->assertWantedText('g=[g2]');
- }
-
- function testAdditionalFormValues() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->assertTrue($this->clickSubmit('Go!', array('add' => 'A')));
- $this->assertWantedText('go=[Go!]');
- $this->assertWantedText('add=[A]');
- }
-
- function testFormSubmissionByName() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->assertTrue($this->clickSubmitByName('go'));
- $this->assertWantedText('go=[Go!]');
- }
-
- function testFormSubmissionByNameAndadditionalParameters() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->assertTrue($this->clickSubmitByName('go', array('add' => 'A')));
- $this->assertWantedText('go=[Go!]');
- $this->assertWantedText('add=[A]');
- }
-
- function testFormSubmissionBySubmitButtonLabeledSubmit() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->assertTrue($this->clickSubmitByName('test'));
- $this->assertWantedText('test=[Submit]');
- }
-
- function testFormSubmissionWithIds() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->assertFieldById(1, '');
- $this->assertFieldById(2, 'Default text');
- $this->assertFieldById(3, '');
- $this->assertFieldById(4, 'd1');
- $this->assertFieldById(5, false);
- $this->setFieldById(1, 'aaa');
- $this->setFieldById(2, 'bbb');
- $this->setFieldById(3, 'ccc');
- $this->setFieldById(4, 'D2');
- $this->setFieldById(5, 'on');
- $this->assertTrue($this->clickSubmitById(99));
- $this->assertWantedText('a=[aaa]');
- $this->assertWantedText('b=[bbb]');
- $this->assertWantedText('c=[ccc]');
- $this->assertWantedText('d=[d2]');
- $this->assertWantedText('e=[on]');
- $this->assertWantedText('go=[Go!]');
- }
-
- function testImageSubmissionByLabel() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->assertTrue($this->clickImage('Image go!', 10, 12));
- $this->assertWantedText('go_x=[10]');
- $this->assertWantedText('go_y=[12]');
- }
-
- function testImageSubmissionByLabelWithAdditionalParameters() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->assertTrue($this->clickImage('Image go!', 10, 12, array('add' => 'A')));
- $this->assertWantedText('add=[A]');
- }
-
- function testImageSubmissionByName() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->assertTrue($this->clickImageByName('go', 10, 12));
- $this->assertWantedText('go_x=[10]');
- $this->assertWantedText('go_y=[12]');
- }
-
- function testImageSubmissionById() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->assertTrue($this->clickImageById(97, 10, 12));
- $this->assertWantedText('go_x=[10]');
- $this->assertWantedText('go_y=[12]');
- }
-
- function testButtonSubmissionByLabel() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->assertTrue($this->clickSubmit('Button go!', 10, 12));
- $this->assertWantedPattern('/go=\[ButtonGo\]/s');
- }
-
- function testSelfSubmit() {
- $this->get('http://www.lastcraft.com/test/self_form.php');
- $this->assertNoUnwantedPattern('/<p>submitted<\/p>/i');
- $this->assertNoUnwantedPattern('/<p>wrong form<\/p>/i');
- $this->assertTitle('Test of form self submission');
- $this->assertTrue($this->clickSubmit());
- $this->assertWantedPattern('/<p>submitted<\/p>/i');
- $this->assertNoUnwantedPattern('/<p>wrong form<\/p>/i');
- $this->assertTitle('Test of form self submission');
- }
-
- function testSettingOfBlankOption() {
- $this->get('http://www.lastcraft.com/test/form.html');
- $this->assertTrue($this->setField('d', ''));
- $this->clickSubmit('Go!');
- $this->assertWantedText('d=[]');
- }
- }
-
- class TestOfLiveMultiValueWidgets extends WebTestCase {
-
- function setUp() {
- $this->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- }
-
- function testDefaultFormValueSubmission() {
- $this->get('http://www.lastcraft.com/test/multiple_widget_form.html');
- $this->assertField('a', array('a2', 'a3'));
- $this->assertField('b', array('b2', 'b3'));
- $this->assertField('c[]', array('c2', 'c3'));
- $this->assertTrue($this->clickSubmit('Go!'));
- $this->assertWantedText('a=[a2, a3]');
- $this->assertWantedText('b=[b2, b3]');
- $this->assertWantedText('c=[c2, c3]');
- }
-
- function testSubmittingMultipleValues() {
- $this->get('http://www.lastcraft.com/test/multiple_widget_form.html');
- $this->setField('a', array('a1', 'a4'));
- $this->assertField('a', array('a1', 'a4'));
- $this->assertField('a', array('a4', 'a1'));
- $this->setField('b', array('b1', 'b4'));
- $this->assertField('b', array('b1', 'b4'));
- $this->setField('c[]', array('c1', 'c4'));
- $this->assertField('c[]', array('c1', 'c4'));
- $this->assertTrue($this->clickSubmit('Go!'));
- $this->assertWantedText('a=[a1, a4]');
- $this->assertWantedText('b=[b1, b4]');
- $this->assertWantedText('c=[c1, c4]');
- }
-
- function testSavantStyleHiddenFieldDefaults() {
- $this->get('http://www.lastcraft.com/test/savant_style_form.html');
- $this->assertField('a', array('a0'));
- $this->assertField('b', array('b0'));
- $this->assertTrue($this->clickSubmit('Go!'));
- $this->assertWantedText('a=[a0]');
- $this->assertWantedText('b=[b0]');
- }
-
- function testSavantStyleHiddenDefaultsAreOverridden() {
- $this->get('http://www.lastcraft.com/test/savant_style_form.html');
- $this->assertTrue($this->setField('a', array('a1')));
- $this->assertTrue($this->setField('b', 'b1'));
- $this->assertTrue($this->clickSubmit('Go!'));
- $this->assertWantedText('a=[a1]');
- $this->assertWantedText('b=[b1]');
- }
-
- function testSavantStyleFormSettingById() {
- $this->get('http://www.lastcraft.com/test/savant_style_form.html');
- $this->assertFieldById(1, array('a0'));
- $this->assertFieldById(4, array('b0'));
- $this->assertTrue($this->setFieldById(2, 'a1'));
- $this->assertTrue($this->setFieldById(5, 'b1'));
- $this->assertTrue($this->clickSubmitById(99));
- $this->assertWantedText('a=[a1]');
- $this->assertWantedText('b=[b1]');
- }
- }
-
- class TestOfLiveHistoryNavigation extends WebTestCase {
-
- function setUp() {
- $this->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- }
-
- function testRetry() {
- $this->get('http://www.lastcraft.com/test/cookie_based_counter.php');
- $this->assertWantedPattern('/count: 1/i');
- $this->retry();
- $this->assertWantedPattern('/count: 2/i');
- $this->retry();
- $this->assertWantedPattern('/count: 3/i');
- }
-
- function testOfBackButton() {
- $this->get('http://www.lastcraft.com/test/1.html');
- $this->clickLink('2');
- $this->assertTitle('2');
- $this->assertTrue($this->back());
- $this->assertTitle('1');
- $this->assertTrue($this->forward());
- $this->assertTitle('2');
- $this->assertFalse($this->forward());
- }
-
- function testGetRetryResubmitsData() {
- $this->assertTrue($this->get(
- 'http://www.lastcraft.com/test/network_confirm.php?a=aaa'));
- $this->assertWantedPattern('/Request method.*?<dd>GET<\/dd>/');
- $this->assertWantedText('a=[aaa]');
- $this->retry();
- $this->assertWantedPattern('/Request method.*?<dd>GET<\/dd>/');
- $this->assertWantedText('a=[aaa]');
- }
-
- function testGetRetryResubmitsExtraData() {
- $this->assertTrue($this->get(
- 'http://www.lastcraft.com/test/network_confirm.php',
- array('a' => 'aaa')));
- $this->assertWantedPattern('/Request method.*?<dd>GET<\/dd>/');
- $this->assertWantedText('a=[aaa]');
- $this->retry();
- $this->assertWantedPattern('/Request method.*?<dd>GET<\/dd>/');
- $this->assertWantedText('a=[aaa]');
- }
-
- function testPostRetryResubmitsData() {
- $this->assertTrue($this->post(
- 'http://www.lastcraft.com/test/network_confirm.php',
- array('a' => 'aaa')));
- $this->assertWantedPattern('/Request method.*?<dd>POST<\/dd>/');
- $this->assertWantedText('a=[aaa]');
- $this->retry();
- $this->assertWantedPattern('/Request method.*?<dd>POST<\/dd>/');
- $this->assertWantedText('a=[aaa]');
- }
-
- function testGetRetryResubmitsRepeatedData() {
- $this->assertTrue($this->get(
- 'http://www.lastcraft.com/test/network_confirm.php?a=1&a=2'));
- $this->assertWantedPattern('/Request method.*?<dd>GET<\/dd>/');
- $this->assertWantedText('a=[1, 2]');
- $this->retry();
- $this->assertWantedPattern('/Request method.*?<dd>GET<\/dd>/');
- $this->assertWantedText('a=[1, 2]');
- }
- }
-
- class TestOfLiveAuthentication extends WebTestCase {
-
- function setUp() {
- $this->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- }
-
- function testChallengeFromProtectedPage() {
- $this->get('http://www.lastcraft.com/test/protected/');
- $this->assertResponse(401);
- $this->assertAuthentication('Basic');
- $this->assertRealm('SimpleTest basic authentication');
- $this->authenticate('test', 'secret');
- $this->assertResponse(200);
- $this->retry();
- $this->assertResponse(200);
- }
-
- function testEncodedAuthenticationFetchesPage() {
- $this->get('http://test:secret@www.lastcraft.com/test/protected/');
- $this->assertResponse(200);
- }
-
- function testRealmExtendsToWholeDirectory() {
- $this->get('http://www.lastcraft.com/test/protected/1.html');
- $this->authenticate('test', 'secret');
- $this->clickLink('2');
- $this->assertResponse(200);
- $this->clickLink('3');
- $this->assertResponse(200);
- }
-
- function testRedirectKeepsAuthentication() {
- $this->get('http://www.lastcraft.com/test/protected/local_redirect.php');
- $this->authenticate('test', 'secret');
- $this->assertTitle('Simple test target file');
- }
-
- function testSessionRestartLosesAuthentication() {
- $this->get('http://www.lastcraft.com/test/protected/');
- $this->authenticate('test', 'secret');
- $this->assertResponse(200);
- $this->restart();
- $this->get('http://www.lastcraft.com/test/protected/');
- $this->assertResponse(401);
- }
- }
-
- class TestOfLoadingFrames extends WebTestCase {
-
- function setUp() {
- $this->addHeader('User-Agent: SimpleTest ' . SimpleTestOptions::getVersion());
- }
-
- function testNoFramesContentWhenFramesDisabled() {
- $this->ignoreFrames();
- $this->get('http://www.lastcraft.com/test/one_page_frameset.html');
- $this->assertTitle('Frameset for testing of SimpleTest');
- $this->assertWantedText('This content is for no frames only');
- }
-
- function testPatternMatchCanReadTheOnlyFrame() {
- $this->get('http://www.lastcraft.com/test/one_page_frameset.html');
- $this->assertWantedText('A target for the SimpleTest test suite');
- $this->assertNoUnwantedText('This content is for no frames only');
- }
-
- function testMessyFramesetResponsesByName() {
- $this->assertTrue($this->get(
- 'http://www.lastcraft.com/test/messy_frameset.html'));
- $this->assertTitle('Frameset for testing of SimpleTest');
-
- $this->assertTrue($this->setFrameFocus('Front controller'));
- $this->assertResponse(200);
- $this->assertWantedText('Simple test front controller');
-
- $this->assertTrue($this->setFrameFocus('One'));
- $this->assertResponse(200);
- $this->assertLink('2');
-
- $this->assertTrue($this->setFrameFocus('Frame links'));
- $this->assertResponse(200);
- $this->assertLink('Set one to 2');
-
- $this->assertTrue($this->setFrameFocus('Counter'));
- $this->assertResponse(200);
- $this->assertWantedText('Count: 1');
-
- $this->assertTrue($this->setFrameFocus('Redirected'));
- $this->assertResponse(200);
- $this->assertWantedText('r=rrr');
-
- $this->assertTrue($this->setFrameFocus('Protected'));
- $this->assertResponse(401);
-
- $this->assertTrue($this->setFrameFocus('Protected redirect'));
- $this->assertResponse(401);
-
- $this->assertTrue($this->setFrameFocusByIndex(1));
- $this->assertResponse(200);
- $this->assertWantedText('Simple test front controller');
-
- $this->assertTrue($this->setFrameFocusByIndex(2));
- $this->assertResponse(200);
- $this->assertLink('2');
-
- $this->assertTrue($this->setFrameFocusByIndex(3));
- $this->assertResponse(200);
- $this->assertLink('Set one to 2');
-
- $this->assertTrue($this->setFrameFocusByIndex(4));
- $this->assertResponse(200);
- $this->assertWantedTExt('Count: 1');
-
- $this->assertTrue($this->setFrameFocusByIndex(5));
- $this->assertResponse(200);
- $this->assertWantedText('r=rrr');
-
- $this->assertTrue($this->setFrameFocusByIndex(6));
- $this->assertResponse(401);
-
- $this->assertTrue($this->setFrameFocusByIndex(7));
- }
-
- function testReloadingFramesetPage() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->assertWantedText('Count: 1');
- $this->retry();
- $this->assertWantedText('Count: 2');
- $this->retry();
- $this->assertWantedText('Count: 3');
- }
-
- function testReloadingSingleFrameWithCookieCounter() {
- $this->get('http://www.lastcraft.com/test/counting_frameset.html');
- $this->setFrameFocus('a');
- $this->assertWantedText('Count: 1');
- $this->setFrameFocus('b');
- $this->assertWantedText('Count: 2');
-
- $this->setFrameFocus('a');
- $this->retry();
- $this->assertWantedText('Count: 3');
- $this->retry();
- $this->assertWantedText('Count: 4');
- $this->setFrameFocus('b');
- $this->assertWantedText('Count: 2');
- }
-
- function testReloadingFrameWhenUnfocusedReloadsWholeFrameset() {
- $this->get('http://www.lastcraft.com/test/counting_frameset.html');
- $this->setFrameFocus('a');
- $this->assertWantedText('Count: 1');
- $this->setFrameFocus('b');
- $this->assertWantedText('Count: 2');
-
- $this->clearFrameFocus('a');
- $this->retry();
-
- $this->assertTitle('Frameset for testing of SimpleTest');
- $this->setFrameFocus('a');
- $this->assertWantedText('Count: 3');
- $this->setFrameFocus('b');
- $this->assertWantedText('Count: 4');
- }
-
- function testClickingNormalLinkReplacesJustThatFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->clickLink('2');
- $this->assertLink('3');
- $this->assertWantedText('Simple test front controller');
- }
-
- function testJumpToNamedPageReplacesJustThatFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->assertWantedPattern('/Simple test front controller/');
- $this->clickLink('Index');
- $this->assertResponse(200);
- $this->assertWantedText('[action=index]');
- $this->assertWantedText('Count: 1');
- }
-
- function testJumpToUnnamedPageReplacesJustThatFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->clickLink('No page');
- $this->assertResponse(200);
- $this->assertWantedText('Simple test front controller');
- $this->assertWantedText('[action=no_page]');
- $this->assertWantedText('Count: 1');
- }
-
- function testJumpToUnnamedPageWithBareParameterReplacesJustThatFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->clickLink('Bare action');
- $this->assertResponse(200);
- $this->assertWantedText('Simple test front controller');
- $this->assertWantedText('[action=]');
- $this->assertWantedText('Count: 1');
- }
-
- function testJumpToUnnamedPageWithEmptyQueryReplacesJustThatFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->clickLink('Empty query');
- $this->assertResponse(200);
- $this->assertWantedPattern('/Simple test front controller/');
- $this->assertWantedPattern('/raw get data.*?\[\].*?get data/si');
- $this->assertWantedPattern('/Count: 1/');
- }
-
- function testJumpToUnnamedPageWithEmptyLinkReplacesJustThatFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->clickLink('Empty link');
- $this->assertResponse(200);
- $this->assertWantedPattern('/Simple test front controller/');
- $this->assertWantedPattern('/raw get data.*?\[\].*?get data/si');
- $this->assertWantedPattern('/Count: 1/');
- }
-
- function testJumpBackADirectoryLevelReplacesJustThatFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->clickLink('Down one');
- $this->assertWantedPattern('/index of \/test/i');
- $this->assertWantedPattern('/Count: 1/');
- }
-
- function testSubmitToNamedPageReplacesJustThatFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->assertWantedPattern('/Simple test front controller/');
- $this->clickSubmit('Index');
- $this->assertResponse(200);
- $this->assertWantedText('[action=Index]');
- $this->assertWantedText('Count: 1');
- }
-
- function testSubmitToSameDirectoryReplacesJustThatFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->clickSubmit('Same directory');
- $this->assertResponse(200);
- $this->assertWantedText('[action=Same+directory]');
- $this->assertWantedText('Count: 1');
- }
-
- function testSubmitToEmptyActionReplacesJustThatFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->clickSubmit('Empty action');
- $this->assertResponse(200);
- $this->assertWantedText('[action=Empty+action]');
- $this->assertWantedText('Count: 1');
- }
-
- function testSubmitToNoActionReplacesJustThatFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->clickSubmit('No action');
- $this->assertResponse(200);
- $this->assertWantedText('[action=No+action]');
- $this->assertWantedText('Count: 1');
- }
-
- function testSubmitBackADirectoryLevelReplacesJustThatFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->clickSubmit('Down one');
- $this->assertWantedPattern('/index of \/test/i');
- $this->assertWantedPattern('/Count: 1/');
- }
-
- function testTopLinkExitsFrameset() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->clickLink('Exit the frameset');
- $this->assertTitle('Simple test target file');
- }
-
- function testLinkInOnePageCanLoadAnother() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->assertNoLink('3');
- $this->clickLink('Set one to 2');
- $this->assertLink('3');
- $this->assertNoLink('2');
- $this->assertTitle('Frameset for testing of SimpleTest');
- }
- }
-
- class TestOfFrameAuthentication extends WebTestCase {
-
- function testUnauthenticatedFrameSendsChallenge() {
- $this->get('http://www.lastcraft.com/test/protected/');
- $this->setFrameFocus('Protected');
- $this->assertAuthentication('Basic');
- $this->assertRealm('SimpleTest basic authentication');
- $this->assertResponse(401);
- }
-
- function testCanReadFrameFromAlreadyAuthenticatedRealm() {
- $this->get('http://www.lastcraft.com/test/protected/');
- $this->authenticate('test', 'secret');
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->setFrameFocus('Protected');
- $this->assertResponse(200);
- $this->assertWantedText('A target for the SimpleTest test suite');
- }
-
- function testCanAuthenticateFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->setFrameFocus('Protected');
- $this->authenticate('test', 'secret');
- $this->assertResponse(200);
- $this->assertWantedText('A target for the SimpleTest test suite');
- $this->clearFrameFocus();
- $this->assertWantedText('Count: 1');
- }
-
- function testCanAuthenticateRedirectedFrame() {
- $this->get('http://www.lastcraft.com/test/messy_frameset.html');
- $this->setFrameFocus('Protected redirect');
- $this->assertResponse(401);
- $this->authenticate('test', 'secret');
- $this->assertResponse(200);
- $this->assertWantedText('A target for the SimpleTest test suite');
- $this->clearFrameFocus();
- $this->assertWantedText('Count: 1');
- }
- }
-
- class TestOfNestedFrames extends WebTestCase {
-
- function testCanNavigateToSpecificContent() {
- $this->get('http://www.lastcraft.com/test/nested_frameset.html');
- $this->assertTitle('Nested frameset for testing of SimpleTest');
-
- $this->assertWantedPattern('/This is frame A/');
- $this->assertWantedPattern('/This is frame B/');
- $this->assertWantedPattern('/Simple test front controller/');
- $this->assertLink('2');
- $this->assertLink('Set one to 2');
- $this->assertWantedPattern('/Count: 1/');
- $this->assertWantedPattern('/r=rrr/');
-
- $this->setFrameFocus('pair');
- $this->assertWantedPattern('/This is frame A/');
- $this->assertWantedPattern('/This is frame B/');
- $this->assertNoUnwantedPattern('/Simple test front controller/');
- $this->assertNoLink('2');
-
- $this->setFrameFocus('aaa');
- $this->assertWantedPattern('/This is frame A/');
- $this->assertNoUnwantedPattern('/This is frame B/');
-
- $this->clearFrameFocus();
- $this->assertResponse(200);
- $this->setFrameFocus('messy');
- $this->assertResponse(200);
- $this->setFrameFocus('Front controller');
- $this->assertResponse(200);
- $this->assertWantedPattern('/Simple test front controller/');
- $this->assertNoLink('2');
- }
-
- function testReloadingFramesetPage() {
- $this->get('http://www.lastcraft.com/test/nested_frameset.html');
- $this->assertWantedPattern('/Count: 1/');
- $this->retry();
- $this->assertWantedPattern('/Count: 2/');
- $this->retry();
- $this->assertWantedPattern('/Count: 3/');
- }
-
- function testRetryingNestedPageOnlyRetriesThatSet() {
- $this->get('http://www.lastcraft.com/test/nested_frameset.html');
- $this->assertWantedPattern('/Count: 1/');
- $this->setFrameFocus('messy');
- $this->retry();
- $this->assertWantedPattern('/Count: 2/');
- $this->setFrameFocus('Counter');
- $this->retry();
- $this->assertWantedPattern('/Count: 3/');
-
- $this->clearFrameFocus();
- $this->setFrameFocus('messy');
- $this->setFrameFocus('Front controller');
- $this->retry();
-
- $this->clearFrameFocus();
- $this->assertWantedPattern('/Count: 3/');
- }
-
- function testAuthenticatingNestedPage() {
- $this->get('http://www.lastcraft.com/test/nested_frameset.html');
- $this->setFrameFocus('messy');
- $this->setFrameFocus('Protected');
- $this->assertAuthentication('Basic');
- $this->assertRealm('SimpleTest basic authentication');
- $this->assertResponse(401);
-
- $this->authenticate('test', 'secret');
- $this->assertResponse(200);
- $this->assertWantedPattern('/A target for the SimpleTest test suite/');
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/adapter_test.php b/tests/UnitTests/simpletest/test/adapter_test.php deleted file mode 100644 index d7f67caf..00000000 --- a/tests/UnitTests/simpletest/test/adapter_test.php +++ /dev/null @@ -1,74 +0,0 @@ -<?php
- // $Id: adapter_test.php,v 1.7 2005/01/13 01:31:57 lastcraft Exp $
-
- class SameTestClass {
- }
-
- class TestOfPearAdapter extends PHPUnit_TestCase {
-
- function testBoolean() {
- $this->assertTrue(true, "PEAR true");
- $this->assertFalse(false, "PEAR false");
- }
-
- function testName() {
- $this->assertTrue($this->getName() == get_class($this));
- }
-
- function testPass() {
- $this->pass("PEAR pass");
- }
-
- function testNulls() {
- $value = null;
- $this->assertNull($value, "PEAR null");
- $value = 0;
- $this->assertNotNull($value, "PEAR not null");
- }
-
- function testType() {
- $this->assertType("Hello", "string", "PEAR type");
- }
-
- function testEquals() {
- $this->assertEquals(12, 12, "PEAR identity");
- $this->setLooselyTyped(true);
- $this->assertEquals("12", 12, "PEAR equality");
- }
-
- function testSame() {
- $same = &new SameTestClass();
- $this->assertSame($same, $same, "PEAR same");
- }
-
- function testRegExp() {
- $this->assertRegExp('/hello/', "A big hello from me", "PEAR regex");
- }
- }
-
- class TestOfPhpUnitAdapter extends TestCase {
- function TestOfPhpUnitAdapter() {
- $this->TestCase("TestOfPhpUnitAdapter");
- }
-
- function testBoolean() {
- $this->assert(true, "PHP Unit true");
- }
-
- function testName() {
- $this->assertTrue($this->name() == "TestOfPhpUnitAdapter");
- }
-
- function testEquals() {
- $this->assertEquals(12, 12, "PHP Unit equality");
- }
-
- function testMultilineEquals() {
- $this->assertEquals("a\nb\n", "a\nb\n", "PHP Unit equality");
- }
-
- function testRegExp() {
- $this->assertRegexp('/hello/', "A big hello from me", "PEAR regex");
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/all_tests.php b/tests/UnitTests/simpletest/test/all_tests.php deleted file mode 100644 index d5e8a9de..00000000 --- a/tests/UnitTests/simpletest/test/all_tests.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php
- // $Id: all_tests.php,v 1.20 2005/02/05 04:51:31 lastcraft Exp $
- define('TEST', __FILE__);
- require_once('../unit_tester.php');
- require_once('../shell_tester.php');
- require_once('../reporter.php');
- require_once('../mock_objects.php');
- require_once('unit_tests.php');
-
- // Uncomment and modify the following line if you are accessing
- // the net via a proxy server.
- //
- // SimpleTestOptions::useProxy('http://my-proxy', 'optional username', 'optional password');
-
- class AllTests extends GroupTest {
- function AllTests() {
- $this->GroupTest('All tests for SimpleTest ' . SimpleTestOptions::getVersion());
- $this->addTestCase(new UnitTests());
- $this->addTestFile('shell_test.php');
- $this->addTestFile('live_test.php');
- $this->addTestFile('acceptance_test.php');
- $this->addTestFile('real_sites_test.php');
- }
- }
-
- $test = new AllTests();
- if (SimpleReporter::inCli()) {
- exit ($test->run(new TextReporter()) ? 0 : 1);
- }
- $test->run(new HtmlReporter());
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/authentication_test.php b/tests/UnitTests/simpletest/test/authentication_test.php deleted file mode 100644 index 3a12a2ff..00000000 --- a/tests/UnitTests/simpletest/test/authentication_test.php +++ /dev/null @@ -1,141 +0,0 @@ -<?php
- // $Id: authentication_test.php,v 1.8 2004/09/24 22:55:10 lastcraft Exp $
- require_once(dirname(__FILE__) . '/../authentication.php');
- require_once(dirname(__FILE__) . '/../http.php');
- Mock::generate('SimpleHttpRequest');
-
- class TestOfRealm extends UnitTestCase {
-
- function testWithinSameUrl() {
- $realm = &new SimpleRealm(
- 'Basic',
- new SimpleUrl('http://www.here.com/path/hello.html'));
- $this->assertTrue($realm->isWithin(
- new SimpleUrl('http://www.here.com/path/hello.html')));
- }
-
- function testInsideWithLongerUrl() {
- $realm = &new SimpleRealm(
- 'Basic',
- new SimpleUrl('http://www.here.com/path/'));
- $this->assertTrue($realm->isWithin(
- new SimpleUrl('http://www.here.com/path/hello.html')));
- }
-
- function testBelowRootIsOutside() {
- $realm = &new SimpleRealm(
- 'Basic',
- new SimpleUrl('http://www.here.com/path/'));
- $this->assertTrue($realm->isWithin(
- new SimpleUrl('http://www.here.com/path/more/hello.html')));
- }
-
- function testOldNetscapeDefinitionIsOutside() {
- $realm = &new SimpleRealm(
- 'Basic',
- new SimpleUrl('http://www.here.com/path/'));
- $this->assertFalse($realm->isWithin(
- new SimpleUrl('http://www.here.com/pathmore/hello.html')));
- }
-
- function testDifferentPageNameStillInside() {
- $realm = &new SimpleRealm(
- 'Basic',
- new SimpleUrl('http://www.here.com/path/hello.html'));
- $this->assertTrue($realm->isWithin(
- new SimpleUrl('http://www.here.com/path/goodbye.html')));
- }
-
- function testNewUrlInSameDirectoryDoesNotChangeRealm() {
- $realm = &new SimpleRealm(
- 'Basic',
- new SimpleUrl('http://www.here.com/path/hello.html'));
- $realm->stretch(new SimpleUrl('http://www.here.com/path/goodbye.html'));
- $this->assertTrue($realm->isWithin(
- new SimpleUrl('http://www.here.com/path/index.html')));
- $this->assertFalse($realm->isWithin(
- new SimpleUrl('http://www.here.com/index.html')));
- }
-
- function testNewUrlMakesRealmTheCommonPath() {
- $realm = &new SimpleRealm(
- 'Basic',
- new SimpleUrl('http://www.here.com/path/here/hello.html'));
- $realm->stretch(new SimpleUrl('http://www.here.com/path/there/goodbye.html'));
- $this->assertTrue($realm->isWithin(
- new SimpleUrl('http://www.here.com/path/here/index.html')));
- $this->assertTrue($realm->isWithin(
- new SimpleUrl('http://www.here.com/path/there/index.html')));
- $this->assertTrue($realm->isWithin(
- new SimpleUrl('http://www.here.com/path/index.html')));
- $this->assertFalse($realm->isWithin(
- new SimpleUrl('http://www.here.com/index.html')));
- $this->assertFalse($realm->isWithin(
- new SimpleUrl('http://www.here.com/paths/index.html')));
- $this->assertFalse($realm->isWithin(
- new SimpleUrl('http://www.here.com/pathindex.html')));
- }
- }
-
- class TestOfAuthenticator extends UnitTestCase {
-
- function testNoRealms() {
- $request = &new MockSimpleHttpRequest($this);
- $request->expectNever('addHeaderLine');
- $authenticator = &new SimpleAuthenticator();
- $authenticator->addHeaders($request, new SimpleUrl('http://here.com/'));
- $request->tally();
- }
-
- function &createSingleRealm() {
- $authenticator = &new SimpleAuthenticator();
- $authenticator->addRealm(
- new SimpleUrl('http://www.here.com/path/hello.html'),
- 'Basic',
- 'Sanctuary');
- $authenticator->setIdentityForRealm('www.here.com', 'Sanctuary', 'test', 'secret');
- return $authenticator;
- }
-
- function testOutsideRealm() {
- $request = &new MockSimpleHttpRequest($this);
- $request->expectNever('addHeaderLine');
- $authenticator = &$this->createSingleRealm();
- $authenticator->addHeaders(
- $request,
- new SimpleUrl('http://www.here.com/hello.html'));
- $request->tally();
- }
-
- function testWithinRealm() {
- $request = &new MockSimpleHttpRequest($this);
- $request->expectOnce('addHeaderLine');
- $authenticator = &$this->createSingleRealm();
- $authenticator->addHeaders(
- $request,
- new SimpleUrl('http://www.here.com/path/more/hello.html'));
- $request->tally();
- }
-
- function testRestartingClearsRealm() {
- $request = &new MockSimpleHttpRequest($this);
- $request->expectNever('addHeaderLine');
- $authenticator = &$this->createSingleRealm();
- $authenticator->restartSession();
- $authenticator->addHeaders(
- $request,
- new SimpleUrl('http://www.here.com/hello.html'));
- $request->tally();
- }
-
- function testDifferentHostIsOutsideRealm() {
- $request = &new MockSimpleHttpRequest($this);
- $request->expectNever('addHeaderLine');
- $authenticator = &$this->createSingleRealm();
- $authenticator->addHeaders(
- $request,
- new SimpleUrl('http://here.com/path/hello.html'));
- $request->tally();
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/browser_test.php b/tests/UnitTests/simpletest/test/browser_test.php deleted file mode 100644 index b7d23fc4..00000000 --- a/tests/UnitTests/simpletest/test/browser_test.php +++ /dev/null @@ -1,870 +0,0 @@ -<?php
- // $Id: browser_test.php,v 1.100 2005/02/22 02:53:49 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../browser.php');
- require_once(dirname(__FILE__) . '/../user_agent.php');
- require_once(dirname(__FILE__) . '/../http.php');
- require_once(dirname(__FILE__) . '/../page.php');
- require_once(dirname(__FILE__) . '/../encoding.php');
-
- Mock::generate('SimpleHttpResponse');
- Mock::generate('SimplePage');
- Mock::generate('SimpleForm');
- Mock::generate('SimpleUserAgent');
- Mock::generatePartial(
- 'SimpleBrowser',
- 'MockParseSimpleBrowser',
- array('_createUserAgent', '_parse'));
- Mock::generatePartial(
- 'SimpleBrowser',
- 'MockUserAgentSimpleBrowser',
- array('_createUserAgent'));
-
- class TestOfHistory extends UnitTestCase {
-
- function testEmptyHistoryHasFalseContents() {
- $history = &new SimpleBrowserHistory();
- $this->assertIdentical($history->getMethod(), false);
- $this->assertIdentical($history->getUrl(), false);
- $this->assertIdentical($history->getParameters(), false);
- }
-
- function testCannotMoveInEmptyHistory() {
- $history = &new SimpleBrowserHistory();
- $this->assertFalse($history->back());
- $this->assertFalse($history->forward());
- }
-
- function testCurrentTargetAccessors() {
- $history = &new SimpleBrowserHistory();
- $history->recordEntry(
- 'GET',
- new SimpleUrl('http://www.here.com/'),
- new SimpleFormEncoding());
- $this->assertIdentical($history->getMethod(), 'GET');
- $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.here.com/'));
- $this->assertIdentical($history->getParameters(), new SimpleFormEncoding());
- }
-
- function testSecondEntryAccessors() {
- $history = &new SimpleBrowserHistory();
- $history->recordEntry(
- 'GET',
- new SimpleUrl('http://www.first.com/'),
- new SimpleFormEncoding());
- $history->recordEntry(
- 'POST',
- new SimpleUrl('http://www.second.com/'),
- new SimpleFormEncoding(array('a' => 1)));
- $this->assertIdentical($history->getMethod(), 'POST');
- $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.second.com/'));
- $this->assertIdentical(
- $history->getParameters(),
- new SimpleFormEncoding(array('a' => 1)));
- }
-
- function testGoingBackwards() {
- $history = &new SimpleBrowserHistory();
- $history->recordEntry(
- 'GET',
- new SimpleUrl('http://www.first.com/'),
- new SimpleFormEncoding());
- $history->recordEntry(
- 'POST',
- new SimpleUrl('http://www.second.com/'),
- new SimpleFormEncoding(array('a' => 1)));
- $this->assertTrue($history->back());
- $this->assertIdentical($history->getMethod(), 'GET');
- $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.first.com/'));
- $this->assertIdentical($history->getParameters(), new SimpleFormEncoding());
- }
-
- function testGoingBackwardsOffBeginning() {
- $history = &new SimpleBrowserHistory();
- $history->recordEntry(
- 'GET',
- new SimpleUrl('http://www.first.com/'),
- new SimpleFormEncoding());
- $this->assertFalse($history->back());
- $this->assertIdentical($history->getMethod(), 'GET');
- $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.first.com/'));
- $this->assertIdentical($history->getParameters(), new SimpleFormEncoding());
- }
-
- function testGoingForwardsOffEnd() {
- $history = &new SimpleBrowserHistory();
- $history->recordEntry(
- 'GET',
- new SimpleUrl('http://www.first.com/'),
- new SimpleFormEncoding());
- $this->assertFalse($history->forward());
- $this->assertIdentical($history->getMethod(), 'GET');
- $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.first.com/'));
- $this->assertIdentical($history->getParameters(), new SimpleFormEncoding());
- }
-
- function testGoingBackwardsAndForwards() {
- $history = &new SimpleBrowserHistory();
- $history->recordEntry(
- 'GET',
- new SimpleUrl('http://www.first.com/'),
- new SimpleFormEncoding());
- $history->recordEntry(
- 'POST',
- new SimpleUrl('http://www.second.com/'),
- new SimpleFormEncoding(array('a' => 1)));
- $this->assertTrue($history->back());
- $this->assertTrue($history->forward());
- $this->assertIdentical($history->getMethod(), 'POST');
- $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.second.com/'));
- $this->assertIdentical(
- $history->getParameters(),
- new SimpleFormEncoding(array('a' => 1)));
- }
-
- function testNewEntryReplacesNextOne() {
- $history = &new SimpleBrowserHistory();
- $history->recordEntry(
- 'GET',
- new SimpleUrl('http://www.first.com/'),
- new SimpleFormEncoding());
- $history->recordEntry(
- 'POST',
- new SimpleUrl('http://www.second.com/'),
- new SimpleFormEncoding(array('a' => 1)));
- $history->back();
- $history->recordEntry(
- 'GET',
- new SimpleUrl('http://www.third.com/'),
- new SimpleFormEncoding());
- $this->assertIdentical($history->getMethod(), 'GET');
- $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.third.com/'));
- $this->assertIdentical($history->getParameters(), new SimpleFormEncoding());
- }
-
- function testNewEntryDropsFutureEntries() {
- $history = &new SimpleBrowserHistory();
- $history->recordEntry(
- 'GET',
- new SimpleUrl('http://www.first.com/'),
- new SimpleFormEncoding());
- $history->recordEntry(
- 'GET',
- new SimpleUrl('http://www.second.com/'),
- new SimpleFormEncoding());
- $history->recordEntry(
- 'GET',
- new SimpleUrl('http://www.third.com/'),
- new SimpleFormEncoding());
- $history->back();
- $history->back();
- $history->recordEntry(
- 'GET',
- new SimpleUrl('http://www.fourth.com/'),
- new SimpleFormEncoding());
- $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.fourth.com/'));
- $this->assertFalse($history->forward());
- $history->back();
- $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.first.com/'));
- $this->assertFalse($history->back());
- }
- }
-
- class TestOfParsedPageAccess extends UnitTestCase {
-
- function &loadPage(&$page) {
- $response = &new MockSimpleHttpResponse($this);
-
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', $response);
-
- $browser = &new MockParseSimpleBrowser($this);
- $browser->setReturnReference('_createUserAgent', $agent);
- $browser->setReturnReference('_parse', $page);
- $browser->SimpleBrowser();
-
- $browser->get('http://this.com/page.html');
- return $browser;
- }
-
- function testAccessorsWhenNoPage() {
- $agent = &new MockSimpleUserAgent($this);
-
- $browser = &new MockParseSimpleBrowser($this);
- $browser->setReturnReference('_createUserAgent', $agent);
- $browser->SimpleBrowser();
-
- $this->assertEqual($browser->getContent(), '');
- }
-
- function testParse() {
- $page = &new MockSimplePage($this);
- $page->setReturnValue('getRequest', "GET here.html\r\n\r\n");
- $page->setReturnValue('getRaw', 'Raw HTML');
- $page->setReturnValue('getTitle', 'Here');
- $page->setReturnValue('getFrameFocus', 'Frame');
- $page->setReturnValue('getMimeType', 'text/html');
- $page->setReturnValue('getResponseCode', 200);
- $page->setReturnValue('getAuthentication', 'Basic');
- $page->setReturnValue('getRealm', 'Somewhere');
- $page->setReturnValue('getTransportError', 'Ouch!');
-
- $browser = &$this->loadPage($page);
-
- $this->assertEqual($browser->getRequest(), "GET here.html\r\n\r\n");
- $this->assertEqual($browser->getContent(), 'Raw HTML');
- $this->assertEqual($browser->getTitle(), 'Here');
- $this->assertEqual($browser->getFrameFocus(), 'Frame');
- $this->assertIdentical($browser->getResponseCode(), 200);
- $this->assertEqual($browser->getMimeType(), 'text/html');
- $this->assertEqual($browser->getAuthentication(), 'Basic');
- $this->assertEqual($browser->getRealm(), 'Somewhere');
- $this->assertEqual($browser->getTransportError(), 'Ouch!');
- }
-
- function testLinkAffirmationWhenPresent() {
- $page = &new MockSimplePage($this);
- $page->setReturnValue('getUrlsByLabel', array('http://www.nowhere.com'));
- $page->expectOnce('getUrlsByLabel', array('a link label'));
-
- $browser = &$this->loadPage($page);
- $this->assertTrue($browser->isLink('a link label'));
-
- $page->tally();
- }
-
- function testLinkAffirmationByIdWhenPresent() {
- $page = &new MockSimplePage($this);
- $page->setReturnValue('getUrlById', true, array(99));
- $page->setReturnValue('getUrlById', false, array('*'));
-
- $browser = &$this->loadPage($page);
- $this->assertTrue($browser->isLinkById(99));
- $this->assertFalse($browser->isLinkById(98));
-
- $page->tally();
- }
-
- function testFormHandling() {
- $page = &new MockSimplePage($this);
- $page->setReturnValue('getField', 'Value');
- $page->expectOnce('getField', array('key'));
- $page->expectOnce('setField', array('key', 'Value'));
- $page->setReturnValue('getFieldById', 'Id value');
- $page->expectOnce('getFieldById', array(99));
- $page->expectOnce('setFieldById', array(99, 'Id value'));
-
- $browser = &$this->loadPage($page);
- $this->assertEqual($browser->getField('key'), 'Value');
- $this->assertEqual($browser->getFieldById(99), 'Id value');
- $browser->setField('key', 'Value');
- $browser->setFieldById(99, 'Id value');
-
- $page->tally();
- }
- }
-
- class TestOfBrowserNavigation extends UnitTestCase {
-
- function &createBrowser(&$agent, &$page) {
- $browser = &new MockParseSimpleBrowser($this);
- $browser->setReturnReference('_createUserAgent', $agent);
- $browser->setReturnReference('_parse', $page);
- $browser->SimpleBrowser();
- return $browser;
- }
-
- function testClickLinkRequestsPage() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
- $agent->expectArgumentsAt(
- 0,
- 'fetchResponse',
- array('GET', new SimpleUrl('http://this.com/page.html'), false));
- $agent->expectArgumentsAt(
- 1,
- 'fetchResponse',
- array('GET', new SimpleUrl('http://this.com/new.html'), false));
- $agent->expectCallCount('fetchResponse', 2);
-
- $page = &new MockSimplePage($this);
- $page->setReturnValue('getUrlsByLabel', array(new SimpleUrl('http://this.com/new.html')));
- $page->expectOnce('getUrlsByLabel', array('New'));
- $page->setReturnValue('getRaw', 'A page');
-
- $browser = &$this->createBrowser($agent, $page);
- $browser->get('http://this.com/page.html');
- $this->assertTrue($browser->clickLink('New'));
-
- $agent->tally();
- $page->tally();
- }
-
- function testClickLinkWithUnknownFrameStillRequestsWholePage() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
- $agent->expectArgumentsAt(
- 0,
- 'fetchResponse',
- array('GET', new SimpleUrl('http://this.com/page.html'), false));
- $target = new SimpleUrl('http://this.com/new.html');
- $target->setTarget('missing');
- $agent->expectArgumentsAt(
- 1,
- 'fetchResponse',
- array('GET', $target, false));
- $agent->expectCallCount('fetchResponse', 2);
-
- $parsed_url = new SimpleUrl('http://this.com/new.html');
- $parsed_url->setTarget('missing');
-
- $page = &new MockSimplePage($this);
- $page->setReturnValue('getUrlsByLabel', array($parsed_url));
- $page->setReturnValue('hasFrames', false);
- $page->expectOnce('getUrlsByLabel', array('New'));
- $page->setReturnValue('getRaw', 'A page');
-
- $browser = &$this->createBrowser($agent, $page);
- $browser->get('http://this.com/page.html');
- $this->assertTrue($browser->clickLink('New'));
-
- $agent->tally();
- $page->tally();
- }
-
- function testClickingMissingLinkFails() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
-
- $page = &new MockSimplePage($this);
- $page->setReturnValue('getUrlsByLabel', array());
- $page->setReturnValue('getRaw', 'stuff');
-
- $browser = &$this->createBrowser($agent, $page);
- $this->assertTrue($browser->get('http://this.com/page.html'));
- $this->assertFalse($browser->clickLink('New'));
- }
-
- function testClickIndexedLink() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
- $agent->expectArgumentsAt(
- 1,
- 'fetchResponse',
- array('GET', new SimpleUrl('1.html'), false));
- $agent->expectCallCount('fetchResponse', 2);
-
- $page = &new MockSimplePage($this);
- $page->setReturnValue(
- 'getUrlsByLabel',
- array(new SimpleUrl('0.html'), new SimpleUrl('1.html')));
- $page->setReturnValue('getRaw', 'A page');
-
- $browser = &$this->createBrowser($agent, $page);
- $browser->get('http://this.com/page.html');
- $this->assertTrue($browser->clickLink('New', 1));
-
- $agent->tally();
- }
-
- function testClinkLinkById() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
- $agent->expectArgumentsAt(1, 'fetchResponse', array(
- 'GET',
- new SimpleUrl('http://this.com/link.html'),
- false));
- $agent->expectCallCount('fetchResponse', 2);
-
- $page = &new MockSimplePage($this);
- $page->setReturnValue('getUrlById', new SimpleUrl('http://this.com/link.html'));
- $page->expectOnce('getUrlById', array(2));
- $page->setReturnValue('getRaw', 'A page');
-
- $browser = &$this->createBrowser($agent, $page);
- $browser->get('http://this.com/page.html');
- $this->assertTrue($browser->clickLinkById(2));
-
- $agent->tally();
- $page->tally();
- }
-
- function testClickingMissingLinkIdFails() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
-
- $page = &new MockSimplePage($this);
- $page->setReturnValue('getUrlById', false);
-
- $browser = &$this->createBrowser($agent, $page);
- $browser->get('http://this.com/page.html');
- $this->assertFalse($browser->clickLink(0));
- }
-
- function testSubmitFormByLabel() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
- $agent->expectArgumentsAt(1, 'fetchResponse', array(
- 'POST',
- new SimpleUrl('http://this.com/handler.html'),
- new SimpleFormEncoding(array('a' => 'A'))));
- $agent->expectCallCount('fetchResponse', 2);
-
- $form = &new MockSimpleForm($this);
- $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html'));
- $form->setReturnValue('getMethod', 'post');
- $form->setReturnValue('submitButtonByLabel', new SimpleFormEncoding(array('a' => 'A')));
- $form->expectOnce('submitButtonByLabel', array('Go', false));
-
- $page = &new MockSimplePage($this);
- $page->setReturnReference('getFormBySubmitLabel', $form);
- $page->expectOnce('getFormBySubmitLabel', array('Go'));
- $page->setReturnValue('getRaw', 'stuff');
-
- $browser = &$this->createBrowser($agent, $page);
- $browser->get('http://this.com/page.html');
- $this->assertTrue($browser->clickSubmit('Go'));
-
- $agent->tally();
- $page->tally();
- $form->tally();
- }
-
- function testDefaultSubmitFormByLabel() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
- $agent->expectArgumentsAt(1, 'fetchResponse', array(
- 'GET',
- new SimpleUrl('http://this.com/page.html'),
- new SimpleFormEncoding(array('a' => 'A'))));
- $agent->expectCallCount('fetchResponse', 2);
-
- $form = &new MockSimpleForm($this);
- $form->setReturnValue('getAction', new SimpleUrl('http://this.com/page.html'));
- $form->setReturnValue('getMethod', 'get');
- $form->setReturnValue('submitButtonByLabel', new SimpleFormEncoding(array('a' => 'A')));
-
- $page = &new MockSimplePage($this);
- $page->setReturnReference('getFormBySubmitLabel', $form);
- $page->expectOnce('getFormBySubmitLabel', array('Submit'));
- $page->setReturnValue('getRaw', 'stuff');
- $page->setReturnValue('getUrl', new SimpleUrl('http://this.com/page.html'));
-
- $browser = &$this->createBrowser($agent, $page);
- $browser->get('http://this.com/page.html');
- $this->assertTrue($browser->clickSubmit());
-
- $agent->tally();
- $page->tally();
- $form->tally();
- }
-
- function testSubmitFormByName() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
-
- $form = &new MockSimpleForm($this);
- $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html'));
- $form->setReturnValue('getMethod', 'post');
- $form->setReturnValue('submitButtonByName', new SimpleFormEncoding(array('a' => 'A')));
-
- $page = &new MockSimplePage($this);
- $page->setReturnReference('getFormBySubmitName', $form);
- $page->expectOnce('getFormBySubmitName', array('me'));
- $page->setReturnValue('getRaw', 'stuff');
-
- $browser = &$this->createBrowser($agent, $page);
- $browser->get('http://this.com/page.html');
- $this->assertTrue($browser->clickSubmitByName('me'));
-
- $page->tally();
- }
-
- function testSubmitFormById() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
-
- $form = &new MockSimpleForm($this);
- $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html'));
- $form->setReturnValue('getMethod', 'post');
- $form->setReturnValue('submitButtonById', new SimpleFormEncoding(array('a' => 'A')));
- $form->expectOnce('submitButtonById', array(99, false));
-
- $page = &new MockSimplePage($this);
- $page->setReturnReference('getFormBySubmitId', $form);
- $page->expectOnce('getFormBySubmitId', array(99));
- $page->setReturnValue('getRaw', 'stuff');
-
- $browser = &$this->createBrowser($agent, $page);
- $browser->get('http://this.com/page.html');
- $this->assertTrue($browser->clickSubmitById(99));
-
- $page->tally();
- $form->tally();
- }
-
- function testSubmitFormByImageLabel() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
-
- $form = &new MockSimpleForm($this);
- $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html'));
- $form->setReturnValue('getMethod', 'post');
- $form->setReturnValue('submitImageByLabel', new SimpleFormEncoding(array('a' => 'A')));
- $form->expectOnce('submitImageByLabel', array('Go!', 10, 11, false));
-
- $page = &new MockSimplePage($this);
- $page->setReturnReference('getFormByImageLabel', $form);
- $page->expectOnce('getFormByImageLabel', array('Go!'));
- $page->setReturnValue('getRaw', 'stuff');
-
- $browser = &$this->createBrowser($agent, $page);
- $browser->get('http://this.com/page.html');
- $this->assertTrue($browser->clickImage('Go!', 10, 11));
-
- $page->tally();
- $form->tally();
- }
-
- function testSubmitFormByImageName() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
-
- $form = &new MockSimpleForm($this);
- $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html'));
- $form->setReturnValue('getMethod', 'post');
- $form->setReturnValue('submitImageByName', new SimpleFormEncoding(array('a' => 'A')));
- $form->expectOnce('submitImageByName', array('a', 10, 11, false));
-
- $page = &new MockSimplePage($this);
- $page->setReturnReference('getFormByImageName', $form);
- $page->expectOnce('getFormByImageName', array('a'));
- $page->setReturnValue('getRaw', 'stuff');
-
- $browser = &$this->createBrowser($agent, $page);
- $browser->get('http://this.com/page.html');
- $this->assertTrue($browser->clickImageByName('a', 10, 11));
-
- $page->tally();
- $form->tally();
- }
-
- function testSubmitFormByImageId() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
-
- $form = &new MockSimpleForm($this);
- $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html'));
- $form->setReturnValue('getMethod', 'post');
- $form->setReturnValue('submitImageById', new SimpleFormEncoding(array('a' => 'A')));
- $form->expectOnce('submitImageById', array(99, 10, 11, false));
-
- $page = &new MockSimplePage($this);
- $page->setReturnReference('getFormByImageId', $form);
- $page->expectOnce('getFormByImageId', array(99));
- $page->setReturnValue('getRaw', 'stuff');
-
- $browser = &$this->createBrowser($agent, $page);
- $browser->get('http://this.com/page.html');
- $this->assertTrue($browser->clickImageById(99, 10, 11));
-
- $page->tally();
- $form->tally();
- }
-
- function testSubmitFormByFormId() {
- $agent = &new MockSimpleUserAgent($this);
- $agent->setReturnReference('fetchResponse', new MockSimpleHttpResponse($this));
- $agent->expectArgumentsAt(1, 'fetchResponse', array(
- 'POST',
- new SimpleUrl('http://this.com/handler.html'),
- new SimpleFormEncoding(array('a' => 'A'))));
- $agent->expectCallCount('fetchResponse', 2);
-
- $form = &new MockSimpleForm($this);
- $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html'));
- $form->setReturnValue('getMethod', 'post');
- $form->setReturnValue('submit', new SimpleFormEncoding(array('a' => 'A')));
-
- $page = &new MockSimplePage($this);
- $page->setReturnReference('getFormById', $form);
- $page->expectOnce('getFormById', array(33));
- $page->setReturnValue('getRaw', 'stuff');
-
- $browser = &$this->createBrowser($agent, $page);
- $browser->get('http://this.com/page.html');
- $this->assertTrue($browser->submitFormById(33));
-
- $agent->tally();
- $page->tally();
- }
- }
-
- class TestOfBrowserFrames extends UnitTestCase {
-
- function &createBrowser(&$agent) {
- $browser = &new MockUserAgentSimpleBrowser($this);
- $browser->setReturnReference('_createUserAgent', $agent);
- $browser->SimpleBrowser();
- return $browser;
- }
-
- function &createUserAgent($pages) {
- $agent = &new MockSimpleUserAgent($this);
- foreach ($pages as $url => $raw) {
- $url = new SimpleUrl($url);
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getUrl', $url);
- $response->setReturnValue('getContent', $raw);
- $agent->setReturnReference('fetchResponse', $response, array('*', $url, '*'));
- }
- return $agent;
- }
-
- function testSimplePageHasNoFrames() {
- $browser = &$this->createBrowser($this->createUserAgent(
- array('http://site.with.no.frames/' => 'A non-framed page')));
- $this->assertEqual(
- $browser->get('http://site.with.no.frames/'),
- 'A non-framed page');
- $this->assertIdentical($browser->getFrames(), 'http://site.with.no.frames/');
- }
-
- function testFramesetWithNoFrames() {
- $browser = &$this->createBrowser($this->createUserAgent(
- array('http://site.with.no.frames/' => '<frameset></frameset>')));
- $this->assertEqual(
- $browser->get('http://site.with.no.frames/'),
- '');
- $this->assertIdentical($browser->getFrames(), array());
- }
-
- function testFramesetWithSingleFrame() {
- $frameset = '<frameset><frame name="a" src="frame.html"></frameset>';
- $browser = &$this->createBrowser($this->createUserAgent(array(
- 'http://site.with.one.frame/' => $frameset,
- 'http://site.with.one.frame/frame.html' => 'A frame')));
-
- $this->assertEqual(
- $browser->get('http://site.with.one.frame/'),
- 'A frame');
- $this->assertIdentical(
- $browser->getFrames(),
- array('a' => 'http://site.with.one.frame/frame.html'));
- }
-
- function testTitleTakenFromFramesetPage() {
- $frameset = '<title>Frameset title</title>' .
- '<frameset><frame name="a" src="frame.html"></frameset>';
- $browser = &$this->createBrowser($this->createUserAgent(array(
- 'http://site.with.one.frame/' => $frameset,
- 'http://site.with.one.frame/frame.html' => '<title>Page title</title>')));
-
- $browser->get('http://site.with.one.frame/');
- $this->assertEqual($browser->getTitle(), 'Frameset title');
- }
-
- function testFramesetWithSingleUnnamedFrame() {
- $frameset = '<frameset><frame src="frame.html"></frameset>';
- $browser = &$this->createBrowser($this->createUserAgent(array(
- 'http://site.with.one.frame/' => $frameset,
- 'http://site.with.one.frame/frame.html' => 'One frame')));
-
- $this->assertEqual(
- $browser->get('http://site.with.one.frame/'),
- 'One frame');
- $this->assertIdentical(
- $browser->getFrames(),
- array(1 => 'http://site.with.one.frame/frame.html'));
- }
-
- function testFramesetWithMultipleFrames() {
- $frameset = '<frameset>' .
- '<frame name="a" src="frame_a.html">' .
- '<frame name="b" src="frame_b.html">' .
- '<frame name="c" src="frame_c.html">' .
- '</frameset>';
- $browser = &$this->createBrowser($this->createUserAgent(array(
- 'http://site.with.frames/' => $frameset,
- 'http://site.with.frames/frame_a.html' => 'A frame',
- 'http://site.with.frames/frame_b.html' => 'B frame',
- 'http://site.with.frames/frame_c.html' => 'C frame')));
-
- $this->assertEqual(
- $browser->get('http://site.with.frames/'),
- 'A frameB frameC frame');
- $this->assertIdentical($browser->getFrames(), array(
- 'a' => 'http://site.with.frames/frame_a.html',
- 'b' => 'http://site.with.frames/frame_b.html',
- 'c' => 'http://site.with.frames/frame_c.html'));
- }
-
- function testFrameFocusByName() {
- $frameset = '<frameset>' .
- '<frame name="a" src="frame_a.html">' .
- '<frame name="b" src="frame_b.html">' .
- '<frame name="c" src="frame_c.html">' .
- '</frameset>';
- $browser = &$this->createBrowser($this->createUserAgent(array(
- 'http://site.with.frames/' => $frameset,
- 'http://site.with.frames/frame_a.html' => 'A frame',
- 'http://site.with.frames/frame_b.html' => 'B frame',
- 'http://site.with.frames/frame_c.html' => 'C frame')));
-
- $browser->get('http://site.with.frames/');
- $browser->setFrameFocus('a');
- $this->assertEqual($browser->getContent(), 'A frame');
- $browser->setFrameFocus('b');
- $this->assertEqual($browser->getContent(), 'B frame');
- $browser->setFrameFocus('c');
- $this->assertEqual($browser->getContent(), 'C frame');
- }
-
- function testFramesetWithSomeNamedFrames() {
- $frameset = '<frameset>' .
- '<frame name="a" src="frame_a.html">' .
- '<frame src="frame_b.html">' .
- '<frame name="c" src="frame_c.html">' .
- '<frame src="frame_d.html">' .
- '</frameset>';
- $browser = &$this->createBrowser($this->createUserAgent(array(
- 'http://site.with.frames/' => $frameset,
- 'http://site.with.frames/frame_a.html' => 'A frame',
- 'http://site.with.frames/frame_b.html' => 'B frame',
- 'http://site.with.frames/frame_c.html' => 'C frame',
- 'http://site.with.frames/frame_d.html' => 'D frame')));
-
- $this->assertEqual(
- $browser->get('http://site.with.frames/'),
- 'A frameB frameC frameD frame');
- $this->assertIdentical($browser->getFrames(), array(
- 'a' => 'http://site.with.frames/frame_a.html',
- 2 => 'http://site.with.frames/frame_b.html',
- 'c' => 'http://site.with.frames/frame_c.html',
- 4 => 'http://site.with.frames/frame_d.html'));
- }
-
- function testFrameFocusWithMixedNamesAndIndexes() {
- $frameset = '<frameset>' .
- '<frame name="a" src="frame_a.html">' .
- '<frame src="frame_b.html">' .
- '<frame name="c" src="frame_c.html">' .
- '<frame src="frame_d.html">' .
- '</frameset>';
- $browser = &$this->createBrowser($this->createUserAgent(array(
- 'http://site.with.frames/' => $frameset,
- 'http://site.with.frames/frame_a.html' => 'A frame',
- 'http://site.with.frames/frame_b.html' => 'B frame',
- 'http://site.with.frames/frame_c.html' => 'C frame',
- 'http://site.with.frames/frame_d.html' => 'D frame')));
-
- $browser->get('http://site.with.frames/');
- $browser->setFrameFocus('a');
- $this->assertEqual($browser->getContent(), 'A frame');
- $browser->setFrameFocus(2);
- $this->assertEqual($browser->getContent(), 'B frame');
- $browser->setFrameFocus('c');
- $this->assertEqual($browser->getContent(), 'C frame');
- $browser->setFrameFocus(4);
- $this->assertEqual($browser->getContent(), 'D frame');
- $browser->clearFrameFocus();
- $this->assertEqual($browser->getContent(), 'A frameB frameC frameD frame');
- }
-
- function testNestedFrameset() {
- $inner = '<frameset>' .
- '<frame name="page" src="page.html">' .
- '</frameset>';
- $outer = '<frameset>' .
- '<frame name="inner" src="inner.html">' .
- '</frameset>';
- $browser = &$this->createBrowser($this->createUserAgent(array(
- 'http://site.with.nested.frame/' => $outer,
- 'http://site.with.nested.frame/inner.html' => $inner,
- 'http://site.with.nested.frame/page.html' => 'The page')));
-
- $this->assertEqual(
- $browser->get('http://site.with.nested.frame/'),
- 'The page');
- $this->assertIdentical($browser->getFrames(), array(
- 'inner' => array(
- 'page' => 'http://site.with.nested.frame/page.html')));
- }
-
- function testCanNavigateToNestedFrame() {
- $inner = '<frameset>' .
- '<frame name="one" src="one.html">' .
- '<frame name="two" src="two.html">' .
- '</frameset>';
- $outer = '<frameset>' .
- '<frame name="inner" src="inner.html">' .
- '<frame name="three" src="three.html">' .
- '</frameset>';
- $browser = &$this->createBrowser($this->createUserAgent(array(
- 'http://site.with.nested.frames/' => $outer,
- 'http://site.with.nested.frames/inner.html' => $inner,
- 'http://site.with.nested.frames/one.html' => 'Page one',
- 'http://site.with.nested.frames/two.html' => 'Page two',
- 'http://site.with.nested.frames/three.html' => 'Page three')));
-
- $browser->get('http://site.with.nested.frames/');
- $this->assertEqual($browser->getContent(), 'Page onePage twoPage three');
-
- $this->assertTrue($browser->setFrameFocus('inner'));
- $this->assertEqual($browser->getFrameFocus(), array('inner'));
- $this->assertTrue($browser->setFrameFocus('one'));
- $this->assertEqual($browser->getFrameFocus(), array('inner', 'one'));
- $this->assertEqual($browser->getContent(), 'Page one');
-
- $this->assertTrue($browser->setFrameFocus('two'));
- $this->assertEqual($browser->getFrameFocus(), array('inner', 'two'));
- $this->assertEqual($browser->getContent(), 'Page two');
-
- $browser->clearFrameFocus();
- $this->assertTrue($browser->setFrameFocus('three'));
- $this->assertEqual($browser->getFrameFocus(), array('three'));
- $this->assertEqual($browser->getContent(), 'Page three');
-
- $this->assertTrue($browser->setFrameFocus('inner'));
- $this->assertEqual($browser->getContent(), 'Page onePage two');
- }
-
- function testCanNavigateToNestedFrameByIndex() {
- $inner = '<frameset>' .
- '<frame src="one.html">' .
- '<frame src="two.html">' .
- '</frameset>';
- $outer = '<frameset>' .
- '<frame src="inner.html">' .
- '<frame src="three.html">' .
- '</frameset>';
- $browser = &$this->createBrowser($this->createUserAgent(array(
- 'http://site.with.nested.frames/' => $outer,
- 'http://site.with.nested.frames/inner.html' => $inner,
- 'http://site.with.nested.frames/one.html' => 'Page one',
- 'http://site.with.nested.frames/two.html' => 'Page two',
- 'http://site.with.nested.frames/three.html' => 'Page three')));
-
- $browser->get('http://site.with.nested.frames/');
- $this->assertEqual($browser->getContent(), 'Page onePage twoPage three');
-
- $this->assertTrue($browser->setFrameFocusByIndex(1));
- $this->assertEqual($browser->getFrameFocus(), array(1));
- $this->assertTrue($browser->setFrameFocusByIndex(1));
- $this->assertEqual($browser->getFrameFocus(), array(1, 1));
- $this->assertEqual($browser->getContent(), 'Page one');
-
- $this->assertTrue($browser->setFrameFocusByIndex(2));
- $this->assertEqual($browser->getFrameFocus(), array(1, 2));
- $this->assertEqual($browser->getContent(), 'Page two');
-
- $browser->clearFrameFocus();
- $this->assertTrue($browser->setFrameFocusByIndex(2));
- $this->assertEqual($browser->getFrameFocus(), array(2));
- $this->assertEqual($browser->getContent(), 'Page three');
-
- $this->assertTrue($browser->setFrameFocusByIndex(1));
- $this->assertEqual($browser->getContent(), 'Page onePage two');
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/dumper_test.php b/tests/UnitTests/simpletest/test/dumper_test.php deleted file mode 100644 index f0169247..00000000 --- a/tests/UnitTests/simpletest/test/dumper_test.php +++ /dev/null @@ -1,87 +0,0 @@ -<?php
- // $Id: dumper_test.php,v 1.4 2004/09/24 22:55:10 lastcraft Exp $
-
- class DumperDummy {
- }
-
- class TestOfTextFormatting extends UnitTestCase {
-
- function testClipping() {
- $dumper = new SimpleDumper();
- $this->assertEqual(
- $dumper->clipString("Hello", 6),
- "Hello",
- "Hello, 6->%s");
- $this->assertEqual(
- $dumper->clipString("Hello", 5),
- "Hello",
- "Hello, 5->%s");
- $this->assertEqual(
- $dumper->clipString("Hello world", 3),
- "Hel...",
- "Hello world, 3->%s");
- $this->assertEqual(
- $dumper->clipString("Hello world", 6, 3),
- "Hello ...",
- "Hello world, 6, 3->%s");
- $this->assertEqual(
- $dumper->clipString("Hello world", 3, 6),
- "...o w...",
- "Hello world, 3, 6->%s");
- $this->assertEqual(
- $dumper->clipString("Hello world", 4, 11),
- "...orld",
- "Hello world, 4, 11->%s");
- $this->assertEqual(
- $dumper->clipString("Hello world", 4, 12),
- "...orld",
- "Hello world, 4, 12->%s");
- }
-
- function testDescribeNull() {
- $dumper = new SimpleDumper();
- $this->assertWantedPattern('/null/i', $dumper->describeValue(null));
- }
-
- function testDescribeBoolean() {
- $dumper = new SimpleDumper();
- $this->assertWantedPattern('/boolean/i', $dumper->describeValue(true));
- $this->assertWantedPattern('/true/i', $dumper->describeValue(true));
- $this->assertWantedPattern('/false/i', $dumper->describeValue(false));
- }
-
- function testDescribeString() {
- $dumper = new SimpleDumper();
- $this->assertWantedPattern('/string/i', $dumper->describeValue('Hello'));
- $this->assertWantedPattern('/Hello/', $dumper->describeValue('Hello'));
- }
-
- function testDescribeInteger() {
- $dumper = new SimpleDumper();
- $this->assertWantedPattern('/integer/i', $dumper->describeValue(35));
- $this->assertWantedPattern('/35/', $dumper->describeValue(35));
- }
-
- function testDescribeFloat() {
- $dumper = new SimpleDumper();
- $this->assertWantedPattern('/float/i', $dumper->describeValue(0.99));
- $this->assertWantedPattern('/0\.99/', $dumper->describeValue(0.99));
- }
-
- function testDescribeArray() {
- $dumper = new SimpleDumper();
- $this->assertWantedPattern('/array/i', $dumper->describeValue(array(1, 4)));
- $this->assertWantedPattern('/2/i', $dumper->describeValue(array(1, 4)));
- }
-
- function testDescribeObject() {
- $dumper = new SimpleDumper();
- $this->assertWantedPattern(
- '/object/i',
- $dumper->describeValue(new DumperDummy()));
- $this->assertWantedPattern(
- '/DumperDummy/i',
- $dumper->describeValue(new DumperDummy()));
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/encoding_test.php b/tests/UnitTests/simpletest/test/encoding_test.php deleted file mode 100644 index 409283d5..00000000 --- a/tests/UnitTests/simpletest/test/encoding_test.php +++ /dev/null @@ -1,134 +0,0 @@ -<?php
- // $Id: encoding_test.php,v 1.6 2005/01/02 23:43:28 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../url.php');
-
- class FormEncodingTestCase extends UnitTestCase {
-
- function testEmpty() {
- $encoding = &new SimpleFormEncoding();
- $this->assertIdentical($encoding->getValue('a'), false);
- $this->assertIdentical($encoding->getKeys(), array());
- $this->assertIdentical($encoding->asString(), '');
- }
-
- function testPrefilled() {
- $encoding = &new SimpleFormEncoding(array('a' => 'aaa'));
- $this->assertIdentical($encoding->getValue('a'), 'aaa');
- $this->assertIdentical($encoding->getKeys(), array('a'));
- $this->assertIdentical($encoding->asString(), 'a=aaa');
- }
-
- function testPrefilledWithObject() {
- $encoding = &new SimpleFormEncoding(new SimpleFormEncoding(array('a' => 'aaa')));
- $this->assertIdentical($encoding->getValue('a'), 'aaa');
- $this->assertIdentical($encoding->getKeys(), array('a'));
- $this->assertIdentical($encoding->asString(), 'a=aaa');
- }
-
- function testMultiplePrefilled() {
- $encoding = &new SimpleFormEncoding(array('a' => array('a1', 'a2')));
- $this->assertIdentical($encoding->getValue('a'), array('a1', 'a2'));
- $this->assertIdentical($encoding->asString(), 'a=a1&a=a2');
- }
-
- function testSingleParameter() {
- $encoding = &new SimpleFormEncoding();
- $encoding->add('a', 'Hello');
- $this->assertEqual($encoding->getValue('a'), 'Hello');
- $this->assertIdentical($encoding->asString(), 'a=Hello');
- }
-
- function testFalseParameter() {
- $encoding = &new SimpleFormEncoding();
- $encoding->add('a', false);
- $this->assertEqual($encoding->getValue('a'), false);
- $this->assertIdentical($encoding->asString(), '');
- }
-
- function testUrlEncoding() {
- $encoding = &new SimpleFormEncoding();
- $encoding->add('a', 'Hello there!');
- $this->assertIdentical($encoding->asString(), 'a=Hello+there%21');
- }
-
- function testMultipleParameter() {
- $encoding = &new SimpleFormEncoding();
- $encoding->add('a', 'Hello');
- $encoding->add('b', 'Goodbye');
- $this->assertIdentical($encoding->asString(), 'a=Hello&b=Goodbye');
- }
-
- function testEmptyParameters() {
- $encoding = &new SimpleFormEncoding();
- $encoding->add('a', '');
- $encoding->add('b', '');
- $this->assertIdentical($encoding->asString(), 'a=&b=');
- }
-
- function testRepeatedParameter() {
- $encoding = &new SimpleFormEncoding();
- $encoding->add('a', 'Hello');
- $encoding->add('a', 'Goodbye');
- $this->assertIdentical($encoding->getValue('a'), array('Hello', 'Goodbye'));
- $this->assertIdentical($encoding->asString(), 'a=Hello&a=Goodbye');
- }
-
- function testDefaultCoordinatesAreUnset() {
- $encoding = &new SimpleFormEncoding();
- $this->assertIdentical($encoding->getX(), false);
- $this->assertIdentical($encoding->getY(), false);
- }
-
- function testSettingCoordinates() {
- $encoding = &new SimpleFormEncoding();
- $encoding->setCoordinates('32', '45');
- $this->assertIdentical($encoding->getX(), 32);
- $this->assertIdentical($encoding->getY(), 45);
- $this->assertIdentical($encoding->asString(), '?32,45');
- }
-
- function testClearingCordinates() {
- $encoding = &new SimpleFormEncoding();
- $encoding->setCoordinates('32', '45');
- $encoding->setCoordinates();
- $this->assertIdentical($encoding->getX(), false);
- $this->assertIdentical($encoding->getY(), false);
- }
-
- function testAddingLists() {
- $encoding = &new SimpleFormEncoding();
- $encoding->add('a', array('Hello', 'Goodbye'));
- $this->assertIdentical($encoding->getValue('a'), array('Hello', 'Goodbye'));
- $this->assertIdentical($encoding->asString(), 'a=Hello&a=Goodbye');
- }
-
- function testMergeInHash() {
- $encoding = &new SimpleFormEncoding(array('a' => 'A1', 'b' => 'B'));
- $encoding->merge(array('a' => 'A2'));
- $this->assertIdentical($encoding->getValue('a'), array('A1', 'A2'));
- $this->assertIdentical($encoding->getValue('b'), 'B');
- }
-
- function testMergeInObject() {
- $encoding = &new SimpleFormEncoding(array('a' => 'A1', 'b' => 'B'));
- $encoding->merge(new SimpleFormEncoding(array('a' => 'A2')));
- $this->assertIdentical($encoding->getValue('a'), array('A1', 'A2'));
- $this->assertIdentical($encoding->getValue('b'), 'B');
- }
-
- function testMergeInObjectWithCordinates() {
- $incoming = new SimpleFormEncoding(array('a' => 'A2'));
- $incoming->setCoordinates(25, 24);
-
- $encoding = &new SimpleFormEncoding(array('a' => 'A1'));
- $encoding->setCoordinates(1, 2);
- $encoding->merge($incoming);
-
- $this->assertIdentical($encoding->getValue('a'), array('A1', 'A2'));
- $this->assertIdentical($encoding->getX(), 25);
- $this->assertIdentical($encoding->getY(), 24);
- $this->assertIdentical($encoding->asString(), 'a=A1&a=A2?25,24');
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/errors_test.php b/tests/UnitTests/simpletest/test/errors_test.php deleted file mode 100644 index 57aff612..00000000 --- a/tests/UnitTests/simpletest/test/errors_test.php +++ /dev/null @@ -1,139 +0,0 @@ -<?php
- require_once(dirname(__FILE__) . '/../errors.php');
-
- class TestOfErrorQueue extends UnitTestCase {
-
- function setUp() {
- $queue = &SimpleErrorQueue::instance();
- $queue->clear();
- }
-
- function tearDown() {
- $queue = &SimpleErrorQueue::instance();
- $queue->clear();
- }
-
- function testSingleton() {
- $this->assertReference(
- SimpleErrorQueue::instance(),
- SimpleErrorQueue::instance());
- $this->assertIsA(SimpleErrorQueue::instance(), 'SimpleErrorQueue');
- }
-
- function testEmpty() {
- $queue = &SimpleErrorQueue::instance();
- $this->assertTrue($queue->isEmpty());
- $this->assertFalse($queue->extract());
- }
-
- function testOrder() {
- $queue = &SimpleErrorQueue::instance();
- $queue->add(1024, 'Ouch', 'here.php', 100, array());
- $this->assertFalse($queue->isEmpty());
- $queue->add(512, 'Yuk', 'there.php', 101, array());
- $this->assertEqual(
- $queue->extract(),
- array(1024, 'Ouch', 'here.php', 100, array()));
- $this->assertEqual(
- $queue->extract(),
- array(512, 'Yuk', 'there.php', 101, array()));
- $this->assertFalse($queue->extract());
- }
- }
-
- class TestOfErrorTrap extends UnitTestCase {
- var $_old;
-
- function setUp() {
- $this->_old = error_reporting(E_ALL);
- set_error_handler('simpleTestErrorHandler');
- }
-
- function tearDown() {
- restore_error_handler();
- error_reporting($this->_old);
- }
-
- function testTrappedErrorPlacedInQueue() {
- $queue = &SimpleErrorQueue::instance();
- $this->assertFalse($queue->extract());
- trigger_error('Ouch!');
- list($severity, $message, $file, $line, $globals) = $queue->extract();
- $this->assertEqual($message, 'Ouch!');
- $this->assertEqual($file, __FILE__);
- $this->assertFalse($queue->extract());
- }
- }
-
- class TestOfErrors extends UnitTestCase {
- var $_old;
-
- function setUp() {
- $this->_old = error_reporting(E_ALL);
- }
-
- function tearDown() {
- error_reporting($this->_old);
- }
-
- function testDefaultWhenAllReported() {
- error_reporting(E_ALL);
- trigger_error('Ouch!');
- $this->assertError('Ouch!');
- }
-
- function testNoticeWhenReported() {
- error_reporting(E_ALL);
- trigger_error('Ouch!', E_USER_NOTICE);
- $this->assertError('Ouch!');
- }
-
- function testWarningWhenReported() {
- error_reporting(E_ALL);
- trigger_error('Ouch!', E_USER_WARNING);
- $this->assertError('Ouch!');
- }
-
- function testErrorWhenReported() {
- error_reporting(E_ALL);
- trigger_error('Ouch!', E_USER_ERROR);
- $this->assertError('Ouch!');
- }
-
- function testNoNoticeWhenNotReported() {
- error_reporting(0);
- trigger_error('Ouch!', E_USER_NOTICE);
- $this->assertNoErrors();
- }
-
- function testNoWarningWhenNotReported() {
- error_reporting(0);
- trigger_error('Ouch!', E_USER_WARNING);
- $this->assertNoErrors();
- }
-
- function testNoErrorWhenNotReported() {
- error_reporting(0);
- trigger_error('Ouch!', E_USER_ERROR);
- $this->assertNoErrors();
- }
-
- function testNoticeSuppressedWhenReported() {
- error_reporting(E_ALL);
- @trigger_error('Ouch!', E_USER_NOTICE);
- $this->assertNoErrors();
- }
-
- function testWarningSuppressedWhenReported() {
- error_reporting(E_ALL);
- @trigger_error('Ouch!', E_USER_WARNING);
- $this->assertNoErrors();
- }
-
- function testErrorSuppressedWhenReported() {
- error_reporting(E_ALL);
- @trigger_error('Ouch!', E_USER_ERROR);
- $this->assertNoErrors();
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/expectation_test.php b/tests/UnitTests/simpletest/test/expectation_test.php deleted file mode 100644 index f16db28a..00000000 --- a/tests/UnitTests/simpletest/test/expectation_test.php +++ /dev/null @@ -1,282 +0,0 @@ -<?php
- // $Id: expectation_test.php,v 1.19 2005/01/13 01:31:57 lastcraft Exp $
- require_once(dirname(__FILE__).DIRECTORY_SEPARATOR . '../expectation.php');
-
- class TestOfEquality extends UnitTestCase {
-
- function testBoolean() {
- $is_true = &new EqualExpectation(true);
- $this->assertTrue($is_true->test(true));
- $this->assertFalse($is_true->test(false));
- $this->assertWantedPattern(
- '/equal expectation.*?boolean: true/i',
- $is_true->testMessage(true));
- $this->assertWantedPattern(
- '/fails.*?boolean.*?boolean/i',
- $is_true->testMessage(false));
- }
-
- function testStringMatch() {
- $hello = &new EqualExpectation("Hello");
- $this->assertTrue($hello->test("Hello"));
- $this->assertFalse($hello->test("Goodbye"));
- $this->assertWantedPattern('/Equal expectation.*?Hello/', $hello->testMessage("Hello"));
- $this->assertWantedPattern('/fails/', $hello->testMessage("Goodbye"));
- $this->assertWantedPattern('/fails.*?goodbye/i', $hello->testMessage("Goodbye"));
- }
-
- function testStringPosition() {
- $comparisons = array(
- "ab" => 2,
- "a" => 1,
- "abcz" => 3,
- "abz" => 2,
- "az" => 1,
- "z" => 0);
- $str = &new EqualExpectation("abc");
- foreach ($comparisons as $compare => $position) {
- $this->assertWantedPattern(
- "/at character $position/",
- $str->testMessage($compare));
- }
- $str = &new EqualExpectation("abcd");
- foreach ($comparisons as $compare => $position) {
- $this->assertWantedPattern(
- "/at character $position/",
- $str->testMessage($compare));
- }
- }
-
- function testInteger() {
- $fifteen = &new EqualExpectation(15);
- $this->assertTrue($fifteen->test(15));
- $this->assertFalse($fifteen->test(14));
- $this->assertWantedPattern(
- '/equal expectation.*?15/i',
- $fifteen->testMessage(15));
- $this->assertWantedPattern(
- '/fails.*?15.*?14/i',
- $fifteen->testMessage(14));
- }
-
- function testFloat() {
- $pi = &new EqualExpectation(3.14);
- $this->assertTrue($pi->test(3.14));
- $this->assertFalse($pi->test(3.15));
- $this->assertWantedPattern(
- '/float.*?3\.14/i',
- $pi->testMessage(3.14));
- $this->assertWantedPattern(
- '/fails.*?3\.14.*?3\.15/i',
- $pi->testMessage(3.15));
- }
-
- function testArray() {
- $colours = &new EqualExpectation(array("r", "g", "b"));
- $this->assertTrue($colours->test(array("r", "g", "b")));
- $this->assertFalse($colours->test(array("g", "b", "r")));
- $this->assertEqual(
- $colours->testMessage(array("r", "g", "b")),
- "Equal expectation [Array: 3 items]");
- $this->assertWantedPattern('/fails/', $colours->testMessage(array("r", "g", "z")));
- $this->assertWantedPattern(
- '/\[2\] at character 0/',
- $colours->testMessage(array("r", "g", "z")));
- $this->assertWantedPattern(
- '/key.*? does not match/',
- $colours->testMessage(array("r", "g")));
- $this->assertWantedPattern(
- '/key.*? does not match/',
- $colours->testMessage(array("r", "g", "b", "z")));
- }
-
- function testHash() {
- $is_blue = &new EqualExpectation(array("r" => 0, "g" => 0, "b" => 255));
- $this->assertTrue($is_blue->test(array("r" => 0, "g" => 0, "b" => 255)));
- $this->assertFalse($is_blue->test(array("r" => 0, "g" => 255, "b" => 0)));
- $this->assertWantedPattern(
- '/array.*?3 items/i',
- $is_blue->testMessage(array("r" => 0, "g" => 0, "b" => 255)));
- $this->assertWantedPattern(
- '/fails.*?\[b\]/',
- $is_blue->testMessage(array("r" => 0, "g" => 0, "b" => 254)));
- }
-
- function testNestedHash() {
- $tree = &new EqualExpectation(array(
- "a" => 1,
- "b" => array(
- "c" => 2,
- "d" => "Three")));
- $this->assertWantedPattern(
- '/member.*?\[b\].*?\[d\].*?at character 5/',
- $tree->testMessage(array(
- "a" => 1,
- "b" => array(
- "c" => 2,
- "d" => "Threeish"))));
- }
-
- function testHashWithOutOfOrderKeysShouldStillMatch() {
- $any_order = &new EqualExpectation(array('a' => 1, 'b' => 2));
- $this->assertTrue($any_order->test(array('b' => 2, 'a' => 1)));
- }
- }
-
- class TestOfInequality extends UnitTestCase {
-
- function testStringMismatch() {
- $not_hello = &new NotEqualExpectation("Hello");
- $this->assertTrue($not_hello->test("Goodbye"));
- $this->assertFalse($not_hello->test("Hello"));
- $this->assertWantedPattern(
- '/at character 0/',
- $not_hello->testMessage("Goodbye"));
- $this->assertWantedPattern(
- '/matches/',
- $not_hello->testMessage("Hello"));
- }
- }
-
- class RecursiveNasty {
- var $_me;
-
- function RecursiveNasty() {
- $this->_me = $this;
- }
- }
-
- class TestOfIdentity extends UnitTestCase {
-
- function testType() {
- $string = &new IdenticalExpectation("37");
- $this->assertTrue($string->test("37"));
- $this->assertFalse($string->test(37));
- $this->assertFalse($string->test("38"));
- $this->assertWantedPattern(
- '/identical.*?string.*?37/i',
- $string->testMessage("37"));
- $this->assertWantedPattern(
- '/fails.*?37/',
- $string->testMessage(37));
- $this->assertWantedPattern(
- '/at character 1/',
- $string->testMessage("38"));
- }
-
- function _testNastyPhp5Bug() {
- $this->assertFalse(new RecursiveNasty() != new RecursiveNasty());
- }
-
- function _testReallyHorribleRecursiveStructure() {
- $hopeful = &new IdenticalExpectation(new RecursiveNasty());
- $this->assertTrue($hopeful->test(new RecursiveNasty()));
- }
- }
-
- class TestOfNonIdentity extends UnitTestCase {
-
- function testType() {
- $string = &new NotIdenticalExpectation("37");
- $this->assertTrue($string->test("38"));
- $this->assertTrue($string->test(37));
- $this->assertFalse($string->test("37"));
- $this->assertWantedPattern(
- '/at character 1/',
- $string->testMessage("38"));
- $this->assertWantedPattern(
- '/passes.*?type/',
- $string->testMessage(37));
- }
- }
-
- class TestOfPatterns extends UnitTestCase {
-
- function testWanted() {
- $pattern = &new WantedPatternExpectation('/hello/i');
- $this->assertTrue($pattern->test("Hello world"));
- $this->assertFalse($pattern->test("Goodbye world"));
- }
-
- function testUnwanted() {
- $pattern = &new UnwantedPatternExpectation('/hello/i');
- $this->assertFalse($pattern->test("Hello world"));
- $this->assertTrue($pattern->test("Goodbye world"));
- }
- }
-
- class ExpectedMethodTarget {
- function hasThisMethod() {}
- }
-
- class TestOfMethodExistence extends UnitTestCase {
-
- function testHasMethod() {
- $instance = &new ExpectedMethodTarget();
- $expectation = &new MethodExistsExpectation('hasThisMethod');
- $this->assertTrue($expectation->test($instance));
- $expectation = &new MethodExistsExpectation('doesNotHaveThisMethod');
- $this->assertFalse($expectation->test($instance));
- }
- }
-
- class TestOfIsA extends UnitTestCase {
-
- function testString() {
- $expectation = &new IsAExpectation('string');
- $this->assertTrue($expectation->test('Hello'));
- $this->assertFalse($expectation->test(5));
- }
-
- function testBoolean() {
- $expectation = &new IsAExpectation('boolean');
- $this->assertTrue($expectation->test(true));
- $this->assertFalse($expectation->test(1));
- }
-
- function testBool() {
- $expectation = &new IsAExpectation('bool');
- $this->assertTrue($expectation->test(true));
- $this->assertFalse($expectation->test(1));
- }
-
- function testDouble() {
- $expectation = &new IsAExpectation('double');
- $this->assertTrue($expectation->test(5.0));
- $this->assertFalse($expectation->test(5));
- }
-
- function testFloat() {
- $expectation = &new IsAExpectation('float');
- $this->assertTrue($expectation->test(5.0));
- $this->assertFalse($expectation->test(5));
- }
-
- function testReal() {
- $expectation = &new IsAExpectation('real');
- $this->assertTrue($expectation->test(5.0));
- $this->assertFalse($expectation->test(5));
- }
-
- function testInteger() {
- $expectation = &new IsAExpectation('integer');
- $this->assertTrue($expectation->test(5));
- $this->assertFalse($expectation->test(5.0));
- }
-
- function testInt() {
- $expectation = &new IsAExpectation('int');
- $this->assertTrue($expectation->test(5));
- $this->assertFalse($expectation->test(5.0));
- }
- }
-
- class TestOfNotA extends UnitTestCase {
-
- function testString() {
- $expectation = &new NotAExpectation('string');
- $this->assertFalse($expectation->test('Hello'));
- $this->assertTrue($expectation->test(5));
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/form_test.php b/tests/UnitTests/simpletest/test/form_test.php deleted file mode 100644 index b24eab2c..00000000 --- a/tests/UnitTests/simpletest/test/form_test.php +++ /dev/null @@ -1,326 +0,0 @@ -<?php
- // $Id: form_test.php,v 1.12 2005/02/22 02:17:06 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../form.php');
- require_once(dirname(__FILE__) . '/../encoding.php');
-
- class TestOfForm extends UnitTestCase {
-
- function testFormAttributes() {
- $tag = new SimpleFormTag(array('method' => 'GET', 'action' => 'here.php', 'id' => '33'));
- $form = new SimpleForm($tag, new SimpleUrl('http://host/a/index.html'));
- $this->assertEqual($form->getMethod(), 'get');
- $this->assertEqual(
- $form->getAction(),
- new SimpleUrl('http://host/a/here.php'));
- $this->assertIdentical($form->getId(), '33');
- $this->assertNull($form->getValue('a'));
- }
-
- function testEmptyAction() {
- $tag = new SimpleFormTag(array('method' => 'GET', 'action' => '', 'id' => '33'));
- $form = new SimpleForm($tag, new SimpleUrl('http://host/a/index.html'));
- $this->assertEqual(
- $form->getAction(),
- new SimpleUrl('http://host/a/index.html'));
- }
-
- function testMissingAction() {
- $tag = new SimpleFormTag(array('method' => 'GET', 'id' => '33'));
- $form = new SimpleForm($tag, new SimpleUrl('http://host/a/index.html'));
- $this->assertEqual(
- $form->getAction(),
- new SimpleUrl('http://host/a/index.html'));
- }
-
- function testRootAction() {
- $tag = new SimpleFormTag(array('method' => 'GET', 'action' => '/', 'id' => '33'));
- $form = new SimpleForm($tag, new SimpleUrl('http://host/a/index.html'));
- $this->assertEqual(
- $form->getAction(),
- new SimpleUrl('http://host/'));
- }
-
- function testDefaultFrameTargetOnForm() {
- $tag = new SimpleFormTag(array('method' => 'GET', 'action' => 'here.php', 'id' => '33'));
- $form = new SimpleForm($tag, new SimpleUrl('http://host/a/index.html'));
- $form->setDefaultTarget('frame');
-
- $expected = new SimpleUrl('http://host/a/here.php');
- $expected->setTarget('frame');
- $this->assertEqual($form->getAction(), $expected);
- }
-
- function testTextWidget() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('htp://host'));
- $form->addWidget(new SimpleTextTag(
- array('name' => 'me', 'type' => 'text', 'value' => 'Myself')));
- $this->assertIdentical($form->getValue('me'), 'Myself');
- $this->assertTrue($form->setField('me', 'Not me'));
- $this->assertFalse($form->setField('not_present', 'Not me'));
- $this->assertIdentical($form->getValue('me'), 'Not me');
- $this->assertNull($form->getValue('not_present'));
- }
-
- function testTextWidgetById() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('htp://host'));
- $form->addWidget(new SimpleTextTag(
- array('name' => 'me', 'type' => 'text', 'value' => 'Myself', 'id' => 50)));
- $this->assertIdentical($form->getValueById(50), 'Myself');
- $this->assertTrue($form->setFieldById(50, 'Not me'));
- $this->assertIdentical($form->getValueById(50), 'Not me');
- }
-
- function testSubmitEmpty() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('htp://host'));
- $this->assertIdentical($form->submit(), new SimpleFormEncoding());
- }
-
- function testSubmitButton() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('http://host'));
- $form->addWidget(new SimpleSubmitTag(
- array('type' => 'submit', 'name' => 'go', 'value' => 'Go!', 'id' => '9')));
- $this->assertTrue($form->hasSubmitName('go'));
- $this->assertEqual($form->getValue('go'), 'Go!');
- $this->assertEqual($form->getValueById(9), 'Go!');
- $this->assertEqual(
- $form->submitButtonByName('go'),
- new SimpleFormEncoding(array('go' => 'Go!')));
- $this->assertEqual(
- $form->submitButtonByLabel('Go!'),
- new SimpleFormEncoding(array('go' => 'Go!')));
- $this->assertEqual(
- $form->submitButtonById(9),
- new SimpleFormEncoding(array('go' => 'Go!')));
- }
-
- function testSubmitWithAdditionalParameters() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('http://host'));
- $form->addWidget(new SimpleSubmitTag(
- array('type' => 'submit', 'name' => 'go', 'value' => 'Go!', 'id' => '9')));
- $this->assertEqual(
- $form->submitButtonByName('go', array('a' => 'A')),
- new SimpleFormEncoding(array('go' => 'Go!', 'a' => 'A')));
- $this->assertEqual(
- $form->submitButtonByLabel('Go!', array('a' => 'A')),
- new SimpleFormEncoding(array('go' => 'Go!', 'a' => 'A')));
- $this->assertEqual(
- $form->submitButtonById(9, array('a' => 'A')),
- new SimpleFormEncoding(array('go' => 'Go!', 'a' => 'A')));
- }
-
- function testSubmitButtonWithLabelOfSubmit() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('http://host'));
- $form->addWidget(new SimpleSubmitTag(
- array('type' => 'submit', 'name' => 'test', 'value' => 'Submit', 'id' => '9')));
- $this->assertTrue($form->hasSubmitName('test'));
- $this->assertEqual($form->getValue('test'), 'Submit');
- $this->assertEqual($form->getValueById(9), 'Submit');
- $this->assertEqual(
- $form->submitButtonByName('test'),
- new SimpleFormEncoding(array('test' => 'Submit')));
- $this->assertEqual(
- $form->submitButtonByLabel('Submit'),
- new SimpleFormEncoding(array('test' => 'Submit')));
- $this->assertEqual(
- $form->submitButtonById(9),
- new SimpleFormEncoding(array('test' => 'Submit')));
- }
-
- function testSubmitButtonWithWhitespacePaddedLabelOfSubmit() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('http://host'));
- $form->addWidget(new SimpleSubmitTag(
- array('type' => 'submit', 'name' => 'test', 'value' => ' Submit ', 'id' => '9')));
- $this->assertEqual($form->getValue('test'), ' Submit ');
- $this->assertEqual($form->getValueById(9), ' Submit ');
- $this->assertEqual(
- $form->submitButtonByLabel('Submit'),
- new SimpleFormEncoding(array('test' => ' Submit ')));
- }
-
- function testImageSubmitButton() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('htp://host'));
- $form->addWidget(new SimpleImageSubmitTag(array(
- 'type' => 'image',
- 'src' => 'source.jpg',
- 'name' => 'go',
- 'alt' => 'Go!',
- 'id' => '9')));
- $this->assertTrue($form->hasImageLabel('Go!'));
- $this->assertEqual(
- $form->submitImageByLabel('Go!', 100, 101),
- new SimpleFormEncoding(array('go.x' => 100, 'go.y' => 101)));
- $this->assertTrue($form->hasImageName('go'));
- $this->assertEqual(
- $form->submitImageByName('go', 100, 101),
- new SimpleFormEncoding(array('go.x' => 100, 'go.y' => 101)));
- $this->assertTrue($form->hasImageId(9));
- $this->assertEqual(
- $form->submitImageById(9, 100, 101),
- new SimpleFormEncoding(array('go.x' => 100, 'go.y' => 101)));
- }
-
- function testImageSubmitButtonWithAdditionalData() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('htp://host'));
- $form->addWidget(new SimpleImageSubmitTag(array(
- 'type' => 'image',
- 'src' => 'source.jpg',
- 'name' => 'go',
- 'alt' => 'Go!',
- 'id' => '9')));
- $this->assertEqual(
- $form->submitImageByLabel('Go!', 100, 101, array('a' => 'A')),
- new SimpleFormEncoding(array('go.x' => 100, 'go.y' => 101, 'a' => 'A')));
- $this->assertTrue($form->hasImageName('go'));
- $this->assertEqual(
- $form->submitImageByName('go', 100, 101, array('a' => 'A')),
- new SimpleFormEncoding(array('go.x' => 100, 'go.y' => 101, 'a' => 'A')));
- $this->assertTrue($form->hasImageId(9));
- $this->assertEqual(
- $form->submitImageById(9, 100, 101, array('a' => 'A')),
- new SimpleFormEncoding(array('go.x' => 100, 'go.y' => 101, 'a' => 'A')));
- }
-
- function testButtonTag() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('http://host'));
- $widget = new SimpleButtonTag(
- array('type' => 'submit', 'name' => 'go', 'value' => 'Go', 'id' => '9'));
- $widget->addContent('Go!');
- $form->addWidget($widget);
- $this->assertTrue($form->hasSubmitName('go'));
- $this->assertTrue($form->hasSubmitLabel('Go!'));
- $this->assertEqual(
- $form->submitButtonByName('go'),
- new SimpleFormEncoding(array('go' => 'Go')));
- $this->assertEqual(
- $form->submitButtonByLabel('Go!'),
- new SimpleFormEncoding(array('go' => 'Go')));
- $this->assertEqual(
- $form->submitButtonById(9),
- new SimpleFormEncoding(array('go' => 'Go')));
- }
-
- function testSingleSelectFieldSubmitted() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('htp://host'));
- $select = new SimpleSelectionTag(array('name' => 'a'));
- $select->addTag(new SimpleOptionTag(
- array('value' => 'aaa', 'selected' => '')));
- $form->addWidget($select);
- $this->assertIdentical(
- $form->submit(),
- new SimpleFormEncoding(array('a' => 'aaa')));
- }
-
- function testUnchecked() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('htp://host'));
- $form->addWidget(new SimpleCheckboxTag(
- array('name' => 'me', 'type' => 'checkbox')));
- $this->assertIdentical($form->getValue('me'), false);
- $this->assertTrue($form->setField('me', 'on'));
- $this->assertEqual($form->getValue('me'), 'on');
- $this->assertFalse($form->setField('me', 'other'));
- $this->assertEqual($form->getValue('me'), 'on');
- }
-
- function testChecked() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('htp://host'));
- $form->addWidget(new SimpleCheckboxTag(
- array('name' => 'me', 'value' => 'a', 'type' => 'checkbox', 'checked' => '')));
- $this->assertIdentical($form->getValue('me'), 'a');
- $this->assertFalse($form->setField('me', 'on'));
- $this->assertEqual($form->getValue('me'), 'a');
- $this->assertTrue($form->setField('me', false));
- $this->assertEqual($form->getValue('me'), false);
- }
-
- function testSingleUncheckedRadioButton() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('htp://host'));
- $form->addWidget(new SimpleRadioButtonTag(
- array('name' => 'me', 'value' => 'a', 'type' => 'radio')));
- $this->assertIdentical($form->getValue('me'), false);
- $this->assertTrue($form->setField('me', 'a'));
- $this->assertIdentical($form->getValue('me'), 'a');
- }
-
- function testSingleCheckedRadioButton() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('htp://host'));
- $form->addWidget(new SimpleRadioButtonTag(
- array('name' => 'me', 'value' => 'a', 'type' => 'radio', 'checked' => '')));
- $this->assertIdentical($form->getValue('me'), 'a');
- $this->assertFalse($form->setField('me', 'other'));
- }
-
- function testUncheckedRadioButtons() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('htp://host'));
- $form->addWidget(new SimpleRadioButtonTag(
- array('name' => 'me', 'value' => 'a', 'type' => 'radio')));
- $form->addWidget(new SimpleRadioButtonTag(
- array('name' => 'me', 'value' => 'b', 'type' => 'radio')));
- $this->assertIdentical($form->getValue('me'), false);
- $this->assertTrue($form->setField('me', 'a'));
- $this->assertIdentical($form->getValue('me'), 'a');
- $this->assertTrue($form->setField('me', 'b'));
- $this->assertIdentical($form->getValue('me'), 'b');
- $this->assertFalse($form->setField('me', 'c'));
- $this->assertIdentical($form->getValue('me'), 'b');
- }
-
- function testCheckedRadioButtons() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('htp://host'));
- $form->addWidget(new SimpleRadioButtonTag(
- array('name' => 'me', 'value' => 'a', 'type' => 'radio')));
- $form->addWidget(new SimpleRadioButtonTag(
- array('name' => 'me', 'value' => 'b', 'type' => 'radio', 'checked' => '')));
- $this->assertIdentical($form->getValue('me'), 'b');
- $this->assertTrue($form->setField('me', 'a'));
- $this->assertIdentical($form->getValue('me'), 'a');
- }
-
- function testMultipleFieldsWithSameKey() {
- $form = new SimpleForm(
- new SimpleFormTag(array()),
- new SimpleUrl('htp://host'));
- $form->addWidget(new SimpleCheckboxTag(
- array('name' => 'a', 'type' => 'checkbox', 'value' => 'me')));
- $form->addWidget(new SimpleCheckboxTag(
- array('name' => 'a', 'type' => 'checkbox', 'value' => 'you')));
- $this->assertIdentical($form->getValue('a'), false);
- $this->assertTrue($form->setField('a', 'me'));
- $this->assertIdentical($form->getValue('a'), 'me');
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/frames_test.php b/tests/UnitTests/simpletest/test/frames_test.php deleted file mode 100644 index 5226d070..00000000 --- a/tests/UnitTests/simpletest/test/frames_test.php +++ /dev/null @@ -1,569 +0,0 @@ -<?php
- // $Id: frames_test.php,v 1.28 2004/11/30 05:34:00 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../tag.php');
- require_once(dirname(__FILE__) . '/../page.php');
- require_once(dirname(__FILE__) . '/../frames.php');
-
- Mock::generate('SimplePage');
- Mock::generate('SimpleForm');
-
- class TestOfFrameset extends UnitTestCase {
-
- function testTitleReadFromFramesetPage() {
- $page = &new MockSimplePage($this);
- $page->setReturnValue('getTitle', 'This page');
- $frameset = &new SimpleFrameset($page);
- $this->assertEqual($frameset->getTitle(), 'This page');
- }
-
- function TestHeadersReadFromFramesetByDefault() {
- $page = &new MockSimplePage($this);
- $page->setReturnValue('getHeaders', 'Header: content');
- $page->setReturnValue('getMimeType', 'text/xml');
- $page->setReturnValue('getResponseCode', 401);
- $page->setReturnValue('getTransportError', 'Could not parse headers');
- $page->setReturnValue('getAuthentication', 'Basic');
- $page->setReturnValue('getRealm', 'Safe place');
-
- $frameset = &new SimpleFrameset($page);
-
- $this->assertIdentical($frameset->getHeaders(), 'Header: content');
- $this->assertIdentical($frameset->getMimeType(), 'text/xml');
- $this->assertIdentical($frameset->getResponseCode(), 401);
- $this->assertIdentical($frameset->getTransportError(), 'Could not parse headers');
- $this->assertIdentical($frameset->getAuthentication(), 'Basic');
- $this->assertIdentical($frameset->getRealm(), 'Safe place');
- }
-
- function testEmptyFramesetHasNoContent() {
- $page = &new MockSimplePage($this);
- $page->setReturnValue('getRaw', 'This content');
- $frameset = &new SimpleFrameset($page);
- $this->assertEqual($frameset->getRaw(), '');
- }
-
- function testRawContentIsFromOnlyFrame() {
- $page = &new MockSimplePage($this);
- $page->expectNever('getRaw');
-
- $frame = &new MockSimplePage($this);
- $frame->setReturnValue('getRaw', 'Stuff');
-
- $frameset = &new SimpleFrameset($page);
- $frameset->addFrame($frame);
- $this->assertEqual($frameset->getRaw(), 'Stuff');
- }
-
- function testRawContentIsFromAllFrames() {
- $page = &new MockSimplePage($this);
- $page->expectNever('getRaw');
-
- $frame1 = &new MockSimplePage($this);
- $frame1->setReturnValue('getRaw', 'Stuff1');
-
- $frame2 = &new MockSimplePage($this);
- $frame2->setReturnValue('getRaw', 'Stuff2');
-
- $frameset = &new SimpleFrameset($page);
- $frameset->addFrame($frame1);
- $frameset->addFrame($frame2);
- $this->assertEqual($frameset->getRaw(), 'Stuff1Stuff2');
- }
-
- function testTextContentIsFromOnlyFrame() {
- $page = &new MockSimplePage($this);
- $page->expectNever('getText');
-
- $frame = &new MockSimplePage($this);
- $frame->setReturnValue('getText', 'Stuff');
-
- $frameset = &new SimpleFrameset($page);
- $frameset->addFrame($frame);
- $this->assertEqual($frameset->getText(), 'Stuff');
- }
-
- function testTextContentIsFromAllFrames() {
- $page = &new MockSimplePage($this);
- $page->expectNever('getText');
-
- $frame1 = &new MockSimplePage($this);
- $frame1->setReturnValue('getText', 'Stuff1');
-
- $frame2 = &new MockSimplePage($this);
- $frame2->setReturnValue('getText', 'Stuff2');
-
- $frameset = &new SimpleFrameset($page);
- $frameset->addFrame($frame1);
- $frameset->addFrame($frame2);
- $this->assertEqual($frameset->getText(), 'Stuff1 Stuff2');
- }
-
- function testFieldIsFirstInFramelist() {
- $frame1 = &new MockSimplePage($this);
- $frame1->setReturnValue('getField', null);
- $frame1->expectOnce('getField', array('a'));
-
- $frame2 = &new MockSimplePage($this);
- $frame2->setReturnValue('getField', 'A');
- $frame2->expectOnce('getField', array('a'));
-
- $frame3 = &new MockSimplePage($this);
- $frame3->expectNever('getField');
-
- $page = &new MockSimplePage($this);
- $frameset = &new SimpleFrameset($page);
- $frameset->addFrame($frame1);
- $frameset->addFrame($frame2);
- $frameset->addFrame($frame3);
-
- $this->assertIdentical($frameset->getField('a'), 'A');
- $frame1->tally();
- $frame2->tally();
- }
-
- function testFrameReplacementByIndex() {
- $page = &new MockSimplePage($this);
- $page->expectNever('getRaw');
-
- $frame1 = &new MockSimplePage($this);
- $frame1->setReturnValue('getRaw', 'Stuff1');
-
- $frame2 = &new MockSimplePage($this);
- $frame2->setReturnValue('getRaw', 'Stuff2');
-
- $frameset = &new SimpleFrameset($page);
- $frameset->addFrame($frame1);
- $frameset->setFrame(array(1), $frame2);
- $this->assertEqual($frameset->getRaw(), 'Stuff2');
- }
-
- function testFrameReplacementByName() {
- $page = &new MockSimplePage($this);
- $page->expectNever('getRaw');
-
- $frame1 = &new MockSimplePage($this);
- $frame1->setReturnValue('getRaw', 'Stuff1');
-
- $frame2 = &new MockSimplePage($this);
- $frame2->setReturnValue('getRaw', 'Stuff2');
-
- $frameset = &new SimpleFrameset($page);
- $frameset->addFrame($frame1, 'a');
- $frameset->setFrame(array('a'), $frame2);
- $this->assertEqual($frameset->getRaw(), 'Stuff2');
- }
- }
-
- class TestOfFrameNavigation extends UnitTestCase {
-
- function testStartsWithoutFrameFocus() {
- $page = &new MockSimplePage($this);
- $frameset = &new SimpleFrameset($page);
- $frameset->addFrame($frame);
- $this->assertFalse($frameset->getFrameFocus());
- }
-
- function testCanFocusOnSingleFrame() {
- $page = &new MockSimplePage($this);
- $page->expectNever('getRaw');
-
- $frame = &new MockSimplePage($this);
- $frame->setReturnValue('getFrameFocus', array());
- $frame->setReturnValue('getRaw', 'Stuff');
-
- $frameset = &new SimpleFrameset($page);
- $frameset->addFrame($frame);
-
- $this->assertFalse($frameset->setFrameFocusByIndex(0));
- $this->assertTrue($frameset->setFrameFocusByIndex(1));
- $this->assertEqual($frameset->getRaw(), 'Stuff');
- $this->assertFalse($frameset->setFrameFocusByIndex(2));
- $this->assertIdentical($frameset->getFrameFocus(), array(1));
- }
-
- function testContentComesFromFrameInFocus() {
- $page = &new MockSimplePage($this);
-
- $frame1 = &new MockSimplePage($this);
- $frame1->setReturnValue('getRaw', 'Stuff1');
- $frame1->setReturnValue('getFrameFocus', array());
-
- $frame2 = &new MockSimplePage($this);
- $frame2->setReturnValue('getRaw', 'Stuff2');
- $frame2->setReturnValue('getFrameFocus', array());
-
- $frameset = &new SimpleFrameset($page);
- $frameset->addFrame($frame1);
- $frameset->addFrame($frame2);
-
- $this->assertTrue($frameset->setFrameFocusByIndex(1));
- $this->assertEqual($frameset->getFrameFocus(), array(1));
- $this->assertEqual($frameset->getRaw(), 'Stuff1');
-
- $this->assertTrue($frameset->setFrameFocusByIndex(2));
- $this->assertEqual($frameset->getFrameFocus(), array(2));
- $this->assertEqual($frameset->getRaw(), 'Stuff2');
-
- $this->assertFalse($frameset->setFrameFocusByIndex(3));
- $this->assertEqual($frameset->getFrameFocus(), array(2));
-
- $frameset->clearFrameFocus();
- $this->assertEqual($frameset->getRaw(), 'Stuff1Stuff2');
- }
- function testCanFocusByName() {
- $page = &new MockSimplePage($this);
-
- $frame1 = &new MockSimplePage($this);
- $frame1->setReturnValue('getRaw', 'Stuff1');
- $frame1->setReturnValue('getFrameFocus', array());
-
- $frame2 = &new MockSimplePage($this);
- $frame2->setReturnValue('getRaw', 'Stuff2');
- $frame2->setReturnValue('getFrameFocus', array());
-
- $frameset = &new SimpleFrameset($page);
- $frameset->addFrame($frame1, 'A');
- $frameset->addFrame($frame2, 'B');
-
- $this->assertTrue($frameset->setFrameFocus('A'));
- $this->assertEqual($frameset->getFrameFocus(), array('A'));
- $this->assertEqual($frameset->getRaw(), 'Stuff1');
-
- $this->assertTrue($frameset->setFrameFocusByIndex(2));
- $this->assertEqual($frameset->getFrameFocus(), array('B'));
- $this->assertEqual($frameset->getRaw(), 'Stuff2');
-
- $this->assertFalse($frameset->setFrameFocus('z'));
-
- $frameset->clearFrameFocus();
- $this->assertEqual($frameset->getRaw(), 'Stuff1Stuff2');
- }
- }
-
- class TestOfFramesetPageInterface extends UnitTestCase {
- var $_page_interface;
- var $_frameset_interface;
-
- function TestOfFramesetPageInterface() {
- $this->UnitTestCase();
- $this->_page_interface = $this->_getPageMethods();
- $this->_frameset_interface = $this->_getFramesetMethods();
- }
-
- function assertListInAnyOrder($list, $expected) {
- sort($list);
- sort($expected);
- $this->assertEqual($list, $expected);
- }
-
- function _getPageMethods() {
- $methods = array();
- foreach (get_class_methods('SimplePage') as $method) {
- if (strtolower($method) == strtolower('SimplePage')) {
- continue;
- }
- if (strtolower($method) == strtolower('getFrameset')) {
- continue;
- }
- if (strncmp($method, '_', 1) == 0) {
- continue;
- }
- if (strncmp($method, 'accept', 6) == 0) {
- continue;
- }
- $methods[] = $method;
- }
- return $methods;
- }
-
- function _getFramesetMethods() {
- $methods = array();
- foreach (get_class_methods('SimpleFrameset') as $method) {
- if (strtolower($method) == strtolower('SimpleFrameset')) {
- continue;
- }
- if (strncmp($method, '_', 1) == 0) {
- continue;
- }
- if (strncmp($method, 'add', 3) == 0) {
- continue;
- }
- $methods[] = $method;
- }
- return $methods;
- }
-
- function testFramsetHasPageInterface() {
- $difference = array();
- foreach ($this->_page_interface as $method) {
- if (! in_array($method, $this->_frameset_interface)) {
- $this->fail("No [$method] in Frameset class");
- return;
- }
- }
- $this->pass('Frameset covers Page interface');
- }
-
- function testHeadersReadFromFrameIfInFocus() {
- $frame = &new MockSimplePage($this);
- $frame->setReturnValue('getUrl', new SimpleUrl('http://localhost/stuff'));
-
- $frame->setReturnValue('getRequest', 'POST stuff');
- $frame->setReturnValue('getMethod', 'POST');
- $frame->setReturnValue('getRequestData', array('a' => 'A'));
- $frame->setReturnValue('getHeaders', 'Header: content');
- $frame->setReturnValue('getMimeType', 'text/xml');
- $frame->setReturnValue('getResponseCode', 401);
- $frame->setReturnValue('getTransportError', 'Could not parse headers');
- $frame->setReturnValue('getAuthentication', 'Basic');
- $frame->setReturnValue('getRealm', 'Safe place');
-
- $frameset = &new SimpleFrameset(new MockSimplePage($this));
- $frameset->addFrame($frame);
- $frameset->setFrameFocusByIndex(1);
-
- $url = new SimpleUrl('http://localhost/stuff');
- $url->setTarget(1);
- $this->assertIdentical($frameset->getUrl(), $url);
-
- $this->assertIdentical($frameset->getRequest(), 'POST stuff');
- $this->assertIdentical($frameset->getMethod(), 'POST');
- $this->assertIdentical($frameset->getRequestData(), array('a' => 'A'));
- $this->assertIdentical($frameset->getHeaders(), 'Header: content');
- $this->assertIdentical($frameset->getMimeType(), 'text/xml');
- $this->assertIdentical($frameset->getResponseCode(), 401);
- $this->assertIdentical($frameset->getTransportError(), 'Could not parse headers');
- $this->assertIdentical($frameset->getAuthentication(), 'Basic');
- $this->assertIdentical($frameset->getRealm(), 'Safe place');
- }
-
- function testAbsoluteUrlsComeFromBothFrames() {
- $page = &new MockSimplePage($this);
- $page->expectNever('getAbsoluteUrls');
-
- $frame1 = &new MockSimplePage($this);
- $frame1->setReturnValue(
- 'getAbsoluteUrls',
- array('http://www.lastcraft.com/', 'http://myserver/'));
-
- $frame2 = &new MockSimplePage($this);
- $frame2->setReturnValue(
- 'getAbsoluteUrls',
- array('http://www.lastcraft.com/', 'http://test/'));
-
- $frameset = &new SimpleFrameset($page);
- $frameset->addFrame($frame1);
- $frameset->addFrame($frame2);
- $this->assertListInAnyOrder(
- $frameset->getAbsoluteUrls(),
- array('http://www.lastcraft.com/', 'http://myserver/', 'http://test/'));
- }
-
- function testRelativeUrlsComeFromBothFrames() {
- $frame1 = &new MockSimplePage($this);
- $frame1->setReturnValue(
- 'getRelativeUrls',
- array('/', '.', '/test/', 'goodbye.php'));
-
- $frame2 = &new MockSimplePage($this);
- $frame2->setReturnValue(
- 'getRelativeUrls',
- array('/', '..', '/test/', 'hello.php'));
-
- $page = &new MockSimplePage($this);
- $page->expectNever('getRelativeUrls');
-
- $frameset = &new SimpleFrameset($page);
- $frameset->addFrame($frame1);
- $frameset->addFrame($frame2);
- $this->assertListInAnyOrder(
- $frameset->getRelativeUrls(),
- array('/', '.', '/test/', 'goodbye.php', '..', 'hello.php'));
- }
-
- function testLabelledUrlsComeFromBothFrames() {
- $frame1 = &new MockSimplePage($this);
- $frame1->setReturnValue(
- 'getUrlsByLabel',
- array(new SimpleUrl('goodbye.php')),
- array('a'));
-
- $frame2 = &new MockSimplePage($this);
- $frame2->setReturnValue(
- 'getUrlsByLabel',
- array(new SimpleUrl('hello.php')),
- array('a'));
-
- $frameset = &new SimpleFrameset(new MockSimplePage($this));
- $frameset->addFrame($frame1);
- $frameset->addFrame($frame2, 'Two');
-
- $expected1 = new SimpleUrl('goodbye.php');
- $expected1->setTarget(1);
- $expected2 = new SimpleUrl('hello.php');
- $expected2->setTarget('Two');
- $this->assertEqual(
- $frameset->getUrlsByLabel('a'),
- array($expected1, $expected2));
- }
-
- function testUrlByIdComesFromFirstFrameToRespond() {
- $frame1 = &new MockSimplePage($this);
- $frame1->setReturnValue('getUrlById', new SimpleUrl('four.php'), array(4));
- $frame1->setReturnValue('getUrlById', false, array(5));
-
- $frame2 = &new MockSimplePage($this);
- $frame2->setReturnValue('getUrlById', false, array(4));
- $frame2->setReturnValue('getUrlById', new SimpleUrl('five.php'), array(5));
-
- $frameset = &new SimpleFrameset(new MockSimplePage($this));
- $frameset->addFrame($frame1);
- $frameset->addFrame($frame2);
-
- $four = new SimpleUrl('four.php');
- $four->setTarget(1);
- $this->assertEqual($frameset->getUrlById(4), $four);
- $five = new SimpleUrl('five.php');
- $five->setTarget(2);
- $this->assertEqual($frameset->getUrlById(5), $five);
- }
-
- function testReadUrlsFromFrameInFocus() {
- $frame1 = &new MockSimplePage($this);
- $frame1->setReturnValue('getAbsoluteUrls', array('a'));
- $frame1->setReturnValue('getRelativeUrls', array('r'));
- $frame1->setReturnValue('getUrlsByLabel', array(new SimpleUrl('l')));
- $frame1->setReturnValue('getUrlById', new SimpleUrl('i'));
-
- $frame2 = &new MockSimplePage($this);
- $frame2->expectNever('getAbsoluteUrls');
- $frame2->expectNever('getRelativeUrls');
- $frame2->expectNever('getUrlsByLabel');
- $frame2->expectNever('getUrlById');
-
- $frameset = &new SimpleFrameset(new MockSimplePage($this));
- $frameset->addFrame($frame1, 'A');
- $frameset->addFrame($frame2, 'B');
- $frameset->setFrameFocus('A');
-
- $this->assertIdentical($frameset->getAbsoluteUrls(), array('a'));
- $this->assertIdentical($frameset->getRelativeUrls(), array('r'));
- $expected = new SimpleUrl('l');
- $expected->setTarget('A');
- $this->assertIdentical($frameset->getUrlsByLabel('label'), array($expected));
- $expected = new SimpleUrl('i');
- $expected->setTarget('A');
- $this->assertIdentical($frameset->getUrlById(99), $expected);
- }
-
- function testReadFrameTaggedUrlsFromFrameInFocus() {
- $frame = &new MockSimplePage($this);
-
- $by_label = new SimpleUrl('l');
- $by_label->setTarget('L');
- $frame->setReturnValue('getUrlsByLabel', array($by_label));
-
- $by_id = new SimpleUrl('i');
- $by_id->setTarget('I');
- $frame->setReturnValue('getUrlById', $by_id);
-
- $frameset = &new SimpleFrameset(new MockSimplePage($this));
- $frameset->addFrame($frame, 'A');
- $frameset->setFrameFocus('A');
-
- $this->assertIdentical($frameset->getUrlsByLabel('label'), array($by_label));
- $this->assertIdentical($frameset->getUrlById(99), $by_id);
- }
-
- function testFindingFormsByAllFinders() {
- $finders = array(
- 'getFormBySubmitLabel', 'getFormBySubmitName',
- 'getFormBySubmitId', 'getFormByImageLabel',
- 'getFormByImageName', 'getFormByImageId', 'getFormById');
- $forms = array();
-
- $frame = &new MockSimplePage($this);
- for ($i = 0; $i < count($finders); $i++) {
- $forms[$i] = &new MockSimpleForm($this);
- $frame->setReturnReference($finders[$i], $forms[$i], array('a'));
- }
-
- $frameset = &new SimpleFrameset(new MockSimplePage($this));
- $frameset->addFrame(new MockSimplePage($this), 'A');
- $frameset->addFrame($frame, 'B');
- for ($i = 0; $i < count($finders); $i++) {
- $method = $finders[$i];
- $this->assertReference($frameset->$method('a'), $forms[$i]);
- }
-
- $frameset->setFrameFocus('A');
- for ($i = 0; $i < count($finders); $i++) {
- $method = $finders[$i];
- $this->assertNull($frameset->$method('a'));
- }
-
- $frameset->setFrameFocus('B');
- for ($i = 0; $i < count($finders); $i++) {
- $method = $finders[$i];
- $this->assertReference($frameset->$method('a'), $forms[$i]);
- }
- }
-
- function testSettingAllFrameFieldsWhenNoFrameFocus() {
- $frame1 = &new MockSimplePage($this);
- $frame1->expectOnce('setField', array('a', 'A'));
- $frame1->expectOnce('setFieldById', array(22, 'A'));
-
- $frame2 = &new MockSimplePage($this);
- $frame2->expectOnce('setField', array('a', 'A'));
- $frame2->expectOnce('setFieldById', array(22, 'A'));
-
- $frameset = &new SimpleFrameset(new MockSimplePage($this));
- $frameset->addFrame($frame1, 'A');
- $frameset->addFrame($frame2, 'B');
-
- $frameset->setField('a', 'A');
- $frameset->setFieldById(22, 'A');
- $frame1->tally();
- $frame2->tally();
- }
-
- function testOnlySettingFieldFromFocusedFrame() {
- $frame1 = &new MockSimplePage($this);
- $frame1->expectOnce('setField', array('a', 'A'));
- $frame1->expectOnce('setFieldById', array(22, 'A'));
-
- $frame2 = &new MockSimplePage($this);
- $frame2->expectNever('setField');
- $frame2->expectNever('setFieldById');
-
- $frameset = &new SimpleFrameset(new MockSimplePage($this));
- $frameset->addFrame($frame1, 'A');
- $frameset->addFrame($frame2, 'B');
- $frameset->setFrameFocus('A');
-
- $frameset->setField('a', 'A');
- $frameset->setFieldById(22, 'A');
- $frame1->tally();
- }
-
- function testOnlyGettingFieldFromFocusedFrame() {
- $frame1 = &new MockSimplePage($this);
- $frame1->setReturnValue('getField', 'f', array('a'));
- $frame1->setReturnValue('getFieldById', 'i', array(7));
-
- $frame2 = &new MockSimplePage($this);
- $frame2->expectNever('getField');
- $frame2->expectNever('getFieldById');
-
- $frameset = &new SimpleFrameset(new MockSimplePage($this));
- $frameset->addFrame($frame1, 'A');
- $frameset->addFrame($frame2, 'B');
- $frameset->setFrameFocus('A');
-
- $this->assertIdentical($frameset->getField('a'), 'f');
- $this->assertIdentical($frameset->getFieldById(7), 'i');
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/http_test.php b/tests/UnitTests/simpletest/test/http_test.php deleted file mode 100644 index ca201be0..00000000 --- a/tests/UnitTests/simpletest/test/http_test.php +++ /dev/null @@ -1,567 +0,0 @@ -<?php
- // $Id: http_test.php,v 1.79 2005/01/02 22:46:10 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../encoding.php');
- require_once(dirname(__FILE__) . '/../http.php');
- require_once(dirname(__FILE__) . '/../socket.php');
- Mock::generate('SimpleSocket');
- Mock::generate('SimpleRoute');
- Mock::generatePartial('SimpleRoute', 'PartialSimpleRoute', array('_createSocket'));
- Mock::generatePartial(
- 'SimpleProxyRoute',
- 'PartialSimpleProxyRoute',
- array('_createSocket'));
-
- class TestOfCookie extends UnitTestCase {
-
- function testCookieDefaults() {
- $cookie = new SimpleCookie("name");
- $this->assertFalse($cookie->getValue());
- $this->assertEqual($cookie->getPath(), "/");
- $this->assertIdentical($cookie->getHost(), false);
- $this->assertFalse($cookie->getExpiry());
- $this->assertFalse($cookie->isSecure());
- }
-
- function testCookieAccessors() {
- $cookie = new SimpleCookie(
- "name",
- "value",
- "/path",
- "Mon, 18 Nov 2002 15:50:29 GMT",
- true);
- $this->assertEqual($cookie->getName(), "name");
- $this->assertEqual($cookie->getValue(), "value");
- $this->assertEqual($cookie->getPath(), "/path/");
- $this->assertEqual($cookie->getExpiry(), "Mon, 18 Nov 2002 15:50:29 GMT");
- $this->assertTrue($cookie->isSecure());
- }
-
- function testFullHostname() {
- $cookie = new SimpleCookie("name");
- $this->assertTrue($cookie->setHost("host.name.here"));
- $this->assertEqual($cookie->getHost(), "host.name.here");
- $this->assertTrue($cookie->setHost("host.com"));
- $this->assertEqual($cookie->getHost(), "host.com");
- }
-
- function testHostTruncation() {
- $cookie = new SimpleCookie("name");
- $cookie->setHost("this.host.name.here");
- $this->assertEqual($cookie->getHost(), "host.name.here");
- $cookie->setHost("this.host.com");
- $this->assertEqual($cookie->getHost(), "host.com");
- $this->assertTrue($cookie->setHost("dashes.in-host.com"));
- $this->assertEqual($cookie->getHost(), "in-host.com");
- }
-
- function testBadHosts() {
- $cookie = new SimpleCookie("name");
- $this->assertFalse($cookie->setHost("gibberish"));
- $this->assertFalse($cookie->setHost("host.here"));
- $this->assertFalse($cookie->setHost("host..com"));
- $this->assertFalse($cookie->setHost("..."));
- $this->assertFalse($cookie->setHost("host.com."));
- }
-
- function testHostValidity() {
- $cookie = new SimpleCookie("name");
- $cookie->setHost("this.host.name.here");
- $this->assertTrue($cookie->isValidHost("host.name.here"));
- $this->assertTrue($cookie->isValidHost("that.host.name.here"));
- $this->assertFalse($cookie->isValidHost("bad.host"));
- $this->assertFalse($cookie->isValidHost("nearly.name.here"));
- }
-
- function testPathValidity() {
- $cookie = new SimpleCookie("name", "value", "/path");
- $this->assertFalse($cookie->isValidPath("/"));
- $this->assertTrue($cookie->isValidPath("/path/"));
- $this->assertTrue($cookie->isValidPath("/path/more"));
- }
-
- function testSessionExpiring() {
- $cookie = new SimpleCookie("name", "value", "/path");
- $this->assertTrue($cookie->isExpired(0));
- }
-
- function testTimestampExpiry() {
- $cookie = new SimpleCookie("name", "value", "/path", 456);
- $this->assertFalse($cookie->isExpired(0));
- $this->assertTrue($cookie->isExpired(457));
- $this->assertFalse($cookie->isExpired(455));
- }
-
- function testDateExpiry() {
- $cookie = new SimpleCookie(
- "name",
- "value",
- "/path",
- "Mon, 18 Nov 2002 15:50:29 GMT");
- $this->assertTrue($cookie->isExpired("Mon, 18 Nov 2002 15:50:30 GMT"));
- $this->assertFalse($cookie->isExpired("Mon, 18 Nov 2002 15:50:28 GMT"));
- }
-
- function testAging() {
- $cookie = new SimpleCookie("name", "value", "/path", 200);
- $cookie->agePrematurely(199);
- $this->assertFalse($cookie->isExpired(0));
- $cookie->agePrematurely(2);
- $this->assertTrue($cookie->isExpired(0));
- }
- }
-
- class TestOfDirectRoute extends UnitTestCase {
-
- function testDefaultGetRequest() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("GET /here.html HTTP/1.0\r\n"));
- $socket->expectArgumentsAt(1, 'write', array("Host: a.valid.host\r\n"));
- $socket->expectArgumentsAt(2, 'write', array("Connection: close\r\n"));
- $socket->expectCallCount('write', 3);
-
- $route = &new PartialSimpleRoute($this);
- $route->setReturnReference('_createSocket', $socket);
- $route->SimpleRoute(new SimpleUrl('http://a.valid.host/here.html'));
-
- $this->assertReference($route->createConnection('GET', 15), $socket);
- $socket->tally();
- }
-
- function testDefaultPostRequest() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("POST /here.html HTTP/1.0\r\n"));
- $socket->expectArgumentsAt(1, 'write', array("Host: a.valid.host\r\n"));
- $socket->expectArgumentsAt(2, 'write', array("Connection: close\r\n"));
- $socket->expectCallCount('write', 3);
-
- $route = &new PartialSimpleRoute($this);
- $route->setReturnReference('_createSocket', $socket);
- $route->SimpleRoute(new SimpleUrl('http://a.valid.host/here.html'));
-
- $route->createConnection('POST', 15);
- $socket->tally();
- }
-
- function testGetWithPort() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("GET /here.html HTTP/1.0\r\n"));
- $socket->expectArgumentsAt(1, 'write', array("Host: a.valid.host:81\r\n"));
- $socket->expectArgumentsAt(2, 'write', array("Connection: close\r\n"));
- $socket->expectCallCount('write', 3);
-
- $route = &new PartialSimpleRoute($this);
- $route->setReturnReference('_createSocket', $socket);
- $route->SimpleRoute(new SimpleUrl('http://a.valid.host:81/here.html'));
-
- $route->createConnection('GET', 15);
- $socket->tally();
- }
-
- function testGetWithParameters() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("GET /here.html?a=1&b=2 HTTP/1.0\r\n"));
- $socket->expectArgumentsAt(1, 'write', array("Host: a.valid.host\r\n"));
- $socket->expectArgumentsAt(2, 'write', array("Connection: close\r\n"));
- $socket->expectCallCount('write', 3);
-
- $route = &new PartialSimpleRoute($this);
- $route->setReturnReference('_createSocket', $socket);
- $route->SimpleRoute(new SimpleUrl('http://a.valid.host/here.html?a=1&b=2'));
-
- $route->createConnection('GET', 15);
- $socket->tally();
- }
- }
-
- class TestOfProxyRoute extends UnitTestCase {
-
- function testDefaultGet() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("GET http://a.valid.host/here.html HTTP/1.0\r\n"));
- $socket->expectArgumentsAt(1, 'write', array("Host: my-proxy:8080\r\n"));
- $socket->expectArgumentsAt(2, 'write', array("Connection: close\r\n"));
- $socket->expectCallCount('write', 3);
-
- $route = &new PartialSimpleProxyRoute($this);
- $route->setReturnReference('_createSocket', $socket);
- $route->SimpleProxyRoute(
- new SimpleUrl('http://a.valid.host/here.html'),
- new SimpleUrl('http://my-proxy'));
-
- $route->createConnection('GET', 15);
- $socket->tally();
- }
-
- function testDefaultPost() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("POST http://a.valid.host/here.html HTTP/1.0\r\n"));
- $socket->expectArgumentsAt(1, 'write', array("Host: my-proxy:8080\r\n"));
- $socket->expectArgumentsAt(2, 'write', array("Connection: close\r\n"));
- $socket->expectCallCount('write', 3);
-
- $route = &new PartialSimpleProxyRoute($this);
- $route->setReturnReference('_createSocket', $socket);
- $route->SimpleProxyRoute(
- new SimpleUrl('http://a.valid.host/here.html'),
- new SimpleUrl('http://my-proxy'));
-
- $route->createConnection('POST', 15);
- $socket->tally();
- }
-
- function testGetWithPort() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("GET http://a.valid.host:81/here.html HTTP/1.0\r\n"));
- $socket->expectArgumentsAt(1, 'write', array("Host: my-proxy:8081\r\n"));
- $socket->expectArgumentsAt(2, 'write', array("Connection: close\r\n"));
- $socket->expectCallCount('write', 3);
-
- $route = &new PartialSimpleProxyRoute($this);
- $route->setReturnReference('_createSocket', $socket);
- $route->SimpleProxyRoute(
- new SimpleUrl('http://a.valid.host:81/here.html'),
- new SimpleUrl('http://my-proxy:8081'));
-
- $route->createConnection('GET', 15);
- $socket->tally();
- }
-
- function testGetWithParameters() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("GET http://a.valid.host/here.html?a=1&b=2 HTTP/1.0\r\n"));
- $socket->expectArgumentsAt(1, 'write', array("Host: my-proxy:8080\r\n"));
- $socket->expectArgumentsAt(2, 'write', array("Connection: close\r\n"));
- $socket->expectCallCount('write', 3);
-
- $route = &new PartialSimpleProxyRoute($this);
- $route->setReturnReference('_createSocket', $socket);
- $route->SimpleProxyRoute(
- new SimpleUrl('http://a.valid.host/here.html?a=1&b=2'),
- new SimpleUrl('http://my-proxy'));
-
- $route->createConnection('GET', 15);
- $socket->tally();
- }
-
- function testGetWithAuthentication() {
- $encoded = base64_encode('Me:Secret');
-
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("GET http://a.valid.host/here.html HTTP/1.0\r\n"));
- $socket->expectArgumentsAt(1, 'write', array("Host: my-proxy:8080\r\n"));
- $socket->expectArgumentsAt(2, 'write', array("Proxy-Authorization: Basic $encoded\r\n"));
- $socket->expectArgumentsAt(3, 'write', array("Connection: close\r\n"));
- $socket->expectCallCount('write', 4);
-
- $route = &new PartialSimpleProxyRoute($this);
- $route->setReturnReference('_createSocket', $socket);
- $route->SimpleProxyRoute(
- new SimpleUrl('http://a.valid.host/here.html'),
- new SimpleUrl('http://my-proxy'),
- 'Me',
- 'Secret');
-
- $route->createConnection('GET', 15);
- $socket->tally();
- }
- }
-
- class TestOfHttpRequest extends UnitTestCase {
-
- function testReadingBadConnection() {
- $socket = &new MockSimpleSocket($this);
-
- $route = &new MockSimpleRoute($this);
- $route->setReturnReference('createConnection', $socket);
-
- $request = &new SimpleHttpRequest($route, 'GET');
-
- $reponse = &$request->fetch(15);
- $this->assertTrue($reponse->isError());
- }
-
- function testReadingGoodConnection() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectOnce('write', array("\r\n"));
-
- $route = &new MockSimpleRoute($this);
- $route->setReturnReference('createConnection', $socket);
- $route->expectArguments('createConnection', array('GET', 15));
-
- $request = &new SimpleHttpRequest($route, 'GET');
-
- $this->assertIsA($request->fetch(15), 'SimpleHttpResponse');
- $socket->tally();
- $route->tally();
- }
-
- function testWritingAdditionalHeaders() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("My: stuff\r\n"));
- $socket->expectArgumentsAt(1, 'write', array("\r\n"));
- $socket->expectCallCount('write', 2);
-
- $route = &new MockSimpleRoute($this);
- $route->setReturnReference('createConnection', $socket);
-
- $request = &new SimpleHttpRequest($route, 'GET');
- $request->addHeaderLine('My: stuff');
- $request->fetch(15);
-
- $socket->tally();
- }
-
- function testCookieWriting() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("Cookie: a=A\r\n"));
- $socket->expectArgumentsAt(1, 'write', array("\r\n"));
- $socket->expectCallCount('write', 2);
-
- $route = &new MockSimpleRoute($this);
- $route->setReturnReference('createConnection', $socket);
-
- $request = &new SimpleHttpRequest($route, 'GET');
- $request->setCookie(new SimpleCookie('a', 'A'));
-
- $this->assertIsA($request->fetch(15), 'SimpleHttpResponse');
- $socket->tally();
- }
-
- function testMultipleCookieWriting() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("Cookie: a=A;b=B\r\n"));
-
- $route = &new MockSimpleRoute($this);
- $route->setReturnReference('createConnection', $socket);
-
- $request = &new SimpleHttpRequest($route, 'GET');
- $request->setCookie(new SimpleCookie('a', 'A'));
- $request->setCookie(new SimpleCookie('b', 'B'));
-
- $request->fetch(15);
- $socket->tally();
- }
- }
-
- class TestOfHttpPostRequest extends UnitTestCase {
-
- function testReadingBadConnection() {
- $socket = &new MockSimpleSocket($this);
-
- $route = &new MockSimpleRoute($this);
- $route->setReturnReference('createConnection', $socket);
-
- $request = &new SimpleHttpRequest($route, 'POST', '');
-
- $reponse = &$request->fetch(15);
- $this->assertTrue($reponse->isError());
- }
-
- function testReadingGoodConnection() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("Content-Length: 0\r\n"));
- $socket->expectArgumentsAt(1, 'write', array("Content-Type: application/x-www-form-urlencoded\r\n"));
- $socket->expectArgumentsAt(2, 'write', array("\r\n"));
- $socket->expectArgumentsAt(3, 'write', array(""));
-
- $route = &new MockSimpleRoute($this);
- $route->setReturnReference('createConnection', $socket);
- $route->expectArguments('createConnection', array('POST', 15));
-
- $request = &new SimpleHttpRequest($route, 'POST', new SimpleFormEncoding());
-
- $this->assertIsA($request->fetch(15), 'SimpleHttpResponse');
- $socket->tally();
- $route->tally();
- }
-
- function testContentHeadersCalculated() {
- $socket = &new MockSimpleSocket($this);
- $socket->expectArgumentsAt(0, 'write', array("Content-Length: 3\r\n"));
- $socket->expectArgumentsAt(1, 'write', array("Content-Type: application/x-www-form-urlencoded\r\n"));
- $socket->expectArgumentsAt(2, 'write', array("\r\n"));
- $socket->expectArgumentsAt(3, 'write', array("a=A"));
-
- $route = &new MockSimpleRoute($this);
- $route->setReturnReference('createConnection', $socket);
- $route->expectArguments('createConnection', array('POST', 15));
-
- $request = &new SimpleHttpRequest(
- $route,
- 'POST',
- new SimpleFormEncoding(array('a' => 'A')));
-
- $this->assertIsA($request->fetch(15), 'SimpleHttpResponse');
- $socket->tally();
- $route->tally();
- }
- }
-
- class TestOfHttpHeaders extends UnitTestCase {
-
- function testParseBasicHeaders() {
- $headers = new SimpleHttpHeaders("HTTP/1.1 200 OK\r\n" .
- "Date: Mon, 18 Nov 2002 15:50:29 GMT\r\n" .
- "Content-Type: text/plain\r\n" .
- "Server: Apache/1.3.24 (Win32) PHP/4.2.3\r\n" .
- "Connection: close");
- $this->assertIdentical($headers->getHttpVersion(), "1.1");
- $this->assertIdentical($headers->getResponseCode(), 200);
- $this->assertEqual($headers->getMimeType(), "text/plain");
- }
-
- function testParseOfCookies() {
- $headers = new SimpleHttpHeaders("HTTP/1.1 200 OK\r\n" .
- "Date: Mon, 18 Nov 2002 15:50:29 GMT\r\n" .
- "Content-Type: text/plain\r\n" .
- "Server: Apache/1.3.24 (Win32) PHP/4.2.3\r\n" .
- "Set-Cookie: a=aaa; expires=Wed, 25-Dec-02 04:24:20 GMT; path=/here/\r\n" .
- "Set-Cookie: b=bbb\r\n" .
- "Connection: close");
- $cookies = $headers->getNewCookies();
- $this->assertEqual(count($cookies), 2);
- $this->assertEqual($cookies[0]->getName(), "a");
- $this->assertEqual($cookies[0]->getValue(), "aaa");
- $this->assertEqual($cookies[0]->getPath(), "/here/");
- $this->assertEqual($cookies[0]->getExpiry(), "Wed, 25 Dec 2002 04:24:20 GMT");
- $this->assertEqual($cookies[1]->getName(), "b");
- $this->assertEqual($cookies[1]->getValue(), "bbb");
- $this->assertEqual($cookies[1]->getPath(), "/");
- $this->assertEqual($cookies[1]->getExpiry(), "");
- }
-
- function testRedirect() {
- $headers = new SimpleHttpHeaders("HTTP/1.1 301 OK\r\n" .
- "Content-Type: text/plain\r\n" .
- "Content-Length: 0\r\n" .
- "Location: http://www.somewhere-else.com/\r\n" .
- "Connection: close");
- $this->assertIdentical($headers->getResponseCode(), 301);
- $this->assertEqual($headers->getLocation(), "http://www.somewhere-else.com/");
- $this->assertTrue($headers->isRedirect());
- }
-
- function testParseChallenge() {
- $headers = new SimpleHttpHeaders("HTTP/1.1 401 Authorization required\r\n" .
- "Content-Type: text/plain\r\n" .
- "Connection: close\r\n" .
- "WWW-Authenticate: Basic realm=\"Somewhere\"");
- $this->assertEqual($headers->getAuthentication(), 'Basic');
- $this->assertEqual($headers->getRealm(), 'Somewhere');
- $this->assertTrue($headers->isChallenge());
- }
- }
-
- class TestOfHttpResponse extends UnitTestCase {
-
- function testBadRequest() {
- $socket = &new MockSimpleSocket($this);
- $socket->setReturnValue('getSent', '');
-
- $response = &new SimpleHttpResponse($socket, 'GET', new SimpleUrl('here'));
- $this->assertTrue($response->isError());
- $this->assertWantedPattern('/Nothing fetched/', $response->getError());
- $this->assertIdentical($response->getContent(), false);
- $this->assertIdentical($response->getSent(), '');
- }
-
- function testBadSocketDuringResponse() {
- $socket = &new MockSimpleSocket($this);
- $socket->setReturnValueAt(0, "read", "HTTP/1.1 200 OK\r\n");
- $socket->setReturnValueAt(1, "read", "Date: Mon, 18 Nov 2002 15:50:29 GMT\r\n");
- $socket->setReturnValue("read", "");
- $socket->setReturnValue('getSent', 'HTTP/1.1 ...');
-
- $response = &new SimpleHttpResponse($socket, 'GET', new SimpleUrl('here'));
- $this->assertTrue($response->isError());
- $this->assertEqual($response->getContent(), '');
- $this->assertEqual($response->getSent(), 'HTTP/1.1 ...');
- }
-
- function testIncompleteHeader() {
- $socket = &new MockSimpleSocket($this);
- $socket->setReturnValueAt(0, "read", "HTTP/1.1 200 OK\r\n");
- $socket->setReturnValueAt(1, "read", "Date: Mon, 18 Nov 2002 15:50:29 GMT\r\n");
- $socket->setReturnValueAt(2, "read", "Content-Type: text/plain\r\n");
- $socket->setReturnValue("read", "");
-
- $response = &new SimpleHttpResponse($socket, 'GET', new SimpleUrl('here'));
- $this->assertTrue($response->isError());
- $this->assertEqual($response->getContent(), "");
- }
-
- function testParseOfResponseHeaders() {
- $socket = &new MockSimpleSocket($this);
- $socket->setReturnValueAt(0, "read", "HTTP/1.1 200 OK\r\nDate: Mon, 18 Nov 2002 15:50:29 GMT\r\n");
- $socket->setReturnValueAt(1, "read", "Content-Type: text/plain\r\n");
- $socket->setReturnValueAt(2, "read", "Server: Apache/1.3.24 (Win32) PHP/4.2.3\r\nConne");
- $socket->setReturnValueAt(3, "read", "ction: close\r\n\r\nthis is a test file\n");
- $socket->setReturnValueAt(4, "read", "with two lines in it\n");
- $socket->setReturnValue("read", "");
-
- $response = &new SimpleHttpResponse($socket, 'GET', new SimpleUrl('here'));
- $this->assertFalse($response->isError());
- $this->assertEqual(
- $response->getContent(),
- "this is a test file\nwith two lines in it\n");
- $headers = $response->getHeaders();
- $this->assertIdentical($headers->getHttpVersion(), "1.1");
- $this->assertIdentical($headers->getResponseCode(), 200);
- $this->assertEqual($headers->getMimeType(), "text/plain");
- $this->assertFalse($headers->isRedirect());
- $this->assertFalse($headers->getLocation());
- }
-
- function testParseOfCookies() {
- $socket = &new MockSimpleSocket($this);
- $socket->setReturnValueAt(0, "read", "HTTP/1.1 200 OK\r\n");
- $socket->setReturnValueAt(1, "read", "Date: Mon, 18 Nov 2002 15:50:29 GMT\r\n");
- $socket->setReturnValueAt(2, "read", "Content-Type: text/plain\r\n");
- $socket->setReturnValueAt(3, "read", "Server: Apache/1.3.24 (Win32) PHP/4.2.3\r\n");
- $socket->setReturnValueAt(4, "read", "Set-Cookie: a=aaa; expires=Wed, 25-Dec-02 04:24:20 GMT; path=/here/\r\n");
- $socket->setReturnValueAt(5, "read", "Connection: close\r\n");
- $socket->setReturnValueAt(6, "read", "\r\n");
- $socket->setReturnValue("read", "");
-
- $response = &new SimpleHttpResponse($socket, 'GET', new SimpleUrl('here'));
- $this->assertFalse($response->isError());
- $headers = $response->getHeaders();
- $cookies = $headers->getNewCookies();
- $this->assertEqual($cookies[0]->getName(), "a");
- $this->assertEqual($cookies[0]->getValue(), "aaa");
- $this->assertEqual($cookies[0]->getPath(), "/here/");
- $this->assertEqual($cookies[0]->getExpiry(), "Wed, 25 Dec 2002 04:24:20 GMT");
- }
-
- function testRedirect() {
- $socket = &new MockSimpleSocket($this);
- $socket->setReturnValueAt(0, "read", "HTTP/1.1 301 OK\r\n");
- $socket->setReturnValueAt(1, "read", "Content-Type: text/plain\r\n");
- $socket->setReturnValueAt(2, "read", "Location: http://www.somewhere-else.com/\r\n");
- $socket->setReturnValueAt(3, "read", "Connection: close\r\n");
- $socket->setReturnValueAt(4, "read", "\r\n");
- $socket->setReturnValue("read", "");
-
- $response = &new SimpleHttpResponse($socket, 'GET', new SimpleUrl('here'));
- $headers = $response->getHeaders();
- $this->assertTrue($headers->isRedirect());
- $this->assertEqual($headers->getLocation(), "http://www.somewhere-else.com/");
- }
-
- function testRedirectWithPort() {
- $socket = &new MockSimpleSocket($this);
- $socket->setReturnValueAt(0, "read", "HTTP/1.1 301 OK\r\n");
- $socket->setReturnValueAt(1, "read", "Content-Type: text/plain\r\n");
- $socket->setReturnValueAt(2, "read", "Location: http://www.somewhere-else.com:80/\r\n");
- $socket->setReturnValueAt(3, "read", "Connection: close\r\n");
- $socket->setReturnValueAt(4, "read", "\r\n");
- $socket->setReturnValue("read", "");
-
- $response = &new SimpleHttpResponse($socket, 'GET', new SimpleUrl('here'));
- $headers = $response->getHeaders();
- $this->assertTrue($headers->isRedirect());
- $this->assertEqual($headers->getLocation(), "http://www.somewhere-else.com:80/");
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/live_test.php b/tests/UnitTests/simpletest/test/live_test.php deleted file mode 100644 index 193284e7..00000000 --- a/tests/UnitTests/simpletest/test/live_test.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php
- // $Id: live_test.php,v 1.95 2004/09/24 22:55:13 lastcraft Exp $
- require_once(dirname(__FILE__) . '/../unit_tester.php');
- require_once(dirname(__FILE__) . '/../socket.php');
- require_once(dirname(__FILE__) . '/../http.php');
- require_once(dirname(__FILE__) . '/../options.php');
-
- if (SimpleTestOptions::getDefaultProxy()) {
- SimpleTestOptions::ignore('LiveHttpTestCase');
- }
-
- class LiveHttpTestCase extends UnitTestCase {
-
- function testBadSocket() {
- $socket = &new SimpleSocket('bad_url', 111, 5);
- $this->assertTrue($socket->isError());
- $this->assertWantedPattern(
- '/Cannot open \\[bad_url:111\\] with \\[.*?\\] within \\[5\\] seconds/',
- $socket->getError());
- $this->assertFalse($socket->isOpen());
- $this->assertFalse($socket->write('A message'));
- }
-
- function testSocketClosure() {
- $socket = &new SimpleSocket('www.lastcraft.com', 80, 15);
- $this->assertTrue($socket->isOpen());
- $this->assertTrue($socket->write("GET /test/network_confirm.php HTTP/1.0\r\n"));
- $socket->write("Host: www.lastcraft.com\r\n");
- $socket->write("Connection: close\r\n\r\n");
- $this->assertEqual($socket->read(8), "HTTP/1.1");
- $socket->close();
- $this->assertIdentical($socket->read(8), false);
- }
-
- function testRecordOfSentCharacters() {
- $socket = &new SimpleSocket('www.lastcraft.com', 80, 15);
- $this->assertTrue($socket->write("GET /test/network_confirm.php HTTP/1.0\r\n"));
- $socket->write("Host: www.lastcraft.com\r\n");
- $socket->write("Connection: close\r\n\r\n");
- $socket->close();
- $this->assertEqual($socket->getSent(),
- "GET /test/network_confirm.php HTTP/1.0\r\n" .
- "Host: www.lastcraft.com\r\n" .
- "Connection: close\r\n\r\n");
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/options_test.php b/tests/UnitTests/simpletest/test/options_test.php deleted file mode 100644 index 8535f739..00000000 --- a/tests/UnitTests/simpletest/test/options_test.php +++ /dev/null @@ -1,95 +0,0 @@ -<?php
- // $Id: options_test.php,v 1.9 2005/01/13 01:31:57 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../options.php');
-
- class TestOfOptions extends UnitTestCase {
-
- function testMockBase() {
- $old_class = SimpleTestOptions::getMockBaseClass();
- SimpleTestOptions::setMockBaseClass('Fred');
- $this->assertEqual(SimpleTestOptions::getMockBaseClass(), 'Fred');
- SimpleTestOptions::setMockBaseClass($old_class);
- }
-
- function testStubBase() {
- $old_class = SimpleTestOptions::getStubBaseClass();
- SimpleTestOptions::setStubBaseClass('Fred');
- $this->assertEqual(SimpleTestOptions::getStubBaseClass(), 'Fred');
- SimpleTestOptions::setStubBaseClass($old_class);
- }
-
- function testIgnoreList() {
- $this->assertFalse(SimpleTestOptions::isIgnored('ImaginaryTestCase'));
- SimpleTestOptions::ignore('ImaginaryTestCase');
- $this->assertTrue(SimpleTestOptions::isIgnored('ImaginaryTestCase'));
- }
- }
-
- class ComparisonClass {
- }
-
- class ComparisonSubclass extends ComparisonClass {
- }
-
- class TestOfCompatibility extends UnitTestCase {
-
- function testIsA() {
- $this->assertTrue(SimpleTestCompatibility::isA(
- new ComparisonClass(),
- 'ComparisonClass'));
- $this->assertFalse(SimpleTestCompatibility::isA(
- new ComparisonClass(),
- 'ComparisonSubclass'));
- $this->assertTrue(SimpleTestCompatibility::isA(
- new ComparisonSubclass(),
- 'ComparisonClass'));
- }
-
- function testIdentityOfObjects() {
- $object1 = new ComparisonClass();
- $object2 = new ComparisonClass();
- $this->assertIdentical($object1, $object2);
- }
-
- function testReferences () {
- $thing = "Hello";
- $thing_reference = &$thing;
- $thing_copy = $thing;
- $this->assertTrue(SimpleTestCompatibility::isReference(
- $thing,
- $thing));
- $this->assertTrue(SimpleTestCompatibility::isReference(
- $thing,
- $thing_reference));
- $this->assertFalse(SimpleTestCompatibility::isReference(
- $thing,
- $thing_copy));
- }
-
- function testObjectReferences () {
- $object = &new ComparisonClass();
- $object_reference = &$object;
- $object_copy = new ComparisonClass();
- $object_assignment = $object;
- $this->assertTrue(SimpleTestCompatibility::isReference(
- $object,
- $object));
- $this->assertTrue(SimpleTestCompatibility::isReference(
- $object,
- $object_reference));
- $this->assertFalse(SimpleTestCompatibility::isReference(
- $object,
- $object_copy));
- if (version_compare(phpversion(), '5', '>=')) {
- $this->assertTrue(SimpleTestCompatibility::isReference(
- $object,
- $object_assignment));
- } else {
- $this->assertFalse(SimpleTestCompatibility::isReference(
- $object,
- $object_assignment));
- }
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/page_test.php b/tests/UnitTests/simpletest/test/page_test.php deleted file mode 100644 index d9e87d12..00000000 --- a/tests/UnitTests/simpletest/test/page_test.php +++ /dev/null @@ -1,792 +0,0 @@ -<?php
- // $Id: page_test.php,v 1.74 2005/01/03 03:41:14 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../http.php');
- require_once(dirname(__FILE__) . '/../page.php');
- require_once(dirname(__FILE__) . '/../parser.php');
-
- Mock::generate('SimpleSaxParser');
- Mock::generate('SimplePage');
- Mock::generate('SimpleHttpResponse');
- Mock::generate('SimpleHttpHeaders');
- Mock::generate('SimplePageBuilder');
- Mock::generatePartial(
- 'SimplePageBuilder',
- 'PartialSimplePageBuilder',
- array('_createPage', '_createParser'));
-
- class TestOfPageBuilder extends UnitTestCase {
-
- function testLink() {
- $tag = &new SimpleAnchorTag(array('href' => 'http://somewhere'));
- $tag->addContent('Label');
-
- $page = &new MockSimplePage($this);
- $page->expectArguments('acceptTag', array($tag));
- $page->expectCallCount('acceptTag', 1);
-
- $builder = &new PartialSimplePageBuilder($this);
- $builder->setReturnReference('_createPage', $page);
- $builder->setReturnReference('_createParser', new MockSimpleSaxParser($this));
- $builder->SimplePageBuilder();
-
- $builder->parse(new MockSimpleHttpResponse($this));
- $this->assertTrue($builder->startElement(
- 'a',
- array('href' => 'http://somewhere')));
- $this->assertTrue($builder->addContent('Label'));
- $this->assertTrue($builder->endElement('a'));
-
- $page->tally();
- }
-
- function testLinkWithId() {
- $tag = &new SimpleAnchorTag(array("href" => "http://somewhere", "id" => "44"));
- $tag->addContent("Label");
-
- $page = &new MockSimplePage($this);
- $page->expectArguments("acceptTag", array($tag));
- $page->expectCallCount("acceptTag", 1);
-
- $builder = &new PartialSimplePageBuilder($this);
- $builder->setReturnReference('_createPage', $page);
- $builder->setReturnReference('_createParser', new MockSimpleSaxParser($this));
- $builder->SimplePageBuilder();
-
- $builder->parse(new MockSimpleHttpResponse($this));
- $this->assertTrue($builder->startElement(
- "a",
- array("href" => "http://somewhere", "id" => "44")));
- $this->assertTrue($builder->addContent("Label"));
- $this->assertTrue($builder->endElement("a"));
-
- $page->tally();
- }
-
- function testLinkExtraction() {
- $tag = &new SimpleAnchorTag(array("href" => "http://somewhere"));
- $tag->addContent("Label");
-
- $page = &new MockSimplePage($this);
- $page->expectArguments("acceptTag", array($tag));
- $page->expectCallCount("acceptTag", 1);
-
- $builder = &new PartialSimplePageBuilder($this);
- $builder->setReturnReference('_createPage', $page);
- $builder->setReturnReference('_createParser', new MockSimpleSaxParser($this));
- $builder->SimplePageBuilder();
-
- $builder->parse(new MockSimpleHttpResponse($this));
- $this->assertTrue($builder->addContent("Starting stuff"));
- $this->assertTrue($builder->startElement(
- "a",
- array("href" => "http://somewhere")));
- $this->assertTrue($builder->addContent("Label"));
- $this->assertTrue($builder->endElement("a"));
- $this->assertTrue($builder->addContent("Trailing stuff"));
-
- $page->tally();
- }
-
- function testMultipleLinks() {
- $a1 = new SimpleAnchorTag(array("href" => "http://somewhere"));
- $a1->addContent("1");
-
- $a2 = new SimpleAnchorTag(array("href" => "http://elsewhere"));
- $a2->addContent("2");
-
- $page = &new MockSimplePage($this);
- $page->expectArgumentsAt(0, "acceptTag", array($a1));
- $page->expectArgumentsAt(1, "acceptTag", array($a2));
- $page->expectCallCount("acceptTag", 2);
-
- $builder = &new PartialSimplePageBuilder($this);
- $builder->setReturnReference('_createPage', $page);
- $builder->setReturnReference('_createParser', new MockSimpleSaxParser($this));
- $builder->SimplePageBuilder();
-
- $builder->parse(new MockSimpleHttpResponse($this));
- $builder->startElement("a", array("href" => "http://somewhere"));
- $builder->addContent("1");
- $builder->endElement("a");
- $builder->addContent("Padding");
- $builder->startElement("a", array("href" => "http://elsewhere"));
- $builder->addContent("2");
- $builder->endElement("a");
-
- $page->tally();
- }
-
- function testTitle() {
- $tag = &new SimpleTitleTag(array());
- $tag->addContent("HereThere");
-
- $page = &new MockSimplePage($this);
- $page->expectArguments("acceptTag", array($tag));
- $page->expectCallCount("acceptTag", 1);
-
- $builder = &new PartialSimplePageBuilder($this);
- $builder->setReturnReference('_createPage', $page);
- $builder->setReturnReference('_createParser', new MockSimpleSaxParser($this));
- $builder->SimplePageBuilder();
-
- $builder->parse(new MockSimpleHttpResponse($this));
- $builder->startElement("title", array());
- $builder->addContent("Here");
- $builder->addContent("There");
- $builder->endElement("title");
-
- $page->tally();
- }
-
- function testForm() {
- $page = &new MockSimplePage($this);
- $page->expectOnce("acceptFormStart", array(new SimpleFormTag(array())));
- $page->expectOnce("acceptFormEnd", array());
-
- $builder = &new PartialSimplePageBuilder($this);
- $builder->setReturnReference('_createPage', $page);
- $builder->setReturnReference('_createParser', new MockSimpleSaxParser($this));
- $builder->SimplePageBuilder();
-
- $builder->parse(new MockSimpleHttpResponse($this));
- $builder->startElement("form", array());
- $builder->addContent("Stuff");
- $builder->endElement("form");
- $page->tally();
- }
- }
-
- class TestOfPageParsing extends UnitTestCase {
-
- function testParseMechanics() {
- $parser = &new MockSimpleSaxParser($this);
- $parser->expectOnce('parse', array('stuff'));
-
- $page = &new MockSimplePage($this);
- $page->expectOnce('acceptPageEnd');
-
- $builder = &new PartialSimplePageBuilder($this);
- $builder->setReturnReference('_createPage', $page);
- $builder->setReturnReference('_createParser', $parser);
- $builder->SimplePageBuilder();
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', 'stuff');
-
- $builder->parse($response);
- $parser->tally();
- $page->tally();
- }
- }
-
- class TestOfErrorPage extends UnitTestCase {
-
- function testInterface() {
- $page = &new SimplePage();
- $this->assertEqual($page->getTransportError(), 'No page fetched yet');
- $this->assertIdentical($page->getRaw(), false);
- $this->assertIdentical($page->getHeaders(), false);
- $this->assertIdentical($page->getMimeType(), false);
- $this->assertIdentical($page->getResponseCode(), false);
- $this->assertIdentical($page->getAuthentication(), false);
- $this->assertIdentical($page->getRealm(), false);
- $this->assertFalse($page->hasFrames());
- $this->assertIdentical($page->getAbsoluteUrls(), array());
- $this->assertIdentical($page->getRelativeUrls(), array());
- $this->assertIdentical($page->getTitle(), false);
- }
- }
-
- class TestOfPageHeaders extends UnitTestCase {
-
- function testUrlAccessor() {
- $headers = &new MockSimpleHttpHeaders($this);
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getHeaders', $headers);
- $response->setReturnValue('getMethod', 'POST');
- $response->setReturnValue('getUrl', new SimpleUrl('here'));
- $response->setReturnValue('getRequestData', array('a' => 'A'));
-
- $page = &new SimplePage($response);
- $this->assertEqual($page->getMethod(), 'POST');
- $this->assertEqual($page->getUrl(), new SimpleUrl('here'));
- $this->assertEqual($page->getRequestData(), array('a' => 'A'));
- }
-
- function testTransportError() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getError', 'Ouch');
-
- $page = &new SimplePage($response);
- $this->assertEqual($page->getTransportError(), 'Ouch');
- }
-
- function testHeadersAccessor() {
- $headers = &new MockSimpleHttpHeaders($this);
- $headers->setReturnValue('getRaw', 'My: Headers');
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getHeaders', $headers);
-
- $page = &new SimplePage($response);
- $this->assertEqual($page->getHeaders(), 'My: Headers');
- }
-
- function testMimeAccessor() {
- $headers = &new MockSimpleHttpHeaders($this);
- $headers->setReturnValue('getMimeType', 'text/html');
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getHeaders', $headers);
-
- $page = &new SimplePage($response);
- $this->assertEqual($page->getMimeType(), 'text/html');
- }
-
- function testResponseAccessor() {
- $headers = &new MockSimpleHttpHeaders($this);
- $headers->setReturnValue('getResponseCode', 301);
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getHeaders', $headers);
-
- $page = &new SimplePage($response);
- $this->assertIdentical($page->getResponseCode(), 301);
- }
-
- function testAuthenticationAccessors() {
- $headers = &new MockSimpleHttpHeaders($this);
- $headers->setReturnValue('getAuthentication', 'Basic');
- $headers->setReturnValue('getRealm', 'Secret stuff');
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getHeaders', $headers);
-
- $page = &new SimplePage($response);
- $this->assertEqual($page->getAuthentication(), 'Basic');
- $this->assertEqual($page->getRealm(), 'Secret stuff');
- }
- }
-
- class TestOfHtmlPage extends UnitTestCase {
-
- function testRawAccessor() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', 'Raw HTML');
-
- $page = &new SimplePage($response);
- $this->assertEqual($page->getRaw(), 'Raw HTML');
- }
-
- function testTextAccessor() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', '<b>Some</b> "messy" HTML');
-
- $page = &new SimplePage($response);
- $this->assertEqual($page->getText(), 'Some "messy" HTML');
- }
-
- function testNoLinks() {
- $page = &new SimplePage(new MockSimpleHttpResponse($this));
- $this->assertIdentical($page->getAbsoluteUrls(), array(), 'abs->%s');
- $this->assertIdentical($page->getRelativeUrls(), array(), 'rel->%s');
- $this->assertIdentical($page->getUrlsByLabel('Label'), array());
- }
-
- function testAddAbsoluteLink() {
- $link = &new SimpleAnchorTag(array('href' => 'http://somewhere.com'));
- $link->addContent('Label');
-
- $page = &new SimplePage(new MockSimpleHttpResponse($this));
- $page->AcceptTag($link);
-
- $this->assertEqual($page->getAbsoluteUrls(), array('http://somewhere.com'), 'abs->%s');
- $this->assertIdentical($page->getRelativeUrls(), array(), 'rel->%s');
- $this->assertEqual(
- $page->getUrlsByLabel('Label'),
- array(new SimpleUrl('http://somewhere.com')));
- }
-
- function testAddStrictRelativeLink() {
- $link = &new SimpleAnchorTag(array('href' => './somewhere.php'));
- $link->addContent('Label');
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getUrl', new SimpleUrl('http://host/'));
-
- $page = &new SimplePage($response);
- $page->AcceptTag($link);
-
- $this->assertEqual($page->getAbsoluteUrls(), array(), 'abs->%s');
- $this->assertIdentical($page->getRelativeUrls(), array('./somewhere.php'), 'rel->%s');
- $this->assertEqual(
- $page->getUrlsByLabel('Label'),
- array(new SimpleUrl('http://host/somewhere.php')));
- }
-
- function testAddRelativeLink() {
- $link = &new SimpleAnchorTag(array('href' => 'somewhere.php'));
- $link->addContent('Label');
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getUrl', new SimpleUrl('http://host/'));
-
- $page = &new SimplePage($response);
- $page->AcceptTag($link);
-
- $this->assertEqual($page->getAbsoluteUrls(), array(), 'abs->%s');
- $this->assertIdentical($page->getRelativeUrls(), array('somewhere.php'), 'rel->%s');
- $this->assertEqual(
- $page->getUrlsByLabel('Label'),
- array(new SimpleUrl('http://host/somewhere.php')));
- }
-
- function testLinkIds() {
- $link = &new SimpleAnchorTag(array('href' => './somewhere.php', 'id' => 33));
- $link->addContent('Label');
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getUrl', new SimpleUrl('http://host/'));
-
- $page = &new SimplePage($response);
- $page->AcceptTag($link);
-
- $this->assertEqual(
- $page->getUrlsByLabel('Label'),
- array(new SimpleUrl('http://host/somewhere.php')));
- $this->assertFalse($page->getUrlById(0));
- $this->assertEqual(
- $page->getUrlById(33),
- new SimpleUrl('http://host/somewhere.php'));
- }
-
- function testFindLinkWithNormalisation() {
- $link = &new SimpleAnchorTag(array('href' => './somewhere.php', 'id' => 33));
- $link->addContent(' <em>Long & thin</em> ');
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getUrl', new SimpleUrl('http://host/'));
-
- $page = &new SimplePage($response);
- $page->AcceptTag($link);
-
- $this->assertEqual(
- $page->getUrlsByLabel('Long & thin'),
- array(new SimpleUrl('http://host/somewhere.php')));
- }
-
- function testFindLinkWithImage() {
- $link = &new SimpleAnchorTag(array('href' => './somewhere.php', 'id' => 33));
- $link->addContent('<img src="pic.jpg" alt="<A picture>">');
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getUrl', new SimpleUrl('http://host/'));
-
- $page = &new SimplePage($response);
- $page->AcceptTag($link);
-
- $this->assertEqual(
- $page->getUrlsByLabel('<A picture>'),
- array(new SimpleUrl('http://host/somewhere.php')));
- }
-
- function testTitleSetting() {
- $title = &new SimpleTitleTag(array());
- $title->addContent('Title');
- $page = &new SimplePage(new MockSimpleHttpResponse($this));
- $page->AcceptTag($title);
- $this->assertEqual($page->getTitle(), 'Title');
- }
-
- function testFramesetAbsence() {
- $url = new SimpleUrl('here');
- $response = new MockSimpleHttpResponse($this);
- $response->setReturnValue('getUrl', $url);
- $page = &new SimplePage($response);
- $this->assertFalse($page->hasFrames());
- $this->assertIdentical($page->getFrameset(), false);
- }
-
- function testHasEmptyFrameset() {
- $page = &new SimplePage(new MockSimpleHttpResponse($this));
- $page->acceptFramesetStart(new SimpleTag('frameset', array()));
- $page->acceptFramesetEnd();
- $this->assertTrue($page->hasFrames());
- $this->assertIdentical($page->getFrameset(), array());
- }
-
- function testFramesInPage() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getUrl', new SimpleUrl('http://here'));
-
- $page = &new SimplePage($response);
- $page->acceptFrame(new SimpleFrameTag(array('src' => '1.html')));
- $page->acceptFramesetStart(new SimpleTag('frameset', array()));
- $page->acceptFrame(new SimpleFrameTag(array('src' => '2.html')));
- $page->acceptFrame(new SimpleFrameTag(array('src' => '3.html')));
- $page->acceptFramesetEnd();
- $page->acceptFrame(new SimpleFrameTag(array('src' => '4.html')));
-
- $this->assertTrue($page->hasFrames());
- $this->assertIdentical($page->getFrameset(), array(
- 1 => new SimpleUrl('http://here/2.html'),
- 2 => new SimpleUrl('http://here/3.html')));
- }
-
- function testNamedFramesInPage() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getUrl', new SimpleUrl('http://here'));
-
- $page = &new SimplePage($response);
- $page->acceptFramesetStart(new SimpleTag('frameset', array()));
- $page->acceptFrame(new SimpleFrameTag(array('src' => '1.html')));
- $page->acceptFrame(new SimpleFrameTag(array('src' => '2.html', 'name' => 'A')));
- $page->acceptFrame(new SimpleFrameTag(array('src' => '3.html', 'name' => 'B')));
- $page->acceptFrame(new SimpleFrameTag(array('src' => '4.html')));
- $page->acceptFramesetEnd();
-
- $this->assertTrue($page->hasFrames());
- $this->assertIdentical($page->getFrameset(), array(
- 1 => new SimpleUrl('http://here/1.html'),
- 'A' => new SimpleUrl('http://here/2.html'),
- 'B' => new SimpleUrl('http://here/3.html'),
- 4 => new SimpleUrl('http://here/4.html')));
- }
- }
-
- class TestOfForms extends UnitTestCase {
-
- function testButtons() {
- $page = &new SimplePage(new MockSimpleHttpResponse($this));
- $page->acceptFormStart(
- new SimpleFormTag(array("method" => "GET", "action" => "here.php")));
- $page->AcceptTag(
- new SimpleSubmitTag(array("type" => "submit", "name" => "s")));
- $page->acceptFormEnd();
- $form = &$page->getFormBySubmitLabel("Submit");
- $this->assertEqual(
- $form->submitButtonByLabel("Submit"),
- new SimpleFormEncoding(array("s" => "Submit")));
- }
- }
-
- class TestOfPageScraping extends UnitTestCase {
-
- function &parse($response) {
- $builder = &new SimplePageBuilder();
- return $builder->parse($response);
- }
-
- function testEmptyPage() {
- $page = &new SimplePage(new MockSimpleHttpResponse($this));
- $this->assertIdentical($page->getAbsoluteUrls(), array());
- $this->assertIdentical($page->getRelativeUrls(), array());
- $this->assertIdentical($page->getTitle(), false);
- }
-
- function testUninterestingPage() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', '<html><body><p>Stuff</p></body></html>');
-
- $page = &$this->parse($response);
- $this->assertIdentical($page->getAbsoluteUrls(), array());
- $this->assertIdentical($page->getRelativeUrls(), array());
- }
-
- function testLinksPage() {
- $raw = '<html>';
- $raw .= '<a href="there.html">There</a>';
- $raw .= '<a href="http://there.com/that.html" id="0">That page</a>';
- $raw .= '</html>';
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', $raw);
- $response->setReturnValue('getUrl', new SimpleUrl('http://www.here.com/a/index.html'));
-
- $page = &$this->parse($response);
- $this->assertIdentical(
- $page->getAbsoluteUrls(),
- array('http://there.com/that.html'));
- $this->assertIdentical(
- $page->getRelativeUrls(),
- array('there.html'));
- $this->assertIdentical(
- $page->getUrlsByLabel('There'),
- array(new SimpleUrl('http://www.here.com/a/there.html')));
- $this->assertEqual(
- $page->getUrlById('0'),
- new SimpleUrl('http://there.com/that.html'));
- }
-
- function testTitle() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', '<html><head><title>Me</title></head></html>');
-
- $page = &$this->parse($response);
- $this->assertEqual($page->getTitle(), 'Me');
- }
-
- function testNastyTitle() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue(
- 'getContent',
- '<html><head><Title> <b>Me&Me </TITLE></b></head></html>');
-
- $page = &$this->parse($response);
- $this->assertEqual($page->getTitle(), "Me&Me");
- }
-
- function testCompleteForm() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent',
- '<html><head><form>' .
- '<input type="text" name="here" value="Hello">' .
- '</form></head></html>');
-
- $page = &$this->parse($response);
- $this->assertEqual($page->getField('here'), "Hello");
- }
-
- function testUnclosedForm() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent',
- '<html><head><form>' .
- '<input type="text" name="here" value="Hello">' .
- '</head></html>');
-
- $page = &$this->parse($response);
- $this->assertEqual($page->getField('here'), "Hello");
- }
-
- function testEmptyFrameset() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue(
- 'getContent',
- '<html><frameset></frameset></html>');
-
- $page = &$this->parse($response);
- $this->assertTrue($page->hasFrames());
- $this->assertIdentical($page->getFrameset(), array());
- }
-
- function testSingleFrame() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue(
- 'getContent',
- '<html><frameset><frame src="a.html"></frameset></html>');
- $response->setReturnValue('getUrl', new SimpleUrl('http://host/'));
-
- $page = &$this->parse($response);
- $this->assertTrue($page->hasFrames());
- $this->assertIdentical(
- $page->getFrameset(),
- array(1 => new SimpleUrl('http://host/a.html')));
- }
-
- function testSingleFrameInNestedFrameset() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent',
- '<html><frameset><frameset>' .
- '<frame src="a.html">' .
- '</frameset></frameset></html>');
- $response->setReturnValue('getUrl', new SimpleUrl('http://host/'));
-
- $page = &$this->parse($response);
- $this->assertTrue($page->hasFrames());
- $this->assertIdentical(
- $page->getFrameset(),
- array(1 => new SimpleUrl('http://host/a.html')));
- }
-
- function testFrameWithNoSource() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue(
- 'getContent',
- '<html><frameset><frame></frameset></html>');
-
- $page = &$this->parse($response);
- $this->assertTrue($page->hasFrames());
- $this->assertIdentical($page->getFrameset(), array());
- }
-
- function testFramesCollectedWithNestedFramesetTags() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent',
- '<html><frameset>' .
- '<frame src="a.html">' .
- '<frameset><frame src="b.html"></frameset>' .
- '<frame src="c.html">' .
- '</frameset></html>');
- $response->setReturnValue('getUrl', new SimpleUrl('http://host/'));
-
- $page = &$this->parse($response);
- $this->assertTrue($page->hasFrames());
- $this->assertIdentical($page->getFrameset(), array(
- 1 => new SimpleUrl('http://host/a.html'),
- 2 => new SimpleUrl('http://host/b.html'),
- 3 => new SimpleUrl('http://host/c.html')));
- }
-
- function testNamedFrames() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', '<html><frameset>' .
- '<frame src="a.html">' .
- '<frame name="_one" src="b.html">' .
- '<frame src="c.html">' .
- '<frame src="d.html" name="_two">' .
- '</frameset></html>');
- $response->setReturnValue('getUrl', new SimpleUrl('http://host/'));
-
- $page = &$this->parse($response);
- $this->assertTrue($page->hasFrames());
- $this->assertIdentical($page->getFrameset(), array(
- 1 => new SimpleUrl('http://host/a.html'),
- '_one' => new SimpleUrl('http://host/b.html'),
- 3 => new SimpleUrl('http://host/c.html'),
- '_two' => new SimpleUrl('http://host/d.html')));
- }
-
- function testFindFormByLabel() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue(
- 'getContent',
- '<html><head><form><input type="submit"></form></head></html>');
-
- $page = &$this->parse($response);
- $this->assertNull($page->getFormBySubmitLabel('submit'));
- $this->assertIsA($page->getFormBySubmitName('submit'), 'SimpleForm');
- $this->assertIsA($page->getFormBySubmitLabel('Submit'), 'SimpleForm');
- }
-
- function testConfirmSubmitAttributesAreCaseInsensitive() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue(
- 'getContent',
- '<html><head><FORM><INPUT TYPE="SUBMIT"></FORM></head></html>');
-
- $page = &$this->parse($response);
- $this->assertIsA($page->getFormBySubmitName('submit'), 'SimpleForm');
- $this->assertIsA($page->getFormBySubmitLabel('Submit'), 'SimpleForm');
- }
-
- function testFindFormByImage() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', '<html><head><form>' .
- '<input type="image" id=100 alt="Label" name="me">' .
- '</form></head></html>');
-
- $page = &$this->parse($response);
- $this->assertIsA($page->getFormByImageLabel('Label'), 'SimpleForm');
- $this->assertIsA($page->getFormByImageName('me'), 'SimpleForm');
- $this->assertIsA($page->getFormByImageId(100), 'SimpleForm');
- }
-
- function testFindFormByButtonTag() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', '<html><head><form>' .
- '<button type="submit" name="b" value="B">BBB</button>' .
- '</form></head></html>');
-
- $page = &$this->parse($response);
- $this->assertNull($page->getFormBySubmitLabel('b'));
- $this->assertNull($page->getFormBySubmitLabel('B'));
- $this->assertIsA($page->getFormBySubmitName('b'), 'SimpleForm');
- $this->assertIsA($page->getFormBySubmitLabel('BBB'), 'SimpleForm');
- }
-
- function testFindFormById() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue(
- 'getContent',
- '<html><head><form id="55"><input type="submit"></form></head></html>');
-
- $page = &$this->parse($response);
- $this->assertNull($page->getFormById(54));
- $this->assertIsA($page->getFormById(55), 'SimpleForm');
- }
-
- function testReadingTextField() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', '<html><head><form>' .
- '<input type="text" name="a">' .
- '<input type="text" name="b" value="bbb" id=3>' .
- '</form></head></html>');
-
- $page = &$this->parse($response);
- $this->assertNull($page->getField('missing'));
- $this->assertIdentical($page->getField('a'), '');
- $this->assertIdentical($page->getField('b'), 'bbb');
- }
-
- function testReadingTextFieldIsCaseInsensitive() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', '<html><head><FORM>' .
- '<INPUT TYPE="TEXT" NAME="a">' .
- '<INPUT TYPE="TEXT" NAME="b" VALUE="bbb" id=3>' .
- '</FORM></head></html>');
-
- $page = &$this->parse($response);
- $this->assertNull($page->getField('missing'));
- $this->assertIdentical($page->getField('a'), '');
- $this->assertIdentical($page->getField('b'), 'bbb');
- }
-
- function testSettingTextField() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', '<html><head><form>' .
- '<input type="text" name="a">' .
- '<input type="text" name="b" id=3>' .
- '<input type="submit">' .
- '</form></head></html>');
-
- $page = &$this->parse($response);
- $this->assertTrue($page->setField('a', 'aaa'));
- $this->assertEqual($page->getField('a'), 'aaa');
- $this->assertTrue($page->setFieldById(3, 'bbb'));
- $this->assertEqual($page->getFieldById(3), 'bbb');
- $this->assertFalse($page->setField('z', 'zzz'));
- $this->assertNull($page->getField('z'));
- }
-
- function testReadingTextArea() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', '<html><head><form>' .
- '<textarea name="a">aaa</textarea>' .
- '<input type="submit">' .
- '</form></head></html>');
-
- $page = &$this->parse($response);
- $this->assertEqual($page->getField('a'), 'aaa');
- }
-
- function testSettingTextArea() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', '<html><head><form>' .
- '<textarea name="a">aaa</textarea>' .
- '<input type="submit">' .
- '</form></head></html>');
-
- $page = &$this->parse($response);
- $this->assertTrue($page->setField('a', 'AAA'));
- $this->assertEqual($page->getField('a'), 'AAA');
- }
-
- function testSettingSelectionField() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', '<html><head><form>' .
- '<select name="a">' .
- '<option>aaa</option>' .
- '<option selected>bbb</option>' .
- '</select>' .
- '<input type="submit">' .
- '</form></head></html>');
-
- $page = &$this->parse($response);
- $this->assertEqual($page->getField('a'), 'bbb');
- $this->assertFalse($page->setField('a', 'ccc'));
- $this->assertTrue($page->setField('a', 'aaa'));
- $this->assertEqual($page->getField('a'), 'aaa');
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/parse_error_test.php b/tests/UnitTests/simpletest/test/parse_error_test.php deleted file mode 100644 index dd0f4eae..00000000 --- a/tests/UnitTests/simpletest/test/parse_error_test.php +++ /dev/null @@ -1,10 +0,0 @@ -<?php
- // $Id: parse_error_test.php,v 1.1 2005/01/24 00:32:14 lastcraft Exp $
-
- require_once('../unit_tester.php');
- require_once('../reporter.php');
-
- $test = &new GroupTest('This should fail');
- $test->addTestFile('test_with_parse_error.php');
- $test->run(new HtmlReporter());
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/parser_test.php b/tests/UnitTests/simpletest/test/parser_test.php deleted file mode 100644 index 6356c343..00000000 --- a/tests/UnitTests/simpletest/test/parser_test.php +++ /dev/null @@ -1,648 +0,0 @@ -<?php
- // $Id: parser_test.php,v 1.51 2004/11/30 05:34:00 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../parser.php');
-
- Mock::generate('SimpleSaxParser');
-
- class TestOfParallelRegex extends UnitTestCase {
-
- function testNoPatterns() {
- $regex = &new ParallelRegex(false);
- $this->assertFalse($regex->match("Hello", $match));
- $this->assertEqual($match, "");
- }
-
- function testNoSubject() {
- $regex = &new ParallelRegex(false);
- $regex->addPattern(".*");
- $this->assertTrue($regex->match("", $match));
- $this->assertEqual($match, "");
- }
-
- function testMatchAll() {
- $regex = &new ParallelRegex(false);
- $regex->addPattern(".*");
- $this->assertTrue($regex->match("Hello", $match));
- $this->assertEqual($match, "Hello");
- }
-
- function testCaseSensitive() {
- $regex = &new ParallelRegex(true);
- $regex->addPattern("abc");
- $this->assertTrue($regex->match("abcdef", $match));
- $this->assertEqual($match, "abc");
- $this->assertTrue($regex->match("AAABCabcdef", $match));
- $this->assertEqual($match, "abc");
- }
-
- function testCaseInsensitive() {
- $regex = &new ParallelRegex(false);
- $regex->addPattern("abc");
- $this->assertTrue($regex->match("abcdef", $match));
- $this->assertEqual($match, "abc");
- $this->assertTrue($regex->match("AAABCabcdef", $match));
- $this->assertEqual($match, "ABC");
- }
-
- function testMatchMultiple() {
- $regex = &new ParallelRegex(true);
- $regex->addPattern("abc");
- $regex->addPattern("ABC");
- $this->assertTrue($regex->match("abcdef", $match));
- $this->assertEqual($match, "abc");
- $this->assertTrue($regex->match("AAABCabcdef", $match));
- $this->assertEqual($match, "ABC");
- $this->assertFalse($regex->match("Hello", $match));
- }
-
- function testPatternLabels() {
- $regex = &new ParallelRegex(false);
- $regex->addPattern("abc", "letter");
- $regex->addPattern("123", "number");
- $this->assertIdentical($regex->match("abcdef", $match), "letter");
- $this->assertEqual($match, "abc");
- $this->assertIdentical($regex->match("0123456789", $match), "number");
- $this->assertEqual($match, "123");
- }
- }
-
- class TestOfStateStack extends UnitTestCase {
-
- function testStartState() {
- $stack = &new SimpleStateStack("one");
- $this->assertEqual($stack->getCurrent(), "one");
- }
-
- function testExhaustion() {
- $stack = &new SimpleStateStack("one");
- $this->assertFalse($stack->leave());
- }
-
- function testStateMoves() {
- $stack = &new SimpleStateStack("one");
- $stack->enter("two");
- $this->assertEqual($stack->getCurrent(), "two");
- $stack->enter("three");
- $this->assertEqual($stack->getCurrent(), "three");
- $this->assertTrue($stack->leave());
- $this->assertEqual($stack->getCurrent(), "two");
- $stack->enter("third");
- $this->assertEqual($stack->getCurrent(), "third");
- $this->assertTrue($stack->leave());
- $this->assertTrue($stack->leave());
- $this->assertEqual($stack->getCurrent(), "one");
- }
- }
-
- class TestParser {
-
- function accept() {
- }
-
- function a() {
- }
-
- function b() {
- }
- }
- Mock::generate('TestParser');
-
- class TestOfLexer extends UnitTestCase {
-
- function testEmptyPage() {
- $handler = &new MockTestParser($this);
- $handler->expectNever("accept");
- $handler->setReturnValue("accept", true);
- $handler->expectNever("accept");
- $handler->setReturnValue("accept", true);
- $lexer = &new SimpleLexer($handler);
- $lexer->addPattern("a+");
- $this->assertTrue($lexer->parse(""));
- }
-
- function testSinglePattern() {
- $handler = &new MockTestParser($this);
- $handler->expectArgumentsAt(0, "accept", array("aaa", LEXER_MATCHED));
- $handler->expectArgumentsAt(1, "accept", array("x", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(2, "accept", array("a", LEXER_MATCHED));
- $handler->expectArgumentsAt(3, "accept", array("yyy", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(4, "accept", array("a", LEXER_MATCHED));
- $handler->expectArgumentsAt(5, "accept", array("x", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(6, "accept", array("aaa", LEXER_MATCHED));
- $handler->expectArgumentsAt(7, "accept", array("z", LEXER_UNMATCHED));
- $handler->expectCallCount("accept", 8);
- $handler->setReturnValue("accept", true);
- $lexer = &new SimpleLexer($handler);
- $lexer->addPattern("a+");
- $this->assertTrue($lexer->parse("aaaxayyyaxaaaz"));
- $handler->tally();
- }
-
- function testMultiplePattern() {
- $handler = &new MockTestParser($this);
- $target = array("a", "b", "a", "bb", "x", "b", "a", "xxxxxx", "a", "x");
- for ($i = 0; $i < count($target); $i++) {
- $handler->expectArgumentsAt($i, "accept", array($target[$i], '*'));
- }
- $handler->expectCallCount("accept", count($target));
- $handler->setReturnValue("accept", true);
- $lexer = &new SimpleLexer($handler);
- $lexer->addPattern("a+");
- $lexer->addPattern("b+");
- $this->assertTrue($lexer->parse("ababbxbaxxxxxxax"));
- $handler->tally();
- }
- }
-
- class TestOfLexerModes extends UnitTestCase {
-
- function testIsolatedPattern() {
- $handler = &new MockTestParser($this);
- $handler->expectArgumentsAt(0, "a", array("a", LEXER_MATCHED));
- $handler->expectArgumentsAt(1, "a", array("b", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(2, "a", array("aa", LEXER_MATCHED));
- $handler->expectArgumentsAt(3, "a", array("bxb", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(4, "a", array("aaa", LEXER_MATCHED));
- $handler->expectArgumentsAt(5, "a", array("x", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(6, "a", array("aaaa", LEXER_MATCHED));
- $handler->expectArgumentsAt(7, "a", array("x", LEXER_UNMATCHED));
- $handler->expectCallCount("a", 8);
- $handler->setReturnValue("a", true);
- $lexer = &new SimpleLexer($handler, "a");
- $lexer->addPattern("a+", "a");
- $lexer->addPattern("b+", "b");
- $this->assertTrue($lexer->parse("abaabxbaaaxaaaax"));
- $handler->tally();
- }
-
- function testModeChange() {
- $handler = &new MockTestParser($this);
- $handler->expectArgumentsAt(0, "a", array("a", LEXER_MATCHED));
- $handler->expectArgumentsAt(1, "a", array("b", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(2, "a", array("aa", LEXER_MATCHED));
- $handler->expectArgumentsAt(3, "a", array("b", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(4, "a", array("aaa", LEXER_MATCHED));
- $handler->expectArgumentsAt(0, "b", array(":", LEXER_ENTER));
- $handler->expectArgumentsAt(1, "b", array("a", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(2, "b", array("b", LEXER_MATCHED));
- $handler->expectArgumentsAt(3, "b", array("a", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(4, "b", array("bb", LEXER_MATCHED));
- $handler->expectArgumentsAt(5, "b", array("a", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(6, "b", array("bbb", LEXER_MATCHED));
- $handler->expectArgumentsAt(7, "b", array("a", LEXER_UNMATCHED));
- $handler->expectCallCount("a", 5);
- $handler->expectCallCount("b", 8);
- $handler->setReturnValue("a", true);
- $handler->setReturnValue("b", true);
- $lexer = &new SimpleLexer($handler, "a");
- $lexer->addPattern("a+", "a");
- $lexer->addEntryPattern(":", "a", "b");
- $lexer->addPattern("b+", "b");
- $this->assertTrue($lexer->parse("abaabaaa:ababbabbba"));
- $handler->tally();
- }
-
- function testNesting() {
- $handler = &new MockTestParser($this);
- $handler->setReturnValue("a", true);
- $handler->setReturnValue("b", true);
- $handler->expectArgumentsAt(0, "a", array("aa", LEXER_MATCHED));
- $handler->expectArgumentsAt(1, "a", array("b", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(2, "a", array("aa", LEXER_MATCHED));
- $handler->expectArgumentsAt(3, "a", array("b", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(0, "b", array("(", LEXER_ENTER));
- $handler->expectArgumentsAt(1, "b", array("bb", LEXER_MATCHED));
- $handler->expectArgumentsAt(2, "b", array("a", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(3, "b", array("bb", LEXER_MATCHED));
- $handler->expectArgumentsAt(4, "b", array(")", LEXER_EXIT));
- $handler->expectArgumentsAt(4, "a", array("aa", LEXER_MATCHED));
- $handler->expectArgumentsAt(5, "a", array("b", LEXER_UNMATCHED));
- $handler->expectCallCount("a", 6);
- $handler->expectCallCount("b", 5);
- $lexer = &new SimpleLexer($handler, "a");
- $lexer->addPattern("a+", "a");
- $lexer->addEntryPattern("(", "a", "b");
- $lexer->addPattern("b+", "b");
- $lexer->addExitPattern(")", "b");
- $this->assertTrue($lexer->parse("aabaab(bbabb)aab"));
- $handler->tally();
- }
-
- function testSingular() {
- $handler = &new MockTestParser($this);
- $handler->setReturnValue("a", true);
- $handler->setReturnValue("b", true);
- $handler->expectArgumentsAt(0, "a", array("aa", LEXER_MATCHED));
- $handler->expectArgumentsAt(1, "a", array("aa", LEXER_MATCHED));
- $handler->expectArgumentsAt(2, "a", array("xx", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(3, "a", array("xx", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(0, "b", array("b", LEXER_SPECIAL));
- $handler->expectArgumentsAt(1, "b", array("bbb", LEXER_SPECIAL));
- $handler->expectCallCount("a", 4);
- $handler->expectCallCount("b", 2);
- $lexer = &new SimpleLexer($handler, "a");
- $lexer->addPattern("a+", "a");
- $lexer->addSpecialPattern("b+", "a", "b");
- $this->assertTrue($lexer->parse("aabaaxxbbbxx"));
- $handler->tally();
- }
-
- function testUnwindTooFar() {
- $handler = &new MockTestParser($this);
- $handler->setReturnValue("a", true);
- $handler->expectArgumentsAt(0, "a", array("aa", LEXER_MATCHED));
- $handler->expectArgumentsAt(1, "a", array(")", LEXER_EXIT));
- $handler->expectCallCount("a", 2);
- $lexer = &new SimpleLexer($handler, "a");
- $lexer->addPattern("a+", "a");
- $lexer->addExitPattern(")", "a");
- $this->assertFalse($lexer->parse("aa)aa"));
- $handler->tally();
- }
- }
-
- class TestOfLexerHandlers extends UnitTestCase {
-
- function testModeMapping() {
- $handler = &new MockTestParser($this);
- $handler->setReturnValue("a", true);
- $handler->expectArgumentsAt(0, "a", array("aa", LEXER_MATCHED));
- $handler->expectArgumentsAt(1, "a", array("(", LEXER_ENTER));
- $handler->expectArgumentsAt(2, "a", array("bb", LEXER_MATCHED));
- $handler->expectArgumentsAt(3, "a", array("a", LEXER_UNMATCHED));
- $handler->expectArgumentsAt(4, "a", array("bb", LEXER_MATCHED));
- $handler->expectArgumentsAt(5, "a", array(")", LEXER_EXIT));
- $handler->expectArgumentsAt(6, "a", array("b", LEXER_UNMATCHED));
- $handler->expectCallCount("a", 7);
- $lexer = &new SimpleLexer($handler, "mode_a");
- $lexer->addPattern("a+", "mode_a");
- $lexer->addEntryPattern("(", "mode_a", "mode_b");
- $lexer->addPattern("b+", "mode_b");
- $lexer->addExitPattern(")", "mode_b");
- $lexer->mapHandler("mode_a", "a");
- $lexer->mapHandler("mode_b", "a");
- $this->assertTrue($lexer->parse("aa(bbabb)b"));
- $handler->tally();
- }
- }
-
- Mock::generate("HtmlSaxParser");
-
- class TestOfHtmlLexer extends UnitTestCase {
- var $_handler;
- var $_lexer;
-
- function setUp() {
- $this->_handler = &new MockSimpleSaxParser($this);
- $this->_handler->setReturnValue("acceptStartToken", true);
- $this->_handler->setReturnValue("acceptEndToken", true);
- $this->_handler->setReturnValue("acceptAttributeToken", true);
- $this->_handler->setReturnValue("acceptEntityToken", true);
- $this->_handler->setReturnValue("acceptTextToken", true);
- $this->_handler->setReturnValue("ignore", true);
- $this->_lexer = &SimpleSaxParser::createLexer($this->_handler);
- }
-
- function tearDown() {
- $this->_handler->tally();
- }
-
- function testUninteresting() {
- $this->_handler->expectOnce("acceptTextToken", array("<html></html>", "*"));
- $this->assertTrue($this->_lexer->parse("<html></html>"));
- }
-
- function testSkipCss() {
- $this->_handler->expectMaximumCallCount("acceptTextToken", 0);
- $this->_handler->expectAtLeastOnce("ignore");
- $this->assertTrue($this->_lexer->parse("<style>Lot's of styles</style>"));
- }
-
- function testSkipJavaScript() {
- $this->_handler->expectMaximumCallCount("acceptTextToken", 0);
- $this->_handler->expectAtLeastOnce("ignore");
- $this->assertTrue($this->_lexer->parse("<SCRIPT>Javascript code {';:^%^%£$'@\"*(}</SCRIPT>"));
- }
-
- function testSkipComments() {
- $this->_handler->expectMaximumCallCount("acceptTextToken", 0);
- $this->_handler->expectAtLeastOnce("ignore");
- $this->assertTrue($this->_lexer->parse("<!-- <style>Lot's of styles</style> -->"));
- }
-
- function testTitleTag() {
- $this->_handler->expectArgumentsAt(0, "acceptStartToken", array("<title", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptStartToken", array(">", "*"));
- $this->_handler->expectCallCount("acceptStartToken", 2);
- $this->_handler->expectOnce("acceptTextToken", array("Hello", "*"));
- $this->_handler->expectOnce("acceptEndToken", array("</title>", "*"));
- $this->assertTrue($this->_lexer->parse("<title>Hello</title>"));
- }
-
- function testFramesetTag() {
- $this->_handler->expectArgumentsAt(0, "acceptStartToken", array("<frameset", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptStartToken", array(">", "*"));
- $this->_handler->expectCallCount("acceptStartToken", 2);
- $this->_handler->expectOnce("acceptTextToken", array("Frames", "*"));
- $this->_handler->expectOnce("acceptEndToken", array("</frameset>", "*"));
- $this->assertTrue($this->_lexer->parse("<frameset>Frames</frameset>"));
- }
-
- function testInputTag() {
- $this->_handler->expectArgumentsAt(0, "acceptStartToken", array("<input", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptStartToken", array("name", "*"));
- $this->_handler->expectArgumentsAt(2, "acceptStartToken", array("value", "*"));
- $this->_handler->expectArgumentsAt(3, "acceptStartToken", array(">", "*"));
- $this->_handler->expectArgumentsAt(0, "acceptAttributeToken", array("=a.b.c", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptAttributeToken", array("= d", "*"));
- $this->assertTrue($this->_lexer->parse("<input name=a.b.c value = d>"));
- }
-
- function testEmptyLink() {
- $this->_handler->expectArgumentsAt(0, "acceptStartToken", array("<a", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptStartToken", array(">", "*"));
- $this->_handler->expectCallCount("acceptStartToken", 2);
- $this->_handler->expectOnce("acceptEndToken", array("</a>", "*"));
- $this->assertTrue($this->_lexer->parse("<html><a></a></html>"));
- }
-
- function testLabelledLink() {
- $this->_handler->expectArgumentsAt(0, "acceptStartToken", array("<a", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptStartToken", array(">", "*"));
- $this->_handler->expectCallCount("acceptStartToken", 2);
- $this->_handler->expectOnce("acceptEndToken", array("</a>", "*"));
- $this->_handler->expectArgumentsAt(0, "acceptTextToken", array("<html>", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptTextToken", array("label", "*"));
- $this->_handler->expectArgumentsAt(2, "acceptTextToken", array("</html>", "*"));
- $this->_handler->expectCallCount("acceptTextToken", 3);
- $this->assertTrue($this->_lexer->parse("<html><a>label</a></html>"));
- }
-
- function testLinkAddress() {
- $this->_handler->expectArgumentsAt(0, "acceptTextToken", array("<html>", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptTextToken", array("label", "*"));
- $this->_handler->expectArgumentsAt(2, "acceptTextToken", array("</html>", "*"));
- $this->_handler->expectCallCount("acceptTextToken", 3);
- $this->_handler->expectArgumentsAt(0, "acceptStartToken", array("<a", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptStartToken", array("href", "*"));
- $this->_handler->expectArgumentsAt(2, "acceptStartToken", array(">", "*"));
- $this->_handler->expectCallCount("acceptStartToken", 3);
- $this->_handler->expectArgumentsAt(0, "acceptAttributeToken", array("= '", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptAttributeToken", array("here.html", "*"));
- $this->_handler->expectArgumentsAt(2, "acceptAttributeToken", array("'", "*"));
- $this->_handler->expectCallCount("acceptAttributeToken", 3);
- $this->assertTrue($this->_lexer->parse("<html><a href = 'here.html'>label</a></html>"));
- }
-
- function testEncodedLinkAddress() {
- $this->_handler->expectArgumentsAt(0, "acceptTextToken", array("<html>", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptTextToken", array("label", "*"));
- $this->_handler->expectArgumentsAt(2, "acceptTextToken", array("</html>", "*"));
- $this->_handler->expectCallCount("acceptTextToken", 3);
- $this->_handler->expectArgumentsAt(0, "acceptStartToken", array("<a", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptStartToken", array("href", "*"));
- $this->_handler->expectArgumentsAt(2, "acceptStartToken", array(">", "*"));
- $this->_handler->expectCallCount("acceptStartToken", 3);
- $this->_handler->expectArgumentsAt(0, "acceptAttributeToken", array("= '", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptAttributeToken", array("here&there.html", "*"));
- $this->_handler->expectArgumentsAt(2, "acceptAttributeToken", array("'", "*"));
- $this->_handler->expectCallCount("acceptAttributeToken", 3);
- $this->assertTrue($this->_lexer->parse("<html><a href = 'here&there.html'>label</a></html>"));
- }
-
- function testEmptyLinkWithId() {
- $this->_handler->expectArgumentsAt(0, "acceptTextToken", array("<html>", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptTextToken", array("label", "*"));
- $this->_handler->expectArgumentsAt(2, "acceptTextToken", array("</html>", "*"));
- $this->_handler->expectCallCount("acceptTextToken", 3);
- $this->_handler->expectArgumentsAt(0, "acceptStartToken", array("<a", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptStartToken", array("id", "*"));
- $this->_handler->expectArgumentsAt(2, "acceptStartToken", array(">", "*"));
- $this->_handler->expectCallCount("acceptStartToken", 3);
- $this->_handler->expectArgumentsAt(0, "acceptAttributeToken", array("=\"", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptAttributeToken", array("0", "*"));
- $this->_handler->expectArgumentsAt(2, "acceptAttributeToken", array("\"", "*"));
- $this->_handler->expectCallCount("acceptAttributeToken", 3);
- $this->assertTrue($this->_lexer->parse("<html><a id=\"0\">label</a></html>"));
- }
-
- function testComplexLink() {
- $this->_handler->expectArgumentsAt(0, "acceptStartToken", array("<a", LEXER_ENTER));
- $this->_handler->expectArgumentsAt(1, "acceptStartToken", array("HREF", "*"));
- $this->_handler->expectArgumentsAt(2, "acceptStartToken", array("bool", "*"));
- $this->_handler->expectArgumentsAt(3, "acceptStartToken", array("Style", "*"));
- $this->_handler->expectArgumentsAt(4, "acceptStartToken", array(">", LEXER_EXIT));
- $this->_handler->expectCallCount("acceptStartToken", 5);
- $this->_handler->expectArgumentsAt(0, "acceptAttributeToken", array("= '", "*"));
- $this->_handler->expectArgumentsAt(1, "acceptAttributeToken", array("here.html", LEXER_UNMATCHED));
- $this->_handler->expectArgumentsAt(2, "acceptAttributeToken", array("'", "*"));
- $this->_handler->expectArgumentsAt(3, "acceptAttributeToken", array("=\"", "*"));
- $this->_handler->expectArgumentsAt(4, "acceptAttributeToken", array("'coo", "*"));
- $this->_handler->expectArgumentsAt(5, "acceptAttributeToken", array('\"', "*"));
- $this->_handler->expectArgumentsAt(6, "acceptAttributeToken", array("l'", "*"));
- $this->_handler->expectArgumentsAt(7, "acceptAttributeToken", array("\"", "*"));
- $this->_handler->expectCallCount("acceptAttributeToken", 8);
- $this->assertTrue($this->_lexer->parse("<HTML><a HREF = 'here.html' bool Style=\"'coo\\\"l'\">label</A></Html>"));
- }
-
- function testSubmit() {
- $this->_handler->expectArgumentsAt(0, "acceptStartToken", array("<input", LEXER_ENTER));
- $this->_handler->expectArgumentsAt(1, "acceptStartToken", array("type", "*"));
- $this->_handler->expectArgumentsAt(2, "acceptStartToken", array("name", "*"));
- $this->_handler->expectArgumentsAt(3, "acceptStartToken", array("value", "*"));
- $this->_handler->expectArgumentsAt(4, "acceptStartToken", array("/", "*"));
- $this->_handler->expectArgumentsAt(5, "acceptStartToken", array(">", LEXER_EXIT));
- $this->_handler->expectCallCount("acceptStartToken", 6);
- $this->assertTrue($this->_lexer->parse('<input type="submit" name="N" value="V" />'));
- }
-
- function testFramesParsedWithoutError() {
- $this->assertTrue($this->_lexer->parse(
- '<frameset><frame src="frame.html"></frameset>'));
- $this->assertTrue($this->_lexer->parse(
- '<frameset><frame src="frame.html"><noframes>Hello</noframes></frameset>'));
- }
- }
-
- class TestOfTextExtraction extends UnitTestCase {
-
- function testSpaceNormalisation() {
- $this->assertEqual(
- SimpleSaxParser::normalise("\nOne\tTwo \nThree\t"),
- 'One Two Three');
- }
-
- function testTagSuppression() {
- $this->assertEqual(
- SimpleSaxParser::normalise('<b>Hello</b>'),
- 'Hello');
- }
-
- function testAdjoiningTagSuppression() {
- $this->assertEqual(
- SimpleSaxParser::normalise('<b>Hello</b><em>Goodbye</em>'),
- 'HelloGoodbye');
- }
-
- function testExtractImageAltTextWithDifferentQuotes() {
- $this->assertEqual(
- SimpleSaxParser::normalise('<img alt="One"><img alt=\'Two\'><img alt=Three>'),
- 'One Two Three');
- }
-
- function testExtractImageAltTextMultipleTimes() {
- $this->assertEqual(
- SimpleSaxParser::normalise('<img alt="One"><img alt="Two"><img alt="Three">'),
- 'One Two Three');
- }
-
- function testHtmlEntityTranslation() {
- $this->assertEqual(
- SimpleSaxParser::normalise('<>"&'),
- '<>"&');
- }
- }
-
- class TestSimpleSaxParser extends SimpleSaxParser {
- var $_lexer;
-
- function TestSimpleSaxParser(&$listener, &$lexer) {
- $this->_lexer = &$lexer;
- $this->SimpleSaxParser($listener);
- }
-
- function &createLexer() {
- return $this->_lexer;
- }
- }
-
- Mock::generate("SimpleSaxListener");
- Mock::generate("SimpleLexer");
-
- class TestOfSaxGeneration extends UnitTestCase {
- var $_listener;
- var $_lexer;
-
- function setUp() {
- $this->_listener = &new MockSimpleSaxListener($this);
- $this->_lexer = &new MockSimpleLexer($this);
- $this->_parser = &new TestSimpleSaxParser($this->_listener, $this->_lexer);
- }
-
- function tearDown() {
- $this->_listener->tally();
- $this->_lexer->tally();
- }
-
- function testLexerFailure() {
- $this->_lexer->setReturnValue("parse", false);
- $this->assertFalse($this->_parser->parse("<html></html>"));
- }
-
- function testLexerSuccess() {
- $this->_lexer->setReturnValue("parse", true);
- $this->assertTrue($this->_parser->parse("<html></html>"));
- }
-
- function testSimpleLinkStart() {
- $this->_parser->parse("");
- $this->_listener->expectOnce("startElement", array("a", array()));
- $this->_listener->setReturnValue("startElement", true);
- $this->assertTrue($this->_parser->acceptStartToken("<a", LEXER_ENTER));
- $this->assertTrue($this->_parser->acceptStartToken(">", LEXER_EXIT));
- }
-
- function testSimpleTitleStart() {
- $this->_parser->parse("");
- $this->_listener->expectOnce("startElement", array("title", array()));
- $this->_listener->setReturnValue("startElement", true);
- $this->assertTrue($this->_parser->acceptStartToken("<title", LEXER_ENTER));
- $this->assertTrue($this->_parser->acceptStartToken(">", LEXER_EXIT));
- }
-
- function testLinkStart() {
- $this->_parser->parse("");
- $this->_listener->expectOnce("startElement", array("a", array("href" => "here.html")));
- $this->_listener->setReturnValue("startElement", true);
- $this->assertTrue($this->_parser->acceptStartToken("<a", LEXER_ENTER));
- $this->assertTrue($this->_parser->acceptStartToken("href", LEXER_MATCHED));
- $this->assertTrue($this->_parser->acceptAttributeToken("=\"", LEXER_ENTER));
- $this->assertTrue($this->_parser->acceptAttributeToken("here.html", LEXER_UNMATCHED));
- $this->assertTrue($this->_parser->acceptAttributeToken("\"", LEXER_EXIT));
- $this->assertTrue($this->_parser->acceptStartToken(">", LEXER_EXIT));
- }
-
- function testLinkStartWithEncodedUrl() {
- $this->_parser->parse("");
- $this->_listener->expectOnce(
- "startElement",
- array("a", array("href" => "here&there.html")));
- $this->_listener->setReturnValue("startElement", true);
- $this->assertTrue($this->_parser->acceptStartToken("<a", LEXER_ENTER));
- $this->assertTrue($this->_parser->acceptStartToken("href", LEXER_MATCHED));
- $this->assertTrue($this->_parser->acceptAttributeToken("=\"", LEXER_ENTER));
- $this->assertTrue($this->_parser->acceptAttributeToken("here&there.html", LEXER_UNMATCHED));
- $this->assertTrue($this->_parser->acceptAttributeToken("\"", LEXER_EXIT));
- $this->assertTrue($this->_parser->acceptStartToken(">", LEXER_EXIT));
- }
-
- function testLinkStartWithId() {
- $this->_parser->parse("");
- $this->_listener->expectOnce(
- "startElement",
- array("a", array("id" => "0")));
- $this->_listener->setReturnValue("startElement", true);
- $this->assertTrue($this->_parser->acceptStartToken("<a", LEXER_ENTER));
- $this->assertTrue($this->_parser->acceptStartToken("id", LEXER_MATCHED));
- $this->assertTrue($this->_parser->acceptAttributeToken("= \"", LEXER_ENTER));
- $this->assertTrue($this->_parser->acceptAttributeToken("0", LEXER_UNMATCHED));
- $this->assertTrue($this->_parser->acceptAttributeToken("\"", LEXER_EXIT));
- $this->assertTrue($this->_parser->acceptStartToken(">", LEXER_EXIT));
- }
-
- function testLinkEnd() {
- $this->_parser->parse("");
- $this->_listener->expectOnce("endElement", array("a"));
- $this->_listener->setReturnValue("endElement", true);
- $this->assertTrue($this->_parser->acceptEndToken("</a>", LEXER_SPECIAL));
- }
-
- function testInput() {
- $this->_parser->parse("");
- $this->_listener->expectOnce(
- "startElement",
- array("input", array("name" => "a")));
- $this->_listener->setReturnValue("startElement", true);
- $this->assertTrue($this->_parser->acceptStartToken("<input", LEXER_ENTER));
- $this->assertTrue($this->_parser->acceptStartToken("name", LEXER_MATCHED));
- $this->assertTrue($this->_parser->acceptAttributeToken("= a", LEXER_SPECIAL));
- $this->assertTrue($this->_parser->acceptStartToken(">", LEXER_EXIT));
- }
-
- function testButton() {
- $this->_parser->parse("");
- $this->_listener->expectOnce(
- "startElement",
- array("button", array("name" => "a")));
- $this->_listener->setReturnValue("startElement", true);
- $this->assertTrue($this->_parser->acceptStartToken("<button", LEXER_ENTER));
- $this->assertTrue($this->_parser->acceptStartToken("name", LEXER_MATCHED));
- $this->assertTrue($this->_parser->acceptAttributeToken("= a", LEXER_SPECIAL));
- $this->assertTrue($this->_parser->acceptStartToken(">", LEXER_EXIT));
- }
-
- function testContent() {
- $this->_parser->parse("");
- $this->_listener->expectOnce("addContent", array("stuff"));
- $this->_listener->setReturnValue("addContent", true);
- $this->assertTrue($this->_parser->acceptTextToken("stuff", LEXER_UNMATCHED));
- }
-
- function testIgnore() {
- $this->_parser->parse("");
- $this->_listener->expectNever("addContent");
- $this->assertTrue($this->_parser->ignore("stuff", LEXER_UNMATCHED));
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/real_sites_test.php b/tests/UnitTests/simpletest/test/real_sites_test.php deleted file mode 100644 index 728b6e95..00000000 --- a/tests/UnitTests/simpletest/test/real_sites_test.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php
- // $Id: real_sites_test.php,v 1.16 2004/12/05 21:12:33 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../web_tester.php');
-
- class LiveSitesTestCase extends WebTestCase {
-
- function testLastCraft() {
- $this->assertTrue($this->get('http://www.lastcraft.com'));
- $this->assertResponse(array(200));
- $this->assertMime(array('text/html'));
- $this->clickLink('About');
- $this->assertTitle('About Last Craft');
- }
-
- function testSourceforge() {
- $this->assertTrue($this->get('http://sourceforge.net/'));
- $this->setField('words', 'simpletest');
- $this->assertTrue($this->clickImageByName('imageField'));
- $this->assertTitle('SourceForge.net: Search');
- $this->assertTrue($this->clickLink('SimpleTest'));
- $this->clickLink('statistics');
- $this->assertWantedPattern('/Statistics for the past 7 days/');
- $this->assertTrue($this->setField('report', 'Monthly'));
- $this->clickSubmit('Change Stats View');
- $this->assertWantedPattern('/Statistics for the past \d+ months/');
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/remote_test.php b/tests/UnitTests/simpletest/test/remote_test.php deleted file mode 100644 index 16c82b31..00000000 --- a/tests/UnitTests/simpletest/test/remote_test.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php
- // $Id: remote_test.php,v 1.4 2004/04/07 19:12:13 lastcraft Exp $
- require_once('../remote.php');
- require_once('../reporter.php');
-
- // The following URL will depend on your own installation.
- $base_url = 'http://uno/simple/';
-
- $test = &new GroupTest('Remote tests');
- $test->addTestCase(new RemoteTestCase(
- $base_url . 'test/visual_test.php?xml=yes',
- $base_url . 'test/visual_test.php?xml=yes&dry=yes'));
- if (SimpleReporter::inCli()) {
- exit ($test->run(new XmlReporter()) ? 0 : 1);
- }
- $test->run(new HtmlReporter());
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/shell_test.php b/tests/UnitTests/simpletest/test/shell_test.php deleted file mode 100644 index 7d5631a3..00000000 --- a/tests/UnitTests/simpletest/test/shell_test.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php
- // $Id: shell_test.php,v 1.8 2004/09/24 22:55:18 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../shell_tester.php');
-
- class TestOfShell extends UnitTestCase {
-
- function testEcho() {
- $shell = &new SimpleShell();
- $this->assertIdentical($shell->execute('echo Hello'), 0);
- $this->assertWantedPattern('/Hello/', $shell->getOutput());
- }
-
- function testBadCommand() {
- $shell = &new SimpleShell();
- $this->assertNotEqual($ret = $shell->execute('blurgh! 2>&1'), 0);
- }
- }
-
- class TestOfShellTesterAndShell extends ShellTestCase {
-
- function testEcho() {
- $this->assertTrue($this->execute('echo Hello'));
- $this->assertExitCode(0);
- $this->assertoutput('Hello');
- }
-
- function testFileExistence() {
- $this->assertFileExists(dirname(__FILE__) . '/all_tests.php');
- $this->assertFileNotExists('wibble');
- }
-
- function testFilePatterns() {
- $this->assertFilePattern('/all_tests/i', dirname(__FILE__) . '/all_tests.php');
- $this->assertNoFilePattern('/sputnik/i', dirname(__FILE__) . '/all_tests.php');
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/shell_tester_test.php b/tests/UnitTests/simpletest/test/shell_tester_test.php deleted file mode 100644 index 5a8f5b59..00000000 --- a/tests/UnitTests/simpletest/test/shell_tester_test.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php
- // $Id: shell_tester_test.php,v 1.5 2004/09/24 22:55:18 lastcraft Exp $
-
- Mock::generate('SimpleShell');
-
- class TestOfShellTestCase extends ShellTestCase {
- var $_mock_shell = false;
-
- function &_getShell() {
- return $this->_mock_shell;
- }
-
- function testExitCode() {
- $this->_mock_shell = &new MockSimpleShell($this);
- $this->_mock_shell->setReturnValue('execute', 0);
- $this->_mock_shell->expectOnce('execute', array('ls'));
- $this->assertTrue($this->execute('ls'));
- $this->assertExitCode(0);
- $this->_mock_shell->tally();
- }
-
- function testOutput() {
- $this->_mock_shell = &new MockSimpleShell($this);
- $this->_mock_shell->setReturnValue('execute', 0);
- $this->_mock_shell->setReturnValue('getOutput', "Line 1\nLine 2\n");
- $this->assertOutput("Line 1\nLine 2\n");
- }
-
- function testOutputPatterns() {
- $this->_mock_shell = &new MockSimpleShell($this);
- $this->_mock_shell->setReturnValue('execute', 0);
- $this->_mock_shell->setReturnValue('getOutput', "Line 1\nLine 2\n");
- $this->assertOutputPattern('/line/i');
- $this->assertNoOutputPattern('/line 2/');
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/simple_mock_test.php b/tests/UnitTests/simpletest/test/simple_mock_test.php deleted file mode 100644 index cdae14f9..00000000 --- a/tests/UnitTests/simpletest/test/simple_mock_test.php +++ /dev/null @@ -1,648 +0,0 @@ -<?php
- // $Id: simple_mock_test.php,v 1.41 2005/01/23 22:20:52 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../expectation.php');
-
- class TestOfWildcardExpectation extends UnitTestCase {
-
- function testSimpleInteger() {
- $expectation = new WildcardExpectation();
- $this->assertTrue($expectation->test(33));
- $this->assertWantedPattern(
- '/matches.*33/i',
- $expectation->testMessage(33));
- }
- }
-
- class TestOfParametersExpectation extends UnitTestCase {
-
- function testEmptyMatch() {
- $expectation = new ParametersExpectation(array());
- $this->assertTrue($expectation->test(array()));
- $this->assertFalse($expectation->test(array(33)));
- }
-
- function testSingleMatch() {
- $expectation = new ParametersExpectation(array(0));
- $this->assertFalse($expectation->test(array(1)));
- $this->assertTrue($expectation->test(array(0)));
- }
-
- function testAnyMatch() {
- $expectation = new ParametersExpectation(false);
- $this->assertTrue($expectation->test(array()));
- $this->assertTrue($expectation->test(array(1, 2)));
- }
-
- function testMissingParameter() {
- $expectation = new ParametersExpectation(array(0));
- $this->assertFalse($expectation->test(array()));
- }
-
- function testNullParameter() {
- $expectation = new ParametersExpectation(array(null));
- $this->assertTrue($expectation->test(array(null)));
- $this->assertFalse($expectation->test(array()));
- }
-
- function testWildcardExpectations() {
- $expectation = new ParametersExpectation(array(new WildcardExpectation()));
- $this->assertFalse($expectation->test(array()));
- $this->assertIdentical($expectation->test(array(null)), true);
- $this->assertIdentical($expectation->test(array(13)), true);
- }
-
- function testOtherExpectations() {
- $expectation = new ParametersExpectation(
- array(new WantedPatternExpectation('/hello/i')));
- $this->assertFalse($expectation->test(array('Goodbye')));
- $this->assertTrue($expectation->test(array('hello')));
- $this->assertTrue($expectation->test(array('Hello')));
- }
-
- function testIdentityOnly() {
- $expectation = new ParametersExpectation(array("0"));
- $this->assertFalse($expectation->test(array(0)));
- $this->assertTrue($expectation->test(array("0")));
- }
-
- function testLongList() {
- $expectation = new ParametersExpectation(
- array("0", 0, new WildcardExpectation(), false));
- $this->assertTrue($expectation->test(array("0", 0, 37, false)));
- $this->assertFalse($expectation->test(array("0", 0, 37, true)));
- $this->assertFalse($expectation->test(array("0", 0, 37)));
- }
- }
-
- class TestOfCallMap extends UnitTestCase {
-
- function testEmpty() {
- $map = new CallMap();
- $this->assertFalse($map->isMatch("any", array()));
- $this->assertNull($map->findFirstMatch("any", array()));
- }
-
- function testExactValue() {
- $map = new CallMap();
- $map->addValue(array(0), "Fred");
- $map->addValue(array(1), "Jim");
- $map->addValue(array("1"), "Tom");
- $this->assertTrue($map->isMatch(array(0)));
- $this->assertEqual($map->findFirstMatch(array(0)), "Fred");
- $this->assertTrue($map->isMatch(array(1)));
- $this->assertEqual($map->findFirstMatch(array(1)), "Jim");
- $this->assertEqual($map->findFirstMatch(array("1")), "Tom");
- }
-
- function testExactReference() {
- $map = new CallMap();
- $ref = "Fred";
- $map->addReference(array(0), $ref);
- $this->assertEqual($map->findFirstMatch(array(0)), "Fred");
- $ref2 = &$map->findFirstMatch(array(0));
- $this->assertReference($ref2, $ref);
- }
-
- function testWildcard() {
- $map = new CallMap();
- $map->addValue(array(new WildcardExpectation(), 1, 3), "Fred");
- $this->assertTrue($map->isMatch(array(2, 1, 3)));
- $this->assertEqual($map->findFirstMatch(array(2, 1, 3)), "Fred");
- }
-
- function testAllWildcard() {
- $map = new CallMap();
- $this->assertFalse($map->isMatch(array(2, 1, 3)));
- $map->addValue("", "Fred");
- $this->assertTrue($map->isMatch(array(2, 1, 3)));
- $this->assertEqual($map->findFirstMatch(array(2, 1, 3)), "Fred");
- }
-
- function testOrdering() {
- $map = new CallMap();
- $map->addValue(array(1, 2), "1, 2");
- $map->addValue(array(1, 3), "1, 3");
- $map->addValue(array(1), "1");
- $map->addValue(array(1, 4), "1, 4");
- $map->addValue(array(new WildcardExpectation()), "Any");
- $map->addValue(array(2), "2");
- $map->addValue("", "Default");
- $map->addValue(array(), "None");
- $this->assertEqual($map->findFirstMatch(array(1, 2)), "1, 2");
- $this->assertEqual($map->findFirstMatch(array(1, 3)), "1, 3");
- $this->assertEqual($map->findFirstMatch(array(1, 4)), "1, 4");
- $this->assertEqual($map->findFirstMatch(array(1)), "1");
- $this->assertEqual($map->findFirstMatch(array(2)), "Any");
- $this->assertEqual($map->findFirstMatch(array(3)), "Any");
- $this->assertEqual($map->findFirstMatch(array()), "Default");
- }
- }
-
- class Dummy {
- function Dummy() {
- }
-
- function aMethod($parameter) {
- return $parameter;
- }
-
- function anotherMethod() {
- return true;
- }
- }
-
- Stub::generate("Dummy");
- Stub::generate("Dummy", "AnotherStubDummy");
- Stub::generate("Dummy", "StubDummyWithExtraMethods", array("extraMethod"));
-
- class SpecialSimpleStub extends SimpleStub {
- function SpecialSimpleStub($wildcard) {
- $this->SimpleStub($wildcard);
- }
- }
- SimpleTestOptions::setStubBaseClass("SpecialSimpleStub");
- Stub::generate("Dummy", "SpecialStubDummy");
- SimpleTestOptions::setStubBaseClass("SimpleStub");
-
- class TestOfStubGeneration extends UnitTestCase {
-
- function testCloning() {
- $stub = &new StubDummy();
- $this->assertTrue(method_exists($stub, "aMethod"));
- $this->assertNull($stub->aMethod());
- }
-
- function testCloningWithExtraMethod() {
- $stub = &new StubDummyWithExtraMethods();
- $this->assertTrue(method_exists($stub, "extraMethod"));
- }
-
- function testCloningWithChosenClassName() {
- $stub = &new AnotherStubDummy();
- $this->assertTrue(method_exists($stub, "aMethod"));
- }
-
- function testCloningWithDifferentBaseClass() {
- $stub = &new SpecialStubDummy();
- $this->assertIsA($stub, "SpecialSimpleStub");
- $this->assertTrue(method_exists($stub, "aMethod"));
- }
- }
-
- class TestOfServerStubReturns extends UnitTestCase {
-
- function testDefaultReturn() {
- $stub = &new StubDummy();
- $stub->setReturnValue("aMethod", "aaa");
- $this->assertIdentical($stub->aMethod(), "aaa");
- $this->assertIdentical($stub->aMethod(), "aaa");
- }
-
- function testParameteredReturn() {
- $stub = &new StubDummy();
- $stub->setReturnValue("aMethod", "aaa", array(1, 2, 3));
- $this->assertNull($stub->aMethod());
- $this->assertIdentical($stub->aMethod(1, 2, 3), "aaa");
- }
-
- function testReferenceReturned() {
- $stub = &new StubDummy();
- $object = new Dummy();
- $stub->setReturnReference("aMethod", $object, array(1, 2, 3));
- $this->assertReference($stub->aMethod(1, 2, 3), $object);
- }
-
- function testWildcardReturn() {
- $stub = &new StubDummy("wild");
- $stub->setReturnValue("aMethod", "aaa", array(1, "wild", 3));
- $this->assertIdentical($stub->aMethod(1, "something", 3), "aaa");
- $this->assertIdentical($stub->aMethod(1, "anything", 3), "aaa");
- }
-
- function testAllWildcardReturn() {
- $stub = &new StubDummy("wild");
- $stub->setReturnValue("aMethod", "aaa");
- $this->assertIdentical($stub->aMethod(1, 2, 3), "aaa");
- $this->assertIdentical($stub->aMethod(), "aaa");
- }
-
- function testCallCount() {
- $stub = &new StubDummy();
- $this->assertEqual($stub->getCallCount("aMethod"), 0);
- $stub->aMethod();
- $this->assertEqual($stub->getCallCount("aMethod"), 1);
- $stub->aMethod();
- $this->assertEqual($stub->getCallCount("aMethod"), 2);
- }
-
- function testMultipleMethods() {
- $stub = &new StubDummy();
- $stub->setReturnValue("aMethod", 100, array(1));
- $stub->setReturnValue("aMethod", 200, array(2));
- $stub->setReturnValue("anotherMethod", 10, array(1));
- $stub->setReturnValue("anotherMethod", 20, array(2));
- $this->assertIdentical($stub->aMethod(1), 100);
- $this->assertIdentical($stub->anotherMethod(1), 10);
- $this->assertIdentical($stub->aMethod(2), 200);
- $this->assertIdentical($stub->anotherMethod(2), 20);
- }
-
- function testReturnSequence() {
- $stub = &new StubDummy();
- $stub->setReturnValueAt(0, "aMethod", "aaa");
- $stub->setReturnValueAt(1, "aMethod", "bbb");
- $stub->setReturnValueAt(3, "aMethod", "ddd");
- $this->assertIdentical($stub->aMethod(), "aaa");
- $this->assertIdentical($stub->aMethod(), "bbb");
- $this->assertNull($stub->aMethod());
- $this->assertIdentical($stub->aMethod(), "ddd");
- }
-
- function testReturnReferenceSequence() {
- $stub = &new StubDummy();
- $object = new Dummy();
- $stub->setReturnReferenceAt(1, "aMethod", $object);
- $this->assertNull($stub->aMethod());
- $this->assertReference($stub->aMethod(), $object);
- $this->assertNull($stub->aMethod());
- }
-
- function testComplicatedReturnSequence() {
- $stub = &new StubDummy("wild");
- $object = new Dummy();
- $stub->setReturnValueAt(1, "aMethod", "aaa", array("a"));
- $stub->setReturnValueAt(1, "aMethod", "bbb");
- $stub->setReturnReferenceAt(2, "aMethod", $object, array("wild", 2));
- $stub->setReturnValueAt(2, "aMethod", "value", array("wild", 3));
- $stub->setReturnValue("aMethod", 3, array(3));
- $this->assertNull($stub->aMethod());
- $this->assertEqual($stub->aMethod("a"), "aaa");
- $this->assertReference($stub->aMethod(1, 2), $object);
- $this->assertEqual($stub->aMethod(3), 3);
- $this->assertNull($stub->aMethod());
- }
-
- function testMultipleMethodSequences() {
- $stub = &new StubDummy();
- $stub->setReturnValueAt(0, "aMethod", "aaa");
- $stub->setReturnValueAt(1, "aMethod", "bbb");
- $stub->setReturnValueAt(0, "anotherMethod", "ccc");
- $stub->setReturnValueAt(1, "anotherMethod", "ddd");
- $this->assertIdentical($stub->aMethod(), "aaa");
- $this->assertIdentical($stub->anotherMethod(), "ccc");
- $this->assertIdentical($stub->aMethod(), "bbb");
- $this->assertIdentical($stub->anotherMethod(), "ddd");
- }
-
- function testSequenceFallback() {
- $stub = &new StubDummy();
- $stub->setReturnValueAt(0, "aMethod", "aaa", array('a'));
- $stub->setReturnValueAt(1, "aMethod", "bbb", array('a'));
- $stub->setReturnValue("aMethod", "AAA");
- $this->assertIdentical($stub->aMethod('a'), "aaa");
- $this->assertIdentical($stub->aMethod('b'), "AAA");
- }
-
- function testMethodInterference() {
- $stub = &new StubDummy();
- $stub->setReturnValueAt(0, "anotherMethod", "aaa");
- $stub->setReturnValue("aMethod", "AAA");
- $this->assertIdentical($stub->aMethod(), "AAA");
- $this->assertIdentical($stub->anotherMethod(), "aaa");
- }
- }
-
- Mock::generate("Dummy");
- Mock::generate("Dummy", "AnotherMockDummy");
- Mock::generate("Dummy", "MockDummyWithExtraMethods", array("extraMethod"));
-
- class SpecialSimpleMock extends SimpleMock {
- function SpecialSimpleMock(&$test, $wildcard) {
- $this->SimpleMock($test, $wildcard);
- }
- }
- SimpleTestOptions::setMockBaseClass("SpecialSimpleMock");
- Mock::generate("Dummy", "SpecialMockDummy");
- SimpleTestOptions::setMockBaseClass("SimpleMock");
-
- class TestOfMockGeneration extends UnitTestCase {
-
- function testCloning() {
- $mock = &new MockDummy($this);
- $this->assertTrue(method_exists($mock, "aMethod"));
- $this->assertNull($mock->aMethod());
- }
-
- function testCloningWithExtraMethod() {
- $mock = &new MockDummyWithExtraMethods($this);
- $this->assertTrue(method_exists($mock, "extraMethod"));
- }
-
- function testCloningWithChosenClassName() {
- $mock = &new AnotherMockDummy($this);
- $this->assertTrue(method_exists($mock, "aMethod"));
- }
-
- function testCloningWithDifferentBaseClass() {
- $mock = &new SpecialMockDummy($this);
- $this->assertIsA($mock, "SpecialSimpleMock");
- $this->assertTrue(method_exists($mock, "aMethod"));
- }
- }
-
- class TestOfMockReturns extends UnitTestCase {
-
- function testNoUnitTesterSetThrowsError() {
- $mock = &new MockDummy();
- $this->assertErrorPattern('/missing argument/i');
- $this->assertErrorPattern('/no unit tester/i');
- }
-
- function testParameteredReturn() {
- $mock = &new MockDummy($this);
- $mock->setReturnValue("aMethod", "aaa", array(1, 2, 3));
- $this->assertNull($mock->aMethod());
- $this->assertIdentical($mock->aMethod(1, 2, 3), "aaa");
- }
-
- function testReferenceReturned() {
- $mock = &new MockDummy($this);
- $object = new Dummy();
- $mock->setReturnReference("aMethod", $object, array(1, 2, 3));
- $this->assertReference($mock->aMethod(1, 2, 3), $object);
- }
-
- function testWildcardReturn() {
- $mock = &new MockDummy($this, "wild");
- $mock->setReturnValue("aMethod", "aaa", array(1, "wild", 3));
- $this->assertIdentical($mock->aMethod(1, "something", 3), "aaa");
- $this->assertIdentical($mock->aMethod(1, "anything", 3), "aaa");
- }
-
- function testPatternMatchReturn() {
- $mock = &new MockDummy($this);
- $mock->setReturnValue(
- "aMethod",
- "aaa",
- array(new wantedPatternExpectation('/hello/i')));
- $this->assertIdentical($mock->aMethod('Hello'), "aaa");
- $this->assertNull($mock->aMethod('Goodbye'));
- }
-
- function testCallCount() {
- $mock = &new MockDummy($this);
- $this->assertEqual($mock->getCallCount("aMethod"), 0);
- $mock->aMethod();
- $this->assertEqual($mock->getCallCount("aMethod"), 1);
- $mock->aMethod();
- $this->assertEqual($mock->getCallCount("aMethod"), 2);
- }
-
- function testReturnReferenceSequence() {
- $mock = &new MockDummy($this);
- $object = new Dummy();
- $mock->setReturnReferenceAt(1, "aMethod", $object);
- $this->assertNull($mock->aMethod());
- $this->assertReference($mock->aMethod(), $object);
- $this->assertNull($mock->aMethod());
- $this->swallowErrors();
- }
- }
-
- Mock::generate("SimpleTestCase");
-
- class TestOfMockTally extends UnitTestCase {
-
- function testZeroCallCount() {
- $mock = &new MockDummy($this);
- $mock->expectCallCount("aMethod", 0);
- $mock->tally();
- }
-
- function testExpectedCallCount() {
- $mock = &new MockDummy($this);
- $mock->expectCallCount("aMethod", 2);
- $mock->aMethod();
- $mock->aMethod();
- $mock->tally();
- }
- }
-
- class TestOfMockExpectations extends UnitTestCase {
- var $_test;
-
- function TestOfMockExpectations() {
- $this->UnitTestCase();
- }
-
- function setUp() {
- $this->_test = &new MockSimpleTestCase($this);
- }
-
- function tearDown() {
- $this->_test->tally();
- }
-
- function testSettingExpectationOnNonMethodThrowsError() {
- $mock = &new MockDummy($this);
- $mock->expectMaximumCallCount("aMissingMethod", 2);
- $this->assertError();
- }
-
- function testMaxCallsDetectsOverrun() {
- $this->_test->expectOnce("assertTrue", array(false, '*'));
- $mock = &new MockDummy($this->_test);
- $mock->expectMaximumCallCount("aMethod", 2);
- $mock->aMethod();
- $mock->aMethod();
- $mock->aMethod();
- }
-
- function testTallyOnMaxCallsSendsPassOnUnderrun() {
- $this->_test->expectOnce("assertTrue", array(true, '*'));
- $mock = &new MockDummy($this->_test);
- $mock->expectMaximumCallCount("aMethod", 2);
- $mock->aMethod();
- $mock->aMethod();
- $mock->tally();
- }
-
- function testExpectNeverDetectsOverrun() {
- $this->_test->expectOnce("assertTrue", array(false, '*'));
- $mock = &new MockDummy($this->_test);
- $mock->expectNever("aMethod");
- $mock->aMethod();
- }
-
- function testTallyOnExpectNeverSendsPassOnUnderrun() {
- $this->_test->expectOnce("assertTrue", array(true, '*'));
- $mock = &new MockDummy($this->_test);
- $mock->expectNever("aMethod");
- $mock->tally();
- }
-
- function testMinCalls() {
- $this->_test->expectOnce("assertTrue", array(true, '*'));
- $mock = &new MockDummy($this->_test);
- $mock->expectMinimumCallCount("aMethod", 2);
- $mock->aMethod();
- $mock->aMethod();
- $mock->tally();
- }
-
- function testFailedNever() {
- $this->_test->expectOnce("assertTrue", array(false, '*'));
- $mock = &new MockDummy($this->_test);
- $mock->expectNever("aMethod");
- $mock->aMethod();
- }
-
- function testUnderOnce() {
- $this->_test->expectOnce("assertTrue", array(false, '*'));
- $mock = &new MockDummy($this->_test);
- $mock->expectOnce("aMethod");
- $mock->tally();
- }
-
- function testOverOnce() {
- $this->_test->expectOnce("assertTrue", array(false, '*'));
- $mock = &new MockDummy($this->_test);
- $mock->expectOnce("aMethod");
- $mock->aMethod();
- $mock->aMethod();
- $mock->tally();
- $this->swallowErrors();
- }
-
- function testUnderAtLeastOnce() {
- $this->_test->expectOnce("assertTrue", array(false, '*'));
- $mock = &new MockDummy($this->_test);
- $mock->expectAtLeastOnce("aMethod");
- $mock->tally();
- }
-
- function testZeroArguments() {
- $mock = &new MockDummy($this);
- $mock->expectArguments("aMethod", array());
- $mock->aMethod();
- }
-
- function testExpectedArguments() {
- $mock = &new MockDummy($this);
- $mock->expectArguments("aMethod", array(1, 2, 3));
- $mock->aMethod(1, 2, 3);
- }
-
- function testFailedArguments() {
- $this->_test->expectOnce("assertTrue", array(false, "*"));
- $mock = &new MockDummy($this->_test);
- $mock->expectArguments("aMethod", array("this"));
- $mock->aMethod("that");
- }
-
- function testWildcardArguments() {
- $mock = &new MockDummy($this, "wild");
- $mock->expectArguments("aMethod", array("wild", 123, "wild"));
- $mock->aMethod(100, 123, 101);
- }
-
- function testSpecificSequence() {
- $mock = &new MockDummy($this);
- $mock->expectArgumentsAt(1, "aMethod", array(1, 2, 3));
- $mock->expectArgumentsAt(2, "aMethod", array("Hello"));
- $mock->aMethod();
- $mock->aMethod(1, 2, 3);
- $mock->aMethod("Hello");
- $mock->aMethod();
- }
-
- function testFailedSequence() {
- $this->_test->expectArguments("assertTrue", array(false, "*"));
- $this->_test->expectCallCount("assertTrue", 2);
- $mock = &new MockDummy($this->_test);
- $mock->expectArgumentsAt(0, "aMethod", array(1, 2, 3));
- $mock->expectArgumentsAt(1, "aMethod", array("Hello"));
- $mock->aMethod(1, 2);
- $mock->aMethod("Goodbye");
- }
-
- function testBadArgParameter() {
- $mock = &new MockDummy($this);
- $mock->expectArguments("aMethod", "foo");
- $this->assertErrorPattern('/\$args.*not an array/i');
- $mock->aMethod();
- $mock->tally();
- }
- }
-
- class TestOfMockComparisons extends UnitTestCase {
-
- function testTestCaseRegistry() {
- $test = &new MockSimpleTestCase($this);
- $class = SimpleMock::registerTest($test);
- $this->assertReference($test, SimpleMock::injectTest($class));
- }
-
- function testEqualComparisonOfMocksDoesNotCrash() {
- $expectation = &new EqualExpectation(new MockDummy($this));
- $this->assertTrue($expectation->test(new MockDummy($this), true));
- }
-
- function testIdenticalComparisonOfMocksDoesNotCrash() {
- $expectation = &new IdenticalExpectation(new MockDummy($this));
- $this->assertTrue($expectation->test(new MockDummy($this)));
- }
- }
-
- SimpleTestOptions::addPartialMockCode('function sayHello() { return "Hello"; }');
- Mock::generatePartial("Dummy", "TestDummy", array("anotherMethod"));
- SimpleTestOptions::addPartialMockCode();
-
- class TestOfPartialMocks extends UnitTestCase {
-
- function testMethodReplacement() {
- $mock = &new TestDummy($this);
- $this->assertEqual($mock->aMethod(99), 99);
- $this->assertNull($mock->anotherMethod());
- }
-
- function testSettingReturns() {
- $mock = &new TestDummy($this);
- $mock->setReturnValue("anotherMethod", 33, array(3));
- $mock->setReturnValue("anotherMethod", 22);
- $mock->setReturnValueAt(2, "anotherMethod", 44, array(3));
- $this->assertEqual($mock->anotherMethod(), 22);
- $this->assertEqual($mock->anotherMethod(3), 33);
- $this->assertEqual($mock->anotherMethod(3), 44);
- }
-
- function testReferences() {
- $mock = &new TestDummy($this);
- $object = new Dummy();
- $mock->setReturnReferenceAt(0, "anotherMethod", $object, array(3));
- $this->assertReference($mock->anotherMethod(3), $object);
- }
-
- function testExpectations() {
- $mock = &new TestDummy($this);
- $mock->expectCallCount("anotherMethod", 2);
- $mock->expectArguments("anotherMethod", array(77));
- $mock->expectArgumentsAt(1, "anotherMethod", array(66));
- $mock->anotherMethod(77);
- $mock->anotherMethod(66);
- $mock->tally();
- }
-
- function testAdditionalPartialMockCode() {
- $dummy = &new TestDummy($this);
- $this->assertEqual($dummy->sayHello(), 'Hello');
- }
-
- function testSettingExpectationOnMissingMethodThrowsError() {
- $mock = &new TestDummy($this);
- $mock->expectCallCount("aMissingMethod", 2);
- $this->assertError();
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/socket_test.php b/tests/UnitTests/simpletest/test/socket_test.php deleted file mode 100644 index a872d8c5..00000000 --- a/tests/UnitTests/simpletest/test/socket_test.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php
- // $Id: socket_test.php,v 1.16 2004/09/24 22:55:19 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../socket.php');
-
- Mock::generate('SimpleSocket');
-
- class TestOfSimpleStickyError extends UnitTestCase {
-
- function testSettingError() {
- $error = new SimpleStickyError();
- $this->assertFalse($error->isError());
- $error->_setError('Ouch');
- $this->assertTrue($error->isError());
- $this->assertEqual($error->getError(), 'Ouch');
- }
-
- function testClearingError() {
- $error = new SimpleStickyError();
- $error->_setError('Ouch');
- $this->assertTrue($error->isError());
- $error->_clearError();
- $this->assertFalse($error->isError());
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/tag_test.php b/tests/UnitTests/simpletest/test/tag_test.php deleted file mode 100644 index c18788af..00000000 --- a/tests/UnitTests/simpletest/test/tag_test.php +++ /dev/null @@ -1,462 +0,0 @@ -<?php
- // $Id: tag_test.php,v 1.52 2005/02/10 02:44:24 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../tag.php');
-
- class TestOfTag extends UnitTestCase {
-
- function testStartValuesWithoutAdditionalContent() {
- $tag = new SimpleTitleTag(array('a' => '1', 'b' => ''));
- $this->assertEqual($tag->getTagName(), 'title');
- $this->assertIdentical($tag->getAttribute('a'), '1');
- $this->assertIdentical($tag->getAttribute('b'), true);
- $this->assertIdentical($tag->getAttribute('c'), false);
- $this->assertIdentical($tag->getContent(), '');
- }
-
- function testTitleContent() {
- $tag = &new SimpleTitleTag(array());
- $this->assertTrue($tag->expectEndTag());
- $tag->addContent('Hello');
- $tag->addContent('World');
- $this->assertEqual($tag->getText(), 'HelloWorld');
- }
-
- function testMessyTitleContent() {
- $tag = &new SimpleTitleTag(array());
- $this->assertTrue($tag->expectEndTag());
- $tag->addContent('<b>Hello</b>');
- $tag->addContent('<em>World</em>');
- $this->assertEqual($tag->getText(), 'HelloWorld');
- }
-
- function testTagWithNoEnd() {
- $tag = &new SimpleTextTag(array());
- $this->assertFalse($tag->expectEndTag());
- }
-
- function testAnchorHref() {
- $tag = &new SimpleAnchorTag(array('href' => 'http://here/'));
- $this->assertEqual($tag->getHref(), 'http://here/');
-
- $tag = &new SimpleAnchorTag(array('href' => ''));
- $this->assertIdentical($tag->getAttribute('href'), true);
- $this->assertIdentical($tag->getHref(), '');
-
- $tag = &new SimpleAnchorTag(array());
- $this->assertIdentical($tag->getAttribute('href'), false);
- $this->assertIdentical($tag->getHref(), '');
- }
-
- function testIsIdMatchesIdAttribute() {
- $tag = &new SimpleAnchorTag(array('href' => 'http://here/', 'id' => 7));
- $this->assertIdentical($tag->getAttribute('id'), '7');
- $this->assertTrue($tag->isId(7));
- }
- }
-
- class TestOfWidget extends UnitTestCase {
-
- function testTextEmptyDefault() {
- $tag = &new SimpleTextTag(array('' => 'text'));
- $this->assertIdentical($tag->getDefault(), '');
- $this->assertIdentical($tag->getValue(), '');
- }
-
- function testTextDefault() {
- $tag = &new SimpleTextTag(array('value' => 'aaa'));
- $this->assertEqual($tag->getDefault(), 'aaa');
- $this->assertEqual($tag->getValue(), 'aaa');
- }
-
- function testSettingTextValue() {
- $tag = &new SimpleTextTag(array('value' => 'aaa'));
- $tag->setValue('bbb');
- $this->assertEqual($tag->getValue(), 'bbb');
- $tag->resetValue();
- $this->assertEqual($tag->getValue(), 'aaa');
- }
-
- function testFailToSetHiddenValue() {
- $tag = &new SimpleTextTag(array('value' => 'aaa', 'type' => 'hidden'));
- $this->assertFalse($tag->setValue('bbb'));
- $this->assertEqual($tag->getValue(), 'aaa');
- }
-
- function testSubmitDefaults() {
- $tag = &new SimpleSubmitTag(array('type' => 'submit'));
- $this->assertEqual($tag->getName(), 'submit');
- $this->assertEqual($tag->getValue(), 'Submit');
- $this->assertFalse($tag->setValue('Cannot set this'));
- $this->assertEqual($tag->getValue(), 'Submit');
- $this->assertEqual($tag->getLabel(), 'Submit');
- $this->assertEqual($tag->getSubmitValues(), array('submit' => 'Submit'));
- }
-
- function testPopulatedSubmit() {
- $tag = &new SimpleSubmitTag(
- array('type' => 'submit', 'name' => 's', 'value' => 'Ok!'));
- $this->assertEqual($tag->getName(), 's');
- $this->assertEqual($tag->getValue(), 'Ok!');
- $this->assertEqual($tag->getLabel(), 'Ok!');
- $this->assertEqual($tag->getSubmitValues(), array('s' => 'Ok!'));
- }
-
- function testImageSubmit() {
- $tag = &new SimpleImageSubmitTag(
- array('type' => 'image', 'name' => 's', 'alt' => 'Label'));
- $this->assertEqual($tag->getName(), 's');
- $this->assertEqual($tag->getLabel(), 'Label');
- $this->assertEqual(
- $tag->getSubmitValues(20, 30),
- array('s.x' => 20, 's.y' => 30));
- }
-
- function testImageSubmitTitlePreferredOverAltForLabel() {
- $tag = &new SimpleImageSubmitTag(
- array('type' => 'image', 'name' => 's', 'alt' => 'Label', 'title' => 'Title'));
- $this->assertEqual($tag->getLabel(), 'Title');
- }
-
- function testButton() {
- $tag = &new SimpleButtonTag(
- array('type' => 'submit', 'name' => 's', 'value' => 'do'));
- $tag->addContent('I am a button');
- $this->assertEqual($tag->getName(), 's');
- $this->assertEqual($tag->getValue(), 'do');
- $this->assertEqual($tag->getLabel(), 'I am a button');
- $this->assertEqual($tag->getSubmitValues(), array('s' => 'do'));
- }
- }
-
- class TestOfTextArea extends UnitTestCase {
-
- function testDefault() {
- $tag = &new SimpleTextAreaTag(array('name' => 'a'));
- $tag->addContent('Some text');
- $this->assertEqual($tag->getName(), 'a');
- $this->assertEqual($tag->getDefault(), 'Some text');
- }
-
- function testWrapping() {
- $tag = &new SimpleTextAreaTag(array('cols' => '10', 'wrap' => 'physical'));
- $tag->addContent("Lot's of text that should be wrapped");
- $this->assertEqual(
- $tag->getDefault(),
- "Lot's of\ntext that\nshould be\nwrapped");
- $tag->setValue("New long text\nwith two lines");
- $this->assertEqual(
- $tag->getValue(),
- "New long\ntext\nwith two\nlines");
- }
- }
-
- class TestOfSelection extends UnitTestCase {
-
- function testEmpty() {
- $tag = &new SimpleSelectionTag(array('name' => 'a'));
- $this->assertIdentical($tag->getValue(), '');
- }
-
- function testSingle() {
- $tag = &new SimpleSelectionTag(array('name' => 'a'));
- $option = &new SimpleOptionTag(array());
- $option->addContent('AAA');
- $tag->addTag($option);
- $this->assertEqual($tag->getValue(), 'AAA');
- }
-
- function testSingleDefault() {
- $tag = &new SimpleSelectionTag(array('name' => 'a'));
- $option = &new SimpleOptionTag(array('selected' => ''));
- $option->addContent('AAA');
- $tag->addTag($option);
- $this->assertEqual($tag->getValue(), 'AAA');
- }
-
- function testSingleMappedDefault() {
- $tag = &new SimpleSelectionTag(array('name' => 'a'));
- $option = &new SimpleOptionTag(array('selected' => '', 'value' => 'aaa'));
- $option->addContent('AAA');
- $tag->addTag($option);
- $this->assertEqual($tag->getValue(), 'aaa');
- }
-
- function testStartsWithDefault() {
- $tag = &new SimpleSelectionTag(array('name' => 'a'));
- $a = &new SimpleOptionTag(array());
- $a->addContent('AAA');
- $tag->addTag($a);
- $b = &new SimpleOptionTag(array('selected' => ''));
- $b->addContent('BBB');
- $tag->addTag($b);
- $c = &new SimpleOptionTag(array());
- $c->addContent('CCC');
- $tag->addTag($c);
- $this->assertEqual($tag->getValue(), 'BBB');
- }
-
- function testSettingOption() {
- $tag = &new SimpleSelectionTag(array('name' => 'a'));
- $a = &new SimpleOptionTag(array());
- $a->addContent('AAA');
- $tag->addTag($a);
- $b = &new SimpleOptionTag(array('selected' => ''));
- $b->addContent('BBB');
- $tag->addTag($b);
- $c = &new SimpleOptionTag(array());
- $c->addContent('CCC');
- $tag->setValue('AAA');
- $this->assertEqual($tag->getValue(), 'AAA');
- }
-
- function testSettingMappedOption() {
- $tag = &new SimpleSelectionTag(array('name' => 'a'));
- $a = &new SimpleOptionTag(array('value' => 'aaa'));
- $a->addContent('AAA');
- $tag->addTag($a);
- $b = &new SimpleOptionTag(array('value' => 'bbb', 'selected' => ''));
- $b->addContent('BBB');
- $tag->addTag($b);
- $c = &new SimpleOptionTag(array('value' => 'ccc'));
- $c->addContent('CCC');
- $tag->addTag($c);
- $tag->setValue('AAA');
- $this->assertEqual($tag->getValue(), 'aaa');
- }
-
- function testSelectionDespiteSpuriousWhitespace() {
- $tag = &new SimpleSelectionTag(array('name' => 'a'));
- $a = &new SimpleOptionTag(array());
- $a->addContent(' AAA ');
- $tag->addTag($a);
- $b = &new SimpleOptionTag(array('selected' => ''));
- $b->addContent(' BBB ');
- $tag->addTag($b);
- $c = &new SimpleOptionTag(array());
- $c->addContent(' CCC ');
- $tag->addTag($c);
- $this->assertEqual($tag->getValue(), ' BBB ');
- $tag->setValue('AAA');
- $this->assertEqual($tag->getValue(), ' AAA ');
- }
-
- function testFailToSetIllegalOption() {
- $tag = &new SimpleSelectionTag(array('name' => 'a'));
- $a = &new SimpleOptionTag(array());
- $a->addContent('AAA');
- $tag->addTag($a);
- $b = &new SimpleOptionTag(array('selected' => ''));
- $b->addContent('BBB');
- $tag->addTag($b);
- $c = &new SimpleOptionTag(array());
- $c->addContent('CCC');
- $tag->addTag($c);
- $this->assertFalse($tag->setValue('Not present'));
- $this->assertEqual($tag->getValue(), 'BBB');
- }
-
- function testNastyOptionValuesThatLookLikeFalse() {
- $tag = &new SimpleSelectionTag(array('name' => 'a'));
- $a = &new SimpleOptionTag(array('value' => '1'));
- $a->addContent('One');
- $tag->addTag($a);
- $b = &new SimpleOptionTag(array('value' => '0'));
- $b->addContent('Zero');
- $tag->addTag($b);
- $this->assertIdentical($tag->getValue(), '1');
- $tag->setValue('Zero');
- $this->assertIdentical($tag->getValue(), '0');
- }
-
- function testBlankOption() {
- $tag = &new SimpleSelectionTag(array('name' => 'A'));
- $a = &new SimpleOptionTag(array());
- $tag->addTag($a);
- $b = &new SimpleOptionTag(array());
- $b->addContent('b');
- $tag->addTag($b);
- $this->assertIdentical($tag->getValue(), '');
- $tag->setValue('b');
- $this->assertIdentical($tag->getValue(), 'b');
- $tag->setValue('');
- $this->assertIdentical($tag->getValue(), '');
- }
-
- function testMultipleDefaultWithNoSelections() {
- $tag = &new MultipleSelectionTag(array('name' => 'a', 'multiple' => ''));
- $a = &new SimpleOptionTag(array());
- $a->addContent('AAA');
- $tag->addTag($a);
- $b = &new SimpleOptionTag(array());
- $b->addContent('BBB');
- $tag->addTag($b);
- $this->assertIdentical($tag->getDefault(), array());
- $this->assertIdentical($tag->getValue(), array());
- }
-
- function testMultipleDefaultWithSelections() {
- $tag = &new MultipleSelectionTag(array('name' => 'a', 'multiple' => ''));
- $a = &new SimpleOptionTag(array('selected' => ''));
- $a->addContent('AAA');
- $tag->addTag($a);
- $b = &new SimpleOptionTag(array('selected' => ''));
- $b->addContent('BBB');
- $tag->addTag($b);
- $this->assertIdentical($tag->getDefault(), array('AAA', 'BBB'));
- $this->assertIdentical($tag->getValue(), array('AAA', 'BBB'));
- }
-
- function testSettingMultiple() {
- $tag = &new MultipleSelectionTag(array('name' => 'a', 'multiple' => ''));
- $a = &new SimpleOptionTag(array('selected' => ''));
- $a->addContent('AAA');
- $tag->addTag($a);
- $b = &new SimpleOptionTag(array());
- $b->addContent('BBB');
- $tag->addTag($b);
- $c = &new SimpleOptionTag(array('selected' => ''));
- $c->addContent('CCC');
- $tag->addTag($c);
- $this->assertIdentical($tag->getDefault(), array('AAA', 'CCC'));
- $this->assertTrue($tag->setValue(array('BBB', 'CCC')));
- $this->assertIdentical($tag->getValue(), array('BBB', 'CCC'));
- $this->assertTrue($tag->setValue(array()));
- $this->assertIdentical($tag->getValue(), array());
- }
-
- function testFailToSetIllegalOptionsInMultiple() {
- $tag = &new MultipleSelectionTag(array('name' => 'a', 'multiple' => ''));
- $a = &new SimpleOptionTag(array('selected' => ''));
- $a->addContent('AAA');
- $tag->addTag($a);
- $b = &new SimpleOptionTag(array());
- $b->addContent('BBB');
- $tag->addTag($b);
- $this->assertFalse($tag->setValue(array('CCC')));
- $this->assertTrue($tag->setValue(array('AAA', 'BBB')));
- $this->assertFalse($tag->setValue(array('AAA', 'CCC')));
- }
- }
-
- class TestOfRadioGroup extends UnitTestCase {
-
- function testEmptyGroup() {
- $group = &new SimpleRadioGroup();
- $this->assertIdentical($group->getDefault(), false);
- $this->assertIdentical($group->getValue(), false);
- $this->assertFalse($group->setValue('a'));
- }
-
- function testReadingSingleButtonGroup() {
- $group = &new SimpleRadioGroup();
- $group->addWidget(new SimpleRadioButtonTag(
- array('value' => 'A', 'checked' => '')));
- $this->assertIdentical($group->getDefault(), 'A');
- $this->assertIdentical($group->getValue(), 'A');
- }
-
- function testReadingMultipleButtonGroup() {
- $group = &new SimpleRadioGroup();
- $group->addWidget(new SimpleRadioButtonTag(
- array('value' => 'A')));
- $group->addWidget(new SimpleRadioButtonTag(
- array('value' => 'B', 'checked' => '')));
- $this->assertIdentical($group->getDefault(), 'B');
- $this->assertIdentical($group->getValue(), 'B');
- }
-
- function testFailToSetUnlistedValue() {
- $group = &new SimpleRadioGroup();
- $group->addWidget(new SimpleRadioButtonTag(array('value' => 'z')));
- $this->assertFalse($group->setValue('a'));
- $this->assertIdentical($group->getValue(), false);
- }
-
- function testSettingNewValueClearsTheOldOne() {
- $group = &new SimpleRadioGroup();
- $group->addWidget(new SimpleRadioButtonTag(
- array('value' => 'A')));
- $group->addWidget(new SimpleRadioButtonTag(
- array('value' => 'B', 'checked' => '')));
- $this->assertTrue($group->setValue('A'));
- $this->assertIdentical($group->getValue(), 'A');
- }
-
- function testIsIdMatchesAnyWidgetInSet() {
- $group = &new SimpleRadioGroup();
- $group->addWidget(new SimpleRadioButtonTag(
- array('value' => 'A', 'id' => 'i1')));
- $group->addWidget(new SimpleRadioButtonTag(
- array('value' => 'B', 'id' => 'i2')));
- $this->assertFalse($group->isId('i0'));
- $this->assertTrue($group->isId('i1'));
- $this->assertTrue($group->isId('i2'));
- }
- }
-
- class TestOfTagGroup extends UnitTestCase {
-
- function testReadingMultipleCheckboxGroup() {
- $group = &new SimpleCheckboxGroup();
- $group->addWidget(new SimpleCheckboxTag(array('value' => 'A')));
- $group->addWidget(new SimpleCheckboxTag(
- array('value' => 'B', 'checked' => '')));
- $this->assertIdentical($group->getDefault(), 'B');
- $this->assertIdentical($group->getValue(), 'B');
- }
-
- function testReadingMultipleUncheckedItems() {
- $group = &new SimpleCheckboxGroup();
- $group->addWidget(new SimpleCheckboxTag(array('value' => 'A')));
- $group->addWidget(new SimpleCheckboxTag(array('value' => 'B')));
- $this->assertIdentical($group->getDefault(), false);
- $this->assertIdentical($group->getValue(), false);
- }
-
- function testReadingMultipleCheckedItems() {
- $group = &new SimpleCheckboxGroup();
- $group->addWidget(new SimpleCheckboxTag(
- array('value' => 'A', 'checked' => '')));
- $group->addWidget(new SimpleCheckboxTag(
- array('value' => 'B', 'checked' => '')));
- $this->assertIdentical($group->getDefault(), array('A', 'B'));
- $this->assertIdentical($group->getValue(), array('A', 'B'));
- }
-
- function testSettingSingleValue() {
- $group = &new SimpleCheckboxGroup();
- $group->addWidget(new SimpleCheckboxTag(array('value' => 'A')));
- $group->addWidget(new SimpleCheckboxTag(array('value' => 'B')));
- $this->assertTrue($group->setValue('A'));
- $this->assertIdentical($group->getValue(), 'A');
- $this->assertTrue($group->setValue('B'));
- $this->assertIdentical($group->getValue(), 'B');
- }
-
- function testSettingMultipleValues() {
- $group = &new SimpleCheckboxGroup();
- $group->addWidget(new SimpleCheckboxTag(array('value' => 'A')));
- $group->addWidget(new SimpleCheckboxTag(array('value' => 'B')));
- $this->assertTrue($group->setValue(array('A', 'B')));
- $this->assertIdentical($group->getValue(), array('A', 'B'));
- }
-
- function testSettingNoValue() {
- $group = &new SimpleCheckboxGroup();
- $group->addWidget(new SimpleCheckboxTag(array('value' => 'A')));
- $group->addWidget(new SimpleCheckboxTag(array('value' => 'B')));
- $this->assertTrue($group->setValue(false));
- $this->assertIdentical($group->getValue(), false);
- }
-
- function testIsIdMatchesAnyIdInSet() {
- $group = &new SimpleCheckboxGroup();
- $group->addWidget(new SimpleCheckboxTag(array('id' => 1, 'value' => 'A')));
- $group->addWidget(new SimpleCheckboxTag(array('id' => 2, 'value' => 'B')));
- $this->assertFalse($group->isId(0));
- $this->assertTrue($group->isId(1));
- $this->assertTrue($group->isId(2));
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/test_with_parse_error.php b/tests/UnitTests/simpletest/test/test_with_parse_error.php deleted file mode 100644 index affdb751..00000000 --- a/tests/UnitTests/simpletest/test/test_with_parse_error.php +++ /dev/null @@ -1,8 +0,0 @@ -<?php
- // $Id: test_with_parse_error.php,v 1.1 2005/01/24 00:32:14 lastcraft Exp $
-
- class TestCaseWithParseError extends UnitTestCase {
- wibble
- }
-
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/unit_tester_test.php b/tests/UnitTests/simpletest/test/unit_tester_test.php deleted file mode 100644 index a6a61f8d..00000000 --- a/tests/UnitTests/simpletest/test/unit_tester_test.php +++ /dev/null @@ -1,52 +0,0 @@ -<?php
- // $Id: unit_tester_test.php,v 1.3 2005/02/18 22:40:58 lastcraft Exp $
-
- class TestOfUnitTester extends UnitTestCase {
-
- function testAssertTrueReturnsAssertionAsBoolean() {
- $this->assertTrue($this->assertTrue(true));
- }
-
- function testAssertFalseReturnsAssertionAsBoolean() {
- $this->assertTrue($this->assertFalse(false));
- }
-
- function testAssertEqualReturnsAssertionAsBoolean() {
- $this->assertTrue($this->assertEqual(5, 5));
- }
-
- function testAssertIdenticalReturnsAssertionAsBoolean() {
- $this->assertTrue($this->assertIdentical(5, 5));
- }
-
- function testCoreAssertionsDoNotThrowErrors() {
- $this->assertIsA($this, 'UnitTestCase');
- $this->assertNotA($this, 'WebTestCase');
- }
- }
-
- class JBehaveStyleRunner extends SimpleRunner {
- function JBehaveStyleRunner(&$test_case, &$scorer) {
- $this->SimpleRunner($test_case, $scorer);
- }
-
- function _isTest($method) {
- return strtolower(substr($method, 0, 6)) == 'should';
- }
- }
-
- class TestOfJBehaveStyleRunner extends UnitTestCase {
-
- function &_createRunner(&$reporter) {
- return new JBehaveStyleRunner($this, $reporter);
- }
-
- function testFail() {
- $this->fail('This should not be run');
- }
-
- function shouldBeRun() {
- $this->pass('This should be run');
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/unit_tests.php b/tests/UnitTests/simpletest/test/unit_tests.php deleted file mode 100644 index ebc38ffe..00000000 --- a/tests/UnitTests/simpletest/test/unit_tests.php +++ /dev/null @@ -1,49 +0,0 @@ -<?php
- // $Id: unit_tests.php,v 1.47 2005/02/05 04:51:31 lastcraft Exp $
- if (! defined('TEST')) {
- define('TEST', __FILE__);
- }
- require_once('../unit_tester.php');
- require_once('../web_tester.php');
- require_once('../shell_tester.php');
- require_once('../reporter.php');
- require_once('../mock_objects.php');
- require_once('../extensions/pear_test_case.php');
- require_once('../extensions/phpunit_test_case.php');
-
- class UnitTests extends GroupTest {
- function UnitTests() {
- $this->GroupTest('Unit tests');
- $this->addTestFile('errors_test.php');
- $this->addTestFile('options_test.php');
- $this->addTestFile('dumper_test.php');
- $this->addTestFile('expectation_test.php');
- $this->addTestFile('unit_tester_test.php');
- $this->addTestFile('simple_mock_test.php');
- $this->addTestFile('adapter_test.php');
- $this->addTestFile('socket_test.php');
- $this->addTestFile('encoding_test.php');
- $this->addTestFile('url_test.php');
- $this->addTestFile('http_test.php');
- $this->addTestFile('authentication_test.php');
- $this->addTestFile('user_agent_test.php');
- $this->addTestFile('parser_test.php');
- $this->addTestFile('tag_test.php');
- $this->addTestFile('form_test.php');
- $this->addTestFile('page_test.php');
- $this->addTestFile('frames_test.php');
- $this->addTestFile('browser_test.php');
- $this->addTestFile('web_tester_test.php');
- $this->addTestFile('shell_tester_test.php');
- $this->addTestFile('xml_test.php');
- }
- }
-
- if (TEST == __FILE__) {
- $test = &new UnitTests();
- if (SimpleReporter::inCli()) {
- exit ($test->run(new TextReporter()) ? 0 : 1);
- }
- $test->run(new HtmlReporter());
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/url_test.php b/tests/UnitTests/simpletest/test/url_test.php deleted file mode 100644 index 5ca01260..00000000 --- a/tests/UnitTests/simpletest/test/url_test.php +++ /dev/null @@ -1,369 +0,0 @@ -<?php
- // $Id: url_test.php,v 1.19 2005/01/02 23:43:28 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../url.php');
-
- class TestOfUrl extends UnitTestCase {
-
- function testDefaultUrl() {
- $url = new SimpleUrl('');
- $this->assertEqual($url->getScheme(), '');
- $this->assertEqual($url->getHost(), '');
- $this->assertEqual($url->getScheme('http'), 'http');
- $this->assertEqual($url->getHost('localhost'), 'localhost');
- $this->assertEqual($url->getPath(), '');
- }
-
- function testBasicParsing() {
- $url = new SimpleUrl('https://www.lastcraft.com/test/');
- $this->assertEqual($url->getScheme(), 'https');
- $this->assertEqual($url->getHost(), 'www.lastcraft.com');
- $this->assertEqual($url->getPath(), '/test/');
- }
-
- function testRelativeUrls() {
- $url = new SimpleUrl('../somewhere.php');
- $this->assertEqual($url->getScheme(), false);
- $this->assertEqual($url->getHost(), false);
- $this->assertEqual($url->getPath(), '../somewhere.php');
- }
-
- function testParseBareParameter() {
- $url = new SimpleUrl('?a');
- $this->assertEqual($url->getPath(), '');
- $this->assertEqual($url->getEncodedRequest(), '?a=');
- }
-
- function testParseEmptyParameter() {
- $url = new SimpleUrl('?a=');
- $this->assertEqual($url->getPath(), '');
- $this->assertEqual($url->getEncodedRequest(), '?a=');
- }
-
- function testParseParameterPair() {
- $url = new SimpleUrl('?a=A');
- $this->assertEqual($url->getPath(), '');
- $this->assertEqual($url->getEncodedRequest(), '?a=A');
- }
-
- function testParseMultipleParameters() {
- $url = new SimpleUrl('?a=A&b=B');
- $this->assertEqual($url->getEncodedRequest(), '?a=A&b=B');
- }
-
- function testParsingParameterMixture() {
- $url = new SimpleUrl('?a=A&b=&c');
- $this->assertEqual($url->getEncodedRequest(), '?a=A&b=&c=');
- }
-
- function testAddParameters() {
- $url = new SimpleUrl('');
- $url->addRequestParameter('a', 'A');
- $this->assertEqual($url->getEncodedRequest(), '?a=A');
- $url->addRequestParameter('b', 'B');
- $this->assertEqual($url->getEncodedRequest(), '?a=A&b=B');
- $url->addRequestParameter('a', 'aaa');
- $this->assertEqual($url->getEncodedRequest(), '?a=A&a=aaa&b=B');
- }
-
- function testClearingParameters() {
- $url = new SimpleUrl('');
- $url->addRequestParameter('a', 'A');
- $url->clearRequest();
- $this->assertIdentical($url->getEncodedRequest(), '');
- }
-
- function testEncodingParameters() {
- $url = new SimpleUrl('');
- $url->addRequestParameter('a', '?!"\'#~@[]{}:;<>,./|£$%^&*()_+-=');
- $this->assertIdentical(
- $request = $url->getEncodedRequest(),
- '?a=%3F%21%22%27%23%7E%40%5B%5D%7B%7D%3A%3B%3C%3E%2C.%2F%7C%A3%24%25%5E%26%2A%28%29_%2B-%3D');
- }
-
- function testDecodingParameters() {
- $url = new SimpleUrl('?a=%3F%21%22%27%23%7E%40%5B%5D%7B%7D%3A%3B%3C%3E%2C.%2F%7C%A3%24%25%5E%26%2A%28%29_%2B-%3D');
- $this->assertEqual(
- $url->getEncodedRequest(),
- '?a=' . urlencode('?!"\'#~@[]{}:;<>,./|£$%^&*()_+-='));
- }
-
- function testSettingCordinates() {
- $url = new SimpleUrl('');
- $url->setCoordinates('32', '45');
- $this->assertIdentical($url->getX(), 32);
- $this->assertIdentical($url->getY(), 45);
- $this->assertEqual($url->getEncodedRequest(), '?32,45');
- }
-
- function testParseCordinates() {
- $url = new SimpleUrl('?32,45');
- $this->assertIdentical($url->getX(), 32);
- $this->assertIdentical($url->getY(), 45);
- $this->assertEqual($url->getEncodedRequest(), '?32,45');
- }
-
- function testClearingCordinates() {
- $url = new SimpleUrl('?32,45');
- $url->setCoordinates();
- $this->assertIdentical($url->getX(), false);
- $this->assertIdentical($url->getY(), false);
- }
-
- function testParsingParameterCordinateMixture() {
- $url = new SimpleUrl('?a=A&b=&c?32,45');
- $this->assertIdentical($url->getX(), 32);
- $this->assertIdentical($url->getY(), 45);
- $this->assertEqual($url->getEncodedRequest(), '?a=A&b=&c=?32,45');
- }
-
- function testParsingParameterWithBadCordinates() {
- $url = new SimpleUrl('?a=A&b=&c?32');
- $this->assertIdentical($url->getX(), false);
- $this->assertIdentical($url->getY(), false);
- $this->assertEqual($url->getEncodedRequest(), '?a=A&b=&c?32=');
- }
-
- function testPageSplitting() {
- $url = new SimpleUrl('./here/../there/somewhere.php');
- $this->assertEqual($url->getPath(), './here/../there/somewhere.php');
- $this->assertEqual($url->getPage(), 'somewhere.php');
- $this->assertEqual($url->getBasePath(), './here/../there/');
- }
-
- function testAbsolutePathPageSplitting() {
- $url = new SimpleUrl("http://host.com/here/there/somewhere.php");
- $this->assertEqual($url->getPath(), "/here/there/somewhere.php");
- $this->assertEqual($url->getPage(), "somewhere.php");
- $this->assertEqual($url->getBasePath(), "/here/there/");
- }
-
- function testSplittingUrlWithNoPageGivesEmptyPage() {
- $url = new SimpleUrl('/here/there/');
- $this->assertEqual($url->getPath(), '/here/there/');
- $this->assertEqual($url->getPage(), '');
- $this->assertEqual($url->getBasePath(), '/here/there/');
- }
-
- function testPathNormalisation() {
- $this->assertEqual(
- SimpleUrl::normalisePath('https://host.com/I/am/here/../there/somewhere.php'),
- 'https://host.com/I/am/there/somewhere.php');
- }
-
- function testUsernameAndPasswordAreUrlDecoded() {
- $url = new SimpleUrl('http://' . urlencode('test@test') .
- ':' . urlencode('$!£@*&%') . '@www.lastcraft.com');
- $this->assertEqual($url->getUsername(), 'test@test');
- $this->assertEqual($url->getPassword(), '$!£@*&%');
- }
-
- function testBlitz() {
- $this->assertUrl(
- "https://username:password@www.somewhere.com:243/this/that/here.php?a=1&b=2#anchor",
- array("https", "username", "password", "www.somewhere.com", 243, "/this/that/here.php", "com", "?a=1&b=2", "anchor"),
- array("a" => "1", "b" => "2"));
- $this->assertUrl(
- "username:password@www.somewhere.com/this/that/here.php?a=1",
- array(false, "username", "password", "www.somewhere.com", false, "/this/that/here.php", "com", "?a=1", false),
- array("a" => "1"));
- $this->assertUrl(
- "username:password@somewhere.com:243?1,2",
- array(false, "username", "password", "somewhere.com", 243, "/", "com", "?1,2", false),
- array(),
- array(1, 2));
- $this->assertUrl(
- "https://www.somewhere.com",
- array("https", false, false, "www.somewhere.com", false, "/", "com", "", false));
- $this->assertUrl(
- "username@www.somewhere.com:243#anchor",
- array(false, "username", false, "www.somewhere.com", 243, "/", "com", "", "anchor"));
- $this->assertUrl(
- "/this/that/here.php?a=1&b=2?3,4",
- array(false, false, false, false, false, "/this/that/here.php", false, "?a=1&b=2?3,4", false),
- array("a" => "1", "b" => "2"),
- array(3, 4));
- $this->assertUrl(
- "username@/here.php?a=1&b=2",
- array(false, "username", false, false, false, "/here.php", false, "?a=1&b=2", false),
- array("a" => "1", "b" => "2"));
- }
-
- function testAmbiguousHosts() {
- $this->assertUrl(
- "tigger",
- array(false, false, false, false, false, "tigger", false, "", false));
- $this->assertUrl(
- "/tigger",
- array(false, false, false, false, false, "/tigger", false, "", false));
- $this->assertUrl(
- "//tigger",
- array(false, false, false, "tigger", false, "/", false, "", false));
- $this->assertUrl(
- "//tigger/",
- array(false, false, false, "tigger", false, "/", false, "", false));
- $this->assertUrl(
- "tigger.com",
- array(false, false, false, "tigger.com", false, "/", "com", "", false));
- $this->assertUrl(
- "me.net/tigger",
- array(false, false, false, "me.net", false, "/tigger", "net", "", false));
- }
-
- function testAsString() {
- $this->assertPreserved('https://www.here.com');
- $this->assertPreserved('http://me:secret@www.here.com');
- $this->assertPreserved('http://here/there');
- $this->assertPreserved('http://here/there?a=A&b=B');
- $this->assertPreserved('http://here/there?a=1&a=2');
- $this->assertPreserved('http://here/there?a=1&a=2?9,8');
- $this->assertPreserved('http://host?a=1&a=2');
- $this->assertPreserved('http://host#stuff');
- $this->assertPreserved('http://me:secret@www.here.com/a/b/c/here.html?a=A?7,6');
- }
-
- function assertUrl($raw, $parts, $params = false, $coords = false) {
- if (! is_array($params)) {
- $params = array();
- }
- $url = new SimpleUrl($raw);
- $this->assertIdentical($url->getScheme(), $parts[0], "[$raw] scheme -> %s");
- $this->assertIdentical($url->getUsername(), $parts[1], "[$raw] username -> %s");
- $this->assertIdentical($url->getPassword(), $parts[2], "[$raw] password -> %s");
- $this->assertIdentical($url->getHost(), $parts[3], "[$raw] host -> %s");
- $this->assertIdentical($url->getPort(), $parts[4], "[$raw] port -> %s");
- $this->assertIdentical($url->getPath(), $parts[5], "[$raw] path -> %s");
- $this->assertIdentical($url->getTld(), $parts[6], "[$raw] tld -> %s");
- $this->assertIdentical($url->getEncodedRequest(), $parts[7], "[$raw] encoded -> %s");
- $this->assertIdentical($url->getFragment(), $parts[8], "[$raw] fragment -> %s");
- if ($coords) {
- $this->assertIdentical($url->getX(), $coords[0], "[$raw] x -> %s");
- $this->assertIdentical($url->getY(), $coords[1], "[$raw] y -> %s");
- }
- }
-
- function assertPreserved($string) {
- $url = new SimpleUrl($string);
- $this->assertEqual($url->asString(), $string);
- }
- }
-
- class TestOfAbsoluteUrls extends UnitTestCase {
-
- function testMakingAbsolute() {
- $url = new SimpleUrl('../there/somewhere.php');
- $this->assertEqual($url->getPath(), '../there/somewhere.php');
- $absolute = $url->makeAbsolute('https://host.com:1234/I/am/here/');
- $this->assertEqual($absolute->getScheme(), 'https');
- $this->assertEqual($absolute->getHost(), 'host.com');
- $this->assertEqual($absolute->getPort(), 1234);
- $this->assertEqual($absolute->getPath(), '/I/am/there/somewhere.php');
- }
-
- function testMakingAnEmptyUrlAbsolute() {
- $url = new SimpleUrl('');
- $this->assertEqual($url->getPath(), '');
- $absolute = $url->makeAbsolute('http://host.com/I/am/here/page.html');
- $this->assertEqual($absolute->getScheme(), 'http');
- $this->assertEqual($absolute->getHost(), 'host.com');
- $this->assertEqual($absolute->getPath(), '/I/am/here/page.html');
- }
-
- function testMakingAnEmptyUrlAbsoluteWithMissingPageName() {
- $url = new SimpleUrl('');
- $this->assertEqual($url->getPath(), '');
- $absolute = $url->makeAbsolute('http://host.com/I/am/here/');
- $this->assertEqual($absolute->getScheme(), 'http');
- $this->assertEqual($absolute->getHost(), 'host.com');
- $this->assertEqual($absolute->getPath(), '/I/am/here/');
- }
-
- function testMakingAShortQueryUrlAbsolute() {
- $url = new SimpleUrl('?a#b');
- $this->assertEqual($url->getPath(), '');
- $absolute = $url->makeAbsolute('http://host.com/I/am/here/');
- $this->assertEqual($absolute->getScheme(), 'http');
- $this->assertEqual($absolute->getHost(), 'host.com');
- $this->assertEqual($absolute->getPath(), '/I/am/here/');
- $this->assertEqual($absolute->getEncodedRequest(), '?a=');
- $this->assertEqual($absolute->getFragment(), 'b');
- }
-
- function testMakingADirectoryUrlAbsolute() {
- $url = new SimpleUrl('hello/');
- $this->assertEqual($url->getPath(), 'hello/');
- $this->assertEqual($url->getBasePath(), 'hello/');
- $this->assertEqual($url->getPage(), '');
- $absolute = $url->makeAbsolute('http://host.com/I/am/here/page.html');
- $this->assertEqual($absolute->getPath(), '/I/am/here/hello/');
- }
-
- function testMakingARootUrlAbsolute() {
- $url = new SimpleUrl('/');
- $this->assertEqual($url->getPath(), '/');
- $absolute = $url->makeAbsolute('http://host.com/I/am/here/page.html');
- $this->assertEqual($absolute->getPath(), '/');
- }
-
- function testMakingARootPageUrlAbsolute() {
- $url = new SimpleUrl('/here.html');
- $absolute = $url->makeAbsolute('http://host.com/I/am/here/page.html');
- $this->assertEqual($absolute->getPath(), '/here.html');
- }
-
- function testMakingCoordinateUrlAbsolute() {
- $url = new SimpleUrl('?1,2');
- $this->assertEqual($url->getPath(), '');
- $absolute = $url->makeAbsolute('http://host.com/I/am/here/');
- $this->assertEqual($absolute->getScheme(), 'http');
- $this->assertEqual($absolute->getHost(), 'host.com');
- $this->assertEqual($absolute->getPath(), '/I/am/here/');
- $this->assertEqual($absolute->getX(), 1);
- $this->assertEqual($absolute->getY(), 2);
- }
-
- function testMakingAbsoluteAppendedPath() {
- $url = new SimpleUrl('./there/somewhere.php');
- $absolute = $url->makeAbsolute('https://host.com/here/');
- $this->assertEqual($absolute->getPath(), '/here/there/somewhere.php');
- }
-
- function testMakingAbsoluteBadlyFormedAppendedPath() {
- $url = new SimpleUrl('there/somewhere.php');
- $absolute = $url->makeAbsolute('https://host.com/here/');
- $this->assertEqual($absolute->getPath(), '/here/there/somewhere.php');
- }
-
- function testMakingAbsoluteHasNoEffectWhenAlreadyAbsolute() {
- $url = new SimpleUrl('https://test:secret@www.lastcraft.com:321/stuff/?a=1#f');
- $absolute = $url->makeAbsolute('http://host.com/here/');
- $this->assertEqual($absolute->getScheme(), 'https');
- $this->assertEqual($absolute->getUsername(), 'test');
- $this->assertEqual($absolute->getPassword(), 'secret');
- $this->assertEqual($absolute->getHost(), 'www.lastcraft.com');
- $this->assertEqual($absolute->getPort(), 321);
- $this->assertEqual($absolute->getPath(), '/stuff/');
- $this->assertEqual($absolute->getEncodedRequest(), '?a=1');
- $this->assertEqual($absolute->getFragment(), 'f');
- }
-
- function testMakingHostOnlyAbsoluteDoesNotCarryAnyOtherInformation() {
- $url = new SimpleUrl('http://www.lastcraft.com');
- $absolute = $url->makeAbsolute('https://host.com:81/here/');
- $this->assertEqual($absolute->getScheme(), 'http');
- $this->assertEqual($absolute->getHost(), 'www.lastcraft.com');
- $this->assertIdentical($absolute->getPort(), false);
- $this->assertEqual($absolute->getPath(), '/');
- }
- }
-
- class TestOfFrameUrl extends UnitTestCase {
-
- function testTargetAttachment() {
- $url = new SimpleUrl('http://www.site.com/home.html');
- $this->assertIdentical($url->getTarget(), false);
- $url->setTarget('A frame');
- $this->assertIdentical($url->getTarget(), 'A frame');
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/user_agent_test.php b/tests/UnitTests/simpletest/test/user_agent_test.php deleted file mode 100644 index 578327c6..00000000 --- a/tests/UnitTests/simpletest/test/user_agent_test.php +++ /dev/null @@ -1,546 +0,0 @@ -<?php
- // $Id: user_agent_test.php,v 1.22 2005/01/02 22:46:10 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../user_agent.php');
- require_once(dirname(__FILE__) . '/../authentication.php');
- require_once(dirname(__FILE__) . '/../http.php');
- require_once(dirname(__FILE__) . '/../encoding.php');
- Mock::generate('SimpleHttpRequest');
- Mock::generate('SimpleHttpResponse');
- Mock::generate('SimpleHttpHeaders');
- Mock::generatePartial('SimpleUserAgent', 'MockRequestUserAgent', array('_createHttpRequest'));
-
- class TestOfSimpleCookieJar extends UnitTestCase {
-
- function testAddCookie() {
- $jar = new SimpleCookieJar();
- $jar->setCookie(new SimpleCookie("a", "A"));
- $cookies = $jar->getValidCookies();
- $this->assertEqual(count($cookies), 1);
- $this->assertEqual($cookies[0]->getValue(), "A");
- }
-
- function testHostFilter() {
- $jar = new SimpleCookieJar();
- $cookie = new SimpleCookie('a', 'A');
- $cookie->setHost('my-host.com');
- $jar->setCookie($cookie);
- $cookie = new SimpleCookie('b', 'B');
- $cookie->setHost('another-host.com');
- $jar->setCookie($cookie);
- $cookie = new SimpleCookie('c', 'C');
- $jar->setCookie($cookie);
- $cookies = $jar->getValidCookies('my-host.com');
- $this->assertEqual(count($cookies), 2);
- $this->assertEqual($cookies[0]->getValue(), 'A');
- $this->assertEqual($cookies[1]->getValue(), 'C');
- $this->assertEqual(count($jar->getValidCookies('another-host.com')), 2);
- $this->assertEqual(count($jar->getValidCookies('www.another-host.com')), 2);
- $this->assertEqual(count($jar->getValidCookies('new-host.org')), 1);
- $this->assertEqual(count($jar->getValidCookies()), 3);
- }
-
- function testPathFilter() {
- $jar = new SimpleCookieJar();
- $jar->setCookie(new SimpleCookie("a", "A", "/path/"));
- $this->assertEqual(count($jar->getValidCookies(false, "/")), 0);
- $this->assertEqual(count($jar->getValidCookies(false, "/elsewhere")), 0);
- $this->assertEqual(count($jar->getValidCookies(false, "/path/")), 1);
- $this->assertEqual(count($jar->getValidCookies(false, "/path")), 1);
- $this->assertEqual(count($jar->getValidCookies(false, "/pa")), 0);
- $this->assertEqual(count($jar->getValidCookies(false, "/path/here/")), 1);
- }
-
- function testPathFilterDeeply() {
- $jar = new SimpleCookieJar();
- $jar->setCookie(new SimpleCookie("a", "A", "/path/more_path/"));
- $this->assertEqual(count($jar->getValidCookies(false, "/path/")), 0);
- $this->assertEqual(count($jar->getValidCookies(false, "/path")), 0);
- $this->assertEqual(count($jar->getValidCookies(false, "/pa")), 0);
- $this->assertEqual(count($jar->getValidCookies(false, "/path/more_path/")), 1);
- $this->assertEqual(count($jar->getValidCookies(false, "/path/more_path/and_more")), 1);
- $this->assertEqual(count($jar->getValidCookies(false, "/path/not_here/")), 0);
- }
-
- function testMultipleCookieWithDifferentPaths() {
- $jar = new SimpleCookieJar();
- $jar->setCookie(new SimpleCookie("a", "abc", "/"));
- $jar->setCookie(new SimpleCookie("a", "123", "/path/here/"));
- $cookies = $jar->getValidCookies("my-host.com", "/");
- $this->assertEqual($cookies[0]->getPath(), "/");
- $cookies = $jar->getValidCookies("my-host.com", "/path/");
- $this->assertEqual($cookies[0]->getPath(), "/");
- $cookies = $jar->getValidCookies("my-host.com", "/path/here");
- $this->assertEqual($cookies[0]->getPath(), "/");
- $this->assertEqual($cookies[1]->getPath(), "/path/here/");
- $cookies = $jar->getValidCookies("my-host.com", "/path/here/there");
- $this->assertEqual($cookies[0]->getPath(), "/");
- $this->assertEqual($cookies[1]->getPath(), "/path/here/");
- }
-
- function testOverwrite() {
- $jar = new SimpleCookieJar();
- $jar->setCookie(new SimpleCookie("a", "abc", "/"));
- $jar->setCookie(new SimpleCookie("a", "cde", "/"));
- $cookies = $jar->getValidCookies();
- $this->assertIdentical($cookies[0]->getValue(), "cde");
- }
-
- function testClearSessionCookies() {
- $jar = new SimpleCookieJar();
- $jar->setCookie(new SimpleCookie("a", "A", "/"));
- $jar->restartSession();
- $this->assertEqual(count($jar->getValidCookies(false, "/")), 0);
- }
-
- function testExpiryFilterByDate() {
- $cookie = new SimpleCookie("a", "A", "/", "Wed, 25-Dec-02 04:24:20 GMT");
- $jar = new SimpleCookieJar();
- $jar->setCookie($cookie);
- $jar->restartSession("Wed, 25-Dec-02 04:24:19 GMT");
- $this->assertIdentical($list = $jar->getValidCookies(false, "/"), array($cookie));
- $jar->restartSession("Wed, 25-Dec-02 04:24:21 GMT");
- $this->assertIdentical($list = $jar->getValidCookies(false, "/"), array());
- }
-
- function testExpiryFilterByAgeing() {
- $cookie = new SimpleCookie("a", "A", "/", "Wed, 25-Dec-02 04:24:20 GMT");
- $jar = new SimpleCookieJar();
- $jar->setCookie($cookie);
- $jar->restartSession("Wed, 25-Dec-02 04:24:19 GMT");
- $this->assertIdentical($list = $jar->getValidCookies(false, "/"), array($cookie));
- $jar->agePrematurely(2);
- $jar->restartSession("Wed, 25-Dec-02 04:24:19 GMT");
- $this->assertIdentical($list = $jar->getValidCookies(false, "/"), array());
- }
-
- function testCookieClearing() {
- $jar = new SimpleCookieJar();
- $jar->setCookie(new SimpleCookie("a", "abc", "/"));
- $jar->setCookie(new SimpleCookie("a", "", "/"));
- $this->assertEqual(count($cookies = $jar->getValidCookies(false, "/")), 1);
- $this->assertIdentical($cookies[0]->getValue(), "");
- }
-
- function testCookieClearByDate() {
- $jar = new SimpleCookieJar();
- $jar->setCookie(new SimpleCookie("a", "abc", "/", "Wed, 25-Dec-02 04:24:21 GMT"));
- $jar->setCookie(new SimpleCookie("a", "def", "/", "Wed, 25-Dec-02 04:24:19 GMT"));
- $cookies = $jar->getValidCookies(false, "/");
- $this->assertIdentical($cookies[0]->getValue(), "def");
- $jar->restartSession("Wed, 25-Dec-02 04:24:20 GMT");
- $this->assertEqual(count($jar->getValidCookies(false, "/")), 0);
- }
- }
-
- class TestOfFetchingUrlParameters extends UnitTestCase {
-
- function testGet() {
- $headers = &new MockSimpleHttpHeaders($this);
- $headers->setReturnValue('getMimeType', 'text/html');
- $headers->setReturnValue('getResponseCode', 200);
- $headers->setReturnValue('getNewCookies', array());
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', 'stuff');
- $response->setReturnReference('getHeaders', $headers);
-
- $request = &new MockSimpleHttpRequest($this);
- $request->setReturnReference('fetch', $response);
-
- $agent = &new MockRequestUserAgent($this);
- $agent->setReturnReference('_createHttpRequest', $request);
- $agent->expectOnce('_createHttpRequest', array(
- 'GET',
- new SimpleUrl('http://test:secret@this.com/page.html?a=A&b=B'),
- new SimpleFormEncoding()));
- $agent->SimpleUserAgent();
-
- $agent->fetchResponse(
- 'GET',
- new SimpleUrl('http://test:secret@this.com/page.html'),
- new SimpleFormEncoding(array('a' => 'A', 'b' => 'B')));
- $agent->tally();
- }
-
- function testHead() {
- $headers = &new MockSimpleHttpHeaders($this);
- $headers->setReturnValue('getMimeType', 'text/html');
- $headers->setReturnValue('getResponseCode', 200);
- $headers->setReturnValue('getNewCookies', array());
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', 'stuff');
- $response->setReturnReference('getHeaders', $headers);
-
- $request = &new MockSimpleHttpRequest($this);
- $request->setReturnReference('fetch', $response);
-
- $url = new SimpleUrl('http://this.com/page.html');
- $url->addRequestParameters(array('a' => 'A', 'b' => 'B'));
-
- $agent = &new MockRequestUserAgent($this);
- $agent->setReturnReference('_createHttpRequest', $request);
- $agent->expectOnce('_createHttpRequest', array(
- 'HEAD',
- new SimpleUrl('http://test:secret@this.com/page.html?a=A&b=B'),
- new SimpleFormEncoding()));
- $agent->SimpleUserAgent();
-
- $agent->fetchResponse(
- 'HEAD',
- new SimpleUrl('http://test:secret@this.com/page.html'),
- new SimpleFormEncoding(array('a' => 'A', 'b' => 'B')));
- $agent->tally();
- }
-
- function testPost() {
- $headers = &new MockSimpleHttpHeaders($this);
- $headers->setReturnValue('getMimeType', 'text/html');
- $headers->setReturnValue('getResponseCode', 200);
- $headers->setReturnValue('getNewCookies', array());
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', 'stuff');
- $response->setReturnReference('getHeaders', $headers);
-
- $request = &new MockSimpleHttpRequest($this);
- $request->setReturnReference('fetch', $response);
-
- $agent = &new MockRequestUserAgent($this);
- $agent->setReturnReference('_createHttpRequest', $request);
- $agent->expectOnce('_createHttpRequest', array(
- 'POST',
- new SimpleUrl('http://test:secret@this.com/page.html'),
- new SimpleFormEncoding(array('a' => 'A', 'b' => 'B'))));
- $agent->SimpleUserAgent();
-
- $agent->fetchResponse(
- 'POST',
- new SimpleUrl('http://test:secret@this.com/page.html'),
- new SimpleFormEncoding(array('a' => 'A', 'b' => 'B')));
- $agent->tally();
- }
- }
-
- class TestOfAdditionalHeaders extends UnitTestCase {
-
- function testAdditionalHeaderAddedToRequest() {
- $headers = &new MockSimpleHttpHeaders($this);
- $headers->setReturnValue('getNewCookies', array());
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnReference('getHeaders', $headers);
-
- $request = &new MockSimpleHttpRequest($this);
- $request->setReturnReference('fetch', $response);
- $request->expectOnce(
- 'addHeaderLine',
- array('User-Agent: SimpleTest'));
-
- $agent = &new MockRequestUserAgent($this);
- $agent->setReturnReference('_createHttpRequest', $request);
- $agent->SimpleUserAgent();
-
- $agent->addHeader('User-Agent: SimpleTest');
- $response = &$agent->fetchResponse('GET', new SimpleUrl('http://this.host/'));
- $request->tally();
- }
- }
-
- class TestOfBrowserCookies extends UnitTestCase {
-
- function &_createStandardResponse() {
- $headers = &new MockSimpleHttpHeaders($this);
- $headers->setReturnValue("getNewCookies", array());
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue("isError", false);
- $response->setReturnValue("getContent", "stuff");
- $response->setReturnReference("getHeaders", $headers);
- return $response;
- }
-
- function &_createCookieSite($cookies) {
- $headers = &new MockSimpleHttpHeaders($this);
- $headers->setReturnValue("getNewCookies", $cookies);
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue("isError", false);
- $response->setReturnReference("getHeaders", $headers);
- $response->setReturnValue("getContent", "stuff");
-
- $request = &new MockSimpleHttpRequest($this);
- $request->setReturnReference("fetch", $response);
- return $request;
- }
-
- function &_createPartialFetcher(&$request) {
- $agent = &new MockRequestUserAgent($this);
- $agent->setReturnReference('_createHttpRequest', $request);
- $agent->SimpleUserAgent();
- return $agent;
- }
-
- function testSendingExistingCookie() {
- $request = &new MockSimpleHttpRequest($this);
- $request->setReturnReference('fetch', $this->_createStandardResponse());
- $request->expectOnce('setCookie', array(new SimpleCookie('a', 'A')));
-
- $agent = &$this->_createPartialFetcher($request);
- $agent->setCookie('a', 'A');
- $response = $agent->fetchResponse(
- 'GET',
- new SimpleUrl('http://this.com/this/path/page.html'),
- array());
- $this->assertEqual($response->getContent(), "stuff");
- $request->tally();
- }
-
- function testOverwriteCookieThatAlreadyExists() {
- $request = &$this->_createCookieSite(array(new SimpleCookie("a", "AAAA", "this/path/")));
- $agent = &$this->_createPartialFetcher($request);
-
- $agent->setCookie("a", "A");
- $agent->fetchResponse(
- "GET",
- new SimpleUrl('http://this.com/this/path/page.html'),
- array());
- $this->assertEqual($agent->getCookieValue("this.com", "this/path/", "a"), "AAAA");
- }
-
- function testClearCookieBySettingExpiry() {
- $request = &$this->_createCookieSite(array(
- new SimpleCookie("a", "b", "this/path/", "Wed, 25-Dec-02 04:24:19 GMT")));
- $agent = &$this->_createPartialFetcher($request);
-
- $agent->setCookie("a", "A", "this/path/", "Wed, 25-Dec-02 04:24:21 GMT");
- $agent->fetchResponse(
- 'GET',
- new SimpleUrl('http://this.com/this/path/page.html'),
- array());
- $this->assertIdentical(
- $agent->getCookieValue("this.com", "this/path/", "a"),
- "b");
- $agent->restart("Wed, 25-Dec-02 04:24:20 GMT");
- $this->assertIdentical(
- $agent->getCookieValue("this.com", "this/path/", "a"),
- false);
- }
-
- function testAgeingAndClearing() {
- $request = &$this->_createCookieSite(array(
- new SimpleCookie("a", "A", "this/path/", "Wed, 25-Dec-02 04:24:21 GMT")));
- $agent = &$this->_createPartialFetcher($request);
-
- $agent->fetchResponse(
- 'GET',
- new SimpleUrl('http://this.com/this/path/page.html'),
- array());
- $agent->restart("Wed, 25-Dec-02 04:24:20 GMT");
- $this->assertIdentical(
- $agent->getCookieValue("this.com", "this/path/", "a"),
- "A");
- $agent->ageCookies(2);
- $agent->restart("Wed, 25-Dec-02 04:24:20 GMT");
- $this->assertIdentical(
- $agent->getCookieValue("this.com", "this/path/", "a"),
- false);
- }
-
- function testReadingIncomingAndSetCookies() {
- $request = &$this->_createCookieSite(array(
- new SimpleCookie("a", "AAA", "this/path/")));
- $agent = &$this->_createPartialFetcher($request);
-
- $this->assertNull($agent->getBaseCookieValue("a", false));
- $agent->fetchResponse(
- 'GET',
- new SimpleUrl('http://this.com/this/path/page.html'),
- array());
- $agent->setCookie("b", "BBB", "this.com", "this/path/");
- $this->assertEqual(
- $agent->getBaseCookieValue("a", new SimpleUrl('http://this.com/this/path/page.html')),
- "AAA");
- $this->assertEqual(
- $agent->getBaseCookieValue("b", new SimpleUrl('http://this.com/this/path/page.html')),
- "BBB");
- }
- }
-
- class TestOfHttpRedirects extends UnitTestCase {
-
- function &createRedirect($content, $redirect) {
- $headers = &new MockSimpleHttpHeaders($this);
- $headers->setReturnValue('getNewCookies', array());
- $headers->setReturnValue('isRedirect', (boolean)$redirect);
- $headers->setReturnValue('getLocation', $redirect);
- $headers->setReturnValue('getNewCookies', array());
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('getContent', $content);
- $response->setReturnReference('getHeaders', $headers);
-
- $request = &new MockSimpleHttpRequest($this);
- $request->setReturnReference('fetch', $response);
- return $request;
- }
-
- function testDisabledRedirects() {
- $agent = &new MockRequestUserAgent($this);
- $agent->setReturnReference(
- '_createHttpRequest',
- $this->createRedirect('stuff', 'there.html'));
- $agent->expectOnce('_createHttpRequest');
- $agent->SimpleUserAgent();
-
- $agent->setMaximumRedirects(0);
- $response = &$agent->fetchResponse('GET', new SimpleUrl('here.html'));
-
- $this->assertEqual($response->getContent(), 'stuff');
- $agent->tally();
- }
-
- function testSingleRedirect() {
- $agent = &new MockRequestUserAgent($this);
- $agent->setReturnReferenceAt(
- 0,
- '_createHttpRequest',
- $this->createRedirect('first', 'two.html'));
- $agent->setReturnReferenceAt(
- 1,
- '_createHttpRequest',
- $this->createRedirect('second', 'three.html'));
- $agent->expectCallCount('_createHttpRequest', 2);
- $agent->SimpleUserAgent();
-
- $agent->setMaximumRedirects(1);
- $response = &$agent->fetchResponse('GET', new SimpleUrl('one.html'));
-
- $this->assertEqual($response->getContent(), 'second');
- $agent->tally();
- }
-
- function testDoubleRedirect() {
- $agent = &new MockRequestUserAgent($this);
- $agent->setReturnReferenceAt(
- 0,
- '_createHttpRequest',
- $this->createRedirect('first', 'two.html'));
- $agent->setReturnReferenceAt(
- 1,
- '_createHttpRequest',
- $this->createRedirect('second', 'three.html'));
- $agent->setReturnReferenceAt(
- 2,
- '_createHttpRequest',
- $this->createRedirect('third', 'four.html'));
- $agent->expectCallCount('_createHttpRequest', 3);
- $agent->SimpleUserAgent();
-
- $agent->setMaximumRedirects(2);
- $response = &$agent->fetchResponse('GET', new SimpleUrl('one.html'));
-
- $this->assertEqual($response->getContent(), 'third');
- $agent->tally();
- }
-
- function testSuccessAfterRedirect() {
- $agent = &new MockRequestUserAgent($this);
- $agent->setReturnReferenceAt(
- 0,
- '_createHttpRequest',
- $this->createRedirect('first', 'two.html'));
- $agent->setReturnReferenceAt(
- 1,
- '_createHttpRequest',
- $this->createRedirect('second', false));
- $agent->setReturnReferenceAt(
- 2,
- '_createHttpRequest',
- $this->createRedirect('third', 'four.html'));
- $agent->expectCallCount('_createHttpRequest', 2);
- $agent->SimpleUserAgent();
-
- $agent->setMaximumRedirects(2);
- $response = &$agent->fetchResponse('GET', new SimpleUrl('one.html'));
-
- $this->assertEqual($response->getContent(), 'second');
- $agent->tally();
- }
-
- function testRedirectChangesPostToGet() {
- $agent = &new MockRequestUserAgent($this);
- $agent->setReturnReferenceAt(
- 0,
- '_createHttpRequest',
- $this->createRedirect('first', 'two.html'));
- $agent->expectArgumentsAt(0, '_createHttpRequest', array('POST', '*', '*'));
- $agent->setReturnReferenceAt(
- 1,
- '_createHttpRequest',
- $this->createRedirect('second', 'three.html'));
- $agent->expectArgumentsAt(1, '_createHttpRequest', array('GET', '*', '*'));
- $agent->expectCallCount('_createHttpRequest', 2);
- $agent->SimpleUserAgent();
-
- $agent->setMaximumRedirects(1);
- $response = &$agent->fetchResponse('POST', new SimpleUrl('one.html'));
-
- $agent->tally();
- }
- }
-
- class TestOfBadHosts extends UnitTestCase {
-
- function &_createSimulatedBadHost() {
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnValue('isError', true);
- $response->setReturnValue('getError', 'Bad socket');
- $response->setReturnValue('getContent', false);
-
- $request = &new MockSimpleHttpRequest($this);
- $request->setReturnReference('fetch', $response);
- return $request;
- }
-
- function testUntestedHost() {
- $request = &$this->_createSimulatedBadHost();
-
- $agent = &new MockRequestUserAgent($this);
- $agent->setReturnReference('_createHttpRequest', $request);
- $agent->SimpleUserAgent();
-
- $response = &$agent->fetchResponse(
- 'GET',
- new SimpleUrl('http://this.host/this/path/page.html'));
- $this->assertTrue($response->isError());
- }
- }
-
- class TestOfAuthorisation extends UnitTestCase {
-
- function testAuthenticateHeaderAdded() {
- $headers = &new MockSimpleHttpHeaders($this);
- $headers->setReturnValue('getNewCookies', array());
-
- $response = &new MockSimpleHttpResponse($this);
- $response->setReturnReference('getHeaders', $headers);
-
- $request = &new MockSimpleHttpRequest($this);
- $request->setReturnReference('fetch', $response);
- $request->expectOnce(
- 'addHeaderLine',
- array('Authorization: Basic ' . base64_encode('test:secret')));
-
- $agent = &new MockRequestUserAgent($this);
- $agent->setReturnReference('_createHttpRequest', $request);
- $agent->SimpleUserAgent();
-
- $response = &$agent->fetchResponse(
- 'GET',
- new SimpleUrl('http://test:secret@this.host'));
- $request->tally();
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/visual_test.php b/tests/UnitTests/simpletest/test/visual_test.php deleted file mode 100644 index 975ef050..00000000 --- a/tests/UnitTests/simpletest/test/visual_test.php +++ /dev/null @@ -1,386 +0,0 @@ -<?php
- // $Id: visual_test.php,v 1.29 2004/11/22 19:20:00 lastcraft Exp $
-
- // NOTE:
- // Some of these tests are designed to fail! Do not be alarmed.
- // ----------------
-
- // The following tests are a bit hacky. Whilst Kent Beck tried to
- // build a unit tester with a unit tester I am not that brave.
- // Instead I have just hacked together odd test scripts until
- // I have enough of a tester to procede more formally.
- //
- // The proper tests start in all_tests.php
- require_once('../unit_tester.php');
- require_once('../shell_tester.php');
- require_once('../mock_objects.php');
- require_once('../reporter.php');
- require_once('../xml.php');
-
- class TestDisplayClass {
- var $_a;
-
- function TestDisplayClass($a) {
- $this->_a = $a;
- }
- }
-
- class TestOfUnitTestCaseOutput extends UnitTestCase {
-
- function testOfResults() {
- $this->pass('Pass');
- $this->fail('Fail'); // Fail.
- }
-
- function testTrue() {
- $this->assertTrue(true);
- $this->assertTrue(false); // Fail.
- }
-
- function testFalse() {
- $this->assertFalse(true); // Fail.
- $this->assertFalse(false);
- }
-
- function testExpectation() {
- $expectation = &new EqualExpectation(25, 'My expectation message: %s');
- $this->assertExpectation($expectation, 25, 'My assert message : %s');
- $this->assertExpectation($expectation, 24, 'My assert message : %s'); // Fail.
- }
-
- function testNull() {
- $this->assertNull(null, "%s -> Pass");
- $this->assertNull(false, "%s -> Fail"); // Fail.
- $this->assertNotNull(null, "%s -> Fail"); // Fail.
- $this->assertNotNull(false, "%s -> Pass");
- }
-
- function testType() {
- $this->assertIsA("hello", "string", "%s -> Pass");
- $this->assertIsA(14, "string", "%s -> Fail"); // Fail.
- $this->assertIsA($this, "TestOfUnitTestCaseOutput", "%s -> Pass");
- $this->assertIsA($this, "UnitTestCase", "%s -> Pass");
- $this->assertIsA(14, "TestOfUnitTestCaseOutput", "%s -> Fail"); // Fail.
- $this->assertIsA($this, "TestReporter", "%s -> Fail"); // Fail.
- }
-
- function testTypeEquality() {
- $this->assertEqual("0", 0, "%s -> Pass");
- $this->assertNotEqual("0", 0, "%s -> Fail"); // Fail.
- }
-
- function testNullEquality() {
- $this->assertEqual(null, 1, "%s -> Fail"); // Fail.
- $this->assertNotEqual(null, 1, "%s -> Pass");
- $this->assertEqual(1, null, "%s -> Fail"); // Fail.
- $this->assertNotEqual(1, null, "%s -> Pass");
- }
-
- function testIntegerEquality() {
- $this->assertEqual(1, 2, "%s -> Fail"); // Fail.
- $this->assertNotEqual(1, 2, "%s -> Pass");
- }
-
- function testStringEquality() {
- $this->assertEqual("a", "a", "%s -> Pass");
- $this->assertNotEqual("a", "a", "%s -> Fail"); // Fail.
- $this->assertEqual("aa", "ab", "%s -> Fail"); // Fail.
- $this->assertNotEqual("aa", "ab", "%s -> Pass");
- }
-
- function testHashEquality() {
- $this->assertEqual(array("a" => "A", "b" => "B"), array("b" => "B", "a" => "A"), "%s -> Pass");
- $this->assertEqual(array("a" => "A", "b" => "B"), array("b" => "B", "a" => "Z"), "%s -> Pass");
- }
-
- function testStringIdentity() {
- $a = "fred";
- $b = $a;
- $this->assertIdentical($a, $b, "%s -> Pass");
- $this->assertNotIdentical($a, $b, "%s -> Fail"); // Fail.
- }
-
- function testTypeIdentity() {
- $a = "0";
- $b = 0;
- $this->assertIdentical($a, $b, "%s -> Fail"); // Fail.
- $this->assertNotIdentical($a, $b, "%s -> Pass");
- }
-
- function testNullIdentity() {
- $this->assertIdentical(null, 1, "%s -> Fail"); // Fail.
- $this->assertNotIdentical(null, 1, "%s -> Pass");
- $this->assertIdentical(1, null, "%s -> Fail"); // Fail.
- $this->assertNotIdentical(1, null, "%s -> Pass");
- }
-
- function testHashIdentity() {
- $this->assertIdentical(array("a" => "A", "b" => "B"), array("b" => "B", "a" => "A"), "%s -> fail"); // Fail.
- }
-
- function testObjectEquality() {
- $this->assertEqual(new TestDisplayClass(4), new TestDisplayClass(4), "%s -> Pass");
- $this->assertNotEqual(new TestDisplayClass(4), new TestDisplayClass(4), "%s -> Fail"); // Fail.
- $this->assertEqual(new TestDisplayClass(4), new TestDisplayClass(5), "%s -> Fail"); // Fail.
- $this->assertNotEqual(new TestDisplayClass(4), new TestDisplayClass(5), "%s -> Pass");
- }
-
- function testObjectIndentity() {
- $this->assertIdentical(new TestDisplayClass(false), new TestDisplayClass(false), "%s -> Pass");
- $this->assertNotIdentical(new TestDisplayClass(false), new TestDisplayClass(false), "%s -> Fail"); // Fail.
- $this->assertIdentical(new TestDisplayClass(false), new TestDisplayClass(0), "%s -> Fail"); // Fail.
- $this->assertNotIdentical(new TestDisplayClass(false), new TestDisplayClass(0), "%s -> Pass");
- }
-
- function testReference() {
- $a = "fred";
- $b = &$a;
- $this->assertReference($a, $b, "%s -> Pass");
- $this->assertCopy($a, $b, "%s -> Fail"); // Fail.
- $c = "Hello";
- $this->assertReference($a, $c, "%s -> Fail"); // Fail.
- $this->assertCopy($a, $c, "%s -> Pass");
- }
-
- function testPatterns() {
- $this->assertWantedPattern('/hello/i', "Hello there", "%s -> Pass");
- $this->assertNoUnwantedPattern('/hello/', "Hello there", "%s -> Pass");
- $this->assertWantedPattern('/hello/', "Hello there", "%s -> Fail"); // Fail.
- $this->assertNoUnwantedPattern('/hello/i', "Hello there", "%s -> Fail"); // Fail.
- }
-
- function testLongStrings() {
- $text = "";
- for ($i = 0; $i < 10; $i++) {
- $text .= "0123456789";
- }
- $this->assertEqual($text, $text);
- $this->assertEqual($text . $text, $text . "a" . $text); // Fail.
- }
-
- function testErrorDisplay() {
- trigger_error('Default'); // Exception.
- trigger_error('Error', E_USER_ERROR); // Exception.
- trigger_error('Warning', E_USER_WARNING); // Exception.
- trigger_error('Notice', E_USER_NOTICE); // Exception.
- }
-
- function testErrorTrap() {
- $this->assertNoErrors("%s -> Pass");
- $this->assertError(); // Fail.
- trigger_error('Error 1');
- $this->assertNoErrors("%s -> Fail"); // Fail.
- $this->assertError();
- $this->assertNoErrors("%s -> Pass at end");
- }
-
- function testErrorText() {
- trigger_error('Error 2');
- $this->assertError('Error 2', "%s -> Pass");
- trigger_error('Error 3');
- $this->assertError('Error 2', "%s -> Fail"); // Fail.
- }
-
- function testErrorPatterns() {
- trigger_error('Error 2');
- $this->assertErrorPattern('/Error 2/', "%s -> Pass");
- trigger_error('Error 3');
- $this->assertErrorPattern('/Error 2/', "%s -> Fail"); // Fail.
- }
-
- function testDumping() {
- $this->dump(array("Hello"), "Displaying a variable");
- }
-
- function testSignal() {
- $fred = "signal as a string";
- $this->signal("Signal", $fred); // Signal.
- }
- }
-
- class Dummy {
- function Dummy() {
- }
-
- function a() {
- }
- }
- Mock::generate('Dummy');
-
- class TestOfMockObjectsOutput extends UnitTestCase {
-
- function testCallCounts() {
- $dummy = &new MockDummy($this);
- $dummy->expectCallCount('a', 1, 'My message: %s');
- $dummy->a();
- $dummy->tally();
- $dummy->a();
- $dummy->tally();
- }
-
- function testMinimumCallCounts() {
- $dummy = &new MockDummy($this);
- $dummy->expectMinimumCallCount('a', 2, 'My message: %s');
- $dummy->a();
- $dummy->tally();
- $dummy->a();
- $dummy->tally();
- }
-
- function testEmptyMatching() {
- $dummy = &new MockDummy($this);
- $dummy->expectArguments('a', array());
- $dummy->a();
- $dummy->a(null); // Fail.
- }
-
- function testEmptyMatchingWithCustomMessage() {
- $dummy = &new MockDummy($this);
- $dummy->expectArguments('a', array(), 'My expectation message: %s');
- $dummy->a();
- $dummy->a(null); // Fail.
- }
-
- function testNullMatching() {
- $dummy = &new MockDummy($this);
- $dummy->expectArguments('a', array(null));
- $dummy->a(null);
- $dummy->a(); // Fail.
- }
-
- function testBooleanMatching() {
- $dummy = &new MockDummy($this);
- $dummy->expectArguments('a', array(true, false));
- $dummy->a(true, false);
- $dummy->a(true, true); // Fail.
- }
-
- function testIntegerMatching() {
- $dummy = &new MockDummy($this);
- $dummy->expectArguments('a', array(32, 33));
- $dummy->a(32, 33);
- $dummy->a(32, 34); // Fail.
- }
-
- function testFloatMatching() {
- $dummy = &new MockDummy($this);
- $dummy->expectArguments('a', array(3.2, 3.3));
- $dummy->a(3.2, 3.3);
- $dummy->a(3.2, 3.4); // Fail.
- }
-
- function testStringMatching() {
- $dummy = &new MockDummy($this);
- $dummy->expectArguments('a', array('32', '33'));
- $dummy->a('32', '33');
- $dummy->a('32', '34'); // Fail.
- }
-
- function testEmptyMatchingWithCustomExpectationMessage() {
- $dummy = &new MockDummy($this);
- $dummy->expectArguments(
- 'a',
- array(new EqualExpectation('A', 'My part expectation message: %s')),
- 'My expectation message: %s');
- $dummy->a('A');
- $dummy->a('B'); // Fail.
- }
-
- function testArrayMatching() {
- $dummy = &new MockDummy($this);
- $dummy->expectArguments('a', array(array(32), array(33)));
- $dummy->a(array(32), array(33));
- $dummy->a(array(32), array('33')); // Fail.
- }
-
- function testObjectMatching() {
- $a = new Dummy();
- $a->a = 'a';
- $b = new Dummy();
- $b->b = 'b';
- $dummy = &new MockDummy($this);
- $dummy->expectArguments('a', array($a, $b));
- $dummy->a($a, $b);
- $dummy->a($a, $a); // Fail.
- }
-
- function testBigList() {
- $dummy = &new MockDummy($this);
- $dummy->expectArguments('a', array(false, 0, 1, 1.0));
- $dummy->a(false, 0, 1, 1.0);
- $dummy->a(true, false, 2, 2.0); // Fail.
- }
- }
-
- class TestOfPastBugs extends UnitTestCase {
-
- function testMixedTypes() {
- $this->assertEqual(array(), null, "%s -> Pass");
- $this->assertIdentical(array(), null, "%s -> Fail"); // Fail.
- }
-
- function testMockWildcards() {
- $dummy = &new MockDummy($this);
- $dummy->expectArguments('a', array('*', array(33)));
- $dummy->a(array(32), array(33));
- $dummy->a(array(32), array('33')); // Fail.
- }
- }
-
- class TestOfVisualShell extends ShellTestCase {
-
- function testDump() {
- $this->execute('ls');
- $this->dumpOutput();
- $this->execute('dir');
- $this->dumpOutput();
- }
-
- function testDumpOfList() {
- $this->execute('ls');
- $this->dump($this->getOutputAsList());
- }
- }
-
- class AllOutputReporter extends HtmlReporter {
-
- function _getCss() {
- return parent::_getCss() . ' .pass { color: darkgreen; }';
- }
-
- function paintPass($message) {
- parent::paintPass($message);
- print "<span class=\"pass\">Pass</span>: ";
- $breadcrumb = $this->getTestList();
- array_shift($breadcrumb);
- print implode(" -> ", $breadcrumb);
- print " -> " . htmlentities($message) . "<br />\n";
- }
-
- function paintSignal($type, &$payload) {
- print "<span class=\"fail\">$type</span>: ";
- $breadcrumb = $this->getTestList();
- array_shift($breadcrumb);
- print implode(" -> ", $breadcrumb);
- print " -> " . htmlentities(serialize($payload)) . "<br />\n";
- }
- }
-
- $test = &new GroupTest("Visual test with 49 passes, 49 fails and 4 exceptions");
- $test->addTestCase(new TestOfUnitTestCaseOutput());
- $test->addTestCase(new TestOfMockObjectsOutput());
- $test->addTestCase(new TestOfPastBugs());
- $test->addTestCase(new TestOfVisualShell());
-
- if (isset($_GET['xml']) || in_array('xml', (isset($argv) ? $argv : array()))) {
- $reporter = &new XmlReporter();
- } elseif(SimpleReporter::inCli()) {
- $reporter = &new TextReporter();
- } else {
- $reporter = &new AllOutputReporter();
- }
- if (isset($_GET['dry']) || in_array('dry', (isset($argv) ? $argv : array()))) {
- $reporter->makeDry();
- }
- exit ($test->run($reporter) ? 0 : 1);
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/web_tester_test.php b/tests/UnitTests/simpletest/test/web_tester_test.php deleted file mode 100644 index 6d3219c5..00000000 --- a/tests/UnitTests/simpletest/test/web_tester_test.php +++ /dev/null @@ -1,133 +0,0 @@ -<?php
- // $Id: web_tester_test.php,v 1.10 2004/11/30 05:34:00 lastcraft Exp $
-
- class TestOfFieldExpectation extends UnitTestCase {
-
- function testStringMatchingIsCaseSensitive() {
- $expectation = new FieldExpectation('a');
- $this->assertTrue($expectation->test('a'));
- $this->assertTrue($expectation->test(array('a')));
- $this->assertFalse($expectation->test('A'));
- }
-
- function testMatchesInteger() {
- $expectation = new FieldExpectation('1');
- $this->assertTrue($expectation->test('1'));
- $this->assertTrue($expectation->test(1));
- $this->assertTrue($expectation->test(array('1')));
- $this->assertTrue($expectation->test(array(1)));
- }
-
- function testNonStringFailsExpectation() {
- $expectation = new FieldExpectation('a');
- $this->assertFalse($expectation->test(null));
- }
-
- function testUnsetFieldCanBeTestedFor() {
- $expectation = new FieldExpectation(false);
- $this->assertTrue($expectation->test(false));
- }
-
- function testMultipleValuesCanBeInAnyOrder() {
- $expectation = new FieldExpectation(array('a', 'b'));
- $this->assertTrue($expectation->test(array('a', 'b')));
- $this->assertTrue($expectation->test(array('b', 'a')));
- $this->assertFalse($expectation->test(array('a', 'a')));
- $this->assertFalse($expectation->test('a'));
- }
-
- function testSingleItemCanBeArrayOrString() {
- $expectation = new FieldExpectation(array('a'));
- $this->assertTrue($expectation->test(array('a')));
- $this->assertTrue($expectation->test('a'));
- }
- }
-
- class TestOfHeaderExpectations extends UnitTestCase {
-
- function testExpectingOnlyTheHeaderName() {
- $expectation = new HttpHeaderExpectation('a');
- $this->assertIdentical($expectation->test(false), false);
- $this->assertIdentical($expectation->test('a: A'), true);
- $this->assertIdentical($expectation->test('A: A'), true);
- $this->assertIdentical($expectation->test('a: B'), true);
- $this->assertIdentical($expectation->test(' a : A '), true);
- }
-
- function testHeaderValueAsWell() {
- $expectation = new HttpHeaderExpectation('a', 'A');
- $this->assertIdentical($expectation->test(false), false);
- $this->assertIdentical($expectation->test('a: A'), true);
- $this->assertIdentical($expectation->test('A: A'), true);
- $this->assertIdentical($expectation->test('A: a'), false);
- $this->assertIdentical($expectation->test('a: B'), false);
- $this->assertIdentical($expectation->test(' a : A '), true);
- $this->assertIdentical($expectation->test(' a : AB '), false);
- }
-
- function testMultilineSearch() {
- $expectation = new HttpHeaderExpectation('a', 'A');
- $this->assertIdentical($expectation->test("aa: A\r\nb: B\r\nc: C"), false);
- $this->assertIdentical($expectation->test("aa: A\r\na: A\r\nb: B"), true);
- }
-
- function testMultilineSearchWithPadding() {
- $expectation = new HttpHeaderExpectation('a', ' A ');
- $this->assertIdentical($expectation->test("aa:A\r\nb:B\r\nc:C"), false);
- $this->assertIdentical($expectation->test("aa:A\r\na:A\r\nb:B"), true);
- }
-
- function testPatternMatching() {
- $expectation = new HttpHeaderPatternExpectation('a', '/A/');
- $this->assertIdentical($expectation->test('a: A'), true);
- $this->assertIdentical($expectation->test('A: A'), true);
- $this->assertIdentical($expectation->test('A: a'), false);
- $this->assertIdentical($expectation->test('a: B'), false);
- $this->assertIdentical($expectation->test(' a : A '), true);
- $this->assertIdentical($expectation->test(' a : AB '), true);
- }
-
- function testCaseInsensitivePatternMatching() {
- $expectation = new HttpHeaderPatternExpectation('a', '/A/i');
- $this->assertIdentical($expectation->test('a: a'), true);
- $this->assertIdentical($expectation->test('a: B'), false);
- $this->assertIdentical($expectation->test(' a : A '), true);
- $this->assertIdentical($expectation->test(' a : BAB '), true);
- $this->assertIdentical($expectation->test(' a : bab '), true);
- }
-
- function testUnwantedHeader() {
- $expectation = new HttpUnwantedHeaderExpectation('a');
- $this->assertIdentical($expectation->test(''), true);
- $this->assertIdentical($expectation->test('stuff'), true);
- $this->assertIdentical($expectation->test('b: B'), true);
- $this->assertIdentical($expectation->test('a: A'), false);
- $this->assertIdentical($expectation->test('A: A'), false);
- }
-
- function testMultilineUnwantedSearch() {
- $expectation = new HttpUnwantedHeaderExpectation('a');
- $this->assertIdentical($expectation->test("aa:A\r\nb:B\r\nc:C"), true);
- $this->assertIdentical($expectation->test("aa:A\r\na:A\r\nb:B"), false);
- }
- }
-
- class TestOfTextExpectations extends UnitTestCase {
-
- function testMatchingSubString() {
- $expectation = new WantedTextExpectation('wanted');
- $this->assertIdentical($expectation->test(''), false);
- $this->assertIdentical($expectation->test('Wanted'), false);
- $this->assertIdentical($expectation->test('wanted'), true);
- $this->assertIdentical($expectation->test('the wanted text is here'), true);
- }
-
- function testNotMatchingSubString() {
- $expectation = new UnwantedTextExpectation('wanted');
- $this->assertIdentical($expectation->test(''), true);
- $this->assertIdentical($expectation->test('Wanted'), true);
- $this->assertIdentical($expectation->test('wanted'), false);
- $this->assertIdentical($expectation->test('the wanted text is here'), false);
- }
- }
-?>
\ No newline at end of file diff --git a/tests/UnitTests/simpletest/test/xml_test.php b/tests/UnitTests/simpletest/test/xml_test.php deleted file mode 100644 index afb3faf9..00000000 --- a/tests/UnitTests/simpletest/test/xml_test.php +++ /dev/null @@ -1,189 +0,0 @@ -<?php
- // $Id: xml_test.php,v 1.16 2004/09/24 22:55:38 lastcraft Exp $
-
- require_once(dirname(__FILE__) . '/../xml.php');
-
- Mock::generate('SimpleScorer');
-
- if (! function_exists('xml_parser_create')) {
- SimpleTestOptions::ignore('TestOfXmlStructureParsing');
- SimpleTestOptions::ignore('TestOfXmlResultsParsing');
- }
-
- class TestOfNestingTags extends UnitTestCase {
-
- function testGroupSize() {
- $nesting = new NestingGroupTag(array('SIZE' => 2));
- $this->assertEqual($nesting->getSize(), 2);
- }
- }
-
- class TestOfXmlStructureParsing extends UnitTestCase {
-
- function testValidXml() {
- $listener = &new MockSimpleScorer($this);
- $listener->expectNever('paintGroupStart');
- $listener->expectNever('paintGroupEnd');
- $listener->expectNever('paintCaseStart');
- $listener->expectNever('paintCaseEnd');
- $parser = &new SimpleTestXmlParser($listener);
- $this->assertTrue($parser->parse("<?xml version=\"1.0\"?>\n"));
- $this->assertTrue($parser->parse("<run>\n"));
- $this->assertTrue($parser->parse("</run>\n"));
- }
-
- function testEmptyGroup() {
- $listener = &new MockSimpleScorer($this);
- $listener->expectOnce('paintGroupStart', array('a_group', 7));
- $listener->expectOnce('paintGroupEnd', array('a_group'));
- $parser = &new SimpleTestXmlParser($listener);
- $parser->parse("<?xml version=\"1.0\"?>\n");
- $parser->parse("<run>\n");
- $this->assertTrue($parser->parse("<group size=\"7\">\n"));
- $this->assertTrue($parser->parse("<name>a_group</name>\n"));
- $this->assertTrue($parser->parse("</group>\n"));
- $parser->parse("</run>\n");
- $listener->tally();
- }
-
- function testEmptyCase() {
- $listener = &new MockSimpleScorer($this);
- $listener->expectOnce('paintCaseStart', array('a_case'));
- $listener->expectOnce('paintCaseEnd', array('a_case'));
- $parser = &new SimpleTestXmlParser($listener);
- $parser->parse("<?xml version=\"1.0\"?>\n");
- $parser->parse("<run>\n");
- $this->assertTrue($parser->parse("<case>\n"));
- $this->assertTrue($parser->parse("<name>a_case</name>\n"));
- $this->assertTrue($parser->parse("</case>\n"));
- $parser->parse("</run>\n");
- $listener->tally();
- }
-
- function testEmptyMethod() {
- $listener = &new MockSimpleScorer($this);
- $listener->expectOnce('paintCaseStart', array('a_case'));
- $listener->expectOnce('paintCaseEnd', array('a_case'));
- $listener->expectOnce('paintMethodStart', array('a_method'));
- $listener->expectOnce('paintMethodEnd', array('a_method'));
- $parser = &new SimpleTestXmlParser($listener);
- $parser->parse("<?xml version=\"1.0\"?>\n");
- $parser->parse("<run>\n");
- $parser->parse("<case>\n");
- $parser->parse("<name>a_case</name>\n");
- $this->assertTrue($parser->parse("<test>\n"));
- $this->assertTrue($parser->parse("<name>a_method</name>\n"));
- $this->assertTrue($parser->parse("</test>\n"));
- $parser->parse("</case>\n");
- $parser->parse("</run>\n");
- $listener->tally();
- }
-
- function testNestedGroup() {
- $listener = &new MockSimpleScorer($this);
- $listener->expectArgumentsAt(0, 'paintGroupStart', array('a_group', 7));
- $listener->expectArgumentsAt(1, 'paintGroupStart', array('b_group', 3));
- $listener->expectCallCount('paintGroupStart', 2);
- $listener->expectArgumentsAt(0, 'paintGroupEnd', array('b_group'));
- $listener->expectArgumentsAt(1, 'paintGroupEnd', array('a_group'));
- $listener->expectCallCount('paintGroupEnd', 2);
- $parser = &new SimpleTestXmlParser($listener);
- $parser->parse("<?xml version=\"1.0\"?>\n");
- $parser->parse("<run>\n");
- $this->assertTrue($parser->parse("<group size=\"7\">\n"));
- $this->assertTrue($parser->parse("<name>a_group</name>\n"));
- $this->assertTrue($parser->parse("<group size=\"3\">\n"));
- $this->assertTrue($parser->parse("<name>b_group</name>\n"));
- $this->assertTrue($parser->parse("</group>\n"));
- $this->assertTrue($parser->parse("</group>\n"));
- $parser->parse("</run>\n");
- $listener->tally();
- }
- }
-
- class AnyOldSignal {
- var $stuff = true;
- }
-
- class TestOfXmlResultsParsing extends UnitTestCase {
-
- function sendValidStart(&$parser) {
- $parser->parse("<?xml version=\"1.0\"?>\n");
- $parser->parse("<run>\n");
- $parser->parse("<case>\n");
- $parser->parse("<name>a_case</name>\n");
- $parser->parse("<test>\n");
- $parser->parse("<name>a_method</name>\n");
- }
-
- function sendValidEnd(&$parser) {
- $parser->parse("</test>\n");
- $parser->parse("</case>\n");
- $parser->parse("</run>\n");
- }
-
- function testPass() {
- $listener = &new MockSimpleScorer($this);
- $listener->expectOnce('paintPass', array('a_message'));
- $parser = &new SimpleTestXmlParser($listener);
- $this->sendValidStart($parser);
- $this->assertTrue($parser->parse("<pass>a_message</pass>\n"));
- $this->sendValidEnd($parser);
- $listener->tally();
- }
-
- function testFail() {
- $listener = &new MockSimpleScorer($this);
- $listener->expectOnce('paintFail', array('a_message'));
- $parser = &new SimpleTestXmlParser($listener);
- $this->sendValidStart($parser);
- $this->assertTrue($parser->parse("<fail>a_message</fail>\n"));
- $this->sendValidEnd($parser);
- $listener->tally();
- }
-
- function testException() {
- $listener = &new MockSimpleScorer($this);
- $listener->expectOnce('paintException', array('a_message'));
- $parser = &new SimpleTestXmlParser($listener);
- $this->sendValidStart($parser);
- $this->assertTrue($parser->parse("<exception>a_message</exception>\n"));
- $this->sendValidEnd($parser);
- $listener->tally();
- }
-
- function testSignal() {
- $signal = new AnyOldSignal();
- $signal->stuff = "Hello";
- $listener = &new MockSimpleScorer($this);
- $listener->expectOnce('paintSignal', array('a_signal', $signal));
- $parser = &new SimpleTestXmlParser($listener);
- $this->sendValidStart($parser);
- $this->assertTrue($parser->parse(
- "<signal type=\"a_signal\"><![CDATA[" .
- serialize($signal) . "]]></signal>\n"));
- $this->sendValidEnd($parser);
- $listener->tally();
- }
-
- function testMessage() {
- $listener = &new MockSimpleScorer($this);
- $listener->expectOnce('paintMessage', array('a_message'));
- $parser = &new SimpleTestXmlParser($listener);
- $this->sendValidStart($parser);
- $this->assertTrue($parser->parse("<message>a_message</message>\n"));
- $this->sendValidEnd($parser);
- $listener->tally();
- }
-
- function testFormattedMessage() {
- $listener = &new MockSimpleScorer($this);
- $listener->expectOnce('paintFormattedMessage', array("\na\tmessage\n"));
- $parser = &new SimpleTestXmlParser($listener);
- $this->sendValidStart($parser);
- $this->assertTrue($parser->parse("<formatted><![CDATA[\na\tmessage\n]]></formatted>\n"));
- $this->sendValidEnd($parser);
- $listener->tally();
- }
- }
-?>
\ No newline at end of file |