Chris@0: root = $root; Chris@0: $this->sitePath = $site_path; Chris@0: $this->enabledModules = $enabled_modules; Chris@0: $this->keyValue = $key_value; Chris@0: $this->includeTests = $include_tests; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets all available update functions. Chris@0: * Chris@0: * @return callable[] Chris@0: * A list of update functions. Chris@0: */ Chris@0: protected function getAvailableUpdateFunctions() { Chris@0: $regexp = '/^(?.+)_' . $this->updateType . '_(?.+)$/'; Chris@0: $functions = get_defined_functions(); Chris@0: Chris@0: $updates = []; Chris@0: foreach (preg_grep('/_' . $this->updateType . '_/', $functions['user']) as $function) { Chris@0: // If this function is a module update function, add it to the list of Chris@0: // module updates. Chris@0: if (preg_match($regexp, $function, $matches)) { Chris@0: if (in_array($matches['module'], $this->enabledModules)) { Chris@0: $updates[] = $matches['module'] . '_' . $this->updateType . '_' . $matches['name']; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: // Ensure that the update order is deterministic. Chris@0: sort($updates); Chris@0: return $updates; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Find all update functions that haven't been executed. Chris@0: * Chris@0: * @return callable[] Chris@0: * A list of update functions. Chris@0: */ Chris@0: public function getPendingUpdateFunctions() { Chris@0: // We need a) the list of active modules (we get that from the config Chris@0: // bootstrap factory) and b) the path to the modules, we use the extension Chris@0: // discovery for that. Chris@0: Chris@0: $this->scanExtensionsAndLoadUpdateFiles(); Chris@0: Chris@0: // First figure out which hook_{$this->updateType}_NAME got executed Chris@0: // already. Chris@0: $existing_update_functions = $this->keyValue->get('existing_updates', []); Chris@0: Chris@0: $available_update_functions = $this->getAvailableUpdateFunctions(); Chris@0: $not_executed_update_functions = array_diff($available_update_functions, $existing_update_functions); Chris@0: Chris@0: return $not_executed_update_functions; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Loads all update files for a given list of extension. Chris@0: * Chris@0: * @param \Drupal\Core\Extension\Extension[] $module_extensions Chris@0: * The extensions used for loading. Chris@0: */ Chris@0: protected function loadUpdateFiles(array $module_extensions) { Chris@0: // Load all the {$this->updateType}.php files. Chris@0: foreach ($this->enabledModules as $module) { Chris@0: if (isset($module_extensions[$module])) { Chris@0: $this->loadUpdateFile($module_extensions[$module]); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Loads the {$this->updateType}.php file for a given extension. Chris@0: * Chris@0: * @param \Drupal\Core\Extension\Extension $module Chris@0: * The extension of the module to load its file. Chris@0: */ Chris@0: protected function loadUpdateFile(Extension $module) { Chris@0: $filename = $this->root . '/' . $module->getPath() . '/' . $module->getName() . ".{$this->updateType}.php"; Chris@0: if (file_exists($filename)) { Chris@0: include_once $filename; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns a list of all the pending updates. Chris@0: * Chris@0: * @return array[] Chris@0: * An associative array keyed by module name which contains all information Chris@0: * about database updates that need to be run, and any updates that are not Chris@0: * going to proceed due to missing requirements. Chris@0: * Chris@0: * The subarray for each module can contain the following keys: Chris@0: * - start: The starting update that is to be processed. If this does not Chris@0: * exist then do not process any updates for this module as there are Chris@0: * other requirements that need to be resolved. Chris@0: * - pending: An array of all the pending updates for the module including Chris@0: * the description from source code comment for each update function. Chris@0: * This array is keyed by the update name. Chris@0: */ Chris@0: public function getPendingUpdateInformation() { Chris@0: $functions = $this->getPendingUpdateFunctions(); Chris@0: Chris@0: $ret = []; Chris@0: foreach ($functions as $function) { Chris@0: list($module, $update) = explode("_{$this->updateType}_", $function); Chris@0: // The description for an update comes from its Doxygen. Chris@0: $func = new \ReflectionFunction($function); Chris@0: $description = trim(str_replace(["\n", '*', '/'], '', $func->getDocComment()), ' '); Chris@0: $ret[$module]['pending'][$update] = $description; Chris@0: if (!isset($ret[$module]['start'])) { Chris@0: $ret[$module]['start'] = $update; Chris@0: } Chris@0: } Chris@0: return $ret; Chris@0: } Chris@0: Chris@0: /** Chris@17: * Registers that update functions were executed. Chris@0: * Chris@0: * @param string[] $function_names Chris@0: * The executed update functions. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function registerInvokedUpdates(array $function_names) { Chris@0: $executed_updates = $this->keyValue->get('existing_updates', []); Chris@0: $executed_updates = array_merge($executed_updates, $function_names); Chris@0: $this->keyValue->set('existing_updates', $executed_updates); Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns all available updates for a given module. Chris@0: * Chris@0: * @param string $module_name Chris@0: * The module name. Chris@0: * Chris@0: * @return callable[] Chris@0: * A list of update functions. Chris@0: */ Chris@0: public function getModuleUpdateFunctions($module_name) { Chris@0: $this->scanExtensionsAndLoadUpdateFiles(); Chris@0: $all_functions = $this->getAvailableUpdateFunctions(); Chris@0: Chris@0: return array_filter($all_functions, function ($function_name) use ($module_name) { Chris@0: list($function_module_name,) = explode("_{$this->updateType}_", $function_name); Chris@0: return $function_module_name === $module_name; Chris@0: }); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Scans all module + profile extensions and load the update files. Chris@0: */ Chris@0: protected function scanExtensionsAndLoadUpdateFiles() { Chris@0: // Scan the module list. Chris@0: $extension_discovery = new ExtensionDiscovery($this->root, FALSE, [], $this->sitePath); Chris@0: $module_extensions = $extension_discovery->scan('module'); Chris@0: Chris@0: $profile_extensions = $extension_discovery->scan('profile'); Chris@0: $extensions = array_merge($module_extensions, $profile_extensions); Chris@0: Chris@0: $this->loadUpdateFiles($extensions); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Filters out already executed update functions by module. Chris@0: * Chris@0: * @param string $module Chris@0: * The module name. Chris@0: */ Chris@0: public function filterOutInvokedUpdatesByModule($module) { Chris@0: $existing_update_functions = $this->keyValue->get('existing_updates', []); Chris@0: Chris@0: $remaining_update_functions = array_filter($existing_update_functions, function ($function_name) use ($module) { Chris@0: return strpos($function_name, "{$module}_{$this->updateType}_") !== 0; Chris@0: }); Chris@0: Chris@0: $this->keyValue->set('existing_updates', array_values($remaining_update_functions)); Chris@0: } Chris@0: Chris@0: }