Skip to content
Snippets Groups Projects
Commit f24e304f authored by Steven Wittens's avatar Steven Wittens
Browse files

#84875: Dependency system for core

parent 96533c93
No related branches found
No related tags found
2 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10
...@@ -130,7 +130,30 @@ function module_rebuild_cache() { ...@@ -130,7 +130,30 @@ function module_rebuild_cache() {
db_query("INSERT INTO {system} (name, description, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", $file->name, $file->info['description'], 'module', $file->filename, $file->status, $file->throttle, $bootstrap); db_query("INSERT INTO {system} (name, description, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", $file->name, $file->info['description'], 'module', $file->filename, $file->status, $file->throttle, $bootstrap);
} }
} }
$files = _module_build_dependents($files);
return $files;
}
/**
* Find dependents; modules that are required by other modules.
* Adds an array of dependents to the $file->info array.
*
* @return
* The list of files array with dependents added where applicable.
*/
function _module_build_dependents($files) {
foreach ($files as $filename => $file) {
if (is_array($file->info['dependencies'])) {
foreach ($file->info['dependencies'] as $dependency) {
if (!empty($files[$dependency]) && is_array($files[$dependency]->info)) {
if (!isset($files[$dependency]->info['dependents'])) {
$files[$dependency]->info['dependents'] = array();
}
$files[$dependency]->info['dependents'][] = $filename;
}
}
}
}
return $files; return $files;
} }
...@@ -148,8 +171,18 @@ function module_rebuild_cache() { ...@@ -148,8 +171,18 @@ function module_rebuild_cache() {
* and variable_set() for that. * and variable_set() for that.
* - You may not use double-quotes in a value. * - You may not use double-quotes in a value.
* *
* Information stored in the module.info file:
* name - The real name of the module for display purposes.
* description - A brief description of the module.
* dependencies - A space delimited list of the short names (shortname) of other modules this module depends on.
*
* Example of .info file:
* name = Forum
* description = Enables threaded discussions about general topics.
* dependencies = taxonomy comment
*
* @param $filename * @param $filename
* The file we are parsing. Accepts file with relative or absolute path. * The file we are parsing. Accepts file with relative or absolute path.
* @return * @return
* The info array. * The info array.
*/ */
...@@ -159,6 +192,9 @@ function _module_parse_info_file($filename) { ...@@ -159,6 +192,9 @@ function _module_parse_info_file($filename) {
if (file_exists($filename)) { if (file_exists($filename)) {
$info = parse_ini_file($filename); $info = parse_ini_file($filename);
} }
if (isset($info['dependencies'])) {
$info['dependencies'] = explode(" ", $info['dependencies']);
}
return $info; return $info;
} }
......
; $Id$ ; $Id$
name = Forum name = Forum
description = Enables threaded discussions about general topics. description = Enables threaded discussions about general topics.
dependencies = taxonomy comment
...@@ -39,6 +39,20 @@ div.admin .expert-link { ...@@ -39,6 +39,20 @@ div.admin .expert-link {
padding-right: 4px; padding-right: 4px;
} }
div.admin-dependencies, div.admin-required {
font-size: 0.9em;
color: #444;
}
span.admin-disabled {
color: #800;
}
span.admin-enabled {
color: #080;
}
span.admin-missing {
color: #f00;
}
/** /**
* Formatting for status report * Formatting for status report
*/ */
......
...@@ -43,8 +43,11 @@ function system_help($section) { ...@@ -43,8 +43,11 @@ function system_help($section) {
$theme = array_pop($reference); $theme = array_pop($reference);
return t('<p>These options control the display settings for the <code>%template</code> theme. When your site is displayed using this theme, these settings will be used. By clicking "Reset to defaults," you can choose to use the <a href="@global">global settings</a> for this theme.</p>', array('%template' => $theme, '@global' => url('admin/build/themes/settings'))); return t('<p>These options control the display settings for the <code>%template</code> theme. When your site is displayed using this theme, these settings will be used. By clicking "Reset to defaults," you can choose to use the <a href="@global">global settings</a> for this theme.</p>', array('%template' => $theme, '@global' => url('admin/build/themes/settings')));
case 'admin/settings/modules': case 'admin/settings/modules':
return t('<p>Modules are plugins for Drupal that extend its core functionality. Here you can select which modules are enabled. Click on the name of the module in the navigation menu for their individual configuration pages. Once a module is enabled, new <a href="@permissions">permissions</a> might be made available. Modules can automatically be temporarily disabled to reduce server load when your site becomes extremely busy by enabling the throttle.module and checking throttle. The auto-throttle functionality must be enabled on the <a href="@throttle">throttle configuration page</a> after having enabled the throttle module.</p> if (empty($_POST) || $_POST['op'] != t('Save configuration')) {
return t('<p>Modules are plugins for Drupal that extend its core functionality. Here you can select which modules are enabled. Click on the name of the module in the navigation menu for their individual configuration pages. Once a module is enabled, new <a href="@permissions">permissions</a> might be made available. Modules can automatically be temporarily disabled to reduce server load when your site becomes extremely busy by enabling the throttle.module and checking throttle. The auto-throttle functionality must be enabled on the <a href="@throttle">throttle configuration page</a> after having enabled the throttle module.</p>
<p>It is important that <a href="@update-php">update.php</a> is run every time a module is updated to a newer version.</p>', array('@permissions' => url('admin/user/access'), '@throttle' => url('admin/settings/throttle'), '@update-php' => $base_url .'/update.php')); <p>It is important that <a href="@update-php">update.php</a> is run every time a module is updated to a newer version.</p>', array('@permissions' => url('admin/user/access'), '@throttle' => url('admin/settings/throttle'), '@update-php' => $base_url .'/update.php'));
}
break;
case 'admin/logs/status': case 'admin/logs/status':
return t('<p>Here you can find a short overview of your Drupal site\'s parameters as well as any problems detected with your installation. It is useful to copy/paste this information when you need support.</p>'); return t('<p>Here you can find a short overview of your Drupal site\'s parameters as well as any problems detected with your installation. It is useful to copy/paste this information when you need support.</p>');
} }
...@@ -1217,80 +1220,245 @@ function system_themes_submit($form_id, $form_values) { ...@@ -1217,80 +1220,245 @@ function system_themes_submit($form_id, $form_values) {
} }
/** /**
* Menu callback; displays a listing of all modules. * Menu callback; provides module enable/disable interface.
*
* Modules can be enabled or disabled and set for throttling if the throttle module is enabled.
* The list of modules gets populated by module.info files, which contain each module's name,
* description and dependencies.
* @sa _module_parse_info_file for information on module.info descriptors.
*
* Dependency checking is performed to ensure that a module cannot be enabled if the module has
* disabled dependencies and also to ensure that the module cannot be disabled if the module has
* enabled dependents.
*
* @return
* The form array.
*/ */
function system_modules() { function system_modules($form_values = NULL) {
// Get current list of modules // Get current list of modules.
$files = module_rebuild_cache(); $files = module_rebuild_cache();
if ($confirm_form = system_modules_confirm_form($files, $form_values)) {
return $confirm_form;
}
// Store module list for validation callback.
$form['validation_modules'] = array('#type' => 'value', '#value' => $files);
// Create storage for disabled modules as browser will disable checkboxes.
$form['disabled_modules'] = array('#type' => 'value', '#value' => array());
// Array for disabling checkboxes in callback system_module_disable.
$disabled = array();
// Traverse the files retrieved and build the form.
foreach ($files as $filename => $file) { foreach ($files as $filename => $file) {
$info = $file->info; $form['name'][$filename] = array('#value' => $file->info['name']);
$form['name'][$file->name] = array('#value' => $info['name']); $form['description'][$filename] = array('#value' => t($file->info['description']));
$form['description'][$file->name] = array('#value' => t($info['description'])); $options[$filename] = '';
$options[$file->name] = '';
if ($file->status) { if ($file->status) {
$status[] = $file->name; $status[] = $file->name;
} }
if ($file->throttle) { if ($file->throttle) {
$throttle[] = $file->name; $throttle[] = $file->name;
} }
$dependencies = array();
// Check for missing dependencies.
if (is_array($file->info['dependencies'])) {
foreach ($file->info['dependencies'] as $dependency) {
if (!isset($files[$dependency]) || !$files[$dependency]->status) {
if (isset($files[$dependency])) {
$dependencies[] = $files[$dependency]->info['name'] . t(' (<span class="admin-disabled">disabled</span>)');
}
else {
$dependencies[] = drupal_ucfirst($dependency) . t(' (<span class="admin-missing">missing</span>)');
$disabled[] = $filename;
$form['disabled_modules']['#value'][$filename] = FALSE;
}
}
else {
$dependencies[] = $files[$dependency]->info['name'] . t(' (<span class="admin-enabled">enabled</span>)');
}
}
// Add text for dependencies.
if (!empty($dependencies)) {
$form['description'][$filename]['dependencies'] = array(
'#value' => t('Depends on: !dependencies', array('!dependencies' => implode(', ', $dependencies))),
'#prefix' => '<div class="admin-dependencies">',
'#suffix' => '</div>',
);
}
}
// Mark dependents disabled so user can not remove modules being depended on.
$dependents = array();
if (is_array($file->info['dependents'])) {
foreach ($file->info['dependents'] as $dependent) {
if ($files[$dependent]->status == 1) {
$dependents[] = $files[$dependent]->info['name'] . t(' (<span class="admin-enabled">enabled</span>)');
$disabled[] = $filename;
$form['disabled_modules']['#value'][$filename] = TRUE;
}
else {
$dependents[] = $files[$dependent]->info['name'] . t(' (<span class="admin-disabled">disabled</span>)');
}
}
}
// Add text for enabled dependents.
if (!empty($dependents)){
$form['description'][$filename]['required'] = array(
'#value' => t('Required by: !required', array('!required' => implode(', ', $dependents))),
'#prefix' => '<div class="admin-required">',
'#suffix' => '</div>',
);
}
} }
// Handle status checkboxes, including overriding the generated // Merge in required modules.
// checkboxes for required modules. $modules_required = array('block', 'filter', 'node', 'system', 'user', 'watchdog');
$form['status'] = array('#type' => 'checkboxes', '#default_value' => $status, '#options' => $options); foreach ($modules_required as $required) {
$required = array('block', 'filter', 'node', 'system', 'user', 'watchdog'); $form['description'][$required]['core'] = array(
foreach ($required as $require) { '#value' => t('Required for Drupal core'),
$form['status'][$require] = array('#type' => 'hidden', '#value' => 1, '#suffix' => t('required')); '#prefix' => '<div class="admin-required">',
'#suffix' => '</div>',
);
$disabled[] = $required;
$form['disabled_modules']['#value'][$required] = TRUE;
} }
/** // Handle status checkboxes, including overriding
* Handle throttle checkboxes, including overriding the generated checkboxes for required modules. // the generated checkboxes for required modules.
*/ $form['status'] = array(
'#type' => 'checkboxes',
'#default_value' => $status,
'#options' => $options,
'#process' => array(
'expand_checkboxes' => array(),
'system_modules_disable' => array($disabled),
),
);
// Handle throttle checkboxes, including overriding the
// generated checkboxes for required modules.
if (module_exists('throttle')) { if (module_exists('throttle')) {
$form['throttle'] = array('#type' => 'checkboxes', '#default_value' => $throttle, '#options' => $options); $form['throttle'] = array(
$throttle_required = array_merge($required, array('throttle')); '#type' => 'checkboxes',
foreach ($throttle_required as $require) { '#default_value' => $throttle,
$form['throttle'][$require] = array('#type' => 'hidden', '#value' => 0, '#suffix' => t('required')); '#options' => $options,
} '#process' => array(
'expand_checkboxes' => array(),
'system_modules_disable' => array(array_merge($modules_required, array('throttle'))),
),
);
} }
$form['buttons']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration')); $form['buttons']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save configuration'),
);
$form['#multistep'] = TRUE;
return $form; return $form;
} }
function theme_system_modules($form) { /**
foreach (element_children($form['name']) as $key) { * Form process callback function to disable check boxes.
$row = array(); */
$row[] = array('data' => drupal_render($form['status'][$key]), 'align' => 'center'); function system_modules_disable($form, $edit, $disabled) {
foreach ($disabled as $key) {
$form[$key]['#attributes']['disabled'] = 'disabled';
}
return $form;
}
if (module_exists('throttle')) { function system_modules_confirm_form($modules, $form_values = array()) {
$row[] = array('data' => drupal_render($form['throttle'][$key]), 'align' => 'center'); $form = array();
$items = array();
// Check values for submitted dependency errors.
if ($dependencies = system_module_build_dependencies($modules, $form_values)) {
// preserve the already switched on modules
foreach ($modules as $name => $module) {
if ($module->status) {
$form['status'][$name] = array('#type' => 'hidden', '#value' => 1);
}
} }
$row[] = drupal_render($form['name'][$key]);
$row[] = drupal_render($form['description'][$key]);
$rows[] = $row;
}
$header = array(t('Enabled')); $form['validation_modules'] = array('#type' => 'value', '#value' => $modules);
if (module_exists('throttle')) { $form['status']['#tree'] = TRUE;
$header[] = t('Throttle'); foreach ($dependencies as $name => $missing_dependencies) {
$form['status'][$name] = array('#type' => 'hidden', '#value' => 1);
foreach ($missing_dependencies as $k => $dependency) {
$form['status'][$dependency] = array('#type' => 'hidden', '#value' => 1);
$info = $modules[$dependency]->info;
$missing_dependencies[$k] = $info['name'] ? $info['name'] : drupal_ucfirst($dependency);
}
$t_argument = array(
'%module' => $modules[$name]->info['name'],
'%dependencies' => implode(', ', $missing_dependencies),
'!modules' => format_plural(count($missing_dependencies), ' module', ' modules'),
);
$items[] = t('You must enable the %dependencies !modules to install %module.', $t_argument);
}
$form['text'] = array('#value' => theme('item_list', $items));
}
if ($form) {
// Set some default form values
$form = confirm_form(
$form,
t('Some required modules must be enabled'),
'admin/settings/modules',
t('Would you like to continue with enabling the above?'),
t('Continue'),
t('Cancel'));
return $form;
} }
$header[] = t('Name');
$header[] = t('Description');
$output = theme('table', $header, $rows);
$output .= drupal_render($form);
return $output;
} }
function system_module_build_dependencies($modules, $form_values) {
static $dependencies;
if (!isset($dependencies) && isset($form_values)) {
$dependencies = array();
foreach ($modules as $name => $module) {
// If the module is disabled, will be switched on and it has dependencies.
if (!$module->status && $form_values['status'][$name] && isset($module->info['dependencies'])) {
foreach ($module->info['dependencies'] as $dependency) {
if (!$form_values['status'][$dependency] && isset($modules[$dependency])) {
if (!isset($dependencies[$name])) {
$dependencies[$name] = array();
}
$dependencies[$name][] = $dependency;
}
}
}
}
}
return $dependencies;
}
/**
* Submit callback; handles modules form submission.
*/
function system_modules_submit($form_id, $form_values) { function system_modules_submit($form_id, $form_values) {
include_once './includes/install.inc'; include_once './includes/install.inc';
$new_modules = array(); $new_modules = array();
// Enable/disable modules that have already been installed // Merge in disabled active modules since they should be enabled.
// They don't appear because disabled checkboxes are not submited
// by browsers.
$form_values['status'] = array_merge($form_values['status'], $form_values['disabled_modules']);
// Check values for dependency that we can't install.
if ($dependencies = system_module_build_dependencies($form_values['validation_modules'], $form_values)) {
// These are the modules that depend on existing modules.
foreach (array_keys($dependencies) as $name) {
$form_values['status'][$name] = 0;
}
}
foreach ($form_values['status'] as $key => $choice) { foreach ($form_values['status'] as $key => $choice) {
if ($choice) { if ($choice) {
if (drupal_get_installed_schema_version($key) == SCHEMA_UNINSTALLED) { if (drupal_get_installed_schema_version($key) == SCHEMA_UNINSTALLED) {
...@@ -1305,30 +1473,68 @@ function system_modules_submit($form_id, $form_values) { ...@@ -1305,30 +1473,68 @@ function system_modules_submit($form_id, $form_values) {
} }
} }
module_list(TRUE, FALSE); $old_module_list = module_list();
// Install new modules // Install new modules.
foreach ($new_modules as $module) { foreach ($new_modules as $module) {
if (drupal_check_module($module)) { if (drupal_check_module($module)) {
drupal_install_module($module); drupal_install_module($module);
} }
} }
$current_module_list = module_list(TRUE, FALSE);
if (is_array($form_values['throttle'])) { if (is_array($form_values['throttle'])) {
foreach ($form_values['throttle'] as $key => $choice) { foreach ($form_values['throttle'] as $key => $choice) {
if ($choice) { db_query("UPDATE {system} SET throttle = %d WHERE type = 'module' and name = '%s'", $choice ? 1 : 0, $key);
db_query("UPDATE {system} SET throttle = 1 WHERE type = 'module' and name = '%s'", $key);
}
} }
} }
menu_rebuild(); if ($old_module_list != $current_module_list) {
node_types_rebuild(); menu_rebuild();
node_types_rebuild();
drupal_set_message(t('The configuration options have been saved.'));
}
drupal_set_message(t('The configuration options have been saved.')); // If there where unmet dependencies and they haven't confirmed don't redirect.
if ($dependencies && !isset($form_values['confirm'])) {
return FALSE;
}
return 'admin/settings/modules'; return 'admin/settings/modules';
} }
/**
* Theme call back for the modules form.
*/
function theme_system_modules($form) {
if (isset($form['confirm'])) {
return drupal_render($form);
}
foreach (element_children($form['name']) as $key) {
$row = array();
$row[] = array('data' => drupal_render($form['status'][$key]), 'align' => 'center');
if (module_exists('throttle')) {
$row[] = array('data' => drupal_render($form['throttle'][$key]), 'align' => 'center');
}
$row[] = '<strong>'. drupal_render($form['name'][$key]) .'</strong>';
$row[] = drupal_render($form['description'][$key]);
$rows[] = $row;
}
$header = array(t('Enabled'));
if (module_exists('throttle')) {
$header[] = t('Throttle');
}
$header[] = t('Name');
$header[] = t('Description');
$output = theme('table', $header, $rows);
$output .= drupal_render($form);
return $output;
}
/** /**
* Menu callback: run cron manually. * Menu callback: run cron manually.
*/ */
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment