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 }
|