/var/www/html/wp-content/plugins/woocommerce/src/Admin/Features/MarketingRecommendations/Init.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
<?php

namespace Automattic\WooCommerce\Admin\Features\MarketingRecommendations;

use 
Automattic\WooCommerce\Admin\RemoteSpecs\RemoteSpecsEngine;

defined'ABSPATH' ) || exit;

/**
 * Marketing Recommendations engine.
 * This goes through the specs and gets marketing recommendations.
 */
class Init extends RemoteSpecsEngine {
    
/**
     * Slug of the category specifying marketing extensions on the WooCommerce.com store.
     *
     * @var string
     */
    
const MARKETING_EXTENSION_CATEGORY_SLUG 'marketing';

    
/**
     * Slug of the subcategory specifying marketing channels on the WooCommerce.com store.
     *
     * @var string
     */
    
const MARKETING_CHANNEL_SUBCATEGORY_SLUG 'sales-channels';

    
/**
     * Constructor.
     */
    
public function __construct() {
        
add_action'woocommerce_updated', array( __CLASS__'delete_specs_transient' ) );
    }

    
/**
     * Delete the specs transient.
     */
    
public static function delete_specs_transient() {
        
MarketingRecommendationsDataSourcePoller::get_instance()->delete_specs_transient();
        
MiscRecommendationsDataSourcePoller::get_instance()->delete_specs_transient();
    }

    
/**
     * Get specs or fetch remotely if they don't exist.
     */
    
public static function get_specs() {
        if ( 
'no' === get_option'woocommerce_show_marketplace_suggestions''yes' ) ) {
            return 
DefaultMarketingRecommendations::get_all();
        }
        
$specs MarketingRecommendationsDataSourcePoller::get_instance()->get_specs_from_data_sources();

        
// Fetch specs if they don't yet exist.
        
if ( ! is_array$specs ) || === count$specs ) ) {
            return 
DefaultMarketingRecommendations::get_all();
        }

        return 
$specs;
    }

    
/**
     * Get misc recommendations specs or fetch remotely if they don't exist.
     *
     * @since 9.5.0
     */
    
public static function get_misc_recommendations_specs() {
        if ( 
'no' === get_option'woocommerce_show_marketplace_suggestions''yes' ) ) {
            return array();
        }
        
$specs MiscRecommendationsDataSourcePoller::get_instance()->get_specs_from_data_sources();

        
// Return empty specs if they don't yet exist.
        
if ( ! is_array$specs ) ) {
            return array();
        }

        return 
$specs;
    }

    
/**
     * Process specs.
     *
     * @param array|null $specs Marketing recommendations spec array.
     * @return array
     */
    
protected static function evaluate_specs( ?array $specs null ) {
        
$suggestions = array();
        
$errors      = array();

        foreach ( 
$specs as $spec ) {
            try {
                
$suggestions[] = self::object_to_array$spec );
            } catch ( 
\Throwable $e ) {
                
$errors[] = $e;
            }
        }

        return array(
            
'suggestions' => $suggestions,
            
'errors'      => $errors,
        );
    }

    
/**
     * Load recommended plugins from WooCommerce.com
     *
     * @return array
     */
    
public static function get_recommended_plugins(): array {
        
$specs   self::get_specs();
        
$results self::evaluate_specs$specs );

        
$specs_to_return $results['suggestions'];
        
$specs_to_save   null;

        if ( empty( 
$specs_to_return ) ) {
            
// When suggestions is empty, replace it with defaults and save for 3 hours.
            
$specs_to_save   DefaultMarketingRecommendations::get_all();
            
$specs_to_return self::evaluate_specs$specs_to_save )['suggestions'];
        } elseif ( 
count$results['errors'] ) > ) {
            
// When suggestions is not empty but has errors, save it for 3 hours.
            
$specs_to_save $specs;
        }

        if ( 
$specs_to_save ) {
            
MarketingRecommendationsDataSourcePoller::get_instance()->set_specs_transient$specs_to_saveHOUR_IN_SECONDS );
        }
        
$errors $results['errors'];
        if ( ! empty( 
$errors ) ) {
            
self::log_errors$errors );
        }

        return 
$specs_to_return;
    }

    
/**
     * Return only the recommended marketing channels from WooCommerce.com.
     *
     * @return array
     */
    
public static function get_recommended_marketing_channels(): array {
        return 
array_filter(
            
self::get_recommended_plugins(),
            function ( array 
$plugin_data ) {
                return 
self::is_marketing_channel_plugin$plugin_data );
            }
        );
    }

    
/**
     * Return all recommended marketing extensions EXCEPT the marketing channels from WooCommerce.com.
     *
     * @return array
     */
    
public static function get_recommended_marketing_extensions_excluding_channels(): array {
        return 
array_filter(
            
self::get_recommended_plugins(),
            function ( array 
$plugin_data ) {
                return 
self::is_marketing_plugin$plugin_data ) && ! self::is_marketing_channel_plugin$plugin_data );
            }
        );
    }

    
/**
     * Load misc recommendations from WooCommerce.com
     *
     * @since 9.5.0
     * @return array
     */
    
public static function get_misc_recommendations(): array {
        
$specs   self::get_misc_recommendations_specs();
        
$results self::evaluate_specs$specs );

        
$specs_to_return $results['suggestions'];
        
$specs_to_save   null;

        if ( empty( 
$specs_to_return ) ) {
            
// When misc_recommendations is empty, replace it with defaults and save for 3 hours.
            
$specs_to_save = array();
        } elseif ( 
count$results['errors'] ) > ) {
            
// When misc_recommendations is not empty but has errors, save it for 3 hours.
            
$specs_to_save $specs;
        }

        if ( 
$specs_to_save ) {
            
MiscRecommendationsDataSourcePoller::get_instance()->set_specs_transient$specs_to_saveHOUR_IN_SECONDS );
        }
        
$errors $results['errors'];
        if ( ! empty( 
$errors ) ) {
            
self::log_errors$errors );
        }

        return 
$specs_to_return;
    }

    
/**
     * Returns whether a plugin is a marketing extension.
     *
     * @param array $plugin_data The plugin properties returned by the API.
     *
     * @return bool
     */
    
protected static function is_marketing_plugin( array $plugin_data ): bool {
        
$categories $plugin_data['categories'] ?? array();

        return 
in_arrayself::MARKETING_EXTENSION_CATEGORY_SLUG$categoriestrue );
    }

    
/**
     * Returns whether a plugin is a marketing channel.
     *
     * @param array $plugin_data The plugin properties returned by the API.
     *
     * @return bool
     */
    
protected static function is_marketing_channel_plugin( array $plugin_data ): bool {
        if ( ! 
self::is_marketing_plugin$plugin_data ) ) {
            return 
false;
        }

        
$subcategories $plugin_data['subcategories'] ?? array();
        foreach ( 
$subcategories as $subcategory ) {
            if ( isset( 
$subcategory['slug'] ) && self::MARKETING_CHANNEL_SUBCATEGORY_SLUG === $subcategory['slug'] ) {
                return 
true;
            }
        }

        return 
false;
    }

    
/**
     * Convert an object to an array.
     * This is used to convert the specs to an array so that they can be returned by the API.
     *
     * @param mixed $obj Object to convert.
     * @param array &$visited Reference to an array keeping track of all seen objects to detect circular references.
     * @return array
     */
    
public static function object_to_array$obj, &$visited = array() ) {
        if ( 
is_object$obj ) ) {
            if ( 
in_array$obj$visitedtrue ) ) {
                
// Circular reference detected.
                
return null;
            }
            
$visited[] = $obj;
            
$obj       = (array) $obj;
        }
        if ( 
is_array$obj ) ) {
            
$new = array();
            foreach ( 
$obj as $key => $val ) {
                
$new$key ] = self::object_to_array$val$visited );
            }
        } else {
            
$new $obj;
        }
        return 
$new;
    }
}