Completed
Branch FET-9795-new-interfaces (4c886e)
by
unknown
196:07 queued 180:18
created

EE_Messages_Validator::validate()   F

Complexity

Conditions 26
Paths 872

Size

Total Lines 122
Code Lines 72

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 26
eloc 72
c 1
b 1
f 0
nc 872
nop 0
dl 0
loc 122
rs 2.2136

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
if (!defined('EVENT_ESPRESSO_VERSION') )
4
	exit('NO direct script access allowed');
5
6
/**
7
 * EE_Messages_Validator class
8
 *
9
 * This class is the parent class for handling validation of message template fields.
10
 * Children classes follow a certain naming format
11
 * (i.e. /email/EE_Messages_Email_Payment_Validator.class.php)
12
 * and they simply serve the function of defining any special validation rules
13
 * for the context->field for that messenger/message_type combination when templates are edited.
14
 *
15
 * @abstract
16
 * @package		Event Espresso
17
 * @subpackage	includes/core/messages/validators/EE_Messages_Validator.core.php
18
 * @author		Darren Ethier
19
 *
20
 * ------------------------------------------------------------------------
21
 */
22
abstract class EE_Messages_Validator extends EE_Base {
23
24
25
26
	/**
27
	 * These properties just hold the name for the Messenger and Message Type (defined by child classes).
28
	 * These are used for retrieving objects etc.
29
	 *
30
*@var string
31
	 */
32
	protected $_m_name;
33
	protected $_mt_name;
34
35
36
37
	/**
38
	 * This will hold any error messages from the validation process.
39
	 *
40
	 * The _errors property holds an associative array of error messages
41
	 * listing the field as the key and the message as the value.
42
	 *
43
	 * @var array()
44
	 */
45
	private $_errors = array();
46
47
48
49
50
	/**
51
	 * holds an array of fields being validated
52
	 * @var string
53
	 */
54
	protected $_fields;
55
56
57
58
	/**
59
	 * this will hold the incoming context
60
	 * @var string
61
	 */
62
	protected $_context;
63
64
65
66
67
	/**
68
	 * this holds an array of fields and the relevant validation information
69
	 * that the incoming fields data get validated against.
70
	 * This gets setup in the _set_props() method.
71
	 *
72
	 * @var array
73
	 */
74
	protected $_validators;
75
76
77
78
79
	/**
80
	 * holds the messenger object
81
	 * @var object
82
	 */
83
	protected $_messenger;
84
85
86
87
	/**
88
	 * holds the message type object
89
	 * @var object
90
	 */
91
	protected $_message_type;
92
93
94
95
	/**
96
	 * will hold any valid_shortcode modifications made by the _modify_validator() method.
97
	 * @var array
98
	 */
99
	protected $_valid_shortcodes_modifier;
100
101
102
103
	/**
104
	 * There may be times where a message type wants to include a shortcode group but exclude specific
105
	 * shortcodes.  If that's the case then it can set this property as an array of shortcodes to exclude and
106
	 * they will not be allowed.
107
	 * Array should be indexed by field and values are an array of specific shortcodes to exclude.
108
	 * @var array
109
	 */
110
	protected $_specific_shortcode_excludes = array();
111
112
113
114
	/**
115
	 * Runs the validator using the incoming fields array as the fields/values to check.
116
	 *
117
	 *
118
	 * @param array $fields The fields sent by the EEM object.
119
	 * @param       $context
120
	 * @throws \EE_Error
121
	 */
122
	public function __construct( $fields, $context ) {
123
		//check that _m_name and _mt_name have been set by child class otherwise we get out.
124
		if ( empty($this->_m_name ) || empty( $this->_mt_name) )
125
			throw new EE_Error(
126
				__(
127
					'EE_Messages_Validator child classes MUST set the $_m_name and $_mt_name property.  Check that the child class is doing this',
128
					'event_espresso'
129
				)
130
			);
131
		$this->_fields = $fields;
0 ignored issues
show
Documentation Bug introduced by
It seems like $fields of type array is incompatible with the declared type string of property $_fields.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
132
		$this->_context = $context;
133
134
		//load messenger and message_type objects and the related shortcode objects.
135
		$this->_load_objects();
136
137
138
		//modify any messenger/message_type specific validation instructions.  This is what child classes define.
139
		$this->_modify_validator();
140
141
142
		//let's set validators property
143
		$this->_set_validators();
144
	}
145
146
147
148
	/**
149
	 * Child classes instantiate this and use it to modify the _validator_config array property
150
	 * for the messenger using messengers set_validate_config() method.
151
	 * This is so we can specify specific validation instructions for a messenger/message_type combo
152
	 * that aren't handled by the defaults setup in the messenger.
153
	 *
154
	 * @abstract
155
	 * @access protected
156
	 * @return void
157
	 */
158
	abstract protected function _modify_validator();
159
160
161
162
	/**
163
	 * loads all objects used by validator
164
	 *
165
	 * @access private
166
	 * @throws \EE_Error
167
	 */
168
	private function _load_objects() {
169
		//load messenger
170
		$messenger = ucwords( str_replace( '_', ' ', $this->_m_name ) );
171
		$messenger = str_replace( ' ', '_', $messenger );
172
		$messenger = 'EE_' . $messenger . '_messenger';
173
174 View Code Duplication
		if ( ! class_exists( $messenger ) ) {
175
			throw new EE_Error(
176
				sprintf(
177
					__( 'There is no messenger class for the given string (%s)', 'event_espresso' ),
178
					$this->_m_name
179
				)
180
			);
181
		}
182
183
		$this->_messenger = new $messenger();
184
185
		//load message type
186
		$message_type = ucwords( str_replace( '_', ' ', $this->_mt_name ) );
187
		$message_type = str_replace( ' ', '_', $message_type );
188
		$message_type = 'EE_' . $message_type . '_message_type';
189
190 View Code Duplication
		if ( !class_exists( $message_type ) ) {
191
			throw new EE_Error(
192
				sprintf(
193
					__( 'There is no message type class for the given string (%s)', 'event_espresso' ),
194
					$this->_mt_name
195
				)
196
			);
197
		}
198
199
		$this->_message_type = new $message_type();
200
201
	}
202
203
204
205
	/**
206
	 * used to set the $_validators property
207
	 *
208
	 * @access private
209
	 * @return void
210
	 */
211
	private function _set_validators() {
212
		// let's get all valid shortcodes from mt and message type
213
		// (messenger will have its set in the _validator_config property for the messenger)
214
		$mt_codes = $this->_message_type->get_valid_shortcodes();
215
216
217
		//get messenger validator_config
218
		$msgr_validator = $this->_messenger->get_validator_config();
219
220
221
		//we only want the valid shortcodes for the given context!
222
		$context = $this->_context;
223
		$mt_codes = $mt_codes[$context];
224
225
		// in this first loop we're just getting all shortcode group indexes from the msgr_validator
226
		// into a single array (so we can get the appropriate shortcode objects for the groups)
227
		$shortcode_groups = $mt_codes;
228
		$groups_per_field = array();
229
230
		foreach ( $msgr_validator as $field => $config ) {
231
			if ( empty($config) || !isset($config['shortcodes']) )
232
				continue;  //Nothing to see here.
233
			$groups_per_field[$field] = array_intersect( $config['shortcodes'], $mt_codes );
234
			$shortcode_groups = array_merge( $config[ 'shortcodes'], $shortcode_groups );
235
		}
236
237
		$shortcode_groups = array_unique( $shortcode_groups);
238
239
		// okay now we've got our groups.
240
		// Let's get the codes from the objects into an array indexed by group for easy retrieval later.
241
		$codes_from_objs = array();
242
243
		foreach ( $shortcode_groups as $group ) {
244
			$ref = ucwords( str_replace('_', ' ', $group ) );
245
			$ref = str_replace( ' ', '_', $ref );
246
			$classname = 'EE_' . $ref . '_Shortcodes';
247
			if ( class_exists( $classname ) ) {
248
				$a = new ReflectionClass( $classname );
249
				$obj = $a->newInstance();
250
				$codes_from_objs[$group] = $obj->get_shortcodes();
251
			}
252
		}
253
254
255
		//let's just replace the $mt shortcode group indexes with the actual shortcodes (unique)
256
		$final_mt_codes = array();
257
		foreach ( $mt_codes as $group ) {
258
			$final_mt_codes = array_merge( $final_mt_codes, $codes_from_objs[$group] );
259
		}
260
261
		$mt_codes = $final_mt_codes;
262
263
264
		// k now in this next loop we're going to loop through $msgr_validator again
265
		// and setup the _validators property from the data we've setup so far.
266
		foreach ( $msgr_validator as $field => $config ) {
267
			//if required shortcode is not in our list of codes for the given field, then we skip this field.
268
			$required = isset($config['required'])
269
				? array_intersect($config['required'], array_keys($mt_codes))
270
				: true;
271
			if ( empty($required) )
272
				continue;
273
274
			//If we have an override then we use it to indicate the codes we want.
275
			if ( isset( $this->_valid_shortcodes_modifier[$context][$field] ) ) {
276
				$this->_validators[ $field ][ 'shortcodes' ] = $this->_reassemble_valid_shortcodes_from_group(
277
					$this->_valid_shortcodes_modifier[ $context ][ $field ],
278
					$codes_from_objs
279
				);
280
			}
281
282
			//if we have specific shortcodes for a field then we need to use them
283
			else if ( isset( $groups_per_field[$field] ) ) {
284
				$this->_validators[ $field ][ 'shortcodes' ] = $this->_reassemble_valid_shortcodes_from_group(
285
					$groups_per_field[ $field ],
286
					$codes_from_objs
287
				);
288
			}
289
290
			//if empty config then we're assuming we're just going to use the shortcodes from the message type context
291
			else if ( empty( $config ) ) {
292
				$this->_validators[$field]['shortcodes'] = $mt_codes;
293
			}
294
295
			//if we have specific shortcodes then we need to use them
296
			else if ( isset($config['specific_shortcodes'] ) ) {
297
				$this->_validators[$field]['shortcodes'] = $config['specific_shortcodes'];
298
			}
299
300
			//otherwise the shortcodes are what is set by the messenger for that field
301
			else {
302
				foreach ( $config['shortcodes'] as $group ) {
303
					$this->_validators[$field]['shortcodes'] = isset($this->_validators[$field]['shortcodes'])
304
						? array_merge( $this->_validators[$field]['shortcodes'], $codes_from_objs[$group] )
305
						: $codes_from_objs[$group];
306
				}
307
			}
308
309
			//now let's just make sure that any excluded specific shortcodes are removed.
310
			$specific_excludes = $this->get_specific_shortcode_excludes();
311
			if ( isset( $specific_excludes[$field] ) ) {
312
				foreach( $specific_excludes[$field] as $sex ) {
313
					if ( isset( $this->_validators[$field]['shortcodes'][$sex] ) )
314
						unset( $this->_validators[$field]['shortcodes'][$sex] );
315
				}
316
			}
317
318
			//hey! don't forget to include the type if present!
319
			$this->_validators[$field]['type'] = isset( $config['type'] ) ? $config['type'] : NULL;
320
		}
321
	}
322
323
324
	/**
325
	 * This just returns the validators property that contains information
326
	 * about the various shortcodes and their availability with each field
327
	 *
328
	 *
329
	 * @return array
330
	 */
331
	public function get_validators() {
332
		return $this->_validators;
333
	}
334
335
336
337
	/**
338
	 * This simply returns the specific shortcode_excludes property that is set.
339
	 *
340
	 * @since 4.5.0
341
	 *
342
	 * @return array
343
	 */
344
	public function get_specific_shortcode_excludes() {
345
		//specific validator filter
346
		$shortcode_excludes = apply_filters(
347
			'FHEE__' . get_class( $this ) . '__get_specific_shortcode_excludes;',
348
			$this->_specific_shortcode_excludes,
349
			$this->_context
350
		);
351
		//global filter
352
		return apply_filters(
353
			'FHEE__EE_Messages_Validator__get_specific_shortcode_excludes',
354
			$shortcode_excludes,
355
			$this->_context,
356
			$this
357
		);
358
	}
359
360
361
362
	/**
363
	 * This is the main method that handles validation
364
	 *
365
	 * What it does is loop through the _fields (the ones that get validated)
366
	 * and checks them against the shortcodes array for the field and the 'type' indicated by the
367
	 *
368
	 * @access public
369
	 * @return mixed (bool|array)  if errors present we return the array otherwise true
370
	 */
371
	public function validate() {
372
		//some defaults
373
		$template_fields = $this->_messenger->get_template_fields();
374
		//loop through the fields and check!
375
		foreach ( $this->_fields as $field => $value ) {
0 ignored issues
show
Bug introduced by
The expression $this->_fields of type string is not traversable.
Loading history...
376
			$this->_errors[$field] = array();
377
			$err_msg = '';
378
			$field_label = '';
379
			//if field is not present in the _validators array then we continue
380
			if ( !isset( $this->_validators[$field] ) ) {
381
				unset( $this->_errors[$field] );
382
				continue;
383
			}
384
385
			//get the translated field label!
386
			//first check if it's in the main fields list
387
			if ( isset( $template_fields[$field] ) ) {
388
				if ( empty( $template_fields[$field] ) )
389
					$field_label = $field; //most likely the field is found in the 'extra' array.
390
				else
391
					$field_label = $template_fields[$field]['label'];
392
			}
393
394
			// if field label is empty OR is equal to the current field
395
			// then we need to loop through the 'extra' fields in the template_fields config (if present)
396
			if ( isset( $template_fields['extra'] ) && ( empty($field_label) ) || $field_label == $field ) {
397
				foreach( $template_fields['extra'] as $main_field => $secondary_field ) {
398
					foreach ( $secondary_field as $name => $values ) {
399
						if ( $name == $field ) {
400
							$field_label = $values['label'];
401
						}
402
403
						// if we've got a 'main' secondary field, let's see if that matches what field we're on
404
						// which means it contains the label for this field.
405
						if ( $name == 'main' && $main_field == $field_label )
406
							$field_label = $values['label'];
407
					}
408
				}
409
			}
410
411
			//field is present. Let's validate shortcodes first (but only if shortcodes present).
412
			if (
413
				isset( $this->_validators[ $field ][ 'shortcodes' ] )
414
				&& ! empty( $this->_validators[ $field ][ 'shortcodes' ] )
415
			) {
416
				$invalid_shortcodes = $this->_invalid_shortcodes( $value, $this->_validators[$field]['shortcodes'] );
417
				// if true then that means there is a returned error message
418
				// that we'll need to add to the _errors array for this field.
419
				if ( $invalid_shortcodes ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $invalid_shortcodes of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
420
					$v_s = array_keys($this->_validators[$field]['shortcodes']);
421
					$err_msg = sprintf(
422
						__(
423
							'%3$sThe following shortcodes were found in the "%1$s" field that ARE not valid: %2$s%4$s',
424
							'event_espresso'
425
						),
426
						'<strong>' . $field_label . '</strong>',
427
						$invalid_shortcodes,
428
						'<p>',
429
						'</p >'
430
					);
431
					$err_msg .= sprintf(
432
						__( '%2$sValid shortcodes for this field are: %1$s%3$s', 'event_espresso' ),
433
						implode( ', ', $v_s ),
434
						'<strong>',
435
						'</strong>'
436
					);
437
				}
438
			}
439
440
			//if there's a "type" to be validated then let's do that too.
441
			if ( isset( $this->_validators[$field]['type'] ) && !empty( $this->_validators[$field]['type'] ) ) {
442
				switch ( $this->_validators[$field]['type'] ) {
443
					case 'number' :
444
						if ( !is_numeric($value) )
445
							$err_msg .= sprintf(
446
								__(
447
									'%3$sThe %1$s field is supposed to be a number. The value given (%2$s)  is not.  Please double-check and make sure the field contains a number%4$s',
448
									'event_espresso'
449
								),
450
								$field_label,
451
								$value,
452
								'<p>',
453
								'</p >'
454
							);
455
						break;
456
					case 'email' :
457
						$valid_email = $this->_validate_email($value);
458
						if ( !$valid_email )
459
							$err_msg .= htmlentities(
460
								sprintf(
461
									__(
462
										'The %1$s field has at least one string that is not a valid email address record.  Valid emails are in the format: "Name <[email protected]>" or "[email protected]" and multiple emails can be separated by a comma.'
463
									),
464
									$field_label
465
466
								)
467
							);
468
						break;
469
					default :
470
						break;
471
				}
472
			}
473
474
			//if $err_msg isn't empty let's setup the _errors array for this field.
475
			if ( !empty($err_msg ) ) {
476
				$this->_errors[$field]['msg'] = $err_msg;
477
			} else {
478
				unset( $this->_errors[$field] );
479
			}
480
		}
481
482
		// if we have ANY errors, then we want to make sure we return the values
483
		// for ALL the fields so the user doesn't have to retype them all.
484
		if ( !empty( $this->_errors ) ) {
485
			foreach ( $this->_fields as $field => $value ) {
0 ignored issues
show
Bug introduced by
The expression $this->_fields of type string is not traversable.
Loading history...
486
				$this->_errors[$field]['value'] = stripslashes($value);
487
			}
488
		}
489
490
		//return any errors or just TRUE if everything validates
491
		return empty( $this->_errors ) ? TRUE : $this->_errors;
492
	}
493
494
495
496
	/**
497
	 * Reassembles and returns an array of valid shortcodes
498
	 * given the array of groups and array of shortcodes indexed by group.
499
	 *
500
	 * @param  array $groups          array of shortcode groups that we want shortcodes for
501
	 * @param  array $codes_from_objs All the codes available.
502
	 * @return array                   an array of actual shortcodes (that will be used for validation).
503
	 */
504
	private function _reassemble_valid_shortcodes_from_group( $groups, $codes_from_objs ) {
505
		$shortcodes = array();
506
		foreach ( $groups as $group ) {
507
			$shortcodes = array_merge( $shortcodes, $codes_from_objs[$group] );
508
		}
509
		return $shortcodes;
510
	}
511
512
513
514
	/**
515
	 * Validates a string against a list of accepted shortcodes
516
	 *
517
	 * This function takes in an array of shortcodes
518
	 * and makes sure that the given string ONLY contains shortcodes in that array.
519
	 *
520
	 * @param  string $value            string to evaluate
521
	 * @param  array  $valid_shortcodes array of shortcodes that are acceptable.
522
	 * @return mixed (bool|string)  return either a list of invalid shortcodes OR false if the shortcodes validate.
523
	 */
524
	protected function _invalid_shortcodes($value, $valid_shortcodes) {
525
		//first we need to go through the string and get the shortcodes in the string
526
		preg_match_all( '/(\[.+?\])/', $value, $matches );
527
		$incoming_shortcodes = (array) $matches[0];
528
529
		//get a diff of the shortcodes in the string vs the valid shortcodes
530
		$diff = array_diff( $incoming_shortcodes, array_keys($valid_shortcodes) );
531
532
		//we need to account for custom codes so let's loop through the diff and remove any of those type of codes
533
		foreach ( $diff as $ind => $code ) {
534
			if ( preg_match('/(\[[A-Za-z0-9\_]+_\*)/', $code ) ) {
535
				//strip the shortcode so we just have the BASE string (i.e. [ANSWER_*] )
536
				$dynamic_sc = preg_replace('/(_\*+.+)/', '_*]', $code);
537
				//does this exist in the $valid_shortcodes?  If so then unset.
538
				if ( isset( $valid_shortcodes[$dynamic_sc] ) ) {
539
					unset( $diff[$ind] );
540
				}
541
			}
542
		}
543
544
		if ( empty( $diff ) ) return FALSE; //there is no diff, we have no invalid shortcodes, so return
545
546
		//made it here? then let's assemble the error message
547
		$invalid_shortcodes = implode( '</strong>,<strong>', $diff );
548
		$invalid_shortcodes = '<strong>' . $invalid_shortcodes . '</strong>';
549
		return $invalid_shortcodes;
550
	}
551
552
553
554
555
	/**
556
	 * Validates an incoming string and makes sure we have valid emails in the string.
557
	 * @param  string $value incoming value to validate
558
	 * @return bool        true if the string validates, false if it doesn't
559
	 */
560
	protected function _validate_email( $value ) {
561
		$validate = TRUE;
562
		$or_val = $value;
563
564
		// empty strings will validate because this is how a message template
565
		// for a particular context can be "turned off" (if there is no email then no message)
566
		if ( empty( $value ) )
567
			return $validate;
568
569
		// first determine if there ARE any shortcodes.
570
		// If there are shortcodes and then later we find that there were no other valid emails
571
		// but the field isn't empty...
572
		// that means we've got extra commas that were left after stripping out shortcodes so probably still valid.
573
		$has_shortcodes = preg_match('/(\[.+?\])/', $value);
574
575
		//first we need to strip out all the shortcodes!
576
		$value = preg_replace('/(\[.+?\])/', '', $value);
577
578
		// if original value is not empty and new value is, then we've parsed out a shortcode
579
		// and we now have an empty string which DOES validate.
580
		// We also validate complete empty field for email because
581
		// its possible that this message is being "turned off" for a particular context
582
583
584
		if ( !empty($or_val) && empty($value) )
585
			return $validate;
586
587
		//trim any commas from beginning and end of string ( after whitespace trimmed );
588
		$value = trim( trim($value), ',' );
589
590
591
		//next we need to split up the string if its comma delimited.
592
		$emails = explode(',', $value);
593
		$empty = FALSE; //used to indicate that there is an empty comma.
594
		//now let's loop through the emails and do our checks
595
		foreach ( $emails as $email ) {
596
			if ( empty($email) ) {
597
				$empty = TRUE;
598
				continue;
599
			}
600
601
			//trim whitespace
602
			$email = trim($email);
603
			//either its of type "[email protected]", or its of type "fname lname <[email protected]>"
604
			if(is_email($email)){
605
				continue;
606
			}else{
607
				$matches = array();
608
				$validate = preg_match( '/(.*)<(.+)>/', $email, $matches ) ? TRUE : FALSE;
609
				if( $validate && is_email($matches[2])){
610
					continue;
611
				}else{
612
					return false;
613
				}
614
			}
615
		}
616
617
		$validate = $empty && !$has_shortcodes ? FALSE : $validate;
618
619
		return $validate;
620
621
	}
622
623
624
625
626
	/**
627
	 * Magic getter
628
	 * Using this to provide back compat with add-ons referencing deprecated properties.
629
	 * @param string $property  Property being requested
630
	 * @throws Exception
631
	 * @return mixed
632
	 */
633
	public function __get( $property ) {
634
		$expected_properties_map = array(
635
			/**
636
			 * @deprecated 4.9.0
637
			 */
638
			'_MSGR' => '_messenger',
639
			/**
640
			 * @deprecated 4.9.0
641
			 */
642
			'_MSGTYP' => '_message_type'
643
		);
644
645
		if ( isset( $expected_properties_map[ $property ] ) ) {
646
			return $this->{$expected_properties_map[ $property ]};
647
		}
648
649
		throw new Exception(
650
			sprintf(
651
				__( 'The property %1$s being requested on %2$s does not exist', 'event_espresso' ),
652
				$property,
653
				get_class( $this )
654
			)
655
		);
656
	}
657
658
}
659