/var/www/html_us/wp-content/plugins/woocommerce/src/Blocks/Patterns/PTKPatternsStore.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
<?php

namespace Automattic\WooCommerce\Blocks\Patterns;

use 
Automattic\WooCommerce\Admin\Features\Features;
use 
WP_Upgrader;

/**
 * PTKPatterns class.
 *
 * @internal
 */
class PTKPatternsStore {
    const 
TRANSIENT_NAME 'ptk_patterns';

    const 
CATEGORY_MAPPING = array(
        
'testimonials' => 'reviews',
    );

    
/**
     * PatternsToolkit instance.
     *
     * @var PTKClient $ptk_client
     */
    
private PTKClient $ptk_client;

    
/**
     * Constructor for the class.
     *
     * @param PTKClient $ptk_client An instance of PatternsToolkit.
     */
    
public function __constructPTKClient $ptk_client ) {
        
$this->ptk_client $ptk_client;

        if ( 
Features::is_enabled'pattern-toolkit-full-composability' ) ) {
            
// We want to flush the cached patterns when:
            // - The WooCommerce plugin is deactivated.
            // - The `woocommerce_allow_tracking` option is disabled.
            //
            // We also want to re-fetch the patterns and update the cache when:
            // - The `woocommerce_allow_tracking` option changes to enabled.
            // - The WooCommerce plugin is activated (if `woocommerce_allow_tracking` is enabled).
            // - The WooCommerce plugin is updated.

            
add_action'woocommerce_activated_plugin', array( $this'flush_or_fetch_patterns' ), 10);
            
add_action'update_option_woocommerce_allow_tracking', array( $this'flush_or_fetch_patterns' ), 10);
            
add_action'deactivated_plugin', array( $this'flush_cached_patterns' ), 10);
            
add_action'upgrader_process_complete', array( $this'fetch_patterns_on_plugin_update' ), 10);

            
// This is the scheduled action that takes care of flushing and re-fetching the patterns from the PTK API.
            
add_action'fetch_patterns', array( $this'fetch_patterns' ) );
        }
    }

    
/**
     * Resets the cached patterns when the `woocommerce_allow_tracking` option is disabled.
     * Resets and fetch the patterns from the PTK when it is enabled (if the scheduler
     * is initialized, it's done asynchronously via a scheduled action).
     *
     * @return void
     */
    
public function flush_or_fetch_patterns() {
        if ( 
$this->allowed_tracking_is_enabled() ) {
            
$this->schedule_fetch_patterns();
            return;
        }

        
$this->flush_cached_patterns();
    }

    
/**
     * Schedule an async action to fetch the PTK patterns when the scheduler is initialized.
     *
     * @return void
     */
    
private function schedule_fetch_patterns() {
        if ( 
did_action'action_scheduler_init' ) ) {
            
$this->schedule_action_if_not_pending'fetch_patterns' );
        } else {
            
add_action(
                
'action_scheduler_init',
                function () {
                    
$this->schedule_action_if_not_pending'fetch_patterns' );
                }
            );
        }
    }

    
/**
     * Schedule an action if it's not already pending.
     *
     * @param string $action The action name to schedule.
     * @return void
     */
    
private function schedule_action_if_not_pending$action ) {
        
$last_request get_transient'last_fetch_patterns_request' );
        
// The most efficient way to check for an existing action is to use `as_has_scheduled_action`, but in unusual
        // cases where another plugin has loaded a very old version of Action Scheduler, it may not be available to us.

        
$has_scheduled_action function_exists'as_has_scheduled_action' ) ? 'as_has_scheduled_action' 'as_next_scheduled_action';
        if ( 
call_user_func$has_scheduled_action$action ) || false !== $last_request ) {
            return;
        }

        
as_schedule_single_actiontime(), $action );
        
set_transient'last_fetch_patterns_request'time(), HOUR_IN_SECONDS );
    }

    
/**
     * Get the patterns from the Patterns Toolkit cache.
     *
     * @return array
     */
    
public function get_patterns() {
        
$patterns get_transientself::TRANSIENT_NAME );

        
// Only if the transient is not set, we schedule fetching the patterns from the PTK.
        
if ( false === $patterns ) {
            
$this->schedule_fetch_patterns();
            return array();
        }

        return 
$patterns;
    }

    
/**
     * Filter the patterns that have external dependencies.
     *
     * @param array $patterns The patterns to filter.
     * @return array
     */
    
private function filter_patterns( array $patterns ) {
        return 
array_values(
            
array_filter(
                
$patterns,
                function ( 
$pattern ) {
                    if ( ! isset( 
$pattern['ID'] ) ) {
                        return 
true;
                    }

                    if ( isset( 
$pattern['post_type'] ) && 'wp_block' !== $pattern['post_type'] ) {
                        return 
false;
                    }

                    if ( 
$this->has_external_dependencies$pattern ) ) {
                        return 
false;
                    }

                    return 
true;
                }
            )
        );
    }

    
/**
     * Re-fetch the patterns when the WooCommerce plugin is updated.
     *
     * @param WP_Upgrader $upgrader_object WP_Upgrader instance.
     * @param array       $options Array of bulk item update data.
     *
     * @return void
     */
    
public function fetch_patterns_on_plugin_update$upgrader_object$options ) {
        if ( 
'update' === $options['action'] && 'plugin' === $options['type'] && isset( $options['plugins'] ) ) {
            foreach ( 
$options['plugins'] as $plugin ) {
                if ( 
str_contains$plugin'woocommerce.php' ) ) {
                    
$this->schedule_fetch_patterns();
                }
            }
        }
    }

    
/**
     * Reset the cached patterns to fetch them again from the PTK.
     *
     * @return void
     */
    
public function flush_cached_patterns() {
        
delete_transientself::TRANSIENT_NAME );
    }

    
/**
     * Reset the cached patterns and fetch them again from the PTK API.
     *
     * @return void
     */
    
public function fetch_patterns() {
        if ( ! 
$this->allowed_tracking_is_enabled() ) {
            return;
        }

        
$this->flush_cached_patterns();

        
$patterns $this->ptk_client->fetch_patterns(
            array(
                
// This is the site where the patterns are stored. Despite the 'wpcomstaging.com' domain suggesting a staging environment, this URL points to the production environment where stable versions of the patterns are maintained.
                
'site'       => 'wooblockpatterns.wpcomstaging.com',
                
'categories' => array(
                    
'_woo_intro',
                    
'_woo_featured_selling',
                    
'_woo_about',
                    
'_woo_reviews',
                    
'_woo_social_media',
                    
'_woo_woocommerce',
                    
'_dotcom_imported_intro',
                    
'_dotcom_imported_about',
                    
'_dotcom_imported_services',
                    
'_dotcom_imported_reviews',
                ),
            )
        );
        if ( 
is_wp_error$patterns ) ) {
            
wc_get_logger()->warning(
                
sprintf(
                
// translators: %s is a generated error message.
                    
__'Failed to get WooCommerce patterns from the PTK: "%s"''woocommerce' ),
                    
$patterns->get_error_message()
                ),
            );
            return;
        }

        
$patterns $this->filter_patterns$patterns );
        
$patterns $this->map_categories$patterns );

        
set_transientself::TRANSIENT_NAME$patterns );
    }

    
/**
     * Check if the user allowed tracking.
     *
     * @return bool
     */
    
private function allowed_tracking_is_enabled(): bool {
        return 
'yes' === get_option'woocommerce_allow_tracking' );
    }

    
/**
     * Change the categories of the patterns to match the ones used in the CYS flow
     *
     * @param array $patterns The patterns to map categories for.
     * @return array The patterns with the categories mapped.
     */
    
private function map_categories( array $patterns ) {
        return 
array_map(
            function ( 
$pattern ) {
                if ( isset( 
$pattern['categories'] ) ) {
                    foreach ( 
$pattern['categories'] as $key => $category ) {
                        if ( isset( 
$category['slug'] ) && isset( self::CATEGORY_MAPPING$key ] ) ) {
                            
$new_category self::CATEGORY_MAPPING$key ];
                            unset( 
$pattern['categories'][ $key ] );
                            
$pattern['categories'][ $new_category ]['slug']  = $new_category;
                            
$pattern['categories'][ $new_category ]['title'] = ucfirst$new_category );
                        }
                    }
                }

                return 
$pattern;
            },
            
$patterns
        
);
    }

    
/**
     * Check if the pattern has external dependencies.
     *
     * @param array $pattern The pattern to check.
     *
     * @return bool
     */
    
private function has_external_dependencies$pattern ) {
        if ( ! isset( 
$pattern['dependencies'] ) || ! is_array$pattern['dependencies'] ) ) {
            return 
false;
        }

        foreach ( 
$pattern['dependencies'] as $dependency ) {
            if ( 
'woocommerce' !== $dependency ) {
                return 
true;
            }
        }

        return 
false;
    }
}