Completed
Pull Request — master (#429)
by Nathan
31:14
created

UserDefinedForm_Controller::init()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 24
rs 8.9713
cc 2
eloc 15
nc 2
nop 0
1
<?php
2
3
/**
4
 * @package userforms
5
 */
6
7
class UserDefinedForm extends Page {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
8
9
	/**
10
	 * @var string
11
	 */
12
	private static $description = 'Adds a customizable form.';
0 ignored issues
show
Unused Code introduced by
The property $description is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
13
14
	/**
15
	 * @var string Required Identifier
16
	 */
17
	private static $required_identifier = null;
0 ignored issues
show
Unused Code introduced by
The property $required_identifier is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
18
19
	/**
20
	 * @var string
21
	 */
22
	private static $email_template_directory = 'userforms/templates/email/';
0 ignored issues
show
Unused Code introduced by
The property $email_template_directory is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
23
24
	/**
25
	 * Should this module automatically upgrade on dev/build?
26
	 *
27
	 * @config
28
	 * @var bool
29
	 */
30
	private static $upgrade_on_build = true;
0 ignored issues
show
Unused Code introduced by
The property $upgrade_on_build is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
31
32
	/**
33
	 * Built in extensions required by this page
34
	 * @config
35
	 * @var array
36
	 */
37
	private static $extensions = array(
0 ignored issues
show
Unused Code introduced by
The property $extensions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
38
		'UserFormFieldEditorExtension'
39
	);
40
41
	/**
42
	 * @var array Fields on the user defined form page.
43
	 */
44
	private static $db = array(
0 ignored issues
show
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
45
		"SubmitButtonText" => "Varchar",
46
		"ClearButtonText" => "Varchar",
47
		"OnCompleteMessage" => "HTMLText",
48
		"ShowClearButton" => "Boolean",
49
		'DisableSaveSubmissions' => 'Boolean',
50
		'EnableLiveValidation' => 'Boolean',
51
		'HideFieldLabels' => 'Boolean',
52
		'DisplayErrorMessagesAtTop' => 'Boolean',
53
		'DisableAuthenicatedFinishAction' => 'Boolean',
54
		'DisableCsrfSecurityToken' => 'Boolean'
55
	);
56
57
	/**
58
	 * @var array Default values of variables when this page is created
59
	 */
60
	private static $defaults = array(
0 ignored issues
show
Unused Code introduced by
The property $defaults is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
61
		'Content' => '$UserDefinedForm',
62
		'DisableSaveSubmissions' => 0,
63
		'OnCompleteMessage' => '<p>Thanks, we\'ve received your submission.</p>'
64
	);
65
66
	/**
67
	 * @var array
68
	 */
69
	private static $has_many = array(
0 ignored issues
show
Unused Code introduced by
The property $has_many is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
70
		"Submissions" => "SubmittedForm",
71
		"EmailRecipients" => "UserDefinedForm_EmailRecipient"
72
	);
73
74
	/**
75
	 * @var array
76
	 * @config
77
	 */
78
	private static $casting = array(
0 ignored issues
show
Unused Code introduced by
The property $casting is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
79
		'ErrorContainerID' => 'Text'
80
	);
81
82
	/**
83
	 * Error container selector which matches the element for grouped messages
84
	 *
85
	 * @var string
86
	 * @config
87
	 */
88
	private static $error_container_id = 'error-container';
0 ignored issues
show
Unused Code introduced by
The property $error_container_id is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
89
90
	/**
91
	 * Temporary storage of field ids when the form is duplicated.
92
	 * Example layout: array('EditableCheckbox3' => 'EditableCheckbox14')
93
	 * @var array
94
	 */
95
	protected $fieldsFromTo = array();
96
97
	/**
98
	 * @return FieldList
99
	 */
100
	 public function getCMSFields() {
101
		Requirements::css(USERFORMS_DIR . '/css/UserForm_cms.css');
102
103
		$self = $this;
104
105
		$this->beforeUpdateCMSFields(function($fields) use ($self) {
106
107
			// define tabs
108
			$fields->findOrMakeTab('Root.FormOptions', _t('UserDefinedForm.CONFIGURATION', 'Configuration'));
109
			$fields->findOrMakeTab('Root.Recipients', _t('UserDefinedForm.RECIPIENTS', 'Recipients'));
110
			$fields->findOrMakeTab('Root.Submissions', _t('UserDefinedForm.SUBMISSIONS', 'Submissions'));
111
112
			// text to show on complete
113
			$onCompleteFieldSet = new CompositeField(
114
				$label = new LabelField('OnCompleteMessageLabel',_t('UserDefinedForm.ONCOMPLETELABEL', 'Show on completion')),
115
				$editor = new HtmlEditorField( 'OnCompleteMessage', '', _t('UserDefinedForm.ONCOMPLETEMESSAGE', $self->OnCompleteMessage))
116
			);
117
118
			$onCompleteFieldSet->addExtraClass('field');
119
120
			$editor->setRows(3);
121
			$label->addExtraClass('left');
122
123
			// Define config for email recipients
124
			$emailRecipientsConfig = GridFieldConfig_RecordEditor::create(10);
125
			$emailRecipientsConfig->getComponentByType('GridFieldAddNewButton')
126
				->setButtonName(
127
					_t('UserDefinedForm.ADDEMAILRECIPIENT', 'Add Email Recipient')
128
				);
129
130
			// who do we email on submission
131
			$emailRecipients = new GridField(
132
				'EmailRecipients',
133
				_t('UserDefinedForm.EMAILRECIPIENTS', 'Email Recipients'),
134
				$self->EmailRecipients(),
135
				$emailRecipientsConfig
136
			);
137
			$emailRecipients
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface GridFieldComponent as the method setItemRequestClass() does only exist in the following implementations of said interface: GridFieldAddNewMultiClass, GridFieldDetailForm.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
138
				->getConfig()
139
				->getComponentByType('GridFieldDetailForm')
140
				->setItemRequestClass('UserFormRecipientItemRequest');
141
142
			$fields->addFieldsToTab('Root.FormOptions', $onCompleteFieldSet);
143
			$fields->addFieldToTab('Root.Recipients', $emailRecipients);
144
			$fields->addFieldsToTab('Root.FormOptions', $self->getFormOptions());
145
146
147
			// view the submissions
148
			$submissions = new GridField(
149
				'Submissions',
150
				_t('UserDefinedForm.SUBMISSIONS', 'Submissions'),
151
				 $self->Submissions()->sort('Created', 'DESC')
152
			);
153
154
			// make sure a numeric not a empty string is checked against this int column for SQL server
155
			$parentID = (!empty($self->ID)) ? (int) $self->ID : 0;
156
157
			// get a list of all field names and values used for print and export CSV views of the GridField below.
158
			$columnSQL = <<<SQL
159
SELECT "Name", "Title"
160
FROM "SubmittedFormField"
161
LEFT JOIN "SubmittedForm" ON "SubmittedForm"."ID" = "SubmittedFormField"."ParentID"
162
WHERE "SubmittedForm"."ParentID" = '$parentID'
163
ORDER BY "Title" ASC
164
SQL;
165
			// Sanitise periods in title
166
			$columns = array();
167
			foreach(DB::query($columnSQL)->map() as $name => $title) {
168
				$columns[$name] = trim(strtr($title, '.', ' '));
169
			}
170
171
			$config = new GridFieldConfig();
172
			$config->addComponent(new GridFieldToolbarHeader());
173
			$config->addComponent($sort = new GridFieldSortableHeader());
174
			$config->addComponent($filter = new UserFormsGridFieldFilterHeader());
175
			$config->addComponent(new GridFieldDataColumns());
176
			$config->addComponent(new GridFieldEditButton());
177
			$config->addComponent(new GridFieldDeleteAction());
178
			$config->addComponent(new GridFieldPageCount('toolbar-header-right'));
179
			$config->addComponent($pagination = new GridFieldPaginator(25));
180
			$config->addComponent(new GridFieldDetailForm());
181
			$config->addComponent(new GridFieldButtonRow('after'));
182
			$config->addComponent($export = new GridFieldExportButton('buttons-after-left'));
183
			$config->addComponent($print = new GridFieldPrintButton('buttons-after-left'));
184
185
			/**
186
			 * Support for {@link https://github.com/colymba/GridFieldBulkEditingTools}
187
			 */
188
			if(class_exists('GridFieldBulkManager')) {
189
				$config->addComponent(new GridFieldBulkManager());
190
			}
191
192
			$sort->setThrowExceptionOnBadDataType(false);
193
			$filter->setThrowExceptionOnBadDataType(false);
194
			$pagination->setThrowExceptionOnBadDataType(false);
195
196
			// attach every column to the print view form
197
			$columns['Created'] = 'Created';
198
			$filter->setColumns($columns);
199
200
			// print configuration
201
202
			$print->setPrintHasHeader(true);
203
			$print->setPrintColumns($columns);
204
205
			// export configuration
206
			$export->setCsvHasHeader(true);
207
			$export->setExportColumns($columns);
208
209
			$submissions->setConfig($config);
210
			$fields->addFieldToTab('Root.Submissions', $submissions);
211
			$fields->addFieldToTab('Root.FormOptions', new CheckboxField('DisableSaveSubmissions', _t('UserDefinedForm.SAVESUBMISSIONS', 'Disable Saving Submissions to Server')));
212
213
		});
214
215
		$fields = parent::getCMSFields();
216
217
		return $fields;
218
	}
219
220
	/**
221
	 * Allow overriding the EmailRecipients on a {@link DataExtension}
222
	 * so you can customise who receives an email.
223
	 * Converts the RelationList to an ArrayList so that manipulation
224
	 * of the original source data isn't possible.
225
	 *
226
	 * @return ArrayList
227
	 */
228
	public function FilteredEmailRecipients($data = null, $form = null) {
229
		$recipients = new ArrayList($this->EmailRecipients()->toArray());
230
231
		// Filter by rules
232
		$recipients = $recipients->filterByCallback(function($recipient) use ($data, $form) {
233
			return $recipient->canSend($data, $form);
234
		});
235
236
		$this->extend('updateFilteredEmailRecipients', $recipients, $data, $form);
237
238
		return $recipients;
239
	}
240
241
	/**
242
	 * Custom options for the form. You can extend the built in options by
243
	 * using {@link updateFormOptions()}
244
	 *
245
	 * @return FieldList
246
	 */
247
	public function getFormOptions() {
248
		$submit = ($this->SubmitButtonText) ? $this->SubmitButtonText : _t('UserDefinedForm.SUBMITBUTTON', 'Submit');
249
		$clear = ($this->ClearButtonText) ? $this->ClearButtonText : _t('UserDefinedForm.CLEARBUTTON', 'Clear');
250
251
		$options = new FieldList(
252
			new TextField("SubmitButtonText", _t('UserDefinedForm.TEXTONSUBMIT', 'Text on submit button:'), $submit),
253
			new TextField("ClearButtonText", _t('UserDefinedForm.TEXTONCLEAR', 'Text on clear button:'), $clear),
254
			new CheckboxField("ShowClearButton", _t('UserDefinedForm.SHOWCLEARFORM', 'Show Clear Form Button'), $this->ShowClearButton),
255
			new CheckboxField("EnableLiveValidation", _t('UserDefinedForm.ENABLELIVEVALIDATION', 'Enable live validation')),
256
			new CheckboxField("HideFieldLabels", _t('UserDefinedForm.HIDEFIELDLABELS', 'Hide field labels')),
257
			new CheckboxField("DisplayErrorMessagesAtTop", _t('UserDefinedForm.DISPLAYERRORMESSAGESATTOP', 'Display error messages above the form?')),
258
			new CheckboxField('DisableCsrfSecurityToken', _t('UserDefinedForm.DISABLECSRFSECURITYTOKEN', 'Disable CSRF Token')),
259
			new CheckboxField('DisableAuthenicatedFinishAction', _t('UserDefinedForm.DISABLEAUTHENICATEDFINISHACTION', 'Disable Authentication on finish action'))
260
		);
261
262
		$this->extend('updateFormOptions', $options);
263
264
		return $options;
265
	}
266
267
	/**
268
	 * Get the HTML id of the error container displayed above the form.
269
	 *
270
	 * @return string
271
	 */
272
	public function getErrorContainerID() {
273
		return $this->config()->error_container_id;
274
	}
275
276
	public function requireDefaultRecords() {
277
		parent::requireDefaultRecords();
278
279
		if(!$this->config()->upgrade_on_build) {
280
			return;
281
		}
282
283
		// Perform migrations
284
		Injector::inst()
285
			->create('UserFormsUpgradeService')
286
			->setQuiet(true)
287
			->run();
288
289
		DB::alteration_message('Migrated userforms', 'changed');
290
	}
291
292
293
	/**
294
	 * Validate formfields
295
	 */
296
	public function getCMSValidator() {
297
		return new UserFormValidator();
298
	}
299
}
300
301
/**
302
 * Controller for the {@link UserDefinedForm} page type.
303
 *
304
 * @package userforms
305
 */
306
307
class UserDefinedForm_Controller extends Page_Controller {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
308
309
	private static $finished_anchor = '#uff';
0 ignored issues
show
Unused Code introduced by
The property $finished_anchor is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
310
311
	private static $allowed_actions = array(
0 ignored issues
show
Unused Code introduced by
The property $allowed_actions is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
312
		'index',
313
		'ping',
314
		'Form',
315
		'finished'
316
	);
317
318
	public function init() {
319
		parent::init();
320
321
		// load the jquery
322
		$lang = i18n::get_lang_from_locale(i18n::get_locale());
323
		Requirements::css(USERFORMS_DIR . '/css/UserForm.css');
324
		Requirements::javascript(FRAMEWORK_DIR .'/thirdparty/jquery/jquery.js');
325
		Requirements::javascript(USERFORMS_DIR . '/thirdparty/jquery-validate/jquery.validate.min.js');
326
		Requirements::add_i18n_javascript(USERFORMS_DIR . '/javascript/lang');
327
		Requirements::javascript(USERFORMS_DIR . '/javascript/UserForm.js');
328
329
		Requirements::javascript(
330
			USERFORMS_DIR . "/thirdparty/jquery-validate/localization/messages_{$lang}.min.js"
331
		);
332
		Requirements::javascript(
333
			USERFORMS_DIR . "/thirdparty/jquery-validate/localization/methods_{$lang}.min.js"
334
		);
335
		if($this->HideFieldLabels) {
336
			Requirements::javascript(USERFORMS_DIR . '/thirdparty/Placeholders.js/Placeholders.min.js');
337
		}
338
339
		// Bind a confirmation message when navigating away from a partially completed form.
340
		Requirements::javascript(USERFORMS_DIR . '/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js');
341
	}
342
343
	/**
344
	 * Using $UserDefinedForm in the Content area of the page shows
345
	 * where the form should be rendered into. If it does not exist
346
	 * then default back to $Form.
347
	 *
348
	 * @return array
349
	 */
350
	public function index() {
351
		if($this->Content && $form = $this->Form()) {
352
			$hasLocation = stristr($this->Content, '$UserDefinedForm');
353
			if($hasLocation) {
354
				$content = preg_replace('/(<p[^>]*>)?\\$UserDefinedForm(<\\/p>)?/i', $form->forTemplate(), $this->Content);
355
				return array(
356
					'Content' => DBField::create_field('HTMLText', $content),
357
					'Form' => ""
358
				);
359
			}
360
		}
361
362
		return array(
363
			'Content' => DBField::create_field('HTMLText', $this->Content),
364
			'Form' => $this->Form()
365
		);
366
	}
367
368
	/**
369
	 * Keep the session alive for the user.
370
	 *
371
	 * @return int
372
	 */
373
	public function ping() {
374
		return 1;
375
	}
376
377
	/**
378
	 * Get the form for the page. Form can be modified by calling {@link updateForm()}
379
	 * on a UserDefinedForm extension.
380
	 *
381
	 * @return Forms
382
	 */
383
	public function Form() {
384
		$form = UserForm::create($this);
385
		$this->generateConditionalJavascript();
386
		return $form;
387
	}
388
389
	/**
390
	 * Generate the javascript for the conditional field show / hiding logic.
391
	 *
392
	 * @return void
393
	 */
394
	public function generateConditionalJavascript() {
395
		$default = "";
396
		$rules = "";
397
398
		$watch = array();
399
		$watchLoad = array();
400
401
		if($this->Fields()) {
402
			foreach($this->Fields() as $field) {
403
				$holderSelector = $field->getSelectorHolder();
404
405
				// Is this Field Show by Default
406
				if(!$field->ShowOnLoad) {
407
					$default .= "{$holderSelector}.hide().trigger('userform.field.hide');\n";
408
				}
409
410
				// Check for field dependencies / default
411
				foreach($field->DisplayRules() as $rule) {
412
413
					// Get the field which is effected
414
					$formFieldWatch = EditableFormField::get()->byId($rule->ConditionFieldID);
415
416
					// Skip deleted fields
417
					if(!$formFieldWatch) {
418
						continue;
419
					}
420
421
					$fieldToWatch = $formFieldWatch->getSelectorField($rule);
422
					$fieldToWatchOnLoad = $formFieldWatch->getSelectorField($rule, true);
423
424
					// show or hide?
425
					$view = ($rule->Display == 'Hide') ? 'hide' : 'show';
426
					$opposite = ($view == "show") ? "hide" : "show";
427
428
					// what action do we need to keep track of. Something nicer here maybe?
429
					// @todo encapulsation
430
					$action = "change";
431
432
					if($formFieldWatch instanceof EditableTextField) {
433
						$action = "keyup";
434
					}
435
436
					// is this field a special option field
437
					$checkboxField = false;
438
					$radioField = false;
439
440
					if(in_array($formFieldWatch->ClassName, array('EditableCheckboxGroupField', 'EditableCheckbox'))) {
441
						$action = "click";
442
						$checkboxField = true;
443
					} else if ($formFieldWatch->ClassName == "EditableRadioField") {
444
						$radioField = true;
445
					}
446
447
					// and what should we evaluate
448
					switch($rule->ConditionOption) {
449
						case 'IsNotBlank':
450
							$expression = ($checkboxField || $radioField) ? '$(this).is(":checked")' :'$(this).val() != ""';
451
452
							break;
453
						case 'IsBlank':
454
							$expression = ($checkboxField || $radioField) ? '!($(this).is(":checked"))' : '$(this).val() == ""';
455
456
							break;
457 View Code Duplication
						case 'HasValue':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
458
							if ($checkboxField) {
459
								$expression = '$(this).prop("checked")';
460
							} else if ($radioField) {
461
								// We cannot simply get the value of the radio group, we need to find the checked option first.
462
								$expression = '$(this).parents(".field, .control-group").find("input:checked").val()=="'. $rule->FieldValue .'"';
463
							} else {
464
								$expression = '$(this).val() == "'. $rule->FieldValue .'"';
465
							}
466
467
							break;
468
						case 'ValueLessThan':
469
							$expression = '$(this).val() < parseFloat("'. $rule->FieldValue .'")';
470
471
							break;
472
						case 'ValueLessThanEqual':
473
							$expression = '$(this).val() <= parseFloat("'. $rule->FieldValue .'")';
474
475
							break;
476
						case 'ValueGreaterThan':
477
							$expression = '$(this).val() > parseFloat("'. $rule->FieldValue .'")';
478
479
							break;
480
						case 'ValueGreaterThanEqual':
481
							$expression = '$(this).val() >= parseFloat("'. $rule->FieldValue .'")';
482
483
							break;
484 View Code Duplication
						default: // ==HasNotValue
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
485
							if ($checkboxField) {
486
								$expression = '!$(this).prop("checked")';
487
							} else if ($radioField) {
488
								// We cannot simply get the value of the radio group, we need to find the checked option first.
489
								$expression = '$(this).parents(".field, .control-group").find("input:checked").val()!="'. $rule->FieldValue .'"';
490
							} else {
491
								$expression = '$(this).val() != "'. $rule->FieldValue .'"';
492
							}
493
494
							break;
495
					}
496
497
					if(!isset($watch[$fieldToWatch])) {
498
						$watch[$fieldToWatch] = array();
499
					}
500
501
					$watch[$fieldToWatch][] = array(
502
						'expression' => $expression,
503
						'holder_selector' => $holderSelector,
504
						'view' => $view,
505
						'opposite' => $opposite,
506
						'action' => $action
507
					);
508
509
					$watchLoad[$fieldToWatchOnLoad] = true;
510
				}
511
			}
512
		}
513
514
		if($watch) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $watch of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
515
			foreach($watch as $key => $values) {
516
				$logic = array();
517
				$actions = array();
518
519
				foreach($values as $rule) {
520
					// Assign action
521
					$actions[$rule['action']] = $rule['action'];
522
523
					// Assign behaviour
524
					$expression = $rule['expression'];
525
					$holder = $rule['holder_selector'];
526
					$view = $rule['view']; // hide or show
527
					$opposite = $rule['opposite'];
528
					// Generated javascript for triggering visibility
529
					$logic[] = <<<"EOS"
530
if({$expression}) {
531
	{$holder}
532
		.{$view}()
533
		.trigger('userform.field.{$view}');
534
} else {
535
	{$holder}
536
		.{$opposite}()
537
		.trigger('userform.field.{$opposite}');
538
}
539
EOS;
540
				}
541
542
				$logic = implode("\n", $logic);
543
				$rules .= $key.".each(function() {\n
544
	$(this).data('userformConditions', function() {\n
545
		$logic\n
546
	}); \n
547
});\n";
548
				foreach($actions as $action) {
549
					$rules .= $key.".$action(function() {
550
	$(this).data('userformConditions').call(this);\n
551
});\n";
552
				}
553
			}
554
		}
555
556
		if($watchLoad) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $watchLoad of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
557
			foreach($watchLoad as $key => $value) {
558
				$rules .= $key.".each(function() {
559
	$(this).data('userformConditions').call(this);\n
560
});\n";
561
			}
562
		}
563
564
		// Only add customScript if $default or $rules is defined
565
		if($default  || $rules) {
566
			Requirements::customScript(<<<JS
567
				(function($) {
568
					$(document).ready(function() {
569
						$default
570
571
						$rules
572
					})
573
				})(jQuery);
574
JS
575
, 'UserFormsConditional');
576
		}
577
	}
578
579
	/**
580
	 * Process the form that is submitted through the site
581
	 *
582
	 * {@see UserForm::validate()} for validation step prior to processing
583
	 *
584
	 * @param array $data
585
	 * @param Form $form
586
	 *
587
	 * @return Redirection
588
	 */
589
	public function process($data, $form) {
0 ignored issues
show
Coding Style introduced by
process uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
590
		$submittedForm = Object::create('SubmittedForm');
591
		$submittedForm->SubmittedByID = ($id = Member::currentUserID()) ? $id : 0;
592
		$submittedForm->ParentID = $this->ID;
593
594
		// if saving is not disabled save now to generate the ID
595
		if(!$this->DisableSaveSubmissions) {
596
			$submittedForm->write();
597
		}
598
599
		$attachments = array();
600
		$submittedFields = new ArrayList();
601
602
		foreach($this->Fields() as $field) {
603
			if(!$field->showInReports()) {
604
				continue;
605
			}
606
607
			$submittedField = $field->getSubmittedFormField();
608
			$submittedField->ParentID = $submittedForm->ID;
609
			$submittedField->Name = $field->Name;
610
			$submittedField->Title = $field->getField('Title');
611
612
			// save the value from the data
613
			if($field->hasMethod('getValueFromData')) {
614
				$submittedField->Value = $field->getValueFromData($data);
615
			} else {
616
				if(isset($data[$field->Name])) {
617
					$submittedField->Value = $data[$field->Name];
618
				}
619
			}
620
621
			if(!empty($data[$field->Name])) {
622
				if(in_array("EditableFileField", $field->getClassAncestry())) {
623
					if(isset($_FILES[$field->Name])) {
624
						$foldername = $field->getFormField()->getFolderName();
625
626
						// create the file from post data
627
						$upload = new Upload();
628
						$file = new File();
629
						$file->ShowInSearch = 0;
630
						try {
631
							$upload->loadIntoFile($_FILES[$field->Name], $file, $foldername);
632
						} catch( ValidationException $e ) {
633
							$validationResult = $e->getResult();
634
							$form->addErrorMessage($field->Name, $validationResult->message(), 'bad');
635
							Controller::curr()->redirectBack();
636
							return;
637
						}
638
639
						// write file to form field
640
						$submittedField->UploadedFileID = $file->ID;
641
642
						// attach a file only if lower than 1MB
643
						if($file->getAbsoluteSize() < 1024*1024*1) {
644
							$attachments[] = $file;
645
						}
646
					}
647
				}
648
			}
649
650
			$submittedField->extend('onPopulationFromField', $field);
651
652
			if(!$this->DisableSaveSubmissions) {
653
				$submittedField->write();
654
			}
655
656
			$submittedFields->push($submittedField);
657
		}
658
659
		$emailData = array(
660
			"Sender" => Member::currentUser(),
661
			"Fields" => $submittedFields
662
		);
663
664
		$this->extend('updateEmailData', $emailData, $attachments);
665
666
		// email users on submit.
667
		if($recipients = $this->FilteredEmailRecipients($data, $form)) {
668
669
			foreach($recipients as $recipient) {
670
				$email = new UserFormRecipientEmail($submittedFields);
671
				$mergeFields = $this->getMergeFieldsMap($emailData['Fields']);
672
	
673
				if($attachments) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attachments of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
674
					foreach($attachments as $file) {
675
						if($file->ID != 0) {
676
							$email->attachFile(
677
								$file->Filename,
678
								$file->Filename,
679
								HTTP::get_mime_type($file->Filename)
680
							);
681
						}
682
					}
683
				}
684
				
685
				$parsedBody = SSViewer::execute_string($recipient->getEmailBodyContent(), $mergeFields);
686
687
				if (!$recipient->SendPlain && $recipient->emailTemplateExists()) {
688
					$email->setTemplate($recipient->EmailTemplate);
689
				}
690
691
				$email->populateTemplate($recipient);
692
				$email->populateTemplate($emailData);
693
				$email->setFrom($recipient->EmailFrom);
694
				$email->setBody($parsedBody);
695
				$email->setTo($recipient->EmailAddress);
696
				$email->setSubject($recipient->EmailSubject);
697
698
				if($recipient->EmailReplyTo) {
699
					$email->setReplyTo($recipient->EmailReplyTo);
700
				}
701
702
				// check to see if they are a dynamic reply to. eg based on a email field a user selected
703 View Code Duplication
				if($recipient->SendEmailFromField()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
704
					$submittedFormField = $submittedFields->find('Name', $recipient->SendEmailFromField()->Name);
705
706
					if($submittedFormField && is_string($submittedFormField->Value)) {
707
						$email->setReplyTo($submittedFormField->Value);
708
					}
709
				}
710
				// check to see if they are a dynamic reciever eg based on a dropdown field a user selected
711 View Code Duplication
				if($recipient->SendEmailToField()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
712
					$submittedFormField = $submittedFields->find('Name', $recipient->SendEmailToField()->Name);
713
714
					if($submittedFormField && is_string($submittedFormField->Value)) {
715
						$email->setTo($submittedFormField->Value);
716
					}
717
				}
718
719
				// check to see if there is a dynamic subject
720 View Code Duplication
				if($recipient->SendEmailSubjectField()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
721
					$submittedFormField = $submittedFields->find('Name', $recipient->SendEmailSubjectField()->Name);
722
723
					if($submittedFormField && trim($submittedFormField->Value)) {
724
						$email->setSubject($submittedFormField->Value);
725
					}
726
				}
727
728
				$this->extend('updateEmail', $email, $recipient, $emailData);
729
730
				if($recipient->SendPlain) {
731
					$body = strip_tags($recipient->getEmailBodyContent()) . "\n";
732
					if(isset($emailData['Fields']) && !$recipient->HideFormData) {
733
						foreach($emailData['Fields'] as $Field) {
734
							$body .= $Field->Title .': '. $Field->Value ." \n";
735
						}
736
					}
737
738
					$email->setBody($body);
739
					$email->sendPlain();
740
				}
741
				else {
742
					$email->send();
743
				}
744
			}
745
		}
746
747
		$submittedForm->extend('updateAfterProcess');
748
749
		Session::clear("FormInfo.{$form->FormName()}.errors");
750
		Session::clear("FormInfo.{$form->FormName()}.data");
751
752
		$referrer = (isset($data['Referrer'])) ? '?referrer=' . urlencode($data['Referrer']) : "";
753
754
		// set a session variable from the security ID to stop people accessing
755
		// the finished method directly.
756
		if(!$this->DisableAuthenicatedFinishAction) {
757
			if (isset($data['SecurityID'])) {
758
				Session::set('FormProcessed',$data['SecurityID']);
759
			} else {
760
				// if the form has had tokens disabled we still need to set FormProcessed
761
				// to allow us to get through the finshed method
762
				if (!$this->Form()->getSecurityToken()->isEnabled()) {
763
					$randNum = rand(1, 1000);
764
					$randHash = md5($randNum);
765
					Session::set('FormProcessed',$randHash);
766
					Session::set('FormProcessedNum',$randNum);
767
				}
768
			}
769
		}
770
771
		if(!$this->DisableSaveSubmissions) {
772
			Session::set('userformssubmission'. $this->ID, $submittedForm->ID);
773
		}
774
775
		return $this->redirect($this->Link('finished') . $referrer . $this->config()->finished_anchor);
776
	}
777
778
	/**
779
	 * Allows the use of field values in email body.
780
	 *
781
	 * @param ArrayList fields
782
	 * @return ArrayData
783
	 */
784
	private function getMergeFieldsMap($fields = array()) {
785
		$data = new ArrayData(array());
786
787
		foreach ($fields as $field) {
788
			$data->setField($field->Name, DBField::create_field('Text', $field->Value));
789
		}
790
791
		return $data;
792
	}
793
794
	/**
795
	 * This action handles rendering the "finished" message, which is
796
	 * customizable by editing the ReceivedFormSubmission template.
797
	 *
798
	 * @return ViewableData
799
	 */
800
	public function finished() {
0 ignored issues
show
Coding Style introduced by
finished uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
801
		$submission = Session::get('userformssubmission'. $this->ID);
802
803
		if($submission) {
804
			$submission = SubmittedForm::get()->byId($submission);
805
		}
806
807
		$referrer = isset($_GET['referrer']) ? urldecode($_GET['referrer']) : null;
808
809
		if(!$this->DisableAuthenicatedFinishAction) {
810
			$formProcessed = Session::get('FormProcessed');
811
812
			if (!isset($formProcessed)) {
813
				return $this->redirect($this->Link() . $referrer);
814
			} else {
815
				$securityID = Session::get('SecurityID');
816
				// make sure the session matches the SecurityID and is not left over from another form
817
				if ($formProcessed != $securityID) {
818
					// they may have disabled tokens on the form
819
					$securityID = md5(Session::get('FormProcessedNum'));
820
					if ($formProcessed != $securityID) {
821
						return $this->redirect($this->Link() . $referrer);
822
					}
823
				}
824
			}
825
826
			Session::clear('FormProcessed');
827
		}
828
829
		return $this->customise(array(
830
			'Content' => $this->customise(array(
831
				'Submission' => $submission,
832
				'Link' => $referrer
833
			))->renderWith('ReceivedFormSubmission'),
834
			'Form' => '',
835
		));
836
	}
837
}
838