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

Contents of /contributions/modules/module_builder/module_builder.module

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


Revision 1.41 - (show annotations) (download) (as text)
Sun Feb 8 22:02:17 2009 UTC (9 months, 2 weeks ago) by joachim
Branch: MAIN
CVS Tags: HEAD
Branch point for: DRUPAL-6--2
Changes since 1.40: +263 -24 lines
File MIME type: text/x-php
a working version for D6! no download or writing yet...
1 <?php
2 // $Id: module_builder.module,v 1.40 2009/02/08 17:38:13 joachim Exp $
3
4 /**
5 * @file
6 * Builds scaffolding for custom modules.
7 */
8
9 /**
10 * Version of hook info to retrieve
11 */
12 define('MODULE_BUILDER_VERSION', 'DRUPAL-6--1');
13
14 /* Constant values extracted from code */
15 /**
16 * Base URL for accessing Drupal CVS.
17 */
18 define('MODULE_BUILDER_CVS_URL', 'http://cvs.drupal.org');
19 /**
20 * URL for retreiving the hook description files.
21 */
22 define('MODULE_BUILDER_HOOKS_URL', MODULE_BUILDER_CVS_URL .'/viewcvs/drupal/contributions/docs/developer/hooks/?pathrev='. MODULE_BUILDER_VERSION);
23 /**
24 * Pattern for finding the hook file URLs from the MODULE_BUILDER_HOOKS_URL page.
25 */
26 define('MODULE_BUILDER_URL_PATTERN', '!<td>\&nbsp;<a href="(.*?)"!');
27 /**
28 * Pattern that captures 3 things about a hook from one of the hook files: Description, Declaraion and Name (in that order).
29 */
30 define('MODULE_BUILDER_EXTRACT_HOOK_PATTERN', '#\/\*\*\n \* (\w.*?)\s+\*\s+\*.*?(function (hook_.*?)\(.*?\) {)#ms');
31 /**
32 * Indexes into MODULE_BUILDER_EXTRACT_HOOK_PATTERN capture list: that returns the hook description from the hook files.
33 */
34 define('MODULE_BUILDER_HOOK_DESCRIPTIONS', 1); // returns the hook description
35 define('MODULE_BUILDER_HOOK_DECLARATIONS', 2); // returns the hook declaration
36 define('MODULE_BUILDER_HOOK_NAMES', 3); // returns the hook name
37 /**
38 * After finding a hook file URL with MODULE_BUILDER_URL_PATTERN, this captures the path and filename.
39 */
40 define('MODULE_BUILDER_FILE_PATTERN', '!(.*)/(.*?)\?!');
41 /**
42 * In case we have an expanded CVS Id, this matches that, and captures the version number (although we don't use that). This is then replaced with MODULE_BUILDER_ID_COMMENT.
43 */
44 define('MODULE_BUILDER_FULL_ID_PATTERN', '#\/\/ \$Id(.*?)\$#');
45 /**
46 * Took this regex from the PHP manual page on Functions
47 */
48 define('MODULE_BUILDER_FUNCTION_PATTERN', '#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*#');
49 /**
50 * Used to strip INFO messages out of generated file for advanced users.
51 */
52 define('MODULE_BUILDER_INFO_PATTERN', '#\s+/\* INFO:(.*?)\*/#ms');
53 /**
54 * Captures a template name and body from a template file.
55 */
56 define('MODULE_BUILDER_TEMPLATE_PATTERN', '#== START (.*?) ==(.*?)== END ==#ms');
57 /**
58 * Path from module base to the normal hook groups template. MODULE_BUILDER_CUSTOM_HOOK_GROUPS_TEMPLATE_PATH used if it exists.
59 */
60 define('MODULE_BUILDER_HOOK_GROUPS_TEMPLATE_PATH', '/templates/hook_groups.template');
61 /**
62 * Path from module base to the custom hook groups template. Only used if it exists, otherwise MODULE_BUILDER_HOOK_GROUPS_TEMPLATE_PATH is used.
63 */
64 define('MODULE_BUILDER_CUSTOM_HOOK_GROUPS_TEMPLATE_PATH', '/template/hook_groups-custom.template');
65 /**
66 * Used to replace a full CVS Id tag with a starter ID.
67 */
68 // The weird syntax stops this from getting mangled by CVS
69 define('MODULE_BUILDER_ID_COMMENT', '// $'.'Id$');
70 /**
71 * Used at the top of the .info file.
72 */
73 // The weird syntax stops this from getting mangled by CVS
74 define('MODULE_BUILDER_INFO_ID_COMMENT', '; $'.'Id$');
75
76
77 /* Default default values for some variables */
78 define('MODULE_BUILDER_HEADER_DEFAULT', '// $'.'Id$
79
80 /**
81 * @file
82 * TODO: Enter file description here.
83 */
84 ');
85
86 /**
87 * @defgroup module_builder_core Core Drupal hooks
88 */
89
90 /**
91 * Implementation of hook_perm().
92 *
93 * @ingroup module_builder_core
94 */
95 function module_builder_perm() {
96 return array('access module builder');
97 }
98
99 /**
100 * Implementation of hook_menu().
101 *
102 * @ingroup module_builder_core
103 */
104 function module_builder_menu() {
105 $items['module_builder'] = array(
106 'title' => 'Module builder',
107 'description' => t('Builds scaffolding for custom modules.'),
108 'page callback' => 'drupal_get_form',
109 'page arguments' => array('module_builder_page'),
110 'access arguments' => array('access module builder'),
111 'type' => MENU_NORMAL_ITEM,
112 );
113 $items['admin/settings/module_builder'] = array(
114 'title' => 'Module builder',
115 'description' => t('Set default header and footer, api download location, defaults for detail and download and force the api to be re-downloaded.'),
116 'page callback' => 'drupal_get_form',
117 'page arguments' => array('module_builder_admin_settings'),
118 'access arguments' => array('access module builder'),
119 'type' => MENU_NORMAL_ITEM,
120 );
121 $items['admin/settings/module_builder/settings'] = array(
122 'title' => 'Settings',
123 'description' => t('Set default header and footer, folder locations, and defaults for detail.'),
124 'page callback' => 'drupal_get_form',
125 'page arguments' => array('module_builder_admin_settings'),
126 'access arguments' => array('access module builder'),
127 'type' => MENU_DEFAULT_LOCAL_TASK,
128 );
129 $items['admin/settings/module_builder/update'] = array(
130 'title' => 'Update hooks',
131 'description' => t('Download hook documentation.'),
132 'page callback' => 'drupal_get_form',
133 'page arguments' => array('module_builder_admin_update'),
134 'access arguments' => array('access module builder'),
135 'type' => MENU_LOCAL_TASK,
136 );
137
138 return $items;
139 }
140
141 /**
142 * Admin settings page.
143 *
144 * @ingroup module_builder_core
145 */
146 function module_builder_admin_settings($form_state) {
147
148 $form['module_builder_hooks_directory'] = array(
149 '#type' => 'textfield',
150 '#title' => t('Path to hook documentation directory'),
151 '#description' => t('Subdirectory in the directory "%dir" where local copies of hook documentation should be stored.', array('%dir' => file_directory_path() .'/')),
152 '#default_value' => variable_get('module_builder_hooks_directory', 'hooks'),
153 );
154
155 $form['module_builder_write_directory'] = array(
156 '#type' => 'textfield',
157 '#title' => t('Path to write module files'),
158 '#description' => t('Subdirectory in the directory "%dir" where module files should be written.', array('%dir' => file_directory_path() .'/')),
159 '#default_value' => variable_get('module_builder_write_directory', 'modules'),
160 );
161
162 $form['module_builder_header'] = array(
163 '#type' => 'textarea',
164 '#title' => t('Module header'),
165 '#description' => t('This is the code that will be displayed at the top of your module file.'),
166 '#rows' => 15,
167 '#default_value' => variable_get('module_builder_header', MODULE_BUILDER_HEADER_DEFAULT),
168 );
169 $form['module_builder_footer'] = array(
170 '#type' => 'textarea',
171 '#title' => t('Module footer'),
172 '#description' => t('This is the code that will be displayed at the bottom of your module file.'),
173 '#rows' => 15,
174 '#default_value' => variable_get('module_builder_footer', ''),
175 );
176 $form['module_builder_detail'] = array(
177 '#type' => 'radios',
178 '#title' => t('Code detail level'),
179 '#description' => t('This setting will either display or suppress additional explanatory comments in the resulting module code to help new developers.'),
180 '#options' => array(
181 1 => t("<strong>Beginner</strong>: I'm just starting out with Drupal development; please display lots of helpful comments in my module code!"),
182 0 => t("<strong>Advanced</strong>: I already know what I'm doing; don't put in a bunch of crap in my module file that I don't need!"),
183 ),
184 '#default_value' => variable_get('module_builder_detail', 0),
185 );
186 /*
187 $form['module_builder_download'] = array(
188 '#type' => 'radios',
189 '#title' => t('Download module file checkbox defaults to'),
190 '#description' => t('When checked, this will automatically generate your module file for you and prompt your browser to download it.'),
191 '#options' => array(
192 1 => t('Enabled'),
193 0 => t('Disabled'),
194 ),
195 '#default_value' => variable_get('module_builder_download', 1),
196 );
197 */
198 return system_settings_form($form);
199 }
200
201 /**
202 * Admin hook update form.
203 */
204 function module_builder_admin_update($form_state) {
205 $last_update = variable_get('module_builder_last_update', 0);
206
207 $form['last_update'] = array(
208 '#type' => 'item',
209 '#value' => $last_update ?
210 t('Your last hook documentation update was %date.', array('%date' => $last_update)) :
211 t('The hook documentation has not yet been downloaded.'),
212 );
213
214 $form['update'] = array(
215 '#type' => 'submit',
216 '#value' => 'Download',
217 );
218
219 return $form;
220 }
221
222 /**
223 * Admin hook update form submit handler.
224 * Download hooks documentation.
225 */
226 function module_builder_admin_update_submit($form, $form_state) {
227 _module_builder_check_settings();
228 module_builder_update_documentation();
229 }
230
231 /**
232 * Create a directory to store hook files if it does not exist.
233 *
234 * This logic blatantly ripped off from image.module -- thanks James! :)
235 */
236 function _module_builder_check_settings() {
237 // sanity check. need to verify /files exists before we do anything. see http://drupal.org/node/367138
238 $files = file_create_path();
239 file_check_directory($files, FILE_CREATE_DIRECTORY);
240 // check hooks directory exists or create it
241 $hooks_path = file_create_path(variable_get('module_builder_hooks_directory', 'hooks'));
242 file_check_directory($hooks_path, FILE_CREATE_DIRECTORY, 'module_builder_hooks_directory');
243 }
244
245 /**
246 * @defgroup module_builder_callback Functions which are the menu callbacks for this module
247 */
248
249 /**
250 * Displays module builder interface via a multi-step form.
251 * The steps are:
252 *
253 * - input => shows a form where the user can enter module options.
254 * - module => shows the generated module and info files.
255 * - download => pushes a file for download.
256 * - write => writes files.
257 *
258 * @ingroup module_builder_callback
259 * @param $form_values will be NULL when the page is first displayed,
260 * when the form is submitted, this will be an array of the submitted
261 * values.
262 * @return
263 * One of three results depending on the state of this multi-step form.
264 * Form for entering module options
265 * Form showing built module and info file
266 * Nothing, but file is pushed to client for download
267 */
268 # fun fun fun!
269 # http://drupal.org/node/144132
270 function module_builder_page($form_state) {
271 $step = 'input';
272 if (isset($form_state['clicked_button'])) {
273 $step = $form_state['clicked_button']['#name'];
274 }
275 #dsm($step);
276 switch ($step) {
277 case 'input':
278 // sanity check first time around
279 _module_builder_check_settings();
280 // fall through
281 case 'back':
282 $form = module_builder_page_input($form_state);
283 break;
284 case 'generate':
285 $form = module_builder_page_generate($form_state);
286 break;
287 case 'download':
288 $form = module_builder_page_download($form_state);
289 break;
290 }
291
292 return $form;
293 }
294
295 // build page 1
296 function module_builder_page_input($form_state) {
297 /*
298 // built some default values.
299 // these are either hardcoded or what the user already put into the form on a previous go round.
300 $form_default_values = array(
301 'module_root_name' => 'mymodule',
302 );
303 if (isset($form_state['storage']['input'])) {
304 $form_default_values = array_merge($form_default_values, $form_state['storage']['input']);
305 }
306
307 #dsm($form_default_values);
308 #dsm($form_state['storage']['input']);
309 dsm($form_state);
310 */
311
312 // sanity check first time around
313 #_module_builder_check_settings(); // moved up to main form builder
314 // Get list of hooks from downloaded documentation, organized in fieldsets.
315 $hook_groups = module_builder_get_hook_data();
316 if (!is_array($hook_groups) || !count($hook_groups)) {
317 form_set_error('hooks', t('No hooks were found. Please check the documentation path specified in the <a href="!settings">%administer >> %settings >> %modulebuilder</a> page.', array('!settings' => url('admin/settings/module_builder'), '%administer' => 'Administer', '%settings' => 'Site configuration', '%modulebuilder' => "Module builder")));
318 return $form;
319 }
320
321 // Include CSS for formatting
322 drupal_add_css(drupal_get_path('module', 'module_builder') . '/includes/module_builder.css');
323
324 // Module properties
325 $form['module_root_name'] = array(
326 '#type' => 'textfield',
327 '#title' => t('Machine-readable name'),
328 '#description' => t('This string is used to name the module files and to prefix all of your functions. This must only contain letters, numbers, and underscores, and may not start with a number.'),
329 '#required' => TRUE,
330 '#default_value' => 'mymodule', # $form_default_values['module_root_name'],
331 '#repopulate' => TRUE,
332 );
333 $form['module_readable_name'] = array(
334 '#type' => 'textfield',
335 '#title' => t('Name'),
336 '#description' => t('Name of your module as it will appear on the module admin page.'),
337 '#required' => TRUE,
338 '#default_value' => 'My Module',
339 '#repopulate' => TRUE,
340 );
341 $form['module_short_description'] = array(
342 '#type' => 'textfield',
343 '#title' => t('Description'),
344 '#description' => t('This text will appear in the module listing at <a href="!listing">%administer >> %build >> %modules</a>.', array('!listing' => url('admin/build/modules'), '%administer' => 'Administer', '%build' => 'Site building', '%modules' => 'Modules')),
345 '#required' => TRUE,
346 '#default_value' => 'Does awesome things. Makes tea. Washes up. Favours of a personal nature.',
347 '#repopulate' => TRUE,
348 );
349 $form['module_help_text'] = array(
350 '#type' => 'textarea',
351 '#title' => t('Help text'),
352 '#description' => t('Help text (HTML) to appear in <a href="!help">%administer >> %help >> module_name</a> page.', array('!help' => url('admin/help'), '%administer' => 'Administer', '%help' => 'Help')),
353 '#repopulate' => TRUE,
354 );
355 $form['module_dependencies'] = array(
356 '#type' => 'textfield',
357 '#title' => t('Dependencies'),
358 '#description' => t('Space seperated list of other modules that your module requires.'),
359 '#repopulate' => TRUE,
360 );
361 $form['module_package'] = array(
362 '#type' => 'textfield',
363 '#title' => t('Package'),
364 '#description' => t('If your module comes with other modules or is meant to be used exclusively with other modules, enter the package name here. Suggested package names: Audio, Bot, CCK, Chat, E-Commerce, Event, Feed parser, Organic groups, Station, Video, Views and Voting.'),
365 '#repopulate' => TRUE,
366 );
367
368 // Check for custom hook_groups file, else use default
369 $path = drupal_get_path('module', 'module_builder');
370 if (file_exists($path . MODULE_BUILDER_CUSTOM_HOOK_GROUPS_TEMPLATE_PATH)) {
371 $template_file = file_get_contents($path . MODULE_BUILDER_CUSTOM_HOOK_GROUPS_TEMPLATE_PATH);
372 }
373 else {
374 $template_file = file_get_contents($path . MODULE_BUILDER_HOOK_GROUPS_TEMPLATE_PATH);
375 }
376
377 $form['hook_groups'] = array(
378 '#type' => 'fieldset',
379 '#title' => t('Hook groupings'),
380 '#description' => t('Selecting one or more of these features will automatically select appropriate hooks for you.'),
381 );
382
383 drupal_add_js($path . '/includes/module_builder.js');
384
385 // Get list of hook presets from installed template.
386 $hook_presets = module_builder_parse_template($template_file);
387 foreach ($hook_presets as $hook_preset_name => $hook_preset ){
388 $hooks = explode("\n", $hook_preset['template']);
389 $hook_array = array();
390 foreach ($hooks as $hook) {
391 $hook = trim($hook);
392 if (!empty($hook)) {
393 $hook_array[] = "'$hook'";
394 }
395 }
396
397 $form['hook_groups']['groups-'. $hook_preset_name] = array(
398 '#type' => 'checkbox',
399 '#title' => $hook_preset_name,
400 '#attributes' => array(
401 'onclick' => 'check_hooks(this, ['. implode(', ', $hook_array) .'])',
402 ),
403 //'#description' => $hook_groups[$i]['data'],
404
405 // TODO: For some reason this gives me some wacky error under PHP 5:
406 // Fatal error: Cannot use string offset as an array
407 //'#default_value' => $edit['hook_groups'][$i],
408 );
409 }
410
411
412 // Build hooks list
413 $form['hooks'] = array(
414 '#type' => 'checkboxes',
415 '#title' => t('Use the following specific hooks'),
416 );
417
418 foreach ($hook_groups as $hook_group => $hooks) {
419 $form['hooks'][$hook_group] = array(
420 '#type' => 'fieldset',
421 '#title' => $hook_group .' hooks',
422 '#collapsible' => TRUE,
423 '#collapsed' => TRUE,
424 '#theme' => 'module_builder_hook_list',
425 );
426 foreach ($hooks as $hook) {
427 $name = $hook['name'];
428 $desc = $hook['description'];
429 $form['hooks'][$hook_group][$name] = array(
430 '#type' => 'checkbox',
431 '#title' => str_replace('hook_', '', $name),
432 '#description' => $desc,
433 '#repopulate' => TRUE,
434
435
436 // TODO: For some reason this gives me some wacky error under PHP 5:
437 // Fatal error: Cannot use string offset as an array
438 //'#default_value' => $edit['hooks'][$hook_group][$hook],
439
440 // TODO: I would *like* to do something like the following, so some of the long
441 // descriptions don't totally mangle the page output, but need a way to do like
442 // a "onmouseover" effect to display the full thing. Note that 'title' is not
443 // a valid attribute for divs. :\
444 //'#description' => truncate_utf8($desc, 40, TRUE, TRUE),
445
446 );
447 // Set some default hooks
448 if ($name == 'hook_menu') {
449 $form['hooks'][$hook_group][$name]['#default_value'] = 1;
450 }
451 }
452 // Sort list alphabetically
453 ksort($form['hooks'][$hook_group]);
454 }
455
456 $form['generate_module'] = array(
457 '#type' => 'submit',
458 '#name' => 'generate',
459 '#value' => t('Generate'),
460 );
461 $form['#submit'] = array('module_builder_page_input_submit');
462
463 #if ($form_state['rebuild']) { // fails as a test!?!?!
464 if ($form_state['values']) {
465 #dsm('rebuild');
466 $form = _form_repopulate($form, $form_state);
467 dsm($form_state['storage']['input']);
468 }
469
470 return $form;
471 }
472
473 /**
474 * Repopulate form with user values.
475 */
476 function _form_repopulate($form, $form_state) {
477 #dsm($form);
478 #dsm(element_children($form));
479 #dsm($form_state);
480 foreach (element_children($form) as $key) {
481 if (isset($form[$key]['#repopulate'])) {
482 #dsm('repop: ');
483 #dsm($key);
484 #$form[$key]['#default_value'] = 'repop!'; // this obviously works
485 #$form[$key]['#default_value'] = $form_state['values'][$key]; // arg! here we only have values from page 2!
486 $form[$key]['#default_value'] = $form_state['storage']['input'][$key]; // this obviously works
487 }
488 // recurse into children
489 $form[$key] = _form_repopulate($form[$key], $form_state);
490 } // each element_children
491 return $form;
492 }
493
494 /**
495 * Implementation of hook_theme.
496 */
497 function module_builder_theme($existing, $type, $theme, $path) {
498 return array(
499 'module_builder_hook_list' => array(
500 'arguments' => array('form' => array()),
501 ),
502 );
503 }
504
505 /**
506 * Theme function for hook list
507 */
508 function theme_module_builder_hook_list($form) {
509 $output = "<ul class=\"hook-group clear-block\">\n";
510 foreach (element_children($form) as $key) {
511 $output .= " <li>". drupal_render($form[$key]) ."</li>\n";
512 }
513 $output .= "</ul>\n";
514 return $output;
515 }
516
517
518 /**
519 * submit page 1
520 */
521 function module_builder_page_input_submit($form, &$form_state) {
522 $form_state['rebuild'] = TRUE;
523
524 // stash input.... these values will follow us around everywhere like a little dog...
525 $form_state['storage']['input'] = $form_state['values'];
526 foreach (array('generate', 'generate_module', 'form_build_id', 'form_token', 'form_id') as $key) {
527 unset($form_state['storage']['input'][$key]);
528 }
529 }
530
531 /**
532 * page 2: generate code
533 */
534 function module_builder_page_generate($form_state) {
535 $code = $form_state['values']['module_code'] ? $form_state['values']['module_code'] : generate_module($form_state['values']);
536 $info = $form_state['values']['module_info'] ? $form_state['values']['module_info'] : generate_info($form_state['values']) ;
537 // damn I miss perl at times like this. fugly syntax.
538
539 $form['back'] = array(
540 '#type' => 'submit',
541 '#value' => t('Back'),
542 '#name' => 'back',
543 );
544
545 $form['code_instructions'] = array(
546 '#value' => t('Please copy and paste the following text into a file called !module.', array('!module' => $form_values['module_root_name'] .'.module')),
547 '#prefix' => '<div id="module-message">',
548 '#suffix' => '</div>',
549 );
550 $form['module_code'] = array(
551 '#type' => 'textarea',
552 '#title' => t('Module code'),
553 '#rows' => 20,
554 '#default_value' => $code,
555 '#prefix' => '<div id="module-code">',
556 '#suffix' => '</div>',
557 );
558 /*
559 $form['download_module'] = array(
560 '#type' => 'submit',
561 '#value' => t('Download module'),
562 );
563 $form['write_module'] = array(
564 '#type' => 'button',
565 '#value' => t('Write module file'),
566 );
567 */
568
569 $form['info_instructions'] = array(
570 '#value' => t('Please copy and paste the following text into a file called !module.', array('!module' => $form_values['module_root_name'] .'.info')),
571 '#prefix' => '<div id="module-message">',
572 '#suffix' => '</div>',
573 );
574 $form['module_info'] = array(
575 '#type' => 'textarea',
576 '#title' => t('Module info'),
577 '#rows' => 20,
578 '#default_value' => $info,
579 '#prefix' => '<div id="module-info">',
580 '#suffix' => '</div>',
581 );
582 /*
583 $form['download_info'] = array(
584 '#type' => 'submit',
585 '#name' => 'op',
586 '#value' => t('Download info file'),
587 );
588 $form['write_info'] = array(
589 '#type' => 'button',
590 '#value' => t('Write info file'),
591 );
592 */
593
594 // handle the write buttons
595 ## $form['#after_build'] = array('module_builder_write_buttons');
596
597 return $form;
598 }
599
600
601
602
603
604
605
606 ############################# old code below here
607
608
609
610 /**
611 * This still needs some work. Set a bunch of check boxes, forward, back, uncheck
612 * the boxes, forward and back and the boxes get turned back on for some reason.
613 * Otherwise this seems pretty good.
614 */
615 function _module_builder_save_old_form_values($form, $form_values, $indent='') {
616 static $excludes;
617
618 if (!isset($excludes)) {
619 $excludes = array('op', 'form_build_id', 'form_token', 'form_id', 'generate_module', 'module_code', 'module_info');
620 }
621 if (isset($form['#multistep_excludes']) && is_array($form['#multistep_excludes'])) {
622 $excludes = array_merge($excludes, $form['#multistep_excludes']);
623 }
624 if (isset($form_values)) {
625 foreach ($form_values as $key => $value) {
626 //print_r($indent . $key .' => '. $value ."\n");
627 $include = !in_array($key, $excludes);
628 if ($include) {
629 if (is_array($value)) {
630 if (!isset($form[$key])) {
631 $form[$key] = array();
632 }
633 $form[$key] = _module_builder_save_old_form_values($form[$key], $value, $indent .' ');
634 $form[$key]['#tree'] = TRUE;
635 }
636 else {
637 if (isset($form[$key])) {
638 $form[$key]['#value'] = $value;
639 }
640 else {
641 $form[$key] = array(
642 '#type' => 'hidden',
643 '#value' => $value,
644 );
645 }
646 }
647 }
648 }
649 }
650
651 return $form;
652 }
653
654 /**
655 * Module form: 'input' step. Collect module data.
656 */
657 function Xmodule_builder_page_input($form, $form_values) {
658
659 _module_builder_check_settings();
660
661 // Include CSS for formatting
662 drupal_add_css(drupal_get_path('module', 'module_builder') . '/includes/module_builder.css');
663
664 // Module properties
665 $form['module_root_name'] = array(
666 '#type' => 'textfield',
667 '#title' => t('Machine-readable name'),
668 '#description' => t('This string is used to name the module files and to prefix all of your functions. This must only contain letters, numbers, and underscores, and may not start with a number.'),
669 '#required' => TRUE,
670 '#default_value' => 'mymodule',
671 );
672 $form['module_readable_name'] = array(
673 '#type' => 'textfield',
674 '#title' => t('Name'),
675 '#description' => t('Name of your module as it will appear on the module admin page.'),
676 '#required' => TRUE,
677 '#default_value' => 'My Module',
678 );
679 $form['module_short_description'] = array(
680 '#type' => 'textfield',
681 '#title' => t('Description'),
682 '#description' => t('This text will appear in the module listing at <a href="!listing">%administer >> %build >> %modules</a>.', array('!listing' => url('admin/build/modules'), '%administer' => 'Administer', '%build' => 'Site building', '%modules' => 'Modules')),
683 '#required' => TRUE,
684 '#default_value' => 'Does awesome things. Makes tea. Washes up. Favours of a personal nature.',
685 );
686 $form['module_help_text'] = array(
687 '#type' => 'textarea',
688 '#title' => t('Help text'),
689 '#description' => t('Help text (HTML) to appear in <a href="!help">%administer >> %help >> module_name</a> page.', array('!help' => url('admin/help'), '%administer' => 'Administer', '%help' => 'Help')),
690 );
691 $form['module_dependencies'] = array(
692 '#type' => 'textfield',
693 '#title' => t('Dependencies'),
694 '#description' => t('Space seperated list of other modules that your module requires.'),
695 );
696 $form['module_package'] = array(
697 '#type' => 'textfield',
698 '#title' => t('Package'),
699 '#description' => t('If your module comes with other modules or is meant to be used exclusively with other modules, enter the package name here. Suggested package names: Audio, Bot, CCK, Chat, E-Commerce, Event, Feed parser, Organic groups, Station, Video, Views and Voting.'),
700 );
701
702 // Check for custom hook_groups file, else use default
703 $path = drupal_get_path('module', 'module_builder');
704 if (file_exists($path . MODULE_BUILDER_CUSTOM_HOOK_GROUPS_TEMPLATE_PATH)) {
705 $template_file = file_get_contents($path . MODULE_BUILDER_CUSTOM_HOOK_GROUPS_TEMPLATE_PATH);
706 }
707 else {
708 $template_file = file_get_contents($path . MODULE_BUILDER_HOOK_GROUPS_TEMPLATE_PATH);
709 }
710
711 $form['hook_groups'] = array(
712 '#type' => 'fieldset',
713 '#title' => t('Hook groupings'),
714 '#description' => t('Selecting one or more of these features will automatically select appropriate hooks for you.'),
715 );
716
717 drupal_add_js($path . '/includes/module_builder.js');
718
719 // Get list of hook groups from installed template.
720 $hook_groups = module_builder_parse_template($template_file);
721 foreach ($hook_groups as $hook_group_name => $hook_group ){
722 $hooks = explode("\n", $hook_group['template']);
723 $hook_array = array();
724 foreach ($hooks as $hook) {
725 $hook = trim($hook);
726 if (!empty($hook)) {
727 $hook_array[] = "'$hook'";
728 }
729 }
730
731 $form['hook_groups']['groups-'. $hook_group_name] = array(
732 '#type' => 'checkbox',
733 '#title' => $hook_group_name,
734 '#attributes' => array(
735 'onclick' => 'check_hooks(this, ['. implode(', ', $hook_array) .'])',
736 ),
737 //'#description' => $hook_groups[$i]['data'],
738
739 // TODO: For some reason this gives me some wacky error under PHP 5:
740 // Fatal error: Cannot use string offset as an array
741 //'#default_value' => $edit['hook_groups'][$i],
742 );
743 }
744
745 // Get list of hooks from downloaded documentation, organized in fieldsets.
746 $hook_groups = module_builder_get_hook_data();
747
748 if (!is_array($hook_groups) || !count($hook_groups)) {
749 form_set_error('hooks', t('No hooks were found. Please check the documentation path specified in the <a href="!settings">%administer >> %settings >> %modulebuilder</a> page.', array('!settings' => url('admin/settings/module_builder'), '%administer' => 'Administer', '%settings' => 'Site configuration', '%modulebuilder' => "Module builder")));
750 }
751 else {
752
753 // Build hooks list
754 $form['hooks'] = array(
755 '#type' => 'checkboxes',
756 '#title' => t('Use the following specific hooks'),
757 );
758
759 foreach ($hook_groups as $hook_group => $hooks) {
760 $form['hooks'][$hook_group] = array(
761 '#type' => 'fieldset',
762 '#title' => $hook_group .' hooks',
763 '#collapsible' => TRUE,
764 '#collapsed' => TRUE,
765 '#theme' => 'module_builder_hook_list',
766 );
767 foreach ($hooks as $hook) {
768 $name = $hook['name'];
769 $desc = $hook['description'];
770 $form['hooks'][$hook_group][$name] = array(
771 '#type' => 'checkbox',
772 '#title' => str_replace('hook_', '', $name),
773 '#description' => $desc,
774
775 // TODO: For some reason this gives me some wacky error under PHP 5:
776 // Fatal error: Cannot use string offset as an array
777 //'#default_value' => $edit['hooks'][$hook_group][$hook],
778
779 // TODO: I would *like* to do something like the following, so some of the long
780 // descriptions don't totally mangle the page output, but need a way to do like
781 // a "onmouseover" effect to display the full thing. Note that 'title' is not
782 // a valid attribute for divs. :\
783 //'#description' => truncate_utf8($desc, 40, TRUE, TRUE),
784
785 );
786 // Set some default hooks
787 if ($name == 'hook_menu') {
788 $form['hooks'][$hook_group][$name]['#default_value'] = 1;
789 }
790 }
791 // Sort list alphabetically
792 ksort($form['hooks'][$hook_group]);
793 }
794
795 /*
796 $form['download'] = array(
797 '#type' => 'checkbox',
798 '#title' => t('Automatically generate module file for download?'),
799 '#description' => t('When checked, this will automatically generate your module file for you and prompt your browser to download it.'),
800 '#default_value' => variable_get('module_builder_download', 1),
801 );
802 */
803 $form['generate_module'] = array(
804 '#type' => 'submit',
805 '#name' => 'op',
806 '#value' => t('Generate'),
807 );
808 }
809
810 return $form;
811 }
812
813
814 /**
815 * Module form: 'module' step. Generate the module code.
816 */
817 function Xmodule_builder_page_module($form, $form_values) {
818
819 // Include link in breadcrumb to go back to main module builder form
820 /*
821 $breadcrumb = drupal_get_breadcrumb();
822 $breadcrumb[] = l(t('Module builder'), 'module_builder');
823 drupal_set_breadcrumb($breadcrumb);
824 */
825
826 $code = $form_values['module_code'] ? $form_values['module_code'] : generate_module($form_values);
827 $info = $form_values['module_info'] ? $form_values['module_info'] : generate_info($form_values) ;
828 // damn I miss perl at times like this. fugly syntax.
829
830 $form['back'] = array(
831 '#type' => 'submit',
832 '#name' => 'op',
833 '#value' => t('Back'),
834 );
835 $form['code_instructions'] = array(
836 '#value' => t('Please copy and paste the following text into a file called !module.', array('!module' => $form_values['module_root_name'] .'.module')),
837 '#prefix' => '<div id="module-message">',
838 '#suffix' => '</div>',
839 );
840 $form['module_code'] = array(
841 '#type' => 'textarea',
842 '#title' => t('Module code'),
843 '#rows' => 20,
844 '#default_value' => $code,
845 '#prefix' => '<div id="module-code">',
846 '#suffix' => '</div>',
847 );
848 $form['download_module'] = array(
849 '#type' => 'submit',
850 '#name' => 'op',
851 '#value' => t('Download module'),
852 );
853 $form['write_module'] = array(
854 '#type' => 'button',
855 '#value' => t('Write module file'),
856 );
857
858 $form['info_instructions'] = array(
859 '#value' => t('Please copy and paste the following text into a file called !module.', array('!module' => $form_values['module_root_name'] .'.info')),
860 '#prefix' => '<div id="module-message">',
861 '#suffix' => '</div>',
862 );
863 $form['module_info'] = array(
864 '#type' => 'textarea',
865 '#title' => t('Module info'),
866 '#rows' => 20,
867 '#default_value' => $info,
868 '#prefix' => '<div id="module-info">',
869 '#suffix' => '</div>',
870 );
871 $form['download_info'] = array(
872 '#type' => 'submit',
873 '#name' => 'op',
874 '#value' => t('Download info file'),
875 );
876 $form['write_info'] = array(
877 '#type' => 'button',
878 '#value' => t('Write info file'),
879 );
880 $form['#multistep_excludes'] = array('module_code', 'module_info');
881
882 // handle the write buttons
883 $form['#after_build'] = array('module_builder_write_buttons');
884
885 return $form;
886 }
887
888 /**
889 * Module form: 'download' step. Download the files.
890 */
891 function Xmodule_builder_page_download($form, $form_values) {
892 $file_content = '';
893 $file_ext = '.txt';
894 if ($form_values['op'] == t('Download module')) {
895 $file_content = $form_values['module_code'];
896 $file_ext = '.module';
897 }
898 else if ($form_values['op'] == t('Download info file')) {
899 $file_content = $form_values['module_info'];
900 $file_ext = '.info';
901 }
902 else {
903 form_set_error('Problem creating file for download.');
904 drupal_goto('module_builder');
905 }
906
907 if (strlen($file_content) > 0) {
908 $file_name = $form_values['module_root_name'] . $file_ext;
909 header("Content-disposition: attachment; filename=$file_name");
910 header('Content-Type: application/force-download');
911 header('Content-Transfer-Encoding: binary');
912 header('Content-Length: '. strlen($file_content));
913 header('Pragma: no-cache');
914 header('Expires: 0');
915 echo $file_content;
916 exit();
917 }
918 }
919
920 /*
921 $form['#after_build'] = array('module_builder_write_buttons');
922 */
923 /**
924 * Form after build callback.
925 * If update button was clicked, update hooks documentation. Rest of form is not submitted.
926 * Cribbed from node_form_add_preview()
927 */
928 function Xmodule_builder_write_buttons($form) {
929 static $been_here = FALSE; // ugly but I'm going round in circles trying to figure out how best to do this
930 if ($been_here) {
931 return $form;
932 }
933 $been_here = TRUE;
934
935 global $form_values;
936
937 $op = isset($form_values['op']) ? $form_values['op'] : '';
938
939 if ($op == t('Write module file')) {
940 _module_builder_write_file($form_values['module_root_name'], '.module', $form_values['module_code']);
941 }
942 elseif ($op == t('Write info file')) {
943 _module_builder_write_file($form_values['module_root_name'], '.info', $form_values['module_info']);
944 }
945
946 return $form;
947 }
948
949 /**
950 * Helper function to write files
951 * saves moving this code while mucking about with different formsAPI approaches & quicker to shortcircuit
952 */
953 function _module_builder_write_file($basename, $extension, $content) {
954 if (strlen($content) == 0) {
955 return;
956 }
957
958 $directory = file_create_path(variable_get('module_builder_module_write_directory', 'modules') .'/'. $basename);
959 file_check_directory($directory, FILE_CREATE_DIRECTORY);
960 $file_name = $basename . $extension;
961
962 $created_file = file_save_data($content, "$directory/$file_name", FILE_EXISTS_REPLACE);
963
964 if ($created_file) {
965 drupal_set_message(t("File @file has been written.", array('@file' => $created_file)));
966 }
967 else {
968 drupal_set_message(t("There was a problem writing the file @file.", array('@file' => "$directory/$file_name")), 'error');
969 }
970 }
971
972 /**
973 * Module form: 'write' step
974 */
975 function Xmodule_builder_page_write($form, $form_values) {
976 ####### bug!!!!!!!!!
977 dpr('writing page: ' . $form_values['op']);
978
979 if ($form_values['op'] == t('Write module file')) {
980 _module_builder_write_file($form_values['module_root_name'], '.module', $form_values['module_code']);
981 }
982 else if ($form_values['op'] == t('Write info file')) {
983 _module_builder_write_file($form_values['module_root_name'], '.info', $form_values['module_info']);
984 }
985 else {
986 form_set_error('Problem creating file for writing.');
987 drupal_goto('module_builder');
988 }
989
990 if (strlen($file_content) > 0) {
991 $directory = file_create_path(variable_get('module_builder_module_write_directory', 'modules') .'/'. $form_values['module_root_name']);
992 file_check_directory($directory, FILE_CREATE_DIRECTORY);
993 $file_name = $form_values['module_root_name'] . $file_ext;
994
995 $created_file = file_save_data($file_content, "$directory/$file_name", FILE_EXISTS_REPLACE);
996
997 if ($created_file) {
998 drupal_set_message(t("File @file has been written.", array('@file' => $created_file)));
999 }
1000 else {
1001 drupal_set_message(t("There was a problem writing the file @file.", array('@file' => "$directory/$file_name")), 'error');
1002 }
1003 }
1004
1005 // return to the module step to write or download some more.
1006 //return module_builder_page_module($form, $form_values);
1007
1008 return $form;
1009 }
1010
1011 /**
1012 * Makes sure that valid values have been provided to the Module Builder.
1013 *
1014 * @ingroup module_builder_callback
1015 */
1016 function Xmodule_builder_page_validate($form, &$form_state) {
1017 # TODO
1018 if ($form_values['op'] == 'input') {
1019 // Ensure module_root_name was entered, and check for special characters
1020 if (!empty($form_values['module_root_name'])) {
1021 if (!preg_match(MODULE_BUILDER_FUNCTION_PATTERN, $form_values['module_root_name'])) {
1022 form_set_error('module_root_name', t('The module root name must only contain letters, numbers, and underscores, and may not start with a number.'));
1023 }
1024 }
1025
1026 // Make sure at least one hook was chosen
1027 $hook_selected = false;
1028 foreach ($form_values['hooks'] as $file => $hooks) {
1029 foreach ($hooks as $hook) {
1030 if ($hook == 1) {
1031 $hook_selected = true;
1032 break;
1033 }
1034 }
1035 if ($hook_selected) {
1036 break;
1037 }
1038 }
1039 if (!$hook_selected) {
1040 form_set_error('hooks', t('You must select at least one hook.'));
1041 }
1042 }
1043 }
1044 ###################################################### end old code
1045
1046 function generate_module($form_values) {
1047 // Grab header from settings
1048 $header = "<?php\n". variable_get('module_builder_header', MODULE_BUILDER_HEADER_DEFAULT);
1049
1050 // Grab footer from settings
1051 $footer = variable_get('module_builder_footer', '');
1052
1053 // begin assembling data
1054 // build an array $hook_data of all the stuff we know about hooks
1055 // of the form:
1056 // 'hook_foo' => array( 'declaration' => DATA, 'template' => DATA )
1057 $hook_data = array();
1058
1059 // Check for custom functions file, else use default
1060 $path = drupal_get_path('module', 'module_builder') .'/templates';
1061 if (file_exists("$path/hooks-custom.template")) {
1062 $template_file = file_get_contents("$path/hooks-custom.template");
1063 }
1064 else {
1065 $template_file = file_get_contents("$path/hooks.template");
1066 }
1067
1068 // Get array of our hook function body templates from our own / custom template files.
1069 // This is not necessarily all hooks that exist -- not all have template entries.
1070 // This array is in the same order as they occur in the files and already in the format wanted.
1071 $hook_data = module_builder_parse_template($template_file);
1072
1073 // Check for node hooks; these will overwrite core hooks if found
1074 if ($form_values['hooks']['node']['hook_node_info']) {
1075 if (file_exists("$path/node_hooks-custom.template")) {
1076 $template_file = file_get_contents("$path/node_hooks-custom.template");
1077 }
1078 else {
1079 $template_file = file_get_contents("$path/node_hooks.template");
1080 }
1081 $custom_hooks = module_builder_parse_template($template_file);
1082 foreach ($custom_hooks as $hook_name => $hook_template) {
1083 // add or clobber our existing templates
1084 $hook_data[$hook_name] = $hook_template;
1085 }
1086 }
1087
1088 // Get array of the hook function declarations from the downloaded hook data.
1089 // This is a complete list of all hooks that exist.
1090 // In the form: 'hook_foo' => 'declaration'
1091 // This array is the order they are in the files from d.org: alphabetical apparently.
1092 $hook_function_declarations = module_builder_get_hook_data(MODULE_BUILDER_HOOK_DECLARATIONS);
1093
1094 // iterate over the downloaded declarations,
1095 // merge into our data, adding in those not yet in our data array.
1096 foreach ($hook_function_declarations as $hook_name => $hook_declaration ) {
1097 // this adds the declaration to the array items already there
1098 // autovivification takes care of creating new array items if not already there
1099 $hook_data[$hook_name]['declaration'] = $hook_declaration;
1100 }
1101
1102 // $hook_data is now a complete representation of all we know about hooks
1103
1104 // now look at form values. These come in two arrays from the sets of checkboes in the UI.
1105 // merge into one array of hook_name => TRUE/FALSE
1106 $requested_hooks = array();
1107 foreach ($form_values['hooks'] as $hook_group) {
1108 $requested_hooks += $hook_group;
1109 }
1110
1111 // Begin code generation
1112 $code = '';
1113
1114 // iterate over our data array, because it's in a pretty order
1115 foreach ($hook_data as $hook_name => $hook) {
1116 if (!$requested_hooks[$hook_name]) {
1117 // don't want this one. skip it
1118 continue;
1119 }
1120
1121 // Display PHP doc
1122 $code .= "
1123 /**
1124 * Implementation of $hook_name().
1125 */
1126 ";
1127
1128 // decode html entities and put in the module name
1129 $code .= htmlspecialchars_decode(str_replace('hook', $form_values['module_root_name'], $hook['declaration']));
1130
1131 // See if function bodies exist; if so, use function bodies from template
1132 if ($hook['template']) {
1133 // Strip out INFO: comments for advanced users
1134 if (!variable_get('module_builder_detail', 0)) {
1135 $hook['template'] = preg_replace(MODULE_BUILDER_INFO_PATTERN, '', $hook['template']);
1136 }
1137
1138 $code .= $hook['template'];
1139 }
1140 else {
1141 $code .= "\n\n";
1142 }
1143 $code .= "}\n\n";
1144 }
1145
1146
1147 // Replace variables
1148 $variables = array(
1149 '%module' => $form_values['module_root_name'],
1150 '%description' => str_replace("'", "\'", $form_values['module_short_description']),
1151 '%name' => !empty($form_values['module_readable_name']) ? str_replace("'", "\'", $form_values['module_readable_name']) : $form_values['module_root_name'],
1152 '%help' => !empty($form_values['module_help_text']) ? str_replace("'", "\'", $form_values['module_help_text']) : t('TODO: Create admin help text.'),
1153 '%readable' => str_replace("'", "\'", $form_values['module_readable_name']),
1154 );
1155 $code = strtr($code, $variables);
1156
1157 // Replace full-blown Id tag with just starter
1158 // (excuse the weird concatenation stuff; CVS hijacks it otherwise :))
1159 $code = preg_replace(MODULE_BUILDER_FULL_ID_PATTERN, MODULE_BUILDER_ID_COMMENT, $code);
1160
1161 // Prepare final code
1162 $code = $header . $code . $footer;
1163
1164 return $code;
1165 }
1166
1167 function generate_info($form_values) {
1168 $info = MODULE_BUILDER_INFO_ID_COMMENT ."\n";
1169 $info .= 'name = ';
1170 if (!empty($form_values['module_readable_name'])) {
1171 $info .= $form_values['module_readable_name'];
1172 }
1173 else {
1174 $info .= $form_values['module_root_name'];
1175 }
1176 $info .= "\n";
1177 $info .= 'description = '. $form_values['module_short_description'] ."\n";
1178 if (!empty($form_values['module_dependencies'])) {
1179 $info .= 'dependencies = '. $form_values['module_dependencies'] ."\n";
1180 }
1181 if (!empty($form_values['module_package'])) {
1182 $info .= 'package = '. $form_values['module_package'] ."\n";
1183 }
1184
1185 return $info;
1186 }
1187
1188 /**
1189 * Parse a module_builder template file.
1190 *
1191 * Template files are composed of several sections in the form of:
1192 *
1193 * == START [title of template section] ==
1194 * [the body of the template section]
1195 * == END ==
1196 *
1197 * @param string $file
1198 * The template file to parse
1199 * @return Array
1200 * Return array keyed by hook name, whose values are of the form: array('template' => TEMPLATE)
1201 */
1202 function module_builder_parse_template($file) {
1203 $data = array();
1204 preg_match_all(MODULE_BUILDER_TEMPLATE_PATTERN, $file, $matches);
1205 $count = count($matches[0]);
1206 for ($i = 0; $i < $count; $i++) {
1207 $data[$matches[1][$i]] = array(
1208 #'title' => $matches[1][$i],
1209 'template' => $matches[2][$i]
1210 );
1211 /*
1212 $hook_custom_declarations[] = array(
1213 'title' => $matches[1][$i],
1214 'data' => $matches[2][$i]
1215 );
1216 */
1217 }
1218 return $data;
1219 }
1220
1221