Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
60.00% |
3 / 5 |
CRAP | |
79.17% |
38 / 48 |
FormCache | |
0.00% |
0 / 1 |
|
60.00% |
3 / 5 |
23.62 | |
79.17% |
38 / 48 |
__construct | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 9 |
|||
getCache | |
100.00% |
1 / 1 |
7 | |
100.00% |
13 / 13 |
|||
loadCachedFormState | |
0.00% |
0 / 1 |
5.02 | |
90.91% |
10 / 11 |
|||
setCache | |
100.00% |
1 / 1 |
6 | |
100.00% |
12 / 12 |
|||
deleteCache | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
<?php | |
/** | |
* @file | |
* Contains \Drupal\Core\Form\FormCache. | |
*/ | |
namespace Drupal\Core\Form; | |
use Drupal\Component\Utility\Crypt; | |
use Drupal\Core\Access\CsrfTokenGenerator; | |
use Drupal\Core\Extension\ModuleHandlerInterface; | |
use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface; | |
use Drupal\Core\PageCache\RequestPolicyInterface; | |
use Drupal\Core\Session\AccountInterface; | |
use Psr\Log\LoggerInterface; | |
use Symfony\Component\HttpFoundation\RequestStack; | |
/** | |
* Encapsulates the caching of a form and its form state. | |
* | |
* @ingroup form_api | |
*/ | |
class FormCache implements FormCacheInterface { | |
/** | |
* The factory for expirable key value stores used by form cache. | |
* | |
* @var \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface | |
*/ | |
protected $keyValueExpirableFactory; | |
/** | |
* The CSRF token generator to validate the form token. | |
* | |
* @var \Drupal\Core\Access\CsrfTokenGenerator | |
*/ | |
protected $csrfToken; | |
/** | |
* The current user. | |
* | |
* @var \Drupal\Core\Session\AccountInterface | |
*/ | |
protected $currentUser; | |
/** | |
* The module handler. | |
* | |
* @var \Drupal\Core\Extension\ModuleHandlerInterface | |
*/ | |
protected $moduleHandler; | |
/** | |
* Logger channel. | |
* | |
* @var \Drupal\Core\Logger\LoggerChannelInterface | |
*/ | |
protected $logger; | |
/** | |
* The config factory. | |
* | |
* @var \Drupal\Core\Config\ConfigFactoryInterface | |
*/ | |
protected $configFactory; | |
/** | |
* The request stack. | |
* | |
* @var \Symfony\Component\HttpFoundation\RequestStack | |
*/ | |
protected $requestStack; | |
/** | |
* A policy rule determining the cacheability of a request. | |
* | |
* @var \Drupal\Core\PageCache\RequestPolicyInterface | |
*/ | |
protected $requestPolicy; | |
/** | |
* The app root. | |
* | |
* @var string | |
*/ | |
protected $root; | |
/** | |
* Constructs a new FormCache. | |
* | |
* @param string $root | |
* The app root. | |
* @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $key_value_expirable_factory | |
* The key value expirable factory, used to create key value expirable | |
* stores for the form cache and form state cache. | |
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler | |
* The module handler. | |
* @param \Drupal\Core\Session\AccountInterface $current_user | |
* The current user. | |
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token | |
* The CSRF token generator. | |
* @param \Psr\Log\LoggerInterface $logger | |
* A logger instance. | |
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack | |
* The request stack. | |
* @param \Drupal\Core\PageCache\RequestPolicyInterface $request_policy | |
* A policy rule determining the cacheability of a request. | |
*/ | |
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) { | |
$this->root = $root; | |
$this->keyValueExpirableFactory = $key_value_expirable_factory; | |
$this->moduleHandler = $module_handler; | |
$this->currentUser = $current_user; | |
$this->logger = $logger; | |
$this->csrfToken = $csrf_token; | |
$this->requestStack = $request_stack; | |
$this->requestPolicy = $request_policy; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getCache($form_build_id, FormStateInterface $form_state) { | |
if ($form = $this->keyValueExpirableFactory->get('form')->get($form_build_id)) { | |
if ((isset($form['#cache_token']) && $this->csrfToken->validate($form['#cache_token'])) || (!isset($form['#cache_token']) && $this->currentUser->isAnonymous())) { | |
$this->loadCachedFormState($form_build_id, $form_state); | |
// Generate a new #build_id if the cached form was rendered on a | |
// cacheable page. | |
$build_info = $form_state->getBuildInfo(); | |
if (!empty($build_info['immutable'])) { | |
$form['#build_id_old'] = $form['#build_id']; | |
$form['#build_id'] = 'form-' . Crypt::randomBytesBase64(); | |
$form['form_build_id']['#value'] = $form['#build_id']; | |
$form['form_build_id']['#id'] = $form['#build_id']; | |
unset($build_info['immutable']); | |
$form_state->setBuildInfo($build_info); | |
} | |
return $form; | |
} | |
} | |
} | |
/** | |
* Loads the cached form state. | |
* | |
* @param string $form_build_id | |
* The unique form build ID. | |
* @param \Drupal\Core\Form\FormStateInterface $form_state | |
* The current state of the form. | |
*/ | |
protected function loadCachedFormState($form_build_id, FormStateInterface $form_state) { | |
if ($stored_form_state = $this->keyValueExpirableFactory->get('form_state')->get($form_build_id)) { | |
// Re-populate $form_state for subsequent rebuilds. | |
$form_state->setFormState($stored_form_state); | |
// If the original form is contained in include files, load the files. | |
// @see \Drupal\Core\Form\FormStateInterface::loadInclude() | |
$build_info = $form_state->getBuildInfo(); | |
$build_info += ['files' => []]; | |
foreach ($build_info['files'] as $file) { | |
if (is_array($file)) { | |
$file += array('type' => 'inc', 'name' => $file['module']); | |
$this->moduleHandler->loadInclude($file['module'], $file['type'], $file['name']); | |
} | |
elseif (file_exists($file)) { | |
require_once $this->root . '/' . $file; | |
} | |
} | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function setCache($form_build_id, $form, FormStateInterface $form_state) { | |
// 6 hours cache life time for forms should be plenty. | |
$expire = 21600; | |
// Ensure that the form build_id embedded in the form structure is the same | |
// as the one passed in as a parameter. This is an additional safety measure | |
// to prevent legacy code operating directly with | |
// \Drupal::formBuilder()->getCache() and \Drupal::formBuilder()->setCache() | |
// from accidentally overwriting immutable form state. | |
if (isset($form['#build_id']) && $form['#build_id'] != $form_build_id) { | |
$this->logger->error('Form build-id mismatch detected while attempting to store a form in the cache.'); | |
return; | |
} | |
// Cache form structure. | |
if (isset($form)) { | |
if ($this->currentUser->isAuthenticated()) { | |
$form['#cache_token'] = $this->csrfToken->get(); | |
} | |
unset($form['#build_id_old']); | |
$this->keyValueExpirableFactory->get('form')->setWithExpire($form_build_id, $form, $expire); | |
} | |
if ($data = $form_state->getCacheableArray()) { | |
$this->keyValueExpirableFactory->get('form_state')->setWithExpire($form_build_id, $data, $expire); | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function deleteCache($form_build_id) { | |
$this->keyValueExpirableFactory->get('form')->delete($form_build_id); | |
$this->keyValueExpirableFactory->get('form_state')->delete($form_build_id); | |
} | |
} |