annotate core/lib/Drupal/Core/Update/UpdateKernel.php @ 19:fa3358dc1485 tip

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