GravityView  1.22.6
The best, easiest way to display Gravity Forms entries on your website.
class-gravityview-merge-tags.php
Go to the documentation of this file.
1 <?php
2 
3 /**
4  * Enhance Gravity Forms' merge tag functionality by adding additional merge tags
5  * @since 1.8.4
6  */
8 
9  /**
10  * @since 1.8.4
11  */
12  public function __construct() {
13  $this->add_hooks();
14  }
15 
16  /**
17  * Tap in to gform_replace_merge_tags to add merge tags
18  * @since 1.8.4
19  */
20  private function add_hooks() {
21 
22  /** @see GFCommon::replace_variables_prepopulate **/
23  add_filter( 'gform_replace_merge_tags', array( 'GravityView_Merge_Tags', 'replace_gv_merge_tags' ), 10, 7 );
24 
25  // Process after 10 priority
26  add_filter( 'gform_merge_tag_filter', array( 'GravityView_Merge_Tags', 'process_modifiers' ), 20, 5 );
27 
28  }
29 
30  /**
31  * Process custom GravityView modifiers for Merge Tags
32  *
33  * Is not processed on `{all_fields}` Merge Tag.
34  *
35  * @since 1.17
36  *
37  * @param string $value The current merge tag value to be filtered.
38  * @param string $merge_tag If the merge tag being executed is an individual field merge tag (i.e. {Name:3}), this variable will contain the field's ID. If not, this variable will contain the name of the merge tag (i.e. all_fields).
39  * @param string $modifier The string containing any modifiers for this merge tag. For example, "maxwords:10" would be the modifiers for the following merge tag: `{Text:2:maxwords:10}`.
40  * @param GF_Field $field The current field.
41  * @param mixed $raw_value The raw value submitted for this field.
42  *
43  * @return string If no modifiers passed, $raw_value is not a string, or {all_fields} Merge Tag is used, original value. Otherwise, output from modifier methods.
44  */
45  public static function process_modifiers( $value, $merge_tag, $modifier, $field, $raw_value ) {
46 
47  // No modifier was set or the raw value was empty
48  if( 'all_fields' === $merge_tag || '' === $modifier || ! is_string( $raw_value ) || '' === $raw_value ) {
49  return $value;
50  }
51 
52  // matching regex => the value is the method to call to replace the value.
53  $gv_modifiers = array(
54  'maxwords:(\d+)' => 'modifier_maxwords', /** @see modifier_maxwords */
55  'wpautop' => 'modifier_wpautop', /** @see modifier_wpautop */
56  'timestamp' => 'modifier_timestamp', /** @see modifier_timestamp */
57  );
58 
59  $return = $value;
60 
61  foreach ( $gv_modifiers as $gv_modifier => $method ) {
62 
63  // Only match the regex if it's the first modifer; this allows us to enforce our own modifier structure
64  preg_match( '/^' . $gv_modifier .'/ism', $modifier, $matches );
65 
66  if( ! empty( $matches ) ) {
67  // The called method is passed the raw value and the full matches array
68  $return = self::$method( $raw_value, $matches );
69  break;
70  }
71  }
72 
73  return $return;
74  }
75 
76  /**
77  * Convert Date field values to timestamp int
78  *
79  * @since 1.17
80  *
81  * @uses strtotime()
82  *
83  * @param string $raw_value Value to filter
84  * @param array $matches Regex matches group
85  *
86  * @return int Timestamp value of date. `-1` if not a valid timestamp.
87  */
88  private static function modifier_timestamp( $raw_value, $matches ) {
89 
90  if( empty( $matches[0] ) ) {
91  return $raw_value;
92  }
93 
94  $timestamp = strtotime( $raw_value );
95 
96  // Can return false or -1, depending on PHP version.
97  return ( $timestamp && $timestamp > 0 ) ? $timestamp : -1;
98  }
99 
100  /**
101  * Run the Merge Tag value through the wpautop function
102  *
103  * @since 1.17
104  *
105  * @uses wpautop
106  *
107  * @param string $raw_value Value to filter
108  * @param array $matches Regex matches group
109  *
110  * @return string Modified value, if longer than the passed `maxwords` modifier
111  */
112  private static function modifier_wpautop( $raw_value, $matches ) {
113 
114  if( empty( $matches[0] ) || ! function_exists( 'wpautop' ) ) {
115  return $raw_value;
116  }
117 
118  return trim( wpautop( $raw_value ) );
119  }
120 
121  /**
122  * Trim the Merge Tag's length in words.
123  *
124  * Notes:
125  * - HTML tags are preserved
126  * - HTML entities are encoded, but if they are separated by word breaks, they will be counted as words
127  * Example: "one & two" will be counted as three words, but "one& two" will be counted as two words
128  *
129  * @since 1.17
130  *
131  * @param string $raw_value Value to filter
132  * @param array $matches Regex matches group
133  *
134  * @return string Modified value, if longer than the passed `maxwords` modifier
135  */
136  private static function modifier_maxwords( $raw_value, $matches ) {
137 
138  if( ! is_string( $raw_value ) || empty( $matches[1] ) || ! function_exists( 'wp_trim_words' ) ) {
139  return $raw_value;
140  }
141 
142  $max = intval( $matches[1] );
143 
144  $more_placeholder = '[GVMORE]';
145 
146  /**
147  * Use htmlentities instead, so that entities are double-encoded, and decoding restores original values.
148  * @see https://core.trac.wordpress.org/ticket/29533#comment:3
149  */
150  $return = force_balance_tags( wp_specialchars_decode( wp_trim_words( htmlentities( $raw_value ), $max, $more_placeholder ) ) );
151 
152  $return = str_replace( $more_placeholder, '&hellip;', $return );
153 
154  return $return;
155  }
156 
157  /**
158  * Alias for GFCommon::replace_variables()
159  *
160  * Before 1.15.3, it would check for merge tags before passing to Gravity Forms to improve speed.
161  *
162  * @since 1.15.3 - Now that Gravity Forms added speed improvements in 1.9.15, it's more of an alias with a filter
163  * to disable or enable replacements.
164  * @since 1.8.4 - Moved to GravityView_Merge_Tags
165  * @since 1.15.1 - Add support for $url_encode and $esc_html arguments
166  * @since 1.22.4 - Added $nl2br, $format, $aux_data args
167  *
168  * @param string $text Text to replace variables in
169  * @param array $form GF Form array
170  * @param array $entry GF Entry array
171  * @param bool $url_encode Pass return value through `url_encode()`
172  * @param bool $esc_html Pass return value through `esc_html()`
173  * @param bool $nl2br Convert newlines to <br> HTML tags
174  * @param string $format The format requested for the location the merge is being used. Possible values: html, text or url.
175  * @param array $aux_data Additional data to be used to replace merge tags {@see https://www.gravityhelp.com/documentation/article/gform_merge_tag_data/}
176  * @return string Text with variables maybe replaced
177  */
178  public static function replace_variables($text, $form = array(), $entry = array(), $url_encode = false, $esc_html = true, $nl2br = true, $format = 'html', $aux_data = array() ) {
179 
180  /**
181  * @filter `gravityview_do_replace_variables` Turn off merge tag variable replacements.\n
182  * Useful where you want to process variables yourself. We do this in the Math Extension.
183  * @since 1.13
184  *
185  * @param[in,out] boolean $do_replace_variables True: yes, replace variables for this text; False: do not replace variables.
186  * @param[in] string $text Text to replace variables in
187  * @param[in] array $form GF Form array
188  * @param[in] array $entry GF Entry array
189  */
190  $do_replace_variables = apply_filters( 'gravityview/merge_tags/do_replace_variables', true, $text, $form, $entry );
191 
192  if ( strpos( $text, '{' ) === false || ! $do_replace_variables ) {
193  return $text;
194  }
195 
196  /**
197  * Make sure the required keys are set for GFCommon::replace_variables
198  *
199  * @internal Reported to GF Support on 12/3/2016
200  * @internal Fixed $form['title'] in Gravity Forms
201  * @see https://github.com/gravityforms/gravityforms/pull/27/files
202  */
203  $form['title'] = isset( $form['title'] ) ? $form['title'] : '';
204  $form['id'] = isset( $form['id'] ) ? $form['id'] : '';
205  $form['fields'] = isset( $form['fields'] ) ? $form['fields'] : array();
206 
207  return GFCommon::replace_variables( $text, $form, $entry, $url_encode, $esc_html, $nl2br, $format, $aux_data );
208  }
209 
210  /**
211  * Run GravityView filters when using GFCommon::replace_variables()
212  *
213  * Instead of adding multiple hooks, add all hooks into this one method to improve speed
214  *
215  * @since 1.8.4
216  *
217  * @param string $text Text to replace
218  * @param array|bool $form Gravity Forms form array. When called inside {@see GFCommon::replace_variables()} (now deprecated), `false`
219  * @param array|bool $entry Entry array. When called inside {@see GFCommon::replace_variables()} (now deprecated), `false`
220  * @param bool $url_encode Whether to URL-encode output
221  * @param bool $esc_html Whether to apply `esc_html()` to output
222  *
223  * @return mixed
224  */
225  public static function replace_gv_merge_tags( $text, $form = array(), $entry = array(), $url_encode = false, $esc_html = false ) {
226 
227  if( '' === $text ) {
228  return $text;
229  }
230 
231  /**
232  * This prevents the gform_replace_merge_tags filter from being called twice, as defined in:
233  * @see GFCommon::replace_variables()
234  * @see GFCommon::replace_variables_prepopulate()
235  * @todo Remove eventually: Gravity Forms fixed this issue in 1.9.14
236  */
237  if( false === $form ) {
238  return $text;
239  }
240 
241  $text = self::replace_get_variables( $text, $form, $entry, $url_encode );
242 
243  $text = self::replace_current_post( $text, $form, $entry, $url_encode, $esc_html );
244 
245  return $text;
246  }
247 
248  /**
249  * Format Merge Tags using GVCommon::format_date()
250  *
251  * @uses GVCommon::format_date()
252  *
253  * @see http://docs.gravityview.co/article/331-date-created-merge-tag for documentation
254  *
255  * @param string $date_created The Gravity Forms date created format
256  * @param string $property Any modifiers for the merge tag (`human`, `format:m/d/Y`)
257  *
258  * @return int|string If timestamp requested, timestamp int. Otherwise, string output.
259  */
260  public static function format_date( $date_created = '', $property = '' ) {
261 
262  // Expand all modifiers, skipping escaped colons. str_replace worked better than preg_split( "/(?<!\\):/" )
263  $exploded = explode( ':', str_replace( '\:', '|COLON|', $property ) );
264 
265  $atts = array(
266  'format' => self::get_format_from_modifiers( $exploded, false ),
267  'human' => in_array( 'human', $exploded ), // {date_created:human}
268  'diff' => in_array( 'diff', $exploded ), // {date_created:diff}
269  'raw' => in_array( 'raw', $exploded ), // {date_created:raw}
270  'timestamp' => in_array( 'timestamp', $exploded ), // {date_created:timestamp}
271  'time' => in_array( 'time', $exploded ), // {date_created:time}
272  );
273 
274  $formatted_date = GVCommon::format_date( $date_created, $atts );
275 
276  return $formatted_date;
277  }
278 
279  /**
280  * If there is a `:format` modifier in a merge tag, grab the formatting
281  *
282  * The `:format` modifier should always have the format follow it; it's the next item in the array
283  * In `foo:format:bar`, "bar" will be the returned format
284  *
285  * @since 1.16
286  *
287  * @param array $exploded Array of modifiers with a possible `format` value
288  * @param string $backup The backup value to use, if not found
289  *
290  * @return string If format is found, the passed format. Otherwise, the backup.
291  */
292  private static function get_format_from_modifiers( $exploded, $backup = '' ) {
293 
294  $return = $backup;
295 
296  $format_key_index = array_search( 'format', $exploded );
297 
298  // If there's a "format:[php date format string]" date format, grab it
299  if ( false !== $format_key_index && isset( $exploded[ $format_key_index + 1 ] ) ) {
300  // Return escaped colons placeholder
301  $return = str_replace( '|COLON|', ':', $exploded[ $format_key_index + 1 ] );
302  }
303 
304  return $return;
305  }
306 
307  /**
308  * Add a {current_post} Merge Tag for information about the current post (in the loop or singular)
309  *
310  * {current_post} is replaced with the current post's permalink by default, when no modifiers are passed.
311  * Pass WP_Post properties as :modifiers to access.
312  *
313  * {current_post} is the same as {embed_post}, except:
314  *
315  * - Adds support for {current_post:permalink}
316  * - Works on post archives, as well as singular
317  *
318  * @see https://www.gravityhelp.com/documentation/article/merge-tags/#embed-post for examples
319  * @see GFCommon::replace_variables_prepopulate - Code is there for {custom_field} and {embed_post} Merge Tags
320  *
321  * @param string $original_text Text to replace
322  * @param array $form Gravity Forms form array
323  * @param array $entry Entry array
324  * @param bool $url_encode Whether to URL-encode output
325  * @param bool $esc_html Indicates if the esc_html function should be applied.
326  *
327  * @return string Original text, if no {current_post} Merge Tags found, otherwise text with Merge Tags replaced
328  */
329  public static function replace_current_post( $original_text, $form = array(), $entry = array(), $url_encode = false, $esc_html = false ) {
330 
331  $return = $original_text;
332 
333  // Is there a {current_post} or {current_post:[xyz]} merge tag?
334  preg_match_all( "/{current_post(:(.*?))?}/ism", $original_text, $matches, PREG_SET_ORDER );
335 
336  // If there are no matches OR the Entry `created_by` isn't set or is 0 (no user)
337  if ( empty( $matches ) ) {
338  return $original_text;
339  }
340 
341  $current_post = get_post();
342 
343  // WP_Error, arrays and NULL aren't welcome here.
344  if ( ! $current_post || ! is_a( $current_post, 'WP_Post' ) ) {
345  return $original_text;
346  }
347 
348  foreach ( (array) $matches as $match ) {
349  $full_tag = $match[0];
350  $modifier = rgar( $match, 2, 'permalink' );
351 
352  $replacement = false;
353 
354  if ( 'permalink' === $modifier ) {
355  $replacement = get_permalink( $current_post );
356  } elseif ( isset( $current_post->{$modifier} ) ) {
357  /** @see WP_Post Post properties */
358  $replacement = $current_post->{$modifier};
359  }
360 
361  if ( $replacement ) {
362 
363  if ( $esc_html ) {
364  $replacement = esc_html( $replacement );
365  }
366 
367  if( $url_encode ) {
368  $replacement = urlencode( $replacement );
369  }
370 
371  $return = str_replace( $full_tag, $replacement, $return );
372  }
373  }
374 
375  return $return;
376  }
377 
378  /**
379  * Allow passing variables via URL to be displayed in Merge Tags
380  *
381  * Works with `[gvlogic]`:
382  * [gvlogic if="{get:example}" is="false"]
383  * ?example=false
384  * [else]
385  * ?example wasn't "false". It's {get:example}!
386  * [/gvlogic]
387  *
388  * Supports passing arrays:
389  * URL: `example[]=Example+One&example[]=Example+(with+comma)%2C+Two`
390  * Merge Tag: `{get:example}`
391  * Output: `Example One, Example (with comma), Two`
392  *
393  * @since 1.15
394  * @param string $text Text to replace
395  * @param array $form Gravity Forms form array
396  * @param array $entry Entry array
397  * @param bool $url_encode Whether to URL-encode output
398  *
399  * @return string Original text, if no Merge Tags found, otherwise text with Merge Tags replaced
400  */
401  public static function replace_get_variables( $text, $form = array(), $entry = array(), $url_encode = false ) {
402 
403  // Is there is {get:[xyz]} merge tag?
404  preg_match_all( "/{get:(.*?)}/ism", $text, $matches, PREG_SET_ORDER );
405 
406  // If there are no matches OR the Entry `created_by` isn't set or is 0 (no user)
407  if( empty( $matches ) ) {
408  return $text;
409  }
410 
411  foreach ( $matches as $match ) {
412 
413  $full_tag = $match[0];
414  $property = $match[1];
415 
416  $value = stripslashes_deep( rgget( $property ) );
417 
418  /**
419  * @filter `gravityview/merge_tags/get/glue/` Modify the glue used to convert an array of `{get}` values from an array to string
420  * @since 1.15
421  * @param[in,out] string $glue String used to `implode()` $_GET values Default: ', '
422  * @param[in] string $property The current name of the $_GET parameter being combined
423  */
424  $glue = apply_filters( 'gravityview/merge_tags/get/glue/', ', ', $property );
425 
426  $value = is_array( $value ) ? implode( $glue, $value ) : $value;
427 
428  $value = $url_encode ? urlencode( $value ) : $value;
429 
430  /**
431  * @filter `gravityview/merge_tags/get/esc_html/{url parameter name}` Disable esc_html() from running on `{get}` merge tag
432  * By default, all values passed through URLs will be escaped for security reasons. If for some reason you want to
433  * pass HTML in the URL, for example, you will need to return false on this filter. It is strongly recommended that you do
434  * not disable this filter.
435  * @since 1.15
436  * @param bool $esc_html Whether to esc_html() the value. Default: `true`
437  */
438  $esc_html = apply_filters('gravityview/merge_tags/get/esc_html/' . $property, true );
439 
440  $value = $esc_html ? esc_html( $value ) : $value;
441 
442  /**
443  * @filter `gravityview/merge_tags/get/esc_html/{url parameter name}` Modify the value of the `{get}` replacement before being used
444  * @param[in,out] string $value Value that will replace `{get}`
445  * @param[in] string $text Text that contains `{get}` (before replacement)
446  * @param[in] array $form Gravity Forms form array
447  * @param[in] array $entry Entry array
448  */
449  $value = apply_filters('gravityview/merge_tags/get/value/' . $property, $value, $text, $form, $entry );
450 
451  $text = str_replace( $full_tag, $value, $text );
452  }
453 
454  unset( $value, $glue, $matches );
455 
456  return $text;
457  }
458 }
459 
static replace_current_post( $original_text, $form=array(), $entry=array(), $url_encode=false, $esc_html=false)
Add a {current_post} Merge Tag for information about the current post (in the loop or singular) ...
static replace_gv_merge_tags( $text, $form=array(), $entry=array(), $url_encode=false, $esc_html=false)
Run GravityView filters when using GFCommon::replace_variables()
static get_format_from_modifiers( $exploded, $backup='')
If there is a :format modifier in a merge tag, grab the formatting.
static replace_variables($text, $form=array(), $entry=array(), $url_encode=false, $esc_html=true, $nl2br=true, $format='html', $aux_data=array())
Alias for GFCommon::replace_variables()
if(gv_empty( $field['value'], false, false)) $format
Enhance Gravity Forms&#39; merge tag functionality by adding additional merge tags.
static replace_get_variables( $text, $form=array(), $entry=array(), $url_encode=false)
Allow passing variables via URL to be displayed in Merge Tags.
$merge_tag
Merge tag is already generated by the class.
Definition: widget-poll.php:14
static modifier_wpautop( $raw_value, $matches)
Run the Merge Tag value through the wpautop function.
static modifier_maxwords( $raw_value, $matches)
Trim the Merge Tag&#39;s length in words.
static format_date( $date_string='', $args=array())
Allow formatting date and time based on GravityView standards.
static modifier_timestamp( $raw_value, $matches)
Convert Date field values to timestamp int.
static format_date( $date_created='', $property='')
Format Merge Tags using GVCommon::format_date()
static process_modifiers( $value, $merge_tag, $modifier, $field, $raw_value)
Process custom GravityView modifiers for Merge Tags.
$entry
Definition: notes.php:27
add_hooks()
Tap in to gform_replace_merge_tags to add merge tags.
$field
Definition: gquiz_grade.php:11