Completed
Push — master ( 1fd6be...155036 )
by Zack
13s
created

GVLogic_Shortcode::set_is_match()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 6 and the first side effect is on line 391.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

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

Loading history...
2
3
/**
4
 * Shortcode to handle showing/hiding content in merge tags. Works great with GravityView Custom Content fields
5
 */
6
class GVLogic_Shortcode {
0 ignored issues
show
Coding Style introduced by
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
7
8
	private static $SUPPORTED_SCALAR_OPERATORS = array( 'is', 'isnot', 'contains', 'starts_with', 'ends_with' );
9
10
	private static $SUPPORTED_NUMERIC_OPERATORS = array( 'greater_than', 'less_than' );
11
12
	private static $SUPPORTED_ARRAY_OPERATORS = array( 'in', 'not_in', 'isnot', 'contains' );
13
14
	private static $SUPPORTED_CUSTOM_OPERATORS = array( 'equals', 'greater_than_or_is', 'greater_than_or_equals', 'less_than_or_is', 'less_than_or_equals', 'not_contains' );
15
16
	/**
17
	 * Attributes passed to the shortcode
18
	 * @var array
19
	 */
20
	var $passed_atts;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $passed_atts.

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...
21
22
	/**
23
	 * Content inside the shortcode, displayed if matched
24
	 * @var string
25
	 */
26
	var $passed_content;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $passed_content.

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...
27
28
	/**
29
	 * Parsed attributes
30
	 * @var array
31
	 */
32
	var $atts = array();
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $atts.

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...
33
34
	/**
35
	 * Parsed content, shown if matched
36
	 * @var string
37
	 */
38
	var $content = '';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $content.

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...
39
40
	/**
41
	 * Content shown if not matched
42
	 * This is set by having `[else]` inside the $content block
43
	 * @var string
44
	 */
45
	var $else_content = '';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $else_content.

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...
46
47
	/**
48
	 * The current shortcode name being processed
49
	 * @var string
50
	 */
51
	var $shortcode = 'gvlogic';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $shortcode.

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...
52
53
	/**
54
	 * The left side of the comparison
55
	 * @var string
56
	 */
57
	var $if = '';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $if.

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...
58
59
	/**
60
	 * The right side of the comparison
61
	 * @var string
62
	 */
63
	var $comparison = '';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $comparison.

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...
64
65
	/**
66
	 * The comparison operator
67
	 * @var string
68
	 */
69
	var $operation = 'is';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $operation.

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...
70
71
	/**
72
	 * Does the comparison pass?
73
	 * @var bool
74
	 */
75
	var $is_match = false;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $is_match.

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...
76
77
	/**
78
	 * @var GVLogic_Shortcode
79
	 */
80
	private static $instance;
81
82
	/**
83
	 * Instantiate!
84
	 * @return GVLogic_Shortcode
85
	 */
86
	public static function get_instance() {
87
88
		if( empty( self::$instance ) ) {
89
			self::$instance = new self;
90
		}
91
92
		return self::$instance;
93
	}
94
95
	/**
96
	 * Add the WordPress hooks
97
	 * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
98
	 */
99
	private function __construct() {
100
		$this->add_hooks();
101
	}
102
103
	/**
104
	 * Register the shortcode
105
	 * @return void
106
	 */
107
	private function add_hooks() {
108
		add_shortcode( 'gvlogic', array( $this, 'shortcode' ) );
109
		add_shortcode( 'gvlogicelse', array( $this, 'shortcode' ) );
110
	}
111
112
	/**
113
	 * Get array of supported operators
114
	 * @param bool $with_values
115
	 *
116
	 * @return array
117
	 */
118 2
	private function get_operators( $with_values = false ) {
119
120 2
		$operators = array_merge( self::$SUPPORTED_ARRAY_OPERATORS, self::$SUPPORTED_NUMERIC_OPERATORS, self::$SUPPORTED_SCALAR_OPERATORS, self::$SUPPORTED_CUSTOM_OPERATORS );
121
122 2
		if( $with_values ) {
123 2
			$operators_with_values = array();
124 2
			foreach( $operators as $key ) {
125 2
				$operators_with_values[ $key ] = '';
126
			}
127 2
			return $operators_with_values;
128
		} else {
129 2
			return $operators;
130
		}
131
	}
132
133
	/**
134
	 * Set the operation for the shortcode.
135
	 * @param string $operation
136
	 *
137
	 * @return bool True: it's an allowed operation type and was added. False: invalid operation type
138
	 */
139 2
	private function set_operation( $operation = '' ) {
140
141 2
		if( empty( $operation ) ) {
142
			return false;
143
		}
144
145 2
		$operators = $this->get_operators( false );
146
147 2
		if( !in_array( $operation, $operators ) ) {
0 ignored issues
show
introduced by
Expected 1 space after "!"; 0 found
Loading history...
148
			do_action( 'gravityview_log_debug', __METHOD__ .' Attempted to add invalid operation type.', $operation );
149
			return false;
150
		}
151
152 2
		$this->operation = $operation;
153 2
		return true;
154
	}
155
156
	/**
157
	 * Set the operation and comparison for the shortcode
158
	 *
159
	 * Loop through each attribute passed to the shortcode and see if it's a valid operator. If so, set it.
160
	 * Example: [gvlogic if="{example}" greater_than="5"]
161
	 * `greater_than` will be set as the operator
162
	 * `5` will be set as the comparison value
163
	 *
164
	 * @return bool True: we've got an operation and comparison value; False: no, we don't
165
	 */
166 2
	private function setup_operation_and_comparison() {
167
168 2
		foreach( $this->atts as $key => $value ) {
169
170 2
			$valid = $this->set_operation( $key );
171
172 2
			if( $valid ) {
173 2
				$this->comparison = $value;
174 2
				return true;
175
			}
176
		}
177
178
		return false;
179
	}
180
181
	/**
182
	 * @param array $atts User defined attributes in shortcode tag.
183
	 * @param null $content
184
	 * @param string $shortcode_tag
185
	 *
186
	 * @return string|null
187
	 */
188 3
	public function shortcode( $atts = array(), $content = NULL, $shortcode_tag = '' ) {
0 ignored issues
show
Coding Style introduced by
TRUE, FALSE and NULL must be lowercase; expected null, but found NULL.
Loading history...
189
190
		// Don't process except on frontend
191 3
		if ( defined( 'GRAVITYVIEW_FUTURE_CORE_LOADED' ) && gravityview()->request->is_admin() ) {
192
			return null;
193
			/** Deprecated in favor of gravityview()->request->is_admin(). */
194 3
		} else if ( GravityView_Plugin::is_admin() ) {
0 ignored issues
show
Deprecated Code introduced by
The method GravityView_Plugin::is_admin() has been deprecated.

This method has been deprecated.

Loading history...
195
			return null;
196
		}
197
198 3
		if( empty( $atts ) ) {
199
			do_action( 'gravityview_log_error', __METHOD__.' $atts are empty.', $atts );
200
			return null;
201
		}
202
203 3
		$this->passed_atts = $atts;
204 3
		$this->passed_content = $content;
205 3
		$this->content = '';
206 3
		$this->else_content = '';
207 3
		$this->atts = array();
208 3
		$this->shortcode = $shortcode_tag;
209
210 3
		$this->parse_atts();
211
212
		// We need an "if"
213 3
		if( false === $this->if ) {
214
			do_action( 'gravityview_log_error', __METHOD__.' $atts->if is empty.', $this->passed_atts );
215
			return null;
216
		}
217
218 3
		$setup = $this->setup_operation_and_comparison();
219
220
		// We need an operation and comparison value
221 3
		if( ! $setup ) {
222
			do_action( 'gravityview_log_error', __METHOD__.' No valid operators were passed.', $this->atts );
223
			return null;
224
		}
225
226
		// Check if it's a match
227 3
		$this->set_is_match();
228
229
		// Set the content and else_content
230 3
		$this->set_content_and_else_content();
231
232
		// Return the value!
233 3
		$output = $this->get_output();
234
235 3
		return $output;
236
	}
237
238
	/**
239
	 * Does the if and the comparison match?
240
	 * @uses GVCommon::matches_operation
241
	 *
242
	 * @return void
243
	 */
244 2
	private function set_is_match() {
245 2
		$this->is_match = GVCommon::matches_operation( $this->if, $this->comparison, $this->operation );
246 2
	}
247
248
	/**
249
	 * Get the output for the shortcode, based on whether there's a matched value
250
	 *
251
	 * @return string HTML/Text output of the shortcode
252
	 */
253 2
	private function get_output() {
254
255 2
		if( $this->is_match ) {
256 2
			$output = $this->content;
257
		} else {
258 2
			$output = $this->else_content;
259
		}
260
261
		// Get recursive!
262 2
		$output = do_shortcode( $output );
263
264
		/**
265
		 * @filter `gravityview/gvlogic/output` Modify the [gvlogic] output
266
		 * @param string $output HTML/text output
267
		 * @param GVLogic_Shortcode $this This class
268
		 */
269 2
		$output = apply_filters('gravityview/gvlogic/output', $output, $this );
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
270
271 2
		do_action( 'gravityview_log_debug', __METHOD__ .' Output: ', $output );
272
273 2
		return $output;
274
	}
275
276
	/**
277
	 * Check for `[else]` tag inside the shortcode content. If exists, set the else_content variable.
278
	 * If not, use the `else` attribute passed by the shortcode, if exists.
279
	 *
280
	 * @return void
281
	 */
282 3
	private function set_content_and_else_content() {
283
284 3
		$passed_content = $this->passed_content;
285
286 3
		list( $before_else, $after_else ) = array_pad( explode( '[else]', $passed_content ), 2, NULL );
0 ignored issues
show
Coding Style introduced by
TRUE, FALSE and NULL must be lowercase; expected null, but found NULL.
Loading history...
287 3
		list( $before_else_if, $after_else_if ) = array_pad( explode( '[else', $passed_content ), 2, NULL );
0 ignored issues
show
Coding Style introduced by
TRUE, FALSE and NULL must be lowercase; expected null, but found NULL.
Loading history...
288
289 3
		$else_attr = isset( $this->atts['else'] ) ? $this->atts['else'] : NULL;
0 ignored issues
show
Coding Style introduced by
TRUE, FALSE and NULL must be lowercase; expected null, but found NULL.
Loading history...
290 3
		$else_content = isset( $after_else ) ? $after_else : $else_attr;
291
292
		// The content is everything OTHER than the [else]
293 3
		$this->content = $before_else_if;
294
295 3
		if ( ! $this->is_match ) {
296 3
			if( $elseif_content = $this->process_elseif( $before_else ) ) {
297 1
				$this->else_content = $elseif_content;
298
			} else {
299 3
				$this->else_content = $else_content;
300
			}
301
		}
302 3
	}
303
304
	/**
305
	 * Handle additional conditional logic inside the [else] pseudo-shortcode
306
	 *
307
	 * @since 1.21.2
308
	 *
309
	 * @param string $before_else Shortcode content before the [else] tag (if it exists)
310
	 *
311
	 * @return bool|string False: No [else if] statements found. Otherwise, return the matched content.
312
	 */
313 3
	private function process_elseif( $before_else ) {
314
315 3
		$regex = get_shortcode_regex( array( 'else' ) );
316
317
		// 2. Check if there are any ELSE IF statements
318 3
		preg_match_all( '/' . $regex . '/', $before_else . '[/else]', $else_if_matches, PREG_SET_ORDER );
319
320
		// 3. The ELSE IF statements that remain should be processed to see if they are valid
321 3
		foreach ( $else_if_matches as $key => $else_if_match ) {
322
323
			// If $else_if_match[5] exists and has content, check for more shortcodes
324 1
			preg_match_all( '/' . $regex . '/', $else_if_match[5] . '[/else]', $recursive_matches, PREG_SET_ORDER );
325
326
			// If the logic passes, this is the value that should be used for $this->else_content
327 1
			$else_if_value = $else_if_match[5];
328 1
			$check_elseif_match = $else_if_match[0];
329
330
			// Retrieve the value of the match that is currently being checked, without any other [else] tags
331 1
			if( ! empty( $recursive_matches[0][0] ) ) {
332 1
				$else_if_value = str_replace( $recursive_matches[0][0], '', $else_if_value );
333 1
				$check_elseif_match = str_replace( $recursive_matches[0][0], '', $check_elseif_match );
334
			}
335
336 1
			$check_elseif_match = str_replace( '[else', '[gvlogicelse', $check_elseif_match );
337 1
			$check_elseif_match = str_replace( '[/else', '[/gvlogicelse', $check_elseif_match );
338
339
			// Make sure to close the tag
340 1
			if ( '[/gvlogicelse]' !== substr( $check_elseif_match, -14, 14 ) ) {
341 1
				$check_elseif_match .= '[/gvlogicelse]';
342
			}
343
344
			// The shortcode returned a value; it was a match
345 1
			if ( $result = do_shortcode( $check_elseif_match ) ) {
346 1
				return $else_if_value;
347
			}
348
349
			// Process any remaining [else] tags
350 1
			return $this->process_elseif( $else_if_match[5] );
351
		}
352
353 3
		return false;
354
	}
355
356
	/**
357
	 * Process the attributes passed to the shortcode. Make sure they're valid
358
	 * @return void
359
	 */
360 2
	private function parse_atts() {
361
362
		$supported = array(
363 2
			'if' => false,
364
			'else' => false,
365
		);
366
367 2
		$supported_args = $supported + $this->get_operators( true );
368
369
		// Whittle down the attributes to only valid pairs
370 2
		$this->atts = shortcode_atts( $supported_args, $this->passed_atts, $this->shortcode );
371
372
		// Only keep the passed attributes after making sure that they're valid pairs
373 2
		$this->atts = function_exists( 'array_intersect_key' ) ? array_intersect_key( $this->passed_atts, $this->atts ) : $this->atts;
374
375
		// Strip whitespace if it's not default false
376 2
		$this->if = ( isset( $this->atts['if'] ) && is_string( $this->atts['if'] ) ) ? trim( $this->atts['if'] ) : false;
377
378
		/**
379
		 * @action `gravityview/gvlogic/parse_atts/after` Modify shortcode attributes after it's been parsed
380
		 * @see https://gist.github.com/zackkatz/def9b295b80c4ae109760ffba200f498 for an example
381
		 * @since 1.21.5
382
		 * @param GVLogic_Shortcode $this The GVLogic_Shortcode instance
383
		 */
384 2
		do_action( 'gravityview/gvlogic/parse_atts/after', $this );
385
386
		// Make sure the "if" isn't processed in self::setup_operation_and_comparison()
387 2
		unset( $this->atts['if'] );
388 2
	}
389
}
390
391
GVLogic_Shortcode::get_instance();
392