GravityView  1.19.4
The best, easiest way to display Gravity Forms entries on your website.
class-cache.php
Go to the documentation of this file.
1 <?php
2 
3 /**
4  * Handle caching using transients for GravityView
5  */
7 
8  const BLACKLIST_OPTION_NAME = 'gravityview_cache_blacklist';
9 
10  /**
11  * Form ID, or array of Form IDs
12  *
13  * @var mixed
14  */
15  protected $form_ids;
16 
17  /**
18  * Extra request parameters used to generate the query. This is used to generate the unique transient key.
19  *
20  * @var array
21  */
22  protected $args;
23 
24  /**
25  * The transient key used to store the cached item. 45 characters long.
26  *
27  * @var string
28  */
29  private $key = '';
30 
31  /**
32  * @since 1.13.1
33  * @var array Columns in the database for leads
34  */
35  private $lead_db_columns = array( 'id', 'form_id', 'post_id', 'date_created', 'is_starred', 'is_read', 'ip', 'source_url', 'user_agent', 'currency', 'payment_status', 'payment_date', 'payment_amount', 'transaction_id', 'is_fulfilled', 'created_by', 'transaction_type', 'status' );
36 
37  /**
38  *
39  * @param array|int $form_ids Form ID or array of form IDs used in a request
40  * @param array $args Extra request parameters used to generate the query. This is used to generate the unique transient key.
41  */
42  function __construct( $form_ids = NULL, $args = array() ) {
43 
44  $this->add_hooks();
45 
46  if ( ! is_null( $form_ids ) ) {
47 
48  $this->form_ids = $form_ids;
49 
50  $this->args = $args;
51 
52  $this->set_key();
53  }
54  }
55 
56  /**
57  * Add actions for clearing out caches when entries are updated.
58  */
59  function add_hooks() {
60 
61  // Schedule cleanup of expired transients
62  add_action( 'wp', array( $this, 'schedule_transient_cleanup' ) );
63 
64  // Hook in to the scheduled cleanup, if scheduled
65  add_action( 'gravityview-expired-transients', array( $this, 'delete_expired_transients' ) );
66 
67  // Trigger this when you need to prevent any results from being cached with forms that have been modified
68  add_action( 'gravityview_clear_form_cache', array( $this, 'blacklist_add' ) );
69 
70  /**
71  * @since 1.14
72  */
73  add_action( 'gravityview_clear_entry_cache', array( $this, 'entry_status_changed' ) );
74 
75  add_action( 'gform_after_update_entry', array( $this, 'entry_updated' ), 10, 2 );
76 
77  add_action( 'gform_entry_created', array( $this, 'entry_created' ), 10, 2 );
78 
79  /**
80  * @see RGFormsModel::update_lead_property() Trigger when any entry property changes
81  */
82  foreach( $this->lead_db_columns as $column ) {
83  add_action( 'gform_update_' . $column, array( $this, 'entry_status_changed' ), 10, 3 );
84  }
85 
86  add_action( 'gform_delete_lead', array( $this, 'entry_status_changed' ), 10 );
87  }
88 
89  /**
90  * Force refreshing a cache when an entry is deleted.
91  *
92  * The `gform_delete_lead` action is called before the lead is deleted; we fetch the entry to find out the form ID so it can be added to the blacklist.
93  *
94  * @since 1.5.1
95  *
96  * @param int $lead_id Entry ID
97  * @param string $property_value Previous value of the lead status passed by gform_update_status hook
98  * @param string $previous_value Previous value of the lead status passed by gform_update_status hook
99  *
100  * @return void
101  */
102  public function entry_status_changed( $lead_id, $property_value = '', $previous_value = '' ) {
103 
104  /** @var array $entry */
105  $entry = GFAPI::get_entry( $lead_id );
106 
107  if ( is_wp_error( $entry ) ) {
108 
109  /** @var WP_Error $entry */
110  do_action( 'gravityview_log_error', __METHOD__ . ' Could not retrieve entry ' . $lead_id . ' to delete it: ' . $entry->get_error_message() );
111 
112  return;
113  }
114 
115  do_action( 'gravityview_log_debug', __METHOD__ . ' adding form ' . $entry['form_id'] . ' to blacklist because entry #' . $lead_id . ' was deleted', array( 'value' => $property_value, 'previous' => $previous_value ) );
116 
117  $this->blacklist_add( $entry['form_id'] );
118  }
119 
120  /**
121  * When an entry is updated, add the entry's form to the cache blacklist
122  *
123  * @param array $form GF form array
124  * @param int $lead_id Entry ID
125  *
126  * @return void
127  */
128  public function entry_updated( $form, $lead_id ) {
129 
130  do_action( 'gravityview_log_debug', 'GravityView_Cache[entry_updated] adding form ' . $form['id'] . ' to blacklist because entry #' . $lead_id . ' was updated' );
131 
132  $this->blacklist_add( $form['id'] );
133  }
134 
135  /**
136  * When an entry is created, add the entry's form to the cache blacklist
137  *
138  * We don't want old caches; when an entry is added, we want to clear the cache.
139  *
140  * @param array $entry GF entry array
141  * @param array $form GF form array
142  *
143  * @return void
144  */
145  public function entry_created( $entry, $form ) {
146 
147  do_action( 'gravityview_log_debug', 'GravityView_Cache[entry_created] adding form ' . $form['id'] . ' to blacklist because entry #' . $entry['id'] . ' was created' );
148 
149  $this->blacklist_add( $form['id'] );
150  }
151 
152  /**
153  * Calculate the prefix based on the Form IDs
154  *
155  * @param int|array $form_ids Form IDs to generate prefix for
156  *
157  * @return string Prefix for the cache string used in set_key()
158  */
159  protected function get_cache_key_prefix( $form_ids = NULL ) {
160 
161  if ( is_null( $form_ids ) ) {
163  }
164 
165  // Normally just one form, but supports multiple forms
166  //
167  // Array of IDs 12, 5, 14 would result in `f-12f-5f-14`
168  $forms = 'f:' . implode( '&f:', (array) $form_ids );
169 
170  // Prefix for transient keys
171  // Now the prefix would be: `gv-cache-f-12f-5f-14`
172  return 'gv-cache-' . $forms . '-';
173 
174  }
175 
176  /**
177  * Set the transient key based on the form IDs and the arguments passed to the class
178  */
179  protected function set_key() {
180 
181  // Don't set key if no forms have been set.
182  if ( empty( $this->form_ids ) ) {
183  return;
184  }
185 
186  $key = $this->get_cache_key_prefix() . sha1( serialize( $this->args ) );
187 
188  // The transient name column can handle up to 64 characters.
189  // The `_transient_timeout_` prefix that is prepended to the string is 11 characters.
190  // 64 - 19 = 45
191  // We make sure the key isn't too long or else WP doesn't store data.
192  $this->key = substr( $key, 0, 45 );
193  }
194 
195  /**
196  * Allow public access to get transient key
197  *
198  * @return string Transient key
199  */
200  public function get_key() {
201  return $this->key;
202  }
203 
204  /**
205  * Add form IDs to a "blacklist" to force the cache to be refreshed
206  *
207  *
208  *
209  * @param int|array $form_ids Form IDs to force to be updated
210  *
211  * @return boolean False if value was not updated and true if value was updated.
212  */
213  public function blacklist_add( $form_ids ) {
214 
215  $blacklist = get_option( self::BLACKLIST_OPTION_NAME, array() );
216 
217  $form_ids = is_array( $form_ids ) ? $form_ids : array( $form_ids );
218 
219  do_action( 'gravityview_log_debug', 'GravityView_Cache[blacklist_add] Adding form IDs to cache blacklist', array(
220  '$form_ids' => $form_ids,
221  '$blacklist' => $blacklist
222  ) );
223 
224  // Add the passed form IDs
225  $blacklist = array_merge( (array) $blacklist, $form_ids );
226 
227  // Don't duplicate
228  $blacklist = array_unique( $blacklist );
229 
230  return update_option( self::BLACKLIST_OPTION_NAME, $blacklist );
231 
232  }
233 
234  /**
235  * Remove Form IDs from blacklist
236  *
237  * @param int|array $form_ids Form IDs to add
238  *
239  * @return boolean Whether the removal was successful
240  */
241  public function blacklist_remove( $form_ids ) {
242 
243  $blacklist = get_option( self::BLACKLIST_OPTION_NAME, array() );
244 
245  $updated_list = array_diff( $blacklist, (array) $form_ids );
246 
247  do_action( 'gravityview_log_debug', 'GravityView_Cache[blacklist_remove] Removing form IDs from cache blacklist', array(
248  '$form_ids' => $form_ids,
249  '$blacklist' => $blacklist,
250  '$updated_list' => $updated_list
251  ) );
252 
253  return update_option( self::BLACKLIST_OPTION_NAME, $updated_list );
254  }
255 
256 
257  /**
258  * Is a form ID in the cache blacklist
259  *
260  * @param int|array $form_ids Form IDs to check if in blacklist
261  *
262  * @return [type] [description]
263  */
264  function in_blacklist( $form_ids = NULL ) {
265 
266  $blacklist = get_option( self::BLACKLIST_OPTION_NAME, array() );
267 
268  // Use object var if exists
269  $form_ids = is_null( $form_ids ) ? $this->form_ids : $form_ids;
270 
271  if ( empty( $form_ids ) ) {
272 
273  do_action( 'gravityview_log_debug', 'GravityView_Cache[in_blacklist] Did not add form to blacklist; empty form ID', $form_ids );
274 
275  return false;
276  }
277 
278  foreach ( (array) $form_ids as $form_id ) {
279 
280  if ( in_array( $form_id, $blacklist ) ) {
281 
282  do_action( 'gravityview_log_debug', 'GravityView_Cache[in_blacklist] Form #' . esc_attr( $form_id ) . ' is in the cache blacklist' );
283 
284  return true;
285  }
286  }
287 
288  return false;
289  }
290 
291 
292  /**
293  * Get transient result
294  *
295  * @param string $key Transient key to fetch
296  *
297  * @return mixed False: Not using cache or cache was a WP_Error object; NULL: no results found; Mixed: cache value
298  */
299  public function get( $key = NULL ) {
300 
301  $key = is_null( $key ) ? $this->key : $key;
302 
303  if ( ! $this->use_cache() ) {
304 
305  do_action( 'gravityview_log_debug', 'GravityView_Cache[get] Not using cached results because of GravityView_Cache->use_cache() results' );
306 
307  return false;
308  }
309 
310  do_action( 'gravityview_log_debug', 'GravityView_Cache[get] Fetching request with transient key ' . $key );
311 
312  $result = get_transient( $key );
313 
314  if ( is_wp_error( $result ) ) {
315 
316  do_action( 'gravityview_log_debug', 'GravityView_Cache[get] Fetching request resulted in error:', $result );
317 
318  return false;
319 
320  } elseif ( $result ) {
321 
322  do_action( 'gravityview_log_debug', 'GravityView_Cache[get] Cached results found for transient key ' . $key );
323 
324  return $result;
325  }
326 
327  do_action( 'gravityview_log_debug', 'GravityView_Cache[get] No cached results found for transient key ' . $key );
328 
329  return NULL;
330 
331  }
332 
333  /**
334  * Cache content as a transient.
335  *
336  * Cache time defaults to 1 week
337  *
338  * @param [type] $content [description]
339  * @param [type] $filter_name Name used to modify the cache time. Will be set to `gravityview_cache_time_{$filter_name}`.
340  */
341  public function set( $content, $filter_name = '' ) {
342 
343  // Don't cache empty results
344  if ( ! empty( $content ) ) {
345 
346  /**
347  * @filter `gravityview_cache_time_{$filter_name}` Modify the cache time for a type of cache
348  * @param int $time_in_seconds Default: `DAY_IN_SECONDS`
349  */
350  $cache_time = (int) apply_filters( 'gravityview_cache_time_' . $filter_name, DAY_IN_SECONDS );
351 
352  do_action( 'gravityview_log_debug', 'GravityView_Cache[set] Setting cache with transient key ' . $this->key . ' for ' . $cache_time . ' seconds' );
353 
354  return set_transient( $this->key, $content, $cache_time );
355 
356  }
357 
358  do_action( 'gravityview_log_debug', 'GravityView_Cache[set] Cache not set; content is empty' );
359 
360  return false;
361 
362  }
363 
364  /**
365  * Delete cached transients based on form IDs
366  *
367  * @todo Use REGEX to match forms when array of form IDs is passed, instead of using a simple LIKE
368  * @todo Rate limit deleting to prevent abuse
369  *
370  * @param int|array $form_ids Form IDs to delete
371  *
372  * @return [type] [description]
373  */
374  public function delete( $form_ids = NULL ) {
375  global $wpdb;
376 
377  // Use object var if exists
378  $form_ids = is_null( $form_ids ) ? $this->form_ids : $form_ids;
379 
380  if ( empty( $form_ids ) ) {
381  do_action( 'gravityview_log_debug', 'GravityView_Cache[delete] Did not delete cache; empty form IDs' );
382 
383  return;
384  }
385 
386  foreach ( (array) $form_ids as $form_id ) {
387 
388  $key = $this->get_cache_key_prefix( $form_id );
389 
390  // WordPress 4.0+
391  if ( is_callable( array( $wpdb, 'esc_like' ) ) ) {
392  $key = $wpdb->esc_like( $key );
393  } else {
394  $key = like_escape( $key );
395  }
396 
397  $key = "%" . $key . "%";
398 
399  $sql = $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE `option_name` LIKE %s", $key );
400 
401  $result = $wpdb->query( $sql );
402 
403  do_action( 'gravityview_log_debug', 'GravityView_Cache[delete] Deleting cache for form #' . $form_id, array(
404  $sql,
405  sprintf( 'Deleted results: %d', $result )
406  ) );
407  }
408 
409  }
410 
411  /**
412  * Schedule expired transient cleanup twice a day.
413  *
414  * Can be overruled by the `gravityview_cleanup_transients` filter (returns boolean)
415  *
416  * @return void
417  */
418  public function schedule_transient_cleanup() {
419 
420  /**
421  * @filter `gravityview_cleanup_transients` Override GravityView cleanup of transients by setting this to false
422  * @param boolean $cleanup Whether to run the GravityView auto-cleanup of transients. Default: `true`
423  */
424  $cleanup = apply_filters( 'gravityview_cleanup_transients', true );
425 
426  if ( ! $cleanup ) {
427  return;
428  }
429 
430  if ( ! wp_next_scheduled( 'gravityview-expired-transients' ) ) {
431  wp_schedule_event( time(), 'daily', 'gravityview-expired-transients' );
432  }
433  }
434 
435  /**
436  * Delete expired transients.
437  *
438  * The code is copied from the Delete Expired Transients, with slight modifications to track # of results and to get the blog ID dynamically
439  *
440  * @see https://wordpress.org/plugins/delete-expired-transients/ Plugin where the code was taken from
441  * @see DelxtransCleaners::clearBlogExpired()
442  * @return void
443  */
444  public function delete_expired_transients() {
445  global $wpdb;
446 
447  // Added this line, which isn't in the plugin
448  $blog_id = get_current_blog_id();
449 
450  $num_results = 0;
451 
452  // get current PHP time, offset by a minute to avoid clashes with other tasks
453  $threshold = time() - 60;
454 
455  // get table name for options on specified blog
456  $table = $wpdb->get_blog_prefix( $blog_id ) . 'options';
457 
458  // delete expired transients, using the paired timeout record to find them
459  $sql = "
460  delete from t1, t2
461  using $table t1
462  join $table t2 on t2.option_name = replace(t1.option_name, '_timeout', '')
463  where (t1.option_name like '\_transient\_timeout\_%' or t1.option_name like '\_site\_transient\_timeout\_%')
464  and t1.option_value < '$threshold'
465  ";
466 
467  $num_results = $wpdb->query( $sql );
468 
469  // delete orphaned transient expirations
470  // also delete NextGEN Gallery 2.x display cache timeout aliases
471  $sql = "
472  delete from $table
473  where (
474  option_name like '\_transient\_timeout\_%'
475  or option_name like '\_site\_transient\_timeout\_%'
476  or option_name like 'displayed\_galleries\_%'
477  or option_name like 'displayed\_gallery\_rendering\_%'
478  )
479  and option_value < '$threshold'
480  ";
481 
482  $num_results += $wpdb->query( $sql );
483 
484  do_action( 'gravityview_log_debug', 'GravityView_Cache[delete_expired_transients] Deleted ' . $num_results . ' expired transient records from the database' );
485  }
486 
487  /**
488  * Check whether to use cached results, if available
489  *
490  * If the user can edit posts, they are able to override whether to cache results by adding `cache` or `nocache` to the URL requested.
491  *
492  * @return boolean True: use cache; False: don't use cache
493  */
494  public function use_cache() {
495 
496  $use_cache = true;
497 
498  if ( GVCommon::has_cap( 'edit_gravityviews' ) ) {
499 
500  if ( isset( $_GET['cache'] ) || isset( $_GET['nocache'] ) ) {
501 
502  do_action( 'gravityview_log_debug', 'GravityView_Cache[use_cache] Not using cache: ?cache or ?nocache is in the URL' );
503 
504  $use_cache = false;
505  }
506 
507  }
508 
509  // Has the form been flagged as having changed items in it?
510  if ( $this->in_blacklist() ) {
511 
512  // Delete caches for all items with form IDs XYZ
513  $this->delete( $this->form_ids );
514 
515  // Remove the form from
516  $this->blacklist_remove( $this->form_ids );
517 
518  }
519 
520  // Check the blacklist
521 
522  /**
523  * @filter `gravityview_use_cache` Modify whether to use the cache or not
524  * @param[out,in] boolean $use_cache Previous setting
525  * @param[out] GravityView_Cache $this The GravityView_Cache object
526  */
527  $use_cache = apply_filters( 'gravityview_use_cache', $use_cache, $this );
528 
529  return (boolean) $use_cache;
530  }
531 
532 }
533 
add_hooks()
Add actions for clearing out caches when entries are updated.
Definition: class-cache.php:59
get_key()
Allow public access to get transient key.
in_blacklist($form_ids=NULL)
Is a form ID in the cache blacklist.
entry_updated($form, $lead_id)
When an entry is updated, add the entry&#39;s form to the cache blacklist.
$forms
Definition: data-source.php:20
blacklist_remove($form_ids)
Remove Form IDs from blacklist.
new GravityView_Cache
blacklist_add($form_ids)
Add form IDs to a "blacklist" to force the cache to be refreshed.
__construct($form_ids=NULL, $args=array())
Definition: class-cache.php:42
entry_created($entry, $form)
When an entry is created, add the entry&#39;s form to the cache blacklist.
static has_cap($caps= '', $object_id=null, $user_id=null)
Alias of GravityView_Roles_Capabilities::has_cap()
set_key()
Set the transient key based on the form IDs and the arguments passed to the class.
Handle caching using transients for GravityView.
Definition: class-cache.php:6
const BLACKLIST_OPTION_NAME
Definition: class-cache.php:8
schedule_transient_cleanup()
Schedule expired transient cleanup twice a day.
delete_expired_transients()
Delete expired transients.
get_cache_key_prefix($form_ids=NULL)
Calculate the prefix based on the Form IDs.
if(empty($created_by)) $form_id
$entry
Definition: notes.php:27
use_cache()
Check whether to use cached results, if available.
if(empty($field_settings['content'])) $content
Definition: custom.php:37