Skip to content

Commit 4b6af6a

Browse files
committed
Add plugin updater
1 parent 8870f7a commit 4b6af6a

File tree

2 files changed

+264
-0
lines changed

2 files changed

+264
-0
lines changed

plugin/lib/class-plugin-updater.php

+247
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
<?php
2+
/**
3+
* The PluginUpdater class which can be used to pull plugin updates from a new location.
4+
* @package your-plugin-package-name
5+
*/
6+
7+
namespace WPEngine_PHPCompat;
8+
9+
// Exit if accessed directly.
10+
if ( ! defined( 'ABSPATH' ) ) {
11+
exit;
12+
}
13+
14+
use stdClass;
15+
16+
/**
17+
* The PluginUpdater class which can be used to pull plugin updates from a new location.
18+
*/
19+
class PluginUpdater {
20+
/**
21+
* The URL where the api is located.
22+
* @var string
23+
*/
24+
private $api_url;
25+
26+
/**
27+
* The amount of time to wait before checking for new updates.
28+
* @var int
29+
*/
30+
private $cache_time;
31+
32+
/**
33+
* These properties are passed in when instantiating to identify the plugin and it's update location.
34+
* @var array
35+
*/
36+
private $properties;
37+
38+
/**
39+
* Get the class constructed.
40+
*
41+
* @param array $properties These properties are passed in when instantiating to identify the plugin and it's update location.
42+
*/
43+
public function __construct( $properties ) {
44+
if (
45+
empty( $properties['plugin_slug'] ) ||
46+
empty( $properties['plugin_basename'] )
47+
) {
48+
error_log( 'WPE Secure Plugin Updater received a malformed request.' );
49+
return;
50+
}
51+
52+
$this->api_url = 'https://wpe-plugin-updates.wpengine.com/';
53+
54+
$this->cache_time = time() + HOUR_IN_SECONDS * 5;
55+
56+
$this->properties = $this->get_full_plugin_properties( $properties, $this->api_url );
57+
58+
if ( ! $this->properties ) {
59+
return;
60+
}
61+
62+
$this->register();
63+
}
64+
65+
/**
66+
* Get the full plugin properties, including the directory name, version, basename, and add a transient name.
67+
*
68+
* @param array $properties These properties are passed in when instantiating to identify the plugin and it's update location.
69+
* @param int $api_url The URL where the api is located.
70+
*/
71+
public function get_full_plugin_properties( $properties, $api_url ) {
72+
$plugins = \get_plugins();
73+
74+
// Scan through all plugins installed and find the one which matches this one in question.
75+
foreach ( $plugins as $plugin_basename => $plugin_data ) {
76+
// Match using the passed-in plugin's basename.
77+
if ( $plugin_basename === $properties['plugin_basename'] ) {
78+
// Add the values we need to the properties.
79+
$properties['plugin_dirname'] = dirname( $plugin_basename );
80+
$properties['plugin_version'] = $plugin_data['Version'];
81+
$properties['plugin_update_transient_name'] = 'wpesu-plugin-' . sanitize_title( $properties['plugin_dirname'] );
82+
$properties['plugin_update_transient_exp_name'] = 'wpesu-plugin-' . sanitize_title( $properties['plugin_dirname'] ) . '-expiry';
83+
$properties['plugin_manifest_url'] = trailingslashit( $api_url ) . trailingslashit( $properties['plugin_slug'] ) . 'info.json';
84+
85+
return $properties;
86+
}
87+
}
88+
89+
// No matching plugin was found installed.
90+
return null;
91+
}
92+
93+
/**
94+
* Register hooks.
95+
*
96+
* @return void
97+
*/
98+
public function register() {
99+
add_filter( 'plugins_api', array( $this, 'filter_plugin_update_info' ), 20, 3 );
100+
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'filter_plugin_update_transient' ) );
101+
}
102+
103+
/**
104+
* Filter the plugin update transient to take over update notifications.
105+
*
106+
* @param ?object $transient_value The value of the `site_transient_update_plugins` transient.
107+
*
108+
* @handles site_transient_update_plugins
109+
* @return object|null
110+
*/
111+
public function filter_plugin_update_transient( $transient_value ) {
112+
// No update object exists. Return early.
113+
if ( empty( $transient_value ) ) {
114+
return $transient_value;
115+
}
116+
117+
$result = $this->fetch_plugin_info();
118+
119+
if ( false === $result ) {
120+
return $transient_value;
121+
}
122+
123+
$res = $this->parse_plugin_info( $result );
124+
125+
if ( version_compare( $this->properties['plugin_version'], $result->version, '<' ) ) {
126+
$transient_value->response[ $res->plugin ] = $res;
127+
$transient_value->checked[ $res->plugin ] = $result->version;
128+
} else {
129+
$transient_value->no_update[ $res->plugin ] = $res;
130+
}
131+
132+
return $transient_value;
133+
}
134+
135+
/**
136+
* Filters the plugin update information.
137+
*
138+
* @param object $res The response to be modified for the plugin in question.
139+
* @param string $action The action in question.
140+
* @param object $args The arguments for the plugin in question.
141+
*
142+
* @handles plugins_api
143+
* @return object
144+
*/
145+
public function filter_plugin_update_info( $res, $action, $args ) {
146+
// Do nothing if this is not about getting plugin information.
147+
if ( 'plugin_information' !== $action ) {
148+
return $res;
149+
}
150+
151+
// Do nothing if it is not our plugin.
152+
if ( $this->properties['plugin_dirname'] !== $args->slug ) {
153+
return $res;
154+
}
155+
156+
$result = $this->fetch_plugin_info();
157+
158+
// Do nothing if we don't get the correct response from the server.
159+
if ( false === $result ) {
160+
return $res;
161+
}
162+
163+
return $this->parse_plugin_info( $result );
164+
}
165+
166+
/**
167+
* Fetches the plugin update object from the WP Product Info API.
168+
*
169+
* @return object|false
170+
*/
171+
private function fetch_plugin_info() {
172+
// Fetch cache first.
173+
$expiry = get_option( $this->properties['plugin_update_transient_exp_name'], 0 );
174+
$response = get_option( $this->properties['plugin_update_transient_name'] );
175+
176+
if ( empty( $expiry ) || time() > $expiry || empty( $response ) ) {
177+
$response = wp_remote_get(
178+
$this->properties['plugin_manifest_url'],
179+
array(
180+
'timeout' => 10,
181+
'headers' => array(
182+
'Accept' => 'application/json',
183+
),
184+
)
185+
);
186+
187+
if (
188+
is_wp_error( $response ) ||
189+
200 !== wp_remote_retrieve_response_code( $response ) ||
190+
empty( wp_remote_retrieve_body( $response ) )
191+
) {
192+
return false;
193+
}
194+
195+
$response = wp_remote_retrieve_body( $response );
196+
197+
// Cache the response.
198+
update_option( $this->properties['plugin_update_transient_exp_name'], $this->cache_time, false );
199+
update_option( $this->properties['plugin_update_transient_name'], $response, false );
200+
}
201+
202+
$decoded_response = json_decode( $response );
203+
204+
if ( json_last_error() !== JSON_ERROR_NONE ) {
205+
return false;
206+
}
207+
208+
return $decoded_response;
209+
}
210+
211+
/**
212+
* Parses the product info response into an object that WordPress would be able to understand.
213+
*
214+
* @param object $response The response object.
215+
*
216+
* @return stdClass
217+
*/
218+
private function parse_plugin_info( $response ) {
219+
220+
global $wp_version;
221+
222+
$res = new stdClass();
223+
$res->name = $response->name;
224+
$res->slug = $response->slug;
225+
$res->version = $response->version;
226+
$res->requires = $response->requires;
227+
$res->download_link = $response->download_link;
228+
$res->trunk = $response->download_link;
229+
$res->new_version = $response->version;
230+
$res->plugin = $this->properties['plugin_basename'];
231+
$res->package = $response->download_link;
232+
233+
// Plugin information modal and core update table use a strict version comparison, which is weird.
234+
// If we're genuinely not compatible with the point release, use our WP tested up to version.
235+
// otherwise use exact same version as WP to avoid false positive.
236+
$res->tested = 1 === version_compare( substr( $wp_version, 0, 3 ), $response->tested )
237+
? $response->tested
238+
: $wp_version;
239+
240+
$res->sections = array(
241+
'description' => $response->sections->description,
242+
'changelog' => $response->sections->changelog,
243+
);
244+
245+
return $res;
246+
}
247+
}

plugin/wpengine-phpcompat.php

+17
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* Requires PHP: 5.6
99
* Author: WP Engine
1010
* Author URI: https://wpengine.com/
11+
* Update URI: false
1112
* License: GPLv2 or later
1213
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
1314
* Text Domain: wpe-php-compat
@@ -178,3 +179,19 @@ function upgrade( $upgrader, $hook_extra ) {
178179
register_activation_hook( __FILE__, __NAMESPACE__ . '\activate' );
179180
register_uninstall_hook( __FILE__, __NAMESPACE__ . '\uninstall' );
180181
add_action( 'upgrader_process_complete', __NAMESPACE__ . '\upgrade', 10, 2 );
182+
183+
/**
184+
* Registers the plugin updater.
185+
*
186+
* @return void
187+
*/
188+
function check_for_upgrades() {
189+
$properties = array(
190+
'plugin_slug' => 'php-compatibility-checker',
191+
'plugin_basename' => plugin_basename( __FILE__ ),
192+
);
193+
194+
require_once __DIR__ . '/lib/class-plugin-updater.php';
195+
new \WPEngine_PHPCompat\PluginUpdater( $properties );
196+
}
197+
add_action( 'admin_init', __NAMESPACE__ . '\check_for_upgrades' );

0 commit comments

Comments
 (0)