Security update for Core, with self-updated composer
[yaffs-website] / web / core / lib / Drupal / Core / Form / FormCache.php
1 <?php
2
3 namespace Drupal\Core\Form;
4
5 use Drupal\Component\Utility\Crypt;
6 use Drupal\Core\Access\CsrfTokenGenerator;
7 use Drupal\Core\Extension\ModuleHandlerInterface;
8 use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
9 use Drupal\Core\PageCache\RequestPolicyInterface;
10 use Drupal\Core\Session\AccountInterface;
11 use Drupal\Core\Site\Settings;
12 use Psr\Log\LoggerInterface;
13 use Symfony\Component\HttpFoundation\RequestStack;
14
15 /**
16  * Encapsulates the caching of a form and its form state.
17  *
18  * @ingroup form_api
19  */
20 class FormCache implements FormCacheInterface {
21
22   /**
23    * The factory for expirable key value stores used by form cache.
24    *
25    * @var \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface
26    */
27   protected $keyValueExpirableFactory;
28
29   /**
30    * The CSRF token generator to validate the form token.
31    *
32    * @var \Drupal\Core\Access\CsrfTokenGenerator
33    */
34   protected $csrfToken;
35
36   /**
37    * The current user.
38    *
39    * @var \Drupal\Core\Session\AccountInterface
40    */
41   protected $currentUser;
42
43   /**
44    * The module handler.
45    *
46    * @var \Drupal\Core\Extension\ModuleHandlerInterface
47    */
48   protected $moduleHandler;
49
50   /**
51    * Logger channel.
52    *
53    * @var \Drupal\Core\Logger\LoggerChannelInterface
54    */
55   protected $logger;
56
57   /**
58    * The config factory.
59    *
60    * @var \Drupal\Core\Config\ConfigFactoryInterface
61    */
62   protected $configFactory;
63
64   /**
65    * The request stack.
66    *
67    * @var \Symfony\Component\HttpFoundation\RequestStack
68    */
69   protected $requestStack;
70
71   /**
72    * A policy rule determining the cacheability of a request.
73    *
74    * @var \Drupal\Core\PageCache\RequestPolicyInterface
75    */
76   protected $requestPolicy;
77
78   /**
79    * The app root.
80    *
81    * @var string
82    */
83   protected $root;
84
85   /**
86    * Constructs a new FormCache.
87    *
88    * @param string $root
89    *   The app root.
90    * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $key_value_expirable_factory
91    *   The key value expirable factory, used to create key value expirable
92    *   stores for the form cache and form state cache.
93    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
94    *   The module handler.
95    * @param \Drupal\Core\Session\AccountInterface $current_user
96    *   The current user.
97    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
98    *   The CSRF token generator.
99    * @param \Psr\Log\LoggerInterface $logger
100    *   A logger instance.
101    * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
102    *   The request stack.
103    * @param \Drupal\Core\PageCache\RequestPolicyInterface $request_policy
104    *   A policy rule determining the cacheability of a request.
105    */
106   public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, ModuleHandlerInterface $module_handler, AccountInterface $current_user, CsrfTokenGenerator $csrf_token, LoggerInterface $logger, RequestStack $request_stack, RequestPolicyInterface $request_policy) {
107     $this->root = $root;
108     $this->keyValueExpirableFactory = $key_value_expirable_factory;
109     $this->moduleHandler = $module_handler;
110     $this->currentUser = $current_user;
111     $this->logger = $logger;
112     $this->csrfToken = $csrf_token;
113     $this->requestStack = $request_stack;
114     $this->requestPolicy = $request_policy;
115   }
116
117   /**
118    * {@inheritdoc}
119    */
120   public function getCache($form_build_id, FormStateInterface $form_state) {
121     if ($form = $this->keyValueExpirableFactory->get('form')->get($form_build_id)) {
122       if ((isset($form['#cache_token']) && $this->csrfToken->validate($form['#cache_token'])) || (!isset($form['#cache_token']) && $this->currentUser->isAnonymous())) {
123         $this->loadCachedFormState($form_build_id, $form_state);
124
125         // Generate a new #build_id if the cached form was rendered on a
126         // cacheable page.
127         $build_info = $form_state->getBuildInfo();
128         if (!empty($build_info['immutable'])) {
129           $form['#build_id_old'] = $form['#build_id'];
130           $form['#build_id'] = 'form-' . Crypt::randomBytesBase64();
131           $form['form_build_id']['#value'] = $form['#build_id'];
132           $form['form_build_id']['#id'] = $form['#build_id'];
133           unset($build_info['immutable']);
134           $form_state->setBuildInfo($build_info);
135         }
136         return $form;
137       }
138     }
139   }
140
141   /**
142    * Loads the cached form state.
143    *
144    * @param string $form_build_id
145    *   The unique form build ID.
146    * @param \Drupal\Core\Form\FormStateInterface $form_state
147    *   The current state of the form.
148    */
149   protected function loadCachedFormState($form_build_id, FormStateInterface $form_state) {
150     if ($stored_form_state = $this->keyValueExpirableFactory->get('form_state')->get($form_build_id)) {
151       // Re-populate $form_state for subsequent rebuilds.
152       $form_state->setFormState($stored_form_state);
153
154       // If the original form is contained in include files, load the files.
155       // @see \Drupal\Core\Form\FormStateInterface::loadInclude()
156       $build_info = $form_state->getBuildInfo();
157       $build_info += ['files' => []];
158       foreach ($build_info['files'] as $file) {
159         if (is_array($file)) {
160           $file += ['type' => 'inc', 'name' => $file['module']];
161           $this->moduleHandler->loadInclude($file['module'], $file['type'], $file['name']);
162         }
163         elseif (file_exists($file)) {
164           require_once $this->root . '/' . $file;
165         }
166       }
167     }
168   }
169
170   /**
171    * {@inheritdoc}
172    */
173   public function setCache($form_build_id, $form, FormStateInterface $form_state) {
174     // Cache forms for 6 hours by default.
175     $expire = Settings::get('form_cache_expiration', 21600);
176
177     // Ensure that the form build_id embedded in the form structure is the same
178     // as the one passed in as a parameter. This is an additional safety measure
179     // to prevent legacy code operating directly with
180     // \Drupal::formBuilder()->getCache() and \Drupal::formBuilder()->setCache()
181     // from accidentally overwriting immutable form state.
182     if (isset($form['#build_id']) && $form['#build_id'] != $form_build_id) {
183       $this->logger->error('Form build-id mismatch detected while attempting to store a form in the cache.');
184       return;
185     }
186
187     // Cache form structure.
188     if (isset($form)) {
189       if ($this->currentUser->isAuthenticated()) {
190         $form['#cache_token'] = $this->csrfToken->get();
191       }
192       unset($form['#build_id_old']);
193       $this->keyValueExpirableFactory->get('form')->setWithExpire($form_build_id, $form, $expire);
194     }
195
196     if ($data = $form_state->getCacheableArray()) {
197       $this->keyValueExpirableFactory->get('form_state')->setWithExpire($form_build_id, $data, $expire);
198     }
199   }
200
201   /**
202    * {@inheritdoc}
203    */
204   public function deleteCache($form_build_id) {
205     $this->keyValueExpirableFactory->get('form')->delete($form_build_id);
206     $this->keyValueExpirableFactory->get('form_state')->delete($form_build_id);
207   }
208
209 }