Chris@0: root = $root; Chris@0: $this->keyValueExpirableFactory = $key_value_expirable_factory; Chris@0: $this->cache = $cache; Chris@0: $this->state = $state; Chris@0: $this->moduleHandler = $module_handler; Chris@0: $this->account = $account; Chris@0: $this->bareHtmlPageRenderer = $bare_html_page_renderer; Chris@0: $this->postUpdateRegistry = $post_update_registry; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function create(ContainerInterface $container) { Chris@0: return new static( Chris@0: $container->get('app.root'), Chris@0: $container->get('keyvalue.expirable'), Chris@0: $container->get('cache.default'), Chris@0: $container->get('state'), Chris@0: $container->get('module_handler'), Chris@0: $container->get('current_user'), Chris@0: $container->get('bare_html_page_renderer'), Chris@0: $container->get('update.post_update_registry') Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns a database update page. Chris@0: * Chris@0: * @param string $op Chris@0: * The update operation to perform. Can be any of the below: Chris@0: * - info Chris@0: * - selection Chris@0: * - run Chris@0: * - results Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The current request object. Chris@0: * Chris@0: * @return \Symfony\Component\HttpFoundation\Response Chris@0: * A response object object. Chris@0: */ Chris@0: public function handle($op, Request $request) { Chris@0: require_once $this->root . '/core/includes/install.inc'; Chris@0: require_once $this->root . '/core/includes/update.inc'; Chris@0: Chris@0: drupal_load_updates(); Chris@0: update_fix_compatibility(); Chris@0: Chris@0: if ($request->query->get('continue')) { Chris@0: $_SESSION['update_ignore_warnings'] = TRUE; Chris@0: } Chris@0: Chris@0: $regions = []; Chris@0: $requirements = update_check_requirements(); Chris@0: $severity = drupal_requirements_severity($requirements); Chris@0: if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && empty($_SESSION['update_ignore_warnings']))) { Chris@0: $regions['sidebar_first'] = $this->updateTasksList('requirements'); Chris@0: $output = $this->requirements($severity, $requirements, $request); Chris@0: } Chris@0: else { Chris@0: switch ($op) { Chris@0: case 'selection': Chris@0: $regions['sidebar_first'] = $this->updateTasksList('selection'); Chris@0: $output = $this->selection($request); Chris@0: break; Chris@0: Chris@0: case 'run': Chris@0: $regions['sidebar_first'] = $this->updateTasksList('run'); Chris@0: $output = $this->triggerBatch($request); Chris@0: break; Chris@0: Chris@0: case 'info': Chris@0: $regions['sidebar_first'] = $this->updateTasksList('info'); Chris@0: $output = $this->info($request); Chris@0: break; Chris@0: Chris@0: case 'results': Chris@0: $regions['sidebar_first'] = $this->updateTasksList('results'); Chris@0: $output = $this->results($request); Chris@0: break; Chris@0: Chris@0: // Regular batch ops : defer to batch processing API. Chris@0: default: Chris@0: require_once $this->root . '/core/includes/batch.inc'; Chris@0: $regions['sidebar_first'] = $this->updateTasksList('run'); Chris@0: $output = _batch_page($request); Chris@0: break; Chris@0: } Chris@0: } Chris@0: Chris@0: if ($output instanceof Response) { Chris@0: return $output; Chris@0: } Chris@0: $title = isset($output['#title']) ? $output['#title'] : $this->t('Drupal database update'); Chris@0: Chris@0: return $this->bareHtmlPageRenderer->renderBarePage($output, $title, 'maintenance_page', $regions); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the info database update page. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The current request. Chris@0: * Chris@0: * @return array Chris@0: * A render array. Chris@0: */ Chris@0: protected function info(Request $request) { Chris@0: // Change query-strings on css/js files to enforce reload for all users. Chris@0: _drupal_flush_css_js(); Chris@0: // Flush the cache of all data for the update status module. Chris@0: $this->keyValueExpirableFactory->get('update')->deleteAll(); Chris@0: $this->keyValueExpirableFactory->get('update_available_release')->deleteAll(); Chris@0: Chris@0: $build['info_header'] = [ Chris@0: '#markup' => '

' . $this->t('Use this utility to update your database whenever a new release of Drupal or a module is installed.') . '

' . $this->t('For more detailed information, see the upgrading handbook. If you are unsure what these terms mean you should probably contact your hosting provider.') . '

', Chris@0: ]; Chris@0: Chris@0: $info[] = $this->t("Back up your code. Hint: when backing up module code, do not leave that backup in the 'modules' or 'sites/*/modules' directories as this may confuse Drupal's auto-discovery mechanism."); Chris@0: $info[] = $this->t('Put your site into maintenance mode.', [ Chris@0: ':url' => Url::fromRoute('system.site_maintenance_mode')->toString(TRUE)->getGeneratedUrl(), Chris@0: ]); Chris@0: $info[] = $this->t('Back up your database. This process will change your database values and in case of emergency you may need to revert to a backup.'); Chris@0: $info[] = $this->t('Install your new files in the appropriate location, as described in the handbook.'); Chris@0: $build['info'] = [ Chris@0: '#theme' => 'item_list', Chris@0: '#list_type' => 'ol', Chris@0: '#items' => $info, Chris@0: ]; Chris@0: $build['info_footer'] = [ Chris@0: '#markup' => '

' . $this->t('When you have performed the steps above, you may proceed.') . '

', Chris@0: ]; Chris@0: Chris@0: $build['link'] = [ Chris@0: '#type' => 'link', Chris@0: '#title' => $this->t('Continue'), Chris@0: '#attributes' => ['class' => ['button', 'button--primary']], Chris@0: // @todo Revisit once https://www.drupal.org/node/2548095 is in. Chris@0: '#url' => Url::fromUri('base://selection'), Chris@0: ]; Chris@0: return $build; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Renders a list of available database updates. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The current request. Chris@0: * Chris@0: * @return array Chris@0: * A render array. Chris@0: */ Chris@0: protected function selection(Request $request) { Chris@0: // Make sure there is no stale theme registry. Chris@0: $this->cache->deleteAll(); Chris@0: Chris@0: $count = 0; Chris@0: $incompatible_count = 0; Chris@0: $build['start'] = [ Chris@0: '#tree' => TRUE, Chris@0: '#type' => 'details', Chris@0: ]; Chris@0: Chris@0: // Ensure system.module's updates appear first. Chris@0: $build['start']['system'] = []; Chris@0: Chris@0: $starting_updates = []; Chris@0: $incompatible_updates_exist = FALSE; Chris@0: $updates_per_module = []; Chris@0: foreach (['update', 'post_update'] as $update_type) { Chris@0: switch ($update_type) { Chris@0: case 'update': Chris@0: $updates = update_get_update_list(); Chris@0: break; Chris@0: case 'post_update': Chris@0: $updates = $this->postUpdateRegistry->getPendingUpdateInformation(); Chris@0: break; Chris@0: } Chris@0: foreach ($updates as $module => $update) { Chris@0: if (!isset($update['start'])) { Chris@0: $build['start'][$module] = [ Chris@0: '#type' => 'item', Chris@0: '#title' => $module . ' module', Chris@0: '#markup' => $update['warning'], Chris@0: '#prefix' => '
', Chris@0: '#suffix' => '
', Chris@0: ]; Chris@0: $incompatible_updates_exist = TRUE; Chris@0: continue; Chris@0: } Chris@0: if (!empty($update['pending'])) { Chris@0: $updates_per_module += [$module => []]; Chris@0: $updates_per_module[$module] = array_merge($updates_per_module[$module], $update['pending']); Chris@0: $build['start'][$module] = [ Chris@0: '#type' => 'hidden', Chris@0: '#value' => $update['start'], Chris@0: ]; Chris@0: // Store the previous items in order to merge normal updates and Chris@0: // post_update functions together. Chris@0: $build['start'][$module] = [ Chris@0: '#theme' => 'item_list', Chris@0: '#items' => $updates_per_module[$module], Chris@0: '#title' => $module . ' module', Chris@0: ]; Chris@0: Chris@0: if ($update_type === 'update') { Chris@0: $starting_updates[$module] = $update['start']; Chris@0: } Chris@0: } Chris@0: if (isset($update['pending'])) { Chris@0: $count = $count + count($update['pending']); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: // Find and label any incompatible updates. Chris@0: foreach (update_resolve_dependencies($starting_updates) as $data) { Chris@0: if (!$data['allowed']) { Chris@0: $incompatible_updates_exist = TRUE; Chris@0: $incompatible_count++; Chris@0: $module_update_key = $data['module'] . '_updates'; Chris@0: if (isset($build['start'][$module_update_key]['#items'][$data['number']])) { Chris@0: if ($data['missing_dependencies']) { Chris@0: $text = $this->t('This update will been skipped due to the following missing dependencies:') . '' . implode(', ', $data['missing_dependencies']) . ''; Chris@0: } Chris@0: else { Chris@0: $text = $this->t("This update will be skipped due to an error in the module's code."); Chris@0: } Chris@0: $build['start'][$module_update_key]['#items'][$data['number']] .= '
' . $text . '
'; Chris@0: } Chris@0: // Move the module containing this update to the top of the list. Chris@0: $build['start'] = [$module_update_key => $build['start'][$module_update_key]] + $build['start']; Chris@0: } Chris@0: } Chris@0: Chris@0: // Warn the user if any updates were incompatible. Chris@0: if ($incompatible_updates_exist) { Chris@17: $this->messenger()->addWarning($this->t('Some of the pending updates cannot be applied because their dependencies were not met.')); Chris@0: } Chris@0: Chris@0: if (empty($count)) { Chris@17: $this->messenger()->addStatus($this->t('No pending updates.')); Chris@0: unset($build); Chris@0: $build['links'] = [ Chris@0: '#theme' => 'links', Chris@0: '#links' => $this->helpfulLinks($request), Chris@0: ]; Chris@0: Chris@0: // No updates to run, so caches won't get flushed later. Clear them now. Chris@0: drupal_flush_all_caches(); Chris@0: } Chris@0: else { Chris@0: $build['help'] = [ Chris@0: '#markup' => '

' . $this->t('The version of Drupal you are updating from has been automatically detected.') . '

', Chris@0: '#weight' => -5, Chris@0: ]; Chris@0: if ($incompatible_count) { Chris@0: $build['start']['#title'] = $this->formatPlural( Chris@0: $count, Chris@0: '1 pending update (@number_applied to be applied, @number_incompatible skipped)', Chris@0: '@count pending updates (@number_applied to be applied, @number_incompatible skipped)', Chris@0: ['@number_applied' => $count - $incompatible_count, '@number_incompatible' => $incompatible_count] Chris@0: ); Chris@0: } Chris@0: else { Chris@0: $build['start']['#title'] = $this->formatPlural($count, '1 pending update', '@count pending updates'); Chris@0: } Chris@0: // @todo Simplify with https://www.drupal.org/node/2548095 Chris@0: $base_url = str_replace('/update.php', '', $request->getBaseUrl()); Chris@0: $url = (new Url('system.db_update', ['op' => 'run']))->setOption('base_url', $base_url); Chris@0: $build['link'] = [ Chris@0: '#type' => 'link', Chris@0: '#title' => $this->t('Apply pending updates'), Chris@0: '#attributes' => ['class' => ['button', 'button--primary']], Chris@0: '#weight' => 5, Chris@0: '#url' => $url, Chris@0: '#access' => $url->access($this->currentUser()), Chris@0: ]; Chris@0: } Chris@0: Chris@0: return $build; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Displays results of the update script with any accompanying errors. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The current request. Chris@0: * Chris@0: * @return array Chris@0: * A render array. Chris@0: */ Chris@0: protected function results(Request $request) { Chris@0: // @todo Simplify with https://www.drupal.org/node/2548095 Chris@0: $base_url = str_replace('/update.php', '', $request->getBaseUrl()); Chris@0: Chris@0: // Report end result. Chris@0: $dblog_exists = $this->moduleHandler->moduleExists('dblog'); Chris@0: if ($dblog_exists && $this->account->hasPermission('access site reports')) { Chris@0: $log_message = $this->t('All errors have been logged.', [ Chris@0: ':url' => Url::fromRoute('dblog.overview')->setOption('base_url', $base_url)->toString(TRUE)->getGeneratedUrl(), Chris@0: ]); Chris@0: } Chris@0: else { Chris@0: $log_message = $this->t('All errors have been logged.'); Chris@0: } Chris@0: Chris@0: if (!empty($_SESSION['update_success'])) { Chris@0: $message = '

' . $this->t('Updates were attempted. If you see no failures below, you may proceed happily back to your site. Otherwise, you may need to update your database manually.', [':url' => Url::fromRoute('')->setOption('base_url', $base_url)->toString(TRUE)->getGeneratedUrl()]) . ' ' . $log_message . '

'; Chris@0: } Chris@0: else { Chris@0: $last = reset($_SESSION['updates_remaining']); Chris@0: list($module, $version) = array_pop($last); Chris@0: $message = '

' . $this->t('The update process was aborted prematurely while running update #@version in @module.module.', [ Chris@0: '@version' => $version, Chris@0: '@module' => $module, Chris@0: ]) . ' ' . $log_message; Chris@0: if ($dblog_exists) { Chris@0: $message .= ' ' . $this->t('You may need to check the watchdog database table manually.'); Chris@0: } Chris@0: $message .= '

'; Chris@0: } Chris@0: Chris@0: if (Settings::get('update_free_access')) { Chris@0: $message .= '

' . $this->t("Reminder: don't forget to set the \$settings['update_free_access'] value in your settings.php file back to FALSE.") . '

'; Chris@0: } Chris@0: Chris@0: $build['message'] = [ Chris@0: '#markup' => $message, Chris@0: ]; Chris@0: $build['links'] = [ Chris@0: '#theme' => 'links', Chris@0: '#links' => $this->helpfulLinks($request), Chris@0: ]; Chris@0: Chris@0: // Output a list of info messages. Chris@0: if (!empty($_SESSION['update_results'])) { Chris@0: $all_messages = []; Chris@0: foreach ($_SESSION['update_results'] as $module => $updates) { Chris@0: if ($module != '#abort') { Chris@0: $module_has_message = FALSE; Chris@0: $info_messages = []; Chris@0: foreach ($updates as $name => $queries) { Chris@0: $messages = []; Chris@0: foreach ($queries as $query) { Chris@0: // If there is no message for this update, don't show anything. Chris@0: if (empty($query['query'])) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: if ($query['success']) { Chris@0: $messages[] = [ Chris@0: '#wrapper_attributes' => ['class' => ['success']], Chris@0: '#markup' => $query['query'], Chris@0: ]; Chris@0: } Chris@0: else { Chris@0: $messages[] = [ Chris@0: '#wrapper_attributes' => ['class' => ['failure']], Chris@0: '#markup' => '' . $this->t('Failed:') . ' ' . $query['query'], Chris@0: ]; Chris@0: } Chris@0: } Chris@0: Chris@0: if ($messages) { Chris@0: $module_has_message = TRUE; Chris@0: if (is_numeric($name)) { Chris@0: $title = $this->t('Update #@count', ['@count' => $name]); Chris@0: } Chris@0: else { Chris@0: $title = $this->t('Update @name', ['@name' => trim($name, '_')]); Chris@0: } Chris@0: $info_messages[] = [ Chris@0: '#theme' => 'item_list', Chris@0: '#items' => $messages, Chris@0: '#title' => $title, Chris@0: ]; Chris@0: } Chris@0: } Chris@0: Chris@0: // If there were any messages then prefix them with the module name Chris@0: // and add it to the global message list. Chris@0: if ($module_has_message) { Chris@0: $all_messages[] = [ Chris@0: '#type' => 'container', Chris@0: '#prefix' => '

' . $this->t('@module module', ['@module' => $module]) . '

', Chris@0: '#children' => $info_messages, Chris@0: ]; Chris@0: } Chris@0: } Chris@0: } Chris@0: if ($all_messages) { Chris@0: $build['query_messages'] = [ Chris@0: '#type' => 'container', Chris@0: '#children' => $all_messages, Chris@0: '#attributes' => ['class' => ['update-results']], Chris@0: '#prefix' => '

' . $this->t('The following updates returned messages:') . '

', Chris@0: ]; Chris@0: } Chris@0: } Chris@0: unset($_SESSION['update_results']); Chris@0: unset($_SESSION['update_success']); Chris@0: unset($_SESSION['update_ignore_warnings']); Chris@0: Chris@0: return $build; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Renders a list of requirement errors or warnings. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The current request. Chris@0: * Chris@0: * @return array Chris@0: * A render array. Chris@0: */ Chris@0: public function requirements($severity, array $requirements, Request $request) { Chris@0: $options = $severity == REQUIREMENT_WARNING ? ['continue' => 1] : []; Chris@0: // @todo Revisit once https://www.drupal.org/node/2548095 is in. Something Chris@0: // like Url::fromRoute('system.db_update')->setOptions() should then be Chris@0: // possible. Chris@0: $try_again_url = Url::fromUri($request->getUriForPath(''))->setOptions(['query' => $options])->toString(TRUE)->getGeneratedUrl(); Chris@0: Chris@0: $build['status_report'] = [ Chris@0: '#type' => 'status_report', Chris@0: '#requirements' => $requirements, Chris@17: '#suffix' => $this->t('Check the messages and try again.', [':url' => $try_again_url]), Chris@0: ]; Chris@0: Chris@0: $build['#title'] = $this->t('Requirements problem'); Chris@0: return $build; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides the update task list render array. Chris@0: * Chris@0: * @param string $active Chris@0: * The active task. Chris@0: * Can be one of 'requirements', 'info', 'selection', 'run', 'results'. Chris@0: * Chris@0: * @return array Chris@0: * A render array. Chris@0: */ Chris@0: protected function updateTasksList($active = NULL) { Chris@0: // Default list of tasks. Chris@0: $tasks = [ Chris@0: 'requirements' => $this->t('Verify requirements'), Chris@0: 'info' => $this->t('Overview'), Chris@0: 'selection' => $this->t('Review updates'), Chris@0: 'run' => $this->t('Run updates'), Chris@0: 'results' => $this->t('Review log'), Chris@0: ]; Chris@0: Chris@0: $task_list = [ Chris@0: '#theme' => 'maintenance_task_list', Chris@0: '#items' => $tasks, Chris@0: '#active' => $active, Chris@0: ]; Chris@0: return $task_list; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Starts the database update batch process. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The current request object. Chris@0: */ Chris@0: protected function triggerBatch(Request $request) { Chris@0: $maintenance_mode = $this->state->get('system.maintenance_mode', FALSE); Chris@0: // Store the current maintenance mode status in the session so that it can Chris@0: // be restored at the end of the batch. Chris@0: $_SESSION['maintenance_mode'] = $maintenance_mode; Chris@0: // During the update, always put the site into maintenance mode so that Chris@0: // in-progress schema changes do not affect visiting users. Chris@0: if (empty($maintenance_mode)) { Chris@0: $this->state->set('system.maintenance_mode', TRUE); Chris@0: } Chris@0: Chris@0: $operations = []; Chris@0: Chris@0: // Resolve any update dependencies to determine the actual updates that will Chris@0: // be run and the order they will be run in. Chris@0: $start = $this->getModuleUpdates(); Chris@0: $updates = update_resolve_dependencies($start); Chris@0: Chris@0: // Store the dependencies for each update function in an array which the Chris@0: // batch API can pass in to the batch operation each time it is called. (We Chris@0: // do not store the entire update dependency array here because it is Chris@0: // potentially very large.) Chris@0: $dependency_map = []; Chris@0: foreach ($updates as $function => $update) { Chris@0: $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : []; Chris@0: } Chris@0: Chris@0: // Determine updates to be performed. Chris@0: foreach ($updates as $function => $update) { Chris@0: if ($update['allowed']) { Chris@0: // Set the installed version of each module so updates will start at the Chris@0: // correct place. (The updates are already sorted, so we can simply base Chris@0: // this on the first one we come across in the above foreach loop.) Chris@0: if (isset($start[$update['module']])) { Chris@0: drupal_set_installed_schema_version($update['module'], $update['number'] - 1); Chris@0: unset($start[$update['module']]); Chris@0: } Chris@0: $operations[] = ['update_do_one', [$update['module'], $update['number'], $dependency_map[$function]]]; Chris@0: } Chris@0: } Chris@0: Chris@0: $post_updates = $this->postUpdateRegistry->getPendingUpdateFunctions(); Chris@0: Chris@0: if ($post_updates) { Chris@0: // Now we rebuild all caches and after that execute the hook_post_update() Chris@0: // functions. Chris@0: $operations[] = ['drupal_flush_all_caches', []]; Chris@0: foreach ($post_updates as $function) { Chris@0: $operations[] = ['update_invoke_post_update', [$function]]; Chris@0: } Chris@0: } Chris@0: Chris@0: $batch['operations'] = $operations; Chris@0: $batch += [ Chris@0: 'title' => $this->t('Updating'), Chris@0: 'init_message' => $this->t('Starting updates'), Chris@0: 'error_message' => $this->t('An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.'), Chris@0: 'finished' => ['\Drupal\system\Controller\DbUpdateController', 'batchFinished'], Chris@0: ]; Chris@0: batch_set($batch); Chris@0: Chris@0: // @todo Revisit once https://www.drupal.org/node/2548095 is in. Chris@0: return batch_process(Url::fromUri('base://results'), Url::fromUri('base://start')); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Finishes the update process and stores the results for eventual display. Chris@0: * Chris@0: * After the updates run, all caches are flushed. The update results are Chris@0: * stored into the session (for example, to be displayed on the update results Chris@0: * page in update.php). Additionally, if the site was off-line, now that the Chris@0: * update process is completed, the site is set back online. Chris@0: * Chris@0: * @param $success Chris@0: * Indicate that the batch API tasks were all completed successfully. Chris@0: * @param array $results Chris@0: * An array of all the results that were updated in update_do_one(). Chris@0: * @param array $operations Chris@0: * A list of all the operations that had not been completed by the batch API. Chris@0: */ Chris@0: public static function batchFinished($success, $results, $operations) { Chris@0: // No updates to run, so caches won't get flushed later. Clear them now. Chris@0: drupal_flush_all_caches(); Chris@0: Chris@0: $_SESSION['update_results'] = $results; Chris@0: $_SESSION['update_success'] = $success; Chris@0: $_SESSION['updates_remaining'] = $operations; Chris@0: Chris@0: // Now that the update is done, we can put the site back online if it was Chris@0: // previously not in maintenance mode. Chris@0: if (empty($_SESSION['maintenance_mode'])) { Chris@0: \Drupal::state()->set('system.maintenance_mode', FALSE); Chris@0: } Chris@0: unset($_SESSION['maintenance_mode']); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides links to the homepage and administration pages. Chris@0: * Chris@0: * @param \Symfony\Component\HttpFoundation\Request $request Chris@0: * The current request. Chris@0: * Chris@0: * @return array Chris@0: * An array of links. Chris@0: */ Chris@0: protected function helpfulLinks(Request $request) { Chris@0: // @todo Simplify with https://www.drupal.org/node/2548095 Chris@0: $base_url = str_replace('/update.php', '', $request->getBaseUrl()); Chris@0: $links['front'] = [ Chris@0: 'title' => $this->t('Front page'), Chris@0: 'url' => Url::fromRoute('')->setOption('base_url', $base_url), Chris@0: ]; Chris@0: if ($this->account->hasPermission('access administration pages')) { Chris@0: $links['admin-pages'] = [ Chris@0: 'title' => $this->t('Administration pages'), Chris@0: 'url' => Url::fromRoute('system.admin')->setOption('base_url', $base_url), Chris@0: ]; Chris@0: } Chris@0: return $links; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Retrieves module updates. Chris@0: * Chris@0: * @return array Chris@0: * The module updates that can be performed. Chris@0: */ Chris@0: protected function getModuleUpdates() { Chris@0: $return = []; Chris@0: $updates = update_get_update_list(); Chris@0: foreach ($updates as $module => $update) { Chris@0: $return[$module] = $update['start']; Chris@0: } Chris@0: Chris@0: return $return; Chris@0: } Chris@0: Chris@0: }