PluginProbe
BlockSpare – Gutenberg Blocks for News, Magazine, Blog & Business Websites / 1.2.3
BlockSpare – Gutenberg Blocks for News, Magazine, Blog & Business Websites v1.2.3
4.1.0 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 1.1.2 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 2.1.2 2.5.0 2.5.3 2.6.1 All 38 releases
blockspare / freemius / includes / class-fs-plugin-updater.php

class-fs-plugin-updater.php in BlockSpare – Gutenberg Blocks for News, Magazine, Blog & Business Websites 1.2.3, at freemius/includes/class-fs-plugin-updater.php

1,519 lines 59.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package Freemius
4 * @copyright Copyright (c) 2015, Freemius, Inc.
5 * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6 * @since 1.0.4
7 *
8 * @link https://github.com/easydigitaldownloads/EDD-License-handler/blob/master/EDD_SL_Plugin_Updater.php
9 */
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 class FS_Plugin_Updater {
16
17 /**
18 * @var Freemius
19 * @since 1.0.4
20 */
21 private $_fs;
22 /**
23 * @var FS_Logger
24 * @since 1.0.4
25 */
26 private $_logger;
27 /**
28 * @var object
29 * @since 1.1.8.1
30 */
31 private $_update_details;
32 /**
33 * @var array
34 * @since 2.1.2
35 */
36 private $_translation_updates;
37
38 private static $_upgrade_basename = null;
39
40 #--------------------------------------------------------------------------------
41 #region Singleton
42 #--------------------------------------------------------------------------------
43
44 /**
45 * @var FS_Plugin_Updater[]
46 * @since 2.0.0
47 */
48 private static $_INSTANCES = array();
49
50 /**
51 * @param Freemius $freemius
52 *
53 * @return FS_Plugin_Updater
54 */
55 static function instance( Freemius $freemius ) {
56 $key = $freemius->get_id();
57
58 if ( ! isset( self::$_INSTANCES[ $key ] ) ) {
59 self::$_INSTANCES[ $key ] = new self( $freemius );
60 }
61
62 return self::$_INSTANCES[ $key ];
63 }
64
65 #endregion
66
67 private function __construct( Freemius $freemius ) {
68 $this->_fs = $freemius;
69
70 $this->_logger = FS_Logger::get_logger( WP_FS__SLUG . '_' . $freemius->get_slug() . '_updater', WP_FS__DEBUG_SDK, WP_FS__ECHO_DEBUG_SDK );
71
72 $this->filters();
73 }
74
75 /**
76 * Initiate required filters.
77 *
78 * @author Vova Feldman (@svovaf)
79 * @since 1.0.4
80 */
81 private function filters() {
82 // Override request for plugin information
83 add_filter( 'plugins_api', array( &$this, 'plugins_api_filter' ), 10, 3 );
84
85 $this->add_transient_filters();
86
87 /**
88 * If user has the premium plugin's code but do NOT have an active license,
89 * encourage him to upgrade by showing that there's a new release, but instead
90 * of showing an update link, show upgrade link to the pricing page.
91 *
92 * @since 1.1.6
93 *
94 */
95 // WP 2.9+
96 add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array(
97 &$this,
98 'catch_plugin_update_row'
99 ), 9 );
100 add_action( "after_plugin_row_{$this->_fs->get_plugin_basename()}", array(
101 &$this,
102 'edit_and_echo_plugin_update_row'
103 ), 11, 2 );
104
105 if ( ! $this->_fs->has_any_active_valid_license() ) {
106 add_action( 'admin_head', array( &$this, 'catch_plugin_information_dialog_contents' ) );
107 }
108
109 if ( ! WP_FS__IS_PRODUCTION_MODE ) {
110 add_filter( 'http_request_host_is_external', array(
111 $this,
112 'http_request_host_is_external_filter'
113 ), 10, 3 );
114 }
115
116 if ( $this->_fs->is_premium() ) {
117 if ( ! $this->is_correct_folder_name() ) {
118 add_filter( 'upgrader_post_install', array( &$this, '_maybe_update_folder_name' ), 10, 3 );
119 }
120
121 add_filter( 'upgrader_pre_install', array( 'FS_Plugin_Updater', '_store_basename_for_source_adjustment' ), 1, 2 );
122 add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 );
123
124 if ( ! $this->_fs->has_any_active_valid_license() ) {
125 add_filter( 'wp_prepare_themes_for_js', array( &$this, 'change_theme_update_info_html' ), 10, 1 );
126 }
127 }
128 }
129
130 /**
131 * @author Leo Fajardo (@leorw)
132 * @since 2.1.4
133 */
134 function catch_plugin_information_dialog_contents() {
135 if (
136 'plugin-information' !== fs_request_get( 'tab', false ) ||
137 $this->_fs->get_slug() !== fs_request_get( 'plugin', false )
138 ) {
139 return;
140 }
141
142 add_action( 'admin_footer', array( &$this, 'edit_and_echo_plugin_information_dialog_contents' ), 0, 1 );
143
144 ob_start();
145 }
146
147 /**
148 * @author Leo Fajardo (@leorw)
149 * @since 2.1.4
150 *
151 * @param string $hook_suffix
152 */
153 function edit_and_echo_plugin_information_dialog_contents( $hook_suffix ) {
154 if (
155 'plugin-information' !== fs_request_get( 'tab', false ) ||
156 $this->_fs->get_slug() !== fs_request_get( 'plugin', false )
157 ) {
158 return;
159 }
160
161 $license = $this->_fs->_get_license();
162
163 $subscription = ( is_object( $license ) && ! $license->is_lifetime() ) ?
164 $this->_fs->_get_subscription( $license->id ) :
165 null;
166
167 $contents = ob_get_clean();
168
169 $update_button_id_attribute_pos = strpos( $contents, 'id="plugin_update_from_iframe"' );
170
171 if ( false !== $update_button_id_attribute_pos ) {
172 $update_button_start_pos = strrpos(
173 substr( $contents, 0, $update_button_id_attribute_pos ),
174 '<a'
175 );
176
177 $update_button_end_pos = ( strpos( $contents, '</a>', $update_button_id_attribute_pos ) + strlen( '</a>' ) );
178
179 /**
180 * The part of the contents without the update button.
181 *
182 * @author Leo Fajardo (@leorw)
183 * @since 2.2.5
184 */
185 $modified_contents = substr( $contents, 0, $update_button_start_pos );
186
187 $update_button = substr( $contents, $update_button_start_pos, ( $update_button_end_pos - $update_button_start_pos ) );
188
189 /**
190 * Replace the plugin information dialog's "Install Update Now" button's text and URL. If there's a license,
191 * the text will be "Renew license" and will link to the checkout page with the license's billing cycle
192 * and quota. If there's no license, the text will be "Buy license" and will link to the pricing page.
193 */
194 $update_button = preg_replace(
195 '/(\<a.+)(id="plugin_update_from_iframe")(.+href=")([^\s]+)(".*\>)(.+)(\<\/a>)/is',
196 is_object( $license ) ?
197 sprintf(
198 '$1$3%s$5%s$7',
199 $this->_fs->checkout_url(
200 is_object( $subscription ) ?
201 ( 1 == $subscription->billing_cycle ? WP_FS__PERIOD_MONTHLY : WP_FS__PERIOD_ANNUALLY ) :
202 WP_FS__PERIOD_LIFETIME,
203 false,
204 array( 'licenses' => $license->quota )
205 ),
206 fs_text_inline( 'Renew license', 'renew-license', $this->_fs->get_slug() )
207 ) :
208 sprintf(
209 '$1$3%s$5%s$7',
210 $this->_fs->pricing_url(),
211 fs_text_inline( 'Buy license', 'buy-license', $this->_fs->get_slug() )
212 ),
213 $update_button
214 );
215
216 /**
217 * Append the modified button.
218 *
219 * @author Leo Fajardo (@leorw)
220 * @since 2.2.5
221 */
222 $modified_contents .= $update_button;
223
224 /**
225 * Append the remaining part of the contents after the update button.
226 *
227 * @author Leo Fajardo (@leorw)
228 * @since 2.2.5
229 */
230 $modified_contents .= substr( $contents, $update_button_end_pos );
231
232 $contents = $modified_contents;
233 }
234
235 echo $contents;
236 }
237
238 /**
239 * @author Vova Feldman (@svovaf)
240 * @since 2.0.0
241 */
242 private function add_transient_filters() {
243 if ( $this->_fs->is_premium() && ! $this->_fs->is_tracking_allowed() ) {
244 $this->_logger->log( 'Opted out sites cannot receive automatic software updates.' );
245
246 return;
247 }
248
249 add_filter( 'pre_set_site_transient_update_plugins', array(
250 &$this,
251 'pre_set_site_transient_update_plugins_filter'
252 ) );
253
254 add_filter( 'pre_set_site_transient_update_themes', array(
255 &$this,
256 'pre_set_site_transient_update_plugins_filter'
257 ) );
258 }
259
260 /**
261 * @author Vova Feldman (@svovaf)
262 * @since 2.0.0
263 */
264 private function remove_transient_filters() {
265 remove_filter( 'pre_set_site_transient_update_plugins', array(
266 &$this,
267 'pre_set_site_transient_update_plugins_filter'
268 ) );
269
270 remove_filter( 'pre_set_site_transient_update_themes', array(
271 &$this,
272 'pre_set_site_transient_update_plugins_filter'
273 ) );
274 }
275
276 /**
277 * Capture plugin update row by turning output buffering.
278 *
279 * @author Vova Feldman (@svovaf)
280 * @since 1.1.6
281 */
282 function catch_plugin_update_row() {
283 ob_start();
284 }
285
286 /**
287 * Overrides default update message format with "renew your license" message.
288 *
289 * @author Vova Feldman (@svovaf)
290 * @since 1.1.6
291 *
292 * @param string $file
293 * @param array $plugin_data
294 */
295 function edit_and_echo_plugin_update_row( $file, $plugin_data ) {
296 $plugin_update_row = ob_get_clean();
297
298 $current = get_site_transient( 'update_plugins' );
299 if ( ! isset( $current->response[ $file ] ) ) {
300 echo $plugin_update_row;
301
302 return;
303 }
304
305 $r = $current->response[ $file ];
306
307 $has_beta_update = $this->_fs->has_beta_update();
308
309 if ( $this->_fs->has_any_active_valid_license() ) {
310 if ( $has_beta_update ) {
311 /**
312 * Turn the "new version" text into "new Beta version".
313 *
314 * Sample input:
315 * There is a new version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
316 * Output:
317 * There is a new Beta version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
318 *
319 * @author Leo Fajardo (@leorw)
320 * @since 2.3.0
321 */
322 $plugin_update_row = preg_replace(
323 '/(\<div.+>)(.+)(\<a.+href="([^\s]+)"([^\<]+)\>.+\<a.+)(\<\/div\>)/is',
324 (
325 '$1' .
326 sprintf(
327 fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ),
328 $has_beta_update ?
329 fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) :
330 fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() ),
331 $this->_fs->get_plugin_title()
332 ) .
333 ' ' .
334 '$3' .
335 '$6'
336 ),
337 $plugin_update_row
338 );
339 }
340 } else {
341 /**
342 * Turn the "new version" text into a link that opens the plugin information dialog when clicked and
343 * make the "View version x details" text link to the checkout page instead of opening the plugin
344 * information dialog when clicked.
345 *
346 * Sample input:
347 * There is a new version of Awesome Plugin available. <a href="...>View version x.y.z details</a> or <a href="...>update now</a>.
348 * Output:
349 * There is a <a href="...>new version</a> of Awesome Plugin available. <a href="...>Buy a license now</a> to access version x.y.z security & feature updates, and support.
350 * OR
351 * There is a <a href="...>new Beta version</a> of Awesome Plugin available. <a href="...>Buy a license now</a> to access version x.y.z security & feature updates, and support.
352 *
353 * @author Leo Fajardo (@leorw)
354 */
355 $plugin_update_row = preg_replace(
356 '/(\<div.+>)(.+)(\<a.+href="([^\s]+)"([^\<]+)\>.+\<a.+)(\<\/div\>)/is',
357 (
358 '$1' .
359 sprintf(
360 fs_text_inline( 'There is a %s of %s available.', 'new-version-available', $this->_fs->get_slug() ),
361 sprintf(
362 '<a href="$4"%s>%s</a>',
363 '$5',
364 $has_beta_update ?
365 fs_text_inline( 'new Beta version', 'new-beta-version', $this->_fs->get_slug() ) :
366 fs_text_inline( 'new version', 'new-version', $this->_fs->get_slug() )
367 ),
368 $this->_fs->get_plugin_title()
369 ) .
370 ' ' .
371 $this->_fs->version_upgrade_checkout_link( $r->new_version ) .
372 '$6'
373 ),
374 $plugin_update_row
375 );
376 }
377
378 if (
379 $this->_fs->is_plugin() &&
380 isset( $r->upgrade_notice ) &&
381 strlen( trim( $r->upgrade_notice ) ) > 0
382 ) {
383 $slug = $this->_fs->get_slug();
384
385 $upgrade_notice_html = sprintf(
386 '<p class="notice fs-upgrade-notice fs-slug-%1$s fs-type-%2$s" data-slug="%1$s" data-type="%2$s"><strong>%3$s</strong> %4$s</p>',
387 $slug,
388 $this->_fs->get_module_type(),
389 fs_text_inline( 'Important Upgrade Notice:', 'upgrade_notice', $slug ),
390 esc_html( $r->upgrade_notice )
391 );
392
393 $plugin_update_row = str_replace( '</div>', '</div>' . $upgrade_notice_html, $plugin_update_row );
394 }
395
396 echo $plugin_update_row;
397 }
398
399 /**
400 * @author Leo Fajardo (@leorw)
401 * @since 2.0.2
402 *
403 * @param array $prepared_themes
404 *
405 * @return array
406 */
407 function change_theme_update_info_html( $prepared_themes ) {
408 $theme_basename = $this->_fs->get_plugin_basename();
409
410 if ( ! isset( $prepared_themes[ $theme_basename ] ) ) {
411 return $prepared_themes;
412 }
413
414 $themes_update = get_site_transient( 'update_themes' );
415 if ( ! isset( $themes_update->response[ $theme_basename ] ) ||
416 empty( $themes_update->response[ $theme_basename ]['package'] )
417 ) {
418 return $prepared_themes;
419 }
420
421 $prepared_themes[ $theme_basename ]['update'] = preg_replace(
422 '/(\<p.+>)(.+)(\<a.+\<a.+)\.(.+\<\/p\>)/is',
423 '$1 $2 ' . $this->_fs->version_upgrade_checkout_link( $themes_update->response[ $theme_basename ]['new_version'] ) .
424 '$4',
425 $prepared_themes[ $theme_basename ]['update']
426 );
427
428 // Set to false to prevent the "Update now" link for the context theme from being shown on the "Themes" page.
429 $prepared_themes[ $theme_basename ]['hasPackage'] = false;
430
431 return $prepared_themes;
432 }
433
434 /**
435 * Since WP version 3.6, a new security feature was added that denies access to repository with a local ip.
436 * During development mode we want to be able updating plugin versions via our localhost repository. This
437 * filter white-list all domains including "api.freemius".
438 *
439 * @link http://www.emanueletessore.com/wordpress-download-failed-valid-url-provided/
440 *
441 * @author Vova Feldman (@svovaf)
442 * @since 1.0.4
443 *
444 * @param bool $allow
445 * @param string $host
446 * @param string $url
447 *
448 * @return bool
449 */
450 function http_request_host_is_external_filter( $allow, $host, $url ) {
451 return ( false !== strpos( $host, 'freemius' ) ) ? true : $allow;
452 }
453
454 /**
455 * Check for Updates at the defined API endpoint and modify the update array.
456 *
457 * This function dives into the update api just when WordPress creates its update array,
458 * then adds a custom API call and injects the custom plugin data retrieved from the API.
459 * It is reassembled from parts of the native WordPress plugin update code.
460 * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
461 *
462 * @author Vova Feldman (@svovaf)
463 * @since 1.0.4
464 *
465 * @uses FS_Api
466 *
467 * @param object $transient_data Update array build by WordPress.
468 *
469 * @return object Modified update array with custom plugin data.
470 */
471 function pre_set_site_transient_update_plugins_filter( $transient_data ) {
472 $this->_logger->entrance();
473
474 /**
475 * "plugins" or "themes".
476 *
477 * @author Leo Fajardo (@leorw)
478 * @since 1.2.2
479 */
480 $module_type = $this->_fs->get_module_type() . 's';
481
482 /**
483 * Ensure that we don't mix plugins update info with themes update info.
484 *
485 * @author Leo Fajardo (@leorw)
486 * @since 1.2.2
487 */
488 if ( "pre_set_site_transient_update_{$module_type}" !== current_filter() ) {
489 return $transient_data;
490 }
491
492 if ( empty( $transient_data ) ||
493 defined( 'WP_FS__UNINSTALL_MODE' )
494 ) {
495 return $transient_data;
496 }
497
498 global $wp_current_filter;
499
500 $current_plugin_version = $this->_fs->get_plugin_version();
501
502 if ( ! empty( $wp_current_filter ) && 'upgrader_process_complete' === $wp_current_filter[0] ) {
503 if (
504 is_null( $this->_update_details ) ||
505 ( is_object( $this->_update_details ) && $this->_update_details->new_version !== $current_plugin_version )
506 ) {
507 /**
508 * After an update, clear the stored update details and reparse the plugin's main file in order to get
509 * the updated version's information and prevent the previous update information from showing up on the
510 * updates page.
511 *
512 * @author Leo Fajardo (@leorw)
513 * @since 2.3.1
514 */
515 $this->_update_details = null;
516 $current_plugin_version = $this->_fs->get_plugin_version( true );
517 }
518 }
519
520 if ( ! isset( $this->_update_details ) ) {
521 // Get plugin's newest update.
522 $new_version = $this->_fs->get_update(
523 false,
524 fs_request_get_bool( 'force-check' ),
525 WP_FS__TIME_24_HOURS_IN_SEC / 24,
526 $current_plugin_version
527 );
528
529 $this->_update_details = false;
530
531 if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) {
532 $this->_logger->log( 'Found newer plugin version ' . $new_version->version );
533
534 /**
535 * Cache plugin details locally since set_site_transient( 'update_plugins' )
536 * called multiple times and the non wp.org plugins are filtered after the
537 * call to .org.
538 *
539 * @since 1.1.8.1
540 */
541 $this->_update_details = $this->get_update_details( $new_version );
542 }
543 }
544
545 if ( is_object( $this->_update_details ) ) {
546 if ( ! isset( $transient_data->response ) ) {
547 $transient_data->response = array();
548 }
549
550 // Add plugin to transient data.
551 $transient_data->response[ $this->_fs->premium_plugin_basename() ] = $this->_fs->is_plugin() ?
552 $this->_update_details :
553 (array) $this->_update_details;
554 } else if ( isset( $transient_data->response ) ) {
555 /**
556 * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version.
557 *
558 * @author Leo Fajardo (@leorw)
559 * @since 2.3.0
560 */
561 unset( $transient_data->response[ $this->_fs->premium_plugin_basename() ] );
562 }
563
564 $slug = $this->_fs->get_slug();
565
566 if ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() ) {
567 if ( ! isset( $this->_translation_updates ) ) {
568 $this->_translation_updates = array();
569
570 if ( current_user_can( 'update_languages' ) ) {
571 $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug );
572 if ( ! empty( $translation_updates ) ) {
573 $this->_translation_updates = $translation_updates;
574 }
575 }
576 }
577
578 if ( ! empty( $this->_translation_updates ) ) {
579 $all_translation_updates = ( isset( $transient_data->translations ) && is_array( $transient_data->translations ) ) ?
580 $transient_data->translations :
581 array();
582
583 $current_plugin_translation_updates_map = array();
584 foreach ( $all_translation_updates as $key => $translation_update ) {
585 if ( $module_type === ( $translation_update['type'] . 's' ) && $slug === $translation_update['slug'] ) {
586 $current_plugin_translation_updates_map[ $translation_update['language'] ] = $translation_update;
587 unset( $all_translation_updates[ $key ] );
588 }
589 }
590
591 foreach ( $this->_translation_updates as $translation_update ) {
592 $lang = $translation_update['language'];
593 if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) ||
594 version_compare( $translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>' )
595 ) {
596 $current_plugin_translation_updates_map[ $lang ] = $translation_update;
597 }
598 }
599
600 $transient_data->translations = array_merge( $all_translation_updates, array_values( $current_plugin_translation_updates_map ) );
601 }
602 }
603
604 return $transient_data;
605 }
606
607 /**
608 * Get module's required data for the updates mechanism.
609 *
610 * @author Vova Feldman (@svovaf)
611 * @since 2.0.0
612 *
613 * @param \FS_Plugin_Tag $new_version
614 *
615 * @return object
616 */
617 function get_update_details( FS_Plugin_Tag $new_version ) {
618 $update = new stdClass();
619 $update->slug = $this->_fs->get_slug();
620 $update->new_version = $new_version->version;
621 $update->url = WP_FS__ADDRESS;
622 $update->package = $new_version->url;
623 $update->tested = $new_version->tested_up_to_version;
624 $update->requires = $new_version->requires_platform_version;
625
626 $icon = $this->_fs->get_local_icon_url();
627
628 if ( ! empty( $icon ) ) {
629 $update->icons = array(
630 // '1x' => $icon,
631 // '2x' => $icon,
632 'default' => $icon,
633 );
634 }
635
636 if ( $this->_fs->is_premium() ) {
637 $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false );
638
639 if (
640 isset( $latest_tag->readme ) &&
641 isset( $latest_tag->readme->upgrade_notice ) &&
642 ! empty( $latest_tag->readme->upgrade_notice )
643 ) {
644 $update->upgrade_notice = $latest_tag->readme->upgrade_notice;
645 }
646 }
647
648 $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename();
649
650 return $update;
651 }
652
653 /**
654 * @author Leo Fajardo (@leorw)
655 * @since 2.3.0
656 *
657 * @param FS_Plugin_Tag $new_version
658 *
659 * @return bool
660 */
661 private function is_new_version_premium( FS_Plugin_Tag $new_version ) {
662 $query_str = parse_url( $new_version->url, PHP_URL_QUERY );
663 if ( empty( $query_str ) ) {
664 return false;
665 }
666
667 parse_str( $query_str, $params );
668
669 return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] );
670 }
671
672 /**
673 * Update the updates transient with the module's update information.
674 *
675 * This method is required for multisite environment.
676 * If a module is site activated (not network) and not on the main site,
677 * the module will NOT be executed on the network level, therefore, the
678 * custom updates logic will not be executed as well, so unless we force
679 * the injection of the update into the updates transient, premium updates
680 * will not work.
681 *
682 * @author Vova Feldman (@svovaf)
683 * @since 2.0.0
684 *
685 * @param \FS_Plugin_Tag $new_version
686 */
687 function set_update_data( FS_Plugin_Tag $new_version ) {
688 $this->_logger->entrance();
689
690 if ( ! $this->is_new_version_premium( $new_version ) ) {
691 return;
692 }
693
694 $transient_key = "update_{$this->_fs->get_module_type()}s";
695
696 $transient_data = get_site_transient( $transient_key );
697
698 $transient_data = is_object( $transient_data ) ?
699 $transient_data :
700 new stdClass();
701
702 // Alias.
703 $basename = $this->_fs->get_plugin_basename();
704 $is_plugin = $this->_fs->is_plugin();
705
706 if ( ! isset( $transient_data->response ) ||
707 ! is_array( $transient_data->response )
708 ) {
709 $transient_data->response = array();
710 } else if ( ! empty( $transient_data->response[ $basename ] ) ) {
711 $version = $is_plugin ?
712 ( ! empty( $transient_data->response[ $basename ]->new_version ) ?
713 $transient_data->response[ $basename ]->new_version :
714 null
715 ) : ( ! empty( $transient_data->response[ $basename ]['new_version'] ) ?
716 $transient_data->response[ $basename ]['new_version'] :
717 null
718 );
719
720 if ( $version == $new_version->version ) {
721 // The update data is already set.
722 return;
723 }
724 }
725
726 // Remove the added filters.
727 $this->remove_transient_filters();
728
729 $this->_update_details = $this->get_update_details( $new_version );
730
731 // Set update data in transient.
732 $transient_data->response[ $basename ] = $is_plugin ?
733 $this->_update_details :
734 (array) $this->_update_details;
735
736 if ( ! isset( $transient_data->checked ) ||
737 ! is_array( $transient_data->checked )
738 ) {
739 $transient_data->checked = array();
740 }
741
742 // Flag the module as if it was already checked.
743 $transient_data->checked[ $basename ] = $this->_fs->get_plugin_version();
744 $transient_data->last_checked = time();
745
746 set_site_transient( $transient_key, $transient_data );
747
748 $this->add_transient_filters();
749 }
750
751 /**
752 * @author Leo Fajardo (@leorw)
753 * @since 2.0.2
754 */
755 function delete_update_data() {
756 $this->_logger->entrance();
757
758 $transient_key = "update_{$this->_fs->get_module_type()}s";
759
760 $transient_data = get_site_transient( $transient_key );
761
762 // Alias
763 $basename = $this->_fs->get_plugin_basename();
764
765 if ( ! is_object( $transient_data ) ||
766 ! isset( $transient_data->response ) ||
767 ! is_array( $transient_data->response ) ||
768 empty( $transient_data->response[ $basename ] )
769 ) {
770 return;
771 }
772
773 unset( $transient_data->response[ $basename ] );
774
775 // Remove the added filters.
776 $this->remove_transient_filters();
777
778 set_site_transient( $transient_key, $transient_data );
779
780 $this->add_transient_filters();
781 }
782
783 /**
784 * Try to fetch plugin's info from .org repository.
785 *
786 * @author Vova Feldman (@svovaf)
787 * @since 1.0.5
788 *
789 * @param string $action
790 * @param object $args
791 *
792 * @return bool|mixed
793 */
794 static function _fetch_plugin_info_from_repository( $action, $args ) {
795 $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/';
796 if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) {
797 $url = set_url_scheme( $url, 'https' );
798 }
799
800 $args = array(
801 'timeout' => 15,
802 'body' => array(
803 'action' => $action,
804 'request' => serialize( $args )
805 )
806 );
807
808 $request = wp_remote_post( $url, $args );
809
810 if ( is_wp_error( $request ) ) {
811 return false;
812 }
813
814 $res = maybe_unserialize( wp_remote_retrieve_body( $request ) );
815
816 if ( ! is_object( $res ) && ! is_array( $res ) ) {
817 return false;
818 }
819
820 return $res;
821 }
822
823 /**
824 * Fetches module translation updates from wordpress.org.
825 *
826 * @author Leo Fajardo (@leorw)
827 * @since 2.1.2
828 *
829 * @param string $module_type
830 * @param string $slug
831 *
832 * @return array|null
833 */
834 private function fetch_wp_org_module_translation_updates( $module_type, $slug ) {
835 $plugin_data = $this->_fs->get_plugin_data();
836
837 $locales = array_values( get_available_languages() );
838 $locales = apply_filters( "{$module_type}_update_check_locales", $locales );
839 $locales = array_unique( $locales );
840
841 $plugin_basename = $this->_fs->get_plugin_basename();
842 if ( 'themes' === $module_type ) {
843 $plugin_basename = $slug;
844 }
845
846 global $wp_version;
847
848 $request_args = array(
849 'timeout' => 15,
850 'body' => array(
851 "{$module_type}" => json_encode(
852 array(
853 "{$module_type}" => array(
854 $plugin_basename => array(
855 'Name' => trim( str_replace( $this->_fs->get_plugin()->premium_suffix, '', $plugin_data['Name'] ) ),
856 'Author' => $plugin_data['Author'],
857 )
858 )
859 )
860 ),
861 'translations' => json_encode( $this->get_installed_translations( $module_type, $slug ) ),
862 'locale' => json_encode( $locales )
863 ),
864 'user-agent' => ( 'WordPress/' . $wp_version . '; ' . home_url( '/' ) )
865 );
866
867 $url = "http://api.wordpress.org/{$module_type}/update-check/1.1/";
868 if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) {
869 $url = set_url_scheme( $url, 'https' );
870 }
871
872 $raw_response = Freemius::safe_remote_post(
873 $url,
874 $request_args,
875 WP_FS__TIME_24_HOURS_IN_SEC,
876 WP_FS__TIME_12_HOURS_IN_SEC,
877 false
878 );
879
880 if ( is_wp_error( $raw_response ) ) {
881 return null;
882 }
883
884 $response = json_decode( wp_remote_retrieve_body( $raw_response ), true );
885
886 if ( ! is_array( $response ) ) {
887 return null;
888 }
889
890 if ( ! isset( $response['translations'] ) || empty( $response['translations'] ) ) {
891 return null;
892 }
893
894 return $response['translations'];
895 }
896
897 /**
898 * @author Leo Fajardo (@leorw)
899 * @since 2.1.2
900 *
901 * @param string $module_type
902 * @param string $slug
903 *
904 * @return array
905 */
906 private function get_installed_translations( $module_type, $slug ) {
907 if ( function_exists( 'wp_get_installed_translations' ) ) {
908 return wp_get_installed_translations( $module_type );
909 }
910
911 $dir = "/{$module_type}";
912
913 if ( ! is_dir( WP_LANG_DIR . $dir ) )
914 return array();
915
916 $files = scandir( WP_LANG_DIR . $dir );
917 if ( ! $files )
918 return array();
919
920 $language_data = array();
921
922 foreach ( $files as $file ) {
923 if ( 0 !== strpos( $file, $slug ) ) {
924 continue;
925 }
926
927 if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "{$dir}/{$file}" ) ) {
928 continue;
929 }
930
931 if ( substr( $file, -3 ) !== '.po' ) {
932 continue;
933 }
934
935 if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) {
936 continue;
937 }
938
939 if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) ) {
940 continue;
941 }
942
943 list( , $textdomain, $language ) = $match;
944
945 if ( '' === $textdomain ) {
946 $textdomain = 'default';
947 }
948
949 $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "{$dir}/{$file}" );
950 }
951
952 return $language_data;
953 }
954
955 /**
956 * Updates information on the "View version x.x details" page with custom data.
957 *
958 * @author Vova Feldman (@svovaf)
959 * @since 1.0.4
960 *
961 * @uses FS_Api
962 *
963 * @param object $data
964 * @param string $action
965 * @param mixed $args
966 *
967 * @return object
968 */
969 function plugins_api_filter( $data, $action = '', $args = null ) {
970 $this->_logger->entrance();
971
972 if ( ( 'plugin_information' !== $action ) ||
973 ! isset( $args->slug )
974 ) {
975 return $data;
976 }
977
978 $addon = false;
979 $is_addon = false;
980 $addon_version = false;
981
982 if ( $this->_fs->get_slug() !== $args->slug ) {
983 $addon = $this->_fs->get_addon_by_slug( $args->slug );
984
985 if ( ! is_object( $addon ) ) {
986 return $data;
987 }
988
989 if ( $this->_fs->is_addon_activated( $addon->id ) ) {
990 $addon_version = $this->_fs->get_addon_instance( $addon->id )->get_plugin_version();
991 } else if ( $this->_fs->is_addon_installed( $addon->id ) ) {
992 $addon_plugin_data = get_plugin_data(
993 ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $addon->id ) ),
994 false,
995 false
996 );
997
998 if ( ! empty( $addon_plugin_data ) ) {
999 $addon_version = $addon_plugin_data['Version'];
1000 }
1001 }
1002
1003 $is_addon = true;
1004 }
1005
1006 $plugin_in_repo = false;
1007 if ( ! $is_addon ) {
1008 // Try to fetch info from .org repository.
1009 $data = self::_fetch_plugin_info_from_repository( $action, $args );
1010
1011 $plugin_in_repo = ( false !== $data );
1012 }
1013
1014 if ( ! $plugin_in_repo ) {
1015 $data = $args;
1016
1017 // Fetch as much as possible info from local files.
1018 $plugin_local_data = $this->_fs->get_plugin_data();
1019 $data->name = $plugin_local_data['Name'];
1020 $data->author = $plugin_local_data['Author'];
1021 $data->sections = array(
1022 'description' => 'Upgrade ' . $plugin_local_data['Name'] . ' to latest.',
1023 );
1024
1025 // @todo Store extra plugin info on Freemius or parse readme.txt markup.
1026 /*$info = $this->_fs->get_api_site_scope()->call('/information.json');
1027
1028 if ( !isset($info->error) ) {
1029 $data = $info;
1030 }*/
1031 }
1032
1033 $plugin_version = $is_addon ?
1034 $addon_version :
1035 $this->_fs->get_plugin_version();
1036
1037 // Get plugin's newest update.
1038 $new_version = $this->get_latest_download_details( $is_addon ? $addon->id : false, $plugin_version );
1039
1040 if ( ! is_object( $new_version ) || empty( $new_version->version ) ) {
1041 $data->version = $plugin_version;
1042 } else {
1043 if ( $is_addon ) {
1044 $data->name = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
1045 $data->slug = $addon->slug;
1046 $data->url = WP_FS__ADDRESS;
1047 $data->package = $new_version->url;
1048 }
1049
1050 if ( ! $plugin_in_repo ) {
1051 $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created;
1052 $data->requires = $new_version->requires_platform_version;
1053 $data->tested = $new_version->tested_up_to_version;
1054 }
1055
1056 $data->version = $new_version->version;
1057 $data->download_link = $new_version->url;
1058
1059 if ( isset( $new_version->readme ) && is_object( $new_version->readme ) ) {
1060 $new_version_readme_data = $new_version->readme;
1061 if ( isset( $new_version_readme_data->sections ) ) {
1062 $new_version_readme_data->sections = (array) $new_version_readme_data->sections;
1063 } else {
1064 $new_version_readme_data->sections = array();
1065 }
1066
1067 if ( isset( $data->sections ) ) {
1068 if ( isset( $data->sections['screenshots'] ) ) {
1069 $new_version_readme_data->sections['screenshots'] = $data->sections['screenshots'];
1070 }
1071
1072 if ( isset( $data->sections['reviews'] ) ) {
1073 $new_version_readme_data->sections['reviews'] = $data->sections['reviews'];
1074 }
1075 }
1076
1077 if ( isset( $new_version_readme_data->banners ) ) {
1078 $new_version_readme_data->banners = (array) $new_version_readme_data->banners;
1079 } else if ( isset( $data->banners ) ) {
1080 $new_version_readme_data->banners = $data->banners;
1081 }
1082
1083 $wp_org_sections = array(
1084 'author',
1085 'author_profile',
1086 'rating',
1087 'ratings',
1088 'num_ratings',
1089 'support_threads',
1090 'support_threads_resolved',
1091 'active_installs',
1092 'added',
1093 'homepage'
1094 );
1095
1096 foreach ( $wp_org_sections as $wp_org_section ) {
1097 if ( isset( $data->{$wp_org_section} ) ) {
1098 $new_version_readme_data->{$wp_org_section} = $data->{$wp_org_section};
1099 }
1100 }
1101
1102 $data = $new_version_readme_data;
1103 }
1104 }
1105
1106 return $data;
1107 }
1108
1109 /**
1110 * @author Vova Feldman (@svovaf)
1111 * @since 1.2.1.7
1112 *
1113 * @param number|bool $addon_id
1114 * @param bool|string $newer_than Since 2.2.1
1115 * @param bool|string $fetch_readme Since 2.2.1
1116 *
1117 * @return object
1118 */
1119 private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) {
1120 return $this->_fs->_fetch_latest_version( $addon_id, true, WP_FS__TIME_24_HOURS_IN_SEC, $newer_than, $fetch_readme );
1121 }
1122
1123 /**
1124 * Checks if a given basename has a matching folder name
1125 * with the current context plugin.
1126 *
1127 * @author Vova Feldman (@svovaf)
1128 * @since 1.2.1.6
1129 *
1130 * @return bool
1131 */
1132 private function is_correct_folder_name() {
1133 return ( $this->_fs->get_target_folder_name() == trim( dirname( $this->_fs->get_plugin_basename() ), '/\\' ) );
1134 }
1135
1136 /**
1137 * This is a special after upgrade handler for migrating modules
1138 * that didn't use the '-premium' suffix folder structure before
1139 * the migration.
1140 *
1141 * @author Vova Feldman (@svovaf)
1142 * @since 1.2.1.6
1143 *
1144 * @param bool $response Install response.
1145 * @param array $hook_extra Extra arguments passed to hooked filters.
1146 * @param array $result Installation result data.
1147 *
1148 * @return bool
1149 */
1150 function _maybe_update_folder_name( $response, $hook_extra, $result ) {
1151 $basename = $this->_fs->get_plugin_basename();
1152
1153 if ( true !== $response ||
1154 empty( $hook_extra ) ||
1155 empty( $hook_extra['plugin'] ) ||
1156 $basename !== $hook_extra['plugin']
1157 ) {
1158 return $response;
1159 }
1160
1161 $active_plugins_basenames = get_option( 'active_plugins' );
1162
1163 foreach ( $active_plugins_basenames as $key => $active_plugin_basename ) {
1164 if ( $basename === $active_plugin_basename ) {
1165 // Get filename including extension.
1166 $filename = basename( $basename );
1167
1168 $new_basename = plugin_basename(
1169 trailingslashit( $this->_fs->is_premium() ? $this->_fs->get_premium_slug() : $this->_fs->get_slug() ) .
1170 $filename
1171 );
1172
1173 // Verify that the expected correct path exists.
1174 if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $new_basename ) ) ) {
1175 // Override active plugin name.
1176 $active_plugins_basenames[ $key ] = $new_basename;
1177 update_option( 'active_plugins', $active_plugins_basenames );
1178 }
1179
1180 break;
1181 }
1182 }
1183
1184 return $response;
1185 }
1186
1187 #----------------------------------------------------------------------------------
1188 #region Auto Activation
1189 #----------------------------------------------------------------------------------
1190
1191 /**
1192 * Installs and active a plugin when explicitly requested that from a 3rd party service.
1193 *
1194 * This logic was inspired by the TGMPA GPL licensed library by Thomas Griffin.
1195 *
1196 * @link http://tgmpluginactivation.com/
1197 *
1198 * @author Vova Feldman
1199 * @since 1.2.1.7
1200 *
1201 * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/
1202 *
1203 * @uses WP_Filesystem
1204 * @uses WP_Error
1205 * @uses WP_Upgrader
1206 * @uses Plugin_Upgrader
1207 * @uses Plugin_Installer_Skin
1208 * @uses Plugin_Upgrader_Skin
1209 *
1210 * @param number|bool $plugin_id
1211 *
1212 * @return array
1213 */
1214 function install_and_activate_plugin( $plugin_id = false ) {
1215 if ( ! empty( $plugin_id ) && ! FS_Plugin::is_valid_id( $plugin_id ) ) {
1216 // Invalid plugin ID.
1217 return array(
1218 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
1219 'code' => 'invalid_module_id',
1220 );
1221 }
1222
1223 $is_addon = false;
1224 if ( FS_Plugin::is_valid_id( $plugin_id ) &&
1225 $plugin_id != $this->_fs->get_id()
1226 ) {
1227 $addon = $this->_fs->get_addon( $plugin_id );
1228
1229 if ( ! is_object( $addon ) ) {
1230 // Invalid add-on ID.
1231 return array(
1232 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
1233 'code' => 'invalid_module_id',
1234 );
1235 }
1236
1237 $slug = $addon->slug;
1238 $premium_slug = $addon->premium_slug;
1239 $title = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
1240
1241 $is_addon = true;
1242 } else {
1243 $slug = $this->_fs->get_slug();
1244 $premium_slug = $this->_fs->get_premium_slug();
1245 $title = $this->_fs->get_plugin_title() .
1246 ( $this->_fs->is_addon() ? ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ) : '' );
1247 }
1248
1249 if ( $this->is_premium_plugin_active( $plugin_id ) ) {
1250 // Premium version already activated.
1251 return array(
1252 'message' => $is_addon ?
1253 $this->_fs->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ) :
1254 $this->_fs->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ),
1255 'code' => 'premium_installed',
1256 );
1257 }
1258
1259 $latest_version = $this->get_latest_download_details( $plugin_id, false, false );
1260 $target_folder = $premium_slug;
1261
1262 // Prep variables for Plugin_Installer_Skin class.
1263 $extra = array();
1264 $extra['slug'] = $target_folder;
1265 $source = $latest_version->url;
1266 $api = null;
1267
1268 $install_url = add_query_arg(
1269 array(
1270 'action' => 'install-plugin',
1271 'plugin' => urlencode( $slug ),
1272 ),
1273 'update.php'
1274 );
1275
1276 if ( ! class_exists( 'Plugin_Upgrader', false ) ) {
1277 // Include required resources for the installation.
1278 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1279 }
1280
1281 $skin_args = array(
1282 'type' => 'web',
1283 'title' => sprintf( $this->_fs->get_text_inline( 'Installing plugin: %s', 'installing-plugin-x' ), $title ),
1284 'url' => esc_url_raw( $install_url ),
1285 'nonce' => 'install-plugin_' . $slug,
1286 'plugin' => '',
1287 'api' => $api,
1288 'extra' => $extra,
1289 );
1290
1291 // $skin = new Automatic_Upgrader_Skin( $skin_args );
1292 // $skin = new Plugin_Installer_Skin( $skin_args );
1293 $skin = new WP_Ajax_Upgrader_Skin( $skin_args );
1294
1295 // Create a new instance of Plugin_Upgrader.
1296 $upgrader = new Plugin_Upgrader( $skin );
1297
1298 // Perform the action and install the plugin from the $source urldecode().
1299 add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 );
1300
1301 $install_result = $upgrader->install( $source );
1302
1303 remove_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1 );
1304
1305 if ( is_wp_error( $install_result ) ) {
1306 return array(
1307 'message' => $install_result->get_error_message(),
1308 'code' => $install_result->get_error_code(),
1309 );
1310 } elseif ( is_wp_error( $skin->result ) ) {
1311 return array(
1312 'message' => $skin->result->get_error_message(),
1313 'code' => $skin->result->get_error_code(),
1314 );
1315 } elseif ( $skin->get_errors()->get_error_code() ) {
1316 return array(
1317 'message' => $skin->get_error_messages(),
1318 'code' => 'unknown',
1319 );
1320 } elseif ( is_null( $install_result ) ) {
1321 global $wp_filesystem;
1322
1323 $error_code = 'unable_to_connect_to_filesystem';
1324 $error_message = $this->_fs->get_text_inline( 'Unable to connect to the filesystem. Please confirm your credentials.' );
1325
1326 // Pass through the error from WP_Filesystem if one was raised.
1327 if ( $wp_filesystem instanceof WP_Filesystem_Base &&
1328 is_wp_error( $wp_filesystem->errors ) &&
1329 $wp_filesystem->errors->get_error_code()
1330 ) {
1331 $error_message = $wp_filesystem->errors->get_error_message();
1332 }
1333
1334 return array(
1335 'message' => $error_message,
1336 'code' => $error_code,
1337 );
1338 }
1339
1340 // Grab the full path to the main plugin's file.
1341 $plugin_activate = $upgrader->plugin_info();
1342
1343 // Try to activate the plugin.
1344 $activation_result = $this->try_activate_plugin( $plugin_activate );
1345
1346 if ( is_wp_error( $activation_result ) ) {
1347 return array(
1348 'message' => $activation_result->get_error_message(),
1349 'code' => $activation_result->get_error_code(),
1350 );
1351 }
1352
1353 return $skin->get_upgrade_messages();
1354 }
1355
1356 /**
1357 * Tries to activate a plugin. If fails, returns the error.
1358 *
1359 * @author Vova Feldman
1360 * @since 1.2.1.7
1361 *
1362 * @param string $file_path Path within wp-plugins/ to main plugin file.
1363 * This determines the styling of the output messages.
1364 *
1365 * @return bool|WP_Error
1366 */
1367 protected function try_activate_plugin( $file_path ) {
1368 $activate = activate_plugin( $file_path, '', $this->_fs->is_network_active() );
1369
1370 return is_wp_error( $activate ) ?
1371 $activate :
1372 true;
1373 }
1374
1375 /**
1376 * Check if a premium module version is already active.
1377 *
1378 * @author Vova Feldman
1379 * @since 1.2.1.7
1380 *
1381 * @param number|bool $plugin_id
1382 *
1383 * @return bool
1384 */
1385 private function is_premium_plugin_active( $plugin_id = false ) {
1386 if ( $plugin_id != $this->_fs->get_id() ) {
1387 return $this->_fs->is_addon_activated( $plugin_id, true );
1388 }
1389
1390 return is_plugin_active( $this->_fs->premium_plugin_basename() );
1391 }
1392
1393 /**
1394 * Store the basename since it's not always available in the `_maybe_adjust_source_dir` method below.
1395 *
1396 * @author Leo Fajardo (@leorw)
1397 * @since 2.2.1
1398 *
1399 * @param bool|WP_Error $response Response.
1400 * @param array $hook_extra Extra arguments passed to hooked filters.
1401 *
1402 * @return bool|WP_Error
1403 */
1404 static function _store_basename_for_source_adjustment( $response, $hook_extra ) {
1405 if ( isset( $hook_extra['plugin'] ) ) {
1406 self::$_upgrade_basename = $hook_extra['plugin'];
1407 } else if ( isset( $hook_extra['theme'] ) ) {
1408 self::$_upgrade_basename = $hook_extra['theme'];
1409 } else {
1410 self::$_upgrade_basename = null;
1411 }
1412
1413 return $response;
1414 }
1415
1416 /**
1417 * Adjust the plugin directory name if necessary.
1418 * Assumes plugin has a folder (not a single file plugin).
1419 *
1420 * The final destination directory of a plugin is based on the subdirectory name found in the
1421 * (un)zipped source. In some cases this subdirectory name is not the same as the expected
1422 * slug and the plugin will not be recognized as installed. This is fixed by adjusting
1423 * the temporary unzipped source subdirectory name to the expected plugin slug.
1424 *
1425 * @author Vova Feldman
1426 * @since 1.2.1.7
1427 * @since 2.2.1 The method was converted to static since when the admin update bulk products via the Updates section, the logic applies the `upgrader_source_selection` filter for every product that is being updated.
1428 *
1429 * @param string $source Path to upgrade/zip-file-name.tmp/subdirectory/.
1430 * @param string $remote_source Path to upgrade/zip-file-name.tmp.
1431 * @param \WP_Upgrader $upgrader Instance of the upgrader which installs the plugin.
1432 *
1433 * @return string|WP_Error
1434 */
1435 static function _maybe_adjust_source_dir( $source, $remote_source, $upgrader ) {
1436 if ( ! is_object( $GLOBALS['wp_filesystem'] ) ) {
1437 return $source;
1438 }
1439
1440 $basename = self::$_upgrade_basename;
1441 $is_theme = false;
1442
1443 // Figure out what the slug is supposed to be.
1444 if ( isset( $upgrader->skin->options['extra'] ) ) {
1445 // Set by the auto-install logic.
1446 $desired_slug = $upgrader->skin->options['extra']['slug'];
1447 } else if ( ! empty( $basename ) ) {
1448 /**
1449 * If it doesn't end with ".php", it's a theme.
1450 *
1451 * @author Leo Fajardo (@leorw)
1452 * @since 2.2.1
1453 */
1454 $is_theme = ( ! fs_ends_with( $basename, '.php' ) );
1455
1456 $desired_slug = ( ! $is_theme ) ?
1457 dirname( $basename ) :
1458 // Theme slug
1459 $basename;
1460 } else {
1461 // Can't figure out the desired slug, stop the execution.
1462 return $source;
1463 }
1464
1465 if ( is_multisite() ) {
1466 /**
1467 * If we are running in a multisite environment and the product is not network activated,
1468 * the instance will not exist anyway. Therefore, try to update the source if necessary
1469 * regardless if the Freemius instance of the product exists or not.
1470 *
1471 * @author Vova Feldman
1472 */
1473 } else if ( ! empty( $basename ) ) {
1474 $fs = Freemius::get_instance_by_file(
1475 $basename,
1476 $is_theme ?
1477 WP_FS__MODULE_TYPE_THEME :
1478 WP_FS__MODULE_TYPE_PLUGIN
1479 );
1480
1481 if ( ! is_object( $fs ) ) {
1482 /**
1483 * If the Freemius instance does not exist on a non-multisite network environment, it means that:
1484 * 1. The product is not powered by Freemius; OR
1485 * 2. The product is not activated, therefore, we don't mind if after the update the folder name will change.
1486 *
1487 * @author Leo Fajardo (@leorw)
1488 * @since 2.2.1
1489 */
1490 return $source;
1491 }
1492 }
1493
1494 $subdir_name = untrailingslashit( str_replace( trailingslashit( $remote_source ), '', $source ) );
1495
1496 if ( ! empty( $subdir_name ) && $subdir_name !== $desired_slug ) {
1497 $from_path = untrailingslashit( $source );
1498 $to_path = trailingslashit( $remote_source ) . $desired_slug;
1499
1500 if ( true === $GLOBALS['wp_filesystem']->move( $from_path, $to_path ) ) {
1501 return trailingslashit( $to_path );
1502 }
1503
1504 return new WP_Error(
1505 'rename_failed',
1506 fs_text_inline( 'The remote plugin package does not contain a folder with the desired slug and renaming did not work.', 'module-package-rename-failure' ),
1507 array(
1508 'found' => $subdir_name,
1509 'expected' => $desired_slug
1510 )
1511 );
1512 }
1513
1514 return $source;
1515 }
1516
1517 #endregion
1518 }
1519