Completed
Push — master ( 1b7620...a4da27 )
by Zack
12s
created

includes/class-gvlogic-shortcode.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * Shortcode to handle showing/hiding content in merge tags. Works great with GravityView Custom Content fields
5
 */
6
class GVLogic_Shortcode {
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;
21
22
	/**
23
	 * Content inside the shortcode, displayed if matched
24
	 * @var string
25
	 */
26
	var $passed_content;
27
28
	/**
29
	 * Parsed attributes
30
	 * @var array
31
	 */
32
	var $atts = array();
33
34
	/**
35
	 * Parsed content, shown if matched
36
	 * @var string
37
	 */
38
	var $content = '';
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 = '';
46
47
	/**
48
	 * The current shortcode name being processed
49
	 * @var string
50
	 */
51
	var $shortcode = 'gvlogic';
52
53
	/**
54
	 * The left side of the comparison
55
	 * @var string
56
	 */
57
	var $if = '';
58
59
	/**
60
	 * The right side of the comparison
61
	 * @var string
62
	 */
63
	var $comparison = '';
64
65
	/**
66
	 * The comparison operator
67
	 * @var string
68
	 */
69
	var $operation = 'is';
70
71
	/**
72
	 * Does the comparison pass?
73
	 * @var bool
74
	 */
75
	var $is_match = false;
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
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 ) ) {
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 = '' ) {
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() ) {
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 );
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
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
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;
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