mirror of
https://github.com/silverstripe/silverstripe-userforms.git
synced 2024-10-22 17:05:42 +02:00
521 lines
18 KiB
PHP
521 lines
18 KiB
PHP
<?php
|
|
|
|
namespace SilverStripe\UserForms\Control;
|
|
|
|
use PageController;
|
|
use SilverStripe\Assets\File;
|
|
use SilverStripe\Assets\Upload;
|
|
use SilverStripe\Control\Controller;
|
|
use SilverStripe\Control\Email\Email;
|
|
use SilverStripe\Control\HTTPResponse;
|
|
use SilverStripe\Control\HTTPRequest;
|
|
use SilverStripe\Core\Manifest\ModuleLoader;
|
|
use SilverStripe\Forms\Form;
|
|
use SilverStripe\i18n\i18n;
|
|
use SilverStripe\ORM\ArrayList;
|
|
use SilverStripe\ORM\FieldType\DBField;
|
|
use SilverStripe\ORM\ValidationException;
|
|
use SilverStripe\Security\Security;
|
|
use SilverStripe\UserForms\Form\UserForm;
|
|
use SilverStripe\UserForms\Model\EditableFormField\EditableFileField;
|
|
use SilverStripe\UserForms\Model\Submission\SubmittedForm;
|
|
use SilverStripe\View\ArrayData;
|
|
use SilverStripe\View\Requirements;
|
|
use SilverStripe\View\SSViewer;
|
|
|
|
/**
|
|
* Controller for the {@link UserDefinedForm} page type.
|
|
*
|
|
* @package userforms
|
|
*/
|
|
class UserDefinedFormController extends PageController
|
|
{
|
|
private static $finished_anchor = '#uff';
|
|
|
|
private static $allowed_actions = [
|
|
'index',
|
|
'ping',
|
|
'Form',
|
|
'finished'
|
|
];
|
|
|
|
protected function init()
|
|
{
|
|
parent::init();
|
|
|
|
$page = $this->data();
|
|
|
|
// load the css
|
|
if (!$page->config()->get('block_default_userforms_css')) {
|
|
Requirements::css('silverstripe/userforms:client/dist/styles/userforms.css');
|
|
}
|
|
|
|
// load the jquery
|
|
if (!$page->config()->get('block_default_userforms_js')) {
|
|
Requirements::javascript('//code.jquery.com/jquery-3.3.1.min.js');
|
|
Requirements::javascript(
|
|
'silverstripe/userforms:client/thirdparty/jquery-validate/jquery.validate.min.js'
|
|
);
|
|
Requirements::javascript('silverstripe/admin:client/dist/js/i18n.js');
|
|
Requirements::add_i18n_javascript('silverstripe/userforms:client/lang');
|
|
Requirements::javascript('silverstripe/userforms:client/dist/js/userforms.js');
|
|
|
|
$this->addUserFormsValidatei18n();
|
|
|
|
// Bind a confirmation message when navigating away from a partially completed form.
|
|
if ($page::config()->get('enable_are_you_sure')) {
|
|
Requirements::javascript(
|
|
'silverstripe/userforms:client/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add the necessary jQuery validate i18n translation files, either by locale or by langauge,
|
|
* e.g. 'en_NZ' or 'en'. This adds "methods_abc.min.js" as well as "messages_abc.min.js" from the
|
|
* jQuery validate thirdparty library.
|
|
*/
|
|
protected function addUserFormsValidatei18n()
|
|
{
|
|
$module = ModuleLoader::getModule('silverstripe/userforms');
|
|
|
|
$candidates = [
|
|
i18n::getData()->langFromLocale(i18n::config()->get('default_locale')),
|
|
i18n::config()->get('default_locale'),
|
|
i18n::getData()->langFromLocale(i18n::get_locale()),
|
|
i18n::get_locale(),
|
|
];
|
|
|
|
foreach ($candidates as $candidate) {
|
|
foreach (['messages', 'methods'] as $candidateType) {
|
|
$localisationCandidate = "client/thirdparty/jquery-validate/localization/{$candidateType}_{$candidate}.min.js";
|
|
|
|
$resource = $module->getResource($localisationCandidate);
|
|
if ($resource->exists()) {
|
|
Requirements::javascript($resource->getRelativePath());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Using $UserDefinedForm in the Content area of the page shows
|
|
* where the form should be rendered into. If it does not exist
|
|
* then default back to $Form.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function index(HTTPRequest $request = null)
|
|
{
|
|
$form = $this->Form();
|
|
if ($this->Content && $form && !$this->config()->disable_form_content_shortcode) {
|
|
$hasLocation = stristr($this->Content, '$UserDefinedForm');
|
|
if ($hasLocation) {
|
|
/** @see Requirements_Backend::escapeReplacement */
|
|
$formEscapedForRegex = addcslashes($form->forTemplate(), '\\$');
|
|
$content = preg_replace(
|
|
'/(<p[^>]*>)?\\$UserDefinedForm(<\\/p>)?/i',
|
|
$formEscapedForRegex,
|
|
$this->Content
|
|
);
|
|
return [
|
|
'Content' => DBField::create_field('HTMLText', $content),
|
|
'Form' => ''
|
|
];
|
|
}
|
|
}
|
|
|
|
return [
|
|
'Content' => DBField::create_field('HTMLText', $this->Content),
|
|
'Form' => $this->Form()
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Keep the session alive for the user.
|
|
*
|
|
* @return int
|
|
*/
|
|
public function ping()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Get the form for the page. Form can be modified by calling {@link updateForm()}
|
|
* on a UserDefinedForm extension.
|
|
*
|
|
* @return Form
|
|
*/
|
|
public function Form()
|
|
{
|
|
$form = UserForm::create($this, 'Form_' . $this->ID);
|
|
/** @skipUpgrade */
|
|
$form->setFormAction(Controller::join_links($this->Link(), 'Form'));
|
|
$this->generateConditionalJavascript();
|
|
return $form;
|
|
}
|
|
|
|
/**
|
|
* Generate the javascript for the conditional field show / hiding logic.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function generateConditionalJavascript()
|
|
{
|
|
$rules = '';
|
|
$form = $this->data();
|
|
$formFields = $form->Fields();
|
|
|
|
$watch = [];
|
|
|
|
if ($formFields) {
|
|
/** @var EditableFormField $field */
|
|
foreach ($formFields as $field) {
|
|
if ($result = $field->formatDisplayRules()) {
|
|
$watch[] = $result;
|
|
}
|
|
}
|
|
}
|
|
if ($watch) {
|
|
$rules .= $this->buildWatchJS($watch);
|
|
}
|
|
|
|
// Only add customScript if $default or $rules is defined
|
|
if ($rules) {
|
|
Requirements::customScript(<<<JS
|
|
(function($) {
|
|
$(document).ready(function() {
|
|
{$rules}
|
|
});
|
|
})(jQuery);
|
|
JS
|
|
, 'UserFormsConditional-' . $form->ID);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process the form that is submitted through the site
|
|
*
|
|
* {@see UserForm::validate()} for validation step prior to processing
|
|
*
|
|
* @param array $data
|
|
* @param Form $form
|
|
*
|
|
* @return HTTPResponse
|
|
*/
|
|
public function process($data, $form)
|
|
{
|
|
$submittedForm = SubmittedForm::create();
|
|
$submittedForm->SubmittedByID = Security::getCurrentUser() ? Security::getCurrentUser()->ID : 0;
|
|
$submittedForm->ParentClass = get_class($this->data());
|
|
$submittedForm->ParentID = $this->ID;
|
|
|
|
// if saving is not disabled save now to generate the ID
|
|
if (!$this->DisableSaveSubmissions) {
|
|
$submittedForm->write();
|
|
}
|
|
|
|
$attachments = [];
|
|
$submittedFields = ArrayList::create();
|
|
|
|
foreach ($this->data()->Fields() as $field) {
|
|
if (!$field->showInReports()) {
|
|
continue;
|
|
}
|
|
|
|
$submittedField = $field->getSubmittedFormField();
|
|
$submittedField->ParentID = $submittedForm->ID;
|
|
$submittedField->Name = $field->Name;
|
|
$submittedField->Title = $field->getField('Title');
|
|
|
|
// save the value from the data
|
|
if ($field->hasMethod('getValueFromData')) {
|
|
$submittedField->Value = $field->getValueFromData($data);
|
|
} else {
|
|
if (isset($data[$field->Name])) {
|
|
$submittedField->Value = $data[$field->Name];
|
|
}
|
|
}
|
|
|
|
if (!empty($data[$field->Name])) {
|
|
if (in_array(EditableFileField::class, $field->getClassAncestry())) {
|
|
if (!empty($_FILES[$field->Name]['name'])) {
|
|
$foldername = $field->getFormField()->getFolderName();
|
|
|
|
// create the file from post data
|
|
$upload = Upload::create();
|
|
$file = File::create();
|
|
$file->ShowInSearch = 0;
|
|
try {
|
|
$upload->loadIntoFile($_FILES[$field->Name], $file, $foldername);
|
|
} catch (ValidationException $e) {
|
|
$validationResult = $e->getResult();
|
|
$form->addErrorMessage($field->Name, $validationResult->message(), 'bad');
|
|
Controller::curr()->redirectBack();
|
|
return;
|
|
}
|
|
|
|
// write file to form field
|
|
$submittedField->UploadedFileID = $file->ID;
|
|
|
|
// attach a file only if lower than 1MB
|
|
if ($file->getAbsoluteSize() < 1024 * 1024 * 1) {
|
|
$attachments[] = $file;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$submittedField->extend('onPopulationFromField', $field);
|
|
|
|
if (!$this->DisableSaveSubmissions) {
|
|
$submittedField->write();
|
|
}
|
|
|
|
$submittedFields->push($submittedField);
|
|
}
|
|
|
|
$emailData = [
|
|
'Sender' => Security::getCurrentUser(),
|
|
'HideFormData' => false,
|
|
'Fields' => $submittedFields,
|
|
'Body' => '',
|
|
];
|
|
|
|
$this->extend('updateEmailData', $emailData, $attachments);
|
|
|
|
// email users on submit.
|
|
if ($recipients = $this->FilteredEmailRecipients($data, $form)) {
|
|
foreach ($recipients as $recipient) {
|
|
$email = Email::create()
|
|
->setHTMLTemplate('email/SubmittedFormEmail')
|
|
->setPlainTemplate('email/SubmittedFormEmail');
|
|
|
|
// Merge fields are used for CMS authors to reference specific form fields in email content
|
|
$mergeFields = $this->getMergeFieldsMap($emailData['Fields']);
|
|
|
|
if ($attachments) {
|
|
foreach ($attachments as $file) {
|
|
/** @var File $file */
|
|
if ((int) $file->ID === 0) {
|
|
continue;
|
|
}
|
|
|
|
$email->addAttachmentFromData(
|
|
$file->getString(),
|
|
$file->getFilename(),
|
|
$file->getMimeType()
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!$recipient->SendPlain && $recipient->emailTemplateExists()) {
|
|
$email->setHTMLTemplate($recipient->EmailTemplate);
|
|
}
|
|
|
|
// Add specific template data for the current recipient
|
|
$emailData['HideFormData'] = (bool) $recipient->HideFormData;
|
|
// Include any parsed merge field references from the CMS editor - this is already escaped
|
|
$emailData['Body'] = SSViewer::execute_string($recipient->getEmailBodyContent(), $mergeFields);
|
|
|
|
// Push the template data to the Email's data
|
|
foreach ($emailData as $key => $value) {
|
|
$email->addData($key, $value);
|
|
}
|
|
|
|
// check to see if they are a dynamic reply to. eg based on a email field a user selected
|
|
if ($recipient->SendEmailFromField()) {
|
|
$submittedFormField = $submittedFields->find('Name', $recipient->SendEmailFromField()->Name);
|
|
|
|
if ($submittedFormField && is_string($submittedFormField->Value)) {
|
|
$email->setReplyTo(explode(',', $submittedFormField->Value));
|
|
}
|
|
} elseif ($recipient->EmailReplyTo) {
|
|
$email->setReplyTo(explode(',', $recipient->EmailReplyTo));
|
|
}
|
|
|
|
// check for a specified from; otherwise fall back to server defaults
|
|
if ($recipient->EmailFrom) {
|
|
$email->setFrom(explode(',', $recipient->EmailFrom));
|
|
}
|
|
|
|
// check to see if they are a dynamic reciever eg based on a dropdown field a user selected
|
|
if ($recipient->SendEmailToField()) {
|
|
$submittedFormField = $submittedFields->find('Name', $recipient->SendEmailToField()->Name);
|
|
|
|
if ($submittedFormField && is_string($submittedFormField->Value)) {
|
|
$email->setTo(explode(',', $submittedFormField->Value));
|
|
} else {
|
|
$email->setTo(explode(',', $recipient->EmailAddress));
|
|
}
|
|
} else {
|
|
$email->setTo(explode(',', $recipient->EmailAddress));
|
|
}
|
|
|
|
// check to see if there is a dynamic subject
|
|
if ($recipient->SendEmailSubjectField()) {
|
|
$submittedFormField = $submittedFields->find('Name', $recipient->SendEmailSubjectField()->Name);
|
|
|
|
if ($submittedFormField && trim($submittedFormField->Value)) {
|
|
$email->setSubject($submittedFormField->Value);
|
|
} else {
|
|
$email->setSubject($recipient->EmailSubject);
|
|
}
|
|
} else {
|
|
$email->setSubject($recipient->EmailSubject);
|
|
}
|
|
|
|
$this->extend('updateEmail', $email, $recipient, $emailData);
|
|
|
|
if ((bool)$recipient->SendPlain) {
|
|
$body = strip_tags($recipient->getEmailBodyContent()) . "\n";
|
|
if (isset($emailData['Fields']) && !$emailData['HideFormData']) {
|
|
foreach ($emailData['Fields'] as $field) {
|
|
$body .= $field->Title . ': ' . $field->Value . " \n";
|
|
}
|
|
}
|
|
|
|
$email->setBody($body);
|
|
$email->sendPlain();
|
|
} else {
|
|
$email->send();
|
|
}
|
|
}
|
|
}
|
|
|
|
$submittedForm->extend('updateAfterProcess');
|
|
|
|
$session = $this->getRequest()->getSession();
|
|
$session->clear("FormInfo.{$form->FormName()}.errors");
|
|
$session->clear("FormInfo.{$form->FormName()}.data");
|
|
|
|
$referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
|
|
|
|
// set a session variable from the security ID to stop people accessing
|
|
// the finished method directly.
|
|
if (!$this->DisableAuthenicatedFinishAction) {
|
|
if (isset($data['SecurityID'])) {
|
|
$session->set('FormProcessed', $data['SecurityID']);
|
|
} else {
|
|
// if the form has had tokens disabled we still need to set FormProcessed
|
|
// to allow us to get through the finshed method
|
|
if (!$this->Form()->getSecurityToken()->isEnabled()) {
|
|
$randNum = rand(1, 1000);
|
|
$randHash = md5($randNum);
|
|
$session->set('FormProcessed', $randHash);
|
|
$session->set('FormProcessedNum', $randNum);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$this->DisableSaveSubmissions) {
|
|
$session->set('userformssubmission'. $this->ID, $submittedForm->ID);
|
|
}
|
|
|
|
return $this->redirect($this->Link('finished') . $referrer . $this->config()->get('finished_anchor'));
|
|
}
|
|
|
|
/**
|
|
* Allows the use of field values in email body.
|
|
*
|
|
* @param ArrayList $fields
|
|
* @return ArrayData
|
|
*/
|
|
protected function getMergeFieldsMap($fields = [])
|
|
{
|
|
$data = ArrayData::create([]);
|
|
|
|
foreach ($fields as $field) {
|
|
$data->setField($field->Name, DBField::create_field('Text', $field->Value));
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* This action handles rendering the "finished" message, which is
|
|
* customizable by editing the ReceivedFormSubmission template.
|
|
*
|
|
* @return ViewableData
|
|
*/
|
|
public function finished()
|
|
{
|
|
$submission = $this->getRequest()->getSession()->get('userformssubmission'. $this->ID);
|
|
|
|
if ($submission) {
|
|
$submission = SubmittedForm::get()->byId($submission);
|
|
}
|
|
|
|
$referrer = isset($_GET['referrer']) ? urldecode($_GET['referrer']) : null;
|
|
|
|
if (!$this->DisableAuthenicatedFinishAction) {
|
|
$formProcessed = $this->getRequest()->getSession()->get('FormProcessed');
|
|
|
|
if (!isset($formProcessed)) {
|
|
return $this->redirect($this->Link() . $referrer);
|
|
} else {
|
|
$securityID = $this->getRequest()->getSession()->get('SecurityID');
|
|
// make sure the session matches the SecurityID and is not left over from another form
|
|
if ($formProcessed != $securityID) {
|
|
// they may have disabled tokens on the form
|
|
$securityID = md5($this->getRequest()->getSession()->get('FormProcessedNum'));
|
|
if ($formProcessed != $securityID) {
|
|
return $this->redirect($this->Link() . $referrer);
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->getRequest()->getSession()->clear('FormProcessed');
|
|
}
|
|
|
|
$data = [
|
|
'Submission' => $submission,
|
|
'Link' => $referrer
|
|
];
|
|
|
|
$this->extend('updateReceivedFormSubmissionData', $data);
|
|
|
|
return $this->customise([
|
|
'Content' => $this->customise($data)->renderWith(__CLASS__ . '_ReceivedFormSubmission'),
|
|
'Form' => '',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Outputs the required JS from the $watch input
|
|
*
|
|
* @param array $watch
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function buildWatchJS($watch)
|
|
{
|
|
$result = '';
|
|
foreach ($watch as $key => $rule) {
|
|
$events = implode(' ', $rule['events']);
|
|
$selectors = implode(', ', $rule['selectors']);
|
|
$conjunction = $rule['conjunction'];
|
|
$operations = implode(" {$conjunction} ", $rule['operations']);
|
|
$target = $rule['targetFieldID'];
|
|
|
|
$result .= <<<EOS
|
|
\n
|
|
$('.userform').on('{$events}',
|
|
"{$selectors}",
|
|
function (){
|
|
if ({$operations}) {
|
|
$('{$target}').{$rule['view']};
|
|
} else {
|
|
$('{$target}').{$rule['opposite']};
|
|
}
|
|
});
|
|
$("{$target}").find('.hide').removeClass('hide');
|
|
EOS;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
}
|