PluginProbe
Passster – Password Protect Pages and Content / 4.2.12
Passster – Password Protect Pages and Content v4.2.12
4.3.16 4.3.15 4.3.14 4.3.12 4.3.13 4.3.11 4.3.10 4.3.9 4.3.8 4.3.7 4.3.6 4.3.5 trunk 3.5.4 3.5.5.2 3.5.5.8 3.5.5.9 4.0 4.1.4 4.2.10 4.2.11 4.2.12 4.2.13 4.2.14 4.2.15 All 48 releases
content-protector / inc / freemius / includes / class-fs-plugin-updater.php

class-fs-plugin-updater.php in Passster – Password Protect Pages and Content 4.2.12, at inc/freemius/includes/class-fs-plugin-updater.php

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