You are here

module_builder.module in Module Builder 7

Builds scaffolding for custom modules.

File

module_builder.module
View source
<?php

/**
 * @file
 * Builds scaffolding for custom modules.
 */

/**
 * 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.
 */
define('MODULE_BUILDER_FULL_ID_PATTERN', '#\\/\\/ \\$Id(.*?)\\$#');

/**
 * Took this regex from the PHP manual page on Functions
 */
define('MODULE_BUILDER_FUNCTION_PATTERN', '#^[a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*#');

/**
 * Path from module base to the normal hook groups template. MODULE_BUILDER_CUSTOM_HOOK_GROUPS_TEMPLATE_PATH used if it exists.
 */
define('MODULE_BUILDER_HOOK_GROUPS_TEMPLATE_PATH', '/templates/hook_groups.template');

/**
 * Path from module base to the custom hook groups template. Only used if it exists, otherwise MODULE_BUILDER_HOOK_GROUPS_TEMPLATE_PATH is used.
 */
define('MODULE_BUILDER_CUSTOM_HOOK_GROUPS_TEMPLATE_PATH', '/template/hook_groups-custom.template');

/**
 * Used to replace a full CVS Id tag with a starter ID.
 */

// The weird syntax stops this from getting mangled by CVS
define('MODULE_BUILDER_ID_COMMENT', '// $' . 'Id$');

/* Default default values for some variables */
define('MODULE_BUILDER_HEADER_DEFAULT', '// $' . 'Id$

/**
 * @file
 * TODO: Enter file description here.
 */
');

/**
 * @defgroup module_builder_core Core Drupal hooks
 */

/**
 * Implementation of hook_init().
 */
function module_builder_init() {

  // Set our environment.
  define('MODULE_BUILDER_ENV', 'drupal');

  // Include common code.
  include_once './' . drupal_get_path('module', 'module_builder') . '/includes/common.inc';
}

/**
 * Implementation of hook_permission().
 *
 * @ingroup module_builder_core
 */
function module_builder_permission() {
  return array(
    'access module builder' => array(
      'title' => t('Access module builder'),
    ),
  );
}

/**
 * Implementation of hook_menu().
 *
 * @ingroup module_builder_core
 */
function module_builder_menu() {
  $items['module_builder'] = array(
    'title' => 'Module builder',
    'description' => t('Builds scaffolding for custom modules.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'module_builder_page',
    ),
    'file' => 'includes/module_builder.pages.inc',
    'access arguments' => array(
      'access module builder',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/config/development/module_builder'] = array(
    'title' => 'Module builder',
    'description' => t('Set default header and footer, api download location, defaults for detail and download and force the api to be re-downloaded.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'module_builder_admin_settings',
    ),
    'file' => 'includes/module_builder.admin.inc',
    'access arguments' => array(
      'access module builder',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/config/development/module_builder/settings'] = array(
    'title' => 'Settings',
    'description' => t('Set default header and footer, folder locations, and defaults for detail.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'module_builder_admin_settings',
    ),
    'file' => 'includes/module_builder.admin.inc',
    'access arguments' => array(
      'access module builder',
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/config/development/module_builder/update'] = array(
    'title' => 'Update hooks',
    'description' => t('Download hook documentation.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'module_builder_admin_update',
    ),
    'file' => 'includes/module_builder.admin.inc',
    'access arguments' => array(
      'access module builder',
    ),
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

/**
 * Implementation of hook_theme.
 */
function module_builder_theme($existing, $type, $theme, $path) {
  return array(
    'module_builder_hook_list' => array(
      'arguments' => array(
        'form' => array(),
      ),
    ),
  );
}

############################# old code below here

/**
 * This still needs some work. Set a bunch of check boxes, forward, back, uncheck
 * the boxes, forward and back and the boxes get turned back on for some reason.
 * Otherwise this seems pretty good.
 */
function _module_builder_save_old_form_values($form, $form_values, $indent = '') {
  static $excludes;
  if (!isset($excludes)) {
    $excludes = array(
      'op',
      'form_build_id',
      'form_token',
      'form_id',
      'generate_module',
      'module_code',
      'module_info',
    );
  }
  if (isset($form['#multistep_excludes']) && is_array($form['#multistep_excludes'])) {
    $excludes = array_merge($excludes, $form['#multistep_excludes']);
  }
  if (isset($form_values)) {
    foreach ($form_values as $key => $value) {

      //print_r($indent . $key .' => '. $value ."\n");
      $include = !in_array($key, $excludes);
      if ($include) {
        if (is_array($value)) {
          if (!isset($form[$key])) {
            $form[$key] = array();
          }
          $form[$key] = _module_builder_save_old_form_values($form[$key], $value, $indent . '    ');
          $form[$key]['#tree'] = TRUE;
        }
        else {
          if (isset($form[$key])) {
            $form[$key]['#value'] = $value;
          }
          else {
            $form[$key] = array(
              '#type' => 'hidden',
              '#value' => $value,
            );
          }
        }
      }
    }
  }
  return $form;
}

/**
 * Module form: 'input' step. Collect module data.
 */
function Xmodule_builder_page_input($form, $form_values) {
  _module_builder_check_settings();

  // Include CSS for formatting
  drupal_add_css(drupal_get_path('module', 'module_builder') . '/theme/module_builder.css');

  // Module properties
  $form['module_root_name'] = array(
    '#type' => 'textfield',
    '#title' => t('Machine-readable name'),
    '#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.'),
    '#required' => TRUE,
    '#default_value' => 'mymodule',
  );
  $form['module_readable_name'] = array(
    '#type' => 'textfield',
    '#title' => t('Name'),
    '#description' => t('Name of your module as it will appear on the module admin page.'),
    '#required' => TRUE,
    '#default_value' => 'My Module',
  );
  $form['module_short_description'] = array(
    '#type' => 'textfield',
    '#title' => t('Description'),
    '#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',
    )),
    '#required' => TRUE,
    '#default_value' => 'Does awesome things. Makes tea. Washes up. Favours of a personal nature.',
  );
  $form['module_help_text'] = array(
    '#type' => 'textarea',
    '#title' => t('Help text'),
    '#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',
    )),
  );
  $form['module_dependencies'] = array(
    '#type' => 'textfield',
    '#title' => t('Dependencies'),
    '#description' => t('Space seperated list of other modules that your module requires.'),
  );
  $form['module_package'] = array(
    '#type' => 'textfield',
    '#title' => t('Package'),
    '#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.'),
  );

  // Check for custom hook_groups file, else use default
  $path = drupal_get_path('module', 'module_builder');
  if (file_exists($path . MODULE_BUILDER_CUSTOM_HOOK_GROUPS_TEMPLATE_PATH)) {
    $template_file = file_get_contents($path . MODULE_BUILDER_CUSTOM_HOOK_GROUPS_TEMPLATE_PATH);
  }
  else {
    $template_file = file_get_contents($path . MODULE_BUILDER_HOOK_GROUPS_TEMPLATE_PATH);
  }
  $form['hook_groups'] = array(
    '#type' => 'fieldset',
    '#title' => t('Hook groupings'),
    '#description' => t('Selecting one or more of these features will automatically select appropriate hooks for you.'),
  );
  drupal_add_js($path . '/theme/module_builder.js');

  // Get list of hook groups from installed template.
  // Include generating component file.
  module_builder_include('process');
  $hook_groups = module_builder_parse_template($template_file);
  foreach ($hook_groups as $hook_group_name => $hook_group) {
    $hooks = explode("\n", $hook_group['template']);
    $hook_array = array();
    foreach ($hooks as $hook) {
      $hook = trim($hook);
      if (!empty($hook)) {
        $hook_array[] = "'{$hook}'";
      }
    }
    $form['hook_groups']['groups-' . $hook_group_name] = array(
      '#type' => 'checkbox',
      '#title' => $hook_group_name,
      '#attributes' => array(
        'onclick' => 'check_hooks(this, [' . implode(', ', $hook_array) . '])',
      ),
    );
  }

  // Get list of hooks from downloaded documentation, organized in fieldsets.
  $hook_groups = module_builder_get_hook_data();
  if (!is_array($hook_groups) || !count($hook_groups)) {
    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/update'),
      '%administer' => 'Administer',
      '%settings' => 'Site configuration',
      '%modulebuilder' => "Module builder",
    )));
  }
  else {

    // Build hooks list
    $form['hooks'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Use the following specific hooks'),
    );
    foreach ($hook_groups as $hook_group => $hooks) {
      $form['hooks'][$hook_group] = array(
        '#type' => 'fieldset',
        '#title' => $hook_group . ' hooks',
        '#collapsible' => TRUE,
        '#collapsed' => TRUE,
        '#theme' => 'module_builder_hook_list',
      );
      foreach ($hooks as $hook) {
        $name = $hook['name'];
        $desc = $hook['description'];
        $form['hooks'][$hook_group][$name] = array(
          '#type' => 'checkbox',
          '#title' => str_replace('hook_', '', $name),
          '#description' => $desc,
        );

        // Set some default hooks
        if ($name == 'hook_menu') {
          $form['hooks'][$hook_group][$name]['#default_value'] = 1;
        }
      }

      // Sort list alphabetically
      ksort($form['hooks'][$hook_group]);
    }

    /*
    $form['download'] = array(
      '#type' => 'checkbox',
      '#title' => t('Automatically generate module file for download?'),
      '#description' => t('When checked, this will automatically generate your module file for you and prompt your browser to download it.'),
      '#default_value' => variable_get('module_builder_download', 1),
    );
    */
    $form['generate_module'] = array(
      '#type' => 'submit',
      '#name' => 'op',
      '#value' => t('Generate'),
    );
  }
  return $form;
}

/**
 * Module form: 'module' step. Generate the module code.
 */
function Xmodule_builder_page_module($form, $form_values) {

  // Include link in breadcrumb to go back to main module builder form

  /*
  $breadcrumb = drupal_get_breadcrumb();
  $breadcrumb[] = l(t('Module builder'), 'module_builder');
  drupal_set_breadcrumb($breadcrumb);
  */
  $code = $form_values['module_code'] ? $form_values['module_code'] : module_builder_generate_module($form_values);
  $info = $form_values['module_info'] ? $form_values['module_info'] : module_builder_generate_info($form_values);

  // damn I miss perl at times like this. fugly syntax.
  $form['back'] = array(
    '#type' => 'submit',
    '#name' => 'op',
    '#value' => t('Back'),
  );
  $form['code_instructions'] = array(
    '#value' => t('Please copy and paste the following text into a file called !module.', array(
      '!module' => $form_values['module_root_name'] . '.module',
    )),
    '#prefix' => '<div id="module-message">',
    '#suffix' => '</div>',
  );
  $form['module_code'] = array(
    '#type' => 'textarea',
    '#title' => t('Module code'),
    '#rows' => 20,
    '#default_value' => $code,
    '#prefix' => '<div id="module-code">',
    '#suffix' => '</div>',
  );
  $form['download_module'] = array(
    '#type' => 'submit',
    '#name' => 'op',
    '#value' => t('Download module'),
  );
  $form['write_module'] = array(
    '#type' => 'button',
    '#value' => t('Write module file'),
  );
  $form['info_instructions'] = array(
    '#value' => t('Please copy and paste the following text into a file called !module.', array(
      '!module' => $form_values['module_root_name'] . '.info',
    )),
    '#prefix' => '<div id="module-message">',
    '#suffix' => '</div>',
  );
  $form['module_info'] = array(
    '#type' => 'textarea',
    '#title' => t('Module info'),
    '#rows' => 20,
    '#default_value' => $info,
    '#prefix' => '<div id="module-info">',
    '#suffix' => '</div>',
  );
  $form['download_info'] = array(
    '#type' => 'submit',
    '#name' => 'op',
    '#value' => t('Download info file'),
  );
  $form['write_info'] = array(
    '#type' => 'button',
    '#value' => t('Write info file'),
  );
  $form['#multistep_excludes'] = array(
    'module_code',
    'module_info',
  );

  // handle the write buttons
  $form['#after_build'] = array(
    'module_builder_write_buttons',
  );
  return $form;
}

/**
 * Module form: 'download' step. Download the files.
 */
function Xmodule_builder_page_download($form, $form_values) {
  $file_content = '';
  $file_ext = '.txt';
  if ($form_values['op'] == t('Download module')) {
    $file_content = $form_values['module_code'];
    $file_ext = '.module';
  }
  elseif ($form_values['op'] == t('Download info file')) {
    $file_content = $form_values['module_info'];
    $file_ext = '.info';
  }
  else {
    form_set_error('Problem creating file for download.');
    drupal_goto('module_builder');
  }
  if (strlen($file_content) > 0) {
    $file_name = $form_values['module_root_name'] . $file_ext;
    header("Content-disposition: attachment; filename={$file_name}");
    header('Content-Type: application/force-download');
    header('Content-Transfer-Encoding: binary');
    header('Content-Length: ' . strlen($file_content));
    header('Pragma: no-cache');
    header('Expires: 0');
    echo $file_content;
    exit;
  }
}

/*
  $form['#after_build'] = array('module_builder_write_buttons');
*/

/**
 * Form after build callback.
 * If update button was clicked, update hooks documentation. Rest of form is not submitted.
 * Cribbed from node_form_add_preview()
 */
function Xmodule_builder_write_buttons($form) {
  static $been_here = FALSE;

  // ugly but I'm going round in circles trying to figure out how best to do this
  if ($been_here) {
    return $form;
  }
  $been_here = TRUE;
  global $form_values;
  $op = isset($form_values['op']) ? $form_values['op'] : '';
  if ($op == t('Write module file')) {
    _module_builder_write_file($form_values['module_root_name'], '.module', $form_values['module_code']);
  }
  elseif ($op == t('Write info file')) {
    _module_builder_write_file($form_values['module_root_name'], '.info', $form_values['module_info']);
  }
  return $form;
}

/**
 * Helper function to write files
 * saves moving this code while mucking about with different formsAPI approaches & quicker to shortcircuit
 */
function _module_builder_write_file($basename, $extension, $content) {
  if (strlen($content) == 0) {
    return;
  }
  $directory = file_create_path(variable_get('module_builder_module_write_directory', 'modules') . '/' . $basename);
  file_check_directory($directory, FILE_CREATE_DIRECTORY);
  $file_name = $basename . $extension;
  $created_file = file_save_data($content, "{$directory}/{$file_name}", FILE_EXISTS_REPLACE);
  if ($created_file) {
    drupal_set_message(t("File @file has been written.", array(
      '@file' => $created_file,
    )));
  }
  else {
    drupal_set_message(t("There was a problem writing the file @file.", array(
      '@file' => "{$directory}/{$file_name}",
    )), 'error');
  }
}

/**
 * Module form: 'write' step
 */
function Xmodule_builder_page_write($form, $form_values) {

  ####### bug!!!!!!!!!
  dpr('writing page: ' . $form_values['op']);
  if ($form_values['op'] == t('Write module file')) {
    _module_builder_write_file($form_values['module_root_name'], '.module', $form_values['module_code']);
  }
  elseif ($form_values['op'] == t('Write info file')) {
    _module_builder_write_file($form_values['module_root_name'], '.info', $form_values['module_info']);
  }
  else {
    form_set_error('Problem creating file for writing.');
    drupal_goto('module_builder');
  }
  if (strlen($file_content) > 0) {
    $directory = file_create_path(variable_get('module_builder_module_write_directory', 'modules') . '/' . $form_values['module_root_name']);
    file_check_directory($directory, FILE_CREATE_DIRECTORY);
    $file_name = $form_values['module_root_name'] . $file_ext;
    $created_file = file_save_data($file_content, "{$directory}/{$file_name}", FILE_EXISTS_REPLACE);
    if ($created_file) {
      drupal_set_message(t("File @file has been written.", array(
        '@file' => $created_file,
      )));
    }
    else {
      drupal_set_message(t("There was a problem writing the file @file.", array(
        '@file' => "{$directory}/{$file_name}",
      )), 'error');
    }
  }

  // return to the module step to write or download some more.

  //return module_builder_page_module($form, $form_values);
  return $form;
}

/**
 * Makes sure that valid values have been provided to the Module Builder.
 *
 * @ingroup module_builder_callback
 */
function Xmodule_builder_page_validate($form, &$form_state) {

  # TODO
  if ($form_values['op'] == 'input') {

    // Ensure module_root_name was entered, and check for special characters
    if (!empty($form_values['module_root_name'])) {
      if (!preg_match(MODULE_BUILDER_FUNCTION_PATTERN, $form_values['module_root_name'])) {
        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.'));
      }
    }

    // Make sure at least one hook was chosen
    $hook_selected = FALSE;
    foreach ($form_values['hooks'] as $file => $hooks) {
      foreach ($hooks as $hook) {
        if ($hook == 1) {
          $hook_selected = TRUE;
          break;
        }
      }
      if ($hook_selected) {
        break;
      }
    }
    if (!$hook_selected) {
      form_set_error('hooks', t('You must select at least one hook.'));
    }
  }
}

###################################################### end old code

Functions

Namesort descending Description
module_builder_init Implementation of hook_init().
module_builder_menu Implementation of hook_menu().
module_builder_permission Implementation of hook_permission().
module_builder_theme Implementation of hook_theme.
Xmodule_builder_page_download Module form: 'download' step. Download the files.
Xmodule_builder_page_input Module form: 'input' step. Collect module data.
Xmodule_builder_page_module Module form: 'module' step. Generate the module code.
Xmodule_builder_page_validate Makes sure that valid values have been provided to the Module Builder.
Xmodule_builder_page_write Module form: 'write' step
Xmodule_builder_write_buttons Form after build callback. If update button was clicked, update hooks documentation. Rest of form is not submitted. Cribbed from node_form_add_preview()
_module_builder_save_old_form_values This still needs some work. Set a bunch of check boxes, forward, back, uncheck the boxes, forward and back and the boxes get turned back on for some reason. Otherwise this seems pretty good.
_module_builder_write_file Helper function to write files saves moving this code while mucking about with different formsAPI approaches & quicker to shortcircuit

Constants

Namesort descending Description
MODULE_BUILDER_CUSTOM_HOOK_GROUPS_TEMPLATE_PATH Path from module base to the custom hook groups template. Only used if it exists, otherwise MODULE_BUILDER_HOOK_GROUPS_TEMPLATE_PATH is used.
MODULE_BUILDER_FULL_ID_PATTERN 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.
MODULE_BUILDER_FUNCTION_PATTERN Took this regex from the PHP manual page on Functions
MODULE_BUILDER_HEADER_DEFAULT
MODULE_BUILDER_HOOK_GROUPS_TEMPLATE_PATH Path from module base to the normal hook groups template. MODULE_BUILDER_CUSTOM_HOOK_GROUPS_TEMPLATE_PATH used if it exists.
MODULE_BUILDER_ID_COMMENT