entityManager->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) { $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_upates_any')) { unset($definitions['token']); } return $definitions; } /** * {@inheritdoc} */ public function loadDraft(WebformInterface $webform, EntityInterface $source_entity = NULL, AccountInterface $account = NULL) { $query = $this->getQuery(); $query->condition('in_draft', TRUE); $query->condition('webform_id', $webform->id()); $query->condition('uid', $account->id()); if ($source_entity) { $query->condition('entity_type', $source_entity->getEntityTypeId()); $query->condition('entity_id', $source_entity->id()); } else { $query->notExists('entity_type'); $query->notExists('entity_id'); } if ($entity_ids = $query->execute()) { return $this->load(reset($entity_ids)); } else { return NULL; } } /** * {@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} */ public function loadMultiple(array $ids = NULL) { /** @var \Drupal\webform\WebformSubmissionInterface[] $webform_submissions */ $webform_submissions = parent::loadMultiple($ids); $this->loadData($webform_submissions); return $webform_submissions; } /** * {@inheritdoc} */ public function deleteAll(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, $limit = NULL, $max_sid = NULL) { $query = $this->getQuery() ->sort('sid'); 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()); } if ($limit) { $query->range(0, $limit); } if ($max_sid) { $query->condition('sid', $max_sid, '<='); } $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) { $query = $this->getQuery(); $query->condition('in_draft', FALSE); 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()); } if ($account) { $query->condition('uid', $account->id()); } // Issue: Query count method is not working for SQL Lite. // return $query->count()->execute(); // Work-around: Manually count the number of entity ids. return count($query->execute()); } /** * {@inheritdoc} */ public function getMaxSubmissionId(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL) { $query = $this->getQuery(); $query->sort('sid', 'DESC'); 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()); } if ($account) { $query->condition('uid', $account->id()); } $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) ->execute(); return $result->fetchAssoc() ? TRUE : FALSE; } /****************************************************************************/ // Paging methods. /****************************************************************************/ /** * {@inheritdoc} */ public function getFirstSubmission(WebformInterface $webform, EntityInterface $source_entity = NULL, AccountInterface $account = NULL) { return $this->getTerminusSubmission($webform, $source_entity, $account, 'ASC'); } /** * {@inheritdoc} */ public function getLastSubmission(WebformInterface $webform, EntityInterface $source_entity = NULL, AccountInterface $account = NULL) { return $this->getTerminusSubmission($webform, $source_entity, $account, 'DESC'); } /** * {@inheritdoc} */ public function getPreviousSubmission(WebformSubmissionInterface $webform_submission, EntityInterface $source_entity = NULL, AccountInterface $account = NULL) { return $this->getSiblingSubmission($webform_submission, $source_entity, $account, 'previous'); } /** * {@inheritdoc} */ public function getNextSubmission(WebformSubmissionInterface $webform_submission, EntityInterface $source_entity = NULL, AccountInterface $account = NULL) { return $this->getSiblingSubmission($webform_submission, $source_entity, $account, '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 = \Drupal::service('entity_type.repository')->getEntityTypeLabels(); ksort($entity_type_labels); return array_intersect_key($entity_type_labels, array_flip($entity_types)); } /** * {@inheritdoc} */ protected function getTerminusSubmission(WebformInterface $webform, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $sort = 'DESC') { $query = $this->getQuery(); $query->condition('webform_id', $webform->id()); $query->condition('in_draft', FALSE); $query->range(0, 1); if ($source_entity) { $query->condition('entity_type', $source_entity->getEntityTypeId()); $query->condition('entity_id', $source_entity->id()); } if ($account) { $query->condition('uid', $account->id()); } $query->sort('sid', $sort); return ($entity_ids = $query->execute()) ? $this->load(reset($entity_ids)) : NULL; } /** * {@inheritdoc} */ protected function getSiblingSubmission(WebformSubmissionInterface $webform_submission, EntityInterface $entity = NULL, AccountInterface $account = NULL, $direction = 'previous') { $webform = $webform_submission->getWebform(); $query = $this->getQuery(); $query->condition('webform_id', $webform->id()); $query->range(0, 1); if ($entity) { $query->condition('entity_type', $entity->getEntityTypeId()); $query->condition('entity_id', $entity->id()); } if ($account) { $query->condition('uid', $account->id()); } if ($direction == 'previous') { $query->condition('sid', $webform_submission->id(), '<'); $query->sort('sid', 'DESC'); } else { $query->condition('sid', $webform_submission->id(), '>'); $query->sort('sid', 'ASC'); } return ($entity_ids = $query->execute()) ? $this->load(reset($entity_ids)) : NULL; } /****************************************************************************/ // WebformSubmissionEntityList methods. /****************************************************************************/ /** * {@inheritdoc} */ public function getCustomColumns(WebformInterface $webform = NULL, EntityInterface $source_entity = NULL, AccountInterface $account = NULL, $include_elements = TRUE) { // Get custom columns from the webform's state. if ($source_entity) { $source_key = $source_entity->getEntityTypeId() . '.' . $source_entity->id(); $custom_column_names = $webform->getState("results.custom.columns.$source_key", []); // If the source entity does not have custom columns, then see if we // can use the main webform as the default custom columns. if (empty($custom_column_names) && $webform->getState("results.custom.default", FALSE)) { $custom_column_names = $webform->getState('results.custom.columns', []); } } else { $custom_column_names = $webform->getState('results.custom.columns', []); } if (empty($custom_column_names)) { return $this->getDefaultColumns($webform, $source_entity, $account, $include_elements); } // Get custom column with labels. $columns = $this->getColumns($webform, $source_entity, $account, $include_elements); $custom_columns = []; foreach ($custom_column_names as $column_name) { if (isset($columns[$column_name])) { $custom_columns[$column_name] = $columns[$column_name]; } } return $custom_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); // 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 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'), 'default' => FALSE, ]; // UUID. $columns['uuid'] = [ 'title' => $this->t('UUID'), 'default' => FALSE, ]; // Sticky (Starred/Unstarred). if (empty($account)) { $columns['sticky'] = [ 'title' => $this->t('Starred'), ]; // Notes. $columns['notes'] = [ 'title' => $this->t('Notes'), ]; } // Created. $columns['created'] = [ 'title' => $this->t('Created'), ]; // Completed. $columns['completed'] = [ 'title' => $this->t('Completed'), 'default' => FALSE, ]; // Changed. $columns['changed'] = [ 'title' => $this->t('Changed'), 'default' => FALSE, ]; // 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 && \Drupal::moduleHandler()->moduleExists('language')) { $columns['langcode'] = [ 'title' => $this->t('Language'), ]; } // Remote address. $columns['remote_addr'] = [ 'title' => $this->t('IP address'), ]; // Webform. if (empty($webform) && empty($source_entity)) { $columns['webform_id'] = [ 'title' => $this->t('Webform'), ]; } // Webform elements. if ($webform && $include_elements) { /** @var \Drupal\webform\WebformElementManagerInterface $element_manager */ $element_manager = \Drupal::service('plugin.manager.webform.element'); $elements = $webform->getElementsInitializedFlattenedAndHasValue('view'); foreach ($elements as $element) { /** @var \Drupal\webform\WebformElementInterface $element_handler */ $element_handler = $element_manager->createInstance($element['#type']); $columns += $element_handler->getTableColumn($element); } } // Operations. if (empty($account)) { $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 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; } $key = "results.custom.$name"; if (!$source_entity) { return $webform->getState($key, $default); } $source_key = $source_entity->getEntityTypeId() . '.' . $source_entity->id(); if ($webform->hasState("$key.$source_key")) { return $webform->getState("$key.$source_key", $default); } if ($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); } 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} */ 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(); if (!$entity->serial()) { $entity->set('serial', $this->getNextSerial($entity)); } $result = parent::doSave($id, $entity); // Save data. $this->saveData($entity, !$is_new); // DEBUG: dsm($entity->getState()); // Log transaction. $webform = $entity->getWebform(); $context = [ '@id' => $entity->id(), '@form' => $webform->label(), 'link' => $entity->toLink(t('Edit'), 'edit-form')->toString(), ]; switch ($entity->getState()) { case WebformSubmissionInterface::STATE_DRAFT: \Drupal::logger('webform')->notice('@form: Submission #@id draft saved.', $context); break; case WebformSubmissionInterface::STATE_UPDATED: \Drupal::logger('webform')->notice('@form: Submission #@id updated.', $context); break; case WebformSubmissionInterface::STATE_COMPLETED: if ($result === SAVED_NEW) { \Drupal::logger('webform')->notice('@form: Submission #@id created.', $context); } else { \Drupal::logger('webform')->notice('@form: Submission #@id completed.', $context); } break; } return $result; } /** * Returns the next serial number. * * @return int * The next serial number. */ protected function getNextSerial(WebformSubmissionInterface $webform_submission) { $webform = $webform_submission->getWebform(); $next_serial = $webform->getState('next_serial'); $max_serial = $this->getMaxSerial($webform); $serial = max($next_serial, $max_serial); $webform->setState('next_serial', $serial + 1); return $serial; } /** * {@inheritdoc} */ public function getMaxSerial(WebformInterface $webform) { $query = db_select('webform_submission'); $query->condition('webform_id', $webform->id()); $query->addExpression('MAX(serial)'); return $query->execute()->fetchField() + 1; } /** * {@inheritdoc} */ protected function doPostSave(EntityInterface $entity, $update) { /** @var \Drupal\webform\WebformSubmissionInterface $entity */ parent::doPostSave($entity, $update); $this->invokeWebformElements('postSave', $entity, $update); $this->invokeWebformHandlers('postSave', $entity, $update); } /** * {@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); } // Log deleted. foreach ($entities as $entity) { \Drupal::logger('webform') ->notice('Deleted @form: Submission #@id.', [ '@id' => $entity->id(), '@form' => $entity->getWebform()->label(), ]); } return $return; } /****************************************************************************/ // Invoke methods. /****************************************************************************/ /** * {@inheritdoc} */ public function invokeWebformHandlers($method, WebformSubmissionInterface $webform_submission, &$context1 = NULL, &$context2 = NULL) { $webform = $webform_submission->getWebform(); $webform->invokeHandlers($method, $webform_submission, $context1, $context2); } /** * {@inheritdoc} */ public function invokeWebformElements($method, WebformSubmissionInterface $webform_submission, &$context1 = NULL, &$context2 = NULL) { $webform = $webform_submission->getWebform(); $webform->invokeElements($method, $webform_submission, $context1, $context2); } /****************************************************************************/ // Purge methods. /****************************************************************************/ /** * {@inheritdoc} */ public function purge($count) { $days_to_seconds = 60 * 60 * 24; $query = $this->entityManager->getStorage('webform')->getQuery(); $query->condition('settings.purge', [self::PURGE_DRAFT, self::PURGE_COMPLETED, self::PURGE_ALL], 'IN'); $query->condition('settings.purge_days', 0, '>'); $webforms_to_purge = array_values($query->execute()); $webform_submissions_to_purge = []; if (!empty($webforms_to_purge)) { $webforms_to_purge = $this->entityManager->getStorage('webform')->loadMultiple($webforms_to_purge); foreach ($webforms_to_purge as $webform) { $query = $this->getQuery(); $query->condition('created', REQUEST_TIME - ($webform->getSetting('purge_days') * $days_to_seconds), '<'); $query->condition('webform_id', $webform->id()); switch ($webform->getSetting('purge')) { case self::PURGE_DRAFT: $query->condition('in_draft', TRUE); break; case self::PURGE_COMPLETED: $query->condition('in_draft', FALSE); break; } $query->range(0, $count - count($webform_submissions_to_purge)); $result = array_values($query->execute()); if (!empty($result)) { $webform_submissions_to_purge = array_merge($webform_submissions_to_purge, $result); } if (count($webform_submissions_to_purge) == $count) { // We've collected enough webform submissions for purging in this run. break; } } } if (!empty($webform_submissions_to_purge)) { $webform_submissions_to_purge = $this->loadMultiple($webform_submissions_to_purge); $this->delete($webform_submissions_to_purge); } } /****************************************************************************/ // Data handlers. /****************************************************************************/ /** * 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)) { /** @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(); $submissions_data = []; while ($record = $result->fetchAssoc()) { $sid = $record['sid']; $name = $record['name']; $elements = $webform_submissions[$sid]->getWebform()->getElementsInitializedFlattenedAndHasValue(); $element = (isset($elements[$name])) ? $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_submissions[$sid]->setData($submission_data); $webform_submissions[$sid]->setOriginalData($submission_data); } } } /** * Save webform submission data to the 'webform_submission_data' table. * * @param \Drupal\webform\WebformSubmissionInterface $webform_submission * A webform submission. * @param bool $delete_first * TRUE to delete any data first. For new submissions this is not needed. */ protected 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(); $rows = []; foreach ($data as $name => $item) { $element = (isset($elements[$name])) ? $elements[$name] : ['#webform_multiple' => FALSE, '#webform_composite' => FALSE]; 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(); } /** * Delete webform submission data fromthe '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(); } }