'Database Optimization', 'description' => 'Optimizing the site database and clearing tables cache_form.', 'page callback' => 'drupal_get_form', 'page arguments' => array('optimizedb_admin'), 'access arguments' => array('administer optimizedb settings'), 'type' => MENU_NORMAL_ITEM, ); $items['admin/config/development/optimizedb/settings'] = array( 'title' => 'Database Optimization', 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items['admin/config/development/optimizedb/hide'] = array( 'title' => 'Hide notification', 'page callback' => 'optimizedb_hide_notification', 'access arguments' => array('administer optimizedb settings'), 'type' => MENU_LOCAL_TASK, ); $items['admin/config/development/optimizedb/list_tables'] = array( 'title' => 'List of tables in the database', 'page callback' => 'drupal_get_form', 'page arguments' => array('optimizedb_list_tables'), 'access arguments' => array('administer optimizedb settings'), 'type' => MENU_LOCAL_TASK, ); return $items; } /** * Implements hook_permission(). */ function optimizedb_permission() { return array( 'administer optimizedb settings' => array( 'title' => t('Database Optimization'), 'description' => t('Startup and configuration module.'), ), ); } /** * Configuring the module. */ function optimizedb_admin() { $form = array(); $form['executing_commands'] = array( '#type' => 'fieldset', '#title' => t('Executing commands manually'), ); $form['executing_commands']['clear'] = array( '#type' => 'submit', '#value' => t('Clear cache_form table'), '#submit' => array('optimizedb_admin_clear_table_submit'), ); $form['executing_commands']['clear_all'] = array( '#type' => 'submit', '#value' => t('Clear an entire table cache_form'), '#submit' => array('optimizedb_admin_clear_table_all_submit'), ); $form['executing_commands']['optimize'] = array( '#type' => 'submit', '#value' => t('Optimize tables'), '#submit' => array('optimizedb_admin_optimize_table_submit'), ); $form['executing_commands']['info'] = array( '#markup' => t('Clear cache_form table - clear the cache in a table cache_form, which has expired.' . '
' . 'Clear an entire table cache_form - deleting all the cache in a table cache_form.'), '#prefix' => '
', ); $form['optimizedb_auto'] = array( '#type' => 'fieldset', '#title' => t('Automatic clear cache_form table.'), ); $form['optimizedb_auto']['optimize_clear_type'] = array( '#type' => 'select', '#title' => t('Cache removal option'), '#options' => array( 0 => t('Delete cache which expired'), 1 => t('Delete entire cache'), ), '#default_value' => variable_get('optimize_clear_type', 0), ); $last_clear = variable_get('optimizedb_last_clear', 0); $form['optimizedb_auto']['optimizedb_clear_period'] = array( '#type' => 'select', '#title' => t('Clear cache_form table every'), '#description' => t('Last run: @date ago.', array( '@date' => _optimizedb_date($last_clear), )), '#default_value' => variable_get('optimizedb_clear_period', 0), '#options' => array( 0 => t('Disabled'), 100 => t('When performing Cron'), 1 => t('@count day', array('@count' => 1)), 2 => t('2 day'), 7 => t('7 days'), 14 => t('14 days'), 30 => t('30 days'), 60 => t('60 days'), ), ); switch (db_driver()) { case 'mysql': $table_length = db_query("SHOW TABLE STATUS LIKE '{cache_form}';")->fetchObject(); break; case 'pgsql': $table_length = db_query("SELECT pg_total_relation_size('{cache_form}') AS \"Data_length\", 0 AS \"Index_length\"")->fetchObject(); break; } $table_length = $table_length->Data_length + $table_length->Index_length; $form['optimizedb_auto']['lenght'] = array( '#markup' => t('The current size of the table @length.', array( '@length' => _optimizedb_format_size($table_length), )), ); $form['optimize_table'] = array( '#type' => 'fieldset', '#title' => t('Optimization settings database'), ); $last_optimization = variable_get('optimizedb_last_optimization', 0); $form['optimize_table']['optimizedb_optimization_period'] = array( '#type' => 'select', '#title' => t('Receive notification of the need to optimize the database, every.'), '#description' => t('Last run: @date ago.', array( '@date' => _optimizedb_date($last_optimization), )), '#default_value' => variable_get('optimizedb_optimization_period', 0), '#options' => array( 0 => t('Disabled'), 1 => t('@count day', array('@count' => 1)), 2 => t('2 day'), 7 => t('7 days'), 14 => t('14 days'), 30 => t('30 days'), 60 => t('60 days'), ), ); $size_tables = _optimizedb_format_size(variable_get('optimizedb_tables_size', 0)); $form['optimize_table']['tables'] = array( '#type' => 'item', '#title' => t('Current information on all database tables.'), '#markup' => t('The size of all tables in the database: @size. View the size of the tables separately, you can on the page - List of tables in the database.', array( '@size' => $size_tables, '@url' => url('admin/config/development/optimizedb/list_tables'), )), ); return system_settings_form($form); } /** * Page hide notification. */ function optimizedb_hide_notification() { $time = REQUEST_TIME; $notify_optimize = variable_get('optimizedb_notify_optimize', FALSE); // There is a need to disable the notification? if ($notify_optimize) { variable_set('optimizedb_notify_optimize', FALSE); // If the notification of the need to optimize hiding, so she runs. variable_set('optimizedb_last_optimization', $time); $optimization_period = (int) variable_get('optimizedb_optimization_period', 0); $time_next_optimization = strtotime('+ ' . $optimization_period . ' day', $time); drupal_set_message(t('The following message on the need to perform optimization, you get - @date.', array( '@date' => format_date($time_next_optimization), ))); } else { drupal_set_message(t('Alerts are not available')); } return ''; } /** * List of tables in the database with the size and sorting. * * @return string * Table with a list of tables. */ function optimizedb_list_tables($form, &$form_state) { $headers = array( 'name' => array( 'data' => t('Table name'), ), 'size' => array( 'data' => t('Table size'), 'field' => 'size', 'sort' => 'desc', ), ); $rows = _optimizedb_tables_list(); // Desc or asc. $sort = tablesort_get_sort($headers); $sort_table = &drupal_static('_optimizedb_list_tables_sort:sort'); $sort_table = $sort; // Sort descending and ascending. usort($rows, '_optimizedb_list_tables_sort'); // Parameter "size_byte" us only needed to sort, now his unit to remove. $rows = array_map('optimizedb_list_tables_remove_site_byte', $rows); $operations_tables_result = isset($_SESSION['optimizedb_list_tables_operations']) ? $_SESSION['optimizedb_list_tables_operations'] : NULL; if (!is_null($operations_tables_result)) { if ($operations_tables_result == array()) { drupal_set_message(t('The operation completed successfully.')); } else { $form['operations_tables'] = array( '#type' => 'fieldset', '#title' => t('Errors that arose during the operation:'), ); $form['operations_tables']['errors'] = array( '#markup' => theme('table', array( 'header' => array( array('data' => t('Table name')), array('data' => t('Type of problem')), array('data' => t('Information about the problem')), ), 'rows' => $operations_tables_result, )), ); } } $_SESSION['optimizedb_list_tables_operations'] = NULL; if (db_driver() == 'mysql') { $form['operations'] = array( '#type' => 'fieldset', '#title' => t('Operations with tables:'), ); $form['operations']['check_tables'] = array( '#type' => 'submit', '#value' => t('Check tables'), '#submit' => array('optimizedb_list_tables_check_tables_submit'), ); $form['operations']['repair_tables'] = array( '#type' => 'submit', '#value' => t('Repair tables'), '#submit' => array('optimizedb_list_tables_repair_tables_submit'), ); $form['operations']['optimize_tables'] = array( '#type' => 'submit', '#value' => t('Optimize tables'), '#submit' => array('optimizedb_list_tables_optimize_tables_submit'), ); } $form['tables'] = array( '#type' => 'tableselect', '#header' => $headers, '#options' => $rows, '#empty' => t('No content available.'), ); return $form; } /** * Implements hook_FORMID_submit(). */ function optimizedb_list_tables_check_tables_submit($form, &$form_state) { _optimizedb_list_tables_operation_execute($form, $form_state, 'CHECK TABLE'); } /** * Implements hook_FORMID_submit(). */ function optimizedb_list_tables_repair_tables_submit($form, &$form_state) { _optimizedb_list_tables_operation_execute($form, $form_state, 'REPAIR TABLE'); } /** * Implements hook_FORMID_submit(). */ function optimizedb_list_tables_optimize_tables_submit($form, &$form_state) { _optimizedb_list_tables_operation_execute($form, $form_state, 'OPTIMIZE TABLE'); } /** * Performing operations on tables. * * @param array $form * The parameter with the hook - hook_FORMID_submit(). * @param array $form_state * The parameter with the hook - hook_FORMID_submit(). * @param string $operation_name * The operation to be performed with tables. */ function _optimizedb_list_tables_operation_execute($form, $form_state, $operation_name) { $tables_selected = $form_state['values']['tables']; if (db_driver() == 'mysql') { if (!empty($tables_selected)) { $table_origin = $form['tables']['#options']; $count = 0; foreach ($tables_selected as $table => $value) { if ($table == $value && is_string($value) && isset($table_origin[$value])) { $table_name = $table_origin[$value]['name']; $operations[] = array('_optimizedb_list_tables_operation_execute_run', array(array( 'name' => $table_name, 'operation' => $operation_name, ))); ++$count; } } if ($count == 0) { drupal_set_message(t('To execute, you must select at least one table from the list.'), 'error'); } else { $batch = array( 'operations' => $operations, 'finished' => '_optimizedb_list_tables_operation_execute_finished', ); batch_set($batch); } } else { drupal_set_message(t('To execute, you must select at least one table from the list.'), 'error'); } } else { drupal_set_message(t('Function to check tables is only available for databases MySQL.'), 'error'); } } /** * Batch function. */ function _optimizedb_list_tables_operation_execute_run($param, &$context) { $operation_result = db_query($param['operation'] . ' ' . $param['name'])->fetchAll(); foreach ($operation_result as $result) { if (isset($result->Msg_type) && in_array(strtolower($result->Msg_type), array('error', 'warning'))) { $context['results']['errors'][] = array( array('data' => $param['name']), array('data' => $result->Msg_type), array('data' => $result->Msg_text), ); } } } /** * Completion operation. */ function _optimizedb_list_tables_operation_execute_finished($success, $results, $operations) { if ($success) { if (isset($results['errors'])) { $result = $results['errors']; } else { $result = array(); } $_SESSION['optimizedb_list_tables_operations'] = $result; } else { drupal_set_message(t('Completed with errors.'), 'error'); } } /** * Remove the "size_byte" of the array. * * @param array $row * Array. * * @return array * Array. */ function optimizedb_list_tables_remove_site_byte($row) { unset($row['size_byte']); return $row; } /** * Sorting table data size. * * Callback for usort() in optimizedb_list_tables(). */ function _optimizedb_list_tables_sort($a, $b) { $sort = &drupal_static(__FUNCTION__ . ':sort'); if ($sort == 'asc') { return $a['size_byte'] > $b['size_byte']; } return $a['size_byte'] < $b['size_byte']; } /** * Convert bytes to the usual size for people. * * @param int $size * Size in bytes. * * @return string * The correct size. */ function _optimizedb_format_size($size) { $metrics[0] = 'Byte'; $metrics[1] = 'KB'; $metrics[2] = 'MB'; $metrics[3] = 'GB'; $metrics[4] = 'TB'; $metric = 0; while (floor($size / 1024) > 0) { ++$metric; $size /= 1024; } $ret = (round($size, 1) . ' ' . (isset($metrics[$metric]) ? $metrics[$metric] : '??')); return $ret; } /** * Output how much time passed from the specified date. * * @param int $timestamp * The date in Unix format. * * @return string * The formatted date. */ function _optimizedb_date($timestamp) { $timestamp = (int) $timestamp; if ($timestamp == 0) { return t('None'); } $difference = REQUEST_TIME - $timestamp; $text = format_interval($difference, 1); return $text; } /** * List and the size of the database tables. * * @return array * An array with a list of database tables. */ function _optimizedb_tables_list() { switch (db_driver()) { case 'mysql': $tables = db_query("SHOW TABLE STATUS", array(), array( 'fetch' => PDO::FETCH_OBJ, )); break; case 'pgsql': $tables = db_query("SELECT table_name as \"Name\", pg_total_relation_size(table_name) AS \"Data_length\", 0 as \"Index_length\" FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name", array(), array('fetch' => PDO::FETCH_OBJ)); break; } $result = array(); if ($tables) { $size_tables = 0; foreach ($tables as $table) { $length = $table->Data_length + $table->Index_length; $result[$table->Name] = array( 'name' => $table->Name, 'size' => _optimizedb_format_size($length), 'size_byte' => $length, ); $size_tables += $length; } // The total size of the tables. variable_set('optimizedb_tables_size', $size_tables); } return (array) $result; } /** * Implements hook_FORMID_submit(). */ function optimizedb_admin_optimize_table_submit($form, &$form_state) { switch (db_driver()) { case 'mysql': $tables = db_query("SHOW TABLES", array(), array( 'fetch' => PDO::FETCH_NUM, )); break; case 'pgsql': $tables = db_query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' ORDER BY table_name", array(), array('fetch' => PDO::FETCH_NUM)); break; } foreach ($tables as $table) { $operations[] = array('optimizedb_optimize_batch_run', array($table[0])); } $batch = array( 'operations' => $operations, 'finished' => 'optimizedb_optimize_batch_finished', ); batch_set($batch); // Calling this function, you need to run the optimization from // the command line. if (PHP_SAPI == 'cli') { drush_backend_batch_process(); } } /** * Batch function. */ function optimizedb_optimize_batch_run($table, &$context) { // Standard the status the result operation. $status = 'success'; switch (db_driver()) { case 'mysql': try { $result = db_query('OPTIMIZE TABLE ' . $table)->fetchObject(); if (isset($result->Msg_type) && in_array(strtolower($result->Msg_type), array('error', 'warning'))) { $status = 'error'; } } catch (Exception $e) { $status = 'error'; } break; case 'pgsql': try { db_query('VACUUM ANALYZE ' . $table); } catch (Exception $e) { $status = 'error'; } break; } if (PHP_SAPI == 'cli') { drush_print(dt('Table "@name" been optimized.', array( '@name' => $table, ))); } $context['results'][$status][] = $table; } /** * Completion optimization. */ function optimizedb_optimize_batch_finished($success, $results, $operations) { if ($success) { if (isset($results['error'])) { $message_error = 'When optimizing experiencing errors with tables: @tables. You need as quickly as possible to check the table. Other tables were successfully optimized.'; if (PHP_SAPI == 'cli') { drush_log(dt($message_error, array( '@tables' => implode(', ', $results['error']), '@url' => url('admin/config/development/optimizedb/list_tables'), )), 'error'); } else { drupal_set_message(t($message_error, array( '@tables' => implode(', ', $results['error']), '@url' => url('admin/config/development/optimizedb/list_tables'), )), 'error'); } } else { $message_success = 'Optimized @count tables.'; if (PHP_SAPI == 'cli') { drush_log(dt($message_success, array( '@count' => count($results['success']), )), 'success'); } else { drupal_set_message(t($message_success, array( '@count' => count($results['success']), ))); } } } else { $message_error = 'Completed with errors.'; if (PHP_SAPI == 'cli') { drush_log(dt($message_error), 'error'); } else { drupal_set_message(t($message_error), 'error'); } } variable_set('optimizedb_notify_optimize', FALSE); variable_set('optimizedb_last_optimization', REQUEST_TIME); cache_clear_all('optimizedb_tables_status', 'cache'); } /** * Implements hook_FORMID_submit(). */ function optimizedb_admin_clear_table_submit($form, &$form_state) { _optimizedb_clear_table(0); drupal_set_message(t('The table is cleared.')); } /** * Implements hook_FORMID_submit(). */ function optimizedb_admin_clear_table_all_submit($form, &$form_state) { _optimizedb_clear_table(1); drupal_set_message(t('The table is cleared.')); } /** * Cleaning table cache_form and cleaning time record. * * @param int $type * 0 - Delete cache which expired * 1 - Delete entire cache */ function _optimizedb_clear_table($type = NULL) { if (is_null($type)) { $type = variable_get('optimize_clear_type', 0); } if ((int) $type == 1) { db_truncate('cache_form')->execute(); } else { $count_all = (int) db_select('cache_form') ->countQuery() ->execute() ->fetchField(); if ($count_all !== 0) { $count_expire = (int) db_select('cache_form') ->condition('expire', REQUEST_TIME, '<') ->countQuery() ->execute() ->fetchField(); if ($count_all == $count_expire) { db_truncate('cache_form')->execute(); } else { db_delete('cache_form') ->condition('expire', REQUEST_TIME, '<') ->execute(); } } } // Update size tables. _optimizedb_tables_list(); variable_set('optimizedb_last_clear', REQUEST_TIME); } /** * Implements hook_cron(). */ function optimizedb_cron() { // Clear cache_form table. $clear_period = (int) variable_get('optimizedb_clear_period', 0); if ($clear_period !== 0) { $last_clear = variable_get('optimizedb_last_clear', 0); $time_next_clear = strtotime('+ ' . $clear_period . ' day', ($last_clear == 0 ? REQUEST_TIME : $last_clear)); if ($clear_period == 100 || $time_next_clear <= REQUEST_TIME) { _optimizedb_clear_table(); } } // Check whether there is a need to optimize. $optimization_period = (int) variable_get('optimizedb_optimization_period', 0); if ($optimization_period !== 0) { $last_optimization = variable_get('optimizedb_last_optimization', 0); $time_next_optimization = strtotime('+ ' . $optimization_period . ' day', ($last_optimization == 0 ? REQUEST_TIME : $last_optimization)); if ($time_next_optimization <= REQUEST_TIME) { variable_set('optimizedb_notify_optimize', TRUE); } } }