
| Current Path : /var/www/html/12park/web/modules/contrib/webform/src/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : /var/www/html/12park/web/modules/contrib/webform/src/WebformSubmissionStorage.php |
<?php
namespace Drupal\webform;
use Drupal\Core\Database\Database;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Drupal\webform\EntityStorage\WebformEntityStorageTrait;
use Drupal\webform\Plugin\WebformElement\WebformManagedFileBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the webform submission storage.
*/
class WebformSubmissionStorage extends SqlContentEntityStorage implements WebformSubmissionStorageInterface {
use WebformEntityStorageTrait;
/**
* Array used to element data schema.
*
* @var array
*/
protected $elementDataSchema = [];
/**
* The time service.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $time;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountProxyInterface
*/
protected $currentUser;
/**
* The replica kill switch.
*
* @var \Drupal\Core\Database\ReplicaKillSwitch
*/
protected $replicaKillSwitch;
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* The stream wrapper manager.
*
* @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
*/
protected $streamWrapperManager;
/**
* The logger factory.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected $loggerFactory;
/**
* The entity type repository.
*
* @var \Drupal\Core\Entity\EntityTypeRepositoryInterface
*/
protected $entityTypeRepository;
/**
* The webform access rules manager service.
*
* @var \Drupal\webform\WebformAccessRulesManagerInterface
*/
protected $accessRulesManager;
/**
* The webform element plugin manager.
*
* @var \Drupal\webform\Plugin\WebformElementManagerInterface
*/
protected $elementManager;
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
$instance = parent::createInstance($container, $entity_type);
$instance->time = $container->get('datetime.time');
$instance->replicaKillSwitch = $container->get('database.replica_kill_switch');
$instance->currentUser = $container->get('current_user');
$instance->fileSystem = $container->get('file_system');
$instance->streamWrapperManager = $container->get('stream_wrapper_manager');
$instance->loggerFactory = $container->get('logger.factory');
$instance->entityTypeRepository = $container->get('entity_type.repository');
$instance->accessRulesManager = $container->get('webform.access_rules_manager');
$instance->elementManager = $container->get('plugin.manager.webform.element');
return $instance;
}
/**
* {@inheritdoc}
*/
public function getFieldDefinitions() {
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $definitions */
$field_definitions = $this->entityFieldManager->getBaseFieldDefinitions('webform_submission');
// For now never let any see or export the serialize YAML data field.
unset($field_definitions['data']);
$definitions = [];
foreach ($field_definitions as $field_name => $field_definition) {
// Exclude the 'map' field type which is used by the metatag.module.
if ($field_definition->getType() === 'map') {
continue;
}
$definitions[$field_name] = [
'title' => $field_definition->getLabel(),
'name' => $field_name,
'type' => $field_definition->getType(),
'target_type' => $field_definition->getSetting('target_type'),
];
}
return $definitions;
}
/**
* {@inheritdoc}
*/
public function checkFieldDefinitionAccess(WebformInterface $webform, array $definitions) {
if (!$webform->access('submission_update_any')) {
unset($definitions['token']);
}
return $definitions;
}
/**
* {@inheritdoc}
*/
protected function doCreate(array $values) {
/** @var \Drupal\webform\WebformSubmissionInterface $entity */
$entity = parent::doCreate($values);
if (!empty($values['data'])) {
$data = (is_array($values['data'])) ? $values['data'] : Yaml::decode($values['data']);
$entity->setData($data);
}
return $entity;
}
/**
* {@inheritdoc}
*/
protected function doLoadMultiple(?array $ids = NULL) {
/** @var \Drupal\webform\WebformSubmissionInterface[] $webform_submissions */
$webform_submissions = parent::doLoadMultiple($ids);
$this->loadData($webform_submissions);
return $webform_submissions;
}
/**
* {@inheritdoc}
*/
public function loadByEntities(?WebformInterface $webform = NULL, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL) {
$properties = [];
if ($webform) {
$properties['webform_id'] = $webform->id();
}
if ($source_entity) {
$properties['entity_type'] = $source_entity->getEntityTypeId();
$properties['entity_id'] = $source_entity->id();
}
if ($account) {
$properties['uid'] = $account->id();
}
return $this->loadByProperties($properties);
}
/**
* {@inheritdoc}
*/
public function loadFromToken($token, WebformInterface $webform, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL) {
// Check token.
if (!$token) {
return NULL;
}
// Check that (secure) tokens are enabled for the webform.
if (!$account && !$webform->getSetting('token_update')) {
return NULL;
}
// Attempt to load the submission using the token.
$properties = ['token' => $token];
// Add optional source entity to properties.
if ($source_entity) {
$properties['entity_type'] = $source_entity->getEntityTypeId();
$properties['entity_id'] = $source_entity->id();
}
// Add optional user account to properties.
if ($account) {
$properties['uid'] = $account->id();
}
$entities = $this->loadByProperties($properties);
if (empty($entities)) {
return NULL;
}
/** @var \Drupal\webform\WebformSubmissionInterface $entity */
$entity = reset($entities);
// Make sure the submission is associated with the webform.
if ($entity->getWebform()->id() !== $webform->id()) {
return NULL;
}
return $entity;
}
/**
* {@inheritdoc}
*/
protected function buildPropertyQuery(QueryInterface $entity_query, array $values) {
// Add account query when ever filtered by uid.
if (isset($values['uid'])) {
$uids = (array) $values['uid'];
$accounts = User::loadMultiple($uids);
if ($accounts) {
$or_condition_group = $entity_query->orConditionGroup();
foreach ($accounts as $account) {
$this->addQueryConditions($or_condition_group, NULL, NULL, $account);
}
$entity_query->condition($or_condition_group);
unset($values['uid']);
}
}
parent::buildPropertyQuery($entity_query, $values);
}
/**
* {@inheritdoc}
*/
public function deleteAll(?WebformInterface $webform = NULL, ?EntityInterface $source_entity = NULL, $limit = NULL, $max_sid = NULL) {
$query = $this->getQuery();
$query->accessCheck(FALSE);
$this->addQueryConditions($query, $webform, $source_entity, NULL);
if ($max_sid) {
$query->condition('sid', $max_sid, '<=');
}
$query->sort('sid');
if ($limit) {
$query->range(0, $limit);
}
$entity_ids = $query->execute();
$entities = $this->loadMultiple($entity_ids);
$this->delete($entities);
return count($entities);
}
/**
* {@inheritdoc}
*/
public function getTotal(?WebformInterface $webform = NULL, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, array $options = []) {
// Default total to only look at completed submissions.
$options += [
'in_draft' => FALSE,
];
$query = $this->getQuery();
$query->accessCheck(FALSE);
$this->addQueryConditions($query, $webform, $source_entity, $account, $options);
$query->count();
return $query->execute();
}
/**
* {@inheritdoc}
*/
public function getMaxSubmissionId(?WebformInterface $webform = NULL, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL) {
$query = $this->getQuery();
$query->accessCheck(FALSE);
$this->addQueryConditions($query, $webform, $source_entity, $account);
$query->sort('sid', 'DESC');
$query->range(0, 1);
$result = $query->execute();
return reset($result);
}
/**
* {@inheritdoc}
*/
public function hasSubmissionValue(WebformInterface $webform, $element_key) {
/** @var \Drupal\Core\Database\StatementInterface $result */
$result = $this->database->select('webform_submission_data', 'sd')
->fields('sd', ['sid'])
->condition('sd.webform_id', $webform->id())
->condition('sd.name', $element_key)
->range(0, 1)
->execute();
return $result->fetchAssoc() ? TRUE : FALSE;
}
/* ************************************************************************ */
// Source entity methods.
/* ************************************************************************ */
/**
* {@inheritdoc}
*/
public function getSourceEntitiesTotal(WebformInterface $webform) {
$query = $this->database->select('webform_submission', 's')
->fields('s', ['entity_type', 'entity_id'])
->condition('webform_id', $webform->id())
->condition('entity_type', '', '<>')
->isNotNull('entity_type')
->condition('entity_id', '', '<>')
->isNotNull('entity_id')
->distinct();
return (int) $query->countQuery()->execute()->fetchField();
}
/**
* {@inheritdoc}
*/
public function getSourceEntities(WebformInterface $webform) {
/** @var \Drupal\Core\Database\StatementInterface $result */
$result = $this->database->select('webform_submission', 's')
->fields('s', ['entity_type', 'entity_id'])
->condition('webform_id', $webform->id())
->condition('entity_type', '', '<>')
->isNotNull('entity_type')
->condition('entity_id', '', '<>')
->isNotNull('entity_id')
->distinct()
->execute();
$source_entities = [];
while ($record = $result->fetchAssoc()) {
$source_entities[$record['entity_type']][$record['entity_id']] = $record['entity_id'];
}
return $source_entities;
}
/**
* {@inheritdoc}
*/
public function getSourceEntitiesAsOptions(WebformInterface $webform) {
$options = [];
$source_entities = $this->getSourceEntities($webform);
foreach ($source_entities as $entity_type => $entity_ids) {
$optgroup = (string) $this->entityTypeManager->getDefinition($entity_type)->getCollectionLabel();
$entities = $this->entityTypeManager->getStorage($entity_type)->loadMultiple($entity_ids);
foreach ($entities as $entity_id => $entity) {
if ($entity instanceof TranslatableInterface && $entity->hasTranslation($this->languageManager->getCurrentLanguage()->getId())) {
$entity = $entity->getTranslation($this->languageManager->getCurrentLanguage()->getId());
}
$option_value = "$entity_type:$entity_id";
$option_text = $entity->label();
$options[$optgroup][$option_value] = $option_text;
}
if (isset($options[$optgroup])) {
asort($options[$optgroup]);
}
}
return (count($options) === 1) ? reset($options) : $options;
}
/* ************************************************************************ */
// Query methods.
/* ************************************************************************ */
/**
* Add condition to submission query.
*
* @param \Drupal\Core\Database\Query\AlterableInterface|\Drupal\Core\Entity\Query\ConditionInterface $query
* A SQL query or entity conditions.
* @param \Drupal\webform\WebformInterface $webform
* (optional) A webform.
* @param \Drupal\Core\Entity\EntityInterface|null $source_entity
* (optional) A webform submission source entity.
* @param \Drupal\Core\Session\AccountInterface $account
* (optional) The current user account.
* @param array $options
* (optional) Additional options and query conditions.
* Options/conditions include:
* - in_draft (boolean): NULL will return all saved submissions and drafts.
* Defaults to NULL
* - check_source_entity (boolean): Check that a source entity is defined.
* - interval (int): Limit total within an seconds interval.
* - check_access (boolean): Check access to the submission.
*/
public function addQueryConditions($query, ?WebformInterface $webform = NULL, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, array $options = []) {
// Set default options/conditions.
$options += [
'check_source_entity' => FALSE,
'in_draft' => NULL,
'interval' => NULL,
'access_check' => TRUE,
];
if ($webform) {
$query->condition('webform_id', $webform->id());
}
if ($source_entity) {
$query->condition('entity_type', $source_entity->getEntityTypeId());
$query->condition('entity_id', $source_entity->id());
}
elseif ($options['check_source_entity']) {
$query->notExists('entity_type');
$query->notExists('entity_id');
}
if ($account) {
$query->condition('uid', $account->id());
// Add anonymous submission ids stored in $_SESSION.
if ($account->isAnonymous() && (int) $account->id() === (int) $this->currentUser->id()) {
$sids = $this->getAnonymousSubmissionIds($account);
if (empty($sids)) {
// Look for NULL sid to force returning no results.
$query->condition('sid', NULL);
}
else {
$query->condition('sid', $sids, 'IN');
}
}
}
if ($options['in_draft'] !== NULL) {
// Cast boolean to integer to support SQLite.
$query->condition('in_draft', (int) $options['in_draft']);
}
if ($options['interval']) {
$query->condition('completed', $this->time->getRequestTime() - $options['interval'], '>');
}
if ($options['access_check'] === FALSE) {
$query->accessCheck(FALSE);
}
}
/* ************************************************************************ */
// Paging methods.
/* ************************************************************************ */
/**
* {@inheritdoc}
*/
public function getFirstSubmission(WebformInterface $webform, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, array $options = []) {
return $this->getTerminusSubmission($webform, $source_entity, $account, $options, 'first');
}
/**
* {@inheritdoc}
*/
public function getLastSubmission(WebformInterface $webform, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, array $options = []) {
return $this->getTerminusSubmission($webform, $source_entity, $account, $options, 'last');
}
/**
* {@inheritdoc}
*/
public function getPreviousSubmission(WebformSubmissionInterface $webform_submission, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, array $options = []) {
return $this->getSiblingSubmission($webform_submission, $source_entity, $account, $options, 'previous');
}
/**
* {@inheritdoc}
*/
public function getNextSubmission(WebformSubmissionInterface $webform_submission, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, array $options = []) {
return $this->getSiblingSubmission($webform_submission, $source_entity, $account, $options, 'next');
}
/**
* {@inheritdoc}
*/
public function getSourceEntityTypes(WebformInterface $webform) {
$entity_types = Database::getConnection()->select('webform_submission', 's')
->distinct()
->fields('s', ['entity_type'])
->condition('s.webform_id', $webform->id())
->condition('s.entity_type', 'webform', '<>')
->orderBy('s.entity_type', 'ASC')
->execute()
->fetchCol();
$entity_type_labels = $this->entityTypeRepository->getEntityTypeLabels();
ksort($entity_type_labels);
return array_intersect_key($entity_type_labels, array_flip($entity_types));
}
/**
* {@inheritdoc}
*/
public function getSourceEntityAsOptions(WebformInterface $webform, $entity_type) {
$entity_ids = Database::getConnection()->select('webform_submission', 's')
->fields('s', ['entity_id'])
->condition('s.webform_id', $webform->id())
->condition('s.entity_type', $entity_type)
->distinct()
->execute()
->fetchCol();
// Limit the number of source entities loaded to 100.
if (empty($entity_ids) || count($entity_ids) > 100) {
return [];
}
$entities = $this->entityTypeManager->getStorage($entity_type)->loadMultiple($entity_ids);
$options = [];
foreach ($entities as $entity_id => $entity) {
$options[$entity_id] = $entity->label();
}
asort($options);
return $options;
}
/**
* Get a webform submission's terminus (aka first or last).
*
* @param \Drupal\webform\WebformInterface $webform
* A webform.
* @param \Drupal\Core\Entity\EntityInterface|null $source_entity
* (optional) A webform submission source entity.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user account.
* @param array $options
* (optional) Additional options and query conditions.
* @param string $terminus
* Submission terminus, first or last.
*
* @return \Drupal\webform\WebformSubmissionInterface|null
* The webform submission's terminus (aka first or last).
*/
protected function getTerminusSubmission(WebformInterface $webform, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, array $options = [], $terminus = 'first') {
$options += ['in_draft' => FALSE];
$query = $this->getQuery();
$query->accessCheck(TRUE);
$this->addQueryConditions($query, $webform, $source_entity, $account, $options);
$query->sort('sid', ($terminus === 'first') ? 'ASC' : 'DESC');
$query->range(0, 1);
return ($entity_ids = $query->execute()) ? $this->load(reset($entity_ids)) : NULL;
}
/**
* Get a webform submission's sibling.
*
* @param \Drupal\webform\WebformSubmissionInterface $webform_submission
* A webform submission.
* @param \Drupal\Core\Entity\EntityInterface|null $source_entity
* (optional) A webform submission source entity.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user account.
* @param array $options
* (optional) Additional options and query conditions.
* @param string $direction
* Direction of the sibling.
*
* @return \Drupal\webform\WebformSubmissionInterface|null
* The webform submission's sibling.
*/
protected function getSiblingSubmission(WebformSubmissionInterface $webform_submission, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, array $options = [], $direction = 'previous') {
$webform = $webform_submission->getWebform();
$query = $this->getQuery();
$query->accessCheck(TRUE);
$this->addQueryConditions($query, $webform, $source_entity, $account, $options);
if ($direction === 'previous') {
$query->condition('sid', $webform_submission->id(), '<');
$query->sort('sid', 'DESC');
}
else {
$query->condition('sid', $webform_submission->id(), '>');
$query->sort('sid', 'ASC');
}
$query->range(0, 1);
$submission = ($entity_ids = $query->execute()) ? $this->load(reset($entity_ids)) : NULL;
// If account is specified, we need make sure the user can view the submission.
if ($submission && $account && !$submission->access('view', $account)) {
return NULL;
}
return $submission;
}
/* ************************************************************************ */
// WebformSubmissionEntityList methods.
/* ************************************************************************ */
/**
* {@inheritdoc}
*/
public function getCustomColumns(?WebformInterface $webform = NULL, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, $include_elements = TRUE) {
$column_names = $this->getCustomSetting('columns', [], $webform, $source_entity)
?: $this->getDefaultColumnNames($webform, $source_entity, $account, $include_elements);
$columns = $this->getColumns($webform, $source_entity, $account, $include_elements);
return $this->filterColumns($column_names, $columns);
}
/**
* {@inheritdoc}
*/
public function getUserColumns(?WebformInterface $webform = NULL, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, $include_elements = TRUE) {
$column_names = ($webform) ? $webform->getSetting('submission_user_columns', []) : [];
$column_names = $column_names ?: $this->getUserDefaultColumnNames($webform, $source_entity, $account, $include_elements);
$columns = $this->getColumns($webform, $source_entity, $account, $include_elements);
unset($columns['sid']);
return $this->filterColumns($column_names, $columns);
}
/**
* {@inheritdoc}
*/
public function getDefaultColumns(?WebformInterface $webform = NULL, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, $include_elements = TRUE) {
$columns = $this->getColumns($webform, $source_entity, $account, $include_elements);
// Unset columns.
unset(
// Admin columns.
$columns['sid'],
$columns['label'],
$columns['uuid'],
$columns['in_draft'],
$columns['completed'],
$columns['changed']
);
// Hide certain unnecessary columns, that have default set to FALSE.
foreach ($columns as $column_name => $column) {
if (isset($column['default']) && $column['default'] === FALSE) {
unset($columns[$column_name]);
}
}
return $columns;
}
/**
* {@inheritdoc}
*/
public function getSubmissionsColumns() {
$columns = $this->getColumns(NULL, NULL, NULL, FALSE);
// Unset columns.
// Note: 'serial' is displayed instead of 'sid'.
unset(
// Admin columns.
$columns['serial'],
$columns['label'],
$columns['uuid'],
$columns['in_draft'],
$columns['completed'],
$columns['changed'],
// User columns.
$columns['sticky'],
$columns['locked'],
$columns['notes'],
$columns['uid']
);
return $columns;
}
/**
* {@inheritdoc}
*/
public function getUsersSubmissionsColumns() {
$columns = $this->getColumns(NULL, NULL, NULL, FALSE);
// Unset columns.
// Note: Displaying 'label' instead of 'serial' or 'sid'.
unset(
// Admin columns.
$columns['sid'],
$columns['serial'],
$columns['uuid'],
$columns['in_draft'],
$columns['completed'],
$columns['changed'],
// User columns.
$columns['sticky'],
$columns['locked'],
$columns['notes'],
$columns['uid'],
// References columns.
$columns['webform_id'],
$columns['entity'],
// Operations.
$columns['operations']
);
return $columns;
}
/**
* {@inheritdoc}
*/
public function getColumns(?WebformInterface $webform = NULL, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, $include_elements = TRUE) {
$view_any = ($webform && $webform->access('submission_view_any')) ? TRUE : FALSE;
$columns = [];
// Serial number.
$columns['serial'] = [
'title' => $this->t('#'),
];
// Submission ID.
$columns['sid'] = [
'title' => $this->t('SID'),
];
// Submission label.
$columns['label'] = [
'title' => $this->t('Submission title'),
'sort' => FALSE,
];
// UUID.
$columns['uuid'] = [
'title' => $this->t('UUID'),
];
// Draft.
$columns['in_draft'] = [
'title' => $this->t('In draft'),
];
if (empty($account)) {
// Sticky (Starred/Unstarred).
$columns['sticky'] = [
'title' => $this->t('Starred'),
];
// Locked.
$columns['locked'] = [
'title' => $this->t('Locked'),
];
// Notes.
$columns['notes'] = [
'title' => $this->t('Notes'),
];
}
// Created.
$columns['created'] = [
'title' => $this->t('Created'),
];
// Completed.
$columns['completed'] = [
'title' => $this->t('Completed'),
];
// Changed.
$columns['changed'] = [
'title' => $this->t('Changed'),
];
// Source entity.
if ($view_any && empty($source_entity)) {
$columns['entity'] = [
'title' => $this->t('Submitted to'),
'sort' => FALSE,
];
}
// Submitted by.
if (empty($account)) {
$columns['uid'] = [
'title' => $this->t('User'),
];
}
// Submission language.
if ($view_any && $this->moduleHandler->moduleExists('language')) {
$columns['langcode'] = [
'title' => $this->t('Language'),
];
}
// Remote address.
$columns['remote_addr'] = [
'title' => $this->t('IP address'),
];
// Webform and source entity for entity.webform_submission.collection.
// @see /admin/structure/webform/submissions/manage
if (empty($webform) && empty($source_entity)) {
$columns['webform_id'] = [
'title' => $this->t('Webform', [], ['context' => 'form']),
];
$columns['entity'] = [
'title' => $this->t('Submitted to'),
'sort' => FALSE,
];
}
// Webform elements.
if ($webform && $include_elements) {
$elements = $webform->getElementsInitializedFlattenedAndHasValue('view');
foreach ($elements as $element) {
/** @var \Drupal\webform\Plugin\WebformElementInterface $element_plugin */
$element_plugin = $this->elementManager->createInstance($element['#type']);
// Replace tokens which can be used in an element's #title.
$element_plugin->replaceTokens($element, $webform);
$columns += $element_plugin->getTableColumn($element);
}
}
// Operations.
$columns['operations'] = [
'title' => $this->t('Operations'),
'sort' => FALSE,
];
// Add name and format to all columns.
foreach ($columns as $name => &$column) {
$column['name'] = $name;
$column['format'] = 'value';
}
return $columns;
}
/**
* {@inheritdoc}
*/
public function getUserDefaultColumnNames(?WebformInterface $webform = NULL, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, $include_elements = TRUE) {
return ['serial', 'created', 'remote_addr'];
}
/**
* {@inheritdoc}
*/
public function getDefaultColumnNames(?WebformInterface $webform = NULL, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL, $include_elements = TRUE) {
$columns = $this->getDefaultColumns($webform, $source_entity, $account, $include_elements);
return array_keys($columns);
}
/**
* Get specified columns in specified order.
*
* @param array $column_names
* An associative array of column names.
* @param array $columns
* An associative array containing all available columns.
*
* @return array
* An associative array containing all specified columns.
*/
protected function filterColumns(array $column_names, array $columns) {
$filtered_columns = [];
foreach ($column_names as $column_name) {
if (isset($columns[$column_name])) {
$filtered_columns[$column_name] = $columns[$column_name];
}
}
return $filtered_columns;
}
/* ************************************************************************ */
// Custom settings methods.
/* ************************************************************************ */
/**
* {@inheritdoc}
*/
public function getCustomSetting($name, $default, ?WebformInterface $webform = NULL, ?EntityInterface $source_entity = NULL) {
// Return the default value is webform and source entity is not defined.
if (!$webform && !$source_entity) {
return $default;
}
$results_customize = $webform->getSetting('results_customize', TRUE);
$key = "results.custom.$name";
if (!$source_entity) {
if ($results_customize && $webform->hasUserData($key)) {
return $webform->getUserData($key);
}
elseif ($webform->hasState($key)) {
return $webform->getState($key);
}
else {
return $default;
}
}
$source_entity_key = $key . '.' . $source_entity->getEntityTypeId() . '.' . $source_entity->id();
if ($results_customize && $webform->hasUserData($source_entity_key)) {
return $webform->getUserData($source_entity_key);
}
elseif ($webform->hasState($source_entity_key)) {
return $webform->getState($source_entity_key);
}
elseif ($webform->getState('results.custom.default', FALSE)) {
return $webform->getState($key, $default);
}
else {
return $default;
}
}
/* ************************************************************************ */
// Invoke WebformElement and WebformHandler plugin methods.
/* ************************************************************************ */
/**
* {@inheritdoc}
*/
public function create(array $values = []) {
/** @var \Drupal\webform\WebformSubmissionInterface $entity */
// Pre create is called via the WebformSubmission entity.
// @see: \Drupal\webform\Entity\WebformSubmission::preCreate
$entity = parent::create($values);
$this->invokeWebformElements('postCreate', $entity);
$this->invokeWebformHandlers('postCreate', $entity);
return $entity;
}
/**
* {@inheritdoc}
*/
protected function postLoad(array &$entities) {
/** @var \Drupal\webform\WebformSubmissionInterface $entity */
$return = parent::postLoad($entities);
foreach ($entities as $entity) {
$this->invokeWebformElements('postLoad', $entity);
$this->invokeWebformHandlers('postLoad', $entity);
// If this is an anonymous draft.
// We must add $SESSION to the submission's cache context.
// @see \Drupal\webform\WebformSubmissionStorage::loadDraft
// @todo Add support for 'view own submission' permission.
if ($entity->isDraft() && $entity->getOwner()->isAnonymous()) {
$entity->addCacheContexts(['session']);
}
}
return $return;
}
/**
* {@inheritdoc}
*/
protected function doPreSave(EntityInterface $entity) {
/** @var \Drupal\webform\WebformSubmissionInterface $entity */
$id = parent::doPreSave($entity);
$this->invokeWebformElements('preSave', $entity);
$this->invokeWebformHandlers('preSave', $entity);
return $id;
}
/**
* {@inheritdoc}
*/
public function save(EntityInterface $entity) {
// Set serial number using the webform table, this is done outside of the
// main transaction initialized in parent::save() to reduce blocking
// concurrent webform submissions for as little time as possible.
if (!$entity->serial() && !$entity->getWebform()->getSetting('serial_disabled')) {
$next_serial = $this->getWebformStorage()->getSerial($entity->getWebform());
$entity->set('serial', $next_serial);
}
return parent::save($entity);
}
/**
* {@inheritdoc}
*/
protected function doSave($id, EntityInterface $entity) {
/** @var \Drupal\webform\WebformSubmissionInterface $entity */
if ($entity->getWebform()->getSetting('results_disabled')) {
return WebformSubmissionStorageInterface::SAVED_DISABLED;
}
$is_new = $entity->isNew();
$result = parent::doSave($id, $entity);
// Save data.
$this->saveData($entity, !$is_new);
// Set serial number to the submission id.
if (!$entity->serial() && $entity->getWebform()->getSetting('serial_disabled')) {
$this->database->update('webform_submission')
->fields(['serial' => $entity->id()])
->condition('sid', $entity->id())
->execute();
$entity->set('serial', $entity->id());
}
// Set anonymous draft token.
// This only needs to be called for new anonymous submissions.
if ($is_new) {
$this->setAnonymousSubmission($entity);
}
return $result;
}
/**
* {@inheritdoc}
*/
protected function doPostSave(EntityInterface $entity, $update) {
/** @var \Drupal\webform\WebformSubmissionInterface $entity */
parent::doPostSave($entity, $update);
$webform = $entity->getWebform();
if ($webform->hasSubmissionLog()) {
// Log webform submission events to the 'webform_submission' log.
$context = [
'@title' => $entity->label(),
'link' => ($entity->id()) ? $entity->toLink($this->t('Edit'), 'edit-form')->toString() : NULL,
'webform_submission' => $entity,
];
switch ($entity->getState()) {
case WebformSubmissionInterface::STATE_DRAFT_UPDATED:
case WebformSubmissionInterface::STATE_DRAFT_CREATED:
if ($update) {
$message = '@title draft updated.';
$context['operation'] = 'draft updated';
}
else {
$message = '@title draft created.';
$context['operation'] = 'draft created';
}
break;
case WebformSubmissionInterface::STATE_COMPLETED:
if ($update) {
$message = '@title completed using saved draft.';
$context['operation'] = 'submission completed';
}
else {
$message = '@title created.';
$context['operation'] = 'submission created';
}
break;
case WebformSubmissionInterface::STATE_CONVERTED:
$message = '@title converted from anonymous to @user.';
$context['operation'] = 'submission converted';
$context['@user'] = $entity->getOwner()->label();
break;
case WebformSubmissionInterface::STATE_UPDATED:
$message = '@title updated.';
$context['operation'] = 'submission updated';
break;
case WebformSubmissionInterface::STATE_UNSAVED:
$message = '@title submitted.';
$context['operation'] = 'submission submitted';
break;
case WebformSubmissionInterface::STATE_LOCKED:
$message = '@title locked.';
$context['operation'] = 'submission locked';
break;
default:
throw new \Exception('Unexpected webform submission state');
}
$this->loggerFactory->get('webform_submission')->notice($message, $context);
}
elseif (!$webform->getSetting('results_disabled')) {
// Log general events to the 'webform'.
switch ($entity->getState()) {
case WebformSubmissionInterface::STATE_DRAFT_CREATED:
$message = '@title draft created.';
break;
case WebformSubmissionInterface::STATE_DRAFT_UPDATED:
$message = '@title draft updated.';
break;
case WebformSubmissionInterface::STATE_UPDATED:
$message = '@title updated.';
break;
case WebformSubmissionInterface::STATE_COMPLETED:
$message = ($update) ? '@title completed.' : '@title created.';
break;
default:
$message = NULL;
break;
}
if ($message) {
$context = [
'@id' => $entity->id(),
'@title' => $entity->label(),
'link' => ($entity->id()) ? $entity->toLink($this->t('Edit'), 'edit-form')->toString() : NULL,
];
$this->loggerFactory->get('webform')->notice($message, $context);
}
}
$this->invokeWebformElements('postSave', $entity, $update);
$this->invokeWebformHandlers('postSave', $entity, $update);
}
/**
* {@inheritdoc}
*/
public function resave(EntityInterface $entity) {
/** @var \Drupal\webform\WebformSubmissionInterface $entity */
$transaction = $this->database->startTransaction();
try {
$return = $this->doSave($entity->id(), $entity);
// Ignore replica server temporarily.
$this->replicaKillSwitch->trigger();
return $return;
}
catch (\Exception $e) {
$transaction->rollBack();
watchdog_exception($this->entityTypeId, $e);
throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function delete(array $entities) {
/** @var \Drupal\webform\WebformSubmissionInterface $entity */
if (!$entities) {
// If no entities were passed, do nothing.
return;
}
foreach ($entities as $entity) {
$this->invokeWebformElements('preDelete', $entity);
$this->invokeWebformHandlers('preDelete', $entity);
}
$return = parent::delete($entities);
$this->deleteData($entities);
foreach ($entities as $entity) {
$this->invokeWebformElements('postDelete', $entity);
$this->invokeWebformHandlers('postDelete', $entity);
WebformManagedFileBase::deleteFiles($entity);
}
// Remove empty webform submission specific file directory
// for all stream wrappers.
// @see \Drupal\webform\Plugin\WebformElement\WebformManagedFileBase
// @see \Drupal\webform\Plugin\WebformElement\WebformSignature
foreach ($entities as $entity) {
$webform = $entity->getWebform();
if ($webform) {
$stream_wrappers = array_keys($this->streamWrapperManager
->getNames(StreamWrapperInterface::WRITE_VISIBLE));
foreach ($stream_wrappers as $stream_wrapper) {
$file_directory = $stream_wrapper . '://webform/' . $webform->id() . '/' . $entity->id();
// Clear empty webform submission directory.
if (is_dir($file_directory)
&& empty($this->fileSystem->scanDirectory($file_directory, '/.*/'))) {
$this->fileSystem->deleteRecursive($file_directory);
}
}
}
}
// Log deleted.
foreach ($entities as $entity) {
$webform = $entity->getWebform();
$this->loggerFactory->get('webform')
->notice('Deleted @form: Submission #@id.', [
'@id' => $entity->id(),
'@form' => ($webform) ? $webform->label() : '[' . $this->t('Webform', [], ['context' => 'form']) . ']',
]);
}
return $return;
}
/* ************************************************************************ */
// Invoke methods.
/* ************************************************************************ */
/**
* {@inheritdoc}
*/
public function invokeWebformHandlers($method, WebformSubmissionInterface $webform_submission, &$context1 = NULL, &$context2 = NULL) {
$webform = $webform_submission->getWebform();
if ($webform) {
return $webform->invokeHandlers($method, $webform_submission, $context1, $context2);
}
}
/**
* {@inheritdoc}
*/
public function invokeWebformElements($method, WebformSubmissionInterface $webform_submission, &$context1 = NULL, &$context2 = NULL) {
$webform = $webform_submission->getWebform();
if ($webform) {
$webform->invokeElements($method, $webform_submission, $context1, $context2);
}
}
/* ************************************************************************ */
// Purge methods.
/* ************************************************************************ */
/**
* {@inheritdoc}
*/
public function purge($count) {
$days_to_seconds = 60 * 60 * 24;
$query = $this->getWebformStorage()->getQuery();
$query->accessCheck(FALSE);
$query->condition('settings.purge', [WebformSubmissionStorageInterface::PURGE_DRAFT, WebformSubmissionStorageInterface::PURGE_COMPLETED, WebformSubmissionStorageInterface::PURGE_ALL], 'IN');
$query->condition('settings.purge_days', 0, '>');
$webform_ids = array_values($query->execute());
$remaining = $count;
if (!empty($webform_ids)) {
$webforms = $this->getWebformStorage()->loadMultiple($webform_ids);
foreach ($webforms as $webform) {
$query = $this->getQuery();
// Since results of this query are never displayed to the user and we
// actually need to query the entire dataset of webform submissions, we
// are disabling access check.
$query->accessCheck(FALSE);
$query->condition('created', $this->time->getRequestTime() - ($webform->getSetting('purge_days') * $days_to_seconds), '<');
$query->condition('webform_id', $webform->id());
switch ($webform->getSetting('purge')) {
case WebformSubmissionStorageInterface::PURGE_DRAFT:
$query->condition('in_draft', 1);
break;
case WebformSubmissionStorageInterface::PURGE_COMPLETED:
$query->condition('in_draft', 0);
break;
}
$query->range(0, $remaining);
$sids = array_values($query->execute());
if (empty($sids)) {
continue;
}
$remaining -= count($sids);
$webform_submissions = $this->loadMultiple($sids);
$webform->invokeHandlers('prePurge', $webform_submissions);
$this->moduleHandler()->invokeAll('webform_submissions_pre_purge', [&$webform_submissions]);
$this->delete($webform_submissions);
$webform->invokeHandlers('postPurge', $webform_submissions);
$this->moduleHandler()->invokeAll('webform_submissions_post_purge', [$webform_submissions]);
if ($remaining === 0) {
// We've collected enough webform submissions for purging in this run.
break;
}
}
}
}
/* ************************************************************************ */
// Data handlers.
/* ************************************************************************ */
/**
* {@inheritdoc}
*/
public function saveData(WebformSubmissionInterface $webform_submission, $delete_first = TRUE) {
// Get submission data rows.
$data = $webform_submission->getData();
$webform_id = $webform_submission->getWebform()->id();
$sid = $webform_submission->id();
$elements = $webform_submission->getWebform()->getElementsInitializedFlattenedAndHasValue();
$computed_elements = $webform_submission->getWebform()->getElementsComputed();
$rows = [];
foreach ($data as $name => $item) {
$element = $elements[$name] ?? ['#webform_multiple' => FALSE, '#webform_composite' => FALSE];
// Check if this is a computed element which is not
// stored in the database.
$is_computed_element = (isset($computed_elements[$name])) ? TRUE : FALSE;
if ($is_computed_element && empty($element['#store'])) {
continue;
}
if ($element['#webform_composite']) {
if (is_array($item)) {
$composite_items = (empty($element['#webform_multiple'])) ? [$item] : $item;
foreach ($composite_items as $delta => $composite_item) {
foreach ($composite_item as $property => $value) {
$rows[] = [
'webform_id' => $webform_id,
'sid' => $sid,
'name' => $name,
'property' => $property,
'delta' => $delta,
'value' => (string) $value,
];
}
}
}
}
elseif ($element['#webform_multiple']) {
if (is_array($item)) {
foreach ($item as $delta => $value) {
$rows[] = [
'webform_id' => $webform_id,
'sid' => $sid,
'name' => $name,
'property' => '',
'delta' => $delta,
'value' => (string) $value,
];
}
}
}
else {
$rows[] = [
'webform_id' => $webform_id,
'sid' => $sid,
'name' => $name,
'property' => '',
'delta' => 0,
'value' => (string) $item,
];
}
}
if ($delete_first) {
// Delete existing submission data rows.
$this->database->delete('webform_submission_data')
->condition('sid', $sid)
->execute();
}
// Insert new submission data rows.
$query = $this->database
->insert('webform_submission_data')
->fields(['webform_id', 'sid', 'name', 'property', 'delta', 'value']);
foreach ($rows as $row) {
$query->values($row);
}
$query->execute();
}
/**
* Save webform submission data from the 'webform_submission_data' table.
*
* @param array $webform_submissions
* An array of webform submissions.
*/
protected function loadData(array &$webform_submissions) {
// Load webform submission data.
if ($sids = array_keys($webform_submissions)) {
$submissions_data = [];
// Initialize all multiple value elements to make sure a value is defined.
$webform_default_data = [];
foreach ($webform_submissions as $sid => $webform_submission) {
/** @var \Drupal\webform\WebformInterface $webform */
$webform = $webform_submissions[$sid]->getWebform();
$webform_id = $webform->id();
if (!isset($webform_default_data[$webform_id])) {
$webform_default_data[$webform_id] = [];
$elements = ($webform) ? $webform->getElementsInitializedFlattenedAndHasValue() : [];
foreach ($elements as $element_key => $element) {
if (!empty($element['#webform_multiple'])) {
$webform_default_data[$webform_id][$element_key] = [];
}
}
}
$submissions_data[$sid] = $webform_default_data[$webform_id];
}
/** @var \Drupal\Core\Database\StatementInterface $result */
$result = $this->database->select('webform_submission_data', 'sd')
->fields('sd', ['webform_id', 'sid', 'name', 'property', 'delta', 'value'])
->condition('sd.sid', $sids, 'IN')
->orderBy('sd.sid', 'ASC')
->orderBy('sd.name', 'ASC')
->orderBy('sd.property', 'ASC')
->orderBy('sd.delta', 'ASC')
->execute();
while ($record = $result->fetchAssoc()) {
$sid = $record['sid'];
$name = $record['name'];
/** @var \Drupal\webform\WebformInterface $webform */
$webform = $webform_submissions[$sid]->getWebform();
$elements = ($webform) ? $webform->getElementsInitializedFlattenedAndHasValue() : [];
$element = $elements[$name] ?? ['#webform_multiple' => FALSE, '#webform_composite' => FALSE];
if ($element['#webform_composite']) {
if ($element['#webform_multiple']) {
$submissions_data[$sid][$name][$record['delta']][$record['property']] = $record['value'];
}
else {
$submissions_data[$sid][$name][$record['property']] = $record['value'];
}
}
elseif ($element['#webform_multiple']) {
$submissions_data[$sid][$name][$record['delta']] = $record['value'];
}
else {
$submissions_data[$sid][$name] = $record['value'];
}
}
// Set webform submission data via setData().
foreach ($submissions_data as $sid => $submission_data) {
$webform_submission = $webform_submissions[$sid];
$webform_submission->setData($submission_data);
$webform_submission->setOriginalData($webform_submission->getData());
}
}
}
/**
* Delete webform submission data from the 'webform_submission_data' table.
*
* @param array $webform_submissions
* An array of webform submissions.
*/
protected function deleteData(array $webform_submissions) {
$sids = [];
foreach ($webform_submissions as $webform_submission) {
$sids[$webform_submission->id()] = $webform_submission->id();
}
$this->database->delete('webform_submission_data')
->condition('sid', $sids, 'IN')
->execute();
}
/* ************************************************************************ */
// Draft methods.
/* ************************************************************************ */
/**
* {@inheritdoc}
*/
public function loadDraft(WebformInterface $webform, ?EntityInterface $source_entity = NULL, ?AccountInterface $account = NULL) {
$options = [
'check_source_entity' => TRUE,
'in_draft' => TRUE,
];
$query = $this->getQuery();
// Because draft is somewhat different from a complete webform submission,
// we allow to bypass access check. Moreover, draft here is enforced to be
// authored by the $account user. Thus we hardly open any security breach
// here.
$query->accessCheck(FALSE);
$this->addQueryConditions($query, $webform, $source_entity, $account, $options);
// Only load the most recent draft.
$query->sort('sid', 'DESC');
return ($sids = $query->execute()) ? $this->load(reset($sids)) : NULL;
}
/* ************************************************************************ */
// Anonymous submission methods.
/* ************************************************************************ */
/**
* {@inheritdoc}
*/
public function userLogin(UserInterface $account) {
if (empty($_SESSION['webform_submissions'])) {
return;
}
// Move all anonymous submissions to UID of this account.
$query = $this->getQuery();
$query->accessCheck(FALSE);
$query->condition('uid', 0);
$query->condition('sid', $_SESSION['webform_submissions'], 'IN');
$query->sort('sid');
if ($sids = $query->execute()) {
$webform_submissions = $this->loadMultiple($sids);
foreach ($webform_submissions as $sid => $webform_submission) {
$webform = $webform_submission->getWebform();
// Do not convert confidential submissions and check for convert
// anonymous setting.
if ($webform->isConfidential() || empty($webform->getSetting('form_convert_anonymous'))) {
continue;
}
unset($_SESSION['webform_submissions'][$sid]);
$webform_submission->convert($account);
}
}
// Now that the user has logged in because when the log out $_SESSION is
// completely reset.
unset($_SESSION['webform_submissions']);
}
/**
* {@inheritdoc}
*/
public function getAnonymousSubmissionIds(AccountInterface $account) {
// Make sure the account and current user are identical.
if ((int) $account->id() !== (int) $this->currentUser->id()) {
return NULL;
}
if (empty($_SESSION['webform_submissions'])) {
return NULL;
}
// Cleanup sids because drafts could have been purged or the webform
// submission could have been deleted.
$_SESSION['webform_submissions'] = $this->getQuery()
// Disable access check because user having 'sid' in their $_SESSION already
// implies they have access to it.
->accessCheck(FALSE)
->condition('sid', $_SESSION['webform_submissions'], 'IN')
->sort('sid')
->execute();
if (empty($_SESSION['webform_submissions'])) {
unset($_SESSION['webform_submissions']);
return NULL;
}
return $_SESSION['webform_submissions'];
}
/**
* {@inheritdoc}
*/
public function hasAnonymousSubmissionTracking(WebformSubmissionInterface $webform_submission) {
$webform = $webform_submission->getWebform();
if ($webform->isResultsDisabled()) {
return FALSE;
}
if ($this->currentUser->hasPermission('view own webform submission')) {
return TRUE;
}
elseif ($this->accessRulesManager->checkWebformSubmissionAccess('view_own', $this->currentUser, $webform_submission)->isAllowed()) {
return TRUE;
}
elseif ($webform->getSetting('limit_user') || ($webform->getSetting('entity_limit_user') && $webform_submission->getSourceEntity())) {
return TRUE;
}
elseif ($webform->getSetting('form_convert_anonymous')) {
return TRUE;
}
elseif ($webform->getSetting('draft') === WebformInterface::DRAFT_ALL) {
return TRUE;
}
elseif ($webform->hasAnonymousSubmissionTrackingHandler()) {
return TRUE;
}
else {
return FALSE;
}
}
/**
* Track anonymous submissions.
*
* Anonymous submission are tracked so that they can be assigned to the user
* if they login.
*
* We use session for storing draft tokens. So we can only do it for the
* current user.
*
* We do not use PrivateTempStore because it utilizes session ID as the key in
* key-value hash map where it stores its data. During user login the session
* ID is regenerated (see user_login_finalize()) so it is not suitable for us
* since we need to "carry" the draft tokens from anonymous session to the
* logged in one.
*
* @param \Drupal\webform\WebformSubmissionInterface $webform_submission
* A webform submission.
*
* @see WebformSubmissionStorage::loadDraft
* @see WebformSubmissionStorage::userLogin
*/
protected function setAnonymousSubmission(WebformSubmissionInterface $webform_submission) {
// Make sure the account and current user are identical.
if ((int) $webform_submission->getOwnerId() !== (int) $this->currentUser->id()) {
return;
}
// Make sure the submission is anonymous.
if (!$webform_submission->getOwner()->isAnonymous()) {
return;
}
// Check if anonymous users are allowed to save submission using $_SESSION.
if ($this->hasAnonymousSubmissionTracking($webform_submission)) {
$_SESSION['webform_submissions'][$webform_submission->id()] = $webform_submission->id();
}
}
}