array( 'title' => t('Administer code conversions'), 'description' => t('Manage code conversion tasks for Coder Upgrade.'), ), ); } /** * Implements hook_menu(). */ function coder_upgrade_menu() { // Conversion items. $items['admin/config/development/coder/upgrade'] = array( 'title' => 'Upgrade', 'description' => 'Convert module code from version 6.x to 7.x.', 'page callback' => 'drupal_get_form', 'page arguments' => array('coder_upgrade_conversions_form'), 'access arguments' => array('administer code conversions'), 'type' => MENU_LOCAL_TASK, ); // Run items. $items['admin/config/development/coder/upgrade/run'] = array( 'title' => 'Run', 'type' => MENU_DEFAULT_LOCAL_TASK, ); // Settings related items. $items['admin/config/development/coder/upgrade/settings'] = array( 'title' => 'Settings', 'description' => 'Configure the module conversion suite.', 'page callback' => 'drupal_get_form', 'page arguments' => array('coder_upgrade_settings_form'), 'access arguments' => array('administer site configuration'), 'type' => MENU_LOCAL_TASK, ); // Patch files. $items['files/coder_upgrade/patches/%'] = array( 'title' => 'Patches', 'description' => 'Display patch file.', 'page callback' => 'coder_upgrade_patch_display', 'page arguments' => array(3), 'access arguments' => array('administer code conversions'), 'type' => MENU_CALLBACK, ); return $items; } /** * Implements hook_menu_alter(). */ function coder_upgrade_menu_alter(&$items) { if (!module_exists('coder_review')) { // Remove the Coder menu item. unset($items['admin/config/development/coder']); // Promote the menu items defined by this module. $items['admin/config/development/coder/upgrade']['title'] = 'Coder Upgrade'; $items['admin/config/development/coder/upgrade']['type'] = MENU_NORMAL_ITEM; } } /** * Form builder for the settings form. */ function coder_upgrade_settings_form($form, &$form_state) { module_load_include('inc', 'grammar_parser', 'grammar_parser.parser'); module_load_include('inc', 'coder_upgrade', 'conversions/coder_upgrade.main'); $parser = new PGPParser(); $path = coder_upgrade_directory_path('', FALSE); $form['coder_upgrade_dir'] = array( '#title' => t('Module base directory'), '#type' => 'textfield', '#required' => TRUE, '#default_value' => variable_get('coder_upgrade_dir', DEADWOOD_DIR), '#description' => t('Directory beneath the file system path (!path) in which are housed the old, new and patch directories. Default is !default. In the old directory, place the 6.x module code to be upgraded. The upgraded code is saved to the new directory and patch files are written to the patch directory.', array('!path' => $path, '!default' => DEADWOOD_DIR)), '#size' => 30, '#maxlength' => 255, '#validate' => array('coder_upgrade_validate_dir'), ); $form['coder_upgrade_upgrade_core'] = array( '#type' => 'checkbox', '#title' => t('Update Drupal core modules'), '#default_value' => variable_get('coder_upgrade_upgrade_core', FALSE), '#description' => t('If checked, then the list of modules to select for upgrade will be the Drupal core modules. Otherwise, the list will be created from contributed modules.'), ); $form['coder_upgrade_replace_files'] = array( '#type' => 'checkbox', '#title' => t('Replace files'), '#default_value' => variable_get('coder_upgrade_replace_files', FALSE), '#description' => t('If checked, then the original file will be written to the output directory shown above and then replaced with the upgraded file. Otherwise, the upgraded file will be written to the output directory shown above.'), ); $form['coder_upgrade_preserve_array_format'] = array( '#type' => 'checkbox', '#title' => t('Preserve array formatting'), '#default_value' => variable_get('coder_upgrade_preserve_array_format', FALSE), '#description' => t('If checked, then array expressions will be formatted to match the input. If not checked, then array expressions will be formatted per Drupal coding standards. This means array expressions will be inlined in function calls, function parameters, and when a single value. Otherwise, the expression will will be multiline formatted.'), ); $form['coder_upgrade_enable_debug_output'] = array( '#type' => 'checkbox', '#title' => t('Enable debug output from coder upgrade'), '#default_value' => variable_get('coder_upgrade_enable_debug_output', FALSE), '#description' => t('If checked, then coder upgrade debug output will be written to the file !file. WARNING: This option should NOT be enabled except for testing and development purposes, and then only on smaller files containing the code to be debugged.', array('!file' => coder_upgrade_path('debug'))), ); $form['coder_upgrade_enable_parser_debug_output'] = array( '#type' => 'checkbox', '#title' => t('Enable debug output from grammar parser'), '#default_value' => variable_get('coder_upgrade_enable_parser_debug_output', FALSE), '#description' => t('If checked, then grammar parser debug output will be written to the file !file. WARNING: This option should NOT be enabled except for testing and development purposes, and then only on smaller files containing the code to be debugged.', array('!file' => $parser->debugPath())), ); $path = coder_upgrade_path('theme_cache'); $form['performance'] = array( '#type' => 'fieldset', '#title' => t('Performance'), '#description' => t('To minimize the chance of hitting PHP memory and processing time limits, it is recommended to run coder upgrade in a separate process without a Drupal bootstrap. These options were set when this module was enabled. If Drupal core has been updated on this site, then click the button below to refresh the core theme information cache file at !file.', array('!file' => $path)), '#tree' => FALSE, ); $form['performance']['coder_upgrade_use_separate_process'] = array( '#type' => 'checkbox', '#title' => t('Use separate process'), '#default_value' => variable_get('coder_upgrade_use_separate_process', TRUE), '#description' => t('If checked, then coder upgrade will be run in a separate process without a Drupal bootstrap. This helps to minimize the chance of hitting PHP memory and processing time limits.'), ); $form['performance']['theme_cache'] = array( '#type' => 'submit', '#value' => t('Cache core theme information'), '#submit' => array('coder_upgrade_create_theme_cache_submit'), ); $form['#submit'][] = 'coder_upgrade_settings_form_submit'; return system_settings_form($form); } /** * Submit handler for the settings form. * * Rename module input and output directories based on user settings. */ function coder_upgrade_settings_form_submit($form, &$form_state) { $values = $form_state['values']; $op = isset($values['op']) ? $values['op'] : ''; $cur = variable_get('coder_upgrade_dir', DEADWOOD_DIR); $new = $op == t('Reset to defaults') ? DEADWOOD_DIR : $values['coder_upgrade_dir']; if ($new != $cur) { if (rename(coder_upgrade_directory_path($cur, FALSE), coder_upgrade_directory_path($new, FALSE))) { variable_set('coder_upgrade_dir_old', $new . '/old'); variable_set('coder_upgrade_dir_new', $new . '/new'); variable_set('coder_upgrade_dir_patch', $new . '/patch'); drupal_set_message(t('Base directory was renamed to ' . $new . '.')); } else { // Reset the directory variable. variable_set('coder_upgrade_dir', $cur); drupal_set_message(t('Could not rename base directory'), 'error'); } } } /** * Submit callback; creates a core theme information cache file. * * This file is used when the upgrade routines are run in a separate process. */ function coder_upgrade_create_theme_cache_submit($form, &$form_state) { global $_coder_upgrade_theme_registry; module_load_include('inc', 'coder_upgrade', 'conversions/coder_upgrade.main'); module_load_include('inc', 'coder_upgrade', 'conversions/coder_upgrade.begin'); // Create a core theme information cache. coder_upgrade_cache_theme_registry(); // Write cache to file. $path = coder_upgrade_path('theme_cache'); if (file_put_contents($path, serialize($_coder_upgrade_theme_registry)) === FALSE) { drupal_set_message(t('Could not write to core theme information cache file.'), 'error'); return; } drupal_set_message(t('Core theme information cache created at !file.', array('!file' => $path))); } /** * Form builder for the module conversion form. * * The tab contents are assembled in helper functions which allows other modules * to "customize" this form directly without resorting to hook_form_alter. */ function coder_upgrade_conversions_form($form, &$form_state) { // Set default values. list($upgrades, $extensions, $directories, $modules) = coder_upgrade_conversions_form_defaults($form_state); // Build the form. $form['tabs'] = array( '#type' => 'vertical_tabs', '#default_tab' => 'edit-directories', ); $form['tabs']['upgrades'] = coder_upgrade_upgrades_build($upgrades); $form['tabs']['extensions'] = coder_upgrade_extensions_build($extensions); $form['tabs']['directories'] = coder_upgrade_directories_build($directories); $form['tabs']['modules'] = coder_upgrade_modules_build($modules); $form['convert'] = array( '#type' => 'submit', '#value' => t('Convert files'), // '#disabled' => empty($form['tabs']['directories']['list']['#options']), ); return $form; } /** * Returns form content for upgrades tab. * * @param array $upgrades * User selections or default values. * @return array * Form item. */ function coder_upgrade_upgrades_build(&$upgrades) { // Create the list of upgrade options from the coder upgrade plug-ins. // Maintain a secondary list based on title only, to make sorting possible. $upgrades_all = _coder_upgrade_upgrades(); foreach ($upgrades_all as $name => $upgrade) { $upgrade_options[$name] = isset($upgrade['link']) ? l($upgrade['title'], $upgrade['link']) : $upgrade['title']; if (isset($upgrade['description'])) { $upgrade_options[$name] .= ' (' . $upgrade['description'] . ')'; } $upgrades_sort[$name] = $upgrade['title']; } // Sort the upgrades by title. asort($upgrades_sort); foreach ($upgrades_sort as $name => $upgrade) { $upgrades_sort[$name] = $upgrade_options[$name]; } // Build the upgrade list. $header = array( 'category' => array('data' => t('Category'), 'field' => 'category'), // 'description' => array('data' => t('Description'), 'field' => 'description'), ); $i = 0; $rows = array(); foreach ($upgrades_sort as $name => $upgrade) { $row = array(); $row['category'] = $upgrades_sort[$name]; $row['description'] = 'Missing'; $row['#weight'] = ++$i; $rows[$name] = $row; } $upgrade_fs = array( '#type' => 'fieldset', '#title' => t('Upgrades'), '#description' => t('Apply the selected conversion routines ...'), '#tree' => TRUE, ); $upgrade_fs['list'] = array( '#type' => 'tableselect', '#header' => $header, '#options' => $rows, '#default_value' => isset($upgrades) ? $upgrades : array(), '#empty' => t('No routines available'), ); return $upgrade_fs; } /** * Returns form content for file extensions tab. * * @param array $extensions * User selections or default values. * @return array * Form item. */ function coder_upgrade_extensions_build(&$extensions) { // Build the file extension list. $types = array( 'inc' => 'PHP code files', 'info' => 'Info files used with module installation', 'install' => 'PHP code files used with module installation, update and uninstallation', 'module' => 'PHP code files', 'php' => 'PHP code files', 'profile' => 'PHP code files used with site installation', 'test' => 'SimpleTest files', 'theme' => 'PHP code files used with theming', ); $header = array( 'extension' => array('data' => t('Extension'), 'field' => 'extension'), 'description' => array('data' => t('Description'), 'field' => 'description'), ); $i = 0; $rows = array(); foreach ($types as $key => $description) { $row = array(); $row['extension'] = $key; $row['description'] = $description; $row['#weight'] = ++$i; $rows[$key] = $row; } $extension_fs = array( '#type' => 'fieldset', '#title' => t('Extensions'), '#description' => t('... to files with the selected file extensions ...'), '#tree' => TRUE, ); $extension_fs['list'] = array( '#type' => 'tableselect', '#header' => $header, '#options' => $rows, '#default_value' => isset($extensions) ? $extensions : array(), '#empty' => t('No extensions available'), ); return $extension_fs; } /** * Returns form content for directories tab. * * @param array $directories * User selections or default values. * @return array * Form item. */ function coder_upgrade_directories_build(&$directories) { // Build the directory list. $deadwood_dir = variable_get('coder_upgrade_dir_old', DEADWOOD_OLD); $path = realpath(coder_upgrade_directory_path('old', FALSE)); $dirs = coder_upgrade_scan_directory($path); if (!$dirs) { drupal_set_message(t('Please place modules to be converted in @path.', array('@path' => $path)), 'error'); } $header = array( 'name' => array('data' => t('Name'), 'field' => 'name'), 'path' => array('data' => t('Location'), 'field' => 'path'), ); $i = 0; $rows = array(); foreach ($dirs as $dir) { $row = array(); $row['name'] = isset($directories[$dir]) ? l($dir, coder_upgrade_patch_link($dir)) : $dir; $row['path'] = $deadwood_dir . '/' . $dir; $row['#weight'] = ++$i; $rows[$dir] = $row; } $directory_fs = array( '#type' => 'fieldset', '#title' => t('Directories'), '#description' => t('... residing in the selected directories (beneath the files directory), or ...'), '#tree' => TRUE, ); $directory_fs['list'] = array( '#type' => 'tableselect', '#header' => $header, '#options' => $rows, '#default_value' => isset($directories) ? $directories : array(), '#empty' => t('No directories available'), ); return $directory_fs; } /** * Returns form content for modules tab. * * @param array $modules * User selections or default values. * @return array * Form item. */ function coder_upgrade_modules_build(&$modules) { // Build the module list. $header = array( 'name' => array('data' => t('Name'), 'field' => 'name'), 'path' => array('data' => t('Location'), 'field' => 'path'), ); $i = 0; $rows = coder_upgrade_module_list(); foreach ($rows as $key => $row) { $rows[$key]['name'] = isset($modules[$key]) ? l($row['name'], coder_upgrade_patch_link($key)) : $row['name']; $rows[$key]['#weight'] = ++$i; } $module_fs = array( '#type' => 'fieldset', '#title' => t('Modules'), '#description' => t('... residing in the selected modules (beneath the drupal directory).'), '#tree' => TRUE, ); $module_fs['list'] = array( '#type' => 'tableselect', '#header' => $header, '#options' => $rows, '#default_value' => isset($modules) ? $modules : array(), '#empty' => t('No modules available'), ); return $module_fs; } /** * Validation handler for the module conversion form. */ function coder_upgrade_conversions_form_validate($form, &$form_state) { // Set keys to validate. $keys = coder_upgrade_selection_types($form_state); $count = 0; // Validate the user selections. $values = $form_state['values']; foreach ($values as $key => $list) { if (!in_array($key, $keys)) { continue; } $selections = coder_upgrade_selections_extract($list['list']); if (in_array($key, array('upgrades', 'extensions')) && !count($selections)) { form_set_error($key, t('Please select at least one item in the %item tab.', array('%item' => ucfirst($key)))); } elseif (in_array($key, array('directories', 'modules'))) { $count += count($selections); } } // Determine if keys contains both 'directories' and 'modules'. $test = array_diff(array('directories', 'modules'), $keys); if (!$count && empty($test)) { form_set_error($key, t('Please select at least one item in the %item1 or %item2 tabs.', array('%item1' => ucfirst('directories'), '%item2' => ucfirst('modules')))); } } /** * Submit handler for the module conversion form. * * Execute the selected module conversion code on the selected file types in the * selected directories or modules. */ function coder_upgrade_conversions_form_submit($form, &$form_state) { // Rebuild form with user selections. $form_state['rebuild'] = TRUE; // Apply conversion functions. $success = coder_upgrade_conversions_apply($form_state); if ($success) { drupal_set_message(t('Module conversion code was run.')); drupal_set_message(t('Click to view the !log.', array('!log' => l(t('conversion log file'), coder_upgrade_path('log'))))); drupal_set_message(t('Patch files may be viewed by clicking on Name links in the Directories and Modules tabs below.')); } else { drupal_set_message(t('Module conversion code was not run.'), 'error'); } } /** * Applies the module conversion code. * * Execute the selected module conversion code on the selected file types in the * selected directories or modules. * * @return boolean * TRUE if conversion code was successful, FALSE otherwise. */ function coder_upgrade_conversions_apply($form_state) { // Prepare conversion parameters. list($upgrades, $extensions, $items) = coder_upgrade_conversions_prepare($form_state); // Apply conversion functions. module_load_include('inc', 'coder_upgrade', 'conversions/coder_upgrade.main'); if (variable_get('coder_upgrade_use_separate_process', TRUE)) { // Conversion code will be run in a separate process. drupal_set_message(t('Module conversion code will run in a separate process.')); $path = coder_upgrade_parameters_save($upgrades, $extensions, $items); $script = drupal_get_path('module', 'coder_upgrade') . '/scripts/coder_upgrade.run.php'; $output = coder_upgrade_directory_path('base') . 'coder_upgrade.run.txt'; $command = "php $script -- file=$path > $output"; // " 2>&1"; // Execute the command and capture the output. exec($command, $errors, $success); $success = $success === 0; if ($success && !empty($errors)) { drupal_set_message(trim(implode("\n", $errors)), 'error'); } } else { // Conversion code will be run in the same process. drupal_set_message(t('Module conversion code will run in the same process.')); $success = coder_upgrade_start($upgrades, $extensions, $items); } return $success; } /** * Returns the parameters to submit for module conversion. */ function coder_upgrade_conversions_prepare($form_state) { // Gather the submitted parameters. list($upgrades, $extensions, $directories, $modules) = coder_upgrade_selections($form_state); // TODO Cache this list so we don't have to query all the files again. $upgrades_all = _coder_upgrade_upgrades(); foreach ($upgrades as $module => $upgrade) { $upgrades[$module] = array( 'files' => isset($upgrades_all[$module]['files']) ? $upgrades_all[$module]['files'] : array(), ); } $old_dir = DRUPAL_ROOT . '/' . coder_upgrade_directory_path('old'); $new_dir = DRUPAL_ROOT . '/' . coder_upgrade_directory_path('new'); // Combine directory and module items into a single list. // Omit name from key so as to allow for duplicate names. // TODO Handle duplicate names when making new conversion directories. // Could intersect keys in $directories, $modules; then add counter suffix to new_dir??? // Use global counter??? Or copy $directories to files/dirs and $modules to files/modules??? $items = array(); foreach ($directories as $key => $directory) { $items[] = array( 'name' => $key, 'old_dir' => $old_dir . $key, 'new_dir' => $new_dir . $key, ); } $last = 'xx_XX'; $rows = coder_upgrade_module_list(); foreach ($modules as $key => $module) { if (isset($rows[$key])) { $row = $rows[$key]; if (strpos($row['dir'] . '/', $last . '/') === 0) { // Omit modules contained in subdirectory of a parent module. continue; } $last = $row['dir']; $items[] = array( 'name' => $key, 'old_dir' => $row['dir'], 'new_dir' => $new_dir . $key, ); } } return array($upgrades, $extensions, $items); } /** * Saves the runtime parameters to a file for use by script. */ function coder_upgrade_parameters_save($upgrades, $extensions, $items) { // Add path to upgrades array for use by script. foreach ($upgrades as $module => &$upgrade) { $upgrade['path'] = drupal_get_path('module', $module); } // Create paths array. $paths = array( 'files_base' => coder_upgrade_directory_path('', FALSE), 'modules_base' => str_replace('/coder', '', drupal_get_path('module', 'coder')), ); // Create variables array. $variables = array( 'coder_upgrade_dir' => variable_get('coder_upgrade_dir', DEADWOOD_DIR), 'coder_upgrade_dir_patch' => variable_get('coder_upgrade_dir_patch', DEADWOOD_PATCH), 'coder_upgrade_replace_files' => variable_get('coder_upgrade_replace_files', FALSE), 'coder_upgrade_preserve_array_format' => variable_get('coder_upgrade_preserve_array_format', FALSE), 'coder_upgrade_enable_debug_output' => variable_get('coder_upgrade_enable_debug_output', FALSE), 'coder_upgrade_enable_parser_debug_output' => variable_get('coder_upgrade_enable_parser_debug_output', FALSE), 'coder_upgrade_use_separate_process' => variable_get('coder_upgrade_use_separate_process', TRUE), ); // Create parameters array. $parameters['paths'] = $paths; $parameters['theme_cache'] = coder_upgrade_path('theme_cache'); $parameters['variables'] = $variables; $parameters['upgrades'] = $upgrades; $parameters['extensions'] = $extensions; $parameters['items'] = $items; // Write parameters to file. $path = coder_upgrade_path('runtime'); // @todo Use random name and delete afterwards. file_put_contents($path, serialize($parameters)); return $path; } /** * Sets the default values to display on the module conversions form. * * @return array * Arrays of default values. */ function coder_upgrade_conversions_form_defaults($form_state) { // D7: the key is used (and the value is irrelevant); D6: the value. $upgrades = array('coder_upgrade' => 1); $extensions = array( 'inc' => TRUE, 'info' => TRUE, 'install' => TRUE, 'module' => TRUE, // 'php' => FALSE, // 'profile' => FALSE, // 'test' => FALSE, // 'theme' => FALSE, ); $directories = array(); // 'samples' => 1; $modules = array(); if (!isset($form_state['values'])) { return array($upgrades, $extensions, $directories, $modules); } // Set defaults from submitted values. return coder_upgrade_selections($form_state); } /** * Returns all submitted values. * * @param array $values * Array of $form_state['values']. * @return array * Arrays of submitted values. */ function coder_upgrade_selections($form_state) { // Initialize these as not all may be set by some form users. $upgrades = $extensions = $directories = $modules = array(); // Set keys to validate. $keys = coder_upgrade_selection_types($form_state); // Build arrays of each user selection type. $values = $form_state['values']; foreach ($keys as $key) { if (isset($values[$key])) { $$key = coder_upgrade_selections_extract($values[$key]['list']); } } return array($upgrades, $extensions, $directories, $modules); } /** * Returns a list of submitted values. * * @param array $values * Array slice from $form_state['values']. * @return array * Array of submitted values. */ function coder_upgrade_selections_extract($values) { $selections = array(); foreach ($values as $key => $value) { if ($value) { $selections[$key] = $key; } } return $selections; } /** * Returns a list of selection types. * * @param array $form_state * Array of form state information. * @return array * Array of selection types to process. */ function coder_upgrade_selection_types($form_state) { if (isset($form_state['defaults']) && is_array($form_state['defaults']) && $form_state['defaults']) { return $form_state['defaults']; } return array('upgrades', 'extensions', 'directories', 'modules'); } /** * Returns list of contributed modules. * * @param null $core * Indicates whether to return core modules regardless of settings variable. * @param integer $status * Indicates status of modules to return. * @return array * Array of contributed modules. */ function coder_upgrade_module_list($core = NULL, $status = -1) { $test = is_null($core) ? variable_get('coder_upgrade_upgrade_core', FALSE) : $core; $like = $test ? 'LIKE' : 'NOT LIKE'; $where = $status == -1 ? '' : 'AND status = :status'; // Faster to query DB than to rescan files using _system_get_module_data(). $sql = "SELECT name, filename, type, status, info, REPLACE(filename, CONCAT('/', name, '.', type), '') AS directory FROM {system} WHERE type = 'module' AND filename $like 'modules/%' $where ORDER BY directory, name"; $default_value = 0; $results = db_query($sql, array(':status' => $status)); $rows = array(); foreach ($results as $module) { $info = unserialize($module->info); $row = array(); $row['name'] = $info['name']; $row['path'] = dirname($module->filename); // $module->filename; $row['dir'] = $module->directory; // dirname($module->filename); // $row['filename'] = $module->filename; // Add this for later calls to module_list(). // $row['status'] = $module->status; // TODO Pull files from this table??? // Would need to change the conversion code to not read the filesystem. $rows[$module->name] = $row; } return $rows; } /** * Returns link to patch file. * * @param string $name * String of the patch filename. * @return string * Link to file. */ function coder_upgrade_patch_link($name) { return 'files/coder_upgrade/patches/' . $name . '.patch'; } /** * Returns patch file wrapped in html tags. * * @param string $filename * String of the patch filename. * @return string * HTML output. */ function coder_upgrade_patch_display($filename) { echo '
' . check_plain(file_get_contents(coder_upgrade_patch_path($filename))) . '
'; } /** * Returns path to patch file. * * @param string $filename * String of the patch filename. * @return string * Path to file. */ function coder_upgrade_patch_path($filename) { static $dirname = ''; if (!$dirname) { $dirname = coder_upgrade_directory_path('patch'); } return $dirname . "$filename"; }