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

Contents of /contributions/modules/patterns/patterns.module

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


Revision 1.3 - (show annotations) (download) (as text)
Wed Nov 26 05:05:30 2008 UTC (12 months ago) by vaish
Branch: MAIN
CVS Tags: HEAD
Changes since 1.2: +75 -219 lines
File MIME type: text/x-php
Update patterns UI and pattern files.
1 <?php
2 // $Id: patterns.module,v 1.2 2008/10/13 21:35:20 sarvab Exp $
3
4 /**
5 * @file
6 * Enables extremely simple adding/removing features to your site with minimal to no configuration
7 */
8
9 /**
10 * @todo:
11 * @ Enable pattern configurations
12 * @ **done**Enable actions to see ids created/updated from other actions inside the pattern (tokens?)
13 * @ **semi-done** Reset patterns
14 * @ Enable components to analyze the current pattern for better validation
15 * @ Allow module version restricting
16 * @ Put in functionality to auto-download modules (and the correct version)
17 * @ Enable backups before running patterns and reverting back to those backups
18 * @ Implement a progress meter
19 * @ Handle default values better to allow for absolute minimal actions
20 * @ Enable interactive actions by allowing patterns to resume from a saved position
21 * @ Implement an export feature for all available form_ids
22 * @ Allow resuming failed patterns
23 * @ In the pattern details, list any sub-patterns directly on the patterns listing page
24 */
25
26 /**
27 * Implementation of hook_perm().
28 */
29 function patterns_perm() {
30 return array('administer patterns');
31 }
32
33 /**
34 * Implementation of hook_menu().
35 */
36 function patterns_menu($may_cache) {
37 $items = array();
38
39 if ($may_cache) {
40 $items[] = array('path' => 'admin/build/patterns',
41 'description' => t('Administer patterns available for your site'),
42 'access' => user_access('administer patterns'),
43 'title' => t('Patterns'),
44 'callback' => 'patterns_list',
45 'type' => MENU_NORMAL_ITEM
46 );
47 $items[] = array('path' => 'admin/build/patterns/list',
48 'title' => t('List'),
49 'callback' => 'patterns_list',
50 'type' => MENU_DEFAULT_LOCAL_TASK,
51 'weight' => -10
52 );
53 // $items[] = array('path' => 'admin/build/patterns/settings',
54 // 'title' => t('Settings'),
55 // 'callback' => 'drupal_get_form',
56 // 'callback arguments' => array('patterns_settings'),
57 // 'type' => MENU_LOCAL_TASK,
58 // 'weight' => 10
59 // );
60 $items[] = array('path' => 'admin/build/patterns/configure',
61 'title' => t('Configure Pattern'),
62 'callback' => 'drupal_get_form',
63 'callback arguments' => array('patterns_configure_pattern'),
64 'type' => MENU_CALLBACK
65 );
66 $items[] = array('path' => 'admin/build/patterns/edit',
67 'title' => t('Edit Pattern'),
68 'callback' => 'drupal_get_form',
69 'callback arguments' => array('patterns_edit'),
70 'type' => MENU_CALLBACK
71 );
72 // $items[] = array('path' => 'admin/build/patterns/info',
73 // 'title' => t('Pattern Details'),
74 // 'callback' => 'patterns_info',
75 // 'type' => MENU_CALLBACK
76 // );
77 $items[] = array('path' => 'admin/build/patterns/enable',
78 'access' => user_access('administer patterns'),
79 'title' => t('Enable Pattern'),
80 'callback' => 'drupal_get_form',
81 'callback arguments' => array('patterns_enable_pattern'),
82 'type' => MENU_CALLBACK
83 );
84 $items[] = array('path' => 'admin/build/patterns/disable',
85 'access' => user_access('administer patterns'),
86 'title' => t('Disable Pattern'),
87 'callback' => 'drupal_get_form',
88 'callback arguments' => array('patterns_disable_pattern'),
89 'type' => MENU_CALLBACK
90 );
91 $items[] = array('path' => 'admin/build/patterns/modules',
92 'access' => user_access('administer patterns'),
93 'title' => t('Pattern Modules'),
94 'callback' => 'patterns_modules_page',
95 'type' => MENU_CALLBACK
96 );
97 $items[] = array('path' => 'admin/build/patterns/revert',
98 'access' => user_access('administer patterns'),
99 'title' => t('Revert Pattern'),
100 'callback' => 'patterns_revert',
101 'type' => MENU_CALLBACK
102 );
103 $items[] = array('path' => 'admin/build/patterns/import',
104 'access' => user_access('administer patterns'),
105 'title' => t('Import'),
106 'callback' => 'drupal_get_form',
107 'callback arguments' => array('patterns_import_source'),
108 'type' => MENU_LOCAL_TASK
109 );
110 $items[] = array('path' => 'admin/build/patterns/import/xmltext',
111 'access' => user_access('administer patterns'),
112 'title' => t('Import via XML Source'),
113 'type' => MENU_DEFAULT_LOCAL_TASK
114 );
115 $items[] = array('path' => 'admin/build/patterns/import/xmlfile',
116 'access' => user_access('administer patterns'),
117 'title' => t('Import via XML File'),
118 'callback' => 'drupal_get_form',
119 'callback arguments' => array('patterns_import_file'),
120 'type' => MENU_LOCAL_TASK
121 );
122 $items[] = array('path' => 'admin/build/patterns/import/xmlurl',
123 'access' => user_access('administer patterns'),
124 'title' => t('Import via XML URL'),
125 'callback' => 'drupal_get_form',
126 'callback arguments' => array('patterns_import_url'),
127 'type' => MENU_LOCAL_TASK
128 );
129 // $items[] = array('path' => 'admin/build/patterns/import/server',
130 // 'access' => user_access('administer patterns'),
131 // 'title' => t('Import via Patterns Server'),
132 // 'callback' => 'drupal_get_form',
133 // 'callback arguments' => array('patterns_import_server'),
134 // 'type' => MENU_LOCAL_TASK
135 // );
136 }
137
138 return $items;
139 }
140
141 /**
142 * Display the import pattern form
143 */
144 function patterns_import_source() {
145 $form['xmlname'] = array(
146 '#type' => 'textfield',
147 '#title' => t('Pattern Identifier'),
148 '#description' => t('Machine readable name for the pattern. The actual title should be included in the pattern itself.'),
149 '#required' => true
150 );
151 $form['xmlsource'] = array(
152 '#type' => 'textarea',
153 '#rows' => 15,
154 '#title' => t('Enter Pattern XML'),
155 '#description' => t('Imported patterns are not executed until you run them manually. You can leave off the <?xml> tag at the top.')
156 );
157 $form['submit'] = array(
158 '#type' => 'submit',
159 '#value' => t('Submit')
160 );
161 $form['#base'] = 'patterns_import';
162
163 return $form;
164 }
165
166 /**
167 * Display the pattern settings form
168 */
169 // function patterns_settings() {
170 // drupal_set_message('This feature not implemented yet.');
171 // }
172
173 /**
174 * Display the import pattern from server form
175 */
176 // function patterns_import_server() {
177 // drupal_set_message('This feature not implemented yet.');
178 // }
179
180 /**
181 * Display the import pattern file form
182 */
183 function patterns_import_file() {
184 $form['#attributes']['enctype'] = 'multipart/form-data';
185 $form['xmlfile'] = array(
186 '#type' => 'file',
187 '#title' => t('Upload XML Pattern File'),
188 '#description' => t('Imported patterns are not executed until you run them manually.'),
189 '#size' => 48
190 );
191 $form['submit'] = array(
192 '#type' => 'submit',
193 '#value' => t('Submit')
194 );
195 $form['#base'] = 'patterns_import';
196
197 return $form;
198 }
199
200 /**
201 * Display the import pattern url form
202 */
203 function patterns_import_url() {
204 $form['xmlurl'] = array(
205 '#type' => 'textfield',
206 '#title' => t('Specify an XML url'),
207 '#description' => t('Import a pattern from a remote URL. Imported patterns are not executed until you run them manually.'),
208 '#size' => 48
209 );
210 $form['submit'] = array(
211 '#type' => 'submit',
212 '#value' => t('Submit')
213 );
214 $form['#base'] = 'patterns_import';
215
216 return $form;
217 }
218
219 function patterns_import_validate($form_id, $values) {
220 global $form_values;
221
222 if ($file = file_check_upload('xmlfile')) {
223 $form_values['xmlsource'] = file_get_contents($file->filepath);
224 }
225 else if (isset($values['xmlfile'])) {
226 form_set_error('xmlfile', t('Error uploading XML file.'));
227 }
228 else if ($values['xmlurl']) {
229 if (!ini_get('allow_url_fopen')) {
230 form_set_error('xmlsource', t('allow_url_fopen must be enabled in your php configuration in order to use this feature.'));
231 return;
232 }
233
234 if (!($form_values['xmlsource'] = file_get_contents($values['xmlurl']))) {
235 form_set_error('xmlsource', t('Failed to retreive the pattern specified.'));
236 return;
237 }
238
239 $form_values['xmlname'] = preg_replace('/\.[^\.]*$/', '', basename($form_values['xmlurl']));
240 }
241
242 if (strpos($form_values['xmlsource'], '<?xml') !== 0) {
243 $form_values['xmlsource'] = '<?xml version="1.0" encoding="ISO-8859-1"?>' . $form_values['xmlsource'];
244 }
245
246 if ($form_values['xmlname'] && preg_match('/[^a-zA-Z0-9_]/', $form_values['xmlname'])) {
247 form_set_error('xmlname', t('You can only include letters, numbers, and underscores in the pattern identifier.'));
248 }
249 else if ($form_values['xmlname'] && preg_match('/^_/', $form_values['xmlname'])) {
250 form_set_error('xmlname', t('You cannot start the pattern identifier with an underscore.'));
251 }
252
253
254 $parse = xml_parser_create();
255 xml_parse_into_struct($parse, $form_values['xmlsource'], $vals, $index);
256
257 // Check that the xml was properly parsed and also that the
258 // root <pattern> tag and also an <info> tag were used.
259 if (!$vals || $vals[0]['tag'] != 'PATTERN' || $vals[1]['tag'] != 'INFO') {
260 form_set_error('xmlsource', t('Error parsing the XML, please check your syntax and try again.'));
261 }
262 }
263
264 function patterns_import_submit($form_id, $form_values) {
265 if ($file = file_check_upload('xmlfile')) {
266 // Currently nothing is happening with files saved here....
267 if (file_check_directory(file_create_path(variable_get('patterns_save_xml', 'patterns')))) {
268 file_save_upload('xmlfile', variable_get('patterns_save_xml', 'patterns'));
269 }
270 else {
271 drupal_set_message(t('Pattern was registered but the file was not saved on the server because of improperly setup files directory.'));
272 }
273 }
274 else if ($form_values['xmlsource']) {
275 file_save_data($form_values['xmlsource'], variable_get('patterns_save_xml', 'patterns') .'/'. $form_values['xmlname'] .'.xml', FILE_EXISTS_REPLACE);
276 }
277
278 // Reload patterns
279 patterns_get_patterns(true);
280
281 return 'admin/build/patterns';
282 }
283
284 function patterns_list() {
285
286 drupal_add_css(drupal_get_path('module', 'patterns') .'/patterns.css');
287 drupal_add_js(drupal_get_path('module', 'patterns') .'/patterns.js');
288
289 patterns_load();
290 $patterns = patterns_get_patterns();
291 if (empty($patterns)) return t('No patterns available.');
292
293 // $header = array(t('Title'), t('Status'), t('Version'), t('Public'), t('Actions'));
294 $header = array(t('Title'), t('Status'), t('Version'), t('Actions'));
295
296 // List all patterns
297 $rows = array();
298 foreach($patterns as $pid => $pattern) {
299 $actions = array();
300 if (!$pattern->status) {
301 $actions[] = l(t('Run'), 'admin/build/patterns/enable/'. $pid);
302 }
303 else if ($pattern->enabled >= $pattern->updated) {
304 $actions[] = l(t('Re-Run'), 'admin/build/patterns/enable/'. $pid);
305 }
306 else {
307 $actions[] = l(t('Run Update'), 'admin/build/patterns/enable/'. $pid);
308 }
309 $actions[] = l(t('Edit'), 'admin/build/patterns/edit/'. $pid);
310 $actions = implode('&nbsp;&nbsp;', $actions);
311
312 $cells = array();
313 // $title = l($pattern->title, 'admin/build/patterns/info/'. $pid, array('class' => 'pattern-title', 'id' => 'pid-'. $pid));
314 $title = '<span id="pid-'. $pid .'" class="pattern-title">'. $pattern->title .'</span>';
315 // $view_more = '<div>'. t('Clik on pattern title to see more details.') .'</div>';
316 $info = array();
317 $info[] = t('Author: ') . $pattern->info['author'];
318 $info[] = t('Email: ') . $pattern->info['author_email'];
319 $info[] = t('Web: ') . $pattern->info['author_website'];
320 $author = theme('item_list', $info);
321 $title .= '<div id="pid-'. $pid .'-info" class="pattern-info">'. $author . $pattern->description . $view_more .'</div>';
322 $cells[] = $title;
323 $cells[] = $pattern->status ? t('Enabled') : t('Disabled');
324 $cells[] = $pattern->info['version'];
325 // $cells[] = $pattern->public ? t('Yes') : t('No');
326 $cells[] = $actions;
327 $category = $pattern->info['category'] ? $pattern->info['category'] : t('Other');
328 $rows[$category][] = $cells;
329 }
330
331 ksort($rows);
332 foreach ($rows as $title => $category) {
333 $fieldset = array(
334 '#title' => t($title),
335 '#collapsible' => TRUE,
336 '#collapsed' => FALSE,
337 '#value' => theme('table', $header, $category),
338 );
339 $output .= theme('fieldset', $fieldset);
340 }
341
342 return $output;
343 }
344
345 /**
346 * Menu callback to undo a patterns update changes
347 */
348 function patterns_revert($pid) {
349 if ($name = db_result(db_query('SELECT name FROM {patterns} WHERE pid = "%d"', $pid))) {
350 $path = file_create_path(variable_get('patterns_save_xml', 'patterns') .'/enabled/'. $name .'.xml');
351 $new = db_result(db_query('SELECT file FROM {patterns} WHERE pid = "%d"'));
352 }
353 else {
354 drupal_set_message(t('The pattern you specified does not exist.'), 'error');
355 drupal_goto('admin/build/patterns');
356 }
357
358 if (file_exists($path)) {
359 if (file_move($path, $new, FILE_EXISTS_REPLACE)) {
360 drupal_set_message(t('This pattern was reverted to the state it was at when it was enabled.'));
361 drupal_goto();
362 }
363 }
364 else {
365 drupal_set_message(t('The patterns enabled-state was not saved properly, therefore it cannot be reverted.'), 'error');
366 }
367
368 drupal_goto('admin/build/patterns');
369 }
370
371 /**
372 * Menu callback to display patterns details page
373 */
374 // function patterns_info($pid = null) {
375 // if (!is_numeric($pid)) {
376 // drupal_set_message(t('You must specify a pattern.'));
377 // return;
378 // }
379 //
380 // $pattern = patterns_get_pattern($pid);
381 //
382 // $output = '';
383 // return $output;
384 // }
385
386 /**
387 * Menu callback to edit a patterns data
388 */
389 function patterns_edit($pid = null) {
390 if (!is_numeric($pid)) {
391 drupal_set_message(t('You must specify a pattern to edit.'));
392 return;
393 }
394
395 $pattern = patterns_get_pattern($pid);
396
397 // TODO: Turn php into xml here.
398
399 // For now just allow modifying the original xml, which
400 // means the modification cannot be further modified
401
402 if (!$pattern->file) {
403 drupal_set_message(t('This pattern does not seem to have an XML source file to base the modifications off of.'), 'error');
404 return;
405 }
406
407 $xml = file_get_contents($pattern->file);
408
409 $form['name'] = array(
410 '#type' => 'value',
411 '#value' => $pattern->name
412 );
413 $form['pid'] = array(
414 '#type' => 'value',
415 '#value' => $pattern->pid
416 );
417 if ($pattern->enabled <= $pattern->updated) {
418 $form['revert'] = array(
419 '#type' => 'markup',
420 '#value' => l(t('Undo update changes to the state when you enabled the pattern.'), 'admin/build/patterns/revert/'. $pid, array(), drupal_get_destination())
421 );
422 }
423 $form['xml'] = array(
424 '#type' => 'textarea',
425 '#title' => t('Pattern\'s XML'),
426 '#rows' => 25,
427 '#default_value' => $xml
428 );
429 $form['submit'] = array(
430 '#type' => 'submit',
431 '#value' => t('Submit')
432 );
433
434 return $form;
435 }
436
437 /**
438 * Validate pattern modifications (make sure proper XML)
439 */
440 function patterns_edit_validate($form_id, $form_values) {
441 // Do validations....
442 $path = file_create_path(variable_get('patterns_save_xml', 'patterns'));
443
444 if (!file_check_directory($path, true)) {
445 form_set_error('form_token', t('Unable to create @path to save the new pattern to.', array('@path' => $path)));
446 }
447 }
448
449 /**
450 * Submit edits to the pattern
451 */
452 function patterns_edit_submit($form_id, $form_values) {
453 // If this is an enabled pattern, make sure the enabled pattern is saved in its current state
454 if ($file = db_result(db_query('SELECT file FROM {patterns} WHERE status = 1 AND name = "%s"', $form_values['name']))) {
455 $dir = file_directory_path() .'/'. variable_get('patterns_save_xml', 'patterns') .'/enabled';
456 file_check_directory($dir, true);
457 $path = $dir .'/'. $form_values['name'] .'.xml';
458
459 if (!file_exists($path)) {
460 file_copy($file, $path, FILE_EXISTS_ERROR);
461 }
462 }
463
464 // Save the new pattern into the pattern files dir.
465 $path = file_directory_path() .'/'. variable_get('patterns_save_xml', 'patterns') .'/'. $form_values['name'] .'.xml';
466
467 file_save_data($form_values['xml'], $path, FILE_EXISTS_REPLACE);
468
469 $old = db_result(db_query('SELECT file FROM {patterns} WHERE name = "%s"', $form_values['name']));
470
471 // Load and save pattern
472 if ($pattern = patterns_load_xml($path)) {
473 if ($old) {
474 db_query('UPDATE {patterns} SET file = "%s", updated = "%s" WHERE pid = "%d"', $path, time(), $form_values['pid']);
475 }
476
477 patterns_save_pattern($pattern, $path);
478 }
479
480 drupal_set_message(t('%name was saved.', array('%name' => $form_values['name'])));
481
482 return 'admin/build/patterns';
483 }
484
485 /**
486 * List the modules used by a particular pattern
487 */
488 function patterns_modules_page($pid) {
489 $pattern = patterns_get_pattern($pid);
490
491 drupal_set_title($pattern->title .' '. t('(Pattern Modules)'));
492
493 $modules = patterns_modules($pattern);
494 $modules_info = module_rebuild_cache();
495 $modules_list = module_list();
496
497 // Get module name, whether its to be disabled or enabled,
498 // whether the module is available or not, and whether it is
499 // currently enabled or not
500 foreach($modules as $module) {
501 $row = array();
502 $delete = is_array($module) ? $module['delete'] : false;
503 $module = is_array($module) ? $module['value'] : $module;
504 $available = array_key_exists($module, $modules_info);
505 $enabled = array_key_exists($module, $modules_list);
506 $row[] = $module;
507 $row[] = $available ? t('Yes') : '<span class="alert">'. t('No') .'</span>';
508 $row[] = $enabled ? t('Yes') : '<span class="alert">'. t('No') .'</span>';
509 $row[] = $delete ? t('Delete') : t('Enable');
510 $rows[] = $row;
511
512 if (!$available) {
513 $not_available = true;
514 }
515 }
516
517 if ($not_available) {
518 drupal_set_message(t('Some modules are not availalble, please download them before running this pattern.'), 'error');
519 }
520 else {
521 drupal_set_message(t('All modules required by this module are available. Click !here to run this pattern.', array('!here' => l(t('here'), 'admin/build/patterns/enable/'. $pid))));
522 }
523
524 return theme('table', array(t('Name'), t('Available'), t('Enabled'), t('Pattern Action')), $rows, t('Modules used for this pattern'));
525 }
526
527 function patterns_load() {
528 static $loaded = false;
529
530 if ($loaded) {
531 return;
532 }
533
534 require_once drupal_get_path('module', 'patterns') .'/patterns.inc';
535
536 foreach(file_scan_directory(drupal_get_path('module', 'patterns') .'/components', '.\.inc') as $file) {
537 include_once $file->filename;
538 }
539
540 $loaded = true;
541 }
542
543 function patterns_get_patterns($reset = true) {
544 patterns_load();
545
546 if ($reset || !variable_get('patterns_loaded', false)) {
547 // Get a listing of enabled patterns
548 $enabled = array();
549 $result = db_query('SELECT file FROM {patterns} WHERE status = 1');
550
551 while ($pattern = db_fetch_object($result)) {
552 $enabled[] = $result->file;
553 }
554
555 $path = file_create_path(variable_get('patterns_save_xml', 'patterns'));
556 $priority = array();
557
558 // Get uploaded patterns first
559 if (file_check_directory($path)) {
560 // Check if .htaccess file exists in path, if not insert it
561 _patterns_check_file_dir();
562
563 foreach(file_scan_directory($path, '.\.xml') as $file) {
564 // Don't save enabled pattern backups
565 if (strpos($file->filename, $path .'/enabled/') === 0) {
566 continue;
567 }
568
569 // Set priority so these patterns won't get over-written
570 $priority[] = $file->name;
571
572 // Can't update existing patterns that are enabled
573 if (in_array($file->filename, $enabled)) {
574 continue;
575 }
576
577 // Load and save pattern
578 if ($pattern = patterns_load_xml($file->filename)) {
579 patterns_save_pattern($pattern, $file->filename);
580 }
581 }
582 }
583
584 // Get per-site patterns next
585 foreach(file_scan_directory(conf_path() .'/patterns', '.\.xml') as $file) {
586 // Can't update existing patterns that are enabled
587 if (in_array($file->filename, $enabled) || in_array($file->name, $priority)) {
588 continue;
589 }
590
591 $priority[] = $file->name;
592
593 // Load and save pattern
594 if ($pattern = patterns_load_xml($file->filename)) {
595 patterns_save_pattern($pattern, $file->filename);
596 }
597 }
598
599 // Get profile patterns next
600 global $profile;
601 foreach(file_scan_directory('profiles/'. $profile .'/patterns', '.\.xml') as $file) {
602 // Can't update existing patterns that are enabled
603 if (in_array($file->filename, $enabled) || in_array($file->name, $priority)) {
604 continue;
605 }
606
607 $priority[] = $file->name;
608
609 // Load and save pattern
610 if ($pattern = patterns_load_xml($file->filename)) {
611 patterns_save_pattern($pattern, $file->filename);
612 }
613 }
614
615 // Last get the default patterns
616 foreach(file_scan_directory(drupal_get_path('module', 'patterns') .'/patterns', '.\.xml') as $file) {
617 // Can't update existing patterns that are enabled
618 if (in_array($file->filename, $enabled) || in_array($file->name, $priority)) {
619 continue;
620 }
621
622 $pattern = patterns_load_xml($file->filename);
623
624 // Load and save pattern
625 if ($pattern = patterns_load_xml($file->filename)) {
626 patterns_save_pattern($pattern, $file->filename);
627 }
628 }
629
630 variable_set('patterns_loaded', time());
631 }
632
633 $result = db_query('SELECT * FROM {patterns}');
634
635 while ($pattern = db_fetch_object($result)) {
636 $patterns[$pattern->pid] = $pattern;
637 $data = unserialize($patterns[$pattern->pid]->pattern);
638 $patterns[$pattern->pid]->pattern = $data;
639 foreach ($data as $key => $section) {
640 if ($data[$key]['tag'] == 'info') {
641 $patterns[$pattern->pid]->info = array();
642 array_shift($section);
643 foreach($section as $property) {
644 $patterns[$pattern->pid]->info[$property['tag']] = $property['value'];
645 }
646 }
647 }
648 }
649
650 return $patterns;
651 }
652
653 function patterns_save_pattern($pattern, $xmlpath = '') {
654 $name = basename($xmlpath, '.xml');
655
656 foreach($pattern[0] as $index => $value) {
657 if (is_numeric($index)) {
658 switch($value['tag']) {
659 case 'title':
660 $title = $value['value'];
661 break;
662 case 'description':
663 $description = $value['value'];
664 break;
665 case 'author':
666 $author = $value['value'];
667 break;
668 }
669 }
670 }
671
672 if ($pid = db_result(db_query('SELECT pid FROM {patterns} WHERE name = "%s"', $name))) {
673 $updated = db_result(db_query('SELECT updated FROM {patterns} WHERE pid = "%d"', $pid));
674 if (($new_updated = filemtime(file_create_path($xmlpath))) > $updated) {
675 db_query('UPDATE {patterns} SET pattern = "%s", title = "%s", file = "%s", updated = "%s", description = "%s" WHERE pid = %d', serialize($pattern), $title, $xmlpath, $new_updated, $description, $pid);
676 }
677 else {
678 db_query('UPDATE {patterns} SET pattern = "%s", title = "%s", file = "%s", description = "%s" WHERE pid = %d', serialize($pattern), $title, $xmlpath, $description, $pid);
679 }
680 }
681 else {
682 $pid = db_next_id('{patterns}_pid');
683 db_query('INSERT INTO {patterns} VALUES(%d, "%s", 0, "%s", "%s", 0, "%s", "%s", "%s")', $pid, $name, $xmlpath, time(), $title, $description, serialize($pattern));
684 }
685 }
686
687 function patterns_get_pattern($id) {
688 if (is_numeric($id)) {
689 $pattern = db_fetch_object(db_query('SELECT * FROM {patterns} WHERE pid = "%d"', $id));
690 }
691 else if (is_string($id)) {
692 $pattern = db_fetch_object(db_query('SELECT * FROM {patterns} WHERE name = "%s"', $id));
693 }
694
695 // Get the actual data. Data is stored in serialized form in the db.
696 $pattern->pattern = unserialize($pattern->pattern);
697
698 // Rearrange the data in a nice way for each component.
699 // Make sure actions are processed differently so order is preserved.
700 $pattern->pattern = patterns_rearrange_data($pattern->pattern);
701
702 return $pattern;
703 }
704
705 function patterns_load_xml($path) {
706 if (!file_exists($path)) {
707 return;
708 }
709
710 $xml = file_get_contents($path);
711
712 $pattern = patterns_from_source($xml);
713
714 return $pattern;
715 }
716
717 /**
718 * Create a pattern from an XML data source
719 */
720 function patterns_from_source($xml) {
721 $parse = xml_parser_create();
722 xml_parse_into_struct($parse, $xml, $vals, $index);
723
724 // Create a multi-dimensional array representing the XML structure
725 $pattern = current(_patterns_parse_tag($vals, 0));
726
727 return $pattern;
728 }
729
730 /**
731 * Recurse through the values of a parsed xml file to create a
732 * multi-dimensional representation of the data.
733 */
734 function _patterns_parse_tag($data, $index) {
735 $pattern = array();
736
737 while ($current = $data[$index]) {
738 $type = $current['type'];
739
740 foreach((array)$current['attributes'] as $key => $value) {
741 $current[strtolower($key)] = $value;
742 }
743
744 $current['tag'] = strtolower($current['tag']);
745
746 unset($current['type'], $current['level'], $current['attributes']);
747
748 if (!trim($current['value']) && $current['value'] != "0") {
749 unset($current['value']);
750 }
751
752 switch($type) {
753 case 'open':
754 $index++;
755 $current += _patterns_parse_tag($data, &$index);
756 $pattern[] = $current;
757 break;
758 case 'close':
759 $index++;
760 return $pattern;
761 break;
762 case 'complete':
763 // In order to support more complex/non-standard features we can use serialized data
764 if ($current['attributes']['serialized']) {
765 $value = unserialize($current['value']);
766
767 if (isset($value)) {
768 $current['value'] = $value;
769 }
770 }
771
772 // This enables tags like <blog /> to turn into array('blog' => 'blog')
773 // which is useful for checkbox/select type forms
774 if (!isset($current['value'])) {
775 $current['value'] = $current['tag'];
776 }
777
778 $pattern[] = $current;
779 break;
780 }
781
782 $index++;
783 }
784
785 return $pattern;
786 }
787
788 function patterns_disable_pattern($pid) {
789 $form['pid'] = array(
790 '#type' => 'value',
791 '#value' => $pid
792 );
793
794 $pattern = patterns_get_pattern($pid);
795
796 return confirm_form($form, t('Proceed with disabling pattern %pattern?', array('%pattern' => $pattern->title)), 'admin/build/patterns', '');
797 }
798
799 function patterns_enable_pattern($pid) {
800 $form['pid'] = array(
801 '#type' => 'value',
802 '#value' => $pid
803 );
804
805 $disclaimer = t('Please be sure to backup your site before running a pattern. Patterns are not guaranteed to be reversable in case they do not execute well or if unforseen side effects occur.');
806
807 $pattern = patterns_get_pattern($pid);
808
809 return confirm_form($form, t('Proceed with running pattern %pattern?', array('%pattern' => $pattern->title)), 'admin/build/patterns', $disclaimer);
810 }
811
812 function patterns_disable_pattern_submit($form_id, $form_values) {
813 $pid = $form_values['pid'];
814 $pattern = patterns_get_pattern($pid);
815
816 if (patterns_execute_pattern($pattern, true, true)) {
817 return 'admin/build/patterns';
818 }
819 }
820
821 function patterns_enable_pattern_submit($form_id, $form_values) {
822 $pid = $form_values['pid'];
823
824 patterns_load();
825 $pattern = patterns_get_pattern($pid);
826
827 if (patterns_execute_pattern($pattern, false, true)) {
828 return 'admin/build/patterns';
829 }
830 }
831
832 function patterns_execute_pattern($pattern, $reverse = false, $interact = false, $jump = null) {
833 patterns_load();
834 set_time_limit(0);
835
836 if (!is_object($pattern)) {
837 $pattern = patterns_get_pattern($pattern);
838
839 if (!$pattern) {
840 return false;
841 }
842 }
843
844 $errors = $action_list = $identifiers = array();
845 $error = true;
846
847 // Pattern info
848 $status = $pattern->status;
849 $title = $pattern->title;
850 $pid = $pattern->pid;
851
852 // From here on out just need the actual pattern data
853 $pattern = $pattern->pattern;
854
855 // Split the pattern up into modules and actions. Submit modules as its
856 // own pattern programattically.
857 $modules = $actions = array();
858 for($i=0;$tag=$pattern[$i];$i++) {
859 if ($tag['tag'] == 'modules') {
860 $modules = $tag;
861 }
862 else if ($tag['tag'] == 'actions') {
863 $actions = $tag;
864 unset($actions['tag']);
865 }
866 }
867
868 // If there are no actions or modules, most likely the pattern
869 // was not created correctly.
870 if (empty($actions) && empty($modules)) {
871 drupal_set_message(t('Could not recognize pattern %title, aborting.', array('%title' => $title)), 'error');
872 return true;
873 }
874
875 if ($modules && (!$interact || ($interact && !$jump))) {
876 // Make the modules look like a normal pattern so they can be executed
877 // on their own.
878 $obj = new stdClass();
879 $obj->title = t('Enable/disable %title pattern modules', array('%title' => $title));
880 $obj->status = $status;
881 $obj->pattern = array(array('tag' => 'actions', $modules));
882 $modules = $obj;
883
884 // Modules need to be enabled first so the rest of the pattern
885 // can proceed smoothly.
886 if (!$reverse) {
887 $error = patterns_execute_pattern($modules, $reverse, $interact);
888 module_rebuild_cache();
889 }
890 }
891
892 // Keep a list of what modules handle what tags
893 $tag_modules = patterns_invoke($empty, 'tag modules');
894
895 // If an interactive pattern needs resuming, remove the
896 // already executed actions
897 if ($interact && $jump > 0) {
898 array_splice($actions, 0, $jump);
899 }
900
901 // Prepare actions for validation/processing
902 foreach($actions as $key => $data) {
903 patterns_invoke($actions[$key], 'prepare');
904 }
905
906 // Reverse a pattern for disabling
907 if ($reverse && $status) {
908 $actions = array_reverse($actions);
909
910 foreach($actions as $key => $data) {
911 $continue = patterns_invoke($data, 'reverse');
912
913 if ($continue === false) {
914 drupal_set_message(t('[Error] Disabling of this pattern is not supported at this time.'));
915 return false;
916 }
917
918 $actions[$key] = $data;
919 }
920 }
921
922 // Pre validate tags with their appropriate components
923 foreach($actions as $key => $data) {
924 if (!array_key_exists($data['tag'], $tag_modules)) {
925 $errors[$data['tag']][] = t('Invalid Pattern: <%tag> is not a valid tag', array('%tag' => $data['tag']));
926 }
927 else {
928 $error = patterns_invoke($data, 'pre-validate');
929 if ($error) {
930 $errors[$data['tag']][] = t('Invalid Pattern: !msg', array('!msg' => $error));
931 }
932 }
933 }
934
935 if (count($errors)) {
936 foreach($errors as $error) {
937 drupal_set_message(implode('<br>', $error), 'error');
938 }
939
940 return;
941 }
942
943 // Build and execute a list of actions
944 foreach($actions as $key => $data) {
945 // Prepare actions for processing, ensure smooth pattern executions, and return form ids for execution
946 $return = patterns_invoke($data, 'form_id');
947
948 // If prepare removed the data, dont continue with this action
949 if (!$data || !$return) {
950 continue;
951 }
952
953 if (is_string($return)) {
954 $form_ids = array($return);
955 }
956 else if ($return) {
957 $form_ids = $return;
958 }
959
960 // Build the action
961 foreach($form_ids as $form_id) {
962 $clone = $data;
963 $error = patterns_invoke($clone, 'validate', $form_id);
964
965 if ($error) {
966 if (is_array($error)) {
967 foreach($error as $msg) {
968 drupal_set_message($msg, 'error');
969 }
970
971 $errors[$clone['tag']] = t('Broken Pattern: %msg', array('%msg' => implode('<br>', $error)));
972 }
973 else {
974 drupal_set_message($error, 'error');
975 $errors[$clone['tag']] = t('Broken Pattern: %msg', array('%msg' => $error));
976 }
977
978
979 return;
980 }
981
982 // If tokens are enabled, apply tokens to the action values
983 // before processing
984 if (module_exists('token')) {
985 _patterns_recurse_tokens($clone, $identifiers);
986 //array_walk($clone, '_patterns_replace_tokens', $identifiers);
987 }
988
989 // Get the form data for the action
990 $values = patterns_invoke($clone, 'build', $form_id);
991
992 // Dont execute the action if a string was returned, indicating the pattern component
993 // most likely handled the action on its own and this is the message to display.
994 if (is_string($values)) {
995 drupal_set_message($values);
996 }
997 else {
998 // Get any extra parameters required for the action
999 $params = patterns_invoke($clone, 'params', $form_id, $values);
1000
1001 if (isset($params) && !is_array($params)) {
1002 $params = array($params);
1003 }
1004
1005 // Execute action
1006 patterns_execute_action($form_id, $values, $params);
1007
1008 if (form_get_errors()) {
1009 $descriptions = patterns_invoke($clone, 'actions');
1010
1011 drupal_set_message(t('An error occured running action #%num (%action)', array('%num' => $key+1, '%action' => $descriptions[$form_id])), 'error');
1012 $error = true;
1013 break;
1014 }
1015 }
1016
1017 patterns_invoke($clone, 'cleanup', $form_id);
1018
1019 // Clear the cache in case it causes problems
1020 cache_clear_all();
1021 }
1022 if ($error) {
1023 break;
1024 }
1025
1026 // Get any primary identifiers from the action for further actions to take advantage of
1027 $id = null;
1028 $id = patterns_invoke($clone, 'identifier', $form_id);
1029 if (isset($id)) {
1030 $identifiers[$key+1] = $id;
1031 }
1032 }
1033
1034 if (empty($errors)) {
1035 if ($reverse) {
1036 if ($modules) {
1037 // Modules need to be disabled last so the rest of the pattern
1038 // can reverse itself properly
1039 $error = patterns_execute_pattern($modules, $reverse, $interact);
1040 }
1041
1042 // Mark pattern as disabled
1043 if ($pid) {
1044 db_query('UPDATE {patterns} SET status = 0 WHERE pid = %d', $pid);
1045 }
1046
1047 drupal_set_message(t('Pattern reversed successfully.'));
1048 }
1049 else {
1050 // Mark pattern as enabled
1051 if ($pid) {
1052 db_query('UPDATE {patterns} SET status = 1, enabled = "%s" WHERE pid = %d', time(), $pid);
1053 }
1054
1055 drupal_set_message(t('Pattern ran successfully.'));
1056 }
1057 }
1058
1059 return !$error;
1060 }
1061
1062 /**
1063 * Execute an action
1064 */
1065 function patterns_execute_action($form_id, $values, $params) {
1066 // Make sure we always have a clear cache for everything
1067 $result = db_query('SHOW TABLES LIKE "cache_%"');
1068
1069 while ($table = db_fetch_array($result)) {
1070 $table = current($table);
1071 cache_clear_all(null, $table);
1072 }
1073
1074 $args = array($form_id, $values);
1075
1076 if (is_array($params)) {
1077 $args = array_merge($args, $params);
1078 }
1079
1080 patterns_executing(true);
1081
1082 //$form = call_user_func_array('drupal_retrieve_form', $args);
1083 //$form['#post'] = $values;
1084 //$return = drupal_process_form($form_id, $form);
1085
1086 $return = call_user_func_array('drupal_execute', $args);
1087
1088 patterns_executing(false);
1089
1090 return $return;
1091 }
1092
1093 function patterns_executing($b = null) {
1094 static $executing = false;
1095
1096 if (is_bool($b)) {
1097 $executing = $b;
1098 }
1099
1100 return $executing;
1101 }
1102
1103 function patterns_rearrange_data($pattern) {
1104 foreach($pattern as $key => $value) {
1105 if (is_string($key)) {
1106 unset($pattern[$key]);
1107 }
1108 else {
1109 if ($value['tag'] == 'actions') {
1110 $pattern[$key] = patterns_rearrange_data($value);
1111 $pattern[$key]['tag'] = 'actions';
1112 }
1113 else {
1114 $pattern[$key] = _patterns_rearrange_data($value);
1115 }
1116 }
1117 }
1118
1119 return $pattern;
1120 }
1121
1122 /**
1123 * Return a list of modules for a pattern
1124 */
1125 function patterns_modules($pattern) {
1126 $pattern = $pattern->pattern;
1127
1128 for($i=0;$tag=$pattern[$i];$i++) {
1129 if ($tag['tag'] == 'modules') {
1130 unset($tag['tag']);
1131 $modules = $tag;
1132 break;
1133 }
1134 }
1135
1136 return $modules;
1137 }
1138
1139 function patterns_process_modules($modules, $op = 'enable') {
1140 // Enable at the beginning of the pattern. Disable at the end.
1141 for($i=0;$module=$modules[$i];$i++) {
1142 if (($op == 'enable' && $module['delete']) || ($op == 'disable' && !$module['delete'])) {
1143 unset($modules[$i]);
1144 }
1145 }
1146
1147 patterns_invoke($empty, 'tag modules');
1148 $error = patterns_invoke($modules, 'pre-validate');
1149
1150 // Error validating modules
1151 if ($error) {
1152 return $error;
1153 }
1154
1155 patterns_invoke($modules, 'prepare');
1156 }
1157
1158 function patterns_invoke(&$data, $op, $form_id = null, $a = null) {
1159 static $tag_modules;
1160
1161 if (!is_array($tag_modules) || $op == 'tag modules') {
1162 // Get a list of tags and their modules
1163 foreach(module_implements('patterns') as $module) {
1164 $tags = module_invoke($module, 'patterns', 'tags');
1165
1166 foreach($tags as $tag => $value) {
1167 if (is_array($value)) {
1168 $tag_modules[$tag] = $module;
1169 }
1170 else {
1171 $tag_modules[$value] = $module;