Commit 276dc1c8 authored by mferanda's avatar mferanda Committed by jrockowitz

Issue #2888862 by mferanda, jrockowitz: Provide a mechanism to lock a webform submission

parent 15c9942d
langcode: en
status: true
dependencies:
module:
- webform
id: webform_submission_make_lock_action
label: 'Lock submission'
type: webform_submission
plugin: webform_submission_make_lock_action
configuration: { }
langcode: en
status: true
dependencies:
module:
- webform
id: webform_submission_make_unlock_action
label: 'Unlock submission'
type: webform_submission
plugin: webform_submission_make_unlock_action
configuration: { }
......@@ -37,6 +37,7 @@ settings:
default_submission_label: '[webform_submission:submitted-to]: Submission #[webform_submission:serial]'
default_submission_login_message: 'Please login to access this submission.'
default_submission_exception_message: 'Unable to process this submission. Please contact the site administrator.'
default_submission_locked_message: 'This submission has been locked.'
default_submission_log: false
preview_classes: |
messages messages--error
......
......@@ -9,3 +9,11 @@ action.configuration.webform_submission_make_sticky_action:
action.configuration.webform_submission_make_unsticky_action:
type: action_configuration_default
label: 'Unstar/Unflag selected submission configuration'
action.configuration.webform_submission_make_lock_action:
type: action_configuration_default
label: 'Lock selected submission configuration'
action.configuration.webform_submission_make_unlock_action:
type: action_configuration_default
label: 'Unlock selected submission configuration'
......@@ -131,6 +131,9 @@ webform.webform.*:
submission_exception_message:
type: text
label: 'Submission exception message'
submission_locked_message:
type: text
label: 'Submission locked message'
submission_log:
type: boolean
label: 'Submission logging'
......
......@@ -18,6 +18,9 @@ webform.handler.action:
sticky:
label: 'Flag'
type: boolean
locked:
label: 'Locked'
type: boolean
data:
label: 'Data'
type: text
......
......@@ -108,6 +108,9 @@ webform.settings:
default_submission_exception_message:
type: text
label: 'Default submission exception message'
default_submission_locked_message:
type: text
label: 'Default submission locked message'
form_classes:
type: string
label: 'Form CSS classes '
......
......@@ -91,7 +91,7 @@ a.webform-results__custom + .ajax-progress-throbber {
}
/**
* Results icons (sticky & notes)
* Results icons (notes, sticky, and locked)
*/
.webform-icon {
display: inline-block;
......@@ -105,6 +105,8 @@ a.webform-results__custom + .ajax-progress-throbber {
vertical-align: -2px;
}
/* Notes */
.webform-icon-notes--on {
background-image: url(../images/icons/notes-on.svg);
}
......@@ -124,6 +126,8 @@ a:focus .webform-icon-notes--off {
background-image: url(../images/icons/notes-link.svg);
}
/* Sticky */
.webform-icon-sticky {
background: transparent url(../images/icons/sticky.svg) no-repeat left top;
display: inline-block;
......@@ -146,6 +150,30 @@ a:focus .webform-icon-notes--off {
background: transparent url(../images/icons/sticky-link.svg) no-repeat left top;
}
/* Locked */
.webform-icon-locked--on {
background-image: url(../images/icons/locked-on.svg);
}
.webform-icon-locked--off {
background-image: url(../images/icons/locked-off.svg);
}
.webform-icon-locked--link {
background-image: url(../images/icons/locked-on-link.svg);
}
a:hover .webform-icon-locked--on,
a:focus .webform-icon-locked--on {
background-image: url(../images/icons/locked-off-link.svg);
}
a:hover .webform-icon-locked--off,
a:focus .webform-icon-locked--off {
background-image: url(../images/icons/locked-on-link.svg);
}
/**
* Submission view table.
* @see /admin/structure/webform/manage/{webform_id}/submission/{webform_submission_id}/table
......
<?xml version="1.0" encoding="utf-8"?>
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="#0074bd" d="M1376 768q40 0 68 28t28 68v576q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-576q0-40 28-68t68-28h32v-320q0-185 131.5-316.5t316.5-131.5 316.5 131.5 131.5 316.5q0 26-19 45t-45 19h-64q-26 0-45-19t-19-45q0-106-75-181t-181-75-181 75-75 181v320h736z"/></svg>
<?xml version="1.0" encoding="utf-8"?>
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="#bebebe" d="M1376 768q40 0 68 28t28 68v576q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-576q0-40 28-68t68-28h32v-320q0-185 131.5-316.5t316.5-131.5 316.5 131.5 131.5 316.5q0 26-19 45t-45 19h-64q-26 0-45-19t-19-45q0-106-75-181t-181-75-181 75-75 181v320h736z"/></svg>
<?xml version="1.0" encoding="utf-8"?>
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="#0074bd" d="M640 768h512v-192q0-106-75-181t-181-75-181 75-75 181v192zm832 96v576q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-576q0-40 28-68t68-28h32v-192q0-184 132-316t316-132 316 132 132 316v192h32q40 0 68 28t28 68z"/></svg>
<?xml version="1.0" encoding="utf-8"?>
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path fill="#787878" d="M640 768h512v-192q0-106-75-181t-181-75-181 75-75 181v192zm832 96v576q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-576q0-40 28-68t68-28h32v-192q0-184 132-316t316-132 316 132 132 316v192h32q40 0 68 28t28 68z"/></svg>
......@@ -6,11 +6,13 @@
*/
use Drupal\Core\Cache\Cache;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Database\Database;
use Drupal\Core\Serialization\Yaml;
use Drupal\Core\Render\Element;
use Drupal\webform\Entity\Webform;
use Drupal\webform\Entity\WebformOptions;
use Drupal\webform\Plugin\WebformHandler\ActionWebformHandler;
use Drupal\webform\Plugin\WebformHandler\EmailWebformHandler;
use Drupal\webform\WebformInterface;
use Drupal\webform\Plugin\WebformHandler\RemotePostWebformHandler;
......@@ -1839,3 +1841,67 @@ function webform_update_8098() {
}
}
}
/**
* Issue #2888862: Provide a mechanism to lock a webform submission.
*/
function webform_update_8099() {
// Copied from: node_update_8001()
//
// Install the definition that this field had in
// \Drupal\webform\Entity\WebformSubmission::baseFieldDefinitions()
// at the time that this update function was written. If/when code is
// deployed that changes that definition, the corresponding module must
// implement an update function that invokes
// \Drupal::entityDefinitionUpdateManager()->updateFieldStorageDefinition()
// with the new definition.
$storage_definition = BaseFieldDefinition::create('boolean')
->setLabel(t('Locked'))
->setDescription(t('A flag that indicates a locked webform submission.'))
->setDefaultValue(FALSE);
\Drupal::entityDefinitionUpdateManager()
->installFieldStorageDefinition('locked', 'webform_submission', 'webform', $storage_definition);
// Set default value.
\Drupal::database()->update('webform_submission')
->fields(['locked' => 0])
->execute();
// Add submission locked message to admin and webform settings.
_webform_update_admin_settings();
_webform_update_webform_settings();
}
/**
* Issue #2888862: Provide a mechanism to lock a webform submission. Update handlers.
*/
function webform_update_8100() {
// Add locked to action handler.
_webform_update_webform_handler_configuration(ActionWebformHandler::class);
// Add locked to remote post handler's excluded data.
/** @var \Drupal\webform\WebformInterface[] $webforms */
$webforms = Webform::loadMultiple();
foreach ($webforms as $webform) {
$has_handler = FALSE;
$handlers = $webform->getHandlers();
foreach ($handlers as $handler) {
if ($handler instanceof RemotePostWebformHandler) {
$has_handler = TRUE;
$configuration = $handler->getConfiguration();
$settings = $configuration['settings'];
if ($settings['excluded_data']) {
$settings['excluded_data']['locked'] = 'locked';
$configuration['settings'] = $settings;
$handler->setConfiguration($configuration);
}
}
}
if ($has_handler) {
$webform->save();
}
}
}
......@@ -307,6 +307,7 @@ function template_preprocess_webform_submission_information(array &$variables) {
$variables['completed'] = WebformDateHelper::format($webform_submission->getCompletedTime());
$variables['changed'] = WebformDateHelper::format($webform_submission->getChangedTime());
$variables['sticky'] = $webform_submission->isSticky() ? t('Yes') : '';
$variables['locked'] = $webform_submission->isLocked() ? t('Yes') : '';
$variables['notes'] = $webform_submission->getNotes();
// @see \Drupal\Core\Field\Plugin\Field\FieldFormatter\LanguageFormatter::viewValue()
......
......@@ -5,6 +5,7 @@ namespace Drupal\webform\Controller;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Controller\ControllerBase;
use Drupal\webform\WebformSubmissionInterface;
......@@ -37,4 +38,29 @@ class WebformSubmissionController extends ControllerBase {
return $response;
}
/**
* Toggle webform submission locked.
*
* @param \Drupal\webform\WebformSubmissionInterface $webform_submission
* A webform submission.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* An Ajax response that toggle the lock icon.
*/
public function locked(WebformSubmissionInterface $webform_submission) {
// Toggle locked.
$webform_submission->setLocked(!$webform_submission->isLocked())->save();
// Get state.
$state = $webform_submission->isLocked() ? 'on' : 'off';
// Get selector.
$selector = '#webform-submission-' . $webform_submission->id() . '-locked';
$response = new AjaxResponse();
$response->addCommand(new HtmlCommand($selector, new FormattableMarkup('<span class="webform-icon webform-icon-lock webform-icon-locked--@state"></span>', ['@state' => $state])));
$response->addCommand(new InvokeCommand($selector, 'trigger', ['blur']));
return $response;
}
}
......@@ -826,6 +826,7 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface {
'submission_login' => FALSE,
'submission_login_message' => '',
'submission_exception_message' => '',
'submission_locked_message' => '',
'wizard_progress_bar' => TRUE,
'wizard_progress_pages' => FALSE,
'wizard_progress_percentage' => FALSE,
......@@ -970,7 +971,7 @@ class Webform extends ConfigEntityBundleBase implements WebformInterface {
if (empty($webform_submission)
&& $operation === 'view_own'
&& $this->checkAccessRule($access_rules[$operation], $account)) {
return TRUE;
return TRUE;
}
// If webform submission is set then check the webform submission owner.
......
......@@ -98,7 +98,7 @@ class WebformSubmission extends ContentEntityBase implements WebformSubmissionIn
protected $originalData = [];
/**
* Flag to indicated is submission is being converted from anonymous to authenticated.
* Flag to indicated if submission is being converted from anonymous to authenticated.
*
* @var bool
*/
......@@ -192,6 +192,11 @@ class WebformSubmission extends ContentEntityBase implements WebformSubmissionIn
->setDescription(t('The ID of the entity of which this webform submission was submitted from.'))
->setSetting('max_length', 255);
$fields['locked'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Locked'))
->setDescription(t('A flag that indicates a locked webform submission.'))
->setDefaultValue(FALSE);
$fields['sticky'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Sticky'))
->setDescription(t('A flag that indicate the status of the webform submission.'))
......@@ -299,6 +304,14 @@ class WebformSubmission extends ContentEntityBase implements WebformSubmissionIn
return $this;
}
/**
* {@inheritdoc}
*/
public function setLocked($locked) {
$this->set('locked', $locked);
return $this;
}
/**
* {@inheritdoc}
*/
......@@ -516,6 +529,13 @@ class WebformSubmission extends ContentEntityBase implements WebformSubmissionIn
return $this->get('completed')->value ? TRUE : FALSE;
}
/**
* {@inheritdoc}
*/
public function isLocked() {
return $this->get('locked')->value ? TRUE: FALSE;
}
/**
* {@inheritdoc}
*/
......@@ -543,6 +563,9 @@ class WebformSubmission extends ContentEntityBase implements WebformSubmissionIn
elseif ($this->isDraft()) {
return self::STATE_DRAFT;
}
elseif ($this->isLocked()) {
return self::STATE_LOCKED;
}
elseif ($this->completed->value == $this->changed->value) {
return self::STATE_COMPLETED;
}
......@@ -579,9 +602,10 @@ class WebformSubmission extends ContentEntityBase implements WebformSubmissionIn
$duplicate->set('changed', NULL);
$duplicate->set('completed', NULL);
// Clear admin notes and sticky.
// Clear admin notes, sticky, and locked.
$duplicate->set('notes', '');
$duplicate->set('sticky', FALSE);
$duplicate->set('locked', FALSE);
return $duplicate;
}
......
......@@ -94,6 +94,12 @@ class WebformEntitySettingsSubmissionsForm extends WebformEntitySettingsBaseForm
'#description' => $this->t('A message to be displayed if submission handling breaks.'),
'#default_value' => $settings['submission_exception_message'],
];
$form['submission_settings']['submission_locked_message'] = [
'#type' => 'webform_html_editor',
'#title' => $this->t('Submission locked message'),
'#description' => $this->t('A message to be displayed if submission is lockec.'),
'#default_value' => $settings['submission_locked_message'],
];
$form['submission_settings']['next_serial'] = [
'#type' => 'number',
'#title' => $this->t('Next submission number'),
......@@ -111,7 +117,7 @@ class WebformEntitySettingsSubmissionsForm extends WebformEntitySettingsBaseForm
// @see \Drupal\webform\Form\WebformResultsCustomForm::buildForm
$available_columns = $webform_submission_storage->getColumns($webform);
// Remove columns that should never be displayed to users.
$available_columns = array_diff_key($available_columns, array_flip(['uuid', 'in_draft', 'entity', 'sticky', 'notes', 'uid', 'operations']));
$available_columns = array_diff_key($available_columns, array_flip(['uuid', 'in_draft', 'entity', 'sticky', 'locked', 'notes', 'uid', 'operations']));
$custom_columns = $webform_submission_storage->getUserColumns($webform);
// Change sid's # to an actual label.
$available_columns['sid']['title'] = $this->t('Submission ID');
......@@ -139,7 +145,6 @@ class WebformEntitySettingsSubmissionsForm extends WebformEntitySettingsBaseForm
'#default_value' => $columns_default_value,
];
// Submission access denied.
$form['submission_access_denied'] = [
'#type' => 'details',
......
......@@ -75,6 +75,12 @@ class WebformAdminConfigSubmissionsForm extends WebformAdminConfigBaseForm {
'#required' => TRUE,
'#default_value' => $settings['default_submission_exception_message'],
];
$form['submission_settings']['default_submission_locked_message'] = [
'#type' => 'webform_html_editor',
'#title' => $this->t('Default locked message'),
'#required' => TRUE,
'#default_value' => $settings['default_submission_locked_message'],
];
$form['submission_settings']['default_submission_label'] = [
'#type' => 'textfield',
'#title' => $this->t('Default submission label'),
......
<?php
namespace Drupal\webform\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Locks a webform submission.
*
* @Action(
* id = "webform_submission_make_lock_action",
* label = @Translation("Lock selected submission"),
* type = "webform_submission"
* )
*/
class LockWebformSubmission extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
/** @var \Drupal\webform\WebformSubmissionInterface $entity */
$entity->setLocked(TRUE)->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\webform\WebformSubmissionInterface $object */
$result = $object->locked->access('edit', $account, TRUE)
->andIf($object->access('update', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
}
<?php
namespace Drupal\webform\Plugin\Action;
use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
/**
* Unlocks a webform submission.
*
* @Action(
* id = "webform_submission_make_unlock_action",
* label = @Translation("Unlock selected submission"),
* type = "webform_submission"
* )
*/
class UnlockWebformSubmission extends ActionBase {
/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
/** @var \Drupal\webform\WebformSubmissionInterface $entity */
$entity->setLocked(FALSE)->save();
}
/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
/** @var \Drupal\webform\WebformSubmissionInterface $object */
$result = $object->locked->access('edit', $account, TRUE)
->andIf($object->access('update', $account, TRUE));
return $return_as_object ? $result : $result->isAllowed();
}
}
......@@ -73,6 +73,7 @@ class ActionWebformHandler extends WebformHandlerBase {
WebformSubmissionInterface::STATE_CONVERTED => $this->t('Converted'),
WebformSubmissionInterface::STATE_COMPLETED => $this->t('Completed'),
WebformSubmissionInterface::STATE_UPDATED => $this->t('Updated'),
WebformSubmissionInterface::STATE_LOCKED => $this->t('Locked'),
];
$this->configuration['states'] = array_intersect_key($states, array_combine($this->configuration['states'], $this->configuration['states']));
......@@ -103,6 +104,7 @@ class ActionWebformHandler extends WebformHandlerBase {
'states' => [WebformSubmissionInterface::STATE_COMPLETED],
'notes' => '',
'sticky' => NULL,
'locked' => NULL,
'data' => '',
'message' => '',
'message_type' => 'status',
......@@ -148,6 +150,17 @@ class ActionWebformHandler extends WebformHandlerBase {
],
'#default_value' => ($this->configuration['sticky'] === NULL) ? '' : ($this->configuration['sticky'] ? '1' : '0'),
];
$form['actions']['locked'] = [
'#type' => 'select',
'#title' => $this->t('Change lock'),
'#description' => $this->t('Webform submissions can only be unlocked programatically.'),
'#options' => [
'' => '',
'1' => $this->t('Lock'),
'0' => $this->t('Unlock'),
],
'#default_value' => ($this->configuration['locked'] === NULL) ? '' : ($this->configuration['locked'] ? '1' : '0'),
];
$form['actions']['notes'] = [
'#type' => 'webform_codemirror',
'#mode' => 'text',
......@@ -243,6 +256,9 @@ class ActionWebformHandler extends WebformHandlerBase {
// Cleanup sticky.
$this->configuration['sticky'] = ($this->configuration['sticky'] === '') ? NULL : (bool) $this->configuration['sticky'];
// Cleanup locked.
$this->configuration['locked'] = ($this->configuration['locked'] === '') ? NULL : (bool) $this->configuration['locked'];
// Cast debug.
$this->configuration['debug'] = (bool) $this->configuration['debug'];
}
......@@ -273,6 +289,11 @@ class ActionWebformHandler extends WebformHandlerBase {
$webform_submission->setSticky($this->configuration['sticky']);
}
// Set locked.
if ($this->configuration['locked'] !== NULL) {
$webform_submission->setLocked($this->configuration['locked']);
}
// Append notes.
if ($this->configuration['notes']) {
$notes = rtrim($webform_submission->getNotes());
......@@ -295,7 +316,7 @@ class ActionWebformHandler extends WebformHandlerBase {
$this->tokenManager->replace($this->configuration['message'], $webform_submission)
);
$message_type = $this->configuration['message_type'];
drupal_set_message(\Drupal::service('renderer')->render($message), $message_type);
drupal_set_message(\Drupal::service('renderer')->renderPlain($message), $message_type);
}
// Resave the webform submission without trigger any hooks or handlers.
......@@ -337,6 +358,13 @@ class ActionWebformHandler extends WebformHandlerBase {
'#wrapper_attributes' => ['class' => ['container-inline'], 'style' => 'margin: 0'],
];
$build['locked'] = [
'#type' => 'item',
'#title' => $this->t('Lock'),
'#markup' => ($this->configuration['locked'] === NULL) ? '' : ($this->configuration['locked'] ? $this->t('Locked') : $this->t('Unlocked')),
'#wrapper_attributes' => ['class' => ['container-inline'], 'style' => 'margin: 0'],
];
$build['notes'] = [
'#type' => 'item',
'#title' => $this->t('Notes'),
......
......@@ -33,7 +33,7 @@ class WebformExporterOptionsTest extends WebformTestBase {
// Set default edit export settings.
$edit = [
// Exclude all columns except sid.
'excluded_columns' => 'serial,uuid,token,uri,created,completed,changed,in_draft,current_page,remote_addr,uid,langcode,webform_id,entity_type,entity_id,sticky,notes',
'excluded_columns' => 'serial,uuid,token,uri,created,completed,changed,in_draft,current_page,remote_addr,uid,langcode,webform_id,entity_type,entity_id,sticky,locked,notes',
];
// Check default options.
......
......@@ -4,6 +4,7 @@ namespace Drupal\webform\Tests\Handler;
use Drupal\webform\Entity\Webform;
use Drupal\webform\Entity\WebformSubmission;
use Drupal\webform\WebformSubmissionInterface;
use Drupal\webform\Tests\WebformTestBase;
/**
......@@ -37,6 +38,9 @@ class WebformHandlerActionTest extends WebformTestBase {
// Check that submission is not flagged (sticky).
$this->assertFalse($webform_submission->isSticky());
// Check that submission is unlocked.
$this->assertFalse($webform_submission->isLocked());
// Check that submission notes is empty.
$this->assertTrue(empty($webform_submission->getNotes()));
......@@ -76,6 +80,7 @@ class WebformHandlerActionTest extends WebformTestBase {
// Check messages.
$this->assertRaw('Submission has been unflagged.');
//$this->assertRaw('Submission has been unlocked.');
$this->assertRaw('Submission notes have been updated.');
// Reload the webform submission.
......@@ -93,6 +98,34 @@ class WebformHandlerActionTest extends WebformTestBase {
// Check that notes_last is updated with second note.
$this->assertEqual($webform_submission->getElementData('notes_last'), 'This is the second note');
// Lock submission.
$edit = [
'lock' => 'locked',
];
$this->drupalPostForm("admin/structure/webform/manage/test_handler_action/submission/$sid/edit", $edit, t('Save'));
// Check locked message.
$this->assertRaw('Submission has been locked.');
// Reload the webform submission.
\Drupal::entityTypeManager()->getStorage('webform_submission')->resetCache();
$webform_submission = WebformSubmission::load($sid);
// Check that submission is locked.
$this->assertTrue($webform_submission->isLocked());
$this->assertEqual(WebformSubmissionInterface::STATE_LOCKED, $webform_submission->getState());