PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.7.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.7.0
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / vendor / team-updraft / lib-onboarding-wizard / Wizard / Installer / Installer.php

Installer.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 4.7.0, at vendor/team-updraft/lib-onboarding-wizard/Wizard/Installer/Installer.php

503 lines 15.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Updraftplus\Wp_Optimize\Wizard\Installer;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7 if ( ! function_exists( 'is_plugin_active' ) ) {
8 include_once ABSPATH . 'wp-admin/includes/plugin.php';
9 }
10 /**
11 * Install suggested plugins
12 */
13 class Installer {
14
15 private string $slug = '';
16 /**
17 * The slug of the plugin that is calling this class.
18 * It is used to remove the calling plugin from the recommended list.
19 *
20 * @var string
21 */
22 private string $caller_slug = '';
23 private array $plugins = [];
24
25 public $all_installed = true;
26 /**
27 * Constructor
28 */
29 public function __construct( string $caller_slug, string $slug = '' ) {
30 if ( ! current_user_can( 'install_plugins' ) ) {
31 return;
32 }
33
34 $this->caller_slug = $this->sanitize_slug( $caller_slug );
35 $this->slug = $this->sanitize_slug( $slug );
36 }
37
38 /**
39 * Sanitize the slug against our list of accepted plugins.
40 */
41 private function sanitize_slug( string $slug ): string {
42 // get array of slugs from plugins array.
43 $plugins = $this->get_plugins_raw();
44 $slugs = array_map(
45 function ( $plugin ) {
46 return $plugin['slug'];
47 },
48 $plugins
49 );
50
51 // check if slug is in array.
52 if ( in_array( $slug, $slugs, true ) ) {
53 return $slug;
54 }
55 return '';
56 }
57
58 /**
59 * Check if plugin is downloaded
60 */
61 public function plugin_is_downloaded( string $slug ): bool {
62 return file_exists( trailingslashit( WP_PLUGIN_DIR ) . $this->get_activation_slug( $slug ) );
63 }
64
65 /**
66 * Get a single plugin by slug
67 *
68 * @param string $slug The slug of the plugin to get. This is actually the folder name, but used as 'slug' in the WP Api.
69 * @param bool $raw Optional, default false. If true, return the raw array of plugins. If false, return the cleaned and enriched array. Normally only used within this class to prevent infinite loops.
70 * @return array<string, mixed>|null
71 */
72 public function get_plugin( string $slug, bool $raw = false ): ?array {
73 if ( empty( $slug ) ) {
74 return null;
75 }
76
77 $plugins = $this->get_plugins_raw();
78
79 $found_plugins = array_filter(
80 $plugins,
81 function ( $plugin ) use ( $slug ) {
82 return $plugin['slug'] === $slug;
83 }
84 );
85
86 if ( empty( $found_plugins ) ) {
87 return null;
88 }
89
90 if ( $raw ) {
91 // If raw is true, return the first found plugin without enrichment.
92 return reset( $found_plugins );
93 }
94
95 $found_plugin = $this->enrich_plugin_data( $found_plugins );
96 return reset( $found_plugin );
97 }
98
99 /**
100 * Get plugins array, filtered by conditions, enriched with additional information, and limited to a maximum number of plugins.
101 * 1) Remove plugins with conditions that do not meet the requirements (e.g. WooCommerce but no WooCommerce).
102 * 2) If still over 3 plugins, drop plugins where a similar possibly conflicting plugin already is installed. E.g. WordFence is installed => drop AIOS
103 * 3) Still over 3? Remove plugins that are already installed
104 * 4) Still over 3? grab a random set of 3, keep this stable for one week.
105 *
106 * @param bool $include_niche_plugins If '$include_niche_plugins' = true, plugins that are only useful in a specific niche are not shown. E.g. specific niche WooCommerce plugins where we cannot determine if
107 * the user has any use for them.
108 * @param int $max Optional, default 3. Determines how many plugins to return. If more than $max plugins are available, only the first $max will be returned.
109 * @return array<int, array<string, mixed>>
110 * Returns a list of plugin arrays, a single plugin array, or null if not found.
111 */
112 public function get_plugins( bool $include_niche_plugins = true, int $max = 3 ): array {
113 $plugins = $this->get_plugins_raw();
114 // Remove the calling plugin from the list.
115 foreach ( $plugins as $index => $plugin ) {
116 if ( $plugin['slug'] === $this->caller_slug ) {
117 unset( $plugins[ $index ] );
118 }
119 }
120
121 $plugins = $this->enrich_plugin_data( $plugins );
122
123 // If we don't want niche plugins, remove them from the list.
124 if ( ! $include_niche_plugins ) {
125 foreach ( $plugins as $index => $plugin ) {
126 if ( isset( $plugin['niche'] ) && $plugin['niche'] === true ) {
127 unset( $plugins[ $index ] );
128 }
129 }
130 }
131
132 // check php and wp version requirements
133 global $wp_version;
134 foreach ( $plugins as $index => $plugin ) {
135 $required_php_version = $plugin['php'] ?? '5.6';
136 $required_wp_version = $plugin['wp'] ?? '4.0';
137 if (
138 version_compare( PHP_VERSION, $required_php_version, '<' ) ||
139 version_compare( $wp_version, $required_wp_version, '<' ) ) {
140 unset( $plugins[ $index ] );
141 }
142 }
143
144 // Remove plugins where the conditions regarding required plugins or other are not met.
145 foreach ( $plugins as $index => $plugin ) {
146 if ( ! isset( $plugin['conditions'] ) ) {
147 continue;
148 }
149
150 foreach ( $plugin['conditions'] as $condition_type => $condition_value ) {
151 if ( $condition_type === 'plugin' && ! defined( $condition_value ) ) {
152 unset( $plugins[ $index ] );
153 // Skip to the next plugin.
154 continue 2;
155 }
156 }
157 }
158
159 // still more than $max plugins? Check if similar plugins are already installed.
160 if ( count( $plugins ) > $max ) {
161 foreach ( $plugins as $index => $plugin ) {
162 $conflicting_plugins = $plugin['conflicting_plugins'] ?? [];
163 foreach ( $conflicting_plugins as $conflicting_plugin_constant ) {
164 if ( defined( $conflicting_plugin_constant ) ) {
165 unset( $plugins[ $index ] );
166 }
167 if ( count( $plugins ) <= $max ) {
168 break;
169 }
170 }
171 }
172 }
173
174 // if more than 3 plugins in the list, remove installed plugins until we're down to 3.
175 if ( count( $plugins ) > $max ) {
176 foreach ( $plugins as $index => $plugin ) {
177 if ( $plugin['action'] === 'installed' || $plugin['action'] === 'upgrade-to-pro' ) {
178 unset( $plugins[ $index ] );
179 }
180 if ( count( $plugins ) <= $max ) {
181 break;
182 }
183 }
184 }
185
186 $plugins = array_values( $plugins );
187 foreach ( $plugins as $index => $plugin ) {
188 if ( !in_array($plugin['action'],array('installed', 'upgrade-to-pro'))) {
189 $this->all_installed = false;
190 }
191 }
192
193 // if still more than $max plugins, return $max plugins.
194 // in other plugins, we randomize, showing 3 different each week.
195 // in onboarding we always show the top 3.
196 if ( count( $plugins ) > $max ) {
197 if ( $include_niche_plugins ) {
198 $plugins = $this->get_random_selection( $plugins, $max );
199 } else {
200 // just take the first $max plugins.
201 $plugins = array_slice( $plugins, 0, $max );
202 }
203 }
204 // reset array keys again to avoid gaps in the array.
205 $plugins = array_values( $plugins );
206
207 return $plugins;
208 }
209
210 /**
211 * Enrich plugin data with additional information.
212 *
213 * @param array<int, array<string, mixed>> $plugins
214 * @return array<int, array<string, mixed>>
215 */
216 private function enrich_plugin_data( array $plugins ): array {
217
218 $icon_url = plugin_dir_url(__FILE__) . 'icons/';
219 $admin_url = is_multisite() ? network_admin_url( 'plugin-install.php?s=' ) : admin_url( 'plugin-install.php?s=' );
220 foreach ( $plugins as $index => $plugin ) {
221 $action = $this->get_plugin_action( $plugin['slug'] );
222 $plugins[ $index ]['action'] = $action;
223 $plugins[ $index ] ['id'] = $plugin['slug'];
224 $plugins[ $index ]['icon'] = isset( $plugin['icon'] ) ? $icon_url . $plugin['icon'] : '';
225 $search_url = '#';
226 if ( $action !== 'installed' && $action !== 'upgrade-to-pro' ) {
227 $search_url = isset( $plugin['search_url'] ) ? $admin_url . $plugin['search_url'] : '';
228 }
229 // add utm parameters to the upgrade_url.
230 if ( isset( $plugin['upgrade_url'] ) ) {
231 $plugins[ $index ]['upgrade_url'] = add_query_arg(
232 [
233 'utm_source' => 'teamupdraft',
234 'utm_plugin' => $this->caller_slug,
235 ],
236 $plugin['upgrade_url']
237 );
238 }
239 $plugins[ $index ]['search_url'] = $search_url;
240 }
241 return $plugins;
242 }
243
244 /**
245 * Get raw plugins array, cached in the class variable.
246 *
247 * @return array<int, array<string, mixed>>
248 * Returns a list of plugin arrays.
249 */
250 private function get_plugins_raw(): array {
251 if ( ! empty( $this->plugins ) ) {
252 return $this->plugins;
253 }
254
255 $json_path = __DIR__ . '/plugins.json';
256
257 // phpcs:disable WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
258 $json = file_get_contents( $json_path );
259 // decode the json file.
260 $this->plugins = json_decode( $json, true );
261
262 // add slug to each plugin array.
263 foreach ( $this->plugins as $index => $plugin ) {
264 // the slug as used in this class is actually the foldername of the plugin, so we can use the file path to get it.
265 $this->plugins[ $index ]['slug'] = explode( '/', $plugin['file'] )[0];
266 }
267
268 $defaults = [
269 'file' => '',
270 'slug' => '',
271 'icon' => '',
272 'search_url' => '',
273 'constant_free' => '',
274 'premium' => [
275 'type' => '',
276 'value' => '',
277 ],
278 'wordpress_url' => '',
279 'upgrade_url' => '',
280 'title' => '',
281 'description' => '',
282 'conflicting_plugins' => [],
283 ];
284
285 $this->plugins = array_map(
286 function ( $plugin ) use ( $defaults ) {
287 return wp_parse_args( $plugin, $defaults );
288 },
289 $this->plugins
290 );
291
292 return $this->plugins;
293 }
294
295 /**
296 * Get random selection of plugins.
297 *
298 * @return array<int, array<string, mixed>>|array<string, mixed>
299 * Returns a list of plugin arrays, a single plugin array
300 */
301 private function get_random_selection( array $plugins, int $max ): array {
302 $indexes = get_site_transient( 'teamupdraft_random_plugin_indexes' );
303 if ( false === $indexes ) {
304 $indexes = array_rand( $plugins, $max );
305 $indexes = is_array( $indexes ) ? $indexes : [ $indexes ];
306 set_site_transient( 'teamupdraft_random_plugin_indexes', $indexes, WEEK_IN_SECONDS );
307 }
308
309 $new_plugins = [];
310 foreach ( $indexes as $index ) {
311 if ( isset( $plugins[ $index ] ) ) {
312 $new_plugins[] = $plugins[ $index ];
313 }
314 }
315 return $new_plugins;
316 }
317
318 /**
319 * Check if a plugin is installed based on the detection type and value.
320 */
321 public function premium_is_installed( string $slug ): bool {
322 $plugin = $this->get_plugin( $slug, true );
323 if ( empty( $plugin ) ) {
324 return true;
325 }
326
327 if ( ! isset( $plugin['premium'] ) ) {
328 return true;
329 }
330
331 $detection_type = $plugin['premium']['type'] ?? '';
332 $detection_value = $plugin['premium']['value'] ?? '';
333
334 if ( $detection_type === 'constant' ) {
335 return defined( $detection_value );
336 } elseif ( $detection_type === 'slug' ) {
337 return file_exists( WP_PLUGIN_DIR . '/' . $detection_value );
338 } elseif ( $detection_type === 'function' ) {
339 return function_exists( $detection_value );
340 } elseif ( $detection_type === 'class' ) {
341 return class_exists( $detection_value );
342 }
343 return false;
344 }
345
346 /**
347 * Get the next action that can be completed for this plugin.
348 */
349 public function get_plugin_action( string $slug ): string {
350 $action = 'installed';
351 $plugin = $this->get_plugin( $slug, true );
352 if ( $this->premium_is_installed( $slug ) ) {
353 $action = 'installed';
354 } elseif ( ! $this->plugin_is_downloaded( $slug ) && ! $this->plugin_is_activated( $slug ) ) {
355 $action = 'download';
356 } elseif ( $this->plugin_is_downloaded( $slug ) && ! $this->plugin_is_activated( $slug ) ) {
357 $action = 'activate';
358 } elseif ( isset( $plugin['premium'] ) ) {
359 $action = 'upgrade-to-pro';
360 }
361
362 return $action;
363 }
364
365 /**
366 * Check if plugin is activated
367 */
368 public function plugin_is_activated( string $slug ): bool {
369 $plugin = $this->get_plugin( $slug, true );
370 if ( isset( $plugin['constant_free'] ) &&
371 ( defined( $plugin['constant_free'] ) || class_exists( $plugin['constant_free'] ) )
372 ) {
373 return true;
374 }
375 return is_plugin_active( $this->get_activation_slug( $slug ) );
376 }
377
378 /**
379 * Install plugin
380 */
381 public function install( string $step ): void {
382 if ( ! current_user_can( 'install_plugins' ) ) {
383 return;
384 }
385
386 if ( $step === 'download' ) {
387 $this->download_plugin();
388 }
389 if ( $step === 'activate' ) {
390 $this->activate_plugin();
391 }
392 }
393
394 /**
395 * Get slug to activate plugin with
396 */
397 public function get_activation_slug( string $slug ): ?string {
398 $plugin = $this->get_plugin( $slug, true );
399 if ( empty( $plugin ) ) {
400 return null;
401 }
402 if ( ! isset( $plugin['file'] ) ) {
403 return null;
404 }
405 return $plugin['file'];
406 }
407
408 /**
409 * Download the plugin
410 */
411 public function download_plugin(): bool {
412 if ( ! current_user_can( 'install_plugins' ) ) {
413 return false;
414 }
415
416 if ( get_site_transient( 'teamupdraft_plugin_download_active' ) !== $this->slug ) {
417 set_site_transient( 'teamupdraft_plugin_download_active', $this->slug, MINUTE_IN_SECONDS );
418 $download_url = $this->get_plugin_info( $this->slug, 'download_url' );
419 if ( ! empty( $download_url ) ) {
420 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
421 require_once ABSPATH . 'wp-admin/includes/file.php';
422 include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
423 $skin = new \WP_Ajax_Upgrader_Skin();
424 $upgrader = new \Plugin_Upgrader( $skin );
425 $result = $upgrader->install( $download_url );
426 delete_site_transient( 'teamupdraft_plugin_download_active' );
427 if ( is_wp_error( $result ) ) {
428 return false;
429 }
430 } else {
431 delete_site_transient( 'teamupdraft_plugin_download_active' );
432 return false;
433 }
434 }
435 return true;
436 }
437
438 /**
439 * Activate the plugin
440 */
441 public function activate_plugin(): bool {
442 if ( ! current_user_can( 'install_plugins' ) ) {
443 return false;
444 }
445 $file = $this->get_activation_slug( $this->slug );
446 $networkwide = is_multisite();
447 $result = activate_plugin( $file, '', $networkwide );
448 if ( is_wp_error( $result ) ) {
449 return false;
450 }
451 // plugin successfully activated. We save an option so the just installed plugin knows how it was installed.
452 update_site_option( 'teamupdraft_installation_source_' . $this->slug, $this->caller_slug );
453 return true;
454 }
455
456 /**
457 * Retrieve plugin info for rating or download.
458 *
459 * @uses plugins_api() Get the plugin data.
460 * @param string $slug The WP.org directory repo slug of the plugin.
461 * @param string $type The type of info we need, download_url, rating, or num_ratings.
462 * @version 1.0
463 */
464 public function get_plugin_info( string $slug, string $type ): string {
465
466 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
467 $slug = $this->sanitize_slug( $slug );
468 $plugin_info = get_site_transient( 'teamupdraft_' . $slug . '_plugin_info' );
469 if ( empty( $plugin_info ) ) {
470 $plugin_info_total = plugins_api( 'plugin_information', [ 'slug' => $slug ] );
471 if ( ! is_wp_error( $plugin_info_total ) ) {
472
473 $download_link = '';
474 if ( isset( $plugin_info_total->versions['trunk'] ) ) {
475 $download_link = $plugin_info_total->versions['trunk'];
476 } elseif ( isset( $plugin_info_total->download_link ) ) {
477 $download_link = $plugin_info_total->download_link;
478 } elseif ( isset( $plugin_info_total->slug ) && ! empty( $plugin_info_total->slug ) ) {
479 // Fallback: Construct plugin page link on WordPress.org
480 $download_link = 'https://wordpress.org/plugins/' . sanitize_title($plugin_info_total->slug) . '/';
481 }
482
483 $plugin_info = [
484 // the plugin_info properties are not described, but do exist.
485 // @phpstan-ignore-next-line.
486 'download_url' => esc_url_raw($download_link),
487 // @phpstan-ignore-next-line.
488 'rating' => $plugin_info_total->rating,
489 // @phpstan-ignore-next-line.
490 'num_ratings' => $plugin_info_total->num_ratings,
491 ];
492 set_site_transient( 'teamupdraft_' . $slug . '_plugin_info', $plugin_info, WEEK_IN_SECONDS );
493 }
494 }
495
496 if ( isset( $plugin_info[ $type ] ) ) {
497 return $plugin_info[ $type ];
498 }
499
500 return '';
501 }
502 }
503