Completed
Push — master ( 2ddace...f4e16f )
by Zack
07:25 queued 01:59
created

GravityView_Field::is_choice_value_enabled()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 13
nc 6
nop 0
dl 0
loc 26
rs 8.5806
c 0
b 0
f 0
1
<?php
2
/**
3
 * @file class-gravityview-field.php
4
 * @package GravityView
5
 * @subpackage includes\fields
6
 */
7
8
/**
9
 * Modify field settings by extending this class.
10
 */
11
abstract class GravityView_Field {
12
13
	/**
14
	 * The name of the GravityView field type
15
	 * Example: `created_by`, `text`, `fileupload`, `address`, `entry_link`
16
	 * @var string
17
	 */
18
	public $name;
19
20
	/**
21
	 * @internal Not yet implemented
22
	 * @since 1.15.2
23
	 * @type string The description of the field in the field picker
24
	 */
25
	public $description;
26
27
	/**
28
	 * @since 1.15.2
29
	 * @type string The label of the field in the field picker
30
	 */
31
	public $label;
32
33
	/**
34
	 * @var string The default search label used by the search widget, if not set
35
	 */
36
	public $default_search_label;
37
38
	/**
39
	 * `standard`, `advanced`, `post`, `pricing`, `meta`, `gravityview`
40
	 * @since 1.15.2
41
	 * @type string The group belongs to this field in the field picker
42
	 */
43
	public $group;
44
45
	/**
46
	 * @internal Not yet implemented
47
	 * @type boolean Can the field be searched?
48
	 * @since 1.15.2
49
	 */
50
	public $is_searchable;
51
52
	/**
53
	 * @internal Not yet implemented
54
	 * @type array $search_operators The type of search operators available for this field
55
	 * @since 1.15.2
56
	 */
57
	public $search_operators;
58
59
	/**
60
	 * @type boolean Can the field be sorted in search?
61
	 * @since 1.15.2
62
	 */
63
	public $is_sortable = true;
64
65
	/**
66
	 * @type boolean Is field content number-based?
67
	 * @since 1.15.2
68
	 */
69
	public $is_numeric;
70
71
	/**
72
	 * @var null|string The key used to search and sort entry meta in Gravity Forms. Used if the field stores data as custom entry meta.
73
	 * @see https://www.gravityhelp.com/documentation/article/gform_entry_meta/
74
	 * @since TODO
75
	 */
76
	public $entry_meta_key = null;
77
78
	/**
79
	 * @var string|array Optional. The callback function after entry meta is updated, only used if $entry_meta_key is set.
80
	 * @see https://www.gravityhelp.com/documentation/article/gform_entry_meta/
81
	 * @since TODO
82
	 */
83
	var $entry_meta_update_callback = null;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $entry_meta_update_callback.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
84
85
	/**
86
	 * @var bool Whether to show meta when set to true automatically adds the column to the entry list, without having to edit and add the column for display
87
	 * @since TODO
88
	 */
89
	var $entry_meta_is_default_column = false;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $entry_meta_is_default_column.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
90
91
	/**
92
	 * @internal Not yet implemented
93
	 * @todo implement supports_context() method
94
	 * The contexts in which a field is available. Some fields aren't editable, for example.
95
	 * - `singular` is an alias for both `single` and `edit`
96
	 * - `multiple` is an alias for `directory` (backward compatibility)
97
	 * @type array
98
	 * @since 1.15.2
99
	 */
100
	public $contexts = array( 'single', 'multiple', 'edit', 'export' );
101
102
	/**
103
	 * @since 1.15.2
104
	 * @since 1.16.2.2 Changed access to public (previously, protected)
105
	 * @type string The name of a corresponding Gravity Forms GF_Field class, if exists
106
	 */
107
	public $_gf_field_class_name;
108
109
	/**
110
	 * @var string The field ID being requested
111
	 * @since 1.14
112
	 */
113
	protected $_field_id = '';
114
115
	/**
116
	 * @var string Field options to be rendered
117
	 * @since 1.14
118
	 */
119
	protected $_field_options = array();
120
121
	/**
122
	 * @var bool|string Name of merge tag (without curly brackets), if the field has custom GravityView merge tags to add. Otherwise, false.
123
	 * @since 1.16
124
	 */
125
	protected $_custom_merge_tag = false;
126
127
	/**
128
	 * GravityView_Field constructor.
129
	 */
130
	public function __construct() {
131
132
		// Modify the field options based on the name of the field type
133
		add_filter( sprintf( 'gravityview_template_%s_options', $this->name ), array( &$this, 'field_options' ), 10, 5 );
134
135
		add_filter( 'gravityview/sortable/field_blacklist', array( $this, '_filter_sortable_fields' ), 1 );
136
137
		if( $this->entry_meta_key ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entry_meta_key of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
138
			add_filter( 'gform_entry_meta', array( $this, 'add_entry_meta' ) );
139
			add_filter( 'gravityview/common/sortable_fields', array( $this, 'add_sortable_field' ), 10, 2 );
140
		}
141
142
		if( $this->_custom_merge_tag ) {
143
			add_filter( 'gform_custom_merge_tags', array( $this, '_filter_gform_custom_merge_tags' ), 10, 4 );
144
			add_filter( 'gform_replace_merge_tags', array( $this, '_filter_gform_replace_merge_tags' ), 10, 7 );
145
		}
146
147
		if( 'meta' === $this->group || '' !== $this->default_search_label ) {
148
			add_filter( 'gravityview_search_field_label', array( $this, 'set_default_search_label' ), 10, 3 );
149
		}
150
151
		GravityView_Fields::register( $this );
152
	}
153
154
	/**
155
	 * Add the field to the Filter & Sort available fields
156
	 *
157
	 * @since TODO
158
	 *
159
	 * @param array $fields Sub-set of GF form fields that are sortable
160
	 *
161
	 * @return array Modified $fields array to include approval status in the sorting dropdown
162
	 */
163
	public function add_sortable_field( $fields ) {
164
165
		$added_field = array(
166
			'label' => $this->label,
167
			'type'  => $this->name
0 ignored issues
show
introduced by
Each line in an array declaration must end in a comma
Loading history...
168
		);
169
170
		$fields["{$this->entry_meta_key}"] = $added_field;
0 ignored issues
show
introduced by
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
171
172
		return $fields;
173
	}
174
175
	/**
176
	 * Allow setting a default search label for search fields based on the field type
177
	 *
178
	 * Useful for entry meta "fields" that don't have Gravity Forms labels, like `created_by`
179
	 *
180
	 * @since 1.17.3
181
	 *
182
	 * @param string $label Existing label text, sanitized.
183
	 * @param array $gf_field Gravity Forms field array, as returned by `GFFormsModel::get_field()`
184
	 * @param array $field Field setting as sent by the GV configuration - has `field`, `input` (input type), and `label` keys
185
	 *
186
	 * @return string
187
	 */
188
	function set_default_search_label( $label = '', $gf_field = null, $field = array() ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
189
190
		if( $this->name === $field['field'] && '' === $label ) {
191
			$label = esc_html( $this->default_search_label );
192
		}
193
194
		return $label;
195
	}
196
197
	/**
198
	 * Match the merge tag in replacement text for the field.  DO NOT OVERRIDE.
199
	 *
200
	 * @see replace_merge_tag Override replace_merge_tag() to handle any matches
201
	 *
202
	 * @since 1.16
203
	 *
204
	 * @param string $text Text to replace
205
	 * @param array $form Gravity Forms form array
206
	 * @param array $entry Entry array
207
	 * @param bool $url_encode Whether to URL-encode output
208
	 *
209
	 * @return string Original text if {_custom_merge_tag} isn't found. Otherwise, replaced text.
210
	 */
211
	public function _filter_gform_replace_merge_tags( $text, $form = array(), $entry = array(), $url_encode = false, $esc_html = false  ) {
212
213
		/**
214
		 * This prevents the gform_replace_merge_tags filter from being called twice, as defined in:
215
		 * @see GFCommon::replace_variables()
216
		 * @see GFCommon::replace_variables_prepopulate()
217
		 * @todo Remove eventually: Gravity Forms fixed this issue in 1.9.14
218
		 */
219
		if( false === $form ) {
220
			return $text;
221
		}
222
223
		// Is there is field merge tag? Strip whitespace off the ned, too.
224
		preg_match_all( '/{' . preg_quote( $this->_custom_merge_tag ) . ':?(.*?)(?:\s)?}/ism', $text, $matches, PREG_SET_ORDER );
225
226
		// If there are no matches, return original text
227
		if ( empty( $matches ) ) {
228
			return $text;
229
		}
230
231
		return $this->replace_merge_tag( $matches, $text, $form, $entry, $url_encode, $esc_html );
232
	}
233
234
	/**
235
	 * Run GravityView filters when using GFCommon::replace_variables()
236
	 *
237
	 * Instead of adding multiple hooks, add all hooks into this one method to improve speed
238
	 *
239
	 * @since 1.8.4
240
	 *
241
	 * @param array $matches Array of Merge Tag matches found in text by preg_match_all
242
	 * @param string $text Text to replace
243
	 * @param array|bool $form Gravity Forms form array. When called inside {@see GFCommon::replace_variables()} (now deprecated), `false`
244
	 * @param array|bool $entry Entry array.  When called inside {@see GFCommon::replace_variables()} (now deprecated), `false`
245
	 * @param bool $url_encode Whether to URL-encode output
246
	 * @param bool $esc_html Whether to apply `esc_html()` to output
247
	 *
248
	 * @return mixed
249
	 */
250
	public function replace_merge_tag( $matches = array(), $text = '', $form = array(), $entry = array(), $url_encode = false, $esc_html = false ) {
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
251
252
		foreach( $matches as $match ) {
253
254
			$full_tag = $match[0];
255
256
			// Strip the Merge Tags
257
			$tag = str_replace( array( '{', '}'), '', $full_tag );
0 ignored issues
show
introduced by
No space before closing parenthesis of array is bad style
Loading history...
258
259
			// Replace the value from the entry, if exists
260
			if( isset( $entry[ $tag ] ) ) {
261
262
				$value = $entry[ $tag ];
263
264
				if( is_callable( array( $this, 'get_content') ) ) {
0 ignored issues
show
introduced by
No space before closing parenthesis of array is bad style
Loading history...
265
					$value = $this->get_content( $value );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class GravityView_Field as the method get_content() does only exist in the following sub-classes of GravityView_Field: GravityView_Field_Date_Created, GravityView_Field_Is_Fulfilled, GravityView_Field_Payment_Amount, GravityView_Field_Payment_Date, GravityView_Field_Transaction_Type. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
266
				}
267
268
				$text = str_replace( $full_tag, $value, $text );
269
			}
270
		}
271
272
		unset( $value, $tag, $full_tag );
273
274
		return $text;
275
	}
276
277
	/**
278
	 * Add custom merge tags to merge tag options. DO NOT OVERRIDE.
279
	 *
280
	 * @internal Not to be overridden by fields
281
	 *
282
	 * @since 1.8.4
283
	 *
284
	 * @param array $custom_merge_tags
285
	 * @param int $form_id GF Form ID
286
	 * @param GF_Field[] $fields Array of fields in the form
287
	 * @param string $element_id The ID of the input that Merge Tags are being used on
288
	 *
289
	 * @return array Modified merge tags
290
	 */
291
	public function _filter_gform_custom_merge_tags( $custom_merge_tags = array(), $form_id, $fields = array(), $element_id = '' ) {
0 ignored issues
show
Unused Code introduced by
The parameter $element_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
292
293
		$form = GVCommon::get_form( $form_id );
294
295
		$field_merge_tags = $this->custom_merge_tags( $form, $fields );
296
297
		return array_merge( $custom_merge_tags, $field_merge_tags );
298
	}
299
300
	/**
301
	 * Add custom Merge Tags to Merge Tag options, if custom Merge Tags exist
302
	 *
303
	 * Should be overridden if there's more than one Merge Tag to add or if the Merge Tag isn't {_custom_merge_tag}
304
	 *
305
	 * @since 1.16
306
	 *
307
	 * @param array $form GF Form array
308
	 * @param GF_Field[] $fields Array of fields in the form
309
	 *
310
	 * @return array Merge tag array with `label` and `tag` keys based on class `label` and `_custom_merge_tag` variables
311
	 */
312
	protected function custom_merge_tags( $form = array(), $fields = array() ) {
0 ignored issues
show
Unused Code introduced by
The parameter $form is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $fields is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
313
314
		// Use variables to make it unnecessary for other fields to override
315
		$merge_tags = array(
316
			array(
317
				'label' => $this->label,
318
				'tag' => '{' . $this->_custom_merge_tag . '}',
319
			),
320
		);
321
322
		return $merge_tags;
323
	}
324
325
	/**
326
	 * Use field settings to modify whether a field is sortable
327
	 *
328
	 * @see GravityView_frontend::is_field_sortable
329
	 * @since 1.15.3
330
	 *
331
	 * @param array $not_sortable Existing field types that aren't sortable
332
	 *
333
	 * @return array
334
	 */
335
	public function _filter_sortable_fields( $not_sortable ) {
336
337
		if( ! $this->is_sortable ) {
338
			$not_sortable[] = $this->name;
339
		}
340
341
		return $not_sortable;
342
	}
343
344
	/**
345
	 * Add the custom entry meta key to make it searchable and sortable
346
	 *
347
	 * @see https://www.gravityhelp.com/documentation/article/gform_entry_meta/
348
	 *
349
	 * @param array $entry_meta Array of custom entry meta keys with associative arrays
350
	 *
351
	 * @return mixed
352
	 */
353
	function add_entry_meta( $entry_meta ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
354
355
		if( ! isset( $entry_meta["{$this->entry_meta_key}"] ) ) {
0 ignored issues
show
introduced by
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
356
357
			$added_meta = array(
358
				'label'             => $this->label,
359
				'is_numeric'        => $this->is_numeric,
360
				'is_default_column' => $this->entry_meta_is_default_column,
361
			);
362
363
			if ( $this->entry_meta_update_callback && is_callable( $this->entry_meta_update_callback ) ) {
364
				$added_meta['update_entry_meta_callback'] = $this->entry_meta_update_callback;
365
			}
366
367
			$entry_meta["{$this->entry_meta_key}"] = $added_meta;
0 ignored issues
show
introduced by
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
368
369
		} else {
370
			do_action( 'gravityview_log_error', __METHOD__ . ' Entry meta already set: ' . $this->entry_meta_key, $entry_meta["{$this->entry_meta_key}"] );
0 ignored issues
show
introduced by
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
371
		}
372
373
		return $entry_meta;
374
	}
375
376
	private function field_support_options() {
377
		$options = array(
378
			'link_to_post' => array(
379
				'type' => 'checkbox',
380
				'label' => __( 'Link to the post', 'gravityview' ),
381
				'desc' => __( 'Link to the post created by the entry.', 'gravityview' ),
382
				'value' => false,
383
			),
384
			'link_to_term' => array(
385
				'type' => 'checkbox',
386
				'label' => __( 'Link to the category or tag', 'gravityview' ),
387
				'desc' => __( 'Link to the current category or tag. "Link to single entry" must be unchecked.', 'gravityview' ),
388
				'value' => false,
389
			),
390
			'dynamic_data' => array(
391
				'type' => 'checkbox',
392
				'label' => __( 'Use the live post data', 'gravityview' ),
393
				'desc' => __( 'Instead of using the entry data, instead use the current post data.', 'gravityview' ),
394
				'value' => true,
395
			),
396
			'date_display' => array(
397
				'type' => 'text',
398
				'label' => __( 'Override Date Format', 'gravityview' ),
399
				'desc' => sprintf( __( 'Define how the date is displayed (using %sthe PHP date format%s)', 'gravityview'), '<a href="https://codex.wordpress.org/Formatting_Date_and_Time">', '</a>' ),
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
400
				/**
401
				 * @filter `gravityview_date_format` Override the date format with a [PHP date format](https://codex.wordpress.org/Formatting_Date_and_Time)
402
				 * @param[in,out] null|string $date_format Date Format (default: null)
403
				 */
404
				'value' => apply_filters( 'gravityview_date_format', null )
405
			),
406
			'new_window' => array(
407
				'type' => 'checkbox',
408
				'label' => __( 'Open link in a new tab or window?', 'gravityview' ),
409
				'value' => false,
410
			),
411
		);
412
413
		/**
414
		 * @filter `gravityview_field_support_options` Modify the settings that a field supports
415
		 * @param array $options Options multidimensional array with each key being the input name, with each array setting having `type`, `label`, `desc` and `value` (default values) keys
416
		 */
417
		return apply_filters( 'gravityview_field_support_options', $options );
418
	}
419
420
	function add_field_support( $key = '', &$field_options ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
421
422
		$options = $this->field_support_options();
423
424
		if( isset( $options[ $key ] ) ) {
425
			$field_options[ $key ] = $options[ $key ];
426
		}
427
428
		return $field_options;
429
	}
430
431
	/**
432
	 * Tap in here to modify field options.
433
	 *
434
	 * Here's an example:
435
	 *
436
	 * <pre>
437
	 * $field_options['name_display'] = array(
438
	 * 	'type' => 'select',
439
	 * 	'label' => __( 'User Format', 'gravityview' ),
440
	 * 	'desc' => __( 'How should the User information be displayed?', 'gravityview'),
441
	 * 	'choices' => array(
442
	 * 		array(
443
	 *		 	'value' => 'display_name',
444
	 *		  	'label' => __('Display Name (Example: "Ellen Ripley")', 'gravityview'),
445
	 *		),
446
	 *  	array(
447
	 *			'value' => 'user_login',
448
	 *			'label' => __('Username (Example: "nostromo")', 'gravityview')
449
	 *		),
450
	 * 	 'value' => 'display_name'
451
	 * );
452
	 * </pre>
453
	 *
454
	 * @param  array      $field_options [description]
455
	 * @param  string      $template_id   [description]
456
	 * @param  string      $field_id      [description]
457
	 * @param  string      $context       [description]
458
	 * @param  string      $input_type    [description]
459
	 * @return array                     [description]
460
	 */
461
	public function field_options( $field_options, $template_id, $field_id, $context, $input_type ) {
462
463
		$this->_field_options = $field_options;
464
		$this->_field_id = $field_id;
465
466
		return $field_options;
467
	}
468
469
	/**
470
	 * Check whether the `enableChoiceValue` flag is set for a GF field
471
	 *
472
	 * Gets the current form ID, gets the field at that ID, then checks for the enableChoiceValue value.
473
	 *
474
	 * @access protected
475
	 *
476
	 * @uses GFAPI::get_form
477
	 *
478
	 * @since 1.17
479
	 *
480
	 * @return bool True: Enable Choice Value is active for field; False: not active, or form invalid, or form not found.
481
	 */
482
	protected function is_choice_value_enabled() {
483
484
		// If "Add Field" button is processing, get the Form ID
485
		$connected_form = rgpost( 'form_id' );
486
487
		// Otherwise, get the Form ID from the Post page
488
		if( empty( $connected_form ) ) {
489
			$connected_form = gravityview_get_form_id( get_the_ID() );
490
		}
491
492
		if( empty( $connected_form ) ) {
493
			do_action( 'gravityview_log_error', sprintf( '%s: Form not found for form ID "%s"', __METHOD__, $connected_form ) );
494
			return false;
495
		}
496
497
		$form = GFAPI::get_form( $connected_form );
498
499
		if ( ! $form ) {
500
			do_action( 'gravityview_log_error', sprintf( '%s: Form not found for field ID of "%s", when checking for a form with ID of "%s"', __METHOD__, $this->_field_id, $connected_form ) );
501
			return false;
502
		}
503
504
		$field = gravityview_get_field( $form, $this->_field_id );
505
506
		return ! empty( $field->enableChoiceValue );
507
	}
508
509
}
510