UpdateKernel.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. <?php
  2. namespace Drupal\Core\Update;
  3. use Drupal\Core\DrupalKernel;
  4. use Drupal\Core\Session\AnonymousUserSession;
  5. use Drupal\Core\Site\Settings;
  6. use Drupal\Core\StackMiddleware\ReverseProxyMiddleware;
  7. use Symfony\Cmf\Component\Routing\RouteObjectInterface;
  8. use Symfony\Component\DependencyInjection\ContainerInterface;
  9. use Symfony\Component\HttpFoundation\ParameterBag;
  10. use Symfony\Component\HttpFoundation\Request;
  11. use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
  12. /**
  13. * Defines a kernel which is used primarily to run the update of Drupal.
  14. *
  15. * We use a dedicated kernel + front controller (update.php) in order to be able
  16. * to repair Drupal if it is in a broken state.
  17. *
  18. * @see update.php
  19. * @see \Drupal\system\Controller\DbUpdateController
  20. */
  21. class UpdateKernel extends DrupalKernel {
  22. /**
  23. * {@inheritdoc}
  24. */
  25. public function discoverServiceProviders() {
  26. parent::discoverServiceProviders();
  27. $this->serviceProviderClasses['app']['update_kernel'] = 'Drupal\Core\Update\UpdateServiceProvider';
  28. }
  29. /**
  30. * {@inheritdoc}
  31. */
  32. protected function initializeContainer() {
  33. // Always force a container rebuild, in order to be able to override some
  34. // services, see \Drupal\Core\Update\UpdateServiceProvider.
  35. $this->containerNeedsRebuild = TRUE;
  36. $container = parent::initializeContainer();
  37. return $container;
  38. }
  39. /**
  40. * {@inheritdoc}
  41. */
  42. protected function cacheDrupalContainer(array $container_definition) {
  43. // Don't save this particular container to cache, so it does not leak into
  44. // the main site at all.
  45. return FALSE;
  46. }
  47. /**
  48. * {@inheritdoc}
  49. */
  50. public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
  51. try {
  52. static::bootEnvironment();
  53. // First boot up basic things, like loading the include files.
  54. $this->initializeSettings($request);
  55. ReverseProxyMiddleware::setSettingsOnRequest($request, Settings::getInstance());
  56. $this->boot();
  57. $container = $this->getContainer();
  58. /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
  59. $request_stack = $container->get('request_stack');
  60. $request_stack->push($request);
  61. $this->preHandle($request);
  62. // Handle the actual request. We need the session both for authentication
  63. // as well as the DB update, like
  64. // \Drupal\system\Controller\DbUpdateController::batchFinished.
  65. $this->bootSession($request, $type);
  66. $result = $this->handleRaw($request);
  67. $this->shutdownSession($request);
  68. return $result;
  69. }
  70. catch (\Exception $e) {
  71. return $this->handleException($e, $request, $type);
  72. }
  73. }
  74. /**
  75. * Generates the actual result of update.php.
  76. *
  77. * The actual logic of the update is done in the db update controller.
  78. *
  79. * @param \Symfony\Component\HttpFoundation\Request $request
  80. * The incoming request.
  81. *
  82. * @return \Symfony\Component\HttpFoundation\Response
  83. * A response object.
  84. *
  85. * @see \Drupal\system\Controller\DbUpdateController
  86. */
  87. protected function handleRaw(Request $request) {
  88. $container = $this->getContainer();
  89. $this->handleAccess($request, $container);
  90. /** @var \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver */
  91. $controller_resolver = $container->get('controller_resolver');
  92. /** @var callable $db_update_controller */
  93. $db_update_controller = $controller_resolver->getControllerFromDefinition('\Drupal\system\Controller\DbUpdateController::handle');
  94. $this->setupRequestMatch($request);
  95. /** @var \Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface $argument_resolver */
  96. $argument_resolver = $container->get('http_kernel.controller.argument_resolver');
  97. $arguments = $argument_resolver->getArguments($request, $db_update_controller);
  98. return call_user_func_array($db_update_controller, $arguments);
  99. }
  100. /**
  101. * Boots up the session.
  102. *
  103. * This method + shutdownSession() basically simulates what
  104. * \Drupal\Core\StackMiddleware\Session does.
  105. *
  106. * @param \Symfony\Component\HttpFoundation\Request $request
  107. * The incoming request.
  108. */
  109. protected function bootSession(Request $request) {
  110. $container = $this->getContainer();
  111. /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */
  112. $session = $container->get('session');
  113. $session->start();
  114. $request->setSession($session);
  115. }
  116. /**
  117. * Ensures that the session is saved.
  118. *
  119. * @param \Symfony\Component\HttpFoundation\Request $request
  120. * The incoming request.
  121. */
  122. protected function shutdownSession(Request $request) {
  123. if ($request->hasSession()) {
  124. $request->getSession()->save();
  125. }
  126. }
  127. /**
  128. * Set up the request with fake routing data for update.php.
  129. *
  130. * This fake routing data is needed in order to make batch API work properly.
  131. *
  132. * @param \Symfony\Component\HttpFoundation\Request $request
  133. * The incoming request.
  134. */
  135. protected function setupRequestMatch(Request $request) {
  136. $path = $request->getPathInfo();
  137. $args = explode('/', ltrim($path, '/'));
  138. $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'system.db_update');
  139. $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, $this->getContainer()->get('router.route_provider')->getRouteByName('system.db_update'));
  140. $op = $args[0] ?: 'info';
  141. $request->attributes->set('op', $op);
  142. $request->attributes->set('_raw_variables', new ParameterBag(['op' => $op]));
  143. }
  144. /**
  145. * Checks if the current user has rights to access updates page.
  146. *
  147. * If the current user does not have the rights, an exception is thrown.
  148. *
  149. * @param \Symfony\Component\HttpFoundation\Request $request
  150. * The incoming request.
  151. *
  152. * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
  153. * Thrown when update.php should not be accessible.
  154. */
  155. protected function handleAccess(Request $request) {
  156. /** @var \Drupal\Core\Authentication\AuthenticationManager $authentication_manager */
  157. $authentication_manager = $this->getContainer()->get('authentication');
  158. $account = $authentication_manager->authenticate($request) ?: new AnonymousUserSession();
  159. /** @var \Drupal\Core\Session\AccountProxyInterface $current_user */
  160. $current_user = $this->getContainer()->get('current_user');
  161. $current_user->setAccount($account);
  162. /** @var \Drupal\system\Access\DbUpdateAccessCheck $db_update_access */
  163. $db_update_access = $this->getContainer()->get('access_check.db_update');
  164. if (!Settings::get('update_free_access', FALSE) && !$db_update_access->access($account)->isAllowed()) {
  165. throw new AccessDeniedHttpException('In order to run update.php you need to either have "Administer software updates" permission or have set $settings[\'update_free_access\'] in your settings.php.');
  166. }
  167. }
  168. /**
  169. * {@inheritdoc}
  170. */
  171. public function loadLegacyIncludes() {
  172. parent::loadLegacyIncludes();
  173. static::fixSerializedExtensionObjects($this->container);
  174. }
  175. /**
  176. * Fixes caches and theme info if they contain old Extension objects.
  177. *
  178. * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
  179. * The container.
  180. *
  181. * @internal
  182. * This function is only to be called by the Drupal core update process.
  183. * Additionally, this function will be removed in minor release of Drupal.
  184. *
  185. * @todo https://www.drupal.org/project/drupal/issues/3031322 Remove once
  186. * Drupal 8.6.x is not supported.
  187. */
  188. public static function fixSerializedExtensionObjects(ContainerInterface $container) {
  189. // Create a custom error handler that will clear caches if a warning occurs
  190. // while getting 'system.theme.data' from state. If this state value was
  191. // created by Drupal <= 8.6.7 then when it is read by Drupal >= 8.6.8 there
  192. // will be PHP warnings. This silently fixes Drupal so that the update can
  193. // continue.
  194. $clear_caches = FALSE;
  195. $callable = function ($errno, $errstr) use ($container, &$clear_caches) {
  196. if ($errstr === 'Class Drupal\Core\Extension\Extension has no unserializer') {
  197. $clear_caches = TRUE;
  198. }
  199. };
  200. set_error_handler($callable, E_ERROR | E_WARNING);
  201. $container->get('state')->get('system.theme.data', []);
  202. restore_error_handler();
  203. if ($clear_caches) {
  204. // Reset static caches in profile list so the module list is rebuilt
  205. // correctly.
  206. $container->get('extension.list.profile')->reset();
  207. foreach ($container->getParameter('cache_bins') as $service_id => $bin) {
  208. $container->get($service_id)->deleteAll();
  209. }
  210. // The system.theme.data key is no longer used in Drupal 8.7.x.
  211. $container->get('state')->delete('system.theme.data');
  212. // Also rebuild themes because it uses state as cache.
  213. $container->get('theme_handler')->refreshInfo();
  214. }
  215. }
  216. }