diff --git a/core/lib/Drupal/Component/Graph/Graph.php b/core/lib/Drupal/Component/Graph/Graph.php
index 8fc7d21..f5c560b 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($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;
}