| 1 |
<?php |
| 2 |
/** |
| 3 |
* Plugin updates handling. |
| 4 |
* |
| 5 |
* @package Plugin |
| 6 |
* @author Pierre Lannoy <https://pierre.lannoy.fr/>. |
| 7 |
* @since 1.0.0 |
| 8 |
*/ |
| 9 |
|
| 10 |
namespace APCuManager\Plugin; |
| 11 |
|
| 12 |
use APCuManager\System\Nag; |
| 13 |
use APCuManager\System\Option; |
| 14 |
use APCuManager\System\Cache; |
| 15 |
use APCuManager\System\Http; |
| 16 |
use Exception; |
| 17 |
use APCuManager\Plugin\Feature\Schema; |
| 18 |
use APCuManager\System\APCu; |
| 19 |
use APCuManager\System\Markdown; |
| 20 |
|
| 21 |
/** |
| 22 |
* Plugin updates handling. |
| 23 |
* |
| 24 |
* This class defines all code necessary to handle the plugin's updates. |
| 25 |
* |
| 26 |
* @package Plugin |
| 27 |
* @author Pierre Lannoy <https://pierre.lannoy.fr/>. |
| 28 |
* @since 1.0.0 |
| 29 |
*/ |
| 30 |
class Updater { |
| 31 |
|
| 32 |
private $name = APCM_PRODUCT_NAME; |
| 33 |
|
| 34 |
private $slug = APCM_SLUG; |
| 35 |
|
| 36 |
private $version = APCM_VERSION; |
| 37 |
|
| 38 |
private $product = APCM_PRODUCT_URL; |
| 39 |
|
| 40 |
/** |
| 41 |
* Initializes the class, set its properties and performs |
| 42 |
* post-update processes if needed. |
| 43 |
* |
| 44 |
* @since 1.0.0 |
| 45 |
*/ |
| 46 |
public function __construct() { |
| 47 |
$old = Option::network_get( 'version' ); |
| 48 |
Option::network_set( 'version', APCM_VERSION ); |
| 49 |
if ( APCM_VERSION !== $old ) { |
| 50 |
if ( '0.0.0' === $old ) { |
| 51 |
$this->install(); |
| 52 |
// phpcs:ignore |
| 53 |
$message = sprintf( esc_html__( '%1$s has been correctly installed.', 'apcu-manager' ), APCM_PRODUCT_NAME ); |
| 54 |
} else { |
| 55 |
$this->update( $old ); |
| 56 |
// phpcs:ignore |
| 57 |
$message = sprintf( esc_html__( '%1$s has been correctly updated from version %2$s to version %3$s.', 'apcu-manager' ), APCM_PRODUCT_NAME, $old, APCM_VERSION ); |
| 58 |
\DecaLog\Engine::eventsLogger( APCM_SLUG )->notice( $message ); |
| 59 |
// phpcs:ignore |
| 60 |
$message .= ' ' . sprintf( __( 'See <a href="%s">what\'s new</a>.', 'apcu-manager' ), admin_url( 'admin.php?page=apcm-settings&tab=about' ) ); |
| 61 |
} |
| 62 |
Nag::add( 'update', 'info', $message ); |
| 63 |
// Updates dropin function. |
| 64 |
apcm_reset_earlyloading(); |
| 65 |
APCu::reset(); |
| 66 |
} |
| 67 |
if ( ! ( defined( 'POO_SELFUPDATE_BYPASS' ) && POO_SELFUPDATE_BYPASS ) ) { |
| 68 |
add_filter( 'plugins_api', [ $this, 'plugin_info' ], PHP_INT_MIN, 3 ); |
| 69 |
add_filter( 'site_transient_update_plugins', [ $this, 'info_update' ] ); |
| 70 |
add_action( 'upgrader_process_complete', [ $this, 'info_reset' ], 10, 2 ); |
| 71 |
add_filter( 'clean_url', [ $this, 'filter_logo' ], PHP_INT_MAX, 3 ); |
| 72 |
} |
| 73 |
} |
| 74 |
|
| 75 |
/** |
| 76 |
* Performs post-installation processes. |
| 77 |
* |
| 78 |
* @since 1.0.0 |
| 79 |
*/ |
| 80 |
private function install() { |
| 81 |
|
| 82 |
} |
| 83 |
|
| 84 |
/** |
| 85 |
* Performs post-update processes. |
| 86 |
* |
| 87 |
* @param string $from The version from which the plugin is updated. |
| 88 |
* @since 1.0.0 |
| 89 |
*/ |
| 90 |
private function update( $from ) { |
| 91 |
$schema = new Schema(); |
| 92 |
$schema->update(); |
| 93 |
} |
| 94 |
|
| 95 |
/** |
| 96 |
* Get the changelog. |
| 97 |
* |
| 98 |
* @param array $attributes 'style' => 'markdown', 'html'. |
| 99 |
* 'mode' => 'raw', 'clean'. |
| 100 |
* @return string The output of the shortcode, ready to print. |
| 101 |
* @since 1.0.0 |
| 102 |
*/ |
| 103 |
public function sc_get_changelog( $attributes ) { |
| 104 |
$md = new Markdown(); |
| 105 |
|
| 106 |
return $md->get_shortcode( 'CHANGELOG.md', $attributes ); |
| 107 |
} |
| 108 |
|
| 109 |
/** |
| 110 |
* Acquires infos about update |
| 111 |
* |
| 112 |
* @return object The remote info. |
| 113 |
*/ |
| 114 |
private function gather_info() { |
| 115 |
$remotes = Cache::get_global( 'data_update-infos' ); |
| 116 |
if ( ! $remotes ) { |
| 117 |
$remotes = new \stdClass(); |
| 118 |
|
| 119 |
$remote = wp_remote_get( |
| 120 |
str_replace( 'github.com', 'raw.githubusercontent.com', $this->product ) . '/refs/heads/master/plugin.json', |
| 121 |
[ |
| 122 |
'timeout' => 10, |
| 123 |
'headers' => [ |
| 124 |
'Accept' => 'application/vnd.github+json', |
| 125 |
'user-agent' => Http::user_agent(), |
| 126 |
] |
| 127 |
] |
| 128 |
); |
| 129 |
if ( is_wp_error( $remote ) || 200 !== wp_remote_retrieve_response_code( $remote ) || empty( wp_remote_retrieve_body( $remote ) ) ) { |
| 130 |
return false; |
| 131 |
} |
| 132 |
$plugin_info = json_decode( wp_remote_retrieve_body( $remote ), true ); |
| 133 |
$remotes->tested = '6.8.99999';// $plugin_info['tested'] ?? '10.0'; |
| 134 |
$remotes->requires = $plugin_info['requires'] ?? '6.2'; |
| 135 |
$remotes->requires_php = $plugin_info['requires_php'] ?? '7.1'; |
| 136 |
$remotes->author = $plugin_info['author'] ?? '<a href="https://perfops.one">Pierre Lannoy / PerfOps One</a>'; |
| 137 |
$remotes->author_profile = $plugin_info['author_profile'] ?? 'https://profiles.wordpress.org/pierrelannoy/'; |
| 138 |
|
| 139 |
$remote = wp_remote_get( |
| 140 |
'https://releases.perfops.one/' . $this->slug . '.json', |
| 141 |
[ |
| 142 |
'timeout' => 10, |
| 143 |
'headers' => [ |
| 144 |
'user-agent' => Http::user_agent(), |
| 145 |
], |
| 146 |
] |
| 147 |
); |
| 148 |
if ( is_wp_error( $remote ) || 200 !== wp_remote_retrieve_response_code( $remote ) || empty( wp_remote_retrieve_body( $remote ) ) ) { |
| 149 |
return false; |
| 150 |
} |
| 151 |
$release_info = json_decode( wp_remote_retrieve_body( $remote ), true ); |
| 152 |
if ( is_null( $release_info ) || ! is_array( $release_info ) ) { |
| 153 |
return false; |
| 154 |
} |
| 155 |
if ( array_key_exists( 'tag_name', $release_info ) && array_key_exists( 'name', $release_info ) && array_key_exists( 'published_at', $release_info ) && array_key_exists( 'body', $release_info ) && array_key_exists( 'assets', $release_info ) && is_array( $release_info['assets'] ) ) { |
| 156 |
$remotes->version = $release_info['tag_name']; |
| 157 |
$remotes->download_url = $release_info['assets'][0]['browser_download_url'] ?? '-'; |
| 158 |
$remotes->last_updated = substr( $release_info['published_at'], 0, strpos( $release_info['published_at'], 'T' ) ); |
| 159 |
$remotes->changelog = '## ' . $release_info['name'] . "\r\n" . $release_info['body']; |
| 160 |
} else { |
| 161 |
return false; |
| 162 |
} |
| 163 |
if ( '-' === $remotes->download_url ) { |
| 164 |
return false; |
| 165 |
} |
| 166 |
|
| 167 |
Cache::set_global( 'data_update-infos', $remotes, DAY_IN_SECONDS ); |
| 168 |
} |
| 169 |
|
| 170 |
return $remotes; |
| 171 |
} |
| 172 |
|
| 173 |
/** |
| 174 |
* Filters the url logo to be sure it is svg-inlined. |
| 175 |
* |
| 176 |
* @param string $good_protocol_url The cleaned URL to be returned. |
| 177 |
* @param string $original_url The URL prior to cleaning. |
| 178 |
* @param string $_context If 'display', replace ampersands and single quotes only. |
| 179 |
* |
| 180 |
* @return string $good_protocol_url The cleaned URL. |
| 181 |
*/ |
| 182 |
function filter_logo( $good_protocol_url, $original_url, $_context ) { |
| 183 |
if ( 'https://data.' . $this->slug === $original_url ) { |
| 184 |
return Core::get_base64_logo(); |
| 185 |
} |
| 186 |
|
| 187 |
return $good_protocol_url; |
| 188 |
} |
| 189 |
|
| 190 |
/** |
| 191 |
* Filters the response for the current WordPress.org Plugin Installation API request. |
| 192 |
* |
| 193 |
* @param false|object|array $result The result object or array. |
| 194 |
* @param string $action The type of information being requested from the Plugin Installation API. |
| 195 |
* @param object $args Plugin API arguments. |
| 196 |
* @@return false|object|array The result object or array. |
| 197 |
*/ |
| 198 |
function plugin_info( $res, $action, $args ) { |
| 199 |
if ( 'plugin_information' !== $action ) { |
| 200 |
return $res; |
| 201 |
} |
| 202 |
if ( $this->slug !== $args->slug ) { |
| 203 |
return $res; |
| 204 |
} |
| 205 |
$infos = $this->gather_info(); |
| 206 |
if ( ! $infos ) { |
| 207 |
return $res; |
| 208 |
} |
| 209 |
$md = new Markdown(); |
| 210 |
if ( ! is_object( $res ) ) { |
| 211 |
$res = new \stdClass(); |
| 212 |
} |
| 213 |
$res->name = $this->name; |
| 214 |
$res->homepage = 'https://perfops.one/' . $this->slug; |
| 215 |
$res->slug = $this->slug; |
| 216 |
$res->is_community = true; |
| 217 |
$res->external_repository_url = $this->product; |
| 218 |
$res->tested = $infos->tested; |
| 219 |
$res->requires = $infos->requires; |
| 220 |
$res->requires_php = $infos->requires_php; |
| 221 |
$res->last_updated = $infos->last_updated; |
| 222 |
$res->author = $infos->author; |
| 223 |
$res->author_profile = $infos->author_profile; |
| 224 |
$res->version = $infos->version; |
| 225 |
$res->download_link = $infos->download_url; |
| 226 |
$res->trunk = $infos->download_url; |
| 227 |
if ( isset( $res->sections['changelog'] ) ) { |
| 228 |
$res->sections['changelog'] = $md->get_inline( $infos->changelog, [] ) . '<br/><br/><p><a target="_blank" href="' . $res->homepage . '-changelog">CHANGELOG »</a></p>'; |
| 229 |
} else { |
| 230 |
$res->sections = [ |
| 231 |
'changelog' => $md->get_inline( $infos->changelog, [] ) . '<br/><br/><p><a target="_blank" href="' . $res->homepage . '-changelog">CHANGELOG »</a></p>', |
| 232 |
]; |
| 233 |
} |
| 234 |
if ( isset( $res->banners['low'] ) && isset( $res->banners['high'] )) { |
| 235 |
$res->banners['low'] = str_replace( 'github.com', 'raw.githubusercontent.com', $this->product ) . '/refs/heads/master/.wordpress-org/banner-772x250.jpg'; |
| 236 |
$res->banners['high'] = str_replace( 'github.com', 'raw.githubusercontent.com', $this->product ) . '/refs/heads/master/.wordpress-org/banner-1544x500.jpg'; |
| 237 |
} else { |
| 238 |
$res->banners = [ |
| 239 |
'low' => str_replace( 'github.com', 'raw.githubusercontent.com', $this->product ) . '/refs/heads/master/.wordpress-org/banner-772x250.jpg', |
| 240 |
'high' => str_replace( 'github.com', 'raw.githubusercontent.com', $this->product ) . '/refs/heads/master/.wordpress-org/banner-1544x500.jpg' |
| 241 |
]; |
| 242 |
} |
| 243 |
|
| 244 |
return $res; |
| 245 |
} |
| 246 |
|
| 247 |
/** |
| 248 |
* Updates infos transient |
| 249 |
* |
| 250 |
* @param object $transient The transient to update. |
| 251 |
* |
| 252 |
* @return object The updated transient. |
| 253 |
*/ |
| 254 |
public function info_update( $transient ) { |
| 255 |
if ( empty( $transient->checked ) ) { |
| 256 |
return $transient; |
| 257 |
} |
| 258 |
$remote = $this->gather_info(); |
| 259 |
if ( $remote && version_compare( $this->version, $remote->version, '<' ) && version_compare( $remote->requires, get_bloginfo( 'version' ), '<=' ) && version_compare( $remote->requires_php, PHP_VERSION, '<' ) ) { |
| 260 |
$res = new \stdClass(); |
| 261 |
$res->slug = $this->slug; |
| 262 |
$res->plugin = $this->slug . '/' . $this->slug . '.php'; |
| 263 |
$res->new_version = $remote->version; |
| 264 |
//$res->tested = $remote->tested . '.99999'; |
| 265 |
$res->package = $remote->download_url; |
| 266 |
$res->icons = [ |
| 267 |
'svg' => 'https://data.' . $this->slug |
| 268 |
]; |
| 269 |
$transient->response[ $res->plugin ] = $res; |
| 270 |
} |
| 271 |
|
| 272 |
return $transient; |
| 273 |
} |
| 274 |
|
| 275 |
/** |
| 276 |
* Reset update infos |
| 277 |
* |
| 278 |
* @param Plugin_Upgrader $upgrader Upgrader instance. |
| 279 |
* @param array $options Array of bulk item update data. |
| 280 |
*/ |
| 281 |
public function info_reset( $upgrader, $options ) { |
| 282 |
if ( 'update' === $options['action'] && 'plugin' === $options['type'] ) { |
| 283 |
Cache::delete_global( 'data_update-infos' ); |
| 284 |
} |
| 285 |
} |
| 286 |
} |
| 287 |
|