t('Table prefixing'), 'path' => 'admin/build/domain/prefix', 'access' => user_access('administer table prefixing'), 'type' => MENU_LOCAL_TASK, 'callback' => 'drupal_get_form', 'callback arguments' => array('domain_prefix_configure_form') ); } else { $items[] = array( 'title' => t('Domain prefix settings'), 'path' => 'admin/build/domain/prefix/'. arg(4), 'access' => user_access('administer table prefixing'), 'type' => MENU_CALLBACK, 'callback' => 'drupal_get_form', 'callback arguments' => array('domain_prefix_form', arg(4)) ); $items[] = array( 'title' => t('Domain prefix update'), 'path' => 'domain_prefix_update', 'access' => user_access('administer table prefixing'), 'type' => MENU_CALLBACK, 'callback' => 'domain_prefix_update', ); } return $items; } /** * Implement hook_perm */ function domain_prefix_perm() { return array('administer table prefixing'); } /** * Check for existing table prefixing. */ function domain_prefix_get_prefix() { global $db_prefix; // Check for existing table prefixing. $prefix = NULL; if (!empty($db_prefix)) { if (is_array($db_prefix)) { $prefix = $db_prefix['default']; } else { $prefix = $db_prefix; } } return $prefix; } /** * Get the tables with the active prefix * * @param $prefix * The table prefix used with this domain. Optional. */ function domain_prefix_get_tables($prefix = NULL) { // Check for default prefix settings. if (empty($prefix)) { $prefix = domain_prefix_get_prefix(); } switch ($GLOBALS['db_type']) { case 'pgsql': if (empty($prefix)) { $result = db_query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"); } else { $result = db_query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name LIKE '{$prefix}%%'"); } break; default: // MySQL and MySQLi implementation. if (empty($prefix)) { $result = db_query("SHOW TABLES"); } else { $result = db_query("SHOW TABLES LIKE '{$prefix}%%'"); } break; } $tables = array(); $disallow = domain_prefix_disallow(); while ($data = db_fetch_array($result)) { // Chop table prefixes. $str = current($data); if (!empty($prefix)) { $str = preg_replace('/'. $prefix .'/', '', $str, 1); } if (!in_array($str, $disallow) && substr($str, 0, 7) != 'domain_') { $tables[$str]['tablename'] = $str; $tables[$str]['module'] = domain_prefix_module_lookup($str); } } // Sort them by module uasort($tables, '_domain_prefix_sort'); return $tables; } /** * Helper sort function */ function _domain_prefix_sort($a, $b) { $_a = str_replace('_', '', $a['tablename']); $_b = str_replace('_', '', $b['tablename']); if ($a['module'] == $b['module']) { return strcmp($_a, $_b); } return strcmp($a['module'], $b['module']); } /** * FormsAPI for generating the configuration form */ function domain_prefix_configure_form() { // We must be on the root domain! $default = domain_default(); domain_goto($default); // Get the tables for the root installation. $tables = domain_prefix_get_tables($prefix); // Remove the disallowed tables. $disallow = domain_prefix_disallow(); // Get the current settings. $info = unserialize(variable_get('domain_prefix', NULL)); $settings = $info['settings']; $source_defaults = $info['sources']; $form = array(); $form['domain'] = array( '#type' => 'fieldset', '#title' => t('Domain creation rules'), '#collapsible' => TRUE, '#collapsed' => FALSE ); $form['domain']['domain_prefix_options'] = array( '#type' => 'radios', '#title' => t('Domain creation options'), '#description' => t('Determines what actions to take when creating new domain records.'), '#options' => array(1 => t('Generate tables as defined below'), 0 => t('Do not generate any tables')), '#default_value' => variable_get('domain_prefix_options', 1), '#required' => TRUE ); $last = ''; // Flag for module grouping. // Get the source table data. $root = domain_default(); foreach ($tables as $table => $info) { if (!in_array($table, $disallow) && substr($table, 0, 7) != 'domain_') { if (empty($settings[$table])) { $settings[$table] = DOMAIN_PREFIX_IGNORE; $source_defaults['_source_'. $table] = 0; } $module = $info['module']; if ($last != $module) { $last = $module; } else { $module = ''; } $options = array(); $options[DOMAIN_PREFIX_IGNORE] = t('ignore'); $options[DOMAIN_PREFIX_CREATE] = t('create'); $options[DOMAIN_PREFIX_COPY] = t('copy'); $form['domain_prefix'][$table] = array( '#type' => 'radios', '#title' => $table, '#options' => $options, '#default_value' => $settings[$table], '#description' => $module ); // Get the table copying options for this entry. // Can't pass a zero through FormAPI select. $sources = array(); $sources[0] = $root['sitename']; // Check to see if other table prefixes have been created. $result = db_query("SELECT dp.domain_id, d.sitename FROM {domain_prefix} dp INNER JOIN {domain} d ON dp.domain_id = d.domain_id WHERE dp.tablename = '%s' AND dp.status > 1", $table); while ($data = db_fetch_array($result)) { $sources[$data['domain_id']] = $data['sitename']; } $form['domain_source']['_source_'. $table] = array( '#type' => 'select', '#title' => '', '#options' => $sources, '#default_value' => $source_defaults['_source_'. $table], ); } } $form['submit'] = array( '#type' => 'submit', '#value' => t('Save prefix settings'), ); $form['restore'] = array( '#type' => 'submit', '#value' => t('Restore defaults'), ); return $form; } /** * Implement hook_domainlinks() * * @param $domain * The currently active $domain array, provided by domain_lookup(). */ function domain_prefix_domainlinks($domain) { if (user_access('administer table prefixing')) { $links[] = array( 'title' => t('tables'), 'path' => 'admin/build/domain/prefix/'. $domain['domain_id'] ); return $links; } } /** * FormsAPI for the domain_prefix_configure_form. */ function domain_prefix_configure_form_submit($form_id, $form_values) { // Throw away what we don't need. $options = $form_values['domain_prefix_options']; $unset = array('op', 'submit', 'restore', 'form_token', 'form_id', 'domain_prefix_options'); $data = $form_values; foreach ($unset as $key) { unset($data[$key]); } if ($form_values['op'] == $form_values['restore']) { variable_del('domain_prefix'); drupal_set_message(t('Default prefix settings reset.')); } else { // Process the source data. foreach ($data as $key => $value) { if (substr($key, 0, 8) == '_source_') { $info['sources'][$key] = $value; } else { $info['settings'][$key] = $value; } } $settings = serialize($info); variable_set('domain_prefix', $settings); drupal_set_message(t('Default prefix settings changed.')); } variable_set('domain_prefix_options', $options); } /** * FormsAPI theming for domain_prefix_configure_form. */ function theme_domain_prefix_configure_form($form) { $output = t('
These settings control advanced functions. Please read the documentation carefully.
'); $header = array(t('Table'), t('Source'), t('Ignore'), t('Create'), t('Copy')); if ($form['prefix_theme']['#value'] > 0) { $header[] = t('Update'); $header[] = t('Drop'); unset($form['prefix_theme']); } $output = drupal_render($form['domain']); foreach (element_children($form['domain_prefix']) as $key) { if ($form['domain_prefix'][$key]['#description']) { $rows[] = array(''. $form['domain_prefix'][$key]['#description'] .'', array('', 'colspan' => 4)); } $row = array(); $row[] = ' - '. $form['domain_prefix'][$key]['#title']; // Insert the source selection element. $row[] = drupal_render($form['domain_source']['_source_'. $key]); foreach (element_children($form['domain_prefix'][$key]) as $option) { $row[] = drupal_render($form['domain_prefix'][$key][$option]); } // Throw the rest away, since we already rendered what we want from it. $render = drupal_render($form['domain_prefix'][$key]); $rows[] = $row; } $output .= theme('table', $header, $rows); $output .= drupal_render($form); return $output; } /** * The table prefixing page for a domain. * * @param $domain_id * The domain_id taken from {domain}. * @param $arguments * An array of additional hidden key/value pairs to pass to the form. * Used by child modules to control behaviors. */ function domain_prefix_form($domain_id, $arguments = array()) { // We must be on the root domain! $default = domain_default(); domain_goto($default); $domain = domain_lookup($domain_id); drupal_set_title(t('Table prefixing for @domain', array('@domain' => $domain['sitename']))); // Remove the disallowed tables. $disallow = domain_prefix_disallow(); // Get the default table set and settings. $default = domain_prefix_get_tables(); // Get the current settings. $info = unserialize(variable_get('domain_prefix', NULL)); $settings = $info['settings']; $source_defaults = $info['sources']; // Check the defaults against those saved for this domain. $result = db_query("SELECT tablename, source FROM {domain_prefix} WHERE domain_id = %d", $domain['domain_id']); while ($sourcedata = db_fetch_array($result)) { $source_defaults['_source_'. $sourcedata['tablename']] = $sourcedata['source']; } // Get the root source table data. $root = domain_default(); if (empty($settings) && !$_POST) { drupal_set_message(t('There are no default settings configured.')); } // Get the stored table data for this domain. $tables = domain_prefix_lookup($domain_id); $submit = t('Update domain tables'); if (empty($tables)) { if (!$_POST && !$form_values['execute'] && !$arguments['user_submitted']) { drupal_set_message(t('The table creation sequence has not run for this domain.')); } $submit = t('Generate domain tables'); $table_options = $default; } else { $table_options = array(); $settings = array(); foreach ($tables as $name => $table) { if (array_key_exists($name, $default)) { $table_options[$table['tablename']] = $table; $settings[$table['tablename']] = $table['status']; } } } // Sort them by module uasort($table_options, '_domain_prefix_sort'); // All tables are prefixed as 'domain_#_' $prefix = domain_prefix_string($domain_id); // Generate the form. $form = array(); // The $arguments arrray allows other modules to pass values to change the bahavior // of submit and validate functions. if (!empty($arguments)) { $form['domain_arguments'] = array('#type' => 'value', '#value' => $arguments); } $delete_flag = 0; // Flag for the theme function delete column $last = ''; // Flag for module groupings. foreach ($table_options as $table => $info) { if (!in_array($table, $disallow)) { if (empty($settings[$table])) { $settings[$table] = DOMAIN_PREFIX_IGNORE; } $options = array(); $options[DOMAIN_PREFIX_IGNORE] = t('ignore'); $options[DOMAIN_PREFIX_CREATE] = t('create'); $options[DOMAIN_PREFIX_COPY] = t('copy'); if ($settings[$table] > 0) { $exists = domain_prefix_table_exists($prefix, $table); if ($exists > 0) { $options[DOMAIN_PREFIX_UPDATE] = t('update'); $options[DOMAIN_PREFIX_DROP] = t('drop'); $delete_flag++; } } $module = $info['module']; if ($last != $module) { $last = $module; } else { $module = ''; } $form['domain_prefix'][$table] = array( '#type' => 'radios', '#title' => $table, '#options' => $options, '#default_value' => $settings[$table], '#description' => $module ); // Get the table copying options for this entry. // Can't pass a zero through FormAPI select. $sources = array(); $sources[0] = $root['sitename']; // Check to see if other table prefixes have been created. $result = db_query("SELECT dp.domain_id, d.sitename FROM {domain_prefix} dp INNER JOIN {domain} d ON dp.domain_id = d.domain_id WHERE dp.tablename = '%s' AND dp.status > 1", $table); while ($data = db_fetch_array($result)) { // Cannot copy onto itself. if ($data['domain_id'] != $domain['domain_id']) { $sources[$data['domain_id']] = $data['sitename']; } } $form['domain_source']['_source_'. $table] = array( '#type' => 'select', '#title' => '', '#options' => $sources, '#default_value' => $source_defaults['_source_'. $table], ); } } $form['#theme'] = 'domain_prefix_configure_form'; $form['prefix_theme'] = array('#type' => 'value', '#value' => $delete_flag); $form['domain_id'] = array('#type' => 'value', '#value' => $domain_id); $form['submit'] = array( '#type' => 'submit', '#value' => $submit ); return $form; } /** * Does a table exist -- we use this to bypass Drupal's default table prefixing check. * * @param $prefix * The table prefix used with this domain. Optional. * @param $table * The name of the table being acted upon. */ function domain_prefix_table_exists($prefix, $table) { global $db_type; $string = db_escape_table($prefix . $table); switch ($db_type) { case 'pgsql': $query = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '{%s}'"; break; default: $query = "SHOW TABLES LIKE '{%s}'"; break; } return db_num_rows(db_query($query, $string)); } /** * FormsAPI for domain_prefix_form. */ function domain_prefix_form_submit($form_id, $form_values) { // Flag messages for the administrative user only. $msg = TRUE; $create = TRUE; if ($form_values['domain_arguments']['user_submitted']) { $msg = FALSE; // Should we create tables for user domains? $create = variable_get('domain_user_prefixing', 0); } if ($create) { // Throw away what we don't need. $prefix = domain_prefix_string($form_values['domain_id']); $unset = array('prefix_theme', 'domain_id', 'op', 'submit', 'restore', 'form_token', 'form_id', 'execute'); $data = $form_values; foreach ($unset as $key) { unset($data[$key]); } // Delete existing records, but get the existing values first. $current = domain_prefix_lookup($form_values['domain_id']); db_query("DELETE FROM {domain_prefix} WHERE domain_id = %d", $form_values['domain_id']); // Get the database type. $db_type = $GLOBALS['db_type']; foreach ($data as $key => $value) { // Do not process tables for the source elements. // But be sure to set the proper source table prefix for copying data. if (substr($key, 0, 8) != '_source_') { $source = $data['_source_'. $key]; if ($source > 0) { $source_prefix = domain_prefix_string($source); } else { $source_prefix = ''; } $update = FALSE; if (empty($value)) { $value = DOMAIN_PREFIX_IGNORE; $update = TRUE; } $newtable = db_escape_table($prefix . $key); $module = domain_prefix_module_lookup($key); $exists = domain_prefix_table_exists($prefix, $key); $oldtable = db_escape_table($key); $sourcetable = db_escape_table($source_prefix . $key); if ($value == DOMAIN_PREFIX_CREATE) { if (!$exists) { if ($db_type == 'pgsql') { db_query("CREATE TABLE {%s} AS SELECT * FROM {%s}", $newtable, $sourcetable); db_query("TRUNCATE TABLE {%s}", $newtable); } else { db_query("CREATE TABLE {%s} LIKE {%s}", $newtable, $sourcetable); } if ($msg) { drupal_set_message(t('!string table created.', array('!string' => $newtable))); } $update = TRUE; } else if ($current[$oldtable]['status'] == DOMAIN_PREFIX_COPY) { drupal_set_message(t('!string table cannot be created, since it already exists.', array('!string' => $newtable))); } } else if ($value == DOMAIN_PREFIX_COPY) { if (!$exists) { if ($db_type == 'pgsql') { db_query("CREATE TABLE {%s} AS SELECT * FROM {%s}", $newtable, $sourcetable); } else { db_query("CREATE TABLE {%s} LIKE {%s}", $newtable, $sourcetable); db_query("INSERT INTO {%s} SELECT * FROM {%s}", $newtable, $sourcetable); } // Update {sequences} table. domain_prefix_update_sequences('update', $newtable, $sourcetable); if ($msg) { drupal_set_message(t('!string table copied.', array('!string' => $newtable))); } $update = TRUE; } else if ($current[$oldtable]['status'] == DOMAIN_PREFIX_CREATE) { drupal_set_message(t('!string table cannot be copied, since it already exists.', array('!string' => $newtable))); } } else if ($value == DOMAIN_PREFIX_UPDATE) { if ($exists > 0) { db_query("TRUNCATE TABLE {%s}", $newtable); db_query("INSERT INTO {%s} SELECT * FROM {%s}", $newtable, $sourcetable); // Update {sequences} table. domain_prefix_update_sequences('update', $newtable, $sourcetable); if ($msg) { drupal_set_message(t('!string table updated from source.', array('!string' => $newtable))); } $update = TRUE; // Set the stored value to "copy" for record keeping. $value = DOMAIN_PREFIX_COPY; } } else if ($value == DOMAIN_PREFIX_DROP) { if ($exists > 0) { db_query("DROP TABLE {%s}", $newtable); $value = DOMAIN_PREFIX_IGNORE; // Update {sequences} table. domain_prefix_update_sequences('drop', $newtable, $sourcetable); if ($msg) { drupal_set_message(t('!string table dropped.', array('!string' => $newtable))); } $update = TRUE; } else { drupal_set_message(t('!string table does not exist.', array('!string' => $newtable))); } } // Update our records. if (!$update && $value != 1) { $value = $current[$oldtable]['status']; } db_query("INSERT INTO {domain_prefix} (domain_id, status, tablename, module, source) VALUES (%d, %d, '%s', '%s', %d)", $form_values['domain_id'], $value, $key, $module, $form_values['_source_'. $key]); } } } // Clear the cache. cache_clear_all(); } /** * Correct the {sequences} table if doing an update. * * @param $op * The operation to perform, either 'update' or 'drop.' * @param $newtable * The name of the table being updated. * @param $sourcetable * The name of the table providing the tempalte for the update */ function domain_prefix_update_sequences($op, $newtable, $sourcetable) { $result = db_query("SELECT name, id FROM {sequences} WHERE name LIKE '{%s%}'", $sourcetable); $dbprefix = domain_prefix_get_prefix(); $source = explode('_', $dbprefix . $sourcetable); while ($variable = db_fetch_array($result)) { // We have to match the variable source name, to prevent duplicates. $var = explode('_', $variable['name']); array_pop($var); // Toss out the last element. $diff = array_diff($var, $source); if (empty($diff)) { $newvariable = $newtable . substr($variable['name'], strlen($dbprefix . $sourcetable)); $target = db_result(db_query("SELECT id FROM {sequences} WHERE name = '{%s}'", $newvariable)); if (!$target) { if ($op == 'update') { db_query("INSERT INTO {sequences} (name, id) VALUES ('{%s}', %d)", $newvariable, $variable['id']); } } else { if ($op == 'update') { db_query("UPDATE {sequences} SET id = %d WHERE name = '{%s}'", $variable['id'], $newvariable); } else { db_query("DELETE FROM {sequences} WHERE name = '{%s}'", $newvariable); } } } } } /** * Derive a module name from the table name * * In future, SchemaAPI will do this for us. * For now, we will have to map core tables. * * @param $table * The name of the table being acted upon. */ function domain_prefix_module_lookup($table) { $match = explode('_', $table); $module = $match[0]; switch ($table) { default: break; case 'boxes': $module = 'blocks'; break; case 'file_revisions': $module = 'files'; break; case 'cache_menu': $module = 'menu'; break; case 'filter_formats': $module = 'filters'; break; case 'access': case 'permission': case 'role': case 'users': case 'users_roles': $module = 'users'; break; case 'url_alias': $module = 'path'; break; case 'variable': $module = 'cache'; break; case 'client': case 'client_system': $module = 'drupal'; break; case 'accesslog': $module = 'statistics'; break; case 'term_data': case 'term_hierarchy': case 'term_node': case 'term_relation': case 'term_synonym': $module = 'taxonomy'; break; } return $module; } /** * Lookup stored table information for a domain. * * @param $domain_id * The domain_id taken from {domain}. */ function domain_prefix_lookup($domain_id, $clear = FALSE) { static $domain_prefix; if ($clear || !isset($domain_prefix[$domain_id])) { $domain_prefix[$domain_id] = array(); $result = db_query("SELECT domain_id, status, tablename, module, source FROM {domain_prefix} WHERE domain_id = %d", $domain_id); while ($data = db_fetch_array($result)) { $domain_prefix[$domain_id][$data['tablename']] = $data; } } return $domain_prefix[$domain_id]; } /** * Names of tables explicitly not allowed to be copied */ function domain_prefix_disallow() { return array( 'domain', 'domain_conf', 'domain_prefix', 'domain_theme', 'domain_user' ); } /** * Implement hook_domainupdate(). */ function domain_prefix_domainupdate($op, $domain = array(), $edit = array()) { switch ($op) { case 'create': $rule = variable_get('domain_prefix_options', 1); if ($rule) { // Get the current settings. $settings = unserialize(variable_get('domain_prefix', NULL)); if (!empty($settings)) { $settings['domain_id'] = $domain['domain_id']; drupal_execute('domain_prefix_form', $settings, $domain['domain_id'], $edit['domain_arguments']); } } break; case 'delete': domain_prefix_drop_records($domain['domain_id']); break; } } /** * Drop tables created by this module. * * @param $domain_id * The domain_id taken from {domain}. */ function domain_prefix_drop_records($domain_id) { $result = db_query("SELECT tablename FROM {domain_prefix} WHERE domain_id = %d AND status > 1", $domain_id); $prefix = domain_prefix_string($domain_id); while ($tables = db_fetch_array($result)) { $table = db_escape_table($prefix . $tables['tablename']); $exists = domain_prefix_table_exists($prefix, $tables['tablename']); if ($exists > 0) { db_query("DROP TABLE {%s}", $table); drupal_set_message(t('!string table dropped.', array('!string' => $table))); } } db_query("DELETE FROM {domain_prefix} WHERE domain_id = %d", $domain_id); } /** * Uniform prefix string * * @param $domain_id * The domain_id taken from {domain}. */ function domain_prefix_string($domain_id) { return 'domain_'. $domain_id .'_'; } /** * Implement hook_domaininstall() */ function domain_prefix_domaininstall() { // If Domain Prefix is being used, check to see that it is installed correctly. if (module_exists('domain_prefix') && !function_exists('_domain_prefix_load')) { drupal_set_message(t('The Domain Prefix module is not installed correctly. Please edit your settings.php file as described in INSTALL.txt', array('!url' => base_path() . drupal_get_path('module', 'domain_prefix') .'/INSTALL.txt'))); } }