Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 275
0.00% covered (danger)
0.00%
0 / 12
CRAP
n/a
0 / 0
wpcomsh_woa_post_process_job_cache_flush
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
wpcomsh_woa_post_clone_woocommerce
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_woa_post_transfer_update_safecss_to_custom_css
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_woa_post_transfer_maybe_enable_woocommerce_hpos_log
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
wpcomsh_woa_post_transfer_maybe_enable_woocommerce_hpos
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
30
wpcomsh_woa_post_transfer_woo_express_trial_deactivate_plugins
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_woa_post_clone_set_staging_environment_type
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_woa_post_clone_clear_performance_profiler_data
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
20
wpcomsh_woa_post_transfer_install_marketplace_software
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
wpcomsh_woa_post_process_maybe_enable_wordads
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
wpcomsh_woa_post_process_store_woocommerce_connection_details
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 1
240
wpcomsh_woa_post_process_activate_jetpack_modules
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * WPCOMSH functions file.
4 *
5 * @package wpcomsh
6 */
7
8// phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
9
10/**
11 * Clear cache after other post process actions are complete.
12 *
13 * @param array $args       Arguments.
14 * @param array $assoc_args Associated arguments.
15 */
16function wpcomsh_woa_post_process_job_cache_flush( $args, $assoc_args ) {
17    WP_CLI::runcommand(
18        'cache flush',
19        array(
20            'launch'     => false,
21            'exit_error' => false,
22        )
23    );
24}
25add_action( 'wpcomsh_woa_post_transfer', 'wpcomsh_woa_post_process_job_cache_flush', 99, 2 );
26add_action( 'wpcomsh_woa_post_clone', 'wpcomsh_woa_post_process_job_cache_flush', 99, 2 );
27add_action( 'wpcomsh_woa_post_reset', 'wpcomsh_woa_post_process_job_cache_flush', 99, 2 );
28
29/**
30 * Clear WooCommerce plugin cache post clone.
31 *
32 * @param array $args       Arguments.
33 * @param array $assoc_args Associated arguments.
34 */
35function wpcomsh_woa_post_clone_woocommerce( $args, $assoc_args ) {
36    $plugins = array(
37        'woocommerce-payments' => function () {
38            $account = \WC_Payments::get_account_service();
39            $account->clear_cache();
40        },
41    );
42
43    foreach ( $plugins as $plugin => $callback ) {
44        $result = WP_CLI::runcommand(
45            sprintf( '--skip-plugins --skip-themes plugin is-active %s', $plugin ),
46            array(
47                'launch'     => false,
48                'return'     => 'all',
49                'exit_error' => false,
50            )
51        );
52        if ( 0 !== $result->return_code ) {
53            WP_CLI::log( sprintf( 'Skipping inactive plugin: %s', $plugin ) );
54            continue;
55        }
56
57        $callback();
58
59        WP_CLI::log( sprintf( 'Callback executed for %s', $plugin ) );
60    }
61}
62add_action( 'wpcomsh_woa_post_clone', 'wpcomsh_woa_post_clone_woocommerce', 10, 2 );
63
64/**
65 * Convert `safecss` WPCOM-specific post type to `custom_css`.
66 *
67 * @param array $args       Arguments.
68 * @param array $assoc_args Associated arguments.
69 */
70function wpcomsh_woa_post_transfer_update_safecss_to_custom_css( $args, $assoc_args ) {
71    $safecss_posts = get_posts(
72        array(
73            'numberposts' => 1,
74            'post_type'   => 'safecss',
75        )
76    );
77
78    foreach ( $safecss_posts as $safecss_post ) {
79        $safecss_post_id = $safecss_post->ID;
80
81        wp_update_post(
82            (object) array(
83                'ID'        => $safecss_post_id,
84                'post_type' => 'custom_css',
85            )
86        );
87
88        WP_CLI::runcommand(
89            "theme mod set custom_css_post_id {$safecss_post_id}",
90            array(
91                'launch'     => false,
92                'exit_error' => false,
93            )
94        );
95    }
96
97    WP_CLI::success( 'safecss posts updated to custom_css' );
98}
99add_action( 'wpcomsh_woa_post_transfer', 'wpcomsh_woa_post_transfer_update_safecss_to_custom_css', 10, 2 );
100add_action( 'wpcomsh_woa_post_reset', 'wpcomsh_woa_post_transfer_update_safecss_to_custom_css', 10, 2 );
101
102/**
103 * Debug and error logging for the post-transfer action to enable HPOS.
104 *
105 * @param string $message Message to log.
106 */
107function wpcomsh_woa_post_transfer_maybe_enable_woocommerce_hpos_log( $message ) {
108    $message = sprintf( 'maybe_enable_woocommerce_hpos: %s', $message );
109
110    // The error_log call can be uncommented for debugging.
111    // error_log( $message );
112    WPCOMSH_Log::unsafe_direct_log( $message );
113}
114
115/**
116 * Enable HPOS for WooCommerce sites that don't already have it enabled.
117 *
118 * @param array $args       Arguments.
119 * @param array $assoc_args Associated arguments.
120 */
121function wpcomsh_woa_post_transfer_maybe_enable_woocommerce_hpos( $args, $assoc_args ) {
122    // This flag is only set for sites with ECOMMERCE_MANAGED_PLUGINS. Sites without this feature are skipped.
123    $enable_woocommerce_hpos = WP_CLI\Utils\get_flag_value( $assoc_args, 'enable_woocommerce_hpos', false );
124    if ( ! $enable_woocommerce_hpos ) {
125        return;
126    }
127
128    // Verify WooCommerce is installed and active.
129    $woocommerce_is_active = is_plugin_active( 'woocommerce/woocommerce.php' );
130
131    if ( false === $woocommerce_is_active ) {
132        wpcomsh_woa_post_transfer_maybe_enable_woocommerce_hpos_log( 'WooCommerce not active' );
133        return;
134    }
135
136    // Verify HPOS isn't already enabled
137    $option_value = get_option( 'woocommerce_custom_orders_table_enabled', false );
138
139    if ( 'yes' === $option_value ) {
140        wpcomsh_woa_post_transfer_maybe_enable_woocommerce_hpos_log( 'HPOS is already enabled' );
141        return;
142    }
143
144    // Enable HPOS
145    $result = WP_CLI::runcommand(
146        'wc hpos enable',
147        array(
148            'return'     => 'all',
149            'launch'     => false,
150            'exit_error' => false,
151        )
152    );
153    if ( 0 !== $result->return_code ) {
154        wpcomsh_woa_post_transfer_maybe_enable_woocommerce_hpos_log( sprintf( 'Error enabling HPOS: %s', $result->stderr ) );
155        return;
156    }
157
158    wpcomsh_woa_post_transfer_maybe_enable_woocommerce_hpos_log( 'Successfully enabled HPOS' );
159}
160add_action( 'wpcomsh_woa_post_transfer', 'wpcomsh_woa_post_transfer_maybe_enable_woocommerce_hpos', 10, 2 );
161
162/**
163 * Woo Express: Free Trial - deactivate simple site activated plugins.
164 *
165 * @param array $args       Arguments.
166 * @param array $assoc_args Associated arguments.
167 */
168function wpcomsh_woa_post_transfer_woo_express_trial_deactivate_plugins( $args, $assoc_args ) {
169    $deactivate_plugins = WP_CLI\Utils\get_flag_value( $assoc_args, 'woo-express-trial-deactivate-plugins', false );
170    if ( ! $deactivate_plugins ) {
171        return;
172    }
173
174    WP_CLI::runcommand(
175        '--skip-plugins --skip-themes plugin deactivate crowdsignal-forms polldaddy',
176        array(
177            'launch'     => false,
178            'exit_error' => false,
179        )
180    );
181
182    WP_CLI::success( 'Woo Express plugins deactivated' );
183}
184add_action( 'wpcomsh_woa_post_transfer', 'wpcomsh_woa_post_transfer_woo_express_trial_deactivate_plugins', 10, 2 );
185
186/**
187 * Sets the environment type for the site.
188 *
189 * @param array $args       Arguments.
190 * @param array $assoc_args Associated arguments.
191 */
192function wpcomsh_woa_post_clone_set_staging_environment_type( $args, $assoc_args ) {
193    $set_staging_environment = WP_CLI\Utils\get_flag_value( $assoc_args, 'set-staging-environment-type', false );
194    if ( ! $set_staging_environment ) {
195        return;
196    }
197
198    WP_CLI::runcommand(
199        'config set WP_ENVIRONMENT_TYPE staging --type=constant',
200        array(
201            'launch'     => false,
202            'exit_error' => false,
203        )
204    );
205
206    WP_CLI::success( 'Staging environment set' );
207}
208add_action( 'wpcomsh_woa_post_clone', 'wpcomsh_woa_post_clone_set_staging_environment_type', 10, 2 );
209
210/**
211 * Clear performance profiler data.
212 *
213 * @param array $args       Arguments.
214 * @param array $assoc_args Associated arguments.
215 */
216function wpcomsh_woa_post_clone_clear_performance_profiler_data( $args, $assoc_args ) {
217    global $wpdb;
218
219    $clear_performance_profiler_data = WP_CLI\Utils\get_flag_value( $assoc_args, 'clear-performance-profiler-data', false );
220    if ( ! $clear_performance_profiler_data ) {
221        return;
222    }
223
224    if ( get_option( 'wpcom_performance_report_url' ) ) {
225        WP_CLI::log( 'Deleting performance profiler option' );
226        $result = WP_CLI::runcommand(
227            'option delete wpcom_performance_report_url',
228            array(
229                'launch'     => false,
230                'exit_error' => false,
231                'return'     => 'all',
232            )
233        );
234
235        if ( 0 === $result->return_code ) {
236            WP_CLI::success( 'Performance profiler option deleted' );
237        } else {
238            WP_CLI::warning( 'Failed to delete performance profiler option: ' . $result->stderr );
239        }
240    }
241
242    WP_CLI::log( 'Deleting performance profiler postmeta (if they exist)' );
243    $query   = "DELETE FROM {$wpdb->postmeta} WHERE meta_key = '_wpcom_performance_report_url'";
244    $command = sprintf( 'db query "%s"', $query );
245    WP_CLI::runcommand(
246        $command,
247        array(
248            'launch'     => false,
249            'exit_error' => false,
250        )
251    );
252}
253add_action( 'wpcomsh_woa_post_clone', 'wpcomsh_woa_post_clone_clear_performance_profiler_data', 10, 2 );
254
255/**
256 * Install marketplace software after a site transfer.
257 *
258 * @param array $args       Arguments.
259 * @param array $assoc_args Associated arguments.
260 */
261function wpcomsh_woa_post_transfer_install_marketplace_software( $args, $assoc_args ) {
262    $install_marketplace_software = WP_CLI\Utils\get_flag_value( $assoc_args, 'install-marketplace-software', false );
263    if ( ! $install_marketplace_software ) {
264        return;
265    }
266
267    $result = ( new Marketplace_Software_Manager() )->install_marketplace_software();
268    if ( is_wp_error( $result ) ) {
269        WP_CLI::error( $result->get_error_message() );
270        WPCOMSH_Log::unsafe_direct_log( $result->get_error_message() );
271    }
272}
273add_action( 'wpcomsh_woa_post_transfer', 'wpcomsh_woa_post_transfer_install_marketplace_software', 10, 2 );
274
275/**
276 * Sets WordAds options and enables the WordAds Jetpack module if required.
277 *
278 * @param array $args Arguments.
279 * @param array $assoc_args Associated arguments.
280 *
281 * @return void
282 */
283function wpcomsh_woa_post_process_maybe_enable_wordads( $args, $assoc_args ) {
284
285    // wordads-options is expected to be a JSON object with option name=>value pairs.
286    $wordads_options = WP_CLI\Utils\get_flag_value( $assoc_args, 'wordads-options', false );
287
288    if ( false === $wordads_options ) {
289        return;
290    }
291
292    $options_decoded = json_decode( $wordads_options, true );
293
294    if ( ! is_array( $options_decoded ) ) {
295        return;
296    }
297
298    // Set WordAds options.
299    foreach ( $options_decoded as $option => $value ) {
300        // Convert boolean options to string first to work around update_option not setting the option if the value is false.
301        // This sets the option to either '1' if true or '' if false.
302        update_option( $option, is_bool( $value ) ? (string) $value : $value );
303    }
304
305    // Activate the WordAds module.
306    WP_CLI::runcommand(
307        'jetpack module activate wordads',
308        array(
309            'launch'     => false,
310            'exit_error' => false,
311        )
312    );
313
314    WP_CLI::success( 'WordAds options transferred and module activated' );
315}
316add_action( 'wpcomsh_woa_post_transfer', 'wpcomsh_woa_post_process_maybe_enable_wordads', 10, 2 );
317
318/**
319 * Checks for WooCommerce connection details, validates them, and stores them in the database.
320 *
321 * @param array $args       Positional arguments.
322 * @param array $assoc_args Named arguments.
323 */
324function wpcomsh_woa_post_process_store_woocommerce_connection_details( $args, $assoc_args ) {
325    $woocommerce_connection_details = WP_CLI\Utils\get_flag_value( $assoc_args, 'store-woocommerce-connection-details', false );
326    if ( ! $woocommerce_connection_details ) {
327        return;
328    }
329
330    // Validate that we have a valid JSON object.
331    $woocommerce_connection_details_decoded = json_decode( $woocommerce_connection_details, true );
332    if ( ! is_array( $woocommerce_connection_details_decoded ) ) {
333        WP_CLI::warning( 'Invalid WooCommerce connection details provided: ' . $woocommerce_connection_details );
334
335        WPCOMSH_Log::unsafe_direct_log( 'wp wpcomsh: Invalid WooCommerce connection details provided', array( 'woocommerce_connection_details' => $woocommerce_connection_details ) );
336
337        return;
338    }
339
340    $valid_keys = array(
341        'auth'           => array(
342            'access_token',
343            'access_token_secret',
344            'site_id',
345            'user_id',
346            'updated',
347        ),
348        'auth_user_data' => array(
349            'email',
350        ),
351    );
352
353    $required_root_keys = array( 'auth' );
354
355    foreach ( $required_root_keys as $required_root_key ) {
356        if ( ! isset( $woocommerce_connection_details_decoded[ $required_root_key ] ) ) {
357            WP_CLI::warning( 'Invalid WooCommerce connection details provided. Missing ' . $required_root_key );
358
359            WPCOMSH_Log::unsafe_direct_log(
360                'wp wpcomsh: Invalid WooCommerce connection details provided. Missing ' . $required_root_key,
361                array( 'woocommerce_connection_details' => $woocommerce_connection_details_decoded )
362            );
363            return;
364        }
365    }
366
367    $unexpected_root_keys = array_diff( array_keys( $woocommerce_connection_details_decoded ), array_keys( $valid_keys ) );
368    if ( ! empty( $unexpected_root_keys ) ) {
369        WP_CLI::warning( 'Unexpected WooCommerce connection details provided. Ignoring the following root key(s): ' . implode( ', ', $unexpected_root_keys ) );
370        WPCOMSH_Log::unsafe_direct_log(
371            'wp wpcomsh: Unexpected additional WooCommerce connection details',
372            array(
373                'extra_keys'                     => $unexpected_root_keys,
374                'woocommerce_connection_details' => $woocommerce_connection_details_decoded,
375            )
376        );
377        // Keep processing the valid data, so avoid returning early..
378    }
379
380    $option_data = array();
381
382    foreach ( $valid_keys as $valid_key => $required_key_fields ) {
383        if ( ! isset( $woocommerce_connection_details_decoded[ $valid_key ] ) ) {
384            // If the data isn't present, keep going - we validate presence for required keys above.
385            continue;
386        }
387
388        if ( ! is_array( $woocommerce_connection_details_decoded[ $valid_key ] ) ) {
389            WP_CLI::warning( 'Invalid WooCommerce connection details provided. Missing ' . $valid_key );
390            WPCOMSH_Log::unsafe_direct_log(
391                'wp wpcomsh: Invalid WooCommerce connection details provided. Missing ' . $valid_key,
392                array( 'woocommerce_connection_details' => $woocommerce_connection_details_decoded )
393            );
394            return;
395        }
396
397        if ( count( $required_key_fields ) !== count( $woocommerce_connection_details_decoded[ $valid_key ] ) ) {
398            WP_CLI::warning( 'Missing or extra WooCommerce connection details provided. Mismatch in ' . $valid_key );
399            // Keep processing the valid data - we may have new fields that the code isn't ready for.
400        }
401
402        foreach ( $required_key_fields as $required_key_field ) {
403            if ( ! isset( $woocommerce_connection_details_decoded[ $valid_key ][ $required_key_field ] ) ) {
404                WP_CLI::warning( 'Invalid WooCommerce connection details provided. Missing ' . $valid_key . ' => ' . $required_key_field );
405                WPCOMSH_Log::unsafe_direct_log(
406                    'wp wpcomsh: Invalid WooCommerce connection details provided. Missing required field',
407                    array(
408                        'missing_path'                   => "$valid_key => $required_key_field",
409                        'woocommerce_connection_details' => $woocommerce_connection_details_decoded,
410                    )
411                );
412                return;
413            }
414
415            $option_data[ $valid_key ][ $required_key_field ] = $woocommerce_connection_details_decoded[ $valid_key ][ $required_key_field ];
416        }
417    }
418
419    if ( empty( $option_data ) ) {
420        WP_CLI::warning( 'No WooCommerce connection details to update' );
421        WPCOMSH_Log::unsafe_direct_log(
422            'wp wpcomsh: No WooCommerce connection details to update',
423            array( 'woocommerce_connection_details' => $woocommerce_connection_details_decoded )
424        );
425        return;
426    }
427
428    update_option( 'woocommerce_helper_data', $option_data );
429
430    WP_CLI::success( 'WooCommerce connection details stored' );
431
432    if ( class_exists( 'WC_Helper' ) && method_exists( 'WC_Helper', 'refresh_helper_subscriptions' ) ) {
433        // @phan-suppress-current-line UnusedPluginSuppression @phan-suppress-next-line PhanUndeclaredStaticMethod -- We check if the class and method exist before using them; see https://github.com/phan/phan/issues/1204
434        WC_Helper::refresh_helper_subscriptions();
435
436        WP_CLI::success( 'Cleared WooCommerce Helper cache' );
437    }
438}
439add_action( 'wpcomsh_woa_post_clone', 'wpcomsh_woa_post_process_store_woocommerce_connection_details', 10, 2 );
440add_action( 'wpcomsh_woa_post_reset', 'wpcomsh_woa_post_process_store_woocommerce_connection_details', 10, 2 );
441add_action( 'wpcomsh_woa_post_transfer', 'wpcomsh_woa_post_process_store_woocommerce_connection_details', 10, 2 );
442
443/**
444 * Ensures that specific Jetpack modules are activated after a transfer.
445 * Addresses the issue where certain modules like blocks, account-protection, blaze, and wpcom-reader
446 * may be disabled during the transfer process.
447 *
448 * @param array $args       Arguments.
449 * @param array $assoc_args Associated arguments.
450 */
451function wpcomsh_woa_post_process_activate_jetpack_modules( $args, $assoc_args ) {
452
453    if ( ! is_plugin_active( 'jetpack/jetpack.php' ) ) {
454        WP_CLI::warning( 'Jetpack plugin is not active, skipping module activation' );
455        return;
456    }
457
458    // First, make sure the jetpack_blocks_disabled option is deleted
459    delete_option( 'jetpack_blocks_disabled' );
460
461    $modules_to_activate = array(
462        'account-protection',
463        'blaze',
464        'blocks',
465        'wpcom-reader',
466    );
467
468    $activated_modules = array();
469
470    foreach ( $modules_to_activate as $module ) {
471        $result = WP_CLI::runcommand(
472            "jetpack module activate $module",
473            array(
474                'return'     => 'all',
475                'launch'     => false,
476                'exit_error' => false,
477            )
478        );
479
480        if ( 0 === $result->return_code ) {
481            WP_CLI::log( sprintf( 'Successfully activated Jetpack module: %s', $module ) );
482            $activated_modules[] = $module;
483        } else {
484            WP_CLI::warning( sprintf( 'Failed to activate Jetpack module: %s - %s', $module, $result->stderr ) );
485        }
486    }
487
488    // Get a list of all active modules to verify
489    $active_modules_result = WP_CLI::runcommand(
490        'jetpack module list --status=active',
491        array(
492            'return'     => 'all',
493            'launch'     => false,
494            'exit_error' => false,
495        )
496    );
497
498    WP_CLI::log( 'Currently active Jetpack modules:' );
499    WP_CLI::log( $active_modules_result->stdout );
500
501    if ( count( $activated_modules ) === count( $modules_to_activate ) ) {
502        WP_CLI::success( 'Jetpack modules activation completed' );
503    }
504}
505
506// Add this action for all three operation types to ensure modules are always activated
507add_action( 'wpcomsh_woa_post_transfer', 'wpcomsh_woa_post_process_activate_jetpack_modules', 10, 2 );
508add_action( 'wpcomsh_woa_post_reset', 'wpcomsh_woa_post_process_activate_jetpack_modules', 10, 2 );