From b076a460e5418ae8f0db0b4b392d91853fd2a21b Mon Sep 17 00:00:00 2001 From: thomascube Date: Wed, 26 Oct 2005 22:12:36 +0000 Subject: Finished message sorting and fixed some skin issues --- CHANGELOG | 4 +- UPGRADING | 23 +- config/main.inc.php.dist | 8 +- index.php | 12 +- program/include/main.inc | 4 +- program/include/rcube_imap.inc | 4 +- program/include/rcube_mdb2.inc | 34 +- program/js/app.js | 21 +- program/lib/MDB2/Driver/Datatype/mysql.php | 543 +++++++++++++++++ program/lib/MDB2/Driver/Manager/mysql.php | 749 ++++++++++++++++++++++++ program/lib/MDB2/Driver/Native/mysql.php | 58 ++ program/lib/MDB2/Driver/Reverse/mysql.php | 298 ++++++++++ program/lib/MDB2/Driver/mysql.php | 911 +++++++++++++++++++++++++++++ program/lib/imap.inc | 11 +- program/localization/de/labels.inc | 3 + program/localization/en/labels.inc | 3 + program/localization/nl/labels.inc | 11 +- program/localization/nl/messages.inc | 2 +- program/steps/mail/func.inc | 50 +- program/steps/mail/list.inc | 21 +- program/steps/mail/show.inc | 2 +- skins/default/includes/header.html | 7 - skins/default/includes/taskbar.html | 6 + skins/default/mail.css | 6 + skins/default/templates/addidentity.html | 2 +- skins/default/templates/addressbook.html | 1 + skins/default/templates/compose.html | 1 + skins/default/templates/editidentity.html | 2 +- skins/default/templates/identities.html | 2 +- skins/default/templates/mail.html | 5 +- skins/default/templates/managefolders.html | 2 +- skins/default/templates/message.html | 1 + skins/default/templates/settings.html | 3 +- 33 files changed, 2737 insertions(+), 73 deletions(-) create mode 100644 program/lib/MDB2/Driver/Datatype/mysql.php create mode 100644 program/lib/MDB2/Driver/Manager/mysql.php create mode 100644 program/lib/MDB2/Driver/Native/mysql.php create mode 100644 program/lib/MDB2/Driver/Reverse/mysql.php create mode 100644 program/lib/MDB2/Driver/mysql.php create mode 100644 skins/default/includes/taskbar.html diff --git a/CHANGELOG b/CHANGELOG index 7683bd9f0..144bdb1fd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -65,4 +65,6 @@ CHANGELOG RoundCube Webmail - Get IMAP server capabilities in array - Check for NAMESPACE capability before sending command - Set default user language from config 'locale_string' -- Added sorting patch (not finished yet) +- Added sorting patch for message list +- Make default sort col/order configurable +- diff --git a/UPGRADING b/UPGRADING index 714df94ac..85666dc21 100644 --- a/UPGRADING +++ b/UPGRADING @@ -19,6 +19,9 @@ from versions 0.1-alpha and 0.1-20050811 $rcmail_config['prettydate'] = TRUE; $rcmail_config['smtp_port'] = 25; $rcmail_config['default_port'] = 143; + $rcmail_config['session_lifetime'] = 20; + $rcmail_config['message_sort_col'] = 'date'; + $rcmail_config['message_sort_order'] = 'DESC'; - replace database properties (db_type, db_host, db_user, db_pass, $d_name) in /config/db.inc.php with the following line: $rcmail_config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcubemail'; @@ -34,6 +37,9 @@ from version 0.1-20050820 $rcmail_config['prettydate'] = TRUE; $rcmail_config['smtp_port'] = 25; $rcmail_config['default_port'] = 143; + $rcmail_config['session_lifetime'] = 20; + $rcmail_config['message_sort_col'] = 'date'; + $rcmail_config['message_sort_order'] = 'DESC'; - replace database properties (db_type, db_host, db_user, db_pass, $d_name) in /config/db.inc.php with the following line: $rcmail_config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcubemail'; @@ -44,5 +50,18 @@ from version 0.1-20051007 - replace index.php - replace all files in folder /program/ - replace all files in folder /skins/default/ -- add $rcmail_config['smtp_auth_type'] if you need to specify an auth method for SMTP -- $rcmail_config['session_lifetime'] to specify the session lifetime in minutes \ No newline at end of file +- add these lines to /config/main.inc.php + $rcmail_config['smtp_auth_type'] = ''; // if you need to specify an auth method for SMTP + $rcmail_config['session_lifetime'] = 20; // to specify the session lifetime in minutes + $rcmail_config['message_sort_col'] = 'date'; + $rcmail_config['message_sort_order'] = 'DESC'; + + +from version 0.1-20051021 +---------------------------------------- +- replace index.php +- replace all files in folder /program/ +- replace all files in folder /skins/default/ +- add these lines to /config/main.inc.php + $rcmail_config['message_sort_col'] = 'date'; + $rcmail_config['message_sort_order'] = 'DESC'; diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist index 5031db47f..816c6184c 100644 --- a/config/main.inc.php.dist +++ b/config/main.inc.php.dist @@ -100,6 +100,13 @@ $rcmail_config['trash_mbox'] = 'Trash'; // display these folders separately in the mailbox list $rcmail_config['default_imap_folders'] = array('INBOX', 'Drafts', 'Sent', 'Junk', 'Trash'); +// default sort col +$rcmail_config['message_sort_col'] = 'date'; + +// default sort order +$rcmail_config['message_sort_order'] = 'DESC'; + + /***** these settings can be overwritten by user's preferences *****/ @@ -115,6 +122,5 @@ $rcmail_config['prefer_html'] = TRUE; // show pretty dates as standard $rcmail_config['prettydate'] = TRUE; - // end of config file ?> diff --git a/index.php b/index.php index c80100a33..f10886f36 100644 --- a/index.php +++ b/index.php @@ -42,19 +42,17 @@ */ // define global vars -$INSTALL_PATH = './'; +$INSTALL_PATH = dirname($_SERVER['SCRIPT_FILENAME']); $OUTPUT_TYPE = 'html'; $JS_OBJECT_NAME = 'rcmail'; -$CURRENT_PATH = dirname($_SERVER['SCRIPT_FILENAME']); -if ($CURRENT_PATH!='') - $CURRENT_PATH.='/'; +if ($INSTALL_PATH!='') + $INSTALL_PATH .= '/'; -// set environment first // RC include folders MUST be included FIRST to avoid other // possible not compatible libraries (i.e PEAR) to be included // instead the ones provided by RC -ini_set('include_path', $INSTALL_PATH.PATH_SEPARATOR.$CURRENT_PATH.'program'.PATH_SEPARATOR.$CURRENT_PATH.'program/lib'.PATH_SEPARATOR.ini_get('include_path')); +ini_set('include_path', $INSTALL_PATH.PATH_SEPARATOR.$INSTALL_PATH.'program'.PATH_SEPARATOR.$INSTALL_PATH.'program/lib'.PATH_SEPARATOR.ini_get('include_path')); ini_set('session.name', 'sessid'); ini_set('session.use_cookies', 1); @@ -143,7 +141,7 @@ else if ($_action=='logout' && isset($_SESSION['user_id'])) } // check session cookie and auth string -else if ($_action!='login' && $_auth && $sess_auth) +else if ($_action!='login' && $sess_auth) { if ($_auth !== $sess_auth || $_auth != rcmail_auth_hash($_SESSION['client_id'], $_SESSION['auth_time']) || ($CONFIG['session_lifetime'] && $SESS_CHANGED + $CONFIG['session_lifetime']*60 < mktime())) diff --git a/program/include/main.inc b/program/include/main.inc index 0e206166e..ce9eaf069 100644 --- a/program/include/main.inc +++ b/program/include/main.inc @@ -947,8 +947,10 @@ function format_date($date, $format=NULL) if (is_numeric($date)) $ts = $date; - else + else if (!empty($date)) $ts = strtotime($date); + else + return ''; // convert time to user's timezone $timestamp = $ts - date('Z', $ts) + ($CONFIG['timezone'] * 3600); diff --git a/program/include/rcube_imap.inc b/program/include/rcube_imap.inc index 47999c860..8253442ab 100644 --- a/program/include/rcube_imap.inc +++ b/program/include/rcube_imap.inc @@ -433,7 +433,7 @@ class rcube_imap } - // old function; replaced 2005/10/18 + // original function; replaced 2005/10/18 // private method for listing message header function _list_headers($mailbox='', $page=NULL, $sort_field='date', $sort_order='DESC') { @@ -478,7 +478,7 @@ class rcube_imap // return complete list of messages if (strtolower($page)=='all') return $a_headers; - + $start_msg = ($this->list_page-1) * $this->page_size; return array_slice($a_headers, $start_msg, $this->page_size); } diff --git a/program/include/rcube_mdb2.inc b/program/include/rcube_mdb2.inc index a61f0b899..53590aa0b 100755 --- a/program/include/rcube_mdb2.inc +++ b/program/include/rcube_mdb2.inc @@ -55,7 +55,7 @@ class rcube_db function dsn_connect($dsn) { // Use persistent connections if available - $dbh = MDB2::factory($dsn, array('persistent' => $true)); + $dbh = MDB2::factory($dsn, array('persistent' => TRUE)); if (PEAR::isError($dbh)) raise_error(array('code' => 500, @@ -104,21 +104,37 @@ class rcube_db // Query database function query() + { + $params = func_get_args(); + $query = array_shift($params); + + return $this->_query($query, 0, 0, $params); + } + + function limitquery() + { + $params = func_get_args(); + $query = array_shift($params); + $offset = array_shift($params); + $numrows = array_shift($params); + + return $this->_query($query, $offset, $numrows, $params); + } function _query($query, $offset, $numrows, $params) @@ -194,23 +210,39 @@ class rcube_db } function quoteIdentifier ( $str ) + { + if (!$this->db_handle) + $this->db_connect('r'); + + return $this->db_handle->quoteIdentifier($str); + } function unixtimestamp($field) + { + switch($this->db_provider) + { + case 'pgsql': + return "EXTRACT (EPOCH FROM $field)"; + break; + default: + return "UNIX_TIMESTAMP($field)"; + } + } function _add_result($res, $query) diff --git a/program/js/app.js b/program/js/app.js index 7179898d8..f56a7d866 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -6,7 +6,7 @@ | Copyright (C) 2005, RoundCube Dev, - Switzerland | | Licensed under the GNU GPL | | | - | Modified: 2005/10/21 (roundcube) | + | Modified: 2005/10/26 (roundcube) | | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli | @@ -445,6 +445,25 @@ function rcube_webmail() case 'sort': // get the type of sorting + var a_sort = props.split('_'); + var sort_col = a_sort[0]; + var sort_order = a_sort[1].toUpperCase(); + var header; + + if (this.env.sort_col==sort_col && this.env.sort_order==sort_order) + break; + + // set table header class + if (header = document.getElementById('rcmHead'+this.env.sort_col)) + this.set_classname(header, 'sorted'+(this.env.sort_order.toUpperCase()), false); + if (header = document.getElementById('rcmHead'+sort_col)) + this.set_classname(header, 'sorted'+sort_order, true); + + // save new sort properties + this.env.sort_col = sort_col; + this.env.sort_order = sort_order; + + // reload message list this.list_mailbox('', '', props); break; diff --git a/program/lib/MDB2/Driver/Datatype/mysql.php b/program/lib/MDB2/Driver/Datatype/mysql.php new file mode 100644 index 000000000..f57867025 --- /dev/null +++ b/program/lib/MDB2/Driver/Datatype/mysql.php @@ -0,0 +1,543 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Datatype/Common.php'; + +/** + * MDB2 MySQL driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Datatype_mysql extends MDB2_Driver_Datatype_Common +{ + // }}} + // {{{ _getIntegerDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an integer type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $field associative array with the name of the properties + * of the field being declared as array indexes. + * Currently, the types of supported field + * properties are as follows: + * + * unsigned + * Boolean flag that indicates whether the field + * should be declared as unsigned integer if + * possible. + * + * default + * Integer value to be used as default for this + * field. + * + * notnull + * Boolean flag that indicates whether this field is + * constrained to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getIntegerDeclaration($name, $field) + { + if (array_key_exists('autoincrement', $field) && $field['autoincrement']) { + $autoinc = ' AUTO_INCREMENT PRIMARY KEY'; + $default = ''; + } else { + $autoinc = ''; + $default = array_key_exists('default', $field) ? ' DEFAULT '. + $this->quote($field['default'], 'integer') : ''; + } + + $unsigned = (array_key_exists('unsigned', $field) && $field['unsigned']) ? ' UNSIGNED' : ''; + $notnull = (array_key_exists('notnull', $field) && $field['notnull']) ? ' NOT NULL' : ''; + return $name.' INT'.$unsigned.$default.$notnull.$autoinc; + } + + // }}} + // {{{ _getCLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an character + * large object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $field associative array with the name of the + * properties of the field being declared as array + * indexes. Currently, the types of supported field + * properties are as follows: + * + * length + * Integer value that determines the maximum length + * of the large object field. If this argument is + * missing the field should be declared to have the + * longest length allowed by the DBMS. + * + * notnull + * Boolean flag that indicates whether this field + * is constrained to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getCLOBDeclaration($name, $field) + { + if (array_key_exists('length', $field)) { + $length = $field['length']; + if ($length <= 255) { + $type = 'TINYTEXT'; + } else { + if ($length <= 65535) { + $type = 'TEXT'; + } else { + if ($length <= 16777215) { + $type = 'MEDIUMTEXT'; + } else { + $type = 'LONGTEXT'; + } + } + } + } else { + $type = 'LONGTEXT'; + } + $notnull = (array_key_exists('notnull', $field) && $field['notnull']) ? ' NOT NULL' : ''; + return $name.' '.$type.$notnull; + } + + // }}} + // {{{ _getBLOBDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an binary large + * object type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $field associative array with the name of the properties + * of the field being declared as array indexes. + * Currently, the types of supported field + * properties are as follows: + * + * length + * Integer value that determines the maximum length + * of the large object field. If this argument is + * missing the field should be declared to have the + * longest length allowed by the DBMS. + * + * notnull + * Boolean flag that indicates whether this field is + * constrained to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getBLOBDeclaration($name, $field) + { + if (array_key_exists('length', $field)) { + $length = $field['length']; + if ($length <= 255) { + $type = 'TINYBLOB'; + } else { + if ($length <= 65535) { + $type = 'BLOB'; + } else { + if ($length <= 16777215) { + $type = 'MEDIUMBLOB'; + } else { + $type = 'LONGBLOB'; + } + } + } + } else { + $type = 'LONGBLOB'; + } + $notnull = (array_key_exists('notnull', $field) && $field['notnull']) ? ' NOT NULL' : ''; + return $name.' '.$type.$notnull; + } + + // }}} + // {{{ _getDateDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an date type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $field associative array with the name of the properties + * of the field being declared as array indexes. + * Currently, the types of supported field properties + * are as follows: + * + * default + * Date value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is + * constrained to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getDateDeclaration($name, $field) + { + $default = array_key_exists('default', $field) ? ' DEFAULT '. + $this->quote($field['default'], 'date') : ''; + $notnull = (array_key_exists('notnull', $field) && $field['notnull']) ? ' NOT NULL' : ''; + return $name.' DATE'.$default.$notnull; + } + + // }}} + // {{{ _getTimestampDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an timestamp + * type field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $field associative array with the name of the properties + * of the field being declared as array indexes. + * Currently, the types of supported field + * properties are as follows: + * + * default + * Time stamp value to be used as default for this + * field. + * + * notnull + * Boolean flag that indicates whether this field is + * constrained to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getTimestampDeclaration($name, $field) + { + $default = array_key_exists('default', $field) ? ' DEFAULT '. + $this->quote($field['default'], 'timestamp') : ''; + $notnull = (array_key_exists('notnull', $field) && $field['notnull']) ? ' NOT NULL' : ''; + return $name.' DATETIME'.$default.$notnull; + } + + // }}} + // {{{ _getTimeDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an time type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $field associative array with the name of the properties + * of the field being declared as array indexes. + * Currently, the types of supported field + * properties are as follows: + * + * default + * Time value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is + * constrained to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getTimeDeclaration($name, $field) + { + $default = array_key_exists('default', $field) ? ' DEFAULT '. + $this->quote($field['default'], 'time') : ''; + $notnull = (array_key_exists('notnull', $field) && $field['notnull']) ? ' NOT NULL' : ''; + return $name.' TIME'.$default.$notnull; + } + + // }}} + // {{{ _getFloatDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an float type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $field associative array with the name of the properties + * of the field being declared as array indexes. + * Currently, the types of supported field + * properties are as follows: + * + * default + * Integer value to be used as default for this + * field. + * + * notnull + * Boolean flag that indicates whether this field is + * constrained to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getFloatDeclaration($name, $field) + { + $type = 'DOUBLE'; + $default = array_key_exists('default', $field) ? ' DEFAULT '. + $this->quote($field['default'], 'float') : ''; + $notnull = (array_key_exists('notnull', $field) && $field['notnull']) ? ' NOT NULL' : ''; + return $name.' '.$type.$default.$notnull; + } + + // }}} + // {{{ _getDecimalDeclaration() + + /** + * Obtain DBMS specific SQL code portion needed to declare an decimal type + * field to be used in statements like CREATE TABLE. + * + * @param string $name name the field to be declared. + * @param string $field associative array with the name of the properties + * of the field being declared as array indexes. + * Currently, the types of supported field + * properties are as follows: + * + * default + * Integer value to be used as default for this + * field. + * + * notnull + * Boolean flag that indicates whether this field is + * constrained to not be set to null. + * @return string DBMS specific SQL code portion that should be used to + * declare the specified field. + * @access protected + */ + function _getDecimalDeclaration($name, $field) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $type = 'DECIMAL(18,'.$db->options['decimal_places'].')'; + $default = array_key_exists('default', $field) ? ' DEFAULT '. + $this->quote($field['default'], 'decimal') : ''; + $notnull = (array_key_exists('notnull', $field) && $field['notnull']) ? ' NOT NULL' : ''; + return $name.' '.$type.$default.$notnull; + } + + // }}} + // {{{ _quoteBLOB() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param $value + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteBLOB($value) + { + $value = $this->_readFile($value); + return "'".addslashes($value)."'"; + } + + // }}} + // {{{ _quoteFloat() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteFloat($value) + { + return (float)$value; + } + + // }}} + // {{{ _quoteDecimal() + + /** + * Convert a text value into a DBMS specific format that is suitable to + * compose query statements. + * + * @param string $value text string value that is intended to be converted. + * @return string text string that represents the given argument value in + * a DBMS specific format. + * @access protected + */ + function _quoteDecimal($value) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->escape($value); + } + + // }}} + // {{{ mapNativeDatatype() + + /** + * Maps a native array description of a field to a MDB2 datatype and length + * + * @param array $field native field description + * @return array containing the various possible types and the length + * @access public + */ + function mapNativeDatatype($field) + { + $db_type = strtolower($field['type']); + $db_type = strtok($db_type, '(), '); + if ($db_type == 'national') { + $db_type = strtok('(), '); + } + $length = strtok('(), '); + $decimal = strtok('(), '); + $type = array(); + switch ($db_type) { + case 'tinyint': + case 'smallint': + case 'mediumint': + case 'int': + case 'integer': + case 'bigint': + $type[] = 'integer'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/^[is|has]/', $field['field'])) { + $type = array_reverse($type); + } + } + break; + case 'char': + case 'varchar': + $type[] = 'text'; + if ($length == '1') { + $type[] = 'boolean'; + if (preg_match('/[is|has]/', $field['field'])) { + $type = array_reverse($type); + } + } + break; + case 'enum': + preg_match_all('/\'.+\'/U', $field['type'], $matches); + $length = 0; + if (is_array($matches)) { + foreach ($matches[0] as $value) { + $length = max($length, strlen($value)-2); + } + } + case 'set': + $type[] = 'text'; + $type[] = 'integer'; + break; + case 'date': + $type[] = 'date'; + $length = null; + break; + case 'datetime': + case 'timestamp': + $type[] = 'timestamp'; + $length = null; + break; + case 'time': + $type[] = 'time'; + $length = null; + break; + case 'float': + case 'double': + case 'real': + $type[] = 'float'; + break; + case 'decimal': + case 'numeric': + $type[] = 'decimal'; + break; + case 'tinytext': + case 'mediumtext': + case 'longtext': + case 'text': + if ($decimal == 'binary') { + $type[] = 'blob'; + } + $type[] = 'clob'; + $type[] = 'text'; + break; + case 'tinyblob': + case 'mediumblob': + case 'longblob': + case 'blob': + $type[] = 'blob'; + $type[] = 'text'; + $length = null; + break; + case 'year': + $type[] = 'integer'; + $type[] = 'date'; + $length = null; + break; + default: + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->raiseError(MDB2_ERROR, null, null, + 'getTableFieldDefinition: unknown database attribute type'); + } + + return array($type, $length); + } +} + +?> diff --git a/program/lib/MDB2/Driver/Manager/mysql.php b/program/lib/MDB2/Driver/Manager/mysql.php new file mode 100644 index 000000000..120467c77 --- /dev/null +++ b/program/lib/MDB2/Driver/Manager/mysql.php @@ -0,0 +1,749 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Manager/Common.php'; + +/** + * MDB2 MySQL driver for the management modules + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Manager_mysql extends MDB2_Driver_Manager_Common +{ + // {{{ properties + var $verified_table_types = array();# + // }}} + + // }}} + // {{{ _verifyTableType() + + /** + * verify that chosen transactional table hanlder is available in the database + * + * @param string $table_type name of the table handler + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access protected + */ + function _verifyTableType($table_type) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + switch (strtoupper($table_type)) { + case 'BERKELEYDB': + case 'BDB': + $check = array('have_bdb'); + break; + case 'INNODB': + $check = array('have_innobase', 'have_innodb'); + break; + case 'GEMINI': + $check = array('have_gemini'); + break; + case 'HEAP': + case 'ISAM': + case 'MERGE': + case 'MRG_MYISAM': + case 'MYISAM': + case '': + return MDB2_OK; + default: + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + $table_type.' is not a supported table type'); + } + if (isset($this->verified_table_types[$table_type]) + && $this->verified_table_types[$table_type] == $db->connection + ) { + return MDB2_OK; + } + $not_supported = false; + for ($i = 0, $j = count($check); $i < $j; ++$i) { + $query = 'SHOW VARIABLES LIKE '.$db->quote($check[$i], 'text'); + $has = $db->queryRow($query, null, MDB2_FETCHMODE_ORDERED); + if (PEAR::isError($has)) { + return $has; + } + if (is_array($has)) { + $not_supported = true; + if ($has[1] == 'YES') { + $this->verified_table_types[$table_type] = $db->connection; + return MDB2_OK; + } + } + } + if ($not_supported) { + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + $table_type.' is not a supported table type by this MySQL database server'); + } + return $db->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'could not tell if '.$table_type.' is a supported table type'); + } + + // }}} + // {{{ createDatabase() + + /** + * create a new database + * + * @param string $name name of the database that should be created + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createDatabase($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'CREATE DATABASE '.$name; + $result = $db->query($query); + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ dropDatabase() + + /** + * drop an existing database + * + * @param string $name name of the database that should be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropDatabase($name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $query = 'DROP DATABASE '.$name; + $result = $db->query($query); + if (PEAR::isError($result)) { + return $result; + } + return MDB2_OK; + } + + // }}} + // {{{ createTable() + + /** + * create a new table + * + * @param string $name Name of the database that should be created + * @param array $fields Associative array that contains the definition of each field of the new table + * The indexes of the array entries are the names of the fields of the table an + * the array entry values are associative arrays like those that are meant to be + * passed with the field definitions to get[Type]Declaration() functions. + * + * Example + * array( + * + * 'id' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * 'notnull' => 1 + * 'default' => 0 + * ), + * 'name' => array( + * 'type' => 'text', + * 'length' => 12 + * ), + * 'password' => array( + * 'type' => 'text', + * 'length' => 12 + * ) + * ); + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createTable($name, $fields) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (!$name) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'createTable: no valid table name specified'); + } + if (empty($fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'createTable: no fields specified for table "'.$name.'"'); + } + $verify = $this->_verifyTableType($db->options['default_table_type']); + if (PEAR::isError($verify)) { + return $verify; + } + $query_fields = $this->getFieldDeclarationList($fields); + if (PEAR::isError($query_fields)) { + return $db->raiseError(MDB2_ERROR_CANNOT_CREATE, null, null, + 'createTable: '.$this->getUserinfo()); + } + $query = "CREATE TABLE $name ($query_fields)".(strlen($db->options['default_table_type']) + ? ' TYPE='.$db->options['default_table_type'] : ''); + + return $db->query($query); + } + + // }}} + // {{{ alterTable() + + /** + * alter an existing table + * + * @param string $name name of the table that is intended to be changed. + * @param array $changes associative array that contains the details of each type + * of change that is intended to be performed. The types of + * changes that are currently supported are defined as follows: + * + * name + * + * New name for the table. + * + * add + * + * Associative array with the names of fields to be added as + * indexes of the array. The value of each entry of the array + * should be set to another associative array with the properties + * of the fields to be added. The properties of the fields should + * be the same as defined by the Metabase parser. + * + * + * remove + * + * Associative array with the names of fields to be removed as indexes + * of the array. Currently the values assigned to each entry are ignored. + * An empty array should be used for future compatibility. + * + * rename + * + * Associative array with the names of fields to be renamed as indexes + * of the array. The value of each entry of the array should be set to + * another associative array with the entry named name with the new + * field name and the entry named Declaration that is expected to contain + * the portion of the field declaration already in DBMS specific SQL code + * as it is used in the CREATE TABLE statement. + * + * change + * + * Associative array with the names of the fields to be changed as indexes + * of the array. Keep in mind that if it is intended to change either the + * name of a field and any other properties, the change array entries + * should have the new names of the fields as array indexes. + * + * The value of each entry of the array should be set to another associative + * array with the properties of the fields to that are meant to be changed as + * array entries. These entries should be assigned to the new values of the + * respective properties. The properties of the fields should be the same + * as defined by the Metabase parser. + * + * Example + * array( + * 'name' => 'userlist', + * 'add' => array( + * 'quota' => array( + * 'type' => 'integer', + * 'unsigned' => 1 + * ) + * ), + * 'remove' => array( + * 'file_limit' => array(), + * 'time_limit' => array() + * ), + * 'change' => array( + * 'gender' => array( + * 'default' => 'M', + * ) + * ), + * 'rename' => array( + * 'sex' => array( + * 'name' => 'gender', + * ) + * ) + * ) + * + * @param boolean $check indicates whether the function should just check if the DBMS driver + * can perform the requested table alterations if the value is true or + * actually perform them otherwise. + * @access public + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function alterTable($name, $changes, $check) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + foreach ($changes as $change_name => $change) { + switch ($change_name) { + case 'add': + case 'remove': + case 'change': + case 'rename': + case 'name': + break; + default: + return $db->raiseError(MDB2_ERROR_CANNOT_ALTER, null, null, + 'alterTable: change type "'.$change_name.'" not yet supported'); + } + } + + if ($check) { + return MDB2_OK; + } + + $query = (array_key_exists('name', $changes) ? 'RENAME AS '.$changes['name'] : ''); + + if (array_key_exists('add', $changes)) { + foreach ($changes['add'] as $field_name => $field) { + $type_declaration = $db->getDeclaration($field['type'], $field_name, $field); + if (PEAR::isError($type_declaration)) { + return $err; + } + if ($query) { + $query.= ', '; + } + $query.= 'ADD ' . $type_declaration; + } + } + + if (array_key_exists('remove', $changes)) { + foreach ($changes['remove'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + $query.= 'DROP ' . $field_name; + } + } + + $rename = array(); + if (array_key_exists('rename', $changes)) { + foreach ($changes['rename'] as $field_name => $field) { + $rename[$field['name']] = $field_name; + } + } + + if (array_key_exists('change', $changes)) { + foreach ($changes['change'] as $field_name => $field) { + if ($query) { + $query.= ', '; + } + if (isset($rename[$field_name])) { + $old_field_name = $rename[$field_name]; + unset($rename[$field_name]); + } else { + $old_field_name = $field_name; + } + $query.= "CHANGE $field_name " . $db->getDeclaration($field['type'], $old_field_name, $field); + } + } + + if (!empty($rename)) { + foreach ($rename as $rename_name => $renamed_field) { + if ($query) { + $query.= ', '; + } + $old_field_name = $renamed_field; + $field = $changes['rename'][$old_field_name]; + $query.= 'CHANGE ' . $db->getDeclaration($field['type'], $old_field_name, $field); + } + } + + if (!$query) { + return MDB2_OK; + } + + return $db->query("ALTER TABLE $name $query"); + } + + // }}} + // {{{ listDatabases() + + /** + * list all databases + * + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listDatabases() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $databases = $db->queryCol('SHOW DATABASES'); + return $databases; + } + + // }}} + // {{{ listUsers() + + /** + * list all users + * + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listUsers() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $users = $db->queryCol('SELECT DISTINCT USER FROM USER'); + return $users; + } + + // }}} + // {{{ listTables() + + /** + * list all tables in the current database + * + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listTables() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table_names = $db->queryCol('SHOW TABLES'); + if (PEAR::isError($table_names)) { + return $table_names; + } + + $tables = array(); + for ($i = 0, $j = count($table_names); $i < $j; ++$i) { + if (!$this->_isSequenceName($table_names[$i])) + $tables[] = $table_names[$i]; + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $tables = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $tables); + } + + return $tables; + } + + // }}} + // {{{ listTableFields() + + /** + * list all fields in a tables in the current database + * + * @param string $table name of table that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listTableFields($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $fields = $db->queryCol("SHOW COLUMNS FROM $table"); + if (PEAR::isError($fields)) { + return $fields; + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $fields = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $fields); + } + + return $fields; + } + + // }}} + // {{{ createIndex() + + /** + * get the stucture of a field into an array + * + * @param string $table name of the table on which the index is to be created + * @param string $name name of the index to be created + * @param array $definition associative array that defines properties of the index to be created. + * Currently, only one property named FIELDS is supported. This property + * is also an associative with the names of the index fields as array + * indexes. Each entry of this array is set to another type of associative + * array that specifies properties of the index that are specific to + * each field. + * + * Currently, only the sorting property is supported. It should be used + * to define the sorting direction of the index. It may be set to either + * ascending or descending. + * + * Not all DBMS support index sorting direction configuration. The DBMS + * drivers of those that do not support it ignore this property. Use the + * function supports() to determine whether the DBMS driver can manage indexes. + + * Example + * array( + * 'fields' => array( + * 'user_name' => array( + * 'sorting' => 'ascending' + * ), + * 'last_login' => array() + * ) + * ) + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createIndex($table, $name, $definition) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (array_key_exists('primary', $definition) && $definition['primary']) { + $type = 'PRIMARY'; + $name = 'KEY'; + } elseif (array_key_exists('unique', $definition) && $definition['unique']) { + $type = 'UNIQUE'; + } else { + $type = 'INDEX'; + } + + $query = "ALTER TABLE $table ADD $type $name ("; + $query.= implode(', ', array_keys($definition['fields'])); + $query.= ')'; + + return $db->query($query); + } + + // }}} + // {{{ dropIndex() + + /** + * drop existing index + * + * @param string $table name of table that should be used in method + * @param string $name name of the index to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropIndex($table, $name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + return $db->query("ALTER TABLE $table DROP INDEX $name"); + } + + // }}} + // {{{ listTableIndexes() + + /** + * list all indexes in a table + * + * @param string $table name of table that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listTableIndexes($table) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $key_name = 'Key_name'; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + } else { + $key_name = strtoupper($key_name); + } + } + + $query = "SHOW INDEX FROM $table"; + $indexes_all = $db->queryCol($query, 'text', $key_name); + if (PEAR::isError($indexes_all)) { + return $indexes_all; + } + + $indexes = array_unique($indexes_all); + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $indexes = array_map(($db->options['field_case'] == CASE_LOWER ? 'strtolower' : 'strtoupper'), $indexes); + } + + return $indexes; + } + + // }}} + // {{{ createSequence() + + /** + * create sequence + * + * @param string $seq_name name of the sequence to be created + * @param string $start start value of the sequence; default is 1 + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function createSequence($seq_name, $start = 1) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->getSequenceName($seq_name); + $seqcol_name = $db->options['seqcol_name']; + $result = $this->_verifyTableType($db->options['default_table_type']); + if (PEAR::isError($result)) { + return $result; + } + + $res = $db->query("CREATE TABLE $sequence_name". + "($seqcol_name INT NOT NULL AUTO_INCREMENT, PRIMARY KEY ($seqcol_name))". + (strlen($db->options['default_table_type']) ? ' TYPE='.$db->options['default_table_type'] : '') + ); + + if (PEAR::isError($res)) { + return $res; + } + + if ($start == 1) { + return MDB2_OK; + } + + $res = $db->query("INSERT INTO $sequence_name ($seqcol_name) VALUES (".($start-1).')'); + if (!PEAR::isError($res)) { + return MDB2_OK; + } + + // Handle error + $result = $db->query("DROP TABLE $sequence_name"); + if (PEAR::isError($result)) { + return $db->raiseError(MDB2_ERROR, null, null, + 'createSequence: could not drop inconsistent sequence table ('. + $result->getMessage().' ('.$result->getUserinfo().'))'); + } + + return $db->raiseError(MDB2_ERROR, null, null, + 'createSequence: could not create sequence table ('. + $res->getMessage().' ('.$res->getUserinfo().'))'); + } + + // }}} + // {{{ dropSequence() + + /** + * drop existing sequence + * + * @param string $seq_name name of the sequence to be dropped + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function dropSequence($seq_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $sequence_name = $db->getSequenceName($seq_name); + return $db->query("DROP TABLE $sequence_name"); + } + + // }}} + // {{{ listSequences() + + /** + * list all sequences in the current database + * + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function listSequences() + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $table_names = $db->queryCol('SHOW TABLES'); + if (PEAR::isError($table_names)) { + return $table_names; + } + + $sequences = array(); + for ($i = 0, $j = count($table_names); $i < $j; ++$i) { + if ($sqn = $this->_isSequenceName($table_names[$i])) { + $sequences[] = $sqn; + } + } + + return $sequences; + } + + // }}} +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Native/mysql.php b/program/lib/MDB2/Driver/Native/mysql.php new file mode 100644 index 000000000..53970af1c --- /dev/null +++ b/program/lib/MDB2/Driver/Native/mysql.php @@ -0,0 +1,58 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +/** + * MDB2 MySQL driver for the native module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Native_mysql extends MDB2_Module_Common +{ +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/Reverse/mysql.php b/program/lib/MDB2/Driver/Reverse/mysql.php new file mode 100644 index 000000000..c17e4f6d4 --- /dev/null +++ b/program/lib/MDB2/Driver/Reverse/mysql.php @@ -0,0 +1,298 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +require_once 'MDB2/Driver/Reverse/Common.php'; + +/** + * MDB2 MySQL driver for the schema reverse engineering module + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_Reverse_mysql extends MDB2_Driver_Reverse_Common +{ + // {{{ getTableFieldDefinition() + + /** + * get the stucture of a field into an array + * + * @param string $table name of table that should be used in method + * @param string $field_name name of field that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableFieldDefinition($table, $field_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->loadModule('Datatype'); + if (PEAR::isError($result)) { + return $result; + } + $columns = $db->queryAll("SHOW COLUMNS FROM $table", null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($columns)) { + return $columns; + } + foreach ($columns as $column) { + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $column['field'] = strtolower($column['field']); + } else { + $column['field'] = strtoupper($column['field']); + } + } else { + $column = array_change_key_case($column, $db->options['field_case']); + } + if ($field_name == $column['field']) { + list($types, $length) = $db->datatype->mapNativeDatatype($column); + $notnull = false; + if (array_key_exists('null', $column) && $column['null'] != 'YES') { + $notnull = true; + } + $default = false; + if (array_key_exists('default', $column)) { + $default = $column['default']; + if (is_null($default) && $notnull) { + $default = ''; + } + } + $autoincrement = false; + if (array_key_exists('extra', $column) && $column['extra'] == 'auto_increment') { + $autoincrement = true; + } + $definition = array(); + foreach ($types as $key => $type) { + $definition[$key] = array( + 'type' => $type, + 'notnull' => $notnull, + ); + if ($length > 0) { + $definition[$key]['length'] = $length; + } + if ($default !== false) { + $definition[$key]['default'] = $default; + } + if ($autoincrement !== false) { + $definition[$key]['autoincrement'] = $autoincrement; + } + } + return $definition; + } + } + + return $db->raiseError(MDB2_ERROR, null, null, + 'getTableFieldDefinition: it was not specified an existing table column'); + } + + // }}} + // {{{ getTableIndexDefinition() + + /** + * get the stucture of an index into an array + * + * @param string $table name of table that should be used in method + * @param string $index_name name of index that should be used in method + * @return mixed data array on success, a MDB2 error on failure + * @access public + */ + function getTableIndexDefinition($table, $index_name) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + $result = $db->query("SHOW INDEX FROM $table"); + if (PEAR::isError($result)) { + return $result; + } + $definition = array(); + while (is_array($row = $result->fetchRow(MDB2_FETCHMODE_ASSOC))) { + if (!($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) + || $db->options['field_case'] != CASE_LOWER + ) { + $row = array_change_key_case($row, CASE_LOWER); + } + $key_name = $row['key_name']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $key_name = strtolower($key_name); + } else { + $key_name = strtoupper($key_name); + } + } + if ($index_name == $key_name) { + if ($row['key_name'] == 'PRIMARY') { + $definition['primary'] = true; + } elseif (!$row['non_unique']) { + $definition['unique'] = true; + } + $column_name = $row['column_name']; + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $column_name = strtolower($column_name); + } else { + $column_name = strtoupper($column_name); + } + } + $definition['fields'][$column_name] = array(); + if (array_key_exists('collation', $row)) { + $definition['fields'][$column_name]['sorting'] = ($row['collation'] == 'A' + ? 'ascending' : 'descending'); + } + } + } + $result->free(); + if (!array_key_exists('fields', $definition)) { + return $db->raiseError(MDB2_ERROR, null, null, + 'getTableIndexDefinition: it was not specified an existing table index'); + } + return $definition; + } + + // }}} + // {{{ tableInfo() + + /** + * Returns information about a table or a result set + * + * @param object|string $result MDB2_result object from a query or a + * string containing the name of a table. + * While this also accepts a query result + * resource identifier, this behavior is + * deprecated. + * @param int $mode a valid tableInfo mode + * + * @return array an associative array with the information requested. + * A MDB2_Error object on failure. + * + * @see MDB2_Driver_Common::setOption() + */ + function tableInfo($result, $mode = null) + { + $db =& $this->getDBInstance(); + if (PEAR::isError($db)) { + return $db; + } + + if (is_string($result)) { + /* + * Probably received a table name. + * Create a result resource identifier. + */ + $connected = $db->connect(); + if (PEAR::isError($connected)) { + return $connected; + } + $id = @mysql_list_fields($db->database_name, $result, $db->connection); + $got_string = true; + } elseif (MDB2::isResultCommon($result)) { + /* + * Probably received a result object. + * Extract the result resource identifier. + */ + $id = $result->getResource(); + $got_string = false; + } else { + /* + * Probably received a result resource identifier. + * Copy it. + * Deprecated. Here for compatibility only. + */ + $id = $result; + $got_string = false; + } + + if (!is_resource($id)) { + return $db->raiseError(MDB2_ERROR_NEED_MORE_DATA); + } + + if ($db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + if ($db->options['field_case'] == CASE_LOWER) { + $case_func = 'strtolower'; + } else { + $case_func = 'strtoupper'; + } + } else { + $case_func = 'strval'; + } + + $count = @mysql_num_fields($id); + $res = array(); + + if ($mode) { + $res['num_fields'] = $count; + } + + for ($i = 0; $i < $count; $i++) { + $res[$i] = array( + 'table' => $case_func(@mysql_field_table($id, $i)), + 'name' => $case_func(@mysql_field_name($id, $i)), + 'type' => @mysql_field_type($id, $i), + 'len' => @mysql_field_len($id, $i), + 'flags' => @mysql_field_flags($id, $i), + ); + if ($mode & MDB2_TABLEINFO_ORDER) { + $res['order'][$res[$i]['name']] = $i; + } + if ($mode & MDB2_TABLEINFO_ORDERTABLE) { + $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i; + } + } + + // free the result only if we were called on a table + if ($got_string) { + @mysql_free_result($id); + } + return $res; + } +} +?> \ No newline at end of file diff --git a/program/lib/MDB2/Driver/mysql.php b/program/lib/MDB2/Driver/mysql.php new file mode 100644 index 000000000..387512c8c --- /dev/null +++ b/program/lib/MDB2/Driver/mysql.php @@ -0,0 +1,911 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id$ +// + +/** + * MDB2 MySQL driver + * + * @package MDB2 + * @category Database + * @author Lukas Smith + */ +class MDB2_Driver_mysql extends MDB2_Driver_Common +{ + // {{{ properties + var $escape_quotes = "\\"; + + // }}} + // {{{ constructor + + /** + * Constructor + */ + function __construct() + { + parent::__construct(); + + $this->phptype = 'mysql'; + $this->dbsyntax = 'mysql'; + + $this->supported['sequences'] = 'emulated'; + $this->supported['indexes'] = true; + $this->supported['affected_rows'] = true; + $this->supported['transactions'] = false; + $this->supported['summary_functions'] = true; + $this->supported['order_by_text'] = true; + $this->supported['current_id'] = 'emulated'; + $this->supported['limit_queries'] = true; + $this->supported['LOBs'] = true; + $this->supported['replace'] = true; + $this->supported['sub_selects'] = 'emulated'; + $this->supported['auto_increment'] = true; + $this->supported['primary_key'] = true; + + $this->options['default_table_type'] = null; + } + + // }}} + // {{{ errorInfo() + + /** + * This method is used to collect information about an error + * + * @param integer $error + * @return array + * @access public + */ + function errorInfo($error = null) + { + if ($this->connection) { + $native_code = @mysql_errno($this->connection); + $native_msg = @mysql_error($this->connection); + } else { + $native_code = @mysql_errno(); + $native_msg = @mysql_error(); + } + if (is_null($error)) { + static $ecode_map; + if (empty($ecode_map)) { + $ecode_map = array( + 1004 => MDB2_ERROR_CANNOT_CREATE, + 1005 => MDB2_ERROR_CANNOT_CREATE, + 1006 => MDB2_ERROR_CANNOT_CREATE, + 1007 => MDB2_ERROR_ALREADY_EXISTS, + 1008 => MDB2_ERROR_CANNOT_DROP, + 1022 => MDB2_ERROR_ALREADY_EXISTS, + 1044 => MDB2_ERROR_ACCESS_VIOLATION, + 1046 => MDB2_ERROR_NODBSELECTED, + 1048 => MDB2_ERROR_CONSTRAINT, + 1049 => MDB2_ERROR_NOSUCHDB, + 1050 => MDB2_ERROR_ALREADY_EXISTS, + 1051 => MDB2_ERROR_NOSUCHTABLE, + 1054 => MDB2_ERROR_NOSUCHFIELD, + 1061 => MDB2_ERROR_ALREADY_EXISTS, + 1062 => MDB2_ERROR_ALREADY_EXISTS, + 1064 => MDB2_ERROR_SYNTAX, + 1091 => MDB2_ERROR_NOT_FOUND, + 1100 => MDB2_ERROR_NOT_LOCKED, + 1136 => MDB2_ERROR_VALUE_COUNT_ON_ROW, + 1142 => MDB2_ERROR_ACCESS_VIOLATION, + 1146 => MDB2_ERROR_NOSUCHTABLE, + 1216 => MDB2_ERROR_CONSTRAINT, + 1217 => MDB2_ERROR_CONSTRAINT, + ); + } + if ($this->options['portability'] & MDB2_PORTABILITY_ERRORS) { + $ecode_map[1022] = MDB2_ERROR_CONSTRAINT; + $ecode_map[1048] = MDB2_ERROR_CONSTRAINT_NOT_NULL; + $ecode_map[1062] = MDB2_ERROR_CONSTRAINT; + } else { + // Doing this in case mode changes during runtime. + $ecode_map[1022] = MDB2_ERROR_ALREADY_EXISTS; + $ecode_map[1048] = MDB2_ERROR_CONSTRAINT; + $ecode_map[1062] = MDB2_ERROR_ALREADY_EXISTS; + } + if (isset($ecode_map[$native_code])) { + $error = $ecode_map[$native_code]; + } + } + return array($error, $native_code, $native_msg); + } + + // }}} + // {{{ escape() + + /** + * Quotes a string so it can be safely used in a query. It will quote + * the text so it can safely be used within a query. + * + * @param string $text the input string to quote + * @return string quoted string + * @access public + */ + function escape($text) + { + return @mysql_real_escape_string($text); + } + + // }}} + // {{{ quoteIdentifier() + + /** + * Quote a string so it can be safely used as a table or column name + * + * Quoting style depends on which database driver is being used. + * + * MySQL can't handle the backtick character (`) in + * table or column names. + * + * @param string $str identifier name to be quoted + * + * @return string quoted identifier string + * + * @access public + * @internal + */ + function quoteIdentifier($str) + { + return '`' . $str . '`'; + } + + // }}} + // {{{ beginTransaction() + + /** + * Start a transaction. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function beginTransaction() + { + $this->debug('starting transaction', 'beginTransaction'); + if (!$this->supports('transactions')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'beginTransaction: transactions are not in use'); + } + if ($this->in_transaction) { + return MDB2_OK; //nothing to do + } + if (!$this->destructor_registered && $this->opened_persistent) { + $this->destructor_registered = true; + register_shutdown_function('MDB2_closeOpenTransactions'); + } + $result = $this->_doQuery('SET AUTOCOMMIT = 0', true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = true; + return MDB2_OK; + } + + // }}} + // {{{ commit() + + /** + * Commit the database changes done during a transaction that is in + * progress. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function commit() + { + $this->debug('commit transaction', 'commit'); + if (!$this->supports('transactions')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'commit: transactions are not in use'); + } + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR, null, null, + 'commit: transaction changes are being auto committed'); + } + $result = $this->_doQuery('COMMIT', true); + if (PEAR::isError($result)) { + return $result; + } + $result = $this->_doQuery('SET AUTOCOMMIT = 1', true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + } + + // }}} + // {{{ rollback() + + /** + * Cancel any database changes done during a transaction that is in + * progress. + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function rollback() + { + $this->debug('rolling back transaction', 'rollback'); + if (!$this->supports('transactions')) { + return $this->raiseError(MDB2_ERROR_UNSUPPORTED, null, null, + 'rollback: transactions are not in use'); + } + if (!$this->in_transaction) { + return $this->raiseError(MDB2_ERROR, null, null, + 'rollback: transactions can not be rolled back when changes are auto committed'); + } + $result = $this->_doQuery('ROLLBACK', true); + if (PEAR::isError($result)) { + return $result; + } + $result = $this->_doQuery('SET AUTOCOMMIT = 1', true); + if (PEAR::isError($result)) { + return $result; + } + $this->in_transaction = false; + return MDB2_OK; + + } + + // }}} + // {{{ connect() + + /** + * Connect to the database + * + * @return true on success, MDB2 Error Object on failure + */ + function connect() + { + if (is_resource($this->connection)) { + if (count(array_diff($this->connected_dsn, $this->dsn)) == 0 + && $this->opened_persistent == $this->options['persistent'] + ) { + return MDB2_OK; + } + $this->disconnect(false); + } + + if (!PEAR::loadExtension($this->phptype)) { + return $this->raiseError(MDB2_ERROR_NOT_FOUND, null, null, + 'connect: extension '.$this->phptype.' is not compiled into PHP'); + } + + $params = array(); + if ($this->dsn['protocol'] && $this->dsn['protocol'] == 'unix') { + $params[0] = ':' . $this->dsn['socket']; + } else { + $params[0] = $this->dsn['hostspec'] ? $this->dsn['hostspec'] + : 'localhost'; + if ($this->dsn['port']) { + $params[0].= ':' . $this->dsn['port']; + } + } + $params[] = $this->dsn['username'] ? $this->dsn['username'] : null; + $params[] = $this->dsn['password'] ? $this->dsn['password'] : null; + if (!$this->options['persistent']) { + if (isset($this->dsn['new_link']) + && ($this->dsn['new_link'] == 'true' || $this->dsn['new_link'] === true) + ) { + $params[] = true; + } else { + $params[] = false; + } + } + if (version_compare(phpversion(), '4.3.0', '>=')) { + $params[] = isset($this->dsn['client_flags']) + ? $this->dsn['client_flags'] : null; + } + + $connect_function = $this->options['persistent'] ? 'mysql_pconnect' : 'mysql_connect'; + + @ini_set('track_errors', true); + $php_errormsg = ''; + $connection = @call_user_func_array($connect_function, $params); + @ini_restore('track_errors'); + if (!$connection) { + if (($err = @mysql_error()) != '') { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, $err); + } else { + return $this->raiseError(MDB2_ERROR_CONNECT_FAILED, null, null, $php_errormsg); + } + } + + $this->connection = $connection; + $this->connected_dsn = $this->dsn; + $this->connected_database_name = ''; + $this->opened_persistent = $this->options['persistent']; + $this->dbsyntax = $this->dsn['dbsyntax'] ? $this->dsn['dbsyntax'] : $this->phptype; + + $this->supported['transactions'] = false; + if ($this->options['default_table_type']) { + switch (strtoupper($this->options['default_table_type'])) { + case 'BERKELEYDB': + $this->options['default_table_type'] = 'BDB'; + case 'BDB': + case 'INNODB': + case 'GEMINI': + $this->supported['transactions'] = true; + break; + case 'HEAP': + case 'ISAM': + case 'MERGE': + case 'MRG_MYISAM': + case 'MYISAM': + break; + default: + $this->warnings[] = $default_table_type. + ' is not a supported default table type'; + } + } + + if ($this->options['use_transactions'] && !$this->supports('transactions')) { + $this->warnings[] = $this->options['default_table_type']. + ' is not a transaction-safe default table type; switched to INNODB'; + $this->options['default_table_type'] = 'INNODB'; + $this->supported['transactions'] = true; + } + + return MDB2_OK; + } + + // }}} + // {{{ disconnect() + + /** + * Log out and disconnect from the database. + * + * @return mixed true on success, false if not connected and error + * object on error + * @access public + */ + function disconnect($force = true) + { + if (is_resource($this->connection)) { + if (!$this->opened_persistent || $force) { + @mysql_close($this->connection); + } + $this->connection = 0; + } + return MDB2_OK; + } + + // }}} + // {{{ _doQuery() + + /** + * Execute a query + * @param string $query query + * @param boolean $isManip if the query is a manipulation query + * @param resource $connection + * @param string $database_name + * @return result or error object + * @access protected + */ + function _doQuery($query, $isManip = false, $connection = null, $database_name = null) + { + $this->last_query = $query; + $this->debug($query, 'query'); + if ($this->options['disable_query']) { + if ($isManip) { + return 0; + } + return null; + } + + if (is_null($connection)) { + $err = $this->connect(); + if (PEAR::isError($err)) { + return $err; + } + $connection = $this->connection; + } + if (is_null($database_name)) { + $database_name = $this->database_name; + } + + if ($database_name) { + if ($database_name != $this->connected_database_name) { + if (!@mysql_select_db($database_name, $connection)) { + return $this->raiseError(); + } + $this->connected_database_name = $database_name; + } + } + + $function = $this->options['result_buffering'] + ? 'mysql_query' : 'mysql_unbuffered_query'; + $result = @$function($query, $connection); + if (!$result) { + return $this->raiseError(); + } + + if ($isManip) { + return @mysql_affected_rows($connection); + } + return $result; + } + + // }}} + // {{{ _modifyQuery() + + /** + * Changes a query string for various DBMS specific reasons + * + * @param string $query query to modify + * @return the new (modified) query + * @access protected + */ + function _modifyQuery($query, $isManip, $limit, $offset) + { + if ($this->options['portability'] & MDB2_PORTABILITY_DELETE_COUNT) { + // "DELETE FROM table" gives 0 affected rows in MySQL. + // This little hack lets you know how many rows were deleted. + if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) { + $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/', + 'DELETE FROM \1 WHERE 1=1', $query); + } + } + if ($limit > 0 + && !preg_match('/LIMIT\s*\d(\s*(,|OFFSET)\s*\d+)?/i', $query) + ) { + $query = rtrim($query); + if (substr($query, -1) == ';') { + $query = substr($query, 0, -1); + } + if ($isManip) { + return $query . " LIMIT $limit"; + } else { + return $query . " LIMIT $offset, $limit"; + } + } + return $query; + } + + // }}} + // {{{ replace() + + /** + * Execute a SQL REPLACE query. A REPLACE query is identical to a INSERT + * query, except that if there is already a row in the table with the same + * key field values, the REPLACE query just updates its values instead of + * inserting a new row. + * + * The REPLACE type of query does not make part of the SQL standards. Since + * practically only MySQL implements it natively, this type of query is + * emulated through this method for other DBMS using standard types of + * queries inside a transaction to assure the atomicity of the operation. + * + * @access public + * + * @param string $table name of the table on which the REPLACE query will + * be executed. + * @param array $fields associative array that describes the fields and the + * values that will be inserted or updated in the specified table. The + * indexes of the array are the names of all the fields of the table. The + * values of the array are also associative arrays that describe the + * values and other properties of the table fields. + * + * Here follows a list of field properties that need to be specified: + * + * value: + * Value to be assigned to the specified field. This value may be + * of specified in database independent type format as this + * function can perform the necessary datatype conversions. + * + * Default: + * this property is required unless the Null property + * is set to 1. + * + * type + * Name of the type of the field. Currently, all types Metabase + * are supported except for clob and blob. + * + * Default: no type conversion + * + * null + * Boolean property that indicates that the value for this field + * should be set to null. + * + * The default value for fields missing in INSERT queries may be + * specified the definition of a table. Often, the default value + * is already null, but since the REPLACE may be emulated using + * an UPDATE query, make sure that all fields of the table are + * listed in this function argument array. + * + * Default: 0 + * + * key + * Boolean property that indicates that this field should be + * handled as a primary key or at least as part of the compound + * unique index of the table that will determine the row that will + * updated if it exists or inserted a new row otherwise. + * + * This function will fail if no key field is specified or if the + * value of a key field is set to null because fields that are + * part of unique index they may not be null. + * + * Default: 0 + * + * @return mixed MDB2_OK on success, a MDB2 error on failure + */ + function replace($table, $fields) + { + $count = count($fields); + $query = $values = ''; + $keys = $colnum = 0; + for (reset($fields); $colnum < $count; next($fields), $colnum++) { + $name = key($fields); + if ($colnum > 0) { + $query .= ','; + $values.= ','; + } + $query.= $name; + if (isset($fields[$name]['null']) && $fields[$name]['null']) { + $value = 'NULL'; + } else { + $value = $this->quote($fields[$name]['value'], $fields[$name]['type']); + } + $values.= $value; + if (isset($fields[$name]['key']) && $fields[$name]['key']) { + if ($value === 'NULL') { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'replace: key value '.$name.' may not be NULL'); + } + $keys++; + } + } + if ($keys == 0) { + return $this->raiseError(MDB2_ERROR_CANNOT_REPLACE, null, null, + 'replace: not specified which fields are keys'); + } + $query = "REPLACE INTO $table ($query) VALUES ($values)"; + $this->last_query = $query; + $this->debug($query, 'query'); + return $this->_doQuery($query, true); + } + + // }}} + // {{{ nextID() + + /** + * returns the next free id of a sequence + * + * @param string $seq_name name of the sequence + * @param boolean $ondemand when true the seqence is + * automatic created, if it + * not exists + * + * @return mixed MDB2 Error Object or id + * @access public + */ + function nextID($seq_name, $ondemand = true) + { + $sequence_name = $this->getSequenceName($seq_name); + $query = "INSERT INTO $sequence_name (".$this->options['seqcol_name'].") VALUES (NULL)"; + $this->expectError(MDB2_ERROR_NOSUCHTABLE); + $result = $this->_doQuery($query, true); + $this->popExpect(); + if (PEAR::isError($result)) { + if ($ondemand && $result->getCode() == MDB2_ERROR_NOSUCHTABLE) { + $this->loadModule('Manager'); + // Since we are creating the sequence on demand + // we know the first id = 1 so initialize the + // sequence at 2 + $result = $this->manager->createSequence($seq_name, 2); + if (PEAR::isError($result)) { + return $this->raiseError(MDB2_ERROR, null, null, + 'nextID: on demand sequence '.$seq_name.' could not be created'); + } else { + // First ID of a newly created sequence is 1 + return 1; + } + } + return $result; + } + $value = $this->queryOne('SELECT LAST_INSERT_ID()', 'integer'); + if (is_numeric($value)) { + $query = "DELETE FROM $sequence_name WHERE ".$this->options['seqcol_name']." < $value"; + $result = $this->_doQuery($query, true); + if (PEAR::isError($result)) { + $this->warnings[] = 'nextID: could not delete previous sequence table values from '.$seq_name; + } + } + return $value; + } + + // }}} + // {{{ lastInsertID() + + /** + * returns the autoincrement ID if supported or $id + * + * @param mixed $id value as returned by getBeforeId() + * @param string $table name of the table into which a new row was inserted + * @return mixed MDB2 Error Object or id + * @access public + */ + function lastInsertID($table = null, $field = null) + { + return $this->queryOne('SELECT LAST_INSERT_ID()', 'integer'); + } + + // }}} + // {{{ currID() + + /** + * returns the current id of a sequence + * + * @param string $seq_name name of the sequence + * @return mixed MDB2 Error Object or id + * @access public + */ + function currID($seq_name) + { + $sequence_name = $this->getSequenceName($seq_name); + $query = "SELECT MAX(".$this->options['seqcol_name'].") FROM $sequence_name"; + return $this->queryOne($query, 'integer'); + } +} + +class MDB2_Result_mysql extends MDB2_Result_Common +{ + // }}} + // {{{ fetchRow() + + /** + * Fetch a row and insert the data into an existing array. + * + * @param int $fetchmode how the array data should be indexed + * @param int $rownum number of the row where the data can be found + * @return int data array on success, a MDB2 error on failure + * @access public + */ + function &fetchRow($fetchmode = MDB2_FETCHMODE_DEFAULT, $rownum = null) + { + if (!is_null($rownum)) { + $seek = $this->seek($rownum); + if (PEAR::isError($seek)) { + return $seek; + } + } + if ($fetchmode == MDB2_FETCHMODE_DEFAULT) { + $fetchmode = $this->db->fetchmode; + } + if ($fetchmode & MDB2_FETCHMODE_ASSOC) { + $row = @mysql_fetch_assoc($this->result); + if (is_array($row) + && $this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE + ) { + $row = array_change_key_case($row, $this->db->options['field_case']); + } + } else { + $row = @mysql_fetch_row($this->result); + } + + if (!$row) { + if (is_null($this->result)) { + $err =& $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'fetchRow: resultset has already been freed'); + return $err; + } + $null = null; + return $null; + } + if ($this->db->options['portability'] & MDB2_PORTABILITY_EMPTY_TO_NULL) { + $this->db->_fixResultArrayValues($row, MDB2_PORTABILITY_EMPTY_TO_NULL); + } + if (!empty($this->values)) { + $this->_assignBindColumns($row); + } + if (!empty($this->types)) { + $row = $this->db->datatype->convertResultRow($this->types, $row); + } + if ($fetchmode === MDB2_FETCHMODE_OBJECT) { + $object_class = $this->db->options['fetch_class']; + if ($object_class == 'stdClass') { + $row = (object) $row; + } else { + $row = &new $object_class($row); + } + } + ++$this->rownum; + return $row; + } + + // }}} + // {{{ _getColumnNames() + + /** + * Retrieve the names of columns returned by the DBMS in a query result. + * + * @return mixed an associative array variable + * that will hold the names of columns. The + * indexes of the array are the column names + * mapped to lower case and the values are the + * respective numbers of the columns starting + * from 0. Some DBMS may not return any + * columns when the result set does not + * contain any rows. + * + * a MDB2 error on failure + * @access private + */ + function _getColumnNames() + { + $columns = array(); + $numcols = $this->numCols(); + if (PEAR::isError($numcols)) { + return $numcols; + } + for ($column = 0; $column < $numcols; $column++) { + $column_name = @mysql_field_name($this->result, $column); + $columns[$column_name] = $column; + } + if ($this->db->options['portability'] & MDB2_PORTABILITY_FIX_CASE) { + $columns = array_change_key_case($columns, $this->db->options['field_case']); + } + return $columns; + } + + // }}} + // {{{ numCols() + + /** + * Count the number of columns returned by the DBMS in a query result. + * + * @return mixed integer value with the number of columns, a MDB2 error + * on failure + * @access public + */ + function numCols() + { + $cols = @mysql_num_fields($this->result); + if (is_null($cols)) { + if (is_null($this->result)) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'numCols: resultset has already been freed'); + } + return $this->db->raiseError(); + } + return $cols; + } + + // }}} + // {{{ free() + + /** + * Free the internal resources associated with result. + * + * @return boolean true on success, false if result is invalid + * @access public + */ + function free() + { + $free = @mysql_free_result($this->result); + if (!$free) { + if (is_null($this->result)) { + return MDB2_OK; + } + return $this->db->raiseError(); + } + $this->result = null; + return MDB2_OK; + } +} + +class MDB2_BufferedResult_mysql extends MDB2_Result_mysql +{ + // }}} + // {{{ seek() + + /** + * seek to a specific row in a result set + * + * @param int $rownum number of the row where the data can be found + * @return mixed MDB2_OK on success, a MDB2 error on failure + * @access public + */ + function seek($rownum = 0) + { + if ($this->rownum != ($rownum - 1) && !@mysql_data_seek($this->result, $rownum)) { + if (is_null($this->result)) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'seek: resultset has already been freed'); + } + return $this->db->raiseError(MDB2_ERROR_INVALID, null, null, + 'seek: tried to seek to an invalid row number ('.$rownum.')'); + } + $this->rownum = $rownum - 1; + return MDB2_OK; + } + + // }}} + // {{{ valid() + + /** + * check if the end of the result set has been reached + * + * @return mixed true or false on sucess, a MDB2 error on failure + * @access public + */ + function valid() + { + $numrows = $this->numRows(); + if (PEAR::isError($numrows)) { + return $numrows; + } + return $this->rownum < ($numrows - 1); + } + + // }}} + // {{{ numRows() + + /** + * returns the number of rows in a result object + * + * @return mixed MDB2 Error Object or the number of rows + * @access public + */ + function numRows() + { + $rows = @mysql_num_rows($this->result); + if (is_null($rows)) { + if (is_null($this->result)) { + return $this->db->raiseError(MDB2_ERROR_NEED_MORE_DATA, null, null, + 'numRows: resultset has already been freed'); + } + return $this->raiseError(); + } + return $rows; + } +} + +class MDB2_Statement_mysql extends MDB2_Statement_Common +{ + +} +?> \ No newline at end of file diff --git a/program/lib/imap.inc b/program/lib/imap.inc index 7f4c93a60..6ca522084 100644 --- a/program/lib/imap.inc +++ b/program/lib/imap.inc @@ -1220,8 +1220,10 @@ function iil_C_FetchHeaders(&$conn, $mailbox, $message_set){ $i++; $lines[$i] = trim(chop($line)); } - }while($line[0]!=")"); + }while($line[0]!=")" && strncmp($line, $key, strlen($key))); // patch from "Maksim Rubis" + if(strncmp($line, $key, strlen($key))) + { //process header, fill iilBasicHeader obj. // initialize if (is_array($headers)){ @@ -1256,6 +1258,10 @@ function iil_C_FetchHeaders(&$conn, $mailbox, $message_set){ if ($messageID) $messageID = substr(substr($messageID, 1), 0, strlen($messageID)-2); else $messageID = "mid:".$id; $result[$id]->messageID = $messageID; + } + else { + $a=explode(" ", $line); + } } }while(strcmp($a[0], $key)!=0); @@ -1371,6 +1377,7 @@ function iil_SortHeaders($a, $field, $flag){ if ($field=="date"||$field=='internaldate') $field="timestamp"; if (empty($flag)) $flag="ASC"; $flag=strtoupper($flag); + $stripArr = ($field=='subject') ? array('Re: ','Fwd: ','Fw: ',"\"") : array("\""); $c=count($a); if ($c>0){ @@ -1386,7 +1393,7 @@ function iil_SortHeaders($a, $field, $flag){ reset($a); while (list($key, $val)=each($a)){ $data=$a[$key]->$field; - if (is_string($data)) $data=strtoupper(str_replace("\"", "", $data)); + if (is_string($data)) $data=strtoupper(str_replace($stripArr, "", $data)); $index[$key]=$data; } diff --git a/program/localization/de/labels.inc b/program/localization/de/labels.inc index 7464c7657..36ade6413 100644 --- a/program/localization/de/labels.inc +++ b/program/localization/de/labels.inc @@ -172,5 +172,8 @@ $labels['createfolder'] = 'Neuen Ordner erstellen'; $labels['deletefolder'] = 'Ordner löschen'; $labels['managefolders'] = 'Ordner verwalten'; +$labels['sortby'] = 'Sortieren nach'; +$labels['sortasc'] = 'aufsteigend sortieren'; +$labels['sortdesc'] = 'absteigend sortieren'; ?> \ No newline at end of file diff --git a/program/localization/en/labels.inc b/program/localization/en/labels.inc index 35b0e3f5a..e44f8298f 100644 --- a/program/localization/en/labels.inc +++ b/program/localization/en/labels.inc @@ -172,5 +172,8 @@ $labels['createfolder'] = 'Create new folder'; $labels['deletefolder'] = 'Delete folder'; $labels['managefolders'] = 'Manage folders'; +$labels['sortby'] = 'Sort by'; +$labels['sortasc'] = 'Sort ascending'; +$labels['sortdesc'] = 'Sort descending'; ?> \ No newline at end of file diff --git a/program/localization/nl/labels.inc b/program/localization/nl/labels.inc index c2001003e..464d93937 100644 --- a/program/localization/nl/labels.inc +++ b/program/localization/nl/labels.inc @@ -91,12 +91,13 @@ $labels['today'] = 'Vandaag'; // toolbar buttons $labels['writenewmessage'] = 'Schrijf een nieuw bericht'; $labels['replytomessage'] = 'Beantwoord het bericht'; -$labels['forwardmessage'] = 'Doorsturen van bericht'; +$labels['forwardmessage'] = 'Stuur bericht door'; $labels['deletemessage'] = 'Verplaats bericht naar prullenbak'; $labels['printmessage'] = 'Print dit bericht'; $labels['previousmessages'] = 'Toon vorige lijst met berichten'; $labels['nextmessages'] = 'Toon volgende lijst met berichten'; $labels['backtolist'] = 'Terug naar berichtoverzicht'; +$labels['viewsource'] = 'Bron bekijken'; $labels['select'] = 'Selecteer'; $labels['all'] = 'Alle'; @@ -108,7 +109,7 @@ $labels['compose'] = 'Creeer een nieuw message'; $labels['sendmessage'] = 'Verstuur het bericht nu'; $labels['addattachment'] = 'Voeg een bijlage toe'; -$labels['attachments'] = 'Bijlage'; +$labels['attachments'] = 'Bijlages'; $labels['upload'] = 'Toevoegen'; $labels['close'] = 'Sluit'; @@ -140,6 +141,9 @@ $labels['deletecontact'] = 'Verwijder geselecteerde contactpersonen'; $labels['composeto'] = 'Verstuur bericht aan'; $labels['contactsfromto'] = 'Contactpersonen $from tot $to van $count'; +$labels['print'] = 'Print'; +$labels['export'] = 'Exporteer'; + // settings $labels['settingsfor'] = 'Instellingen voor'; @@ -169,5 +173,8 @@ $labels['createfolder'] = 'Maak nieuwe map'; $labels['deletefolder'] = 'Verwijder map'; $labels['managefolders'] = 'Beheer mappen'; +$labels['sortby'] = 'Sorteer op'; +$labels['sortdesc'] = 'Sorteer (z-a)'; +$labels['sortasc'] = 'Sorteer (a-z)'; ?> \ No newline at end of file diff --git a/program/localization/nl/messages.inc b/program/localization/nl/messages.inc index cc5ad47a1..1c9fba963 100644 --- a/program/localization/nl/messages.inc +++ b/program/localization/nl/messages.inc @@ -1,4 +1,4 @@ -'; - + // check to see if we have some settings for sorting - $sort_col = isset($_SESSION['sort_col']) ? $_SESSION['sort_col'] : 'date'; - $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : 'DESC'; + $sort_col = isset($_SESSION['sort_col']) ? $_SESSION['sort_col'] : $CONFIG['message_sort_col']; + $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order']; // get message headers $a_headers = $IMAP->list_headers('', '', $sort_col, $sort_order); @@ -256,6 +256,7 @@ function rcmail_message_list($attrib) // define list of cols to be displayed $a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject'); + $a_sort_cols = array('subject', 'date', 'from', 'to'); // show 'to' instead of from in sent messages if (strtolower($IMAP->get_mailbox_name())=='sent' && ($f = array_search('from', $a_show_cols))) @@ -264,7 +265,7 @@ function rcmail_message_list($attrib) // add table title $out .= "\n \n"; - + $javascript = ''; foreach ($a_show_cols as $col) { @@ -273,26 +274,33 @@ function rcmail_message_list($attrib) // make sort links $sort = ''; - if ($col != 'size') + if (in_array($col, $a_sort_cols) && (!empty($attrib['sortdescbutton']) || !empty($attrib['sortascbutton']))) { + $sort = '  '; + // asc link - $asc = '_ASC'; - $sort .= '' - . ''; + if (!empty($attrib['sortascbutton'])) + { + $sort .= rcube_button(array('command' => 'sort', + 'prop' => $col.'_ASC', + 'image' => $attrib['sortascbutton'], + 'title' => 'sortasc')); + } + // desc link - $desc = '_DESC'; - $sort .= ' ' - . ''; + if (!empty($attrib['sortdescbutton'])) + { + $sort .= rcube_button(array('command' => 'sort', + 'prop' => $col.'_DESC', + 'image' => $attrib['sortdescbutton'], + 'title' => 'sortdesc')); + } } + + $sort_class = $col==$sort_col ? " sorted$sort_order" : ''; // put it all together - $out .= '' . "$col_name  $sort\n"; - - // register sort buttons - $javascript .= "rcmail.register_button('sort', 'sort_{$col_name}_desc', 'link', 'active', '', '');\n"; - $javascript .= "rcmail.register_button('sort', 'sort_{$col_name}_asc', 'link', 'active', '', '');\n"; + $out .= '' . "$col_name$sort\n"; } $out .= ''.($attrib['attachmenticon'] ? sprintf($image_tag, $skin_path, $attrib['attachmenticon'], '') : '')."\n"; @@ -374,6 +382,8 @@ function rcmail_message_list($attrib) $javascript .= sprintf("%s.set_env('messagecount', %d);\n", $JS_OBJECT_NAME, $message_count); $javascript .= sprintf("%s.set_env('current_page', %d);\n", $JS_OBJECT_NAME, $IMAP->list_page); $javascript .= sprintf("%s.set_env('pagecount', %d);\n", $JS_OBJECT_NAME, ceil($message_count/$IMAP->page_size)); + $javascript .= sprintf("%s.set_env('sort_col', '%s');\n", $JS_OBJECT_NAME, $sort_col); + $javascript .= sprintf("%s.set_env('sort_order', '%s');\n", $JS_OBJECT_NAME, $sort_order); if ($attrib['messageicon']) $javascript .= sprintf("%s.set_env('messageicon', '%s%s');\n", $JS_OBJECT_NAME, $skin_path, $attrib['messageicon']); @@ -849,7 +859,7 @@ function rcmail_message_headers($attrib, $headers=NULL) if (!$headers[$hkey]) continue; - if ($hkey=='date') + if ($hkey=='date' && !empty($headers[$hkey])) $header_value = format_date(strtotime($headers[$hkey])); else if (in_array($hkey, array('from', 'to', 'cc', 'reply-to'))) $header_value = rep_specialchars_output(rcmail_address_string($IMAP->decode_header($headers[$hkey]), NULL, $attrib['addicon'])); diff --git a/program/steps/mail/list.inc b/program/steps/mail/list.inc index 5dd652d44..cb1d7a6c7 100644 --- a/program/steps/mail/list.inc +++ b/program/steps/mail/list.inc @@ -31,28 +31,15 @@ if ($sort = isset($_GET['_sort']) ? $_GET['_sort'] : false) // yes, so set the sort vars list($sort_col, $sort_order) = explode('_', $sort); - // iloha mail sort func doesn't know about a 'Sender' col - $sort_col = $sort_col == 'Sender' ? 'From' : $sort_col; - // set session vars for sort (so next page and task switch know how to sort) $_SESSION['sort_col'] = $sort_col; $_SESSION['sort_order'] = $sort_order; } else - { - // if switching folder, use default sorting - if ($_GET['_refresh'] == '1') - { - $sort_col = 'date'; - $sort_order = 'desc'; - unset($_SESSION['sort_col'], $_SESSION['sort_order']); - } - else - { - // use session settings if set, defaults if not - $sort_col = isset($_SESSION['sort_col']) ? $_SESSION['sort_col'] : 'date'; - $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : 'desc'; - } + { + // use session settings if set, defaults if not + $sort_col = isset($_SESSION['sort_col']) ? $_SESSION['sort_col'] : $CONFIG['message_sort_col']; + $sort_order = isset($_SESSION['sort_order']) ? $_SESSION['sort_order'] : $CONFIG['message_sort_order']; } diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc index d9ff5d443..992d06f79 100644 --- a/program/steps/mail/show.inc +++ b/program/steps/mail/show.inc @@ -65,7 +65,7 @@ if ($_GET['_uid']) $javascript .= sprintf("%s.set_env('safemode', '%b');", $JS_OBJECT_NAME, $_GET['_safe']); // get previous and next message UID - $a_msg_index = $IMAP->message_index(); + $a_msg_index = $IMAP->message_index(NULL, $_SESSION['sort_col'], $_SESSION['sort_order']); $MESSAGE['index'] = array_search((string)$_GET['_uid'], $a_msg_index, TRUE); if (isset($a_msg_index[$MESSAGE['index']-1])) diff --git a/skins/default/includes/header.html b/skins/default/includes/header.html index bcb6efb37..a7e034a0b 100644 --- a/skins/default/includes/header.html +++ b/skins/default/includes/header.html @@ -1,10 +1,3 @@ -
- - - - -
- diff --git a/skins/default/includes/taskbar.html b/skins/default/includes/taskbar.html new file mode 100644 index 000000000..ef1aa8268 --- /dev/null +++ b/skins/default/includes/taskbar.html @@ -0,0 +1,6 @@ +
+ + + + +
\ No newline at end of file diff --git a/skins/default/mail.css b/skins/default/mail.css index c3ee3637c..72c1c6c2b 100644 --- a/skins/default/mail.css +++ b/skins/default/mail.css @@ -325,6 +325,12 @@ body.messagelist font-weight: bold; } +#messagelist thead tr td.sortedASC, +#messagelist thead tr td.sortedDESC +{ + background-image: url(images/listheader_dark.gif); +} + #messagelist tbody tr td { height: 16px !important; diff --git a/skins/default/templates/addidentity.html b/skins/default/templates/addidentity.html index 96bb6dd5a..6d29cad1d 100644 --- a/skins/default/templates/addidentity.html +++ b/skins/default/templates/addidentity.html @@ -7,6 +7,7 @@ + @@ -29,7 +30,6 @@ - diff --git a/skins/default/templates/addressbook.html b/skins/default/templates/addressbook.html index b2ed21029..5b66b0594 100644 --- a/skins/default/templates/addressbook.html +++ b/skins/default/templates/addressbook.html @@ -7,6 +7,7 @@ +
diff --git a/skins/default/templates/compose.html b/skins/default/templates/compose.html index 5774b78fa..26c9de525 100644 --- a/skins/default/templates/compose.html +++ b/skins/default/templates/compose.html @@ -24,6 +24,7 @@ function rcmail_toggle_display(id) +
diff --git a/skins/default/templates/editidentity.html b/skins/default/templates/editidentity.html index 07283f5be..53d878d1f 100644 --- a/skins/default/templates/editidentity.html +++ b/skins/default/templates/editidentity.html @@ -7,6 +7,7 @@ + @@ -30,7 +31,6 @@
- diff --git a/skins/default/templates/identities.html b/skins/default/templates/identities.html index 7ae2bf007..c96e154fb 100644 --- a/skins/default/templates/identities.html +++ b/skins/default/templates/identities.html @@ -7,6 +7,7 @@ + @@ -16,7 +17,6 @@

- diff --git a/skins/default/templates/mail.html b/skins/default/templates/mail.html index 56d0df66f..783808ded 100644 --- a/skins/default/templates/mail.html +++ b/skins/default/templates/mail.html @@ -7,6 +7,7 @@ +
@@ -35,7 +36,9 @@ messageIcon="/images/icons/dot.png" unreadIcon="/images/icons/unread.png" repliedIcon="/images/icons/replied.png" - attachmentIcon="/images/icons/attachment.png" /> + attachmentIcon="/images/icons/attachment.png" + sortDescButton="/images/buttons/up_arrow.png" + sortAscButton="/images/buttons/down_arrow.png" />
diff --git a/skins/default/templates/managefolders.html b/skins/default/templates/managefolders.html index a3095c73c..0a9918356 100644 --- a/skins/default/templates/managefolders.html +++ b/skins/default/templates/managefolders.html @@ -7,6 +7,7 @@ + @@ -31,7 +32,6 @@ - diff --git a/skins/default/templates/message.html b/skins/default/templates/message.html index 3c276d0d1..a5c46effa 100644 --- a/skins/default/templates/message.html +++ b/skins/default/templates/message.html @@ -7,6 +7,7 @@ +
diff --git a/skins/default/templates/settings.html b/skins/default/templates/settings.html index b29734d5e..879b9cebc 100644 --- a/skins/default/templates/settings.html +++ b/skins/default/templates/settings.html @@ -7,6 +7,7 @@ + @@ -20,8 +21,6 @@
- - -- cgit v1.2.3