Commit ffc8a026 authored by jrockowitz's avatar jrockowitz Committed by jrockowitz

Issue #2898424 by jrockowitz: Improve Remote Post

parent 69958cbd
......@@ -10,5 +10,6 @@ webform.features.yml
modules/webform_examples/webform_examples.features.yml
modules/webform_example_composite/webform_example_composite.features.yml
modules/webform_example_element/webform_example_element.features.yml
modules/webform_example_remote_post/webform_example_remote_post.features.yml
modules/webform_templates/webform_templates.features.yml
modules/webform_node/webform_node.features.yml
......@@ -898,15 +898,6 @@ webform.handler.remote_post:
type:
label: 'Type'
type: string
insert_url:
label: 'Insert URL'
type: uri
update_url:
label: 'Update URL'
type: uri
delete_url:
label: 'Delete URL'
type: uri
excluded_data:
type: sequence
label: 'Excluded data'
......@@ -916,18 +907,42 @@ webform.handler.remote_post:
custom_data:
label: 'Custom data'
type: string
insert_custom_data:
label: 'Insert custom data'
type: string
update_custom_data:
label: 'Update custom data'
type: string
delete_custom_data:
label: 'Delete custom data'
custom_options:
label: 'Custom options'
type: string
debug:
type: boolean
label: 'Enable debugging'
completed_url:
label: 'Completed URL'
type: uri
completed_custom_data:
label: 'Completed custom data'
type: string
updated_url:
label: 'Updated URL'
type: uri
updated_custom_data:
label: 'Updated custom data'
type: string
deleted_url:
label: 'Deleted URL'
type: uri
deleted_custom_data:
label: 'Deleted custom data'
type: string
draft_url:
label: 'Draft URL'
type: uri
draft_custom_data:
label: 'Draft custom data'
type: string
converted_url:
label: 'Converted URL'
type: uri
converted_custom_data:
label: 'Converted custom data'
type: string
webform.exporter.*:
type: mapping
......
......@@ -56,6 +56,7 @@ echo 'true' > webform.features.yml
echo 'true' > modules/webform_examples/webform_examples.features.yml
echo 'true' > modules/webform_example_element/webform_example_element.features.yml
echo 'true' > modules/webform_example_composite/webform_example_composite.features.yml
echo 'true' > modules/webform_example_element/webform_example_remote_post.features.yml
echo 'true' > modules/webform_templates/webform_templates.features.yml
echo 'true' > modules/webform_node/webform_node.features.yml
......@@ -66,10 +67,12 @@ drush en -y webform\
webform_examples\
webform_examples\
webform_example_element\
webform_example_remote_post\
webform_templates\
webform_test\
webform_test_element\
webform_test_handler\
webform_test_handler_remote_post\
webform_test_options\
webform_test_views\
webform_test_translation\
......@@ -87,10 +90,12 @@ drush features-export -y webform_demo_event_registration
drush features-export -y webform_examples
drush features-export -y webform_example_element
drush features-export -y webform_example_composite
drush features-export -y webform_example_remote_post
drush features-export -y webform_templates
drush features-export -y webform_testdrush cr
drush features-export -y webform_test_element
drush features-export -y webform_test_handler
drush features-export -y webform_test_handler_remote_post
drush features-export -y webform_test_options
drush features-export -y webform_test_views
drush features-export -y webform_test_translation
......@@ -109,10 +114,12 @@ drush webform-tidy -y --dependencies webform_demo_event_registration
drush webform-tidy -y --dependencies webform_examples
drush webform-tidy -y --dependencies webform_example_element
drush webform-tidy -y --dependencies webform_example_composite
drush webform-tidy -y --dependencies webform_example_remote_post
drush webform-tidy -y --dependencies webform_templates
drush webform-tidy -y --dependencies webform_test
drush webform-tidy -y --dependencies webform_test_element
drush webform-tidy -y --dependencies webform_test_handler
drush webform-tidy -y --dependencies webform_test_handler_remote_post
drush webform-tidy -y --dependencies webform_test_options
drush webform-tidy -y --dependencies webform_test_views
drush webform-tidy -y --dependencies webform_test_translation
......@@ -127,10 +134,12 @@ drush features-import -y webform_demo_application_evaluation
drush features-import -y webform_examples
drush features-import -y webform_example_element
drush features-import -y webform_example_composite
drush features-import -y webform_example_remote_post
drush features-import -y webform_templates
drush features-import -y webform_test
drush features-import -y webform_test_element
drush features-import -y webform_test_handler
drush features-import -y webform_test_handler_remote_post
drush features-import -y webform_test_options
drush features-import -y webform_test_views
drush features-import -y webform_test_translation
......
......@@ -3,41 +3,40 @@ status: open
dependencies:
enforced:
module:
- webform_test_handler
- webform_example_remote_post
open: null
close: null
uid: null
template: false
id: test_handler_remote_post
title: 'Test: Handler: Remote post'
description: 'Test remote post handler.'
category: 'Test: Handler'
id: example_remote_post
title: 'Example: Remote post'
description: 'An example of a webform submission posted to a remote server.'
category: Example
elements: |
first_name:
'#title': 'First name'
'#type': textfield
'#required': true
'#default_value': John
last_name:
'#title': 'Last name'
'#type': textfield
'#required': true
'#default_value': Smith
email:
'#title': Email
'#type': email
'#required': true
'#default_value': from@example.com
subject:
'#title': Subject
'#type': textfield
'#required': true
'#default_value': '{subject}'
message:
'#title': Message
'#type': textarea
'#required': true
'#default_value': '{message}'
confirmation_number:
'#title': 'Confirmation number'
'#type': value
'#value': '[webform_handler:remote_post:completed:confirmation_number]'
css: ''
javascript: ''
settings:
......@@ -85,9 +84,10 @@ settings:
draft_auto_save: false
draft_saved_message: ''
draft_loaded_message: ''
confirmation_type: page
confirmation_type: inline
confirmation_title: ''
confirmation_message: ''
confirmation_message: |
<p>Your confirmation number is [webform-submission:values:confirmation_number].</p>
confirmation_url: ''
confirmation_attributes: { }
confirmation_back: true
......@@ -147,9 +147,6 @@ handlers:
status: true
weight: 1
settings:
insert_url: 'http://default/webform_test/remote_post/insert'
update_url: 'http://default/webform_test/remote_post/update'
delete_url: 'http://default/webform_test/remote_post/delete'
type: x-www-form-urlencoded
excluded_data:
serial: serial
......@@ -170,13 +167,21 @@ handlers:
entity_id: entity_id
sticky: sticky
notes: notes
confirmation_number: confirmation_number
custom_data: |
custom_all: true
custom_title: '[webform:title]: Submission #[webform_submission:serial]'
insert_custom_data: |
custom_insert: true
update_custom_data: |
custom_update: true
delete_custom_data: |
custom_delete: true
custom_options: ''
debug: true
completed_url: '/webform_example_remote_post/completed'
completed_custom_data: |
custom_completed: true
updated_url: '/webform_example_remote_post/updated'
updated_custom_data: |
custom_updated: true
deleted_url: '/webform_example_remote_post/deleted'
deleted_custom_data: |
custom_deleted: true
draft_url: ''
draft_custom_data: ''
converted_url: ''
converted_custom_data: ''
<?php
namespace Drupal\webform_test_handler\Controller;
namespace Drupal\webform_example_remote_post\Controller;
use Drupal\Component\Utility\Random;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides route responses for remote post tests.
* Provides route responses for example remote post.
*/
class WebformTestRemotePostController extends ControllerBase {
class WebformExampleRemotePostController extends ControllerBase {
/**
* Returns a webform confirmation page.
* Returns a remote post response.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
* @param string $type
* Type of remote post request (insert, update, or delete)
* Type of remote post request (completed, updated, or deleted)
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* A JSON response with request status and message.
*/
public function index(Request $request, $type) {
$post_data = $request->request->all();
if (strpos(print_r($post_data, TRUE), 'FAIL') !== FALSE) {
$json = [
'status' => 'fail',
'message' => (string) $this->t('Failed to process @type request.', ['@type' => $type]),
];
return new JsonResponse($json, 500);
}
else {
$json = [
'status' => 'success',
'message' => (string) $this->t('Processed @type request.', ['@type' => $type]),
];
return new JsonResponse($json, 200);
}
$random = new Random();
$json = [
'status' => 'success',
'message' => (string) $this->t('Processed @type request.', ['@type' => $type]),
'confirmation_number' => $random->name(20, TRUE),
];
return new JsonResponse($json, 200);
}
}
name: 'Webform Remote Post Example'
type: module
description: 'Provides an example of a webform submission posted to a remote server.'
package: 'Webform example'
core: 8.x
dependencies:
- 'drupal:webform'
<?php
/**
* @file
* Provides an example of a webform submission posted to a remote server.
*/
use Drupal\Core\Url;
/**
* Implements hook_webform_load().
*
* Using hook_webform_load() instead of hook_install() to make sure the
* remote post URLs are set to the correct base URL and path.
*/
function webform_example_remote_post_webform_load(array $entities) {
if (isset($entities['example_remote_post']) && PHP_SAPI !== 'cli') {
// Reset remote post URL to the current base URL and base path.
/** @var \Drupal\webform\WebformInterface $webform */
$webform = $entities['example_remote_post'];
/** @var \Drupal\webform\Plugin\WebformHandler\RemotePostWebformHandler $handler */
$handler = $webform->getHandler('remote_post');
$configuration = $handler->getConfiguration();
$states = ['completed', 'updated', 'deleted'];
foreach ($states as $state) {
// The 'webform_example_remote_post.remote_post' will not be available
// during installation so wrap Url::fromRoute() in try/catch.
try {
if ($configuration['settings'][$state . '_url'] === "/webform_example_remote_post/$state") {
$configuration['settings'][$state . '_url'] = Url::fromRoute('webform_example_remote_post.remote_post', ['type' => $state], ['absolute' => TRUE])->toString();
}
}
catch (\Exception $exception) {
return;
}
}
$handler->setConfiguration($configuration);
}
}
webform_example_remote_post.remote_post:
path: '/webform_example_remote_post/{type}'
defaults:
_controller: '\Drupal\webform_example_remote_post\Controller\WebformExampleRemotePostController::index'
requirements:
# Example remote post handler does not require any access controls.
_access: 'TRUE'
......@@ -3,6 +3,7 @@
namespace Drupal\webform\Tests\Handler;
use Drupal\webform\Entity\Webform;
use Drupal\webform\Entity\WebformSubmission;
use Drupal\webform\Tests\WebformTestBase;
/**
......@@ -17,7 +18,7 @@ class WebformHandlerRemotePostTest extends WebformTestBase {
*
* @var array
*/
public static $modules = ['webform', 'webform_test_handler'];
public static $modules = ['webform', 'webform_test_handler_remote_post'];
/**
* Webforms to load.
......@@ -30,73 +31,109 @@ class WebformHandlerRemotePostTest extends WebformTestBase {
* Test remote post handler.
*/
public function testRemotePostHandler() {
/** @var \Drupal\webform\WebformInterface $webform_handler_remote */
$webform_handler_remote = Webform::load('test_handler_remote_post');
$this->drupalLogin($this->rootUser);
// Check remote post 'create' operation.
$sid = $this->postSubmission($webform_handler_remote);
$this->assertPattern('#<label>Remote operation</label>\s+insert#ms');
$this->assertRaw('custom_insert: true');
$this->assertRaw('custom_all: true');
$this->assertRaw("custom_title: 'Test: Handler: Remote post: Submission #$sid'");
$this->assertRaw('first_name: John');
$this->assertRaw('last_name: Smith');
$this->assertRaw('email: from@example.com');
$this->assertRaw("subject: '{subject}'");
$this->assertRaw("message: '{message}'");
$this->assertNoRaw("sid: '$sid'");
/** @var \Drupal\webform\WebformInterface $webform */
$webform = Webform::load('test_handler_remote_post');
// Check 'completed' operation.
$sid = $this->postSubmission($webform);
$webform_submission = WebformSubmission::load($sid);
$this->assertRaw("form_params:
custom_completed: true
custom_data: true
response_type: '200'
first_name: John
last_name: Smith");
$this->assertRaw('Processed completed request.');
// Check confirmation number is set via the
// [webform_handler:remote_post:completed:confirmation_number] token.
$this->assertRaw('Your confirmation number is ' . $webform_submission->getData('confirmation_number') . '.');
// Check custom header.
$this->assertRaw('{&quot;custom_header&quot;:&quot;true&quot;}');
// Check remote post 'update' operation.
// Sleep for 1 second to make sure submission timestamp is updated.
sleep(1);
// Check 'updated' operation.
$this->drupalPostForm("admin/structure/webform/manage/test_handler_remote_post/submission/$sid/edit", [], t('Save'));
$this->assertRaw('custom_update: true');
$this->assertRaw('custom_all: true');
$this->assertRaw("custom_title: 'Test: Handler: Remote post: Submission #$sid'");
$this->assertRaw('first_name: John');
$this->assertPattern('#<label>Remote operation</label>\s+update#ms');
$this->assertRaw("form_params:
custom_updated: true
custom_data: true
response_type: '200'
first_name: John
last_name: Smith");
$this->assertRaw('Processed updated request.');
// Check remote post 'delete' operation.
// Check 'deleted`' operation.
$this->drupalPostForm("admin/structure/webform/manage/test_handler_remote_post/submission/$sid/delete", [], t('Delete'));
$this->assertRaw('custom_delete: true');
$this->assertRaw('custom_all: true');
$this->assertRaw("custom_title: 'Test: Handler: Remote post: Submission #$sid'");
$this->assertRaw('first_name: John');
$this->assertPattern('#<label>Remote operation</label>\s+delete#ms');
$this->assertRaw("form_params:
custom_deleted: true
custom_data: true
first_name: John
last_name: Smith
response_type: '200'");
$this->assertRaw('Processed deleted request.');
// Switch anonymous user.
$this->drupalLogout();
// Check including data.
$handler = $webform_handler_remote->getHandler('remote_post');
// Check 'draft' operation.
$this->postSubmission($webform, [], t('Save Draft'));
$this->assertRaw("form_params:
custom_draft: true
custom_data: true
response_type: '200'
first_name: John
last_name: Smith");
$this->assertRaw('Processed draft request.');
// Login root user.
$this->drupalLogin($this->rootUser);
// Check 'convert' operation.
$this->assertRaw("form_params:
custom_converted: true
custom_data: true
first_name: John
last_name: Smith
response_type: '200'");
$this->assertRaw('Processed converted request.');
// Check excluded data.
$handler = $webform->getHandler('remote_post');
$configuration = $handler->getConfiguration();
$configuration['settings']['excluded_data'] = [
'subject' => 'subject',
'message' => 'message',
'last_name' => 'last_name',
];
$handler->setConfiguration($configuration);
$webform_handler_remote->save();
$sid = $this->postSubmission($webform_handler_remote);
$webform->save();
$sid = $this->postSubmission($webform);
$this->assertRaw('first_name: John');
$this->assertRaw('last_name: Smith');
$this->assertRaw('email: from@example.com');
$this->assertNoRaw("subject: '{subject}'");
$this->assertNoRaw("message: '{message}'");
$this->assertNoRaw('last_name: Smith');
$this->assertRaw("sid: '$sid'");
// @todo Figure out why the below test is failing on Drupal.org.
// Check remote post 'create' 500 error handling.
// $this->postSubmission($webform_handler_remote, ['first_name' => 'FAIL']);
// $this->assertPattern('#<label>Response status code</label>\s+500#ms');
// @todo Figure out why the below test is failing on Drupal.org.
// Update the remote post handlers insert url to return a 404 error.
// /** @var \Drupal\webform\Plugin\WebformHandler\RemotePostWebformHandler $handler */
// $handler = $webform_handler_remote->getHandler('remote_post');
// $configuration = $handler->getConfiguration();
// $configuration['settings']['insert_url'] .= '/broken';
// $handler->setConfiguration($configuration);
// $webform_handler_remote->save();
// $this->postSubmission($webform_handler_remote, ['first_name' => 'FAIL']);
// $this->assertPattern('#<label>Response status code</label>\s+404#ms');
// Check 500 Internal Server Error.
$this->postSubmission($webform, ['response_type' => '500']);
$this->assertRaw('Failed to process completed request.');
// Check 404 Not Found.
$this->postSubmission($webform, ['response_type' => '404']);
$this->assertRaw('File not found');
// Disable saving of results
$webform->setSetting('results_disabled', TRUE);
$webform->save();
// Check confiramtion number when results disabled.
$sid = $this->postSubmission($webform);
$this->assertNull($sid);
// Get confiramtion number from JSON packet.
preg_match('/&quot;confirmation_number&quot;:&quot;([a-zA-z0-9]+)&quot;/', $this->getRawContent(), $match);
$this->assertRaw('Your confirmation number is ' . $match[1] . '.');
}
}
......@@ -141,10 +141,17 @@ trait WebformTestTrait {
/**
* Get the last submission id.
*
* @return int
* The last submission id.
* @param \Drupal\webform\WebformInterface $webform
* A webform.
*
* @return int|null
* The last submission id. NULL is saving of results is disabled.
*/
protected function getLastSubmissionId($webform) {
protected function getLastSubmissionId(WebformInterface $webform) {
if ($webform->getSetting('results_disabled')) {
return NULL;
}
// Get submission sid.
$url = UrlHelper::parse($this->getUrl());
if (isset($url['query']['sid'])) {
......
......@@ -227,7 +227,7 @@ interface WebformSubmissionInterface extends ContentEntityInterface, EntityOwner
/**
* Track the state of a submission.
*
* @return int
* @return string
* Either STATE_NEW, STATE_DRAFT, STATE_COMPLETED, STATE_UPDATED, or
* STATE_CONVERTED depending on the last save operation performed.
*/
......
......@@ -131,7 +131,6 @@ class WebformSubmissionStorage extends SqlContentEntityStorage implements Webfor
parent::buildPropertyQuery($entity_query, $values);
}
/**
* {@inheritdoc}
*/
......@@ -881,64 +880,9 @@ class WebformSubmissionStorage extends SqlContentEntityStorage implements Webfor
/****************************************************************************/
/**
* 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.
* {@inheritdoc}
*/
protected function saveData(WebformSubmissionInterface $webform_submission, $delete_first = TRUE) {
public function saveData(WebformSubmissionInterface $webform_submission, $delete_first = TRUE) {
// Get submission data rows.
$data = $webform_submission->getData();
$webform_id = $webform_submission->getWebform()->id();
......@@ -1009,6 +953,56 @@ class WebformSubmissionStorage extends SqlContentEntityStorage implements Webfor
$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)) {
/** @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);
}
}
}
/**
* Delete webform submission data from the 'webform_submission_data' table.
*
......
......@@ -394,6 +394,25 @@ interface WebformSubmissionStorageInterface extends ContentEntityStorageInterfac
*/
public function purge($count);
/****************************************************************************/
// Data handlers.
/****************************************************************************/
/**
* Save webform submission data to the 'webform_submission_data' table.