Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\system\Controller;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Core\Cache\CacheBackendInterface;
|
Chris@0
|
6 use Drupal\Core\Controller\ControllerBase;
|
Chris@0
|
7 use Drupal\Core\Extension\ModuleHandlerInterface;
|
Chris@0
|
8 use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
|
Chris@0
|
9 use Drupal\Core\Render\BareHtmlPageRendererInterface;
|
Chris@0
|
10 use Drupal\Core\Session\AccountInterface;
|
Chris@0
|
11 use Drupal\Core\Site\Settings;
|
Chris@0
|
12 use Drupal\Core\State\StateInterface;
|
Chris@0
|
13 use Drupal\Core\Update\UpdateRegistry;
|
Chris@0
|
14 use Drupal\Core\Url;
|
Chris@0
|
15 use Symfony\Component\DependencyInjection\ContainerInterface;
|
Chris@0
|
16 use Symfony\Component\HttpFoundation\Response;
|
Chris@0
|
17 use Symfony\Component\HttpFoundation\Request;
|
Chris@0
|
18
|
Chris@0
|
19 /**
|
Chris@0
|
20 * Controller routines for database update routes.
|
Chris@0
|
21 */
|
Chris@0
|
22 class DbUpdateController extends ControllerBase {
|
Chris@0
|
23
|
Chris@0
|
24 /**
|
Chris@0
|
25 * The keyvalue expirable factory.
|
Chris@0
|
26 *
|
Chris@0
|
27 * @var \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface
|
Chris@0
|
28 */
|
Chris@0
|
29 protected $keyValueExpirableFactory;
|
Chris@0
|
30
|
Chris@0
|
31 /**
|
Chris@0
|
32 * A cache backend interface.
|
Chris@0
|
33 *
|
Chris@0
|
34 * @var \Drupal\Core\Cache\CacheBackendInterface
|
Chris@0
|
35 */
|
Chris@0
|
36 protected $cache;
|
Chris@0
|
37
|
Chris@0
|
38 /**
|
Chris@0
|
39 * The state service.
|
Chris@0
|
40 *
|
Chris@0
|
41 * @var \Drupal\Core\State\StateInterface
|
Chris@0
|
42 */
|
Chris@0
|
43 protected $state;
|
Chris@0
|
44
|
Chris@0
|
45 /**
|
Chris@0
|
46 * The module handler.
|
Chris@0
|
47 *
|
Chris@0
|
48 * @var \Drupal\Core\Extension\ModuleHandlerInterface
|
Chris@0
|
49 */
|
Chris@0
|
50 protected $moduleHandler;
|
Chris@0
|
51
|
Chris@0
|
52 /**
|
Chris@0
|
53 * The current user.
|
Chris@0
|
54 *
|
Chris@0
|
55 * @var \Drupal\Core\Session\AccountInterface
|
Chris@0
|
56 */
|
Chris@0
|
57 protected $account;
|
Chris@0
|
58
|
Chris@0
|
59 /**
|
Chris@0
|
60 * The bare HTML page renderer.
|
Chris@0
|
61 *
|
Chris@0
|
62 * @var \Drupal\Core\Render\BareHtmlPageRendererInterface
|
Chris@0
|
63 */
|
Chris@0
|
64 protected $bareHtmlPageRenderer;
|
Chris@0
|
65
|
Chris@0
|
66 /**
|
Chris@0
|
67 * The app root.
|
Chris@0
|
68 *
|
Chris@0
|
69 * @var string
|
Chris@0
|
70 */
|
Chris@0
|
71 protected $root;
|
Chris@0
|
72
|
Chris@0
|
73 /**
|
Chris@0
|
74 * The post update registry.
|
Chris@0
|
75 *
|
Chris@0
|
76 * @var \Drupal\Core\Update\UpdateRegistry
|
Chris@0
|
77 */
|
Chris@0
|
78 protected $postUpdateRegistry;
|
Chris@0
|
79
|
Chris@0
|
80 /**
|
Chris@0
|
81 * Constructs a new UpdateController.
|
Chris@0
|
82 *
|
Chris@0
|
83 * @param string $root
|
Chris@0
|
84 * The app root.
|
Chris@0
|
85 * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $key_value_expirable_factory
|
Chris@0
|
86 * The keyvalue expirable factory.
|
Chris@0
|
87 * @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
Chris@0
|
88 * A cache backend interface.
|
Chris@0
|
89 * @param \Drupal\Core\State\StateInterface $state
|
Chris@0
|
90 * The state service.
|
Chris@0
|
91 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
Chris@0
|
92 * The module handler.
|
Chris@0
|
93 * @param \Drupal\Core\Session\AccountInterface $account
|
Chris@0
|
94 * The current user.
|
Chris@0
|
95 * @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_page_renderer
|
Chris@0
|
96 * The bare HTML page renderer.
|
Chris@0
|
97 * @param \Drupal\Core\Update\UpdateRegistry $post_update_registry
|
Chris@0
|
98 * The post update registry.
|
Chris@0
|
99 */
|
Chris@0
|
100 public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer, UpdateRegistry $post_update_registry) {
|
Chris@0
|
101 $this->root = $root;
|
Chris@0
|
102 $this->keyValueExpirableFactory = $key_value_expirable_factory;
|
Chris@0
|
103 $this->cache = $cache;
|
Chris@0
|
104 $this->state = $state;
|
Chris@0
|
105 $this->moduleHandler = $module_handler;
|
Chris@0
|
106 $this->account = $account;
|
Chris@0
|
107 $this->bareHtmlPageRenderer = $bare_html_page_renderer;
|
Chris@0
|
108 $this->postUpdateRegistry = $post_update_registry;
|
Chris@0
|
109 }
|
Chris@0
|
110
|
Chris@0
|
111 /**
|
Chris@0
|
112 * {@inheritdoc}
|
Chris@0
|
113 */
|
Chris@0
|
114 public static function create(ContainerInterface $container) {
|
Chris@0
|
115 return new static(
|
Chris@0
|
116 $container->get('app.root'),
|
Chris@0
|
117 $container->get('keyvalue.expirable'),
|
Chris@0
|
118 $container->get('cache.default'),
|
Chris@0
|
119 $container->get('state'),
|
Chris@0
|
120 $container->get('module_handler'),
|
Chris@0
|
121 $container->get('current_user'),
|
Chris@0
|
122 $container->get('bare_html_page_renderer'),
|
Chris@0
|
123 $container->get('update.post_update_registry')
|
Chris@0
|
124 );
|
Chris@0
|
125 }
|
Chris@0
|
126
|
Chris@0
|
127 /**
|
Chris@0
|
128 * Returns a database update page.
|
Chris@0
|
129 *
|
Chris@0
|
130 * @param string $op
|
Chris@0
|
131 * The update operation to perform. Can be any of the below:
|
Chris@0
|
132 * - info
|
Chris@0
|
133 * - selection
|
Chris@0
|
134 * - run
|
Chris@0
|
135 * - results
|
Chris@0
|
136 * @param \Symfony\Component\HttpFoundation\Request $request
|
Chris@0
|
137 * The current request object.
|
Chris@0
|
138 *
|
Chris@0
|
139 * @return \Symfony\Component\HttpFoundation\Response
|
Chris@0
|
140 * A response object object.
|
Chris@0
|
141 */
|
Chris@0
|
142 public function handle($op, Request $request) {
|
Chris@0
|
143 require_once $this->root . '/core/includes/install.inc';
|
Chris@0
|
144 require_once $this->root . '/core/includes/update.inc';
|
Chris@0
|
145
|
Chris@0
|
146 drupal_load_updates();
|
Chris@0
|
147 update_fix_compatibility();
|
Chris@0
|
148
|
Chris@0
|
149 if ($request->query->get('continue')) {
|
Chris@0
|
150 $_SESSION['update_ignore_warnings'] = TRUE;
|
Chris@0
|
151 }
|
Chris@0
|
152
|
Chris@0
|
153 $regions = [];
|
Chris@0
|
154 $requirements = update_check_requirements();
|
Chris@0
|
155 $severity = drupal_requirements_severity($requirements);
|
Chris@0
|
156 if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && empty($_SESSION['update_ignore_warnings']))) {
|
Chris@0
|
157 $regions['sidebar_first'] = $this->updateTasksList('requirements');
|
Chris@0
|
158 $output = $this->requirements($severity, $requirements, $request);
|
Chris@0
|
159 }
|
Chris@0
|
160 else {
|
Chris@0
|
161 switch ($op) {
|
Chris@0
|
162 case 'selection':
|
Chris@0
|
163 $regions['sidebar_first'] = $this->updateTasksList('selection');
|
Chris@0
|
164 $output = $this->selection($request);
|
Chris@0
|
165 break;
|
Chris@0
|
166
|
Chris@0
|
167 case 'run':
|
Chris@0
|
168 $regions['sidebar_first'] = $this->updateTasksList('run');
|
Chris@0
|
169 $output = $this->triggerBatch($request);
|
Chris@0
|
170 break;
|
Chris@0
|
171
|
Chris@0
|
172 case 'info':
|
Chris@0
|
173 $regions['sidebar_first'] = $this->updateTasksList('info');
|
Chris@0
|
174 $output = $this->info($request);
|
Chris@0
|
175 break;
|
Chris@0
|
176
|
Chris@0
|
177 case 'results':
|
Chris@0
|
178 $regions['sidebar_first'] = $this->updateTasksList('results');
|
Chris@0
|
179 $output = $this->results($request);
|
Chris@0
|
180 break;
|
Chris@0
|
181
|
Chris@0
|
182 // Regular batch ops : defer to batch processing API.
|
Chris@0
|
183 default:
|
Chris@0
|
184 require_once $this->root . '/core/includes/batch.inc';
|
Chris@0
|
185 $regions['sidebar_first'] = $this->updateTasksList('run');
|
Chris@0
|
186 $output = _batch_page($request);
|
Chris@0
|
187 break;
|
Chris@0
|
188 }
|
Chris@0
|
189 }
|
Chris@0
|
190
|
Chris@0
|
191 if ($output instanceof Response) {
|
Chris@0
|
192 return $output;
|
Chris@0
|
193 }
|
Chris@0
|
194 $title = isset($output['#title']) ? $output['#title'] : $this->t('Drupal database update');
|
Chris@0
|
195
|
Chris@0
|
196 return $this->bareHtmlPageRenderer->renderBarePage($output, $title, 'maintenance_page', $regions);
|
Chris@0
|
197 }
|
Chris@0
|
198
|
Chris@0
|
199 /**
|
Chris@0
|
200 * Returns the info database update page.
|
Chris@0
|
201 *
|
Chris@0
|
202 * @param \Symfony\Component\HttpFoundation\Request $request
|
Chris@0
|
203 * The current request.
|
Chris@0
|
204 *
|
Chris@0
|
205 * @return array
|
Chris@0
|
206 * A render array.
|
Chris@0
|
207 */
|
Chris@0
|
208 protected function info(Request $request) {
|
Chris@0
|
209 // Change query-strings on css/js files to enforce reload for all users.
|
Chris@0
|
210 _drupal_flush_css_js();
|
Chris@0
|
211 // Flush the cache of all data for the update status module.
|
Chris@0
|
212 $this->keyValueExpirableFactory->get('update')->deleteAll();
|
Chris@0
|
213 $this->keyValueExpirableFactory->get('update_available_release')->deleteAll();
|
Chris@0
|
214
|
Chris@0
|
215 $build['info_header'] = [
|
Chris@0
|
216 '#markup' => '<p>' . $this->t('Use this utility to update your database whenever a new release of Drupal or a module is installed.') . '</p><p>' . $this->t('For more detailed information, see the <a href="https://www.drupal.org/upgrade">upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.') . '</p>',
|
Chris@0
|
217 ];
|
Chris@0
|
218
|
Chris@0
|
219 $info[] = $this->t("<strong>Back up your code</strong>. 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
|
220 $info[] = $this->t('Put your site into <a href=":url">maintenance mode</a>.', [
|
Chris@0
|
221 ':url' => Url::fromRoute('system.site_maintenance_mode')->toString(TRUE)->getGeneratedUrl(),
|
Chris@0
|
222 ]);
|
Chris@0
|
223 $info[] = $this->t('<strong>Back up your database</strong>. This process will change your database values and in case of emergency you may need to revert to a backup.');
|
Chris@0
|
224 $info[] = $this->t('Install your new files in the appropriate location, as described in the handbook.');
|
Chris@0
|
225 $build['info'] = [
|
Chris@0
|
226 '#theme' => 'item_list',
|
Chris@0
|
227 '#list_type' => 'ol',
|
Chris@0
|
228 '#items' => $info,
|
Chris@0
|
229 ];
|
Chris@0
|
230 $build['info_footer'] = [
|
Chris@0
|
231 '#markup' => '<p>' . $this->t('When you have performed the steps above, you may proceed.') . '</p>',
|
Chris@0
|
232 ];
|
Chris@0
|
233
|
Chris@0
|
234 $build['link'] = [
|
Chris@0
|
235 '#type' => 'link',
|
Chris@0
|
236 '#title' => $this->t('Continue'),
|
Chris@0
|
237 '#attributes' => ['class' => ['button', 'button--primary']],
|
Chris@0
|
238 // @todo Revisit once https://www.drupal.org/node/2548095 is in.
|
Chris@0
|
239 '#url' => Url::fromUri('base://selection'),
|
Chris@0
|
240 ];
|
Chris@0
|
241 return $build;
|
Chris@0
|
242 }
|
Chris@0
|
243
|
Chris@0
|
244 /**
|
Chris@0
|
245 * Renders a list of available database updates.
|
Chris@0
|
246 *
|
Chris@0
|
247 * @param \Symfony\Component\HttpFoundation\Request $request
|
Chris@0
|
248 * The current request.
|
Chris@0
|
249 *
|
Chris@0
|
250 * @return array
|
Chris@0
|
251 * A render array.
|
Chris@0
|
252 */
|
Chris@0
|
253 protected function selection(Request $request) {
|
Chris@0
|
254 // Make sure there is no stale theme registry.
|
Chris@0
|
255 $this->cache->deleteAll();
|
Chris@0
|
256
|
Chris@0
|
257 $count = 0;
|
Chris@0
|
258 $incompatible_count = 0;
|
Chris@0
|
259 $build['start'] = [
|
Chris@0
|
260 '#tree' => TRUE,
|
Chris@0
|
261 '#type' => 'details',
|
Chris@0
|
262 ];
|
Chris@0
|
263
|
Chris@0
|
264 // Ensure system.module's updates appear first.
|
Chris@0
|
265 $build['start']['system'] = [];
|
Chris@0
|
266
|
Chris@0
|
267 $starting_updates = [];
|
Chris@0
|
268 $incompatible_updates_exist = FALSE;
|
Chris@0
|
269 $updates_per_module = [];
|
Chris@0
|
270 foreach (['update', 'post_update'] as $update_type) {
|
Chris@0
|
271 switch ($update_type) {
|
Chris@0
|
272 case 'update':
|
Chris@0
|
273 $updates = update_get_update_list();
|
Chris@0
|
274 break;
|
Chris@0
|
275 case 'post_update':
|
Chris@0
|
276 $updates = $this->postUpdateRegistry->getPendingUpdateInformation();
|
Chris@0
|
277 break;
|
Chris@0
|
278 }
|
Chris@0
|
279 foreach ($updates as $module => $update) {
|
Chris@0
|
280 if (!isset($update['start'])) {
|
Chris@0
|
281 $build['start'][$module] = [
|
Chris@0
|
282 '#type' => 'item',
|
Chris@0
|
283 '#title' => $module . ' module',
|
Chris@0
|
284 '#markup' => $update['warning'],
|
Chris@0
|
285 '#prefix' => '<div class="messages messages--warning">',
|
Chris@0
|
286 '#suffix' => '</div>',
|
Chris@0
|
287 ];
|
Chris@0
|
288 $incompatible_updates_exist = TRUE;
|
Chris@0
|
289 continue;
|
Chris@0
|
290 }
|
Chris@0
|
291 if (!empty($update['pending'])) {
|
Chris@0
|
292 $updates_per_module += [$module => []];
|
Chris@0
|
293 $updates_per_module[$module] = array_merge($updates_per_module[$module], $update['pending']);
|
Chris@0
|
294 $build['start'][$module] = [
|
Chris@0
|
295 '#type' => 'hidden',
|
Chris@0
|
296 '#value' => $update['start'],
|
Chris@0
|
297 ];
|
Chris@0
|
298 // Store the previous items in order to merge normal updates and
|
Chris@0
|
299 // post_update functions together.
|
Chris@0
|
300 $build['start'][$module] = [
|
Chris@0
|
301 '#theme' => 'item_list',
|
Chris@0
|
302 '#items' => $updates_per_module[$module],
|
Chris@0
|
303 '#title' => $module . ' module',
|
Chris@0
|
304 ];
|
Chris@0
|
305
|
Chris@0
|
306 if ($update_type === 'update') {
|
Chris@0
|
307 $starting_updates[$module] = $update['start'];
|
Chris@0
|
308 }
|
Chris@0
|
309 }
|
Chris@0
|
310 if (isset($update['pending'])) {
|
Chris@0
|
311 $count = $count + count($update['pending']);
|
Chris@0
|
312 }
|
Chris@0
|
313 }
|
Chris@0
|
314 }
|
Chris@0
|
315
|
Chris@0
|
316 // Find and label any incompatible updates.
|
Chris@0
|
317 foreach (update_resolve_dependencies($starting_updates) as $data) {
|
Chris@0
|
318 if (!$data['allowed']) {
|
Chris@0
|
319 $incompatible_updates_exist = TRUE;
|
Chris@0
|
320 $incompatible_count++;
|
Chris@0
|
321 $module_update_key = $data['module'] . '_updates';
|
Chris@0
|
322 if (isset($build['start'][$module_update_key]['#items'][$data['number']])) {
|
Chris@0
|
323 if ($data['missing_dependencies']) {
|
Chris@0
|
324 $text = $this->t('This update will been skipped due to the following missing dependencies:') . '<em>' . implode(', ', $data['missing_dependencies']) . '</em>';
|
Chris@0
|
325 }
|
Chris@0
|
326 else {
|
Chris@0
|
327 $text = $this->t("This update will be skipped due to an error in the module's code.");
|
Chris@0
|
328 }
|
Chris@0
|
329 $build['start'][$module_update_key]['#items'][$data['number']] .= '<div class="warning">' . $text . '</div>';
|
Chris@0
|
330 }
|
Chris@0
|
331 // Move the module containing this update to the top of the list.
|
Chris@0
|
332 $build['start'] = [$module_update_key => $build['start'][$module_update_key]] + $build['start'];
|
Chris@0
|
333 }
|
Chris@0
|
334 }
|
Chris@0
|
335
|
Chris@0
|
336 // Warn the user if any updates were incompatible.
|
Chris@0
|
337 if ($incompatible_updates_exist) {
|
Chris@17
|
338 $this->messenger()->addWarning($this->t('Some of the pending updates cannot be applied because their dependencies were not met.'));
|
Chris@0
|
339 }
|
Chris@0
|
340
|
Chris@0
|
341 if (empty($count)) {
|
Chris@17
|
342 $this->messenger()->addStatus($this->t('No pending updates.'));
|
Chris@0
|
343 unset($build);
|
Chris@0
|
344 $build['links'] = [
|
Chris@0
|
345 '#theme' => 'links',
|
Chris@0
|
346 '#links' => $this->helpfulLinks($request),
|
Chris@0
|
347 ];
|
Chris@0
|
348
|
Chris@0
|
349 // No updates to run, so caches won't get flushed later. Clear them now.
|
Chris@0
|
350 drupal_flush_all_caches();
|
Chris@0
|
351 }
|
Chris@0
|
352 else {
|
Chris@0
|
353 $build['help'] = [
|
Chris@0
|
354 '#markup' => '<p>' . $this->t('The version of Drupal you are updating from has been automatically detected.') . '</p>',
|
Chris@0
|
355 '#weight' => -5,
|
Chris@0
|
356 ];
|
Chris@0
|
357 if ($incompatible_count) {
|
Chris@0
|
358 $build['start']['#title'] = $this->formatPlural(
|
Chris@0
|
359 $count,
|
Chris@0
|
360 '1 pending update (@number_applied to be applied, @number_incompatible skipped)',
|
Chris@0
|
361 '@count pending updates (@number_applied to be applied, @number_incompatible skipped)',
|
Chris@0
|
362 ['@number_applied' => $count - $incompatible_count, '@number_incompatible' => $incompatible_count]
|
Chris@0
|
363 );
|
Chris@0
|
364 }
|
Chris@0
|
365 else {
|
Chris@0
|
366 $build['start']['#title'] = $this->formatPlural($count, '1 pending update', '@count pending updates');
|
Chris@0
|
367 }
|
Chris@0
|
368 // @todo Simplify with https://www.drupal.org/node/2548095
|
Chris@0
|
369 $base_url = str_replace('/update.php', '', $request->getBaseUrl());
|
Chris@0
|
370 $url = (new Url('system.db_update', ['op' => 'run']))->setOption('base_url', $base_url);
|
Chris@0
|
371 $build['link'] = [
|
Chris@0
|
372 '#type' => 'link',
|
Chris@0
|
373 '#title' => $this->t('Apply pending updates'),
|
Chris@0
|
374 '#attributes' => ['class' => ['button', 'button--primary']],
|
Chris@0
|
375 '#weight' => 5,
|
Chris@0
|
376 '#url' => $url,
|
Chris@0
|
377 '#access' => $url->access($this->currentUser()),
|
Chris@0
|
378 ];
|
Chris@0
|
379 }
|
Chris@0
|
380
|
Chris@0
|
381 return $build;
|
Chris@0
|
382 }
|
Chris@0
|
383
|
Chris@0
|
384 /**
|
Chris@0
|
385 * Displays results of the update script with any accompanying errors.
|
Chris@0
|
386 *
|
Chris@0
|
387 * @param \Symfony\Component\HttpFoundation\Request $request
|
Chris@0
|
388 * The current request.
|
Chris@0
|
389 *
|
Chris@0
|
390 * @return array
|
Chris@0
|
391 * A render array.
|
Chris@0
|
392 */
|
Chris@0
|
393 protected function results(Request $request) {
|
Chris@0
|
394 // @todo Simplify with https://www.drupal.org/node/2548095
|
Chris@0
|
395 $base_url = str_replace('/update.php', '', $request->getBaseUrl());
|
Chris@0
|
396
|
Chris@0
|
397 // Report end result.
|
Chris@0
|
398 $dblog_exists = $this->moduleHandler->moduleExists('dblog');
|
Chris@0
|
399 if ($dblog_exists && $this->account->hasPermission('access site reports')) {
|
Chris@0
|
400 $log_message = $this->t('All errors have been <a href=":url">logged</a>.', [
|
Chris@0
|
401 ':url' => Url::fromRoute('dblog.overview')->setOption('base_url', $base_url)->toString(TRUE)->getGeneratedUrl(),
|
Chris@0
|
402 ]);
|
Chris@0
|
403 }
|
Chris@0
|
404 else {
|
Chris@0
|
405 $log_message = $this->t('All errors have been logged.');
|
Chris@0
|
406 }
|
Chris@0
|
407
|
Chris@0
|
408 if (!empty($_SESSION['update_success'])) {
|
Chris@0
|
409 $message = '<p>' . $this->t('Updates were attempted. If you see no failures below, you may proceed happily back to your <a href=":url">site</a>. Otherwise, you may need to update your database manually.', [':url' => Url::fromRoute('<front>')->setOption('base_url', $base_url)->toString(TRUE)->getGeneratedUrl()]) . ' ' . $log_message . '</p>';
|
Chris@0
|
410 }
|
Chris@0
|
411 else {
|
Chris@0
|
412 $last = reset($_SESSION['updates_remaining']);
|
Chris@0
|
413 list($module, $version) = array_pop($last);
|
Chris@0
|
414 $message = '<p class="error">' . $this->t('The update process was aborted prematurely while running <strong>update #@version in @module.module</strong>.', [
|
Chris@0
|
415 '@version' => $version,
|
Chris@0
|
416 '@module' => $module,
|
Chris@0
|
417 ]) . ' ' . $log_message;
|
Chris@0
|
418 if ($dblog_exists) {
|
Chris@0
|
419 $message .= ' ' . $this->t('You may need to check the <code>watchdog</code> database table manually.');
|
Chris@0
|
420 }
|
Chris@0
|
421 $message .= '</p>';
|
Chris@0
|
422 }
|
Chris@0
|
423
|
Chris@0
|
424 if (Settings::get('update_free_access')) {
|
Chris@0
|
425 $message .= '<p>' . $this->t("<strong>Reminder: don't forget to set the <code>\$settings['update_free_access']</code> value in your <code>settings.php</code> file back to <code>FALSE</code>.</strong>") . '</p>';
|
Chris@0
|
426 }
|
Chris@0
|
427
|
Chris@0
|
428 $build['message'] = [
|
Chris@0
|
429 '#markup' => $message,
|
Chris@0
|
430 ];
|
Chris@0
|
431 $build['links'] = [
|
Chris@0
|
432 '#theme' => 'links',
|
Chris@0
|
433 '#links' => $this->helpfulLinks($request),
|
Chris@0
|
434 ];
|
Chris@0
|
435
|
Chris@0
|
436 // Output a list of info messages.
|
Chris@0
|
437 if (!empty($_SESSION['update_results'])) {
|
Chris@0
|
438 $all_messages = [];
|
Chris@0
|
439 foreach ($_SESSION['update_results'] as $module => $updates) {
|
Chris@0
|
440 if ($module != '#abort') {
|
Chris@0
|
441 $module_has_message = FALSE;
|
Chris@0
|
442 $info_messages = [];
|
Chris@0
|
443 foreach ($updates as $name => $queries) {
|
Chris@0
|
444 $messages = [];
|
Chris@0
|
445 foreach ($queries as $query) {
|
Chris@0
|
446 // If there is no message for this update, don't show anything.
|
Chris@0
|
447 if (empty($query['query'])) {
|
Chris@0
|
448 continue;
|
Chris@0
|
449 }
|
Chris@0
|
450
|
Chris@0
|
451 if ($query['success']) {
|
Chris@0
|
452 $messages[] = [
|
Chris@0
|
453 '#wrapper_attributes' => ['class' => ['success']],
|
Chris@0
|
454 '#markup' => $query['query'],
|
Chris@0
|
455 ];
|
Chris@0
|
456 }
|
Chris@0
|
457 else {
|
Chris@0
|
458 $messages[] = [
|
Chris@0
|
459 '#wrapper_attributes' => ['class' => ['failure']],
|
Chris@0
|
460 '#markup' => '<strong>' . $this->t('Failed:') . '</strong> ' . $query['query'],
|
Chris@0
|
461 ];
|
Chris@0
|
462 }
|
Chris@0
|
463 }
|
Chris@0
|
464
|
Chris@0
|
465 if ($messages) {
|
Chris@0
|
466 $module_has_message = TRUE;
|
Chris@0
|
467 if (is_numeric($name)) {
|
Chris@0
|
468 $title = $this->t('Update #@count', ['@count' => $name]);
|
Chris@0
|
469 }
|
Chris@0
|
470 else {
|
Chris@0
|
471 $title = $this->t('Update @name', ['@name' => trim($name, '_')]);
|
Chris@0
|
472 }
|
Chris@0
|
473 $info_messages[] = [
|
Chris@0
|
474 '#theme' => 'item_list',
|
Chris@0
|
475 '#items' => $messages,
|
Chris@0
|
476 '#title' => $title,
|
Chris@0
|
477 ];
|
Chris@0
|
478 }
|
Chris@0
|
479 }
|
Chris@0
|
480
|
Chris@0
|
481 // If there were any messages then prefix them with the module name
|
Chris@0
|
482 // and add it to the global message list.
|
Chris@0
|
483 if ($module_has_message) {
|
Chris@0
|
484 $all_messages[] = [
|
Chris@0
|
485 '#type' => 'container',
|
Chris@0
|
486 '#prefix' => '<h3>' . $this->t('@module module', ['@module' => $module]) . '</h3>',
|
Chris@0
|
487 '#children' => $info_messages,
|
Chris@0
|
488 ];
|
Chris@0
|
489 }
|
Chris@0
|
490 }
|
Chris@0
|
491 }
|
Chris@0
|
492 if ($all_messages) {
|
Chris@0
|
493 $build['query_messages'] = [
|
Chris@0
|
494 '#type' => 'container',
|
Chris@0
|
495 '#children' => $all_messages,
|
Chris@0
|
496 '#attributes' => ['class' => ['update-results']],
|
Chris@0
|
497 '#prefix' => '<h2>' . $this->t('The following updates returned messages:') . '</h2>',
|
Chris@0
|
498 ];
|
Chris@0
|
499 }
|
Chris@0
|
500 }
|
Chris@0
|
501 unset($_SESSION['update_results']);
|
Chris@0
|
502 unset($_SESSION['update_success']);
|
Chris@0
|
503 unset($_SESSION['update_ignore_warnings']);
|
Chris@0
|
504
|
Chris@0
|
505 return $build;
|
Chris@0
|
506 }
|
Chris@0
|
507
|
Chris@0
|
508 /**
|
Chris@0
|
509 * Renders a list of requirement errors or warnings.
|
Chris@0
|
510 *
|
Chris@0
|
511 * @param \Symfony\Component\HttpFoundation\Request $request
|
Chris@0
|
512 * The current request.
|
Chris@0
|
513 *
|
Chris@0
|
514 * @return array
|
Chris@0
|
515 * A render array.
|
Chris@0
|
516 */
|
Chris@0
|
517 public function requirements($severity, array $requirements, Request $request) {
|
Chris@0
|
518 $options = $severity == REQUIREMENT_WARNING ? ['continue' => 1] : [];
|
Chris@0
|
519 // @todo Revisit once https://www.drupal.org/node/2548095 is in. Something
|
Chris@0
|
520 // like Url::fromRoute('system.db_update')->setOptions() should then be
|
Chris@0
|
521 // possible.
|
Chris@0
|
522 $try_again_url = Url::fromUri($request->getUriForPath(''))->setOptions(['query' => $options])->toString(TRUE)->getGeneratedUrl();
|
Chris@0
|
523
|
Chris@0
|
524 $build['status_report'] = [
|
Chris@0
|
525 '#type' => 'status_report',
|
Chris@0
|
526 '#requirements' => $requirements,
|
Chris@17
|
527 '#suffix' => $this->t('Check the messages and <a href=":url">try again</a>.', [':url' => $try_again_url]),
|
Chris@0
|
528 ];
|
Chris@0
|
529
|
Chris@0
|
530 $build['#title'] = $this->t('Requirements problem');
|
Chris@0
|
531 return $build;
|
Chris@0
|
532 }
|
Chris@0
|
533
|
Chris@0
|
534 /**
|
Chris@0
|
535 * Provides the update task list render array.
|
Chris@0
|
536 *
|
Chris@0
|
537 * @param string $active
|
Chris@0
|
538 * The active task.
|
Chris@0
|
539 * Can be one of 'requirements', 'info', 'selection', 'run', 'results'.
|
Chris@0
|
540 *
|
Chris@0
|
541 * @return array
|
Chris@0
|
542 * A render array.
|
Chris@0
|
543 */
|
Chris@0
|
544 protected function updateTasksList($active = NULL) {
|
Chris@0
|
545 // Default list of tasks.
|
Chris@0
|
546 $tasks = [
|
Chris@0
|
547 'requirements' => $this->t('Verify requirements'),
|
Chris@0
|
548 'info' => $this->t('Overview'),
|
Chris@0
|
549 'selection' => $this->t('Review updates'),
|
Chris@0
|
550 'run' => $this->t('Run updates'),
|
Chris@0
|
551 'results' => $this->t('Review log'),
|
Chris@0
|
552 ];
|
Chris@0
|
553
|
Chris@0
|
554 $task_list = [
|
Chris@0
|
555 '#theme' => 'maintenance_task_list',
|
Chris@0
|
556 '#items' => $tasks,
|
Chris@0
|
557 '#active' => $active,
|
Chris@0
|
558 ];
|
Chris@0
|
559 return $task_list;
|
Chris@0
|
560 }
|
Chris@0
|
561
|
Chris@0
|
562 /**
|
Chris@0
|
563 * Starts the database update batch process.
|
Chris@0
|
564 *
|
Chris@0
|
565 * @param \Symfony\Component\HttpFoundation\Request $request
|
Chris@0
|
566 * The current request object.
|
Chris@0
|
567 */
|
Chris@0
|
568 protected function triggerBatch(Request $request) {
|
Chris@0
|
569 $maintenance_mode = $this->state->get('system.maintenance_mode', FALSE);
|
Chris@0
|
570 // Store the current maintenance mode status in the session so that it can
|
Chris@0
|
571 // be restored at the end of the batch.
|
Chris@0
|
572 $_SESSION['maintenance_mode'] = $maintenance_mode;
|
Chris@0
|
573 // During the update, always put the site into maintenance mode so that
|
Chris@0
|
574 // in-progress schema changes do not affect visiting users.
|
Chris@0
|
575 if (empty($maintenance_mode)) {
|
Chris@0
|
576 $this->state->set('system.maintenance_mode', TRUE);
|
Chris@0
|
577 }
|
Chris@0
|
578
|
Chris@0
|
579 $operations = [];
|
Chris@0
|
580
|
Chris@0
|
581 // Resolve any update dependencies to determine the actual updates that will
|
Chris@0
|
582 // be run and the order they will be run in.
|
Chris@0
|
583 $start = $this->getModuleUpdates();
|
Chris@0
|
584 $updates = update_resolve_dependencies($start);
|
Chris@0
|
585
|
Chris@0
|
586 // Store the dependencies for each update function in an array which the
|
Chris@0
|
587 // batch API can pass in to the batch operation each time it is called. (We
|
Chris@0
|
588 // do not store the entire update dependency array here because it is
|
Chris@0
|
589 // potentially very large.)
|
Chris@0
|
590 $dependency_map = [];
|
Chris@0
|
591 foreach ($updates as $function => $update) {
|
Chris@0
|
592 $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : [];
|
Chris@0
|
593 }
|
Chris@0
|
594
|
Chris@0
|
595 // Determine updates to be performed.
|
Chris@0
|
596 foreach ($updates as $function => $update) {
|
Chris@0
|
597 if ($update['allowed']) {
|
Chris@0
|
598 // Set the installed version of each module so updates will start at the
|
Chris@0
|
599 // correct place. (The updates are already sorted, so we can simply base
|
Chris@0
|
600 // this on the first one we come across in the above foreach loop.)
|
Chris@0
|
601 if (isset($start[$update['module']])) {
|
Chris@0
|
602 drupal_set_installed_schema_version($update['module'], $update['number'] - 1);
|
Chris@0
|
603 unset($start[$update['module']]);
|
Chris@0
|
604 }
|
Chris@0
|
605 $operations[] = ['update_do_one', [$update['module'], $update['number'], $dependency_map[$function]]];
|
Chris@0
|
606 }
|
Chris@0
|
607 }
|
Chris@0
|
608
|
Chris@0
|
609 $post_updates = $this->postUpdateRegistry->getPendingUpdateFunctions();
|
Chris@0
|
610
|
Chris@0
|
611 if ($post_updates) {
|
Chris@0
|
612 // Now we rebuild all caches and after that execute the hook_post_update()
|
Chris@0
|
613 // functions.
|
Chris@0
|
614 $operations[] = ['drupal_flush_all_caches', []];
|
Chris@0
|
615 foreach ($post_updates as $function) {
|
Chris@0
|
616 $operations[] = ['update_invoke_post_update', [$function]];
|
Chris@0
|
617 }
|
Chris@0
|
618 }
|
Chris@0
|
619
|
Chris@0
|
620 $batch['operations'] = $operations;
|
Chris@0
|
621 $batch += [
|
Chris@0
|
622 'title' => $this->t('Updating'),
|
Chris@0
|
623 'init_message' => $this->t('Starting updates'),
|
Chris@0
|
624 '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
|
625 'finished' => ['\Drupal\system\Controller\DbUpdateController', 'batchFinished'],
|
Chris@0
|
626 ];
|
Chris@0
|
627 batch_set($batch);
|
Chris@0
|
628
|
Chris@0
|
629 // @todo Revisit once https://www.drupal.org/node/2548095 is in.
|
Chris@0
|
630 return batch_process(Url::fromUri('base://results'), Url::fromUri('base://start'));
|
Chris@0
|
631 }
|
Chris@0
|
632
|
Chris@0
|
633 /**
|
Chris@0
|
634 * Finishes the update process and stores the results for eventual display.
|
Chris@0
|
635 *
|
Chris@0
|
636 * After the updates run, all caches are flushed. The update results are
|
Chris@0
|
637 * stored into the session (for example, to be displayed on the update results
|
Chris@0
|
638 * page in update.php). Additionally, if the site was off-line, now that the
|
Chris@0
|
639 * update process is completed, the site is set back online.
|
Chris@0
|
640 *
|
Chris@0
|
641 * @param $success
|
Chris@0
|
642 * Indicate that the batch API tasks were all completed successfully.
|
Chris@0
|
643 * @param array $results
|
Chris@0
|
644 * An array of all the results that were updated in update_do_one().
|
Chris@0
|
645 * @param array $operations
|
Chris@0
|
646 * A list of all the operations that had not been completed by the batch API.
|
Chris@0
|
647 */
|
Chris@0
|
648 public static function batchFinished($success, $results, $operations) {
|
Chris@0
|
649 // No updates to run, so caches won't get flushed later. Clear them now.
|
Chris@0
|
650 drupal_flush_all_caches();
|
Chris@0
|
651
|
Chris@0
|
652 $_SESSION['update_results'] = $results;
|
Chris@0
|
653 $_SESSION['update_success'] = $success;
|
Chris@0
|
654 $_SESSION['updates_remaining'] = $operations;
|
Chris@0
|
655
|
Chris@0
|
656 // Now that the update is done, we can put the site back online if it was
|
Chris@0
|
657 // previously not in maintenance mode.
|
Chris@0
|
658 if (empty($_SESSION['maintenance_mode'])) {
|
Chris@0
|
659 \Drupal::state()->set('system.maintenance_mode', FALSE);
|
Chris@0
|
660 }
|
Chris@0
|
661 unset($_SESSION['maintenance_mode']);
|
Chris@0
|
662 }
|
Chris@0
|
663
|
Chris@0
|
664 /**
|
Chris@0
|
665 * Provides links to the homepage and administration pages.
|
Chris@0
|
666 *
|
Chris@0
|
667 * @param \Symfony\Component\HttpFoundation\Request $request
|
Chris@0
|
668 * The current request.
|
Chris@0
|
669 *
|
Chris@0
|
670 * @return array
|
Chris@0
|
671 * An array of links.
|
Chris@0
|
672 */
|
Chris@0
|
673 protected function helpfulLinks(Request $request) {
|
Chris@0
|
674 // @todo Simplify with https://www.drupal.org/node/2548095
|
Chris@0
|
675 $base_url = str_replace('/update.php', '', $request->getBaseUrl());
|
Chris@0
|
676 $links['front'] = [
|
Chris@0
|
677 'title' => $this->t('Front page'),
|
Chris@0
|
678 'url' => Url::fromRoute('<front>')->setOption('base_url', $base_url),
|
Chris@0
|
679 ];
|
Chris@0
|
680 if ($this->account->hasPermission('access administration pages')) {
|
Chris@0
|
681 $links['admin-pages'] = [
|
Chris@0
|
682 'title' => $this->t('Administration pages'),
|
Chris@0
|
683 'url' => Url::fromRoute('system.admin')->setOption('base_url', $base_url),
|
Chris@0
|
684 ];
|
Chris@0
|
685 }
|
Chris@0
|
686 return $links;
|
Chris@0
|
687 }
|
Chris@0
|
688
|
Chris@0
|
689 /**
|
Chris@0
|
690 * Retrieves module updates.
|
Chris@0
|
691 *
|
Chris@0
|
692 * @return array
|
Chris@0
|
693 * The module updates that can be performed.
|
Chris@0
|
694 */
|
Chris@0
|
695 protected function getModuleUpdates() {
|
Chris@0
|
696 $return = [];
|
Chris@0
|
697 $updates = update_get_update_list();
|
Chris@0
|
698 foreach ($updates as $module => $update) {
|
Chris@0
|
699 $return[$module] = $update['start'];
|
Chris@0
|
700 }
|
Chris@0
|
701
|
Chris@0
|
702 return $return;
|
Chris@0
|
703 }
|
Chris@0
|
704
|
Chris@0
|
705 }
|