/var/www/html/wp-content/plugins/woocommerce/includes/class-wc-cache-helper.php


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
<?php
/**
 * WC_Cache_Helper class.
 *
 * @package WooCommerce\Classes
 */

use Automattic\WooCommerce\Caching\CacheNameSpaceTrait;

defined'ABSPATH' ) || exit;

/**
 * WC_Cache_Helper.
 */
class WC_Cache_Helper {
    use 
CacheNameSpaceTrait;

    
/**
     * Transients to delete on shutdown.
     *
     * @var array Array of transient keys.
     */
    
private static $delete_transients = array();

    
/**
     * Hook in methods.
     */
    
public static function init() {
        
add_filter'nocache_headers', array( __CLASS__'additional_nocache_headers' ), 10 );
        
add_action'shutdown', array( __CLASS__'delete_transients_on_shutdown' ), 10 );
        
add_action'template_redirect', array( __CLASS__'geolocation_ajax_redirect' ) );
        
add_action'wc_ajax_update_order_review', array( __CLASS__'update_geolocation_hash' ), );
        
add_action'admin_notices', array( __CLASS__'notices' ) );
        
add_action'delete_version_transients', array( __CLASS__'delete_version_transients' ), 10 );
        
add_action'wp', array( __CLASS__'prevent_caching' ) );
        
add_action'clean_term_cache', array( __CLASS__'clean_term_cache' ), 10);
        
add_action'edit_terms', array( __CLASS__'clean_term_cache' ), 10);
    }

    
/**
     * Set additional nocache headers.
     *
     * @param array $headers Header names and field values.
     * @since 3.6.0
     */
    
public static function additional_nocache_headers$headers ) {
        global 
$wp_query;

        
$agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? wp_unslash$_SERVER['HTTP_USER_AGENT'] ) : ''// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

        
$set_cache false;

        
/**
         * Allow plugins to enable nocache headers. Enabled for Google weblight.
         *
         * @param bool $enable_nocache_headers Flag indicating whether to add nocache headers. Default: false.
         */
        
if ( apply_filters'woocommerce_enable_nocache_headers'false ) ) {
            
$set_cache true;
        }

        
/**
         * Enabled for Google weblight.
         *
         * @see https://support.google.com/webmasters/answer/1061943?hl=en
         */
        
if ( false !== strpos$agent'googleweblight' ) ) {
            
// no-transform: Opt-out of Google weblight. https://support.google.com/webmasters/answer/6211428?hl=en.
            
$set_cache true;
        }

        if ( 
false !== strpos$agent'Chrome' ) && isset( $wp_query ) && is_cart() ) {
            
$set_cache true;
        }

        if ( 
$set_cache ) {
            
$headers['Cache-Control'] = 'no-transform, no-cache, no-store, must-revalidate';
        }
        return 
$headers;
    }

    
/**
     * Add a transient to delete on shutdown.
     *
     * @since 3.6.0
     * @param string|array $keys Transient key or keys.
     */
    
public static function queue_delete_transient$keys ) {
        
self::$delete_transients array_uniquearray_mergeis_array$keys ) ? $keys : array( $keys ), self::$delete_transients ) );
    }

    
/**
     * Transients that don't need to be cleaned right away can be deleted on shutdown to avoid repetition.
     *
     * @since 3.6.0
     */
    
public static function delete_transients_on_shutdown() {
        if ( 
self::$delete_transients ) {
            foreach ( 
self::$delete_transients as $key ) {
                
delete_transient$key );
            }
            
self::$delete_transients = array();
        }
    }

    
/**
     * Used to clear layered nav counts based on passed attribute names.
     *
     * @since 3.6.0
     * @param array $attribute_keys Attribute keys.
     */
    
public static function invalidate_attribute_count$attribute_keys ) {
        if ( 
$attribute_keys ) {
            foreach ( 
$attribute_keys as $attribute_key ) {
                
self::queue_delete_transient'wc_layered_nav_counts_' $attribute_key );
            }
        }
    }

    
/**
     * Get a hash of the customer location.
     *
     * @return string
     */
    
public static function geolocation_ajax_get_location_hash() {
        
$customer             = new WC_Customer0true );
        
$location             = array();
        
$location['country']  = $customer->get_billing_country();
        
$location['state']    = $customer->get_billing_state();
        
$location['postcode'] = $customer->get_billing_postcode();
        
$location['city']     = $customer->get_billing_city();
        
$location_hash        substrmd5strtolowerimplode''$location ) ) ), 012 );

        
/**
         * Controls the location hash used in geolocation-based caching.
         *
         * @since 3.6.0
         *
         * @param string      $location_hash The hash used for geolocation.
         * @param array       $location      The location/address data.
         * @param WC_Customer $customer      The current customer object.
         */
        
return apply_filters'woocommerce_geolocation_ajax_get_location_hash'$location_hash$location$customer );
    }

    
/**
     * Prevent caching on certain pages
     */
    
public static function prevent_caching() {
        if ( ! 
is_blog_installed() ) {
            return;
        }
        
$page_ids array_filter( array( wc_get_page_id'cart' ), wc_get_page_id'checkout' ), wc_get_page_id'myaccount' ) ) );

        if ( 
is_page$page_ids ) ) {
            
self::set_nocache_constants();
            
nocache_headers();
        }
    }

    
/**
     * When using geolocation via ajax, to bust cache, redirect if the location hash does not equal the querystring.
     *
     * This prevents caching of the wrong data for this request.
     */
    
public static function geolocation_ajax_redirect() {
        if ( 
'geolocation_ajax' === get_option'woocommerce_default_customer_address' ) && ! is_checkout() && ! is_cart() && ! is_account_page() && ! wp_doing_ajax() && empty( $_POST ) ) { // WPCS: CSRF ok, input var ok.
            
$location_hash self::geolocation_ajax_get_location_hash();
            
$current_hash  = isset( $_GET['v'] ) ? wc_cleanwp_unslash$_GET['v'] ) ) : ''// WPCS: sanitization ok, input var ok, CSRF ok.
            
if ( empty( $current_hash ) || $current_hash !== $location_hash ) {
                global 
$wp;

                
$redirect_url trailingslashithome_url$wp->request ) );

                if ( ! empty( 
$_SERVER['QUERY_STRING'] ) ) { // WPCS: Input var ok.
                    
$redirect_url add_query_argwp_unslash$_SERVER['QUERY_STRING'] ), ''$redirect_url ); // WPCS: sanitization ok, Input var ok.
                
}

                if ( ! 
get_option'permalink_structure' ) ) {
                    
$redirect_url add_query_arg$wp->query_string''$redirect_url );
                }

                
$redirect_url add_query_arg'v'$location_hashremove_query_arg( array( 'v''add-to-cart' ), $redirect_url ) );
                
wp_safe_redirectesc_url_raw$redirect_url ), 307 );
                exit;
            }
        }
    }

    
/**
     * Updates the `woocommerce_geo_hash` cookie, which is used to help ensure we display
     * the correct pricing etc to customers, according to their billing country.
     *
     * Note that:
     *
     * A) This only sets the cookie if the default customer address is set to "Geolocate (with
     *    Page Caching Support)".
     *
     * B) It is hooked into the `wc_ajax_update_order_review` action, which has the benefit of
     *    ensuring we update the cookie any time the billing country is changed.
     */
    
public static function update_geolocation_hash() {
        if ( 
'geolocation_ajax' === get_option'woocommerce_default_customer_address' ) ) {
            
wc_setcookie'woocommerce_geo_hash', static::geolocation_ajax_get_location_hash(), time() + HOUR_IN_SECONDS );
        }
    }

    
/**
     * Get transient version.
     *
     * When using transients with unpredictable names, e.g. those containing an md5
     * hash in the name, we need a way to invalidate them all at once.
     *
     * When using default WP transients we're able to do this with a DB query to
     * delete transients manually.
     *
     * With external cache however, this isn't possible. Instead, this function is used
     * to append a unique string (based on time()) to each transient. When transients
     * are invalidated, the transient version will increment and data will be regenerated.
     *
     * Raised in issue https://github.com/woocommerce/woocommerce/issues/5777.
     * Adapted from ideas in http://tollmanz.com/invalidation-schemes/.
     *
     * @param  string  $group   Name for the group of transients we need to invalidate.
     * @param  boolean $refresh true to force a new version.
     * @return string transient version based on time(), 10 digits.
     */
    
public static function get_transient_version$group$refresh false ) {
        
$transient_name  $group '-transient-version';
        
$transient_value get_transient$transient_name );

        if ( 
false === $transient_value || true === $refresh ) {
            
$transient_value = (string) time();

            
set_transient$transient_name$transient_value );
        }

        return 
$transient_value;
    }

    
/**
     * Set constants to prevent caching by some plugins.
     *
     * @param  mixed $return Value to return. Previously hooked into a filter.
     * @return mixed
     */
    
public static function set_nocache_constants$return true ) {
        
wc_maybe_define_constant'DONOTCACHEPAGE'true );
        
wc_maybe_define_constant'DONOTCACHEOBJECT'true );
        
wc_maybe_define_constant'DONOTCACHEDB'true );
        return 
$return;
    }

    
/**
     * Notices function.
     */
    
public static function notices() {
        if ( ! 
function_exists'w3tc_pgcache_flush' ) || ! function_exists'w3_instance' ) ) {
            return;
        }

        
$config   w3_instance'W3_Config' );
        
$enabled  $config->get_integer'dbcache.enabled' );
        
$settings array_map'trim'$config->get_array'dbcache.reject.sql' ) );

        if ( 
$enabled && ! in_array'_wc_session_'$settingstrue ) ) {
            
?>
            <div class="error">
                <p>
                <?php
                
/* translators: 1: key 2: URL */
                
echo wp_kses_postsprintf__'In order for <strong>database caching</strong> to work with WooCommerce you must add %1$s to the "Ignored Query Strings" option in <a href="%2$s">W3 Total Cache settings</a>.''woocommerce' ), '<code>_wc_session_</code>'esc_urladmin_url'admin.php?page=w3tc_dbcache' ) ) ) );
                
?>
                </p>
            </div>
            <?php
        
}
    }

    
/**
     * Clean term caches added by WooCommerce.
     *
     * @since 3.3.4
     * @param array|int $ids Array of ids or single ID to clear cache for.
     * @param string    $taxonomy Taxonomy name.
     */
    
public static function clean_term_cache$ids$taxonomy ) {
        if ( 
'product_cat' === $taxonomy ) {
            
$ids is_array$ids ) ? $ids : array( $ids );

            
$clear_ids = array( );

            foreach ( 
$ids as $id ) {
                
$clear_ids[] = $id;
                
$clear_ids   array_merge$clear_idsget_ancestors$id'product_cat''taxonomy' ) );
            }

            
$clear_ids array_unique$clear_ids );

            foreach ( 
$clear_ids as $id ) {
                
wp_cache_delete'product-category-hierarchy-' $id'product_cat' );
            }
        }
    }

    
/**
     * When the transient version increases, this is used to remove all past transients to avoid filling the DB.
     *
     * Note; this only works on transients appended with the transient version, and when object caching is not being used.
     *
     * @deprecated 3.6.0 Adjusted transient usage to include versions within the transient values, making this cleanup obsolete.
     * @since  2.3.10
     * @param string $version Version of the transient to remove.
     */
    
public static function delete_version_transients$version '' ) {
        if ( ! 
wp_using_ext_object_cache() && ! empty( $version ) ) {
            global 
$wpdb;

            
$limit apply_filters'woocommerce_delete_version_transients_limit'1000 );

            if ( ! 
$limit ) {
                return;
            }

            
$affected $wpdb->query$wpdb->prepare"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s LIMIT %d;"'\_transient\_%' $version$limit ) ); // WPCS: cache ok, db call ok.

            // If affected rows is equal to limit, there are more rows to delete. Delete in 30 secs.
            
if ( $affected === $limit ) {
                
wp_schedule_single_eventtime() + 30'delete_version_transients', array( $version ) );
            }
        }
    }
}

WC_Cache_Helper::init();