diff options
author | Michael Gapczynski <GapczynskiM@gmail.com> | 2011-07-04 16:36:30 -0400 |
---|---|---|
committer | Michael Gapczynski <GapczynskiM@gmail.com> | 2011-07-04 16:36:30 -0400 |
commit | 05389827fd8bc70b1092dc2bae1ed0335536cf4c (patch) | |
tree | 84dea83406b3b5d5cc0cf270a4e95875f3956300 /lib | |
parent | e24e2d0e16a360753cc0430f2ba75f903c669b14 (diff) | |
parent | 53ae56097de2098a2ffd1a8dd1076825bc93305d (diff) | |
download | nextcloud-server-05389827fd8bc70b1092dc2bae1ed0335536cf4c.tar.gz nextcloud-server-05389827fd8bc70b1092dc2bae1ed0335536cf4c.zip |
Merge branch 'master' into sharing
Conflicts:
.gitignore
lib/base.php
Diffstat (limited to 'lib')
-rw-r--r-- | lib/HTTP/WebDAV/Server/Filesystem.php | 7 | ||||
-rw-r--r-- | lib/MDB2/Driver/Datatype/sqlite3.php | 412 | ||||
-rw-r--r-- | lib/MDB2/Driver/Function/sqlite3.php | 161 | ||||
-rw-r--r-- | lib/MDB2/Driver/Manager/sqlite3.php | 1364 | ||||
-rw-r--r-- | lib/MDB2/Driver/Native/sqlite3.php | 58 | ||||
-rw-r--r-- | lib/MDB2/Driver/Reverse/sqlite3.php | 607 | ||||
-rw-r--r-- | lib/MDB2/Driver/sqlite3.php | 1361 | ||||
-rw-r--r-- | lib/User/backend.php | 126 | ||||
-rw-r--r-- | lib/User/database.php | 97 | ||||
-rw-r--r-- | lib/User/example.php | 97 | ||||
-rw-r--r-- | lib/app.php | 138 | ||||
-rw-r--r-- | lib/base.php | 33 | ||||
-rw-r--r-- | lib/config.php | 5 | ||||
-rw-r--r-- | lib/database.php | 52 | ||||
-rw-r--r-- | lib/files.php | 15 | ||||
-rw-r--r-- | lib/filestorage.php | 2 | ||||
-rw-r--r-- | lib/filesystem.php | 231 | ||||
-rwxr-xr-x[-rw-r--r--] | lib/helper.php | 43 | ||||
-rw-r--r-- | lib/installer.php | 83 | ||||
-rw-r--r-- | lib/l10n.php | 269 | ||||
-rw-r--r-- | lib/log.php | 14 | ||||
-rw-r--r-- | lib/ocsclient.php | 128 | ||||
-rw-r--r-- | lib/preferences.php | 8 | ||||
-rw-r--r-- | lib/setup.php | 36 | ||||
-rw-r--r-- | lib/template.php | 49 | ||||
-rw-r--r-- | lib/testcase.php | 93 | ||||
-rw-r--r-- | lib/user.php | 121 |
27 files changed, 5194 insertions, 416 deletions
diff --git a/lib/HTTP/WebDAV/Server/Filesystem.php b/lib/HTTP/WebDAV/Server/Filesystem.php index 164892c9d86..5f261643624 100644 --- a/lib/HTTP/WebDAV/Server/Filesystem.php +++ b/lib/HTTP/WebDAV/Server/Filesystem.php @@ -687,9 +687,14 @@ VALUES (?,?,?,?,?,'timeout',?,?)"); function UNLOCK(&$options) { $query = OC_DB::prepare("DELETE FROM *PREFIX*locks + WHERE path = '$options[path]' + AND token = '$options[token]'"); + /* + $query = OC_DB::prepare("DELETE FROM *PREFIX*locks WHERE path = ? AND token = ?"); - $query->execute(array($options[path]),$options[token]); + */ + $query->execute();#array($options[path]),$options[token]); return PEAR::isError($result) ? "409 Conflict" : "204 No Content"; } diff --git a/lib/MDB2/Driver/Datatype/sqlite3.php b/lib/MDB2/Driver/Datatype/sqlite3.php new file mode 100644 index 00000000000..378d5540cbe --- /dev/null +++ b/lib/MDB2/Driver/Datatype/sqlite3.php @@ -0,0 +1,412 @@ +<?php +// vim: set et ts=4 sw=4 fdm=marker: +// +----------------------------------------------------------------------+ +// | PHP version 5 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 2011 Robin Appelman | +// | 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: Robin Appelman <icewind1991@gmail.com | +// +----------------------------------------------------------------------+ +// +// $Id: sqlite.php,v 1.67 2008/02/22 19:58:06 quipo Exp $ +// + +require_once('MDB2/Driver/Datatype/Common.php'); + +/** + * MDB2 SQLite driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_Driver_Datatype_sqlite3 extends MDB2_Driver_Datatype_Common +{ + // {{{ _getCollationFieldDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $collation name of the collation + * + * @return string DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration. + */ + function _getCollationFieldDeclaration($collation) + { + return 'COLLATE '.$collation; + } + + // }}} + // {{{ 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'] : false; + $fixed = !empty($field['fixed']) ? $field['fixed'] : false; + return $fixed ? ($length ? 'CHAR('.$length.')' : 'CHAR('.$db->options['default_text_field_length'].')') + : ($length ? 'VARCHAR('.$length.')' : 'TEXT'); + case 'clob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 255) { + return 'TINYTEXT'; + } elseif ($length <= 65532) { + return 'TEXT'; + } elseif ($length <= 16777215) { + return 'MEDIUMTEXT'; + } + } + return 'LONGTEXT'; + case 'blob': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 255) { + return 'TINYBLOB'; + } elseif ($length <= 65532) { + return 'BLOB'; + } elseif ($length <= 16777215) { + return 'MEDIUMBLOB'; + } + } + return 'LONGBLOB'; + case 'integer': + if (!empty($field['length'])) { + $length = $field['length']; + if ($length <= 2) { + return 'SMALLINT'; + } elseif ($length == 3 || $length == 4) { + return 'INTEGER'; + } elseif ($length > 4) { + return 'BIGINT'; + } + } + return 'INTEGER'; + case 'boolean': + return 'BOOLEAN'; + case 'date': + return 'DATE'; + case 'time': + return 'TIME'; + case 'timestamp': + return 'DATETIME'; + case 'float': + return 'DOUBLE'.($db->options['fixed_float'] ? '('. + ($db->options['fixed_float']+2).','.$db->options['fixed_float'].')' : ''); + case 'decimal': + $length = !empty($field['length']) ? $field['length'] : 18; + $scale = !empty($field['scale']) ? $field['scale'] : $db->options['decimal_places']; + return 'DECIMAL('.$length.','.$scale.')'; + } + return ''; + } + + // }}} + // {{{ _getIntegerDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an integer type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $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: + * + * unsigned + * Boolean flag that indicates whether the field + * should be declared as unsigned integer if + * possible. + * + * default + * Integer 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 protected + */ + function _getIntegerDeclaration($name, $field) + { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $default = $autoinc = ''; + if (!empty($field['autoincrement'])) { + $autoinc = ' PRIMARY KEY AUTOINCREMENT'; + } elseif (array_key_exists('default', $field)) { + if ($field['default'] === '') { + $field['default'] = empty($field['notnull']) ? null : 0; + } + $default = ' DEFAULT '.$this->quote($field['default'], 'integer'); + } + + $notnull = empty($field['notnull']) ? '' : ' NOT NULL'; + $unsigned = empty($field['unsigned']) ? '' : ' UNSIGNED'; + $name = $db->quoteIdentifier($name, true); + if($autoinc){ + return $name.' '.$this->getTypeDeclaration($field).$autoinc; + }else{ + return $name.' '.$this->getTypeDeclaration($field).$unsigned.$default.$notnull.$autoinc; + } + } + + // }}} + // {{{ matchPattern() + + /** + * build a pattern matching string + * + * @access public + * + * @param array $pattern even keys are strings, odd are patterns (% and _) + * @param string $operator optional pattern operator (LIKE, ILIKE and maybe others in the future) + * @param string $field optional field name that is being matched against + * (might be required when emulating ILIKE) + * + * @return string SQL pattern + */ + function matchPattern($pattern, $operator = null, $field = null) + { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $match = ''; + if (!is_null($operator)) { + $field = is_null($field) ? '' : $field.' '; + $operator = strtoupper($operator); + switch ($operator) { + // case insensitive + case 'ILIKE': + $match = $field.'LIKE '; + break; + // case sensitive + case 'LIKE': + $match = $field.'LIKE '; + break; + default: + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'not a supported operator type:'. $operator, __FUNCTION__); + } + } + $match.= "'"; + foreach ($pattern as $key => $value) { + if ($key % 2) { + $match.= $value; + } else { + $match.= $db->escapePattern($db->escape($value)); + } + } + $match.= "'"; + $match.= $this->patternEscapeString(); + return $match; + } + + // }}} + // {{{ _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']); + $length = !empty($field['length']) ? $field['length'] : null; + $unsigned = !empty($field['unsigned']) ? $field['unsigned'] : null; + $fixed = null; + $type = array(); + switch ($db_type) { + case 'boolean': + $type[] = 'boolean'; + break; + case 'tinyint': + $type[] = 'integer'; + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 1; + break; + case 'smallint': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 2; + break; + case 'mediumint': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 3; + break; + case 'int': + case 'integer': + case 'serial': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 4; + break; + case 'bigint': + case 'bigserial': + $type[] = 'integer'; + $unsigned = preg_match('/ unsigned/i', $field['type']); + $length = 8; + break; + case 'clob': + $type[] = 'clob'; + $fixed = false; + break; + case 'tinytext': + case 'mediumtext': + case 'longtext': + case 'text': + case 'varchar': + case 'varchar2': + $fixed = false; + case 'char': + $type[] = 'text'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^(is|has)/', $field['name'])) { + $type = array_reverse($type); + } + } elseif (strstr($db_type, 'text')) { + $type[] = 'clob'; + $type = array_reverse($type); + } + if ($fixed !== false) { + $fixed = true; + } + break; + case 'date': + $type[] = 'date'; + $length = null; + break; + case 'datetime': + case 'timestamp': + $type[] = 'timestamp'; + $length = null; + break; + case 'time': + $type[] = 'time'; + $length = null; + break; + case 'float': + case 'double': + case 'real': + $type[] = 'float'; + break; + case 'decimal': + case 'numeric': + $type[] = 'decimal'; + $length = $length.','.$field['decimal']; + break; + case 'tinyblob': + case 'mediumblob': + case 'longblob': + case 'blob': + $type[] = 'blob'; + $length = null; + break; + case 'year': + $type[] = 'integer'; + $type[] = 'date'; + $length = null; + break; + 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/lib/MDB2/Driver/Function/sqlite3.php b/lib/MDB2/Driver/Function/sqlite3.php new file mode 100644 index 00000000000..0d6b329f0ad --- /dev/null +++ b/lib/MDB2/Driver/Function/sqlite3.php @@ -0,0 +1,161 @@ +<?php +// vim: set et ts=4 sw=4 fdm=marker: +// +----------------------------------------------------------------------+ +// | PHP versions 5 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 2011 Robin Appelman | +// | 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: Robin Appelman <icewind1991@gmail.com> | +// +----------------------------------------------------------------------+ +// +// + +require_once('MDB2/Driver/Function/Common.php'); + +/** + * MDB2 SQLite driver for the function modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_Driver_Function_sqlite3 extends MDB2_Driver_Function_Common +{ + // {{{ constructor + + /** + * Constructor + */ + function __construct($db_index) + { + parent::__construct($db_index); + // create all sorts of UDFs + } + + // {{{ 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. + * + * @return string to call a variable with the current timestamp + * @access public + */ + function now($type = 'timestamp') + { + switch ($type) { + case 'time': + return 'time(\'now\')'; + case 'date': + return 'date(\'now\')'; + case 'timestamp': + default: + return 'datetime(\'now\')'; + } + } + + // }}} + // {{{ 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) + { + return 'strftime("%s",'. $expression.', "utc")'; + } + + // }}} + // {{{ 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 (!is_null($length)) { + return "substr($value,$position,$length)"; + } + return "substr($value,$position,length($value))"; + } + + // }}} + // {{{ 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 '((RANDOM()+2147483648)/4294967296)'; + } + + // }}} + // {{{ replace() + + /** + * return string to call a function to get a replacement inside an SQL statement. + * + * @return string to call a function to get a replace + * @access public + */ + function replace($str, $from_str, $to_str) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $error =& $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'method not implemented', __FUNCTION__); + return $error; + } + + // }}} +} +?> diff --git a/lib/MDB2/Driver/Manager/sqlite3.php b/lib/MDB2/Driver/Manager/sqlite3.php new file mode 100644 index 00000000000..7096126a523 --- /dev/null +++ b/lib/MDB2/Driver/Manager/sqlite3.php @@ -0,0 +1,1364 @@ +<?php +// vim: set et ts=4 sw=4 fdm=marker: +// +----------------------------------------------------------------------+ +// | PHP versions 5 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 2011 Robin Appelman | +// | 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: Robin Appelman <icewind1991@gmail.com> | +// +----------------------------------------------------------------------+ +// +// + +require_once('MDB2/Driver/Manager/Common.php'); + +/** + * MDB2 SQLite driver for the management modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + * @author Lorenzo Alberton <l.alberton@quipo.it> + */ +class MDB2_Driver_Manager_sqlite3 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 info + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($name, $options = array()) + { + global $SERVERROOT; + $datadir=OC_CONFIG::getValue( "datadirectory", "$SERVERROOT/data" ); + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $database_file = $db->_getDatabaseFile($name); + if (file_exists($database_file)) { + return $db->raiseError(MDB2_ERROR_ALREADY_EXISTS, null, null, + 'database already exists', __FUNCTION__); + } + $php_errormsg = ''; + $database_file="$datadir/$database_file.db"; + $handle=new SQLite3($database_file); + if (!$handle) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + (isset($php_errormsg) ? $php_errormsg : 'could not create the database file'), __FUNCTION__); + } + //sqlite doesn't support the latin1 we use +// if (!empty($options['charset'])) { +// $query = 'PRAGMA encoding = ' . $db->quote($options['charset'], 'text'); +// $handle->exec($query); +// } + $handle->close(); + return MDB2_OK; + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @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; + } + + $database_file = $db->_getDatabaseFile($name); + if (!@file_exists($database_file)) { + return $db->raiseError(MDB2_ERROR_CANNOT_DROP, null, null, + 'database does not exist', __FUNCTION__); + } + $result = @unlink($database_file); + if (!$result) { + return $db->raiseError(MDB2_ERROR_CANNOT_DROP, null, null, + (isset($php_errormsg) ? $php_errormsg : 'could not remove the database file'), __FUNCTION__); + } + return MDB2_OK; + } + + // }}} + // {{{ _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['match'])) { + $query .= ' MATCH '.$definition['match']; + } + if (!empty($definition['onupdate']) && (strtoupper($definition['onupdate']) != 'NO ACTION')) { + $query .= ' ON UPDATE '.$definition['onupdate']; + } + 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; + } + + // }}} + // {{{ _getCreateTableQuery() + + /** + * Create a basic SQL query for a new table creation + * @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 + * @param array $options An associative array of table options + * @return mixed string (the SQL query) on success, a MDB2 error on failure + * @see createTable() + */ + function _getCreateTableQuery($name, $fields, $options = array()) + { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!$name) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no valid table name specified', __FUNCTION__); + } + if (empty($fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'no fields specified for table "'.$name.'"', __FUNCTION__); + } + $query_fields = $this->getFieldDeclarationList($fields); + if (PEAR::isError($query_fields)) { + return $query_fields; + } + if (!empty($options['primary'])) { + $query_fields.= ', PRIMARY KEY ('.implode(', ', array_keys($options['primary'])).')'; + } + if (!empty($options['foreign_keys'])) { + foreach ($options['foreign_keys'] as $fkname => $fkdef) { + if (empty($fkdef)) { + continue; + } + $query_fields.= ', CONSTRAINT '.$fkname.' FOREIGN KEY ('.implode(', ', array_keys($fkdef['fields'])).')'; + $query_fields.= ' REFERENCES '.$fkdef['references']['table'].' ('.implode(', ', array_keys($fkdef['references']['fields'])).')'; + $query_fields.= $this->_getAdvancedFKOptions($fkdef); + } + } + + $name = $db->quoteIdentifier($name, true); + $result = 'CREATE '; + if (!empty($options['temporary'])) { + $result .= $this->_getTemporaryTableQuery(); + } + $result .= " TABLE $name ($query_fields)"; + return $result; + } + + // }}} + // {{{ 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 + * @param array $options An associative array of table options + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields, $options = array()) + { + $result = parent::createTable($name, $fields, $options); + if (PEAR::isError($result)) { + return $result; + } + // create triggers to enforce FOREIGN KEY constraints + if (!empty($options['foreign_keys'])) { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + foreach ($options['foreign_keys'] as $fkname => $fkdef) { + if (empty($fkdef)) { + continue; + } + //set actions to default if not set + $fkdef['onupdate'] = empty($fkdef['onupdate']) ? $db->options['default_fk_action_onupdate'] : strtoupper($fkdef['onupdate']); + $fkdef['ondelete'] = empty($fkdef['ondelete']) ? $db->options['default_fk_action_ondelete'] : strtoupper($fkdef['ondelete']); + + $trigger_names = array( + 'insert' => $fkname.'_insert_trg', + 'update' => $fkname.'_update_trg', + 'pk_update' => $fkname.'_pk_update_trg', + 'pk_delete' => $fkname.'_pk_delete_trg', + ); + + //create the [insert|update] triggers on the FK table + $table_fields = array_keys($fkdef['fields']); + $referenced_fields = array_keys($fkdef['references']['fields']); + $query = 'CREATE TRIGGER %s BEFORE %s ON '.$name + .' FOR EACH ROW BEGIN' + .' SELECT RAISE(ROLLBACK, \'%s on table "'.$name.'" violates FOREIGN KEY constraint "'.$fkname.'"\')' + .' WHERE (SELECT '; + $aliased_fields = array(); + foreach ($referenced_fields as $field) { + $aliased_fields[] = $fkdef['references']['table'] .'.'.$field .' AS '.$field; + } + $query .= implode(',', $aliased_fields) + .' FROM '.$fkdef['references']['table'] + .' WHERE '; + $conditions = array(); + for ($i=0; $i<count($table_fields); $i++) { + $conditions[] = $referenced_fields[$i] .' = NEW.'.$table_fields[$i]; + } + $query .= implode(' AND ', $conditions).') IS NULL; END;'; + $result = $db->exec(sprintf($query, $trigger_names['insert'], 'INSERT', 'insert')); + if (PEAR::isError($result)) { + return $result; + } + + $result = $db->exec(sprintf($query, $trigger_names['update'], 'UPDATE', 'update')); + if (PEAR::isError($result)) { + return $result; + } + + //create the ON [UPDATE|DELETE] triggers on the primary table + $restrict_action = 'SELECT RAISE(ROLLBACK, \'%s on table "'.$name.'" violates FOREIGN KEY constraint "'.$fkname.'"\')' + .' WHERE (SELECT '; + $aliased_fields = array(); + foreach ($table_fields as $field) { + $aliased_fields[] = $name .'.'.$field .' AS '.$field; + } + $restrict_action .= implode(',', $aliased_fields) + .' FROM '.$name + .' 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'; + } + $conditions2 = array(); + for ($i=0; $i<count($referenced_fields); $i++) { + $conditions2[] = 'NEW.'.$referenced_fields[$i] .' <> OLD.'.$referenced_fields[$i]; + } + $restrict_action .= implode(' AND ', $conditions).') IS NOT NULL' + .' AND (' .implode(' OR ', $conditions2) .')'; + + $cascade_action_update = 'UPDATE '.$name.' SET '.implode(', ', $new_values) .' WHERE '.implode(' AND ', $conditions); + $cascade_action_delete = 'DELETE FROM '.$name.' WHERE '.implode(' AND ', $conditions); + $setnull_action = 'UPDATE '.$name.' 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($name, $field); + if (PEAR::isError($field_definition)) { + return $field_definition; + } + $default_values[] = $table_field .' = '. $field_definition[0]['default']; + } + $setdefault_action = 'UPDATE '.$name.' 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_names['pk_update'], 'AFTER UPDATE', 'update') . $cascade_action_update. '; END;'; + } elseif ('SET NULL' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setnull_action. '; END;'; + } elseif ('SET DEFAULT' == $fkdef['onupdate']) { + $sql_update = sprintf($query, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . $setdefault_action. '; END;'; + } elseif ('NO ACTION' == $fkdef['onupdate']) { + $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'AFTER UPDATE', 'update') . '; END;'; + } elseif ('RESTRICT' == $fkdef['onupdate']) { + $sql_update = sprintf($query.$restrict_action, $trigger_names['pk_update'], 'BEFORE UPDATE', 'update') . '; END;'; + } + if ('CASCADE' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete') . $cascade_action_delete. '; END;'; + } elseif ('SET NULL' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setnull_action. '; END;'; + } elseif ('SET DEFAULT' == $fkdef['ondelete']) { + $sql_delete = sprintf($query, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . $setdefault_action. '; END;'; + } elseif ('NO ACTION' == $fkdef['ondelete']) { + $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'AFTER DELETE', 'delete') . '; END;'; + } elseif ('RESTRICT' == $fkdef['ondelete']) { + $sql_delete = sprintf($query.$restrict_action, $trigger_names['pk_delete'], 'BEFORE DELETE', 'delete') . '; END;'; + } + + if (PEAR::isError($result)) { + return $result; + } + $result = $db->exec($sql_delete); + if (PEAR::isError($result)) { + return $result; + } + $result = $db->exec($sql_update); + if (PEAR::isError($result)) { + return $result; + } + } + } + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ 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; + } + + //delete the triggers associated to existing FK constraints + $constraints = $this->listTableConstraints($name); + if (!PEAR::isError($constraints) && !empty($constraints)) { + $db->loadModule('Reverse', null, true); + foreach ($constraints as $constraint) { + $definition = $db->reverse->getTableConstraintDefinition($name, $constraint); + if (!PEAR::isError($definition) && !empty($definition['foreign'])) { + $result = $this->_dropFKTriggers($name, $constraint, $definition['references']['table']); + if (PEAR::isError($result)) { + return $result; + } + } + } + } + + $name = $db->quoteIdentifier($name, true); + return $db->exec("DROP 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()) + { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'VACUUM'; + if (!empty($table)) { + $query .= ' '.$db->quoteIdentifier($table, true); + } + return $db->exec($query); + } + + // }}} + // {{{ 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, $options = array()) + { + $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; + } + + $db->loadModule('Reverse', null, true); + + // actually sqlite 2.x supports no ALTER TABLE at all .. so we emulate it + $fields = $db->manager->listTableFields($name); + if (PEAR::isError($fields)) { + return $fields; + } + + $fields = array_flip($fields); + foreach ($fields as $field => $value) { + $definition = $db->reverse->getTableFieldDefinition($name, $field); + if (PEAR::isError($definition)) { + return $definition; + } + $fields[$field] = $definition[0]; + } + + $indexes = $db->manager->listTableIndexes($name); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $indexes = array_flip($indexes); + foreach ($indexes as $index => $value) { + $definition = $db->reverse->getTableIndexDefinition($name, $index); + if (PEAR::isError($definition)) { + return $definition; + } + $indexes[$index] = $definition; + } + + $constraints = $db->manager->listTableConstraints($name); + if (PEAR::isError($constraints)) { + return $constraints; + } + + if (!array_key_exists('foreign_keys', $options)) { + $options['foreign_keys'] = array(); + } + $constraints = array_flip($constraints); + foreach ($constraints as $constraint => $value) { + if (!empty($definition['primary'])) { + if (!array_key_exists('primary', $options)) { + $options['primary'] = $definition['fields']; + //remove from the $constraint array, it's already handled by createTable() + unset($constraints[$constraint]); + } + } else { + $c_definition = $db->reverse->getTableConstraintDefinition($name, $constraint); + if (PEAR::isError($c_definition)) { + return $c_definition; + } + if (!empty($c_definition['foreign'])) { + if (!array_key_exists($constraint, $options['foreign_keys'])) { + $options['foreign_keys'][$constraint] = $c_definition; + } + //remove from the $constraint array, it's already handled by createTable() + unset($constraints[$constraint]); + } else { + $constraints[$constraint] = $c_definition; + } + } + } + + $name_new = $name; + $create_order = $select_fields = array_keys($fields); + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + foreach ($change as $field_name => $field) { + $fields[$field_name] = $field; + $create_order[] = $field_name; + } + break; + case 'remove': + foreach ($change as $field_name => $field) { + unset($fields[$field_name]); + $select_fields = array_diff($select_fields, array($field_name)); + $create_order = array_diff($create_order, array($field_name)); + } + break; + case 'change': + foreach ($change as $field_name => $field) { + $fields[$field_name] = $field['definition']; + } + break; + case 'name': + $name_new = $change; + break; + case 'rename': + foreach ($change as $field_name => $field) { + unset($fields[$field_name]); + $fields[$field['name']] = $field['definition']; + $create_order[array_search($field_name, $create_order)] = $field['name']; + } + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'change type "'.$change_name.'" not yet supported', __FUNCTION__); + } + } + + $data = null; + if (!empty($select_fields)) { + $query = 'SELECT '.implode(', ', $select_fields).' FROM '.$db->quoteIdentifier($name, true); + $data = $db->queryAll($query, null, MDB2_FETCHMODE_ORDERED); + } + + $result = $this->dropTable($name); + if (PEAR::isError($result)) { + return $result; + } + + $result = $this->createTable($name_new, $fields, $options); + if (PEAR::isError($result)) { + return $result; + } + + foreach ($indexes as $index => $definition) { + $this->createIndex($name_new, $index, $definition); + } + + foreach ($constraints as $constraint => $definition) { + $this->createConstraint($name_new, $constraint, $definition); + } + + if (!empty($select_fields) && !empty($data)) { + $query = 'INSERT INTO '.$db->quoteIdentifier($name_new, true); + $query.= '('.implode(', ', array_slice(array_keys($fields), 0, count($select_fields))).')'; + $query.=' VALUES (?'.str_repeat(', ?', (count($select_fields) - 1)).')'; + $stmt =$db->prepare($query, null, MDB2_PREPARE_MANIP); + if (PEAR::isError($stmt)) { + return $stmt; + } + foreach ($data as $row) { + $result = $stmt->execute($row); + if (PEAR::isError($result)) { + return $result; + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ 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; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'list databases is not supported', __FUNCTION__); + } + + // }}} + // {{{ 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; + } + + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'list databases is not supported', __FUNCTION__); + } + + // }}} + // {{{ listViews() + + /** + * list all views in the current database + * + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listViews($dummy=null) + { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sqlite_master WHERE type='view' AND sql NOT NULL"; + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ listTableViews() + + /** + * list the views in the database that reference a given table + * + * @param string table for which all referenced views should be found + * @return mixed array of view names on success, a MDB2 error on failure + * @access public + */ + function listTableViews($table) + { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL"; + $views = $db->queryAll($query, array('text', 'text'), MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($views)) { + return $views; + } + $result = array(); + foreach ($views as $row) { + if (preg_match("/^create view .* \bfrom\b\s+\b{$table}\b /i", $row['sql'])) { + if (!empty($row['name'])) { + $result[$row['name']] = true; + } + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @return mixed array of table names on success, a MDB2 error on failure + * @access public + */ + function listTables($dummy=null) + { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sqlite_master WHERE type='table' AND sql NOT NULL ORDER BY name"; + $table_names = $db->queryCol($query); + if (PEAR::isError($table_names)) { + return $table_names; + } + $result = array(); + foreach ($table_names as $table_name) { + if (!$this->_fixSequenceName($table_name, true)) { + $result[] = $table_name; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $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; + } + + $result = $db->loadModule('Reverse', null, true); + if (PEAR::isError($result)) { + return $result; + } + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= 'name='.$db->quote($table, 'text'); + } + $sql = $db->queryOne($query); + if (PEAR::isError($sql)) { + return $sql; + } + $columns = $db->reverse->_getTableColumns($sql); + $fields = array(); + foreach ($columns as $column) { + 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']); + } + } else { + $column = array_change_key_case($column, $db->options['field_case']); + } + $fields[] = $column['name']; + } + return $fields; + } + + // }}} + // {{{ 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; + } + + $query = "SELECT name FROM sqlite_master WHERE type='trigger' AND sql NOT NULL"; + if (!is_null($table)) { + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= ' AND LOWER(tbl_name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= ' AND tbl_name='.$db->quote($table, 'text'); + } + } + $result = $db->queryCol($query); + if (PEAR::isError($result)) { + return $result; + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} + // {{{ createIndex() + + /** + * Get the stucture of a field into an array + * + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function support() to determine whether the DBMS driver can manage indexes. + + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * ), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->getIndexName($name); + $query = "CREATE INDEX $name ON $table"; + $fields = array(); + foreach ($definition['fields'] as $field_name => $field) { + $field_string = $field_name; + if (!empty($field['sorting'])) { + switch ($field['sorting']) { + case 'ascending': + $field_string.= ' ASC'; + break; + case 'descending': + $field_string.= ' DESC'; + break; + } + } + $fields[] = $field_string; + } + $query .= ' ('.implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ dropIndex() + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropIndex($table, $name) + { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $name = $db->getIndexName($name); + return $db->exec("DROP INDEX $name"); + } + + // }}} + // {{{ 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; + } + + $table = $db->quote($table, 'text'); + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(tbl_name)='.strtolower($table); + } else { + $query.= "tbl_name=$table"; + } + $query.= " AND sql NOT NULL ORDER BY name"; + $indexes = $db->queryCol($query, 'text'); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $sql) { + if (preg_match("/^create index ([^ ]+) on /i", $sql, $tmp)) { + $index = $this->_fixIndexName($tmp[1]); + if (!empty($index)) { + $result[$index] = true; + } + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ 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) + { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!empty($definition['primary'])) { + return $db->manager->alterTable($table, array(), false, array('primary' => $definition['fields'])); + } + + if (!empty($definition['foreign'])) { + return $db->manager->alterTable($table, array(), false, array('foreign_keys' => array($name => $definition))); + } + + $table = $db->quoteIdentifier($table, true); + $name = $db->getIndexName($name); + $query = "CREATE UNIQUE INDEX $name ON $table"; + $fields = array(); + foreach ($definition['fields'] as $field_name => $field) { + $field_string = $field_name; + if (!empty($field['sorting'])) { + switch ($field['sorting']) { + case 'ascending': + $field_string.= ' ASC'; + break; + case 'descending': + $field_string.= ' DESC'; + break; + } + } + $fields[] = $field_string; + } + $query .= ' ('.implode(', ', $fields) . ')'; + return $db->exec($query); + } + + // }}} + // {{{ 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) + { + if ($primary || $name == 'PRIMARY') { + return $this->alterTable($table, array(), false, array('primary' => null)); + } + + $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; + } + //then drop the constraint itself + return $this->alterTable($table, array(), false, array('foreign_keys' => array($name => null))); + } + + $name = $db->getIndexName($name); + return $db->exec("DROP INDEX $name"); + } + + // }}} + // {{{ _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); + $pattern = '/^'.$fkname.'(_pk)?_(insert|update|delete)_trg$/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; + } + + $table = $db->quote($table, 'text'); + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(tbl_name)='.strtolower($table); + } else { + $query.= "tbl_name=$table"; + } + $query.= " AND sql NOT NULL ORDER BY name"; + $indexes = $db->queryCol($query, 'text'); + if (PEAR::isError($indexes)) { + return $indexes; + } + + $result = array(); + foreach ($indexes as $sql) { + if (preg_match("/^create unique index ([^ ]+) on /i", $sql, $tmp)) { + $index = $this->_fixIndexName($tmp[1]); + if (!empty($index)) { + $result[$index] = true; + } + } + } + + // also search in table definition for PRIMARY KEYs... + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.strtolower($table); + } else { + $query.= "name=$table"; + } + $query.= " AND sql NOT NULL ORDER BY name"; + $table_def = $db->queryOne($query, 'text'); + if (PEAR::isError($table_def)) { + return $table_def; + } + if (preg_match("/\bPRIMARY\s+KEY\b/i", $table_def, $tmp)) { + $result['primary'] = true; + } + + // ...and for FOREIGN KEYs + if (preg_match_all("/\bCONSTRAINT\b\s+([^\s]+)\s+\bFOREIGN\s+KEY/imsx", $table_def, $tmp)) { + foreach ($tmp[1] as $fk) { + $result[$fk] = true; + } + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_change_key_case($result, $db->options['field_case']); + } + return array_keys($result); + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @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); + $seqcol_name = $db->quoteIdentifier($db->options['seqcol_name'], true); + $query = "CREATE TABLE $sequence_name ($seqcol_name INTEGER PRIMARY KEY DEFAULT 0 NOT NULL)"; + $res = $db->exec($query); + if (PEAR::isError($res)) { + return $res; + } + if ($start == 1) { + return MDB2_OK; + } + $res = $db->exec("INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'); + if (!PEAR::isError($res)) { + return MDB2_OK; + } + // Handle error + $result = $db->exec("DROP TABLE $sequence_name"); + if (PEAR::isError($result)) { + return $db->raiseError($result, null, null, + 'could not drop inconsistent sequence table', __FUNCTION__); + } + return $db->raiseError($res, null, null, + 'could not create sequence table', __FUNCTION__); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @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 TABLE $sequence_name"); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @return mixed array of sequence names on success, a MDB2 error on failure + * @access public + */ + function listSequences($dummy=null) + { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = "SELECT name FROM sqlite_master WHERE type='table' AND sql NOT NULL ORDER BY name"; + $table_names = $db->queryCol($query); + if (PEAR::isError($table_names)) { + return $table_names; + } + $result = array(); + foreach ($table_names as $table_name) { + if ($sqn = $this->_fixSequenceName($table_name, true)) { + $result[] = $sqn; + } + } + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $result = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $result); + } + return $result; + } + + // }}} +} +?>
\ No newline at end of file diff --git a/lib/MDB2/Driver/Native/sqlite3.php b/lib/MDB2/Driver/Native/sqlite3.php new file mode 100644 index 00000000000..81dc67dea65 --- /dev/null +++ b/lib/MDB2/Driver/Native/sqlite3.php @@ -0,0 +1,58 @@ +<?php +// vim: set et ts=4 sw=4 fdm=marker: +// +----------------------------------------------------------------------+ +// | PHP versions 5 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 2011 Robin Appelman | +// | 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: Robin Appelman <icewind1991@gmail.com> | +// +----------------------------------------------------------------------+ +// +// +require_once 'MDB2/Driver/Native/Common.php'; + +/** + * MDB2 SQLite driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_Driver_Native_sqlite extends MDB2_Driver_Native_Common +{ +} +?>
\ No newline at end of file diff --git a/lib/MDB2/Driver/Reverse/sqlite3.php b/lib/MDB2/Driver/Reverse/sqlite3.php new file mode 100644 index 00000000000..d488977b158 --- /dev/null +++ b/lib/MDB2/Driver/Reverse/sqlite3.php @@ -0,0 +1,607 @@ +<?php +// vim: set et ts=4 sw=4 fdm=marker: +// +----------------------------------------------------------------------+ +// | PHP versions 5 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 2011 Robin Appelman | +// | 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: Robin Appelman <icewind1991@gmail.com> | +// +----------------------------------------------------------------------+ +// +// + +require_once('MDB2/Driver/Reverse/Common.php'); + +/** + * MDB2 SQlite driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_Driver_Reverse_sqlite3 extends MDB2_Driver_Reverse_Common +{ + /** + * Remove SQL comments from the field definition + * + * @access private + */ + function _removeComments($sql) { + $lines = explode("\n", $sql); + foreach ($lines as $k => $line) { + $pieces = explode('--', $line); + if (count($pieces) > 1 && (substr_count($pieces[0], '\'') % 2) == 0) { + $lines[$k] = substr($line, 0, strpos($line, '--')); + } + } + return implode("\n", $lines); + } + + /** + * + */ + function _getTableColumns($sql) + { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + $start_pos = strpos($sql, '('); + $end_pos = strrpos($sql, ')'); + $column_def = substr($sql, $start_pos+1, $end_pos-$start_pos-1); + // replace the decimal length-places-separator with a colon + $column_def = preg_replace('/(\d),(\d)/', '\1:\2', $column_def); + $column_def = $this->_removeComments($column_def); + $column_sql = explode(',', $column_def); + $columns = array(); + $count = count($column_sql); + if ($count == 0) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'unexpected empty table column definition list', __FUNCTION__); + } + $regexp = '/^\s*([^\s]+) +(CHAR|VARCHAR|VARCHAR2|TEXT|BOOLEAN|SMALLINT|INT|INTEGER|DECIMAL|BIGINT|DOUBLE|FLOAT|DATETIME|DATE|TIME|LONGTEXT|LONGBLOB)( ?\(([1-9][0-9]*)(:([1-9][0-9]*))?\))?( NULL| NOT NULL)?( UNSIGNED)?( NULL| NOT NULL)?( PRIMARY KEY)?( DEFAULT (\'[^\']*\'|[^ ]+))?( NULL| NOT NULL)?( PRIMARY KEY)?(\s*\-\-.*)?$/i'; + $regexp2 = '/^\s*([^ ]+) +(PRIMARY|UNIQUE|CHECK)$/i'; + for ($i=0, $j=0; $i<$count; ++$i) { + if (!preg_match($regexp, trim($column_sql[$i]), $matches)) { + if (!preg_match($regexp2, trim($column_sql[$i]))) { + continue; + } + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'unexpected table column SQL definition: "'.$column_sql[$i].'"', __FUNCTION__); + } + $columns[$j]['name'] = trim($matches[1], implode('', $db->identifier_quoting)); + $columns[$j]['type'] = strtolower($matches[2]); + if (isset($matches[4]) && strlen($matches[4])) { + $columns[$j]['length'] = $matches[4]; + } + if (isset($matches[6]) && strlen($matches[6])) { + $columns[$j]['decimal'] = $matches[6]; + } + if (isset($matches[8]) && strlen($matches[8])) { + $columns[$j]['unsigned'] = true; + } + if (isset($matches[9]) && strlen($matches[9])) { + $columns[$j]['autoincrement'] = true; + } + if (isset($matches[12]) && strlen($matches[12])) { + $default = $matches[12]; + if (strlen($default) && $default[0]=="'") { + $default = str_replace("''", "'", substr($default, 1, strlen($default)-2)); + } + if ($default === 'NULL') { + $default = null; + } + $columns[$j]['default'] = $default; + } + if (isset($matches[7]) && strlen($matches[7])) { + $columns[$j]['notnull'] = ($matches[7] === ' NOT NULL'); + } else if (isset($matches[9]) && strlen($matches[9])) { + $columns[$j]['notnull'] = ($matches[9] === ' NOT NULL'); + } else if (isset($matches[13]) && strlen($matches[13])) { + $columns[$j]['notnull'] = ($matches[13] === ' NOT NULL'); + } + ++$j; + } + return $columns; + } + + // {{{ getTableFieldDefinition() + + /** + * Get the stucture 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. + * The returned array contains an array for each field definition, + * with (some of) these indices: + * [notnull] [nativetype] [length] [fixed] [default] [type] [mdb2type] + * @access public + */ + function getTableFieldDefinition($table_name, $field_name) + { + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + list($schema, $table) = $this->splitTableSchema($table_name); + + $result = $db->loadModule('Datatype', null, true); + if (PEAR::isError($result)) { + return $result; + } + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= 'name='.$db->quote($table, 'text'); + } + $sql = $db->queryOne($query); + if (PEAR::isError($sql)) { + return $sql; + } + $columns = $this->_getTableColumns($sql); + foreach ($columns as $column) { + 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']); + } + } else { + $column = array_change_key_case($column, $db->options['field_case']); + } + if ($field_name == $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['notnull'])) { + $notnull = $column['notnull']; + } + $default = false; + if (array_key_exists('default', $column)) { + $default = $column['default']; + if (is_null($default) && $notnull) { + $default = ''; + } + } + $autoincrement = false; + if (!empty($column['autoincrement'])) { + $autoincrement = true; + } + + $definition[0] = array( + 'notnull' => $notnull, + 'nativetype' => preg_replace('/^([a-z]+)[^a-z].*/i', '\\1', $column['type']) + ); + if (!is_null($length)) { + $definition[0]['length'] = $length; + } + if (!is_null($unsigned)) { + $definition[0]['unsigned'] = $unsigned; + } + if (!is_null($fixed)) { + $definition[0]['fixed'] = $fixed; + } + if ($default !== false) { + $definition[0]['default'] = $default; + } + if ($autoincrement !== false) { + $definition[0]['autoincrement'] = $autoincrement; + } + 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; + } + return $definition; + } + } + + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table column', __FUNCTION__); + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * Get the stucture 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($schema, $table) = $this->splitTableSchema($table_name); + + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)=%s AND LOWER(tbl_name)=' . $db->quote(strtolower($table), 'text'); + } else { + $query.= 'name=%s AND tbl_name=' . $db->quote($table, 'text'); + } + $query.= ' AND sql NOT NULL ORDER BY name'; + $index_name_mdb2 = $db->getIndexName($index_name); + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($index_name_mdb2), 'text')); + } else { + $qry = sprintf($query, $db->quote($index_name_mdb2, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + if (PEAR::isError($sql) || empty($sql)) { + // fallback to the given $index_name, without transformation + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($index_name), 'text')); + } else { + $qry = sprintf($query, $db->quote($index_name, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + } + if (PEAR::isError($sql)) { + return $sql; + } + if (!$sql) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + + $sql = strtolower($sql); + $start_pos = strpos($sql, '('); + $end_pos = strrpos($sql, ')'); + $column_names = substr($sql, $start_pos+1, $end_pos-$start_pos-1); + $column_names = explode(',', $column_names); + + if (preg_match("/^create unique/", $sql)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + + $definition = array(); + $count = count($column_names); + for ($i=0; $i<$count; ++$i) { + $column_name = strtok($column_names[$i], ' '); + $collation = strtok(' '); + $definition['fields'][$column_name] = array( + 'position' => $i+1 + ); + if (!empty($collation)) { + $definition['fields'][$column_name]['sorting'] = + ($collation=='ASC' ? 'ascending' : 'descending'); + } + } + + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing table index', __FUNCTION__); + } + return $definition; + } + + // }}} + // {{{ getTableConstraintDefinition() + + /** + * Get the stucture 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($schema, $table) = $this->splitTableSchema($table_name); + + $query = "SELECT sql FROM sqlite_master WHERE type='index' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)=%s AND LOWER(tbl_name)=' . $db->quote(strtolower($table), 'text'); + } else { + $query.= 'name=%s AND tbl_name=' . $db->quote($table, 'text'); + } + $query.= ' AND sql NOT NULL ORDER BY name'; + $constraint_name_mdb2 = $db->getIndexName($constraint_name); + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($constraint_name_mdb2), 'text')); + } else { + $qry = sprintf($query, $db->quote($constraint_name_mdb2, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + if (PEAR::isError($sql) || empty($sql)) { + // fallback to the given $index_name, without transformation + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $qry = sprintf($query, $db->quote(strtolower($constraint_name), 'text')); + } else { + $qry = sprintf($query, $db->quote($constraint_name, 'text')); + } + $sql = $db->queryOne($qry, 'text'); + } + if (PEAR::isError($sql)) { + return $sql; + } + //default values, eventually overridden + $definition = array( + 'primary' => false, + 'unique' => false, + 'foreign' => false, + 'check' => false, + 'fields' => array(), + 'references' => array( + 'table' => '', + 'fields' => array(), + ), + 'onupdate' => '', + 'ondelete' => '', + 'match' => '', + 'deferrable' => false, + 'initiallydeferred' => false, + ); + if (!$sql) { + $query = "SELECT sql FROM sqlite_master WHERE type='table' AND "; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= 'LOWER(name)='.$db->quote(strtolower($table), 'text'); + } else { + $query.= 'name='.$db->quote($table, 'text'); + } + $query.= " AND sql NOT NULL ORDER BY name"; + $sql = $db->queryOne($query, 'text'); + if (PEAR::isError($sql)) { + return $sql; + } + if ($constraint_name == 'primary') { + // search in table definition for PRIMARY KEYs + if (preg_match("/\bPRIMARY\s+KEY\b\s*\(([^)]+)/i", $sql, $tmp)) { + $definition['primary'] = true; + $definition['fields'] = array(); + $column_names = explode(',', $tmp[1]); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + return $definition; + } + if (preg_match("/\"([^\"]+)\"[^\,\"]+\bPRIMARY\s+KEY\b[^\,\)]*/i", $sql, $tmp)) { + $definition['primary'] = true; + $definition['fields'] = array(); + $column_names = explode(',', $tmp[1]); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + return $definition; + } + } else { + // search in table definition for FOREIGN KEYs + $pattern = "/\bCONSTRAINT\b\s+%s\s+ + \bFOREIGN\s+KEY\b\s*\(([^\)]+)\)\s* + \bREFERENCES\b\s+([^\s]+)\s*\(([^\)]+)\)\s* + (?:\bMATCH\s*([^\s]+))?\s* + (?:\bON\s+UPDATE\s+([^\s,\)]+))?\s* + (?:\bON\s+DELETE\s+([^\s,\)]+))?\s* + /imsx"; + $found_fk = false; + if (preg_match(sprintf($pattern, $constraint_name_mdb2), $sql, $tmp)) { + $found_fk = true; + } elseif (preg_match(sprintf($pattern, $constraint_name), $sql, $tmp)) { + $found_fk = true; + } + if ($found_fk) { + $definition['foreign'] = true; + $definition['match'] = 'SIMPLE'; + $definition['onupdate'] = 'NO ACTION'; + $definition['ondelete'] = 'NO ACTION'; + $definition['references']['table'] = $tmp[2]; + $column_names = explode(',', $tmp[1]); + $colpos = 1; + foreach ($column_names as $column_name) { + $definition['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + $referenced_cols = explode(',', $tmp[3]); + $colpos = 1; + foreach ($referenced_cols as $column_name) { + $definition['references']['fields'][trim($column_name)] = array( + 'position' => $colpos++ + ); + } + if (isset($tmp[4])) { + $definition['match'] = $tmp[4]; + } + if (isset($tmp[5])) { + $definition['onupdate'] = $tmp[5]; + } + if (isset($tmp[6])) { + $definition['ondelete'] = $tmp[6]; + } + return $definition; + } + } + $sql = false; + } + if (!$sql) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + $sql = strtolower($sql); + $start_pos = strpos($sql, '('); + $end_pos = strrpos($sql, ')'); + $column_names = substr($sql, $start_pos+1, $end_pos-$start_pos-1); + $column_names = explode(',', $column_names); + + if (!preg_match("/^create unique/", $sql)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + + $definition['unique'] = true; + $count = count($column_names); + for ($i=0; $i<$count; ++$i) { + $column_name = strtok($column_names[$i]," "); + $collation = strtok(" "); + $definition['fields'][$column_name] = array( + 'position' => $i+1 + ); + if (!empty($collation)) { + $definition['fields'][$column_name]['sorting'] = + ($collation=='ASC' ? 'ascending' : 'descending'); + } + } + + if (empty($definition['fields'])) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + $constraint_name . ' is not an existing table constraint', __FUNCTION__); + } + 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 name as trigger_name, + tbl_name AS table_name, + sql AS trigger_body, + NULL AS trigger_type, + NULL AS trigger_event, + NULL AS trigger_comment, + 1 AS trigger_enabled + FROM sqlite_master + WHERE type='trigger'"; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $query.= ' AND LOWER(name)='.$db->quote(strtolower($trigger), 'text'); + } else { + $query.= ' AND name='.$db->quote($trigger, 'text'); + } + $types = array( + 'trigger_name' => 'text', + 'table_name' => 'text', + 'trigger_body' => 'text', + 'trigger_type' => 'text', + 'trigger_event' => 'text', + 'trigger_comment' => 'text', + 'trigger_enabled' => 'boolean', + ); + $def = $db->queryRow($query, $types, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($def)) { + return $def; + } + if (empty($def)) { + return $db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'it was not specified an existing trigger', __FUNCTION__); + } + if (preg_match("/^create\s+(?:temp|temporary)?trigger\s+(?:if\s+not\s+exists\s+)?.*(before|after)?\s+(insert|update|delete)/Uims", $def['trigger_body'], $tmp)) { + $def['trigger_type'] = strtoupper($tmp[1]); + $def['trigger_event'] = strtoupper($tmp[2]); + } + return $def; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table + * + * @param string $result a string containing the name of a table + * @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() + * @since Method available since Release 1.7.0 + */ + function tableInfo($result, $mode = null) + { + if (is_string($result)) { + return parent::tableInfo($result, $mode); + } + + $db =$this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR_NOT_CAPABLE, null, null, + 'This DBMS can not obtain tableInfo from result sets', __FUNCTION__); + } +} + +?>
\ No newline at end of file diff --git a/lib/MDB2/Driver/sqlite3.php b/lib/MDB2/Driver/sqlite3.php new file mode 100644 index 00000000000..3b74afed146 --- /dev/null +++ b/lib/MDB2/Driver/sqlite3.php @@ -0,0 +1,1361 @@ +<?php +// vim: set et ts=4 sw=4 fdm=marker: +// +----------------------------------------------------------------------+ +// | PHP versions 5 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 2011 Robin Appelman | +// | 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: Robin Appelman <icewind1991@gmail.com> | +// +----------------------------------------------------------------------+ +// +// + +/** + * MDB2 SQLite3 driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_Driver_sqlite3 extends MDB2_Driver_Common +{ + // {{{ properties + public $string_quoting = array('start' => "'", 'end' => "'", 'escape' => "'", 'escape_pattern' => false); + + public $identifier_quoting = array('start' => '"', 'end' => '"', 'escape' => '"'); + + public $_lasterror = ''; + + public $fix_assoc_fields_names = false; + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'sqlite3'; + $this->dbsyntax = 'sqlite'; + + $this->supported['sequences'] = 'emulated'; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['current_id'] = 'emulated'; + $this->supported['limit_queries'] = true; + $this->supported['LOBs'] = true; + $this->supported['replace'] = true; + $this->supported['transactions'] = false; + $this->supported['savepoints'] = false; + $this->supported['sub_selects'] = true; + $this->supported['triggers'] = true; + $this->supported['auto_increment'] = true; + $this->supported['primary_key'] = false; // requires alter table implementation + $this->supported['result_introspection'] = false; // not implemented + $this->supported['prepared_statements'] = true; + $this->supported['identifier_quoting'] = true; + $this->supported['pattern_escaping'] = false; + $this->supported['new_link'] = false; + + $this->options['DBA_username'] = false; + $this->options['DBA_password'] = false; + $this->options['base_transaction_name'] = '___php_MDB2_sqlite_auto_commit_off'; + $this->options['fixed_float'] = 0; + $this->options['database_path'] = ''; + $this->options['database_extension'] = ''; + $this->options['server_version'] = ''; + $this->options['max_identifiers_length'] = 128; //no real limit + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + $native_code = null; + if ($this->connection) { + $native_code = $this->connection->lastErrorCode(); + } + $native_msg = $this->_lasterror + ? html_entity_decode($this->_lasterror) : $this->connection->lastErrorMsg(); + + // PHP 5.2+ prepends the function name to $php_errormsg, so we need + // this hack to work around it, per bug #9599. + $native_msg = preg_replace('/^sqlite[a-z_]+\(\)[^:]*: /', '', $native_msg); + + if (is_null($error)) { + static $error_regexps; + if (empty($error_regexps)) { + $error_regexps = array( + '/^no such table:/' => MDB2_ERROR_NOSUCHTABLE, + '/^no such index:/' => MDB2_ERROR_NOT_FOUND, + '/^(table|index) .* already exists$/' => MDB2_ERROR_ALREADY_EXISTS, + '/PRIMARY KEY must be unique/i' => MDB2_ERROR_CONSTRAINT, + '/is not unique/' => MDB2_ERROR_CONSTRAINT, + '/columns .* are not unique/i' => MDB2_ERROR_CONSTRAINT, + '/uniqueness constraint failed/' => MDB2_ERROR_CONSTRAINT, + '/may not be NULL/' => MDB2_ERROR_CONSTRAINT_NOT_NULL, + '/^no such column:/' => MDB2_ERROR_NOSUCHFIELD, + '/no column named/' => MDB2_ERROR_NOSUCHFIELD, + '/column not present in both tables/i' => MDB2_ERROR_NOSUCHFIELD, + '/^near ".*": syntax error$/' => MDB2_ERROR_SYNTAX, + '/[0-9]+ values for [0-9]+ columns/i' => MDB2_ERROR_VALUE_COUNT_ON_ROW, + ); + } + foreach ($error_regexps as $regexp => $code) { + if (preg_match($regexp, $native_msg)) { + $error = $code; + break; + } + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ escape() + + /** + * Quotes a string so it can be safely used in a query. It will quote + * the text so it can safely be used within a query. + * + * @param string the input string to quote + * @param bool escape wildcards + * + * @return string quoted string + * + * @access public + */ + public function escape($text, $escape_wildcards = false) + { + if($this->connection){ + return $this->connection->escapeString($text); + }else{ + return str_replace("'","''",$text);//TODO; more + } + } + + // }}} + // {{{ 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 (!is_null($savepoint)) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } elseif ($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'); + } + $query = 'BEGIN TRANSACTION '.$this->options['base_transaction_name']; + $result =$this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = true; + 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 (!is_null($savepoint)) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + + $query = 'COMMIT TRANSACTION '.$this->options['base_transaction_name']; + $result =$this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ + + /** + * 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 (!is_null($savepoint)) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'savepoints are not supported', __FUNCTION__); + } + + $query = 'ROLLBACK TRANSACTION '.$this->options['base_transaction_name']; + $result =$this->_doQuery($query, true); + if (PEAR::isError($result)) { + return $result; + } + $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) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @since 2.1.1 + */ + static function setTransactionIsolation($isolation,$options=array()) + { + $this->debug('Setting transaction isolation level', __FUNCTION__, array('is_manip' => true)); + switch ($isolation) { + case 'READ UNCOMMITTED': + $isolation = 0; + break; + case 'READ COMMITTED': + case 'REPEATABLE READ': + case 'SERIALIZABLE': + $isolation = 1; + break; + default: + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'isolation level is not supported: '.$isolation, __FUNCTION__); + } + + $query = "PRAGMA read_uncommitted=$isolation"; + return $this->_doQuery($query, true); + } + + // }}} + // {{{ getDatabaseFile() + + /** + * Builds the string with path+dbname+extension + * + * @return string full database path+file + * @access protected + */ + function _getDatabaseFile($database_name) + { + if ($database_name === '' || $database_name === ':memory:') { + return $database_name; + } + return $this->options['database_path'].$database_name.$this->options['database_extension']; + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + **/ + function connect() + { + if($this->connection instanceof SQLite3){ + return MDB2_OK; + } + global $SERVERROOT; + $datadir=OC_CONFIG::getValue( "datadirectory", "$SERVERROOT/data" ); + $database_file = $this->_getDatabaseFile($this->database_name); + if (is_resource($this->connection)) { + //if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 + if (MDB2::areEquals($this->connected_dsn, $this->dsn) + && $this->connected_database_name == $database_file + && $this->opened_persistent == $this->options['persistent'] + ) { + return MDB2_OK; + } + $this->disconnect(false); + } + + if (!PEAR::loadExtension($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'extension '.$this->phptype.' is not compiled into PHP', __FUNCTION__); + } + + if (empty($this->database_name)) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + if ($database_file !== ':memory:') { + if(!strpos($database_file,'.db')){ + $database_file="$datadir/$database_file.db"; + } + if (!file_exists($database_file)) { + if (!touch($database_file)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Could not create database file', __FUNCTION__); + } + if (!isset($this->dsn['mode']) + || !is_numeric($this->dsn['mode']) + ) { + $mode = 0644; + } else { + $mode = octdec($this->dsn['mode']); + } + if (!chmod($database_file, $mode)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Could not be chmodded database file', __FUNCTION__); + } + if (!file_exists($database_file)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'Could not be found database file', __FUNCTION__); + } + } + if (!is_file($database_file)) { + return $this->raiseError(MDB2_ERROR_INVALID, null, null, + 'Database is a directory name', __FUNCTION__); + } + if (!is_readable($database_file)) { + return $this->raiseError(MDB2_ERROR_ACCESS_VIOLATION, null, null, + 'Could not read database file', __FUNCTION__); + } + } + + $php_errormsg = ''; + $this->connection = new SQLite3($database_file); + if(is_callable(array($this->connection,'busyTimeout'))){//busy timout is only available in php>=5.3 + $this->connection->busyTimeout(100); + } + $this->_lasterror = $this->connection->lastErrorMsg(); + if (!$this->connection) { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, + 'unable to establish a connection', __FUNCTION__); + } + + if ($this->fix_assoc_fields_names || + $this->options['portability'] & MDB2_PORTABILITY_FIX_ASSOC_FIELD_NAMES) + { + $this->connection->exec("PRAGMA short_column_names = 1"); + $this->fix_assoc_fields_names = true; + } + + $this->connected_dsn = $this->dsn; + $this->connected_database_name = $database_file; + $this->opened_persistent = $this->getoption('persistent'); + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + + 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) + { + $database_file = $this->_getDatabaseFile($name); + $result = file_exists($database_file); + return $result; + } + + // }}} + // {{{ 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 ($this->connection instanceof SQLite3) { + 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) { + $this->connection->close(); + } + } else { + return false; + } + return parent::disconnect($force); + } + + // }}} + // {{{ _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->options['disable_query']) { + $result = $is_manip ? 0 : null; + return $result; + } + $result=$this->connection->query($query.';'); + $this->_lasterror = $this->connection->lastErrorMsg(); + + if (!$result) { + $err =$this->raiseError(null, null, null, + 'Could not execute statement', __FUNCTION__); + return $err; + } + + $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) + { + return $this->connection->changes(); + } + + // }}} + // {{{ _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 ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) { + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + if ($limit > 0 + && !preg_match('/LIMIT\s*\d(?:\s*(?:,|OFFSET)\s*\d+)?(?:[^\)]*)?$/i', $query) + ) { + $query = rtrim($query); + if (substr($query, -1) == ';') { + $query = substr($query, 0, -1); + } + if ($is_manip) { + $query.= " LIMIT $limit"; + } else { + $query.= " LIMIT $offset,$limit"; + } + } + return $query; + } + + // }}} + // {{{ 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) + { + $server_info = false; + if ($this->connected_server_info) { + $server_info = $this->connected_server_info; + } elseif ($this->options['server_version']) { + $server_info = $this->options['server_version']; + } elseif (function_exists('sqlite_libversion')) { + $server_info = @sqlite_libversion(); + } + if (!$server_info) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'Requires either the "server_version" option or the sqlite_libversion() function', __FUNCTION__); + } + // cache server_info + $this->connected_server_info = $server_info; + if (!$native) { + $tmp = explode('.', $server_info, 3); + $server_info = array( + 'major' => isset($tmp[0]) ? $tmp[0] : null, + 'minor' => isset($tmp[1]) ? $tmp[1] : null, + 'patch' => isset($tmp[2]) ? $tmp[2] : null, + 'extra' => null, + 'native' => $server_info, + ); + } + return $server_info; + } + + // }}} + // {{{ replace() + + /** + * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT + * query, except that if there is already a row in the table with the same + * key field values, the old row is deleted before the new row is inserted. + * + * The REPLACE type of query does not make part of the SQL standards. Since + * practically only SQLite implements it natively, this type of query is + * emulated through this method for other DBMS using standard types of + * queries inside a transaction to assure the atomicity of the operation. + * + * @access public + * + * @param string $table name of the table on which the REPLACE query will + * be executed. + * @param array $fields associative array that describes the fields and the + * values that will be inserted or updated in the specified table. The + * indexes of the array are the names of all the fields of the table. The + * values of the array are also associative arrays that describe the + * values and other properties of the table fields. + * + * Here follows a list of field properties that need to be specified: + * + * value: + * Value to be assigned to the specified field. This value may be + * of specified in database independent type format as this + * function can perform the necessary datatype conversions. + * + * Default: + * this property is required unless the Null property + * is set to 1. + * + * type + * Name of the type of the field. Currently, all types Metabase + * are supported except for clob and blob. + * + * Default: no type conversion + * + * null + * Boolean property that indicates that the value for this field + * should be set to null. + * + * The default value for fields missing in INSERT queries may be + * specified the definition of a table. Often, the default value + * is already null, but since the REPLACE may be emulated using + * an UPDATE query, make sure that all fields of the table are + * listed in this function argument array. + * + * Default: 0 + * + * key + * Boolean property that indicates that this field should be + * handled as a primary key or at least as part of the compound + * unique index of the table that will determine the row that will + * updated if it exists or inserted a new row otherwise. + * + * This function will fail if no key field is specified or if the + * value of a key field is set to null because fields that are + * part of unique index they may not be null. + * + * Default: 0 + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function replace($table, $fields) + { + $count = count($fields); + $query = $values = ''; + $keys = $colnum = 0; + for (reset($fields); $colnum < $count; next($fields), $colnum++) { + $name = key($fields); + if ($colnum > 0) { + $query .= ','; + $values.= ','; + } + $query.= $this->quoteIdentifier($name, true); + if (isset($fields[$name]['null']) && $fields[$name]['null']) { + $value = 'NULL'; + } else { + $type = isset($fields[$name]['type']) ? $fields[$name]['type'] : null; + $value = $this->quote($fields[$name]['value'], $type); + if (PEAR::isError($value)) { + return $value; + } + } + $values.= $value; + if (isset($fields[$name]['key']) && $fields[$name]['key']) { + if ($value === 'NULL') { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'key value '.$name.' may not be NULL', __FUNCTION__); + } + $keys++; + } + } + if ($keys == 0) { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'not specified which fields are keys', __FUNCTION__); + } + + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + + $table = $this->quoteIdentifier($table, true); + $query = "REPLACE INTO $table ($query) VALUES ($values)"; + $result =$this->_doQuery($query, true, $connection); + if (PEAR::isError($result)) { + return $result; + } + return $this->_affectedRows($connection, $result); + } + + // }}} + // {{{ 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); + $seqcol_name = $this->options['seqcol_name']; + $query = "INSERT INTO $sequence_name ($seqcol_name) VALUES (NULL)"; + $this->pushErrorHandling(PEAR_ERROR_RETURN); + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + $result =$this->_doQuery($query, true); + $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 $this->raiseError($result, null, null, + 'on demand sequence '.$seq_name.' could not be created', __FUNCTION__); + } else { + return $this->nextID($seq_name, false); + } + } + return $result; + } + $value = $this->lastInsertID(); + if (is_numeric($value)) { + $query = "DELETE FROM $sequence_name WHERE $seqcol_name < $value"; + $result =$this->_doQuery($query, true); + if (PEAR::isError($result)) { + $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name; + } + } + return $value; + } + + // }}} + // {{{ 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) + { + return $this->connection->lastInsertRowID(); + } + + // }}} + // {{{ currID() + + /** + * Returns the current id of a sequence + * + * @param string $seq_name name of the sequence + * @return mixed MDB2 Error Object or id + * @access public + */ + function currID($seq_name) + { + $sequence_name = $this->quoteIdentifier($this->getSequenceName($seq_name), true); + $seqcol_name = $this->quoteIdentifier($this->options['seqcol_name'], true); + $query = "SELECT MAX($seqcol_name) FROM $sequence_name"; + return $this->queryOne($query, 'integer'); + } + + /** + * 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'] + || $this->supported['prepared_statements'] !== true + ) { + $obj =& parent::prepare($query, $types, $result_types, $lobs); + return $obj; + } + $this->last_query = $query; + $is_manip = ($result_types === MDB2_PREPARE_MANIP); + $offset = $this->offset; + $limit = $this->limit; + $this->offset = $this->limit = 0; + $query = $this->_modifyQuery($query, $is_manip, $limit, $offset); + $result = $this->debug($query, __FUNCTION__, array('is_manip' => $is_manip, 'when' => 'pre')); + if ($result) { + if (PEAR::isError($result)) { + return $result; + } + $query = $result; + } + $placeholder_type_guess = $placeholder_type = null; + $question = '?'; + $colon = ':'; + $positions = array(); + $position = 0; + 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 (is_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 (is_null($placeholder_type)) { + $placeholder_type = $query[$p_position]; + $question = $colon = $placeholder_type; + } + 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; + } + $positions[$p_position] = $parameter; + $query = substr_replace($query, '?', $position, strlen($parameter)+1); + } else { + $positions[$p_position] = count($positions); + } + $position = $p_position + 1; + } else { + $position = $p_position; + } + } + $connection = $this->getConnection(); + if (PEAR::isError($connection)) { + return $connection; + } + $statement =$this->connection->prepare($query); + if (!$statement) { + return $this->db->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'unable to prepare statement: '.$query); + } + + $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; + } +} + +/** + * MDB2 SQLite result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_Result_sqlite3 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 (!is_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) { + //$row = @sqlite_fetch_array($this->result, SQLITE_ASSOC); + $row=$this->result->fetchArray(SQLITE3_ASSOC); + if (is_array($row) + && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + ) { + $row = array_change_key_case($row, $this->db->options['field_case']); + } + } else { + $row=$this->result->fetchArray(SQLITE3_NUM); + } + if (!$row) { + if ($this->result === false) { + $err =$this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'resultset has already been freed', __FUNCTION__); + return $err; + } + $null = null; + return $null; + } + $mode = $this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL; + $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 { + $row = new $object_class($row); + } + } + ++$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 = $this->result->getColumnName($column); + $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. + * + * @access public + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + */ + function numCols() + { + $this->result->numColumns(); + } +} + +/** + * MDB2 SQLite buffered result driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_BufferedResult_sqlite3 extends MDB2_Result_sqlite3 +{ + // {{{ 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) + { + $this->result->reset(); + for($i=0;$i<$rownum;$i++){ + $this->result->fetchArray(); + } + $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() + { + $numrows = $this->numRows(); + if (PEAR::isError($numrows)) { + return $numrows; + } + return $this->rownum < ($numrows - 1); + } + + // }}} + // {{{ numRows() + + /** + * Returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + $rows = 0; + $this->result->reset(); + while($this->result->fetchArray()){ + $rows++; + } + $this->result->reset(); + return $rows; + } +} + +/** + * MDB2 SQLite statement driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith <smith@pooteeweet.org> + */ +class MDB2_Statement_sqlite3 extends MDB2_Statement_Common +{ + // }}} + // {{{ function bindValue($parameter, &$value, $type = null) + + private function getParamType($type){ + switch(strtolower($type)){ + case 'text': + return SQLITE3_TEXT; + case 'boolean': + case 'integer': + return SQLITE3_INTEGER; + case 'float': + return SQLITE3_FLOAT; + case 'blob': + return SQLITE3_BLOB; + } + } + /** + * Set the value of a parameter of a prepared query. + * + * @param int the order number of the parameter in the query + * statement. The order number of the first parameter is 1. + * @param mixed value that is meant to be assigned to specified + * parameter. The type of the value depends on the $type argument. + * @param string specifies the type of the field + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function bindValue($parameter, $value, $type = null){ + if($type){ + $type=$this->getParamType($type); + $this->statement->bindValue($parameter,$value,$type); + }else{ + $this->statement->bindValue($parameter,$value); + } + return MDB2_OK; + } + + /** + * Bind a variable to a parameter of a prepared query. + * + * @param int the order number of the parameter in the query + * statement. The order number of the first parameter is 1. + * @param mixed variable that is meant to be bound to specified + * parameter. The type of the value depends on the $type argument. + * @param string specifies the type of the field + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + */ + function bindParam($parameter, &$value, $type = null){ + if($type){ + $type=$this->getParamType($type); + $this->statement->bindParam($parameter,$value,$type); + }else{ + $this->statement->bindParam($parameter,$value); + } + return MDB2_OK; + } + + /** + * Release resources allocated for the specified prepared query. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function free() + { + $this->statement->close(); + } + + /** + * 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 (is_null($this->statement)) { + $result =& parent::_execute($result_class, $result_wrap_class); + return $result; + } + $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 = $this->statement->execute(); + if ($result==false) { + $err =$this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'cant execute statement', __FUNCTION__); + } + + if ($this->is_manip) { + $affected_rows = $this->db->_affectedRows($connection, $result); + return $affected_rows; + } + + $result =& $this->db->_wrapResult($result, $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; + } + + /** + * Set the values of multiple a parameter of a prepared query in bulk. + * + * @param array specifies all necessary information + * for bindValue() the array elements must use keys corresponding to + * the number of the position of the parameter. + * @param array specifies the types of the fields + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @see bindParam() + */ + function bindValueArray($values, $types = null) + { + $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null); + $parameters = array_keys($values); + foreach ($parameters as $key => $parameter) { + $this->db->pushErrorHandling(PEAR_ERROR_RETURN); + $this->db->expectError(MDB2_ERROR_NOT_FOUND); + $err = $this->bindValue($parameter+1, $values[$parameter], $types[$key]); + $this->db->popExpect(); + $this->db->popErrorHandling(); + if (PEAR::isError($err)) { + if ($err->getCode() == MDB2_ERROR_NOT_FOUND) { + //ignore (extra value for missing placeholder) + continue; + } + return $err; + } + } + return MDB2_OK; + } + // }}} + // {{{ function bindParamArray(&$values, $types = null) + + /** + * Bind the variables of multiple a parameter of a prepared query in bulk. + * + * @param array specifies all necessary information + * for bindParam() the array elements must use keys corresponding to + * the number of the position of the parameter. + * @param array specifies the types of the fields + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * + * @access public + * @see bindParam() + */ + function bindParamArray(&$values, $types = null) + { + $types = is_array($types) ? array_values($types) : array_fill(0, count($values), null); + $parameters = array_keys($values); + foreach ($parameters as $key => $parameter) { + $err = $this->bindParam($parameter+1, $values[$parameter], $types[$key]); + if (PEAR::isError($err)) { + return $err; + } + } + return MDB2_OK; + } + + // }}} + // {{{ function &execute($values = null, $result_class = true, $result_wrap_class = false) + + /** + * Execute a prepared query statement. + * + * @param array specifies all necessary information + * for bindParam() the array elements must use keys corresponding + * to the number of the position of the parameter. + * @param mixed specifies which result class to use + * @param mixed specifies which class to wrap results in + * + * @return mixed MDB2_Result or integer (affected rows) on success, + * a MDB2 error on failure + * @access public + */ + function &execute($values = null, $result_class = true, $result_wrap_class = false) + { + if (is_null($this->positions)) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Prepared statement has already been freed', __FUNCTION__); + } + $values = (array)$values; + if (!empty($values)) { + if(count($this->types)){ + $types=$this->types; + }else{ + $types=null; + } + $err = $this->bindValueArray($values,$types); + if (PEAR::isError($err)) { + return $this->db->raiseError(MDB2_ERROR, null, null, + 'Binding Values failed with message: ' . $err->getMessage(), __FUNCTION__); + } + } + $result =$this->_execute($result_class, $result_wrap_class); + return $result; + } + + function __destruct() { + $this->free(); + } +} + +?>
\ No newline at end of file diff --git a/lib/User/backend.php b/lib/User/backend.php index e99016a5214..1797d0c475a 100644 --- a/lib/User/backend.php +++ b/lib/User/backend.php @@ -4,7 +4,9 @@ * ownCloud * * @author Frank Karlitschek + * @author Dominik Schmidt * @copyright 2010 Frank Karlitschek karlitschek@kde.org + * @copyright 2011 Dominik Schmidt dev@dominik-schmidt.de * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -21,92 +23,64 @@ * */ +/** + * error code for functions not provided by the user backend + */ +define('OC_USER_BACKEND_NOT_IMPLEMENTED', -501); + +/** + * actions that user backends can define + */ +define('OC_USER_BACKEND_CREATE_USER', 0x000001); +define('OC_USER_BACKEND_DELETE_USER', 0x000010); +define('OC_USER_BACKEND_SET_PASSWORD', 0x000100); +define('OC_USER_BACKEND_CHECK_PASSWORD', 0x001000); +define('OC_USER_BACKEND_GET_USERS', 0x010000); +define('OC_USER_BACKEND_USER_EXISTS', 0x100000); /** * abstract base class for user management + * subclass this for your own backends and see OC_USER_EXAMPLE for descriptions */ abstract class OC_USER_BACKEND { - /** - * @brief Create a new user - * @param $uid The username of the user to create - * @param $password The password of the new user - * @returns true/false - * - * Creates a new user. Basic checking of username is done in OC_USER - * itself, not in its subclasses. - */ - public static function createUser($uid, $password){} - - /** - * @brief delete a user - * @param $uid The username of the user to delete - * @returns true/false - * - * Deletes a user - */ - public static function deleteUser( $uid ){} + protected $possibleActions = array( + OC_USER_BACKEND_CREATE_USER => 'createUser', + OC_USER_BACKEND_DELETE_USER => 'deleteUser', + OC_USER_BACKEND_SET_PASSWORD => 'setPassword', + OC_USER_BACKEND_CHECK_PASSWORD => 'checkPassword', + OC_USER_BACKEND_GET_USERS => 'getUsers', + OC_USER_BACKEND_USER_EXISTS => 'userExists' + ); /** - * @brief Try to login a user - * @param $uid The username of the user to log in - * @param $password The password of the user - * @returns true/false - * - * Log in a user - if the password is ok - */ - public static function login($uid, $password){} + * @brief Get all supported actions + * @returns bitwise-or'ed actions + * + * Returns the supported actions as int to be + * compared with OC_USER_BACKEND_CREATE_USER etc. + */ + public function getSupportedActions(){ + $actions = 0; + foreach($this->possibleActions AS $action => $methodName){ + if(method_exists($this, $methodName)) { + $actions |= $action; + } + } - /** - * @brief Kick the user - * @returns true - * - * Logout, destroys session - */ - public static function logout(){} - - /** - * @brief Check if the user is logged in - * @returns true/false - * - * Checks if the user is logged in - */ - public static function isLoggedIn(){} - - /** - * @brief Autogenerate a password - * @returns string - * - * generates a password - */ - public static function generatePassword(){} - - /** - * @brief Set password - * @param $uid The username - * @param $password The new password - * @returns true/false - * - * Change the password of a user - */ - public static function setPassword($uid, $password){} - - /** - * @brief Check if the password is correct - * @param $uid The username - * @param $password The password - * @returns true/false - * - * Check if the password is correct without logging in the user - */ - public static function checkPassword($uid, $password){} + return $actions; + } /** - * @brief Get a list of all users - * @returns array with all uids - * - * Get a list of all users. - */ - public static function getUsers(){} + * @brief Check if backend implements actions + * @param $actions bitwise-or'ed actions + * @returns boolean + * + * Returns the supported actions as int to be + * compared with OC_USER_BACKEND_CREATE_USER etc. + */ + public function implementsActions($actions){ + return (bool)($this->getSupportedActions() & $actions); + } } diff --git a/lib/User/database.php b/lib/User/database.php index eeabb592c22..0396ac30958 100644 --- a/lib/User/database.php +++ b/lib/User/database.php @@ -50,12 +50,8 @@ class OC_USER_DATABASE extends OC_USER_BACKEND { * Creates a new user. Basic checking of username is done in OC_USER * itself, not in its subclasses. */ - public static function createUser( $uid, $password ){ - // Check if the user already exists - $query = OC_DB::prepare( "SELECT * FROM `*PREFIX*users` WHERE uid = ?" ); - $result = $query->execute( array( $uid )); - - if ( $result->numRows() > 0 ){ + public function createUser( $uid, $password ){ + if( $this->userExists($uid) ){ return false; } else{ @@ -73,7 +69,7 @@ class OC_USER_DATABASE extends OC_USER_BACKEND { * * Deletes a user */ - public static function deleteUser( $uid ){ + public function deleteUser( $uid ){ // Delete user-group-relation $query = OC_DB::prepare( "DELETE FROM `*PREFIX*users` WHERE uid = ?" ); $result = $query->execute( array( $uid )); @@ -81,69 +77,6 @@ class OC_USER_DATABASE extends OC_USER_BACKEND { } /** - * @brief Try to login a user - * @param $uid The username of the user to log in - * @param $password The password of the user - * @returns true/false - * - * Log in a user - if the password is ok - */ - public static function login( $uid, $password ){ - // Query - $query = OC_DB::prepare( "SELECT uid FROM *PREFIX*users WHERE uid = ? AND password = ?" ); - $result = $query->execute( array( $uid, sha1( $password ))); - - if( $result->numRows() > 0 ){ - // Set username if name and password are known - $row = $result->fetchRow(); - $_SESSION['user_id'] = $row["uid"]; - OC_LOG::add( "core", $_SESSION['user_id'], "login" ); - return true; - } - else{ - return false; - } - } - - /** - * @brief Kick the user - * @returns true - * - * Logout, destroys session - */ - public static function logout(){ - OC_LOG::add( "core", $_SESSION['user_id'], "logout" ); - $_SESSION['user_id'] = false; - - return true; - } - - /** - * @brief Check if the user is logged in - * @returns true/false - * - * Checks if the user is logged in - */ - public static function isLoggedIn() { - if( isset($_SESSION['user_id']) AND $_SESSION['user_id'] ){ - return true; - } - else{ - return false; - } - } - - /** - * @brief Autogenerate a password - * @returns string - * - * generates a password - */ - public static function generatePassword(){ - return uniqId(); - } - - /** * @brief Set password * @param $uid The username * @param $password The new password @@ -151,12 +84,8 @@ class OC_USER_DATABASE extends OC_USER_BACKEND { * * Change the password of a user */ - public static function setPassword( $uid, $password ){ - // Check if the user already exists - $query = OC_DB::prepare( "SELECT * FROM `*PREFIX*users` WHERE uid = ?" ); - $result = $query->execute( array( $uid )); - - if( $result->numRows() > 0 ){ + public function setPassword( $uid, $password ){ + if( $this->userExists($uid) ){ $query = OC_DB::prepare( "UPDATE *PREFIX*users SET password = ? WHERE uid = ?" ); $result = $query->execute( array( sha1( $password ), $uid )); @@ -175,7 +104,7 @@ class OC_USER_DATABASE extends OC_USER_BACKEND { * * Check if the password is correct without logging in the user */ - public static function checkPassword( $uid, $password ){ + public function checkPassword( $uid, $password ){ $query = OC_DB::prepare( "SELECT uid FROM *PREFIX*users WHERE uid = ? AND password = ?" ); $result = $query->execute( array( $uid, sha1( $password ))); @@ -193,7 +122,7 @@ class OC_USER_DATABASE extends OC_USER_BACKEND { * * Get a list of all users. */ - public static function getUsers(){ + public function getUsers(){ $query = OC_DB::prepare( "SELECT uid FROM *PREFIX*users" ); $result = $query->execute(); @@ -203,4 +132,16 @@ class OC_USER_DATABASE extends OC_USER_BACKEND { } return $users; } + + /** + * @brief check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid){ + $query = OC_DB::prepare( "SELECT * FROM `*PREFIX*users` WHERE uid = ?" ); + $result = $query->execute( array( $uid )); + + return $result->numRows() > 0; + } } diff --git a/lib/User/example.php b/lib/User/example.php new file mode 100644 index 00000000000..4abc1b3d49c --- /dev/null +++ b/lib/User/example.php @@ -0,0 +1,97 @@ +<?php + +/** + * ownCloud + * + * @author Frank Karlitschek + * @copyright 2010 Frank Karlitschek karlitschek@kde.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +require_once('User/backend.php'); + +/** + * abstract reference class for user management + * this class should only be used as a reference for method signatures and their descriptions + */ +abstract class OC_USER_EXAMPLE extends OC_USER_BACKEND { + /** + * @brief Create a new user + * @param $uid The username of the user to create + * @param $password The password of the new user + * @returns true/false + * + * Creates a new user. Basic checking of username is done in OC_USER + * itself, not in its subclasses. + */ + public function createUser($uid, $password){ + return OC_USER_BACKEND_NOT_IMPLEMENTED; + } + + /** + * @brief delete a user + * @param $uid The username of the user to delete + * @returns true/false + * + * Deletes a user + */ + public function deleteUser( $uid ){ + return OC_USER_BACKEND_NOT_IMPLEMENTED; + } + + /** + * @brief Set password + * @param $uid The username + * @param $password The new password + * @returns true/false + * + * Change the password of a user + */ + public function setPassword($uid, $password){ + return OC_USER_BACKEND_NOT_IMPLEMENTED; + } + + /** + * @brief Check if the password is correct + * @param $uid The username + * @param $password The password + * @returns true/false + * + * Check if the password is correct without logging in the user + */ + public function checkPassword($uid, $password){ + return OC_USER_BACKEND_NOT_IMPLEMENTED; + } + + /** + * @brief Get a list of all users + * @returns array with all uids + * + * Get a list of all users. + */ + public function getUsers(){ + return OC_USER_BACKEND_NOT_IMPLEMENTED; + } + + /** + * @brief check if a user exists + * @param string $uid the username + * @return boolean + */ + public function userExists($uid){ + return OC_USER_BACKEND_NOT_IMPLEMENTED; + } +} diff --git a/lib/app.php b/lib/app.php index a56e0c2f9e6..64704f09bbd 100644 --- a/lib/app.php +++ b/lib/app.php @@ -29,7 +29,7 @@ class OC_APP{ static private $init = false; static private $apps = array(); - static private $activeapp = ""; + static private $activeapp = ''; static private $adminpages = array(); static private $settingspages = array(); static private $navigation = array(); @@ -52,33 +52,19 @@ class OC_APP{ } // Our very own core apps are hardcoded - foreach( array( "admin", "files", "log", "help", "settings" ) as $app ){ - require( "$app/appinfo/app.php" ); + foreach( array( 'admin', 'files', 'log', 'help', 'settings' ) as $app ){ + require( $app.'/appinfo/app.php' ); } // The rest comes here - $dir = opendir( "$SERVERROOT/apps" ); - while( false !== ( $filename = readdir( $dir ))){ - if( substr( $filename, 0, 1 ) != '.' ){ - if( file_exists( "$SERVERROOT/apps/$filename/appinfo/app.php" )){ - if(OC_APPCONFIG::getValue($filename,'installed_version',0)==0){ //check if the plugin is fully installed - //install the database - if(is_file("$SERVERROOT/apps/$filename/appinfo/database.xml")){ - OC_DB::createDbFromStructure("$SERVERROOT/apps/$filename/appinfo/database.xml"); - } - - //run appinfo/install.php - if(is_file("$SERVERROOT/apps/$filename/appinfo/install.php")){ - include("$SERVERROOT/apps/$filename/appinfo/install.php"); - } - $info=self::getAppInfo("$SERVERROOT/apps/$filename/appinfo/info.xml"); - OC_APPCONFIG::setValue($filename,'installed_version',$info['version']); - } - require( "apps/$filename/appinfo/app.php" ); + $apps = OC_APPCONFIG::getApps(); + foreach( $apps as $app ){ + if( self::isEnabled( $app )){ + if(is_file($SERVERROOT.'/apps/'.$app.'/appinfo/app.php')){ + require( 'apps/'.$app.'/appinfo/app.php' ); } } } - closedir( $dir ); self::$init = true; @@ -87,14 +73,51 @@ class OC_APP{ } /** + * @brief checks whether or not an app is enabled + * @param $app app + * @returns true/false + * + * This function checks whether or not an app is enabled. + */ + public static function isEnabled( $app ){ + if( 'yes' == OC_APPCONFIG::getValue( $app, 'enabled' )){ + return true; + } + + return false; + } + + /** + * @brief enables an app + * @param $app app + * @returns true/false + * + * This function set an app as enabled in appconfig. + */ + public static function enable( $app ){ + OC_APPCONFIG::setValue( $app, 'enabled', 'yes' ); + } + + /** + * @brief enables an app + * @param $app app + * @returns true/false + * + * This function set an app as enabled in appconfig. + */ + public static function disable( $app ){ + OC_APPCONFIG::setValue( $app, 'enabled', 'no' ); + } + + /** * @brief makes owncloud aware of this app * @param $data array with all information * @returns true/false * * This function registers the application. $data is an associative array. * The following keys are required: - * - id: id of the application, has to be unique ("addressbook") - * - name: Human readable name ("Addressbook") + * - id: id of the application, has to be unique ('addressbook') + * - name: Human readable name ('Addressbook') * - version: array with Version (major, minor, bugfix) ( array(1, 0, 2)) * * The following keys are optional: @@ -125,9 +148,9 @@ class OC_APP{ * This function adds a new entry to the navigation visible to users. $data * is an associative array. * The following keys are required: - * - id: unique id for this entry ("addressbook_index") + * - id: unique id for this entry ('addressbook_index') * - href: link to the page - * - name: Human readable name ("Addressbook") + * - name: Human readable name ('Addressbook') * * The following keys are optional: * - icon: path to the icon of the app @@ -151,9 +174,9 @@ class OC_APP{ * as being active (see activateNavigationEntry()). $data is an associative * array. * The following keys are required: - * - id: unique id for this entry ("addressbook_index") + * - id: unique id for this entry ('addressbook_index') * - href: link to the page - * - name: Human readable name ("Addressbook") + * - name: Human readable name ('Addressbook') * * The following keys are optional: * - icon: path to the icon of the app @@ -173,7 +196,7 @@ class OC_APP{ * @param $id id of the entry * @returns true/false * - * This function sets a navigation entry as active and removes the "active" + * This function sets a navigation entry as active and removes the 'active' * property from all other entries. The templates can use this for * highlighting the current position of the user. */ @@ -201,9 +224,9 @@ class OC_APP{ * This function registers a admin page that will be shown in the admin * menu. $data is an associative array. * The following keys are required: - * - id: unique id for this entry ("files_admin") + * - id: unique id for this entry ('files_admin') * - href: link to the admin page - * - name: Human readable name ("Files Administration") + * - name: Human readable name ('Files Administration') * * The following keys are optional: * - order: integer, that influences the position of your application in @@ -222,16 +245,16 @@ class OC_APP{ * * This function registers a settings page. $data is an associative array. * The following keys are required: - * - app: app the settings belong to ("files") - * - id: unique id for this entry ("files_public") + * - app: app the settings belong to ('files') + * - id: unique id for this entry ('files_public') * - href: link to the admin page - * - name: Human readable name ("Public files") + * - name: Human readable name ('Public files') * * The following keys are optional: * - order: integer, that influences the position of your application in * the list. Lower values come first. * - * For the main settings page of an app, the keys "app" and "id" have to be + * For the main settings page of an app, the keys 'app' and 'id' have to be * the same. */ public static function addSettingsPage( $data = array()){ @@ -245,11 +268,11 @@ class OC_APP{ * @returns associative array * * This function returns an array containing all entries added. The - * entries are sorted by the key "order" ascending. Additional to the keys + * entries are sorted by the key 'order' ascending. Additional to the keys * given for each app the following keys exist: * - active: boolean, signals if the user is on this navigation entry - * - children: array that is empty if the key "active" is false or - * contains the subentries if the key "active" is true + * - children: array that is empty if the key 'active' is false or + * contains the subentries if the key 'active' is true */ public static function getNavigation(){ $navigation = self::proceedNavigation( self::$navigation ); @@ -262,7 +285,7 @@ class OC_APP{ * @returns associative array * * This function returns an array containing all settings pages added. The - * entries are sorted by the key "order" ascending. + * entries are sorted by the key 'order' ascending. */ public static function getSettingsNavigation(){ $navigation = self::proceedNavigation( self::$settingspages ); @@ -276,7 +299,7 @@ class OC_APP{ * @returns associative array * * This function returns an array containing all admin pages added. The - * entries are sorted by the key "order" ascending. + * entries are sorted by the key 'order' ascending. */ public static function getAdminNavigation(){ $navigation = self::proceedNavigation( self::$adminpages ); @@ -290,38 +313,47 @@ class OC_APP{ $found = false; foreach( self::$subnavigation as $parent => $selection ){ foreach( $selection as $subentry ){ - if( $subentry["id"] == self::$activeapp ){ + if( $subentry['id'] == self::$activeapp ){ foreach( $list as &$naventry ){ - if( $naventry["id"] == $parent ){ - $naventry["active"] = true; - $naventry["subnavigation"] = $selection; + if( $naventry['id'] == $parent ){ + $naventry['active'] = true; + $naventry['subnavigation'] = $selection; } else{ - $naventry["active"] = false; + $naventry['active'] = false; } - } + } unset( $naventry ); $found = true; } } } + // Mark subentry as active + foreach( $list as &$naventry ){ + if( $naventry['active'] ){ + foreach( $naventry['subnavigation'] as &$subnaventry ){ + $subnaventry['active'] = $subnaventry['id'] == self::$activeapp? true : false; + } unset( $subnaventry ); + } + } unset( $naventry ); + return $list; } /// This is private as well. It simply works, so don't ask for more details private static function proceedNavigation( $list ){ foreach( $list as &$naventry ){ - $naventry["subnavigation"] = array(); - if( $naventry["id"] == self::$activeapp ){ - $naventry["active"] = true; - if( array_key_exists( $naventry["id"], self::$subnavigation )){ - $naventry["subnavigation"] = self::$subnavigation[$naventry["id"]]; + $naventry['subnavigation'] = array(); + if( $naventry['id'] == self::$activeapp ){ + $naventry['active'] = true; + if( array_key_exists( $naventry['id'], self::$subnavigation )){ + $naventry['subnavigation'] = self::$subnavigation[$naventry['id']]; } } else{ - $naventry["active"] = false; + $naventry['active'] = false; } - } + } unset( $naventry ); usort( $list, create_function( '$a, $b', 'if( $a["order"] == $b["order"] ){return 0;}elseif( $a["order"] < $b["order"] ){return -1;}else{return 1;}' )); diff --git a/lib/base.php b/lib/base.php index e5b6656c2a9..e821d78fd8c 100644 --- a/lib/base.php +++ b/lib/base.php @@ -35,7 +35,11 @@ $SERVERROOT=substr(__FILE__,0,-13); $DOCUMENTROOT=realpath($_SERVER['DOCUMENT_ROOT']); $SERVERROOT=str_replace("\\",'/',$SERVERROOT); $SUBURI=substr(realpath($_SERVER["SCRIPT_FILENAME"]),strlen($SERVERROOT)); -$WEBROOT=substr($_SERVER["SCRIPT_NAME"],0,strlen($_SERVER["SCRIPT_NAME"])-strlen($SUBURI)); +$scriptName=$_SERVER["SCRIPT_NAME"]; +if(substr($scriptName,-1)=='/'){//if the script isn't a file assume index.php + $scriptName.='index.php'; +} +$WEBROOT=substr($scriptName,0,strlen($scriptName)-strlen($SUBURI)); @@ -80,7 +84,12 @@ require_once('appconfig.php'); require_once('files.php'); require_once('filesystem.php'); require_once('filestorage.php'); +<<<<<<< HEAD require_once('apps/files_sharing/sharedstorage.php'); +======= +require_once('l10n.php'); +require_once('preferences.php'); +>>>>>>> master require_once('log.php'); require_once('user.php'); require_once('group.php'); @@ -92,7 +101,7 @@ require_once('search.php'); $error=(count(OC_UTIL::checkServer())>0); -OC_USER::setBackend( OC_CONFIG::getValue( "userbackend", "database" )); +OC_USER::useBackend( OC_CONFIG::getValue( "userbackend", "database" )); OC_GROUP::setBackend( OC_CONFIG::getValue( "groupbackend", "database" )); // Set up file system unless forbidden @@ -119,6 +128,7 @@ if(!$error and !$RUNTIME_NOAPPS ){ class OC_UTIL { public static $scripts=array(); public static $styles=array(); + public static $headers=array(); private static $fsSetup=false; // Can be set up @@ -141,12 +151,12 @@ class OC_UTIL { // If we are not forced to load a specific user we load the one that is logged in if( $user == "" && OC_USER::isLoggedIn()){ - $user = $_SESSION['user_id']; + $user = OC_USER::getUser(); } if( $user != "" ){ //if we aren't logged in, there is no use to set up the filesystem //first set up the local "root" storage and the backupstorage if needed - $rootStorage=OC_FILESYSTEM::createStorage('local',array('datadir'=>$CONFIG_DATADIRECTORY)); + $rootStorage=OC_FILESYSTEM::createStorage('local',array('datadir'=>$CONFIG_DATADIRECTORY_ROOT)); // if( OC_CONFIG::getValue( "enablebackup", false )){ // // This creates the Directorys recursively // if(!is_dir( "$CONFIG_BACKUPDIRECTORY/$user/$root" )){ @@ -187,6 +197,11 @@ class OC_UTIL { } } + public static function tearDownFS(){ + OC_FILESYSTEM::tearDown(); + self::$fsSetup=false; + } + /** * get the current installed version of ownCloud * @return array @@ -229,6 +244,16 @@ class OC_UTIL { } } + /** + * @brief Add a custom element to the header + * @param string tag tag name of the element + * @param array $attributes array of attrobutes for the element + * @param string $text the text content for the element + */ + public static function addHeader( $tag, $attributes, $text=''){ + self::$headers[]=array('tag'=>$tag,'attributes'=>$attributes,'text'=>$text); + } + /** * formats a timestamp in the "right" way * diff --git a/lib/config.php b/lib/config.php index 6af9fcbe5ad..cd18ddd499c 100644 --- a/lib/config.php +++ b/lib/config.php @@ -139,7 +139,7 @@ class OC_CONFIG{ // Include the file, save the data from $CONFIG include( "$SERVERROOT/config/config.php" ); - if( isset( $CONFIG ) || is_array( $CONFIG )){ + if( isset( $CONFIG ) && is_array( $CONFIG )){ self::$cache = $CONFIG; } @@ -169,9 +169,6 @@ class OC_CONFIG{ $value = $value ? 'true' : 'false'; $content .= "\"$key\" => $value,\n"; } - elseif( is_numeric( $value )){ - $content .= "\"$key\" => $value,\n"; - } else{ $value = str_replace( "'", "\\'", $value ); $content .= "\"$key\" => '$value',\n"; diff --git a/lib/database.php b/lib/database.php index 728e7359040..8d7c76756c1 100644 --- a/lib/database.php +++ b/lib/database.php @@ -60,10 +60,10 @@ class OC_DB { 'quote_identifier' => true ); // Add the dsn according to the database type - if( $CONFIG_DBTYPE == 'sqlite' ){ + if( $CONFIG_DBTYPE == 'sqlite' or $CONFIG_DBTYPE == 'sqlite3' ){ // sqlite $dsn = array( - 'phptype' => 'sqlite', + 'phptype' => $CONFIG_DBTYPE, 'database' => "$datadir/$CONFIG_DBNAME.db", 'mode' => '0644' ); } @@ -100,6 +100,9 @@ class OC_DB { // We always, really always want associative arrays self::$DBConnection->setFetchMode(MDB2_FETCHMODE_ASSOC); + + //we need to function module for query pre-procesing + self::$DBConnection->loadModule('Function'); } // we are done. great! @@ -297,15 +300,14 @@ class OC_DB { * and replaces the ` woth ' or " according to the database driver. */ private static function processQuery( $query ){ + self::connect(); // We need Database type and table prefix $CONFIG_DBTYPE = OC_CONFIG::getValue( "dbtype", "sqlite" ); $CONFIG_DBTABLEPREFIX = OC_CONFIG::getValue( "dbtableprefix", "oc_" ); // differences is getting the current timestamp - if( $CONFIG_DBTYPE == 'sqlite' ){ - $query = str_replace( 'NOW()', "strftime('%s', 'now')", $query ); - $query = str_replace( 'now()', "strftime('%s', 'now')", $query ); - } + $query = str_replace( 'NOW()', self::$DBConnection->now(), $query ); + $query = str_replace( 'now()', self::$DBConnection->now(), $query ); // differences in escaping of table names (` for mysql) // Problem: what if there is a ` in the value we want to insert? @@ -321,5 +323,43 @@ class OC_DB { return $query; } + + /** + * @brief drop a table + * @param string $tableNamme the table to drop + */ + public static function dropTable($tableName){ + self::connect(); + self::$DBConnection->loadModule('Manager'); + self::$DBConnection->dropTable($tableName); + } + + /** + * remove all tables defined in a database structure xml file + * @param string $file the xml file describing the tables + */ + public static function removeDBStructure($file){ + $CONFIG_DBNAME = OC_CONFIG::getValue( "dbname", "owncloud" ); + $CONFIG_DBTABLEPREFIX = OC_CONFIG::getValue( "dbtableprefix", "oc_" ); + self::connectScheme(); + + // read file + $content = file_get_contents( $file ); + + // Make changes and save them to a temporary file + $file2 = tempnam( sys_get_temp_dir(), 'oc_db_scheme_' ); + $content = str_replace( '*dbname*', $CONFIG_DBNAME, $content ); + $content = str_replace( '*dbprefix*', $CONFIG_DBTABLEPREFIX, $content ); + file_put_contents( $file2, $content ); + + // get the tables + $definition = self::$schema->parseDatabaseDefinitionFile( $file2 ); + + // Delete our temporary file + unlink( $file2 ); + foreach($definition['tables'] as $name=>$table){ + self::dropTable($name); + } + } } ?>
\ No newline at end of file diff --git a/lib/files.php b/lib/files.php index 9e66f9dab82..d8133667954 100644 --- a/lib/files.php +++ b/lib/files.php @@ -30,15 +30,6 @@ require_once("log.php"); */ class OC_FILES { static $tmpFiles=array(); - /** - * show a web GUI filebrowser - * - * @param basedir $basedir - * @param dir $dir - */ - public static function showBrowser($basedir,$dir){ - echo '<div id="content"></div>'; - } /** * get the content of a directory @@ -101,7 +92,7 @@ class OC_FILES { if(strpos($files,';')){ $files=explode(';',$files); } - echo 't'; + if(is_array($files)){ $zip = new ZipArchive(); $filename = sys_get_temp_dir()."/ownCloud.zip"; @@ -300,13 +291,13 @@ class OC_FILES { global $SERVERROOT; global $WEBROOT; $size=OC_HELPER::humanFileSize($size); - echo $size; $size=substr($size,0,-1);//strip the B $size=str_replace(' ','',$size); //remove the space between the size and the postfix - $content = "ErrorDocument 404 /$WEBROOT/templates/404.php\n";//custom 404 error page + $content = "ErrorDocument 404 /$WEBROOT/core/templates/404.php\n";//custom 404 error page $content.= "php_value upload_max_filesize $size\n";//upload limit $content.= "php_value post_max_size $size\n"; $content.= "SetEnv htaccessWorking true\n"; + $content.= "Options -Indexes\n"; @file_put_contents($SERVERROOT.'/.htaccess', $content); //supress errors in case we don't have permissions for it } } diff --git a/lib/filestorage.php b/lib/filestorage.php index 157e44ff298..819ad2e60b3 100644 --- a/lib/filestorage.php +++ b/lib/filestorage.php @@ -377,7 +377,7 @@ class OC_FILESTORAGE_LOCAL extends OC_FILESTORAGE{ if ($item == '.' || $item == '..') continue; if(is_file($dir.'/'.$item)){ if(unlink($dir.'/'.$item)){ - $this->clearFolderSizeCache($path); + $this->clearFolderSizeCache($dir); } }elseif(is_dir($dir.'/'.$item)){ if (!$this->delTree($dirRelative. "/" . $item)){ diff --git a/lib/filesystem.php b/lib/filesystem.php index a13363f832d..4116cb93164 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -29,10 +29,18 @@ * * Hooks provided: * read(path) - * write(path) - * create(path) (when a file is created both, write and create will be emited) - * delete(path) - * rename(oldpath,newpath) + * write(path, &run) + * post_write(path) + * create(path, &run) (when a file is created, both create and write will be emited in that order) + * post_create(path) + * delete(path, &run) + * post_delete(path) + * rename(oldpath,newpath, &run) + * post_rename(oldpath,newpath) + * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emited in that order) + * post_rename(oldpath,newpath) + * + * the &run parameter can be set to false to prevent the operation from occuring */ class OC_FILESYSTEM{ static private $storages=array(); @@ -68,6 +76,16 @@ class OC_FILESYSTEM{ } /** + * tear down the filesystem, removing all storage providers + */ + static public function tearDown(){ + foreach(self::$storages as $mountpoint=>$storage){ + unset(self::$storages[$mountpoint]); + } + $fakeRoot=''; + } + + /** * create a new storage of a specific type * @param string type * @param array arguments @@ -91,8 +109,10 @@ class OC_FILESYSTEM{ * @return bool */ static public function chroot($fakeRoot){ - if($fakeRoot[0]!=='/'){ - $fakeRoot='/'.$fakeRoot; + if(!$fakeRoot==''){ + if($fakeRoot[0]!=='/'){ + $fakeRoot='/'.$fakeRoot; + } } self::$fakeRoot=$fakeRoot; } @@ -210,14 +230,28 @@ class OC_FILESYSTEM{ static public function mkdir($path){ $parent=substr($path,0,strrpos($path,'/')); if(self::canWrite($parent) and $storage=self::getStorage($path)){ - OC_HOOK::emit( 'OC_FILESYSTEM', 'create', array( 'path' => $path)); - return $storage->mkdir(self::getInternalPath($path)); + $run=true; + OC_HOOK::emit( 'OC_FILESYSTEM', 'create', array( 'path' => $path, 'run' => &$run)); + if($run){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'write', array( 'path' => $path, 'run' => &$run)); + } + if($run){ + $result=$storage->mkdir(self::getInternalPath($path)); + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_create', array( 'path' => $path)); + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_write', array( 'path' => $path)); + return $result; + } } } static public function rmdir($path){ if(self::canWrite($path) and $storage=self::getStorage($path)){ - OC_HOOK::emit( 'OC_FILESYSTEM', 'delete', array( 'path' => $path)); - return $storage->rmdir(self::getInternalPath($path)); + $run=true; + OC_HOOK::emit( 'OC_FILESYSTEM', 'delete', array( 'path' => $path, 'run' => &$run)); + if($run){ + $result=$storage->rmdir(self::getInternalPath($path)); + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_delete', array( 'path' => $path)); + return $result; + } } } static public function opendir($path){ @@ -307,55 +341,94 @@ class OC_FILESYSTEM{ } static public function file_put_contents($path,$data){ if(self::canWrite($path) and $storage=self::getStorage($path)){ - OC_HOOK::emit( 'OC_FILESYSTEM', 'write', array( 'path' => $path)); - if(!self::file_exists($path)){ - OC_HOOK::emit( 'OC_FILESYSTEM', 'create', array( 'path' => $path)); + $run=true; + $exists=self::file_exists($path); + if(!$exists){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'create', array( 'path' => $path, 'run' => &$run)); + } + if($run){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'write', array( 'path' => $path, 'run' => &$run)); + } + if($run){ + $result=$storage->file_put_contents(self::getInternalPath($path),$data); + if(!$exists){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_create', array( 'path' => $path)); + } + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_write', array( 'path' => $path)); + return $result; } - return $storage->file_put_contents(self::getInternalPath($path),$data); } } static public function unlink($path){ if(self::canWrite($path) and $storage=self::getStorage($path)){ - OC_HOOK::emit( 'OC_FILESYSTEM', 'delete', array( 'path' => $path)); - return $storage->unlink(self::getInternalPath($path)); + $run=true; + OC_HOOK::emit( 'OC_FILESYSTEM', 'delete', array( 'path' => $path, 'run' => &$run)); + if($run){ + $result=$storage->unlink(self::getInternalPath($path)); + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_delete', array( 'path' => $path)); + return $result; + } } } static public function rename($path1,$path2){ if(self::canWrite($path1) and self::canWrite($path2)){ - $mp1=self::getMountPoint($path1); - $mp2=self::getMountPoint($path2); - if($mp1==$mp2){ - if($storage=self::getStorage($path1)){ - return $storage->rename(self::getInternalPath($path1),self::getInternalPath($path2)); + $run=true; + OC_HOOK::emit( 'OC_FILESYSTEM', 'rename', array( 'oldpath' => $path1 ,'newpath'=>$path2, 'run' => &$run)); + if($run){ + $mp1=self::getMountPoint($path1); + $mp2=self::getMountPoint($path2); + if($mp1==$mp2){ + if($storage=self::getStorage($path1)){ + $result=$storage->rename(self::getInternalPath($path1),self::getInternalPath($path2)); + } + }elseif($storage1=self::getStorage($path1) and $storage2=self::getStorage($path2)){ + $tmpFile=$storage1->toTmpFile(self::getInternalPath($path1)); + $result=$storage2->fromTmpFile(self::getInternalPath($path2)); + $storage1->unlink(self::getInternalPath($path1)); } - }elseif($storage1=self::getStorage($path1) and $storage2=self::getStorage($path2)){ - $tmpFile=$storage1->toTmpFile(self::getInternalPath($path1)); - $result=$storage2->fromTmpFile(self::getInternalPath($path2)); - $storage1->unlink(self::getInternalPath($path1)); + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_rename', array( 'oldpath' => $path1, 'newpath'=>$path2)); return $result; } - OC_HOOK::emit( 'OC_FILESYSTEM', 'rename', array( 'oldpath' => $path1 ,'newpath'=>$path2)); } } static public function copy($path1,$path2){ if(self::canRead($path1) and self::canWrite($path2)){ - $mp1=self::getMountPoint($path1); - $mp2=self::getMountPoint($path2); - if($mp1==$mp2){ - if($storage=self::getStorage($path1)){ - return $storage->copy(self::getInternalPath($path1),self::getInternalPath($path2)); + $run=true; + OC_HOOK::emit( 'OC_FILESYSTEM', 'copy', array( 'oldpath' => $path1 ,'newpath'=>$path2, 'run' => &$run)); + $exists=self::file_exists($path2); + if($run and !$exists){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'create', array( 'path' => $path2, 'run' => &$run)); + } + if($run){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'write', array( 'path' => $path2, 'run' => &$run)); + } + if($run){ + $mp1=self::getMountPoint($path1); + $mp2=self::getMountPoint($path2); + if($mp1==$mp2){ + if($storage=self::getStorage($path1)){ + $result=$storage->copy(self::getInternalPath($path1),self::getInternalPath($path2)); + } + }elseif($storage1=self::getStorage($path1) and $storage2=self::getStorage($path2)){ + $tmpFile=$storage1->toTmpFile(self::getInternalPath($path1)); + $result=$storage2->fromTmpFile(self::getInternalPath($path2)); + } + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_copy', array( 'oldpath' => $path1 ,'newpath'=>$path2)); + if(!$exists){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_create', array( 'path' => $path)); } - }elseif($storage1=self::getStorage($path1) and $storage2=self::getStorage($path2)){ - $tmpFile=$storage1->toTmpFile(self::getInternalPath($path1)); - return $storage2->fromTmpFile(self::getInternalPath($path2)); + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_write', array( 'path' => $path)); + return $result; } - OC_HOOK::emit( 'OC_FILESYSTEM', 'create', array( 'path' => $path2)); } } static public function fopen($path,$mode){ $allowed=((strpos($path,'r')===false and strpos($path,'r+')!==false and self::canRead) or self::canWrite($path)); if($allowed){ if($storage=self::getStorage($path)){ + $run=true; + $exists=self::file_exists($path); + $write=false; switch($mode){ case 'r': OC_HOOK::emit( 'OC_FILESYSTEM', 'read', array( 'path' => $path)); @@ -365,21 +438,34 @@ class OC_FILESYSTEM{ case 'x+': case 'a+': OC_HOOK::emit( 'OC_FILESYSTEM', 'read', array( 'path' => $path)); - OC_HOOK::emit( 'OC_FILESYSTEM', 'write', array( 'path' => $path)); - if(!self::file_exists($path)){ - OC_HOOK::emit( 'OC_FILESYSTEM', 'create', array( 'path' => $path)); - } + $write=true; break; case 'w': case 'x': case 'a': - OC_HOOK::emit( 'OC_FILESYSTEM', 'write', array( 'path' => $path)); - if(!self::file_exists($path)){ - OC_HOOK::emit( 'OC_FILESYSTEM', 'create', array( 'path' => $path)); - } + $write=true; break; } - return $storage->fopen(self::getInternalPath($path),$mode); + if($write){ + if(!$exists){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'create', array( 'path' => $path)); + } + if($run){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'write', array( 'path' => $path, 'run' => &$run)); + } + } + if($run){ + $result=$storage->fopen(self::getInternalPath($path),$mode); + if($write){ + if(!$exists){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_create', array( 'path' => $path)); + } + if($run){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_write', array( 'path' => $path)); + } + } + return $result; + } } } } @@ -391,20 +477,44 @@ class OC_FILESYSTEM{ } static public function fromTmpFile($tmpFile,$path){ if(self::canWrite($path) and $storage=self::getStorage($path)){ - OC_HOOK::emit( 'OC_FILESYSTEM', 'write', array( 'path' => $path)); - if(!self::file_exists($path)){ - OC_HOOK::emit( 'OC_FILESYSTEM', 'create', array( 'path' => $path)); + $run=true; + $exists=self::file_exists($path); + if(!$exists){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'create', array( 'path' => $path, 'run' => &$run)); + } + if($run){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'write', array( 'path' => $path, 'run' => &$run)); + } + if($run){ + $result=$storage->fromTmpFile($tmpFile,self::getInternalPath($path)); + if(!$exists){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_create', array( 'path' => $path)); + } + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_write', array( 'path' => $path)); + return $result; } - return $storage->fromTmpFile($tmpFile,self::getInternalPath($path)); } } static public function fromUploadedFile($tmpFile,$path){ + error_log('upload'); if(self::canWrite($path) and $storage=self::getStorage($path)){ - OC_HOOK::emit( 'OC_FILESYSTEM', 'write', array( 'path' => $path)); - if(!self::file_exists($path)){ - OC_HOOK::emit( 'OC_FILESYSTEM', 'create', array( 'path' => $path)); + $run=true; + $exists=self::file_exists($path); + if(!$exists){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'create', array( 'path' => $path, 'run' => &$run)); + } + if($run){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'write', array( 'path' => $path, 'run' => &$run)); + } + error_log('upload2'); + if($run){ + $result=$storage->fromUploadedFile($tmpFile,self::getInternalPath($path)); + if(!$exists){ + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_create', array( 'path' => $path)); + } + OC_HOOK::emit( 'OC_FILESYSTEM', 'post_write', array( 'path' => $path)); + return $result; } - return $storage->fromUploadedFile($tmpFile,self::getInternalPath($path)); } } static public function getMimeType($path){ @@ -414,8 +524,11 @@ class OC_FILESYSTEM{ } static public function delTree($path){ if(self::canWrite($path) and $storage=self::getStorage($path)){ - OC_HOOK::emit( 'OC_FILESYSTEM', 'delete', array( 'path' => $path)); - return $storage->delTree(self::getInternalPath($path)); + $run=true; + OC_HOOK::emit( 'OC_FILESYSTEM', 'delete', array( 'path' => $path, 'run' => &$run)); + if($run){ + return $storage->delTree(self::getInternalPath($path)); + } } } static public function find($path){ @@ -463,10 +576,12 @@ class OC_FILESYSTEM{ $fakeRootLength=strlen(self::$fakeRoot); foreach(self::$storages as $mountpoint=>$storage){ $results=$storage->search($query); - foreach($results as $result){ - $file=str_replace('//','/',$mountpoint.$result); - $file=substr($file,$fakeRootLength); - $files[]=$file; + if(is_array($results)){ + foreach($results as $result){ + $file=str_replace('//','/',$mountpoint.$result); + $file=substr($file,$fakeRootLength); + $files[]=$file; + } } } return $files; diff --git a/lib/helper.php b/lib/helper.php index 072607f742b..96d5bfad826 100644..100755 --- a/lib/helper.php +++ b/lib/helper.php @@ -29,22 +29,39 @@ class OC_HELPER { * @brief Creates an url * @param $app app * @param $file file + * @param $redirect_url redirect_url variable is appended to the URL * @returns the url * * Returns a url to the given app and file. */ - public static function linkTo( $app, $file ){ + public static function linkTo( $app, $file , $redirect_url=NULL ){ global $WEBROOT; global $SERVERROOT; - if(!empty($app)) { + if( $app != '' ){ $app .= '/'; // Check if the app is in the app folder - if( file_exists( $SERVERROOT . '/apps/'. $app )){ - return $WEBROOT . '/apps/' . $app . $file; + if( file_exists( $SERVERROOT . '/apps/'. $app.$file )){ + $urlLinkTo = $WEBROOT . '/apps/' . $app . $file; + } + else{ + $urlLinkTo = $WEBROOT . '/' . $app . $file; + } + } + else{ + if( file_exists( $SERVERROOT . '/core/'. $file )){ + $urlLinkTo = $WEBROOT . '/core/'.$file; + } + else{ + $urlLinkTo = $WEBROOT . '/'.$file; } } - return $WEBROOT . '/' . $app . $file; + + if($redirect_url) + return $urlLinkTo.'?redirect_url='.$redirect_url; + else + return $urlLinkTo; + } /** @@ -58,14 +75,16 @@ class OC_HELPER { public static function imagePath( $app, $image ){ global $SERVERROOT; global $WEBROOT; + // Check if the app is in the app folder if( file_exists( "$SERVERROOT/apps/$app/img/$image" )){ return "$WEBROOT/apps/$app/img/$image"; } - if( !empty( $app )){ + elseif( !empty( $app )){ return "$WEBROOT/$app/img/$image"; - }else{ - return "$WEBROOT/img/$image"; + } + else{ + return "$WEBROOT/core/img/$image"; } } @@ -84,15 +103,15 @@ class OC_HELPER { // Is it a dir? if( $mimetype == "dir" ){ - return "$WEBROOT/img/places/folder.png"; + return "$WEBROOT/core/img/places/folder.png"; } // Icon exists? - if( file_exists( "$SERVERROOT/img/mimetypes/$mimetype.png" )){ - return "$WEBROOT/img/mimetypes/$mimetype.png"; + if( file_exists( "$SERVERROOT/core/img/mimetypes/$mimetype.png" )){ + return "$WEBROOT/core/img/mimetypes/$mimetype.png"; } else{ - return "$WEBROOT/img/mimetypes/file.png"; + return "$WEBROOT/core/img/mimetypes/file.png"; } } diff --git a/lib/installer.php b/lib/installer.php index 7ab07bf5077..a237caa0983 100644 --- a/lib/installer.php +++ b/lib/installer.php @@ -59,7 +59,7 @@ class OC_INSTALLER{ if(!isset($data['source'])){ error_log("No source specified when installing app"); - return; + return false; } //download the file if necesary @@ -67,13 +67,13 @@ class OC_INSTALLER{ $path=tempnam(sys_get_temp_dir(),'oc_installer_'); if(!isset($data['href'])){ error_log("No href specified when installing app from http"); - return; + return false; } copy($data['href'],$path); }else{ if(!isset($data['path'])){ error_log("No path specified when installing app from local file"); - return; + return false; } $path=$data['path']; } @@ -92,7 +92,7 @@ class OC_INSTALLER{ if($data['source']=='http'){ unlink($path); } - return; + return false; } //load the info.xml file of the app @@ -102,23 +102,33 @@ class OC_INSTALLER{ if($data['source']=='http'){ unlink($path); } - return; + return false; } $info=OC_APP::getAppInfo($extractDir.'/appinfo/info.xml'); $basedir=$SERVERROOT.'/apps/'.$info['id']; //check if an app with the same id is already installed - if(is_dir($basedir)){ + if(self::isInstalled( $info['id'] )){ error_log("App already installed"); OC_HELPER::rmdirr($extractDir); if($data['source']=='http'){ unlink($path); } - return; + return false; + } + + //check if the destination directory already exists + if(is_dir($basedir)){ + error_log("App's directory already exists"); + OC_HELPER::rmdirr($extractDir); + if($data['source']=='http'){ + unlink($path); + } + return false; } if(isset($data['pretent']) and $data['pretent']==true){ - return; + return false; } //copy the app to the correct place @@ -128,7 +138,7 @@ class OC_INSTALLER{ if($data['source']=='http'){ unlink($path); } - return; + return false; } OC_HELPER::copyr($extractDir,$basedir); @@ -150,6 +160,23 @@ class OC_INSTALLER{ //set the installed version OC_APPCONFIG::setValue($info['id'],'installed_version',$info['version']); + OC_APPCONFIG::setValue($info['id'],'enabled','no'); + return true; + } + + /** + * @brief checks whether or not an app is installed + * @param $app app + * @returns true/false + * + * Checks whether or not an app is installed, i.e. registered in apps table. + */ + public static function isInstalled( $app ){ + + if( null == OC_APPCONFIG::getValue( $app, "installed_version" )){ + return false; + } + return true; } @@ -209,4 +236,42 @@ class OC_INSTALLER{ // TODO: write function return true; } + + /** + * @brief Installs shipped apps + * @param $enabled + * + * This function installs all apps found in the 'apps' directory; + * If $enabled is true, apps are installed as enabled. + * If $enabled is false, apps are installed as disabled. + */ + public static function installShippedApps( $enabled ){ + global $SERVERROOT; + $dir = opendir( "$SERVERROOT/apps" ); + while( false !== ( $filename = readdir( $dir ))){ + if( substr( $filename, 0, 1 ) != '.' and is_dir("$SERVERROOT/apps/$filename") ){ + if( file_exists( "$SERVERROOT/apps/$filename/appinfo/app.php" )){ + if(!OC_INSTALLER::isInstalled($filename)){ + //install the database + if(is_file("$SERVERROOT/apps/$filename/appinfo/database.xml")){ + OC_DB::createDbFromStructure("$SERVERROOT/apps/$filename/appinfo/database.xml"); + } + + //run appinfo/install.php + if(is_file("$SERVERROOT/apps/$filename/appinfo/install.php")){ + include("$SERVERROOT/apps/$filename/appinfo/install.php"); + } + $info=OC_APP::getAppInfo("$SERVERROOT/apps/$filename/appinfo/info.xml"); + OC_APPCONFIG::setValue($filename,'installed_version',$info['version']); + if( $enabled ){ + OC_APPCONFIG::setValue($filename,'enabled','yes'); + }else{ + OC_APPCONFIG::setValue($filename,'enabled','no'); + } + } + } + } + } + closedir( $dir ); + } } diff --git a/lib/l10n.php b/lib/l10n.php new file mode 100644 index 00000000000..053c6fbc10e --- /dev/null +++ b/lib/l10n.php @@ -0,0 +1,269 @@ +<?php +/** + * ownCloud + * + * @author Jakob Sack + * @copyright 2010 Frank Karlitschek karlitschek@kde.org + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * This class is for i18n and l10n + */ +class OC_L10N{ + /** + * cache + */ + protected static $cache = array(); + + /** + * The best language + */ + protected static $language = ''; + + /** + * Translations + */ + private $translations = array(); + + /** + * Localization + */ + private $localizations = array( + 'date' => 'd.m.Y', + 'datetime' => 'd.m.Y H:i:s', + 'time' => 'H:i:s' ); + + /** + * @brief The constructor + * @param $app the app requesting l10n + * @param $lang default: null Language + * @returns OC_L10N-Object + * + * If language is not set, the constructor tries to find the right + * language. + */ + public function __construct( $app, $lang = null ){ + global $SERVERROOT; + // Find the right language + if( is_null( $lang )){ + $lang = self::findLanguage( $app ); + } + + // Use cache if possible + if(array_key_exists($app.'::'.$lang, self::$cache )){ + + $this->translations = self::$cache[$app.'::'.$lang]['t']; + $this->localizations = self::$cache[$app.'::'.$lang]['l']; + } + else{ + $i18ndir = self::findI18nDir( $app ); + // Localization is in /l10n, Texts are in $i18ndir + // (Just no need to define date/time format etc. twice) + if( file_exists( $i18ndir.$lang.'.php' )){ + // Include the file, save the data from $CONFIG + include( $i18ndir.$lang.'.php' ); + if( isset( $TRANSLATIONS ) && is_array( $TRANSLATIONS )){ + $this->translations = $TRANSLATIONS; + } + } + + if( file_exists( $SERVERROOT.'/core/l10n/l10n-'.$lang.'.php' )){ + // Include the file, save the data from $CONFIG + include( $SERVERROOT.'/core/l10n/l10n-'.$lang.'.php' ); + if( isset( $LOCALIZATIONS ) && is_array( $LOCALIZATIONS )){ + $this->localizations = array_merge( $this->localizations, $LOCALIZATIONS ); + } + } + + self::$cache[$app.'::'.$lang]['t'] = $this->translations; + self::$cache[$app.'::'.$lang]['l'] = $this->localizations; + } + } + + /** + * @brief Translating + * @param $text The text we need a translation for + * @returns Translation or the same text + * + * Returns the translation. If no translation is found, $text will be + * returned. + */ + public function t($text){ + if(array_key_exists($text, $this->translations)){ + return $this->translations[$text]; + } + return $text; + } + + /** + * @brief getTranslations + * @returns Fetch all translations + * + * Returns an associative array with all translations + */ + public function getTranslations(){ + return $this->translations; + } + + /** + * @brief Localization + * @param $type Type of localization + * @param $params parameters for this localization + * @returns String or false + * + * Returns the localized data. + * + * Implemented types: + * - date + * - Creates a date + * - l10n-field: date + * - params: timestamp (int/string) + * - datetime + * - Creates date and time + * - l10n-field: datetime + * - params: timestamp (int/string) + * - time + * - Creates a time + * - l10n-field: time + * - params: timestamp (int/string) + */ + public function l($type, $data){ + switch($type){ + // If you add something don't forget to add it to $localizations + // at the top of the page + case 'date': + if( is_string( $data )) $data = strtotime( $data ); + return date( $this->localizations['date'], $data ); + break; + case 'datetime': + if( is_string( $data )) $data = strtotime( $data ); + return date( $this->localizations['datetime'], $data ); + break; + case 'time': + if( is_string( $data )) $data = strtotime( $data ); + return date( $this->localizations['time'], $data ); + break; + default: + return false; + } + } + + /** + * @brief Choose a language + * @param $texts Associative Array with possible strings + * @returns String + * + * $text is an array 'de' => 'hallo welt', 'en' => 'hello world', ... + * + * This function is useful to avoid loading thousands of files if only one + * simple string is needed, for example in appinfo.php + */ + public static function selectLanguage( $text ){ + $lang = self::findLanguage( array_keys( $text )); + return $text[$lang]; + } + + /** + * @brief find the best language + * @param $app Array or string, details below + * @returns language + * + * If $app is an array, ownCloud assumes that these are the available + * languages. Otherwise ownCloud tries to find the files in the l10n + * folder. + * + * If nothing works it returns 'en' + */ + public static function findLanguage( $app = null ){ + if( !is_array( $app) && self::$language != '' ){ + return self::$language; + } + + $available = array(); + if( is_array( $app )){ + $available = $app; + } + else{ + $available=self::findAvailableLanguages( $app ); + } + if( OC_USER::getUser() && OC_PREFERENCES::getValue( OC_USER::getUser(), 'core', 'lang' )){ + $lang = OC_PREFERENCES::getValue( OC_USER::getUser(), 'core', 'lang' ); + self::$language = $lang; + if( array_search( $lang, $available ) !== false ){ + return $lang; + } + } + + if( isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] )){ + $accepted_languages = preg_split( '/,\s*/', $_SERVER['HTTP_ACCEPT_LANGUAGE'] ); + foreach( $accepted_languages as $i ){ + $temp = explode( ';', $i ); + if( array_search( $temp[0], $available ) !== false ){ + return $temp[0]; + } + } + } + + // Last try: English + return 'en'; + } + + /** + * @brief find the l10n directory + * @param $app App that needs to be translated + * @returns directory + */ + protected static function findI18nDir( $app ){ + global $SERVERROOT; + + // find the i18n dir + $i18ndir = $SERVERROOT.'/core/l10n/'; + if( $app != '' ){ + // Check if the app is in the app folder + if( file_exists( $SERVERROOT.'/apps/'.$app.'/l10n/' )){ + $i18ndir = $SERVERROOT.'/apps/'.$app.'/l10n/'; + } + else{ + $i18ndir = $SERVERROOT.'/'.$app.'/l10n/'; + } + } + return $i18ndir; + } + + /** + * @brief find all available languages for an app + * @param $app App that needs to be translated + * @returns array an array of available languages + */ + public static function findAvailableLanguages( $app=null ){ + $available=array('en');//english is always available + $dir = self::findI18nDir( $app ); + if( file_exists($dir)){ + $dh = opendir($dir); + while(( $file = readdir( $dh )) !== false ){ + if( substr( $file, -4, 4 ) == '.php' and strlen($file)==6 ){ + $i = substr( $file, 0, -4 ); + if( $i != '' ){ + $available[] = $i; + } + } + } + closedir($dh); + } + return $available; + } +}
\ No newline at end of file diff --git a/lib/log.php b/lib/log.php index aebeba7d4b3..894575ef059 100644 --- a/lib/log.php +++ b/lib/log.php @@ -79,7 +79,7 @@ class OC_LOG { * - app: only entries for this app */ public static function get( $filter = array()){ - $queryString='SELECT * FROM *PREFIX*log WHERE 1=1 '; + $queryString='SELECT * FROM *PREFIX*log WHERE 1=1 ORDER BY moment DESC'; $params=array(); if(isset($filter['from'])){ $queryString.='AND moment>? '; @@ -120,6 +120,18 @@ class OC_LOG { $query->execute(array($date)); return true; } + + /** + * @brief removes all log entries + * @returns true/false + * + * This function deletes all log entries. + */ + public static function deleteAll(){ + $query=OC_DB::prepare("DELETE FROM *PREFIX*log"); + $query->execute(); + return true; + } /** * @brief filter an array of log entries on action diff --git a/lib/ocsclient.php b/lib/ocsclient.php index a3c4659c6e4..cfd529b2ec4 100644 --- a/lib/ocsclient.php +++ b/lib/ocsclient.php @@ -37,7 +37,10 @@ class OC_OCSCLIENT{ public static function getCategories(){ $url='http://api.apps.owncloud.com/v1/content/categories'; - $xml=file_get_contents($url); + $xml=@file_get_contents($url); + if($xml==FALSE){ + return NULL; + } $data=simplexml_load_string($xml); $tmp=$data->data; @@ -61,16 +64,19 @@ class OC_OCSCLIENT{ * This function returns a list of all the applications on the OCS server */ public static function getApplications($categories){ - if(is_array($categories)) { + if(is_array($categories)) { $categoriesstring=implode('x',$categories); }else{ $categoriesstring=$categories; } $url='http://api.apps.owncloud.com/v1/content/data?categories='.urlencode($categoriesstring).'&sortmode=new&page=0&pagesize=10'; $apps=array(); - $xml=file_get_contents($url); + $xml=@file_get_contents($url); + if($xml==FALSE){ + return NULL; + } $data=simplexml_load_string($xml); - + $tmp=$data->data->content; for($i = 0; $i < count($tmp); $i++) { $app=array(); @@ -90,60 +96,66 @@ class OC_OCSCLIENT{ } - /** - * @brief Get an the applications from the OCS server - * @returns array with application data - * - * This function returns an applications from the OCS server - */ - public static function getApplication($id){ - $url='http://api.apps.owncloud.com/v1/content/data/'.urlencode($id); - - $xml=file_get_contents($url); - $data=simplexml_load_string($xml); - - $tmp=$data->data->content; - $app=array(); - $app['id']=$tmp->id; - $app['name']=$tmp->name; - $app['type']=$tmp->typeid; - $app['typename']=$tmp->typename; - $app['personid']=$tmp->personid; - $app['detailpage']=$tmp->detailpage; - $app['preview1']=$tmp->smallpreviewpic1; - $app['preview2']=$tmp->smallpreviewpic2; - $app['preview3']=$tmp->smallpreviewpic3; - $app['changed']=strtotime($tmp->changed); - $app['description']=$tmp->description; - - return $app; - } - - /** - * @brief Get all the knowledgebase entries from the OCS server - * @returns array with q and a data - * - * This function returns a list of all the knowledgebase entries from the OCS server - */ - public static function getKnownledgebaseEntries(){ - $url='http://api.apps.owncloud.com/v1/knowledgebase/data?type=150&page=0&pagesize=10'; - - $kbe=array(); - $xml=file_get_contents($url); - $data=simplexml_load_string($xml); - - $tmp=$data->data->content; - for($i = 0; $i < count($tmp); $i++) { - $kb=array(); - $kb['id']=$tmp[$i]->id; - $kb['name']=$tmp[$i]->name; - $kb['description']=$tmp[$i]->description; - $kb['answer']=$tmp[$i]->answer; - $kb['preview1']=$tmp[$i]->smallpreviewpic1; - $kbe[]=$kb; - } - return $kbe; - } + /** + * @brief Get an the applications from the OCS server + * @returns array with application data + * + * This function returns an applications from the OCS server + */ + public static function getApplication($id){ + $url='http://api.apps.owncloud.com/v1/content/data/'.urlencode($id); + + $xml=@file_get_contents($url); + if($xml==FALSE){ + return NULL; + } + $data=simplexml_load_string($xml); + + $tmp=$data->data->content; + $app=array(); + $app['id']=$tmp->id; + $app['name']=$tmp->name; + $app['type']=$tmp->typeid; + $app['typename']=$tmp->typename; + $app['personid']=$tmp->personid; + $app['detailpage']=$tmp->detailpage; + $app['preview1']=$tmp->smallpreviewpic1; + $app['preview2']=$tmp->smallpreviewpic2; + $app['preview3']=$tmp->smallpreviewpic3; + $app['changed']=strtotime($tmp->changed); + $app['description']=$tmp->description; + + return $app; + } + + /** + * @brief Get all the knowledgebase entries from the OCS server + * @returns array with q and a data + * + * This function returns a list of all the knowledgebase entries from the OCS server + */ + public static function getKnownledgebaseEntries(){ + $url='http://api.apps.owncloud.com/v1/knowledgebase/data?type=150&page=0&pagesize=10'; + + $kbe=array(); + $xml=@file_get_contents($url); + if($xml==FALSE){ + return NULL; + } + $data=simplexml_load_string($xml); + + $tmp=$data->data->content; + for($i = 0; $i < count($tmp); $i++) { + $kb=array(); + $kb['id']=$tmp[$i]->id; + $kb['name']=$tmp[$i]->name; + $kb['description']=$tmp[$i]->description; + $kb['answer']=$tmp[$i]->answer; + $kb['preview1']=$tmp[$i]->smallpreviewpic1; + $kbe[]=$kb; + } + return $kbe; + } diff --git a/lib/preferences.php b/lib/preferences.php index 5a9ed395085..0f4636f6832 100644 --- a/lib/preferences.php +++ b/lib/preferences.php @@ -139,10 +139,12 @@ class OC_PREFERENCES{ */ public static function setValue( $user, $app, $key, $value ){ // Check if the key does exist - $exists = self::getValue( $user, $app, $key, null ); + $query = OC_DB::prepare( 'SELECT configvalue FROM *PREFIX*preferences WHERE userid = ? AND appid = ? AND configkey = ?' ); + $values=$query->execute(array($user,$app,$key))->fetchAll(); + error_log(print_r($values,true)); + $exists=(count($values)>0); - // null: does not exist. Insert. - if( is_null( $exists )){ + if( !$exists ){ $query = OC_DB::prepare( 'INSERT INTO *PREFIX*preferences ( userid, appid, configkey, configvalue ) VALUES( ?, ?, ?, ? )' ); $query->execute( array( $user, $app, $key, $value )); } diff --git a/lib/setup.php b/lib/setup.php index 72507f221b9..1be4dea286d 100644 --- a/lib/setup.php +++ b/lib/setup.php @@ -1,6 +1,8 @@ <?php -$hasSQLite = is_callable('sqlite_open'); +include_once( 'installer.php' ); + +$hasSQLite = (is_callable('sqlite_open') or class_exists('SQLite3')); $hasMySQL = is_callable('mysql_connect'); $datadir = OC_CONFIG::getValue('datadir', $SERVERROOT.'/data'); $opts = array( @@ -21,7 +23,7 @@ if(isset($_POST['install']) AND $_POST['install']=='true') { OC_TEMPLATE::printGuestPage("", "installation", $options); } else { - header("Location: $WEBROOT"); + header("Location: ".$WEBROOT.'/'); exit(); } } @@ -65,10 +67,15 @@ class OC_SETUP { $username = htmlspecialchars_decode($options['adminlogin']); $password = htmlspecialchars_decode($options['adminpass']); $datadir = htmlspecialchars_decode($options['directory']); + + //use sqlite3 when available, otherise sqlite2 will be used. + if($dbtype=='sqlite' and class_exists('SQLite3')){ + $dbtype='sqlite3'; + } //write the config file OC_CONFIG::setValue('datadirectory', $datadir); - OC_CONFIG::setValue('dbtype', $dbtype); + OC_CONFIG::setValue('dbtype', $dbtype); if($dbtype == 'mysql') { $dbuser = $options['dbuser']; $dbpass = $options['dbpass']; @@ -90,13 +97,20 @@ class OC_SETUP { else { $query="SELECT user FROM mysql.user WHERE user='$dbuser'"; //this should be enough to check for admin rights in mysql if(mysql_query($query, $connection)) { - self::createDBUser($username, $password, $connection); //use the admin login data for the new database user - OC_CONFIG::setValue('dbuser', $username); - OC_CONFIG::setValue('dbpassword', $password); + + //add prefix to the mysql user name to prevent collissions + $dbusername='oc_mysql_'.$username; + //hash the password so we don't need to store the admin config in the config file + $dbpassowrd=md5(time().$password); + + self::createDBUser($dbusername, $dbpassowrd, $connection); + + OC_CONFIG::setValue('dbuser', $dbusername); + OC_CONFIG::setValue('dbpassword', $dbpassowrd); //create the database - self::createDatabase($dbname, $username, $connection); + self::createDatabase($dbname, $dbusername, $connection); } else { OC_CONFIG::setValue('dbuser', $dbuser); @@ -128,6 +142,9 @@ class OC_SETUP { OC_GROUP::createGroup('admin'); OC_GROUP::addToGroup($username, 'admin'); + //guess what this does + OC_INSTALLER::installShippedApps(true); + //create htaccess files for apache hosts self::createHtaccess(); //TODO detect if apache is used @@ -167,10 +184,11 @@ class OC_SETUP { private static function createHtaccess() { global $SERVERROOT; global $WEBROOT; - $content = "ErrorDocument 404 /$WEBROOT/templates/404.php\n";//custom 404 error page + $content = "ErrorDocument 404 /$WEBROOT/core/templates/404.php\n";//custom 404 error page $content.= "php_value upload_max_filesize 20M\n";//upload limit $content.= "php_value post_max_size 20M\n"; $content.= "SetEnv htaccessWorking true\n"; + $content.= "Options -Indexes\n"; @file_put_contents($SERVERROOT.'/.htaccess', $content); //supress errors in case we don't have permissions for it $content = "deny from all"; @@ -178,4 +196,4 @@ class OC_SETUP { } } -?>
\ No newline at end of file +?> diff --git a/lib/template.php b/lib/template.php index 0d6776aa26d..9393fe6908e 100644 --- a/lib/template.php +++ b/lib/template.php @@ -73,8 +73,10 @@ function human_file_size( $bytes ){ class OC_TEMPLATE{ private $renderas; // Create a full page? private $application; // template Application - private $vars; // The smarty object - private $template; // The smarty object + private $vars; // Vars + private $template; // The path to the template + private $l10n; // The l10n-Object + private $headers=array(); //custom headers /** * @brief Constructor @@ -94,8 +96,8 @@ class OC_TEMPLATE{ global $SERVERROOT; // Get the right template folder - $template = "$SERVERROOT/templates/"; - if( $app != "core" && $app != "" ){ + $template = "$SERVERROOT/core/templates/"; + if( $app != "" ){ // Check if the app is in the app folder if( file_exists( "$SERVERROOT/apps/$app/templates/" )){ $template = "$SERVERROOT/apps/$app/templates/"; @@ -113,6 +115,7 @@ class OC_TEMPLATE{ $this->application = $app; $this->template = $template; $this->vars = array(); + $this->l10n = new OC_L10N($app); } /** @@ -149,6 +152,16 @@ class OC_TEMPLATE{ $this->vars[$key] = array( $value ); } } + + /** + * @brief Add a custom element to the header + * @param string tag tag name of the element + * @param array $attributes array of attrobutes for the element + * @param string $text the text content for the element + */ + public function addHeader( $tag, $attributes, $text=''){ + $this->headers[]=array('tag'=>$tag,'attributes'=>$attributes,'text'=>$text); + } /** * @brief Prints the proceeded template @@ -193,7 +206,6 @@ class OC_TEMPLATE{ $search=new OC_TEMPLATE( 'core', 'part.searchbox'); $search->assign('searchurl',OC_HELPER::linkTo( 'search', 'index.php' )); $page->assign('searchbox', $search->fetchPage()); - // Add menu data // Add navigation entry $page->assign( "navigation", OC_APP::getNavigation()); @@ -204,34 +216,48 @@ class OC_TEMPLATE{ $search=new OC_TEMPLATE( 'core', 'part.searchbox'); $search->assign('searchurl',OC_HELPER::linkTo( 'search', 'index.php' )); $page->assign('searchbox', $search->fetchPage()); + // Add menu data if( OC_GROUP::inGroup( $_SESSION["user_id"], "admin" )){ - $page->assign( "settingsnavigation", OC_APP::getSettingsNavigation()); + $page->assign( "adminnavigation", OC_APP::getAdminNavigation()); } - $page->assign( "adminnavigation", OC_APP::getAdminNavigation()); + $page->assign( "settingsnavigation", OC_APP::getSettingsNavigation()); } else { $page = new OC_TEMPLATE( "core", "layout.guest" ); - // Add data if required } // Add the css and js files foreach(OC_UTIL::$scripts as $script){ if(is_file("$SERVERROOT/apps/$script.js" )){ $page->append( "jsfiles", "$WEBROOT/apps/$script.js" ); - }else{ + } + elseif(is_file("$SERVERROOT/$script.js" )){ $page->append( "jsfiles", "$WEBROOT/$script.js" ); } + else{ + $page->append( "jsfiles", "$WEBROOT/core/$script.js" ); + } } foreach(OC_UTIL::$styles as $style){ if(is_file("$SERVERROOT/apps/$style.css" )){ $page->append( "cssfiles", "$WEBROOT/apps/$style.css" ); - }else{ + } + elseif(is_file("$SERVERROOT/$style.css" )){ $page->append( "cssfiles", "$WEBROOT/$style.css" ); } + else{ + $page->append( "cssfiles", "$WEBROOT/core/$style.css" ); + } } - + + // Add custom headers + $page->assign('headers',$this->headers); + foreach(OC_UTIL::$headers as $header){ + $page->append('headers',$header); + } + // Add css files and js files $page->assign( "content", $data ); return $page->fetchPage(); @@ -251,6 +277,7 @@ class OC_TEMPLATE{ private function _fetch(){ // Register the variables $_ = $this->vars; + $l = $this->l10n; // Execute the template ob_start(); diff --git a/lib/testcase.php b/lib/testcase.php new file mode 100644 index 00000000000..19494dc2f19 --- /dev/null +++ b/lib/testcase.php @@ -0,0 +1,93 @@ +<?php + +/** +* ownCloud +* +* @author Robin Appelman +* @copyright 2010 Robin Appelman icewind1991@gmailc.om +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + + +/** + * base class for unit tests + */ +class OC_TestCase{ + private $tests; //array of all tests in this test case + + public function __construct(){ + $this->tests=array(); + $this->results=array(); + $functions=get_class_methods(get_class($this)); + $exclude=get_class_methods('OC_TestCase'); + foreach($functions as $function){ + if(array_search($function,$exclude)===false){ + $this->tests[]=$function; + } + } + } + + public function getTests(){ + return $this->tests; + } + + /** + * function that gets called before each test + */ + private function setup(){ + } + + /** + * function that gets called after each test + */ + private function tearDown(){ + } + + /** + * check if the result equals the expected result + * @param mixed $expected the expected result + * @param mixed $result the actual result + * @param string $error (optional) the error message to display if the result isn't expected + */ + protected function assertEquals($expected,$result,$error=''){ + if($expected!==$result){ + if($expected===true){ + $expected='true'; + } + if($expected===false){ + $expected='false'; + } + if($result===true){ + $result='true'; + } + if($result===false){ + $result='false'; + } + if($error==''){ + $error="Unexpected result, expected '$expected' but was '$result'"; + } + throw new Exception($error); + } + } + + /** + * fail the test + * @param string $error the error message + */ + protected function fail($error){ + throw new Exception($error); + } +}
\ No newline at end of file diff --git a/lib/user.php b/lib/user.php index 09501e59c58..25f555b47b1 100644 --- a/lib/user.php +++ b/lib/user.php @@ -40,7 +40,7 @@ if( !OC_CONFIG::getValue( "installed", false )){ */ class OC_USER { // The backend used for user management - private static $_backend = null; + private static $_usedBackends = array(); // Backends available (except database) private static $_backends = array(); @@ -68,13 +68,23 @@ class OC_USER { } /** - * @brief Sets the backend + * @brief gets used backends + * @returns array of backends + * + * Returns the names of all used backends. + */ + public static function getUsedBackends(){ + return array_keys(self::$_usedBackends); + } + + /** + * @brief Adds the backend to the list of used backends * @param $backend default: database The backend to use for user managment * @returns true/false * * Set the User Authentication Module */ - public static function setBackend( $backend = 'database' ){ + public static function useBackend( $backend = 'database' ){ // You'll never know what happens if( null === $backend OR !is_string( $backend )){ $backend = 'database'; @@ -86,11 +96,11 @@ class OC_USER { case 'mysql': case 'sqlite': require_once('User/database.php'); - self::$_backend = new OC_USER_DATABASE(); + self::$_usedBackends[$backend] = new OC_USER_DATABASE(); break; default: $className = 'OC_USER_' . strToUpper($backend); - self::$_backend = new $className(); + self::$_usedBackends[$backend] = new $className(); break; } @@ -119,7 +129,7 @@ class OC_USER { return false; } // Check if user already exists - if( in_array( $uid, self::getUsers())){ + if( self::userExists($uid) ){ return false; } @@ -127,13 +137,19 @@ class OC_USER { $run = true; OC_HOOK::emit( "OC_USER", "pre_createUser", array( "run" => &$run, "uid" => $uid, "password" => $password )); - if( $run && self::$_backend->createUser( $uid, $password )){ - OC_HOOK::emit( "OC_USER", "post_createUser", array( "uid" => $uid, "password" => $password )); - return true; - } - else{ - return false; + if( $run ){ + //create the user in the first backend that supports creating users + foreach(self::$_usedBackends as $backend){ + if(!$backend->implementsActions(OC_USER_BACKEND_CREATE_USER)) + continue; + + $backend->createUser($uid,$password); + OC_HOOK::emit( "OC_USER", "post_createUser", array( "uid" => $uid, "password" => $password )); + + return true; + } } + return false; } /** @@ -147,7 +163,13 @@ class OC_USER { $run = true; OC_HOOK::emit( "OC_USER", "pre_deleteUser", array( "run" => &$run, "uid" => $uid )); - if( $run && self::$_backend->deleteUser( $uid )){ + if( $run ){ + //delete the user from all backends + foreach(self::$_usedBackends as $backend){ + if($backend->implementsActions(OC_USER_BACKEND_DELETE_USER)){ + $backend->deleteUser($uid); + } + } // We have to delete the user from all groups foreach( OC_GROUP::getUserGroups( $uid ) as $i ){ OC_GROUP::removeFromGroup( $uid, $i ); @@ -174,7 +196,9 @@ class OC_USER { $run = true; OC_HOOK::emit( "OC_USER", "pre_login", array( "run" => &$run, "uid" => $uid )); - if( $run && self::$_backend->login( $uid, $password )){ + if( $run && self::checkPassword( $uid, $password )){ + $_SESSION['user_id'] = $uid; + OC_LOG::add( "core", $_SESSION['user_id'], "login" ); OC_HOOK::emit( "OC_USER", "post_login", array( "uid" => $uid )); return true; } @@ -191,7 +215,9 @@ class OC_USER { */ public static function logout(){ OC_HOOK::emit( "OC_USER", "logout", array()); - return self::$_backend->logout(); + OC_LOG::add( "core", $_SESSION['user_id'], "logout" ); + $_SESSION['user_id'] = false; + return true; } /** @@ -201,7 +227,25 @@ class OC_USER { * Checks if the user is logged in */ public static function isLoggedIn(){ - return self::$_backend->isLoggedIn(); + if( isset($_SESSION['user_id']) AND $_SESSION['user_id'] ){ + return true; + } + else{ + return false; + } + } + + /** + * @brief get the user idea of the user currently logged in. + * @return string uid or false + */ + public static function getUser(){ + if( isset($_SESSION['user_id']) AND $_SESSION['user_id'] ){ + return $_SESSION['user_id']; + } + else{ + return false; + } } /** @@ -211,7 +255,7 @@ class OC_USER { * generates a password */ public static function generatePassword(){ - return substr( md5( uniqId().time()), 0, 10 ); + return uniqId(); } /** @@ -226,7 +270,14 @@ class OC_USER { $run = true; OC_HOOK::emit( "OC_USER", "pre_setPassword", array( "run" => &$run, "uid" => $uid, "password" => $password )); - if( $run && self::$_backend->setPassword( $uid, $password )){ + if( $run ){ + foreach(self::$_usedBackends as $backend){ + if($backend->implementsActions(OC_USER_BACKEND_SET_PASSWORD)){ + if($backend->userExists($uid)){ + $backend->setPassword($uid,$password); + } + } + } OC_HOOK::emit( "OC_USER", "post_setPassword", array( "uid" => $uid, "password" => $password )); return true; } @@ -244,7 +295,14 @@ class OC_USER { * Check if the password is correct without logging in the user */ public static function checkPassword( $uid, $password ){ - return self::$_backend->checkPassword( $uid, $password ); + foreach(self::$_usedBackends as $backend){ + if($backend->implementsActions(OC_USER_BACKEND_CHECK_PASSWORD)){ + $result=$backend->checkPassword( $uid, $password ); + if($result===true){ + return true; + } + } + } } /** @@ -254,6 +312,29 @@ class OC_USER { * Get a list of all users. */ public static function getUsers(){ - return self::$_backend->getUsers(); + $users=array(); + foreach(self::$_usedBackends as $backend){ + if($backend->implementsActions(OC_USER_BACKEND_GET_USERS)){ + $users=array_merge($users,$backend->getUsers()); + } + } + return $users; + } + + /** + * @brief check if a user exists + * @param string $uid the username + * @return boolean + */ + public static function userExists($uid){ + foreach(self::$_usedBackends as $backend){ + if($backend->implementsActions(OC_USER_BACKEND_USER_EXISTS)){ + $result=$backend->userExists($uid); + if($result===true){ + return true; + } + } + } + return false; } } |