diff options
author | Tom Needham <needham.thomas@gmail.com> | 2012-09-13 10:23:41 +0000 |
---|---|---|
committer | Tom Needham <needham.thomas@gmail.com> | 2012-09-13 10:23:41 +0000 |
commit | 227ada32576b7b9de56efe1f5d9ae96c6493be52 (patch) | |
tree | 41f3a88fb646488e043ba638e92e7f313d2cb64c /3rdparty | |
parent | fa5dff22a02aeb5985215454549ab1020382b197 (diff) | |
parent | 5a149dcfab960fe21c2df1bf4f1ba27f1a10b2c8 (diff) | |
download | nextcloud-server-227ada32576b7b9de56efe1f5d9ae96c6493be52.tar.gz nextcloud-server-227ada32576b7b9de56efe1f5d9ae96c6493be52.zip |
Fix merge conflicts
Diffstat (limited to '3rdparty')
41 files changed, 5941 insertions, 59 deletions
diff --git a/3rdparty/MDB2/Driver/Datatype/Common.php b/3rdparty/MDB2/Driver/Datatype/Common.php index 3b02c86acda..dd7f1c7e0a9 100644 --- a/3rdparty/MDB2/Driver/Datatype/Common.php +++ b/3rdparty/MDB2/Driver/Datatype/Common.php @@ -1412,7 +1412,7 @@ class MDB2_Driver_Datatype_Common extends MDB2_Module_Common if (PEAR::isError($db)) { return $db; } - if (isset($db->function) && is_object($this->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { + if (isset($db->function) && is_object($db->function) && is_a($db->function, 'MDB2_Driver_Function_Common')) { return $db->function->now('timestamp'); } return 'CURRENT_TIMESTAMP'; diff --git a/3rdparty/MDB2/Driver/Datatype/oci8.php b/3rdparty/MDB2/Driver/Datatype/oci8.php new file mode 100644 index 00000000000..4d2e792a80e --- /dev/null +++ b/3rdparty/MDB2/Driver/Datatype/oci8.php @@ -0,0 +1,499 @@ +<?php +// +----------------------------------------------------------------------+ +// | PHP versions 4 and 5 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1998-2007 Manuel Lemos, Tomas V.V.Cox, | +// | Stig. S. Bakken, Lukas Smith | +// | All rights reserved. | +// +----------------------------------------------------------------------+ +// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB | +// | API as well as database abstraction for PHP applications. | +// | This LICENSE is in the BSD license style. | +// | | +// | 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 Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken, | +// | Lukas Smith nor the names of his contributors may be used to endorse | +// | or promote products derived from this software without specific prior| +// | written permission. | +// | | +// | 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 | +// | REGENTS 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. | +// +----------------------------------------------------------------------+ +// | Author: Lukas Smith <smith@pooteeweet.org> | +// +----------------------------------------------------------------------+ + +// $Id: oci8.php 295587 2010-02-28 17:16:38Z quipo $ + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 OCI8 driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_Driver_Datatype_oci8 extends MDB2_Driver_Datatype_Common +{ + // {{{ _baseConvertResult() + + /** + * general type conversion method + * + * @param mixed $value refernce to a value to be converted + * @param string $type specifies which type to convert to + * @param boolean $rtrim [optional] when TRUE [default], apply rtrim() to text + * @return object a MDB2 error on failure + * @access protected + */ + function _baseConvertResult($value, $type, $rtrim = true) + { + if (null === $value) { + return null; + } + switch ($type) { + case 'text': + if (is_object($value) && is_a($value, 'OCI-Lob')) { + //LOB => fetch into variable + $clob = $this->_baseConvertResult($value, 'clob', $rtrim); + if (!PEAR::isError($clob) && is_resource($clob)) { + $clob_value = ''; + while (!feof($clob)) { + $clob_value .= fread($clob, 8192); + } + $this->destroyLOB($clob); + } + $value = $clob_value; + } + if ($rtrim) { + $value = rtrim($value); + } + return $value; + case 'date': + return substr($value, 0, strlen('YYYY-MM-DD')); + case 'time': + return substr($value, strlen('YYYY-MM-DD '), strlen('HH:MI:SS')); + } + return parent::_baseConvertResult($value, $type, $rtrim); + } + + // }}} + // {{{ getTypeDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an text type + * field to be used in statements like CREATE TABLE. + * + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access public + */ + function getTypeDeclaration($field) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + switch ($field['type']) { + case 'text': + $length = !empty($field['length']) + ? $field['length'] : $db->options['default_text_field_length']; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? 'CHAR('.$length.')' : 'VARCHAR2('.$length.')'; + case 'clob': + return 'CLOB'; + case 'blob': + return 'BLOB'; + case 'integer': + if (!empty($field['length'])) { + switch((int)$field['length']) { + case 1: $digit = 3; break; + case 2: $digit = 5; break; + case 3: $digit = 8; break; + case 4: $digit = 10; break; + case 5: $digit = 13; break; + case 6: $digit = 15; break; + case 7: $digit = 17; break; + case 8: $digit = 20; break; + default: $digit = 10; + } + return 'NUMBER('.$digit.')'; + } + return 'INT'; + case 'boolean': + return 'NUMBER(1)'; + case 'date': + case 'time': + case 'timestamp': + return 'DATE'; + case 'float': + return 'NUMBER'; + case 'decimal': + $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places']; + return 'NUMBER(*,'.$scale.')'; + } + } + + // }}} + // {{{ _quoteCLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteCLOB($value, $quote, $escape_wildcards) + { + return 'EMPTY_CLOB()'; + } + + // }}} + // {{{ _quoteBLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBLOB($value, $quote, $escape_wildcards) + { + return 'EMPTY_BLOB()'; + } + + // }}} + // {{{ _quoteDate() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteDate($value, $quote, $escape_wildcards) + { + return $this->_quoteText("$value 00:00:00", $quote, $escape_wildcards); + } + + // }}} + // {{{ _quoteTimestamp() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + //function _quoteTimestamp($value, $quote, $escape_wildcards) + //{ + // return $this->_quoteText($value, $quote, $escape_wildcards); + //} + + // }}} + // {{{ _quoteTime() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @param bool $quote determines if the value should be quoted and escaped + * @param bool $escape_wildcards if to escape escape wildcards + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteTime($value, $quote, $escape_wildcards) + { + return $this->_quoteText("0001-01-01 $value", $quote, $escape_wildcards); + } + + // }}} + // {{{ writeLOBToFile() + + /** + * retrieve LOB from the database + * + * @param array $lob array + * @param string $file name of the file into which the LOb should be fetched + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function writeLOBToFile($lob, $file) + { + if (preg_match('/^(\w+:\/\/)(.*)$/', $file, $match)) { + if ($match[1] == 'file://') { + $file = $match[2]; + } + } + $lob_data = stream_get_meta_data($lob); + $lob_index = $lob_data['wrapper_data']->lob_index; + $result = $this->lobs[$lob_index]['resource']->writetofile($file); + $this->lobs[$lob_index]['resource']->seek(0); + if (!$result) { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(null, null, null, + 'Unable to write LOB to file', __FUNCTION__); + } + return MDB2_OK; + } + + // }}} + // {{{ _retrieveLOB() + + /** + * retrieve LOB from the database + * + * @param array $lob array + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function _retrieveLOB(&$lob) + { + if (!is_object($lob['resource'])) { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'attemped to retrieve LOB from non existing or NULL column', __FUNCTION__); + } + + if (!$lob['loaded'] +# && !method_exists($lob['resource'], 'read') + ) { + $lob['value'] = $lob['resource']->load(); + $lob['resource']->seek(0); + } + $lob['loaded'] = true; + return MDB2_OK; + } + + // }}} + // {{{ _readLOB() + + /** + * Read data from large object input stream. + * + * @param array $lob array + * @param blob $data reference to a variable that will hold data to be + * read from the large object input stream + * @param int $length integer value that indicates the largest ammount of + * data to be read from the large object input stream. + * @return mixed length on success, a MDB2 error on failure + * @access protected + */ + function _readLOB($lob, $length) + { + if ($lob['loaded']) { + return parent::_readLOB($lob, $length); + } + + if (!is_object($lob['resource'])) { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'attemped to retrieve LOB from non existing or NULL column', __FUNCTION__); + } + + $data = $lob['resource']->read($length); + if (!is_string($data)) { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(null, null, null, + 'Unable to read LOB', __FUNCTION__); + } + return $data; + } + + // }}} + // {{{ patternEscapeString() + + /** + * build string to define escape pattern string + * + * @access public + * + * + * @return string define escape pattern + */ + function patternEscapeString() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + return " ESCAPE '". $db->string_quoting['escape_pattern'] ."'"; + } + + // }}} + // {{{ _mapNativeDatatype() + + /** + * Maps a native array description of a field to a MDB2 datatype and length + * + * @param array $field native field description + * @return array containing the various possible types, length, sign, fixed + * @access public + */ + function _mapNativeDatatype($field) + { + $db_type = strtolower($field['type']); + $type = array(); + $length = $unsigned = $fixed = null; + if (!empty($field['length'])) { + $length = $field['length']; + } + switch ($db_type) { + case 'integer': + case 'pls_integer': + case 'binary_integer': + $type[] = 'integer'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + break; + case 'varchar': + case 'varchar2': + case 'nvarchar2': + $fixed = false; + case 'char': + case 'nchar': + $type[] = 'text'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'date': + case 'timestamp': + $type[] = 'timestamp'; + $length = null; + break; + case 'float': + $type[] = 'float'; + break; + case 'number': + if (!empty($field['scale'])) { + $type[] = 'decimal'; + $length = $length.','.$field['scale']; + } else { + $type[] = 'integer'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } + } + break; + case 'long': + $type[] = 'text'; + case 'clob': + case 'nclob': + $type[] = 'clob'; + break; + case 'blob': + case 'raw': + case 'long raw': + case 'bfile': + $type[] = 'blob'; + $length = null; + break; + case 'rowid': + case 'urowid': + default: + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'unknown database attribute type: '.$db_type, __FUNCTION__); + } + + if ((int)$length <= 0) { + $length = null; + } + + return array($type, $length, $unsigned, $fixed); + } +} + +?>
\ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Function/oci8.php b/3rdparty/MDB2/Driver/Function/oci8.php new file mode 100644 index 00000000000..757d17fcb8b --- /dev/null +++ b/3rdparty/MDB2/Driver/Function/oci8.php @@ -0,0 +1,187 @@ +<?php +// +----------------------------------------------------------------------+ +// | PHP versions 4 and 5 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox, | +// | Stig. S. Bakken, Lukas Smith | +// | All rights reserved. | +// +----------------------------------------------------------------------+ +// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB | +// | API as well as database abstraction for PHP applications. | +// | This LICENSE is in the BSD license style. | +// | | +// | 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 Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken, | +// | Lukas Smith nor the names of his contributors may be used to endorse | +// | or promote products derived from this software without specific prior| +// | written permission. | +// | | +// | 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 | +// | REGENTS 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. | +// +----------------------------------------------------------------------+ +// | Author: Lukas Smith <smith@pooteeweet.org> | +// +----------------------------------------------------------------------+ + +// $Id: oci8.php 295587 2010-02-28 17:16:38Z quipo $ + +require_once 'MDB2/Driver/Function/Common.php'; + +/** + * MDB2 oci8 driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_Driver_Function_oci8 extends MDB2_Driver_Function_Common +{ + // {{{ executeStoredProc() + + /** + * Execute a stored procedure and return any results + * + * @param string $name string that identifies the function to execute + * @param mixed $params array that contains the paramaters to pass the stored proc + * @param mixed $types array that contains the types of the columns in + * the result set + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * @return mixed a result handle or MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function executeStoredProc($name, $params = null, $types = null, $result_class = true, $result_wrap_class = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'EXEC '.$name; + $query .= $params ? '('.implode(', ', $params).')' : '()'; + return $db->query($query, $types, $result_class, $result_wrap_class); + } + + // }}} + // {{{ functionTable() + + /** + * return string for internal table used when calling only a function + * + * @return string for internal table used when calling only a function + * @access public + */ + function functionTable() + { + return ' FROM dual'; + } + + // }}} + // {{{ now() + + /** + * Return string to call a variable with the current timestamp inside an SQL statement + * There are three special variables for current date and time: + * - CURRENT_TIMESTAMP (date and time, TIMESTAMP type) + * - CURRENT_DATE (date, DATE type) + * - CURRENT_TIME (time, TIME type) + * + * @return string to call a variable with the current timestamp + * @access public + */ + function now($type = 'timestamp') + { + switch ($type) { + case 'date': + case 'time': + case 'timestamp': + default: + return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')'; + } + } + + // }}} + // {{{ unixtimestamp() + + /** + * return string to call a function to get the unix timestamp from a iso timestamp + * + * @param string $expression + * + * @return string to call a variable with the timestamp + * @access public + */ + function unixtimestamp($expression) + { + $utc_offset = 'CAST(SYS_EXTRACT_UTC(SYSTIMESTAMP) AS DATE) - CAST(SYSTIMESTAMP AS DATE)'; + $epoch_date = 'to_date(\'19700101\', \'YYYYMMDD\')'; + return '(CAST('.$expression.' AS DATE) - '.$epoch_date.' + '.$utc_offset.') * 86400 seconds'; + } + + // }}} + // {{{ substring() + + /** + * return string to call a function to get a substring inside an SQL statement + * + * @return string to call a function to get a substring + * @access public + */ + function substring($value, $position = 1, $length = null) + { + if (null !== $length) { + return "SUBSTR($value, $position, $length)"; + } + return "SUBSTR($value, $position)"; + } + + // }}} + // {{{ random() + + /** + * return string to call a function to get random value inside an SQL statement + * + * @return return string to generate float between 0 and 1 + * @access public + */ + function random() + { + return 'dbms_random.value'; + } + + // }}}} + // {{{ guid() + + /** + * Returns global unique identifier + * + * @return string to get global unique identifier + * @access public + */ + function guid() + { + return 'SYS_GUID()'; + } + + // }}}} +} +?>
\ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Manager/oci8.php b/3rdparty/MDB2/Driver/Manager/oci8.php new file mode 100644 index 00000000000..90ae8eb2302 --- /dev/null +++ b/3rdparty/MDB2/Driver/Manager/oci8.php @@ -0,0 +1,1340 @@ +<?php +// +----------------------------------------------------------------------+ +// | PHP versions 4 and 5 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox, | +// | Stig. S. Bakken, Lukas Smith | +// | All rights reserved. | +// +----------------------------------------------------------------------+ +// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB | +// | API as well as database abstraction for PHP applications. | +// | This LICENSE is in the BSD license style. | +// | | +// | 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 Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken, | +// | Lukas Smith nor the names of his contributors may be used to endorse | +// | or promote products derived from this software without specific prior| +// | written permission. | +// | | +// | 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 | +// | REGENTS 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. | +// +----------------------------------------------------------------------+ +// | Author: Lukas Smith <smith@pooteeweet.org> | +// +----------------------------------------------------------------------+ + +// $Id: oci8.php 295587 2010-02-28 17:16:38Z quipo $ + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 oci8 driver for the management modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_Driver_Manager_oci8 extends MDB2_Driver_Manager_Common +{ + // {{{ createDatabase() + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @param array $options array with charset, collation info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($name, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $username = $db->options['database_name_prefix'].$name; + $password = $db->dsn['password'] ? $db->dsn['password'] : $name; + $tablespace = $db->options['default_tablespace'] + ? ' DEFAULT TABLESPACE '.$db->options['default_tablespace'] : ''; + + $query = 'CREATE USER '.$username.' IDENTIFIED BY '.$password.$tablespace; + $result = $db->standaloneQuery($query, null, true); + if (PEAR::isError($result)) { + return $result; + } + $query = 'GRANT CREATE SESSION, CREATE TABLE, UNLIMITED TABLESPACE, CREATE SEQUENCE, CREATE TRIGGER TO '.$username; + $result = $db->standaloneQuery($query, null, true); + if (PEAR::isError($result)) { + $query = 'DROP USER '.$username.' CASCADE'; + $result2 = $db->standaloneQuery($query, null, true); + if (PEAR::isError($result2)) { + return $db->raiseError($result2, null, null, + 'could not setup the database user', __FUNCTION__); + } + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ alterDatabase() + + /** + * alter an existing database + * + * IMPORTANT: the safe way to change the db charset is to do a full import/export! + * If - and only if - the new character set is a strict superset of the current + * character set, it is possible to use the ALTER DATABASE CHARACTER SET to + * expedite the change in the database character set. + * + * @param string $name name of the database that is intended to be changed + * @param array $options array with name, charset info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function alterDatabase($name, $options = array()) + { + //disabled + //return parent::alterDatabase($name, $options); + + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!empty($options['name'])) { + $query = 'ALTER DATABASE ' . $db->quoteIdentifier($name, true) + .' RENAME GLOBAL_NAME TO ' . $db->quoteIdentifier($options['name'], true); + $result = $db->standaloneQuery($query); + if (PEAR::isError($result)) { + return $result; + } + } + + if (!empty($options['charset'])) { + $queries = array(); + $queries[] = 'SHUTDOWN IMMEDIATE'; //or NORMAL + $queries[] = 'STARTUP MOUNT'; + $queries[] = 'ALTER SYSTEM ENABLE RESTRICTED SESSION'; + $queries[] = 'ALTER SYSTEM SET JOB_QUEUE_PROCESSES=0'; + $queries[] = 'ALTER DATABASE OPEN'; + $queries[] = 'ALTER DATABASE CHARACTER SET ' . $options['charset']; + $queries[] = 'ALTER DATABASE NATIONAL CHARACTER SET ' . $options['charset']; + $queries[] = 'SHUTDOWN IMMEDIATE'; //or NORMAL + $queries[] = 'STARTUP'; + + foreach ($queries as $query) { + $result = $db->standaloneQuery($query); + if (PEAR::isError($result)) { + return $result; + } + } + } + + return MDB2_OK; + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param object $db database object that is extended by this class + * @param string $name name of the database that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $username = $db->options['database_name_prefix'].$name; + return $db->standaloneQuery('DROP USER '.$username.' CASCADE', null, true); + } + + + // }}} + // {{{ _makeAutoincrement() + + /** + * add an autoincrement sequence + trigger + * + * @param string $name name of the PK field + * @param string $table name of the table + * @param string $start start value for the sequence + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _makeAutoincrement($name, $table, $start = 1) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table_uppercase = strtoupper($table); + $index_name = $table_uppercase . '_AI_PK'; + $definition = array( + 'primary' => true, + 'fields' => array($name => true), + ); + $idxname_format = $db->getOption('idxname_format'); + $db->setOption('idxname_format', '%s'); + $result = $this->createConstraint($table, $index_name, $definition); + $db->setOption('idxname_format', $idxname_format); + if (PEAR::isError($result)) { + return $db->raiseError($result, null, null, + 'primary key for autoincrement PK could not be created', __FUNCTION__); + } + + if (null === $start) { + $db->beginTransaction(); + $query = 'SELECT MAX(' . $db->quoteIdentifier($name, true) . ') FROM ' . $db->quoteIdentifier($table, true); + $start = $this->db->queryOne($query, 'integer'); + if (PEAR::isError($start)) { + return $start; + } + ++$start; + $result = $this->createSequence($table, $start); + $db->commit(); + } else { + $result = $this->createSequence($table, $start); + } + if (PEAR::isError($result)) { + return $db->raiseError($result, null, null, + 'sequence for autoincrement PK could not be created', __FUNCTION__); + } + $seq_name = $db->getSequenceName($table); + $trigger_name = $db->quoteIdentifier($table_uppercase . '_AI_PK', true); + $seq_name_quoted = $db->quoteIdentifier($seq_name, true); + $table = $db->quoteIdentifier($table, true); + $name = $db->quoteIdentifier($name, true); + $trigger_sql = ' +CREATE TRIGGER '.$trigger_name.' + BEFORE INSERT + ON '.$table.' + FOR EACH ROW +DECLARE + last_Sequence NUMBER; + last_InsertID NUMBER; +BEGIN + SELECT '.$seq_name_quoted.'.NEXTVAL INTO :NEW.'.$name.' FROM DUAL; + IF (:NEW.'.$name.' IS NULL OR :NEW.'.$name.' = 0) THEN + SELECT '.$seq_name_quoted.'.NEXTVAL INTO :NEW.'.$name.' FROM DUAL; + ELSE + SELECT NVL(Last_Number, 0) INTO last_Sequence + FROM User_Sequences + WHERE UPPER(Sequence_Name) = UPPER(\''.$seq_name.'\'); + SELECT :NEW.'.$name.' INTO last_InsertID FROM DUAL; + WHILE (last_InsertID > last_Sequence) LOOP + SELECT '.$seq_name_quoted.'.NEXTVAL INTO last_Sequence FROM DUAL; + END LOOP; + END IF; +END; +'; + $result = $db->exec($trigger_sql); + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ _dropAutoincrement() + + /** + * drop an existing autoincrement sequence + trigger + * + * @param string $table name of the table + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _dropAutoincrement($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = strtoupper($table); + $trigger_name = $table . '_AI_PK'; + $trigger_name_quoted = $db->quote($trigger_name, 'text'); + $query = 'SELECT trigger_name FROM user_triggers'; + $query.= ' WHERE trigger_name='.$trigger_name_quoted.' OR trigger_name='.strtoupper($trigger_name_quoted); + $trigger = $db->queryOne($query); + if (PEAR::isError($trigger)) { + return $trigger; + } + + if ($trigger) { + $trigger_name = $db->quoteIdentifier($table . '_AI_PK', true); + $trigger_sql = 'DROP TRIGGER ' . $trigger_name; + $result = $db->exec($trigger_sql); + if (PEAR::isError($result)) { + return $db->raiseError($result, null, null, + 'trigger for autoincrement PK could not be dropped', __FUNCTION__); + } + + $result = $this->dropSequence($table); + if (PEAR::isError($result)) { + return $db->raiseError($result, null, null, + 'sequence for autoincrement PK could not be dropped', __FUNCTION__); + } + + $index_name = $table . '_AI_PK'; + $idxname_format = $db->getOption('idxname_format'); + $db->setOption('idxname_format', '%s'); + $result1 = $this->dropConstraint($table, $index_name); + $db->setOption('idxname_format', $idxname_format); + $result2 = $this->dropConstraint($table, $index_name); + if (PEAR::isError($result1) && PEAR::isError($result2)) { + return $db->raiseError($result1, null, null, + 'primary key for autoincrement PK could not be dropped', __FUNCTION__); + } + } + + return MDB2_OK; + } + + // }}} + // {{{ _getTemporaryTableQuery() + + /** + * A method to return the required SQL string that fits between CREATE ... TABLE + * to create the table as a temporary table. + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + function _getTemporaryTableQuery() + { + return 'GLOBAL TEMPORARY'; + } + + // }}} + // {{{ _getAdvancedFKOptions() + + /** + * Return the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param array $definition + * @return string + * @access protected + */ + function _getAdvancedFKOptions($definition) + { + $query = ''; + if (!empty($definition['ondelete']) && (strtoupper($definition['ondelete']) != 'NO ACTION')) { + $query .= ' ON DELETE '.$definition['ondelete']; + } + if (!empty($definition['deferrable'])) { + $query .= ' DEFERRABLE'; + } else { + $query .= ' NOT DEFERRABLE'; + } + if (!empty($definition['initiallydeferred'])) { + $query .= ' INITIALLY DEFERRED'; + } else { + $query .= ' INITIALLY IMMEDIATE'; + } + return $query; + } + + // }}} + // {{{ createTable() + + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * The indexes of the array entries are the names of the fields of the table an + * the array entry values are associative arrays like those that are meant to be + * passed with the field definitions to get[Type]Declaration() functions. + * + * Example + * array( + * + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @param array $options An associative array of table options: + * array( + * 'comment' => 'Foo', + * 'temporary' => true|false, + * ); + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $db->beginNestedTransaction(); + $result = parent::createTable($name, $fields, $options); + if (!PEAR::isError($result)) { + foreach ($fields as $field_name => $field) { + if (!empty($field['autoincrement'])) { + $result = $this->_makeAutoincrement($field_name, $name); + } + } + } + $db->completeNestedTransaction(); + return $result; + } + + // }}} + // {{{ dropTable() + + /** + * drop an existing table + * + * @param string $name name of the table that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropTable($name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $db->beginNestedTransaction(); + $result = $this->_dropAutoincrement($name); + if (!PEAR::isError($result)) { + $result = parent::dropTable($name); + } + $db->completeNestedTransaction(); + return $result; + } + + // }}} + // {{{ truncateTable() + + /** + * Truncate an existing table (if the TRUNCATE TABLE syntax is not supported, + * it falls back to a DELETE FROM TABLE query) + * + * @param string $name name of the table that should be truncated + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function truncateTable($name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("TRUNCATE TABLE $name"); + } + + // }}} + // {{{ vacuum() + + /** + * Optimize (vacuum) all the tables in the db (or only the specified table) + * and optionally run ANALYZE. + * + * @param string $table table name (all the tables if empty) + * @param array $options an array with driver-specific options: + * - timeout [int] (in seconds) [mssql-only] + * - analyze [boolean] [pgsql and mysql] + * - full [boolean] [pgsql-only] + * - freeze [boolean] [pgsql-only] + * + * @return mixed MDB2_OK success, a MDB2 error on failure + * @access public + */ + function vacuum($table = null, $options = array()) + { + // not needed in Oracle + return MDB2_OK; + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the MDB2 parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the MDB2 parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'name' => array( + * 'length' => '20', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 20, + * ), + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * 'definition' => array( + * 'type' => 'text', + * 'length' => 1, + * 'default' => 'M', + * ), + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + case 'remove': + case 'change': + case 'name': + case 'rename': + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + if ($check) { + return MDB2_OK; + } + + $name = $db->quoteIdentifier($name, true); + + if (!empty($changes['add']) && is_array($changes['add'])) { + $fields = array(); + foreach ($changes['add'] as $field_name => $field) { + $fields[] = $db->getDeclaration($field['type'], $field_name, $field); + } + $result = $db->exec("ALTER TABLE $name ADD (". implode(', ', $fields).')'); + if (PEAR::isError($result)) { + return $result; + } + } + + if (!empty($changes['change']) && is_array($changes['change'])) { + $fields = array(); + foreach ($changes['change'] as $field_name => $field) { + //fix error "column to be modified to NOT NULL is already NOT NULL" + if (!array_key_exists('notnull', $field)) { + unset($field['definition']['notnull']); + } + $fields[] = $db->getDeclaration($field['definition']['type'], $field_name, $field['definition']); + } + $result = $db->exec("ALTER TABLE $name MODIFY (". implode(', ', $fields).')'); + if (PEAR::isError($result)) { + return $result; + } + } + + if (!empty($changes['rename']) && is_array($changes['rename'])) { + foreach ($changes['rename'] as $field_name => $field) { + $field_name = $db->quoteIdentifier($field_name, true); + $query = "ALTER TABLE $name RENAME COLUMN $field_name TO ".$db->quoteIdentifier($field['name']); + $result = $db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + } + } + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + $fields = array(); + foreach ($changes['remove'] as $field_name => $field) { + $fields[] = $db->quoteIdentifier($field_name, true); + } + $result = $db->exec("ALTER TABLE $name DROP COLUMN ". implode(', ', $fields)); + if (PEAR::isError($result)) { + return $result; + } + } + + if (!empty($changes['name'])) { + $change_name = $db->quoteIdentifier($changes['name'], true); + $result = $db->exec("ALTER TABLE $name RENAME TO ".$change_name); + if (PEAR::isError($result)) { + return $result; + } + } + + return MDB2_OK; + } + + // }}} + // {{{ _fetchCol() + + /** + * Utility method to fetch and format a column from a resultset + * + * @param resource $result + * @param boolean $fixname (used when listing indices or constraints) + * @return mixed array of names on success, a MDB2 error on failure + * @access private + */ + function _fetchCol($result, $fixname = false) + { + if (PEAR::isError($result)) { + return $result; + } + $col = $result->fetchCol(); + if (PEAR::isError($col)) { + return $col; + } + $result->free(); + + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if ($fixname) { + foreach ($col as $k => $v) { + $col[$k] = $this->_fixIndexName($v); + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + && $db->options['field_case'] == CASE_LOWER + ) { + $col = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $col); + } + return $col; + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed array of database names on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!$db->options['emulate_database']) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'database listing is only supported if the "emulate_database" option is enabled', __FUNCTION__); + } + + if ($db->options['database_name_prefix']) { + $query = 'SELECT SUBSTR(username, '; + $query.= (strlen($db->options['database_name_prefix'])+1); + $query.= ") FROM sys.dba_users WHERE username LIKE '"; + $query.= $db->options['database_name_prefix']."%'"; + } else { + $query = 'SELECT username FROM sys.dba_users'; + } + $result = $db->standaloneQuery($query, array('text'), false); + return $this->_fetchCol($result); + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed array of user names on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if ($db->options['emulate_database'] && $db->options['database_name_prefix']) { + $query = 'SELECT SUBSTR(username, '; + $query.= (strlen($db->options['database_name_prefix'])+1); + $query.= ") FROM sys.dba_users WHERE username NOT LIKE '"; + $query.= $db->options['database_name_prefix']."%'"; + } else { + $query = 'SELECT username FROM sys.dba_users'; + } + return $db->queryCol($query); + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @param string owner, the current is default + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews($owner = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT view_name + FROM sys.all_views + WHERE owner=? OR owner=?'; + $stmt = $db->prepare($query); + if (PEAR::isError($stmt)) { + return $stmt; + } + $result = $stmt->execute(array($owner, strtoupper($owner))); + return $this->_fetchCol($result); + } + + // }}} + // {{{ listFunctions() + + /** + * list all functions in the current database + * + * @param string owner, the current is default + * @return mixed array of function names on success, a MDB2 error on failure + * @access public + */ + function listFunctions($owner = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = "SELECT name + FROM sys.all_source + WHERE line = 1 + AND type = 'FUNCTION' + AND (owner=? OR owner=?)"; + $stmt = $db->prepare($query); + if (PEAR::isError($stmt)) { + return $stmt; + } + $result = $stmt->execute(array($owner, strtoupper($owner))); + return $this->_fetchCol($result); + } + + // }}} + // {{{ listTableTriggers() + + /** + * list all triggers in the database that reference a given table + * + * @param string table for which all referenced triggers should be found + * @return mixed array of trigger names on success, a MDB2 error on failure + * @access public + */ + function listTableTriggers($table = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = "SELECT trigger_name + FROM sys.all_triggers + WHERE (table_name=? OR table_name=?) + AND (owner=? OR owner=?)"; + $stmt = $db->prepare($query); + if (PEAR::isError($stmt)) { + return $stmt; + } + $args = array( + $table, + strtoupper($table), + $owner, + strtoupper($owner), + ); + $result = $stmt->execute($args); + return $this->_fetchCol($result); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the database + * + * @param string owner, the current is default + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables($owner = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT table_name + FROM sys.all_tables + WHERE owner=? OR owner=?'; + $stmt = $db->prepare($query); + if (PEAR::isError($stmt)) { + return $stmt; + } + $result = $stmt->execute(array($owner, strtoupper($owner))); + return $this->_fetchCol($result); + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a table in the current database + * + * @param string $table name of table that should be used in method + * @return mixed array of field names on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($owner, $table) = $this->splitTableSchema($table); + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT column_name + FROM all_tab_columns + WHERE (table_name=? OR table_name=?) + AND (owner=? OR owner=?) + ORDER BY column_id'; + $stmt = $db->prepare($query); + if (PEAR::isError($stmt)) { + return $stmt; + } + $args = array( + $table, + strtoupper($table), + $owner, + strtoupper($owner), + ); + $result = $stmt->execute($args); + return $this->_fetchCol($result); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of index names on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($owner, $table) = $this->splitTableSchema($table); + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT i.index_name name + FROM all_indexes i + LEFT JOIN all_constraints c + ON c.index_name = i.index_name + AND c.owner = i.owner + AND c.table_name = i.table_name + WHERE (i.table_name=? OR i.table_name=?) + AND (i.owner=? OR i.owner=?) + AND c.index_name IS NULL + AND i.generated=' .$db->quote('N', 'text'); + $stmt = $db->prepare($query); + if (PEAR::isError($stmt)) { + return $stmt; + } + $args = array( + $table, + strtoupper($table), + $owner, + strtoupper($owner), + ); + $result = $stmt->execute($args); + return $this->_fetchCol($result, true); + } + + // }}} + // {{{ createConstraint() + + /** + * create a constraint on a table + * + * @param string $table name of the table on which the constraint is to be created + * @param string $name name of the constraint to be created + * @param array $definition associative array that defines properties of the constraint to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the constraint fields as array + * constraints. Each entry of this array is set to another type of associative + * array that specifies properties of the constraint that are specific to + * each field. + * + * Example + * array( + * 'fields' => array( + * 'user_name' => array(), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createConstraint($table, $name, $definition) + { + $result = parent::createConstraint($table, $name, $definition); + if (PEAR::isError($result)) { + return $result; + } + if (!empty($definition['foreign'])) { + return $this->_createFKTriggers($table, array($name => $definition)); + } + return MDB2_OK; + } + + // }}} + // {{{ dropConstraint() + + /** + * drop existing constraint + * + * @param string $table name of table that should be used in method + * @param string $name name of the constraint to be dropped + * @param string $primary hint if the constraint is primary + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropConstraint($table, $name, $primary = false) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + //is it a FK constraint? If so, also delete the associated triggers + $db->loadModule('Reverse', null, true); + $definition = $db->reverse->getTableConstraintDefinition($table, $name); + if (!PEAR::isError($definition) && !empty($definition['foreign'])) { + //first drop the FK enforcing triggers + $result = $this->_dropFKTriggers($table, $name, $definition['references']['table']); + if (PEAR::isError($result)) { + return $result; + } + } + + return parent::dropConstraint($table, $name, $primary); + } + + // }}} + // {{{ _createFKTriggers() + + /** + * Create triggers to enforce the FOREIGN KEY constraint on the table + * + * @param string $table table name + * @param array $foreign_keys FOREIGN KEY definitions + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _createFKTriggers($table, $foreign_keys) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + // create triggers to enforce FOREIGN KEY constraints + if ($db->supports('triggers') && !empty($foreign_keys)) { + $table = $db->quoteIdentifier($table, true); + foreach ($foreign_keys as $fkname => $fkdef) { + if (empty($fkdef)) { + continue; + } + $fkdef['onupdate'] = empty($fkdef['onupdate']) ? $db->options['default_fk_action_onupdate'] : strtoupper($fkdef['onupdate']); + if ('RESTRICT' == $fkdef['onupdate'] || 'NO ACTION' == $fkdef['onupdate']) { + // already handled by default + continue; + } + + $trigger_name = substr(strtolower($fkname.'_pk_upd_trg'), 0, $db->options['max_identifiers_length']); + $table_fields = array_keys($fkdef['fields']); + $referenced_fields = array_keys($fkdef['references']['fields']); + + //create the ON UPDATE trigger on the primary table + $restrict_action = ' IF (SELECT '; + $aliased_fields = array(); + foreach ($table_fields as $field) { + $aliased_fields[] = $table .'.'.$field .' AS '.$field; + } + $restrict_action .= implode(',', $aliased_fields) + .' FROM '.$table + .' WHERE '; + $conditions = array(); + $new_values = array(); + $null_values = array(); + for ($i=0; $i<count($table_fields); $i++) { + $conditions[] = $table_fields[$i] .' = :OLD.'.$referenced_fields[$i]; + $new_values[] = $table_fields[$i] .' = :NEW.'.$referenced_fields[$i]; + $null_values[] = $table_fields[$i] .' = NULL'; + } + + $cascade_action = 'UPDATE '.$table.' SET '.implode(', ', $new_values) .' WHERE '.implode(' AND ', $conditions). ';'; + $setnull_action = 'UPDATE '.$table.' SET '.implode(', ', $null_values).' WHERE '.implode(' AND ', $conditions). ';'; + + if ('SET DEFAULT' == $fkdef['onupdate'] || 'SET DEFAULT' == $fkdef['ondelete']) { + $db->loadModule('Reverse', null, true); + $default_values = array(); + foreach ($table_fields as $table_field) { + $field_definition = $db->reverse->getTableFieldDefinition($table, $field); + if (PEAR::isError($field_definition)) { + return $field_definition; + } + $default_values[] = $table_field .' = '. $field_definition[0]['default']; + } + $setdefault_action = 'UPDATE '.$table.' SET '.implode(', ', $default_values).' WHERE '.implode(' AND ', $conditions). ';'; + } + + $query = 'CREATE TRIGGER %s' + .' %s ON '.$fkdef['references']['table'] + .' FOR EACH ROW ' + .' BEGIN '; + + if ('CASCADE' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_name, 'BEFORE UPDATE', 'update') . $cascade_action; + } elseif ('SET NULL' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_name, 'BEFORE UPDATE', 'update') . $setnull_action; + } elseif ('SET DEFAULT' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_name, 'BEFORE UPDATE', 'update') . $setdefault_action; + } + $sql_update .= ' END;'; + $result = $db->exec($sql_update); + if (PEAR::isError($result)) { + if ($result->getCode() === MDB2_ERROR_ALREADY_EXISTS) { + return MDB2_OK; + } + return $result; + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ _dropFKTriggers() + + /** + * Drop the triggers created to enforce the FOREIGN KEY constraint on the table + * + * @param string $table table name + * @param string $fkname FOREIGN KEY constraint name + * @param string $referenced_table referenced table name + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access private + */ + function _dropFKTriggers($table, $fkname, $referenced_table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $triggers = $this->listTableTriggers($table); + $triggers2 = $this->listTableTriggers($referenced_table); + if (!PEAR::isError($triggers2) && !PEAR::isError($triggers)) { + $triggers = array_merge($triggers, $triggers2); + $trigger_name = substr(strtolower($fkname.'_pk_upd_trg'), 0, $db->options['max_identifiers_length']); + $pattern = '/^'.$trigger_name.'$/i'; + foreach ($triggers as $trigger) { + if (preg_match($pattern, $trigger)) { + $result = $db->exec('DROP TRIGGER '.$trigger); + if (PEAR::isError($result)) { + return $result; + } + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ listTableConstraints() + + /** + * list all constraints in a table + * + * @param string $table name of table that should be used in method + * @return mixed array of constraint names on success, a MDB2 error on failure + * @access public + */ + function listTableConstraints($table) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($owner, $table) = $this->splitTableSchema($table); + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT constraint_name + FROM all_constraints + WHERE (table_name=? OR table_name=?) + AND (owner=? OR owner=?)'; + $stmt = $db->prepare($query); + if (PEAR::isError($stmt)) { + return $stmt; + } + $args = array( + $table, + strtoupper($table), + $owner, + strtoupper($owner), + ); + $result = $stmt->execute($args); + return $this->_fetchCol($result, true); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param object $db database object that is extended by this class + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + $query = "CREATE SEQUENCE $sequence_name START WITH $start INCREMENT BY 1 NOCACHE"; + $query.= ($start < 1 ? " MINVALUE $start" : ''); + return $db->exec($query); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param object $db database object that is extended by this class + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($seq_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->quoteIdentifier($db->getSequenceName($seq_name), true); + return $db->exec("DROP SEQUENCE $sequence_name"); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @param string owner, the current is default + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences($owner = null) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT sequence_name + FROM sys.all_sequences + WHERE (sequence_owner=? OR sequence_owner=?)'; + $stmt = $db->prepare($query); + if (PEAR::isError($stmt)) { + return $stmt; + } + $result = $stmt->execute(array($owner, strtoupper($owner))); + if (PEAR::isError($result)) { + return $result; + } + $col = $result->fetchCol(); + if (PEAR::isError($col)) { + return $col; + } + $result->free(); + + foreach ($col as $k => $v) { + $col[$k] = $this->_fixSequenceName($v); + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + && $db->options['field_case'] == CASE_LOWER + ) { + $col = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $col); + } + return $col; + } +} +?>
\ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Native/oci8.php b/3rdparty/MDB2/Driver/Native/oci8.php new file mode 100644 index 00000000000..d198f9687a9 --- /dev/null +++ b/3rdparty/MDB2/Driver/Native/oci8.php @@ -0,0 +1,60 @@ +<?php +// +----------------------------------------------------------------------+ +// | PHP versions 4 and 5 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1998-2006 Manuel Lemos, Tomas V.V.Cox, | +// | Stig. S. Bakken, Lukas Smith, Frank M. Kromann | +// | All rights reserved. | +// +----------------------------------------------------------------------+ +// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB | +// | API as well as database abstraction for PHP applications. | +// | This LICENSE is in the BSD license style. | +// | | +// | 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 Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken, | +// | Lukas Smith nor the names of his contributors may be used to endorse | +// | or promote products derived from this software without specific prior| +// | written permission. | +// | | +// | 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 | +// | REGENTS 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. | +// +----------------------------------------------------------------------+ +// | Author: Lukas Smith <smith@pooteeweet.org> | +// +----------------------------------------------------------------------+ +// +// $Id: oci8.php 215004 2006-06-18 21:59:05Z lsmith $ +// + +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 Oracle driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@dybnet.de> + */ +class MDB2_Driver_Native_oci8 extends MDB2_Driver_Native_Common +{ +} +?>
\ No newline at end of file diff --git a/3rdparty/MDB2/Driver/Reverse/oci8.php b/3rdparty/MDB2/Driver/Reverse/oci8.php new file mode 100644 index 00000000000..d89ad771374 --- /dev/null +++ b/3rdparty/MDB2/Driver/Reverse/oci8.php @@ -0,0 +1,625 @@ +<?php +// +----------------------------------------------------------------------+ +// | PHP versions 4 and 5 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1998-2007 Manuel Lemos, Tomas V.V.Cox, | +// | Stig. S. Bakken, Lukas Smith, Frank M. Kromann, Lorenzo Alberton | +// | All rights reserved. | +// +----------------------------------------------------------------------+ +// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB | +// | API as well as database abstraction for PHP applications. | +// | This LICENSE is in the BSD license style. | +// | | +// | 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 Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken, | +// | Lukas Smith nor the names of his contributors may be used to endorse | +// | or promote products derived from this software without specific prior| +// | written permission. | +// | | +// | 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 | +// | REGENTS 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. | +// +----------------------------------------------------------------------+ +// | Authors: Lukas Smith <smith@pooteeweet.org> | +// | Lorenzo Alberton <l.alberton@quipo.it> | +// +----------------------------------------------------------------------+ +// +// $Id: oci8.php 295587 2010-02-28 17:16:38Z quipo $ +// + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 Oracle driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@dybnet.de> + */ +class MDB2_Driver_Reverse_oci8 extends MDB2_Driver_Reverse_Common +{ + // {{{ getTableFieldDefinition() + + /** + * Get the structure of a field into an array + * + * @param string $table_name name of table that should be used in method + * @param string $field_name name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableFieldDefinition($table_name, $field_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + + list($owner, $table) = $this->splitTableSchema($table_name); + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT column_name AS "name", + data_type AS "type", + nullable AS "nullable", + data_default AS "default", + COALESCE(data_precision, data_length) AS "length", + data_scale AS "scale" + FROM all_tab_columns + WHERE (table_name=? OR table_name=?) + AND (owner=? OR owner=?) + AND (column_name=? OR column_name=?) + ORDER BY column_id'; + $stmt = $db->prepare($query); + if (PEAR::isError($stmt)) { + return $stmt; + } + $args = array( + $table, + strtoupper($table), + $owner, + strtoupper($owner), + $field_name, + strtoupper($field_name) + ); + $result = $stmt->execute($args); + if (PEAR::isError($result)) { + return $result; + } + $column = $result->fetchRow(MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($column)) { + return $column; + } + $stmt->free(); + $result->free(); + + if (empty($column)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $field_name . ' is not a column in table ' . $table_name, __FUNCTION__); + } + + $column = array_change_key_case($column, CASE_LOWER); + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $column['name'] = strtolower($column['name']); + } else { + $column['name'] = strtoupper($column['name']); + } + } + $mapped_datatype = $db->datatype->mapNativeDatatype($column); + if (PEAR::isError($mapped_datatype)) { + return $mapped_datatype; + } + list($types, $length, $unsigned, $fixed) = $mapped_datatype; + $notnull = false; + if (!empty($column['nullable']) && $column['nullable'] == 'N') { + $notnull = true; + } + $default = false; + if (array_key_exists('default', $column)) { + $default = $column['default']; + if ($default === 'NULL') { + $default = null; + } + //ugly hack, but works for the reverse direction + if ($default == "''") { + $default = ''; + } + if ((null === $default) && $notnull) { + $default = ''; + } + } + + $definition[0] = array('notnull' => $notnull, 'nativetype' => $column['type']); + if (null !== $length) { + $definition[0]['length'] = $length; + } + if (null !== $unsigned) { + $definition[0]['unsigned'] = $unsigned; + } + if (null !== $fixed) { + $definition[0]['fixed'] = $fixed; + } + if ($default !== false) { + $definition[0]['default'] = $default; + } + foreach ($types as $key => $type) { + $definition[$key] = $definition[0]; + if ($type == 'clob' || $type == 'blob') { + unset($definition[$key]['default']); + } + $definition[$key]['type'] = $type; + $definition[$key]['mdb2type'] = $type; + } + if ($type == 'integer') { + $query= "SELECT trigger_body + FROM all_triggers + WHERE table_name=? + AND triggering_event='INSERT' + AND trigger_type='BEFORE EACH ROW'"; + // ^^ pretty reasonable mimic for "auto_increment" in oracle? + $stmt = $db->prepare($query); + if (PEAR::isError($stmt)) { + return $stmt; + } + $result = $stmt->execute(strtoupper($table)); + if (PEAR::isError($result)) { + return $result; + } + while ($triggerstr = $result->fetchOne()) { + if (preg_match('/.*SELECT\W+(.+)\.nextval +into +\:NEW\.'.$field_name.' +FROM +dual/im', $triggerstr, $matches)) { + $definition[0]['autoincrement'] = $matches[1]; + } + } + $stmt->free(); + $result->free(); + } + return $definition; + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the structure of an index into an array + * + * @param string $table_name name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table_name, $index_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($owner, $table) = $this->splitTableSchema($table_name); + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT aic.column_name AS "column_name", + aic.column_position AS "column_position", + aic.descend AS "descend", + aic.table_owner AS "table_owner", + alc.constraint_type AS "constraint_type" + FROM all_ind_columns aic + LEFT JOIN all_constraints alc + ON aic.index_name = alc.constraint_name + AND aic.table_name = alc.table_name + AND aic.table_owner = alc.owner + WHERE (aic.table_name=? OR aic.table_name=?) + AND (aic.index_name=? OR aic.index_name=?) + AND (aic.table_owner=? OR aic.table_owner=?) + ORDER BY column_position'; + $stmt = $db->prepare($query); + if (PEAR::isError($stmt)) { + return $stmt; + } + $indexnames = array_unique(array($db->getIndexName($index_name), $index_name)); + $i = 0; + $row = null; + while ((null === $row) && array_key_exists($i, $indexnames)) { + $args = array( + $table, + strtoupper($table), + $indexnames[$i], + strtoupper($indexnames[$i]), + $owner, + strtoupper($owner) + ); + $result = $stmt->execute($args); + if (PEAR::isError($result)) { + return $result; + } + $row = $result->fetchRow(MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($row)) { + return $row; + } + $i++; + } + if (null === $row) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $index_name. ' is not an index on table '. $table_name, __FUNCTION__); + } + if ($row['constraint_type'] == 'U' || $row['constraint_type'] == 'P') { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $index_name. ' is a constraint, not an index on table '. $table_name, __FUNCTION__); + } + + $definition = array(); + while (null !== $row) { + $row = array_change_key_case($row, CASE_LOWER); + $column_name = $row['column_name']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $column_name = strtolower($column_name); + } else { + $column_name = strtoupper($column_name); + } + } + $definition['fields'][$column_name] = array( + 'position' => (int)$row['column_position'], + ); + if (!empty($row['descend'])) { + $definition['fields'][$column_name]['sorting'] = + ($row['descend'] == 'ASC' ? 'ascending' : 'descending'); + } + $row = $result->fetchRow(MDB2_FETCHMODE_ASSOC); + } + $result->free(); + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $index_name. ' is not an index on table '. $table_name, __FUNCTION__); + } + return $definition; + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the structure of a constraint into an array + * + * @param string $table_name name of table that should be used in method + * @param string $constraint_name name of constraint that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableConstraintDefinition($table_name, $constraint_name) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($owner, $table) = $this->splitTableSchema($table_name); + if (empty($owner)) { + $owner = $db->dsn['username']; + } + + $query = 'SELECT alc.constraint_name, + CASE alc.constraint_type WHEN \'P\' THEN 1 ELSE 0 END "primary", + CASE alc.constraint_type WHEN \'R\' THEN 1 ELSE 0 END "foreign", + CASE alc.constraint_type WHEN \'U\' THEN 1 ELSE 0 END "unique", + CASE alc.constraint_type WHEN \'C\' THEN 1 ELSE 0 END "check", + alc.DELETE_RULE "ondelete", + \'NO ACTION\' "onupdate", + \'SIMPLE\' "match", + CASE alc.deferrable WHEN \'NOT DEFERRABLE\' THEN 0 ELSE 1 END "deferrable", + CASE alc.deferred WHEN \'IMMEDIATE\' THEN 0 ELSE 1 END "initiallydeferred", + alc.search_condition AS "search_condition", + alc.table_name, + cols.column_name AS "column_name", + cols.position, + r_alc.table_name "references_table", + r_cols.column_name "references_field", + r_cols.position "references_field_position" + FROM all_cons_columns cols + LEFT JOIN all_constraints alc + ON alc.constraint_name = cols.constraint_name + AND alc.owner = cols.owner + LEFT JOIN all_constraints r_alc + ON alc.r_constraint_name = r_alc.constraint_name + AND alc.r_owner = r_alc.owner + LEFT JOIN all_cons_columns r_cols + ON r_alc.constraint_name = r_cols.constraint_name + AND r_alc.owner = r_cols.owner + AND cols.position = r_cols.position + WHERE (alc.constraint_name=? OR alc.constraint_name=?) + AND alc.constraint_name = cols.constraint_name + AND (alc.owner=? OR alc.owner=?)'; + $tablenames = array(); + if (!empty($table)) { + $query.= ' AND (alc.table_name=? OR alc.table_name=?)'; + $tablenames = array($table, strtoupper($table)); + } + $stmt = $db->prepare($query); + if (PEAR::isError($stmt)) { + return $stmt; + } + + $constraintnames = array_unique(array($db->getIndexName($constraint_name), $constraint_name)); + $c = 0; + $row = null; + while ((null === $row) && array_key_exists($c, $constraintnames)) { + $args = array( + $constraintnames[$c], + strtoupper($constraintnames[$c]), + $owner, + strtoupper($owner) + ); + if (!empty($table)) { + $args = array_merge($args, $tablenames); + } + $result = $stmt->execute($args); + if (PEAR::isError($result)) { + return $result; + } + $row = $result->fetchRow(MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($row)) { + return $row; + } + $c++; + } + + $definition = array( + 'primary' => (boolean)$row['primary'], + 'unique' => (boolean)$row['unique'], + 'foreign' => (boolean)$row['foreign'], + 'check' => (boolean)$row['check'], + 'deferrable' => (boolean)$row['deferrable'], + 'initiallydeferred' => (boolean)$row['initiallydeferred'], + 'ondelete' => $row['ondelete'], + 'onupdate' => $row['onupdate'], + 'match' => $row['match'], + ); + + if ($definition['check']) { + // pattern match constraint for check constraint values into enum-style output: + $enumregex = '/'.$row['column_name'].' in \((.+?)\)/i'; + if (preg_match($enumregex, $row['search_condition'], $rangestr)) { + $definition['fields'][$column_name] = array(); + $allowed = explode(',', $rangestr[1]); + foreach ($allowed as $val) { + $val = trim($val); + $val = preg_replace('/^\'/', '', $val); + $val = preg_replace('/\'$/', '', $val); + array_push($definition['fields'][$column_name], $val); + } + } + } + + while (null !== $row) { + $row = array_change_key_case($row, CASE_LOWER); + $column_name = $row['column_name']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $column_name = strtolower($column_name); + } else { + $column_name = strtoupper($column_name); + } + } + $definition['fields'][$column_name] = array( + 'position' => (int)$row['position'] + ); + if ($row['foreign']) { + $ref_column_name = $row['references_field']; + $ref_table_name = $row['references_table']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $ref_column_name = strtolower($ref_column_name); + $ref_table_name = strtolower($ref_table_name); + } else { + $ref_column_name = strtoupper($ref_column_name); + $ref_table_name = strtoupper($ref_table_name); + } + } + $definition['references']['table'] = $ref_table_name; + $definition['references']['fields'][$ref_column_name] = array( + 'position' => (int)$row['references_field_position'] + ); + } + $lastrow = $row; + $row = $result->fetchRow(MDB2_FETCHMODE_ASSOC); + } + $result->free(); + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not a constraint on table '. $table_name, __FUNCTION__); + } + + return $definition; + } + + // }}} + // {{{ getSequenceDefinition() + + /** + * Get the structure of a sequence into an array + * + * @param string $sequence name of sequence that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getSequenceDefinition($sequence) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->getSequenceName($sequence); + $query = 'SELECT last_number FROM user_sequences'; + $query.= ' WHERE sequence_name='.$db->quote($sequence_name, 'text'); + $query.= ' OR sequence_name='.$db->quote(strtoupper($sequence_name), 'text'); + $start = $db->queryOne($query, 'integer'); + if (PEAR::isError($start)) { + return $start; + } + $definition = array(); + if ($start != 1) { + $definition = array('start' => $start); + } + return $definition; + } + + // }}} + // {{{ getTriggerDefinition() + + /** + * Get the structure of a trigger into an array + * + * EXPERIMENTAL + * + * WARNING: this function is experimental and may change the returned value + * at any time until labelled as non-experimental + * + * @param string $trigger name of trigger that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTriggerDefinition($trigger) + { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'SELECT trigger_name AS "trigger_name", + table_name AS "table_name", + trigger_body AS "trigger_body", + trigger_type AS "trigger_type", + triggering_event AS "trigger_event", + description AS "trigger_comment", + 1 AS "trigger_enabled", + when_clause AS "when_clause" + FROM user_triggers + WHERE trigger_name = \''. strtoupper($trigger).'\''; + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + 'when_clause' => 'text', + ); + $result = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($result)) { + return $result; + } + if (!empty($result['trigger_type'])) { + //$result['trigger_type'] = array_shift(explode(' ', $result['trigger_type'])); + $result['trigger_type'] = preg_replace('/(\S+).*/', '\\1', $result['trigger_type']); + } + return $result; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * NOTE: only supports 'table' and 'flags' if <var>$result</var> + * is a table name. + * + * NOTE: flags won't contain index information. + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::tableInfo() + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $resource = MDB2::isResultCommon($result) ? $result->getResource() : $result; + if (!is_resource($resource)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'Could not generate result resource', __FUNCTION__); + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $case_func = 'strtolower'; + } else { + $case_func = 'strtoupper'; + } + } else { + $case_func = 'strval'; + } + + $count = @OCINumCols($resource); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + $db->loadModule('Datatype', null, true); + for ($i = 0; $i < $count; $i++) { + $column = array( + 'table' => '', + 'name' => $case_func(@OCIColumnName($resource, $i+1)), + 'type' => @OCIColumnType($resource, $i+1), + 'length' => @OCIColumnSize($resource, $i+1), + 'flags' => '', + ); + $res[$i] = $column; + $res[$i]['mdb2type'] = $db->datatype->mapNativeDatatype($res[$i]); + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + return $res; + } +} +?>
\ No newline at end of file diff --git a/3rdparty/MDB2/Driver/mysql.php b/3rdparty/MDB2/Driver/mysql.php index 3008bd04f09..1d22e61f460 100644 --- a/3rdparty/MDB2/Driver/mysql.php +++ b/3rdparty/MDB2/Driver/mysql.php @@ -80,7 +80,7 @@ class MDB2_Driver_mysql extends MDB2_Driver_Common protected $start_transaction = false; - protected $varchar_max_length = 255; + public $varchar_max_length = 255; // }}} // {{{ constructor diff --git a/3rdparty/MDB2/Driver/oci8.php b/3rdparty/MDB2/Driver/oci8.php new file mode 100644 index 00000000000..a1eefc94d15 --- /dev/null +++ b/3rdparty/MDB2/Driver/oci8.php @@ -0,0 +1,1700 @@ +<?php +// vim: set et ts=4 sw=4 fdm=marker: +// +----------------------------------------------------------------------+ +// | PHP versions 4 and 5 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1998-2008 Manuel Lemos, Tomas V.V.Cox, | +// | Stig. S. Bakken, Lukas Smith | +// | All rights reserved. | +// +----------------------------------------------------------------------+ +// | MDB2 is a merge of PEAR DB and Metabases that provides a unified DB | +// | API as well as database abstraction for PHP applications. | +// | This LICENSE is in the BSD license style. | +// | | +// | 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 Manuel Lemos, Tomas V.V.Cox, Stig. S. Bakken, | +// | Lukas Smith nor the names of his contributors may be used to endorse | +// | or promote products derived from this software without specific prior| +// | written permission. | +// | | +// | 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 | +// | REGENTS 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. | +// +----------------------------------------------------------------------+ +// | Author: Lukas Smith <smith@pooteeweet.org> | +// +----------------------------------------------------------------------+ + +// $Id: oci8.php 295587 2010-02-28 17:16:38Z quipo $ + +/** + * MDB2 OCI8 driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_Driver_oci8 extends MDB2_Driver_Common +{ + // {{{ properties + var $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => '@'); + + var $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); + + var $uncommitedqueries = 0; + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'oci8'; + $this->dbsyntax = 'oci8'; + + $this->supported['sequences'] = true; + $this->supported['indexes'] = true; + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['current_id'] = true; + $this->supported['affected_rows'] = true; + $this->supported['transactions'] = true; + $this->supported['savepoints'] = true; + $this->supported['limit_queries'] = true; + $this->supported['LOBs'] = true; + $this->supported['replace'] = 'emulated'; + $this->supported['sub_selects'] = true; + $this->supported['triggers'] = true; + $this->supported['auto_increment'] = false; // implementation is broken + $this->supported['primary_key'] = true; + $this->supported['result_introspection'] = true; + $this->supported['prepared_statements'] = true; + $this->supported['identifier_quoting'] = true; + $this->supported['pattern_escaping'] = true; + $this->supported['new_link'] = true; + + $this->options['DBA_username'] = false; + $this->options['DBA_password'] = false; + $this->options['database_name_prefix'] = false; + $this->options['emulate_database'] = true; + $this->options['default_tablespace'] = false; + $this->options['default_text_field_length'] = 2000; + $this->options['lob_allow_url_include'] = false; + $this->options['result_prefetching'] = false; + $this->options['max_identifiers_length'] = 30; + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + if (is_resource($error)) { + $error_data = @OCIError($error); + $error = null; + } elseif ($this->connection) { + $error_data = @OCIError($this->connection); + } else { + $error_data = @OCIError(); + } + $native_code = $error_data['code']; + $native_msg = $error_data['message']; + if (null === $error) { + static $ecode_map; + if (empty($ecode_map)) { + $ecode_map = array( + 1 => MDB2_ERROR_CONSTRAINT, + 900 => MDB2_ERROR_SYNTAX, + 904 => MDB2_ERROR_NOSUCHFIELD, + 911 => MDB2_ERROR_SYNTAX, //invalid character + 913 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + 921 => MDB2_ERROR_SYNTAX, + 923 => MDB2_ERROR_SYNTAX, + 942 => MDB2_ERROR_NOSUCHTABLE, + 955 => MDB2_ERROR_ALREADY_EXISTS, + 1400 => MDB2_ERROR_CONSTRAINT_NOT_NULL, + 1401 => MDB2_ERROR_INVALID, + 1407 => MDB2_ERROR_CONSTRAINT_NOT_NULL, + 1418 => MDB2_ERROR_NOT_FOUND, + 1435 => MDB2_ERROR_NOT_FOUND, + 1476 => MDB2_ERROR_DIVZERO, + 1722 => MDB2_ERROR_INVALID_NUMBER, + 2289 => MDB2_ERROR_NOSUCHTABLE, + 2291 => MDB2_ERROR_CONSTRAINT, + 2292 => MDB2_ERROR_CONSTRAINT, + 2449 => MDB2_ERROR_CONSTRAINT, + 4081 => MDB2_ERROR_ALREADY_EXISTS, //trigger already exists + 24344 => MDB2_ERROR_SYNTAX, //success with compilation error + ); + } + if (isset($ecode_map[$native_code])) { + $error = $ecode_map[$native_code]; + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ beginTransaction() + + /** + * Start a transaction or set a savepoint. + * + * @param string name of a savepoint to set + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function beginTransaction($savepoint = null) + { + $this->debug('Starting transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (null !== $savepoint) { + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'savepoint cannot be released when changes are auto committed', __FUNCTION__); + } + $query = 'SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + if ($this->in_transaction) { + return MDB2_OK; //nothing to do + } + if (!$this->destructor_registered && $this->opened_persistent) { + $this->destructor_registered = true; + register_shutdown_function('MDB2_closeOpenTransactions'); + } + $this->in_transaction = true; + ++$this->uncommitedqueries; + return MDB2_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the database changes done during a transaction that is in + * progress or release a savepoint. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after committing the pending changes. + * + * @param string name of a savepoint to release + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function commit($savepoint = null) + { + $this->debug('Committing transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'commit/release savepoint cannot be done changes are auto committed', __FUNCTION__); + } + if (null !== $savepoint) { + return MDB2_OK; + } + + if ($this->uncommitedqueries) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + if (!@OCICommit($connection)) { + return $this->raiseError(null, null, null, + 'Unable to commit transaction', __FUNCTION__); + } + $this->uncommitedqueries = 0; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ rollback() + + /** + * Cancel any database changes done during a transaction or since a specific + * savepoint that is in progress. This function may only be called when + * auto-committing is disabled, otherwise it will fail. Therefore, a new + * transaction is implicitly started after canceling the pending changes. + * + * @param string name of a savepoint to rollback to + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function rollback($savepoint = null) + { + $this->debug('Rolling back transaction/savepoint', __FUNCTION__, array('is_manip' => true, 'savepoint' => $savepoint)); + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'rollback cannot be done changes are auto committed', __FUNCTION__); + } + if (null !== $savepoint) { + $query = 'ROLLBACK TO SAVEPOINT '.$savepoint; + return $this->_doQuery($query, true); + } + + if ($this->uncommitedqueries) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + if (!@OCIRollback($connection)) { + return $this->raiseError(null, null, null, + 'Unable to rollback transaction', __FUNCTION__); + } + $this->uncommitedqueries = 0; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ function setTransactionIsolation() + + /** + * Set the transacton isolation level. + * + * @param string standard isolation level + * READ UNCOMMITTED (allows dirty reads) + * READ COMMITTED (prevents dirty reads) + * REPEATABLE READ (prevents nonrepeatable reads) + * SERIALIZABLE (prevents phantom reads) + * @param array some transaction options: + * 'wait' => 'WAIT' | 'NO WAIT' + * 'rw' => 'READ WRITE' | 'READ ONLY' + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + function setTransactionIsolation($isolation, $options = array()) + { + $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); + switch ($isolation) { + case 'READ UNCOMMITTED': + $isolation = 'READ COMMITTED'; + case 'READ COMMITTED': + case 'REPEATABLE READ': + $isolation = 'SERIALIZABLE'; + case 'SERIALIZABLE': + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level is not supported: '.$isolation, __FUNCTION__); + } + + $query = "ALTER SESSION ISOLATION LEVEL $isolation"; + return $this->_doQuery($query, true); + } + + // }}} + // {{{ _doConnect() + + /** + * do the grunt work of the connect + * + * @return connection on success or MDB2 Error Object on failure + * @access protected + */ + function _doConnect($username, $password, $persistent = false) + { + if (!PEAR::loadExtension($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + $sid = ''; + + if (!empty($this->dsn['service']) && $this->dsn['hostspec']) { + //oci8://username:password@foo.example.com[:port]/?service=service + // service name is given, it is assumed that hostspec is really a + // hostname, we try to construct an oracle connection string from this + $port = $this->dsn['port'] ? $this->dsn['port'] : 1521; + $sid = sprintf("(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP) + (HOST=%s) (PORT=%s))) + (CONNECT_DATA=(SERVICE_NAME=%s)))", + $this->dsn['hostspec'], + $port, + $this->dsn['service'] + ); + } elseif ($this->dsn['hostspec']) { + // we are given something like 'oci8://username:password@foo/' + // we have hostspec but not a service name, now we assume that + // hostspec is a tnsname defined in tnsnames.ora + $sid = $this->dsn['hostspec']; + if (isset($this->dsn['port']) && $this->dsn['port']) { + $sid = $sid.':'.$this->dsn['port']; + } + } else { + // oci://username:password@ + // if everything fails, we have to rely on environment variables + // not before a check to 'emulate_database' + if (!$this->options['emulate_database'] && $this->database_name) { + $sid = $this->database_name; + } elseif (getenv('ORACLE_SID')) { + $sid = getenv('ORACLE_SID'); + } elseif ($sid = getenv('TWO_TASK')) { + $sid = getenv('TWO_TASK'); + } else { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'not a valid connection string or environment variable [ORACLE_SID|TWO_TASK] not set', + __FUNCTION__); + } + } + + if (function_exists('oci_connect')) { + if ($this->_isNewLinkSet()) { + $connect_function = 'oci_new_connect'; + } else { + $connect_function = $persistent ? 'oci_pconnect' : 'oci_connect'; + } + + $charset = empty($this->dsn['charset']) ? null : $this->dsn['charset']; + $session_mode = empty($this->dsn['session_mode']) ? null : $this->dsn['session_mode']; + $connection = @$connect_function($username, $password, $sid, $charset, $session_mode); + $error = @OCIError(); + if (isset($error['code']) && $error['code'] == 12541) { + // Couldn't find TNS listener. Try direct connection. + $connection = @$connect_function($username, $password, null, $charset); + } + } else { + $connect_function = $persistent ? 'OCIPLogon' : 'OCILogon'; + $connection = @$connect_function($username, $password, $sid); + + if (!empty($this->dsn['charset'])) { + $result = $this->setCharset($this->dsn['charset'], $connection); + if (PEAR::isError($result)) { + return $result; + } + } + } + + if (!$connection) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + if (empty($this->dsn['disable_iso_date'])) { + $query = "ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'"; + $err = $this->_doQuery($query, true, $connection); + if (PEAR::isError($err)) { + $this->disconnect(false); + return $err; + } + } + + $query = "ALTER SESSION SET NLS_NUMERIC_CHARACTERS='. '"; + $err = $this->_doQuery($query, true, $connection); + if (PEAR::isError($err)) { + $this->disconnect(false); + return $err; + } + + return $connection; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return MDB2_OK on success, MDB2 Error Object on failure + * @access public + */ + function connect() + { + if (is_resource($this->connection)) { + //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 + if (MDB2::areEquals($this->connected_dsn, $this->dsn) + && $this->opened_persistent == $this->options['persistent'] + ) { + return MDB2_OK; + } + $this->disconnect(false); + } + + if ($this->database_name && $this->options['emulate_database']) { + $this->dsn['username'] = $this->options['database_name_prefix'].$this->database_name; + } + + $connection = $this->_doConnect($this->dsn['username'], + $this->dsn['password'], + $this->options['persistent']); + if (PEAR::isError($connection)) { + return $connection; + } + $this->connection = $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = ''; + $this->opened_persistent = $this->options['persistent']; + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + + if ($this->database_name) { + if ($this->database_name != $this->connected_database_name) { + $query = 'ALTER SESSION SET CURRENT_SCHEMA = "' .strtoupper($this->database_name) .'"'; + $result = $this->_doQuery($query); + if (PEAR::isError($result)) { + $err = $this->raiseError($result, null, null, + 'Could not select the database: '.$this->database_name, __FUNCTION__); + return $err; + } + $this->connected_database_name = $this->database_name; + } + } + + $this->as_keyword = ' '; + $server_info = $this->getServerVersion(); + if (is_array($server_info)) { + if ($server_info['major'] >= '10') { + $this->as_keyword = ' AS '; + } + } + return MDB2_OK; + } + + // }}} + // {{{ databaseExists() + + /** + * check if given database name is exists? + * + * @param string $name name of the database that should be checked + * + * @return mixed true/false on success, a MDB2 error on failure + * @access public + */ + function databaseExists($name) + { + $connection = $this->_doConnect($this->dsn['username'], + $this->dsn['password'], + $this->options['persistent']); + if (PEAR::isError($connection)) { + return $connection; + } + + $query = 'ALTER SESSION SET CURRENT_SCHEMA = "' .strtoupper($name) .'"'; + $result = $this->_doQuery($query, true, $connection, false); + if (PEAR::isError($result)) { + if (!MDB2::isError($result, MDB2_ERROR_NOT_FOUND)) { + return $result; + } + return false; + } + return true; + } + + // }}} + // {{{ disconnect() + + /** + * Log out and disconnect from the database. + * + * @param boolean $force if the disconnect should be forced even if the + * connection is opened persistently + * @return mixed true on success, false if not connected and error + * object on error + * @access public + */ + function disconnect($force = true) + { + if (is_resource($this->connection)) { + if ($this->in_transaction) { + $dsn = $this->dsn; + $database_name = $this->database_name; + $persistent = $this->options['persistent']; + $this->dsn = $this->connected_dsn; + $this->database_name = $this->connected_database_name; + $this->options['persistent'] = $this->opened_persistent; + $this->rollback(); + $this->dsn = $dsn; + $this->database_name = $database_name; + $this->options['persistent'] = $persistent; + } + + if (!$this->opened_persistent || $force) { + $ok = false; + if (function_exists('oci_close')) { + $ok = @oci_close($this->connection); + } else { + $ok = @OCILogOff($this->connection); + } + if (!$ok) { + return $this->raiseError(MDB2_ERROR_DISCONNECT_FAILED, + null, null, null, __FUNCTION__); + } + } + $this->uncommitedqueries = 0; + } else { + return false; + } + return parent::disconnect($force); + } + + // }}} + // {{{ standaloneQuery() + + /** + * execute a query as DBA + * + * @param string $query the SQL query + * @param mixed $types array containing the types of the columns in + * the result set + * @param boolean $is_manip if the query is a manipulation query + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function standaloneQuery($query, $types = null, $is_manip = false) + { + $user = $this->options['DBA_username']? $this->options['DBA_username'] : $this->dsn['username']; + $pass = $this->options['DBA_password']? $this->options['DBA_password'] : $this->dsn['password']; + $connection = $this->_doConnect($user, $pass, $this->options['persistent']); + if (PEAR::isError($connection)) { + return $connection; + } + + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + + $result = $this->_doQuery($query, $is_manip, $connection, false); + if (!PEAR::isError($result)) { + if ($is_manip) { + $result = $this->_affectedRows($connection, $result); + } else { + $result = $this->_wrapResult($result, $types, true, false, $limit, $offset); + } + } + + @OCILogOff($connection); + return $result; + } + + // }}} + // {{{ _modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * @param string $query query to modify + * @param boolean $is_manip if it is a DML query + * @param integer $limit limit the number of rows + * @param integer $offset start reading from given offset + * @return string modified query + * @access protected + */ + function _modifyQuery($query, $is_manip, $limit, $offset) + { + if (preg_match('/^\s*SELECT/i', $query)) { + if (!preg_match('/\sFROM\s/i', $query)) { + $query.= " FROM dual"; + } + if ($limit > 0) { + // taken from http://svn.ez.no/svn/ezcomponents/packages/Database + $max = $offset + $limit; + if ($offset > 0) { + $min = $offset + 1; + $query = "SELECT * FROM (SELECT a.*, ROWNUM mdb2rn FROM ($query) a WHERE ROWNUM <= $max) WHERE mdb2rn >= $min"; + } else { + $query = "SELECT a.* FROM ($query) a WHERE ROWNUM <= $max"; + } + } + } + return $query; + } + + /** + * Obtain DBMS specific SQL code portion needed to declare a generic type + * field to be used in statement like CREATE TABLE, without the field name + * and type values (ie. just the character set, default value, if the + * field is permitted to be NULL or not, and the collation options). + * + * @param array $field associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * default + * Text value to be used as default for this field. + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * charset + * Text value with the default CHARACTER SET for this field. + * collation + * Text value with the default COLLATION for this field. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field's options. + * @access protected + */ + function _getDeclarationOptions($field) + { + $charset = empty($field['charset']) ? '' : + ' '.$this->_getCharsetFieldDeclaration($field['charset']); + + $notnull = empty($field['notnull']) ? ' NULL' : ' NOT NULL'; + $default = ''; + if (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $db = $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $valid_default_values = $this->getValidTypes(); + $field['default'] = $valid_default_values[$field['type']]; + if ($field['default'] === '' && ($db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL)) { + $field['default'] = ' '; + } + } + if (null !== $field['default']) { + $default = ' DEFAULT ' . $this->quote($field['default'], $field['type']); + } + } + + $collation = empty($field['collation']) ? '' : + ' '.$this->_getCollationFieldDeclaration($field['collation']); + + return $charset.$default.$notnull.$collation; + } + + // }}} + // {{{ _doQuery() + + /** + * Execute a query + * @param string $query query + * @param boolean $is_manip if the query is a manipulation query + * @param resource $connection + * @param string $database_name + * @return result or error object + * @access protected + */ + function _doQuery($query, $is_manip = false, $connection = null, $database_name = null) + { + $this->last_query = $query; + $result = $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + if ($this->getOption('disable_query')) { + if ($is_manip) { + return 0; + } + return null; + } + + if (null === $connection) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + + $query = str_replace("\r\n", "\n", $query); //for fixing end-of-line character in the PL/SQL in windows + $result = @OCIParse($connection, $query); + if (!$result) { + $err = $this->raiseError(null, null, null, + 'Could not create statement', __FUNCTION__); + return $err; + } + + $mode = $this->in_transaction ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS; + if (!@OCIExecute($result, $mode)) { + $err = $this->raiseError($result, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + if (is_numeric($this->options['result_prefetching'])) { + @ocisetprefetch($result, $this->options['result_prefetching']); + } + + $this->debug($query, 'query', array('is_manip' => $is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ _affectedRows() + + /** + * Returns the number of rows affected + * + * @param resource $result + * @param resource $connection + * @return mixed MDB2 Error Object or the number of rows affected + * @access private + */ + function _affectedRows($connection, $result = null) + { + if (null === $connection) { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + } + return @OCIRowCount($result); + } + + // }}} + // {{{ getServerVersion() + + /** + * return version information about the server + * + * @param bool $native determines if the raw version string should be returned + * @return mixed array/string with version information or MDB2 error object + * @access public + */ + function getServerVersion($native = false) + { + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } else { + $server_info = @ociserverversion($connection); + } + if (!$server_info) { + return $this->raiseError(null, null, null, + 'Could not get server information', __FUNCTION__); + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native) { + if (!preg_match('/ (\d+)\.(\d+)\.(\d+)\.([\d\.]+) /', $server_info, $tmp)) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'Could not parse version information:'.$server_info, __FUNCTION__); + } + $server_info = array( + 'major' => $tmp[1], + 'minor' => $tmp[2], + 'patch' => $tmp[3], + 'extra' => $tmp[4], + 'native' => $server_info, + ); + } + return $server_info; + } + + // }}} + // {{{ prepare() + + /** + * Prepares a query for multiple execution with execute(). + * With some database backends, this is emulated. + * prepare() requires a generic query as string like + * 'INSERT INTO numbers VALUES(?,?)' or + * 'INSERT INTO numbers VALUES(:foo,:bar)'. + * The ? and :name and are placeholders which can be set using + * bindParam() and the query can be sent off using the execute() method. + * The allowed format for :name can be set with the 'bindname_format' option. + * + * @param string $query the query to prepare + * @param mixed $types array that contains the types of the placeholders + * @param mixed $result_types array that contains the types of the columns in + * the result set or MDB2_PREPARE_RESULT, if set to + * MDB2_PREPARE_MANIP the query is handled as a manipulation query + * @param mixed $lobs key (field) value (parameter) pair for all lob placeholders + * @return mixed resource handle for the prepared query on success, a MDB2 + * error on failure + * @access public + * @see bindParam, execute + */ + function prepare($query, $types = null, $result_types = null, $lobs = array()) + { + if ($this->options['emulate_prepared']) { + return parent::prepare($query, $types, $result_types, $lobs); + } + $is_manip = ($result_types === MDB2_PREPARE_MANIP); + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + $placeholder_type_guess = $placeholder_type = null; + $question = '?'; + $colon = ':'; + $positions = array(); + $position = 0; + $parameter = -1; + while ($position < strlen($query)) { + $q_position = strpos($query, $question, $position); + $c_position = strpos($query, $colon, $position); + if ($q_position && $c_position) { + $p_position = min($q_position, $c_position); + } elseif ($q_position) { + $p_position = $q_position; + } elseif ($c_position) { + $p_position = $c_position; + } else { + break; + } + if (null === $placeholder_type) { + $placeholder_type_guess = $query[$p_position]; + } + + $new_pos = $this->_skipDelimitedStrings($query, $position, $p_position); + if (PEAR::isError($new_pos)) { + return $new_pos; + } + if ($new_pos != $position) { + $position = $new_pos; + continue; //evaluate again starting from the new position + } + + if ($query[$position] == $placeholder_type_guess) { + if (null === $placeholder_type) { + $placeholder_type = $query[$p_position]; + $question = $colon = $placeholder_type; + if (!empty($types) && is_array($types)) { + if ($placeholder_type == ':') { + if (is_int(key($types))) { + $types_tmp = $types; + $types = array(); + $count = -1; + } + } else { + $types = array_values($types); + } + } + } + if ($placeholder_type == ':') { + $regexp = '/^.{'.($position+1).'}('.$this->options['bindname_format'].').*$/s'; + $parameter = preg_replace($regexp, '\\1', $query); + if ($parameter === '') { + $err = $this->raiseError(MDB2_ERROR_SYNTAX, null, null, + 'named parameter name must match "bindname_format" option', __FUNCTION__); + return $err; + } + // use parameter name in type array + if (isset($count) && isset($types_tmp[++$count])) { + $types[$parameter] = $types_tmp[$count]; + } + $length = strlen($parameter) + 1; + } else { + ++$parameter; + //$length = strlen($parameter); + $length = 1; // strlen('?') + } + if (!in_array($parameter, $positions)) { + $positions[] = $parameter; + } + if (isset($types[$parameter]) + && ($types[$parameter] == 'clob' || $types[$parameter] == 'blob') + ) { + if (!isset($lobs[$parameter])) { + $lobs[$parameter] = $parameter; + } + $value = $this->quote(true, $types[$parameter]); + if (PEAR::isError($value)) { + return $value; + } + $query = substr_replace($query, $value, $p_position, $length); + $position = $p_position + strlen($value) - 1; + } elseif ($placeholder_type == '?') { + $query = substr_replace($query, ':'.$parameter, $p_position, 1); + $position = $p_position + $length; + } else { + $position = $p_position + 1; + } + } else { + $position = $p_position; + } + } + if (is_array($lobs)) { + $columns = $variables = ''; + foreach ($lobs as $parameter => $field) { + $columns.= ($columns ? ', ' : ' RETURNING ').$field; + $variables.= ($variables ? ', ' : ' INTO ').':'.$parameter; + } + $query.= $columns.$variables; + } + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $statement = @OCIParse($connection, $query); + if (!$statement) { + $err = $this->raiseError(null, null, null, + 'Could not create statement', __FUNCTION__); + return $err; + } + + $class_name = 'MDB2_Statement_'.$this->phptype; + $obj = new $class_name($this, $statement, $positions, $query, $types, $result_types, $is_manip, $limit, $offset); + $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'post', 'result' => $obj)); + return $obj; + } + + // }}} + // {{{ nextID() + + /** + * Returns the next free id of a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true the sequence is + * automatic created, if it + * not exists + * @return mixed MDB2 Error Object or id + * @access public + */ + function nextID($seq_name, $ondemand = true) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $query = "SELECT $sequence_name.nextval FROM DUAL"; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + $result = $this->queryOne($query, 'integer'); + $this->popExpect(); + $this->popErrorHandling(); + if (PEAR::isError($result)) { + if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { + $this->loadModule('Manager', null, true); + $result = $this->manager->createSequence($seq_name); + if (PEAR::isError($result)) { + return $result; + } + return $this->nextId($seq_name, false); + } + } + return $result; + } + + // }}} + // {{{ lastInsertID() + + /** + * Returns the autoincrement ID if supported or $id or fetches the current + * ID in a sequence called: $table.(empty($field) ? '' : '_'.$field) + * + * @param string $table name of the table into which a new row was inserted + * @param string $field name of the field into which a new row was inserted + * @return mixed MDB2 Error Object or id + * @access public + */ + function lastInsertID($table = null, $field = null) + { + $old_seq = $table.(empty($field) ? '' : '_'.$field); + $sequence_name = $this->quoteIdentifier($this->getSequenceName($table), true); + $result = $this->queryOne("SELECT $sequence_name.currval", 'integer'); + if (PEAR::isError($result)) { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($old_seq), true); + $result = $this->queryOne("SELECT $sequence_name.currval", 'integer'); + } + + return $result; + } + + // }}} + // {{{ currId() + + /** + * Returns the current id of a sequence + * + * @param string $seq_name name of the sequence + * @return mixed MDB2_Error or id + * @access public + */ + function currId($seq_name) + { + $sequence_name = $this->getSequenceName($seq_name); + $query = 'SELECT (last_number-1) FROM all_sequences'; + $query.= ' WHERE sequence_name='.$this->quote($sequence_name, 'text'); + $query.= ' OR sequence_name='.$this->quote(strtoupper($sequence_name), 'text'); + return $this->queryOne($query, 'integer'); + } +} + +/** + * MDB2 OCI8 result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_Result_oci8 extends MDB2_Result_Common +{ + // }}} + // {{{ fetchRow() + + /** + * Fetch a row and insert the data into an existing array. + * + * @param int $fetchmode how the array data should be indexed + * @param int $rownum number of the row where the data can be found + * @return int data array on success, a MDB2 error on failure + * @access public + */ + function fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + { + if (null !== $rownum) { + $seek = $this->seek($rownum); + if (PEAR::isError($seek)) { + return $seek; + } + } + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + @OCIFetchInto($this->result, $row, OCI_ASSOC+OCI_RETURN_NULLS); + if (is_array($row) + && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + ) { + $row = array_change_key_case($row, $this->db->options['field_case']); + } + } else { + @OCIFetchInto($this->result, $row, OCI_RETURN_NULLS); + } + if (!$row) { + if (false === $this->result) { + $err = $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + return null; + } + // remove additional column at the end + if ($this->offset > 0) { + array_pop($row); + } + $mode = 0; + $rtrim = false; + if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { + if (empty($this->types)) { + $mode += MDB2_PORTABILITY_RTRIM; + } else { + $rtrim = true; + } + } + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if (!empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); + } + if (!empty($this->values)) { + $this->_assignBindColumns($row); + } + if ($fetchmode === MDB2_FETCHMODE_OBJECT) { + $object_class = $this->db->options['fetch_class']; + if ($object_class == 'stdClass') { + $row = (object) $row; + } else { + $rowObj = new $object_class($row); + $row = $rowObj; + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed Array variable that holds the names of columns as keys + * or an MDB2 error on failure. + * Some DBMS may not return any columns when the result set + * does not contain any rows. + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (PEAR::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_name = @OCIColumnName($this->result, $column + 1); + $columns[$column_name] = $column; + } + if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $columns = array_change_key_case($columns, $this->db->options['field_case']); + } + return $columns; + } + + // }}} + // {{{ numCols() + + /** + * Count the number of columns returned by the DBMS in a query result. + * + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + * @access public + */ + function numCols() + { + $cols = @OCINumCols($this->result); + if (null === $cols) { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return count($this->types); + } + return $this->db->raiseError(null, null, null, + 'Could not get column count', __FUNCTION__); + } + if ($this->offset > 0) { + --$cols; + } + return $cols; + } + + // }}} + // {{{ free() + + /** + * Free the internal resources associated with $result. + * + * @return boolean true on success, false if $result is invalid + * @access public + */ + function free() + { + if (is_resource($this->result) && $this->db->connection) { + $free = @OCIFreeCursor($this->result); + if (false === $free) { + return $this->db->raiseError(null, null, null, + 'Could not free result', __FUNCTION__); + } + } + $this->result = false; + return MDB2_OK; + + } +} + +/** + * MDB2 OCI8 buffered result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_BufferedResult_oci8 extends MDB2_Result_oci8 +{ + var $buffer; + var $buffer_rownum = - 1; + + // {{{ _fillBuffer() + + /** + * Fill the row buffer + * + * @param int $rownum row number upto which the buffer should be filled + if the row number is null all rows are ready into the buffer + * @return boolean true on success, false on failure + * @access protected + */ + function _fillBuffer($rownum = null) + { + if (isset($this->buffer) && is_array($this->buffer)) { + if (null === $rownum) { + if (!end($this->buffer)) { + return false; + } + } elseif (isset($this->buffer[$rownum])) { + return (bool)$this->buffer[$rownum]; + } + } + + $row = true; + while (((null === $rownum) || $this->buffer_rownum < $rownum) + && ($row = @OCIFetchInto($this->result, $buffer, OCI_RETURN_NULLS)) + ) { + ++$this->buffer_rownum; + // remove additional column at the end + if ($this->offset > 0) { + array_pop($buffer); + } + if (empty($this->types)) { + foreach (array_keys($buffer) as $key) { + if (is_a($buffer[$key], 'oci-lob')) { + $buffer[$key] = $buffer[$key]->load(); + } + } + } + $this->buffer[$this->buffer_rownum] = $buffer; + } + + if (!$row) { + ++$this->buffer_rownum; + $this->buffer[$this->buffer_rownum] = false; + return false; + } + return true; + } + + // }}} + // {{{ fetchRow() + + /** + * Fetch a row and insert the data into an existing array. + * + * @param int $fetchmode how the array data should be indexed + * @param int $rownum number of the row where the data can be found + * @return int data array on success, a MDB2 error on failure + * @access public + */ + function fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + { + if (false === $this->result) { + $err = $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + if (null === $this->result) { + return null; + } + if (null !== $rownum) { + $seek = $this->seek($rownum); + if (PEAR::isError($seek)) { + return $seek; + } + } + $target_rownum = $this->rownum + 1; + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if (!$this->_fillBuffer($target_rownum)) { + return null; + } + $row = $this->buffer[$target_rownum]; + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $column_names = $this->getColumnNames(); + foreach ($column_names as $name => $i) { + $column_names[$name] = $row[$i]; + } + $row = $column_names; + } + $mode = 0; + $rtrim = false; + if ($this->db->options['portability'] & MDB2_PORTABILITY_RTRIM) { + if (empty($this->types)) { + $mode += MDB2_PORTABILITY_RTRIM; + } else { + $rtrim = true; + } + } + if ($mode) { + $this->db->_fixResultArrayValues($row, $mode); + } + if (!empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row, $rtrim); + } + if (!empty($this->values)) { + $this->_assignBindColumns($row); + } + if ($fetchmode === MDB2_FETCHMODE_OBJECT) { + $object_class = $this->db->options['fetch_class']; + if ($object_class == 'stdClass') { + $row = (object) $row; + } else { + $rowObj = new $object_class($row); + $row = $rowObj; + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ seek() + + /** + * Seek to a specific row in a result set + * + * @param int $rownum number of the row where the data can be found + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function seek($rownum = 0) + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + $this->rownum = $rownum - 1; + return MDB2_OK; + } + + // }}} + // {{{ valid() + + /** + * Check if the end of the result set has been reached + * + * @return mixed true or false on sucess, a MDB2 error on failure + * @access public + */ + function valid() + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return true; + } + if ($this->_fillBuffer($this->rownum + 1)) { + return true; + } + return false; + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + if (false === $this->result) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + } + if (null === $this->result) { + return 0; + } + $this->_fillBuffer(); + return $this->buffer_rownum; + } + + // }}} + // {{{ free() + + /** + * Free the internal resources associated with $result. + * + * @return boolean true on success, false if $result is invalid + * @access public + */ + function free() + { + $this->buffer = null; + $this->buffer_rownum = null; + return parent::free(); + } +} + +/** + * MDB2 OCI8 statement driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_Statement_oci8 extends MDB2_Statement_Common +{ + // {{{ Variables (Properties) + + var $type_maxlengths = array(); + + // }}} + // {{{ bindParam() + + /** + * Bind a variable to a parameter of a prepared query. + * + * @param int $parameter the order number of the parameter in the query + * statement. The order number of the first parameter is 1. + * @param mixed &$value variable that is meant to be bound to specified + * parameter. The type of the value depends on the $type argument. + * @param string $type specifies the type of the field + * @param int $maxlength specifies the maximum length of the field; if set to -1, the + * current length of $value is used + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function bindParam($parameter, &$value, $type = null, $maxlength = -1) + { + if (!is_numeric($parameter)) { + $parameter = preg_replace('/^:(.*)$/', '\\1', $parameter); + } + if (MDB2_OK === ($ret = parent::bindParam($parameter, $value, $type))) { + $this->type_maxlengths[$parameter] = $maxlength; + } + + return $ret; + } + + // }}} + // {{{ _execute() + + /** + * Execute a prepared query statement helper method. + * + * @param mixed $result_class string which specifies which result class to use + * @param mixed $result_wrap_class string which specifies which class to wrap results in + * + * @return mixed MDB2_Result or integer (affected rows) on success, + * a MDB2 error on failure + * @access private + */ + function _execute($result_class = true, $result_wrap_class = false) + { + if (null === $this->statement) { + return parent::_execute($result_class, $result_wrap_class); + } + $this->db->last_query = $this->query; + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'pre', 'parameters' => $this->values)); + if ($this->db->getOption('disable_query')) { + $result = $this->is_manip ? 0 : null; + return $result; + } + + $connection = $this->db->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $result = MDB2_OK; + $lobs = $quoted_values = array(); + $i = 0; + foreach ($this->positions as $parameter) { + if (!array_key_exists($parameter, $this->values)) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Unable to bind to missing placeholder: '.$parameter, __FUNCTION__); + } + $type = array_key_exists($parameter, $this->types) ? $this->types[$parameter] : null; + if ($type == 'clob' || $type == 'blob') { + $lobs[$i]['file'] = false; + if (is_resource($this->values[$parameter])) { + $fp = $this->values[$parameter]; + $this->values[$parameter] = ''; + while (!feof($fp)) { + $this->values[$parameter] .= fread($fp, 8192); + } + } elseif (is_a($this->values[$parameter], 'OCI-Lob')) { + //do nothing + } elseif ($this->db->getOption('lob_allow_url_include') + && preg_match('/^(\w+:\/\/)(.*)$/', $this->values[$parameter], $match) + ) { + $lobs[$i]['file'] = true; + if ($match[1] == 'file://') { + $this->values[$parameter] = $match[2]; + } + } + $lobs[$i]['value'] = $this->values[$parameter]; + $lobs[$i]['descriptor'] =& $this->values[$parameter]; + // Test to see if descriptor has already been created for this + // variable (i.e. if it has been bound more than once): + if (!is_a($this->values[$parameter], 'OCI-Lob')) { + $this->values[$parameter] = @OCINewDescriptor($connection, OCI_D_LOB); + if (false === $this->values[$parameter]) { + $result = $this->db->raiseError(null, null, null, + 'Unable to create descriptor for LOB in parameter: '.$parameter, __FUNCTION__); + break; + } + } + $lob_type = ($type == 'blob' ? OCI_B_BLOB : OCI_B_CLOB); + if (!@OCIBindByName($this->statement, ':'.$parameter, $lobs[$i]['descriptor'], -1, $lob_type)) { + $result = $this->db->raiseError($this->statement, null, null, + 'could not bind LOB parameter', __FUNCTION__); + break; + } + } else if ($type == OCI_B_BFILE) { + // Test to see if descriptor has already been created for this + // variable (i.e. if it has been bound more than once): + if (!is_a($this->values[$parameter], "OCI-Lob")) { + $this->values[$parameter] = @OCINewDescriptor($connection, OCI_D_FILE); + if (false === $this->values[$parameter]) { + $result = $this->db->raiseError(null, null, null, + 'Unable to create descriptor for BFILE in parameter: '.$parameter, __FUNCTION__); + break; + } + } + if (!@OCIBindByName($this->statement, ':'.$parameter, $this->values[$parameter], -1, $type)) { + $result = $this->db->raiseError($this->statement, null, null, + 'Could not bind BFILE parameter', __FUNCTION__); + break; + } + } else if ($type == OCI_B_ROWID) { + // Test to see if descriptor has already been created for this + // variable (i.e. if it has been bound more than once): + if (!is_a($this->values[$parameter], "OCI-Lob")) { + $this->values[$parameter] = @OCINewDescriptor($connection, OCI_D_ROWID); + if (false === $this->values[$parameter]) { + $result = $this->db->raiseError(null, null, null, + 'Unable to create descriptor for ROWID in parameter: '.$parameter, __FUNCTION__); + break; + } + } + if (!@OCIBindByName($this->statement, ':'.$parameter, $this->values[$parameter], -1, $type)) { + $result = $this->db->raiseError($this->statement, null, null, + 'Could not bind ROWID parameter', __FUNCTION__); + break; + } + } else if ($type == OCI_B_CURSOR) { + // Test to see if cursor has already been allocated for this + // variable (i.e. if it has been bound more than once): + if (!is_resource($this->values[$parameter]) || !get_resource_type($this->values[$parameter]) == "oci8 statement") { + $this->values[$parameter] = @OCINewCursor($connection); + if (false === $this->values[$parameter]) { + $result = $this->db->raiseError(null, null, null, + 'Unable to allocate cursor for parameter: '.$parameter, __FUNCTION__); + break; + } + } + if (!@OCIBindByName($this->statement, ':'.$parameter, $this->values[$parameter], -1, $type)) { + $result = $this->db->raiseError($this->statement, null, null, + 'Could not bind CURSOR parameter', __FUNCTION__); + break; + } + } else { + $maxlength = array_key_exists($parameter, $this->type_maxlengths) ? $this->type_maxlengths[$parameter] : -1; + $this->values[$parameter] = $this->db->quote($this->values[$parameter], $type, false); + $quoted_values[$i] =& $this->values[$parameter]; + if (PEAR::isError($quoted_values[$i])) { + return $quoted_values[$i]; + } + if (!@OCIBindByName($this->statement, ':'.$parameter, $quoted_values[$i], $maxlength)) { + $result = $this->db->raiseError($this->statement, null, null, + 'could not bind non-abstract parameter', __FUNCTION__); + break; + } + } + ++$i; + } + + $lob_keys = array_keys($lobs); + if (!PEAR::isError($result)) { + $mode = (!empty($lobs) || $this->db->in_transaction) ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS; + if (!@OCIExecute($this->statement, $mode)) { + $err = $this->db->raiseError($this->statement, null, null, + 'could not execute statement', __FUNCTION__); + return $err; + } + + if (!empty($lobs)) { + foreach ($lob_keys as $i) { + if ((null !== $lobs[$i]['value']) && $lobs[$i]['value'] !== '') { + if (is_object($lobs[$i]['value'])) { + // Probably a NULL LOB + // @see http://bugs.php.net/bug.php?id=27485 + continue; + } + if ($lobs[$i]['file']) { + $result = $lobs[$i]['descriptor']->savefile($lobs[$i]['value']); + } else { + $result = $lobs[$i]['descriptor']->save($lobs[$i]['value']); + } + if (!$result) { + $result = $this->db->raiseError(null, null, null, + 'Unable to save descriptor contents', __FUNCTION__); + break; + } + } + } + + if (!PEAR::isError($result)) { + if (!$this->db->in_transaction) { + if (!@OCICommit($connection)) { + $result = $this->db->raiseError(null, null, null, + 'Unable to commit transaction', __FUNCTION__); + } + } else { + ++$this->db->uncommitedqueries; + } + } + } + } + + if (PEAR::isError($result)) { + return $result; + } + + if ($this->is_manip) { + $affected_rows = $this->db->_affectedRows($connection, $this->statement); + return $affected_rows; + } + + $result = $this->db->_wrapResult($this->statement, $this->result_types, + $result_class, $result_wrap_class, $this->limit, $this->offset); + $this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result)); + return $result; + } + + // }}} + // {{{ free() + + /** + * Release resources allocated for the specified prepared query. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function free() + { + if (null === $this->positions) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + $result = MDB2_OK; + + if ((null !== $this->statement) && !@OCIFreeStatement($this->statement)) { + $result = $this->db->raiseError(null, null, null, + 'Could not free statement', __FUNCTION__); + } + + parent::free(); + return $result; + } +} +?>
\ No newline at end of file diff --git a/3rdparty/Sabre/CalDAV/CalendarQueryValidator.php b/3rdparty/Sabre/CalDAV/CalendarQueryValidator.php index 4bcd32cdf88..8f674840e87 100755 --- a/3rdparty/Sabre/CalDAV/CalendarQueryValidator.php +++ b/3rdparty/Sabre/CalDAV/CalendarQueryValidator.php @@ -304,28 +304,29 @@ class Sabre_CalDAV_CalendarQueryValidator { // one is the first to trigger. Based on this, we can // determine if we can 'give up' expanding events. $firstAlarm = null; - foreach($expandedEvent->VALARM as $expandedAlarm) { + if ($expandedEvent->VALARM !== null) { + foreach($expandedEvent->VALARM as $expandedAlarm) { - $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime(); - if ($expandedAlarm->isInTimeRange($start, $end)) { - return true; - } + $effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime(); + if ($expandedAlarm->isInTimeRange($start, $end)) { + return true; + } - if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') { - // This is an alarm with a non-relative trigger - // time, likely created by a buggy client. The - // implication is that every alarm in this - // recurring event trigger at the exact same - // time. It doesn't make sense to traverse - // further. - } else { - // We store the first alarm as a means to - // figure out when we can stop traversing. - if (!$firstAlarm || $effectiveTrigger < $firstAlarm) { - $firstAlarm = $effectiveTrigger; + if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') { + // This is an alarm with a non-relative trigger + // time, likely created by a buggy client. The + // implication is that every alarm in this + // recurring event trigger at the exact same + // time. It doesn't make sense to traverse + // further. + } else { + // We store the first alarm as a means to + // figure out when we can stop traversing. + if (!$firstAlarm || $effectiveTrigger < $firstAlarm) { + $firstAlarm = $effectiveTrigger; + } } } - } if (is_null($firstAlarm)) { // No alarm was found. diff --git a/3rdparty/Sabre/CalDAV/Plugin.php b/3rdparty/Sabre/CalDAV/Plugin.php index 5903968c003..c56ab384844 100755 --- a/3rdparty/Sabre/CalDAV/Plugin.php +++ b/3rdparty/Sabre/CalDAV/Plugin.php @@ -49,23 +49,23 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { /** * The email handler for invites and other scheduling messages. - * - * @var Sabre_CalDAV_Schedule_IMip + * + * @var Sabre_CalDAV_Schedule_IMip */ protected $imipHandler; /** * Sets the iMIP handler. * - * iMIP = The email transport of iCalendar scheduling messages. Setting - * this is optional, but if you want the server to allow invites to be sent + * iMIP = The email transport of iCalendar scheduling messages. Setting + * this is optional, but if you want the server to allow invites to be sent * out, you must set a handler. * - * Specifically iCal will plain assume that the server supports this. If - * the server doesn't, iCal will display errors when inviting people to + * Specifically iCal will plain assume that the server supports this. If + * the server doesn't, iCal will display errors when inviting people to * events. * - * @param Sabre_CalDAV_Schedule_IMip $imipHandler + * @param Sabre_CalDAV_Schedule_IMip $imipHandler * @return void */ public function setIMipHandler(Sabre_CalDAV_Schedule_IMip $imipHandler) { @@ -723,12 +723,12 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { if (!$originator) { throw new Sabre_DAV_Exception_BadRequest('The Originator: header must be specified when making POST requests'); - } + } if (!$recipients) { throw new Sabre_DAV_Exception_BadRequest('The Recipient: header must be specified when making POST requests'); - } + } - if (!preg_match('/^mailto:(.*)@(.*)$/', $originator)) { + if (!preg_match('/^mailto:(.*)@(.*)$/i', $originator)) { throw new Sabre_DAV_Exception_BadRequest('Originator must start with mailto: and must be valid email address'); } $originator = substr($originator,7); @@ -737,14 +737,14 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { foreach($recipients as $k=>$recipient) { $recipient = trim($recipient); - if (!preg_match('/^mailto:(.*)@(.*)$/', $recipient)) { + if (!preg_match('/^mailto:(.*)@(.*)$/i', $recipient)) { throw new Sabre_DAV_Exception_BadRequest('Recipients must start with mailto: and must be valid email address'); } $recipient = substr($recipient, 7); $recipients[$k] = $recipient; } - // We need to make sure that 'originator' matches one of the email + // We need to make sure that 'originator' matches one of the email // addresses of the selected principal. $principal = $outboxNode->getOwner(); $props = $this->server->getProperties($principal,array( @@ -760,7 +760,7 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { throw new Sabre_DAV_Exception_Forbidden('The addresses specified in the Originator header did not match any addresses in the owners calendar-user-address-set header'); } - try { + try { $vObject = Sabre_VObject_Reader::read($this->server->httpRequest->getBody(true)); } catch (Sabre_VObject_ParseException $e) { throw new Sabre_DAV_Exception_BadRequest('The request body must be a valid iCalendar object. Parse error: ' . $e->getMessage()); @@ -785,9 +785,10 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { } if (in_array($method, array('REQUEST','REPLY','ADD','CANCEL')) && $componentType==='VEVENT') { - $this->iMIPMessage($originator, $recipients, $vObject); + $result = $this->iMIPMessage($originator, $recipients, $vObject); $this->server->httpResponse->sendStatus(200); - $this->server->httpResponse->sendBody('Messages sent'); + $this->server->httpResponse->setHeader('Content-Type','application/xml'); + $this->server->httpResponse->sendBody($this->generateScheduleResponse($result)); } else { throw new Sabre_DAV_Exception_NotImplemented('This iTIP method is currently not implemented'); } @@ -796,18 +797,83 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin { /** * Sends an iMIP message by email. - * - * @param string $originator - * @param array $recipients - * @param Sabre_VObject_Component $vObject - * @return void + * + * This method must return an array with status codes per recipient. + * This should look something like: + * + * array( + * 'user1@example.org' => '2.0;Success' + * ) + * + * Formatting for this status code can be found at: + * https://tools.ietf.org/html/rfc5545#section-3.8.8.3 + * + * A list of valid status codes can be found at: + * https://tools.ietf.org/html/rfc5546#section-3.6 + * + * @param string $originator + * @param array $recipients + * @param Sabre_VObject_Component $vObject + * @return array */ protected function iMIPMessage($originator, array $recipients, Sabre_VObject_Component $vObject) { if (!$this->imipHandler) { - throw new Sabre_DAV_Exception_NotImplemented('No iMIP handler is setup on this server.'); + $resultStatus = '5.2;This server does not support this operation'; + } else { + $this->imipHandler->sendMessage($originator, $recipients, $vObject); + $resultStatus = '2.0;Success'; + } + + $result = array(); + foreach($recipients as $recipient) { + $result[$recipient] = $resultStatus; + } + + return $result; + + + } + + /** + * Generates a schedule-response XML body + * + * The recipients array is a key->value list, containing email addresses + * and iTip status codes. See the iMIPMessage method for a description of + * the value. + * + * @param array $recipients + * @return string + */ + public function generateScheduleResponse(array $recipients) { + + $dom = new DOMDocument('1.0','utf-8'); + $dom->formatOutput = true; + $xscheduleResponse = $dom->createElement('cal:schedule-response'); + $dom->appendChild($xscheduleResponse); + + foreach($this->server->xmlNamespaces as $namespace=>$prefix) { + + $xscheduleResponse->setAttribute('xmlns:' . $prefix, $namespace); + } - $this->imipHandler->sendMessage($originator, $recipients, $vObject); + + foreach($recipients as $recipient=>$status) { + $xresponse = $dom->createElement('cal:response'); + + $xrecipient = $dom->createElement('cal:recipient'); + $xrecipient->appendChild($dom->createTextNode($recipient)); + $xresponse->appendChild($xrecipient); + + $xrequestStatus = $dom->createElement('cal:request-status'); + $xrequestStatus->appendChild($dom->createTextNode($status)); + $xresponse->appendChild($xrequestStatus); + + $xscheduleResponse->appendChild($xresponse); + + } + + return $dom->saveXML(); } diff --git a/3rdparty/Sabre/CalDAV/Version.php b/3rdparty/Sabre/CalDAV/Version.php index 289a0c83a34..ace9901c089 100755 --- a/3rdparty/Sabre/CalDAV/Version.php +++ b/3rdparty/Sabre/CalDAV/Version.php @@ -14,7 +14,7 @@ class Sabre_CalDAV_Version { /** * Full version number */ - const VERSION = '1.6.3'; + const VERSION = '1.6.4'; /** * Stability : alpha, beta, stable diff --git a/3rdparty/Sabre/CardDAV/Plugin.php b/3rdparty/Sabre/CardDAV/Plugin.php index ca20e468497..96def6dd96b 100755 --- a/3rdparty/Sabre/CardDAV/Plugin.php +++ b/3rdparty/Sabre/CardDAV/Plugin.php @@ -154,7 +154,10 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin { $val = stream_get_contents($val); // Taking out \r to not screw up the xml output - $returnedProperties[200][$addressDataProp] = str_replace("\r","", $val); + //$returnedProperties[200][$addressDataProp] = str_replace("\r","", $val); + // The stripping of \r breaks the Mail App in OSX Mountain Lion + // this is fixed in master, but not backported. /Tanghus + $returnedProperties[200][$addressDataProp] = $val; } } diff --git a/3rdparty/Sabre/DAV/Locks/Plugin.php b/3rdparty/Sabre/DAV/Locks/Plugin.php index fd956950b8a..035b3a63863 100755 --- a/3rdparty/Sabre/DAV/Locks/Plugin.php +++ b/3rdparty/Sabre/DAV/Locks/Plugin.php @@ -292,7 +292,10 @@ class Sabre_DAV_Locks_Plugin extends Sabre_DAV_ServerPlugin { $this->server->tree->getNodeForPath($uri); // We need to call the beforeWriteContent event for RFC3744 - $this->server->broadcastEvent('beforeWriteContent',array($uri)); + // Edit: looks like this is not used, and causing problems now. + // + // See Issue 222 + // $this->server->broadcastEvent('beforeWriteContent',array($uri)); } catch (Sabre_DAV_Exception_NotFound $e) { diff --git a/3rdparty/Sabre/DAV/Version.php b/3rdparty/Sabre/DAV/Version.php index 40cfe81b34f..274646240ab 100755 --- a/3rdparty/Sabre/DAV/Version.php +++ b/3rdparty/Sabre/DAV/Version.php @@ -14,7 +14,7 @@ class Sabre_DAV_Version { /** * Full version number */ - const VERSION = '1.6.3'; + const VERSION = '1.6.4'; /** * Stability : alpha, beta, stable diff --git a/3rdparty/Sabre/HTTP/BasicAuth.php b/3rdparty/Sabre/HTTP/BasicAuth.php index a747cc6a31b..f90ed24f5d8 100755 --- a/3rdparty/Sabre/HTTP/BasicAuth.php +++ b/3rdparty/Sabre/HTTP/BasicAuth.php @@ -46,7 +46,7 @@ class Sabre_HTTP_BasicAuth extends Sabre_HTTP_AbstractAuth { if (strpos(strtolower($auth),'basic')!==0) return false; - return explode(':', base64_decode(substr($auth, 6))); + return explode(':', base64_decode(substr($auth, 6)),2); } diff --git a/3rdparty/Sabre/HTTP/Version.php b/3rdparty/Sabre/HTTP/Version.php index 23dc7f8a7a1..e6b4f7e5358 100755 --- a/3rdparty/Sabre/HTTP/Version.php +++ b/3rdparty/Sabre/HTTP/Version.php @@ -14,7 +14,7 @@ class Sabre_HTTP_Version { /** * Full version number */ - const VERSION = '1.6.2'; + const VERSION = '1.6.4'; /** * Stability : alpha, beta, stable diff --git a/3rdparty/Sabre/VObject/Component/VEvent.php b/3rdparty/Sabre/VObject/Component/VEvent.php index 4cc1e36d7d6..d6b910874d0 100755 --- a/3rdparty/Sabre/VObject/Component/VEvent.php +++ b/3rdparty/Sabre/VObject/Component/VEvent.php @@ -42,14 +42,15 @@ class Sabre_VObject_Component_VEvent extends Sabre_VObject_Component { $effectiveStart = $this->DTSTART->getDateTime(); if (isset($this->DTEND)) { + + // The DTEND property is considered non inclusive. So for a 3 day + // event in july, dtstart and dtend would have to be July 1st and + // July 4th respectively. + // + // See: + // http://tools.ietf.org/html/rfc5545#page-54 $effectiveEnd = $this->DTEND->getDateTime(); - // If this was an all-day event, we should just increase the - // end-date by 1. Otherwise the event will last until the second - // the date changed, by increasing this by 1 day the event lasts - // all of the last day as well. - if ($this->DTSTART->getDateType() == Sabre_VObject_Element_DateTime::DATE) { - $effectiveEnd->modify('+1 day'); - } + } elseif (isset($this->DURATION)) { $effectiveEnd = clone $effectiveStart; $effectiveEnd->add( Sabre_VObject_DateTimeParser::parseDuration($this->DURATION) ); diff --git a/3rdparty/Sabre/VObject/RecurrenceIterator.php b/3rdparty/Sabre/VObject/RecurrenceIterator.php index 833aa091ab7..740270dd8f0 100755 --- a/3rdparty/Sabre/VObject/RecurrenceIterator.php +++ b/3rdparty/Sabre/VObject/RecurrenceIterator.php @@ -337,6 +337,8 @@ class Sabre_VObject_RecurrenceIterator implements Iterator { $this->endDate = clone $this->startDate; if (isset($this->baseEvent->DURATION)) { $this->endDate->add(Sabre_VObject_DateTimeParser::parse($this->baseEvent->DURATION->value)); + } elseif ($this->baseEvent->DTSTART->getDateType()===Sabre_VObject_Property_DateTime::DATE) { + $this->endDate->modify('+1 day'); } } $this->currentDate = clone $this->startDate; @@ -561,7 +563,7 @@ class Sabre_VObject_RecurrenceIterator implements Iterator { */ public function fastForward(DateTime $dt) { - while($this->valid() && $this->getDTEnd() < $dt) { + while($this->valid() && $this->getDTEnd() <= $dt) { $this->next(); } @@ -823,9 +825,40 @@ class Sabre_VObject_RecurrenceIterator implements Iterator { */ protected function nextYearly() { + $currentMonth = $this->currentDate->format('n'); + $currentYear = $this->currentDate->format('Y'); + $currentDayOfMonth = $this->currentDate->format('j'); + + // No sub-rules, so we just advance by year if (!$this->byMonth) { + + // Unless it was a leap day! + if ($currentMonth==2 && $currentDayOfMonth==29) { + + $counter = 0; + do { + $counter++; + // Here we increase the year count by the interval, until + // we hit a date that's also in a leap year. + // + // We could just find the next interval that's dividable by + // 4, but that would ignore the rule that there's no leap + // year every year that's dividable by a 100, but not by + // 400. (1800, 1900, 2100). So we just rely on the datetime + // functions instead. + $nextDate = clone $this->currentDate; + $nextDate->modify('+ ' . ($this->interval*$counter) . ' years'); + } while ($nextDate->format('n')!=2); + $this->currentDate = $nextDate; + + return; + + } + + // The easiest form $this->currentDate->modify('+' . $this->interval . ' years'); return; + } $currentMonth = $this->currentDate->format('n'); @@ -877,8 +910,8 @@ class Sabre_VObject_RecurrenceIterator implements Iterator { } else { - // no byDay or byMonthDay, so we can just loop through the - // months. + // These are the 'byMonth' rules, if there are no byDay or + // byMonthDay sub-rules. do { $currentMonth++; @@ -888,6 +921,7 @@ class Sabre_VObject_RecurrenceIterator implements Iterator { } } while (!in_array($currentMonth, $this->byMonth)); $this->currentDate->setDate($currentYear, $currentMonth, $currentDayOfMonth); + return; } diff --git a/3rdparty/Sabre/VObject/Version.php b/3rdparty/Sabre/VObject/Version.php index 2617c7b129d..9ee03d87118 100755 --- a/3rdparty/Sabre/VObject/Version.php +++ b/3rdparty/Sabre/VObject/Version.php @@ -14,7 +14,7 @@ class Sabre_VObject_Version { /** * Full version number */ - const VERSION = '1.3.3'; + const VERSION = '1.3.4'; /** * Stability : alpha, beta, stable diff --git a/3rdparty/simpletest/extensions/coverage/autocoverage.php b/3rdparty/simpletest/extensions/coverage/autocoverage.php new file mode 100644 index 00000000000..9fc961bf43a --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/autocoverage.php @@ -0,0 +1,29 @@ +<?php +/** + * @package SimpleTest + * @subpackage Extensions + */ +/** + * Include this in any file to start coverage, coverage will automatically end + * when process dies. + */ +require_once(dirname(__FILE__) .'/coverage.php'); + +if (CodeCoverage::isCoverageOn()) { + $coverage = CodeCoverage::getInstance(); + $coverage->startCoverage(); + register_shutdown_function("stop_coverage"); +} + +function stop_coverage() { + # hack until i can think of a way to run tests first and w/o exiting + $autorun = function_exists("run_local_tests"); + if ($autorun) { + $result = run_local_tests(); + } + CodeCoverage::getInstance()->stopCoverage(); + if ($autorun) { + exit($result ? 0 : 1); + } +} +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/bin/php-coverage-close.php b/3rdparty/simpletest/extensions/coverage/bin/php-coverage-close.php new file mode 100755 index 00000000000..9a5a52ba134 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/bin/php-coverage-close.php @@ -0,0 +1,14 @@ +<?php +/** + * Close code coverage data collection, next step is to generate report + * @package SimpleTest + * @subpackage Extensions + */ +/** + * include coverage files + */ +require_once(dirname(__FILE__) . '/../coverage.php'); +$cc = CodeCoverage::getInstance(); +$cc->readSettings(); +$cc->writeUntouched(); +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/bin/php-coverage-open.php b/3rdparty/simpletest/extensions/coverage/bin/php-coverage-open.php new file mode 100755 index 00000000000..c04e1fb512f --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/bin/php-coverage-open.php @@ -0,0 +1,31 @@ +<?php +/** + * Initialize code coverage data collection, next step is to run your tests + * with ini setting auto_prepend_file=autocoverage.php ... + * + * @package SimpleTest + * @subpackage Extensions + */ +# optional arguments: +# --include=<some filepath regexp> these files should be included coverage report +# --exclude=<come filepath regexp> these files should not be included in coverage report +# --maxdepth=2 when considering which file were not touched, scan directories +# +# Example: +# php-coverage-open.php --include='.*\.php$' --include='.*\.inc$' --exclude='.*/tests/.*' +/**#@+ + * include coverage files + */ +require_once(dirname(__FILE__) . '/../coverage_utils.php'); +CoverageUtils::requireSqlite(); +require_once(dirname(__FILE__) . '/../coverage.php'); +/**#@-*/ +$cc = new CodeCoverage(); +$cc->log = 'coverage.sqlite'; +$args = CoverageUtils::parseArguments($_SERVER['argv'], TRUE); +$cc->includes = CoverageUtils::issetOr($args['include[]'], array('.*\.php$')); +$cc->excludes = CoverageUtils::issetOr($args['exclude[]']); +$cc->maxDirectoryDepth = (int)CoverageUtils::issetOr($args['maxdepth'], '1'); +$cc->resetLog(); +$cc->writeSettings(); +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/bin/php-coverage-report.php b/3rdparty/simpletest/extensions/coverage/bin/php-coverage-report.php new file mode 100755 index 00000000000..d61c822d997 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/bin/php-coverage-report.php @@ -0,0 +1,29 @@ +<?php +/** + * Generate a code coverage report + * + * @package SimpleTest + * @subpackage Extensions + */ +# optional arguments: +# --reportDir=some/directory the default is ./coverage-report +# --title='My Coverage Report' title the main page of your report + +/**#@+ + * include coverage files + */ +require_once(dirname(__FILE__) . '/../coverage_utils.php'); +require_once(dirname(__FILE__) . '/../coverage.php'); +require_once(dirname(__FILE__) . '/../coverage_reporter.php'); +/**#@-*/ +$cc = CodeCoverage::getInstance(); +$cc->readSettings(); +$handler = new CoverageDataHandler($cc->log); +$report = new CoverageReporter(); +$args = CoverageUtils::parseArguments($_SERVER['argv']); +$report->reportDir = CoverageUtils::issetOr($args['reportDir'], 'coverage-report'); +$report->title = CoverageUtils::issetOr($args['title'], "Simpletest Coverage"); +$report->coverage = $handler->read(); +$report->untouched = $handler->readUntouchedFiles(); +$report->generate(); +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/coverage.php b/3rdparty/simpletest/extensions/coverage/coverage.php new file mode 100644 index 00000000000..44e5b679b82 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/coverage.php @@ -0,0 +1,196 @@ +<?php +/** +* @package SimpleTest +* @subpackage Extensions +*/ +/** +* load coverage data handle +*/ +require_once dirname(__FILE__) . '/coverage_data_handler.php'; + +/** + * Orchestrates code coverage both in this thread and in subthread under apache + * Assumes this is running on same machine as apache. + * @package SimpleTest + * @subpackage Extensions + */ +class CodeCoverage { + var $log; + var $root; + var $includes; + var $excludes; + var $directoryDepth; + var $maxDirectoryDepth = 20; // reasonable, otherwise arbitrary + var $title = "Code Coverage"; + + # NOTE: This assumes all code shares the same current working directory. + var $settingsFile = './code-coverage-settings.dat'; + + static $instance; + + function writeUntouched() { + $touched = array_flip($this->getTouchedFiles()); + $untouched = array(); + $this->getUntouchedFiles($untouched, $touched, '.', '.'); + $this->includeUntouchedFiles($untouched); + } + + function &getTouchedFiles() { + $handler = new CoverageDataHandler($this->log); + $touched = $handler->getFilenames(); + return $touched; + } + + function includeUntouchedFiles($untouched) { + $handler = new CoverageDataHandler($this->log); + foreach ($untouched as $file) { + $handler->writeUntouchedFile($file); + } + } + + function getUntouchedFiles(&$untouched, $touched, $parentPath, $rootPath, $directoryDepth = 1) { + $parent = opendir($parentPath); + while ($file = readdir($parent)) { + $path = "$parentPath/$file"; + if (is_dir($path)) { + if ($file != '.' && $file != '..') { + if ($this->isDirectoryIncluded($path, $directoryDepth)) { + $this->getUntouchedFiles($untouched, $touched, $path, $rootPath, $directoryDepth + 1); + } + } + } + else if ($this->isFileIncluded($path)) { + $relativePath = CoverageDataHandler::ltrim($rootPath .'/', $path); + if (!array_key_exists($relativePath, $touched)) { + $untouched[] = $relativePath; + } + } + } + closedir($parent); + } + + function resetLog() { + error_log('reseting log'); + $new_file = fopen($this->log, "w"); + if (!$new_file) { + throw new Exception("Could not create ". $this->log); + } + fclose($new_file); + if (!chmod($this->log, 0666)) { + throw new Exception("Could not change ownership on file ". $this->log); + } + $handler = new CoverageDataHandler($this->log); + $handler->createSchema(); + } + + function startCoverage() { + $this->root = getcwd(); + if(!extension_loaded("xdebug")) { + throw new Exception("Could not load xdebug extension"); + }; + xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); + } + + function stopCoverage() { + $cov = xdebug_get_code_coverage(); + $this->filter($cov); + $data = new CoverageDataHandler($this->log); + chdir($this->root); + $data->write($cov); + unset($data); // release sqlite connection + xdebug_stop_code_coverage(); + // make sure we wind up on same current working directory, otherwise + // coverage handler writer doesn't know what directory to chop off + chdir($this->root); + } + + function readSettings() { + if (file_exists($this->settingsFile)) { + $this->setSettings(file_get_contents($this->settingsFile)); + } else { + error_log("could not find file ". $this->settingsFile); + } + } + + function writeSettings() { + file_put_contents($this->settingsFile, $this->getSettings()); + } + + function getSettings() { + $data = array( + 'log' => realpath($this->log), + 'includes' => $this->includes, + 'excludes' => $this->excludes); + return serialize($data); + } + + function setSettings($settings) { + $data = unserialize($settings); + $this->log = $data['log']; + $this->includes = $data['includes']; + $this->excludes = $data['excludes']; + } + + function filter(&$coverage) { + foreach ($coverage as $file => $line) { + if (!$this->isFileIncluded($file)) { + unset($coverage[$file]); + } + } + } + + function isFileIncluded($file) { + if (!empty($this->excludes)) { + foreach ($this->excludes as $path) { + if (preg_match('|' . $path . '|', $file)) { + return False; + } + } + } + + if (!empty($this->includes)) { + foreach ($this->includes as $path) { + if (preg_match('|' . $path . '|', $file)) { + return True; + } + } + return False; + } + + return True; + } + + function isDirectoryIncluded($dir, $directoryDepth) { + if ($directoryDepth >= $this->maxDirectoryDepth) { + return false; + } + if (isset($this->excludes)) { + foreach ($this->excludes as $path) { + if (preg_match('|' . $path . '|', $dir)) { + return False; + } + } + } + + return True; + } + + static function isCoverageOn() { + $coverage = self::getInstance(); + $coverage->readSettings(); + if (empty($coverage->log) || !file_exists($coverage->log)) { + trigger_error('No coverage log'); + return False; + } + return True; + } + + static function getInstance() { + if (self::$instance == NULL) { + self::$instance = new CodeCoverage(); + self::$instance->readSettings(); + } + return self::$instance; + } +} +?> diff --git a/3rdparty/simpletest/extensions/coverage/coverage_calculator.php b/3rdparty/simpletest/extensions/coverage/coverage_calculator.php new file mode 100644 index 00000000000..f1aa57bbab5 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/coverage_calculator.php @@ -0,0 +1,98 @@ +<?php +/** +* @package SimpleTest +* @subpackage Extensions +*/ +/** +* @package SimpleTest +* @subpackage Extensions +*/ +class CoverageCalculator { + + function coverageByFileVariables($file, $coverage) { + $hnd = fopen($file, 'r'); + if ($hnd == null) { + throw new Exception("File $file is missing"); + } + $lines = array(); + for ($i = 1; !feof($hnd); $i++) { + $line = fgets($hnd); + $lineCoverage = $this->lineCoverageCodeToStyleClass($coverage, $i); + $lines[$i] = array('lineCoverage' => $lineCoverage, 'code' => $line); + } + + fclose($hnd); + + $var = compact('file', 'lines', 'coverage'); + return $var; + } + + function lineCoverageCodeToStyleClass($coverage, $line) { + if (!array_key_exists($line, $coverage)) { + return "comment"; + } + $code = $coverage[$line]; + if (empty($code)) { + return "comment"; + } + switch ($code) { + case -1: + return "missed"; + case -2: + return "dead"; + } + + return "covered"; + } + + function totalLoc($total, $coverage) { + return $total + sizeof($coverage); + } + + function lineCoverage($total, $line) { + # NOTE: counting dead code as covered, as it's almost always an executable line + # strange artifact of xdebug or underlying system + return $total + ($line > 0 || $line == -2 ? 1 : 0); + } + + function totalCoverage($total, $coverage) { + return $total + array_reduce($coverage, array(&$this, "lineCoverage")); + } + + static function reportFilename($filename) { + return preg_replace('|[/\\\\]|', '_', $filename) . '.html'; + } + + function percentCoverageByFile($coverage, $file, &$results) { + $byFileReport = self::reportFilename($file); + + $loc = sizeof($coverage); + if ($loc == 0) + return 0; + $lineCoverage = array_reduce($coverage, array(&$this, "lineCoverage")); + $percentage = 100 * ($lineCoverage / $loc); + $results[0][$file] = array('byFileReport' => $byFileReport, 'percentage' => $percentage); + } + + function variables($coverage, $untouched) { + $coverageByFile = array(); + array_walk($coverage, array(&$this, "percentCoverageByFile"), array(&$coverageByFile)); + + $totalLoc = array_reduce($coverage, array(&$this, "totalLoc")); + + if ($totalLoc > 0) { + $totalLinesOfCoverage = array_reduce($coverage, array(&$this, "totalCoverage")); + $totalPercentCoverage = 100 * ($totalLinesOfCoverage / $totalLoc); + } + + $untouchedPercentageDenominator = sizeof($coverage) + sizeof($untouched); + if ($untouchedPercentageDenominator > 0) { + $filesTouchedPercentage = 100 * sizeof($coverage) / $untouchedPercentageDenominator; + } + + $var = compact('coverageByFile', 'totalPercentCoverage', 'totalLoc', 'totalLinesOfCoverage', 'filesTouchedPercentage'); + $var['untouched'] = $untouched; + return $var; + } +} +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/coverage_data_handler.php b/3rdparty/simpletest/extensions/coverage/coverage_data_handler.php new file mode 100644 index 00000000000..bbf81106fc5 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/coverage_data_handler.php @@ -0,0 +1,125 @@ +<?php +/** + * @package SimpleTest + * @subpackage Extensions + */ +/** + * @todo which db abstraction layer is this? + */ +require_once 'DB/sqlite.php'; + +/** + * Persists code coverage data into SQLite database and aggregate data for convienent + * interpretation in report generator. Be sure to not to keep an instance longer + * than you have, otherwise you risk overwriting database edits from another process + * also trying to make updates. + * @package SimpleTest + * @subpackage Extensions + */ +class CoverageDataHandler { + + var $db; + + function __construct($filename) { + $this->filename = $filename; + $this->db = new SQLiteDatabase($filename); + if (empty($this->db)) { + throw new Exception("Could not create sqlite db ". $filename); + } + } + + function createSchema() { + $this->db->queryExec("create table untouched (filename text)"); + $this->db->queryExec("create table coverage (name text, coverage text)"); + } + + function &getFilenames() { + $filenames = array(); + $cursor = $this->db->unbufferedQuery("select distinct name from coverage"); + while ($row = $cursor->fetch()) { + $filenames[] = $row[0]; + } + + return $filenames; + } + + function write($coverage) { + foreach ($coverage as $file => $lines) { + $coverageStr = serialize($lines); + $relativeFilename = self::ltrim(getcwd() . '/', $file); + $sql = "insert into coverage (name, coverage) values ('$relativeFilename', '$coverageStr')"; + # if this fails, check you have write permission + $this->db->queryExec($sql); + } + } + + function read() { + $coverage = array_flip($this->getFilenames()); + foreach($coverage as $file => $garbage) { + $coverage[$file] = $this->readFile($file); + } + return $coverage; + } + + function &readFile($file) { + $sql = "select coverage from coverage where name = '$file'"; + $aggregate = array(); + $result = $this->db->query($sql); + while ($result->valid()) { + $row = $result->current(); + $this->aggregateCoverage($aggregate, unserialize($row[0])); + $result->next(); + } + + return $aggregate; + } + + function aggregateCoverage(&$total, $next) { + foreach ($next as $lineno => $code) { + if (!isset($total[$lineno])) { + $total[$lineno] = $code; + } else { + $total[$lineno] = $this->aggregateCoverageCode($total[$lineno], $code); + } + } + } + + function aggregateCoverageCode($code1, $code2) { + switch($code1) { + case -2: return -2; + case -1: return $code2; + default: + switch ($code2) { + case -2: return -2; + case -1: return $code1; + } + } + return $code1 + $code2; + } + + static function ltrim($cruft, $pristine) { + if(stripos($pristine, $cruft) === 0) { + return substr($pristine, strlen($cruft)); + } + return $pristine; + } + + function writeUntouchedFile($file) { + $relativeFile = CoverageDataHandler::ltrim('./', $file); + $sql = "insert into untouched values ('$relativeFile')"; + $this->db->queryExec($sql); + } + + function &readUntouchedFiles() { + $untouched = array(); + $result = $this->db->query("select filename from untouched order by filename"); + while ($result->valid()) { + $row = $result->current(); + $untouched[] = $row[0]; + $result->next(); + } + + return $untouched; + } +} +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/coverage_reporter.php b/3rdparty/simpletest/extensions/coverage/coverage_reporter.php new file mode 100644 index 00000000000..ba4e7161c2f --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/coverage_reporter.php @@ -0,0 +1,68 @@ +<?php +/** + * @package SimpleTest + * @subpackage Extensions + */ +/**#@+ + * include additional coverage files + */ +require_once dirname(__FILE__) .'/coverage_calculator.php'; +require_once dirname(__FILE__) .'/coverage_utils.php'; +require_once dirname(__FILE__) .'/simple_coverage_writer.php'; +/**#@-*/ + +/** + * Take aggregated coverage data and generate reports from it using smarty + * templates + * @package SimpleTest + * @subpackage Extensions + */ +class CoverageReporter { + var $coverage; + var $untouched; + var $reportDir; + var $title = 'Coverage'; + var $writer; + var $calculator; + + function __construct() { + $this->writer = new SimpleCoverageWriter(); + $this->calculator = new CoverageCalculator(); + } + + function generateSummaryReport($out) { + $variables = $this->calculator->variables($this->coverage, $this->untouched); + $variables['title'] = $this->title; + $report = $this->writer->writeSummary($out, $variables); + fwrite($out, $report); + } + + function generate() { + CoverageUtils::mkdir($this->reportDir); + + $index = $this->reportDir .'/index.html'; + $hnd = fopen($index, 'w'); + $this->generateSummaryReport($hnd); + fclose($hnd); + + foreach ($this->coverage as $file => $cov) { + $byFile = $this->reportDir .'/'. self::reportFilename($file); + $byFileHnd = fopen($byFile, 'w'); + $this->generateCoverageByFile($byFileHnd, $file, $cov); + fclose($byFileHnd); + } + + echo "generated report $index\n"; + } + + function generateCoverageByFile($out, $file, $cov) { + $variables = $this->calculator->coverageByFileVariables($file, $cov); + $variables['title'] = $this->title .' - '. $file; + $this->writer->writeByFile($out, $variables); + } + + static function reportFilename($filename) { + return preg_replace('|[/\\\\]|', '_', $filename) . '.html'; + } +} +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/coverage_utils.php b/3rdparty/simpletest/extensions/coverage/coverage_utils.php new file mode 100644 index 00000000000..d2c3a635f43 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/coverage_utils.php @@ -0,0 +1,114 @@ +<?php +/** + * @package SimpleTest + * @subpackage Extensions + */ +/** + * @package SimpleTest + * @subpackage Extensions + */ +class CoverageUtils { + + static function mkdir($dir) { + if (!file_exists($dir)) { + mkdir($dir, 0777, True); + } else { + if (!is_dir($dir)) { + throw new Exception($dir .' exists as a file, not a directory'); + } + } + } + + static function requireSqlite() { + if (!self::isPackageClassAvailable('DB/sqlite.php', 'SQLiteDatabase')) { + echo "sqlite library is required to be installed and available in include_path"; + exit(1); + } + } + + static function isPackageClassAvailable($includeFile, $class) { + @include_once($includeFile); + return class_exists($class); + } + + /** + * Parses simple parameters from CLI. + * + * Puts trailing parameters into string array in 'extraArguments' + * + * Example: + * $args = CoverageUtil::parseArguments($_SERVER['argv']); + * if ($args['verbose']) echo "Verbose Mode On\n"; + * $files = $args['extraArguments']; + * + * Example CLI: + * --foo=blah -x -h some trailing arguments + * + * if multiValueMode is true + * Example CLI: + * --include=a --include=b --exclude=c + * Then + * $args = CoverageUtil::parseArguments($_SERVER['argv']); + * $args['include[]'] will equal array('a', 'b') + * $args['exclude[]'] will equal array('c') + * $args['exclude'] will equal c + * $args['include'] will equal b NOTE: only keeps last value + * + * @param unknown_type $argv + * @param supportMutliValue - will store 2nd copy of value in an array with key "foo[]" + * @return unknown + */ + static public function parseArguments($argv, $mutliValueMode = False) { + $args = array(); + $args['extraArguments'] = array(); + array_shift($argv); // scriptname + foreach ($argv as $arg) { + if (ereg('^--([^=]+)=(.*)', $arg, $reg)) { + $args[$reg[1]] = $reg[2]; + if ($mutliValueMode) { + self::addItemAsArray($args, $reg[1], $reg[2]); + } + } elseif (ereg('^[-]{1,2}([^[:blank:]]+)', $arg, $reg)) { + $nonnull = ''; + $args[$reg[1]] = $nonnull; + if ($mutliValueMode) { + self::addItemAsArray($args, $reg[1], $nonnull); + } + } else { + $args['extraArguments'][] = $arg; + } + } + + return $args; + } + + /** + * Adds a value as an array of one, or appends to an existing array elements + * + * @param unknown_type $array + * @param unknown_type $item + */ + static function addItemAsArray(&$array, $key, $item) { + $array_key = $key .'[]'; + if (array_key_exists($array_key, $array)) { + $array[$array_key][] = $item; + } else { + $array[$array_key] = array($item); + } + } + + /** + * isset function with default value + * + * Example: $z = CoverageUtils::issetOr($array[$key], 'no value given') + * + * @param unknown_type $val + * @param unknown_type $default + * @return first value unless value is not set then returns 2nd arg or null if no 2nd arg + */ + static public function issetOr(&$val, $default = null) + { + return isset($val) ? $val : $default; + } +} +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/coverage_writer.php b/3rdparty/simpletest/extensions/coverage/coverage_writer.php new file mode 100644 index 00000000000..0a8519cb509 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/coverage_writer.php @@ -0,0 +1,16 @@ +<?php +/** + * @package SimpleTest + * @subpackage Extensions + */ +/** + * @package SimpleTest + * @subpackage Extensions + */ +interface CoverageWriter { + + function writeSummary($out, $variables); + + function writeByFile($out, $variables); +} +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/simple_coverage_writer.php b/3rdparty/simpletest/extensions/coverage/simple_coverage_writer.php new file mode 100644 index 00000000000..7eb73fc8ab9 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/simple_coverage_writer.php @@ -0,0 +1,39 @@ +<?php +/** + * SimpleCoverageWriter class file + * @package SimpleTest + * @subpackage UnitTester + * @version $Id: unit_tester.php 1882 2009-07-01 14:30:05Z lastcraft $ + */ +/** + * base coverage writer class + */ +require_once dirname(__FILE__) .'/coverage_writer.php'; + +/** + * SimpleCoverageWriter class + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleCoverageWriter implements CoverageWriter { + + function writeSummary($out, $variables) { + extract($variables); + $now = date("F j, Y, g:i a"); + ob_start(); + include dirname(__FILE__) . '/templates/index.php'; + $contents = ob_get_contents(); + fwrite ($out, $contents); + ob_end_clean(); + } + + function writeByFile($out, $variables) { + extract($variables); + ob_start(); + include dirname(__FILE__) . '/templates/file.php'; + $contents = ob_get_contents(); + fwrite ($out, $contents); + ob_end_clean(); + } +} +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/templates/file.php b/3rdparty/simpletest/extensions/coverage/templates/file.php new file mode 100644 index 00000000000..70f6903068c --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/templates/file.php @@ -0,0 +1,60 @@ +<html> +<head> +<title><?php echo $title ?></title> +</head> +<style type="text/css"> +body { + font-family: "Gill Sans MT", "Gill Sans", GillSans, Arial, Helvetica, sans-serif; +} +h1 { + font-size: medium; +} +#code { + border-spacing: 0; +} +.lineNo { + color: #ccc; +} +.code, .lineNo { + white-space: pre; + font-family: monospace; +} +.covered { + color: #090; +} +.missed { + color: #f00; +} +.dead { + color: #00f; +} +.comment { + color: #333; +} +</style> +<body> +<h1 id="title"><?php echo $title ?></h1> +<table id="code"> + <tbody> +<?php foreach ($lines as $lineNo => $line) { ?> + <tr> + <td><span class="lineNo"><?php echo $lineNo ?></span></td> + <td><span class="<?php echo $line['lineCoverage'] ?> code"><?php echo htmlentities($line['code']) ?></span></td> + </tr> +<?php } ?> + </tbody> +</table> +<h2>Legend</h2> +<dl> + <dt><span class="missed">Missed</span></dt> + <dd>lines code that <strong>were not</strong> excersized during program execution.</dd> + <dt><span class="covered">Covered</span></dt> + <dd>lines code <strong>were</strong> excersized during program execution.</dd> + <dt><span class="comment">Comment/non executable</span></dt> + <dd>Comment or non-executable line of code.</dd> + <dt><span class="dead">Dead</span></dt> + <dd>lines of code that according to xdebug could not be executed. This is counted as coverage code because + in almost all cases it is code that runnable.</dd> +</dl> +</body> +</html> diff --git a/3rdparty/simpletest/extensions/coverage/templates/index.php b/3rdparty/simpletest/extensions/coverage/templates/index.php new file mode 100644 index 00000000000..e4374e23809 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/templates/index.php @@ -0,0 +1,106 @@ +<html> +<head> +<title><?php echo $title ?></title> +</head> +<style type="text/css"> +h1 { + font-size: medium; +} + +body { + font-family: "Gill Sans MT", "Gill Sans", GillSans, Arial, Helvetica, + sans-serif; +} + +td.percentage { + text-align: right; +} + +caption { + border-bottom: thin solid; + font-weight: bolder; +} + +dt { + font-weight: bolder; +} + +table { + margin: 1em; +} +</style> +<body> +<h1 id="title"><?php echo $title ?></h1> +<table> + <caption>Summary</caption> + <tbody> + <tr> + <td>Total Coverage (<a href="#total-coverage">?</a>) :</td> + <td class="percentage"><span class="totalPercentCoverage"><?php echo number_format($totalPercentCoverage, 0) ?>%</span></td> + </tr> + <tr> + <td>Total Files Covered (<a href="#total-files-covered">?</a>) :</td> + <td class="percentage"><span class="filesTouchedPercentage"><?php echo number_format($filesTouchedPercentage, 0) ?>%</span></td> + </tr> + <tr> + <td>Report Generation Date :</td> + <td><?php echo $now ?></td> + </tr> + </tbody> +</table> +<table id="covered-files"> + <caption>Coverage (<a href="#coverage">?</a>)</caption> + <thead> + <tr> + <th>File</th> + <th>Coverage</th> + </tr> + </thead> + <tbody> + <?php foreach ($coverageByFile as $file => $coverage) { ?> + <tr> + <td><a class="byFileReportLink" href="<?php echo $coverage['byFileReport'] ?>"><?php echo $file ?></a></td> + <td class="percentage"><span class="percentCoverage"><?php echo number_format($coverage['percentage'], 0) ?>%</span></td> + </tr> + <?php } ?> + </tbody> +</table> +<table> + <caption>Files Not Covered (<a href="#untouched">?</a>)</caption> + <tbody> + <?php foreach ($untouched as $key => $file) { ?> + <tr> + <td><span class="untouchedFile"><?php echo $file ?></span></td> + </tr> + <?php } ?> + </tbody> +</table> + +<h2>Glossary</h2> +<dl> + <dt><a name="total-coverage">Total Coverage</a></dt> + <dd>Ratio of all the lines of executable code that were executed to the + lines of code that were not executed. This does not include the files + that were not covered at all.</dd> + <dt><a name="total-files-covered">Total Files Covered</a></dt> + <dd>This is the ratio of the number of files tested, to the number of + files not tested at all.</dd> + <dt><a name="coverage">Coverage</a></dt> + <dd>These files were parsed and loaded by the php interpreter while + running the tests. Percentage is determined by the ratio of number of + lines of code executed to the number of possible executable lines of + code. "dead" lines of code, or code that could not be executed + according to xdebug, are counted as covered because in almost all cases + it is the end of a logical loop.</dd> + <dt><a name="untouched">Files Not Covered</a></dt> + <dd>These files were not loaded by the php interpreter at anytime + during a unit test. You could consider these files having 0% coverage, + but because it is difficult to determine the total coverage unless you + could count the lines for executable code, this is not reflected in the + Total Coverage calculation.</dd> +</dl> + +<p>Code coverage generated by <a href="http://www.simpletest.org">SimpleTest</a></p> + +</body> +</html> diff --git a/3rdparty/simpletest/extensions/coverage/test/coverage_calculator_test.php b/3rdparty/simpletest/extensions/coverage/test/coverage_calculator_test.php new file mode 100644 index 00000000000..64bd8d463fb --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/test/coverage_calculator_test.php @@ -0,0 +1,65 @@ +<?php +require_once(dirname(__FILE__) . '/../../../autorun.php'); + +class CoverageCalculatorTest extends UnitTestCase { + function skip() { + $this->skipIf( + !file_exists('DB/sqlite.php'), + 'The Coverage extension needs to have PEAR installed'); + } + + function setUp() { + require_once dirname(__FILE__) .'/../coverage_calculator.php'; + $this->calc = new CoverageCalculator(); + } + + function testVariables() { + $coverage = array('file' => array(1,1,1,1)); + $untouched = array('missed-file'); + $variables = $this->calc->variables($coverage, $untouched); + $this->assertEqual(4, $variables['totalLoc']); + $this->assertEqual(100, $variables['totalPercentCoverage']); + $this->assertEqual(4, $variables['totalLinesOfCoverage']); + $expected = array('file' => array('byFileReport' => 'file.html', 'percentage' => 100)); + $this->assertEqual($expected, $variables['coverageByFile']); + $this->assertEqual(50, $variables['filesTouchedPercentage']); + $this->assertEqual($untouched, $variables['untouched']); + } + + function testPercentageCoverageByFile() { + $coverage = array(0,0,0,1,1,1); + $results = array(); + $this->calc->percentCoverageByFile($coverage, 'file', $results); + $pct = $results[0]; + $this->assertEqual(50, $pct['file']['percentage']); + $this->assertEqual('file.html', $pct['file']['byFileReport']); + } + + function testTotalLoc() { + $this->assertEqual(13, $this->calc->totalLoc(10, array(1,2,3))); + } + + function testLineCoverage() { + $this->assertEqual(10, $this->calc->lineCoverage(10, -1)); + $this->assertEqual(10, $this->calc->lineCoverage(10, 0)); + $this->assertEqual(11, $this->calc->lineCoverage(10, 1)); + } + + function testTotalCoverage() { + $this->assertEqual(11, $this->calc->totalCoverage(10, array(-1,1))); + } + + static function getAttribute($element, $attribute) { + $a = $element->attributes(); + return $a[$attribute]; + } + + static function dom($stream) { + rewind($stream); + $actual = stream_get_contents($stream); + $html = DOMDocument::loadHTML($actual); + return simplexml_import_dom($html); + } +} + +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/test/coverage_data_handler_test.php b/3rdparty/simpletest/extensions/coverage/test/coverage_data_handler_test.php new file mode 100644 index 00000000000..54c67a4a7b0 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/test/coverage_data_handler_test.php @@ -0,0 +1,83 @@ +<?php +require_once(dirname(__FILE__) . '/../../../autorun.php'); + +class CoverageDataHandlerTest extends UnitTestCase { + function skip() { + $this->skipIf( + !file_exists('DB/sqlite.php'), + 'The Coverage extension needs to have PEAR installed'); + } + + function setUp() { + require_once dirname(__FILE__) .'/../coverage_data_handler.php'; + } + + function testAggregateCoverageCode() { + $handler = new CoverageDataHandler($this->tempdb()); + $this->assertEqual(-2, $handler->aggregateCoverageCode(-2, -2)); + $this->assertEqual(-2, $handler->aggregateCoverageCode(-2, 10)); + $this->assertEqual(-2, $handler->aggregateCoverageCode(10, -2)); + $this->assertEqual(-1, $handler->aggregateCoverageCode(-1, -1)); + $this->assertEqual(10, $handler->aggregateCoverageCode(-1, 10)); + $this->assertEqual(10, $handler->aggregateCoverageCode(10, -1)); + $this->assertEqual(20, $handler->aggregateCoverageCode(10, 10)); + } + + function testSimpleWriteRead() { + $handler = new CoverageDataHandler($this->tempdb()); + $handler->createSchema(); + $coverage = array(10 => -2, 20 => -1, 30 => 0, 40 => 1); + $handler->write(array('file' => $coverage)); + + $actual = $handler->readFile('file'); + $expected = array(10 => -2, 20 => -1, 30 => 0, 40 => 1); + $this->assertEqual($expected, $actual); + } + + function testMultiFileWriteRead() { + $handler = new CoverageDataHandler($this->tempdb()); + $handler->createSchema(); + $handler->write(array( + 'file1' => array(-2, -1, 1), + 'file2' => array(-2, -1, 1) + )); + $handler->write(array( + 'file1' => array(-2, -1, 1) + )); + + $expected = array( + 'file1' => array(-2, -1, 2), + 'file2' => array(-2, -1, 1) + ); + $actual = $handler->read(); + $this->assertEqual($expected, $actual); + } + + function testGetfilenames() { + $handler = new CoverageDataHandler($this->tempdb()); + $handler->createSchema(); + $rawCoverage = array('file0' => array(), 'file1' => array()); + $handler->write($rawCoverage); + $actual = $handler->getFilenames(); + $this->assertEqual(array('file0', 'file1'), $actual); + } + + function testWriteUntouchedFiles() { + $handler = new CoverageDataHandler($this->tempdb()); + $handler->createSchema(); + $handler->writeUntouchedFile('bluejay'); + $handler->writeUntouchedFile('robin'); + $this->assertEqual(array('bluejay', 'robin'), $handler->readUntouchedFiles()); + } + + function testLtrim() { + $this->assertEqual('ber', CoverageDataHandler::ltrim('goo', 'goober')); + $this->assertEqual('some/file', CoverageDataHandler::ltrim('./', './some/file')); + $this->assertEqual('/x/y/z/a/b/c', CoverageDataHandler::ltrim('/a/b/', '/x/y/z/a/b/c')); + } + + function tempdb() { + return tempnam(NULL, 'coverage.test.db'); + } +} +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/test/coverage_reporter_test.php b/3rdparty/simpletest/extensions/coverage/test/coverage_reporter_test.php new file mode 100644 index 00000000000..a8b09962a04 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/test/coverage_reporter_test.php @@ -0,0 +1,22 @@ +<?php +require_once(dirname(__FILE__) . '/../../../autorun.php'); + +class CoverageReporterTest extends UnitTestCase { + function skip() { + $this->skipIf( + !file_exists('DB/sqlite.php'), + 'The Coverage extension needs to have PEAR installed'); + } + + function setUp() { + require_once dirname(__FILE__) .'/../coverage_reporter.php'; + new CoverageReporter(); + } + + function testreportFilename() { + $this->assertEqual("parula.php.html", CoverageReporter::reportFilename("parula.php")); + $this->assertEqual("warbler_parula.php.html", CoverageReporter::reportFilename("warbler/parula.php")); + $this->assertEqual("warbler_parula.php.html", CoverageReporter::reportFilename("warbler\\parula.php")); + } +} +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/test/coverage_test.php b/3rdparty/simpletest/extensions/coverage/test/coverage_test.php new file mode 100644 index 00000000000..f09d03f78a1 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/test/coverage_test.php @@ -0,0 +1,109 @@ +<?php +require_once(dirname(__FILE__) . '/../../../autorun.php'); +require_once(dirname(__FILE__) . '/../../../mock_objects.php'); + +class CodeCoverageTest extends UnitTestCase { + function skip() { + $this->skipIf( + !file_exists('DB/sqlite.php'), + 'The Coverage extension needs to have PEAR installed'); + } + + function setUp() { + require_once dirname(__FILE__) .'/../coverage.php'; + } + + function testIsFileIncluded() { + $coverage = new CodeCoverage(); + $this->assertTrue($coverage->isFileIncluded('aaa')); + $coverage->includes = array('a'); + $this->assertTrue($coverage->isFileIncluded('aaa')); + $coverage->includes = array('x'); + $this->assertFalse($coverage->isFileIncluded('aaa')); + $coverage->excludes = array('aa'); + $this->assertFalse($coverage->isFileIncluded('aaa')); + } + + function testIsFileIncludedRegexp() { + $coverage = new CodeCoverage(); + $coverage->includes = array('modules/.*\.php$'); + $coverage->excludes = array('bad-bunny.php'); + $this->assertFalse($coverage->isFileIncluded('modules/a.test')); + $this->assertFalse($coverage->isFileIncluded('modules/bad-bunny.test')); + $this->assertTrue($coverage->isFileIncluded('modules/test.php')); + $this->assertFalse($coverage->isFileIncluded('module-bad/good-bunny.php')); + $this->assertTrue($coverage->isFileIncluded('modules/good-bunny.php')); + } + + function testIsDirectoryIncludedPastMaxDepth() { + $coverage = new CodeCoverage(); + $coverage->maxDirectoryDepth = 5; + $this->assertTrue($coverage->isDirectoryIncluded('aaa', 1)); + $this->assertFalse($coverage->isDirectoryIncluded('aaa', 5)); + } + + function testIsDirectoryIncluded() { + $coverage = new CodeCoverage(); + $this->assertTrue($coverage->isDirectoryIncluded('aaa', 0)); + $coverage->excludes = array('b$'); + $this->assertTrue($coverage->isDirectoryIncluded('aaa', 0)); + $coverage->includes = array('a$'); // includes are ignore, all dirs are included unless excluded + $this->assertTrue($coverage->isDirectoryIncluded('aaa', 0)); + $coverage->excludes = array('.*a$'); + $this->assertFalse($coverage->isDirectoryIncluded('aaa', 0)); + } + + function testFilter() { + $coverage = new CodeCoverage(); + $data = array('a' => 0, 'b' => 0, 'c' => 0); + $coverage->includes = array('b'); + $coverage->filter($data); + $this->assertEqual(array('b' => 0), $data); + } + + function testUntouchedFiles() { + $coverage = new CodeCoverage(); + $touched = array_flip(array("test/coverage_test.php")); + $actual = array(); + $coverage->includes = array('coverage_test\.php$'); + $parentDir = realpath(dirname(__FILE__)); + $coverage->getUntouchedFiles($actual, $touched, $parentDir, $parentDir); + $this->assertEqual(array("coverage_test.php"), $actual); + } + + function testResetLog() { + $coverage = new CodeCoverage(); + $coverage->log = tempnam(NULL, 'php.xdebug.coverage.test.'); + $coverage->resetLog(); + $this->assertTrue(file_exists($coverage->log)); + } + + function testSettingsSerialization() { + $coverage = new CodeCoverage(); + $coverage->log = '/banana/boat'; + $coverage->includes = array('apple', 'orange'); + $coverage->excludes = array('tomato', 'pea'); + $data = $coverage->getSettings(); + $this->assertNotNull($data); + + $actual = new CodeCoverage(); + $actual->setSettings($data); + $this->assertEqual('/banana/boat', $actual->log); + $this->assertEqual(array('apple', 'orange'), $actual->includes); + $this->assertEqual(array('tomato', 'pea'), $actual->excludes); + } + + function testSettingsCanBeReadWrittenToDisk() { + $settings_file = 'banana-boat-coverage-settings-test.dat'; + $coverage = new CodeCoverage(); + $coverage->log = '/banana/boat'; + $coverage->settingsFile = $settings_file; + $coverage->writeSettings(); + + $actual = new CodeCoverage(); + $actual->settingsFile = $settings_file; + $actual->readSettings(); + $this->assertEqual('/banana/boat', $actual->log); + } +} +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/test/coverage_utils_test.php b/3rdparty/simpletest/extensions/coverage/test/coverage_utils_test.php new file mode 100644 index 00000000000..b900c5d2c43 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/test/coverage_utils_test.php @@ -0,0 +1,70 @@ +<?php +require_once dirname(__FILE__) . '/../../../autorun.php'; + +class CoverageUtilsTest extends UnitTestCase { + function skip() { + $this->skipIf( + !file_exists('DB/sqlite.php'), + 'The Coverage extension needs to have PEAR installed'); + } + + function setUp() { + require_once dirname(__FILE__) .'/../coverage_utils.php'; + } + + function testMkdir() { + CoverageUtils::mkdir(dirname(__FILE__)); + try { + CoverageUtils::mkdir(__FILE__); + $this->fail("Should give error about cannot create dir of a file"); + } catch (Exception $expected) { + } + } + + function testIsPackageClassAvailable() { + $coverageSource = dirname(__FILE__) .'/../coverage_calculator.php'; + $this->assertTrue(CoverageUtils::isPackageClassAvailable($coverageSource, 'CoverageCalculator')); + $this->assertFalse(CoverageUtils::isPackageClassAvailable($coverageSource, 'BogusCoverage')); + $this->assertFalse(CoverageUtils::isPackageClassAvailable('bogus-file', 'BogusCoverage')); + $this->assertTrue(CoverageUtils::isPackageClassAvailable('bogus-file', 'CoverageUtils')); + } + + function testParseArgumentsMultiValue() { + $actual = CoverageUtils::parseArguments(array('scriptname', '--a=b', '--a=c'), True); + $expected = array('extraArguments' => array(), 'a' => 'c', 'a[]' => array('b', 'c')); + $this->assertEqual($expected, $actual); + } + + function testParseArguments() { + $actual = CoverageUtils::parseArguments(array('scriptname', '--a=b', '-c', 'xxx')); + $expected = array('a' => 'b', 'c' => '', 'extraArguments' => array('xxx')); + $this->assertEqual($expected, $actual); + } + + function testParseDoubleDashNoArguments() { + $actual = CoverageUtils::parseArguments(array('scriptname', '--aa')); + $this->assertTrue(isset($actual['aa'])); + } + + function testParseHyphenedExtraArguments() { + $actual = CoverageUtils::parseArguments(array('scriptname', '--alpha-beta=b', 'gamma-lambda')); + $expected = array('alpha-beta' => 'b', 'extraArguments' => array('gamma-lambda')); + $this->assertEqual($expected, $actual); + } + + function testAddItemAsArray() { + $actual = array(); + CoverageUtils::addItemAsArray($actual, 'bird', 'duck'); + $this->assertEqual(array('bird[]' => array('duck')), $actual); + + CoverageUtils::addItemAsArray(&$actual, 'bird', 'pigeon'); + $this->assertEqual(array('bird[]' => array('duck', 'pigeon')), $actual); + } + + function testIssetOr() { + $data = array('bird' => 'gull'); + $this->assertEqual('lab', CoverageUtils::issetOr($data['dog'], 'lab')); + $this->assertEqual('gull', CoverageUtils::issetOr($data['bird'], 'sparrow')); + } +} +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/test/sample/code.php b/3rdparty/simpletest/extensions/coverage/test/sample/code.php new file mode 100644 index 00000000000..a2438f4bee0 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/test/sample/code.php @@ -0,0 +1,4 @@ +<?php +// sample code +$x = 1 + 2; +if (false) echo "dead";
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/test/simple_coverage_writer_test.php b/3rdparty/simpletest/extensions/coverage/test/simple_coverage_writer_test.php new file mode 100644 index 00000000000..2c9f9abc18b --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/test/simple_coverage_writer_test.php @@ -0,0 +1,69 @@ +<?php +require_once(dirname(__FILE__) . '/../../../autorun.php'); + +class SimpleCoverageWriterTest extends UnitTestCase { + function skip() { + $this->skipIf( + !file_exists('DB/sqlite.php'), + 'The Coverage extension needs to have PEAR installed'); + } + + function setUp() { + require_once dirname(__FILE__) .'/../simple_coverage_writer.php'; + require_once dirname(__FILE__) .'/../coverage_calculator.php'; + + } + + function testGenerateSummaryReport() { + $writer = new SimpleCoverageWriter(); + $coverage = array('file' => array(0, 1)); + $untouched = array('missed-file'); + $calc = new CoverageCalculator(); + $variables = $calc->variables($coverage, $untouched); + $variables['title'] = 'coverage'; + $out = fopen("php://memory", 'w'); + $writer->writeSummary($out, $variables); + $dom = self::dom($out); + $totalPercentCoverage = $dom->elements->xpath("//span[@class='totalPercentCoverage']"); + $this->assertEqual('50%', (string)$totalPercentCoverage[0]); + + $fileLinks = $dom->elements->xpath("//a[@class='byFileReportLink']"); + $fileLinkAttr = $fileLinks[0]->attributes(); + $this->assertEqual('file.html', $fileLinkAttr['href']); + $this->assertEqual('file', (string)($fileLinks[0])); + + $untouchedFile = $dom->elements->xpath("//span[@class='untouchedFile']"); + $this->assertEqual('missed-file', (string)$untouchedFile[0]); + } + + function testGenerateCoverageByFile() { + $writer = new SimpleCoverageWriter(); + $cov = array(3 => 1, 4 => -2); // 2 comments, 1 code, 1 dead (1-based indexes) + $out = fopen("php://memory", 'w'); + $file = dirname(__FILE__) .'/sample/code.php'; + $calc = new CoverageCalculator(); + $variables = $calc->coverageByFileVariables($file, $cov); + $variables['title'] = 'coverage'; + $writer->writeByFile($out, $variables); + $dom = self::dom($out); + + $cells = $dom->elements->xpath("//table[@id='code']/tbody/tr/td/span"); + $this->assertEqual("comment code", self::getAttribute($cells[1], 'class')); + $this->assertEqual("comment code", self::getAttribute($cells[3], 'class')); + $this->assertEqual("covered code", self::getAttribute($cells[5], 'class')); + $this->assertEqual("dead code", self::getAttribute($cells[7], 'class')); + } + + static function getAttribute($element, $attribute) { + $a = $element->attributes(); + return $a[$attribute]; + } + + static function dom($stream) { + rewind($stream); + $actual = stream_get_contents($stream); + $html = DOMDocument::loadHTML($actual); + return simplexml_import_dom($html); + } +} +?>
\ No newline at end of file diff --git a/3rdparty/simpletest/extensions/coverage/test/test.php b/3rdparty/simpletest/extensions/coverage/test/test.php new file mode 100644 index 00000000000..0af4dbf3e74 --- /dev/null +++ b/3rdparty/simpletest/extensions/coverage/test/test.php @@ -0,0 +1,14 @@ +<?php +// $Id: $ +require_once(dirname(__FILE__) . '/../../../autorun.php'); + +class CoverageUnitTests extends TestSuite { + function CoverageUnitTests() { + $this->TestSuite('Coverage Unit tests'); + $path = dirname(__FILE__) . '/*_test.php'; + foreach(glob($path) as $test) { + $this->addFile($test); + } + } +} +?>
\ No newline at end of file diff --git a/3rdparty/smb4php/smb.php b/3rdparty/smb4php/smb.php index 12c5890723b..c50b26b935e 100644 --- a/3rdparty/smb4php/smb.php +++ b/3rdparty/smb4php/smb.php @@ -166,6 +166,8 @@ class smb { return false; }elseif(substr($regs[0],0,31)=='NT_STATUS_OBJECT_PATH_NOT_FOUND'){ return false; + }elseif(substr($regs[0],0,29)=='NT_STATUS_FILE_IS_A_DIRECTORY'){ + return false; } trigger_error($regs[0].' params('.$params.')', E_USER_ERROR); } |