1
0
mirror of https://github.com/phpbb/phpbb.git synced 2025-02-25 20:44:01 +01:00
php-phpbb/phpBB/install/database_update.php

4213 lines
117 KiB
PHP
Raw Normal View History

<?php
/**
*
* @package install
* @version $Id$
* @copyright (c) 2006 phpBB Group
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*
*/
define('UPDATES_TO_VERSION', '3.0.9-RC2');
// Enter any version to update from to test updates. The version within the db will not be updated.
define('DEBUG_FROM_VERSION', false);
// Which oldest version does this updater support?
define('OLDEST_FROM_VERSION', '3.0.0');
// Return if we "just include it" to find out for which version the database update is responsible for
if (defined('IN_PHPBB') && defined('IN_INSTALL'))
{
$updates_to_version = UPDATES_TO_VERSION;
$debug_from_version = DEBUG_FROM_VERSION;
$oldest_from_version = OLDEST_FROM_VERSION;
return;
}
/**
*/
define('IN_PHPBB', true);
define('IN_INSTALL', true);
$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './../';
$phpEx = substr(strrchr(__FILE__, '.'), 1);
if (!function_exists('phpbb_require_updated'))
{
function phpbb_require_updated($path, $optional = false)
{
global $phpbb_root_path;
$new_path = $phpbb_root_path . 'install/update/new/' . $path;
$old_path = $phpbb_root_path . $path;
if (file_exists($new_path))
{
require($new_path);
}
else if (!$optional || file_exists($old_path))
{
require($old_path);
}
}
}
phpbb_require_updated('includes/startup.' . $phpEx);
$updates_to_version = UPDATES_TO_VERSION;
$debug_from_version = DEBUG_FROM_VERSION;
$oldest_from_version = OLDEST_FROM_VERSION;
error_reporting(E_ALL);
@set_time_limit(0);
// Include essential scripts
include($phpbb_root_path . 'config.' . $phpEx);
if (!defined('PHPBB_INSTALLED') || empty($dbms) || empty($acm_type))
{
die("Please read: <a href='../docs/INSTALL.html'>INSTALL.html</a> before attempting to update.");
}
// Load Extensions
if (!empty($load_extensions) && function_exists('dl'))
{
$load_extensions = explode(',', $load_extensions);
foreach ($load_extensions as $extension)
{
@dl(trim($extension));
}
}
// Include files
require($phpbb_root_path . 'includes/acm/acm_' . $acm_type . '.' . $phpEx);
require($phpbb_root_path . 'includes/cache.' . $phpEx);
require($phpbb_root_path . 'includes/template.' . $phpEx);
require($phpbb_root_path . 'includes/session.' . $phpEx);
require($phpbb_root_path . 'includes/auth.' . $phpEx);
require($phpbb_root_path . 'includes/functions.' . $phpEx);
phpbb_require_updated('includes/functions_content.' . $phpEx, true);
require($phpbb_root_path . 'includes/functions_admin.' . $phpEx);
require($phpbb_root_path . 'includes/constants.' . $phpEx);
require($phpbb_root_path . 'includes/db/' . $dbms . '.' . $phpEx);
require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx);
// new table constants are separately defined here in case the updater is run
// before the files are updated
if (!defined('LOGIN_ATTEMPT_TABLE'))
{
define('LOGIN_ATTEMPT_TABLE', $table_prefix . 'login_attempts');
}
$user = new user();
$cache = new cache();
$db = new $sql_db();
// Add own hook handler, if present. :o
if (file_exists($phpbb_root_path . 'includes/hooks/index.' . $phpEx))
{
require($phpbb_root_path . 'includes/hooks/index.' . $phpEx);
$phpbb_hook = new phpbb_hook(array('exit_handler', 'phpbb_user_session_handler', 'append_sid', array('template', 'display')));
foreach ($cache->obtain_hooks() as $hook)
{
@include($phpbb_root_path . 'includes/hooks/' . $hook . '.' . $phpEx);
}
}
else
{
$phpbb_hook = false;
}
// Connect to DB
$db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, false);
// We do not need this any longer, unset for safety purposes
unset($dbpasswd);
$user->ip = (!empty($_SERVER['REMOTE_ADDR'])) ? htmlspecialchars($_SERVER['REMOTE_ADDR']) : '';
$user->ip = (stripos($user->ip, '::ffff:') === 0) ? substr($user->ip, 7) : $user->ip;
$sql = "SELECT config_value
FROM " . CONFIG_TABLE . "
WHERE config_name = 'default_lang'";
$result = $db->sql_query($sql);
$row = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
$language = basename(request_var('language', ''));
if (!$language)
{
$language = $row['config_value'];
}
if (!file_exists($phpbb_root_path . 'language/' . $language))
{
die('No language found!');
}
// And finally, load the relevant language files
include($phpbb_root_path . 'language/' . $language . '/common.' . $phpEx);
include($phpbb_root_path . 'language/' . $language . '/acp/common.' . $phpEx);
include($phpbb_root_path . 'language/' . $language . '/install.' . $phpEx);
// Set PHP error handler to ours
//set_error_handler('msg_handler');
// Define some variables for the database update
$inline_update = (request_var('type', 0)) ? true : false;
// To let set_config() calls succeed, we need to make the config array available globally
$config = array();
$sql = 'SELECT *
FROM ' . CONFIG_TABLE;
$result = $db->sql_query($sql);
while ($row = $db->sql_fetchrow($result))
{
$config[$row['config_name']] = $row['config_value'];
}
$db->sql_freeresult($result);
// We do not include DB Tools here, because we can not be sure the file is up-to-date ;)
// Instead, this file defines a clean db_tools version (we are also not able to provide a different file, else the database update will not work standalone)
$db_tools = new updater_db_tools($db, true);
$database_update_info = database_update_info();
$error_ary = array();
$errored = false;
header('Content-type: text/html; charset=UTF-8');
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" dir="<?php echo $lang['DIRECTION']; ?>" lang="<?php echo $lang['USER_LANG']; ?>" xml:lang="<?php echo $lang['USER_LANG']; ?>">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta http-equiv="content-language" content="<?php echo $lang['USER_LANG']; ?>" />
<meta http-equiv="content-style-type" content="text/css" />
<meta http-equiv="imagetoolbar" content="no" />
<title><?php echo $lang['UPDATING_TO_LATEST_STABLE']; ?></title>
<link href="../adm/style/admin.css" rel="stylesheet" type="text/css" media="screen" />
</head>
<body>
<div id="wrap">
<div id="page-header">&nbsp;</div>
<div id="page-body">
<div id="acp">
<div class="panel">
<span class="corners-top"><span></span></span>
<div id="content">
<div id="main" class="install-body">
<h1><?php echo $lang['UPDATING_TO_LATEST_STABLE']; ?></h1>
<br />
<p><?php echo $lang['DATABASE_TYPE']; ?> :: <strong><?php echo $db->sql_layer; ?></strong><br />
<?php
if ($debug_from_version !== false)
{
$config['version'] = $debug_from_version;
}
echo $lang['PREVIOUS_VERSION'] . ' :: <strong>' . $config['version'] . '</strong><br />';
echo $lang['UPDATED_VERSION'] . ' :: <strong>' . $updates_to_version . '</strong></p>';
$current_version = str_replace('rc', 'RC', strtolower($config['version']));
$latest_version = str_replace('rc', 'RC', strtolower($updates_to_version));
$orig_version = $config['version'];
// Fill DB version
if (empty($config['dbms_version']))
{
set_config('dbms_version', $db->sql_server_info(true));
}
// Firebird update from Firebird 2.0 to 2.1+ required?
if ($db->sql_layer == 'firebird')
{
// We do not trust any PHP5 function enabled, we will simply test for a function new in 2.1
$db->sql_return_on_error(true);
$sql = 'SELECT 1 FROM RDB$DATABASE
WHERE BIN_AND(10, 1) = 0';
$result = $db->sql_query($sql);
if (!$result || $db->sql_error_triggered)
{
echo '<br /><br />';
echo '<h1>' . $lang['ERROR'] . '</h1><br />';
echo '<p>' . $lang['FIREBIRD_DBMS_UPDATE_REQUIRED'] . '</p>';
_print_footer();
exit_handler();
exit;
}
$db->sql_freeresult($result);
$db->sql_return_on_error(false);
}
// MySQL update from MySQL 3.x/4.x to > 4.1.x required?
if ($db->sql_layer == 'mysql' || $db->sql_layer == 'mysql4' || $db->sql_layer == 'mysqli')
{
// Verify by fetching column... if the column type matches the new type we update dbms_version...
$sql = "SHOW COLUMNS FROM " . CONFIG_TABLE;
$result = $db->sql_query($sql);
$column_type = '';
while ($row = $db->sql_fetchrow($result))
{
$field = strtolower($row['Field']);
if ($field == 'config_value')
{
$column_type = strtolower($row['Type']);
break;
}
}
$db->sql_freeresult($result);
// If column type is blob, but mysql version says we are on > 4.1.3, then the schema needs an update
if (strpos($column_type, 'blob') !== false && version_compare($db->sql_server_info(true), '4.1.3', '>='))
{
echo '<br /><br />';
echo '<h1>' . $lang['ERROR'] . '</h1><br />';
echo '<p>' . sprintf($lang['MYSQL_SCHEMA_UPDATE_REQUIRED'], $config['dbms_version'], $db->sql_server_info(true)) . '</p>';
_print_footer();
exit_handler();
exit;
}
}
// Now check if the user wants to update from a version we no longer support updates from
if (version_compare($current_version, $oldest_from_version, '<'))
{
echo '<br /><br /><h1>' . $lang['ERROR'] . '</h1><br />';
echo '<p>' . sprintf($lang['DB_UPDATE_NOT_SUPPORTED'], $oldest_from_version, $current_version) . '</p>';
_print_footer();
exit_handler();
exit;
}
// If the latest version and the current version are 'unequal', we will update the version_update_from, else we do not update anything.
if ($inline_update)
{
if ($current_version !== $latest_version)
{
set_config('version_update_from', $orig_version);
}
}
else
{
// If not called from the update script, we will actually remove the traces
$db->sql_query('DELETE FROM ' . CONFIG_TABLE . " WHERE config_name = 'version_update_from'");
}
// Schema updates
?>
<br /><br />
<h1><?php echo $lang['UPDATE_DATABASE_SCHEMA']; ?></h1>
<br />
<p><?php echo $lang['PROGRESS']; ?> :: <strong>
<?php
flush();
// We go through the schema changes from the lowest to the highest version
// We try to also include versions 'in-between'...
$no_updates = true;
$versions = array_keys($database_update_info);
for ($i = 0; $i < sizeof($versions); $i++)
{
$version = $versions[$i];
$schema_changes = $database_update_info[$version];
$next_version = (isset($versions[$i + 1])) ? $versions[$i + 1] : $updates_to_version;
// If the installed version to be updated to is < than the current version, and if the current version is >= as the version to be updated to next, we will skip the process
if (version_compare($version, $current_version, '<') && version_compare($current_version, $next_version, '>='))
{
continue;
}
if (!sizeof($schema_changes))
{
continue;
}
$no_updates = false;
// We run one index after the other... to be consistent with schema changes...
foreach ($schema_changes as $key => $changes)
{
$statements = $db_tools->perform_schema_changes(array($key => $changes));
foreach ($statements as $sql)
{
_sql($sql, $errored, $error_ary);
}
}
}
_write_result($no_updates, $errored, $error_ary);
// Data updates
$error_ary = array();
$errored = $no_updates = false;
?>
<br /><br />
<h1><?php echo $lang['UPDATING_DATA']; ?></h1>
<br />
<p><?php echo $lang['PROGRESS']; ?> :: <strong>
<?php
flush();
$no_updates = true;
$versions = array_keys($database_update_info);
// some code magic
for ($i = 0; $i < sizeof($versions); $i++)
{
$version = $versions[$i];
$next_version = (isset($versions[$i + 1])) ? $versions[$i + 1] : $updates_to_version;
// If the installed version to be updated to is < than the current version, and if the current version is >= as the version to be updated to next, we will skip the process
if (version_compare($version, $current_version, '<') && version_compare($current_version, $next_version, '>='))
{
continue;
}
change_database_data($no_updates, $version);
}
_write_result($no_updates, $errored, $error_ary);
$error_ary = array();
$errored = $no_updates = false;
?>
<br /><br />
<h1><?php echo $lang['UPDATE_VERSION_OPTIMIZE']; ?></h1>
<br />
<p><?php echo $lang['PROGRESS']; ?> :: <strong>
<?php
flush();
if ($debug_from_version === false)
{
// update the version
$sql = "UPDATE " . CONFIG_TABLE . "
SET config_value = '$updates_to_version'
WHERE config_name = 'version'";
_sql($sql, $errored, $error_ary);
}
// Reset permissions
$sql = 'UPDATE ' . USERS_TABLE . "
SET user_permissions = '',
user_perm_from = 0";
_sql($sql, $errored, $error_ary);
// Update the dbms version if everything is ok...
set_config('dbms_version', $db->sql_server_info(true));
/* Optimize/vacuum analyze the tables where appropriate
// this should be done for each version in future along with
// the version number update
switch ($db->sql_layer)
{
case 'mysql':
case 'mysqli':
case 'mysql4':
$sql = 'OPTIMIZE TABLE ' . $table_prefix . 'auth_access, ' . $table_prefix . 'banlist, ' . $table_prefix . 'categories, ' . $table_prefix . 'config, ' . $table_prefix . 'disallow, ' . $table_prefix . 'forum_prune, ' . $table_prefix . 'forums, ' . $table_prefix . 'groups, ' . $table_prefix . 'posts, ' . $table_prefix . 'posts_text, ' . $table_prefix . 'privmsgs, ' . $table_prefix . 'privmsgs_text, ' . $table_prefix . 'ranks, ' . $table_prefix . 'search_results, ' . $table_prefix . 'search_wordlist, ' . $table_prefix . 'search_wordmatch, ' . $table_prefix . 'sessions_keys' . $table_prefix . 'smilies, ' . $table_prefix . 'themes, ' . $table_prefix . 'themes_name, ' . $table_prefix . 'topics, ' . $table_prefix . 'topics_watch, ' . $table_prefix . 'user_group, ' . $table_prefix . 'users, ' . $table_prefix . 'vote_desc, ' . $table_prefix . 'vote_results, ' . $table_prefix . 'vote_voters, ' . $table_prefix . 'words';
_sql($sql, $errored, $error_ary);
break;
case 'postgresql':
_sql("VACUUM ANALYZE", $errored, $error_ary);
break;
}
*/
_write_result($no_updates, $errored, $error_ary);
?>
<br />
<h1><?php echo $lang['UPDATE_COMPLETED']; ?></h1>
<br />
<?php
if (!$inline_update)
{
?>
<p style="color:red"><?php echo $lang['UPDATE_FILES_NOTICE']; ?></p>
<p><?php echo $lang['COMPLETE_LOGIN_TO_BOARD']; ?></p>
<?php
}
else
{
?>
<p><?php echo ((isset($lang['INLINE_UPDATE_SUCCESSFUL'])) ? $lang['INLINE_UPDATE_SUCCESSFUL'] : 'The database update was successful. Now you need to continue the update process.'); ?></p>
<p><a href="<?php echo append_sid("{$phpbb_root_path}install/index.{$phpEx}", "mode=update&amp;sub=file_check&amp;language=$language"); ?>" class="button1"><?php echo (isset($lang['CONTINUE_UPDATE_NOW'])) ? $lang['CONTINUE_UPDATE_NOW'] : 'Continue the update process now'; ?></a></p>
<?php
}
// Add database update to log
add_log('admin', 'LOG_UPDATE_DATABASE', $orig_version, $updates_to_version);
// Now we purge the session table as well as all cache files
$cache->purge();
_print_footer();
garbage_collection();
if (function_exists('exit_handler'))
{
exit_handler();
}
/**
* Print out footer
*/
function _print_footer()
{
echo <<<EOF
</div>
</div>
<span class="corners-bottom"><span></span></span>
</div>
</div>
</div>
<div id="page-footer">
Powered by <a href="http://www.phpbb.com/">phpBB</a> &copy; phpBB Group
</div>
</div>
</body>
</html>
EOF;
}
/**
* Function for triggering an sql statement
*/
function _sql($sql, &$errored, &$error_ary, $echo_dot = true)
{
global $db;
if (defined('DEBUG_EXTRA'))
{
echo "<br />\n{$sql}\n<br />";
}
$db->sql_return_on_error(true);
if ($sql === 'begin')
{
$result = $db->sql_transaction('begin');
}
else if ($sql === 'commit')
{
$result = $db->sql_transaction('commit');
}
else
{
$result = $db->sql_query($sql);
if ($db->sql_error_triggered)
{
$errored = true;
$error_ary['sql'][] = $db->sql_error_sql;
$error_ary['error_code'][] = $db->sql_error_returned;
}
}
$db->sql_return_on_error(false);
if ($echo_dot)
{
echo ". \n";
flush();
}
return $result;
}
function _write_result($no_updates, $errored, $error_ary)
{
global $lang;
if ($no_updates)
{
echo ' ' . $lang['NO_UPDATES_REQUIRED'] . '</strong></p>';
}
else
{
echo ' <span class="success">' . $lang['DONE'] . '</span></strong><br />' . $lang['RESULT'] . ' :: ';
if ($errored)
{
echo ' <strong>' . $lang['SOME_QUERIES_FAILED'] . '</strong> <ul>';
for ($i = 0; $i < sizeof($error_ary['sql']); $i++)
{
echo '<li>' . $lang['ERROR'] . ' :: <strong>' . htmlspecialchars($error_ary['error_code'][$i]['message']) . '</strong><br />';
echo $lang['SQL'] . ' :: <strong>' . htmlspecialchars($error_ary['sql'][$i]) . '</strong><br /><br /></li>';
}
echo '</ul> <br /><br />' . $lang['SQL_FAILURE_EXPLAIN'] . '</p>';
}
else
{
echo '<strong>' . $lang['NO_ERRORS'] . '</strong></p>';
}
}
}
function _add_modules($modules_to_install)
{
global $phpbb_root_path, $phpEx, $db;
include_once($phpbb_root_path . 'includes/acp/acp_modules.' . $phpEx);
$_module = new acp_modules();
foreach ($modules_to_install as $module_mode => $module_data)
{
$_module->module_class = $module_data['class'];
// Determine parent id first
$sql = 'SELECT module_id
FROM ' . MODULES_TABLE . "
WHERE module_class = '" . $db->sql_escape($module_data['class']) . "'
AND module_langname = '" . $db->sql_escape($module_data['cat']) . "'
AND module_mode = ''
AND module_basename = ''";
$result = $db->sql_query($sql);
// There may be more than one categories with the same name
$categories = array();
while ($row = $db->sql_fetchrow($result))
{
$categories[] = (int) $row['module_id'];
}
$db->sql_freeresult($result);
if (!sizeof($categories))
{
continue;
}
// Add the module to all categories found
foreach ($categories as $parent_id)
{
// Check if the module already exists
$sql = 'SELECT *
FROM ' . MODULES_TABLE . "
WHERE module_basename = '" . $db->sql_escape($module_data['base']) . "'
AND module_class = '" . $db->sql_escape($module_data['class']) . "'
AND module_langname = '" . $db->sql_escape($module_data['title']) . "'
AND module_mode = '" . $db->sql_escape($module_mode) . "'
AND module_auth = '" . $db->sql_escape($module_data['auth']) . "'
AND parent_id = {$parent_id}";
$result = $db->sql_query($sql);
$row = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
// If it exists, we simply continue with the next category
if ($row)
{
continue;
}
// Build the module sql row
$module_row = array(
'module_basename' => $module_data['base'],
'module_enabled' => (isset($module_data['enabled'])) ? (int) $module_data['enabled'] : 1,
'module_display' => (isset($module_data['display'])) ? (int) $module_data['display'] : 1,
'parent_id' => $parent_id,
'module_class' => $module_data['class'],
'module_langname' => $module_data['title'],
'module_mode' => $module_mode,
'module_auth' => $module_data['auth'],
);
$_module->update_module_data($module_row, true);
// Ok, do we need to re-order the module, move it up or down?
if (!isset($module_data['after']))
{
continue;
}
$after_mode = $module_data['after'][0];
$after_langname = $module_data['after'][1];
// First of all, get the module id for the module this one has to be placed after
$sql = 'SELECT left_id
FROM ' . MODULES_TABLE . "
WHERE module_class = '" . $db->sql_escape($module_data['class']) . "'
AND module_basename = '" . $db->sql_escape($module_data['base']) . "'
AND module_langname = '" . $db->sql_escape($after_langname) . "'
AND module_mode = '" . $db->sql_escape($after_mode) . "'
AND parent_id = '{$parent_id}'";
$result = $db->sql_query($sql);
$first_left_id = (int) $db->sql_fetchfield('left_id');
$db->sql_freeresult($result);
if (!$first_left_id)
{
continue;
}
// Ok, count the number of modules between $after_mode and the added module
$sql = 'SELECT COUNT(module_id) as num_modules
FROM ' . MODULES_TABLE . "
WHERE module_class = '" . $db->sql_escape($module_data['class']) . "'
AND parent_id = {$parent_id}
AND left_id BETWEEN {$first_left_id} AND {$module_row['left_id']}";
$result = $db->sql_query($sql);
$steps = (int) $db->sql_fetchfield('num_modules');
$db->sql_freeresult($result);
// We need to substract 2
$steps -= 2;
if ($steps <= 0)
{
continue;
}
// Ok, move module up $num_modules times. ;)
$_module->move_module_by($module_row, 'move_up', $steps);
}
}
$_module->remove_cache_file();
}
/****************************************************************************
* ADD YOUR DATABASE SCHEMA CHANGES HERE *
*****************************************************************************/
function database_update_info()
{
return array(
// Changes from 3.0.0 to the next version
'3.0.0' => array(
// Add the following columns
'add_columns' => array(
FORUMS_TABLE => array(
'display_subforum_list' => array('BOOL', 1),
),
SESSIONS_TABLE => array(
'session_forum_id' => array('UINT', 0),
),
),
'drop_keys' => array(
GROUPS_TABLE => array('group_legend'),
),
'add_index' => array(
SESSIONS_TABLE => array(
'session_forum_id' => array('session_forum_id'),
),
GROUPS_TABLE => array(
'group_legend_name' => array('group_legend', 'group_name'),
),
),
),
// No changes from 3.0.1-RC1 to 3.0.1
'3.0.1-RC1' => array(),
// No changes from 3.0.1 to 3.0.2-RC1
'3.0.1' => array(),
// Changes from 3.0.2-RC1 to 3.0.2-RC2
'3.0.2-RC1' => array(
'change_columns' => array(
DRAFTS_TABLE => array(
'draft_subject' => array('STEXT_UNI', ''),
),
FORUMS_TABLE => array(
'forum_last_post_subject' => array('STEXT_UNI', ''),
),
POSTS_TABLE => array(
'post_subject' => array('STEXT_UNI', '', 'true_sort'),
),
PRIVMSGS_TABLE => array(
'message_subject' => array('STEXT_UNI', ''),
),
TOPICS_TABLE => array(
'topic_title' => array('STEXT_UNI', '', 'true_sort'),
'topic_last_post_subject' => array('STEXT_UNI', ''),
),
),
'drop_keys' => array(
SESSIONS_TABLE => array('session_forum_id'),
),
'add_index' => array(
SESSIONS_TABLE => array(
'session_fid' => array('session_forum_id'),
),
),
),
// No changes from 3.0.2-RC2 to 3.0.2
'3.0.2-RC2' => array(),
// Changes from 3.0.2 to 3.0.3-RC1
'3.0.2' => array(
// Add the following columns
'add_columns' => array(
STYLES_TEMPLATE_TABLE => array(
'template_inherits_id' => array('UINT:4', 0),
'template_inherit_path' => array('VCHAR', ''),
),
GROUPS_TABLE => array(
'group_max_recipients' => array('UINT', 0),
),
),
),
// No changes from 3.0.3-RC1 to 3.0.3
'3.0.3-RC1' => array(),
// Changes from 3.0.3 to 3.0.4-RC1
'3.0.3' => array(
'add_columns' => array(
PROFILE_FIELDS_TABLE => array(
'field_show_profile' => array('BOOL', 0),
),
),
'change_columns' => array(
STYLES_TABLE => array(
'style_id' => array('UINT', NULL, 'auto_increment'),
'template_id' => array('UINT', 0),
'theme_id' => array('UINT', 0),
'imageset_id' => array('UINT', 0),
),
STYLES_IMAGESET_TABLE => array(
'imageset_id' => array('UINT', NULL, 'auto_increment'),
),
STYLES_IMAGESET_DATA_TABLE => array(
'image_id' => array('UINT', NULL, 'auto_increment'),
'imageset_id' => array('UINT', 0),
),
STYLES_THEME_TABLE => array(
'theme_id' => array('UINT', NULL, 'auto_increment'),
),
STYLES_TEMPLATE_TABLE => array(
'template_id' => array('UINT', NULL, 'auto_increment'),
),
STYLES_TEMPLATE_DATA_TABLE => array(
'template_id' => array('UINT', 0),
),
FORUMS_TABLE => array(
'forum_style' => array('UINT', 0),
),
USERS_TABLE => array(
'user_style' => array('UINT', 0),
),
),
),
// Changes from 3.0.4-RC1 to 3.0.4
'3.0.4-RC1' => array(),
// Changes from 3.0.4 to 3.0.5-RC1
'3.0.4' => array(
'change_columns' => array(
FORUMS_TABLE => array(
'forum_style' => array('UINT', 0),
),
),
),
// No changes from 3.0.5-RC1 to 3.0.5
'3.0.5-RC1' => array(),
// Changes from 3.0.5 to 3.0.6-RC1
'3.0.5' => array(
'add_columns' => array(
CONFIRM_TABLE => array(
'attempts' => array('UINT', 0),
),
USERS_TABLE => array(
'user_new' => array('BOOL', 1),
'user_reminded' => array('TINT:4', 0),
'user_reminded_time'=> array('TIMESTAMP', 0),
),
GROUPS_TABLE => array(
'group_skip_auth' => array('BOOL', 0, 'after' => 'group_founder_manage'),
),
PRIVMSGS_TABLE => array(
'message_reported' => array('BOOL', 0),
),
REPORTS_TABLE => array(
'pm_id' => array('UINT', 0),
),
PROFILE_FIELDS_TABLE => array(
'field_show_on_vt' => array('BOOL', 0),
),
FORUMS_TABLE => array(
'forum_options' => array('UINT:20', 0),
),
),
'change_columns' => array(
USERS_TABLE => array(
'user_options' => array('UINT:11', 230271),
),
),
'add_index' => array(
REPORTS_TABLE => array(
'post_id' => array('post_id'),
'pm_id' => array('pm_id'),
),
POSTS_TABLE => array(
'post_username' => array('post_username:255'),
),
),
),
// No changes from 3.0.6-RC1 to 3.0.6-RC2
'3.0.6-RC1' => array(),
// No changes from 3.0.6-RC2 to 3.0.6-RC3
'3.0.6-RC2' => array(),
// No changes from 3.0.6-RC3 to 3.0.6-RC4
'3.0.6-RC3' => array(),
// No changes from 3.0.6-RC4 to 3.0.6
'3.0.6-RC4' => array(),
// Changes from 3.0.6 to 3.0.7-RC1
'3.0.6' => array(
'drop_keys' => array(
LOG_TABLE => array('log_time'),
),
'add_index' => array(
TOPICS_TRACK_TABLE => array(
'topic_id' => array('topic_id'),
),
),
),
// No changes from 3.0.7-RC1 to 3.0.7-RC2
'3.0.7-RC1' => array(),
// No changes from 3.0.7-RC2 to 3.0.7
'3.0.7-RC2' => array(),
// No changes from 3.0.7 to 3.0.7-PL1
'3.0.7' => array(),
// No changes from 3.0.7-PL1 to 3.0.8-RC1
'3.0.7-PL1' => array(),
// No changes from 3.0.8-RC1 to 3.0.8
'3.0.8-RC1' => array(),
// Changes from 3.0.8 to 3.0.9-RC1
'3.0.8' => array(
'add_tables' => array(
LOGIN_ATTEMPT_TABLE => array(
'COLUMNS' => array(
'attempt_id' => array('UINT', NULL, 'auto_increment'),
'attempt_ip' => array('VCHAR:40', ''),
'attempt_browser' => array('VCHAR:150', ''),
'attempt_forwarded_for' => array('VCHAR:255', ''),
'attempt_time' => array('TIMESTAMP', 0),
'user_id' => array('UINT', 0),
'username' => array('VCHAR_UNI:255', 0),
'username_clean' => array('VCHAR_CI', 0),
),
'PRIMARY_KEY' => 'attempt_id',
'KEYS' => array(
'att_ip' => array('INDEX', array('attempt_ip', 'attempt_time')),
'att_for' => array('INDEX', array('attempt_forwarded_for', 'attempt_time')),
'att_time' => array('INDEX', array('attempt_time')),
'user_id' => array('INDEX', 'user_id'),
),
),
),
'change_columns' => array(
BBCODES_TABLE => array(
'bbcode_id' => array('USINT', 0),
),
),
),
// No changes from 3.0.9-RC1 to 3.0.9-RC2
'3.0.9-RC1' => array(),
);
}
/****************************************************************************
* ADD YOUR DATABASE DATA CHANGES HERE *
* REMEMBER: You NEED to enter a schema array above and a data array here, *
* even if both or one of them are empty. *
*****************************************************************************/
function change_database_data(&$no_updates, $version)
{
global $db, $errored, $error_ary, $config, $phpbb_root_path, $phpEx;
switch ($version)
{
case '3.0.0':
$sql = 'UPDATE ' . TOPICS_TABLE . "
SET topic_last_view_time = topic_last_post_time
WHERE topic_last_view_time = 0";
_sql($sql, $errored, $error_ary);
// Update smiley sizes
$smileys = array('icon_e_surprised.gif', 'icon_eek.gif', 'icon_cool.gif', 'icon_lol.gif', 'icon_mad.gif', 'icon_razz.gif', 'icon_redface.gif', 'icon_cry.gif', 'icon_evil.gif', 'icon_twisted.gif', 'icon_rolleyes.gif', 'icon_exclaim.gif', 'icon_question.gif', 'icon_idea.gif', 'icon_arrow.gif', 'icon_neutral.gif', 'icon_mrgreen.gif', 'icon_e_ugeek.gif');
foreach ($smileys as $smiley)
{
if (file_exists($phpbb_root_path . 'images/smilies/' . $smiley))
{
list($width, $height) = getimagesize($phpbb_root_path . 'images/smilies/' . $smiley);
$sql = 'UPDATE ' . SMILIES_TABLE . '
SET smiley_width = ' . $width . ', smiley_height = ' . $height . "
WHERE smiley_url = '" . $db->sql_escape($smiley) . "'";
_sql($sql, $errored, $error_ary);
}
}
$no_updates = false;
break;
// No changes from 3.0.1-RC1 to 3.0.1
case '3.0.1-RC1':
break;
// changes from 3.0.1 to 3.0.2-RC1
case '3.0.1':
set_config('referer_validation', '1');
set_config('check_attachment_content', '1');
set_config('mime_triggers', 'body|head|html|img|plaintext|a href|pre|script|table|title');
$no_updates = false;
break;
// No changes from 3.0.2-RC1 to 3.0.2-RC2
case '3.0.2-RC1':
break;
// No changes from 3.0.2-RC2 to 3.0.2
case '3.0.2-RC2':
break;
// Changes from 3.0.2 to 3.0.3-RC1
case '3.0.2':
set_config('enable_queue_trigger', '0');
set_config('queue_trigger_posts', '3');
set_config('pm_max_recipients', '0');
// Set maximum number of recipients for the registered users, bots, guests group
$sql = 'UPDATE ' . GROUPS_TABLE . ' SET group_max_recipients = 5
WHERE ' . $db->sql_in_set('group_name', array('GUESTS', 'REGISTERED', 'REGISTERED_COPPA', 'BOTS'));
_sql($sql, $errored, $error_ary);
// Not prefilling yet
set_config('dbms_version', '');
// Add new permission u_masspm_group and duplicate settings from u_masspm
include_once($phpbb_root_path . 'includes/acp/auth.' . $phpEx);
$auth_admin = new auth_admin();
// Only add the new permission if it does not already exist
if (empty($auth_admin->acl_options['id']['u_masspm_group']))
{
$auth_admin->acl_add_option(array('global' => array('u_masspm_group')));
// Now the tricky part, filling the permission
$old_id = $auth_admin->acl_options['id']['u_masspm'];
$new_id = $auth_admin->acl_options['id']['u_masspm_group'];
$tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE);
foreach ($tables as $table)
{
$sql = 'SELECT *
FROM ' . $table . '
WHERE auth_option_id = ' . $old_id;
$result = _sql($sql, $errored, $error_ary);
$sql_ary = array();
while ($row = $db->sql_fetchrow($result))
{
$row['auth_option_id'] = $new_id;
$sql_ary[] = $row;
}
$db->sql_freeresult($result);
if (sizeof($sql_ary))
{
$db->sql_multi_insert($table, $sql_ary);
}
}
// Remove any old permission entries
$auth_admin->acl_clear_prefetch();
}
/**
* Do not resync post counts here. An admin may do this later from the ACP
$start = 0;
$step = ($config['num_posts']) ? (max((int) ($config['num_posts'] / 5), 20000)) : 20000;
$sql = 'UPDATE ' . USERS_TABLE . ' SET user_posts = 0';
_sql($sql, $errored, $error_ary);
do
{
$sql = 'SELECT COUNT(post_id) AS num_posts, poster_id
FROM ' . POSTS_TABLE . '
WHERE post_id BETWEEN ' . ($start + 1) . ' AND ' . ($start + $step) . '
AND post_postcount = 1 AND post_approved = 1
GROUP BY poster_id';
$result = _sql($sql, $errored, $error_ary);
if ($row = $db->sql_fetchrow($result))
{
do
{
$sql = 'UPDATE ' . USERS_TABLE . " SET user_posts = user_posts + {$row['num_posts']} WHERE user_id = {$row['poster_id']}";
_sql($sql, $errored, $error_ary);
}
while ($row = $db->sql_fetchrow($result));
$start += $step;
}
else
{
$start = 0;
}
$db->sql_freeresult($result);
}
while ($start);
*/
$sql = 'UPDATE ' . MODULES_TABLE . '
SET module_auth = \'acl_a_email && cfg_email_enable\'
WHERE module_class = \'acp\'
AND module_basename = \'email\'';
_sql($sql, $errored, $error_ary);
$no_updates = false;
break;
// Changes from 3.0.3-RC1 to 3.0.3
case '3.0.3-RC1':
if ($db->sql_layer == 'oracle')
{
// log_operation is CLOB - but we can change this later
$sql = 'UPDATE ' . LOG_TABLE . "
SET log_operation = 'LOG_DELETE_TOPIC'
WHERE log_operation LIKE 'LOG_TOPIC_DELETED'";
_sql($sql, $errored, $error_ary);
}
else
{
$sql = 'UPDATE ' . LOG_TABLE . "
SET log_operation = 'LOG_DELETE_TOPIC'
WHERE log_operation = 'LOG_TOPIC_DELETED'";
_sql($sql, $errored, $error_ary);
}
$no_updates = false;
break;
// Changes from 3.0.3 to 3.0.4-RC1
case '3.0.3':
// Update the Custom Profile Fields based on previous settings to the new format
$sql = 'SELECT field_id, field_required, field_show_on_reg, field_hide
FROM ' . PROFILE_FIELDS_TABLE;
$result = _sql($sql, $errored, $error_ary);
while ($row = $db->sql_fetchrow($result))
{
$sql_ary = array(
'field_required' => 0,
'field_show_on_reg' => 0,
'field_hide' => 0,
'field_show_profile'=> 0,
);
if ($row['field_required'])
{
$sql_ary['field_required'] = $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1;
}
else if ($row['field_show_on_reg'])
{
$sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1;
}
else if ($row['field_hide'])
{
// Only administrators and moderators can see this CPF, if the view is enabled, they can see it, otherwise just admins in the acp_users module
$sql_ary['field_hide'] = 1;
}
else
{
// equivelant to "none", which is the "Display in user control panel" option
$sql_ary['field_show_profile'] = 1;
}
_sql('UPDATE ' . PROFILE_FIELDS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' WHERE field_id = ' . $row['field_id'], $errored, $error_ary);
}
$no_updates = false;
break;
// Changes from 3.0.4-RC1 to 3.0.4
case '3.0.4-RC1':
break;
// Changes from 3.0.4 to 3.0.5-RC1
case '3.0.4':
// Captcha config variables
set_config('captcha_gd_wave', 0);
set_config('captcha_gd_3d_noise', 1);
set_config('captcha_gd_fonts', 1);
set_config('confirm_refresh', 1);
// Maximum number of keywords
set_config('max_num_search_keywords', 10);
// Remove static config var and put it back as dynamic variable
$sql = 'UPDATE ' . CONFIG_TABLE . "
SET is_dynamic = 1
WHERE config_name = 'search_indexing_state'";
_sql($sql, $errored, $error_ary);
// Hash old MD5 passwords
$sql = 'SELECT user_id, user_password
FROM ' . USERS_TABLE . '
WHERE user_pass_convert = 1';
$result = _sql($sql, $errored, $error_ary);
while ($row = $db->sql_fetchrow($result))
{
if (strlen($row['user_password']) == 32)
{
$sql_ary = array(
'user_password' => phpbb_hash($row['user_password']),
);
_sql('UPDATE ' . USERS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' WHERE user_id = ' . $row['user_id'], $errored, $error_ary);
}
}
$db->sql_freeresult($result);
// Adjust bot entry
$sql = 'UPDATE ' . BOTS_TABLE . "
SET bot_agent = 'ichiro/'
WHERE bot_agent = 'ichiro/2'";
_sql($sql, $errored, $error_ary);
// Before we are able to add a unique key to auth_option, we need to remove duplicate entries
// We get duplicate entries first
$sql = 'SELECT auth_option
FROM ' . ACL_OPTIONS_TABLE . '
GROUP BY auth_option
HAVING COUNT(*) >= 2';
$result = $db->sql_query($sql);
$auth_options = array();
while ($row = $db->sql_fetchrow($result))
{
$auth_options[] = $row['auth_option'];
}
$db->sql_freeresult($result);
// Remove specific auth options
if (!empty($auth_options))
{
foreach ($auth_options as $option)
{
// Select auth_option_ids... the largest id will be preserved
$sql = 'SELECT auth_option_id
FROM ' . ACL_OPTIONS_TABLE . "
WHERE auth_option = '" . $db->sql_escape($option) . "'
ORDER BY auth_option_id DESC";
// sql_query_limit not possible here, due to bug in postgresql layer
$result = $db->sql_query($sql);
// Skip first row, this is our original auth option we want to preserve
$row = $db->sql_fetchrow($result);
while ($row = $db->sql_fetchrow($result))
{
// Ok, remove this auth option...
_sql('DELETE FROM ' . ACL_OPTIONS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id'], $errored, $error_ary);
_sql('DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id'], $errored, $error_ary);
_sql('DELETE FROM ' . ACL_GROUPS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id'], $errored, $error_ary);
_sql('DELETE FROM ' . ACL_USERS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id'], $errored, $error_ary);
}
$db->sql_freeresult($result);
}
}
// Now make auth_option UNIQUE, by dropping the old index and adding a UNIQUE one.
$changes = array(
'drop_keys' => array(
ACL_OPTIONS_TABLE => array('auth_option'),
),
);
global $db_tools;
$statements = $db_tools->perform_schema_changes($changes);
foreach ($statements as $sql)
{
_sql($sql, $errored, $error_ary);
}
$changes = array(
'add_unique_index' => array(
ACL_OPTIONS_TABLE => array(
'auth_option' => array('auth_option'),
),
),
);
$statements = $db_tools->perform_schema_changes($changes);
foreach ($statements as $sql)
{
_sql($sql, $errored, $error_ary);
}
$no_updates = false;
break;
// No changes from 3.0.5-RC1 to 3.0.5
case '3.0.5-RC1':
break;
// Changes from 3.0.5 to 3.0.6-RC1
case '3.0.5':
// Let's see if the GD Captcha can be enabled... we simply look for what *is* enabled...
if (!empty($config['captcha_gd']) && !isset($config['captcha_plugin']))
{
set_config('captcha_plugin', 'phpbb_captcha_gd');
}
else if (!isset($config['captcha_plugin']))
{
set_config('captcha_plugin', 'phpbb_captcha_nogd');
}
// Entries for the Feed Feature
set_config('feed_enable', '0');
set_config('feed_limit', '10');
set_config('feed_overall_forums', '1');
set_config('feed_overall_forums_limit', '15');
set_config('feed_overall_topics', '0');
set_config('feed_overall_topics_limit', '15');
set_config('feed_forum', '1');
set_config('feed_topic', '1');
set_config('feed_item_statistics', '1');
// Entries for smiley pagination
set_config('smilies_per_page', '50');
// Entry for reporting PMs
set_config('allow_pm_report', '1');
// Install modules
$modules_to_install = array(
'feed' => array(
'base' => 'board',
'class' => 'acp',
'title' => 'ACP_FEED_SETTINGS',
'auth' => 'acl_a_board',
'cat' => 'ACP_BOARD_CONFIGURATION',
'after' => array('signature', 'ACP_SIGNATURE_SETTINGS')
),
'warnings' => array(
'base' => 'users',
'class' => 'acp',
'title' => 'ACP_USER_WARNINGS',
'auth' => 'acl_a_user',
'display' => 0,
'cat' => 'ACP_CAT_USERS',
'after' => array('feedback', 'ACP_USER_FEEDBACK')
),
'send_statistics' => array(
'base' => 'send_statistics',
'class' => 'acp',
'title' => 'ACP_SEND_STATISTICS',
'auth' => 'acl_a_server',
'cat' => 'ACP_SERVER_CONFIGURATION'
),
'setting_forum_copy' => array(
'base' => 'permissions',
'class' => 'acp',
'title' => 'ACP_FORUM_PERMISSIONS_COPY',
'auth' => 'acl_a_fauth && acl_a_authusers && acl_a_authgroups && acl_a_mauth',
'cat' => 'ACP_FORUM_BASED_PERMISSIONS',
'after' => array('setting_forum_local', 'ACP_FORUM_PERMISSIONS')
),
'pm_reports' => array(
'base' => 'pm_reports',
'class' => 'mcp',
'title' => 'MCP_PM_REPORTS_OPEN',
'auth' => 'aclf_m_report',
'cat' => 'MCP_REPORTS'
),
'pm_reports_closed' => array(
'base' => 'pm_reports',
'class' => 'mcp',
'title' => 'MCP_PM_REPORTS_CLOSED',
'auth' => 'aclf_m_report',
'cat' => 'MCP_REPORTS'
),
'pm_report_details' => array(
'base' => 'pm_reports',
'class' => 'mcp',
'title' => 'MCP_PM_REPORT_DETAILS',
'auth' => 'aclf_m_report',
'cat' => 'MCP_REPORTS'
),
);
_add_modules($modules_to_install);
// Add newly_registered group... but check if it already exists (we always supported running the updater on any schema)
$sql = 'SELECT group_id
FROM ' . GROUPS_TABLE . "
WHERE group_name = 'NEWLY_REGISTERED'";
$result = $db->sql_query($sql);
$group_id = (int) $db->sql_fetchfield('group_id');
$db->sql_freeresult($result);
if (!$group_id)
{
$sql = 'INSERT INTO ' . GROUPS_TABLE . " (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('NEWLY_REGISTERED', 3, 0, '', 0, '', '', '', 5)";
_sql($sql, $errored, $error_ary);
$group_id = $db->sql_nextid();
}
// Insert new user role... at the end of the chain
$sql = 'SELECT role_id
FROM ' . ACL_ROLES_TABLE . "
WHERE role_name = 'ROLE_USER_NEW_MEMBER'
AND role_type = 'u_'";
$result = $db->sql_query($sql);
$u_role = (int) $db->sql_fetchfield('role_id');
$db->sql_freeresult($result);
if (!$u_role)
{
$sql = 'SELECT MAX(role_order) as max_order_id
FROM ' . ACL_ROLES_TABLE . "
WHERE role_type = 'u_'";
$result = $db->sql_query($sql);
$next_order_id = (int) $db->sql_fetchfield('max_order_id');
$db->sql_freeresult($result);
$next_order_id++;
$sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_NEW_MEMBER', 'ROLE_DESCRIPTION_USER_NEW_MEMBER', 'u_', $next_order_id)";
_sql($sql, $errored, $error_ary);
$u_role = $db->sql_nextid();
if (!$errored)
{
// Now add the correct data to the roles...
// The standard role says that new users are not able to send a PM, Mass PM, are not able to PM groups
$sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $u_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_sendpm', 'u_masspm', 'u_masspm_group')";
_sql($sql, $errored, $error_ary);
// Add user role to group
$sql = 'INSERT INTO ' . ACL_GROUPS_TABLE . " (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES ($group_id, 0, 0, $u_role, 0)";
_sql($sql, $errored, $error_ary);
}
}
// Insert new forum role
$sql = 'SELECT role_id
FROM ' . ACL_ROLES_TABLE . "
WHERE role_name = 'ROLE_FORUM_NEW_MEMBER'
AND role_type = 'f_'";
$result = $db->sql_query($sql);
$f_role = (int) $db->sql_fetchfield('role_id');
$db->sql_freeresult($result);
if (!$f_role)
{
$sql = 'SELECT MAX(role_order) as max_order_id
FROM ' . ACL_ROLES_TABLE . "
WHERE role_type = 'f_'";
$result = $db->sql_query($sql);
$next_order_id = (int) $db->sql_fetchfield('max_order_id');
$db->sql_freeresult($result);
$next_order_id++;
$sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_NEW_MEMBER', 'ROLE_DESCRIPTION_FORUM_NEW_MEMBER', 'f_', $next_order_id)";
_sql($sql, $errored, $error_ary);
$f_role = $db->sql_nextid();
if (!$errored)
{
$sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $f_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'f_%' AND auth_option IN ('f_noapprove')";
_sql($sql, $errored, $error_ary);
}
}
// Set every members user_new column to 0 (old users) only if there is no one yet (this makes sure we do not execute this more than once)
$sql = 'SELECT 1
FROM ' . USERS_TABLE . '
WHERE user_new = 0';
$result = $db->sql_query_limit($sql, 1);
$row = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
if (!$row)
{
$sql = 'UPDATE ' . USERS_TABLE . ' SET user_new = 0';
_sql($sql, $errored, $error_ary);
}
// Newly registered users limit
if (!isset($config['new_member_post_limit']))
{
set_config('new_member_post_limit', (!empty($config['enable_queue_trigger'])) ? $config['queue_trigger_posts'] : 0);
}
if (!isset($config['new_member_group_default']))
{
set_config('new_member_group_default', 0);
}
// To mimick the old "feature" we will assign the forum role to every forum, regardless of the setting (this makes sure there are no "this does not work!!!! YUO!!!" posts...
// Check if the role is already assigned...
$sql = 'SELECT forum_id
FROM ' . ACL_GROUPS_TABLE . '
WHERE group_id = ' . $group_id . '
AND auth_role_id = ' . $f_role;
$result = $db->sql_query($sql);
$is_options = (int) $db->sql_fetchfield('forum_id');
$db->sql_freeresult($result);
// Not assigned at all... :/
if (!$is_options)
{
// Get postable forums
$sql = 'SELECT forum_id
FROM ' . FORUMS_TABLE . '
WHERE forum_type != ' . FORUM_LINK;
$result = $db->sql_query($sql);
while ($row = $db->sql_fetchrow($result))
{
_sql('INSERT INTO ' . ACL_GROUPS_TABLE . ' (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (' . $group_id . ', ' . (int) $row['forum_id'] . ', 0, ' . $f_role . ', 0)', $errored, $error_ary);
}
$db->sql_freeresult($result);
}
// Clear permissions...
include_once($phpbb_root_path . 'includes/acp/auth.' . $phpEx);
$auth_admin = new auth_admin();
$auth_admin->acl_clear_prefetch();
if (!isset($config['allow_avatar']))
{
if ($config['allow_avatar_upload'] || $config['allow_avatar_local'] || $config['allow_avatar_remote'])
{
set_config('allow_avatar', '1');
}
else
{
set_config('allow_avatar', '0');
}
}
if (!isset($config['allow_avatar_remote_upload']))
{
if ($config['allow_avatar_remote'] && $config['allow_avatar_upload'])
{
set_config('allow_avatar_remote_upload', '1');
}
else
{
set_config('allow_avatar_remote_upload', '0');
}
}
// Minimum number of characters
if (!isset($config['min_post_chars']))
{
set_config('min_post_chars', '1');
}
if (!isset($config['allow_quick_reply']))
{
set_config('allow_quick_reply', '1');
}
// Set every members user_options column to enable
// bbcode, smilies and URLs for signatures by default
$sql = 'SELECT user_options
FROM ' . USERS_TABLE . '
WHERE user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')';
$result = $db->sql_query_limit($sql, 1);
$user_option = (int) $db->sql_fetchfield('user_options');
$db->sql_freeresult($result);
// Check if we already updated the database by checking bit 15 which we used to store the sig_bbcode option
if (!($user_option & 1 << 15))
{
// 229376 is the added value to enable all three signature options
$sql = 'UPDATE ' . USERS_TABLE . ' SET user_options = user_options + 229376';
_sql($sql, $errored, $error_ary);
}
if (!isset($config['delete_time']))
{
set_config('delete_time', $config['edit_time']);
}
$no_updates = false;
break;
// No changes from 3.0.6-RC1 to 3.0.6-RC2
case '3.0.6-RC1':
break;
// Changes from 3.0.6-RC2 to 3.0.6-RC3
case '3.0.6-RC2':
// Update the Custom Profile Fields based on previous settings to the new format
$sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . '
SET field_show_on_vt = 1
WHERE field_hide = 0
AND (field_required = 1 OR field_show_on_reg = 1 OR field_show_profile = 1)';
_sql($sql, $errored, $error_ary);
$no_updates = false;
break;
// No changes from 3.0.6-RC3 to 3.0.6-RC4
case '3.0.6-RC3':
break;
// No changes from 3.0.6-RC4 to 3.0.6
case '3.0.6-RC4':
break;
// Changes from 3.0.6 to 3.0.7-RC1
case '3.0.6':
// ATOM Feeds
set_config('feed_overall', '1');
set_config('feed_http_auth', '0');
set_config('feed_limit_post', (string) (isset($config['feed_limit']) ? (int) $config['feed_limit'] : 15));
set_config('feed_limit_topic', (string) (isset($config['feed_overall_topics_limit']) ? (int) $config['feed_overall_topics_limit'] : 10));
set_config('feed_topics_new', (!empty($config['feed_overall_topics']) ? '1' : '0'));
set_config('feed_topics_active', (!empty($config['feed_overall_topics']) ? '1' : '0'));
Do not store email templates in database. [Bug #54505] To explain what this is about, first a short phpBB code history lesson: ;-) r9823 originally introduced the usage of our template class for emails. The messenger class uses set_custom_template() to initialise the template object which neither disables storedb nor inheritance. These two values are set in $user->theme rather than inside a particular template instance (quite a design failure if I may add). Thus the html page that is displayed to the user also determines these settings for the email templates. This obviously causes problems because both emails and other custom templates can quite simply not be stored in the database because the db table only stores the filename, not the path and requires a template id. r9839 then generally disabled storedb and template inheritance for custom templates to fix Bug #40515. This works for custom templates, but not for emails where lots of template objects are created. In such a situation the last call to set(_custom)_template() would now determine the values of storedb and inheritance in _tpl_load. So any page sending emails would neither load its template from the database nor use template inheritance. The same revision also introduced orig_tpl_* variables in set_template() which on their own are very much pointless, but could allow resetting the storedb and inheritance values if they were used to reset $user->theme just before template execution in _tpl_load. In r10150 these orig_tpl_* variables are correctly used to access information about the template of the page being displayed - contrary to the last template used - from within the bbcode, fixing Bug #51285. However r10150 also introduces a pointless $template_mode parameter for set_custom_template(). $template_mode is really just a boolean flag (value you can be 'template' or an arbitrary other value) that if it set circumvents the unsetting of storedb and template inheritance. The very code that had been added to prevent issues with emails and custom templates. Fixing the problem introduced by r8839 but at the same time reintroducing the much greater problem from the original implementation of email templates. And now an explanation of what I did: Based on this I have now changed the set_custom_template method to always disable storedb. It can now properly use inheritance, you simply tell it the path where the parent template can be found, by default the path is false which will turn inheritance off. To make this work the template class now always overwrites $user->theme storedb and inheritance variabbles with orig_tpl_* just before rendering a template in _tpl_load. This way they are guaranteed to always contain the value they had at the time set_template/set_custom_template were called. This fixes [Bug #54505]. In summary, using global state is simply a horrible idea in object oriented programming. Always Pass values, that an object depends on, as parameters - never through magic global variables. Following this principle will safe you from a lot of headaches. Please test this patch as much as possible to make sure templates still work properly for you, focus on multiple languages, missing language files, and custom templates in systems that make use of the template class outside of phpBB itself. git-svn-id: file:///svn/phpbb/branches/phpBB-3_0_0@10460 89ea8834-ac86-4346-8a33-228a782c2dd0
2010-01-26 16:52:46 +00:00
// Delete all text-templates from the template_data
$sql = 'DELETE FROM ' . STYLES_TEMPLATE_DATA_TABLE . '
WHERE template_filename ' . $db->sql_like_expression($db->any_char . '.txt');
_sql($sql, $errored, $error_ary);
$no_updates = false;
break;
// Changes from 3.0.7-RC1 to 3.0.7-RC2
case '3.0.7-RC1':
$sql = 'SELECT user_id, user_email, user_email_hash
FROM ' . USERS_TABLE . '
WHERE user_type <> ' . USER_IGNORE . "
AND user_email <> ''";
$result = $db->sql_query($sql);
$i = 0;
while ($row = $db->sql_fetchrow($result))
{
// Snapshot of the phpbb_email_hash() function
// We cannot call it directly because the auto updater updates the DB first. :/
$user_email_hash = sprintf('%u', crc32(strtolower($row['user_email']))) . strlen($row['user_email']);
if ($user_email_hash != $row['user_email_hash'])
{
$sql_ary = array(
'user_email_hash' => $user_email_hash,
);
$sql = 'UPDATE ' . USERS_TABLE . '
SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
WHERE user_id = ' . (int) $row['user_id'];
_sql($sql, $errored, $error_ary, ($i % 100 == 0));
++$i;
}
}
$db->sql_freeresult($result);
$no_updates = false;
break;
// No changes from 3.0.7-RC2 to 3.0.7
case '3.0.7-RC2':
break;
// No changes from 3.0.7 to 3.0.7-PL1
case '3.0.7':
break;
// Changes from 3.0.7-PL1 to 3.0.8-RC1
case '3.0.7-PL1':
// Update file extension group names to use language strings.
$sql = 'SELECT lang_dir
FROM ' . LANG_TABLE;
$result = $db->sql_query($sql);
$extension_groups_updated = array();
while ($lang_dir = $db->sql_fetchfield('lang_dir'))
{
$lang_dir = basename($lang_dir);
// The language strings we need are either in language/.../acp/attachments.php
// in the update package if we're updating to 3.0.8-RC1 or later,
// or they are in language/.../install.php when we're updating from 3.0.7-PL1 or earlier.
// On an already updated board, they can also already be in language/.../acp/attachments.php
// in the board root.
$lang_files = array(
"{$phpbb_root_path}install/update/new/language/$lang_dir/acp/attachments.$phpEx",
"{$phpbb_root_path}language/$lang_dir/install.$phpEx",
"{$phpbb_root_path}language/$lang_dir/acp/attachments.$phpEx",
);
foreach ($lang_files as $lang_file)
{
if (!file_exists($lang_file))
{
continue;
}
$lang = array();
include($lang_file);
foreach($lang as $lang_key => $lang_val)
{
if (isset($extension_groups_updated[$lang_key]) || strpos($lang_key, 'EXT_GROUP_') !== 0)
{
continue;
}
$sql_ary = array(
'group_name' => substr($lang_key, 10), // Strip off 'EXT_GROUP_'
);
$sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . '
SET ' . $db->sql_build_array('UPDATE', $sql_ary) . "
WHERE group_name = '" . $db->sql_escape($lang_val) . "'";
_sql($sql, $errored, $error_ary);
$extension_groups_updated[$lang_key] = true;
}
}
}
$db->sql_freeresult($result);
// Install modules
$modules_to_install = array(
'post' => array(
'base' => 'board',
'class' => 'acp',
'title' => 'ACP_POST_SETTINGS',
'auth' => 'acl_a_board',
'cat' => 'ACP_MESSAGES',
'after' => array('message', 'ACP_MESSAGE_SETTINGS')
),
);
_add_modules($modules_to_install);
// update
$sql = 'UPDATE ' . MODULES_TABLE . '
SET module_auth = \'cfg_allow_avatar && (cfg_allow_avatar_local || cfg_allow_avatar_remote || cfg_allow_avatar_upload || cfg_allow_avatar_remote_upload)\'
WHERE module_class = \'ucp\'
AND module_basename = \'profile\'
AND module_mode = \'avatar\'';
_sql($sql, $errored, $error_ary);
// add Bing Bot
$bot_name = 'Bing [Bot]';
$bot_name_clean = utf8_clean_string($bot_name);
$sql = 'SELECT user_id
FROM ' . USERS_TABLE . "
WHERE username_clean = '" . $db->sql_escape($bot_name_clean) . "'";
$result = $db->sql_query($sql);
$bing_already_added = (bool) $db->sql_fetchfield('user_id');
$db->sql_freeresult($result);
if (!$bing_already_added)
{
$bot_agent = 'bingbot/';
$bot_ip = '';
$sql = 'SELECT group_id, group_colour
FROM ' . GROUPS_TABLE . "
WHERE group_name = 'BOTS'";
$result = $db->sql_query($sql);
$group_row = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
if (!$group_row)
{
// default fallback, should never get here
$group_row['group_id'] = 6;
$group_row['group_colour'] = '9E8DA7';
}
if (!function_exists('user_add'))
{
include($phpbb_root_path . 'includes/functions_user.' . $phpEx);
}
$user_row = array(
'user_type' => USER_IGNORE,
'group_id' => $group_row['group_id'],
'username' => $bot_name,
'user_regdate' => time(),
'user_password' => '',
'user_colour' => $group_row['group_colour'],
'user_email' => '',
'user_lang' => $config['default_lang'],
'user_style' => $config['default_style'],
'user_timezone' => 0,
'user_dateformat' => $config['default_dateformat'],
'user_allow_massemail' => 0,
);
$user_id = user_add($user_row);
$sql = 'INSERT INTO ' . BOTS_TABLE . ' ' . $db->sql_build_array('INSERT', array(
'bot_active' => 1,
'bot_name' => (string) $bot_name,
'user_id' => (int) $user_id,
'bot_agent' => (string) $bot_agent,
'bot_ip' => (string) $bot_ip,
));
_sql($sql, $errored, $error_ary);
}
// end Bing Bot addition
// Delete shadow topics pointing to not existing topics
$batch_size = 500;
// Set of affected forums we have to resync
$sync_forum_ids = array();
do
{
$sql_array = array(
'SELECT' => 't1.topic_id, t1.forum_id',
'FROM' => array(
TOPICS_TABLE => 't1',
),
'LEFT_JOIN' => array(
array(
'FROM' => array(TOPICS_TABLE => 't2'),
'ON' => 't1.topic_moved_id = t2.topic_id',
),
),
'WHERE' => 't1.topic_moved_id <> 0
AND t2.topic_id IS NULL',
);
$sql = $db->sql_build_query('SELECT', $sql_array);
$result = $db->sql_query_limit($sql, $batch_size);
$topic_ids = array();
while ($row = $db->sql_fetchrow($result))
{
$topic_ids[] = (int) $row['topic_id'];
$sync_forum_ids[(int) $row['forum_id']] = (int) $row['forum_id'];
}
$db->sql_freeresult($result);
if (!empty($topic_ids))
{
$sql = 'DELETE FROM ' . TOPICS_TABLE . '
WHERE ' . $db->sql_in_set('topic_id', $topic_ids);
$db->sql_query($sql);
}
}
while (sizeof($topic_ids) == $batch_size);
// Sync the forums we have deleted shadow topics from.
sync('forum', 'forum_id', $sync_forum_ids, true, true);
// Unread posts search load switch
set_config('load_unreads_search', '1');
// Reduce queue interval to 60 seconds, email package size to 20
if ($config['queue_interval'] == 600)
{
set_config('queue_interval', '60');
}
if ($config['email_package_size'] == 50)
{
set_config('email_package_size', '20');
}
$no_updates = false;
break;
// No changes from 3.0.8-RC1 to 3.0.8
case '3.0.8-RC1':
break;
// Changes from 3.0.8 to 3.0.9-RC1
case '3.0.8':
set_config('ip_login_limit_max', '50');
set_config('ip_login_limit_time', '21600');
set_config('ip_login_limit_use_forwarded', '0');
// Update file extension group names to use language strings, again.
$sql = 'SELECT group_id, group_name
FROM ' . EXTENSION_GROUPS_TABLE . '
WHERE group_name ' . $db->sql_like_expression('EXT_GROUP_' . $db->any_char);
$result = $db->sql_query($sql);
while ($row = $db->sql_fetchrow($result))
{
$sql_ary = array(
'group_name' => substr($row['group_name'], 10), // Strip off 'EXT_GROUP_'
);
$sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . '
SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
WHERE group_id = ' . $row['group_id'];
_sql($sql, $errored, $error_ary);
}
$db->sql_freeresult($result);
global $db_tools, $table_prefix;
// Recover from potentially broken Q&A CAPTCHA table on firebird
// Q&A CAPTCHA was uninstallable, so it's safe to remove these
// without data loss
if ($db_tools->sql_layer == 'firebird')
{
$tables = array(
$table_prefix . 'captcha_questions',
$table_prefix . 'captcha_answers',
$table_prefix . 'qa_confirm',
);
foreach ($tables as $table)
{
if ($db_tools->sql_table_exists($table))
{
$db_tools->sql_table_drop($table);
}
}
}
$no_updates = false;
break;
// No changes from 3.0.9-RC1 to 3.0.9-RC2
case '3.0.9-RC1':
break;
}
}
/**
* Database Tools for handling cross-db actions such as altering columns, etc.
* Currently not supported is returning SQL for creating tables.
*
* @package dbal
*/
class updater_db_tools
{
/**
* Current sql layer
*/
var $sql_layer = '';
/**
* @var object DB object
*/
var $db = NULL;
/**
* The Column types for every database we support
* @var array
*/
var $dbms_type_map = array(
'mysql_41' => array(
'INT:' => 'int(%d)',
'BINT' => 'bigint(20)',
'UINT' => 'mediumint(8) UNSIGNED',
'UINT:' => 'int(%d) UNSIGNED',
'TINT:' => 'tinyint(%d)',
'USINT' => 'smallint(4) UNSIGNED',
'BOOL' => 'tinyint(1) UNSIGNED',
'VCHAR' => 'varchar(255)',
'VCHAR:' => 'varchar(%d)',
'CHAR:' => 'char(%d)',
'XSTEXT' => 'text',
'XSTEXT_UNI'=> 'varchar(100)',
'STEXT' => 'text',
'STEXT_UNI' => 'varchar(255)',
'TEXT' => 'text',
'TEXT_UNI' => 'text',
'MTEXT' => 'mediumtext',
'MTEXT_UNI' => 'mediumtext',
'TIMESTAMP' => 'int(11) UNSIGNED',
'DECIMAL' => 'decimal(5,2)',
'DECIMAL:' => 'decimal(%d,2)',
'PDECIMAL' => 'decimal(6,3)',
'PDECIMAL:' => 'decimal(%d,3)',
'VCHAR_UNI' => 'varchar(255)',
'VCHAR_UNI:'=> 'varchar(%d)',
'VCHAR_CI' => 'varchar(255)',
'VARBINARY' => 'varbinary(255)',
),
'mysql_40' => array(
'INT:' => 'int(%d)',
'BINT' => 'bigint(20)',
'UINT' => 'mediumint(8) UNSIGNED',
'UINT:' => 'int(%d) UNSIGNED',
'TINT:' => 'tinyint(%d)',
'USINT' => 'smallint(4) UNSIGNED',
'BOOL' => 'tinyint(1) UNSIGNED',
'VCHAR' => 'varbinary(255)',
'VCHAR:' => 'varbinary(%d)',
'CHAR:' => 'binary(%d)',
'XSTEXT' => 'blob',
'XSTEXT_UNI'=> 'blob',
'STEXT' => 'blob',
'STEXT_UNI' => 'blob',
'TEXT' => 'blob',
'TEXT_UNI' => 'blob',
'MTEXT' => 'mediumblob',
'MTEXT_UNI' => 'mediumblob',
'TIMESTAMP' => 'int(11) UNSIGNED',
'DECIMAL' => 'decimal(5,2)',
'DECIMAL:' => 'decimal(%d,2)',
'PDECIMAL' => 'decimal(6,3)',
'PDECIMAL:' => 'decimal(%d,3)',
'VCHAR_UNI' => 'blob',
'VCHAR_UNI:'=> array('varbinary(%d)', 'limit' => array('mult', 3, 255, 'blob')),
'VCHAR_CI' => 'blob',
'VARBINARY' => 'varbinary(255)',
),
'firebird' => array(
'INT:' => 'INTEGER',
'BINT' => 'DOUBLE PRECISION',
'UINT' => 'INTEGER',
'UINT:' => 'INTEGER',
'TINT:' => 'INTEGER',
'USINT' => 'INTEGER',
'BOOL' => 'INTEGER',
'VCHAR' => 'VARCHAR(255) CHARACTER SET NONE',
'VCHAR:' => 'VARCHAR(%d) CHARACTER SET NONE',
'CHAR:' => 'CHAR(%d) CHARACTER SET NONE',
'XSTEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE',
'STEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE',
'TEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE',
'MTEXT' => 'BLOB SUB_TYPE TEXT CHARACTER SET NONE',
'XSTEXT_UNI'=> 'VARCHAR(100) CHARACTER SET UTF8',
'STEXT_UNI' => 'VARCHAR(255) CHARACTER SET UTF8',
'TEXT_UNI' => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8',
'MTEXT_UNI' => 'BLOB SUB_TYPE TEXT CHARACTER SET UTF8',
'TIMESTAMP' => 'INTEGER',
'DECIMAL' => 'DOUBLE PRECISION',
'DECIMAL:' => 'DOUBLE PRECISION',
'PDECIMAL' => 'DOUBLE PRECISION',
'PDECIMAL:' => 'DOUBLE PRECISION',
'VCHAR_UNI' => 'VARCHAR(255) CHARACTER SET UTF8',
'VCHAR_UNI:'=> 'VARCHAR(%d) CHARACTER SET UTF8',
'VCHAR_CI' => 'VARCHAR(255) CHARACTER SET UTF8',
'VARBINARY' => 'CHAR(255) CHARACTER SET NONE',
),
'mssql' => array(
'INT:' => '[int]',
'BINT' => '[float]',
'UINT' => '[int]',
'UINT:' => '[int]',
'TINT:' => '[int]',
'USINT' => '[int]',
'BOOL' => '[int]',
'VCHAR' => '[varchar] (255)',
'VCHAR:' => '[varchar] (%d)',
'CHAR:' => '[char] (%d)',
'XSTEXT' => '[varchar] (1000)',
'STEXT' => '[varchar] (3000)',
'TEXT' => '[varchar] (8000)',
'MTEXT' => '[text]',
'XSTEXT_UNI'=> '[varchar] (100)',
'STEXT_UNI' => '[varchar] (255)',
'TEXT_UNI' => '[varchar] (4000)',
'MTEXT_UNI' => '[text]',
'TIMESTAMP' => '[int]',
'DECIMAL' => '[float]',
'DECIMAL:' => '[float]',
'PDECIMAL' => '[float]',
'PDECIMAL:' => '[float]',
'VCHAR_UNI' => '[varchar] (255)',
'VCHAR_UNI:'=> '[varchar] (%d)',
'VCHAR_CI' => '[varchar] (255)',
'VARBINARY' => '[varchar] (255)',
),
'mssqlnative' => array(
'INT:' => '[int]',
'BINT' => '[float]',
'UINT' => '[int]',
'UINT:' => '[int]',
'TINT:' => '[int]',
'USINT' => '[int]',
'BOOL' => '[int]',
'VCHAR' => '[varchar] (255)',
'VCHAR:' => '[varchar] (%d)',
'CHAR:' => '[char] (%d)',
'XSTEXT' => '[varchar] (1000)',
'STEXT' => '[varchar] (3000)',
'TEXT' => '[varchar] (8000)',
'MTEXT' => '[text]',
'XSTEXT_UNI'=> '[varchar] (100)',
'STEXT_UNI' => '[varchar] (255)',
'TEXT_UNI' => '[varchar] (4000)',
'MTEXT_UNI' => '[text]',
'TIMESTAMP' => '[int]',
'DECIMAL' => '[float]',
'DECIMAL:' => '[float]',
'PDECIMAL' => '[float]',
'PDECIMAL:' => '[float]',
'VCHAR_UNI' => '[varchar] (255)',
'VCHAR_UNI:'=> '[varchar] (%d)',
'VCHAR_CI' => '[varchar] (255)',
'VARBINARY' => '[varchar] (255)',
),
'oracle' => array(
'INT:' => 'number(%d)',
'BINT' => 'number(20)',
'UINT' => 'number(8)',
'UINT:' => 'number(%d)',
'TINT:' => 'number(%d)',
'USINT' => 'number(4)',
'BOOL' => 'number(1)',
'VCHAR' => 'varchar2(255)',
'VCHAR:' => 'varchar2(%d)',
'CHAR:' => 'char(%d)',
'XSTEXT' => 'varchar2(1000)',
'STEXT' => 'varchar2(3000)',
'TEXT' => 'clob',
'MTEXT' => 'clob',
'XSTEXT_UNI'=> 'varchar2(300)',
'STEXT_UNI' => 'varchar2(765)',
'TEXT_UNI' => 'clob',
'MTEXT_UNI' => 'clob',
'TIMESTAMP' => 'number(11)',
'DECIMAL' => 'number(5, 2)',
'DECIMAL:' => 'number(%d, 2)',
'PDECIMAL' => 'number(6, 3)',
'PDECIMAL:' => 'number(%d, 3)',
'VCHAR_UNI' => 'varchar2(765)',
'VCHAR_UNI:'=> array('varchar2(%d)', 'limit' => array('mult', 3, 765, 'clob')),
'VCHAR_CI' => 'varchar2(255)',
'VARBINARY' => 'raw(255)',
),
'sqlite' => array(
'INT:' => 'int(%d)',
'BINT' => 'bigint(20)',
'UINT' => 'INTEGER UNSIGNED', //'mediumint(8) UNSIGNED',
'UINT:' => 'INTEGER UNSIGNED', // 'int(%d) UNSIGNED',
'TINT:' => 'tinyint(%d)',
'USINT' => 'INTEGER UNSIGNED', //'mediumint(4) UNSIGNED',
'BOOL' => 'INTEGER UNSIGNED', //'tinyint(1) UNSIGNED',
'VCHAR' => 'varchar(255)',
'VCHAR:' => 'varchar(%d)',
'CHAR:' => 'char(%d)',
'XSTEXT' => 'text(65535)',
'STEXT' => 'text(65535)',
'TEXT' => 'text(65535)',
'MTEXT' => 'mediumtext(16777215)',
'XSTEXT_UNI'=> 'text(65535)',
'STEXT_UNI' => 'text(65535)',
'TEXT_UNI' => 'text(65535)',
'MTEXT_UNI' => 'mediumtext(16777215)',
'TIMESTAMP' => 'INTEGER UNSIGNED', //'int(11) UNSIGNED',
'DECIMAL' => 'decimal(5,2)',
'DECIMAL:' => 'decimal(%d,2)',
'PDECIMAL' => 'decimal(6,3)',
'PDECIMAL:' => 'decimal(%d,3)',
'VCHAR_UNI' => 'varchar(255)',
'VCHAR_UNI:'=> 'varchar(%d)',
'VCHAR_CI' => 'varchar(255)',
'VARBINARY' => 'blob',
),
'postgres' => array(
'INT:' => 'INT4',
'BINT' => 'INT8',
'UINT' => 'INT4', // unsigned
'UINT:' => 'INT4', // unsigned
'USINT' => 'INT2', // unsigned
'BOOL' => 'INT2', // unsigned
'TINT:' => 'INT2',
'VCHAR' => 'varchar(255)',
'VCHAR:' => 'varchar(%d)',
'CHAR:' => 'char(%d)',
'XSTEXT' => 'varchar(1000)',
'STEXT' => 'varchar(3000)',
'TEXT' => 'varchar(8000)',
'MTEXT' => 'TEXT',
'XSTEXT_UNI'=> 'varchar(100)',
'STEXT_UNI' => 'varchar(255)',
'TEXT_UNI' => 'varchar(4000)',
'MTEXT_UNI' => 'TEXT',
'TIMESTAMP' => 'INT4', // unsigned
'DECIMAL' => 'decimal(5,2)',
'DECIMAL:' => 'decimal(%d,2)',
'PDECIMAL' => 'decimal(6,3)',
'PDECIMAL:' => 'decimal(%d,3)',
'VCHAR_UNI' => 'varchar(255)',
'VCHAR_UNI:'=> 'varchar(%d)',
'VCHAR_CI' => 'varchar_ci',
'VARBINARY' => 'bytea',
),
);
/**
* A list of types being unsigned for better reference in some db's
* @var array
*/
var $unsigned_types = array('UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP');
/**
* A list of supported DBMS. We change this class to support more DBMS, the DBMS itself only need to follow some rules.
* @var array
*/
var $supported_dbms = array('firebird', 'mssql', 'mssqlnative', 'mysql_40', 'mysql_41', 'oracle', 'postgres', 'sqlite');
/**
* This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array).
* This mode has no effect on some methods (inserting of data for example). This is expressed within the methods command.
*/
var $return_statements = false;
/**
* Constructor. Set DB Object and set {@link $return_statements return_statements}.
*
* @param phpbb_dbal $db DBAL object
* @param bool $return_statements True if only statements should be returned and no SQL being executed
*/
function updater_db_tools(&$db, $return_statements = false)
{
$this->db = $db;
$this->return_statements = $return_statements;
// Determine mapping database type
switch ($this->db->sql_layer)
{
case 'mysql':
$this->sql_layer = 'mysql_40';
break;
case 'mysql4':
if (version_compare($this->db->sql_server_info(true), '4.1.3', '>='))
{
$this->sql_layer = 'mysql_41';
}
else
{
$this->sql_layer = 'mysql_40';
}
break;
case 'mysqli':
$this->sql_layer = 'mysql_41';
break;
case 'mssql':
case 'mssql_odbc':
$this->sql_layer = 'mssql';
break;
case 'mssqlnative':
$this->sql_layer = 'mssqlnative';
break;
default:
$this->sql_layer = $this->db->sql_layer;
break;
}
}
/**
* Check if table exists
*
*
* @param string $table_name The table name to check for
* @return bool true if table exists, else false
*/
function sql_table_exists($table_name)
{
$this->db->sql_return_on_error(true);
$result = $this->db->sql_query_limit('SELECT * FROM ' . $table_name, 1);
$this->db->sql_return_on_error(false);
if ($result)
{
$this->db->sql_freeresult($result);
return true;
}
return false;
}
/**
* Create SQL Table
*
* @param string $table_name The table name to create
* @param array $table_data Array containing table data.
* @return array Statements if $return_statements is true.
*/
function sql_create_table($table_name, $table_data)
{
// holds the DDL for a column
$columns = $statements = array();
if ($this->sql_table_exists($table_name))
{
return $this->_sql_run_sql($statements);
}
// Begin transaction
$statements[] = 'begin';
// Determine if we have created a PRIMARY KEY in the earliest
$primary_key_gen = false;
// Determine if the table must be created with TEXTIMAGE
$create_textimage = false;
// Determine if the table requires a sequence
$create_sequence = false;
// Begin table sql statement
switch ($this->sql_layer)
{
case 'mssql':
case 'mssqlnative':
$table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n";
break;
default:
$table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n";
break;
}
// Iterate through the columns to create a table
foreach ($table_data['COLUMNS'] as $column_name => $column_data)
{
// here lies an array, filled with information compiled on the column's data
$prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
if (isset($prepared_column['auto_increment']) && strlen($column_name) > 26) // "${column_name}_gen"
{
trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR);
}
// here we add the definition of the new column to the list of columns
switch ($this->sql_layer)
{
case 'mssql':
case 'mssqlnative':
$columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default'];
break;
default:
$columns[] = "\t {$column_name} " . $prepared_column['column_type_sql'];
break;
}
// see if we have found a primary key set due to a column definition if we have found it, we can stop looking
if (!$primary_key_gen)
{
$primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set'];
}
// create textimage DDL based off of the existance of certain column types
if (!$create_textimage)
{
$create_textimage = isset($prepared_column['textimage']) && $prepared_column['textimage'];
}
// create sequence DDL based off of the existance of auto incrementing columns
if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'])
{
$create_sequence = $column_name;
}
}
// this makes up all the columns in the create table statement
$table_sql .= implode(",\n", $columns);
// Close the table for two DBMS and add to the statements
switch ($this->sql_layer)
{
case 'firebird':
$table_sql .= "\n);";
$statements[] = $table_sql;
break;
case 'mssql':
case 'mssqlnative':
$table_sql .= "\n) ON [PRIMARY]" . (($create_textimage) ? ' TEXTIMAGE_ON [PRIMARY]' : '');
$statements[] = $table_sql;
break;
}
// we have yet to create a primary key for this table,
// this means that we can add the one we really wanted instead
if (!$primary_key_gen)
{
// Write primary key
if (isset($table_data['PRIMARY_KEY']))
{
if (!is_array($table_data['PRIMARY_KEY']))
{
$table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']);
}
switch ($this->sql_layer)
{
case 'mysql_40':
case 'mysql_41':
case 'postgres':
case 'sqlite':
$table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';
break;
case 'firebird':
case 'mssql':
case 'mssqlnative':
// We need the data here
$old_return_statements = $this->return_statements;
$this->return_statements = true;
$primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']);
foreach ($primary_key_stmts as $pk_stmt)
{
$statements[] = $pk_stmt;
}
$this->return_statements = $old_return_statements;
break;
case 'oracle':
$table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')';
break;
}
}
}
// close the table
switch ($this->sql_layer)
{
case 'mysql_41':
// make sure the table is in UTF-8 mode
$table_sql .= "\n) CHARACTER SET `utf8` COLLATE `utf8_bin`;";
$statements[] = $table_sql;
break;
case 'mysql_40':
case 'sqlite':
$table_sql .= "\n);";
$statements[] = $table_sql;
break;
case 'postgres':
// do we need to add a sequence for auto incrementing columns?
if ($create_sequence)
{
$statements[] = "CREATE SEQUENCE {$table_name}_seq;";
}
$table_sql .= "\n);";
$statements[] = $table_sql;
break;
case 'oracle':
$table_sql .= "\n)";
$statements[] = $table_sql;
// do we need to add a sequence and a tigger for auto incrementing columns?
if ($create_sequence)
{
// create the actual sequence
$statements[] = "CREATE SEQUENCE {$table_name}_seq";
// the trigger is the mechanism by which we increment the counter
$trigger = "CREATE OR REPLACE TRIGGER t_{$table_name}\n";
$trigger .= "BEFORE INSERT ON {$table_name}\n";
$trigger .= "FOR EACH ROW WHEN (\n";
$trigger .= "\tnew.{$create_sequence} IS NULL OR new.{$create_sequence} = 0\n";
$trigger .= ")\n";
$trigger .= "BEGIN\n";
$trigger .= "\tSELECT {$table_name}_seq.nextval\n";
$trigger .= "\tINTO :new.{$create_sequence}\n";
$trigger .= "\tFROM dual;\n";
$trigger .= "END;";
$statements[] = $trigger;
}
break;
case 'firebird':
if ($create_sequence)
{
$statements[] = "CREATE GENERATOR {$table_name}_gen;";
$statements[] = "SET GENERATOR {$table_name}_gen TO 0;";
$trigger = "CREATE TRIGGER t_$table_name FOR $table_name\n";
$trigger .= "BEFORE INSERT\nAS\nBEGIN\n";
$trigger .= "\tNEW.{$create_sequence} = GEN_ID({$table_name}_gen, 1);\nEND;";
$statements[] = $trigger;
}
break;
}
// Write Keys
if (isset($table_data['KEYS']))
{
foreach ($table_data['KEYS'] as $key_name => $key_data)
{
if (!is_array($key_data[1]))
{
$key_data[1] = array($key_data[1]);
}
$old_return_statements = $this->return_statements;
$this->return_statements = true;
$key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]);
foreach ($key_stmts as $key_stmt)
{
$statements[] = $key_stmt;
}
$this->return_statements = $old_return_statements;
}
}
// Commit Transaction
$statements[] = 'commit';
return $this->_sql_run_sql($statements);
}
/**
* Handle passed database update array.
* Expected structure...
* Key being one of the following
* change_columns: Column changes (only type, not name)
* add_columns: Add columns to a table
* drop_keys: Dropping keys
* drop_columns: Removing/Dropping columns
* add_primary_keys: adding primary keys
* add_unique_index: adding an unique index
* add_index: adding an index (can be column:index_size if you need to provide size)
*
* The values are in this format:
* {TABLE NAME} => array(
* {COLUMN NAME} => array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}),
* {KEY/INDEX NAME} => array({COLUMN NAMES}),
* )
*
* For more information have a look at /develop/create_schema_files.php (only available through SVN)
*/
function perform_schema_changes($schema_changes)
{
if (empty($schema_changes))
{
return;
}
$statements = array();
$sqlite = false;
// For SQLite we need to perform the schema changes in a much more different way
if ($this->db->sql_layer == 'sqlite' && $this->return_statements)
{
$sqlite_data = array();
$sqlite = true;
}
// Add tables?
if (!empty($schema_changes['add_tables']))
{
foreach ($schema_changes['add_tables'] as $table => $table_data)
{
$result = $this->sql_create_table($table, $table_data);
if ($this->return_statements)
{
$statements = array_merge($statements, $result);
}
}
}
// Change columns?
if (!empty($schema_changes['change_columns']))
{
foreach ($schema_changes['change_columns'] as $table => $columns)
{
foreach ($columns as $column_name => $column_data)
{
// If the column exists we change it, else we add it ;)
if ($column_exists = $this->sql_column_exists($table, $column_name))
{
$result = $this->sql_column_change($table, $column_name, $column_data, true);
}
else
{
$result = $this->sql_column_add($table, $column_name, $column_data, true);
}
if ($sqlite)
{
if ($column_exists)
{
$sqlite_data[$table]['change_columns'][] = $result;
}
else
{
$sqlite_data[$table]['add_columns'][] = $result;
}
}
else if ($this->return_statements)
{
$statements = array_merge($statements, $result);
}
}
}
}
// Add columns?
if (!empty($schema_changes['add_columns']))
{
foreach ($schema_changes['add_columns'] as $table => $columns)
{
foreach ($columns as $column_name => $column_data)
{
// Only add the column if it does not exist yet
if ($column_exists = $this->sql_column_exists($table, $column_name))
{
continue;
// This is commented out here because it can take tremendous time on updates
// $result = $this->sql_column_change($table, $column_name, $column_data, true);
}
else
{
$result = $this->sql_column_add($table, $column_name, $column_data, true);
}
if ($sqlite)
{
if ($column_exists)
{
continue;
// $sqlite_data[$table]['change_columns'][] = $result;
}
else
{
$sqlite_data[$table]['add_columns'][] = $result;
}
}
else if ($this->return_statements)
{
$statements = array_merge($statements, $result);
}
}
}
}
// Remove keys?
if (!empty($schema_changes['drop_keys']))
{
foreach ($schema_changes['drop_keys'] as $table => $indexes)
{
foreach ($indexes as $index_name)
{
if (!$this->sql_index_exists($table, $index_name))
{
continue;
}
$result = $this->sql_index_drop($table, $index_name);
if ($this->return_statements)
{
$statements = array_merge($statements, $result);
}
}
}
}
// Drop columns?
if (!empty($schema_changes['drop_columns']))
{
foreach ($schema_changes['drop_columns'] as $table => $columns)
{
foreach ($columns as $column)
{
// Only remove the column if it exists...
if ($this->sql_column_exists($table, $column))
{
$result = $this->sql_column_remove($table, $column, true);
if ($sqlite)
{
$sqlite_data[$table]['drop_columns'][] = $result;
}
else if ($this->return_statements)
{
$statements = array_merge($statements, $result);
}
}
}
}
}
// Add primary keys?
if (!empty($schema_changes['add_primary_keys']))
{
foreach ($schema_changes['add_primary_keys'] as $table => $columns)
{
$result = $this->sql_create_primary_key($table, $columns, true);
if ($sqlite)
{
$sqlite_data[$table]['primary_key'] = $result;
}
else if ($this->return_statements)
{
$statements = array_merge($statements, $result);
}
}
}
// Add unqiue indexes?
if (!empty($schema_changes['add_unique_index']))
{
foreach ($schema_changes['add_unique_index'] as $table => $index_array)
{
foreach ($index_array as $index_name => $column)
{
if ($this->sql_unique_index_exists($table, $index_name))
{
continue;
}
$result = $this->sql_create_unique_index($table, $index_name, $column);
if ($this->return_statements)
{
$statements = array_merge($statements, $result);
}
}
}
}
// Add indexes?
if (!empty($schema_changes['add_index']))
{
foreach ($schema_changes['add_index'] as $table => $index_array)
{
foreach ($index_array as $index_name => $column)
{
if ($this->sql_index_exists($table, $index_name))
{
continue;
}
$result = $this->sql_create_index($table, $index_name, $column);
if ($this->return_statements)
{
$statements = array_merge($statements, $result);
}
}
}
}
if ($sqlite)
{
foreach ($sqlite_data as $table_name => $sql_schema_changes)
{
// Create temporary table with original data
$statements[] = 'begin';
$sql = "SELECT sql
FROM sqlite_master
WHERE type = 'table'
AND name = '{$table_name}'
ORDER BY type DESC, name;";
$result = $this->db->sql_query($sql);
if (!$result)
{
continue;
}
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
// Create a backup table and populate it, destroy the existing one
$statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
$statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
$statements[] = 'DROP TABLE ' . $table_name;
// Get the columns...
preg_match('#\((.*)\)#s', $row['sql'], $matches);
$plain_table_cols = trim($matches[1]);
$new_table_cols = preg_split('/,(?![\s\w]+\))/m', $plain_table_cols);
$column_list = array();
foreach ($new_table_cols as $declaration)
{
$entities = preg_split('#\s+#', trim($declaration));
if ($entities[0] == 'PRIMARY')
{
continue;
}
$column_list[] = $entities[0];
}
// note down the primary key notation because sqlite only supports adding it to the end for the new table
$primary_key = false;
$_new_cols = array();
foreach ($new_table_cols as $key => $declaration)
{
$entities = preg_split('#\s+#', trim($declaration));
if ($entities[0] == 'PRIMARY')
{
$primary_key = $declaration;
continue;
}
$_new_cols[] = $declaration;
}
$new_table_cols = $_new_cols;
// First of all... change columns
if (!empty($sql_schema_changes['change_columns']))
{
foreach ($sql_schema_changes['change_columns'] as $column_sql)
{
foreach ($new_table_cols as $key => $declaration)
{
$entities = preg_split('#\s+#', trim($declaration));
if (strpos($column_sql, $entities[0] . ' ') === 0)
{
$new_table_cols[$key] = $column_sql;
}
}
}
}
if (!empty($sql_schema_changes['add_columns']))
{
foreach ($sql_schema_changes['add_columns'] as $column_sql)
{
$new_table_cols[] = $column_sql;
}
}
// Now drop them...
if (!empty($sql_schema_changes['drop_columns']))
{
foreach ($sql_schema_changes['drop_columns'] as $column_name)
{
// Remove from column list...
$new_column_list = array();
foreach ($column_list as $key => $value)
{
if ($value === $column_name)
{
continue;
}
$new_column_list[] = $value;
}
$column_list = $new_column_list;
// Remove from table...
$_new_cols = array();
foreach ($new_table_cols as $key => $declaration)
{
$entities = preg_split('#\s+#', trim($declaration));
if (strpos($column_name . ' ', $entities[0] . ' ') === 0)
{
continue;
}
$_new_cols[] = $declaration;
}
$new_table_cols = $_new_cols;
}
}
// Primary key...
if (!empty($sql_schema_changes['primary_key']))
{
$new_table_cols[] = 'PRIMARY KEY (' . implode(', ', $sql_schema_changes['primary_key']) . ')';
}
// Add a new one or the old primary key
else if ($primary_key !== false)
{
$new_table_cols[] = $primary_key;
}
$columns = implode(',', $column_list);
// create a new table and fill it up. destroy the temp one
$statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $new_table_cols) . ');';
$statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
$statements[] = 'DROP TABLE ' . $table_name . '_temp';
$statements[] = 'commit';
}
}
if ($this->return_statements)
{
return $statements;
}
}
/**
* Check if a specified column exist
*
* @param string $table Table to check the column at
* @param string $column_name The column to check
*
* @return bool True if column exists, else false
*/
function sql_column_exists($table, $column_name)
{
switch ($this->sql_layer)
{
case 'mysql_40':
case 'mysql_41':
$sql = "SHOW COLUMNS FROM $table";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
// lower case just in case
if (strtolower($row['Field']) == $column_name)
{
$this->db->sql_freeresult($result);
return true;
}
}
$this->db->sql_freeresult($result);
return false;
break;
// PostgreSQL has a way of doing this in a much simpler way but would
// not allow us to support all versions of PostgreSQL
case 'postgres':
$sql = "SELECT a.attname
FROM pg_class c, pg_attribute a
WHERE c.relname = '{$table}'
AND a.attnum > 0
AND a.attrelid = c.oid";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
// lower case just in case
if (strtolower($row['attname']) == $column_name)
{
$this->db->sql_freeresult($result);
return true;
}
}
$this->db->sql_freeresult($result);
return false;
break;
// same deal with PostgreSQL, we must perform more complex operations than
// we technically could
case 'mssql':
case 'mssqlnative':
$sql = "SELECT c.name
FROM syscolumns c
LEFT JOIN sysobjects o ON c.id = o.id
WHERE o.name = '{$table}'";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
// lower case just in case
if (strtolower($row['name']) == $column_name)
{
$this->db->sql_freeresult($result);
return true;
}
}
$this->db->sql_freeresult($result);
return false;
break;
case 'oracle':
$sql = "SELECT column_name
FROM user_tab_columns
WHERE LOWER(table_name) = '" . strtolower($table) . "'";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
// lower case just in case
if (strtolower($row['column_name']) == $column_name)
{
$this->db->sql_freeresult($result);
return true;
}
}
$this->db->sql_freeresult($result);
return false;
break;
case 'firebird':
$sql = "SELECT RDB\$FIELD_NAME as FNAME
FROM RDB\$RELATION_FIELDS
WHERE RDB\$RELATION_NAME = '" . strtoupper($table) . "'";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
// lower case just in case
if (strtolower($row['fname']) == $column_name)
{
$this->db->sql_freeresult($result);
return true;
}
}
$this->db->sql_freeresult($result);
return false;
break;
// ugh, SQLite
case 'sqlite':
$sql = "SELECT sql
FROM sqlite_master
WHERE type = 'table'
AND name = '{$table}'";
$result = $this->db->sql_query($sql);
if (!$result)
{
return false;
}
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
preg_match('#\((.*)\)#s', $row['sql'], $matches);
$cols = trim($matches[1]);
$col_array = preg_split('/,(?![\s\w]+\))/m', $cols);
foreach ($col_array as $declaration)
{
$entities = preg_split('#\s+#', trim($declaration));
if ($entities[0] == 'PRIMARY')
{
continue;
}
if (strtolower($entities[0]) == $column_name)
{
return true;
}
}
return false;
break;
}
}
/**
* Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes.
*
* @param string $table_name Table to check the index at
* @param string $index_name The index name to check
*
* @return bool True if index exists, else false
*/
function sql_index_exists($table_name, $index_name)
{
if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative')
{
$sql = "EXEC sp_statistics '$table_name'";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
if ($row['TYPE'] == 3)
{
if (strtolower($row['INDEX_NAME']) == strtolower($index_name))
{
$this->db->sql_freeresult($result);
return true;
}
}
}
$this->db->sql_freeresult($result);
return false;
}
switch ($this->sql_layer)
{
case 'firebird':
$sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name
FROM RDB\$INDICES
WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "'
AND RDB\$UNIQUE_FLAG IS NULL
AND RDB\$FOREIGN_KEY IS NULL";
$col = 'index_name';
break;
case 'postgres':
$sql = "SELECT ic.relname as index_name
FROM pg_class bc, pg_class ic, pg_index i
WHERE (bc.oid = i.indrelid)
AND (ic.oid = i.indexrelid)
AND (bc.relname = '" . $table_name . "')
AND (i.indisunique != 't')
AND (i.indisprimary != 't')";
$col = 'index_name';
break;
case 'mysql_40':
case 'mysql_41':
$sql = 'SHOW KEYS
FROM ' . $table_name;
$col = 'Key_name';
break;
case 'oracle':
$sql = "SELECT index_name
FROM user_indexes
WHERE table_name = '" . strtoupper($table_name) . "'
AND generated = 'N'
AND uniqueness = 'NONUNIQUE'";
$col = 'index_name';
break;
case 'sqlite':
$sql = "PRAGMA index_list('" . $table_name . "');";
$col = 'name';
break;
}
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique'])
{
continue;
}
// These DBMS prefix index name with the table name
switch ($this->sql_layer)
{
case 'firebird':
case 'oracle':
case 'postgres':
case 'sqlite':
$row[$col] = substr($row[$col], strlen($table_name) + 1);
break;
}
if (strtolower($row[$col]) == strtolower($index_name))
{
$this->db->sql_freeresult($result);
return true;
}
}
$this->db->sql_freeresult($result);
return false;
}
/**
* Check if a specified UNIQUE index exists in table.
*
* @param string $table_name Table to check the index at
* @param string $index_name The index name to check
*
* @return bool True if index exists, else false
*/
function sql_unique_index_exists($table_name, $index_name)
{
if ($this->sql_layer == 'mssql' || $this->sql_layer == 'mssqlnative')
{
$sql = "EXEC sp_statistics '$table_name'";
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
// Usually NON_UNIQUE is the column we want to check, but we allow for both
if ($row['TYPE'] == 3)
{
if (strtolower($row['INDEX_NAME']) == strtolower($index_name))
{
$this->db->sql_freeresult($result);
return true;
}
}
}
$this->db->sql_freeresult($result);
return false;
}
switch ($this->sql_layer)
{
case 'firebird':
$sql = "SELECT LOWER(RDB\$INDEX_NAME) as index_name
FROM RDB\$INDICES
WHERE RDB\$RELATION_NAME = '" . strtoupper($table_name) . "'
AND RDB\$UNIQUE_FLAG IS NOT NULL
AND RDB\$FOREIGN_KEY IS NULL";
$col = 'index_name';
break;
case 'postgres':
$sql = "SELECT ic.relname as index_name, i.indisunique
FROM pg_class bc, pg_class ic, pg_index i
WHERE (bc.oid = i.indrelid)
AND (ic.oid = i.indexrelid)
AND (bc.relname = '" . $table_name . "')
AND (i.indisprimary != 't')";
$col = 'index_name';
break;
case 'mysql_40':
case 'mysql_41':
$sql = 'SHOW KEYS
FROM ' . $table_name;
$col = 'Key_name';
break;
case 'oracle':
$sql = "SELECT index_name, table_owner
FROM user_indexes
WHERE table_name = '" . strtoupper($table_name) . "'
AND generated = 'N'
AND uniqueness = 'UNIQUE'";
$col = 'index_name';
break;
case 'sqlite':
$sql = "PRAGMA index_list('" . $table_name . "');";
$col = 'name';
break;
}
$result = $this->db->sql_query($sql);
while ($row = $this->db->sql_fetchrow($result))
{
if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && ($row['Non_unique'] || $row[$col] == 'PRIMARY'))
{
continue;
}
if ($this->sql_layer == 'sqlite' && !$row['unique'])
{
continue;
}
if ($this->sql_layer == 'postgres' && $row['indisunique'] != 't')
{
continue;
}
// These DBMS prefix index name with the table name
switch ($this->sql_layer)
{
case 'oracle':
// Two cases here... prefixed with U_[table_owner] and not prefixed with table_name
if (strpos($row[$col], 'U_') === 0)
{
$row[$col] = substr($row[$col], strlen('U_' . $row['table_owner']) + 1);
}
else if (strpos($row[$col], strtoupper($table_name)) === 0)
{
$row[$col] = substr($row[$col], strlen($table_name) + 1);
}
break;
case 'firebird':
case 'postgres':
case 'sqlite':
$row[$col] = substr($row[$col], strlen($table_name) + 1);
break;
}
if (strtolower($row[$col]) == strtolower($index_name))
{
$this->db->sql_freeresult($result);
return true;
}
}
$this->db->sql_freeresult($result);
return false;
}
/**
* Private method for performing sql statements (either execute them or return them)
* @access private
*/
function _sql_run_sql($statements)
{
if ($this->return_statements)
{
return $statements;
}
// We could add error handling here...
foreach ($statements as $sql)
{
if ($sql === 'begin')
{
$this->db->sql_transaction('begin');
}
else if ($sql === 'commit')
{
$this->db->sql_transaction('commit');
}
else
{
$this->db->sql_query($sql);
}
}
return true;
}
/**
* Function to prepare some column information for better usage
* @access private
*/
function sql_prepare_column_data($table_name, $column_name, $column_data)
{
if (strlen($column_name) > 30)
{
trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR);
}
// Get type
if (strpos($column_data[0], ':') !== false)
{
list($orig_column_type, $column_length) = explode(':', $column_data[0]);
if (!is_array($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']))
{
$column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'], $column_length);
}
else
{
if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule']))
{
switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][0])
{
case 'div':
$column_length /= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][1];
$column_length = ceil($column_length);
$column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length);
break;
}
}
if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit']))
{
switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][0])
{
case 'mult':
$column_length *= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][1];
if ($column_length > $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][2])
{
$column_type = $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][3];
}
else
{
$column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length);
}
break;
}
}
}
$orig_column_type .= ':';
}
else
{
$orig_column_type = $column_data[0];
$column_type = $this->dbms_type_map[$this->sql_layer][$column_data[0]];
}
// Adjust default value if db-dependant specified
if (is_array($column_data[1]))
{
$column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default'];
}
$sql = '';
$return_array = array();
switch ($this->sql_layer)
{
case 'firebird':
$sql .= " {$column_type} ";
$return_array['column_type_sql_type'] = " {$column_type} ";
if (!is_null($column_data[1]))
{
$sql .= 'DEFAULT ' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' ';
$return_array['column_type_sql_default'] = ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ' ';
}
$sql .= 'NOT NULL';
// This is a UNICODE column and thus should be given it's fair share
if (preg_match('/^X?STEXT_UNI|VCHAR_(CI|UNI:?)/', $column_data[0]))
{
$sql .= ' COLLATE UNICODE';
}
$return_array['auto_increment'] = false;
if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
{
$return_array['auto_increment'] = true;
}
break;
case 'mssql':
case 'mssqlnative':
$sql .= " {$column_type} ";
$sql_default = " {$column_type} ";
// For adding columns we need the default definition
if (!is_null($column_data[1]))
{
// For hexadecimal values do not use single quotes
if (strpos($column_data[1], '0x') === 0)
{
$return_array['default'] = 'DEFAULT (' . $column_data[1] . ') ';
$sql_default .= $return_array['default'];
}
else
{
$return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') ';
$sql_default .= $return_array['default'];
}
}
if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
{
// $sql .= 'IDENTITY (1, 1) ';
$sql_default .= 'IDENTITY (1, 1) ';
}
$return_array['textimage'] = $column_type === '[text]';
$sql .= 'NOT NULL';
$sql_default .= 'NOT NULL';
$return_array['column_type_sql_default'] = $sql_default;
break;
case 'mysql_40':
case 'mysql_41':
$sql .= " {$column_type} ";
// For hexadecimal values do not use single quotes
if (!is_null($column_data[1]) && substr($column_type, -4) !== 'text' && substr($column_type, -4) !== 'blob')
{
$sql .= (strpos($column_data[1], '0x') === 0) ? "DEFAULT {$column_data[1]} " : "DEFAULT '{$column_data[1]}' ";
}
$sql .= 'NOT NULL';
if (isset($column_data[2]))
{
if ($column_data[2] == 'auto_increment')
{
$sql .= ' auto_increment';
}
else if ($this->sql_layer === 'mysql_41' && $column_data[2] == 'true_sort')
{
$sql .= ' COLLATE utf8_unicode_ci';
}
}
break;
case 'oracle':
$sql .= " {$column_type} ";
$sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}' " : '';
// In Oracle empty strings ('') are treated as NULL.
// Therefore in oracle we allow NULL's for all DEFAULT '' entries
// Oracle does not like setting NOT NULL on a column that is already NOT NULL (this happens only on number fields)
if (!preg_match('/number/i', $column_type))
{
$sql .= ($column_data[1] === '') ? '' : 'NOT NULL';
}
$return_array['auto_increment'] = false;
if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
{
$return_array['auto_increment'] = true;
}
break;
case 'postgres':
$return_array['column_type'] = $column_type;
$sql .= " {$column_type} ";
$return_array['auto_increment'] = false;
if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
{
$default_val = "nextval('{$table_name}_seq')";
$return_array['auto_increment'] = true;
}
else if (!is_null($column_data[1]))
{
$default_val = "'" . $column_data[1] . "'";
$return_array['null'] = 'NOT NULL';
$sql .= 'NOT NULL ';
}
$return_array['default'] = $default_val;
$sql .= "DEFAULT {$default_val}";
// Unsigned? Then add a CHECK contraint
if (in_array($orig_column_type, $this->unsigned_types))
{
$return_array['constraint'] = "CHECK ({$column_name} >= 0)";
$sql .= " CHECK ({$column_name} >= 0)";
}
break;
case 'sqlite':
$return_array['primary_key_set'] = false;
if (isset($column_data[2]) && $column_data[2] == 'auto_increment')
{
$sql .= ' INTEGER PRIMARY KEY';
$return_array['primary_key_set'] = true;
}
else
{
$sql .= ' ' . $column_type;
}
$sql .= ' NOT NULL ';
$sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}'" : '';
break;
}
$return_array['column_type_sql'] = $sql;
return $return_array;
}
/**
* Add new column
*/
function sql_column_add($table_name, $column_name, $column_data, $inline = false)
{
$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
$statements = array();
switch ($this->sql_layer)
{
case 'firebird':
// Does not support AFTER statement, only POSITION (and there you need the column position)
$statements[] = 'ALTER TABLE ' . $table_name . ' ADD "' . strtoupper($column_name) . '" ' . $column_data['column_type_sql'];
break;
case 'mssql':
case 'mssqlnative':
// Does not support AFTER, only through temporary table
$statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default'];
break;
case 'mysql_40':
case 'mysql_41':
$after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : '';
$statements[] = 'ALTER TABLE `' . $table_name . '` ADD COLUMN `' . $column_name . '` ' . $column_data['column_type_sql'] . $after;
break;
case 'oracle':
// Does not support AFTER, only through temporary table
$statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql'];
break;
case 'postgres':
// Does not support AFTER, only through temporary table
if (version_compare($this->db->sql_server_info(true), '8.0', '>='))
{
$statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql'];
}
else
{
// old versions cannot add columns with default and null information
$statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint'];
if (isset($column_data['null']))
{
if ($column_data['null'] == 'NOT NULL')
{
$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL';
}
}
if (isset($column_data['default']))
{
$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default'];
}
}
break;
case 'sqlite':
if ($inline && $this->return_statements)
{
return $column_name . ' ' . $column_data['column_type_sql'];
}
if (version_compare(sqlite_libversion(), '3.0') == -1)
{
$sql = "SELECT sql
FROM sqlite_master
WHERE type = 'table'
AND name = '{$table_name}'
ORDER BY type DESC, name;";
$result = $this->db->sql_query($sql);
if (!$result)
{
break;
}
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
$statements[] = 'begin';
// Create a backup table and populate it, destroy the existing one
$statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
$statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
$statements[] = 'DROP TABLE ' . $table_name;
preg_match('#\((.*)\)#s', $row['sql'], $matches);
$new_table_cols = trim($matches[1]);
$old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
$column_list = array();
foreach ($old_table_cols as $declaration)
{
$entities = preg_split('#\s+#', trim($declaration));
if ($entities[0] == 'PRIMARY')
{
continue;
}
$column_list[] = $entities[0];
}
$columns = implode(',', $column_list);
$new_table_cols = $column_name . ' ' . $column_data['column_type_sql'] . ',' . $new_table_cols;
// create a new table and fill it up. destroy the temp one
$statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');';
$statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
$statements[] = 'DROP TABLE ' . $table_name . '_temp';
$statements[] = 'commit';
}
else
{
$statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' [' . $column_data['column_type_sql'] . ']';
}
break;
}
return $this->_sql_run_sql($statements);
}
/**
* Drop column
*/
function sql_column_remove($table_name, $column_name, $inline = false)
{
$statements = array();
switch ($this->sql_layer)
{
case 'firebird':
$statements[] = 'ALTER TABLE ' . $table_name . ' DROP "' . strtoupper($column_name) . '"';
break;
case 'mssql':
case 'mssqlnative':
$statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']';
break;
case 'mysql_40':
case 'mysql_41':
$statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`';
break;
case 'oracle':
$statements[] = 'ALTER TABLE ' . $table_name . ' DROP ' . $column_name;
break;
case 'postgres':
$statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"';
break;
case 'sqlite':
if ($inline && $this->return_statements)
{
return $column_name;
}
if (version_compare(sqlite_libversion(), '3.0') == -1)
{
$sql = "SELECT sql
FROM sqlite_master
WHERE type = 'table'
AND name = '{$table_name}'
ORDER BY type DESC, name;";
$result = $this->db->sql_query($sql);
if (!$result)
{
break;
}
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
$statements[] = 'begin';
// Create a backup table and populate it, destroy the existing one
$statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
$statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
$statements[] = 'DROP TABLE ' . $table_name;
preg_match('#\((.*)\)#s', $row['sql'], $matches);
$new_table_cols = trim($matches[1]);
$old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
$column_list = array();
foreach ($old_table_cols as $declaration)
{
$entities = preg_split('#\s+#', trim($declaration));
if ($entities[0] == 'PRIMARY' || $entities[0] === $column_name)
{
continue;
}
$column_list[] = $entities[0];
}
$columns = implode(',', $column_list);
$new_table_cols = $new_table_cols = preg_replace('/' . $column_name . '[^,]+(?:,|$)/m', '', $new_table_cols);
// create a new table and fill it up. destroy the temp one
$statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');';
$statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
$statements[] = 'DROP TABLE ' . $table_name . '_temp';
$statements[] = 'commit';
}
else
{
$statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name;
}
break;
}
return $this->_sql_run_sql($statements);
}
/**
* Drop Index
*/
function sql_index_drop($table_name, $index_name)
{
$statements = array();
switch ($this->sql_layer)
{
case 'mssql':
case 'mssqlnative':
$statements[] = 'DROP INDEX ' . $table_name . '.' . $index_name;
break;
case 'mysql_40':
case 'mysql_41':
$statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name;
break;
case 'firebird':
case 'oracle':
case 'postgres':
case 'sqlite':
$statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name;
break;
}
return $this->_sql_run_sql($statements);
}
/**
* Add primary key
*/
function sql_create_primary_key($table_name, $column, $inline = false)
{
$statements = array();
switch ($this->sql_layer)
{
case 'firebird':
case 'postgres':
case 'mysql_40':
case 'mysql_41':
$statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')';
break;
case 'mssql':
case 'mssqlnative':
$sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD ";
$sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED (";
$sql .= '[' . implode("],\n\t\t[", $column) . ']';
$sql .= ') ON [PRIMARY]';
$statements[] = $sql;
break;
case 'oracle':
$statements[] = 'ALTER TABLE ' . $table_name . 'add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')';
break;
case 'sqlite':
if ($inline && $this->return_statements)
{
return $column;
}
$sql = "SELECT sql
FROM sqlite_master
WHERE type = 'table'
AND name = '{$table_name}'
ORDER BY type DESC, name;";
$result = $this->db->sql_query($sql);
if (!$result)
{
break;
}
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
$statements[] = 'begin';
// Create a backup table and populate it, destroy the existing one
$statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
$statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
$statements[] = 'DROP TABLE ' . $table_name;
preg_match('#\((.*)\)#s', $row['sql'], $matches);
$new_table_cols = trim($matches[1]);
$old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
$column_list = array();
foreach ($old_table_cols as $declaration)
{
$entities = preg_split('#\s+#', trim($declaration));
if ($entities[0] == 'PRIMARY')
{
continue;
}
$column_list[] = $entities[0];
}
$columns = implode(',', $column_list);
// create a new table and fill it up. destroy the temp one
$statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ', PRIMARY KEY (' . implode(', ', $column) . '));';
$statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
$statements[] = 'DROP TABLE ' . $table_name . '_temp';
$statements[] = 'commit';
break;
}
return $this->_sql_run_sql($statements);
}
/**
* Add unique index
*/
function sql_create_unique_index($table_name, $index_name, $column)
{
$statements = array();
$table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config)
if (strlen($table_name . $index_name) - strlen($table_prefix) > 24)
{
$max_length = $table_prefix + 24;
trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR);
}
switch ($this->sql_layer)
{
case 'firebird':
case 'postgres':
case 'oracle':
case 'sqlite':
$statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')';
break;
case 'mysql_40':
case 'mysql_41':
$statements[] = 'CREATE UNIQUE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')';
break;
case 'mssql':
case 'mssqlnative':
$statements[] = 'CREATE UNIQUE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]';
break;
}
return $this->_sql_run_sql($statements);
}
/**
* Add index
*/
function sql_create_index($table_name, $index_name, $column)
{
$statements = array();
$table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config)
if (strlen($table_name . $index_name) - strlen($table_prefix) > 24)
{
$max_length = $table_prefix + 24;
trigger_error("Index name '{$table_name}_$index_name' on table '$table_name' is too long. The maximum is $max_length characters.", E_USER_ERROR);
}
// remove index length unless MySQL4
if ('mysql_40' != $this->sql_layer)
{
$column = preg_replace('#:.*$#', '', $column);
}
switch ($this->sql_layer)
{
case 'firebird':
case 'postgres':
case 'oracle':
case 'sqlite':
$statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')';
break;
case 'mysql_40':
// add index size to definition as required by MySQL4
foreach ($column as $i => $col)
{
if (false !== strpos($col, ':'))
{
list($col, $index_size) = explode(':', $col);
$column[$i] = "$col($index_size)";
}
}
// no break
case 'mysql_41':
$statements[] = 'CREATE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')';
break;
case 'mssql':
case 'mssqlnative':
$statements[] = 'CREATE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ') ON [PRIMARY]';
break;
}
return $this->_sql_run_sql($statements);
}
/**
* Change column type (not name!)
*/
function sql_column_change($table_name, $column_name, $column_data, $inline = false)
{
$column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data);
$statements = array();
switch ($this->sql_layer)
{
case 'firebird':
// Change type...
if (!empty($column_data['column_type_sql_default']))
{
$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" TYPE ' . ' ' . $column_data['column_type_sql_type'];
$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" SET DEFAULT ' . ' ' . $column_data['column_type_sql_default'];
}
else
{
// TODO: try to change pkey without removing trigger, generator or constraints. ATM this query may fail.
$statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN "' . strtoupper($column_name) . '" TYPE ' . ' ' . $column_data['column_type_sql_type'];
}
break;
case 'mssql':
case 'mssqlnative':
$statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql'];
if (!empty($column_data['default']))
{
// Using TRANSACT-SQL for this statement because we do not want to have colliding data if statements are executed at a later stage
$statements[] = "DECLARE @drop_default_name VARCHAR(100), @cmd VARCHAR(1000)
SET @drop_default_name =
(SELECT so.name FROM sysobjects so
JOIN sysconstraints sc ON so.id = sc.constid
WHERE object_name(so.parent_obj) = '{$table_name}'
AND so.xtype = 'D'
AND sc.colid = (SELECT colid FROM syscolumns
WHERE id = object_id('{$table_name}')
AND name = '{$column_name}'))
IF @drop_default_name <> ''
BEGIN
SET @cmd = 'ALTER TABLE [{$table_name}] DROP CONSTRAINT [' + @drop_default_name + ']'
EXEC(@cmd)
END
SET @cmd = 'ALTER TABLE [{$table_name}] ADD CONSTRAINT [DF_{$table_name}_{$column_name}_1] {$column_data['default']} FOR [{$column_name}]'
EXEC(@cmd)";
}
break;
case 'mysql_40':
case 'mysql_41':
$statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql'];
break;
case 'oracle':
$statements[] = 'ALTER TABLE ' . $table_name . ' MODIFY ' . $column_name . ' ' . $column_data['column_type_sql'];
break;
case 'postgres':
$sql = 'ALTER TABLE ' . $table_name . ' ';
$sql_array = array();
$sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type'];
if (isset($column_data['null']))
{
if ($column_data['null'] == 'NOT NULL')
{
$sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL';
}
else if ($column_data['null'] == 'NULL')
{
$sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL';
}
}
if (isset($column_data['default']))
{
$sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default'];
}
// we don't want to double up on constraints if we change different number data types
if (isset($column_data['constraint']))
{
$constraint_sql = "SELECT consrc as constraint_data
FROM pg_constraint, pg_class bc
WHERE conrelid = bc.oid
AND bc.relname = '{$table_name}'
AND NOT EXISTS (
SELECT *
FROM pg_constraint as c, pg_inherits as i
WHERE i.inhrelid = pg_constraint.conrelid
AND c.conname = pg_constraint.conname
AND c.consrc = pg_constraint.consrc
AND c.conrelid = i.inhparent
)";
$constraint_exists = false;
$result = $this->db->sql_query($constraint_sql);
while ($row = $this->db->sql_fetchrow($result))
{
if (trim($row['constraint_data']) == trim($column_data['constraint']))
{
$constraint_exists = true;
break;
}
}
$this->db->sql_freeresult($result);
if (!$constraint_exists)
{
$sql_array[] = 'ADD ' . $column_data['constraint'];
}
}
$sql .= implode(', ', $sql_array);
$statements[] = $sql;
break;
case 'sqlite':
if ($inline && $this->return_statements)
{
return $column_name . ' ' . $column_data['column_type_sql'];
}
$sql = "SELECT sql
FROM sqlite_master
WHERE type = 'table'
AND name = '{$table_name}'
ORDER BY type DESC, name;";
$result = $this->db->sql_query($sql);
if (!$result)
{
break;
}
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
$statements[] = 'begin';
// Create a temp table and populate it, destroy the existing one
$statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']);
$statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name;
$statements[] = 'DROP TABLE ' . $table_name;
preg_match('#\((.*)\)#s', $row['sql'], $matches);
$new_table_cols = trim($matches[1]);
$old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols);
$column_list = array();
foreach ($old_table_cols as $key => $declaration)
{
$entities = preg_split('#\s+#', trim($declaration));
$column_list[] = $entities[0];
if ($entities[0] == $column_name)
{
$old_table_cols[$key] = $column_name . ' ' . $column_data['column_type_sql'];
}
}
$columns = implode(',', $column_list);
// create a new table and fill it up. destroy the temp one
$statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $old_table_cols) . ');';
$statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;';
$statements[] = 'DROP TABLE ' . $table_name . '_temp';
$statements[] = 'commit';
break;
}
return $this->_sql_run_sql($statements);
}
}
?>