diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/config/.gitignore b/config/.gitignore old mode 100644 new mode 100755 diff --git a/config/config.sample.php b/config/config.sample.php index dfaaa4284d..dc1a62f46e 100755 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -8,4 +8,5 @@ $CONFIG_DBHOST='localhost'; $CONFIG_DBNAME='owncloud-db-name'; $CONFIG_DBUSER='user-name'; $CONFIG_DBPASSWORD='password'; +$CONFIG_DBTABLEPREFIX = 'oc_'; ?> diff --git a/css/small.php b/css/small.php old mode 100644 new mode 100755 diff --git a/db_structure.xml b/db_structure.xml new file mode 100755 index 0000000000..7efb722bd7 --- /dev/null +++ b/db_structure.xml @@ -0,0 +1,381 @@ + + + + *dbname* + true + false + + latin1 + + + + *dbprefix*groups + + + + + group_id + integer + 0 + true + 1 + 4 + + + + group_name + text + + true + 64 + + + + group_name + true + + group_name + ascending + + + + + +
+ + + + *dbprefix*locks + + + + + token + text + + true + 255 + + + + path + text + + true + 200 + + + + created + integer + 0 + true + 4 + + + + modified + integer + 0 + true + 4 + + + + expires + integer + 0 + true + 4 + + + + owner + text + + false + 200 + + + + recursive + integer + 0 + false + 4 + + + + writelock + integer + 0 + false + 4 + + + + exclusivelock + integer + 0 + true + 4 + + + + path_2 + + path + ascending + + + + + path_3 + + path + ascending + + + token + ascending + + + + + expires + + expires + ascending + + + + + locks_pKey + true + + token + ascending + + + + + token + true + + token + ascending + + + + + +
+ + + + *dbprefix*log + + + + + id + integer + 0 + true + 1 + 4 + + + + timestamp + integer + + true + 4 + + + + user + text + + true + 250 + + + + type + integer + + true + 4 + + + + message + text + + true + 250 + + + + +
+ + + + *dbprefix*properties + + + + + path + text + + true + 255 + + + + name + text + + true + 120 + + + + ns + text + DAV: + true + 120 + + + + value + clob + false + + + + path + + path + ascending + + + + + properties_pKey + true + + path + ascending + + + name + ascending + + + ns + ascending + + + + + +
+ + + + *dbprefix*user_group + + + + + user_group_id + integer + 0 + true + 1 + 4 + + + + user_id + integer + + true + 4 + + + + group_id + integer + + true + 4 + + + + +
+ + + + *dbprefix*users + + + + + user_id + integer + 0 + true + 1 + 4 + + + + user_name + text + + true + 64 + + + + user_name_clean + text + + true + 64 + + + + user_password + text + + true + 340 + + + + user_name + true + + user_name + ascending + + + user_name_clean + ascending + + + + + +
+ +
diff --git a/files/api.php b/files/api.php index 014bbb56bb..d6e04d4550 100755 --- a/files/api.php +++ b/files/api.php @@ -55,6 +55,9 @@ if($arguments['action']){ case 'getfiles': echo json_encode(OC_FILES::getdirectorycontent($arguments['dir'])); break; + case 'gettree': + echo json_encode(OC_FILES::getTree($arguments['dir'])); + break; case 'find': echo json_encode(OC_FILESYSTEM::find($arguments['path'])); break; @@ -72,6 +75,8 @@ if($arguments['action']){ echo 'false'; } break; + case 'pull': + return OC_FILES::pull($arguments['source'],$arguments['token'],$arguments['dir'],$arguments['file']); } } diff --git a/files/pull.php b/files/pull.php new file mode 100644 index 0000000000..1cc8242584 --- /dev/null +++ b/files/pull.php @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/inc/HTTP/WebDAV/Server/Filesystem.php b/inc/HTTP/WebDAV/Server/Filesystem.php index b96fb414c2..ea0625a5a1 100755 --- a/inc/HTTP/WebDAV/Server/Filesystem.php +++ b/inc/HTTP/WebDAV/Server/Filesystem.php @@ -150,6 +150,8 @@ */ function fileinfo($path) { + global $CONFIG_DBTABLEPREFIX; + // map URI path to filesystem path $fspath =$path; @@ -183,7 +185,7 @@ $info["props"][] = $this->mkprop("getcontentlength", OC_FILESYSTEM::filesize($fspath)); } // get additional properties from database - $query = "SELECT ns, name, value FROM properties WHERE path = '$path'"; + $query = "SELECT ns, name, value FROM {$CONFIG_DBTABLEPREFIX}properties WHERE path = '$path'"; $res = OC_DB::select($query); foreach($res as $row){ $info["props"][] = $this->mkprop($row["ns"], $row["name"], $row["value"]); @@ -389,6 +391,7 @@ */ function DELETE($options) { + global $CONFIG_DBTABLEPREFIX; $path =$options["path"]; if (!OC_FILESYSTEM::file_exists($path)) { return "404 Not found"; @@ -402,13 +405,13 @@ } } if (OC_FILESYSTEM::is_dir($path)) { - $query = "DELETE FROM properties WHERE path LIKE '".$this->_slashify($options["path"])."%'"; + $query = "DELETE FROM {$CONFIG_DBTABLEPREFIX}properties WHERE path LIKE '".$this->_slashify($options["path"])."%'"; OC_DB::query($query); OC_FILESYSTEM::delTree($path); } else { OC_FILESYSTEM::unlink($path); } - $query = "DELETE FROM properties WHERE path = '$options[path]'"; + $query = "DELETE FROM {$CONFIG_DBTABLEPREFIX}properties WHERE path = '$options[path]'"; OC_DB::query($query); return "204 No Content"; @@ -435,6 +438,7 @@ function COPY($options, $del=false) { // TODO Property updates still broken (Litmus should detect this?) + global $CONFIG_DBTABLEPREFIX; if (!empty($this->_SERVER["CONTENT_LENGTH"])) { // no body parsing yet return "415 Unsupported media type"; @@ -508,13 +512,13 @@ } $destpath = $this->_unslashify($options["dest"]); if (is_dir($source)) { - $query = "UPDATE properties + $query = "UPDATE {$CONFIG_DBTABLEPREFIX}properties SET path = REPLACE(path, '".$options["path"]."', '".$destpath."') WHERE path LIKE '".$this->_slashify($options["path"])."%'"; OC_DB::query($query); } - $query = "UPDATE properties + $query = "UPDATE {$CONFIG_DBTABLEPREFIX}properties SET path = '".$destpath."' WHERE path = '".$options["path"]."'"; OC_DB::query($query); @@ -566,6 +570,7 @@ function PROPPATCH(&$options) { global $prefs, $tab; + global $CONFIG_DBTABLEPREFIX; $msg = ""; $path = $options["path"]; @@ -577,9 +582,9 @@ $options["props"][$key]['status'] = "403 Forbidden"; } else { if (isset($prop["val"])) { - $query = "REPLACE INTO properties SET path = '$options[path]', name = '$prop[name]', ns= '$prop[ns]', value = '$prop[val]'"; + $query = "REPLACE INTO {$CONFIG_DBTABLEPREFIX}properties SET path = '$options[path]', name = '$prop[name]', ns= '$prop[ns]', value = '$prop[val]'"; } else { - $query = "DELETE FROM properties WHERE path = '$options[path]' AND name = '$prop[name]' AND ns = '$prop[ns]'"; + $query = "DELETE FROM {$CONFIG_DBTABLEPREFIX}properties WHERE path = '$options[path]' AND name = '$prop[name]' AND ns = '$prop[ns]'"; } OC_DB::query($query); } @@ -597,6 +602,8 @@ */ function LOCK(&$options) { + global $CONFIG_DBTABLEPREFIX; + // get absolute fs path to requested resource $fspath = $options["path"]; // TODO recursive locks on directories not supported yet @@ -619,12 +626,12 @@ if (isset($options["update"])) { // Lock Update $where = "WHERE path = '$options[path]' AND token = '$options[update]'"; - $query = "SELECT owner, exclusivelock FROM locks $where"; + $query = "SELECT owner, exclusivelock FROM {$CONFIG_DBTABLEPREFIX}locks $where"; $res = OC_DB::select($query); if (is_array($res) and isset($res[0])) { $row=$res[0]; - $query = "UPDATE `locks` SET `expires` = '$options[timeout]', `modified` = ".time()." $where"; + $query = "UPDATE `{$CONFIG_DBTABLEPREFIX}locks` SET `expires` = '$options[timeout]', `modified` = ".time()." $where"; OC_DB::query($query); $options['owner'] = $row['owner']; @@ -634,14 +641,14 @@ return true; } else {//check for indirect refresh $query = "SELECT * - FROM locks + FROM {$CONFIG_DBTABLEPREFIX}locks WHERE recursive = 1 "; $res = OC_DB::select($query); foreach($res as $row){ if(strpos($options['path'],$row['path'])==0){//are we a child of a folder with an recursive lock $where = "WHERE path = '$row[path]' AND token = '$options[update]'"; - $query = "UPDATE `locks` SET `expires` = '$options[timeout]', `modified` = ".time()." $where"; + $query = "UPDATE `{$CONFIG_DBTABLEPREFIX}locks` SET `expires` = '$options[timeout]', `modified` = ".time()." $where"; OC_DB::query($query); $options['owner'] = $row['owner']; $options['scope'] = $row["exclusivelock"] ? "exclusive" : "shared"; @@ -652,7 +659,7 @@ } } - $query = "INSERT INTO `locks` + $query = "INSERT INTO `{$CONFIG_DBTABLEPREFIX}locks` SET `token` = '$options[locktoken]' , `path` = '$options[path]' , `created` = ".time()." @@ -677,7 +684,8 @@ */ function UNLOCK(&$options) { - $query = "DELETE FROM locks + global $CONFIG_DBTABLEPREFIX; + $query = "DELETE FROM {$CONFIG_DBTABLEPREFIX}locks WHERE path = '$options[path]' AND token = '$options[token]'"; OC_DB::query($query); @@ -693,9 +701,11 @@ */ function checkLock($path) { + global $CONFIG_DBTABLEPREFIX; + $result = false; $query = "SELECT * - FROM locks + FROM {$CONFIG_DBTABLEPREFIX}locks WHERE path = '$path' "; $res = OC_DB::select($query); @@ -717,7 +727,7 @@ }else{ //check for recursive locks; $query = "SELECT * - FROM locks + FROM {$CONFIG_DBTABLEPREFIX}locks WHERE recursive = 1 "; $res = OC_DB::select($query); @@ -741,4 +751,4 @@ } } -?> +?> \ No newline at end of file diff --git a/inc/MDB2.php b/inc/MDB2.php index 94991bb0c4..aa4259b9db 100755 --- a/inc/MDB2.php +++ b/inc/MDB2.php @@ -329,11 +329,10 @@ class MDB2 { if (!MDB2::classExists($class_name)) { $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php'; -// echo $file_name; if ($debug) { $include = oc_include_once($file_name); } else { - $include = @oc_include_once($file_name); + $include = oc_include_once($file_name); } if (!$include) { if (!MDB2::fileExists($file_name)) { @@ -958,8 +957,12 @@ class MDB2 function fileExists($file) { // safe_mode does notwork with is_readable() + global $SERVERROOT; if (!@ini_get('safe_mode')) { $dirs = explode(PATH_SEPARATOR, ini_get('include_path')); + $dirs[]=$SERVERROOT; + $dirs[]=$SERVERROOT. DIRECTORY_SEPARATOR .'inc'; +// print_r($dirs);die(); foreach ($dirs as $dir) { if (is_readable($dir . DIRECTORY_SEPARATOR . $file)) { return true; @@ -1920,7 +1923,6 @@ class MDB2_Driver_Common extends PEAR if (PEAR::isError($err)) { return $err; } - // load module in a specific version if ($version) { if (method_exists($class_name, 'getClassName')) { diff --git a/inc/MDB2/Driver/Datatype/Common.php b/inc/MDB2/Driver/Datatype/Common.php index 329bd60768..750dbb2477 100755 --- a/inc/MDB2/Driver/Datatype/Common.php +++ b/inc/MDB2/Driver/Datatype/Common.php @@ -44,7 +44,7 @@ // // $Id: Common.php,v 1.139 2008/12/04 11:50:42 afz Exp $ -require_once 'MDB2/LOB.php'; +oc_require_once('MDB2/LOB.php'); /** * @package MDB2 diff --git a/inc/MDB2/Driver/Datatype/mysql.php b/inc/MDB2/Driver/Datatype/mysql.php index aeec86aefa..944248f57c 100755 --- a/inc/MDB2/Driver/Datatype/mysql.php +++ b/inc/MDB2/Driver/Datatype/mysql.php @@ -46,7 +46,7 @@ // $Id: mysql.php,v 1.65 2008/02/22 19:23:49 quipo Exp $ // -require_once 'MDB2/Driver/Datatype/Common.php'; +oc_require_once('MDB2/Driver/Datatype/Common.php'); /** * MDB2 MySQL driver diff --git a/inc/MDB2/Driver/Datatype/pgsql.php b/inc/MDB2/Driver/Datatype/pgsql.php index 2979680490..fe18729c84 100755 --- a/inc/MDB2/Driver/Datatype/pgsql.php +++ b/inc/MDB2/Driver/Datatype/pgsql.php @@ -44,7 +44,7 @@ // // $Id: pgsql.php,v 1.93 2008/08/28 20:32:57 afz Exp $ -require_once 'MDB2/Driver/Datatype/Common.php'; +oc_require_once('MDB2/Driver/Datatype/Common.php'); /** * MDB2 PostGreSQL driver diff --git a/inc/MDB2/Driver/Datatype/sqlite.php b/inc/MDB2/Driver/Datatype/sqlite.php index 270c139684..533d0e9510 100755 --- a/inc/MDB2/Driver/Datatype/sqlite.php +++ b/inc/MDB2/Driver/Datatype/sqlite.php @@ -46,7 +46,7 @@ // $Id: sqlite.php,v 1.67 2008/02/22 19:58:06 quipo Exp $ // -require_once 'MDB2/Driver/Datatype/Common.php'; +oc_require_once('MDB2/Driver/Datatype/Common.php'); /** * MDB2 SQLite driver diff --git a/inc/MDB2/Driver/Function/mysql.php b/inc/MDB2/Driver/Function/mysql.php index b405c3ec02..aff531c9f3 100755 --- a/inc/MDB2/Driver/Function/mysql.php +++ b/inc/MDB2/Driver/Function/mysql.php @@ -45,7 +45,7 @@ // $Id: mysql.php,v 1.12 2008/02/17 18:54:08 quipo Exp $ // -require_once 'MDB2/Driver/Function/Common.php'; +oc_require_once('MDB2/Driver/Function/Common.php'); /** * MDB2 MySQL driver for the function modules diff --git a/inc/MDB2/Driver/Function/pgsql.php b/inc/MDB2/Driver/Function/pgsql.php index 985dc4ed2f..cb47ea57d9 100755 --- a/inc/MDB2/Driver/Function/pgsql.php +++ b/inc/MDB2/Driver/Function/pgsql.php @@ -44,7 +44,7 @@ // // $Id: pgsql.php,v 1.11 2008/11/09 19:46:50 quipo Exp $ -require_once 'MDB2/Driver/Function/Common.php'; +oc_require_once('MDB2/Driver/Function/Common.php'); /** * MDB2 MySQL driver for the function modules diff --git a/inc/MDB2/Driver/Function/sqlite.php b/inc/MDB2/Driver/Function/sqlite.php index 05fac65baf..f5499599dd 100755 --- a/inc/MDB2/Driver/Function/sqlite.php +++ b/inc/MDB2/Driver/Function/sqlite.php @@ -45,7 +45,7 @@ // $Id: sqlite.php,v 1.10 2008/02/17 18:54:08 quipo Exp $ // -require_once 'MDB2/Driver/Function/Common.php'; +oc_require_once('MDB2/Driver/Function/Common.php'); /** * MDB2 SQLite driver for the function modules diff --git a/inc/MDB2/Driver/Manager/mysql.php b/inc/MDB2/Driver/Manager/mysql.php index e037a0d16a..7bd6a3623a 100755 --- a/inc/MDB2/Driver/Manager/mysql.php +++ b/inc/MDB2/Driver/Manager/mysql.php @@ -45,7 +45,7 @@ // $Id: mysql.php,v 1.113 2008/11/23 20:30:29 quipo Exp $ // -require_once 'MDB2/Driver/Manager/Common.php'; +oc_require_once('MDB2/Driver/Manager/Common.php'); /** * MDB2 MySQL driver for the management modules diff --git a/inc/MDB2/Driver/Manager/pgsql.php b/inc/MDB2/Driver/Manager/pgsql.php index 3f9c121a50..1a7e851897 100755 --- a/inc/MDB2/Driver/Manager/pgsql.php +++ b/inc/MDB2/Driver/Manager/pgsql.php @@ -44,7 +44,7 @@ // // $Id: pgsql.php,v 1.87 2008/11/29 14:09:59 afz Exp $ -require_once 'MDB2/Driver/Manager/Common.php'; +oc_require_once('MDB2/Driver/Manager/Common.php'); /** * MDB2 MySQL driver for the management modules diff --git a/inc/MDB2/Driver/Manager/sqlite.php b/inc/MDB2/Driver/Manager/sqlite.php index a0f899e788..85751d39a3 100755 --- a/inc/MDB2/Driver/Manager/sqlite.php +++ b/inc/MDB2/Driver/Manager/sqlite.php @@ -46,7 +46,7 @@ // $Id: sqlite.php,v 1.76 2008/05/31 11:48:48 quipo Exp $ // -require_once 'MDB2/Driver/Manager/Common.php'; +oc_require_once('MDB2/Driver/Manager/Common.php'); /** * MDB2 SQLite driver for the management modules diff --git a/inc/MDB2/Driver/Reverse/mysql.php b/inc/MDB2/Driver/Reverse/mysql.php index ab1f601ef0..40c62da3ba 100755 --- a/inc/MDB2/Driver/Reverse/mysql.php +++ b/inc/MDB2/Driver/Reverse/mysql.php @@ -45,7 +45,7 @@ // $Id: mysql.php,v 1.80 2008/03/26 21:15:37 quipo Exp $ // -require_once 'MDB2/Driver/Reverse/Common.php'; +oc_require_once('MDB2/Driver/Reverse/Common.php'); /** * MDB2 MySQL driver for the schema reverse engineering module diff --git a/inc/MDB2/Driver/Reverse/pgsql.php b/inc/MDB2/Driver/Reverse/pgsql.php index e8e217cecf..d010292cd8 100755 --- a/inc/MDB2/Driver/Reverse/pgsql.php +++ b/inc/MDB2/Driver/Reverse/pgsql.php @@ -45,7 +45,7 @@ // // $Id: pgsql.php,v 1.75 2008/08/22 16:36:20 quipo Exp $ -require_once 'MDB2/Driver/Reverse/Common.php'; +oc_require_once('MDB2/Driver/Reverse/Common.php'); /** * MDB2 PostGreSQL driver for the schema reverse engineering module diff --git a/inc/MDB2/Driver/Reverse/sqlite.php b/inc/MDB2/Driver/Reverse/sqlite.php index 432e5ecb91..1b85aa71f9 100755 --- a/inc/MDB2/Driver/Reverse/sqlite.php +++ b/inc/MDB2/Driver/Reverse/sqlite.php @@ -46,7 +46,7 @@ // $Id: sqlite.php,v 1.80 2008/05/03 10:30:14 quipo Exp $ // -require_once 'MDB2/Driver/Reverse/Common.php'; +oc_require_once('MDB2/Driver/Reverse/Common.php'); /** * MDB2 SQlite driver for the schema reverse engineering module diff --git a/inc/MDB2/Driver/mysql.php b/inc/MDB2/Driver/mysql.php index fbb00cc6bb..091c479d19 100755 --- a/inc/MDB2/Driver/mysql.php +++ b/inc/MDB2/Driver/mysql.php @@ -1406,7 +1406,7 @@ class MDB2_Result_mysql extends MDB2_Result_Common if ($object_class == 'stdClass') { $row = (object) $row; } else { - $row = &new $object_class($row); + $row = new $object_class($row); } } ++$this->rownum; diff --git a/inc/MDB2/LOB.php b/inc/MDB2/LOB.php index 69db267d69..2cdf67afa9 100755 --- a/inc/MDB2/LOB.php +++ b/inc/MDB2/LOB.php @@ -50,7 +50,7 @@ * @author Lukas Smith */ -require_once 'MDB2.php'; +oc_require_once('MDB2.php'); /** * MDB2_LOB: user land stream wrapper implementation for LOB support diff --git a/inc/MDB2/Schema.php b/inc/MDB2/Schema.php new file mode 100644 index 0000000000..44518b3265 --- /dev/null +++ b/inc/MDB2/Schema.php @@ -0,0 +1,2763 @@ + + * Author: Igor Feghali + * + * @category Database + * @package MDB2_Schema + * @author Lukas Smith + * @author Igor Feghali + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @version CVS: $Id: Schema.php,v 1.132 2009/02/22 21:43:22 ifeghali Exp $ + * @link http://pear.php.net/packages/MDB2_Schema + */ + +// require_once('MDB2.php'); + +define('MDB2_SCHEMA_DUMP_ALL', 0); +define('MDB2_SCHEMA_DUMP_STRUCTURE', 1); +define('MDB2_SCHEMA_DUMP_CONTENT', 2); + +/** + * If you add an error code here, make sure you also add a textual + * version of it in MDB2_Schema::errorMessage(). + */ + +define('MDB2_SCHEMA_ERROR', -1); +define('MDB2_SCHEMA_ERROR_PARSE', -2); +define('MDB2_SCHEMA_ERROR_VALIDATE', -3); +define('MDB2_SCHEMA_ERROR_UNSUPPORTED', -4); // Driver does not support this function +define('MDB2_SCHEMA_ERROR_INVALID', -5); // Invalid attribute value +define('MDB2_SCHEMA_ERROR_WRITER', -6); + +/** + * The database manager is a class that provides a set of database + * management services like installing, altering and dumping the data + * structures of databases. + * + * @category Database + * @package MDB2_Schema + * @author Lukas Smith + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/packages/MDB2_Schema + */ +class MDB2_Schema extends PEAR +{ + // {{{ properties + + var $db; + + var $warnings = array(); + + var $options = array( + 'fail_on_invalid_names' => true, + 'dtd_file' => false, + 'valid_types' => array(), + 'force_defaults' => true, + 'parser' => 'MDB2_Schema_Parser', + 'writer' => 'MDB2_Schema_Writer', + 'validate' => 'MDB2_Schema_Validate', + 'drop_missing_tables' => false + ); + + // }}} + // {{{ apiVersion() + + /** + * Return the MDB2 API version + * + * @return string the MDB2 API version number + * @access public + */ + function apiVersion() + { + return '0.4.3'; + } + + // }}} + // {{{ arrayMergeClobber() + + /** + * Clobbers two arrays together + * + * @param array $a1 array that should be clobbered + * @param array $a2 array that should be clobbered + * + * @return array|false array on success and false on error + * + * @access public + * @author kc@hireability.com + */ + function arrayMergeClobber($a1, $a2) + { + if (!is_array($a1) || !is_array($a2)) { + return false; + } + foreach ($a2 as $key => $val) { + if (is_array($val) && array_key_exists($key, $a1) && is_array($a1[$key])) { + $a1[$key] = MDB2_Schema::arrayMergeClobber($a1[$key], $val); + } else { + $a1[$key] = $val; + } + } + return $a1; + } + + // }}} + // {{{ resetWarnings() + + /** + * reset the warning array + * + * @access public + * @return void + */ + function resetWarnings() + { + $this->warnings = array(); + } + + // }}} + // {{{ getWarnings() + + /** + * Get all warnings in reverse order + * + * This means that the last warning is the first element in the array + * + * @return array with warnings + * @access public + * @see resetWarnings() + */ + function getWarnings() + { + return array_reverse($this->warnings); + } + + // }}} + // {{{ setOption() + + /** + * Sets the option for the db class + * + * @param string $option option name + * @param mixed $value value for the option + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function setOption($option, $value) + { + if (isset($this->options[$option])) { + if (is_null($value)) { + return $this->raiseError(MDB2_SCHEMA_ERROR, null, null, + 'may not set an option to value null'); + } + $this->options[$option] = $value; + return MDB2_OK; + } + return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null, + "unknown option $option"); + } + + // }}} + // {{{ getOption() + + /** + * returns the value of an option + * + * @param string $option option name + * + * @return mixed the option value or error object + * @access public + */ + function getOption($option) + { + if (isset($this->options[$option])) { + return $this->options[$option]; + } + return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, + null, null, "unknown option $option"); + } + + // }}} + // {{{ factory() + + /** + * Create a new MDB2 object for the specified database type + * type + * + * @param string|array|MDB2_Driver_Common &$db 'data source name', see the + * MDB2::parseDSN method for a description of the dsn format. + * Can also be specified as an array of the + * format returned by @see MDB2::parseDSN. + * Finally you can also pass an existing db object to be used. + * @param array $options An associative array of option names and their values. + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + * @see MDB2::parseDSN + */ + function &factory(&$db, $options = array()) + { + $obj =new MDB2_Schema(); + $result = $obj->connect($db, $options); + if (PEAR::isError($result)) { + return $result; + } + return $obj; + } + + // }}} + // {{{ connect() + + /** + * Create a new MDB2 connection object and connect to the specified + * database + * + * @param string|array|MDB2_Driver_Common &$db 'data source name', see the + * MDB2::parseDSN method for a description of the dsn format. + * Can also be specified as an array of the + * format returned by MDB2::parseDSN. + * Finally you can also pass an existing db object to be used. + * @param array $options An associative array of option names and their values. + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + * @see MDB2::parseDSN + */ + function connect(&$db, $options = array()) + { + $db_options = array(); + if (is_array($options)) { + foreach ($options as $option => $value) { + if (array_key_exists($option, $this->options)) { + $result = $this->setOption($option, $value); + if (PEAR::isError($result)) { + return $result; + } + } else { + $db_options[$option] = $value; + } + } + } + $this->disconnect(); + if (!MDB2::isConnection($db)) { + $db =& MDB2::factory($db, $db_options); + } + + if (PEAR::isError($db)) { + return $db; + } + $this->db =& $db; + $this->db->loadModule('Datatype'); + $this->db->loadModule('Manager'); + $this->db->loadModule('Reverse'); + $this->db->loadModule('Function'); + if (empty($this->options['valid_types'])) { + $this->options['valid_types'] = $this->db->datatype->getValidTypes(); + } + + return MDB2_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Log out and disconnect from the database. + * + * @access public + * @return void + */ + function disconnect() + { + if (MDB2::isConnection($this->db)) { + $this->db->disconnect(); + unset($this->db); + } + } + + // }}} + // {{{ parseDatabaseDefinition() + + /** + * Parse a database definition from a file or an array + * + * @param string|array $schema the database schema array or file name + * @param bool $skip_unreadable if non readable files should be skipped + * @param array $variables associative array that the defines the text string values + * that are meant to be used to replace the variables that are + * used in the schema description. + * @param bool $fail_on_invalid_names make function fail on invalid names + * @param array $structure database structure definition + * + * @access public + * @return array + */ + function parseDatabaseDefinition($schema, $skip_unreadable = false, $variables = array(), + $fail_on_invalid_names = true, $structure = false) + { + $database_definition = false; + if (is_string($schema)) { + // if $schema is not readable then we just skip it + // and simply copy the $current_schema file to that file name + if (is_readable($schema)) { + $database_definition = $this->parseDatabaseDefinitionFile($schema, $variables, $fail_on_invalid_names, $structure); + } + } elseif (is_array($schema)) { + $database_definition = $schema; + } + if (!$database_definition && !$skip_unreadable) { + $database_definition = $this->raiseError(MDB2_SCHEMA_ERROR, null, null, + 'invalid data type of schema or unreadable data source'); + } + return $database_definition; + } + + // }}} + // {{{ parseDatabaseDefinitionFile() + + /** + * Parse a database definition file by creating a schema format + * parser object and passing the file contents as parser input data stream. + * + * @param string $input_file the database schema file. + * @param array $variables associative array that the defines the text string values + * that are meant to be used to replace the variables that are + * used in the schema description. + * @param bool $fail_on_invalid_names make function fail on invalid names + * @param array $structure database structure definition + * + * @access public + * @return array + */ + function parseDatabaseDefinitionFile($input_file, $variables = array(), + $fail_on_invalid_names = true, $structure = false) + { + $dtd_file = $this->options['dtd_file']; + if ($dtd_file) { + include_once 'XML/DTD/XmlValidator.php'; + $dtd =new XML_DTD_XmlValidator; + if (!$dtd->isValid($dtd_file, $input_file)) { + return $this->raiseError(MDB2_SCHEMA_ERROR_PARSE, null, null, $dtd->getMessage()); + } + } + + $class_name = $this->options['parser']; + + $result = MDB2::loadClass($class_name, $this->db->getOption('debug')); + if (PEAR::isError($result)) { + return $result; + } + + $parser =new $class_name($variables, $fail_on_invalid_names, $structure, $this->options['valid_types'], $this->options['force_defaults']); + $result = $parser->setInputFile($input_file); + if (PEAR::isError($result)) { + return $result; + } + + $result = $parser->parse(); + if (PEAR::isError($result)) { + return $result; + } + if (PEAR::isError($parser->error)) { + return $parser->error; + } + + return $parser->database_definition; + } + + // }}} + // {{{ getDefinitionFromDatabase() + + /** + * Attempt to reverse engineer a schema structure from an existing MDB2 + * This method can be used if no xml schema file exists yet. + * The resulting xml schema file may need some manual adjustments. + * + * @return array|MDB2_Error array with definition or error object + * @access public + */ + function getDefinitionFromDatabase() + { + $database = $this->db->database_name; + if (empty($database)) { + return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null, + 'it was not specified a valid database name'); + } + $class_name = $this->options['validate']; + + $result = MDB2::loadClass($class_name, $this->db->getOption('debug')); + if (PEAR::isError($result)) { + return $result; + } + + $val =new $class_name($this->options['fail_on_invalid_names'], $this->options['valid_types'], $this->options['force_defaults']); + + $database_definition = array( + 'name' => $database, + 'create' => true, + 'overwrite' => false, + 'charset' => 'utf8', + 'description' => '', + 'comments' => '', + 'tables' => array(), + 'sequences' => array(), + ); + + $tables = $this->db->manager->listTables(); + if (PEAR::isError($tables)) { + return $tables; + } + + foreach ($tables as $table_name) { + $fields = $this->db->manager->listTableFields($table_name); + if (PEAR::isError($fields)) { + return $fields; + } + + $database_definition['tables'][$table_name] = array( + 'was' => '', + 'description' => '', + 'comments' => '', + 'fields' => array(), + 'indexes' => array(), + 'constraints' => array(), + 'initialization' => array() + ); + + $table_definition =& $database_definition['tables'][$table_name]; + foreach ($fields as $field_name) { + $definition = $this->db->reverse->getTableFieldDefinition($table_name, $field_name); + if (PEAR::isError($definition)) { + return $definition; + } + + if (!empty($definition[0]['autoincrement'])) { + $definition[0]['default'] = '0'; + } + + $table_definition['fields'][$field_name] = $definition[0]; + + $field_choices = count($definition); + if ($field_choices > 1) { + $warning = "There are $field_choices type choices in the table $table_name field $field_name (#1 is the default): "; + + $field_choice_cnt = 1; + + $table_definition['fields'][$field_name]['choices'] = array(); + foreach ($definition as $field_choice) { + $table_definition['fields'][$field_name]['choices'][] = $field_choice; + + $warning .= 'choice #'.($field_choice_cnt).': '.serialize($field_choice); + $field_choice_cnt++; + } + $this->warnings[] = $warning; + } + + /** + * The first parameter is used to verify if there are duplicated + * fields which we can guarantee that won't happen when reverse engineering + */ + $result = $val->validateField(array(), $table_definition['fields'][$field_name], $field_name); + if (PEAR::isError($result)) { + return $result; + } + } + + $keys = array(); + + $indexes = $this->db->manager->listTableIndexes($table_name); + if (PEAR::isError($indexes)) { + return $indexes; + } + + if (is_array($indexes)) { + foreach ($indexes as $index_name) { + $this->db->expectError(MDB2_ERROR_NOT_FOUND); + $definition = $this->db->reverse->getTableIndexDefinition($table_name, $index_name); + $this->db->popExpect(); + if (PEAR::isError($definition)) { + if (PEAR::isError($definition, MDB2_ERROR_NOT_FOUND)) { + continue; + } + return $definition; + } + + $keys[$index_name] = $definition; + } + } + + $constraints = $this->db->manager->listTableConstraints($table_name); + if (PEAR::isError($constraints)) { + return $constraints; + } + + if (is_array($constraints)) { + foreach ($constraints as $constraint_name) { + $this->db->expectError(MDB2_ERROR_NOT_FOUND); + $definition = $this->db->reverse->getTableConstraintDefinition($table_name, $constraint_name); + $this->db->popExpect(); + if (PEAR::isError($definition)) { + if (PEAR::isError($definition, MDB2_ERROR_NOT_FOUND)) { + continue; + } + return $definition; + } + + $keys[$constraint_name] = $definition; + } + } + + foreach ($keys as $key_name => $definition) { + if (array_key_exists('foreign', $definition) + && $definition['foreign'] + ) { + /** + * The first parameter is used to verify if there are duplicated + * foreign keys which we can guarantee that won't happen when reverse engineering + */ + $result = $val->validateConstraint(array(), $definition, $key_name); + if (PEAR::isError($result)) { + return $result; + } + + foreach ($definition['fields'] as $field_name => $field) { + /** + * The first parameter is used to verify if there are duplicated + * referencing fields which we can guarantee that won't happen when reverse engineering + */ + $result = $val->validateConstraintField(array(), $field_name); + if (PEAR::isError($result)) { + return $result; + } + + $definition['fields'][$field_name] = ''; + } + + foreach ($definition['references']['fields'] as $field_name => $field) { + /** + * The first parameter is used to verify if there are duplicated + * referenced fields which we can guarantee that won't happen when reverse engineering + */ + $result = $val->validateConstraintReferencedField(array(), $field_name); + if (PEAR::isError($result)) { + return $result; + } + + $definition['references']['fields'][$field_name] = ''; + } + + $table_definition['constraints'][$key_name] = $definition; + } else { + /** + * The first parameter is used to verify if there are duplicated + * indices which we can guarantee that won't happen when reverse engineering + */ + $result = $val->validateIndex(array(), $definition, $key_name); + if (PEAR::isError($result)) { + return $result; + } + + foreach ($definition['fields'] as $field_name => $field) { + /** + * The first parameter is used to verify if there are duplicated + * index fields which we can guarantee that won't happen when reverse engineering + */ + $result = $val->validateIndexField(array(), $field, $field_name); + if (PEAR::isError($result)) { + return $result; + } + + $definition['fields'][$field_name] = $field; + } + + $table_definition['indexes'][$key_name] = $definition; + } + } + + /** + * The first parameter is used to verify if there are duplicated + * tables which we can guarantee that won't happen when reverse engineering + */ + $result = $val->validateTable(array(), $table_definition, $table_name); + if (PEAR::isError($result)) { + return $result; + } + + } + + $sequences = $this->db->manager->listSequences(); + if (PEAR::isError($sequences)) { + return $sequences; + } + + if (is_array($sequences)) { + foreach ($sequences as $sequence_name) { + $definition = $this->db->reverse->getSequenceDefinition($sequence_name); + if (PEAR::isError($definition)) { + return $definition; + } + if (isset($database_definition['tables'][$sequence_name]) + && isset($database_definition['tables'][$sequence_name]['indexes']) + ) { + foreach ($database_definition['tables'][$sequence_name]['indexes'] as $index) { + if (isset($index['primary']) && $index['primary'] + && count($index['fields'] == 1) + ) { + $definition['on'] = array( + 'table' => $sequence_name, + 'field' => key($index['fields']), + ); + break; + } + } + } + + /** + * The first parameter is used to verify if there are duplicated + * sequences which we can guarantee that won't happen when reverse engineering + */ + $result = $val->validateSequence(array(), $definition, $sequence_name); + if (PEAR::isError($result)) { + return $result; + } + + $database_definition['sequences'][$sequence_name] = $definition; + } + } + + $result = $val->validateDatabase($database_definition); + if (PEAR::isError($result)) { + return $result; + } + + return $database_definition; + } + + // }}} + // {{{ createTableIndexes() + + /** + * A method to create indexes for an existing table + * + * @param string $table_name Name of the table + * @param array $indexes An array of indexes to be created + * @param boolean $overwrite If the table/index should be overwritten if it already exists + * + * @return mixed MDB2_Error if there is an error creating an index, MDB2_OK otherwise + * @access public + */ + function createTableIndexes($table_name, $indexes, $overwrite = false) + { + if (!$this->db->supports('indexes')) { + $this->db->debug('Indexes are not supported', __FUNCTION__); + return MDB2_OK; + } + + $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE); + foreach ($indexes as $index_name => $index) { + + // Does the index already exist, and if so, should it be overwritten? + $create_index = true; + $this->db->expectError($errorcodes); + if (!empty($index['primary']) || !empty($index['unique'])) { + $current_indexes = $this->db->manager->listTableConstraints($table_name); + } else { + $current_indexes = $this->db->manager->listTableIndexes($table_name); + } + + $this->db->popExpect(); + if (PEAR::isError($current_indexes)) { + if (!MDB2::isError($current_indexes, $errorcodes)) { + return $current_indexes; + } + } elseif (is_array($current_indexes) && in_array($index_name, $current_indexes)) { + if (!$overwrite) { + $this->db->debug('Index already exists: '.$index_name, __FUNCTION__); + $create_index = false; + } else { + $this->db->debug('Preparing to overwrite index: '.$index_name, __FUNCTION__); + + $this->db->expectError(MDB2_ERROR_NOT_FOUND); + if (!empty($index['primary']) || !empty($index['unique'])) { + $result = $this->db->manager->dropConstraint($table_name, $index_name); + } else { + $result = $this->db->manager->dropIndex($table_name, $index_name); + } + $this->db->popExpect(); + if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_NOT_FOUND)) { + return $result; + } + } + } + + // Check if primary is being used and if it's supported + if (!empty($index['primary']) && !$this->db->supports('primary_key')) { + + // Primary not supported so we fallback to UNIQUE and making the field NOT NULL + $index['unique'] = true; + + $changes = array(); + + foreach ($index['fields'] as $field => $empty) { + $field_info = $this->db->reverse->getTableFieldDefinition($table_name, $field); + if (PEAR::isError($field_info)) { + return $field_info; + } + if (!$field_info[0]['notnull']) { + $changes['change'][$field] = $field_info[0]; + + $changes['change'][$field]['notnull'] = true; + } + } + if (!empty($changes)) { + $this->db->manager->alterTable($table_name, $changes, false); + } + } + + // Should the index be created? + if ($create_index) { + if (!empty($index['primary']) || !empty($index['unique'])) { + $result = $this->db->manager->createConstraint($table_name, $index_name, $index); + } else { + $result = $this->db->manager->createIndex($table_name, $index_name, $index); + } + if (PEAR::isError($result)) { + return $result; + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ createTableConstraints() + + /** + * A method to create foreign keys for an existing table + * + * @param string $table_name Name of the table + * @param array $constraints An array of foreign keys to be created + * @param boolean $overwrite If the foreign key should be overwritten if it already exists + * + * @return mixed MDB2_Error if there is an error creating a foreign key, MDB2_OK otherwise + * @access public + */ + function createTableConstraints($table_name, $constraints, $overwrite = false) + { + if (!$this->db->supports('indexes')) { + $this->db->debug('Indexes are not supported', __FUNCTION__); + return MDB2_OK; + } + + $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE); + foreach ($constraints as $constraint_name => $constraint) { + + // Does the foreign key already exist, and if so, should it be overwritten? + $create_constraint = true; + $this->db->expectError($errorcodes); + $current_constraints = $this->db->manager->listTableConstraints($table_name); + $this->db->popExpect(); + if (PEAR::isError($current_constraints)) { + if (!MDB2::isError($current_constraints, $errorcodes)) { + return $current_constraints; + } + } elseif (is_array($current_constraints) && in_array($constraint_name, $current_constraints)) { + if (!$overwrite) { + $this->db->debug('Foreign key already exists: '.$constraint_name, __FUNCTION__); + $create_constraint = false; + } else { + $this->db->debug('Preparing to overwrite foreign key: '.$constraint_name, __FUNCTION__); + $result = $this->db->manager->dropConstraint($table_name, $constraint_name); + if (PEAR::isError($result)) { + return $result; + } + } + } + + // Should the foreign key be created? + if ($create_constraint) { + $result = $this->db->manager->createConstraint($table_name, $constraint_name, $constraint); + if (PEAR::isError($result)) { + return $result; + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ createTable() + + /** + * Create a table and inititialize the table if data is available + * + * @param string $table_name name of the table to be created + * @param array $table multi dimensional array that contains the + * structure and optional data of the table + * @param bool $overwrite if the table/index should be overwritten if it already exists + * @param array $options an array of options to be passed to the database specific driver + * version of MDB2_Driver_Manager_Common::createTable(). + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function createTable($table_name, $table, $overwrite = false, $options = array()) + { + $create = true; + + $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE); + + $this->db->expectError($errorcodes); + + $tables = $this->db->manager->listTables(); + + $this->db->popExpect(); + if (PEAR::isError($tables)) { + if (!MDB2::isError($tables, $errorcodes)) { + return $tables; + } + } elseif (is_array($tables) && in_array($table_name, $tables)) { + if (!$overwrite) { + $create = false; + $this->db->debug('Table already exists: '.$table_name, __FUNCTION__); + } else { + $result = $this->db->manager->dropTable($table_name); + if (PEAR::isError($result)) { + return $result; + } + $this->db->debug('Overwritting table: '.$table_name, __FUNCTION__); + } + } + + if ($create) { + $result = $this->db->manager->createTable($table_name, $table['fields'], $options); + if (PEAR::isError($result)) { + return $result; + } + } + + if (!empty($table['initialization']) && is_array($table['initialization'])) { + $result = $this->initializeTable($table_name, $table); + if (PEAR::isError($result)) { + return $result; + } + } + + if (!empty($table['indexes']) && is_array($table['indexes'])) { + $result = $this->createTableIndexes($table_name, $table['indexes'], $overwrite); + if (PEAR::isError($result)) { + return $result; + } + } + + if (!empty($table['constraints']) && is_array($table['constraints'])) { + $result = $this->createTableConstraints($table_name, $table['constraints'], $overwrite); + if (PEAR::isError($result)) { + return $result; + } + } + + return MDB2_OK; + } + + // }}} + // {{{ initializeTable() + + /** + * Inititialize the table with data + * + * @param string $table_name name of the table + * @param array $table multi dimensional array that contains the + * structure and optional data of the table + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function initializeTable($table_name, $table) + { + $query_insertselect = 'INSERT INTO %s (%s) (SELECT %s FROM %s %s)'; + + $query_insert = 'INSERT INTO %s (%s) VALUES (%s)'; + $query_update = 'UPDATE %s SET %s %s'; + $query_delete = 'DELETE FROM %s %s'; + + $table_name = $this->db->quoteIdentifier($table_name, true); + + $result = MDB2_OK; + + $support_transactions = $this->db->supports('transactions'); + + foreach ($table['initialization'] as $instruction) { + $query = ''; + switch ($instruction['type']) { + case 'insert': + if (!isset($instruction['data']['select'])) { + $data = $this->getInstructionFields($instruction['data'], $table['fields']); + if (!empty($data)) { + $fields = implode(', ', array_keys($data)); + $values = implode(', ', array_values($data)); + + $query = sprintf($query_insert, $table_name, $fields, $values); + } + } else { + $data = $this->getInstructionFields($instruction['data']['select'], $table['fields']); + $where = $this->getInstructionWhere($instruction['data']['select'], $table['fields']); + + $select_table_name = $this->db->quoteIdentifier($instruction['data']['select']['table'], true); + if (!empty($data)) { + $fields = implode(', ', array_keys($data)); + $values = implode(', ', array_values($data)); + + $query = sprintf($query_insertselect, $table_name, $fields, $values, $select_table_name, $where); + } + } + break; + case 'update': + $data = $this->getInstructionFields($instruction['data'], $table['fields']); + $where = $this->getInstructionWhere($instruction['data'], $table['fields']); + if (!empty($data)) { + array_walk($data, array($this, 'buildFieldValue')); + $fields_values = implode(', ', $data); + + $query = sprintf($query_update, $table_name, $fields_values, $where); + } + break; + case 'delete': + $where = $this->getInstructionWhere($instruction['data'], $table['fields']); + $query = sprintf($query_delete, $table_name, $where); + break; + } + if ($query) { + if ($support_transactions && PEAR::isError($res = $this->db->beginNestedTransaction())) { + return $res; + } + + $result = $this->db->exec($query); + if (PEAR::isError($result)) { + return $result; + } + + if ($support_transactions && PEAR::isError($res = $this->db->completeNestedTransaction())) { + return $res; + } + } + } + return $result; + } + + // }}} + // {{{ buildFieldValue() + + /** + * Appends the contents of second argument + '=' to the beginning of first + * argument. + * + * Used with array_walk() in initializeTable() for UPDATEs. + * + * @param string &$element value of array's element + * @param string $key key of array's element + * + * @return void + * + * @access public + * @see MDB2_Schema::initializeTable() + */ + function buildFieldValue(&$element, $key) + { + $element = $key."=$element"; + } + + // }}} + // {{{ getExpression() + + /** + * Generates a string that represents a value that would be associated + * with a column in a DML instruction. + * + * @param array $element multi dimensional array that contains the + * structure of the current DML instruction. + * @param array $fields_definition multi dimensional array that contains the + * definition for current table's fields + * @param string $type type of given field + * + * @return string + * + * @access public + * @see MDB2_Schema::getInstructionFields(), MDB2_Schema::getInstructionWhere() + */ + function getExpression($element, $fields_definition = array(), $type = null) + { + $str = ''; + switch ($element['type']) { + case 'null': + $str .= 'NULL'; + break; + case 'value': + $str .= $this->db->quote($element['data'], $type); + break; + case 'column': + $str .= $this->db->quoteIdentifier($element['data'], true); + break; + case 'function': + $arguments = array(); + if (!empty($element['data']['arguments']) + && is_array($element['data']['arguments']) + ) { + foreach ($element['data']['arguments'] as $v) { + $arguments[] = $this->getExpression($v, $fields_definition); + } + } + if (method_exists($this->db->function, $element['data']['name'])) { + $user_func = array(&$this->db->function, $element['data']['name']); + + $str .= call_user_func_array($user_func, $arguments); + } else { + $str .= $element['data']['name'].'('; + $str .= implode(', ', $arguments); + $str .= ')'; + } + break; + case 'expression': + $type0 = $type1 = null; + if ($element['data']['operants'][0]['type'] == 'column' + && array_key_exists($element['data']['operants'][0]['data'], $fields_definition) + ) { + $type0 = $fields_definition[$element['data']['operants'][0]['data']]['type']; + } + + if ($element['data']['operants'][1]['type'] == 'column' + && array_key_exists($element['data']['operants'][1]['data'], $fields_definition) + ) { + $type1 = $fields_definition[$element['data']['operants'][1]['data']]['type']; + } + + $str .= '('; + $str .= $this->getExpression($element['data']['operants'][0], $fields_definition, $type1); + $str .= $this->getOperator($element['data']['operator']); + $str .= $this->getExpression($element['data']['operants'][1], $fields_definition, $type0); + $str .= ')'; + break; + } + + return $str; + } + + // }}} + // {{{ getOperator() + + /** + * Returns the matching SQL operator + * + * @param string $op parsed descriptive operator + * + * @return string matching SQL operator + * + * @access public + * @static + * @see MDB2_Schema::getExpression() + */ + function getOperator($op) + { + switch ($op) { + case 'PLUS': + return ' + '; + case 'MINUS': + return ' - '; + case 'TIMES': + return ' * '; + case 'DIVIDED': + return ' / '; + case 'EQUAL': + return ' = '; + case 'NOT EQUAL': + return ' != '; + case 'LESS THAN': + return ' < '; + case 'GREATER THAN': + return ' > '; + case 'LESS THAN OR EQUAL': + return ' <= '; + case 'GREATER THAN OR EQUAL': + return ' >= '; + default: + return ' '.$op.' '; + } + } + + // }}} + // {{{ getInstructionFields() + + /** + * Walks the parsed DML instruction array, field by field, + * storing them and their processed values inside a new array. + * + * @param array $instruction multi dimensional array that contains the + * structure of the current DML instruction. + * @param array $fields_definition multi dimensional array that contains the + * definition for current table's fields + * + * @return array array of strings in the form 'field_name' => 'value' + * + * @access public + * @static + * @see MDB2_Schema::initializeTable() + */ + function getInstructionFields($instruction, $fields_definition = array()) + { + $fields = array(); + if (!empty($instruction['field']) && is_array($instruction['field'])) { + foreach ($instruction['field'] as $field) { + $field_name = $this->db->quoteIdentifier($field['name'], true); + + $fields[$field_name] = $this->getExpression($field['group'], $fields_definition); + } + } + return $fields; + } + + // }}} + // {{{ getInstructionWhere() + + /** + * Translates the parsed WHERE expression of a DML instruction + * (array structure) to a SQL WHERE clause (string). + * + * @param array $instruction multi dimensional array that contains the + * structure of the current DML instruction. + * @param array $fields_definition multi dimensional array that contains the + * definition for current table's fields. + * + * @return string SQL WHERE clause + * + * @access public + * @static + * @see MDB2_Schema::initializeTable() + */ + function getInstructionWhere($instruction, $fields_definition = array()) + { + $where = ''; + if (!empty($instruction['where'])) { + $where = 'WHERE '.$this->getExpression($instruction['where'], $fields_definition); + } + return $where; + } + + // }}} + // {{{ createSequence() + + /** + * Create a sequence + * + * @param string $sequence_name name of the sequence to be created + * @param array $sequence multi dimensional array that contains the + * structure and optional data of the table + * @param bool $overwrite if the sequence should be overwritten if it already exists + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function createSequence($sequence_name, $sequence, $overwrite = false) + { + if (!$this->db->supports('sequences')) { + $this->db->debug('Sequences are not supported', __FUNCTION__); + return MDB2_OK; + } + + $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE); + $this->db->expectError($errorcodes); + $sequences = $this->db->manager->listSequences(); + $this->db->popExpect(); + if (PEAR::isError($sequences)) { + if (!MDB2::isError($sequences, $errorcodes)) { + return $sequences; + } + } elseif (is_array($sequence) && in_array($sequence_name, $sequences)) { + if (!$overwrite) { + $this->db->debug('Sequence already exists: '.$sequence_name, __FUNCTION__); + return MDB2_OK; + } + + $result = $this->db->manager->dropSequence($sequence_name); + if (PEAR::isError($result)) { + return $result; + } + $this->db->debug('Overwritting sequence: '.$sequence_name, __FUNCTION__); + } + + $start = 1; + $field = ''; + if (!empty($sequence['on'])) { + $table = $sequence['on']['table']; + $field = $sequence['on']['field']; + + $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NOT_CAPABLE); + $this->db->expectError($errorcodes); + $tables = $this->db->manager->listTables(); + $this->db->popExpect(); + if (PEAR::isError($tables) && !MDB2::isError($tables, $errorcodes)) { + return $tables; + } + + if (!PEAR::isError($tables) && is_array($tables) && in_array($table, $tables)) { + if ($this->db->supports('summary_functions')) { + $query = "SELECT MAX($field) FROM ".$this->db->quoteIdentifier($table, true); + } else { + $query = "SELECT $field FROM ".$this->db->quoteIdentifier($table, true)." ORDER BY $field DESC"; + } + $start = $this->db->queryOne($query, 'integer'); + if (PEAR::isError($start)) { + return $start; + } + ++$start; + } else { + $this->warnings[] = 'Could not sync sequence: '.$sequence_name; + } + } elseif (!empty($sequence['start']) && is_numeric($sequence['start'])) { + $start = $sequence['start']; + $table = ''; + } + + $result = $this->db->manager->createSequence($sequence_name, $start); + if (PEAR::isError($result)) { + return $result; + } + + return MDB2_OK; + } + + // }}} + // {{{ createDatabase() + + /** + * Create a database space within which may be created database objects + * like tables, indexes and sequences. The implementation of this function + * is highly DBMS specific and may require special permissions to run + * successfully. Consult the documentation or the DBMS drivers that you + * use to be aware of eventual configuration requirements. + * + * @param array $database_definition multi dimensional array that contains the current definition + * @param array $options an array of options to be passed to the + * database specific driver version of + * MDB2_Driver_Manager_Common::createTable(). + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function createDatabase($database_definition, $options = array()) + { + if (!isset($database_definition['name']) || !$database_definition['name']) { + return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null, + 'no valid database name specified'); + } + + $create = (isset($database_definition['create']) && $database_definition['create']); + $overwrite = (isset($database_definition['overwrite']) && $database_definition['overwrite']); + + /** + * + * We need to clean up database name before any query to prevent + * database driver from using a inexistent database + * + */ + $previous_database_name = $this->db->setDatabase(''); + + // Lower / Upper case the db name if the portability deems so. + if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $func = $this->db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'; + + $db_name = $func($database_definition['name']); + } else { + $db_name = $database_definition['name']; + } + + if ($create) { + + $dbExists = $this->db->databaseExists($db_name); + if (PEAR::isError($dbExists)) { + return $dbExists; + } + + if ($dbExists && $overwrite) { + $this->db->expectError(MDB2_ERROR_CANNOT_DROP); + $result = $this->db->manager->dropDatabase($db_name); + $this->db->popExpect(); + if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_CANNOT_DROP)) { + return $result; + } + $dbExists = false; + $this->db->debug('Overwritting database: ' . $db_name, __FUNCTION__); + } + + $dbOptions = array(); + if (array_key_exists('charset', $database_definition) + && !empty($database_definition['charset'])) { + $dbOptions['charset'] = $database_definition['charset']; + } + + if ($dbExists) { + $this->db->debug('Database already exists: ' . $db_name, __FUNCTION__); + if (!empty($dbOptions)) { + $errorcodes = array(MDB2_ERROR_UNSUPPORTED, MDB2_ERROR_NO_PERMISSION); + $this->db->expectError($errorcodes); + $result = $this->db->manager->alterDatabase($db_name, $dbOptions); + $this->db->popExpect(); + if (PEAR::isError($result) && !MDB2::isError($result, $errorcodes)) { + return $result; + } + } + $create = false; + } else { + $this->db->expectError(MDB2_ERROR_UNSUPPORTED); + $result = $this->db->manager->createDatabase($db_name, $dbOptions); + $this->db->popExpect(); + if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_UNSUPPORTED)) { + return $result; + } + $this->db->debug('Creating database: ' . $db_name, __FUNCTION__); + } + } + + $this->db->setDatabase($db_name); + if (($support_transactions = $this->db->supports('transactions')) + && PEAR::isError($result = $this->db->beginNestedTransaction()) + ) { + return $result; + } + + $created_objects = 0; + if (isset($database_definition['tables']) + && is_array($database_definition['tables']) + ) { + foreach ($database_definition['tables'] as $table_name => $table) { + $result = $this->createTable($table_name, $table, $overwrite, $options); + if (PEAR::isError($result)) { + break; + } + $created_objects++; + } + } + if (!PEAR::isError($result) + && isset($database_definition['sequences']) + && is_array($database_definition['sequences']) + ) { + foreach ($database_definition['sequences'] as $sequence_name => $sequence) { + $result = $this->createSequence($sequence_name, $sequence, false, $overwrite); + + if (PEAR::isError($result)) { + break; + } + $created_objects++; + } + } + + if ($support_transactions) { + $res = $this->db->completeNestedTransaction(); + if (PEAR::isError($res)) { + $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null, + 'Could not end transaction ('. + $res->getMessage().' ('.$res->getUserinfo().'))'); + } + } elseif (PEAR::isError($result) && $created_objects) { + $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null, + 'the database was only partially created ('. + $result->getMessage().' ('.$result->getUserinfo().'))'); + } + + $this->db->setDatabase($previous_database_name); + + if (PEAR::isError($result) && $create + && PEAR::isError($result2 = $this->db->manager->dropDatabase($db_name)) + ) { + if (!MDB2::isError($result2, MDB2_ERROR_UNSUPPORTED)) { + return $this->raiseError(MDB2_SCHEMA_ERROR, null, null, + 'Could not drop the created database after unsuccessful creation attempt ('. + $result2->getMessage().' ('.$result2->getUserinfo().'))'); + } + } + + return $result; + } + + // }}} + // {{{ compareDefinitions() + + /** + * Compare a previous definition with the currently parsed definition + * + * @param array $current_definition multi dimensional array that contains the current definition + * @param array $previous_definition multi dimensional array that contains the previous definition + * + * @return array|MDB2_Error array of changes on success, or a error object + * @access public + */ + function compareDefinitions($current_definition, $previous_definition) + { + $changes = array(); + + if (!empty($current_definition['tables']) && is_array($current_definition['tables'])) { + $changes['tables'] = $defined_tables = array(); + foreach ($current_definition['tables'] as $table_name => $table) { + $previous_tables = array(); + if (!empty($previous_definition) && is_array($previous_definition)) { + $previous_tables = $previous_definition['tables']; + } + $change = $this->compareTableDefinitions($table_name, $table, $previous_tables, $defined_tables); + if (PEAR::isError($change)) { + return $change; + } + if (!empty($change)) { + $changes['tables'] = MDB2_Schema::arrayMergeClobber($changes['tables'], $change); + } + } + + if (!empty($previous_definition['tables']) + && is_array($previous_definition['tables'])) { + foreach ($previous_definition['tables'] as $table_name => $table) { + if (empty($defined_tables[$table_name])) { + $changes['tables']['remove'][$table_name] = true; + } + } + } + } + if (!empty($current_definition['sequences']) && is_array($current_definition['sequences'])) { + $changes['sequences'] = $defined_sequences = array(); + foreach ($current_definition['sequences'] as $sequence_name => $sequence) { + $previous_sequences = array(); + if (!empty($previous_definition) && is_array($previous_definition)) { + $previous_sequences = $previous_definition['sequences']; + } + + $change = $this->compareSequenceDefinitions($sequence_name, + $sequence, + $previous_sequences, + $defined_sequences); + if (PEAR::isError($change)) { + return $change; + } + if (!empty($change)) { + $changes['sequences'] = MDB2_Schema::arrayMergeClobber($changes['sequences'], $change); + } + } + if (!empty($previous_definition['sequences']) && is_array($previous_definition['sequences'])) { + foreach ($previous_definition['sequences'] as $sequence_name => $sequence) { + if (empty($defined_sequences[$sequence_name])) { + $changes['sequences']['remove'][$sequence_name] = true; + } + } + } + } + return $changes; + } + + // }}} + // {{{ compareTableFieldsDefinitions() + + /** + * Compare a previous definition with the currently parsed definition + * + * @param string $table_name name of the table + * @param array $current_definition multi dimensional array that contains the current definition + * @param array $previous_definition multi dimensional array that contains the previous definition + * + * @return array|MDB2_Error array of changes on success, or a error object + * @access public + */ + function compareTableFieldsDefinitions($table_name, $current_definition, + $previous_definition) + { + $changes = $defined_fields = array(); + + if (is_array($current_definition)) { + foreach ($current_definition as $field_name => $field) { + $was_field_name = $field['was']; + if (!empty($previous_definition[$field_name]) + && ( + (isset($previous_definition[$field_name]['was']) + && $previous_definition[$field_name]['was'] == $was_field_name) + || !isset($previous_definition[$was_field_name]) + )) { + $was_field_name = $field_name; + } + + if (!empty($previous_definition[$was_field_name])) { + if ($was_field_name != $field_name) { + $changes['rename'][$was_field_name] = array('name' => $field_name, 'definition' => $field); + } + + if (!empty($defined_fields[$was_field_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null, + 'the field "'.$was_field_name. + '" was specified for more than one field of table'); + } + + $defined_fields[$was_field_name] = true; + + $change = $this->db->compareDefinition($field, $previous_definition[$was_field_name]); + if (PEAR::isError($change)) { + return $change; + } + + if (!empty($change)) { + if (array_key_exists('default', $change) + && $change['default'] + && !array_key_exists('default', $field)) { + $field['default'] = null; + } + + $change['definition'] = $field; + + $changes['change'][$field_name] = $change; + } + } else { + if ($field_name != $was_field_name) { + return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null, + 'it was specified a previous field name ("'. + $was_field_name.'") for field "'.$field_name.'" of table "'. + $table_name.'" that does not exist'); + } + + $changes['add'][$field_name] = $field; + } + } + } + + if (isset($previous_definition) && is_array($previous_definition)) { + foreach ($previous_definition as $field_previous_name => $field_previous) { + if (empty($defined_fields[$field_previous_name])) { + $changes['remove'][$field_previous_name] = true; + } + } + } + + return $changes; + } + + // }}} + // {{{ compareTableIndexesDefinitions() + + /** + * Compare a previous definition with the currently parsed definition + * + * @param string $table_name name of the table + * @param array $current_definition multi dimensional array that contains the current definition + * @param array $previous_definition multi dimensional array that contains the previous definition + * + * @return array|MDB2_Error array of changes on success, or a error object + * @access public + */ + function compareTableIndexesDefinitions($table_name, $current_definition, + $previous_definition) + { + $changes = $defined_indexes = array(); + + if (is_array($current_definition)) { + foreach ($current_definition as $index_name => $index) { + $was_index_name = $index['was']; + if (!empty($previous_definition[$index_name]) + && isset($previous_definition[$index_name]['was']) + && $previous_definition[$index_name]['was'] == $was_index_name + ) { + $was_index_name = $index_name; + } + if (!empty($previous_definition[$was_index_name])) { + $change = array(); + if ($was_index_name != $index_name) { + $change['name'] = $was_index_name; + } + + if (!empty($defined_indexes[$was_index_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null, + 'the index "'.$was_index_name.'" was specified for'. + ' more than one index of table "'.$table_name.'"'); + } + $defined_indexes[$was_index_name] = true; + + $previous_unique = array_key_exists('unique', $previous_definition[$was_index_name]) + ? $previous_definition[$was_index_name]['unique'] : false; + + $unique = array_key_exists('unique', $index) ? $index['unique'] : false; + if ($previous_unique != $unique) { + $change['unique'] = $unique; + } + + $previous_primary = array_key_exists('primary', $previous_definition[$was_index_name]) + ? $previous_definition[$was_index_name]['primary'] : false; + + $primary = array_key_exists('primary', $index) ? $index['primary'] : false; + if ($previous_primary != $primary) { + $change['primary'] = $primary; + } + + $defined_fields = array(); + $previous_fields = $previous_definition[$was_index_name]['fields']; + if (!empty($index['fields']) && is_array($index['fields'])) { + foreach ($index['fields'] as $field_name => $field) { + if (!empty($previous_fields[$field_name])) { + $defined_fields[$field_name] = true; + + $previous_sorting = array_key_exists('sorting', $previous_fields[$field_name]) + ? $previous_fields[$field_name]['sorting'] : ''; + + $sorting = array_key_exists('sorting', $field) ? $field['sorting'] : ''; + if ($previous_sorting != $sorting) { + $change['change'] = true; + } + } else { + $change['change'] = true; + } + } + } + if (isset($previous_fields) && is_array($previous_fields)) { + foreach ($previous_fields as $field_name => $field) { + if (empty($defined_fields[$field_name])) { + $change['change'] = true; + } + } + } + if (!empty($change)) { + $changes['change'][$index_name] = $current_definition[$index_name]; + } + } else { + if ($index_name != $was_index_name) { + return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null, + 'it was specified a previous index name ("'.$was_index_name. + ') for index "'.$index_name.'" of table "'.$table_name.'" that does not exist'); + } + $changes['add'][$index_name] = $current_definition[$index_name]; + } + } + } + foreach ($previous_definition as $index_previous_name => $index_previous) { + if (empty($defined_indexes[$index_previous_name])) { + $changes['remove'][$index_previous_name] = $index_previous; + } + } + return $changes; + } + + // }}} + // {{{ compareTableDefinitions() + + /** + * Compare a previous definition with the currently parsed definition + * + * @param string $table_name name of the table + * @param array $current_definition multi dimensional array that contains the current definition + * @param array $previous_definition multi dimensional array that contains the previous definition + * @param array &$defined_tables table names in the schema + * + * @return array|MDB2_Error array of changes on success, or a error object + * @access public + */ + function compareTableDefinitions($table_name, $current_definition, + $previous_definition, &$defined_tables) + { + $changes = array(); + + if (is_array($current_definition)) { + $was_table_name = $table_name; + if (!empty($current_definition['was'])) { + $was_table_name = $current_definition['was']; + } + if (!empty($previous_definition[$was_table_name])) { + $changes['change'][$was_table_name] = array(); + if ($was_table_name != $table_name) { + $changes['change'][$was_table_name] = array('name' => $table_name); + } + if (!empty($defined_tables[$was_table_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null, + 'the table "'.$was_table_name. + '" was specified for more than one table of the database'); + } + $defined_tables[$was_table_name] = true; + if (!empty($current_definition['fields']) && is_array($current_definition['fields'])) { + $previous_fields = array(); + if (isset($previous_definition[$was_table_name]['fields']) + && is_array($previous_definition[$was_table_name]['fields'])) { + $previous_fields = $previous_definition[$was_table_name]['fields']; + } + + $change = $this->compareTableFieldsDefinitions($table_name, + $current_definition['fields'], + $previous_fields); + + if (PEAR::isError($change)) { + return $change; + } + if (!empty($change)) { + $changes['change'][$was_table_name] = + MDB2_Schema::arrayMergeClobber($changes['change'][$was_table_name], $change); + } + } + if (!empty($current_definition['indexes']) && is_array($current_definition['indexes'])) { + $previous_indexes = array(); + if (isset($previous_definition[$was_table_name]['indexes']) + && is_array($previous_definition[$was_table_name]['indexes'])) { + $previous_indexes = $previous_definition[$was_table_name]['indexes']; + } + $change = $this->compareTableIndexesDefinitions($table_name, + $current_definition['indexes'], + $previous_indexes); + + if (PEAR::isError($change)) { + return $change; + } + if (!empty($change)) { + $changes['change'][$was_table_name]['indexes'] = $change; + } + } + if (empty($changes['change'][$was_table_name])) { + unset($changes['change'][$was_table_name]); + } + if (empty($changes['change'])) { + unset($changes['change']); + } + } else { + if ($table_name != $was_table_name) { + return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null, + 'it was specified a previous table name ("'.$was_table_name. + '") for table "'.$table_name.'" that does not exist'); + } + $changes['add'][$table_name] = true; + } + } + + return $changes; + } + + // }}} + // {{{ compareSequenceDefinitions() + + /** + * Compare a previous definition with the currently parsed definition + * + * @param string $sequence_name name of the sequence + * @param array $current_definition multi dimensional array that contains the current definition + * @param array $previous_definition multi dimensional array that contains the previous definition + * @param array &$defined_sequences names in the schema + * + * @return array|MDB2_Error array of changes on success, or a error object + * @access public + */ + function compareSequenceDefinitions($sequence_name, $current_definition, + $previous_definition, &$defined_sequences) + { + $changes = array(); + + if (is_array($current_definition)) { + $was_sequence_name = $sequence_name; + if (!empty($previous_definition[$sequence_name]) + && isset($previous_definition[$sequence_name]['was']) + && $previous_definition[$sequence_name]['was'] == $was_sequence_name + ) { + $was_sequence_name = $sequence_name; + } elseif (!empty($current_definition['was'])) { + $was_sequence_name = $current_definition['was']; + } + if (!empty($previous_definition[$was_sequence_name])) { + if ($was_sequence_name != $sequence_name) { + $changes['change'][$was_sequence_name]['name'] = $sequence_name; + } + + if (!empty($defined_sequences[$was_sequence_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null, + 'the sequence "'.$was_sequence_name.'" was specified as base'. + ' of more than of sequence of the database'); + } + + $defined_sequences[$was_sequence_name] = true; + + $change = array(); + if (!empty($current_definition['start']) + && isset($previous_definition[$was_sequence_name]['start']) + && $current_definition['start'] != $previous_definition[$was_sequence_name]['start'] + ) { + $change['start'] = $previous_definition[$sequence_name]['start']; + } + if (isset($current_definition['on']['table']) + && isset($previous_definition[$was_sequence_name]['on']['table']) + && $current_definition['on']['table'] != $previous_definition[$was_sequence_name]['on']['table'] + && isset($current_definition['on']['field']) + && isset($previous_definition[$was_sequence_name]['on']['field']) + && $current_definition['on']['field'] != $previous_definition[$was_sequence_name]['on']['field'] + ) { + $change['on'] = $current_definition['on']; + } + if (!empty($change)) { + $changes['change'][$was_sequence_name][$sequence_name] = $change; + } + } else { + if ($sequence_name != $was_sequence_name) { + return $this->raiseError(MDB2_SCHEMA_ERROR_INVALID, null, null, + 'it was specified a previous sequence name ("'.$was_sequence_name. + '") for sequence "'.$sequence_name.'" that does not exist'); + } + $changes['add'][$sequence_name] = true; + } + } + return $changes; + } + // }}} + // {{{ verifyAlterDatabase() + + /** + * Verify that the changes requested are supported + * + * @param array $changes associative array that contains the definition of the changes + * that are meant to be applied to the database structure. + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function verifyAlterDatabase($changes) + { + if (!empty($changes['tables']['change']) && is_array($changes['tables']['change'])) { + foreach ($changes['tables']['change'] as $table_name => $table) { + if (!empty($table['indexes']) && is_array($table['indexes'])) { + if (!$this->db->supports('indexes')) { + return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null, + 'indexes are not supported'); + } + $table_changes = count($table['indexes']); + if (!empty($table['indexes']['add'])) { + $table_changes--; + } + if (!empty($table['indexes']['remove'])) { + $table_changes--; + } + if (!empty($table['indexes']['change'])) { + $table_changes--; + } + if ($table_changes) { + return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null, + 'index alteration not yet supported: '.implode(', ', array_keys($table['indexes']))); + } + } + unset($table['indexes']); + $result = $this->db->manager->alterTable($table_name, $table, true); + if (PEAR::isError($result)) { + return $result; + } + } + } + if (!empty($changes['sequences']) && is_array($changes['sequences'])) { + if (!$this->db->supports('sequences')) { + return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null, + 'sequences are not supported'); + } + $sequence_changes = count($changes['sequences']); + if (!empty($changes['sequences']['add'])) { + $sequence_changes--; + } + if (!empty($changes['sequences']['remove'])) { + $sequence_changes--; + } + if (!empty($changes['sequences']['change'])) { + $sequence_changes--; + } + if ($sequence_changes) { + return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null, + 'sequence alteration not yet supported: '.implode(', ', array_keys($changes['sequences']))); + } + } + return MDB2_OK; + } + + // }}} + // {{{ alterDatabaseIndexes() + + /** + * Execute the necessary actions to implement the requested changes + * in the indexes inside a database structure. + * + * @param string $table_name name of the table + * @param array $changes associative array that contains the definition of the changes + * that are meant to be applied to the database structure. + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function alterDatabaseIndexes($table_name, $changes) + { + $alterations = 0; + if (empty($changes)) { + return $alterations; + } + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + foreach ($changes['remove'] as $index_name => $index) { + $this->db->expectError(MDB2_ERROR_NOT_FOUND); + if (!empty($index['primary']) || !empty($index['unique'])) { + $result = $this->db->manager->dropConstraint($table_name, $index_name, !empty($index['primary'])); + } else { + $result = $this->db->manager->dropIndex($table_name, $index_name); + } + $this->db->popExpect(); + if (PEAR::isError($result) && !MDB2::isError($result, MDB2_ERROR_NOT_FOUND)) { + return $result; + } + $alterations++; + } + } + if (!empty($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $index_name => $index) { + /** + * Drop existing index/constraint first. + * Since $changes doesn't tell us whether it's an index or a constraint before the change, + * we have to find out and call the appropriate method. + */ + if (in_array($index_name, $this->db->manager->listTableIndexes($table_name))) { + $result = $this->db->manager->dropIndex($table_name, $index_name); + } elseif (in_array($index_name, $this->db->manager->listTableConstraints($table_name))) { + $result = $this->db->manager->dropConstraint($table_name, $index_name); + } + if (!empty($result) && PEAR::isError($result)) { + return $result; + } + + if (!empty($index['primary']) || !empty($index['unique'])) { + $result = $this->db->manager->createConstraint($table_name, $index_name, $index); + } else { + $result = $this->db->manager->createIndex($table_name, $index_name, $index); + } + if (PEAR::isError($result)) { + return $result; + } + $alterations++; + } + } + if (!empty($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $index_name => $index) { + if (!empty($index['primary']) || !empty($index['unique'])) { + $result = $this->db->manager->createConstraint($table_name, $index_name, $index); + } else { + $result = $this->db->manager->createIndex($table_name, $index_name, $index); + } + if (PEAR::isError($result)) { + return $result; + } + $alterations++; + } + } + + return $alterations; + } + + // }}} + // {{{ alterDatabaseTables() + + /** + * Execute the necessary actions to implement the requested changes + * in the tables inside a database structure. + * + * @param array $current_definition multi dimensional array that contains the current definition + * @param array $previous_definition multi dimensional array that contains the previous definition + * @param array $changes associative array that contains the definition of the changes + * that are meant to be applied to the database structure. + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function alterDatabaseTables($current_definition, $previous_definition, $changes) + { + /* FIXME: tables marked to be added are initialized by createTable(), others don't */ + $alterations = 0; + if (empty($changes)) { + return $alterations; + } + + if (!empty($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $table_name => $table) { + $result = $this->createTable($table_name, $current_definition[$table_name]); + if (PEAR::isError($result)) { + return $result; + } + $alterations++; + } + } + + if ($this->options['drop_missing_tables'] + && !empty($changes['remove']) + && is_array($changes['remove'])) { + foreach ($changes['remove'] as $table_name => $table) { + $result = $this->db->manager->dropTable($table_name); + if (PEAR::isError($result)) { + return $result; + } + $alterations++; + } + } + + if (!empty($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $table_name => $table) { + $indexes = array(); + if (!empty($table['indexes'])) { + $indexes = $table['indexes']; + unset($table['indexes']); + } + if (!empty($indexes['remove'])) { + $result = $this->alterDatabaseIndexes($table_name, array('remove' => $indexes['remove'])); + if (PEAR::isError($result)) { + return $result; + } + unset($indexes['remove']); + $alterations += $result; + } + $result = $this->db->manager->alterTable($table_name, $table, false); + if (PEAR::isError($result)) { + return $result; + } + $alterations++; + + // table may be renamed at this point + if (!empty($table['name'])) { + $table_name = $table['name']; + } + + if (!empty($indexes)) { + $result = $this->alterDatabaseIndexes($table_name, $indexes); + if (PEAR::isError($result)) { + return $result; + } + $alterations += $result; + } + } + } + + return $alterations; + } + + // }}} + // {{{ alterDatabaseSequences() + + /** + * Execute the necessary actions to implement the requested changes + * in the sequences inside a database structure. + * + * @param array $current_definition multi dimensional array that contains the current definition + * @param array $previous_definition multi dimensional array that contains the previous definition + * @param array $changes associative array that contains the definition of the changes + * that are meant to be applied to the database structure. + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function alterDatabaseSequences($current_definition, $previous_definition, $changes) + { + $alterations = 0; + if (empty($changes)) { + return $alterations; + } + + if (!empty($changes['add']) && is_array($changes['add'])) { + foreach ($changes['add'] as $sequence_name => $sequence) { + $result = $this->createSequence($sequence_name, $current_definition[$sequence_name]); + if (PEAR::isError($result)) { + return $result; + } + $alterations++; + } + } + + if (!empty($changes['remove']) && is_array($changes['remove'])) { + foreach ($changes['remove'] as $sequence_name => $sequence) { + $result = $this->db->manager->dropSequence($sequence_name); + if (PEAR::isError($result)) { + return $result; + } + $alterations++; + } + } + + if (!empty($changes['change']) && is_array($changes['change'])) { + foreach ($changes['change'] as $sequence_name => $sequence) { + $result = $this->db->manager->dropSequence($previous_definition[$sequence_name]['was']); + if (PEAR::isError($result)) { + return $result; + } + $result = $this->createSequence($sequence_name, $sequence); + if (PEAR::isError($result)) { + return $result; + } + $alterations++; + } + } + + return $alterations; + } + + // }}} + // {{{ alterDatabase() + + /** + * Execute the necessary actions to implement the requested changes + * in a database structure. + * + * @param array $current_definition multi dimensional array that contains the current definition + * @param array $previous_definition multi dimensional array that contains the previous definition + * @param array $changes associative array that contains the definition of the changes + * that are meant to be applied to the database structure. + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function alterDatabase($current_definition, $previous_definition, $changes) + { + $alterations = 0; + if (empty($changes)) { + return $alterations; + } + + $result = $this->verifyAlterDatabase($changes); + if (PEAR::isError($result)) { + return $result; + } + + if (!empty($current_definition['name'])) { + $previous_database_name = $this->db->setDatabase($current_definition['name']); + } + + if (($support_transactions = $this->db->supports('transactions')) + && PEAR::isError($result = $this->db->beginNestedTransaction()) + ) { + return $result; + } + + if (!empty($changes['tables']) && !empty($current_definition['tables'])) { + $current_tables = isset($current_definition['tables']) ? $current_definition['tables'] : array(); + $previous_tables = isset($previous_definition['tables']) ? $previous_definition['tables'] : array(); + + $result = $this->alterDatabaseTables($current_tables, $previous_tables, $changes['tables']); + if (is_numeric($result)) { + $alterations += $result; + } + } + + if (!PEAR::isError($result) && !empty($changes['sequences'])) { + $current_sequences = isset($current_definition['sequences']) ? $current_definition['sequences'] : array(); + $previous_sequences = isset($previous_definition['sequences']) ? $previous_definition['sequences'] : array(); + + $result = $this->alterDatabaseSequences($current_sequences, $previous_sequences, $changes['sequences']); + if (is_numeric($result)) { + $alterations += $result; + } + } + + if ($support_transactions) { + $res = $this->db->completeNestedTransaction(); + if (PEAR::isError($res)) { + $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null, + 'Could not end transaction ('. + $res->getMessage().' ('.$res->getUserinfo().'))'); + } + } elseif (PEAR::isError($result) && $alterations) { + $result = $this->raiseError(MDB2_SCHEMA_ERROR, null, null, + 'the requested database alterations were only partially implemented ('. + $result->getMessage().' ('.$result->getUserinfo().'))'); + } + + if (isset($previous_database_name)) { + $this->db->setDatabase($previous_database_name); + } + return $result; + } + + // }}} + // {{{ dumpDatabaseChanges() + + /** + * Dump the changes between two database definitions. + * + * @param array $changes associative array that specifies the list of database + * definitions changes as returned by the _compareDefinitions + * manager class function. + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function dumpDatabaseChanges($changes) + { + if (!empty($changes['tables'])) { + if (!empty($changes['tables']['add']) && is_array($changes['tables']['add'])) { + foreach ($changes['tables']['add'] as $table_name => $table) { + $this->db->debug("$table_name:", __FUNCTION__); + $this->db->debug("\tAdded table '$table_name'", __FUNCTION__); + } + } + + if (!empty($changes['tables']['remove']) && is_array($changes['tables']['remove'])) { + if ($this->options['drop_missing_tables']) { + foreach ($changes['tables']['remove'] as $table_name => $table) { + $this->db->debug("$table_name:", __FUNCTION__); + $this->db->debug("\tRemoved table '$table_name'", __FUNCTION__); + } + } else { + foreach ($changes['tables']['remove'] as $table_name => $table) { + $this->db->debug("\tObsolete table '$table_name' left as is", __FUNCTION__); + } + } + } + + if (!empty($changes['tables']['change']) && is_array($changes['tables']['change'])) { + foreach ($changes['tables']['change'] as $table_name => $table) { + if (array_key_exists('name', $table)) { + $this->db->debug("\tRenamed table '$table_name' to '".$table['name']."'", __FUNCTION__); + } + if (!empty($table['add']) && is_array($table['add'])) { + foreach ($table['add'] as $field_name => $field) { + $this->db->debug("\tAdded field '".$field_name."'", __FUNCTION__); + } + } + if (!empty($table['remove']) && is_array($table['remove'])) { + foreach ($table['remove'] as $field_name => $field) { + $this->db->debug("\tRemoved field '".$field_name."'", __FUNCTION__); + } + } + if (!empty($table['rename']) && is_array($table['rename'])) { + foreach ($table['rename'] as $field_name => $field) { + $this->db->debug("\tRenamed field '".$field_name."' to '".$field['name']."'", __FUNCTION__); + } + } + if (!empty($table['change']) && is_array($table['change'])) { + foreach ($table['change'] as $field_name => $field) { + $field = $field['definition']; + if (array_key_exists('type', $field)) { + $this->db->debug("\tChanged field '$field_name' type to '".$field['type']."'", __FUNCTION__); + } + + if (array_key_exists('unsigned', $field)) { + $this->db->debug("\tChanged field '$field_name' type to '". + (!empty($field['unsigned']) && $field['unsigned'] ? '' : 'not ')."unsigned'", + __FUNCTION__); + } + + if (array_key_exists('length', $field)) { + $this->db->debug("\tChanged field '$field_name' length to '". + (!empty($field['length']) ? $field['length']: 'no length')."'", __FUNCTION__); + } + if (array_key_exists('default', $field)) { + $this->db->debug("\tChanged field '$field_name' default to ". + (isset($field['default']) ? "'".$field['default']."'" : 'NULL'), __FUNCTION__); + } + + if (array_key_exists('notnull', $field)) { + $this->db->debug("\tChanged field '$field_name' notnull to ". + (!empty($field['notnull']) && $field['notnull'] ? 'true' : 'false'), + __FUNCTION__); + } + } + } + if (!empty($table['indexes']) && is_array($table['indexes'])) { + if (!empty($table['indexes']['add']) && is_array($table['indexes']['add'])) { + foreach ($table['indexes']['add'] as $index_name => $index) { + $this->db->debug("\tAdded index '".$index_name. + "' of table '$table_name'", __FUNCTION__); + } + } + if (!empty($table['indexes']['remove']) && is_array($table['indexes']['remove'])) { + foreach ($table['indexes']['remove'] as $index_name => $index) { + $this->db->debug("\tRemoved index '".$index_name. + "' of table '$table_name'", __FUNCTION__); + } + } + if (!empty($table['indexes']['change']) && is_array($table['indexes']['change'])) { + foreach ($table['indexes']['change'] as $index_name => $index) { + if (array_key_exists('name', $index)) { + $this->db->debug("\tRenamed index '".$index_name."' to '".$index['name']. + "' on table '$table_name'", __FUNCTION__); + } + if (array_key_exists('unique', $index)) { + $this->db->debug("\tChanged index '".$index_name."' unique to '". + !empty($index['unique'])."' on table '$table_name'", __FUNCTION__); + } + if (array_key_exists('primary', $index)) { + $this->db->debug("\tChanged index '".$index_name."' primary to '". + !empty($index['primary'])."' on table '$table_name'", __FUNCTION__); + } + if (array_key_exists('change', $index)) { + $this->db->debug("\tChanged index '".$index_name. + "' on table '$table_name'", __FUNCTION__); + } + } + } + } + } + } + } + if (!empty($changes['sequences'])) { + if (!empty($changes['sequences']['add']) && is_array($changes['sequences']['add'])) { + foreach ($changes['sequences']['add'] as $sequence_name => $sequence) { + $this->db->debug("$sequence_name:", __FUNCTION__); + $this->db->debug("\tAdded sequence '$sequence_name'", __FUNCTION__); + } + } + if (!empty($changes['sequences']['remove']) && is_array($changes['sequences']['remove'])) { + foreach ($changes['sequences']['remove'] as $sequence_name => $sequence) { + $this->db->debug("$sequence_name:", __FUNCTION__); + $this->db->debug("\tAdded sequence '$sequence_name'", __FUNCTION__); + } + } + if (!empty($changes['sequences']['change']) && is_array($changes['sequences']['change'])) { + foreach ($changes['sequences']['change'] as $sequence_name => $sequence) { + if (array_key_exists('name', $sequence)) { + $this->db->debug("\tRenamed sequence '$sequence_name' to '". + $sequence['name']."'", __FUNCTION__); + } + if (!empty($sequence['change']) && is_array($sequence['change'])) { + foreach ($sequence['change'] as $sequence_name => $sequence) { + if (array_key_exists('start', $sequence)) { + $this->db->debug("\tChanged sequence '$sequence_name' start to '". + $sequence['start']."'", __FUNCTION__); + } + } + } + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ dumpDatabase() + + /** + * Dump a previously parsed database structure in the Metabase schema + * XML based format suitable for the Metabase parser. This function + * may optionally dump the database definition with initialization + * commands that specify the data that is currently present in the tables. + * + * @param array $database_definition multi dimensional array that contains the current definition + * @param array $arguments associative array that takes pairs of tag + * names and values that define dump options. + *
array (
+     *                     'output_mode'    =>    String
+     *                         'file' :   dump into a file
+     *                         default:   dump using a function
+     *                     'output'        =>    String
+     *                         depending on the 'Output_Mode'
+     *                                  name of the file
+     *                                  name of the function
+     *                     'end_of_line'        =>    String
+     *                         end of line delimiter that should be used
+     *                         default: "\n"
+     *                 );
+ * @param int $dump Int that determines what data to dump + * + MDB2_SCHEMA_DUMP_ALL : the entire db + * + MDB2_SCHEMA_DUMP_STRUCTURE : only the structure of the db + * + MDB2_SCHEMA_DUMP_CONTENT : only the content of the db + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function dumpDatabase($database_definition, $arguments, $dump = MDB2_SCHEMA_DUMP_ALL) + { + $class_name = $this->options['writer']; + + $result = MDB2::loadClass($class_name, $this->db->getOption('debug')); + if (PEAR::isError($result)) { + return $result; + } + + // get initialization data + if (isset($database_definition['tables']) && is_array($database_definition['tables']) + && $dump == MDB2_SCHEMA_DUMP_ALL || $dump == MDB2_SCHEMA_DUMP_CONTENT + ) { + foreach ($database_definition['tables'] as $table_name => $table) { + $fields = array(); + $fieldsq = array(); + foreach ($table['fields'] as $field_name => $field) { + $fields[$field_name] = $field['type']; + + $fieldsq[] = $this->db->quoteIdentifier($field_name, true); + } + + $query = 'SELECT '.implode(', ', $fieldsq).' FROM '; + $query .= $this->db->quoteIdentifier($table_name, true); + + $data = $this->db->queryAll($query, $fields, MDB2_FETCHMODE_ASSOC); + + if (PEAR::isError($data)) { + return $data; + } + + if (!empty($data)) { + $initialization = array(); + $lob_buffer_length = $this->db->getOption('lob_buffer_length'); + foreach ($data as $row) { + $rows = array(); + foreach ($row as $key => $lob) { + if (is_resource($lob)) { + $value = ''; + while (!feof($lob)) { + $value .= fread($lob, $lob_buffer_length); + } + $row[$key] = $value; + } + $rows[] = array('name' => $key, 'group' => array('type' => 'value', 'data' => $row[$key])); + } + $initialization[] = array('type' => 'insert', 'data' => array('field' => $rows)); + } + $database_definition['tables'][$table_name]['initialization'] = $initialization; + } + } + } + + $writer = new $class_name($this->options['valid_types']); + return $writer->dumpDatabase($database_definition, $arguments, $dump); + } + + // }}} + // {{{ writeInitialization() + + /** + * Write initialization and sequences + * + * @param string|array $data data file or data array + * @param string|array $structure structure file or array + * @param array $variables associative array that is passed to the argument + * of the same name to the parseDatabaseDefinitionFile function. (there third + * param) + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function writeInitialization($data, $structure = false, $variables = array()) + { + if ($structure) { + $structure = $this->parseDatabaseDefinition($structure, false, $variables); + if (PEAR::isError($structure)) { + return $structure; + } + } + + $data = $this->parseDatabaseDefinition($data, false, $variables, false, $structure); + if (PEAR::isError($data)) { + return $data; + } + + $previous_database_name = null; + if (!empty($data['name'])) { + $previous_database_name = $this->db->setDatabase($data['name']); + } elseif (!empty($structure['name'])) { + $previous_database_name = $this->db->setDatabase($structure['name']); + } + + if (!empty($data['tables']) && is_array($data['tables'])) { + foreach ($data['tables'] as $table_name => $table) { + if (empty($table['initialization'])) { + continue; + } + $result = $this->initializeTable($table_name, $table); + if (PEAR::isError($result)) { + return $result; + } + } + } + + if (!empty($structure['sequences']) && is_array($structure['sequences'])) { + foreach ($structure['sequences'] as $sequence_name => $sequence) { + if (isset($data['sequences'][$sequence_name]) + || !isset($sequence['on']['table']) + || !isset($data['tables'][$sequence['on']['table']]) + ) { + continue; + } + $result = $this->createSequence($sequence_name, $sequence, true); + if (PEAR::isError($result)) { + return $result; + } + } + } + if (!empty($data['sequences']) && is_array($data['sequences'])) { + foreach ($data['sequences'] as $sequence_name => $sequence) { + $result = $this->createSequence($sequence_name, $sequence, true); + if (PEAR::isError($result)) { + return $result; + } + } + } + + if (isset($previous_database_name)) { + $this->db->setDatabase($previous_database_name); + } + + return MDB2_OK; + } + + // }}} + // {{{ updateDatabase() + + /** + * Compare the correspondent files of two versions of a database schema + * definition: the previously installed and the one that defines the schema + * that is meant to update the database. + * If the specified previous definition file does not exist, this function + * will create the database from the definition specified in the current + * schema file. + * If both files exist, the function assumes that the database was previously + * installed based on the previous schema file and will update it by just + * applying the changes. + * If this function succeeds, the contents of the current schema file are + * copied to replace the previous schema file contents. Any subsequent schema + * changes should only be done on the file specified by the $current_schema_file + * to let this function make a consistent evaluation of the exact changes that + * need to be applied. + * + * @param string|array $current_schema filename or array of the updated database schema definition. + * @param string|array $previous_schema filename or array of the previously installed database schema definition. + * @param array $variables associative array that is passed to the argument of the same + * name to the parseDatabaseDefinitionFile function. (there third param) + * @param bool $disable_query determines if the disable_query option should be set to true + * for the alterDatabase() or createDatabase() call + * @param bool $overwrite_old_schema_file Overwrite? + * + * @return bool|MDB2_Error MDB2_OK or error object + * @access public + */ + function updateDatabase($current_schema, $previous_schema = false, + $variables = array(), $disable_query = false, + $overwrite_old_schema_file = false) + { + $current_definition = $this->parseDatabaseDefinition($current_schema, false, $variables, + $this->options['fail_on_invalid_names']); + + if (PEAR::isError($current_definition)) { + return $current_definition; + } + + $previous_definition = false; + if ($previous_schema) { + $previous_definition = $this->parseDatabaseDefinition($previous_schema, true, $variables, + $this->options['fail_on_invalid_names']); + if (PEAR::isError($previous_definition)) { + return $previous_definition; + } + } + + if ($previous_definition) { + $dbExists = $this->db->databaseExists($current_definition['name']); + if (PEAR::isError($dbExists)) { + return $dbExists; + } + + if (!$dbExists) { + return $this->raiseError(MDB2_SCHEMA_ERROR, null, null, + 'database to update does not exist: '.$current_definition['name']); + } + + $changes = $this->compareDefinitions($current_definition, $previous_definition); + if (PEAR::isError($changes)) { + return $changes; + } + + if (is_array($changes)) { + $this->db->setOption('disable_query', $disable_query); + $result = $this->alterDatabase($current_definition, $previous_definition, $changes); + $this->db->setOption('disable_query', false); + if (PEAR::isError($result)) { + return $result; + } + $copy = true; + if ($this->db->options['debug']) { + $result = $this->dumpDatabaseChanges($changes); + if (PEAR::isError($result)) { + return $result; + } + } + } + } else { + $this->db->setOption('disable_query', $disable_query); + $result = $this->createDatabase($current_definition); + $this->db->setOption('disable_query', false); + if (PEAR::isError($result)) { + return $result; + } + } + + if ($overwrite_old_schema_file + && !$disable_query + && is_string($previous_schema) && is_string($current_schema) + && !copy($current_schema, $previous_schema)) { + + return $this->raiseError(MDB2_SCHEMA_ERROR, null, null, + 'Could not copy the new database definition file to the current file'); + } + + return MDB2_OK; + } + // }}} + // {{{ errorMessage() + + /** + * Return a textual error message for a MDB2 error code + * + * @param int|array $value integer error code, null to get the + * current error code-message map, + * or an array with a new error code-message map + * + * @return string error message, or false if the error code was not recognized + * @access public + */ + function errorMessage($value = null) + { + static $errorMessages; + if (is_array($value)) { + $errorMessages = $value; + return MDB2_OK; + } elseif (!isset($errorMessages)) { + $errorMessages = array( + MDB2_SCHEMA_ERROR => 'unknown error', + MDB2_SCHEMA_ERROR_PARSE => 'schema parse error', + MDB2_SCHEMA_ERROR_VALIDATE => 'schema validation error', + MDB2_SCHEMA_ERROR_INVALID => 'invalid', + MDB2_SCHEMA_ERROR_UNSUPPORTED => 'not supported', + MDB2_SCHEMA_ERROR_WRITER => 'schema writer error', + ); + } + + if (is_null($value)) { + return $errorMessages; + } + + if (PEAR::isError($value)) { + $value = $value->getCode(); + } + + return !empty($errorMessages[$value]) ? + $errorMessages[$value] : $errorMessages[MDB2_SCHEMA_ERROR]; + } + + // }}} + // {{{ raiseError() + + /** + * This method is used to communicate an error and invoke error + * callbacks etc. Basically a wrapper for PEAR::raiseError + * without the message string. + * + * @param int|PEAR_Error $code integer error code or and PEAR_Error instance + * @param int $mode error mode, see PEAR_Error docs + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * @param array $options Options, depending on the mode, @see PEAR::setErrorHandling + * @param string $userinfo Extra debug information. Defaults to the last + * query and native error code. + * + * @return object a PEAR error object + * @access public + * @see PEAR_Error + */ + function &raiseError($code = null, $mode = null, $options = null, $userinfo = null) + { + $err =& PEAR::raiseError(null, $code, $mode, $options, + $userinfo, 'MDB2_Schema_Error', true); + return $err; + } + + // }}} + // {{{ isError() + + /** + * Tell whether a value is an MDB2_Schema error. + * + * @param mixed $data the value to test + * @param int $code if $data is an error object, return true only if $code is + * a string and $db->getMessage() == $code or + * $code is an integer and $db->getCode() == $code + * + * @return bool true if parameter is an error + * @access public + */ + function isError($data, $code = null) + { + if (is_a($data, 'MDB2_Schema_Error')) { + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() === $code; + } else { + $code = (array)$code; + return in_array($data->getCode(), $code); + } + } + return false; + } + + // }}} +} + +/** + * MDB2_Schema_Error implements a class for reporting portable database error + * messages. + * + * @category Database + * @package MDB2_Schema + * @author Stig Bakken + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/packages/MDB2_Schema + */ +class MDB2_Schema_Error extends PEAR_Error +{ + /** + * MDB2_Schema_Error constructor. + * + * @param mixed $code error code, or string with error message. + * @param int $mode what 'error mode' to operate in + * @param int $level what error level to use for $mode & PEAR_ERROR_TRIGGER + * @param mixed $debuginfo additional debug info, such as the last query + * + * @access public + */ + function MDB2_Schema_Error($code = MDB2_SCHEMA_ERROR, $mode = PEAR_ERROR_RETURN, + $level = E_USER_NOTICE, $debuginfo = null) + { + $this->PEAR_Error('MDB2_Schema Error: ' . MDB2_Schema::errorMessage($code), $code, + $mode, $level, $debuginfo); + } +} +?> diff --git a/inc/MDB2/Schema/Parser.php b/inc/MDB2/Schema/Parser.php new file mode 100644 index 0000000000..ed31ba03bd --- /dev/null +++ b/inc/MDB2/Schema/Parser.php @@ -0,0 +1,819 @@ + + * Author: Igor Feghali + * + * $Id: Parser.php,v 1.68 2008/11/30 03:34:00 clockwerx Exp $ + * + * @category Database + * @package MDB2_Schema + * @author Christian Dickmann + * @author Igor Feghali + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @version CVS: $Id: Parser.php,v 1.68 2008/11/30 03:34:00 clockwerx Exp $ + * @link http://pear.php.net/packages/MDB2_Schema + */ + + +oc_require_once('XML/Parser.php'); +oc_require_once('MDB2/Schema/Validate.php'); + +/** + * Parses an XML schema file + * + * @category Database + * @package MDB2_Schema + * @author Christian Dickmann + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/packages/MDB2_Schema + */ +class MDB2_Schema_Parser extends XML_Parser +{ + var $database_definition = array(); + + var $elements = array(); + + var $element = ''; + + var $count = 0; + + var $table = array(); + + var $table_name = ''; + + var $field = array(); + + var $field_name = ''; + + var $init = array(); + + var $init_function = array(); + + var $init_expression = array(); + + var $init_field = array(); + + var $index = array(); + + var $index_name = ''; + + var $constraint = array(); + + var $constraint_name = ''; + + var $var_mode = false; + + var $variables = array(); + + var $sequence = array(); + + var $sequence_name = ''; + + var $error; + + var $structure = false; + + var $val; + + function __construct($variables, $fail_on_invalid_names = true, + $structure = false, $valid_types = array(), + $force_defaults = true) + { + // force ISO-8859-1 due to different defaults for PHP4 and PHP5 + // todo: this probably needs to be investigated some more andcleaned up + parent::XML_Parser('ISO-8859-1'); + + $this->variables = $variables; + $this->structure = $structure; + $this->val =new MDB2_Schema_Validate($fail_on_invalid_names, $valid_types, $force_defaults); + } + + function MDB2_Schema_Parser($variables, $fail_on_invalid_names = true, + $structure = false, $valid_types = array(), + $force_defaults = true) + { + $this->__construct($variables, $fail_on_invalid_names, $structure, $valid_types, $force_defaults); + } + + function startHandler($xp, $element, $attribs) + { + if (strtolower($element) == 'variable') { + $this->var_mode = true; + return; + } + + $this->elements[$this->count++] = strtolower($element); + + $this->element = implode('-', $this->elements); + + switch ($this->element) { + /* Initialization */ + case 'database-table-initialization': + $this->table['initialization'] = array(); + break; + + /* Insert */ + /* insert: field+ */ + case 'database-table-initialization-insert': + $this->init = array('type' => 'insert', 'data' => array('field' => array())); + break; + /* insert-select: field+, table, where? */ + case 'database-table-initialization-insert-select': + $this->init['data']['table'] = ''; + break; + + /* Update */ + /* update: field+, where? */ + case 'database-table-initialization-update': + $this->init = array('type' => 'update', 'data' => array('field' => array())); + break; + + /* Delete */ + /* delete: where */ + case 'database-table-initialization-delete': + $this->init = array('type' => 'delete', 'data' => array('where' => array())); + break; + + /* Insert and Update */ + case 'database-table-initialization-insert-field': + case 'database-table-initialization-insert-select-field': + case 'database-table-initialization-update-field': + $this->init_field = array('name' => '', 'group' => array()); + break; + case 'database-table-initialization-insert-field-value': + case 'database-table-initialization-insert-select-field-value': + case 'database-table-initialization-update-field-value': + /* if value tag is empty cdataHandler is not called so we must force value element creation here */ + $this->init_field['group'] = array('type' => 'value', 'data' => ''); + break; + case 'database-table-initialization-insert-field-null': + case 'database-table-initialization-insert-select-field-null': + case 'database-table-initialization-update-field-null': + $this->init_field['group'] = array('type' => 'null'); + break; + case 'database-table-initialization-insert-field-function': + case 'database-table-initialization-insert-select-field-function': + case 'database-table-initialization-update-field-function': + $this->init_function = array('name' => ''); + break; + case 'database-table-initialization-insert-field-expression': + case 'database-table-initialization-insert-select-field-expression': + case 'database-table-initialization-update-field-expression': + $this->init_expression = array(); + break; + + /* All */ + case 'database-table-initialization-insert-select-where': + case 'database-table-initialization-update-where': + case 'database-table-initialization-delete-where': + $this->init['data']['where'] = array('type' => '', 'data' => array()); + break; + case 'database-table-initialization-insert-select-where-expression': + case 'database-table-initialization-update-where-expression': + case 'database-table-initialization-delete-where-expression': + $this->init_expression = array(); + break; + + /* One level simulation of expression-function recursion */ + case 'database-table-initialization-insert-field-expression-function': + case 'database-table-initialization-insert-select-field-expression-function': + case 'database-table-initialization-insert-select-where-expression-function': + case 'database-table-initialization-update-field-expression-function': + case 'database-table-initialization-update-where-expression-function': + case 'database-table-initialization-delete-where-expression-function': + $this->init_function = array('name' => ''); + break; + + /* One level simulation of function-expression recursion */ + case 'database-table-initialization-insert-field-function-expression': + case 'database-table-initialization-insert-select-field-function-expression': + case 'database-table-initialization-insert-select-where-function-expression': + case 'database-table-initialization-update-field-function-expression': + case 'database-table-initialization-update-where-function-expression': + case 'database-table-initialization-delete-where-function-expression': + $this->init_expression = array(); + break; + + /* Definition */ + case 'database': + $this->database_definition = array( + 'name' => '', + 'create' => '', + 'overwrite' => '', + 'charset' => '', + 'description' => '', + 'comments' => '', + 'tables' => array(), + 'sequences' => array() + ); + break; + case 'database-table': + $this->table_name = ''; + + $this->table = array( + 'was' => '', + 'description' => '', + 'comments' => '', + 'fields' => array(), + 'indexes' => array(), + 'constraints' => array(), + 'initialization' => array() + ); + break; + case 'database-table-declaration-field': + case 'database-table-declaration-foreign-field': + case 'database-table-declaration-foreign-references-field': + $this->field_name = ''; + + $this->field = array(); + break; + case 'database-table-declaration-index-field': + $this->field_name = ''; + + $this->field = array('sorting' => '', 'length' => ''); + break; + /* force field attributes to be initialized when the tag is empty in the XML */ + case 'database-table-declaration-field-was': + $this->field['was'] = ''; + break; + case 'database-table-declaration-field-type': + $this->field['type'] = ''; + break; + case 'database-table-declaration-field-fixed': + $this->field['fixed'] = ''; + break; + case 'database-table-declaration-field-default': + $this->field['default'] = ''; + break; + case 'database-table-declaration-field-notnull': + $this->field['notnull'] = ''; + break; + case 'database-table-declaration-field-autoincrement': + $this->field['autoincrement'] = ''; + break; + case 'database-table-declaration-field-unsigned': + $this->field['unsigned'] = ''; + break; + case 'database-table-declaration-field-length': + $this->field['length'] = ''; + break; + case 'database-table-declaration-field-description': + $this->field['description'] = ''; + break; + case 'database-table-declaration-field-comments': + $this->field['comments'] = ''; + break; + case 'database-table-declaration-index': + $this->index_name = ''; + + $this->index = array( + 'was' => '', + 'unique' =>'', + 'primary' => '', + 'fields' => array() + ); + break; + case 'database-table-declaration-foreign': + $this->constraint_name = ''; + + $this->constraint = array( + 'was' => '', + 'match' => '', + 'ondelete' => '', + 'onupdate' => '', + 'deferrable' => '', + 'initiallydeferred' => '', + 'foreign' => true, + 'fields' => array(), + 'references' => array('table' => '', 'fields' => array()) + ); + break; + case 'database-sequence': + $this->sequence_name = ''; + + $this->sequence = array( + 'was' => '', + 'start' => '', + 'description' => '', + 'comments' => '', + 'on' => array('table' => '', 'field' => '') + ); + break; + } + } + + function endHandler($xp, $element) + { + if (strtolower($element) == 'variable') { + $this->var_mode = false; + return; + } + + switch ($this->element) { + /* Initialization */ + + /* Insert */ + case 'database-table-initialization-insert-select': + $this->init['data'] = array('select' => $this->init['data']); + break; + + /* Insert and Delete */ + case 'database-table-initialization-insert-field': + case 'database-table-initialization-insert-select-field': + case 'database-table-initialization-update-field': + $result = $this->val->validateDataField($this->table['fields'], $this->init['data']['field'], $this->init_field); + if (PEAR::isError($result)) { + $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode()); + } else { + $this->init['data']['field'][] = $this->init_field; + } + break; + case 'database-table-initialization-insert-field-function': + case 'database-table-initialization-insert-select-field-function': + case 'database-table-initialization-update-field-function': + $this->init_field['group'] = array('type' => 'function', 'data' => $this->init_function); + break; + case 'database-table-initialization-insert-field-expression': + case 'database-table-initialization-insert-select-field-expression': + case 'database-table-initialization-update-field-expression': + $this->init_field['group'] = array('type' => 'expression', 'data' => $this->init_expression); + break; + + /* All */ + case 'database-table-initialization-insert-select-where-expression': + case 'database-table-initialization-update-where-expression': + case 'database-table-initialization-delete-where-expression': + $this->init['data']['where']['type'] = 'expression'; + $this->init['data']['where']['data'] = $this->init_expression; + break; + case 'database-table-initialization-insert': + case 'database-table-initialization-delete': + case 'database-table-initialization-update': + $this->table['initialization'][] = $this->init; + break; + + /* One level simulation of expression-function recursion */ + case 'database-table-initialization-insert-field-expression-function': + case 'database-table-initialization-insert-select-field-expression-function': + case 'database-table-initialization-insert-select-where-expression-function': + case 'database-table-initialization-update-field-expression-function': + case 'database-table-initialization-update-where-expression-function': + case 'database-table-initialization-delete-where-expression-function': + $this->init_expression['operants'][] = array('type' => 'function', 'data' => $this->init_function); + break; + + /* One level simulation of function-expression recursion */ + case 'database-table-initialization-insert-field-function-expression': + case 'database-table-initialization-insert-select-field-function-expression': + case 'database-table-initialization-insert-select-where-function-expression': + case 'database-table-initialization-update-field-function-expression': + case 'database-table-initialization-update-where-function-expression': + case 'database-table-initialization-delete-where-function-expression': + $this->init_function['arguments'][] = array('type' => 'expression', 'data' => $this->init_expression); + break; + + /* Table definition */ + case 'database-table': + $result = $this->val->validateTable($this->database_definition['tables'], $this->table, $this->table_name); + if (PEAR::isError($result)) { + $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode()); + } else { + $this->database_definition['tables'][$this->table_name] = $this->table; + } + break; + case 'database-table-name': + if (isset($this->structure['tables'][$this->table_name])) { + $this->table = $this->structure['tables'][$this->table_name]; + } + break; + + /* Field declaration */ + case 'database-table-declaration-field': + $result = $this->val->validateField($this->table['fields'], $this->field, $this->field_name); + if (PEAR::isError($result)) { + $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode()); + } else { + $this->table['fields'][$this->field_name] = $this->field; + } + break; + + /* Index declaration */ + case 'database-table-declaration-index': + $result = $this->val->validateIndex($this->table['indexes'], $this->index, $this->index_name); + if (PEAR::isError($result)) { + $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode()); + } else { + $this->table['indexes'][$this->index_name] = $this->index; + } + break; + case 'database-table-declaration-index-field': + $result = $this->val->validateIndexField($this->index['fields'], $this->field, $this->field_name); + if (PEAR::isError($result)) { + $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode()); + } else { + $this->index['fields'][$this->field_name] = $this->field; + } + break; + + /* Foreign Key declaration */ + case 'database-table-declaration-foreign': + $result = $this->val->validateConstraint($this->table['constraints'], $this->constraint, $this->constraint_name); + if (PEAR::isError($result)) { + $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode()); + } else { + $this->table['constraints'][$this->constraint_name] = $this->constraint; + } + break; + case 'database-table-declaration-foreign-field': + $result = $this->val->validateConstraintField($this->constraint['fields'], $this->field_name); + if (PEAR::isError($result)) { + $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode()); + } else { + $this->constraint['fields'][$this->field_name] = ''; + } + break; + case 'database-table-declaration-foreign-references-field': + $result = $this->val->validateConstraintReferencedField($this->constraint['references']['fields'], $this->field_name); + if (PEAR::isError($result)) { + $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode()); + } else { + $this->constraint['references']['fields'][$this->field_name] = ''; + } + break; + + /* Sequence declaration */ + case 'database-sequence': + $result = $this->val->validateSequence($this->database_definition['sequences'], $this->sequence, $this->sequence_name); + if (PEAR::isError($result)) { + $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode()); + } else { + $this->database_definition['sequences'][$this->sequence_name] = $this->sequence; + } + break; + + /* End of File */ + case 'database': + $result = $this->val->validateDatabase($this->database_definition); + if (PEAR::isError($result)) { + $this->raiseError($result->getUserinfo(), 0, $xp, $result->getCode()); + } + break; + } + + unset($this->elements[--$this->count]); + $this->element = implode('-', $this->elements); + } + + function &raiseError($msg = null, $xmlecode = 0, $xp = null, $ecode = MDB2_SCHEMA_ERROR_PARSE) + { + if (is_null($this->error)) { + $error = ''; + if (is_resource($msg)) { + $error .= 'Parser error: '.xml_error_string(xml_get_error_code($msg)); + $xp = $msg; + } else { + $error .= 'Parser error: '.$msg; + if (!is_resource($xp)) { + $xp = $this->parser; + } + } + + if ($error_string = xml_error_string($xmlecode)) { + $error .= ' - '.$error_string; + } + + if (is_resource($xp)) { + $byte = @xml_get_current_byte_index($xp); + $line = @xml_get_current_line_number($xp); + $column = @xml_get_current_column_number($xp); + $error .= " - Byte: $byte; Line: $line; Col: $column"; + } + + $error .= "\n"; + + $this->error =& MDB2_Schema::raiseError($ecode, null, null, $error); + } + return $this->error; + } + + function cdataHandler($xp, $data) + { + if ($this->var_mode == true) { + if (!isset($this->variables[$data])) { + $this->raiseError('variable "'.$data.'" not found', null, $xp); + return; + } + $data = $this->variables[$data]; + } + + switch ($this->element) { + /* Initialization */ + + /* Insert */ + case 'database-table-initialization-insert-select-table': + $this->init['data']['table'] = $data; + break; + + /* Insert and Update */ + case 'database-table-initialization-insert-field-name': + case 'database-table-initialization-insert-select-field-name': + case 'database-table-initialization-update-field-name': + $this->init_field['name'] .= $data; + break; + case 'database-table-initialization-insert-field-value': + case 'database-table-initialization-insert-select-field-value': + case 'database-table-initialization-update-field-value': + $this->init_field['group']['data'] .= $data; + break; + case 'database-table-initialization-insert-field-function-name': + case 'database-table-initialization-insert-select-field-function-name': + case 'database-table-initialization-update-field-function-name': + $this->init_function['name'] .= $data; + break; + case 'database-table-initialization-insert-field-function-value': + case 'database-table-initialization-insert-select-field-function-value': + case 'database-table-initialization-update-field-function-value': + $this->init_function['arguments'][] = array('type' => 'value', 'data' => $data); + break; + case 'database-table-initialization-insert-field-function-column': + case 'database-table-initialization-insert-select-field-function-column': + case 'database-table-initialization-update-field-function-column': + $this->init_function['arguments'][] = array('type' => 'column', 'data' => $data); + break; + case 'database-table-initialization-insert-field-column': + case 'database-table-initialization-insert-select-field-column': + case 'database-table-initialization-update-field-column': + $this->init_field['group'] = array('type' => 'column', 'data' => $data); + break; + + /* All */ + case 'database-table-initialization-insert-field-expression-operator': + case 'database-table-initialization-insert-select-field-expression-operator': + case 'database-table-initialization-insert-select-where-expression-operator': + case 'database-table-initialization-update-field-expression-operator': + case 'database-table-initialization-update-where-expression-operator': + case 'database-table-initialization-delete-where-expression-operator': + $this->init_expression['operator'] = $data; + break; + case 'database-table-initialization-insert-field-expression-value': + case 'database-table-initialization-insert-select-field-expression-value': + case 'database-table-initialization-insert-select-where-expression-value': + case 'database-table-initialization-update-field-expression-value': + case 'database-table-initialization-update-where-expression-value': + case 'database-table-initialization-delete-where-expression-value': + $this->init_expression['operants'][] = array('type' => 'value', 'data' => $data); + break; + case 'database-table-initialization-insert-field-expression-column': + case 'database-table-initialization-insert-select-field-expression-column': + case 'database-table-initialization-insert-select-where-expression-column': + case 'database-table-initialization-update-field-expression-column': + case 'database-table-initialization-update-where-expression-column': + case 'database-table-initialization-delete-where-expression-column': + $this->init_expression['operants'][] = array('type' => 'column', 'data' => $data); + break; + + case 'database-table-initialization-insert-field-function-function': + case 'database-table-initialization-insert-field-function-expression': + case 'database-table-initialization-insert-field-expression-expression': + case 'database-table-initialization-update-field-function-function': + case 'database-table-initialization-update-field-function-expression': + case 'database-table-initialization-update-field-expression-expression': + case 'database-table-initialization-update-where-expression-expression': + case 'database-table-initialization-delete-where-expression-expression': + /* Recursion to be implemented yet */ + break; + + /* One level simulation of expression-function recursion */ + case 'database-table-initialization-insert-field-expression-function-name': + case 'database-table-initialization-insert-select-field-expression-function-name': + case 'database-table-initialization-insert-select-where-expression-function-name': + case 'database-table-initialization-update-field-expression-function-name': + case 'database-table-initialization-update-where-expression-function-name': + case 'database-table-initialization-delete-where-expression-function-name': + $this->init_function['name'] .= $data; + break; + case 'database-table-initialization-insert-field-expression-function-value': + case 'database-table-initialization-insert-select-field-expression-function-value': + case 'database-table-initialization-insert-select-where-expression-function-value': + case 'database-table-initialization-update-field-expression-function-value': + case 'database-table-initialization-update-where-expression-function-value': + case 'database-table-initialization-delete-where-expression-function-value': + $this->init_function['arguments'][] = array('type' => 'value', 'data' => $data); + break; + case 'database-table-initialization-insert-field-expression-function-column': + case 'database-table-initialization-insert-select-field-expression-function-column': + case 'database-table-initialization-insert-select-where-expression-function-column': + case 'database-table-initialization-update-field-expression-function-column': + case 'database-table-initialization-update-where-expression-function-column': + case 'database-table-initialization-delete-where-expression-function-column': + $this->init_function['arguments'][] = array('type' => 'column', 'data' => $data); + break; + + /* One level simulation of function-expression recursion */ + case 'database-table-initialization-insert-field-function-expression-operator': + case 'database-table-initialization-insert-select-field-function-expression-operator': + case 'database-table-initialization-update-field-function-expression-operator': + $this->init_expression['operator'] = $data; + break; + case 'database-table-initialization-insert-field-function-expression-value': + case 'database-table-initialization-insert-select-field-function-expression-value': + case 'database-table-initialization-update-field-function-expression-value': + $this->init_expression['operants'][] = array('type' => 'value', 'data' => $data); + break; + case 'database-table-initialization-insert-field-function-expression-column': + case 'database-table-initialization-insert-select-field-function-expression-column': + case 'database-table-initialization-update-field-function-expression-column': + $this->init_expression['operants'][] = array('type' => 'column', 'data' => $data); + break; + + /* Database */ + case 'database-name': + $this->database_definition['name'] .= $data; + break; + case 'database-create': + $this->database_definition['create'] .= $data; + break; + case 'database-overwrite': + $this->database_definition['overwrite'] .= $data; + break; + case 'database-charset': + $this->database_definition['charset'] .= $data; + break; + case 'database-description': + $this->database_definition['description'] .= $data; + break; + case 'database-comments': + $this->database_definition['comments'] .= $data; + break; + + /* Table declaration */ + case 'database-table-name': + $this->table_name .= $data; + break; + case 'database-table-was': + $this->table['was'] .= $data; + break; + case 'database-table-description': + $this->table['description'] .= $data; + break; + case 'database-table-comments': + $this->table['comments'] .= $data; + break; + + /* Field declaration */ + case 'database-table-declaration-field-name': + $this->field_name .= $data; + break; + case 'database-table-declaration-field-was': + $this->field['was'] .= $data; + break; + case 'database-table-declaration-field-type': + $this->field['type'] .= $data; + break; + case 'database-table-declaration-field-fixed': + $this->field['fixed'] .= $data; + break; + case 'database-table-declaration-field-default': + $this->field['default'] .= $data; + break; + case 'database-table-declaration-field-notnull': + $this->field['notnull'] .= $data; + break; + case 'database-table-declaration-field-autoincrement': + $this->field['autoincrement'] .= $data; + break; + case 'database-table-declaration-field-unsigned': + $this->field['unsigned'] .= $data; + break; + case 'database-table-declaration-field-length': + $this->field['length'] .= $data; + break; + case 'database-table-declaration-field-description': + $this->field['description'] .= $data; + break; + case 'database-table-declaration-field-comments': + $this->field['comments'] .= $data; + break; + + /* Index declaration */ + case 'database-table-declaration-index-name': + $this->index_name .= $data; + break; + case 'database-table-declaration-index-was': + $this->index['was'] .= $data; + break; + case 'database-table-declaration-index-unique': + $this->index['unique'] .= $data; + break; + case 'database-table-declaration-index-primary': + $this->index['primary'] .= $data; + break; + case 'database-table-declaration-index-field-name': + $this->field_name .= $data; + break; + case 'database-table-declaration-index-field-sorting': + $this->field['sorting'] .= $data; + break; + /* Add by Leoncx */ + case 'database-table-declaration-index-field-length': + $this->field['length'] .= $data; + break; + + /* Foreign Key declaration */ + case 'database-table-declaration-foreign-name': + $this->constraint_name .= $data; + break; + case 'database-table-declaration-foreign-was': + $this->constraint['was'] .= $data; + break; + case 'database-table-declaration-foreign-match': + $this->constraint['match'] .= $data; + break; + case 'database-table-declaration-foreign-ondelete': + $this->constraint['ondelete'] .= $data; + break; + case 'database-table-declaration-foreign-onupdate': + $this->constraint['onupdate'] .= $data; + break; + case 'database-table-declaration-foreign-deferrable': + $this->constraint['deferrable'] .= $data; + break; + case 'database-table-declaration-foreign-initiallydeferred': + $this->constraint['initiallydeferred'] .= $data; + break; + case 'database-table-declaration-foreign-field': + $this->field_name .= $data; + break; + case 'database-table-declaration-foreign-references-table': + $this->constraint['references']['table'] .= $data; + break; + case 'database-table-declaration-foreign-references-field': + $this->field_name .= $data; + break; + + /* Sequence declaration */ + case 'database-sequence-name': + $this->sequence_name .= $data; + break; + case 'database-sequence-was': + $this->sequence['was'] .= $data; + break; + case 'database-sequence-start': + $this->sequence['start'] .= $data; + break; + case 'database-sequence-description': + $this->sequence['description'] .= $data; + break; + case 'database-sequence-comments': + $this->sequence['comments'] .= $data; + break; + case 'database-sequence-on-table': + $this->sequence['on']['table'] .= $data; + break; + case 'database-sequence-on-field': + $this->sequence['on']['field'] .= $data; + break; + } + } +} + +?> diff --git a/inc/MDB2/Schema/Parser2.php b/inc/MDB2/Schema/Parser2.php new file mode 100644 index 0000000000..01318473fd --- /dev/null +++ b/inc/MDB2/Schema/Parser2.php @@ -0,0 +1,624 @@ + + * + * @category Database + * @package MDB2_Schema + * @author Igor Feghali + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @version CVS: $Id: Parser2.php,v 1.12 2008/11/30 03:34:00 clockwerx Exp $ + * @link http://pear.php.net/packages/MDB2_Schema + */ + +require_once 'XML/Unserializer.php'; +require_once 'MDB2/Schema/Validate.php'; + +/** + * Parses an XML schema file + * + * @category Database + * @package MDB2_Schema + * @author Lukas Smith + * @author Igor Feghali + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/packages/MDB2_Schema + */ +class MDB2_Schema_Parser2 extends XML_Unserializer +{ + var $database_definition = array(); + + var $database_loaded = array(); + + var $variables = array(); + + var $error; + + var $structure = false; + + var $val; + + var $options = array(); + + var $table = array(); + + var $table_name = ''; + + var $field = array(); + + var $field_name = ''; + + var $index = array(); + + var $index_name = ''; + + var $constraint = array(); + + var $constraint_name = ''; + + var $sequence = array(); + + var $sequence_name = ''; + + var $init = array(); + + function __construct($variables, $fail_on_invalid_names = true, $structure = false, $valid_types = array(), $force_defaults = true) + { + // force ISO-8859-1 due to different defaults for PHP4 and PHP5 + // todo: this probably needs to be investigated some more and cleaned up + $this->options['encoding'] = 'ISO-8859-1'; + + $this->options['XML_UNSERIALIZER_OPTION_ATTRIBUTES_PARSE'] = true; + $this->options['XML_UNSERIALIZER_OPTION_ATTRIBUTES_ARRAYKEY'] = false; + + $this->options['forceEnum'] = array('table', 'field', 'index', 'foreign', 'insert', 'update', 'delete', 'sequence'); + + /* + * todo: find a way to force the following items not to be parsed as arrays + * as it cause problems in functions with multiple arguments + */ + //$this->options['forceNEnum'] = array('value', 'column'); + $this->variables = $variables; + $this->structure = $structure; + + $this->val =& new MDB2_Schema_Validate($fail_on_invalid_names, $valid_types, $force_defaults); + parent::XML_Unserializer($this->options); + } + + function MDB2_Schema_Parser2($variables, $fail_on_invalid_names = true, $structure = false, $valid_types = array(), $force_defaults = true) + { + $this->__construct($variables, $fail_on_invalid_names, $structure, $valid_types, $force_defaults); + } + + function parse() + { + $result = $this->unserialize($this->filename, true); + + if (PEAR::isError($result)) { + return $result; + } else { + $this->database_loaded = $this->getUnserializedData(); + return $this->fixDatabaseKeys($this->database_loaded); + } + } + + function setInputFile($filename) + { + $this->filename = $filename; + return MDB2_OK; + } + + function renameKey(&$arr, $oKey, $nKey) + { + $arr[$nKey] = &$arr[$oKey]; + unset($arr[$oKey]); + } + + function fixDatabaseKeys($database) + { + $this->database_definition = array( + 'name' => '', + 'create' => '', + 'overwrite' => '', + 'charset' => '', + 'description' => '', + 'comments' => '', + 'tables' => array(), + 'sequences' => array() + ); + + if (!empty($database['name'])) { + $this->database_definition['name'] = $database['name']; + } + if (!empty($database['create'])) { + $this->database_definition['create'] = $database['create']; + } + if (!empty($database['overwrite'])) { + $this->database_definition['overwrite'] = $database['overwrite']; + } + if (!empty($database['charset'])) { + $this->database_definition['charset'] = $database['charset']; + } + if (!empty($database['description'])) { + $this->database_definition['description'] = $database['description']; + } + if (!empty($database['comments'])) { + $this->database_definition['comments'] = $database['comments']; + } + + if (!empty($database['table']) && is_array($database['table'])) { + foreach ($database['table'] as $table) { + $this->fixTableKeys($table); + } + } + + if (!empty($database['sequence']) && is_array($database['sequence'])) { + foreach ($database['sequence'] as $sequence) { + $this->fixSequenceKeys($sequence); + } + } + + $result = $this->val->validateDatabase($this->database_definition); + if (PEAR::isError($result)) { + return $this->raiseError($result->getUserinfo()); + } + + return MDB2_OK; + } + + function fixTableKeys($table) + { + $this->table = array( + 'was' => '', + 'description' => '', + 'comments' => '', + 'fields' => array(), + 'indexes' => array(), + 'constraints' => array(), + 'initialization' => array() + ); + + if (!empty($table['name'])) { + $this->table_name = $table['name']; + } else { + $this->table_name = ''; + } + if (!empty($table['was'])) { + $this->table['was'] = $table['was']; + } + if (!empty($table['description'])) { + $this->table['description'] = $table['description']; + } + if (!empty($table['comments'])) { + $this->table['comments'] = $table['comments']; + } + + if (!empty($table['declaration']) && is_array($table['declaration'])) { + if (!empty($table['declaration']['field']) && is_array($table['declaration']['field'])) { + foreach ($table['declaration']['field'] as $field) { + $this->fixTableFieldKeys($field); + } + } + + if (!empty($table['declaration']['index']) && is_array($table['declaration']['index'])) { + foreach ($table['declaration']['index'] as $index) { + $this->fixTableIndexKeys($index); + } + } + + if (!empty($table['declaration']['foreign']) && is_array($table['declaration']['foreign'])) { + foreach ($table['declaration']['foreign'] as $constraint) { + $this->fixTableConstraintKeys($constraint); + } + } + } + + if (!empty($table['initialization']) && is_array($table['initialization'])) { + if (!empty($table['initialization']['insert']) && is_array($table['initialization']['insert'])) { + foreach ($table['initialization']['insert'] as $init) { + $this->fixTableInitializationKeys($init, 'insert'); + } + } + if (!empty($table['initialization']['update']) && is_array($table['initialization']['update'])) { + foreach ($table['initialization']['update'] as $init) { + $this->fixTableInitializationKeys($init, 'update'); + } + } + if (!empty($table['initialization']['delete']) && is_array($table['initialization']['delete'])) { + foreach ($table['initialization']['delete'] as $init) { + $this->fixTableInitializationKeys($init, 'delete'); + } + } + } + + $result = $this->val->validateTable($this->database_definition['tables'], $this->table, $this->table_name); + if (PEAR::isError($result)) { + return $this->raiseError($result->getUserinfo()); + } else { + $this->database_definition['tables'][$this->table_name] = $this->table; + } + + return MDB2_OK; + } + + function fixTableFieldKeys($field) + { + $this->field = array(); + if (!empty($field['name'])) { + $this->field_name = $field['name']; + } else { + $this->field_name = ''; + } + if (!empty($field['was'])) { + $this->field['was'] = $field['was']; + } + if (!empty($field['type'])) { + $this->field['type'] = $field['type']; + } + if (!empty($field['fixed'])) { + $this->field['fixed'] = $field['fixed']; + } + if (isset($field['default'])) { + $this->field['default'] = $field['default']; + } + if (!empty($field['notnull'])) { + $this->field['notnull'] = $field['notnull']; + } + if (!empty($field['autoincrement'])) { + $this->field['autoincrement'] = $field['autoincrement']; + } + if (!empty($field['unsigned'])) { + $this->field['unsigned'] = $field['unsigned']; + } + if (!empty($field['length'])) { + $this->field['length'] = $field['length']; + } + if (!empty($field['description'])) { + $this->field['description'] = $field['description']; + } + if (!empty($field['comments'])) { + $this->field['comments'] = $field['comments']; + } + + $result = $this->val->validateField($this->table['fields'], $this->field, $this->field_name); + if (PEAR::isError($result)) { + return $this->raiseError($result->getUserinfo()); + } else { + $this->table['fields'][$this->field_name] = $this->field; + } + + return MDB2_OK; + } + + function fixTableIndexKeys($index) + { + $this->index = array( + 'was' => '', + 'unique' =>'', + 'primary' => '', + 'fields' => array() + ); + + if (!empty($index['name'])) { + $this->index_name = $index['name']; + } else { + $this->index_name = ''; + } + if (!empty($index['was'])) { + $this->index['was'] = $index['was']; + } + if (!empty($index['unique'])) { + $this->index['unique'] = $index['unique']; + } + if (!empty($index['primary'])) { + $this->index['primary'] = $index['primary']; + } + if (!empty($index['field'])) { + foreach ($index['field'] as $field) { + if (!empty($field['name'])) { + $this->field_name = $field['name']; + } else { + $this->field_name = ''; + } + $this->field = array( + 'sorting' => '', + 'length' => '' + ); + + if (!empty($field['sorting'])) { + $this->field['sorting'] = $field['sorting']; + } + if (!empty($field['length'])) { + $this->field['length'] = $field['length']; + } + + $result = $this->val->validateIndexField($this->index['fields'], $this->field, $this->field_name); + if (PEAR::isError($result)) { + return $this->raiseError($result->getUserinfo()); + } + + $this->index['fields'][$this->field_name] = $this->field; + } + } + + $result = $this->val->validateIndex($this->table['indexes'], $this->index, $this->index_name); + if (PEAR::isError($result)) { + return $this->raiseError($result->getUserinfo()); + } else { + $this->table['indexes'][$this->index_name] = $this->index; + } + + return MDB2_OK; + } + + function fixTableConstraintKeys($constraint) + { + $this->constraint = array( + 'was' => '', + 'match' => '', + 'ondelete' => '', + 'onupdate' => '', + 'deferrable' => '', + 'initiallydeferred' => '', + 'foreign' => true, + 'fields' => array(), + 'references' => array('table' => '', 'fields' => array()) + ); + + if (!empty($constraint['name'])) { + $this->constraint_name = $constraint['name']; + } else { + $this->constraint_name = ''; + } + if (!empty($constraint['was'])) { + $this->constraint['was'] = $constraint['was']; + } + if (!empty($constraint['match'])) { + $this->constraint['match'] = $constraint['match']; + } + if (!empty($constraint['ondelete'])) { + $this->constraint['ondelete'] = $constraint['ondelete']; + } + if (!empty($constraint['onupdate'])) { + $this->constraint['onupdate'] = $constraint['onupdate']; + } + if (!empty($constraint['deferrable'])) { + $this->constraint['deferrable'] = $constraint['deferrable']; + } + if (!empty($constraint['initiallydeferred'])) { + $this->constraint['initiallydeferred'] = $constraint['initiallydeferred']; + } + if (!empty($constraint['field']) && is_array($constraint['field'])) { + foreach ($constraint['field'] as $field) { + $result = $this->val->validateConstraintField($this->constraint['fields'], $field); + if (PEAR::isError($result)) { + return $this->raiseError($result->getUserinfo()); + } + + $this->constraint['fields'][$field] = ''; + } + } + + if (!empty($constraint['references']) && is_array($constraint['references'])) { + /** + * As we forced 'table' to be enumerated + * we have to fix it on the foreign-references-table context + */ + if (!empty($constraint['references']['table']) && is_array($constraint['references']['table'])) { + $this->constraint['references']['table'] = $constraint['references']['table'][0]; + } + + if (!empty($constraint['references']['field']) && is_array($constraint['references']['field'])) { + foreach ($constraint['references']['field'] as $field) { + $result = $this->val->validateConstraintReferencedField($this->constraint['references']['fields'], $field); + if (PEAR::isError($result)) { + return $this->raiseError($result->getUserinfo()); + } + + $this->constraint['references']['fields'][$field] = ''; + } + } + } + + $result = $this->val->validateConstraint($this->table['constraints'], $this->constraint, $this->constraint_name); + if (PEAR::isError($result)) { + return $this->raiseError($result->getUserinfo()); + } else { + $this->table['constraints'][$this->constraint_name] = $this->constraint; + } + + return MDB2_OK; + } + + function fixTableInitializationKeys($element, $type = '') + { + if (!empty($element['select']) && is_array($element['select'])) { + $this->fixTableInitializationDataKeys($element['select']); + $this->init = array( 'select' => $this->init ); + } else { + $this->fixTableInitializationDataKeys($element); + } + + $this->table['initialization'][] = array( 'type' => $type, 'data' => $this->init ); + } + + function fixTableInitializationDataKeys($element) + { + $this->init = array(); + if (!empty($element['field']) && is_array($element['field'])) { + foreach ($element['field'] as $field) { + $name = $field['name']; + unset($field['name']); + + $this->setExpression($field); + $this->init['field'][] = array( 'name' => $name, 'group' => $field ); + } + } + /** + * As we forced 'table' to be enumerated + * we have to fix it on the insert-select context + */ + if (!empty($element['table']) && is_array($element['table'])) { + $this->init['table'] = $element['table'][0]; + } + if (!empty($element['where']) && is_array($element['where'])) { + $this->init['where'] = $element['where']; + $this->setExpression($this->init['where']); + } + } + + function setExpression(&$arr) + { + $element = each($arr); + + $arr = array( 'type' => $element['key'] ); + + $element = $element['value']; + + switch ($arr['type']) { + case 'null': + break; + case 'value': + case 'column': + $arr['data'] = $element; + break; + case 'function': + if (!empty($element) + && is_array($element) + ) { + $arr['data'] = array( 'name' => $element['name'] ); + unset($element['name']); + + foreach ($element as $type => $value) { + if (!empty($value)) { + if (is_array($value)) { + foreach ($value as $argument) { + $argument = array( $type => $argument ); + $this->setExpression($argument); + $arr['data']['arguments'][] = $argument; + } + } else { + $arr['data']['arguments'][] = array( 'type' => $type, 'data' => $value ); + } + } + } + } + break; + case 'expression': + $arr['data'] = array( 'operants' => array(), 'operator' => $element['operator'] ); + unset($element['operator']); + + foreach ($element as $k => $v) { + $argument = array( $k => $v ); + $this->setExpression($argument); + $arr['data']['operants'][] = $argument; + } + break; + } + } + + function fixSequenceKeys($sequence) + { + $this->sequence = array( + 'was' => '', + 'start' => '', + 'description' => '', + 'comments' => '', + 'on' => array('table' => '', 'field' => '') + ); + + if (!empty($sequence['name'])) { + $this->sequence_name = $sequence['name']; + } else { + $this->sequence_name = ''; + } + if (!empty($sequence['was'])) { + $this->sequence['was'] = $sequence['was']; + } + if (!empty($sequence['start'])) { + $this->sequence['start'] = $sequence['start']; + } + if (!empty($sequence['description'])) { + $this->sequence['description'] = $sequence['description']; + } + if (!empty($sequence['comments'])) { + $this->sequence['comments'] = $sequence['comments']; + } + if (!empty($sequence['on']) && is_array($sequence['on'])) { + /** + * As we forced 'table' to be enumerated + * we have to fix it on the sequence-on-table context + */ + if (!empty($sequence['on']['table']) && is_array($sequence['on']['table'])) { + $this->sequence['on']['table'] = $sequence['on']['table'][0]; + } + + /** + * As we forced 'field' to be enumerated + * we have to fix it on the sequence-on-field context + */ + if (!empty($sequence['on']['field']) && is_array($sequence['on']['field'])) { + $this->sequence['on']['field'] = $sequence['on']['field'][0]; + } + } + + $result = $this->val->validateSequence($this->database_definition['sequences'], $this->sequence, $this->sequence_name); + if (PEAR::isError($result)) { + return $this->raiseError($result->getUserinfo()); + } else { + $this->database_definition['sequences'][$this->sequence_name] = $this->sequence; + } + + return MDB2_OK; + } + + function &raiseError($msg = null, $ecode = MDB2_SCHEMA_ERROR_PARSE) + { + if (is_null($this->error)) { + $error = 'Parser error: '.$msg."\n"; + + $this->error =& MDB2_Schema::raiseError($ecode, null, null, $error); + } + return $this->error; + } +} + +?> diff --git a/inc/MDB2/Schema/Reserved/ibase.php b/inc/MDB2/Schema/Reserved/ibase.php new file mode 100644 index 0000000000..b208abc83a --- /dev/null +++ b/inc/MDB2/Schema/Reserved/ibase.php @@ -0,0 +1,436 @@ + | +// +----------------------------------------------------------------------+ +// +// }}} +// {{{ $GLOBALS['_MDB2_Schema_Reserved']['ibase'] +/** + * Has a list of reserved words of Interbase/Firebird + * + * @package MDB2_Schema + * @category Database + * @access protected + * @author Lorenzo Alberton + */ +$GLOBALS['_MDB2_Schema_Reserved']['ibase'] = array( + 'ABS', + 'ABSOLUTE', + 'ACTION', + 'ACTIVE', + 'ADD', + 'ADMIN', + 'AFTER', + 'ALL', + 'ALLOCATE', + 'ALTER', + 'AND', + 'ANY', + 'ARE', + 'AS', + 'ASC', + 'ASCENDING', + 'ASSERTION', + 'AT', + 'AUTHORIZATION', + 'AUTO', + 'AUTODDL', + 'AVG', + 'BACKUP', + 'BASE_NAME', + 'BASED', + 'BASENAME', + 'BEFORE', + 'BEGIN', + 'BETWEEN', + 'BIGINT', + 'BIT', + 'BIT_LENGTH', + 'BLOB', + 'BLOCK', + 'BLOBEDIT', + 'BOOLEAN', + 'BOTH', + 'BOTH', + 'BREAK', + 'BUFFER', + 'BY', + 'CACHE', + 'CASCADE', + 'CASCADED', + 'CASE', + 'CASE', + 'CAST', + 'CATALOG', + 'CHAR', + 'CHAR_LENGTH', + 'CHARACTER', + 'CHARACTER_LENGTH', + 'CHECK', + 'CHECK_POINT_LEN', + 'CHECK_POINT_LENGTH', + 'CLOSE', + 'COALESCE', + 'COLLATE', + 'COLLATION', + 'COLUMN', + 'COMMENT', + 'COMMIT', + 'COMMITTED', + 'COMPILETIME', + 'COMPUTED', + 'CONDITIONAL', + 'CONNECT', + 'CONNECTION', + 'CONSTRAINT', + 'CONSTRAINTS', + 'CONTAINING', + 'CONTINUE', + 'CONVERT', + 'CORRESPONDING', + 'COUNT', + 'CREATE', + 'CROSS', + 'CSTRING', + 'CURRENT', + 'CURRENT_CONNECTION', + 'CURRENT_DATE', + 'CURRENT_ROLE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_TRANSACTION', + 'CURRENT_USER', + 'DATABASE', + 'DATE', + 'DAY', + 'DB_KEY', + 'DEALLOCATE', + 'DEBUG', + 'DEC', + 'DECIMAL', + 'DECLARE', + 'DEFAULT', + 'DEFERRABLE', + 'DEFERRED', + 'DELETE', + 'DELETING', + 'DESC', + 'DESCENDING', + 'DESCRIBE', + 'DESCRIPTOR', + 'DIAGNOSTICS', + 'DIFFERENCE', + 'DISCONNECT', + 'DISPLAY', + 'DISTINCT', + 'DO', + 'DOMAIN', + 'DOUBLE', + 'DROP', + 'ECHO', + 'EDIT', + 'ELSE', + 'END', + 'END-EXEC', + 'ENTRY_POINT', + 'ESCAPE', + 'EVENT', + 'EXCEPT', + 'EXCEPTION', + 'EXEC', + 'EXECUTE', + 'EXISTS', + 'EXIT', + 'EXTERN', + 'EXTERNAL', + 'EXTRACT', + 'FALSE', + 'FETCH', + 'FILE', + 'FILTER', + 'FIRST', + 'FLOAT', + 'FOR', + 'FOREIGN', + 'FOUND', + 'FREE_IT', + 'FROM', + 'FULL', + 'FUNCTION', + 'GDSCODE', + 'GEN_ID', + 'GENERATOR', + 'GET', + 'GLOBAL', + 'GO', + 'GOTO', + 'GRANT', + 'GROUP', + 'GROUP_COMMIT_WAIT', + 'GROUP_COMMIT_WAIT_TIME', + 'HAVING', + 'HELP', + 'HOUR', + 'IDENTITY', + 'IF', + 'IIF', + 'IMMEDIATE', + 'IN', + 'INACTIVE', + 'INDEX', + 'INDICATOR', + 'INIT', + 'INITIALLY', + 'INNER', + 'INPUT', + 'INPUT_TYPE', + 'INSENSITIVE', + 'INSERT', + 'INSERTING', + 'INT', + 'INTEGER', + 'INTERSECT', + 'INTERVAL', + 'INTO', + 'IS', + 'ISOLATION', + 'ISQL', + 'JOIN', + 'KEY', + 'LANGUAGE', + 'LAST', + 'LC_MESSAGES', + 'LC_TYPE', + 'LEADING', + 'LEADING', + 'LEADING', + 'LEAVE', + 'LEFT', + 'LENGTH', + 'LEV', + 'LEVEL', + 'LIKE', + 'LOCAL', + 'LOCK', + 'LOG_BUF_SIZE', + 'LOG_BUFFER_SIZE', + 'LOGFILE', + 'LONG', + 'LOWER', + 'MANUAL', + 'MATCH', + 'MAX', + 'MAX_SEGMENT', + 'MAXIMUM', + 'MAXIMUM_SEGMENT', + 'MERGE', + 'MESSAGE', + 'MIN', + 'MINIMUM', + 'MINUTE', + 'MODULE', + 'MODULE_NAME', + 'MONTH', + 'NAMES', + 'NATIONAL', + 'NATURAL', + 'NCHAR', + 'NEXT', + 'NO', + 'NOAUTO', + 'NOT', + 'NULL', + 'NULLIF', + 'NULLS', + 'NUM_LOG_BUFFERS', + 'NUM_LOG_BUFS', + 'NUMERIC', + 'OCTET_LENGTH', + 'OF', + 'ON', + 'ONLY', + 'OPEN', + 'OPTION', + 'OR', + 'ORDER', + 'OUTER', + 'OUTPUT', + 'OUTPUT_TYPE', + 'OVERFLOW', + 'OVERLAPS', + 'PAD', + 'PAGE', + 'PAGE_SIZE', + 'PAGELENGTH', + 'PAGES', + 'PARAMETER', + 'PARTIAL', + 'PASSWORD', + 'PERCENT', + 'PLAN', + 'POSITION', + 'POST_EVENT', + 'PRECISION', + 'PREPARE', + 'PRESERVE', + 'PRIMARY', + 'PRIOR', + 'PRIVILEGES', + 'PROCEDURE', + 'PUBLIC', + 'QUIT', + 'RAW_PARTITIONS', + 'RDB$DB_KEY', + 'READ', + 'REAL', + 'RECORD_VERSION', + 'RECREATE', + 'RECREATE ROW_COUNT', + 'REFERENCES', + 'RELATIVE', + 'RELEASE', + 'RESERV', + 'RESERVING', + 'RESTART', + 'RESTRICT', + 'RETAIN', + 'RETURN', + 'RETURNING', + 'RETURNING_VALUES', + 'RETURNS', + 'REVOKE', + 'RIGHT', + 'ROLE', + 'ROLLBACK', + 'ROW_COUNT', + 'ROWS', + 'RUNTIME', + 'SAVEPOINT', + 'SCALAR_ARRAY', + 'SCHEMA', + 'SCROLL', + 'SECOND', + 'SECTION', + 'SELECT', + 'SEQUENCE', + 'SESSION', + 'SESSION_USER', + 'SET', + 'SHADOW', + 'SHARED', + 'SHELL', + 'SHOW', + 'SINGULAR', + 'SIZE', + 'SKIP', + 'SMALLINT', + 'SNAPSHOT', + 'SOME', + 'SORT', + 'SPACE', + 'SQL', + 'SQLCODE', + 'SQLERROR', + 'SQLSTATE', + 'SQLWARNING', + 'STABILITY', + 'STARTING', + 'STARTS', + 'STATEMENT', + 'STATIC', + 'STATISTICS', + 'SUB_TYPE', + 'SUBSTRING', + 'SUM', + 'SUSPEND', + 'SYSTEM_USER', + 'TABLE', + 'TEMPORARY', + 'TERMINATOR', + 'THEN', + 'TIES', + 'TIME', + 'TIMESTAMP', + 'TIMEZONE_HOUR', + 'TIMEZONE_MINUTE', + 'TO', + 'TRAILING', + 'TRANSACTION', + 'TRANSLATE', + 'TRANSLATION', + 'TRIGGER', + 'TRIM', + 'TRUE', + 'TYPE', + 'UNCOMMITTED', + 'UNION', + 'UNIQUE', + 'UNKNOWN', + 'UPDATE', + 'UPDATING', + 'UPPER', + 'USAGE', + 'USER', + 'USING', + 'VALUE', + 'VALUES', + 'VARCHAR', + 'VARIABLE', + 'VARYING', + 'VERSION', + 'VIEW', + 'WAIT', + 'WEEKDAY', + 'WHEN', + 'WHENEVER', + 'WHERE', + 'WHILE', + 'WITH', + 'WORK', + 'WRITE', + 'YEAR', + 'YEARDAY', + 'ZONE', +); +// }}} +?> \ No newline at end of file diff --git a/inc/MDB2/Schema/Reserved/mssql.php b/inc/MDB2/Schema/Reserved/mssql.php new file mode 100644 index 0000000000..74ac688578 --- /dev/null +++ b/inc/MDB2/Schema/Reserved/mssql.php @@ -0,0 +1,258 @@ + | +// +----------------------------------------------------------------------+ +// }}} +// {{{ $GLOBALS['_MDB2_Schema_Reserved']['mssql'] +/** + * Has a list of all the reserved words for mssql. + * + * @package MDB2_Schema + * @category Database + * @access protected + * @author David Coallier + */ +$GLOBALS['_MDB2_Schema_Reserved']['mssql'] = array( + 'ADD', + 'CURRENT_TIMESTAMP', + 'GROUP', + 'OPENQUERY', + 'SERIALIZABLE', + 'ALL', + 'CURRENT_USER', + 'HAVING', + 'OPENROWSET', + 'SESSION_USER', + 'ALTER', + 'CURSOR', + 'HOLDLOCK', + 'OPTION', + 'SET', + 'AND', + 'DATABASE', + 'IDENTITY', + 'OR', + 'SETUSER', + 'ANY', + 'DBCC', + 'IDENTITYCOL', + 'ORDER', + 'SHUTDOWN', + 'AS', + 'DEALLOCATE', + 'IDENTITY_INSERT', + 'OUTER', + 'SOME', + 'ASC', + 'DECLARE', + 'IF', + 'OVER', + 'STATISTICS', + 'AUTHORIZATION', + 'DEFAULT', + 'IN', + 'PERCENT', + 'SUM', + 'AVG', + 'DELETE', + 'INDEX', + 'PERM', + 'SYSTEM_USER', + 'BACKUP', + 'DENY', + 'INNER', + 'PERMANENT', + 'TABLE', + 'BEGIN', + 'DESC', + 'INSERT', + 'PIPE', + 'TAPE', + 'BETWEEN', + 'DISK', + 'INTERSECT', + 'PLAN', + 'TEMP', + 'BREAK', + 'DISTINCT', + 'INTO', + 'PRECISION', + 'TEMPORARY', + 'BROWSE', + 'DISTRIBUTED', + 'IS', + 'PREPARE', + 'TEXTSIZE', + 'BULK', + 'DOUBLE', + 'ISOLATION', + 'PRIMARY', + 'THEN', + 'BY', + 'DROP', + 'JOIN', + 'PRINT', + 'TO', + 'CASCADE', + 'DUMMY', + 'KEY', + 'PRIVILEGES', + 'TOP', + 'CASE', + 'DUMP', + 'KILL', + 'PROC', + 'TRAN', + 'CHECK', + 'ELSE', + 'LEFT', + 'PROCEDURE', + 'TRANSACTION', + 'CHECKPOINT', + 'END', + 'LEVEL', + 'PROCESSEXIT', + 'TRIGGER', + 'CLOSE', + 'ERRLVL', + 'LIKE', + 'PUBLIC', + 'TRUNCATE', + 'CLUSTERED', + 'ERROREXIT', + 'LINENO', + 'RAISERROR', + 'TSEQUAL', + 'COALESCE', + 'ESCAPE', + 'LOAD', + 'READ', + 'UNCOMMITTED', + 'COLUMN', + 'EXCEPT', + 'MAX', + 'READTEXT', + 'UNION', + 'COMMIT', + 'EXEC', + 'MIN', + 'RECONFIGURE', + 'UNIQUE', + 'COMMITTED', + 'EXECUTE', + 'MIRROREXIT', + 'REFERENCES', + 'UPDATE', + 'COMPUTE', + 'EXISTS', + 'NATIONAL', + 'REPEATABLE', + 'UPDATETEXT', + 'CONFIRM', + 'EXIT', + 'NOCHECK', + 'REPLICATION', + 'USE', + 'CONSTRAINT', + 'FETCH', + 'NONCLUSTERED', + 'RESTORE', + 'USER', + 'CONTAINS', + 'FILE', + 'NOT', + 'RESTRICT', + 'VALUES', + 'CONTAINSTABLE', + 'FILLFACTOR', + 'NULL', + 'RETURN', + 'VARYING', + 'CONTINUE', + 'FLOPPY', + 'NULLIF', + 'REVOKE', + 'VIEW', + 'CONTROLROW', + 'FOR', + 'OF', + 'RIGHT', + 'WAITFOR', + 'CONVERT', + 'FOREIGN', + 'OFF', + 'ROLLBACK', + 'WHEN', + 'COUNT', + 'FREETEXT', + 'OFFSETS', + 'ROWCOUNT', + 'WHERE', + 'CREATE', + 'FREETEXTTABLE', + 'ON', + 'ROWGUIDCOL', + 'WHILE', + 'CROSS', + 'FROM', + 'ONCE', + 'RULE', + 'WITH', + 'CURRENT', + 'FULL', + 'ONLY', + 'SAVE', + 'WORK', + 'CURRENT_DATE', + 'GOTO', + 'OPEN', + 'SCHEMA', + 'WRITETEXT', + 'CURRENT_TIME', + 'GRANT', + 'OPENDATASOURCE', + 'SELECT', +); +//}}} + +?> diff --git a/inc/MDB2/Schema/Reserved/mysql.php b/inc/MDB2/Schema/Reserved/mysql.php new file mode 100644 index 0000000000..4f0575e0bb --- /dev/null +++ b/inc/MDB2/Schema/Reserved/mysql.php @@ -0,0 +1,284 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: mysql.php,v 1.3 2006/03/01 12:16:40 lsmith Exp $ +// }}} +// {{{ $GLOBALS['_MDB2_Schema_Reserved']['mysql'] +/** + * Has a list of reserved words of mysql + * + * @package MDB2_Schema + * @category Database + * @access protected + * @author David Coalier + */ +$GLOBALS['_MDB2_Schema_Reserved']['mysql'] = array( + 'ADD', + 'ALL', + 'ALTER', + 'ANALYZE', + 'AND', + 'AS', + 'ASC', + 'ASENSITIVE', + 'BEFORE', + 'BETWEEN', + 'BIGINT', + 'BINARY', + 'BLOB', + 'BOTH', + 'BY', + 'CALL', + 'CASCADE', + 'CASE', + 'CHANGE', + 'CHAR', + 'CHARACTER', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'CONDITION', + 'CONNECTION', + 'CONSTRAINT', + 'CONTINUE', + 'CONVERT', + 'CREATE', + 'CROSS', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'CURSOR', + 'DATABASE', + 'DATABASES', + 'DAY_HOUR', + 'DAY_MICROSECOND', + 'DAY_MINUTE', + 'DAY_SECOND', + 'DEC', + 'DECIMAL', + 'DECLARE', + 'DEFAULT', + 'DELAYED', + 'DELETE', + 'DESC', + 'DESCRIBE', + 'DETERMINISTIC', + 'DISTINCT', + 'DISTINCTROW', + 'DIV', + 'DOUBLE', + 'DROP', + 'DUAL', + 'EACH', + 'ELSE', + 'ELSEIF', + 'ENCLOSED', + 'ESCAPED', + 'EXISTS', + 'EXIT', + 'EXPLAIN', + 'FALSE', + 'FETCH', + 'FLOAT', + 'FLOAT4', + 'FLOAT8', + 'FOR', + 'FORCE', + 'FOREIGN', + 'FROM', + 'FULLTEXT', + 'GOTO', + 'GRANT', + 'GROUP', + 'HAVING', + 'HIGH_PRIORITY', + 'HOUR_MICROSECOND', + 'HOUR_MINUTE', + 'HOUR_SECOND', + 'IF', + 'IGNORE', + 'IN', + 'INDEX', + 'INFILE', + 'INNER', + 'INOUT', + 'INSENSITIVE', + 'INSERT', + 'INT', + 'INT1', + 'INT2', + 'INT3', + 'INT4', + 'INT8', + 'INTEGER', + 'INTERVAL', + 'INTO', + 'IS', + 'ITERATE', + 'JOIN', + 'KEY', + 'KEYS', + 'KILL', + 'LABEL', + 'LEADING', + 'LEAVE', + 'LEFT', + 'LIKE', + 'LIMIT', + 'LINES', + 'LOAD', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'LOCK', + 'LONG', + 'LONGBLOB', + 'LONGTEXT', + 'LOOP', + 'LOW_PRIORITY', + 'MATCH', + 'MEDIUMBLOB', + 'MEDIUMINT', + 'MEDIUMTEXT', + 'MIDDLEINT', + 'MINUTE_MICROSECOND', + 'MINUTE_SECOND', + 'MOD', + 'MODIFIES', + 'NATURAL', + 'NOT', + 'NO_WRITE_TO_BINLOG', + 'NULL', + 'NUMERIC', + 'ON', + 'OPTIMIZE', + 'OPTION', + 'OPTIONALLY', + 'OR', + 'ORDER', + 'OUT', + 'OUTER', + 'OUTFILE', + 'PRECISION', + 'PRIMARY', + 'PROCEDURE', + 'PURGE', + 'RAID0', + 'READ', + 'READS', + 'REAL', + 'REFERENCES', + 'REGEXP', + 'RELEASE', + 'RENAME', + 'REPEAT', + 'REPLACE', + 'REQUIRE', + 'RESTRICT', + 'RETURN', + 'REVOKE', + 'RIGHT', + 'RLIKE', + 'SCHEMA', + 'SCHEMAS', + 'SECOND_MICROSECOND', + 'SELECT', + 'SENSITIVE', + 'SEPARATOR', + 'SET', + 'SHOW', + 'SMALLINT', + 'SONAME', + 'SPATIAL', + 'SPECIFIC', + 'SQL', + 'SQLEXCEPTION', + 'SQLSTATE', + 'SQLWARNING', + 'SQL_BIG_RESULT', + 'SQL_CALC_FOUND_ROWS', + 'SQL_SMALL_RESULT', + 'SSL', + 'STARTING', + 'STRAIGHT_JOIN', + 'TABLE', + 'TERMINATED', + 'THEN', + 'TINYBLOB', + 'TINYINT', + 'TINYTEXT', + 'TO', + 'TRAILING', + 'TRIGGER', + 'TRUE', + 'UNDO', + 'UNION', + 'UNIQUE', + 'UNLOCK', + 'UNSIGNED', + 'UPDATE', + 'USAGE', + 'USE', + 'USING', + 'UTC_DATE', + 'UTC_TIME', + 'UTC_TIMESTAMP', + 'VALUES', + 'VARBINARY', + 'VARCHAR', + 'VARCHARACTER', + 'VARYING', + 'WHEN', + 'WHERE', + 'WHILE', + 'WITH', + 'WRITE', + 'X509', + 'XOR', + 'YEAR_MONTH', + 'ZEROFILL', + ); + // }}} +?> diff --git a/inc/MDB2/Schema/Reserved/oci8.php b/inc/MDB2/Schema/Reserved/oci8.php new file mode 100644 index 0000000000..57fe12ddca --- /dev/null +++ b/inc/MDB2/Schema/Reserved/oci8.php @@ -0,0 +1,171 @@ + | +// +----------------------------------------------------------------------+ +// }}} +// {{{ $GLOBALS['_MDB2_Schema_Reserved']['oci8'] +/** + * Has a list of all the reserved words for oracle. + * + * @package MDB2_Schema + * @category Database + * @access protected + * @author David Coallier + */ +$GLOBALS['_MDB2_Schema_Reserved']['oci8'] = array( + 'ACCESS', + 'ELSE', + 'MODIFY', + 'START', + 'ADD', + 'EXCLUSIVE', + 'NOAUDIT', + 'SELECT', + 'ALL', + 'EXISTS', + 'NOCOMPRESS', + 'SESSION', + 'ALTER', + 'FILE', + 'NOT', + 'SET', + 'AND', + 'FLOAT', + 'NOTFOUND ', + 'SHARE', + 'ANY', + 'FOR', + 'NOWAIT', + 'SIZE', + 'ARRAYLEN', + 'FROM', + 'NULL', + 'SMALLINT', + 'AS', + 'GRANT', + 'NUMBER', + 'SQLBUF', + 'ASC', + 'GROUP', + 'OF', + 'SUCCESSFUL', + 'AUDIT', + 'HAVING', + 'OFFLINE ', + 'SYNONYM', + 'BETWEEN', + 'IDENTIFIED', + 'ON', + 'SYSDATE', + 'BY', + 'IMMEDIATE', + 'ONLINE', + 'TABLE', + 'CHAR', + 'IN', + 'OPTION', + 'THEN', + 'CHECK', + 'INCREMENT', + 'OR', + 'TO', + 'CLUSTER', + 'INDEX', + 'ORDER', + 'TRIGGER', + 'COLUMN', + 'INITIAL', + 'PCTFREE', + 'UID', + 'COMMENT', + 'INSERT', + 'PRIOR', + 'UNION', + 'COMPRESS', + 'INTEGER', + 'PRIVILEGES', + 'UNIQUE', + 'CONNECT', + 'INTERSECT', + 'PUBLIC', + 'UPDATE', + 'CREATE', + 'INTO', + 'RAW', + 'USER', + 'CURRENT', + 'IS', + 'RENAME', + 'VALIDATE', + 'DATE', + 'LEVEL', + 'RESOURCE', + 'VALUES', + 'DECIMAL', + 'LIKE', + 'REVOKE', + 'VARCHAR', + 'DEFAULT', + 'LOCK', + 'ROW', + 'VARCHAR2', + 'DELETE', + 'LONG', + 'ROWID', + 'VIEW', + 'DESC', + 'MAXEXTENTS', + 'ROWLABEL', + 'WHENEVER', + 'DISTINCT', + 'MINUS', + 'ROWNUM', + 'WHERE', + 'DROP', + 'MODE', + 'ROWS', + 'WITH', +); +// }}} + +?> diff --git a/inc/MDB2/Schema/Reserved/pgsql.php b/inc/MDB2/Schema/Reserved/pgsql.php new file mode 100644 index 0000000000..d358e9c12f --- /dev/null +++ b/inc/MDB2/Schema/Reserved/pgsql.php @@ -0,0 +1,147 @@ + | +// +----------------------------------------------------------------------+ +// +// }}} +// {{{ $GLOBALS['_MDB2_Schema_Reserved']['pgsql'] +/** + * Has a list of reserved words of pgsql + * + * @package MDB2_Schema + * @category Database + * @access protected + * @author Marcelo Santos Araujo + */ +$GLOBALS['_MDB2_Schema_Reserved']['pgsql'] = array( + 'ALL', + 'ANALYSE', + 'ANALYZE', + 'AND', + 'ANY', + 'AS', + 'ASC', + 'AUTHORIZATION', + 'BETWEEN', + 'BINARY', + 'BOTH', + 'CASE', + 'CAST', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'CONSTRAINT', + 'CREATE', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'DEFAULT', + 'DEFERRABLE', + 'DESC', + 'DISTINCT', + 'DO', + 'ELSE', + 'END', + 'EXCEPT', + 'FALSE', + 'FOR', + 'FOREIGN', + 'FREEZE', + 'FROM', + 'FULL', + 'GRANT', + 'GROUP', + 'HAVING', + 'ILIKE', + 'IN', + 'INITIALLY', + 'INNER', + 'INTERSECT', + 'INTO', + 'IS', + 'ISNULL', + 'JOIN', + 'LEADING', + 'LEFT', + 'LIKE', + 'LIMIT', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'NATURAL', + 'NEW', + 'NOT', + 'NOTNULL', + 'NULL', + 'OFF', + 'OFFSET', + 'OLD', + 'ON', + 'ONLY', + 'OR', + 'ORDER', + 'OUTER', + 'OVERLAPS', + 'PLACING', + 'PRIMARY', + 'REFERENCES', + 'SELECT', + 'SESSION_USER', + 'SIMILAR', + 'SOME', + 'TABLE', + 'THEN', + 'TO', + 'TRAILING', + 'TRUE', + 'UNION', + 'UNIQUE', + 'USER', + 'USING', + 'VERBOSE', + 'WHEN', + 'WHERE' +); +// }}} +?> + diff --git a/inc/MDB2/Schema/Tool.php b/inc/MDB2/Schema/Tool.php new file mode 100644 index 0000000000..9689a0f6d7 --- /dev/null +++ b/inc/MDB2/Schema/Tool.php @@ -0,0 +1,560 @@ + + * $Id: Tool.php,v 1.6 2008/12/13 00:26:07 clockwerx Exp $ + * + * @category Database + * @package MDB2_Schema + * @author Christian Weiske + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @version CVS: $Id: Tool.php,v 1.6 2008/12/13 00:26:07 clockwerx Exp $ + * @link http://pear.php.net/packages/MDB2_Schema + */ + +require_once 'MDB2/Schema.php'; +require_once 'MDB2/Schema/Tool/ParameterException.php'; + +/** +* Command line tool to work with database schemas +* +* Functionality: +* - dump a database schema to stdout +* - import schema into database +* - create a diff between two schemas +* - apply diff to database +* + * @category Database + * @package MDB2_Schema + * @author Christian Weiske + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/packages/MDB2_Schema + */ +class MDB2_Schema_Tool +{ + /** + * Run the schema tool + * + * @param array $args Array of command line arguments + */ + public function __construct($args) + { + $strAction = $this->getAction($args); + try { + $this->{'do' . ucfirst($strAction)}($args); + } catch (MDB2_Schema_Tool_ParameterException $e) { + $this->{'doHelp' . ucfirst($strAction)}($e->getMessage()); + } + }//public function __construct($args) + + + + /** + * Runs the tool with command line arguments + * + * @return void + */ + public static function run() + { + $args = $GLOBALS['argv']; + array_shift($args); + + try { + $tool = new MDB2_Schema_Tool($args); + } catch (Exception $e) { + self::toStdErr($e->getMessage() . "\n"); + } + }//public static function run() + + + + /** + * Reads the first parameter from the argument array and + * returns the action. + * + * @param array &$args Command line parameters + * + * @return string Action to execute + */ + protected function getAction(&$args) + { + if (count($args) == 0) { + return 'help'; + } + $arg = array_shift($args); + switch ($arg) { + case 'h': + case 'help': + case '-h': + case '--help': + return 'help'; + case 'd': + case 'dump': + case '-d': + case '--dump': + return 'dump'; + case 'l': + case 'load': + case '-l': + case '--load': + return 'load'; + case 'i': + case 'diff': + case '-i': + case '--diff': + return 'diff'; + case 'a': + case 'apply': + case '-a': + case '--apply': + return 'apply'; + case 'n': + case 'init': + case '-i': + case '--init': + return 'init'; + default: + throw new MDB2_Schema_Tool_ParameterException("Unknown mode \"$arg\""); + } + }//protected function getAction(&$args) + + + + /** + * Writes the message to stderr + * + * @param string $msg Message to print + * + * @return void + */ + protected static function toStdErr($msg) + { + file_put_contents('php://stderr', $msg); + }//protected static function toStdErr($msg) + + + + /** + * Displays generic help to stdout + * + * @return void + */ + protected function doHelp() + { + self::toStdErr(<< '
', + 'idxname_format' => '%s', + 'debug' => true, + 'quote_identifier' => true, + 'force_defaults' => false, + 'portability' => true, + 'use_transactions' => false, + ); + return $options; + }//protected function getSchemaOptions() + + + + /** + * Checks if the passed parameter is a PEAR_Error object + * and throws an exception in that case. + * + * @param mixed $object Some variable to check + * @param string $location Where the error occured + * + * @return void + */ + protected function throwExceptionOnError($object, $location = '') + { + if (PEAR::isError($object)) { + //FIXME: exception class + //debug_print_backtrace(); + throw new Exception('Error ' . $location + . "\n" . $object->getMessage() + . "\n" . $object->getUserInfo() + ); + } + }//protected function throwExceptionOnError($object, $location = '') + + + + /** + * Loads a file or a dsn from the arguments + * + * @param array &$args Array of arguments to the program + * + * @return array Array of ('file'|'dsn', $value) + */ + protected function getFileOrDsn(&$args) + { + if (count($args) == 0) { + throw new MDB2_Schema_Tool_ParameterException('File or DSN expected'); + } + + $arg = array_shift($args); + if ($arg == '-p') { + $bAskPassword = true; + $arg = array_shift($args); + } else { + $bAskPassword = false; + } + + if (strpos($arg, '://') === false) { + if (file_exists($arg)) { + //File + return array('file', $arg); + } else { + throw new Exception('Schema file does not exist'); + } + } + + //read password if necessary + if ($bAskPassword) { + $password = $this->readPasswordFromStdin($arg); + $arg = self::setPasswordIntoDsn($arg, $password); + self::toStdErr($arg); + } + return array('dsn', $arg); + }//protected function getFileOrDsn(&$args) + + + + /** + * Takes a DSN data source name and integrates the given + * password into it. + * + * @param string $dsn Data source name + * @param string $password Password + * + * @return string DSN with password + */ + protected function setPasswordIntoDsn($dsn, $password) + { + //simple try to integrate password + if (strpos($dsn, '@') === false) { + //no @ -> no user and no password + return str_replace('://', '://:' . $password . '@', $dsn); + } else if (preg_match('|://[^:]+@|', $dsn)) { + //user only, no password + return str_replace('@', ':' . $password . '@', $dsn); + } else if (strpos($dsn, ':@') !== false) { + //abstract version + return str_replace(':@', ':' . $password . '@', $dsn); + } + + return $dsn; + }//protected function setPasswordIntoDsn($dsn, $password) + + + + /** + * Reads a password from stdin + * + * @param string $dsn DSN name to put into the message + * + * @return string Password + */ + protected function readPasswordFromStdin($dsn) + { + $stdin = fopen('php://stdin', 'r'); + self::toStdErr('Please insert password for ' . $dsn . "\n"); + $password = ''; + $breakme = false; + while (false !== ($char = fgetc($stdin))) { + if (ord($char) == 10 || $char == "\n" || $char == "\r") { + break; + } + $password .= $char; + } + fclose($stdin); + + return trim($password); + }//protected function readPasswordFromStdin() + + + + /** + * Creates a database schema dump and sends it to stdout + * + * @param array $args Command line arguments + * + * @return void + */ + protected function doDump($args) + { + $dump_what = MDB2_SCHEMA_DUMP_STRUCTURE; + $arg = ''; + if (count($args)) { + $arg = $args[0]; + } + + switch (strtolower($arg)) { + case 'all': + $dump_what = MDB2_SCHEMA_DUMP_ALL; + array_shift($args); + break; + case 'data': + $dump_what = MDB2_SCHEMA_DUMP_CONTENT; + array_shift($args); + break; + case 'schema': + array_shift($args); + } + + list($type, $dsn) = $this->getFileOrDsn($args); + if ($type == 'file') { + throw new MDB2_Schema_Tool_ParameterException( + 'Dumping a schema file as a schema file does not make much sense' + ); + } + + $schema = MDB2_Schema::factory($dsn, $this->getSchemaOptions()); + $this->throwExceptionOnError($schema); + + $definition = $schema->getDefinitionFromDatabase(); + $this->throwExceptionOnError($definition); + + + $dump_options = array( + 'output_mode' => 'file', + 'output' => 'php://stdout', + 'end_of_line' => "\r\n" + ); + $op = $schema->dumpDatabase( + $definition, $dump_options, $dump_what + ); + $this->throwExceptionOnError($op); + + $schema->disconnect(); + }//protected function doDump($args) + + + + /** + * Loads a database schema + * + * @param array $args Command line arguments + * + * @return void + */ + protected function doLoad($args) + { + list($typeSource, $dsnSource) = $this->getFileOrDsn($args); + list($typeDest, $dsnDest) = $this->getFileOrDsn($args); + + if ($typeDest == 'file') { + throw new MDB2_Schema_Tool_ParameterException( + 'A schema can only be loaded into a database, not a file' + ); + } + + + $schemaDest = MDB2_Schema::factory($dsnDest, $this->getSchemaOptions()); + $this->throwExceptionOnError($schemaDest); + + //load definition + if ($typeSource == 'file') { + $definition = $schemaDest->parseDatabaseDefinitionFile($dsnSource); + $where = 'loading schema file'; + } else { + $schemaSource = MDB2_Schema::factory($dsnSource, $this->getSchemaOptions()); + $this->throwExceptionOnError($schemaSource, 'connecting to source database'); + + $definition = $schemaSource->getDefinitionFromDatabase(); + $where = 'loading definition from database'; + } + $this->throwExceptionOnError($definition, $where); + + + //create destination database from definition + $simulate = false; + $op = $schemaDest->createDatabase($definition, array(), $simulate); + $this->throwExceptionOnError($op, 'creating the database'); + }//protected function doLoad($args) + + + + /** + * Initializes a database with data + * + * @param array $args Command line arguments + * + * @return void + */ + protected function doInit($args) + { + list($typeSource, $dsnSource) = $this->getFileOrDsn($args); + list($typeDest, $dsnDest) = $this->getFileOrDsn($args); + + if ($typeSource != 'file') { + throw new MDB2_Schema_Tool_ParameterException( + 'Data must come from a source file' + ); + } + + if ($typeDest != 'dsn') { + throw new MDB2_Schema_Tool_ParameterException( + 'A schema can only be loaded into a database, not a file' + ); + } + + $schemaDest = MDB2_Schema::factory($dsnDest, $this->getSchemaOptions()); + $this->throwExceptionOnError($schemaDest, 'connecting to destination database'); + + $definition = $schemaDest->getDefinitionFromDatabase(); + $this->throwExceptionOnError($definition, 'loading definition from database'); + + $op = $schemaDest->writeInitialization($dsnSource, $definition); + $this->throwExceptionOnError($op, 'initializing database'); + }//protected function doInit($args) + + +}//class MDB2_Schema_Tool + +?> diff --git a/inc/MDB2/Schema/Tool/ParameterException.php b/inc/MDB2/Schema/Tool/ParameterException.php new file mode 100644 index 0000000000..fab1e03e25 --- /dev/null +++ b/inc/MDB2/Schema/Tool/ParameterException.php @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/inc/MDB2/Schema/Validate.php b/inc/MDB2/Schema/Validate.php new file mode 100644 index 0000000000..21be024ce9 --- /dev/null +++ b/inc/MDB2/Schema/Validate.php @@ -0,0 +1,922 @@ + + * Author: Igor Feghali + * + * @category Database + * @package MDB2_Schema + * @author Christian Dickmann + * @author Igor Feghali + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @version CVS: $Id: Validate.php,v 1.42 2008/11/30 03:34:00 clockwerx Exp $ + * @link http://pear.php.net/packages/MDB2_Schema + */ + +/** + * Validates an XML schema file + * + * @category Database + * @package MDB2_Schema + * @author Igor Feghali + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/packages/MDB2_Schema + */ +class MDB2_Schema_Validate +{ + // {{{ properties + + var $fail_on_invalid_names = true; + + var $valid_types = array(); + + var $force_defaults = true; + + // }}} + // {{{ constructor + + function __construct($fail_on_invalid_names = true, $valid_types = array(), $force_defaults = true) + { + if (empty($GLOBALS['_MDB2_Schema_Reserved'])) { + $GLOBALS['_MDB2_Schema_Reserved'] = array(); + } + + if (is_array($fail_on_invalid_names)) { + $this->fail_on_invalid_names = array_intersect($fail_on_invalid_names, + array_keys($GLOBALS['_MDB2_Schema_Reserved'])); + } elseif ($fail_on_invalid_names === true) { + $this->fail_on_invalid_names = array_keys($GLOBALS['_MDB2_Schema_Reserved']); + } else { + $this->fail_on_invalid_names = array(); + } + $this->valid_types = $valid_types; + $this->force_defaults = $force_defaults; + } + + function MDB2_Schema_Validate($fail_on_invalid_names = true, $valid_types = array(), $force_defaults = true) + { + $this->__construct($fail_on_invalid_names, $valid_types, $force_defaults); + } + + // }}} + // {{{ raiseError() + + function &raiseError($ecode, $msg = null) + { + $error =& MDB2_Schema::raiseError($ecode, null, null, $msg); + return $error; + } + + // }}} + // {{{ isBoolean() + + /** + * Verifies if a given value can be considered boolean. If yes, set value + * to true or false according to its actual contents and return true. If + * not, keep its contents untouched and return false. + * + * @param mixed &$value value to be checked + * + * @return bool + * + * @access public + * @static + */ + function isBoolean(&$value) + { + if (is_bool($value)) { + return true; + } + + if ($value === 0 || $value === 1 || $value === '') { + $value = (bool)$value; + return true; + } + + if (!is_string($value)) { + return false; + } + + switch ($value) { + case '0': + case 'N': + case 'n': + case 'no': + case 'false': + $value = false; + break; + case '1': + case 'Y': + case 'y': + case 'yes': + case 'true': + $value = true; + break; + default: + return false; + } + return true; + } + + // }}} + // {{{ validateTable() + + /* Definition */ + /** + * Checks whether the definition of a parsed table is valid. Modify table + * definition when necessary. + * + * @param array $tables multi dimensional array that contains the + * tables of current database. + * @param array &$table multi dimensional array that contains the + * structure and optional data of the table. + * @param string $table_name name of the parsed table + * + * @return bool|error object + * + * @access public + */ + function validateTable($tables, &$table, $table_name) + { + /* Have we got a name? */ + if (!$table_name) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'a table has to have a name'); + } + + /* Table name duplicated? */ + if (is_array($tables) && isset($tables[$table_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'table "'.$table_name.'" already exists'); + } + + /* Table name reserved? */ + if (is_array($this->fail_on_invalid_names)) { + $name = strtoupper($table_name); + foreach ($this->fail_on_invalid_names as $rdbms) { + if (in_array($name, $GLOBALS['_MDB2_Schema_Reserved'][$rdbms])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'table name "'.$table_name.'" is a reserved word in: '.$rdbms); + } + } + } + + /* Was */ + if (empty($table['was'])) { + $table['was'] = $table_name; + } + + /* Have we got fields? */ + if (empty($table['fields']) || !is_array($table['fields'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'tables need one or more fields'); + } + + /* Autoincrement */ + $autoinc = $primary = false; + foreach ($table['fields'] as $field_name => $field) { + if (!empty($field['autoincrement'])) { + if ($autoinc) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'there was already an autoincrement field in "'.$table_name.'" before "'.$field_name.'"'); + } + $autoinc = $field_name; + } + } + + /* + * Checking Indexes + * this have to be done here otherwise we can't + * guarantee that all table fields were already + * defined in the moment we are parsing indexes + */ + if (!empty($table['indexes']) && is_array($table['indexes'])) { + foreach ($table['indexes'] as $name => $index) { + $skip_index = false; + if (!empty($index['primary'])) { + /* + * Lets see if we should skip this index since there is + * already an auto increment on this field this implying + * a primary key index. + */ + if (count($index['fields']) == '1' + && $autoinc + && array_key_exists($autoinc, $index['fields'])) { + $skip_index = true; + } elseif ($autoinc || $primary) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'there was already an primary index or autoincrement field in "'.$table_name.'" before "'.$name.'"'); + } else { + $primary = true; + } + } + + if (!$skip_index && is_array($index['fields'])) { + foreach ($index['fields'] as $field_name => $field) { + if (!isset($table['fields'][$field_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'index field "'.$field_name.'" does not exist'); + } + if (!empty($index['primary']) + && !$table['fields'][$field_name]['notnull'] + ) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'all primary key fields must be defined notnull in "'.$table_name.'"'); + } + } + } else { + unset($table['indexes'][$name]); + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ validateField() + + /** + * Checks whether the definition of a parsed field is valid. Modify field + * definition when necessary. + * + * @param array $fields multi dimensional array that contains the + * fields of current table. + * @param array &$field multi dimensional array that contains the + * structure of the parsed field. + * @param string $field_name name of the parsed field + * + * @return bool|error object + * + * @access public + */ + function validateField($fields, &$field, $field_name) + { + /* Have we got a name? */ + if (!$field_name) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'field name missing'); + } + + /* Field name duplicated? */ + if (is_array($fields) && isset($fields[$field_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'field "'.$field_name.'" already exists'); + } + + /* Field name reserverd? */ + if (is_array($this->fail_on_invalid_names)) { + $name = strtoupper($field_name); + foreach ($this->fail_on_invalid_names as $rdbms) { + if (in_array($name, $GLOBALS['_MDB2_Schema_Reserved'][$rdbms])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'field name "'.$field_name.'" is a reserved word in: '.$rdbms); + } + } + } + + /* Type check */ + if (empty($field['type'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'no field type specified'); + } + if (!empty($this->valid_types) && !array_key_exists($field['type'], $this->valid_types)) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'no valid field type ("'.$field['type'].'") specified'); + } + + /* Unsigned */ + if (array_key_exists('unsigned', $field) && !$this->isBoolean($field['unsigned'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'unsigned has to be a boolean value'); + } + + /* Fixed */ + if (array_key_exists('fixed', $field) && !$this->isBoolean($field['fixed'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'fixed has to be a boolean value'); + } + + /* Length */ + if (array_key_exists('length', $field) && $field['length'] <= 0) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'length has to be an integer greater 0'); + } + + // if it's a DECIMAL datatype, check if a 'scale' value is provided: + // 8,4 should be translated to DECIMAL(8,4) + if (is_float($this->valid_types[$field['type']]) + && !empty($field['length']) + && strpos($field['length'], ',') !== false + ) { + list($field['length'], $field['scale']) = explode(',', $field['length']); + } + + /* Was */ + if (empty($field['was'])) { + $field['was'] = $field_name; + } + + /* Notnull */ + if (empty($field['notnull'])) { + $field['notnull'] = false; + } + if (!$this->isBoolean($field['notnull'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'field "notnull" has to be a boolean value'); + } + + /* Default */ + if ($this->force_defaults + && !array_key_exists('default', $field) + && $field['type'] != 'clob' && $field['type'] != 'blob' + ) { + $field['default'] = $this->valid_types[$field['type']]; + } + if (array_key_exists('default', $field)) { + if ($field['type'] == 'clob' || $field['type'] == 'blob') { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + '"'.$field['type'].'"-fields are not allowed to have a default value'); + } + if ($field['default'] === '' && !$field['notnull']) { + $field['default'] = null; + } + } + if (isset($field['default']) + && PEAR::isError($result = $this->validateDataFieldValue($field, $field['default'], $field_name)) + ) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'default value of "'.$field_name.'" is incorrect: '.$result->getUserinfo()); + } + + /* Autoincrement */ + if (!empty($field['autoincrement'])) { + if (!$field['notnull']) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'all autoincrement fields must be defined notnull'); + } + + if (empty($field['default'])) { + $field['default'] = '0'; + } elseif ($field['default'] !== '0' && $field['default'] !== 0) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'all autoincrement fields must be defined default "0"'); + } + } + return MDB2_OK; + } + + // }}} + // {{{ validateIndex() + + /** + * Checks whether a parsed index is valid. Modify index definition when + * necessary. + * + * @param array $table_indexes multi dimensional array that contains the + * indexes of current table. + * @param array &$index multi dimensional array that contains the + * structure of the parsed index. + * @param string $index_name name of the parsed index + * + * @return bool|error object + * + * @access public + */ + function validateIndex($table_indexes, &$index, $index_name) + { + if (!$index_name) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'an index has to have a name'); + } + if (is_array($table_indexes) && isset($table_indexes[$index_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'index "'.$index_name.'" already exists'); + } + if (array_key_exists('unique', $index) && !$this->isBoolean($index['unique'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'field "unique" has to be a boolean value'); + } + if (array_key_exists('primary', $index) && !$this->isBoolean($index['primary'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'field "primary" has to be a boolean value'); + } + + /* Have we got fields? */ + if (empty($index['fields']) || !is_array($index['fields'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'indexes need one or more fields'); + } + + if (empty($index['was'])) { + $index['was'] = $index_name; + } + return MDB2_OK; + } + + // }}} + // {{{ validateIndexField() + + /** + * Checks whether a parsed index-field is valid. Modify its definition when + * necessary. + * + * @param array $index_fields multi dimensional array that contains the + * fields of current index. + * @param array &$field multi dimensional array that contains the + * structure of the parsed index-field. + * @param string $field_name name of the parsed index-field + * + * @return bool|error object + * + * @access public + */ + function validateIndexField($index_fields, &$field, $field_name) + { + if (is_array($index_fields) && isset($index_fields[$field_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'index field "'.$field_name.'" already exists'); + } + if (!$field_name) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'the index-field-name is required'); + } + if (empty($field['sorting'])) { + $field['sorting'] = 'ascending'; + } elseif ($field['sorting'] !== 'ascending' && $field['sorting'] !== 'descending') { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'sorting type unknown'); + } + return MDB2_OK; + } + + // }}} + // {{{ validateConstraint() + + /** + * Checks whether a parsed foreign key is valid. Modify its definition when + * necessary. + * + * @param array $table_constraints multi dimensional array that contains the + * constraints of current table. + * @param array &$constraint multi dimensional array that contains the + * structure of the parsed foreign key. + * @param string $constraint_name name of the parsed foreign key + * + * @return bool|error object + * + * @access public + */ + function validateConstraint($table_constraints, &$constraint, $constraint_name) + { + if (!$constraint_name) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'a foreign key has to have a name'); + } + if (is_array($table_constraints) && isset($table_constraints[$constraint_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'foreign key "'.$constraint_name.'" already exists'); + } + + /* Have we got fields? */ + if (empty($constraint['fields']) || !is_array($constraint['fields'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'foreign key "'.$constraint_name.'" need one or more fields'); + } + + /* Have we got referenced fields? */ + if (empty($constraint['references']) || !is_array($constraint['references'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'foreign key "'.$constraint_name.'" need to reference one or more fields'); + } + + /* Have we got referenced table? */ + if (empty($constraint['references']['table'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'foreign key "'.$constraint_name.'" need to reference a table'); + } + + if (empty($constraint['was'])) { + $constraint['was'] = $constraint_name; + } + return MDB2_OK; + } + + // }}} + // {{{ validateConstraintField() + + /** + * Checks whether a foreign-field is valid. + * + * @param array $constraint_fields multi dimensional array that contains the + * fields of current foreign key. + * @param string $field_name name of the parsed foreign-field + * + * @return bool|error object + * + * @access public + */ + function validateConstraintField($constraint_fields, $field_name) + { + if (!$field_name) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'empty value for foreign-field'); + } + if (is_array($constraint_fields) && isset($constraint_fields[$field_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'foreign field "'.$field_name.'" already exists'); + } + return MDB2_OK; + } + + // }}} + // {{{ validateConstraintReferencedField() + + /** + * Checks whether a foreign-referenced field is valid. + * + * @param array $referenced_fields multi dimensional array that contains the + * fields of current foreign key. + * @param string $field_name name of the parsed foreign-field + * + * @return bool|error object + * + * @access public + */ + function validateConstraintReferencedField($referenced_fields, $field_name) + { + if (!$field_name) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'empty value for referenced foreign-field'); + } + if (is_array($referenced_fields) && isset($referenced_fields[$field_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'foreign field "'.$field_name.'" already referenced'); + } + return MDB2_OK; + } + + // }}} + // {{{ validateSequence() + + /** + * Checks whether the definition of a parsed sequence is valid. Modify + * sequence definition when necessary. + * + * @param array $sequences multi dimensional array that contains the + * sequences of current database. + * @param array &$sequence multi dimensional array that contains the + * structure of the parsed sequence. + * @param string $sequence_name name of the parsed sequence + * + * @return bool|error object + * + * @access public + */ + function validateSequence($sequences, &$sequence, $sequence_name) + { + if (!$sequence_name) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'a sequence has to have a name'); + } + + if (is_array($sequences) && isset($sequences[$sequence_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'sequence "'.$sequence_name.'" already exists'); + } + + if (is_array($this->fail_on_invalid_names)) { + $name = strtoupper($sequence_name); + foreach ($this->fail_on_invalid_names as $rdbms) { + if (in_array($name, $GLOBALS['_MDB2_Schema_Reserved'][$rdbms])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'sequence name "'.$sequence_name.'" is a reserved word in: '.$rdbms); + } + } + } + + if (empty($sequence['was'])) { + $sequence['was'] = $sequence_name; + } + + if (!empty($sequence['on']) + && (empty($sequence['on']['table']) || empty($sequence['on']['field'])) + ) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'sequence "'.$sequence_name.'" on a table was not properly defined'); + } + return MDB2_OK; + } + + // }}} + // {{{ validateDatabase() + + /** + * Checks whether a parsed database is valid. Modify its structure and + * data when necessary. + * + * @param array &$database multi dimensional array that contains the + * structure and optional data of the database. + * + * @return bool|error object + * + * @access public + */ + function validateDatabase(&$database) + { + /* Have we got a name? */ + if (!is_array($database) || !isset($database['name']) || !$database['name']) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'a database has to have a name'); + } + + /* Database name reserved? */ + if (is_array($this->fail_on_invalid_names)) { + $name = strtoupper($database['name']); + foreach ($this->fail_on_invalid_names as $rdbms) { + if (in_array($name, $GLOBALS['_MDB2_Schema_Reserved'][$rdbms])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'database name "'.$database['name'].'" is a reserved word in: '.$rdbms); + } + } + } + + /* Create */ + if (isset($database['create']) + && !$this->isBoolean($database['create']) + ) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'field "create" has to be a boolean value'); + } + + /* Overwrite */ + if (isset($database['overwrite']) + && $database['overwrite'] !== '' + && !$this->isBoolean($database['overwrite']) + ) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'field "overwrite" has to be a boolean value'); + } + + /* + * This have to be done here otherwise we can't guarantee that all + * tables were already defined in the moment we are parsing constraints + */ + if (isset($database['tables'])) { + foreach ($database['tables'] as $table_name => $table) { + if (!empty($table['constraints'])) { + foreach ($table['constraints'] as $constraint_name => $constraint) { + $referenced_table_name = $constraint['references']['table']; + + if (!isset($database['tables'][$referenced_table_name])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'referenced table "'.$referenced_table_name.'" of foreign key "'.$constraint_name.'" of table "'.$table_name.'" does not exist'); + } + + if (empty($constraint['references']['fields'])) { + $referenced_table = $database['tables'][$referenced_table_name]; + + $primary = false; + + if (!empty($referenced_table['indexes'])) { + foreach ($referenced_table['indexes'] as $index_name => $index) { + if (array_key_exists('primary', $index) + && $index['primary'] + ) { + $primary = array(); + foreach ($index['fields'] as $field_name => $field) { + $primary[$field_name] = ''; + } + break; + } + } + } + + if (!$primary) { + foreach ($referenced_table['fields'] as $field_name => $field) { + if (array_key_exists('autoincrement', $field) + && $field['autoincrement'] + ) { + $primary = array( $field_name => '' ); + break; + } + } + } + + if (!$primary) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'referenced table "'.$referenced_table_name.'" has no primary key and no referenced field was specified for foreign key "'.$constraint_name.'" of table "'.$table_name.'"'); + } + + $constraint['references']['fields'] = $primary; + } + + /* the same number of referencing and referenced fields ? */ + if (count($constraint['fields']) != count($constraint['references']['fields'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'The number of fields in the referenced key must match those of the foreign key "'.$constraint_name.'"'); + } + + $database['tables'][$table_name]['constraints'][$constraint_name]['references']['fields'] = $constraint['references']['fields']; + } + } + } + } + + /* + * This have to be done here otherwise we can't guarantee that all + * tables were already defined in the moment we are parsing sequences + */ + if (isset($database['sequences'])) { + foreach ($database['sequences'] as $seq_name => $seq) { + if (!empty($seq['on']) + && empty($database['tables'][$seq['on']['table']]['fields'][$seq['on']['field']]) + ) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'sequence "'.$seq_name.'" was assigned on unexisting field/table'); + } + } + } + return MDB2_OK; + } + + // }}} + // {{{ validateDataField() + + /* Data Manipulation */ + /** + * Checks whether a parsed DML-field is valid. Modify its structure when + * necessary. This is called when validating INSERT and + * UPDATE. + * + * @param array $table_fields multi dimensional array that contains the + * definition for current table's fields. + * @param array $instruction_fields multi dimensional array that contains the + * parsed fields of the current DML instruction. + * @param string &$field array that contains the parsed instruction field + * + * @return bool|error object + * + * @access public + */ + function validateDataField($table_fields, $instruction_fields, &$field) + { + if (!$field['name']) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'field-name has to be specified'); + } + + if (is_array($instruction_fields) && isset($instruction_fields[$field['name']])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'field "'.$field['name'].'" already initialized'); + } + + if (is_array($table_fields) && !isset($table_fields[$field['name']])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + '"'.$field['name'].'" is not defined'); + } + + if (!isset($field['group']['type'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + '"'.$field['name'].'" has no initial value'); + } + + if (isset($field['group']['data']) + && $field['group']['type'] == 'value' + && $field['group']['data'] !== '' + && PEAR::isError($result = $this->validateDataFieldValue($table_fields[$field['name']], $field['group']['data'], $field['name'])) + ) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + 'value of "'.$field['name'].'" is incorrect: '.$result->getUserinfo()); + } + + return MDB2_OK; + } + + // }}} + // {{{ validateDataFieldValue() + + /** + * Checks whether a given value is compatible with a table field. This is + * done when parsing a field for a INSERT or UPDATE instruction. + * + * @param array $field_def multi dimensional array that contains the + * definition for current table's fields. + * @param string &$field_value value to fill the parsed field + * @param string $field_name name of the parsed field + * + * @return bool|error object + * + * @access public + * @see MDB2_Schema_Validate::validateInsertField() + */ + function validateDataFieldValue($field_def, &$field_value, $field_name) + { + switch ($field_def['type']) { + case 'text': + case 'clob': + if (!empty($field_def['length']) && strlen($field_value) > $field_def['length']) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + '"'.$field_value.'" is larger than "'.$field_def['length'].'"'); + } + break; + case 'blob': + $field_value = pack('H*', $field_value); + if (!empty($field_def['length']) && strlen($field_value) > $field_def['length']) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + '"'.$field_value.'" is larger than "'.$field_def['type'].'"'); + } + break; + case 'integer': + if ($field_value != ((int)$field_value)) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + '"'.$field_value.'" is not of type "'.$field_def['type'].'"'); + } + //$field_value = (int)$field_value; + if (!empty($field_def['unsigned']) && $field_def['unsigned'] && $field_value < 0) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + '"'.$field_value.'" signed instead of unsigned'); + } + break; + case 'boolean': + if (!$this->isBoolean($field_value)) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + '"'.$field_value.'" is not of type "'.$field_def['type'].'"'); + } + break; + case 'date': + if (!preg_match('/([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})/', $field_value) + && $field_value !== 'CURRENT_DATE' + ) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + '"'.$field_value.'" is not of type "'.$field_def['type'].'"'); + } + break; + case 'timestamp': + if (!preg_match('/([0-9]{4})-([0-9]{1,2})-([0-9]{1,2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/', $field_value) + && strcasecmp($field_value, 'now()') != 0 + && $field_value !== 'CURRENT_TIMESTAMP' + ) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + '"'.$field_value.'" is not of type "'.$field_def['type'].'"'); + } + break; + case 'time': + if (!preg_match("/([0-9]{2}):([0-9]{2}):([0-9]{2})/", $field_value) + && $field_value !== 'CURRENT_TIME' + ) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + '"'.$field_value.'" is not of type "'.$field_def['type'].'"'); + } + break; + case 'float': + case 'double': + if ($field_value != (double)$field_value) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, + '"'.$field_value.'" is not of type "'.$field_def['type'].'"'); + } + //$field_value = (double)$field_value; + break; + } + return MDB2_OK; + } +} + +?> diff --git a/inc/MDB2/Schema/Writer.php b/inc/MDB2/Schema/Writer.php new file mode 100644 index 0000000000..5ae4918dc1 --- /dev/null +++ b/inc/MDB2/Schema/Writer.php @@ -0,0 +1,581 @@ + + * Author: Igor Feghali + * + * @category Database + * @package MDB2_Schema + * @author Lukas Smith + * @author Igor Feghali + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @version CVS: $Id: Writer.php,v 1.40 2008/11/30 03:34:00 clockwerx Exp $ + * @link http://pear.php.net/packages/MDB2_Schema + */ + +/** + * Writes an XML schema file + * + * @category Database + * @package MDB2_Schema + * @author Lukas Smith + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/packages/MDB2_Schema + */ +class MDB2_Schema_Writer +{ + // {{{ properties + + var $valid_types = array(); + + // }}} + // {{{ constructor + + function __construct($valid_types = array()) + { + $this->valid_types = $valid_types; + } + + function MDB2_Schema_Writer($valid_types = array()) + { + $this->__construct($valid_types); + } + + // }}} + // {{{ raiseError() + + /** + * This method is used to communicate an error and invoke error + * callbacks etc. Basically a wrapper for PEAR::raiseError + * without the message string. + * + * @param int|PEAR_Error $code integer error code or and PEAR_Error instance + * @param int $mode error mode, see PEAR_Error docs + * error level (E_USER_NOTICE etc). If error mode is + * PEAR_ERROR_CALLBACK, this is the callback function, + * either as a function name, or as an array of an + * object and method name. For other error modes this + * parameter is ignored. + * @param string $options Extra debug information. Defaults to the last + * query and native error code. + * + * @return object a PEAR error object + * @access public + * @see PEAR_Error + */ + function &raiseError($code = null, $mode = null, $options = null, $userinfo = null) + { + $error =& MDB2_Schema::raiseError($code, $mode, $options, $userinfo); + return $error; + } + + // }}} + // {{{ _escapeSpecialChars() + + /** + * add escapecharacters to all special characters in a string + * + * @param string $string string that should be escaped + * + * @return string escaped string + * @access protected + */ + function _escapeSpecialChars($string) + { + if (!is_string($string)) { + $string = strval($string); + } + + $escaped = ''; + for ($char = 0, $count = strlen($string); $char < $count; $char++) { + switch ($string[$char]) { + case '&': + $escaped .= '&'; + break; + case '>': + $escaped .= '>'; + break; + case '<': + $escaped .= '<'; + break; + case '"': + $escaped .= '"'; + break; + case '\'': + $escaped .= '''; + break; + default: + $code = ord($string[$char]); + if ($code < 32 || $code > 127) { + $escaped .= "&#$code;"; + } else { + $escaped .= $string[$char]; + } + break; + } + } + return $escaped; + } + + // }}} + // {{{ _dumpBoolean() + + /** + * dump the structure of a sequence + * + * @param string $boolean boolean value or variable definition + * + * @return string with xml boolea definition + * @access private + */ + function _dumpBoolean($boolean) + { + if (is_string($boolean)) { + if ($boolean !== 'true' || $boolean !== 'false' + || preg_match('/.*/', $boolean) + ) { + return $boolean; + } + } + return $boolean ? 'true' : 'false'; + } + + // }}} + // {{{ dumpSequence() + + /** + * dump the structure of a sequence + * + * @param string $sequence_definition sequence definition + * @param string $sequence_name sequence name + * @param string $eol end of line characters + * @param integer $dump determines what data to dump + * MDB2_SCHEMA_DUMP_ALL : the entire db + * MDB2_SCHEMA_DUMP_STRUCTURE : only the structure of the db + * MDB2_SCHEMA_DUMP_CONTENT : only the content of the db + * + * @return mixed string xml sequence definition on success, or a error object + * @access public + */ + function dumpSequence($sequence_definition, $sequence_name, $eol, $dump = MDB2_SCHEMA_DUMP_ALL) + { + $buffer = "$eol $eol $sequence_name$eol"; + if ($dump == MDB2_SCHEMA_DUMP_ALL || $dump == MDB2_SCHEMA_DUMP_CONTENT) { + if (!empty($sequence_definition['start'])) { + $start = $sequence_definition['start']; + $buffer .= " $start$eol"; + } + } + + if (!empty($sequence_definition['on'])) { + $buffer .= " $eol"; + $buffer .= " ".$sequence_definition['on']['table']; + $buffer .= "
$eol ".$sequence_definition['on']['field']; + $buffer .= "$eol
$eol"; + } + $buffer .= "
$eol"; + + return $buffer; + } + + // }}} + // {{{ dumpDatabase() + + /** + * Dump a previously parsed database structure in the Metabase schema + * XML based format suitable for the Metabase parser. This function + * may optionally dump the database definition with initialization + * commands that specify the data that is currently present in the tables. + * + * @param array $database_definition unknown + * @param array $arguments associative array that takes pairs of tag + * names and values that define dump options. + * array ( + * 'output_mode' => String + * 'file' : dump into a file + * default: dump using a function + * 'output' => String + * depending on the 'Output_Mode' + * name of the file + * name of the function + * 'end_of_line' => String + * end of line delimiter that should be used + * default: "\n" + * ); + * @param integer $dump determines what data to dump + * MDB2_SCHEMA_DUMP_ALL : the entire db + * MDB2_SCHEMA_DUMP_STRUCTURE : only the structure of the db + * MDB2_SCHEMA_DUMP_CONTENT : only the content of the db + * + * @return mixed MDB2_OK on success, or a error object + * @access public + */ + function dumpDatabase($database_definition, $arguments, $dump = MDB2_SCHEMA_DUMP_ALL) + { + if (!empty($arguments['output'])) { + if (!empty($arguments['output_mode']) && $arguments['output_mode'] == 'file') { + $fp = fopen($arguments['output'], 'w'); + if ($fp === false) { + return $this->raiseError(MDB2_SCHEMA_ERROR_WRITER, null, null, + 'it was not possible to open output file'); + } + + $output = false; + } elseif (is_callable($arguments['output'])) { + $output = $arguments['output']; + } else { + return $this->raiseError(MDB2_SCHEMA_ERROR_WRITER, null, null, + 'no valid output function specified'); + } + } else { + return $this->raiseError(MDB2_SCHEMA_ERROR_WRITER, null, null, + 'no output method specified'); + } + + $eol = isset($arguments['end_of_line']) ? $arguments['end_of_line'] : "\n"; + + $sequences = array(); + if (!empty($database_definition['sequences']) + && is_array($database_definition['sequences']) + ) { + foreach ($database_definition['sequences'] as $sequence_name => $sequence) { + $table = !empty($sequence['on']) ? $sequence['on']['table'] :''; + + $sequences[$table][] = $sequence_name; + } + } + + $buffer = ''.$eol; + $buffer .= "$eol$eol ".$database_definition['name'].""; + $buffer .= "$eol ".$this->_dumpBoolean($database_definition['create']).""; + $buffer .= "$eol ".$this->_dumpBoolean($database_definition['overwrite'])."$eol"; + $buffer .= "$eol ".$database_definition['charset']."$eol"; + + if ($output) { + call_user_func($output, $buffer); + } else { + fwrite($fp, $buffer); + } + + if (!empty($database_definition['tables']) && is_array($database_definition['tables'])) { + foreach ($database_definition['tables'] as $table_name => $table) { + $buffer = "$eol $eol$eol $table_name$eol"; + if ($dump == MDB2_SCHEMA_DUMP_ALL || $dump == MDB2_SCHEMA_DUMP_STRUCTURE) { + $buffer .= "$eol $eol"; + if (!empty($table['fields']) && is_array($table['fields'])) { + foreach ($table['fields'] as $field_name => $field) { + if (empty($field['type'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, null, null, + 'it was not specified the type of the field "'. + $field_name.'" of the table "'.$table_name.'"'); + } + if (!empty($this->valid_types) && !array_key_exists($field['type'], $this->valid_types)) { + return $this->raiseError(MDB2_SCHEMA_ERROR_UNSUPPORTED, null, null, + 'type "'.$field['type'].'" is not yet supported'); + } + $buffer .= "$eol $eol $field_name$eol "; + $buffer .= $field['type']."$eol"; + if (!empty($field['fixed']) && $field['type'] === 'text') { + $buffer .= " ".$this->_dumpBoolean($field['fixed'])."$eol"; + } + if (array_key_exists('default', $field) + && $field['type'] !== 'clob' && $field['type'] !== 'blob' + ) { + $buffer .= ' '.$this->_escapeSpecialChars($field['default'])."$eol"; + } + if (!empty($field['notnull'])) { + $buffer .= " ".$this->_dumpBoolean($field['notnull'])."$eol"; + } else { + $buffer .= " false$eol"; + } + if (!empty($field['autoincrement'])) { + $buffer .= " " . $field['autoincrement'] ."$eol"; + } + if (!empty($field['unsigned'])) { + $buffer .= " ".$this->_dumpBoolean($field['unsigned'])."$eol"; + } + if (!empty($field['length'])) { + $buffer .= ' '.$field['length']."$eol"; + } + $buffer .= " $eol"; + } + } + + if (!empty($table['indexes']) && is_array($table['indexes'])) { + foreach ($table['indexes'] as $index_name => $index) { + if (strtolower($index_name) === 'primary') { + $index_name = $table_name . '_pKey'; + } + $buffer .= "$eol $eol $index_name$eol"; + if (!empty($index['unique'])) { + $buffer .= " ".$this->_dumpBoolean($index['unique'])."$eol"; + } + + if (!empty($index['primary'])) { + $buffer .= " ".$this->_dumpBoolean($index['primary'])."$eol"; + } + + foreach ($index['fields'] as $field_name => $field) { + $buffer .= " $eol $field_name$eol"; + if (!empty($field) && is_array($field)) { + $buffer .= ' '.$field['sorting']."$eol"; + } + $buffer .= " $eol"; + } + $buffer .= " $eol"; + } + } + + if (!empty($table['constraints']) && is_array($table['constraints'])) { + foreach ($table['constraints'] as $constraint_name => $constraint) { + $buffer .= "$eol $eol $constraint_name$eol"; + if (empty($constraint['fields']) || !is_array($constraint['fields'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, null, null, + 'it was not specified a field for the foreign key "'. + $constraint_name.'" of the table "'.$table_name.'"'); + } + if (!is_array($constraint['references']) || empty($constraint['references']['table'])) { + return $this->raiseError(MDB2_SCHEMA_ERROR_VALIDATE, null, null, + 'it was not specified the referenced table of the foreign key "'. + $constraint_name.'" of the table "'.$table_name.'"'); + } + if (!empty($constraint['match'])) { + $buffer .= " ".$constraint['match']."$eol"; + } + if (!empty($constraint['ondelete'])) { + $buffer .= " ".$constraint['ondelete']."$eol"; + } + if (!empty($constraint['onupdate'])) { + $buffer .= " ".$constraint['onupdate']."$eol"; + } + if (!empty($constraint['deferrable'])) { + $buffer .= " ".$constraint['deferrable']."$eol"; + } + if (!empty($constraint['initiallydeferred'])) { + $buffer .= " ".$constraint['initiallydeferred']."$eol"; + } + foreach ($constraint['fields'] as $field_name => $field) { + $buffer .= " $field_name$eol"; + } + $buffer .= " $eol
".$constraint['references']['table']."
$eol"; + foreach ($constraint['references']['fields'] as $field_name => $field) { + $buffer .= " $field_name$eol"; + } + $buffer .= " $eol"; + + $buffer .= " $eol"; + } + } + + $buffer .= "$eol $eol"; + } + + if ($output) { + call_user_func($output, $buffer); + } else { + fwrite($fp, $buffer); + } + + $buffer = ''; + if ($dump == MDB2_SCHEMA_DUMP_ALL || $dump == MDB2_SCHEMA_DUMP_CONTENT) { + if (!empty($table['initialization']) && is_array($table['initialization'])) { + $buffer = "$eol $eol"; + foreach ($table['initialization'] as $instruction) { + switch ($instruction['type']) { + case 'insert': + $buffer .= "$eol $eol"; + foreach ($instruction['data']['field'] as $field) { + $field_name = $field['name']; + + $buffer .= "$eol $eol $field_name$eol"; + $buffer .= $this->writeExpression($field['group'], 5, $arguments); + $buffer .= " $eol"; + } + $buffer .= "$eol $eol"; + break; + case 'update': + $buffer .= "$eol $eol"; + foreach ($instruction['data']['field'] as $field) { + $field_name = $field['name']; + + $buffer .= "$eol $eol $field_name$eol"; + $buffer .= $this->writeExpression($field['group'], 5, $arguments); + $buffer .= " $eol"; + } + + if (!empty($instruction['data']['where']) + && is_array($instruction['data']['where']) + ) { + $buffer .= " $eol"; + $buffer .= $this->writeExpression($instruction['data']['where'], 5, $arguments); + $buffer .= " $eol"; + } + + $buffer .= "$eol $eol"; + break; + case 'delete': + $buffer .= "$eol $eol$eol"; + if (!empty($instruction['data']['where']) + && is_array($instruction['data']['where']) + ) { + $buffer .= " $eol"; + $buffer .= $this->writeExpression($instruction['data']['where'], 5, $arguments); + $buffer .= " $eol"; + } + $buffer .= "$eol $eol"; + break; + } + } + $buffer .= "$eol $eol"; + } + } + $buffer .= "$eol $eol"; + if ($output) { + call_user_func($output, $buffer); + } else { + fwrite($fp, $buffer); + } + + if (isset($sequences[$table_name])) { + foreach ($sequences[$table_name] as $sequence) { + $result = $this->dumpSequence($database_definition['sequences'][$sequence], + $sequence, $eol, $dump); + if (PEAR::isError($result)) { + return $result; + } + + if ($output) { + call_user_func($output, $result); + } else { + fwrite($fp, $result); + } + } + } + } + } + + if (isset($sequences[''])) { + foreach ($sequences[''] as $sequence) { + $result = $this->dumpSequence($database_definition['sequences'][$sequence], + $sequence, $eol, $dump); + if (PEAR::isError($result)) { + return $result; + } + + if ($output) { + call_user_func($output, $result); + } else { + fwrite($fp, $result); + } + } + } + + $buffer = "$eol
$eol"; + if ($output) { + call_user_func($output, $buffer); + } else { + fwrite($fp, $buffer); + fclose($fp); + } + + return MDB2_OK; + } + + // }}} + // {{{ writeExpression() + + /** + * Dumps the structure of an element. Elements can be value, column, + * function or expression. + * + * @param array $element multi dimensional array that represents the parsed element + * of a DML instruction. + * @param integer $offset base indentation width + * @param array $arguments associative array that takes pairs of tag + * names and values that define dump options. + * + * @return string + * + * @access public + * @see MDB2_Schema_Writer::dumpDatabase() + */ + function writeExpression($element, $offset = 0, $arguments = null) + { + $eol = isset($arguments['end_of_line']) ? $arguments['end_of_line'] : "\n"; + $str = ''; + + $indent = str_repeat(' ', $offset); + $noffset = $offset + 1; + + switch ($element['type']) { + case 'value': + $str .= "$indent".$this->_escapeSpecialChars($element['data'])."$eol"; + break; + case 'column': + $str .= "$indent".$this->_escapeSpecialChars($element['data'])."$eol"; + break; + case 'function': + $str .= "$indent$eol$indent ".$this->_escapeSpecialChars($element['data']['name'])."$eol"; + + if (!empty($element['data']['arguments']) + && is_array($element['data']['arguments']) + ) { + foreach ($element['data']['arguments'] as $v) { + $str .= $this->writeExpression($v, $noffset, $arguments); + } + } + + $str .= "$indent$eol"; + break; + case 'expression': + $str .= "$indent$eol"; + $str .= $this->writeExpression($element['data']['operants'][0], $noffset, $arguments); + $str .= "$indent ".$element['data']['operator']."$eol"; + $str .= $this->writeExpression($element['data']['operants'][1], $noffset, $arguments); + $str .= "$indent$eol"; + break; + } + return $str; + } + + // }}} +} +?> diff --git a/inc/XML/Parser.php b/inc/XML/Parser.php index c8e45947c1..6f77b5c66d 100755 --- a/inc/XML/Parser.php +++ b/inc/XML/Parser.php @@ -36,7 +36,7 @@ /** * uses PEAR's error handling */ -require_once 'PEAR.php'; +oc_require_once('PEAR.php'); /** * resource could not be created @@ -376,13 +376,12 @@ class XML_Parser extends PEAR /** * check, if file is a remote file */ - if (eregi('^(http|ftp)://', substr($file, 0, 10))) { + if (preg_match('[^(http|ftp)://]', substr($file, 0, 10))) { if (!ini_get('allow_url_fopen')) { return $this->raiseError('Remote files cannot be parsed, as safe mode is enabled.', XML_PARSER_ERROR_REMOTE); } } - - $fp = @fopen($file, 'rb'); + $fp = fopen($file, 'rb'); if (is_resource($fp)) { $this->fp = $fp; return $fp; @@ -564,7 +563,7 @@ class XML_Parser extends PEAR function raiseError($msg = null, $ecode = 0) { $msg = !is_null($msg) ? $msg : $this->parser; - $err = &new XML_Parser_Error($msg, $ecode); + $err = new XML_Parser_Error($msg, $ecode); return parent::raiseError($err); } diff --git a/inc/lib_base.php b/inc/lib_base.php index 4fcba692b4..5a2a4c8266 100755 --- a/inc/lib_base.php +++ b/inc/lib_base.php @@ -81,7 +81,9 @@ oc_require_once('lib_config.php'); oc_require_once('lib_user.php'); oc_require_once('lib_ocs.php'); @oc_require_once('MDB2.php'); +@oc_require_once('MDB2/Schema.php'); oc_require_once('lib_connect.php'); +oc_require_once('lib_remotestorage.php'); if(!is_dir($CONFIG_DATADIRECTORY_ROOT)){ @@ -312,6 +314,7 @@ class OC_UTIL { */ class OC_DB { static private $DBConnection=false; + static private $schema=false; static private $affected=0; static private $result=false; /** @@ -327,8 +330,11 @@ class OC_DB { global $SERVERROOT; if(!self::$DBConnection){ $options = array( - 'debug' => 0, 'portability' => MDB2_PORTABILITY_ALL, + 'log_line_break' => '
', + 'idxname_format' => '%s', + 'debug' => true, + 'quote_identifier' => true, ); if($CONFIG_DBTYPE=='sqlite'){ $dsn = array( @@ -344,14 +350,22 @@ class OC_DB { 'hostspec' => $CONFIG_DBHOST, 'database' => $CONFIG_DBNAME, ); + }elseif($CONFIG_DBTYPE=='pgsql'){ + $dsn = array( + 'phptype' => 'pgsql', + 'username' => $CONFIG_DBUSER, + 'password' => $CONFIG_DBPASSWORD, + 'hostspec' => $CONFIG_DBHOST, + 'database' => $CONFIG_DBNAME, + ); } - self::$DBConnection=MDB2::connect($dsn,$options); - if (@PEAR::isError(self::$DBConnection)) { + self::$DBConnection=&MDB2::factory($dsn,$options); + if (PEAR::isError(self::$DBConnection)) { echo('can not connect to database, using '.$CONFIG_DBTYPE.'. ('.self::$DBConnection->getUserInfo().')'); die(self::$DBConnection->getMessage()); } self::$DBConnection->setFetchMode(MDB2_FETCHMODE_ASSOC); -// self::$DBConnection->loadModule('Manager'); + self::$schema=&MDB2_Schema::factory(self::$DBConnection); } } @@ -369,6 +383,8 @@ class OC_DB { OC_DB::connect(); if($CONFIG_DBTYPE=='sqlite'){//fix differences between sql versions $cmd=str_replace('`','',$cmd); + }elseif($CONFIG_DBTYPE=='pgsql'){ + $cmd=str_replace('`','"',$cmd); } $result=self::$DBConnection->exec($cmd); if (PEAR::isError($result)) { @@ -390,7 +406,19 @@ class OC_DB { */ static function select($cmd){ OC_DB::connect(); - return self::$DBConnection->queryAll($cmd); + global $CONFIG_DBTYPE; + if($CONFIG_DBTYPE=='sqlite'){//fix differences between sql versions + $cmd=str_replace('`','',$cmd); + }elseif($CONFIG_DBTYPE=='pgsql'){ + $cmd=str_replace('`','"',$cmd); + } + $result=self::$DBConnection->queryAll($cmd); + if (PEAR::isError($result)) { + $entry='DB Error: "'.$result->getMessage().'"
'; + $entry.='Offending command was: '.$cmd.'
'; + die($entry); + } + return $result; } /** @@ -425,12 +453,8 @@ class OC_DB { * @return primarykey */ static function insertid() { - global $CONFIG_DBTYPE; - if($CONFIG_DBTYPE=='sqlite'){ - return self::$DBConnection->lastInsertRowid(); - }elseif($CONFIG_DBTYPE=='mysql'){ - return(mysqli_insert_id(self::$DBConnection)); - } + $id=self::$DBConnection->lastInsertID(); + return $id; } /** @@ -505,7 +529,41 @@ class OC_DB { OC_DB::connect(); return self::$DBConnection->escape($string); } - + + static function getDBStructure($file){ + OC_DB::connect(); + $definition = self::$schema->getDefinitionFromDatabase(); + $dump_options = array( + 'output_mode' => 'file', + 'output' => $file, + 'end_of_line' => "\n" + ); + self::$schema->dumpDatabase($definition, $dump_options, MDB2_SCHEMA_DUMP_STRUCTURE); + } + + static function createDBFromStructure($file){ + OC_DB::connect(); + global $CONFIG_DBNAME; + global $CONFIG_DBTABLEPREFIX; + $content=file_get_contents($file); + $file2=tempnam(sys_get_temp_dir(),'oc_db_scheme_'); + echo $content; + $content=str_replace('*dbname*',$CONFIG_DBNAME,$content); + $content=str_replace('*dbprefix*',$CONFIG_DBTABLEPREFIX,$content); + echo $content; + file_put_contents($file2,$content); + $definition=@self::$schema->parseDatabaseDefinitionFile($file2); + unlink($file2); + if($definition instanceof MDB2_Schema_Error){ + die($definition->getMessage() . ': ' . $definition->getUserInfo()); + } + $ret=@self::$schema->createDatabase($definition); + if($ret instanceof MDB2_Error) { + die ($ret->getMessage() . ': ' . $ret->getUserInfo()); + }else{ + return true; + } + } } @@ -589,12 +647,12 @@ function oc_include_once($file){ global $CONFIG_HTTPFORCESSL; global $CONFIG_DATEFORMAT; global $CONFIG_INSTALLED; - if(is_file($file)){ - return include_once($file); - }elseif(is_file($SERVERROOT.'/'.$file)){ + if(is_file($SERVERROOT.'/'.$file)){ return include_once($SERVERROOT.'/'.$file); }elseif(is_file($SERVERROOT.'/inc/'.$file)){ return include_once($SERVERROOT.'/inc/'.$file); + }elseif(is_file($file)){ + return include_once($file); } } diff --git a/inc/lib_config.php b/inc/lib_config.php index 986d12f4dc..cbf86b17aa 100755 --- a/inc/lib_config.php +++ b/inc/lib_config.php @@ -25,6 +25,7 @@ class OC_CONFIG{ global $CONFIG_HTTPFORCESSL; global $CONFIG_DATEFORMAT; global $CONFIG_DBNAME; + global $CONFIG_DBTABLEPREFIX; global $CONFIG_INSTALLED; $allow=false; if(!$CONFIG_INSTALLED){ @@ -130,6 +131,7 @@ class OC_CONFIG{ global $WEBROOT; global $CONFIG_DBHOST; global $CONFIG_DBNAME; + global $CONFIG_DBTABLEPREFIX; global $CONFIG_INSTALLED; global $CONFIG_DBUSER; global $CONFIG_DBPASSWORD; @@ -184,13 +186,17 @@ class OC_CONFIG{ //create/fill database $CONFIG_DBTYPE=$dbtype; $CONFIG_DBNAME=$_POST['dbname']; - if($dbtype=='mysql'){ + if($dbtype!='sqlite'){ + $CONFIG_DBTABLEPREFIX=$_POST['dbtableprefix']; $CONFIG_DBHOST=$_POST['dbhost']; $CONFIG_DBUSER=$_POST['dbuser']; $CONFIG_DBPASSWORD=$_POST['dbpassword']; + }else{ + $_POST['dbtableprefix']=''; + $CONFIG_DBTABLEPREFIX=''; } try{ - if(isset($_POST['createdatabase']) and $CONFIG_DBTYPE=='mysql'){ + if(isset($_POST['createdatabase']) and $CONFIG_DBTYPE!='sqlite'){ self::createdatabase($_POST['dbadminuser'],$_POST['dbadminpwd']); } }catch(Exception $e){ @@ -209,7 +215,6 @@ class OC_CONFIG{ self::filldatabase(); } }catch(Exception $e){ - echo 'testin'; $error.='error while trying to fill the database
'; } if($CONFIG_DBTYPE=='sqlite'){ @@ -241,7 +246,8 @@ class OC_CONFIG{ $config.='$CONFIG_DATEFORMAT=\''.$_POST['dateformat']."';\n"; $config.='$CONFIG_DBTYPE=\''.$dbtype."';\n"; $config.='$CONFIG_DBNAME=\''.$_POST['dbname']."';\n"; - if($dbtype=='mysql'){ + $config.='$CONFIG_DBTABLEPREFIX=\''.$_POST['dbtableprefix']."';\n"; + if($dbtype!='sqlite'){ $config.='$CONFIG_DBHOST=\''.$_POST['dbhost']."';\n"; $config.='$CONFIG_DBUSER=\''.$_POST['dbuser']."';\n"; $config.='$CONFIG_DBPASSWORD=\''.$_POST['dbpassword']."';\n"; @@ -267,169 +273,73 @@ class OC_CONFIG{ } } } - - /** - * Fills the database with the initial tables - * Note: while the AUTO_INCREMENT function is not supported by SQLite - * the same effect can be achieved by accessing the SQLite pseudo-column - * "rowid" - */ - private static function filldatabase(){ - global $CONFIG_DBTYPE; - if($CONFIG_DBTYPE=='sqlite'){ - $query="CREATE TABLE 'locks' ( - 'token' VARCHAR(255) NOT NULL DEFAULT '', - 'path' varchar(200) NOT NULL DEFAULT '', - 'created' int(11) NOT NULL DEFAULT '0', - 'modified' int(11) NOT NULL DEFAULT '0', - 'expires' int(11) NOT NULL DEFAULT '0', - 'owner' varchar(200) DEFAULT NULL, - 'recursive' int(11) DEFAULT '0', - 'writelock' int(11) DEFAULT '0', - 'exclusivelock' int(11) NOT NULL DEFAULT '0', - PRIMARY KEY ('token'), - UNIQUE ('token') - ); - -CREATE TABLE 'log' ( - `id` INTEGER ASC DEFAULT '' NOT NULL, - 'timestamp' int(11) NOT NULL, - 'user' varchar(250) NOT NULL, - 'type' int(11) NOT NULL, - 'message' varchar(250) NOT NULL, - PRIMARY KEY ('id') -); - - -CREATE TABLE 'properties' ( - 'path' varchar(255) NOT NULL DEFAULT '', - 'name' varchar(120) NOT NULL DEFAULT '', - 'ns' varchar(120) NOT NULL DEFAULT 'DAV:', - 'value' text, - PRIMARY KEY ('path','name','ns') -); - -CREATE TABLE 'users' ( - 'user_id' INTEGER ASC DEFAULT '', - 'user_name' varchar(64) NOT NULL DEFAULT '', - 'user_name_clean' varchar(64) NOT NULL DEFAULT '', - 'user_password' varchar(40) NOT NULL DEFAULT '', - PRIMARY KEY ('user_id'), - UNIQUE ('user_name' ,'user_name_clean') -); - -CREATE TABLE 'groups' ( -'group_id' INTEGER ASC DEFAULT '', -'group_name' VARCHAR( 64 ) NOT NULL DEFAULT '', -PRIMARY KEY ('group_id'), -UNIQUE ('group_name') -); - -CREATE TABLE 'user_group' ( -'user_group_id' INTEGER ASC DEFAULT '', -'user_id' VARCHAR( 64 ) NOT NULL DEFAULT '', -'group_id' VARCHAR( 64 ) NOT NULL DEFAULT '', -PRIMARY KEY ('user_group_id') -) -"; - }elseif($CONFIG_DBTYPE=='mysql'){ - $query="CREATE TABLE IF NOT EXISTS `locks` ( - `token` varchar(255) NOT NULL DEFAULT '', - `path` varchar(200) NOT NULL DEFAULT '', - `created` int(11) NOT NULL DEFAULT '0', - `modified` int(11) NOT NULL DEFAULT '0', - `expires` int(11) NOT NULL DEFAULT '0', - `owner` varchar(200) DEFAULT NULL, - `recursive` int(11) DEFAULT '0', - `writelock` int(11) DEFAULT '0', - `exclusivelock` int(11) NOT NULL DEFAULT '0', - PRIMARY KEY (`token`), - UNIQUE KEY `token` (`token`), - KEY `path` (`path`), - KEY `path_2` (`path`), - KEY `path_3` (`path`,`token`), - KEY `expires` (`expires`) -); - -CREATE TABLE IF NOT EXISTS `log` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `timestamp` int(11) NOT NULL, - `user` varchar(250) NOT NULL, - `type` int(11) NOT NULL, - `message` varchar(250) NOT NULL, - PRIMARY KEY (`id`) -); - - -CREATE TABLE IF NOT EXISTS `properties` ( - `path` varchar(255) NOT NULL DEFAULT '', - `name` varchar(120) NOT NULL DEFAULT '', - `ns` varchar(120) NOT NULL DEFAULT 'DAV:', - `value` text, - PRIMARY KEY (`path`,`name`,`ns`), - KEY `path` (`path`) -); - -CREATE TABLE IF NOT EXISTS `users` ( -`user_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , -`user_name` VARCHAR( 64 ) NOT NULL , -`user_name_clean` VARCHAR( 64 ) NOT NULL , -`user_password` VARCHAR( 340) NOT NULL , -UNIQUE ( -`user_name` , -`user_name_clean` -) -); - -CREATE TABLE IF NOT EXISTS `groups` ( -`group_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , -`group_name` VARCHAR( 64 ) NOT NULL , -UNIQUE ( -`group_name` -) -); - -CREATE TABLE IF NOT EXISTS `user_group` ( -`user_group_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , -`user_id` VARCHAR( 64 ) NOT NULL , -`group_id` VARCHAR( 64 ) NOT NULL -) -"; + + /** + * Fills the database with the initial tables + * Note: while the AUTO_INCREMENT function is not supported by SQLite + * the same effect can be achieved by accessing the SQLite pseudo-column + * "rowid" + */ + private static function filldatabase(){ + global $SERVERROOT; + OC_DB::createDBFromStructure($SERVERROOT.'/db_structure.xml'); + } + + /** + * Create the database and user + * @param string adminUser + * @param string adminPwd + * + */ + private static function createdatabase($adminUser,$adminPwd){ + global $CONFIG_DBHOST; + global $CONFIG_DBNAME; + global $CONFIG_DBUSER; + global $CONFIG_DBPWD; + global $CONFIG_DBTYPE; + //we cant user OC_BD functions here because we need to connect as the administrative user. + if($CONFIG_DBTYPE=='mysql'){ + $connection = @new mysqli($CONFIG_DBHOST, $adminUser, $adminPwd); + if (mysqli_connect_errno()) { + @ob_end_clean(); + echo('

can not connect to database as administrative user.
'); + exit(); + } + $query="SELECT user FROM mysql.user WHERE user='{$_POST['dbuser']}';"; + $result = @$connection->query($query); + if (!$result) { + $entry='DB Error: "'.$connection->error.'"
'; + $entry.='Offending command was: '.$query.'
'; + echo($entry); + } + if($result->num_rows==0){ + $query="CREATE USER '{$_POST['dbuser']}' IDENTIFIED BY '{$_POST['dbpassword']}';"; + }else{ + $query=''; + } + $query.="CREATE DATABASE IF NOT EXISTS `{$_POST['dbname']}`;"; + $query.="GRANT ALL PRIVILEGES ON `{$_POST['dbname']}` . * TO '{$_POST['dbuser']}';"; + $result = @$connection->multi_query($query); + if (!$result) { + $entry='DB Error: "'.$connection->error.'"
'; + $entry.='Offending command was: '.$query.'
'; + echo($entry); + } + $connection->close(); + }elseif($CONFIG_DBTYPE=='pgsql'){ + $connection = pg_connect("user='$adminUser' host='$CONFIG_DBHOST' password='$adminPwd'"); + $query="CREATE USER {$_POST['dbuser']} WITH PASSWORD '{$_POST['dbpassword']}' CREATEDB;"; + $result = pg_exec($connection, $query); + $query="select count(*) from pg_catalog.pg_database where datname = '{$_POST['dbname']}';"; + $result = pg_exec($connection, $query); + if(pg_result($result,0,0)==0){ + $query="CREATE DATABASE {$_POST['dbname']};"; + $result = pg_exec($connection, $query); + $query="ALTER DATABASE {$_POST['dbname']} OWNER TO {$_POST['dbuser']};"; + $result = pg_exec($connection, $query); + } + } } - OC_DB::multiquery($query); - } - - /** - * Create the database and user - * @param string adminUser - * @param string adminPwd - * - */ - private static function createdatabase($adminUser,$adminPwd){ - global $CONFIG_DBHOST; - global $CONFIG_DBNAME; - global $CONFIG_DBUSER; - global $CONFIG_DBPWD; - //we cant user OC_BD functions here because we need to connect as the administrative user. - $connection = @new mysqli($CONFIG_DBHOST, $adminUser, $adminPwd); - if (mysqli_connect_errno()) { - @ob_end_clean(); - echo('

can not connect to database as administrative user.
'); - exit(); - } - $query="CREATE USER '{$_POST['dbuser']}' IDENTIFIED BY '{$_POST['dbpassword']}'; - -CREATE DATABASE IF NOT EXISTS `{$_POST['dbname']}` ; - -GRANT ALL PRIVILEGES ON `{$_POST['dbname']}` . * TO '{$_POST['dbuser']}';"; - $result = @$connection->multi_query($query); - if (!$result) { - $entry='DB Error: "'.$connection->error.'"
'; - $entry.='Offending command was: '.$query.'
'; - echo($entry); - } - $connection->close(); - } } ?> diff --git a/inc/lib_connect.php b/inc/lib_connect.php index ddf03eb6e7..9db867715e 100644 --- a/inc/lib_connect.php +++ b/inc/lib_connect.php @@ -75,9 +75,11 @@ class OC_REMOTE_CLOUD{ curl_setopt($ch, CURLOPT_COOKIEFILE,$this->cookiefile); curl_setopt($ch, CURLOPT_COOKIEJAR,$this->cookiefile); curl_setopt($ch,CURLOPT_RETURNTRANSFER,true); - $result=trim(curl_exec($ch)); + $result=curl_exec($ch); + $result=trim($result); $info=curl_getinfo($ch); $httpCode=$info['http_code']; + curl_close($ch); if($httpCode==200 or $httpCode==0){ return json_decode($result,$assoc); }else{ @@ -120,6 +122,70 @@ class OC_REMOTE_CLOUD{ $this->cookiefile=false; } + /** + * create a new file or directory + * @param string $dir + * @param string $name + * @param string $type + */ + public function newFile($dir,$name,$type){ + if(!$this->connected){ + return false; + } + return $this->apiCall('new',array('dir'=>$dir,'name'=>$name,'type'=>$type),true); + } + + /** + * deletes a file or directory + * @param string $dir + * @param string $file + */ + public function delete($dir,$name){ + if(!$this->connected){ + return false; + } + return $this->apiCall('delete',array('dir'=>$dir,'file'=>$name),true); + } + + /** + * moves a file or directory + * @param string $sorceDir + * @param string $sorceFile + * @param string $targetDir + * @param string $targetFile + */ + public function move($sourceDir,$sourceFile,$targetDir,$targetFile){ + if(!$this->connected){ + return false; + } + return $this->apiCall('move',array('sourcedir'=>$sourceDir,'source'=>$sourceFile,'targetdir'=>$targetDir,'target'=>$targetFile),true); + } + + /** + * copies a file or directory + * @param string $sorceDir + * @param string $sorceFile + * @param string $targetDir + * @param string $targetFile + */ + public function copy($sourceDir,$sourceFile,$targetDir,$targetFile){ + if(!$this->connected){ + return false; + } + return $this->apiCall('copy',array('sourcedir'=>$sourceDir,'source'=>$sourceFile,'targetdir'=>$targetDir,'target'=>$targetFile),true); + } + + /** + * get a file tree + * @param string $dir + */ + public function getTree($dir){ + if(!$this->connected){ + return false; + } + return $this->apiCall('gettree',array('dir'=>$dir),true); + } + /** * get the files inside a directory of the remote cloud * @param string $dir @@ -130,6 +196,53 @@ class OC_REMOTE_CLOUD{ } return $this->apiCall('getfiles',array('dir'=>$dir),true); } + + /** + * get a remove file and save it in a temporary file and return the path of the temporary file + * @param string $dir + * @param string $file + * @return string + */ + public function getFile($dir, $file){ + if(!$this->connected){ + return false; + } + $ch=curl_init(); + if(!$this->cookiefile){ + $this->cookiefile=sys_get_temp_dir().'/remoteCloudCookie'.uniqid(); + } + $tmpfile=tempnam(sys_get_temp_dir(),'remoteCloudFile'); + $fp=fopen($tmpfile,'w+'); + $url=$this->path.="/files/api.php?action=get&dir=$dir&file=$file"; + curl_setopt($ch,CURLOPT_URL,$url); + curl_setopt($ch, CURLOPT_COOKIEFILE,$this->cookiefile); + curl_setopt($ch, CURLOPT_COOKIEJAR,$this->cookiefile); + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_exec($ch); + fclose($fp); + curl_close($ch); + return $tmpfile; + } + + public function sendFile($sourceDir,$sourceFile,$targetDir,$targetFile){ + global $WEBROOT; + $source=$sourceDir.'/'.$sourceFile; + $tmp=OC_FILESYSTEM::toTmpFile($source); + return $this->sendTmpFile($tmp,$targetDir,$targetFile); + } + + public function sendTmpFile($tmp,$targetDir,$targetFile){ + $token=sha1(uniqid().$tmp); + global $WEBROOT; + $file=sys_get_temp_dir().'/'.'remoteCloudFile'.$token; + rename($tmp,$file); + if((isset($CONFIG_HTTPFORCESSL) and $CONFIG_HTTPFORCESSL) or isset($_SERVER['HTTPS']) and $_SERVER['HTTPS'] == 'on') { + $url = "https://". $_SERVER['SERVER_NAME'] . $WEBROOT; + }else{ + $url = "http://". $_SERVER['SERVER_NAME'] . $WEBROOT; + } + return $this->apiCall('pull',array('dir'=>$targetDir,'file'=>$targetFile,'token'=>$token,'source'=>$url),true); + } } function OC_CONNECT_TEST($path,$user,$password){ @@ -146,6 +259,30 @@ function OC_CONNECT_TEST($path,$user,$password){ foreach($files as $file){ echo "{$file['type']} {$file['name']}: {$file['size']} bytes
"; } + echo 'getting file "'.$file['name'].'"...'; + $size=$file['size']; + $file=$remote->getFile('',$file['name']); + if(file_exists($file)){ + $newSize=filesize($file); + if($size!=$newSize){ + echo "fail
Error: $newSize bytes received, $size expected."; + echo '

Recieved file:
'; + readfile($file); + unlink($file); + return; + } + OC_FILESYSTEM::fromTmpFile($file,'/remoteFile'); + echo 'done
'; + echo 'sending file "burning_avatar.png"...'; + $res=$remote->sendFile('','burning_avatar.png','','burning_avatar.png'); + if($res){ + echo 'done
'; + }else{ + echo 'fail
'; + } + }else{ + echo 'fail
'; + } }else{ echo 'fail
'; } diff --git a/inc/lib_files.php b/inc/lib_files.php index 1702ef20de..8f694536f7 100755 --- a/inc/lib_files.php +++ b/inc/lib_files.php @@ -54,7 +54,7 @@ class OC_FILES { $dirs=array(); $file=array(); $files=array(); - if (OC_FILESYSTEM::is_dir($directory)) { + if(OC_FILESYSTEM::is_dir($directory)) { if ($dh = OC_FILESYSTEM::opendir($directory)) { while (($filename = readdir($dh)) !== false) { if($filename<>'.' and $filename<>'..' and substr($filename,0,1)!='.'){ @@ -136,7 +136,11 @@ class OC_FILES { header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Pragma: public'); - header('Content-Length: ' . filesize($filename)); + if($zip){ + header('Content-Length: ' . filesize($filename)); + }else{ + header('Content-Length: ' . OC_FILESYSTEM::filesize($filename)); + } }elseif($zip or !OC_FILESYSTEM::file_exists($filename)){ header("HTTP/1.0 404 Not Found"); die('404 Not Found'); @@ -242,6 +246,44 @@ class OC_FILES { static function getMimeType($path){ return OC_FILESYSTEM::getMimeType($path); } + + /** + * get a file tree + * + * @param string path + * @return array + */ + static function getTree($path){ + return OC_FILESYSTEM::getTree($path); + } + + /** + * pull a file from a remote server + * @param string source + * @param string token + * @param string dir + * @param string file + * @return string guessed mime type + */ + static function pull($source,$token,$dir,$file){ + $tmpfile=tempnam(sys_get_temp_dir(),'remoteCloudFile'); + $fp=fopen($tmpfile,'w+'); + $url=$source.="/files/pull.php?token=$token"; + $ch=curl_init(); + curl_setopt($ch,CURLOPT_URL,$url); + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_exec($ch); + fclose($fp); + $info=curl_getinfo($ch); + $httpCode=$info['http_code']; + curl_close($ch); + if($httpCode==200 or $httpCode==0){ + OC_FILESYSTEM::fromTmpFile($tmpfile,$dir.'/'.$file); + return true; + }else{ + return false; + } + } } function zipAddDir($dir,$zip,$internalDir=''){ @@ -276,4 +318,46 @@ if(!function_exists('sys_get_temp_dir')) { } } +global $FAKEDIRS; +$FAKEDIRS=array(); + +class fakeDirStream{ + private $name; + private $data; + private $index; + + public function dir_opendir($path,$options){ + global $FAKEDIRS; + $url=parse_url($path); + $this->name=substr($path,strlen('fakedir://')); + $this->index=0; + if(isset($FAKEDIRS[$this->name])){ + $this->data=$FAKEDIRS[$this->name]; + }else{ + $this->data=array(); + } + return true; + } + + public function dir_readdir(){ + if($this->index>=count($this->data)){ + return false; + } + $filename=$this->data[$this->index]; + $this->index++; + return $filename; + } + + public function dir_closedir() { + $this->data=false; + $this->name=''; + return true; + } + + public function dir_rewinddir() { + $this->index=0; + return true; + } +} +stream_wrapper_register("fakedir", "fakeDirStream"); ?> \ No newline at end of file diff --git a/inc/lib_filestorage.php b/inc/lib_filestorage.php index 85382a4444..10266d4eaf 100755 --- a/inc/lib_filestorage.php +++ b/inc/lib_filestorage.php @@ -62,7 +62,7 @@ class OC_FILESTORAGE{ public function filemtime($path){} public function fileatime($path){} public function file_get_contents($path){} - public function file_put_contents($path){} + public function file_put_contents($path,$data){} public function unlink($path){} public function rename($path1,$path2){} public function copy($path1,$path2){} @@ -149,8 +149,8 @@ class OC_FILESTORAGE_LOCAL extends OC_FILESTORAGE{ } return $return; } - public function file_put_contents($path){ - if($return=file_put_contents($this->datadir.$path)){ + public function file_put_contents($path,$data){ + if($return=file_put_contents($this->datadir.$path,$data)){ $this->notifyObservers($path,OC_FILEACTION_WRITE); } } diff --git a/inc/lib_filesystem.php b/inc/lib_filesystem.php index 6eb317f442..492e0c5d38 100755 --- a/inc/lib_filesystem.php +++ b/inc/lib_filesystem.php @@ -199,7 +199,7 @@ class OC_FILESYSTEM{ return $storage->file_get_contents(substr($path,strlen(self::getMountPoint($path)))); } } - static public function file_put_contents($path){ + static public function file_put_contents($path,$data){ if(self::canWrite($path) and $storage=self::getStorage($path)){ $this->notifyObservers($path,OC_FILEACTION_WRITE | OC_FILEACTION_CREATE); return $storage->file_put_contents(substr($path,strlen(self::getMountPoint($path)))); diff --git a/inc/lib_log.php b/inc/lib_log.php index 1244a44a93..989308c82f 100755 --- a/inc/lib_log.php +++ b/inc/lib_log.php @@ -48,7 +48,8 @@ class OC_LOG { * @param message $message */ public static function event($user,$type,$message){ - $result = OC_DB::query('insert into log (timestamp,user,type,message) values ("'.time().'","'.addslashes($user).'","'.addslashes($type).'","'.addslashes($message).'")'); + global $CONFIG_DBTABLEPREFIX; + $result = OC_DB::query('INSERT INTO `' . $CONFIG_DBTABLEPREFIX . 'log` (`timestamp`,`user`,`type`,`message`) VALUES ('.time().',\''.addslashes($user).'\','.addslashes($type).',\''.addslashes($message).'\');'); } @@ -57,14 +58,15 @@ class OC_LOG { * */ public static function show(){ - global $CONFIG_DATEFORMAT; + global $CONFIG_DATEFORMAT; + global $CONFIG_DBTABLEPREFIX; echo('
'); - + if(OC_USER::ingroup($_SESSION['username_clean'],'admin')){ - $result = OC_DB::select('select timestamp,user,type,message from log order by timestamp desc limit 20'); + $result = OC_DB::select('select `timestamp`,`user`,`type`,`message` from '.$CONFIG_DBTABLEPREFIX.'log order by timestamp desc limit 20'); }else{ $user=$_SESSION['username_clean']; - $result = OC_DB::select('select timestamp,user,type,message from log where user=\''.$user.'\' order by timestamp desc limit 20'); + $result = OC_DB::select('select `timestamp`,`user`,`type`,`message` from '.$CONFIG_DBTABLEPREFIX.'log where user=\''.$user.'\' order by timestamp desc limit 20'); } foreach($result as $entry){ echo(''); diff --git a/inc/lib_ocs.php b/inc/lib_ocs.php index 52dc95800a..e464d2ed9f 100755 --- a/inc/lib_ocs.php +++ b/inc/lib_ocs.php @@ -372,15 +372,16 @@ class OC_OCS { * @return string xml/json */ private static function activityget($format,$page,$pagesize) { + global $CONFIG_DBTABLEPREFIX; $user=OC_OCS::checkpassword(); - $result = OC_DB::query('select count(*) as co from log'); + $result = OC_DB::query("select count(*) as co from {$CONFIG_DBTABLEPREFIX}log"); $entry=$result->fetchRow(); $totalcount=$entry['co']; OC_DB::free_result($result); - $result = OC_DB::select('select id,timestamp,user,type,message from log order by timestamp desc limit '.($page*$pagesize).','.$pagesize); + $result = OC_DB::select("select id,timestamp,user,type,message from {$CONFIG_DBTABLEPREFIX}log order by timestamp desc limit " . ($page*$pagesize) . ",$pagesize"); $itemscount=count($result); $url='http://'.substr($_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'],0,-11).''; diff --git a/inc/lib_remotestorage.php b/inc/lib_remotestorage.php new file mode 100644 index 0000000000..f8835c1641 --- /dev/null +++ b/inc/lib_remotestorage.php @@ -0,0 +1,353 @@ +. +* +*/ + + +class OC_FILESTORAGE_REMOTE extends OC_FILESTORAGE{ + private $url; + private $username; + private $password; + private $remote=false; + private $statCache; + private $statCacheDir=false; + private $changed=array(); + + private function cacheDir($dir){ + if($this->statCacheDir!=$dir or $this->statCacheDir===false){ + $this->statCache=$this->remote->getFiles($dir); + $keys=array_keys($this->statCache); + $this->statCacheDir=$dir; + } + } + + public function __construct($arguments){ + $this->url=$arguments['url']; + $this->username=$arguments['username']; + $this->password=$arguments['password']; + } + private function connect(){ + if($this->remote===false){ + $this->remote=OC_CONNECT::connect($this->url,$this->username,$this->password); + } + } + public function mkdir($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $return=$this->remote->newFile($parent,$name,'dir'); + if($return){ + $this->notifyObservers($path,OC_FILEACTION_CREATE); + } + return $return; + } + public function rmdir($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $return=$this->remote->delete($parent,$name); + if($return){ + $this->notifyObservers($path,OC_FILEACTION_DELETE); + } + return $return; + } + public function opendir($path){ + $this->connect(); + $this->cacheDir($path); + $dirs=array_keys($this->statCache); + $id=uniqid(); + global $FAKEDIRS; + $FAKEDIRS[$id]=$dirs; + if($return=opendir("fakedir://$id")){ + $this->notifyObservers($path,OC_FILEACTION_READ); + } + return $return; + } + public function is_dir($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $this->cacheDir($path); + if($path=='' or $path=='/'){ + return true; + } + if(!isset($this->statCache[$name])){ + return false; + } + return ($this->statCache[$name]['type'=='dir']); + } + public function is_file($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $this->cacheDir($parent); + if(!isset($this->statCache[$name])){ + return false; + } + return ($this->statCache[$name]['type'!='dir']); + } + public function stat($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $this->cacheDir($parent); + if(!isset($this->statCache[$name])){ + return $false; + } + return $this->statCache[$name]; + } + public function filetype($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $this->cacheDir($parent); + if(!isset($this->statCache[$name])){ + return false; + } + return $this->statCache[$name]['type']; + } + public function filesize($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $this->cacheDir($parent); + if(!isset($this->statCache[$name])){ + return $false; + } + return $this->statCache[$name]['size']; + } + public function is_readable($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $this->cacheDir($parent); + if(!isset($this->statCache[$name])){ + return false; + } + return $this->statCache[$name]['readable']; + } + public function is_writeable($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $this->cacheDir($parent); + if(!isset($this->statCache[$name])){ + return false; + } + return $this->statCache[$name]['writeable']; + } + public function file_exists($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $this->cacheDir($parent); + return isset($this->statCache[$name]); + } + public function readfile($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $file=$this->remote->getFile($parent,$name); + readfile($file); + unlink($file); + } + public function filectime($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $this->cacheDir($parent); + if(!isset($this->statCache[$name])){ + return false; + } + return $this->statCache[$name]['ctime']; + } + public function filemtime($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $this->cacheDir($parent); + if(!isset($this->statCache[$name])){ + return false; + } + return $this->statCache[$name]['mtime']; + } + public function fileatime($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $this->cacheDir($parent); + if(!isset($this->statCache[$name])){ + return false; + } + return $this->statCache[$name]['atime']; + } + public function file_get_contents($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $file=$this->remote->getFile($parent,$name); + file_get_contents($file); + unlink($file); + } + public function file_put_contents($path,$data){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $file=$this->remote->getFile($parent,$name); + $file=tempnam(sys_get_temp_dir(),'oc_'); + file_put_contents($file,$data); + if($return=$this->remote->sendTmpFile($file,$parent,$name)){ + $this->notifyObservers($path,OC_FILEACTION_WRITE); + } + } + public function unlink($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + if($return=$this->remote->delete($paren,$name)){ + $this->notifyObservers($path,OC_FILEACTION_DELETE); + } + return $return; + } + public function rename($path1,$path2){ + $this->connect(); + $parent1=dirname($path1); + $name1=substr($path1,strlen($parent1)+1); + $parent2=dirname($path2); + $name2=substr($path2,strlen($parent2)+1); + if($return=$this->remote->move($parent1,$name1,$parent2,$name2)){ + $this->notifyObservers($path1.'->'.$path2,OC_FILEACTION_RENAME); + } + return $return; + } + public function copy($path1,$path2){ + $this->connect(); + $parent1=dirname($path1); + $name1=substr($path1,strlen($parent1)+1); + $parent2=dirname($path2); + $name2=substr($path2,strlen($parent2)+1); + if($return=$this->copy->rename($parent1,$name1,$parent2,$name2)){ + $this->notifyObservers($path1.'->'.$path2,OC_FILEACTION_RENAME); + } + return $return; + } + public function fopen($path,$mode){ + $this->connect(); + $changed=false; + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + $file=$this->remote->getFile($parent,$name); + if($return=fopen($file,$mode)){ + switch($mode){ + case 'r': + $this->notifyObservers($path,OC_FILEACTION_READ); + break; + case 'r+': + case 'w+': + case 'x+': + case 'a+': + $this->notifyObservers($path,OC_FILEACTION_READ | OC_FILEACTION_WRITE); + $this->changed[]=array('dir'=>$parent,'file'=>$name,'tmp'=>$file); + break; + case 'w': + case 'x': + case 'a': + $this->notifyObservers($path,OC_FILEACTION_WRITE); + $this->changed[]=array('dir'=>$parent,'file'=>$name,'tmp'=>$file); + break; + } + } + return $return; + } + + public function getMimeType($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + if(substr($name,0,1)=='/'){ + $name=substr($name,1); + } + $this->cacheDir($parent); + if(!isset($this->statCache[$name])){ + return false; + } + return $this->statCache[$name]['mime']; + } + + public function toTmpFile($path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + if(substr($name,0,1)=='/'){ + $name=substr($name,1); + } + $filename=$this->remote->getFile($parent,$name); + if($filename){ + $this->notifyObservers($path,OC_FILEACTION_READ); + return $filename; + }else{ + return false; + } + } + + public function fromTmpFile($tmpFile,$path){ + $this->connect(); + $parent=dirname($path); + $name=substr($path,strlen($parent)+1); + if($this->remote->sendTmpFile($tmpFile,$parent,$name)){ + $this->notifyObservers($path,OC_FILEACTION_CREATE); + return true; + }else{ + return false; + } + } + + public function delTree($dir) { + $this->connect(); + $parent=dirname($dir); + $name=substr($dir,strlen($parent)+1); + $return=$this->remote->delete($parent,$name); + if($return=rmdir($dir)){ + $this->notifyObservers($dir,OC_FILEACTION_DELETE); + } + return $return; + } + + public function find($path){ + return $this->getTree($path); + } + + public function getTree($dir) { + $this->connect(); + if($return=$this->remote->getTree($dir)){ + $this->notifyObservers($dir,OC_FILEACTION_READ); + } + return $return; + } + + public function __destruct(){ + foreach($this->changed as $changed){ + $this->remote->sendTmpFile($changed['tmp'],$changed['dir'],$changed['file']); + } + } +} + +?> diff --git a/inc/lib_user.php b/inc/lib_user.php index 7a75e02ece..896478bf28 100755 --- a/inc/lib_user.php +++ b/inc/lib_user.php @@ -27,6 +27,14 @@ if(!$CONFIG_INSTALLED){ $_SESSION['username_clean']=''; } +//cache the userid's an groupid's +if(!isset($_SESSION['user_id_cache'])){ + $_SESSION['user_id_cache']=array(); +} +if(!isset($_SESSION['group_id_cache'])){ + $_SESSION['group_id_cache']=array(); +} + /** * Class for usermanagement * @@ -63,14 +71,15 @@ class OC_USER { * */ public static function createuser($username,$password){ - if(OC_USER::getuserid($username)!=0){ + global $CONFIG_DBTABLEPREFIX; + if(OC_USER::getuserid($username,true)!=0){ return false; }else{ - $password=sha1($password); $usernameclean=strtolower($username); + $password=sha1($password); $username=OC_DB::escape($username); $usernameclean=OC_DB::escape($usernameclean); - $query="INSERT INTO `users` (`user_id` ,`user_name` ,`user_name_clean` ,`user_password`) VALUES (NULL , '$username', '$usernameclean', '$password')"; + $query="INSERT INTO `{$CONFIG_DBTABLEPREFIX}users` (`user_name` ,`user_name_clean` ,`user_password`) VALUES ('$username', '$usernameclean', '$password')"; $result=OC_DB::query($query); return ($result)?true:false; } @@ -82,11 +91,13 @@ class OC_USER { * */ public static function login($username,$password){ + global $CONFIG_DBTABLEPREFIX; + $password=sha1($password); $usernameclean=strtolower($username); $username=OC_DB::escape($username); $usernameclean=OC_DB::escape($usernameclean); - $query="SELECT user_id FROM users WHERE user_name_clean = '$usernameclean' AND user_password = '$password' LIMIT 1"; + $query = "SELECT user_id FROM {$CONFIG_DBTABLEPREFIX}users WHERE user_name_clean = '$usernameclean' AND user_password = '$password' LIMIT 1"; $result=OC_DB::select($query); if(isset($result[0]) && isset($result[0]['user_id'])){ $_SESSION['user_id']=$result[0]['user_id']; @@ -124,9 +135,10 @@ class OC_USER { * */ public static function creategroup($groupname){ - if(OC_USER::getgroupid($groupname)==0){ + global $CONFIG_DBTABLEPREFIX; + if(OC_USER::getgroupid($groupname,true)==0){ $groupname=OC_DB::escape($groupname); - $query="INSERT INTO `groups` (`group_id` ,`group_name`) VALUES (NULL , '$groupname')"; + $query="INSERT INTO `{$CONFIG_DBTABLEPREFIX}groups` (`group_name`) VALUES ('$groupname')"; $result=OC_DB::query($query); return ($result)?true:false; }else{ @@ -138,16 +150,20 @@ class OC_USER { * get the id of a user * */ - public static function getuserid($username){ + public static function getuserid($username,$nocache=false){ + global $CONFIG_DBTABLEPREFIX; $usernameclean=strtolower($username); - $username=OC_DB::escape($username); + if(!$nocache and isset($_SESSION['user_id_cache'][$usernameclean])){//try to use cached value to save an sql query + return $_SESSION['user_id_cache'][$usernameclean]; + } $usernameclean=OC_DB::escape($usernameclean); - $query="SELECT user_id FROM users WHERE user_name_clean = '$usernameclean'"; + $query="SELECT user_id FROM {$CONFIG_DBTABLEPREFIX}users WHERE user_name_clean = '$usernameclean'"; $result=OC_DB::select($query); if(!is_array($result)){ return 0; } if(isset($result[0]) && isset($result[0]['user_id'])){ + $_SESSION['user_id_cache'][$usernameclean]=$result[0]['user_id']; return $result[0]['user_id']; }else{ return 0; @@ -158,14 +174,19 @@ class OC_USER { * get the id of a group * */ - public static function getgroupid($groupname){ + public static function getgroupid($groupname,$nocache=false){ + global $CONFIG_DBTABLEPREFIX; + if(!$nocache and isset($_SESSION['group_id_cache'][$groupname])){//try to use cached value to save an sql query + return $_SESSION['group_id_cache'][$groupname]; + } $groupname=OC_DB::escape($groupname); - $query="SELECT group_id FROM groups WHERE group_name = '$groupname'"; + $query="SELECT group_id FROM {$CONFIG_DBTABLEPREFIX}groups WHERE group_name = '$groupname'"; $result=OC_DB::select($query); if(!is_array($result)){ return 0; } if(isset($result[0]) && isset($result[0]['group_id'])){ + $_SESSION['group_id_cache'][$groupname]=$result[0]['group_id']; return $result[0]['group_id']; }else{ return 0; @@ -176,9 +197,13 @@ class OC_USER { * get the name of a group * */ - public static function getgroupname($groupid){ + public static function getgroupname($groupid,$nocache=false){ + global $CONFIG_DBTABLEPREFIX; + if($nocache and $name=array_search($groupid,$_SESSION['group_id_cache'])){//try to use cached value to save an sql query + return $name; + } $groupid=(integer)$groupid; - $query="SELECT group_name FROM groups WHERE group_id = '$groupid' LIMIT 1"; + $query="SELECT group_name FROM {$CONFIG_DBTABLEPREFIX}groups WHERE group_id = '$groupid' LIMIT 1"; $result=OC_DB::select($query); if(isset($result[0]) && isset($result[0]['group_name'])){ return $result[0]['group_name']; @@ -192,10 +217,12 @@ class OC_USER { * */ public static function ingroup($username,$groupname){ + global $CONFIG_DBTABLEPREFIX; + $userid=OC_USER::getuserid($username); $groupid=OC_USER::getgroupid($groupname); if($groupid>0 and $userid>0){ - $query="SELECT user_group_id FROM user_group WHERE group_id = $groupid AND user_id = $userid LIMIT 1"; + $query="SELECT * FROM {$CONFIG_DBTABLEPREFIX}user_group WHERE group_id = '$groupid' AND user_id = '$userid';"; $result=OC_DB::select($query); if(isset($result[0]) && isset($result[0]['user_group_id'])){ return true; @@ -212,11 +239,13 @@ class OC_USER { * */ public static function addtogroup($username,$groupname){ + global $CONFIG_DBTABLEPREFIX; + if(!OC_USER::ingroup($username,$groupname)){ $userid=OC_USER::getuserid($username); $groupid=OC_USER::getgroupid($groupname); if($groupid!=0 and $userid!=0){ - $query="INSERT INTO `user_group` (`user_group_id` ,`user_id` ,`group_id`) VALUES (NULL , '$userid', '$groupid');"; + $query="INSERT INTO `{$CONFIG_DBTABLEPREFIX}user_group` (`user_id` ,`group_id`) VALUES ('$userid', '$groupid');"; $result=OC_DB::query($query); if($result){ return true; @@ -240,8 +269,10 @@ class OC_USER { * */ public static function getusergroups($username){ + global $CONFIG_DBTABLEPREFIX; + $userid=OC_USER::getuserid($username); - $query="SELECT group_id FROM user_group WHERE user_id = '$userid'"; + $query = "SELECT group_id FROM {$CONFIG_DBTABLEPREFIX}user_group WHERE user_id = '$userid'"; $result=OC_DB::select($query); $groups=array(); if(is_array($result)){ @@ -258,9 +289,11 @@ class OC_USER { * */ public static function setpassword($username,$password){ + global $CONFIG_DBTABLEPREFIX; + $password=sha1($password); $userid=OC_USER::getuserid($username); - $query="UPDATE users SET user_password = '$password' WHERE user_id ='$userid'"; + $query = "UPDATE {$CONFIG_DBTABLEPREFIX}users SET user_password = '$password' WHERE user_id ='$userid'"; $result=OC_DB::query($query); if($result){ return true; @@ -274,11 +307,13 @@ class OC_USER { * */ public static function checkpassword($username,$password){ + global $CONFIG_DBTABLEPREFIX; + $password=sha1($password); $usernameclean=strtolower($username); $username=OC_DB::escape($username); $usernameclean=OC_DB::escape($usernameclean); - $query="SELECT user_id FROM 'users' WHERE user_name_clean = '$usernameclean' AND user_password = '$password' LIMIT 1"; + $query = "SELECT user_id FROM '{$CONFIG_DBTABLEPREFIX}users' WHERE user_name_clean = '$usernameclean' AND user_password = '$password' LIMIT 1"; $result=OC_DB::select($query); if(isset($result[0]) && isset($result[0]['user_id']) && $result[0]['user_id']>0){ return true; diff --git a/inc/templates/adminform.php b/inc/templates/adminform.php index 9846cfee6d..882c0dd377 100755 --- a/inc/templates/adminform.php +++ b/inc/templates/adminform.php @@ -10,6 +10,7 @@ if(!$f) die('Error: Config file (config/config.php) is not writable for the webs if(!isset($fillDB)) $fillDB=true; if(!isset($CONFIG_DBHOST)) $CONFIG_DBHOST='localhost'; if(!isset($CONFIG_DBUSER)) $CONFIG_DBUSER='owncloud'; +if(!isset($CONFIG_DBTABLEPREFIX)) $CONFIG_DBTABLEPREFIX='oc_'; $newuserpassword=OC_USER::generatepassword(); ?>