Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
67.92% covered (warning)
67.92%
36 / 53
70.00% covered (warning)
70.00%
7 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
Hybrid_Product
68.63% covered (warning)
68.63%
35 / 51
70.00% covered (warning)
70.00%
7 / 10
79.27
0.00% covered (danger)
0.00%
0 / 1
 is_plugin_installed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 is_plugin_active
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 is_standalone_plugin_active
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 is_active
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 is_activated
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
3
 activate_plugin
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
4.13
 do_product_specific_activation
38.89% covered (danger)
38.89%
7 / 18
0.00% covered (danger)
0.00%
0 / 1
44.86
 deactivate
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 do_product_specific_deactivation
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 install_and_activate_standalone
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
7.19
1<?php
2/**
3 * Base product
4 *
5 * @package my-jetpack
6 */
7
8namespace Automattic\Jetpack\My_Jetpack;
9
10use Automattic\Jetpack\Modules;
11use Automattic\Jetpack\Plugins_Installer;
12use WP_Error;
13
14if ( ! defined( 'ABSPATH' ) ) {
15    exit( 0 );
16}
17
18/**
19 * Class responsible for handling the hybrid products
20 *
21 * Hybrid products are those that may work both as a stand-alone plugin or with the Jetpack plugin.
22 */
23abstract class Hybrid_Product extends Product {
24    /**
25     * All hybrid products have a standalone plugin
26     *
27     * @var bool
28     */
29    public static $has_standalone_plugin = true;
30
31    /**
32     * For Hybrid products, we can use either the standalone or Jetpack plugin
33     *
34     * @return bool
35     */
36    public static function is_plugin_installed() {
37        return parent::is_plugin_installed() || parent::is_jetpack_plugin_installed();
38    }
39
40    /**
41     * Checks whether the Product is active
42     *
43     * @return boolean
44     */
45    public static function is_plugin_active() {
46        return parent::is_plugin_active() || parent::is_jetpack_plugin_active();
47    }
48
49    /**
50     * Checks whether the standalone plugin for this product is active
51     *
52     * @return boolean
53     */
54    public static function is_standalone_plugin_active() {
55        return parent::is_plugin_active();
56    }
57
58    /**
59     * Checks whether the Product is active
60     *
61     * @return boolean
62     */
63    public static function is_active() {
64        return parent::is_active() && static::is_module_active();
65    }
66
67    /**
68     * Checks whether the site has switched the product on, whether or not it has a plan for it.
69     *
70     * The standalone plugin being active is enough: off the Jetpack plugin, the module may not be available at all.
71     *
72     * @return boolean
73     */
74    public static function is_activated() {
75        return static::is_standalone_plugin_active() || ( static::is_jetpack_plugin_active() && static::is_module_active() );
76    }
77
78    /**
79     * Activates the plugin
80     *
81     * @return null|WP_Error Null on success, WP_Error on invalid file.
82     */
83    public static function activate_plugin() {
84        /*
85         * Activate self-installed plugin if it's installed.
86         */
87        if ( parent::is_plugin_installed() && static::should_use_standalone_plugin() ) {
88            return activate_plugin( static::get_installed_plugin_filename() );
89        }
90
91        /*
92         * Otherwise, activate Jetpack plugin.
93         */
94        if ( static::is_jetpack_plugin_installed() ) {
95            return activate_plugin( static::get_installed_plugin_filename( 'jetpack' ) );
96        }
97
98        return new WP_Error( 'plugin_not_found', __( 'Activation failed. Plugin is not installed', 'jetpack-my-jetpack' ) );
99    }
100
101    /**
102     * Activates the product. If the Hybrid product has declared a jetpack module name, let's try to activate it if Jetpack plugin is active
103     *
104     * @param bool|WP_Error $product_activation Is the result of the top level activation actions. You probably won't do anything if it is an WP_Error.
105     * @param bool          $local              Skip plan reads and activate only modules that work locally.
106     * @return bool|WP_Error
107     */
108    public static function do_product_specific_activation( $product_activation, $local = false ) {
109
110        if ( is_wp_error( $product_activation ) ) {
111            // If we failed to install the stand-alone plugin because the package was not found, let's try and install Jetpack plugin instead.
112            // This might happen, for example, while the stand-alone plugin was not released to the WP.org repository yet.
113            if ( 'no_package' === $product_activation->get_error_code() ) {
114                $product_activation = Plugins_Installer::install_plugin( self::JETPACK_PLUGIN_SLUG );
115                if ( ! is_wp_error( $product_activation ) ) {
116                    $product_activation = static::activate_plugin();
117                }
118            }
119            if ( is_wp_error( $product_activation ) ) {
120                return $product_activation;
121            }
122        }
123
124        $module_name = static::get_module_name();
125        if ( ! empty( $module_name ) ) {
126            if ( $local && ! Main_Features::module_works_locally( $module_name ) ) {
127                return true;
128            }
129
130            // Only activate the module if the plan supports it
131            // We don't want to throw an error for a missing plan here since we try activation before purchase
132            if ( ! $local && static::$requires_plan && ! static::has_any_plan_for_product() ) {
133                return true;
134            }
135
136            $module_activation = ( new Modules() )->activate( $module_name, false, false );
137
138            if ( ! $module_activation ) {
139                return new WP_Error( 'module_activation_failed', __( 'Error activating Jetpack module', 'jetpack-my-jetpack' ) );
140            }
141
142            return $module_activation;
143        }
144
145        return true;
146    }
147
148    /**
149     * Deactivate the plugin and its saved active Jetpack module.
150     * Otherwise products switched on through the Jetpack module stay half on.
151     *
152     * @return true|WP_Error True on success, or an error if module deactivation fails.
153     */
154    public static function deactivate() {
155        // Before the parent call: deactivating the standalone plugin can change which module the product reads.
156        $module_name = static::get_module_name();
157        parent::deactivate();
158        return static::do_product_specific_deactivation( $module_name );
159    }
160
161    /**
162     * Deactivate the product's saved active Jetpack module.
163     *
164     * @since $$next-version$$
165     * @param string|null $module_name Module selected before deactivating the plugin.
166     * @return true|WP_Error True on success, or an error if module deactivation fails.
167     */
168    public static function do_product_specific_deactivation( $module_name ) {
169        $modules              = new Modules();
170        $saved_active_modules = (array) \Jetpack_Options::get_option( 'active_modules', array() );
171
172        // Modules::deactivate() can't remove a module only a plugin's filter forces on, nor a saved one that is unavailable.
173        if (
174            ! empty( $module_name )
175            && in_array( $module_name, $saved_active_modules, true )
176            && $modules->is_active( $module_name )
177        ) {
178            if ( ! $modules->deactivate( $module_name ) ) {
179                return new WP_Error(
180                    'module_deactivation_failed',
181                    __( 'Error deactivating Jetpack module', 'jetpack-my-jetpack' )
182                );
183            }
184        }
185
186        return true;
187    }
188
189    /**
190     * Install and activate the standalone plugin in the case it's missing.
191     *
192     * @return boolean|WP_Error
193     */
194    public static function install_and_activate_standalone() {
195        $result = parent::install_and_activate_standalone();
196
197        if ( is_wp_error( $result ) ) {
198            return $result;
199        }
200
201        /**
202         * Activate the module as well, if the user has a plan
203         * or the product does not require a plan to work
204         */
205        $module_name = static::get_module_name();
206        if ( static::has_any_plan_for_product() && isset( $module_name ) ) {
207            $module_activation = ( new Modules() )->activate( $module_name, false, false );
208
209            if ( ! $module_activation ) {
210                return new WP_Error( 'module_activation_failed', __( 'Error activating Jetpack module', 'jetpack-my-jetpack' ) );
211            }
212        }
213
214        return true;
215    }
216}