/[drupal]/contributions/modules/dba/dba.module
ViewVC logotype

Contents of /contributions/modules/dba/dba.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.62 - (show annotations) (download) (as text)
Thu Dec 11 02:52:55 2008 UTC (11 months, 2 weeks ago) by dww
Branch: MAIN
CVS Tags: HEAD
Changes since 1.61: +6 -7 lines
File MIME type: text/x-php
#343908 by foripepe: Fixed fatal error when checking tables.
1 <?php
2 // $Id: dba.module,v 1.61 2008/12/11 02:42:12 dww Exp $
3
4 /**
5 * @file
6 * Allows administrators direct access to their Drupal database.
7 * Written by Jeremy Andrews <jeremy@kerneltrap.org>, June 2004.
8 * PostgreSQL functionality provided by AAM <aam@ugpl.de>
9 * Major security audit, porting, and maintenance by
10 * Derek "dww" Wright (http://drupal.org/user/46549)
11 */
12
13 define('DBA_BACKUP_EXCLUDE', 'accesslog, cache, search_index, search_total, watchdog');
14
15
16 // Standard Drupal functions.
17 function dba_perm() {
18 return array('dba view database', 'dba administer database');
19 }
20
21 function dba_help($section = '') {
22 switch ($section) {
23 case 'admin/help#dba':
24 $output .= t('The dba module allows site administrators a method for direct database administration. This is a dangerous module, in that it gives unlimited access and control over the active database. With this module, it is possible to corrupt or delete an entire drupal database. Use at your own risk.');
25 break;
26 }
27 return $output;
28 }
29
30 function dba_menu($may_cache) {
31 $items = array();
32 $admin_access = user_access('dba administer database');
33 $access = user_access('dba view database') || $admin_access;
34 if ($may_cache) {
35 // Provide menus to dbas with view permissions.
36 $items[] = array(
37 'path' => 'admin/build/database',
38 'title' => t('Database'),
39 'description' => t("View and edit your site's database directly."),
40 'callback' => 'drupal_get_form',
41 'callback arguments' => 'dba_database_overview_form',
42 'access' => $access,
43 );
44 // Tabs.
45 $items[] = array(
46 'path' => 'admin/build/database/table',
47 'title' => t('Tables'),
48 'callback' => 'drupal_get_form',
49 'callback arguments' => 'dba_database_overview_form',
50 'type' => MENU_DEFAULT_LOCAL_TASK,
51 );
52 $items[] = array(
53 'path' => 'admin/build/database/query',
54 'title' => t('Query database'),
55 'callback' => 'drupal_get_form',
56 'callback arguments' => array('dba_query_form'),
57 'access' => $admin_access,
58 'type' => MENU_LOCAL_TASK,
59 'weight' => 8,
60 );
61 $items[] = array(
62 'path' => 'admin/build/database/script',
63 'title' => t('Run script'),
64 'callback' => 'drupal_get_form',
65 'callback arguments' => array('dba_run_script'),
66 'access' => $admin_access,
67 'type' => MENU_LOCAL_TASK,
68 'weight' => 10,
69 );
70 $items[] = array(
71 'path' => 'admin/settings/dba',
72 'title' => t('Database administration'),
73 'description' => t('Control automatic backups and other settings.'),
74 'callback' => 'drupal_get_form',
75 'callback arguments' => array('dba_settings_form'),
76 'access' => $admin_access,
77 );
78 }
79 elseif (strstr(drupal_get_path_alias($_GET['q']), 'admin/build/database')) {
80 $tables = dba_get_active_tables(0);
81 if (!empty($tables) && count($tables) == 1) {
82 // You can only view or describe one table at a time.
83 $table = reset($tables);
84
85 // Regular subtabs.
86 $items[] = array(
87 'path' => "admin/build/database/table/$table/view",
88 'title' => t('View'),
89 'callback' => 'dba_admin_tables_view',
90 'access' => $access,
91 'type' => MENU_LOCAL_TASK,
92 'weight' => 0,
93 );
94 $items[] = array(
95 'path' => "admin/build/database/table/$table/describe",
96 'title' => t('Describe'),
97 'callback' => 'dba_admin_tables_describe',
98 'access' => $access,
99 'type' => MENU_LOCAL_TASK,
100 'weight' => 2,
101 );
102 if (_is_mysql()) {
103 $items[] = array(
104 'path' => "admin/build/database/table/$table/check",
105 'title' => t('Check'),
106 'callback' => 'dba_admin_tables_check',
107 'access' => $access,
108 'type' => MENU_LOCAL_TASK,
109 'weight' => 4,
110 );
111 $items[] = array(
112 'path' => "admin/build/database/table/$table/optimize",
113 'title' => t('Optimize'),
114 'callback' => 'dba_admin_tables_optimize',
115 'access' => $access,
116 'type' => MENU_LOCAL_TASK,
117 'weight' => 4,
118 );
119 }
120 // Subtabs for dbas with administer permissions.
121 $items[] = array(
122 'path' => "admin/build/database/table/$table/backup",
123 'title' => t('Backup'),
124 'callback' => 'dba_admin_tables_verify_op',
125 'callback arguments' => array('backup'),
126 'access' => $admin_access,
127 'type' => MENU_LOCAL_TASK,
128 'weight' => 8,
129 );
130 $items[] = array(
131 'path' => "admin/build/database/table/$table/empty",
132 'title' => t('Empty'),
133 'callback' => 'dba_admin_tables_verify_op',
134 'callback arguments' => array('empty'),
135 'access' => $admin_access,
136 'type' => MENU_LOCAL_TASK,
137 'weight' => 8,
138 );
139 $items[] = array(
140 'path' => "admin/build/database/table/$table/drop",
141 'title' => t('Drop'),
142 'callback' => 'dba_admin_tables_verify_op',
143 'callback arguments' => array('drop'),
144 'access' => $admin_access,
145 'type' => MENU_LOCAL_TASK,
146 'weight' => 10,
147 );
148 }
149 // Administrative callbacks.
150 $items[] = array(
151 'path' => "admin/build/database/backup",
152 'title' => t('Backup'),
153 'callback' => 'dba_admin_tables_verify_op',
154 'callback arguments' => array('backup'),
155 'access' => $admin_access,
156 'weight' => 15,
157 'type' => (arg(3) == 'backup' ? MENU_LOCAL_TASK : MENU_CALLBACK),
158 );
159 $items[] = array(
160 'path' => "admin/build/database/empty",
161 'title' => t('Empty'),
162 'callback' => 'dba_admin_tables_verify_op',
163 'callback arguments' => array('empty'),
164 'access' => $admin_access,
165 'weight' => 15,
166 'type' => (arg(3) == 'empty' ? MENU_LOCAL_TASK : MENU_CALLBACK),
167 );
168 $items[] = array(
169 'path' => "admin/build/database/drop",
170 'title' => t('Drop'),
171 'callback' => 'dba_admin_tables_verify_op',
172 'callback arguments' => array('drop'),
173 'access' => $admin_access,
174 'weight' => 15,
175 'type' => (arg(3) == 'drop' ? MENU_LOCAL_TASK : MENU_CALLBACK),
176 );
177 if (_is_mysql()) {
178 $items[] = array(
179 'path' => "admin/build/database/check",
180 'title' => t('Check'),
181 'callback' => 'dba_admin_tables_check',
182 'access' => $admin_access,
183 'weight' => 15,
184 'type' => (arg(3) == 'check' ? MENU_LOCAL_TASK : MENU_CALLBACK),
185 );
186 $items[] = array(
187 'path' => "admin/build/database/optimize",
188 'title' => t('Optimize'),
189 'callback' => 'dba_admin_tables_optimize',
190 'access' => $admin_access,
191 'weight' => 15,
192 'type' => (arg(3) == 'optimize' ? MENU_LOCAL_TASK : MENU_CALLBACK),
193 );
194 }
195 }
196 return $items;
197 }
198
199 function dba_settings_form() {
200 if (!user_access('dba administer database')) {
201 drupal_access_denied();
202 module_invoke_all('exit');
203 exit;
204 }
205 // Backups
206 $form['backup'] = array(
207 '#type' => 'fieldset',
208 '#title' => t('Database backups'),
209 '#collapsible' => TRUE,
210 '#collapsed' => FALSE,
211 );
212 $form['backup']['dba_default_filename'] = array(
213 '#type' => 'textfield',
214 '#title' => t('Default backup filename'),
215 '#description' => t('Default filename to use when backing up multiple tables. If backing up only one table, the filename will default to the name of the table. You will have an opportunity to modify this filename when you actually perform the backup. If automatically backing up tables, the name will be prepended with the current date and time.'),
216 '#default_value' => variable_get('dba_default_filename', 'backup.sql'),
217 '#size' => 30,
218 '#maxlength' => 64,
219 );
220
221 $period = drupal_map_assoc(array(0, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
222 $period[0] = t('disabled');
223 $form['backup']['dba_auto_backup_interval'] = array(
224 '#type' => 'select',
225 '#title' => t('Automatically backup database every'),
226 '#default_value' => variable_get('dba_auto_backup_interval', 0),
227 '#options' => $period,
228 '#description' => t('Select how often you wish to have your database automatically backed up. Requires crontab.'),
229 );
230 $backup_interval = variable_get('dba_auto_backup_interval', 0);
231 $backup_path = variable_get('dba_auto_backup_path', file_directory_temp());
232 $backup_exclude = variable_get('dba_auto_backup_exclude_tables', DBA_BACKUP_EXCLUDE);
233
234 if ($backup_interval) {
235 $attributes = array('enabled' => 'enabled');
236 }
237 else {
238 $attributes = array('disabled' => 'disabled');
239 }
240 $form['backup']['dba_auto_backup_path'] = array(
241 '#type' => 'textfield',
242 '#title' => t('Automatic backup path'),
243 '#default_value' => $backup_path,
244 '#size' => 30,
245 '#maxlength' => 255,
246 '#description' => t('If automatic backups are enabled, you must specify a directory where you would like to store the backup files. The path must be absolute and for security reasons should not be accesible to the web.'),
247 '#attributes' => $attributes,
248 );
249
250 if (function_exists('bzcompress')) {
251 $form['backup']['dba_auto_backup_bzip2'] = array(
252 '#type' => 'checkbox',
253 '#title' => t('Compress automatic backups'),
254 '#return_value' => 1,
255 '#default_value' => variable_get('dba_auto_backup_bzip2', 0),
256 '#description' => t('Enable this option to compress automatic backups with <a href="http://sources.redhat.com/bzip2/">bzip2</a>.'),
257 '#attributes' => $attributes,
258 );
259 }
260 else if (function_exists('gzencode')) {
261 $form['backup']['dba_auto_backup_gzip'] = array(
262 '#type' => 'checkbox',
263 '#title' => t('Compress automatic backups'),
264 '#return_value' => 1,
265 '#default_value' => variable_get('dba_auto_backup_gzip', 0),
266 '#description' => t('Enable this option to compress automatic backups with <a href="http://www.gzip.org/zlib/">zlib</a>.'),
267 '#attributes' => $attributes,
268 );
269 }
270 $form['backup']['dba_auto_backup_mail'] = array(
271 '#type' => 'checkbox',
272 '#title' => t('Mail backup to administrator'),
273 '#return_value' => 1,
274 '#default_value' => variable_get('dba_auto_backup_mail', 0),
275 '#description' => t("Enable this option to have a copy of the database backup files mailed to your administrator's email address."),
276 '#attributes' => $attributes,
277 );
278 $form['backup']['dba_auto_backup_exclude_tables'] = array(
279 '#type' => 'textfield',
280 '#title' => t('Automatic backup excluded tables'),
281 '#default_value' => $backup_exclude,
282 '#description' => t("If automatic backups are enabled, you can specify a space-separated list of table names where you only want the table definition (schema) backed up, but not the actual data. This is useful for tables that can be rebuilt (such as the tables related to search indexing) or the watchdog table, which holds log events but no actual site content. Only saving the schema and not the data for these tables can greatly reduce the size of the backups, without losing real content."),
283 '#attributes' => $attributes,
284 );
285
286 // MySQL
287 if (_is_mysql()) {
288 $form['mysql_options'] = array(
289 '#type' => 'fieldset',
290 '#title' => t('MySQL options'),
291 '#collapsible' => TRUE,
292 '#collapsed' => FALSE,
293 );
294 $form['mysql_options']['dba_default_check_type'] = array(
295 '#type' => 'radios',
296 '#title' => t('Default check type'),
297 '#description' => t('MySQL databases support many types of database integrity checks. Select your preferred default type from the list above. Medium is the MySQL recommended default type.'),
298 '#default_value' => variable_get('dba_default_check_type', 'MEDIUM'),
299 '#options' => array('QUICK' => t('Quick'), 'FAST' => t('Fast'), 'CHANGED' => t('Changed'), 'MEDIUM' => t('Medium'), 'EXTENDED' => t('Extended'))
300 );
301 $form['mysql_options']['dba_repair'] = array(
302 '#type' => 'radios',
303 '#title' => t('Repair option'),
304 '#description' => t('By default, the dba module will only display a repair button if a table has been determined to need a repair. Alternatively, you can make the module always display a repair button, or never display a repair button.'),
305 '#default_value' => variable_get('dba_repair', 0),
306 '#options' => array(
307 '0' => t('Automatic'),
308 '1' => t('Always'),
309 '2' => t('Never'),
310 ),
311 );
312 }
313 // Add a validation callback to make sure the backup path is writable.
314 $setting_valid = array('dba_settings_validate' => array());
315 $form['#validate'] = isset($form['#validate']) ? array_merge($form['#validate'], $setting_valid) : $setting_valid;
316 return system_settings_form($form);
317 }
318
319 function dba_settings_validate($form_id, $form_values, $form) {
320 if (!file_check_directory($form_values['dba_auto_backup_path'])) {
321 form_set_error('dba_auto_backup_path', t('The automatic backup path does not exist, or is not writeable. Automatic backups will not begin until you fix this error.'));
322 }
323 elseif ($test = tempnam($form_values['dba_auto_backup_path'], 'dba.')) {
324 file_delete($test);
325 }
326 else {
327 form_set_error('dba_auto_backup_path', t('The automatic backup path exists, but is not writeable. Automatic backups will not begin until you fix this error.'));
328 }
329 }
330
331 function dba_cron() {
332 if ($interval = variable_get('dba_auto_backup_interval', 0)) {
333 // See if it's time for another auto-backup.
334 if ((time() - $interval) >= variable_get('dba_auto_backup_last', 0)) {
335 dba_auto_backup();
336 }
337 }
338 }
339
340 function dba_auto_backup() {
341 $backup_started = time();
342 $path = variable_get('dba_auto_backup_path', file_directory_temp());
343 // See what tables (if any) the admin wants us to only backup
344 // the schema, not the actual data. we need it as an array, so we
345 // lookup the setting as a string, then split() it into an array.
346 $exclude_tables_str = variable_get('dba_auto_backup_exclude_tables', DBA_BACKUP_EXCLUDE);
347 $exclude_tables = split('[ ,]', $exclude_tables_str);
348 // Make sure we have permission to save our backup file.
349 if (file_check_directory($path, FILE_CREATE_DIRECTORY)) {
350 $database = dba_get_database();
351 $filename = format_date(time(), 'custom', 'Y-md-Hi_') . variable_get('dba_default_filename', 'backup.sql');
352
353 $backup = "-- Drupal dba.module database dump\n";
354 $backup .= "--\n";
355 $backup .= "-- Database: $database\n";
356 $backup .= "-- Date: ". format_date(time(), 'large') ."\n\n";
357
358 $tables = dba_get_tables();
359 foreach ($tables as $table) {
360 $backup .= dba_backup_table($table, TRUE, FALSE, in_array($table, $exclude_tables) ? FALSE : TRUE);
361 }
362
363 // Optionally bzip2 compress auto-backup file.
364 if (variable_get('dba_auto_backup_bzip2', 0)) {
365 $backup = bzcompress($backup, 9);
366 $filename = $filename .'.bz2';
367 }
368 // Otherwise, optionally gzip compress auto-backup file.
369 else if (variable_get('dba_auto_backup_gzip', 0)) {
370 if (version_compare(phpversion(), '4.2', '>=')) {
371 $backup = gzencode($backup, 9, FORCE_GZIP);
372 }
373 else {
374 $backup = gzencode($backup, FORCE_GZIP);
375 }
376 $filename = $filename .'.gz';
377 }
378
379 if ($fp = fopen($path ."/$filename", 'wb')) {
380 fwrite($fp, $backup);
381 fclose($fp);
382 variable_set('dba_auto_backup_last', $backup_started);
383
384 // If enabled, email a copy of the backup to the site administrator.
385 if (variable_get('dba_auto_backup_mail', 0)) {
386 $attachment = new stdClass();
387 $attachment->path = $path ."/$filename";
388 $attachment->filename = $filename;
389 dba_mail_backup($attachment);
390 }
391 }
392 }
393 }
394
395 /**
396 * Display the contents of the selected table.
397 */
398 function dba_admin_tables_view() {
399 if (user_access('dba administer database') && arg(6) && arg(7) && arg(8)) {
400 switch (arg(6)) {
401 case 'delete':
402 return drupal_get_form('dba_delete_row', arg(4), arg(7), arg(8));
403 case 'edit':
404 return drupal_get_form('dba_edit_row', arg(4), arg(7), arg(8));
405 }
406 }
407 else {
408 return dba_table_overview(arg(4));
409 }
410 }
411
412 /**
413 * Describe the schema of the selected table.
414 */
415 function dba_admin_tables_describe() {
416 $output = '';
417 if (user_access('dba view database')) {
418 $output = dba_table_describe(arg(4));
419 }
420 print theme('page', $output);
421 }
422
423 /**
424 * MySQL only: check the selected table(s).
425 */
426 function dba_admin_tables_check() {
427 return drupal_get_form('dba_check_tables_form');
428 }
429
430 /**
431 * MySQL only: optimize the selected table(s).
432 */
433 function dba_admin_tables_optimize() {
434 $output = '';
435 if (user_access('dba administer database')) {
436 $output = dba_tables_optimize();
437 unset($_SESSION['dba_tables']);
438 }
439 print theme('page', $output);
440 }
441
442 /**
443 * Menu callback to verify the administrator wants to backup, empty or
444 * drop the selected table(s) by means of a confirm form.
445 */
446 function dba_admin_tables_verify_op($op) {
447 $tables = dba_get_active_tables(0);
448 return drupal_get_form('dba_verify', $tables, $op);
449 }
450
451 function theme_dba_database_overview_form($form) {
452 $output = '';
453 $rows = array();
454 $database = dba_get_database();
455 drupal_set_title(t('View database %database', array('%database' => $database)));
456
457 // It'd be great to use the pager and tablesort, but doesn't appear possible.
458 $header = array('', t('Tables'), t('Rows'));
459 $tables = dba_get_tables();
460 foreach ($tables as $table) {
461 $count = dba_get_row_count($table);
462 $checkbox = drupal_render($form['tables'][$table]);
463 $rows[] = array($checkbox, l($table, "admin/build/database/table/$table/view"), $count);
464 }
465 $output .= dba_select_all_js();
466 $output .= theme('table', $header, $rows);
467 $output .= dba_select_all_js();
468 $output .= drupal_render($form);
469 drupal_set_html_head(checkoff_head());
470 return $output;
471 }
472
473 function dba_database_overview_form() {
474 $tables = dba_get_tables();
475 $form = array();
476 $form['#tree'] = TRUE;
477 $form['tables'] = array();
478 foreach ($tables as $table) {
479 $form['tables'][$table] = array(
480 '#type' => 'checkbox',
481 '#title' => '',
482 '#default_value' => 0,
483 );
484 }
485 if (_is_mysql()) {
486 $form['check'] = array('#type' => 'submit', '#value' => t('Check'));
487 $form['optimize'] = array('#type' => 'submit', '#value' => t('Optimize'));
488 }
489 if (user_access('dba administer database')) {
490 $form['backup'] = array('#type' => 'submit', '#value' => t('Backup'));
491 $form['empty'] = array('#type' => 'submit', '#value' => t('Empty'));
492 $form['drop'] = array('#type' => 'submit', '#value' => t('Drop'));
493 }
494 return $form;
495 }
496
497 function dba_database_overview_form_validate($form_id, $form_values) {
498 if (!array_filter($form_values['tables'])) {
499 $op = isset($_POST['op']) ? $_POST['op'] : '';
500 form_set_error('tables', t('You must select the table(s) to %op.', array('%op' => theme('placeholder', $op))));
501 }
502 }
503
504 function dba_database_overview_form_submit($form_id, $form_values) {
505 $op = isset($_POST['op']) ? $_POST['op'] : '';
506 // We already validated the overview form, so we know we have tables.
507 $form_tables = array_keys(array_filter($form_values['tables']));
508 $_SESSION['dba_tables'] = dba_get_active_tables(0, $form_tables);
509
510 switch ($op) {
511 case t('Check'):
512 return 'admin/build/database/check';
513
514 case t('Optimize'):
515 return 'admin/build/database/optimize';
516
517 case t('Backup'):
518 return 'admin/build/database/backup';
519
520 case t('Empty'):
521 return 'admin/build/database/empty';
522
523 case t('Drop'):
524 return 'admin/build/database/drop';
525 }
526 }
527
528 function dba_select_all_js() {
529 $output = "<a href=\"javascript:checkoff('dba-database-overview-form',1)\">";
530 $output .= t('select all');
531 $output .= "</a>&nbsp;&nbsp;|&nbsp;&nbsp;";
532 $output .= "<a href=\"javascript:checkoff('dba-database-overview-form',0)\">";
533 $output .= t('clear all');
534 $output .= "</a><br>";
535 return $output;
536 }
537
538 function dba_delete_row($table, $key, $keyid) {
539 $rows = array();
540 $keyid = str_replace('__2F_', '/', $keyid);
541 $result = db_query("SELECT * FROM %s WHERE %s = '%s'", $table, $key, $keyid);
542 $row = db_fetch_array($result);
543 $rows[] = array_map('check_plain', (array)$row);
544 $header = array_map('check_plain', array_keys($row));
545 $form = array();
546 $form['row'] = array('#value' => theme('table', $header, $rows));
547 $form['table'] = array('#type' => 'hidden', '#value' => $table);
548 $form['key'] = array('#type' => 'hidden', '#value' => $key);
549 $form['keyid'] = array('#type' => 'hidden', '#value' => $keyid);
550 $form = confirm_form($form,
551 t('Are you sure you want to delete this row from the "%table" table?', array('%table' => $table)),
552 "admin/build/database/table/$table/view",
553 t('By clicking "Delete row" you will permanently remove this row from the %table table. This action cannot be undone.', array('%table' => $table)),
554 t('Delete row'),
555 t('Cancel')
556 );
557 return $form;
558 }
559
560 function dba_edit_row($table, $key, $keyid) {
561 $rows = array();
562 $keyid = str_replace('__2F_', '/', $keyid);
563 $result = db_query("SELECT * FROM %s WHERE %s = '%s'", $table, $key, $keyid);
564 $row = db_fetch_array($result);
565 $header = array_keys($row);
566 foreach ($row as $k => $value) {
567 if ($k == $key) {
568 $form['key'] = array('#type' => 'markup', '#value' => $value);
569 }
570 else {
571 // We store all fields in sub-array 'fields' to avoid naming collisions.
572 $size = strlen($value);
573 if ($size > 255) {
574 $form['field'][$k] = array(
575 '#type' => 'textarea',
576 '#default_value' => $value,
577 '#cols' => 70, '#rows' => 10,
578 );
579 }
580 else {
581 $form['field'][$k] = array(
582 '#type' => 'textfield',
583 '#default_value' => $value,
584 '#size' => $size, '#maxlength' => 255,
585 );
586 }
587 }
588 }
589 $form['header'] = array(
590 '#type' => 'hidden',
591 '#value' => implode(',', $header),
592 );
593 $form['table'] = array('#type' => 'hidden', '#value' => $table);
594 $form['key'] = array('#type' => 'hidden', '#value' => $key);
595 $form['keyid'] = array('#type' => 'hidden', '#value' => $keyid);
596 return confirm_form($form,
597 t('Edit row from the "%table" table', array('%table' => $table)),
598 "admin/build/database/table/$table/view",
599 t('By clicking "Edit row" you will save any changes you make to this row of the %table table. This action cannot be undone.', array('%table' => $table)),
600 t('Edit row'),
601 t('Cancel')
602 );
603 }
604
605 function theme_dba_edit_row($form) {
606 $header = explode(',', $form['header']['#value']);
607 $key = $form['key']['#value'];
608 $keyid = $form['keyid']['#value'];
609 $rows = array();
610 $row = array();
611 foreach ($header as $k => $name) {
612 if ($name == $key) {
613 $row[] = $keyid;
614 }
615 else {
616 $row[] = drupal_render($form['field'][$name]);
617 }
618 }
619 $rows[] = $row;
620 $output = theme('table', $header, $rows);
621 $output .= drupal_render($form);
622 return $output;
623 }
624
625 function dba_edit_row_submit($form_id, $form_values) {
626 if (user_access('dba administer database')) {
627 $key = $form_values['key'];
628 $keyid = $form_values['keyid'];
629 $table = $form_values['table'];
630 $fields = dba_get_fields($table);
631 foreach ($fields as $field) {
632 if ($field != $key) {
633 $value = "{$form_values[$field]}";
634 if (isset($query)) {
635 $query .= ", $field = '". db_escape_string($value) ."'";
636 }
637 else {
638 $query = "$field = '". db_escape_string($value) ."'";
639 }
640 }
641 }
642 // @todo Manual prefixing
643 $query = "UPDATE $table SET $query WHERE $key = '$keyid'";
644 drupal_set_message(check_plain($query));
645 // Use _db_query so we preserve {}'s.
646 _db_query($query);
647 }
648 return "admin/build/database/table/$table/view";
649 }
650
651 function dba_delete_row_submit($form_id, $form_values) {
652 if (user_access('dba administer database')) {
653 $key = $form_values['key'];
654 $keyid = $form_values['keyid'];
655 $table = $form_values['table'];
656 $query = "DELETE FROM $table WHERE $key = '$keyid'";
657 drupal_set_message(check_plain($query));
658 $query = "DELETE FROM %s WHERE %s = '%s'";
659 db_query($query, $table, $key, $keyid);
660 }
661 return "admin/build/database/table/$table/view";
662 }
663
664 function dba_table_overview($table) {
665 $rows = array();
666 $tables = dba_get_active_tables();
667 $quantity = count($tables);
668 if ($quantity == 1) {
669 drupal_set_title(t('View table %table', array('%table' => $table)));
670
671 if (user_access('dba administer database')) {
672 $primary = dba_get_primary_key($table);
673 }
674 else {
675 $primary = NULL;
676 }
677
678 $fields = dba_get_fields($table);
679 foreach ($fields as $field) {
680 $header[] = array('data' => "$field", 'field' => "$field");
681 }
682
683 $sql = "SELECT * FROM {$table}";
684 $sql .= tablesort_sql($header);
685 $result = pager_query($sql, 20);
686
687 if (!is_null($primary)) {
688 $header[] = t('actions');
689 }
690
691 if (db_num_rows($result)) {
692 while ($row = db_fetch_array($result)) {
693 $line = array_map('check_plain', array_values($row));
694 if (!is_null($primary)) {
695 $id = "{$row[$primary]}";
696 $id = str_replace('/', '__2F_', $id);
697 $actions = '['. l(t('edit'), "admin/build/database/table/$table/view/edit/$primary/$id") .']';
698 $actions .= ' ['. l(t('delete'), "admin/build/database/table/$table/view/delete/$primary/$id") .']';
699 $line[] = $actions;
700 }
701 $rows[] = $line;
702 unset($line);
703 }
704 if ($pager = theme('pager', NULL, 20, 0)) {
705 $rows[] = array(array('data' => $pager, 'colspan' => sizeof($fields)));
706 }
707 $output = theme('table', $header, $rows);
708 }
709 else {
710 $output = t('The table is empty.');
711 }
712 }
713 else {
714 drupal_set_message(t('Unable to view more than one table at a time.'), 'error');
715 $output .= dba_database_overview_form();
716 }
717 return $output;
718 }
719
720 function dba_get_primary_key($table) {
721 if (_is_mysql()) {
722 $rows = array();
723 $tables = dba_get_active_tables();
724 $quantity = count($tables);
725 if ($quantity == 1) {
726 $result = dba_describe_table($table, FALSE);
727 while ($row = db_fetch_array($result)) {
728 if ($row['Key'] == "PRI") {
729 return ($row['Field']);
730 }
731 }
732 }
733 else {
734 drupal_set_message(t('Unable to return the primary key for more than one table at a time.'), 'error');
735 }
736 }
737 else {
738 // Not MySQL, so currently unsupported.
739 return;
740 }
741 return;
742 }
743
744 function dba_table_describe($table) {
745 $rows = array();
746 $tables = dba_get_active_tables();
747 $quantity = count($tables);
748 if ($quantity == 1) {
749 drupal_set_title(t('Describe table %table', array('%table' => $table)));
750 $result = dba_describe_table($table);
751 while ($row = db_fetch_array($result)) {
752 if (!$header) {
753 $header = array_keys($row);
754 }
755 $rows[] = (array)($row);
756 }
757 return (theme('table', $header, $rows));
758 }
759 else {
760 drupal_set_message(t('Unable to describe more than one table at a time.'), 'error');
761 $output .= dba_database_overview();
762 }
763 return $output;
764 }
765
766 function dba_query_form() {
767 // Now, add a text area for the admin to enter a query.
768 $form['query'] = array('#type' => 'fieldset', '#title' => t('Query'));
769 $form['query']['dba_query'] = array(
770 '#type' => 'textarea',
771 '#title' => t('Database query'),
772 '#cols' => 70, '#rows' => 10,
773 '#description' => t('Enter the text of your database query. This will be executed directly in your database, so the action can not be undone. Do not wrap your tables in {}, as direct database queries do not support Drupal\'s database prefixing. If you are using a database prefix, you will need to manually include the prefix in your table name. Separate multiple queries with a ";". A sample query: \'SELECT COUNT(*) FROM accesslog;\''),
774 );
775 $form['query']['actions'] = array(
776 '#prefix' => '<div class="container-inline">',
777 '#suffix' => '</div>',
778 );
779 $form['query']['actions']['submit'] = array(
780 '#type' => 'submit',
781 '#value' => t('Execute query'),
782 );
783 $form['query']['actions']['cancel'] = array(
784 '#type' => 'markup',
785 '#value' => l(t('Cancel'), 'admin/database'),
786 );
787
788 // We use #pre_render, so that we can safely use validated form values
789 // to see if a query has been entered, and if so, run the query and
790 // dynamically generate other form elements to display the results.
791 $form['#pre_render'][] = 'dba_query_form_pre_render';
792
793 // We don't want to get redirected, which would run the queries twice.
794 $form['#redirect'] = false;
795
796 return $form;
797 }
798
799 function dba_query_form_pre_render($form_id, &$form) {
800 // If there are no validation errors and there's already a query,
801 // run it and display the results.
802 $dba_query = $form['query']['dba_query']['#value'];
803 if (!form_get_errors() && !empty($dba_query)) {
804 // Execute each sql statement individually.
805 $i = 0;
806 foreach (explode(';', $dba_query) as $sql) {
807 $header = NULL;
808 if (trim($sql) == '') {
809 break;
810 }
811 $result = dba_execute_query($sql);
812 if ($result && $result != 1 && db_num_rows($result)) {
813 while ($row = db_fetch_array($result)) {
814 if (!$header) {
815 $header = array_map('check_plain', array_keys($row));
816 }
817 $rows[] = array_map('check_plain', array_values($row));
818 }
819 }
820 if (!empty($rows)) {
821 $form['results'][$i] = array(
822 '#type' => 'fieldset',
823 '#title' => t('Result') .': '. theme_placeholder($sql),
824 );
825 $form['results'][$i]['result'] = array(
826 '#value' => theme('table', $header, $rows),
827 );
828 unset($rows);
829 }
830 $i++;
831 }
832 $form['results'] = form_builder('dba_query_form', $form['results']);
833 }
834 }
835
836 function dba_execute_query($sql) {
837 if (user_access('dba administer database')) {
838 return _db_query($sql);
839 }
840 }
841
842 function dba_run_script() {
843 if (user_access('dba administer database')) {
844 $form['script'] = array('#type' => 'fieldset', '#title' => 'Script');
845 $form['script']['script_filename'] = array(
846 '#type' => 'file', '#title' => t('Select a script'),
847 '#description' => t('Click the "browse" button to select a database script from your local computer.')
848 );
849 $form['script']['verbose'] = array(
850 '#type' => 'checkbox',
851 '#title' => t('Verbose'),
852 '#return_value' => 1,
853 '#default_value' => 0,
854 '#description' => t('Check this box if you wish to see all queries that are run.'),
855 );
856 $form['script']['submit'] = array(
857 '#type' => 'submit',
858 '#value' => t('Run script'),
859 );
860 $form['#attributes'] = array('enctype' => 'multipart/form-data');
861 }
862 return $form;
863 }
864
865 function dba_run_script_submit($form_id, $form_values) {
866 if ($file = file_save_upload('script_filename')) {
867 // File is now in temporary directory.
868 if (file_exists($file->filepath)) {
869 if ($fp = fopen($file->filepath, 'r')) {
870 $query = NULL;
871 $count = 0;
872 while (!feof($fp)) {
873 $line = fgets($fp, 8192);
874 if ($line && strncmp($line, '--', 2) && strncmp($line, '#', 1)) {
875 $query .= $line;
876 if (strpos($line, ';')) {
877 if (db_query($query, FALSE)) {
878 if ($form_values['verbose']) {
879 drupal_set_message(check_plain($query));
880 }
881 $count++;
882 }
883 else {
884 drupal_set_message(t('Query failed: %query', array('%query' => $query)), 'error');
885 }
886 $query = NULL;
887 }
888 }
889 }
890 fclose ($fp);
891 drupal_set_message(t('Succesfully ran !query from script %filename.', array('!query' => format_plural($count, '1 query', '@count queries'), '%filename' => $file->filename)));
892 }
893 else {
894 drupal_set_message(t('Unable to open script %filename.', array('%filename' => $file->filename)), 'error');
895 }
896 file_delete($file->filepath);
897 }
898 else {
899 drupal_set_message(t('Script %filename does not exist.', array('%filename' => $file->filename)), 'error');
900 }
901 // Cleanup session.
902 unset($_SESSION['file_uploads'][$file->source]);
903 }
904 }
905
906 function dba_check_tables_form() {
907
908 // Setup a form value to remember what table(s) we're operating on.
909 $form['check_tables']['tables']['#type'] = 'hidden';
910 // First, see if we what the active table is, based solely on
911 // $_SESSION and the URL.
912 $tables = dba_get_active_tables(0);
913 unset($_SESSION['dba_tables']);
914 if (!empty($tables)) {
915 // We already know, so this is easy...
916 $form['check_tables']['tables']['#default_value'] = implode(',', $tables);
917 }
918 else {
919 // It must be in the form values, then. In this case, we need to
920 // call form_builder() to safely grab the data out of $_POST.
921 $form['check_tables'] = form_builder('dba_check_tables', $form['check_tables']);
922 if (!empty($form['check_tables']['tables']['#value'])) {
923 $form_tables = explode(',', $form['check_tables']['tables']['#value']);
924 $tables = dba_get_active_tables(0, $form_tables);
925 }
926 }
927 // Make sure we have something to do.
928 if (empty($tables)) {
929 drupal_set_message(t('You must select the tables to check.'), 'error');
930 drupal_goto('admin/build/database');
931 }
932
933 $form['check_options'] = array(
934 '#type' => 'fieldset',
935 '#title' => t('Actions'),
936 );
937 $form['check_options']['check_type'] = array(
938 '#type' => 'radios',
939 '#title' => t('Check type'),
940 '#default_value' => variable_get('dba_default_check_type', 'MEDIUM'),
941 '#options' => array(
942 'QUICK' => t('Quick'),
943 'FAST' => t('Fast'),
944 'CHANGED' => t('Changed'),
945 'MEDIUM' => t('Medium'),
946 'EXTENDED' => t('Extended')
947 ),
948 );
949 $form['check_options']['check'] = array(
950 '#type' => 'submit',
951 '#value' => t('Check again')
952 );
953
954 // Most of the interesting stuff in this form has to be added via
955 // #pre_render, so that we can safely use validated form values to
956 // dynamically generate other form elements. In particular, we add
957 // a fieldset with the results of whatever operation we perform, and
958 // depending on the admin settings and the state of the tables, we
959 // might need to add a 'Repair' button, too.
960 $form['#pre_render'][] = 'dba_check_table_form_pre_render';
961
962 // We don't want to get redirected, which would run the queries twice.
963 $form['#redirect'] = false;
964
965 return $form;
966 }
967
968 function dba_check_table_form_pre_render($form_id, &$form) {
969 if (form_get_errors()) {
970 // If there's a validation error (e.g. #token is wrong), return
971 // immediately since we can't trust $form.
972 return;
973 }
974
975 $action = isset($_POST['op']) ? $_POST['op'] : 'check';
976 $type = $form['check_options']['check_type']['#value'];
977 $tables = explode(',', $form['check_tables']['tables']['#value']);
978
979 if ($action == t('Repair')) {
980 drupal_set_title(t('Performing table repair.'));
981 $result = dba_repair_tables($tables);
982 }
983 else {
984 drupal_set_title(t('Performing %type table check', array('%type' => check_plain($type))));
985 $result = dba_check_tables($tables, $type);
986 }
987
988 // Construct the output of the operation as a table, and see if any
989 // tables need to be repaired.
990 $repair = array();
991 $header = array(
992 t('Table'),
993 t('Operation'),
994 t('Message type'),
995 t('Message text'),
996 );
997 while ($row = db_fetch_object($result)) {
998 $rows[] = (array)($row);
999 if ($row->Msg_type == 'status') {
1000 $status = $row->Msg_text;
1001 if ($status != 'OK' && $status != 'Table is already up to date') {
1002 // An error message will result if we use the database name when trying
1003 // to repair a table and the database has '-' in the name, so to be
1004 // safe we strip off the database name.
1005 $repair_table = explode('.', $row->Table);
1006 $repair[] = $repair_table[1];
1007 }
1008 }
1009 }
1010 $output .= theme('table', $header, $rows);
1011
1012 if ($repair) {
1013 $output .= '<h3>'. t('One or more tables need repairs.') .'</h3>';
1014 $to_repair = 1;
1015 }
1016 else {
1017 $output .= '<h3>'. t('No repairs are required.') .'</h3>';
1018 $to_repair = 0;
1019 }
1020
1021 $form['check_results'] = array(
1022 '#type' => 'fieldset',
1023 '#title' => t('Result'),
1024 '#weight' => -5,
1025 );
1026 $form['check_results']['result'] = array(
1027 '#type' => 'markup',
1028 '#value' => $output,
1029 );
1030 // Since we just added these form elements, but we're already at the
1031 // pre_render stage, we need to manually invoke form_builder() so that
1032 // these elements are rendered in the final form show to the user.
1033 $form['check_results'] = form_builder('dba_check_tables', $form['check_results']);
1034
1035 if (user_access('dba administer database')) {
1036 $repair_option = variable_get('dba_repair', 0);
1037 if (($repair_option == 0 && $to_repair) || $repair_option == 1) {
1038 $form['check_options']['repair'] = array(
1039 '#type' => 'submit',
1040 '#value' => t('Repair'),
1041 '#weight' => 20,
1042 );
1043 if (!$repair_option) {
1044 $form['check_options']['repair_tables'] = array(
1045 '#type' => 'hidden',
1046 '#value' => implode(',', $repair_tables),
1047 );
1048 }
1049 }
1050