Passed
Push — master ( e64af5...ad1bda )
by Chris
03:25
created

MonsterInsights_Updater   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 364
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 125
dl 0
loc 364
rs 6.96
c 1
b 0
f 0
wmc 53

9 Methods

Rating   Name   Duplication   Size   Complexity  
A premium_update() 0 14 4
A premium_update_push() 0 14 4
B update_plugins_filter() 0 29 7
A __construct() 0 37 6
A perform_remote_request() 0 43 3
A plugins_api() 0 9 4
A http_request_args() 0 8 3
A maybe_show_license_expired_message() 0 6 3
F set_plugins_api() 0 43 19

How to fix   Complexity   

Complex Class

Complex classes like MonsterInsights_Updater often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MonsterInsights_Updater, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Updater class.
4
 *
5
 * @since 6.0.0
6
 *
7
 * @package MonsterInsights
8
 * @subpackage Updater
9
 * @author  Chris Christoff
10
 */
11
12
// Exit if accessed directly
13
if ( ! defined( 'ABSPATH' ) ) {
14
    exit;
15
}
16
17
class MonsterInsights_Updater {
18
19
    /**
20
     * Plugin name.
21
     *
22
     * @since 6.0.0
23
     *
24
     * @var bool|string
25
     */
26
    public $plugin_name = false;
27
28
    /**
29
     * Plugin slug.
30
     *
31
     * @since 6.0.0
32
     *
33
     * @var bool|string
34
     */
35
    public $plugin_slug = false;
36
37
    /**
38
     * Plugin path.
39
     *
40
     * @since 6.0.0
41
     *
42
     * @var bool|string
43
     */
44
    public $plugin_path = false;
45
46
    /**
47
     * URL of the plugin.
48
     *
49
     * @since 6.0.0
50
     *
51
     * @var bool|string
52
     */
53
    public $plugin_url = false;
54
55
    /**
56
     * Remote URL for getting plugin updates.
57
     *
58
     * @since 6.0.0
59
     *
60
     * @var bool|string
61
     */
62
    public $remote_url = false;
63
64
    /**
65
     * Version number of the plugin.
66
     *
67
     * @since 6.0.0
68
     *
69
     * @var bool|int
70
     */
71
    public $version = false;
72
73
    /**
74
     * License key for the plugin.
75
     *
76
     * @since 6.0.0
77
     *
78
     * @var bool|string
79
     */
80
    public $key = false;
81
82
    /**
83
     * Holds the update data returned from the API.
84
     *
85
     * @since 6.0.0
86
     *
87
     * @var bool|object
88
     */
89
    public $update = false;
90
91
    /**
92
     * Holds the plugin info details for the update.
93
     *
94
     * @since 6.0.0
95
     *
96
     * @var bool|object
97
     */
98
    public $info = false;
99
100
    /**
101
     * Primary class constructor.
102
     *
103
     * @since 6.0.0
104
     *
105
     * @param array $config Array of updater config args.
106
     */
107
    public function __construct( array $config ) {
108
        // Set class properties.
109
        $accepted_args = array(
110
            'plugin_name',
111
            'plugin_slug',
112
            'plugin_path',
113
            'plugin_url',
114
            'remote_url',
115
            'version',
116
            'key'
117
        );
118
        foreach ( $accepted_args as $arg ) {
119
            $this->$arg = $config[$arg];
120
        }
121
122
        // If the user cannot update plugins, stop processing here.
123
        if ( ! current_user_can( 'update_plugins' ) && ( ! defined( 'DOING_CRON' ) || ! DOING_CRON ) ) {
124
            return;
125
        }
126
127
        // If it's our site, then return so we don't redirect loop.
128
        if ( strpos( site_url(), 'monsterinsights.com' ) !== false ) {
129
            return;
130
        }
131
132
        // Load the updater hooks and filters.
133
        add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'update_plugins_filter' ) );
134
135
        add_filter( 'http_request_args', array( $this, 'http_request_args' ), 10, 2 );
136
        add_filter( 'plugins_api', array( $this, 'plugins_api' ), 10, 3 );
137
138
        // ManageWP premium update filters
139
        //add_filter( 'mwp_premium_update_notification', array( $this, 'premium_update_push' ) );
140
        //add_filter( 'mwp_premium_perform_update', array( $this, 'premium_update' ) );
141
142
	    // Add additional info if the license is expired.
143
	    add_action( 'in_plugin_update_message-'. $this->plugin_path, array( $this, 'maybe_show_license_expired_message' ), 10, 2 );
144
    }
145
146
	public function maybe_show_license_expired_message( $plugin_data, $response ) {
147
		// If there's no download link but there is an update available there's an issue with the license.
148
		if ( empty( $response->package ) ) {
149
			$settings_url = is_network_admin() ? network_admin_url( 'admin.php?page=monsterinsights_network' ) : admin_url( 'admin.php?page=monsterinsights_settings' );
150
			// Translators: First one is a link to the settings page, second one is closing tag, third is a link to MonsterInsights.com
151
			echo '<br />' . sprintf( __( 'In order to enable updates, you need to have a valid license key on the %1$ssettings page%2$s. If your license key is expired or you need a new key, then %3$sclick here to purchase MonsterInsights Pro%2$s.', 'google-analytics-for-wordpress' ), '<a href="' . esc_url( $settings_url ) . '">', '</a>', '<a href="' . monsterinsights_get_url( 'Plugin update', $this->plugin_name ) . '">' );
152
		}
153
	}
154
155
    /**
156
     * Infuse plugin update details when WordPress runs its update checker.
157
     *
158
     * @since 6.0.0
159
     *
160
     * @param object $value  The WordPress update object.
161
     * @return object $value Amended WordPress update object on success, default if object is empty.
162
     */
163
    public function update_plugins_filter( $value ) {
164
165
        // If no update object exists, return early.
166
        if ( empty( $value ) ) {
167
            return $value;
168
        }
169
170
        // Run update check by pinging the external API. If it fails, return the default update object.
171
        if ( ! $this->update ) {
172
            $this->update = $this->perform_remote_request( 'get-plugin-update', array( 'tgm-updater-plugin' => $this->plugin_slug ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->perform_remote_re...=> $this->plugin_slug)) can also be of type string. However, the property $update is declared as type boolean|object. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
173
            if ( ! $this->update || ! empty( $this->update->error ) ) {
0 ignored issues
show
Bug introduced by
The property error does not exist on string|true.
Loading history...
174
                $this->update = false;
175
                return $value;
176
            }
177
        }
178
179
        // Infuse the update object with our data if the version from the remote API is newer.
180
        if ( isset( $this->update->new_version ) && version_compare( $this->version, $this->update->new_version, '<' ) ) {
181
            // The $plugin_update object contains new_version, package, slug and last_update keys.
182
            //$this->update->full_slug             = $this->plugin_slug;
183
            //$this->update->name                  = $this->plugin_name;
184
            $this->update->monsterinsights_plugin  = true;
185
            $this->update->old_version             = $this->version;
186
            $this->update->plugin                  = $this->plugin_path;
187
            $value->response[$this->plugin_path]   = $this->update;
188
        }
189
190
        // Return the update object.
191
        return $value;
192
193
    }
194
195
    /**
196
     * Disables SSL verification to prevent download package failures.
197
     *
198
     * @since 6.0.0
199
     *
200
     * @param array $args  Array of request args.
201
     * @param string $url  The URL to be pinged.
202
     * @return array $args Amended array of request args.
203
     */
204
    public function http_request_args( $args, $url ) {
205
206
        // If this is an SSL request and we are performing an upgrade routine, disable SSL verification.
207
        if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'tgm-updater-action=get-plugin-update' ) ) {
208
            $args['sslverify'] = false;
209
        }
210
211
        return $args;
212
213
    }
214
215
    /**
216
     * Filters the plugins_api function to get our own custom plugin information
217
     * from our private repo.
218
     *
219
     * @since 6.0.0
220
     *
221
     * @param object $api    The original plugins_api object.
222
     * @param string $action The action sent by plugins_api.
223
     * @param array $args    Additional args to send to plugins_api.
224
     * @return object $api   New stdClass with plugin information on success, default response on failure.
225
     */
226
    public function plugins_api( $api, $action = '', $args = null ) {
227
228
        $plugin = ( 'plugin_information' == $action ) && isset( $args->slug ) && ( $this->plugin_slug == $args->slug );
229
230
        // If our plugin matches the request, set our own plugin data, else return the default response.
231
        if ( $plugin ) {
232
            return $this->set_plugins_api( $api );
233
        } else {
234
            return $api;
235
        }
236
237
    }
238
239
    /**
240
     * Pings a remote API to retrieve plugin information for WordPress to display.
241
     *
242
     * @since 6.0.0
243
     *
244
     * @param object $default_api The default API object.
245
     * @return object $api        Return custom plugin information to plugins_api.
246
     */
247
    public function set_plugins_api( $default_api ) {
248
249
        // Perform the remote request to retrieve our plugin information. If it fails, return the default object.
250
        if ( ! $this->info ) {
251
            $this->info = $this->perform_remote_request( 'get-plugin-info', array( 'tgm-updater-plugin' => $this->plugin_slug ) );
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->perform_remote_re...=> $this->plugin_slug)) can also be of type string. However, the property $info is declared as type boolean|object. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
252
            if ( ! $this->info || ! empty( $this->info->error ) ) {
0 ignored issues
show
Bug introduced by
The property error does not exist on string|true.
Loading history...
253
                $this->info = false;
254
                return $default_api;
255
            }
256
        }
257
258
        // Create a new stdClass object and populate it with our plugin information.
259
        $api                        = new stdClass;
260
        $api->name                  = isset( $this->info->name )           ? $this->info->name           : '';
261
        $api->slug                  = isset( $this->info->slug )           ? $this->info->slug           : '';
262
        $api->version               = isset( $this->info->version )        ? $this->info->version        : '';
263
        $api->author                = isset( $this->info->author )         ? $this->info->author         : '';
264
        $api->author_profile        = isset( $this->info->author_profile ) ? $this->info->author_profile : '';
265
        $api->requires              = isset( $this->info->requires )       ? $this->info->requires       : '';
266
        $api->tested                = isset( $this->info->tested )         ? $this->info->tested         : '';
267
        $api->last_updated          = isset( $this->info->last_updated )   ? $this->info->last_updated   : '';
268
        $api->homepage              = isset( $this->info->homepage )       ? $this->info->homepage       : '';
269
270
        $changelog                  = isset( $this->info->changelog )      ? $this->info->changelog       : '';
271
        $description                = isset( $this->info->description )    ? $this->info->description     : '';
272
273
        if ( ! empty( $changelog ) ) {
274
             if ( ! empty( $description ) ) {
275
                $api->sections['description'] = $description;
276
                $api->sections['changelog']   = $changelog;
277
             } else {
278
                $api->sections['changelog']   = $changelog;
279
             }
280
        } else if ( ! empty( $description ) ) {
281
            $api->sections['description'] = $description;
282
        } else {
283
            $api->sections = array();
284
        }
285
286
        $api->download_link         = isset( $this->info->download_link )  ? $this->info->download_link  : '';
287
288
        // Return the new API object with our custom data.
289
        return $api;
290
291
    }
292
293
    // Integration with ManageWP
294
    public function premium_update_push( $premium_update ) {
295
        if ( ! function_exists( 'get_plugin_data' ) ) {
296
            include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
297
        }
298
299
        $update = $this->set_plugins_api( array() );
0 ignored issues
show
Bug introduced by
array() of type array is incompatible with the type object expected by parameter $default_api of MonsterInsights_Updater::set_plugins_api(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

299
        $update = $this->set_plugins_api( /** @scrutinizer ignore-type */ array() );
Loading history...
300
        if ( ! empty( $update ) && version_compare( MONSTERINSIGHTS_VERSION, $update->version, '<' ) ) {
301
            $plugin_data                = get_plugin_data( $update->slug );
302
            $plugin_data['type']        = 'plugin';
303
            $plugin_data['slug']        = $update->slug;
304
            $plugin_data['new_version'] = $update->version;
305
            $premium_update[]           = $plugin_data;
306
        }
307
        return $premium_update;
308
    }
309
310
    // Integration with ManageWP
311
    public function premium_update( $premium_update ) {
312
        if ( ! function_exists( 'get_plugin_data' ) ) {
313
            include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
314
        }
315
316
        $update = $this->set_plugins_api( array() );
0 ignored issues
show
Bug introduced by
array() of type array is incompatible with the type object expected by parameter $default_api of MonsterInsights_Updater::set_plugins_api(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

316
        $update = $this->set_plugins_api( /** @scrutinizer ignore-type */ array() );
Loading history...
317
        if ( ! empty( $update ) && version_compare( MONSTERINSIGHTS_VERSION, $update->version, '<' ) ) {
318
            $plugin_data                = get_plugin_data( $update->slug );
319
            $plugin_data['type']        = 'plugin';
320
            $plugin_data['slug']        = $update->slug;
321
            $plugin_data['url']         = $update->download_link; // OR provide your own callback function for managing the update
322
            array_push( $premium_update, $plugin_data );
323
        }
324
        return $premium_update;
325
    }
326
327
    /**
328
     * Queries the remote URL via wp_remote_post and returns a json decoded response.
329
     *
330
     * @since 6.0.0
331
     *
332
     * @param string $action        The name of the $_POST action var.
333
     * @param array $body           The content to retrieve from the remote URL.
334
     * @param array $headers        The headers to send to the remote URL.
335
     * @param string $return_format The format for returning content from the remote URL.
336
     * @return string|bool          Json decoded response on success, false on failure.
337
     */
338
    public function perform_remote_request( $action, $body = array(), $headers = array(), $return_format = 'json' ) {
339
340
        // Build the body of the request.
341
        $body = wp_parse_args(
342
            $body,
343
            array(
344
                'tgm-updater-action'     => $action,
345
                'tgm-updater-key'        => $this->key,
346
                'tgm-updater-wp-version' => get_bloginfo( 'version' ),
347
                'tgm-updater-referer'    => site_url(),
348
                'tgm-updater-mi-version' => MONSTERINSIGHTS_VERSION,
349
                'tgm-updater-is-pro'     => monsterinsights_is_pro_version(),
350
            )
351
        );
352
        $body = http_build_query( $body, '', '&' );
353
354
        // Build the headers of the request.
355
        $headers = wp_parse_args(
356
            $headers,
357
            array(
358
                'Content-Type'   => 'application/x-www-form-urlencoded',
359
                'Content-Length' => strlen( $body )
360
            )
361
        );
362
363
        // Setup variable for wp_remote_post.
364
        $post = array(
365
            'headers' => $headers,
366
            'body'    => $body
367
        );
368
369
        // Perform the query and retrieve the response.
370
        $response      = wp_remote_post( esc_url_raw( $this->remote_url ), $post );
371
        $response_code = wp_remote_retrieve_response_code( $response );
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type WP_Error; however, parameter $response of wp_remote_retrieve_response_code() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

371
        $response_code = wp_remote_retrieve_response_code( /** @scrutinizer ignore-type */ $response );
Loading history...
372
        $response_body = wp_remote_retrieve_body( $response );
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type WP_Error; however, parameter $response of wp_remote_retrieve_body() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

372
        $response_body = wp_remote_retrieve_body( /** @scrutinizer ignore-type */ $response );
Loading history...
373
374
        // Bail out early if there are any errors.
375
        if ( 200 != $response_code || is_wp_error( $response_body ) ) {
376
            return false;
377
        }
378
379
        // Return the json decoded content.
380
        return json_decode( $response_body );
381
382
    }
383
}
384