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 ); |
|
|
|
|
287
|
3 |
|
list( $before_else_if, $after_else_if ) = array_pad( explode( '[else', $passed_content ), 2, NULL ); |
|
|
|
|
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
|
|
|
|
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.