Passed
Push — master ( 1d2d34...9378b8 )
by Salah
01:33
created

WPGitHubUpdater::api_check()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 12
nc 4
nop 1
dl 0
loc 22
rs 8.9197
c 1
b 0
f 0
1
<?php
2
3
/*
4
 * Copyright (c) 2017 Salah Alkhwlani <[email protected]>
5
 *
6
 * For the full copyright and license information, please view
7
 * the LICENSE file that was distributed with this source code.
8
 */
9
10
namespace Yemenifree\WpSecurity\Helpers;
11
12
/**
13
 * @version 1.6
14
 *
15
 * @author Joachim Kudish <[email protected]>
16
 *
17
 * @link http://jkudish.com
18
 *
19
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
20
 * @copyright Copyright (c) 2011-2013, Joachim Kudish
21
 *
22
 * GNU General Public License, Free Software Foundation
23
 * <http://creativecommons.org/licenses/GPL/2.0/>
24
 *
25
 * This program is free software; you can redistribute it and/or modify
26
 * it under the terms of the GNU General Public License as published by
27
 * the Free Software Foundation; either version 2 of the License, or
28
 * (at your option) any later version.
29
 *
30
 * This program is distributed in the hope that it will be useful,
31
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33
 * GNU General Public License for more details.
34
 *
35
 * You should have received a copy of the GNU General Public License
36
 * along with this program; if not, write to the Free Software
37
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
38
 */
39
class WPGitHubUpdater
40
{
41
    /**
42
     * GitHub Updater version.
43
     */
44
    const VERSION = 1.6;
45
    /**
46
     * @var array the config for the updater
47
     */
48
    public $config;
49
    /**
50
     * @var array any config that is missing from the initialization of this instance
51
     */
52
    public $missing_config;
53
    /**
54
     * @var array temporiraly store the data fetched from GitHub, allows us to only load the data once per class instance
55
     */
56
    private $github_data;
57
58
    /**
59
     * Class Constructor.
60
     *
61
     * @since 1.0
62
     *
63
     * @param array $config the configuration required for the updater to work
64
     *
65
     * @see has_minimum_config()
66
     */
67
    public function __construct($config = [])
68
    {
69
        $defaults = [
70
            'slug' => plugin_basename(__FILE__),
71
            'proper_folder_name' => \dirname(plugin_basename(__FILE__)),
72
            'sslverify' => true,
73
            'access_token' => '',
74
        ];
75
        $this->config = wp_parse_args($config, $defaults);
76
        // if the minimum config isn't set, issue a warning and bail
77
        if (!$this->has_minimum_config()) {
78
            $message = 'The GitHub Updater was initialized without the minimum required configuration, please check the config in your plugin. The following params are missing: ';
79
            $message .= \implode(',', $this->missing_config);
80
            _doing_it_wrong(__CLASS__, $message, self::VERSION);
81
82
            return;
83
        }
84
        $this->set_defaults();
85
        add_filter('pre_set_site_transient_update_plugins', [$this, 'api_check']);
86
        // Hook into the plugin details screen
87
        add_filter('plugins_api', [$this, 'get_plugin_info'], 10, 3);
88
        add_filter('upgrader_post_install', [$this, 'upgrader_post_install'], 10, 3);
89
        // set timeout
90
        add_filter('http_request_timeout', [$this, 'http_request_timeout']);
91
        // set sslverify for zip download
92
        add_filter('http_request_args', [$this, 'http_request_sslverify'], 10, 2);
93
    }
94
95
    public function has_minimum_config()
96
    {
97
        $this->missing_config = [];
98
        $required_config_params = [
99
            'api_url',
100
            'raw_url',
101
            'github_url',
102
            'zip_url',
103
            'requires',
104
            'tested',
105
            'readme',
106
        ];
107
        foreach ($required_config_params as $required_param) {
108
            if (empty($this->config[$required_param])) {
109
                $this->missing_config[] = $required_param;
110
            }
111
        }
112
113
        return empty($this->missing_config);
114
    }
115
116
    /**
117
     * Set defaults.
118
     *
119
     * @since 1.2
120
     */
121
    public function set_defaults()
122
    {
123
        if (!empty($this->config['access_token'])) {
124
            // See Downloading a zipball (private repo) https://help.github.com/articles/downloading-files-from-the-command-line
125
            \extract(\parse_url($this->config['zip_url'])); // $scheme, $host, $path
126
            $zip_url = $scheme . '://api.github.com/repos' . $path;
127
            $zip_url = add_query_arg(['access_token' => $this->config['access_token']], $zip_url);
128
            $this->config['zip_url'] = $zip_url;
129
        }
130
        if (!isset($this->config['new_version'])) {
131
            $this->config['new_version'] = $this->get_new_version();
132
        }
133
        if (!isset($this->config['last_updated'])) {
134
            $this->config['last_updated'] = $this->get_date();
135
        }
136
        if (!isset($this->config['description'])) {
137
            $this->config['description'] = $this->get_description();
138
        }
139
        $plugin_data = $this->get_plugin_data();
140
        if (!isset($this->config['plugin_name'])) {
141
            $this->config['plugin_name'] = $plugin_data['Name'];
142
        }
143
        if (!isset($this->config['version'])) {
144
            $this->config['version'] = $plugin_data['Version'];
145
        }
146
        if (!isset($this->config['author'])) {
147
            $this->config['author'] = $plugin_data['Author'];
148
        }
149
        if (!isset($this->config['homepage'])) {
150
            $this->config['homepage'] = $plugin_data['PluginURI'];
151
        }
152
        if (!isset($this->config['readme'])) {
153
            $this->config['readme'] = 'README.md';
154
        }
155
    }
156
157
    /**
158
     * Get New Version from GitHub.
159
     *
160
     * @since 1.0
161
     *
162
     * @return int $version the version number
163
     */
164
    public function get_new_version()
165
    {
166
        $version = get_site_transient(\md5($this->config['slug']) . '_new_version');
167
        if ($this->overrule_transients() || (!isset($version) || !$version || '' == $version)) {
168
            $raw_response = $this->remote_get(trailingslashit($this->config['raw_url']) . \basename($this->config['slug']));
169
            if (is_wp_error($raw_response)) {
170
                $version = false;
171
            }
172
            if (\is_array($raw_response)) {
173
                if (!empty($raw_response['body'])) {
174
                    \preg_match('/.*Version\:\s*(.*)$/mi', $raw_response['body'], $matches);
175
                }
176
            }
177
            if (empty($matches[1])) {
178
                $version = false;
179
            } else {
180
                $version = $matches[1];
181
            }
182
            // back compat for older readme version handling
183
            // only done when there is no version found in file name
184
            if (false === $version) {
185
                $raw_response = $this->remote_get(trailingslashit($this->config['raw_url']) . $this->config['readme']);
186
                if (is_wp_error($raw_response)) {
187
                    return $version;
188
                }
189
                \preg_match('#^\s*`*~Current Version\:\s*([^~]*)~#im', $raw_response['body'], $__version);
190
                if (isset($__version[1])) {
191
                    $version_readme = $__version[1];
192
                    if (-1 == \version_compare($version, $version_readme)) {
193
                        $version = $version_readme;
194
                    }
195
                }
196
            }
197
            // refresh every 6 hours
198
            if (false !== $version) {
199
                set_site_transient(\md5($this->config['slug']) . '_new_version', $version, 60 * 60 * 6);
200
            }
201
        }
202
203
        return $version;
204
    }
205
206
    /**
207
     * Check wether or not the transients need to be overruled and API needs to be called for every single page load.
208
     *
209
     * @return bool overrule or not
210
     */
211
    public function overrule_transients()
212
    {
213
        return \defined('WP_GITHUB_FORCE_UPDATE') && WP_GITHUB_FORCE_UPDATE;
214
    }
215
216
    /**
217
     * Interact with GitHub.
218
     *
219
     * @param string $query
220
     *
221
     * @since 1.6
222
     *
223
     * @return mixed
224
     */
225
    public function remote_get($query)
226
    {
227
        if (!empty($this->config['access_token'])) {
228
            $query = add_query_arg(['access_token' => $this->config['access_token']], $query);
229
        }
230
        $raw_response = wp_remote_get($query, [
231
            'sslverify' => $this->config['sslverify'],
232
        ]);
233
234
        return $raw_response;
235
    }
236
237
    /**
238
     * Get update date.
239
     *
240
     * @since 1.0
241
     *
242
     * @return string $date the date
243
     */
244
    public function get_date()
245
    {
246
        $_date = $this->get_github_data();
247
248
        return (!empty($_date->updated_at)) ? \date('Y-m-d', \strtotime($_date->updated_at)) : false;
249
    }
250
251
    /**
252
     * Get GitHub Data from the specified repository.
253
     *
254
     * @since 1.0
255
     *
256
     * @return array $github_data the data
257
     */
258
    public function get_github_data()
259
    {
260
        if (isset($this->github_data) && !empty($this->github_data)) {
261
            $github_data = $this->github_data;
262
        } else {
263
            $github_data = get_site_transient(\md5($this->config['slug']) . '_github_data');
264
            if ($this->overrule_transients() || (!isset($github_data) || !$github_data || '' == $github_data)) {
265
                $github_data = $this->remote_get($this->config['api_url']);
266
                if (is_wp_error($github_data)) {
267
                    return false;
268
                }
269
                $github_data = \json_decode($github_data['body']);
270
                // refresh every 6 hours
271
                set_site_transient(\md5($this->config['slug']) . '_github_data', $github_data, 60 * 60 * 6);
272
            }
273
            // Store the data in this class instance for future calls
274
            $this->github_data = $github_data;
275
        }
276
277
        return $github_data;
278
    }
279
280
    /**
281
     * Get plugin description.
282
     *
283
     * @since 1.0
284
     *
285
     * @return string $description the description
286
     */
287
    public function get_description()
288
    {
289
        $_description = $this->get_github_data();
290
291
        return (!empty($_description->description)) ? $_description->description : false;
292
    }
293
294
    /**
295
     * Get Plugin data.
296
     *
297
     * @since 1.0
298
     *
299
     * @return object $data the data
300
     */
301
    public function get_plugin_data()
302
    {
303
        include_once ABSPATH . '/wp-admin/includes/plugin.php';
304
        $data = get_plugin_data(WP_PLUGIN_DIR . '/' . $this->config['slug']);
305
306
        return $data;
307
    }
308
309
    /**
310
     * Callback fn for the http_request_timeout filter.
311
     *
312
     * @since 1.0
313
     *
314
     * @return int timeout value
315
     */
316
    public function http_request_timeout()
317
    {
318
        return 2;
319
    }
320
321
    /**
322
     * Callback fn for the http_request_args filter.
323
     *
324
     * @param unknown $args
325
     * @param unknown $url
326
     *
327
     * @return mixed
328
     */
329
    public function http_request_sslverify($args, $url)
330
    {
331
        if ($this->config['zip_url'] == $url) {
332
            $args['sslverify'] = $this->config['sslverify'];
333
        }
334
335
        return $args;
336
    }
337
338
    /**
339
     * Hook into the plugin update check and connect to GitHub.
340
     *
341
     * @since 1.0
342
     *
343
     * @param object $transient the plugin data transient
344
     *
345
     * @return object $transient updated plugin data transient
346
     */
347
    public function api_check($transient)
348
    {
349
        // Check if the transient contains the 'checked' information
350
        // If not, just return its value without hacking it
351
        if (empty($transient->checked)) {
352
            return $transient;
353
        }
354
        // check the version and decide if it's new
355
        $update = \version_compare($this->config['new_version'], $this->config['version']);
356
        if (1 === $update) {
357
            $response = new stdClass();
358
            $response->new_version = $this->config['new_version'];
359
            $response->slug = $this->config['proper_folder_name'];
360
            $response->url = add_query_arg(['access_token' => $this->config['access_token']], $this->config['github_url']);
361
            $response->package = $this->config['zip_url'];
362
            // If response is false, don't alter the transient
363
            if (false !== $response) {
364
                $transient->response[$this->config['slug']] = $response;
365
            }
366
        }
367
368
        return $transient;
369
    }
370
371
    /**
372
     * Get Plugin info.
373
     *
374
     * @since 1.0
375
     *
376
     * @param bool   $false    always false
377
     * @param string $action   the API function being performed
378
     * @param object $args     plugin arguments
379
     * @param mixed  $response
380
     *
381
     * @return object $response the plugin info
382
     */
383
    public function get_plugin_info($false, $action, $response)
384
    {
385
        // Check if this call API is for the right plugin
386
        if (!isset($response->slug) || $response->slug != $this->config['slug']) {
387
            return false;
388
        }
389
        $response->slug = $this->config['slug'];
390
        $response->plugin_name = $this->config['plugin_name'];
391
        $response->version = $this->config['new_version'];
392
        $response->author = $this->config['author'];
393
        $response->homepage = $this->config['homepage'];
394
        $response->requires = $this->config['requires'];
395
        $response->tested = $this->config['tested'];
396
        $response->downloaded = 0;
397
        $response->last_updated = $this->config['last_updated'];
398
        $response->sections = ['description' => $this->config['description']];
399
        $response->download_link = $this->config['zip_url'];
400
401
        return $response;
402
    }
403
404
    /**
405
     * Upgrader/Updater
406
     * Move & activate the plugin, echo the update message.
407
     *
408
     * @since 1.0
409
     *
410
     * @param bool  $true       always true
411
     * @param mixed $hook_extra not used
412
     * @param array $result     the result of the move
413
     *
414
     * @return array $result the result of the move
415
     */
416
    public function upgrader_post_install($true, $hook_extra, $result)
417
    {
418
        global $wp_filesystem;
419
        // Move & Activate
420
        $proper_destination = WP_PLUGIN_DIR . '/' . $this->config['proper_folder_name'];
421
        $wp_filesystem->move($result['destination'], $proper_destination);
422
        $result['destination'] = $proper_destination;
423
        $activate = activate_plugin(WP_PLUGIN_DIR . '/' . $this->config['slug']);
424
        // Output the update message
425
        $fail = __('The plugin has been updated, but could not be reactivated. Please reactivate it manually.', 'github_plugin_updater');
426
        $success = __('Plugin reactivated successfully.', 'github_plugin_updater');
427
        echo is_wp_error($activate) ? $fail : $success;
428
429
        return $result;
430
    }
431
}
432