PluginProbe
DecaLog / 4.4.0
DecaLog v4.4.0
3.0.2 3.1.0 3.10.0 3.2.0 3.3.0 3.4.0 3.4.1 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.6.3 3.7.0 3.7.1 3.8.0 3.9.0 3.9.1 4.0.0 4.1.0 4.2.0 4.3.0 4.3.1 4.4.0 4.5.0 All 75 releases
decalog / includes / plugin / class-updater.php

class-updater.php in DecaLog 4.4.0, at includes/plugin/class-updater.php

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