diff --git a/core/lib/Drupal/Component/Graph/Graph.php b/core/lib/Drupal/Component/Graph/Graph.php index 8fc7d21..3a6620b 100644 --- a/core/lib/Drupal/Component/Graph/Graph.php +++ b/core/lib/Drupal/Component/Graph/Graph.php @@ -18,6 +18,11 @@ class Graph { protected $graph; /** + * Holds the last visit order, the opposite of a topological sort list. + */ + protected $lastVisited; + + /** * Instantiates the depth first search object. * * @param $graph @@ -40,6 +45,9 @@ class Graph { * $graph[1]['paths'][3] = 1; * $graph[2]['reverse_paths'][1] = 1; * $graph[3]['reverse_paths'][1] = 1; + * $graph[4]['reverse_paths'][2] = 1; + * $graph[4]['reverse_paths'][1] = 1; + * $graph[4]['unlisted_sink'] = TRUE; * @endcode */ public function __construct($graph) { @@ -61,16 +69,14 @@ public function __construct($graph) { * identifier. */ public function searchAndSort() { - $state = array( - // The order of last visit of the depth first search. This is the reverse - // of the topological order if the graph is acyclic. - 'last_visit_order' => array(), - // The components of the graph. - 'components' => array(), - ); + if (isset($this->lastVisited)) { + return; + } + // The components of the graph. + $components = array(); // Perform the actual search. foreach ($this->graph as $start => $data) { - $this->depthFirstSearch($state, $start); + $this->depthFirstSearch($components, $start, $start); } // We do such a numbering that every component starts with 0. This is useful @@ -78,7 +84,7 @@ public function searchAndSort() { // request, and then every 1 weighted etc. $component_weights = array(); - foreach ($state['last_visit_order'] as $vertex) { + foreach ($this->lastVisited as $vertex) { $component = $this->graph[$vertex]['component']; if (!isset($component_weights[$component])) { $component_weights[$component] = 0; @@ -92,10 +98,8 @@ public function searchAndSort() { /** * Performs a depth-first search on a graph. * - * @param $state - * An associative array. The key 'last_visit_order' stores a list of the - * vertices visited. The key components stores list of vertices belonging - * to the same the component. + * @param $components + * The list of vertices belonging to the same the component. * @param $start * An arbitrary vertex where we started traversing the graph. * @param $component @@ -103,11 +107,7 @@ public function searchAndSort() { * * @see Drupal\Component\Graph\Graph::searchAndSort() */ - protected function depthFirstSearch(&$state, $start, &$component = NULL) { - // Assign new component for each new vertex, i.e. when not called recursively. - if (!isset($component)) { - $component = $start; - } + protected function depthFirstSearch(&$components, $start, &$component) { // Nothing to do, if we already visited this vertex. if (isset($this->graph[$start]['paths'])) { return; @@ -117,7 +117,7 @@ protected function depthFirstSearch(&$state, $start, &$component = NULL) { // Assign $start to the current component. $this->graph[$start]['component'] = $component; - $state['components'][$component][] = $start; + $components[$component][] = $start; // Visit edges of $start. if (isset($this->graph[$start]['edges'])) { @@ -129,34 +129,50 @@ protected function depthFirstSearch(&$state, $start, &$component = NULL) { // This vertex already has a component, use that from now on and // reassign all the previously explored vertices. $new_component = $this->graph[$end]['component']; - foreach ($state['components'][$component] as $vertex) { + foreach ($components[$component] as $vertex) { $this->graph[$vertex]['component'] = $new_component; - $state['components'][$new_component][] = $vertex; + $components[$new_component][] = $vertex; } - unset($state['components'][$component]); + unset($components[$component]); $component = $new_component; } - // Only visit existing vertices. - if (isset($this->graph[$end])) { - // Visit the connected vertex. - $this->depthFirstSearch($state, $end, $component); - - // All vertices reachable by $end are also reachable by $start. - $this->graph[$start]['paths'] += $this->graph[$end]['paths']; + if (!isset($this->graph[$end])) { + $this->graph[$end]['unlisted_sink'] = TRUE; } + // Visit the connected vertex. + $this->depthFirstSearch($components, $end, $component); + + // All vertices reachable by $end are also reachable by $start. + $this->graph[$start]['paths'] += $this->graph[$end]['paths']; } } // Now that any other subgraph has been explored, add $start to all reverse // paths. foreach ($this->graph[$start]['paths'] as $end => $v) { - if (isset($this->graph[$end])) { - $this->graph[$end]['reverse_paths'][$start] = $v; - } + $this->graph[$end]['reverse_paths'][$start] = $v; } // Record the order of the last visit. This is the reverse of the // topological order if the graph is acyclic. - $state['last_visit_order'][] = $start; + $this->lastVisited[] = $start; + } + + /** + * Calculates Topological Sorted List of Vertices. + * + * The list is DepthFirst sorted so we can loop over the result like + * @code + * foreach ($graph->getTopologicalSortedList() as $module) { + * module_enable($module); + * } + * @endcode + * + * @return array + */ + public function getTopologicalSortedList() { + $this->searchAndSort(); + return array_reverse($this->lastVisited); } + } diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index c45c1cd..fe7c9e4 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -179,9 +179,16 @@ public function buildModuleDependencies(array $modules) { $graph_object = new Graph($graph); $graph = $graph_object->searchAndSort(); foreach ($graph as $module_name => $data) { - $modules[$module_name]->required_by = isset($data['reverse_paths']) ? $data['reverse_paths'] : array(); - $modules[$module_name]->requires = isset($data['paths']) ? $data['paths'] : array(); - $modules[$module_name]->sort = $data['weight']; + if (empty($data['unlisted_sink'])) { + $modules[$module_name]->required_by = isset($data['reverse_paths']) ? $data['reverse_paths'] : array(); + $modules[$module_name]->requires = isset($data['paths']) ? $data['paths'] : array(); + $modules[$module_name]->sort = $data['weight']; + } + else { + foreach ($data['reverse_paths'] as $key => $v) { + $modules[$key]->missing[$module_name] = $module_name; + } + } } return $modules; } diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index b483e7e..47d987c 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -853,15 +853,18 @@ function system_modules($form, $form_state = array()) { $extra['disabled'] = TRUE; $extra['required_by'][] = $distribution_name . (!empty($module->info['explanation']) ? ' ('. $module->info['explanation'] .')' : ''); } + if (!empty($module->missing)) { + $extra['disabled'] = TRUE; + foreach ($module->missing as $requires) { + $extra['requires'][$requires] = t('@module (missing)', array('@module' => drupal_ucfirst($requires))); + $extra['missing'][$requires] = TRUE; + } + } // If this module requires other modules, add them to the array. foreach ($module->requires as $requires => $v) { - if (!isset($files[$requires])) { - $extra['requires'][$requires] = t('@module (missing)', array('@module' => drupal_ucfirst($requires))); - $extra['disabled'] = TRUE; - } // Only display visible modules. - elseif (isset($visible_files[$requires])) { + if (empty($extra['missing'][$requires])) { $requires_name = $files[$requires]->info['name']; // Disable this module if it is incompatible with the dependency's version. if ($incompatible_version = drupal_check_incompatibility($v, str_replace(DRUPAL_CORE_COMPATIBILITY . '-', '', $files[$requires]->info['version']))) { @@ -1158,7 +1161,7 @@ function system_modules_submit($form, &$form_state) { $dependencies = array(); foreach (array_keys($files[$name]->requires) as $required) { if (empty($modules[$required]['enabled'])) { - if (isset($files[$required])) { + if (empty($files[$name]->missing[$required])) { $dependencies[] = $files[$required]->info['name']; $modules[$required]['enabled'] = TRUE; }