/[drupal]/contributions/modules/demo/demo.admin.inc
ViewVC logotype

Contents of /contributions/modules/demo/demo.admin.inc

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


Revision 1.21 - (show annotations) (download) (as text)
Wed Sep 30 16:56:59 2009 UTC (8 weeks ago) by sun
Branch: MAIN
Changes since 1.20: +16 -28 lines
File MIME type: text/x-php
by sun: Updated for new File API, Database API, Form API, and removed registry.
1 <?php
2 // $Id: demo.admin.inc,v 1.20 2009/07/29 14:01:32 sun Exp $
3
4 /**
5 * @file
6 * Demonstration Site administrative pages
7 */
8
9 /**
10 * Current version of SQL dump structure.
11 */
12 define('DEMO_DUMP_VERSION', '1.1');
13
14 function demo_admin_settings($form, &$form_state) {
15 global $base_url;
16
17 $form['status'] = array(
18 '#type' => 'fieldset',
19 '#title' => t('Status'),
20 '#collapsible' => FALSE,
21 );
22 if (variable_get('demo_reset_last', 0)) {
23 $reset_date = format_date(variable_get('demo_reset_last', 0));
24 }
25 else {
26 $reset_date = t('Never');
27 }
28 $form['status'][] = array(
29 '#markup' => t('<p><strong>Last reset:</strong> !date</p>', array('!date' => $reset_date)),
30 );
31 $form['status'][] = array(
32 '#markup' => t('<p><strong>Default snapshot:</strong> !snapshot</p>', array('!snapshot' => variable_get('demo_dump_cron', t('None')))),
33 );
34
35 $fileconfig = demo_get_fileconfig();
36
37 $form['dump'] = array(
38 '#type' => 'fieldset',
39 '#title' => t('Dump settings'),
40 '#collapsible' => TRUE,
41 '#collapsed' => (variable_get('demo_reset_interval', 0) ? FALSE : TRUE),
42 );
43 $period = drupal_map_assoc(array(0, 1800, 3600, 7200, 10800, 14400, 18000, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
44 $period[0] = t('disabled');
45 $form['dump']['interval'] = array(
46 '#type' => 'select',
47 '#title' => t('Automatically reset site every'),
48 '#default_value' => variable_get('demo_reset_interval', 0),
49 '#options' => $period,
50 '#description' => t('Select how often this demonstration site is automatically reset. Ensure that you have chosen a snapshot for cron runs in <a href="!manage">Manage snapshots</a> first. <strong>Note:</strong> This requires cron to run at least within this interval.', array('!manage' => url('admin/structure/demo/manage'))),
51 );
52
53 $form['dump']['path'] = array(
54 '#type' => 'textfield',
55 '#title' => t('Dump path'),
56 '#default_value' => $fileconfig['path'],
57 '#size' => 30,
58 '#description' => t('Enter a writable directory where dump files of this demonstration site are stored, f.e. %files. The name of this site (e.g. %confpath) is automatically appended to this directory, if required.<br /><br /><strong>Note: For security reasons you should store site dumps outside of the document root of your webspace!</strong>', array('%files' => file_directory_path() . '/demo', '%confpath' => $fileconfig['site'])),
59 );
60 $form[] = array(
61 '#type' => 'submit',
62 '#value' => t('Save'),
63 );
64
65 return $form;
66 }
67
68 function demo_admin_settings_submit($form, &$form_state) {
69 if (!file_check_directory($form_state['values']['path'], FILE_CREATE_DIRECTORY)) {
70 form_set_error('path', t('The snapshot directory %directory could not be created.', array('%directory' => $form_state['values']['path'])));
71 return FALSE;
72 }
73 else {
74 variable_set('demo_dump_path', $form_state['values']['path']);
75 }
76 variable_set('demo_reset_interval', $form_state['values']['interval']);
77 drupal_set_message(t('The configuration options have been saved.'));
78 }
79
80 function demo_manage($form, &$form_state) {
81 $form['dump'] = array(
82 '#type' => 'fieldset',
83 '#title' => t('Available snapshots'),
84 );
85 $form = array_merge_recursive($form, demo_get_dumps());
86 $form[] = array(
87 '#type' => 'submit',
88 '#value' => t('Use for cron runs'),
89 );
90 $form[] = array(
91 '#type' => 'submit',
92 '#value' => t('Delete'),
93 '#submit' => array('demo_manage_delete_submit'),
94 );
95 return $form;
96 }
97
98 function demo_manage_submit($form, &$form_state) {
99 demo_set_default($form_state['values']['filename']);
100 }
101
102 function demo_manage_delete_submit($form, &$form_state) {
103 $form_state['redirect'] = 'admin/structure/demo/delete/' . $form_state['values']['filename'];
104 }
105
106 function demo_delete_confirm($form, &$form_state, $filename) {
107 $fileconfig = demo_get_fileconfig($filename);
108 if (!file_exists($fileconfig['infofile'])) {
109 return drupal_access_denied();
110 }
111
112 $form['filename'] = array(
113 '#type' => 'value',
114 '#value' => $filename,
115 );
116 return confirm_form($form, t('Are you sure you want to delete the snapshot %title?', array('%title' => $filename)), 'admin/structure/demo/manage', t('This action cannot be undone.'), t('Delete'));
117 }
118
119 function demo_delete_confirm_submit($form, &$form_state) {
120 $files = demo_get_fileconfig($form_state['values']['filename']);
121 unlink($files['sqlfile']);
122 unlink($files['infofile']);
123 drupal_set_message(t('Snapshot %title has been deleted.', array('%title' => $form_state['values']['filename'])));
124 $form_state['redirect'] = 'admin/structure/demo/manage';
125 }
126
127 function demo_dump($form, &$form_state) {
128 $form['dump']['filename'] = array(
129 '#title' => t('File name'),
130 '#type' => 'textfield',
131 '#autocomplete_path' => 'demo/autocomplete',
132 '#required' => TRUE,
133 '#maxlength' => 128,
134 '#description' => t('Enter the snapshot file name without file extension. Allowed characters are a-z, 0-9, dashes ("-"), underscores ("_") and dots.'),
135 );
136 $form['dump']['default'] = array(
137 '#title' => t('Set as new default snapshot'),
138 '#type' => 'checkbox',
139 );
140 $form['dump']['description'] = array(
141 '#title' => t('Description'),
142 '#type' => 'textarea',
143 '#rows' => 2,
144 '#description' => t('Optionally enter a description for this snapshot here. If no description is given and a snapshot with the same filename already exists, the previous description is used.'),
145 );
146 return confirm_form($form, t('Are you sure you want to create a new snapshot?'), 'admin/structure/demo', t('If the above filename already exists, creating a new snapshot will overwrite the existing snapshot. This action cannot be undone.'), t('Create'), t('Cancel'));
147 }
148
149 function demo_dump_submit($form, &$form_state) {
150 // Generate info file.
151 $info = demo_set_info($form_state['values']);
152 if (!$info) {
153 return FALSE;
154 }
155
156 // Include database specific functions.
157 $engine = db_driver();
158 $inc_file = drupal_get_path('module', 'demo') . '/database_' . $engine . '_dump.inc';
159 if (file_exists($inc_file)) {
160 require_once $inc_file;
161
162 if (!empty($form_state['values']['default'])) {
163 // Set new default snapshot.
164 demo_set_default($info['filename']);
165 }
166
167 // Increase PHP's max_execution_time for large dumps.
168 @set_time_limit(600);
169
170 // Perform dump.
171 $fileconfig = demo_get_fileconfig($info['filename']);
172 $exclude = array(
173 '{cache}',
174 '{cache_admin_menu}',
175 '{cache_block}',
176 '{cache_content}',
177 '{cache_filter}',
178 '{cache_form}',
179 '{cache_menu}',
180 '{cache_page}',
181 '{cache_path}',
182 '{cache_registry}',
183 '{cache_views}',
184 '{ctools_object_cache}',
185 '{panels_object_cache}',
186 '{views_object_cache}',
187 '{watchdog}',
188 );
189 $exclude = array_map(array('DatabaseConnection', 'prefixTables'), $exclude);
190 demo_dump_db($fileconfig['sqlfile'], $exclude);
191 drupal_set_message(t('Successfully created snapshot %filename.', array('%filename' => $fileconfig['sqlfile'])));
192 }
193 else {
194 drupal_set_message(t('@engine support not implemented yet.', array('@engine' => ucfirst($engine))), 'error');
195 }
196
197 $form_state['redirect'] = 'admin/structure/demo/manage';
198 }
199
200 function demo_reset_confirm($form, &$form_state) {
201 $form['dump'] = array(
202 '#type' => 'fieldset',
203 '#title' => t('Available snapshots'),
204 );
205 $form = array_merge_recursive($form, demo_get_dumps());
206
207 return confirm_form($form, t('Are you sure you want to reset the site?'), 'admin/structure/demo', t('Resetting the site will overwrite all changes that have been made to this Drupal installation since the chosen snapshot.<br /><br /><div style="color: red; font-weight: bold; font-size: 18px;"><center>THIS ACTION CANNOT BE UNDONE!</center><br /></div>'), t('Reset'), t('Cancel'));
208 }
209
210 function demo_reset_confirm_submit($form, &$form_state) {
211 // Increase PHP's max_execution_time for large dumps.
212 @set_time_limit(600);
213
214 // Reset site to chosen snapshot.
215 demo_reset($form_state['values']['filename']);
216 // Save time of last reset.
217 variable_set('demo_reset_last', REQUEST_TIME);
218
219 $form_state['redirect'] = isset($form_state['values']['redirect']) ? $form_state['values']['redirect'] : 'admin/structure/demo';
220 }
221
222 function demo_reset($filename = 'demo_site', $verbose = TRUE) {
223 $fileconfig = demo_get_fileconfig($filename);
224 if (!file_exists($fileconfig['sqlfile']) || !($fp = fopen($fileconfig['sqlfile'], 'r'))) {
225 if ($verbose) {
226 drupal_set_message(t('Unable to open dump file %filename.', array('%filename' => $fileconfig['sqlfile'])), 'error');
227 }
228 watchdog('demo', 'Unable to open dump file %filename.', array('%filename' => $fileconfig['sqlfile']), WATCHDOG_ERROR);
229 return FALSE;
230 }
231
232 // Load any database information in front of reset.
233 $demo_dump_cron = variable_get('demo_dump_cron', $filename);
234 $version = demo_get_info($fileconfig['infofile'], 'version');
235 $is_version_1_0_dump = version_compare($version, '1.1', '<');
236
237 if (file_exists($fileconfig['sqlfile']) && $fp = fopen($fileconfig['sqlfile'], 'r')) {
238 // Drop tables
239 $dt_watchdog = DatabaseConnection::prefixTables('{watchdog}');
240 foreach (demo_enum_tables() as $table) {
241 // Skip watchdog, except for legacy dumps that included the watchdog table
242 if ($table != $dt_watchdog || $is_version_1_0_dump) {
243 db_query("DROP TABLE $table");
244 }
245 }
246
247 // Load data from snapshot
248 $query = '';
249 $success = TRUE;
250 while (!feof($fp)) {
251 $line = fgets($fp, 16384);
252 if ($line && $line != "\n" && strncmp($line, '--', 2) && strncmp($line, '#', 1)) {
253 $query .= $line;
254 if (substr($line, -2) == ";\n") {
255 $options = array(
256 'target' => 'default',
257 'return' => Database::RETURN_NULL,
258 // 'throw_exception' => FALSE,
259 );
260 $stmt = Database::getConnection($options['target'])->prepare($query);
261 if (!$stmt->execute(array(), $options)) {
262 if ($verbose) {
263 // Don't use t() here, as the locale_* tables might not (yet) exist.
264 drupal_set_message(strtr('Query failed: %query', array('%query' => $query)), 'error');
265 }
266 $success = FALSE;
267 }
268 $query = '';
269 }
270 }
271 }
272 }
273 fclose($fp);
274
275 if ($success) {
276 // Allow other modules to act on successful resets.
277 module_invoke_all('demo_reset');
278
279 if ($verbose) {
280 drupal_set_message(t('Successfully restored database from %filename.', array('%filename' => $fileconfig['sqlfile'])));
281 }
282 watchdog('demo', 'Successfully restored database from %filename.', array('%filename' => $fileconfig['sqlfile']), WATCHDOG_NOTICE);
283 }
284 else {
285 if ($verbose) {
286 drupal_set_message(t('Failed restoring database from %filename.', array('%filename' => $fileconfig['sqlfile'])), 'error');
287 }
288 watchdog('demo', 'Failed restoring database from %filename.', array('%filename' => $fileconfig['sqlfile']), WATCHDOG_ERROR);
289 }
290
291 // Reset default dump to load on cron.
292 variable_set('demo_dump_cron', $demo_dump_cron);
293
294 return $success;
295 }
296
297 function demo_get_fileconfig($filename = 'demo_site') {
298 // Build dump path.
299 $fileconfig['path'] = variable_get('demo_dump_path', file_directory_path('private') . '/demo');
300 $fileconfig['dumppath'] = $fileconfig['path'];
301 // Append site name if it is not included in file_directory_path() and if not
302 // storing files in sites/all/files.
303 $fileconfig['site'] = str_replace('sites', '', conf_path());
304 if (strpos($fileconfig['path'], conf_path()) === FALSE && strpos($fileconfig['path'], '/all/') === FALSE) {
305 $fileconfig['dumppath'] .= $fileconfig['site'];
306 }
307
308 // Check if directory exists.
309 file_prepare_directory($fileconfig['path'], FILE_CREATE_DIRECTORY, 'path');
310 if (!file_prepare_directory($fileconfig['dumppath'], FILE_CREATE_DIRECTORY, 'path')) {
311 return FALSE;
312 }
313
314 // Protect dump files.
315 file_create_htaccess($fileconfig['path'], TRUE);
316
317 // Build SQL filename.
318 $fileconfig['sql'] = $filename . '.sql';
319 $fileconfig['sqlfile'] = $fileconfig['dumppath'] . '/' . $fileconfig['sql'];
320
321 // Build info filename.
322 $fileconfig['info'] = $filename . '.info';
323 $fileconfig['infofile'] = $fileconfig['dumppath'] . '/' . $fileconfig['info'];
324
325 return $fileconfig;
326 }
327
328 function demo_get_dumps() {
329 $fileconfig = demo_get_fileconfig();
330
331 // Fetch list of available info files
332 $files = file_scan_directory($fileconfig['dumppath'], '/\.info$/');
333
334 foreach ($files as $file => $object) {
335 $files[$file]->filemtime = filemtime($file);
336 $files[$file]->filesize = filesize(substr($file, 0, -4) . 'sql');
337 }
338
339 // Sort snapshots by date (ascending file modification time)
340 uasort($files, create_function('$a, $b', 'return ($a->filemtime < $b->filemtime);'));
341
342 $options = array();
343 // Forms API does not pass selected value of individual radio buttons,
344 // so we manually insert an internal form value here.
345 $options['dump']['filename'] = array(
346 '#type' => 'value',
347 '#required' => TRUE,
348 '#title' => t('Snapshot'),
349 );
350 foreach ($files as $filename => $file) {
351 // Build basic file info
352 $files[$filename] = (array)$files[$filename];
353 $info = demo_get_info($filename);
354
355 // Convert file info for Forms API
356 $option = array();
357 $option['#type'] = 'radio';
358 $option['#name'] = 'filename';
359 $option['#title'] = $info['filename'] . ' (' . format_date($file->filemtime, 'small') . ', ' . format_size($file->filesize) . ')';
360 $option['#return_value'] = $info['filename'];
361 if ($info['filename'] == variable_get('demo_dump_cron', 'demo_site')) {
362 $option['#value'] = $info['filename'];
363 }
364 $option['#description'] = '';
365 if (!empty($info['description'])) {
366 $option['#description'] .= '<p>' . $info['description'] . '</p>';
367 }
368 $targs = array(
369 '@info-file-url' => url('demo/download/' . $file->name . '/info'),
370 '@sql-file-url' => url('demo/download/' . $file->name . '/sql'),
371 );
372 $option['#description'] .= '<p>' . t('Download: <a href="@info-file-url">.info file</a>, <a href="@sql-file-url">.sql file</a>', $targs) . '</p>';
373 if (count($info['modules']) > 1) {
374 // Remove required core modules and obvious modules from module list.
375 $info['modules'] = array_diff($info['modules'], array('block', 'filter', 'node', 'system', 'user', 'watchdog', 'demo'));
376 // Sort module list alphabetically.
377 sort($info['modules']);
378 $option['#description'] .= t('Modules: ') . implode(', ', $info['modules']);
379 }
380 $option['#attributes'] = array('onclick' => "jQuery('.description', this.parentNode.parentNode).slideToggle();");
381
382 $options['dump'][] = $option;
383 }
384
385 // Attach stylesheet to initially hide descriptions
386 drupal_add_js("jQuery('div.form-item div.description', jQuery('form')).hide();", array('type' => 'inline', 'scope' => 'footer'));
387
388 return $options;
389 }
390
391 function demo_get_info($filename, $field = NULL) {
392 $info = array();
393
394 if (file_exists($filename)) {
395 $info = parse_ini_file($filename);
396
397 if (isset($info['modules'])) {
398 $info['modules'] = explode(" ", $info['modules']);
399 }
400 else {
401 $info['modules'] = NULL;
402 }
403
404 if (!isset($info['version'])) {
405 $info['version'] = '1.0';
406 }
407 }
408
409 if (isset($field)) {
410 return isset($info[$field]) ? $info[$field] : NULL;
411 }
412 else {
413 return $info;
414 }
415 }
416
417 function demo_set_info($values = NULL) {
418 if (isset($values['filename']) && is_array($values)) {
419 // Check for valid filename
420 if (!preg_match('/^[-_\.a-zA-Z0-9]+$/', $values['filename'])) {
421 drupal_set_message(t('Dump filename %title must contain alphanumeric characters, dots, dashes and underscores only. Other characters, including blanks (spaces), are not allowed.', array('%title' => $values['filename'])), 'error');
422 return FALSE;
423 }
424
425 if (!empty($values['description'])) {
426 // parse_ini_file() doesn't allow certain characters in description
427 $s = array("\r\n", "\r", "\n", '"');
428 $r = array(' ', ' ', ' ', "'");
429 $values['description'] = str_replace($s, $r, $values['description']);
430 }
431 else {
432 // If new description is empty, try to use previous description.
433 $old_file = demo_get_fileconfig($values['filename']);
434 $old_description = demo_get_info($old_file['infofile'], 'description');
435 if (!empty($old_description)) {
436 $values['description'] = $old_description;
437 }
438 }
439
440 // Set values
441 $infos = array();
442 $infos['filename'] = $values['filename'];
443 $infos['description'] = '"' . $values['description'] . '"';
444 $infos['modules'] = implode(' ', module_list());
445 $infos['version'] = DEMO_DUMP_VERSION;
446
447 // Write information to .info file
448 $fileconfig = demo_get_fileconfig($values['filename']);
449 $infofile = fopen($fileconfig['infofile'], 'w');
450 foreach ($infos as $key => $info) {
451 fwrite($infofile, $key . ' = ' . $info . "\n");
452 }
453 fclose($infofile);
454
455 return $infos;
456 }
457 }
458
459 /**
460 * Returns a list of tables in the active database.
461 *
462 * Only returns tables whose prefix matches the configured one (or ones, if
463 * there are multiple).
464 */
465 function demo_enum_tables() {
466 global $db_prefix;
467
468 $tables = array();
469
470 if (is_array($db_prefix)) {
471 // Create a regular expression for table prefix matching.
472 $rx = '/^' . implode('|', array_filter($db_prefix)) . '/';
473 }
474 else if ($db_prefix != '') {
475 $rx = '/^' . $db_prefix . '/';
476 }
477
478 switch (db_driver()) {
479 case 'mysql':
480 case 'mysqli':
481 $result = db_query('SHOW TABLES')->fetchCol();
482 break;
483
484 case 'pgsql':
485 $result = db_query('SELECT table_name FROM information_schema.tables WHERE table_schema = :schema', array(':schema' => 'public'))->fetchCol();
486 break;
487 }
488
489 foreach ($result as $table) {
490 if (is_array($db_prefix)) {
491 // Check if table name matches a configured prefix.
492 if (preg_match($rx, $table, $matches)) {
493 $table_prefix = $matches[0];
494 $plain_table = substr($table, strlen($table_prefix));
495 if ($db_prefix[$plain_table] == $table_prefix || $db_prefix['default'] == $table_prefix) {
496 $tables[] = $table;
497 }
498 }
499 }
500 else if ($db_prefix != '') {
501 if (preg_match($rx, $table)) {
502 $tables[] = $table;
503 }
504 }
505 else {
506 $tables[] = $table;
507 }
508 }
509
510 return $tables;
511 }
512
513 /**
514 * Retrieve a pipe delimited string of autocomplete suggestions for existing
515 * snapshot names.
516 */
517 function demo_autocomplete($string = '') {
518 $matches = array();
519 if ($string && $fileconfig = demo_get_fileconfig()) {
520 $string = preg_quote($string);
521 $files = file_scan_directory($fileconfig['dumppath'], '/' . $string . '.*\.info$/');
522 foreach ($files as $file) {
523 $matches[$file->name] = check_plain($file->name);
524 }
525 }
526 drupal_json_output($matches);
527 }
528
529 /**
530 * Transfer (download) a snapshot file.
531 *
532 * @param $filename
533 * The snapshot filename to transfer.
534 * @param $type
535 * The file type, i.e. extension to transfer.
536 *
537 * @todo Allow to download an bundled archive of snapshot files.
538 */
539 function demo_download($filename, $type) {
540 $fileconfig = demo_get_fileconfig($filename);
541 if (!isset($fileconfig[$type . 'file']) || !file_exists($fileconfig[$type . 'file'])) {
542 return MENU_NOT_FOUND;
543 }
544 // Force the client to re-download and trigger a file save download.
545 $headers = array(
546 'Cache-Control: private',
547 'Content-Type: application/octet-stream',
548 'Content-Length: ' . filesize($fileconfig[$type . 'file']),
549 'Content-Disposition: attachment, filename=' . $fileconfig[$type],
550 );
551 file_transfer($fileconfig[$type . 'file'], $headers);
552 }
553
554 /**
555 * Sets a specific snapshot as default for cron runs or the site reset block.
556 *
557 * @param $filename
558 * The filename of the snapshot.
559 */
560 function demo_set_default($filename) {
561 variable_set('demo_dump_cron', $filename);
562 drupal_set_message(t('Snapshot %title will be used for upcoming cron runs.', array('%title' => $filename)));
563 }
564

  ViewVC Help
Powered by ViewVC 1.1.2