GravityView  2.1.1
The best, easiest way to display Gravity Forms entries on your website.
class-gvlogic-shortcode.php
Go to the documentation of this file.
1 <?php
2 
3 /**
4  * Shortcode to handle showing/hiding content in merge tags. Works great with GravityView Custom Content fields
5  */
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  */
21 
22  /**
23  * Content inside the shortcode, displayed if matched
24  * @var string
25  */
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  * @since 1.21.5
68  * @since 2.0 Changed default from "is" to "isnot"
69  * @var string
70  */
71  var $operation = 'isnot';
72 
73  /**
74  * Does the comparison pass?
75  * @var bool
76  */
77  var $is_match = false;
78 
79  /**
80  * @var GVLogic_Shortcode
81  */
82  private static $instance;
83 
84  /**
85  * Instantiate!
86  * @return GVLogic_Shortcode
87  */
88  public static function get_instance() {
89 
90  if( empty( self::$instance ) ) {
91  self::$instance = new self;
92  }
93 
94  return self::$instance;
95  }
96 
97  /**
98  * Add the WordPress hooks
99  * @return void
100  */
101  private function __construct() {
102  $this->add_hooks();
103  }
104 
105  /**
106  * Register the shortcode
107  * @return void
108  */
109  private function add_hooks() {
110  add_shortcode( 'gvlogic', array( $this, 'shortcode' ) );
111  add_shortcode( 'gvlogicelse', array( $this, 'shortcode' ) );
112  }
113 
114  /**
115  * Get array of supported operators
116  * @param bool $with_values
117  *
118  * @return array
119  */
120  private function get_operators( $with_values = false ) {
121 
122  $operators = array_merge( self::$SUPPORTED_ARRAY_OPERATORS, self::$SUPPORTED_NUMERIC_OPERATORS, self::$SUPPORTED_SCALAR_OPERATORS, self::$SUPPORTED_CUSTOM_OPERATORS );
123 
124  if( $with_values ) {
125  $operators_with_values = array();
126  foreach( $operators as $key ) {
127  $operators_with_values[ $key ] = '';
128  }
129  return $operators_with_values;
130  } else {
131  return $operators;
132  }
133  }
134 
135  /**
136  * Set the operation for the shortcode.
137  * @param string $operation
138  *
139  * @return bool True: it's an allowed operation type and was added. False: invalid operation type
140  */
141  private function set_operation( $operation = 'isnot' ) {
142 
143  $operators = $this->get_operators( false );
144 
145  if( !in_array( $operation, $operators ) ) {
146  gravityview()->log->debug( ' Attempted to add invalid operation type. {operation}', array( 'operation' => $operation ) );
147  return false;
148  }
149 
150  $this->operation = $operation;
151  return true;
152  }
153 
154  /**
155  * Set the operation and comparison for the shortcode
156  *
157  * Loop through each attribute passed to the shortcode and see if it's a valid operator. If so, set it.
158  * Example: [gvlogic if="{example}" greater_than="5"]
159  * `greater_than` will be set as the operator
160  * `5` will be set as the comparison value
161  *
162  * @return bool True: we've got an operation and comparison value; False: no, we don't
163  */
164  private function setup_operation_and_comparison() {
165 
166  if ( empty( $this->atts ) ) {
167  return true;
168  }
169 
170  foreach ( $this->atts as $key => $value ) {
171 
172  $valid = $this->set_operation( $key == 'else' ? 'isnot' : $key );
173 
174  if ( $valid ) {
175  $this->comparison = $key == 'else' ? '' : $value;
176  return true;
177  }
178  }
179 
180  return false;
181  }
182 
183  /**
184  * @param array $atts User defined attributes in shortcode tag.
185  * @param null $content
186  * @param string $shortcode_tag
187  *
188  * @return string|null
189  */
190  public function shortcode( $atts = array(), $content = NULL, $shortcode_tag = '' ) {
191 
192  // Don't process except on frontend
193  if ( gravityview()->request->is_admin() ) {
194  return null;
195  }
196 
197  if( empty( $atts ) ) {
198  gravityview()->log->error( '$atts are empty.', array( 'data' => $atts ) );
199  return null;
200  }
201 
202  $this->passed_atts = $atts;
203  $this->passed_content = $content;
204  $this->content = '';
205  $this->else_content = '';
206  $this->atts = array();
207  $this->shortcode = $shortcode_tag;
208 
209  $this->parse_atts();
210 
211  // We need an "if"
212  if( false === $this->if ) {
213  gravityview()->log->error( '$atts->if is empty.', array( 'data' => $this->passed_atts ) );
214  return null;
215  }
216 
217  $setup = $this->setup_operation_and_comparison();
218 
219  // We need an operation and comparison value
220  if( ! $setup ) {
221  gravityview()->log->error( 'No valid operators were passed.', array( 'data' => $this->atts ) );
222  return null;
223  }
224 
225  // Check if it's a match
226  $this->set_is_match();
227 
228  // Set the content and else_content
230 
231  // Return the value!
232  $output = $this->get_output();
233 
234  $this->reset();
235 
236  return $output;
237  }
238 
239  /**
240  * Restore the original settings for the shortcode
241  *
242  * @since 2.0 Needed because $atts can now be empty
243  *
244  * @return void
245  */
246  private function reset() {
247  $this->operation = 'isnot';
248  $this->comparison = '';
249  $this->passed_atts = array();
250  $this->passed_content = '';
251  }
252 
253  /**
254  * Does the if and the comparison match?
255  * @uses GVCommon::matches_operation
256  *
257  * @return void
258  */
259  private function set_is_match() {
260  $this->is_match = GVCommon::matches_operation( $this->if, $this->comparison, $this->operation );
261  }
262 
263  /**
264  * Get the output for the shortcode, based on whether there's a matched value
265  *
266  * @return string HTML/Text output of the shortcode
267  */
268  private function get_output() {
269 
270  if( $this->is_match ) {
272  } else {
274  }
275 
276  // Get recursive!
277  $output = do_shortcode( $output );
278 
279  if ( class_exists( 'GFCommon' ) ) {
280  $output = GFCommon::replace_variables( $output, array(), array(), false, true, false );
281  }
282 
283  /**
284  * @filter `gravityview/gvlogic/output` Modify the [gvlogic] output
285  * @param string $output HTML/text output
286  * @param GVLogic_Shortcode $this This class
287  */
288  $output = apply_filters('gravityview/gvlogic/output', $output, $this );
289 
290  gravityview()->log->debug( 'Output: ', array( 'data' => $output ) );
291 
292  return $output;
293  }
294 
295  /**
296  * Check for `[else]` tag inside the shortcode content. If exists, set the else_content variable.
297  * If not, use the `else` attribute passed by the shortcode, if exists.
298  *
299  * @return void
300  */
301  private function set_content_and_else_content() {
302 
303  $passed_content = $this->passed_content;
304 
305  list( $before_else, $after_else ) = array_pad( explode( '[else]', $passed_content ), 2, NULL );
306  list( $before_else_if, $after_else_if ) = array_pad( explode( '[else', $passed_content ), 2, NULL );
307 
308  $else_attr = isset( $this->atts['else'] ) ? $this->atts['else'] : NULL;
309  $else_content = isset( $after_else ) ? $after_else : $else_attr;
310 
311  // The content is everything OTHER than the [else]
312  $this->content = $before_else_if;
313 
314  if ( ! $this->is_match ) {
315  if( $elseif_content = $this->process_elseif( $before_else ) ) {
316  $this->else_content = $elseif_content;
317  } else {
318  $this->else_content = $else_content;
319  }
320  }
321  }
322 
323  /**
324  * Handle additional conditional logic inside the [else] pseudo-shortcode
325  *
326  * @since 1.21.2
327  *
328  * @param string $before_else Shortcode content before the [else] tag (if it exists)
329  *
330  * @return bool|string False: No [else if] statements found. Otherwise, return the matched content.
331  */
332  private function process_elseif( $before_else ) {
333 
334  $regex = get_shortcode_regex( array( 'else' ) );
335 
336  // 2. Check if there are any ELSE IF statements
337  preg_match_all( '/' . $regex . '/', $before_else . '[/else]', $else_if_matches, PREG_SET_ORDER );
338 
339  // 3. The ELSE IF statements that remain should be processed to see if they are valid
340  foreach ( $else_if_matches as $key => $else_if_match ) {
341 
342  // If $else_if_match[5] exists and has content, check for more shortcodes
343  preg_match_all( '/' . $regex . '/', $else_if_match[5] . '[/else]', $recursive_matches, PREG_SET_ORDER );
344 
345  // If the logic passes, this is the value that should be used for $this->else_content
346  $else_if_value = $else_if_match[5];
347  $check_elseif_match = $else_if_match[0];
348 
349  // Retrieve the value of the match that is currently being checked, without any other [else] tags
350  if( ! empty( $recursive_matches[0][0] ) ) {
351  $else_if_value = str_replace( $recursive_matches[0][0], '', $else_if_value );
352  $check_elseif_match = str_replace( $recursive_matches[0][0], '', $check_elseif_match );
353  }
354 
355  $check_elseif_match = str_replace( '[else', '[gvlogicelse', $check_elseif_match );
356  $check_elseif_match = str_replace( '[/else', '[/gvlogicelse', $check_elseif_match );
357 
358  // Make sure to close the tag
359  if ( '[/gvlogicelse]' !== substr( $check_elseif_match, -14, 14 ) ) {
360  $check_elseif_match .= '[/gvlogicelse]';
361  }
362 
363  // The shortcode returned a value; it was a match
364  if ( $result = do_shortcode( $check_elseif_match ) ) {
365  return $else_if_value;
366  }
367 
368  // Process any remaining [else] tags
369  return $this->process_elseif( $else_if_match[5] );
370  }
371 
372  return false;
373  }
374 
375  /**
376  * Process the attributes passed to the shortcode. Make sure they're valid
377  * @return void
378  */
379  private function parse_atts() {
380 
381  $supported = array(
382  'if' => false,
383  'else' => false,
384  );
385 
386  $supported_args = $supported + $this->get_operators( true );
387 
388  // Whittle down the attributes to only valid pairs
389  $this->atts = shortcode_atts( $supported_args, $this->passed_atts, $this->shortcode );
390 
391  // Only keep the passed attributes after making sure that they're valid pairs
392  $this->atts = array_intersect_key( $this->passed_atts, $this->atts );
393 
394  // Strip whitespace if it's not default false
395  $this->if = ( isset( $this->atts['if'] ) && is_string( $this->atts['if'] ) ) ? trim( $this->atts['if'] ) : false;
396 
397  /**
398  * @action `gravityview/gvlogic/parse_atts/after` Modify shortcode attributes after it's been parsed
399  * @see https://gist.github.com/zackkatz/def9b295b80c4ae109760ffba200f498 for an example
400  * @since 1.21.5
401  * @param GVLogic_Shortcode $this The GVLogic_Shortcode instance
402  */
403  do_action( 'gravityview/gvlogic/parse_atts/after', $this );
404 
405  // Make sure the "if" isn't processed in self::setup_operation_and_comparison()
406  unset( $this->atts['if'] );
407  }
408 }
409 
shortcode( $atts=array(), $content=NULL, $shortcode_tag='')
get_operators( $with_values=false)
Get array of supported operators.
parse_atts()
Process the attributes passed to the shortcode.
add_hooks()
Register the shortcode.
gravityview()
Definition: _stubs.php:26
__construct()
Add the WordPress hooks.
static matches_operation( $val1, $val2, $operation)
Wrapper for the GFFormsModel::matches_operation() method that adds additional comparisons, including: &#39;equals&#39;, &#39;greater_than_or_is&#39;, &#39;greater_than_or_equals&#39;, &#39;less_than_or_is&#39;, &#39;less_than_or_equals&#39;, &#39;not_contains&#39;, &#39;in&#39;, and &#39;not_in&#39;.
static get_instance()
Instantiate!
process_elseif( $before_else)
Handle additional conditional logic inside the [else] pseudo-shortcode.
set_content_and_else_content()
Check for [else] tag inside the shortcode content.
reset()
Restore the original settings for the shortcode.
set_is_match()
Does the if and the comparison match? GVCommon::matches_operation.
setup_operation_and_comparison()
Set the operation and comparison for the shortcode.
set_operation( $operation='isnot')
Set the operation for the shortcode.
Shortcode to handle showing/hiding content in merge tags.
get_output()
Get the output for the shortcode, based on whether there&#39;s a matched value.