PluginProbe
BlockSpare – Gutenberg Blocks, AI Content Generator & Site Builder for News, Magazine & Blogs / 5.0.1
BlockSpare – Gutenberg Blocks, AI Content Generator & Site Builder for News, Magazine & Blogs v5.0.1
5.0.1 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 All 39 releases
← All changes | freemius/includes/class-fs-plugin-updater.php +1627 -1561 2.1.2 → 5.0.1 View file →
@@ -1,1561 +1,1627 @@
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 - // Alias.
546 - $basename = $this->_fs->premium_plugin_basename();
547 -
548 - if ( is_object( $this->_update_details ) ) {
549 - if ( isset( $transient_data->no_update ) ) {
550 - unset( $transient_data->no_update[ $basename ] );
551 - }
552 -
553 - if ( ! isset( $transient_data->response ) ) {
554 - $transient_data->response = array();
555 - }
556 -
557 - // Add plugin to transient data.
558 - $transient_data->response[ $basename ] = $this->_fs->is_plugin() ?
559 - $this->_update_details :
560 - (array) $this->_update_details;
561 - } else {
562 - if ( isset( $transient_data->response ) ) {
563 - /**
564 - * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version.
565 - *
566 - * @author Leo Fajardo (@leorw)
567 - * @since 2.3.0
568 - */
569 - unset( $transient_data->response[ $basename ] );
570 - }
571 -
572 - if ( ! isset( $transient_data->no_update ) ) {
573 - $transient_data->no_update = array();
574 - }
575 -
576 - /**
577 - * Add product to no_update transient data to properly integrate with WP 5.5 auto-updates UI.
578 - *
579 - * @since 2.4.1
580 - * @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/
581 - */
582 - $transient_data->no_update[ $basename ] = $this->_fs->is_plugin() ?
583 - (object) array(
584 - 'id' => $basename,
585 - 'slug' => $this->_fs->get_slug(),
586 - 'plugin' => $basename,
587 - 'new_version' => $this->_fs->get_plugin_version(),
588 - 'url' => '',
589 - 'package' => '',
590 - 'icons' => array(),
591 - 'banners' => array(),
592 - 'banners_rtl' => array(),
593 - 'tested' => '',
594 - 'requires_php' => '',
595 - 'compatibility' => new stdClass(),
596 - ) :
597 - array(
598 - 'theme' => $basename,
599 - 'new_version' => $this->_fs->get_plugin_version(),
600 - 'url' => '',
601 - 'package' => '',
602 - 'requires' => '',
603 - 'requires_php' => '',
604 - );
605 - }
606 -
607 - $slug = $this->_fs->get_slug();
608 -
609 - if ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() ) {
610 - if ( ! isset( $this->_translation_updates ) ) {
611 - $this->_translation_updates = array();
612 -
613 - if ( current_user_can( 'update_languages' ) ) {
614 - $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug );
615 - if ( ! empty( $translation_updates ) ) {
616 - $this->_translation_updates = $translation_updates;
617 - }
618 - }
619 - }
620 -
621 - if ( ! empty( $this->_translation_updates ) ) {
622 - $all_translation_updates = ( isset( $transient_data->translations ) && is_array( $transient_data->translations ) ) ?
623 - $transient_data->translations :
624 - array();
625 -
626 - $current_plugin_translation_updates_map = array();
627 - foreach ( $all_translation_updates as $key => $translation_update ) {
628 - if ( $module_type === ( $translation_update['type'] . 's' ) && $slug === $translation_update['slug'] ) {
629 - $current_plugin_translation_updates_map[ $translation_update['language'] ] = $translation_update;
630 - unset( $all_translation_updates[ $key ] );
631 - }
632 - }
633 -
634 - foreach ( $this->_translation_updates as $translation_update ) {
635 - $lang = $translation_update['language'];
636 - if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) ||
637 - version_compare( $translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>' )
638 - ) {
639 - $current_plugin_translation_updates_map[ $lang ] = $translation_update;
640 - }
641 - }
642 -
643 - $transient_data->translations = array_merge( $all_translation_updates, array_values( $current_plugin_translation_updates_map ) );
644 - }
645 - }
646 -
647 - return $transient_data;
648 - }
649 -
650 - /**
651 - * Get module's required data for the updates mechanism.
652 - *
653 - * @author Vova Feldman (@svovaf)
654 - * @since 2.0.0
655 - *
656 - * @param \FS_Plugin_Tag $new_version
657 - *
658 - * @return object
659 - */
660 - function get_update_details( FS_Plugin_Tag $new_version ) {
661 - $update = new stdClass();
662 - $update->slug = $this->_fs->get_slug();
663 - $update->new_version = $new_version->version;
664 - $update->url = WP_FS__ADDRESS;
665 - $update->package = $new_version->url;
666 - $update->tested = $new_version->tested_up_to_version;
667 - $update->requires = $new_version->requires_platform_version;
668 -
669 - $icon = $this->_fs->get_local_icon_url();
670 -
671 - if ( ! empty( $icon ) ) {
672 - $update->icons = array(
673 -// '1x' => $icon,
674 -// '2x' => $icon,
675 - 'default' => $icon,
676 - );
677 - }
678 -
679 - if ( $this->_fs->is_premium() ) {
680 - $latest_tag = $this->_fs->_fetch_latest_version( $this->_fs->get_id(), false );
681 -
682 - if (
683 - isset( $latest_tag->readme ) &&
684 - isset( $latest_tag->readme->upgrade_notice ) &&
685 - ! empty( $latest_tag->readme->upgrade_notice )
686 - ) {
687 - $update->upgrade_notice = $latest_tag->readme->upgrade_notice;
688 - }
689 - }
690 -
691 - $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename();
692 -
693 - return $update;
694 - }
695 -
696 - /**
697 - * @author Leo Fajardo (@leorw)
698 - * @since 2.3.0
699 - *
700 - * @param FS_Plugin_Tag $new_version
701 - *
702 - * @return bool
703 - */
704 - private function is_new_version_premium( FS_Plugin_Tag $new_version ) {
705 - $query_str = parse_url( $new_version->url, PHP_URL_QUERY );
706 - if ( empty( $query_str ) ) {
707 - return false;
708 - }
709 -
710 - parse_str( $query_str, $params );
711 -
712 - return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] );
713 - }
714 -
715 - /**
716 - * Update the updates transient with the module's update information.
717 - *
718 - * This method is required for multisite environment.
719 - * If a module is site activated (not network) and not on the main site,
720 - * the module will NOT be executed on the network level, therefore, the
721 - * custom updates logic will not be executed as well, so unless we force
722 - * the injection of the update into the updates transient, premium updates
723 - * will not work.
724 - *
725 - * @author Vova Feldman (@svovaf)
726 - * @since 2.0.0
727 - *
728 - * @param \FS_Plugin_Tag $new_version
729 - */
730 - function set_update_data( FS_Plugin_Tag $new_version ) {
731 - $this->_logger->entrance();
732 -
733 - if ( ! $this->is_new_version_premium( $new_version ) ) {
734 - return;
735 - }
736 -
737 - $transient_key = "update_{$this->_fs->get_module_type()}s";
738 -
739 - $transient_data = get_site_transient( $transient_key );
740 -
741 - $transient_data = is_object( $transient_data ) ?
742 - $transient_data :
743 - new stdClass();
744 -
745 - // Alias.
746 - $basename = $this->_fs->get_plugin_basename();
747 - $is_plugin = $this->_fs->is_plugin();
748 -
749 - if ( ! isset( $transient_data->response ) ||
750 - ! is_array( $transient_data->response )
751 - ) {
752 - $transient_data->response = array();
753 - } else if ( ! empty( $transient_data->response[ $basename ] ) ) {
754 - $version = $is_plugin ?
755 - ( ! empty( $transient_data->response[ $basename ]->new_version ) ?
756 - $transient_data->response[ $basename ]->new_version :
757 - null
758 - ) : ( ! empty( $transient_data->response[ $basename ]['new_version'] ) ?
759 - $transient_data->response[ $basename ]['new_version'] :
760 - null
761 - );
762 -
763 - if ( $version == $new_version->version ) {
764 - // The update data is already set.
765 - return;
766 - }
767 - }
768 -
769 - // Remove the added filters.
770 - $this->remove_transient_filters();
771 -
772 - $this->_update_details = $this->get_update_details( $new_version );
773 -
774 - // Set update data in transient.
775 - $transient_data->response[ $basename ] = $is_plugin ?
776 - $this->_update_details :
777 - (array) $this->_update_details;
778 -
779 - if ( ! isset( $transient_data->checked ) ||
780 - ! is_array( $transient_data->checked )
781 - ) {
782 - $transient_data->checked = array();
783 - }
784 -
785 - // Flag the module as if it was already checked.
786 - $transient_data->checked[ $basename ] = $this->_fs->get_plugin_version();
787 - $transient_data->last_checked = time();
788 -
789 - set_site_transient( $transient_key, $transient_data );
790 -
791 - $this->add_transient_filters();
792 - }
793 -
794 - /**
795 - * @author Leo Fajardo (@leorw)
796 - * @since 2.0.2
797 - */
798 - function delete_update_data() {
799 - $this->_logger->entrance();
800 -
801 - $transient_key = "update_{$this->_fs->get_module_type()}s";
802 -
803 - $transient_data = get_site_transient( $transient_key );
804 -
805 - // Alias
806 - $basename = $this->_fs->get_plugin_basename();
807 -
808 - if ( ! is_object( $transient_data ) ||
809 - ! isset( $transient_data->response ) ||
810 - ! is_array( $transient_data->response ) ||
811 - empty( $transient_data->response[ $basename ] )
812 - ) {
813 - return;
814 - }
815 -
816 - unset( $transient_data->response[ $basename ] );
817 -
818 - // Remove the added filters.
819 - $this->remove_transient_filters();
820 -
821 - set_site_transient( $transient_key, $transient_data );
822 -
823 - $this->add_transient_filters();
824 - }
825 -
826 - /**
827 - * Try to fetch plugin's info from .org repository.
828 - *
829 - * @author Vova Feldman (@svovaf)
830 - * @since 1.0.5
831 - *
832 - * @param string $action
833 - * @param object $args
834 - *
835 - * @return bool|mixed
836 - */
837 - static function _fetch_plugin_info_from_repository( $action, $args ) {
838 - $url = $http_url = 'http://api.wordpress.org/plugins/info/1.0/';
839 - if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) {
840 - $url = set_url_scheme( $url, 'https' );
841 - }
842 -
843 - $args = array(
844 - 'timeout' => 15,
845 - 'body' => array(
846 - 'action' => $action,
847 - 'request' => serialize( $args )
848 - )
849 - );
850 -
851 - $request = wp_remote_post( $url, $args );
852 -
853 - if ( is_wp_error( $request ) ) {
854 - return false;
855 - }
856 -
857 - $res = maybe_unserialize( wp_remote_retrieve_body( $request ) );
858 -
859 - if ( ! is_object( $res ) && ! is_array( $res ) ) {
860 - return false;
861 - }
862 -
863 - return $res;
864 - }
865 -
866 - /**
867 - * Fetches module translation updates from wordpress.org.
868 - *
869 - * @author Leo Fajardo (@leorw)
870 - * @since 2.1.2
871 - *
872 - * @param string $module_type
873 - * @param string $slug
874 - *
875 - * @return array|null
876 - */
877 - private function fetch_wp_org_module_translation_updates( $module_type, $slug ) {
878 - $plugin_data = $this->_fs->get_plugin_data();
879 -
880 - $locales = array_values( get_available_languages() );
881 - $locales = apply_filters( "{$module_type}_update_check_locales", $locales );
882 - $locales = array_unique( $locales );
883 -
884 - $plugin_basename = $this->_fs->get_plugin_basename();
885 - if ( 'themes' === $module_type ) {
886 - $plugin_basename = $slug;
887 - }
888 -
889 - global $wp_version;
890 -
891 - $request_args = array(
892 - 'timeout' => 15,
893 - 'body' => array(
894 - "{$module_type}" => json_encode(
895 - array(
896 - "{$module_type}" => array(
897 - $plugin_basename => array(
898 - 'Name' => trim( str_replace( $this->_fs->get_plugin()->premium_suffix, '', $plugin_data['Name'] ) ),
899 - 'Author' => $plugin_data['Author'],
900 - )
901 - )
902 - )
903 - ),
904 - 'translations' => json_encode( $this->get_installed_translations( $module_type, $slug ) ),
905 - 'locale' => json_encode( $locales )
906 - ),
907 - 'user-agent' => ( 'WordPress/' . $wp_version . '; ' . home_url( '/' ) )
908 - );
909 -
910 - $url = "http://api.wordpress.org/{$module_type}/update-check/1.1/";
911 - if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) {
912 - $url = set_url_scheme( $url, 'https' );
913 - }
914 -
915 - $raw_response = Freemius::safe_remote_post(
916 - $url,
917 - $request_args,
918 - WP_FS__TIME_24_HOURS_IN_SEC,
919 - WP_FS__TIME_12_HOURS_IN_SEC,
920 - false
921 - );
922 -
923 - if ( is_wp_error( $raw_response ) ) {
924 - return null;
925 - }
926 -
927 - $response = json_decode( wp_remote_retrieve_body( $raw_response ), true );
928 -
929 - if ( ! is_array( $response ) ) {
930 - return null;
931 - }
932 -
933 - if ( ! isset( $response['translations'] ) || empty( $response['translations'] ) ) {
934 - return null;
935 - }
936 -
937 - return $response['translations'];
938 - }
939 -
940 - /**
941 - * @author Leo Fajardo (@leorw)
942 - * @since 2.1.2
943 - *
944 - * @param string $module_type
945 - * @param string $slug
946 - *
947 - * @return array
948 - */
949 - private function get_installed_translations( $module_type, $slug ) {
950 - if ( function_exists( 'wp_get_installed_translations' ) ) {
951 - return wp_get_installed_translations( $module_type );
952 - }
953 -
954 - $dir = "/{$module_type}";
955 -
956 - if ( ! is_dir( WP_LANG_DIR . $dir ) )
957 - return array();
958 -
959 - $files = scandir( WP_LANG_DIR . $dir );
960 - if ( ! $files )
961 - return array();
962 -
963 - $language_data = array();
964 -
965 - foreach ( $files as $file ) {
966 - if ( 0 !== strpos( $file, $slug ) ) {
967 - continue;
968 - }
969 -
970 - if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "{$dir}/{$file}" ) ) {
971 - continue;
972 - }
973 -
974 - if ( substr( $file, -3 ) !== '.po' ) {
975 - continue;
976 - }
977 -
978 - if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) {
979 - continue;
980 - }
981 -
982 - if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) ) {
983 - continue;
984 - }
985 -
986 - list( , $textdomain, $language ) = $match;
987 -
988 - if ( '' === $textdomain ) {
989 - $textdomain = 'default';
990 - }
991 -
992 - $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "{$dir}/{$file}" );
993 - }
994 -
995 - return $language_data;
996 - }
997 -
998 - /**
999 - * Updates information on the "View version x.x details" page with custom data.
1000 - *
1001 - * @author Vova Feldman (@svovaf)
1002 - * @since 1.0.4
1003 - *
1004 - * @uses FS_Api
1005 - *
1006 - * @param object $data
1007 - * @param string $action
1008 - * @param mixed $args
1009 - *
1010 - * @return object
1011 - */
1012 - function plugins_api_filter( $data, $action = '', $args = null ) {
1013 - $this->_logger->entrance();
1014 -
1015 - if ( ( 'plugin_information' !== $action ) ||
1016 - ! isset( $args->slug )
1017 - ) {
1018 - return $data;
1019 - }
1020 -
1021 - $addon = false;
1022 - $is_addon = false;
1023 - $addon_version = false;
1024 -
1025 - if ( $this->_fs->get_slug() !== $args->slug ) {
1026 - $addon = $this->_fs->get_addon_by_slug( $args->slug );
1027 -
1028 - if ( ! is_object( $addon ) ) {
1029 - return $data;
1030 - }
1031 -
1032 - if ( $this->_fs->is_addon_activated( $addon->id ) ) {
1033 - $addon_version = $this->_fs->get_addon_instance( $addon->id )->get_plugin_version();
1034 - } else if ( $this->_fs->is_addon_installed( $addon->id ) ) {
1035 - $addon_plugin_data = get_plugin_data(
1036 - ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $addon->id ) ),
1037 - false,
1038 - false
1039 - );
1040 -
1041 - if ( ! empty( $addon_plugin_data ) ) {
1042 - $addon_version = $addon_plugin_data['Version'];
1043 - }
1044 - }
1045 -
1046 - $is_addon = true;
1047 - }
1048 -
1049 - $plugin_in_repo = false;
1050 - if ( ! $is_addon ) {
1051 - // Try to fetch info from .org repository.
1052 - $data = self::_fetch_plugin_info_from_repository( $action, $args );
1053 -
1054 - $plugin_in_repo = ( false !== $data );
1055 - }
1056 -
1057 - if ( ! $plugin_in_repo ) {
1058 - $data = $args;
1059 -
1060 - // Fetch as much as possible info from local files.
1061 - $plugin_local_data = $this->_fs->get_plugin_data();
1062 - $data->name = $plugin_local_data['Name'];
1063 - $data->author = $plugin_local_data['Author'];
1064 - $data->sections = array(
1065 - 'description' => 'Upgrade ' . $plugin_local_data['Name'] . ' to latest.',
1066 - );
1067 -
1068 - // @todo Store extra plugin info on Freemius or parse readme.txt markup.
1069 - /*$info = $this->_fs->get_api_site_scope()->call('/information.json');
1070 -
1071 -if ( !isset($info->error) ) {
1072 - $data = $info;
1073 -}*/
1074 - }
1075 -
1076 - $plugin_version = $is_addon ?
1077 - $addon_version :
1078 - $this->_fs->get_plugin_version();
1079 -
1080 - // Get plugin's newest update.
1081 - $new_version = $this->get_latest_download_details( $is_addon ? $addon->id : false, $plugin_version );
1082 -
1083 - if ( ! is_object( $new_version ) || empty( $new_version->version ) ) {
1084 - $data->version = $plugin_version;
1085 - } else {
1086 - if ( $is_addon ) {
1087 - $data->name = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
1088 - $data->slug = $addon->slug;
1089 - $data->url = WP_FS__ADDRESS;
1090 - $data->package = $new_version->url;
1091 - }
1092 -
1093 - if ( ! $plugin_in_repo ) {
1094 - $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created;
1095 - $data->requires = $new_version->requires_platform_version;
1096 - $data->tested = $new_version->tested_up_to_version;
1097 - }
1098 -
1099 - $data->version = $new_version->version;
1100 - $data->download_link = $new_version->url;
1101 -
1102 - if ( isset( $new_version->readme ) && is_object( $new_version->readme ) ) {
1103 - $new_version_readme_data = $new_version->readme;
1104 - if ( isset( $new_version_readme_data->sections ) ) {
1105 - $new_version_readme_data->sections = (array) $new_version_readme_data->sections;
1106 - } else {
1107 - $new_version_readme_data->sections = array();
1108 - }
1109 -
1110 - if ( isset( $data->sections ) ) {
1111 - if ( isset( $data->sections['screenshots'] ) ) {
1112 - $new_version_readme_data->sections['screenshots'] = $data->sections['screenshots'];
1113 - }
1114 -
1115 - if ( isset( $data->sections['reviews'] ) ) {
1116 - $new_version_readme_data->sections['reviews'] = $data->sections['reviews'];
1117 - }
1118 - }
1119 -
1120 - if ( isset( $new_version_readme_data->banners ) ) {
1121 - $new_version_readme_data->banners = (array) $new_version_readme_data->banners;
1122 - } else if ( isset( $data->banners ) ) {
1123 - $new_version_readme_data->banners = $data->banners;
1124 - }
1125 -
1126 - $wp_org_sections = array(
1127 - 'author',
1128 - 'author_profile',
1129 - 'rating',
1130 - 'ratings',
1131 - 'num_ratings',
1132 - 'support_threads',
1133 - 'support_threads_resolved',
1134 - 'active_installs',
1135 - 'added',
1136 - 'homepage'
1137 - );
1138 -
1139 - foreach ( $wp_org_sections as $wp_org_section ) {
1140 - if ( isset( $data->{$wp_org_section} ) ) {
1141 - $new_version_readme_data->{$wp_org_section} = $data->{$wp_org_section};
1142 - }
1143 - }
1144 -
1145 - $data = $new_version_readme_data;
1146 - }
1147 - }
1148 -
1149 - return $data;
1150 - }
1151 -
1152 - /**
1153 - * @author Vova Feldman (@svovaf)
1154 - * @since 1.2.1.7
1155 - *
1156 - * @param number|bool $addon_id
1157 - * @param bool|string $newer_than Since 2.2.1
1158 - * @param bool|string $fetch_readme Since 2.2.1
1159 - *
1160 - * @return object
1161 - */
1162 - private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) {
1163 - return $this->_fs->_fetch_latest_version( $addon_id, true, WP_FS__TIME_24_HOURS_IN_SEC, $newer_than, $fetch_readme );
1164 - }
1165 -
1166 - /**
1167 - * Checks if a given basename has a matching folder name
1168 - * with the current context plugin.
1169 - *
1170 - * @author Vova Feldman (@svovaf)
1171 - * @since 1.2.1.6
1172 - *
1173 - * @return bool
1174 - */
1175 - private function is_correct_folder_name() {
1176 - return ( $this->_fs->get_target_folder_name() == trim( dirname( $this->_fs->get_plugin_basename() ), '/\\' ) );
1177 - }
1178 -
1179 - /**
1180 - * This is a special after upgrade handler for migrating modules
1181 - * that didn't use the '-premium' suffix folder structure before
1182 - * the migration.
1183 - *
1184 - * @author Vova Feldman (@svovaf)
1185 - * @since 1.2.1.6
1186 - *
1187 - * @param bool $response Install response.
1188 - * @param array $hook_extra Extra arguments passed to hooked filters.
1189 - * @param array $result Installation result data.
1190 - *
1191 - * @return bool
1192 - */
1193 - function _maybe_update_folder_name( $response, $hook_extra, $result ) {
1194 - $basename = $this->_fs->get_plugin_basename();
1195 -
1196 - if ( true !== $response ||
1197 - empty( $hook_extra ) ||
1198 - empty( $hook_extra['plugin'] ) ||
1199 - $basename !== $hook_extra['plugin']
1200 - ) {
1201 - return $response;
1202 - }
1203 -
1204 - $active_plugins_basenames = get_option( 'active_plugins' );
1205 -
1206 - foreach ( $active_plugins_basenames as $key => $active_plugin_basename ) {
1207 - if ( $basename === $active_plugin_basename ) {
1208 - // Get filename including extension.
1209 - $filename = basename( $basename );
1210 -
1211 - $new_basename = plugin_basename(
1212 - trailingslashit( $this->_fs->is_premium() ? $this->_fs->get_premium_slug() : $this->_fs->get_slug() ) .
1213 - $filename
1214 - );
1215 -
1216 - // Verify that the expected correct path exists.
1217 - if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $new_basename ) ) ) {
1218 - // Override active plugin name.
1219 - $active_plugins_basenames[ $key ] = $new_basename;
1220 - update_option( 'active_plugins', $active_plugins_basenames );
1221 - }
1222 -
1223 - break;
1224 - }
1225 - }
1226 -
1227 - return $response;
1228 - }
1229 -
1230 - #----------------------------------------------------------------------------------
1231 - #region Auto Activation
1232 - #----------------------------------------------------------------------------------
1233 -
1234 - /**
1235 - * Installs and active a plugin when explicitly requested that from a 3rd party service.
1236 - *
1237 - * This logic was inspired by the TGMPA GPL licensed library by Thomas Griffin.
1238 - *
1239 - * @link http://tgmpluginactivation.com/
1240 - *
1241 - * @author Vova Feldman
1242 - * @since 1.2.1.7
1243 - *
1244 - * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/
1245 - *
1246 - * @uses WP_Filesystem
1247 - * @uses WP_Error
1248 - * @uses WP_Upgrader
1249 - * @uses Plugin_Upgrader
1250 - * @uses Plugin_Installer_Skin
1251 - * @uses Plugin_Upgrader_Skin
1252 - *
1253 - * @param number|bool $plugin_id
1254 - *
1255 - * @return array
1256 - */
1257 - function install_and_activate_plugin( $plugin_id = false ) {
1258 - if ( ! empty( $plugin_id ) && ! FS_Plugin::is_valid_id( $plugin_id ) ) {
1259 - // Invalid plugin ID.
1260 - return array(
1261 - 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
1262 - 'code' => 'invalid_module_id',
1263 - );
1264 - }
1265 -
1266 - $is_addon = false;
1267 - if ( FS_Plugin::is_valid_id( $plugin_id ) &&
1268 - $plugin_id != $this->_fs->get_id()
1269 - ) {
1270 - $addon = $this->_fs->get_addon( $plugin_id );
1271 -
1272 - if ( ! is_object( $addon ) ) {
1273 - // Invalid add-on ID.
1274 - return array(
1275 - 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
1276 - 'code' => 'invalid_module_id',
1277 - );
1278 - }
1279 -
1280 - $slug = $addon->slug;
1281 - $premium_slug = $addon->premium_slug;
1282 - $title = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
1283 -
1284 - $is_addon = true;
1285 - } else {
1286 - $slug = $this->_fs->get_slug();
1287 - $premium_slug = $this->_fs->get_premium_slug();
1288 - $title = $this->_fs->get_plugin_title() .
1289 - ( $this->_fs->is_addon() ? ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ) : '' );
1290 - }
1291 -
1292 - if ( $this->is_premium_plugin_active( $plugin_id ) ) {
1293 - // Premium version already activated.
1294 - return array(
1295 - 'message' => $is_addon ?
1296 - $this->_fs->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ) :
1297 - $this->_fs->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ),
1298 - 'code' => 'premium_installed',
1299 - );
1300 - }
1301 -
1302 - $latest_version = $this->get_latest_download_details( $plugin_id, false, false );
1303 - $target_folder = $premium_slug;
1304 -
1305 - // Prep variables for Plugin_Installer_Skin class.
1306 - $extra = array();
1307 - $extra['slug'] = $target_folder;
1308 - $source = $latest_version->url;
1309 - $api = null;
1310 -
1311 - $install_url = add_query_arg(
1312 - array(
1313 - 'action' => 'install-plugin',
1314 - 'plugin' => urlencode( $slug ),
1315 - ),
1316 - 'update.php'
1317 - );
1318 -
1319 - if ( ! class_exists( 'Plugin_Upgrader', false ) ) {
1320 - // Include required resources for the installation.
1321 - require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1322 - }
1323 -
1324 - $skin_args = array(
1325 - 'type' => 'web',
1326 - 'title' => sprintf( $this->_fs->get_text_inline( 'Installing plugin: %s', 'installing-plugin-x' ), $title ),
1327 - 'url' => esc_url_raw( $install_url ),
1328 - 'nonce' => 'install-plugin_' . $slug,
1329 - 'plugin' => '',
1330 - 'api' => $api,
1331 - 'extra' => $extra,
1332 - );
1333 -
1334 -// $skin = new Automatic_Upgrader_Skin( $skin_args );
1335 -// $skin = new Plugin_Installer_Skin( $skin_args );
1336 - $skin = new WP_Ajax_Upgrader_Skin( $skin_args );
1337 -
1338 - // Create a new instance of Plugin_Upgrader.
1339 - $upgrader = new Plugin_Upgrader( $skin );
1340 -
1341 - // Perform the action and install the plugin from the $source urldecode().
1342 - add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 );
1343 -
1344 - $install_result = $upgrader->install( $source );
1345 -
1346 - remove_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1 );
1347 -
1348 - if ( is_wp_error( $install_result ) ) {
1349 - return array(
1350 - 'message' => $install_result->get_error_message(),
1351 - 'code' => $install_result->get_error_code(),
1352 - );
1353 - } elseif ( is_wp_error( $skin->result ) ) {
1354 - return array(
1355 - 'message' => $skin->result->get_error_message(),
1356 - 'code' => $skin->result->get_error_code(),
1357 - );
1358 - } elseif ( $skin->get_errors()->get_error_code() ) {
1359 - return array(
1360 - 'message' => $skin->get_error_messages(),
1361 - 'code' => 'unknown',
1362 - );
1363 - } elseif ( is_null( $install_result ) ) {
1364 - global $wp_filesystem;
1365 -
1366 - $error_code = 'unable_to_connect_to_filesystem';
1367 - $error_message = $this->_fs->get_text_inline( 'Unable to connect to the filesystem. Please confirm your credentials.' );
1368 -
1369 - // Pass through the error from WP_Filesystem if one was raised.
1370 - if ( $wp_filesystem instanceof WP_Filesystem_Base &&
1371 - is_wp_error( $wp_filesystem->errors ) &&
1372 - $wp_filesystem->errors->get_error_code()
1373 - ) {
1374 - $error_message = $wp_filesystem->errors->get_error_message();
1375 - }
1376 -
1377 - return array(
1378 - 'message' => $error_message,
1379 - 'code' => $error_code,
1380 - );
1381 - }
1382 -
1383 - // Grab the full path to the main plugin's file.
1384 - $plugin_activate = $upgrader->plugin_info();
1385 -
1386 - // Try to activate the plugin.
1387 - $activation_result = $this->try_activate_plugin( $plugin_activate );
1388 -
1389 - if ( is_wp_error( $activation_result ) ) {
1390 - return array(
1391 - 'message' => $activation_result->get_error_message(),
1392 - 'code' => $activation_result->get_error_code(),
1393 - );
1394 - }
1395 -
1396 - return $skin->get_upgrade_messages();
1397 - }
1398 -
1399 - /**
1400 - * Tries to activate a plugin. If fails, returns the error.
1401 - *
1402 - * @author Vova Feldman
1403 - * @since 1.2.1.7
1404 - *
1405 - * @param string $file_path Path within wp-plugins/ to main plugin file.
1406 - * This determines the styling of the output messages.
1407 - *
1408 - * @return bool|WP_Error
1409 - */
1410 - protected function try_activate_plugin( $file_path ) {
1411 - $activate = activate_plugin( $file_path, '', $this->_fs->is_network_active() );
1412 -
1413 - return is_wp_error( $activate ) ?
1414 - $activate :
1415 - true;
1416 - }
1417 -
1418 - /**
1419 - * Check if a premium module version is already active.
1420 - *
1421 - * @author Vova Feldman
1422 - * @since 1.2.1.7
1423 - *
1424 - * @param number|bool $plugin_id
1425 - *
1426 - * @return bool
1427 - */
1428 - private function is_premium_plugin_active( $plugin_id = false ) {
1429 - if ( $plugin_id != $this->_fs->get_id() ) {
1430 - return $this->_fs->is_addon_activated( $plugin_id, true );
1431 - }
1432 -
1433 - return is_plugin_active( $this->_fs->premium_plugin_basename() );
1434 - }
1435 -
1436 - /**
1437 - * Store the basename since it's not always available in the `_maybe_adjust_source_dir` method below.
1438 - *
1439 - * @author Leo Fajardo (@leorw)
1440 - * @since 2.2.1
1441 - *
1442 - * @param bool|WP_Error $response Response.
1443 - * @param array $hook_extra Extra arguments passed to hooked filters.
1444 - *
1445 - * @return bool|WP_Error
1446 - */
1447 - static function _store_basename_for_source_adjustment( $response, $hook_extra ) {
1448 - if ( isset( $hook_extra['plugin'] ) ) {
1449 - self::$_upgrade_basename = $hook_extra['plugin'];
1450 - } else if ( isset( $hook_extra['theme'] ) ) {
1451 - self::$_upgrade_basename = $hook_extra['theme'];
1452 - } else {
1453 - self::$_upgrade_basename = null;
1454 - }
1455 -
1456 - return $response;
1457 - }
1458 -
1459 - /**
1460 - * Adjust the plugin directory name if necessary.
1461 - * Assumes plugin has a folder (not a single file plugin).
1462 - *
1463 - * The final destination directory of a plugin is based on the subdirectory name found in the
1464 - * (un)zipped source. In some cases this subdirectory name is not the same as the expected
1465 - * slug and the plugin will not be recognized as installed. This is fixed by adjusting
1466 - * the temporary unzipped source subdirectory name to the expected plugin slug.
1467 - *
1468 - * @author Vova Feldman
1469 - * @since 1.2.1.7
1470 - * @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.
1471 - *
1472 - * @param string $source Path to upgrade/zip-file-name.tmp/subdirectory/.
1473 - * @param string $remote_source Path to upgrade/zip-file-name.tmp.
1474 - * @param \WP_Upgrader $upgrader Instance of the upgrader which installs the plugin.
1475 - *
1476 - * @return string|WP_Error
1477 - */
1478 - static function _maybe_adjust_source_dir( $source, $remote_source, $upgrader ) {
1479 - if ( ! is_object( $GLOBALS['wp_filesystem'] ) ) {
1480 - return $source;
1481 - }
1482 -
1483 - $basename = self::$_upgrade_basename;
1484 - $is_theme = false;
1485 -
1486 - // Figure out what the slug is supposed to be.
1487 - if ( isset( $upgrader->skin->options['extra'] ) ) {
1488 - // Set by the auto-install logic.
1489 - $desired_slug = $upgrader->skin->options['extra']['slug'];
1490 - } else if ( ! empty( $basename ) ) {
1491 - /**
1492 - * If it doesn't end with ".php", it's a theme.
1493 - *
1494 - * @author Leo Fajardo (@leorw)
1495 - * @since 2.2.1
1496 - */
1497 - $is_theme = ( ! fs_ends_with( $basename, '.php' ) );
1498 -
1499 - $desired_slug = ( ! $is_theme ) ?
1500 - dirname( $basename ) :
1501 - // Theme slug
1502 - $basename;
1503 - } else {
1504 - // Can't figure out the desired slug, stop the execution.
1505 - return $source;
1506 - }
1507 -
1508 - if ( is_multisite() ) {
1509 - /**
1510 - * If we are running in a multisite environment and the product is not network activated,
1511 - * the instance will not exist anyway. Therefore, try to update the source if necessary
1512 - * regardless if the Freemius instance of the product exists or not.
1513 - *
1514 - * @author Vova Feldman
1515 - */
1516 - } else if ( ! empty( $basename ) ) {
1517 - $fs = Freemius::get_instance_by_file(
1518 - $basename,
1519 - $is_theme ?
1520 - WP_FS__MODULE_TYPE_THEME :
1521 - WP_FS__MODULE_TYPE_PLUGIN
1522 - );
1523 -
1524 - if ( ! is_object( $fs ) ) {
1525 - /**
1526 - * If the Freemius instance does not exist on a non-multisite network environment, it means that:
1527 - * 1. The product is not powered by Freemius; OR
1528 - * 2. The product is not activated, therefore, we don't mind if after the update the folder name will change.
1529 - *
1530 - * @author Leo Fajardo (@leorw)
1531 - * @since 2.2.1
1532 - */
1533 - return $source;
1534 - }
1535 - }
1536 -
1537 - $subdir_name = untrailingslashit( str_replace( trailingslashit( $remote_source ), '', $source ) );
1538 -
1539 - if ( ! empty( $subdir_name ) && $subdir_name !== $desired_slug ) {
1540 - $from_path = untrailingslashit( $source );
1541 - $to_path = trailingslashit( $remote_source ) . $desired_slug;
1542 -
1543 - if ( true === $GLOBALS['wp_filesystem']->move( $from_path, $to_path ) ) {
1544 - return trailingslashit( $to_path );
1545 - }
1546 -
1547 - return new WP_Error(
1548 - 'rename_failed',
1549 - 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' ),
1550 - array(
1551 - 'found' => $subdir_name,
1552 - 'expected' => $desired_slug
1553 - )
1554 - );
1555 - }
1556 -
1557 - return $source;
1558 - }
1559 -
1560 - #endregion
1561 - }
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 + // Alias.
544 + $basename = $this->_fs->premium_plugin_basename();
545 +
546 + global $wp_current_filter;
547 +
548 + /**
549 + * During bulk updates, avoid re-injecting update data for the plugin itself once it has already been updated.
550 + *
551 + * If the custom package is re-added to the transient after the plugin update, WordPress may detect the package again and incorrectly report "The plugin is at the latest version" for a pending update, since the custom package version matches the currently installed version.
552 + *
553 + * Behavior differs depending on how the bulk update is triggered. Please refer to the inline comments for each flow below for details.
554 + */
555 + if (
556 + ! empty( $wp_current_filter ) && (
557 + /**
558 + * update-core.php and other upgrader pages:
559 + * The `upgrader_process_complete` action fires only once after all updates have finished. In this case, it is the current action (`$wp_current_filter[0]`), while `self::$_upgrade_basename` may contain any plugin basename.
560 + */
561 + 'upgrader_process_complete' === $wp_current_filter[0] ||
562 + /**
563 + * AJAX bulk updates (e.g., from the Plugins page):
564 + * The `upgrader_process_complete` action fires multiple times — once for each plugin after it finishes updating. In this flow, it is not the current action (`$wp_current_filter[0]`) because it is triggered from another action. Instead, we compare `self::$_upgrade_basename` with the basename of the plugin currently being updated, since the `upgrader_process_complete` action runs separately for each plugin.
565 + */
566 + ( in_array( 'upgrader_process_complete', $wp_current_filter ) && self::$_upgrade_basename === $basename )
567 + )
568 + ) {
569 + return $transient_data;
570 + }
571 +
572 + if ( ! isset( $this->_update_details ) ) {
573 + // Get plugin's newest update.
574 + $new_version = $this->_fs->get_update( false, fs_request_get_bool( 'force-check' ) );
575 +
576 + $this->_update_details = false;
577 +
578 + if ( is_object( $new_version ) && $this->is_new_version_premium( $new_version ) ) {
579 + $this->_logger->log( 'Found newer plugin version ' . $new_version->version );
580 +
581 + /**
582 + * Cache plugin details locally since set_site_transient( 'update_plugins' )
583 + * called multiple times and the non wp.org plugins are filtered after the
584 + * call to .org.
585 + *
586 + * @since 1.1.8.1
587 + */
588 + $this->_update_details = $this->get_update_details( $new_version );
589 + }
590 + }
591 +
592 + if ( is_object( $this->_update_details ) ) {
593 + if ( isset( $transient_data->no_update ) ) {
594 + unset( $transient_data->no_update[ $basename ] );
595 + }
596 +
597 + if ( ! isset( $transient_data->response ) ) {
598 + $transient_data->response = array();
599 + }
600 +
601 + // Add plugin to transient data.
602 + $transient_data->response[ $basename ] = $this->_fs->is_plugin() ?
603 + $this->_update_details :
604 + (array) $this->_update_details;
605 + } else {
606 + if ( isset( $transient_data->response ) ) {
607 + /**
608 + * Ensure that there's no update data for the plugin to prevent upgrading the premium version to the latest free version.
609 + *
610 + * @author Leo Fajardo (@leorw)
611 + * @since 2.3.0
612 + */
613 + unset( $transient_data->response[ $basename ] );
614 + }
615 +
616 + if ( ! isset( $transient_data->no_update ) ) {
617 + $transient_data->no_update = array();
618 + }
619 +
620 + /**
621 + * Add product to no_update transient data to properly integrate with WP 5.5 auto-updates UI.
622 + *
623 + * @since 2.4.1
624 + * @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/
625 + */
626 + $transient_data->no_update[ $basename ] = $this->_fs->is_plugin() ?
627 + (object) array(
628 + 'id' => $basename,
629 + 'slug' => $this->_fs->get_slug(),
630 + 'plugin' => $basename,
631 + 'new_version' => $this->_fs->get_plugin_version(),
632 + 'url' => '',
633 + 'package' => '',
634 + 'icons' => array(),
635 + 'banners' => array(),
636 + 'banners_rtl' => array(),
637 + 'tested' => '',
638 + 'requires_php' => '',
639 + 'compatibility' => new stdClass(),
640 + ) :
641 + array(
642 + 'theme' => $basename,
643 + 'new_version' => $this->_fs->get_plugin_version(),
644 + 'url' => '',
645 + 'package' => '',
646 + 'requires' => '',
647 + 'requires_php' => '',
648 + );
649 + }
650 +
651 + $slug = $this->_fs->get_slug();
652 +
653 + if ( $this->can_fetch_data_from_wp_org() ) {
654 + if ( ! isset( $this->_translation_updates ) ) {
655 + $this->_translation_updates = array();
656 +
657 + $translation_updates = $this->fetch_wp_org_module_translation_updates( $module_type, $slug );
658 + if ( ! empty( $translation_updates ) ) {
659 + $this->_translation_updates = $translation_updates;
660 + }
661 + }
662 +
663 + if ( ! empty( $this->_translation_updates ) ) {
664 + $all_translation_updates = ( isset( $transient_data->translations ) && is_array( $transient_data->translations ) ) ?
665 + $transient_data->translations :
666 + array();
667 +
668 + $current_plugin_translation_updates_map = array();
669 + foreach ( $all_translation_updates as $key => $translation_update ) {
670 + if ( $module_type === ( $translation_update['type'] . 's' ) && $slug === $translation_update['slug'] ) {
671 + $current_plugin_translation_updates_map[ $translation_update['language'] ] = $translation_update;
672 + unset( $all_translation_updates[ $key ] );
673 + }
674 + }
675 +
676 + foreach ( $this->_translation_updates as $translation_update ) {
677 + $lang = $translation_update['language'];
678 + if ( ! isset( $current_plugin_translation_updates_map[ $lang ] ) ||
679 + version_compare( $translation_update['version'], $current_plugin_translation_updates_map[ $lang ]['version'], '>' )
680 + ) {
681 + $current_plugin_translation_updates_map[ $lang ] = $translation_update;
682 + }
683 + }
684 +
685 + $transient_data->translations = array_merge( $all_translation_updates, array_values( $current_plugin_translation_updates_map ) );
686 + }
687 + }
688 +
689 + return $transient_data;
690 + }
691 +
692 + /**
693 + * Get module's required data for the updates' mechanism.
694 + *
695 + * @author Vova Feldman (@svovaf)
696 + * @since 2.0.0
697 + *
698 + * @param \FS_Plugin_Tag $new_version
699 + *
700 + * @return object
701 + */
702 + function get_update_details( FS_Plugin_Tag $new_version ) {
703 + $update = new stdClass();
704 + $update->slug = $this->_fs->get_slug();
705 + $update->new_version = $new_version->version;
706 + $update->url = WP_FS__ADDRESS;
707 + $update->package = $new_version->url;
708 + $update->tested = self::get_tested_wp_version( $new_version->tested_up_to_version );
709 + $update->requires = $new_version->requires_platform_version;
710 + $update->requires_php = $new_version->requires_programming_language_version;
711 +
712 + $icon = $this->_fs->get_local_icon_url();
713 +
714 + if ( ! empty( $icon ) ) {
715 + $update->icons = array(
716 +// '1x' => $icon,
717 +// '2x' => $icon,
718 + 'default' => $icon,
719 + );
720 + }
721 +
722 + if ( $this->_fs->is_premium() && ! empty( $new_version->upgrade_notice ) ) {
723 + $update->upgrade_notice = $new_version->upgrade_notice;
724 + }
725 +
726 + $update->{$this->_fs->get_module_type()} = $this->_fs->get_plugin_basename();
727 +
728 + return $update;
729 + }
730 +
731 + /**
732 + * @author Leo Fajardo (@leorw)
733 + * @since 2.3.0
734 + *
735 + * @param FS_Plugin_Tag $new_version
736 + *
737 + * @return bool
738 + */
739 + private function is_new_version_premium( FS_Plugin_Tag $new_version ) {
740 + $params = fs_parse_url_params( $new_version->url );
741 +
742 + return ( isset( $params['is_premium'] ) && 'true' == $params['is_premium'] );
743 + }
744 +
745 + /**
746 + * Update the updates transient with the module's update information.
747 + *
748 + * This method is required for multisite environment.
749 + * If a module is site activated (not network) and not on the main site,
750 + * the module will NOT be executed on the network level, therefore, the
751 + * custom updates logic will not be executed as well, so unless we force
752 + * the injection of the update into the updates transient, premium updates
753 + * will not work.
754 + *
755 + * @author Vova Feldman (@svovaf)
756 + * @since 2.0.0
757 + *
758 + * @param \FS_Plugin_Tag $new_version
759 + */
760 + function set_update_data( FS_Plugin_Tag $new_version ) {
761 + $this->_logger->entrance();
762 +
763 + if ( ! $this->is_new_version_premium( $new_version ) ) {
764 + return;
765 + }
766 +
767 + $transient_key = "update_{$this->_fs->get_module_type()}s";
768 +
769 + $transient_data = get_site_transient( $transient_key );
770 +
771 + $transient_data = is_object( $transient_data ) ?
772 + $transient_data :
773 + new stdClass();
774 +
775 + // Alias.
776 + $basename = $this->_fs->get_plugin_basename();
777 + $is_plugin = $this->_fs->is_plugin();
778 +
779 + if ( ! isset( $transient_data->response ) ||
780 + ! is_array( $transient_data->response )
781 + ) {
782 + $transient_data->response = array();
783 + } else if ( ! empty( $transient_data->response[ $basename ] ) ) {
784 + $version = $is_plugin ?
785 + ( ! empty( $transient_data->response[ $basename ]->new_version ) ?
786 + $transient_data->response[ $basename ]->new_version :
787 + null
788 + ) : ( ! empty( $transient_data->response[ $basename ]['new_version'] ) ?
789 + $transient_data->response[ $basename ]['new_version'] :
790 + null
791 + );
792 +
793 + if ( $version == $new_version->version ) {
794 + // The update data is already set.
795 + return;
796 + }
797 + }
798 +
799 + // Remove the added filters.
800 + $this->remove_transient_filters();
801 +
802 + $this->_update_details = $this->get_update_details( $new_version );
803 +
804 + // Set update data in transient.
805 + $transient_data->response[ $basename ] = $is_plugin ?
806 + $this->_update_details :
807 + (array) $this->_update_details;
808 +
809 + if ( ! isset( $transient_data->checked ) ||
810 + ! is_array( $transient_data->checked )
811 + ) {
812 + $transient_data->checked = array();
813 + }
814 +
815 + // Flag the module as if it was already checked.
816 + $transient_data->checked[ $basename ] = $this->_fs->get_plugin_version();
817 + $transient_data->last_checked = time();
818 +
819 + set_site_transient( $transient_key, $transient_data );
820 +
821 + $this->add_transient_filters();
822 + }
823 +
824 + /**
825 + * @author Leo Fajardo (@leorw)
826 + * @since 2.0.2
827 + */
828 + function delete_update_data() {
829 + $this->_logger->entrance();
830 +
831 + $transient_key = "update_{$this->_fs->get_module_type()}s";
832 +
833 + $transient_data = get_site_transient( $transient_key );
834 +
835 + // Alias
836 + $basename = $this->_fs->get_plugin_basename();
837 +
838 + if ( ! is_object( $transient_data ) ||
839 + ! isset( $transient_data->response ) ||
840 + ! is_array( $transient_data->response ) ||
841 + empty( $transient_data->response[ $basename ] )
842 + ) {
843 + return;
844 + }
845 +
846 + unset( $transient_data->response[ $basename ] );
847 +
848 + // Remove the added filters.
849 + $this->remove_transient_filters();
850 +
851 + set_site_transient( $transient_key, $transient_data );
852 +
853 + $this->add_transient_filters();
854 + }
855 +
856 + /**
857 + * Try to fetch plugin's info from .org repository.
858 + *
859 + * @author Vova Feldman (@svovaf)
860 + * @since 1.0.5
861 + *
862 + * @param string $action
863 + * @param object $args
864 + *
865 + * @return bool|mixed
866 + */
867 + static function _fetch_plugin_info_from_repository( $action, $args ) {
868 + $url = $http_url = 'http://api.wordpress.org/plugins/info/1.2/';
869 + $url = add_query_arg(
870 + array(
871 + 'action' => $action,
872 + 'request' => $args,
873 + ),
874 + $url
875 + );
876 +
877 + if ( wp_http_supports( array( 'ssl' ) ) ) {
878 + $url = set_url_scheme( $url, 'https' );
879 + }
880 +
881 + // The new endpoint version serves only GET requests.
882 + $request = wp_remote_get( $url, array( 'timeout' => 15 ) );
883 +
884 + if ( is_wp_error( $request ) ) {
885 + return false;
886 + }
887 +
888 + $res = json_decode( wp_remote_retrieve_body( $request ), true );
889 +
890 + if ( is_array( $res ) ) {
891 + // 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).
892 + $res = (object) $res;
893 + }
894 +
895 + if ( ! is_object( $res ) || isset( $res->error ) ) {
896 + return false;
897 + }
898 +
899 + return $res;
900 + }
901 +
902 + /**
903 + * Returns true if the product can fetch data from WordPress.org.
904 + *
905 + * @author Leo Fajardo (@leorw)
906 + * @since 2.7.4
907 + */
908 + private function can_fetch_data_from_wp_org() {
909 + return ( $this->_fs->is_org_repo_compliant() && $this->_fs->is_freemium() );
910 + }
911 +
912 + /**
913 + * Fetches module translation updates from wordpress.org.
914 + *
915 + * @author Leo Fajardo (@leorw)
916 + * @since 2.1.2
917 + *
918 + * @param string $module_type
919 + * @param string $slug
920 + *
921 + * @return array|null
922 + */
923 + private function fetch_wp_org_module_translation_updates( $module_type, $slug ) {
924 + $plugin_data = $this->_fs->get_plugin_data();
925 +
926 + $locales = array_values( get_available_languages() );
927 + $locales = apply_filters( "{$module_type}_update_check_locales", $locales );
928 + $locales = array_unique( $locales );
929 +
930 + $plugin_basename = $this->_fs->get_plugin_basename();
931 + if ( 'themes' === $module_type ) {
932 + $plugin_basename = $slug;
933 + }
934 +
935 + global $wp_version;
936 +
937 + $request_args = array(
938 + 'timeout' => 15,
939 + 'body' => array(
940 + "{$module_type}" => json_encode(
941 + array(
942 + "{$module_type}" => array(
943 + $plugin_basename => array(
944 + 'Name' => trim( str_replace( $this->_fs->get_plugin()->premium_suffix, '', $plugin_data['Name'] ) ),
945 + 'Author' => $plugin_data['Author'],
946 + )
947 + )
948 + )
949 + ),
950 + 'translations' => json_encode( $this->get_installed_translations( $module_type, $slug ) ),
951 + 'locale' => json_encode( $locales )
952 + ),
953 + 'user-agent' => ( 'WordPress/' . $wp_version . '; ' . home_url( '/' ) )
954 + );
955 +
956 + $url = "http://api.wordpress.org/{$module_type}/update-check/1.1/";
957 + if ( $ssl = wp_http_supports( array( 'ssl' ) ) ) {
958 + $url = set_url_scheme( $url, 'https' );
959 + }
960 +
961 + $raw_response = Freemius::safe_remote_post(
962 + $url,
963 + $request_args,
964 + WP_FS__TIME_24_HOURS_IN_SEC,
965 + WP_FS__TIME_12_HOURS_IN_SEC,
966 + false
967 + );
968 +
969 + if ( is_wp_error( $raw_response ) ) {
970 + return null;
971 + }
972 +
973 + $response = json_decode( wp_remote_retrieve_body( $raw_response ), true );
974 +
975 + if ( ! is_array( $response ) ) {
976 + return null;
977 + }
978 +
979 + if ( ! isset( $response['translations'] ) || empty( $response['translations'] ) ) {
980 + return null;
981 + }
982 +
983 + return $response['translations'];
984 + }
985 +
986 + /**
987 + * @author Leo Fajardo (@leorw)
988 + * @since 2.1.2
989 + *
990 + * @param string $module_type
991 + * @param string $slug
992 + *
993 + * @return array
994 + */
995 + private function get_installed_translations( $module_type, $slug ) {
996 + if ( function_exists( 'wp_get_installed_translations' ) ) {
997 + return wp_get_installed_translations( $module_type );
998 + }
999 +
1000 + $dir = "/{$module_type}";
1001 +
1002 + if ( ! is_dir( WP_LANG_DIR . $dir ) )
1003 + return array();
1004 +
1005 + $files = scandir( WP_LANG_DIR . $dir );
1006 + if ( ! $files )
1007 + return array();
1008 +
1009 + $language_data = array();
1010 +
1011 + foreach ( $files as $file ) {
1012 + if ( 0 !== strpos( $file, $slug ) ) {
1013 + continue;
1014 + }
1015 +
1016 + if ( '.' === $file[0] || is_dir( WP_LANG_DIR . "{$dir}/{$file}" ) ) {
1017 + continue;
1018 + }
1019 +
1020 + if ( substr( $file, -3 ) !== '.po' ) {
1021 + continue;
1022 + }
1023 +
1024 + if ( ! preg_match( '/(?:(.+)-)?([a-z]{2,3}(?:_[A-Z]{2})?(?:_[a-z0-9]+)?).po/', $file, $match ) ) {
1025 + continue;
1026 + }
1027 +
1028 + if ( ! in_array( substr( $file, 0, -3 ) . '.mo', $files ) ) {
1029 + continue;
1030 + }
1031 +
1032 + list( , $textdomain, $language ) = $match;
1033 +
1034 + if ( '' === $textdomain ) {
1035 + $textdomain = 'default';
1036 + }
1037 +
1038 + $language_data[ $textdomain ][ $language ] = wp_get_pomo_file_data( WP_LANG_DIR . "{$dir}/{$file}" );
1039 + }
1040 +
1041 + return $language_data;
1042 + }
1043 +
1044 + /**
1045 + * Updates information on the "View version x.x details" page with custom data.
1046 + *
1047 + * @author Vova Feldman (@svovaf)
1048 + * @since 1.0.4
1049 + *
1050 + * @uses FS_Api
1051 + *
1052 + * @param object $data
1053 + * @param string $action
1054 + * @param mixed $args
1055 + *
1056 + * @return object
1057 + */
1058 + function plugins_api_filter( $data, $action = '', $args = null ) {
1059 + $this->_logger->entrance();
1060 +
1061 + if ( ( 'plugin_information' !== $action ) ||
1062 + ! isset( $args->slug )
1063 + ) {
1064 + return $data;
1065 + }
1066 +
1067 + $addon = false;
1068 + $is_addon = false;
1069 + $addon_version = false;
1070 +
1071 + if ( $this->_fs->get_slug() !== $args->slug ) {
1072 + $addon = $this->_fs->get_addon_by_slug( $args->slug );
1073 +
1074 + if ( ! is_object( $addon ) ) {
1075 + return $data;
1076 + }
1077 +
1078 + if ( $this->_fs->is_addon_activated( $addon->id ) ) {
1079 + $addon_version = $this->_fs->get_addon_instance( $addon->id )->get_plugin_version();
1080 + } else if ( $this->_fs->is_addon_installed( $addon->id ) ) {
1081 + $addon_plugin_data = get_plugin_data(
1082 + ( WP_PLUGIN_DIR . '/' . $this->_fs->get_addon_basename( $addon->id ) ),
1083 + false,
1084 + false
1085 + );
1086 +
1087 + if ( ! empty( $addon_plugin_data ) ) {
1088 + $addon_version = $addon_plugin_data['Version'];
1089 + }
1090 + }
1091 +
1092 + $is_addon = true;
1093 + }
1094 +
1095 + $plugin_in_repo = false;
1096 + if ( ! $is_addon && $this->can_fetch_data_from_wp_org() ) {
1097 + // Try to fetch info from .org repository.
1098 + $data = self::_fetch_plugin_info_from_repository( $action, $args );
1099 +
1100 + $plugin_in_repo = ( false !== $data );
1101 + }
1102 +
1103 + if ( ! $plugin_in_repo ) {
1104 + $data = $args;
1105 +
1106 + // Fetch as much as possible info from local files.
1107 + $plugin_local_data = $this->_fs->get_plugin_data();
1108 + $data->name = $plugin_local_data['Name'];
1109 + $data->author = $plugin_local_data['Author'];
1110 + $data->sections = array(
1111 + 'description' => 'Upgrade ' . $plugin_local_data['Name'] . ' to latest.',
1112 + );
1113 +
1114 + // @todo Store extra plugin info on Freemius or parse readme.txt markup.
1115 + /*$info = $this->_fs->get_api_site_scope()->call('/information.json');
1116 +
1117 +if ( !isset($info->error) ) {
1118 + $data = $info;
1119 +}*/
1120 + }
1121 +
1122 + $plugin_version = $is_addon ?
1123 + $addon_version :
1124 + $this->_fs->get_plugin_version();
1125 +
1126 + // Get plugin's newest update.
1127 + $new_version = $this->get_latest_download_details( $is_addon ? $addon->id : false, $plugin_version );
1128 +
1129 + if ( ! is_object( $new_version ) || empty( $new_version->version ) ) {
1130 + $data->version = $plugin_version;
1131 + } else {
1132 + if ( $is_addon ) {
1133 + $data->name = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
1134 + $data->slug = $addon->slug;
1135 + $data->url = WP_FS__ADDRESS;
1136 + $data->package = $new_version->url;
1137 + }
1138 +
1139 + if ( ! $plugin_in_repo ) {
1140 + $data->last_updated = ! is_null( $new_version->updated ) ? $new_version->updated : $new_version->created;
1141 + $data->requires = $new_version->requires_platform_version;
1142 + $data->requires_php = $new_version->requires_programming_language_version;
1143 + $data->tested = $new_version->tested_up_to_version;
1144 + }
1145 +
1146 + $data->version = $new_version->version;
1147 + $data->download_link = $new_version->url;
1148 +
1149 + if ( isset( $new_version->readme ) && is_object( $new_version->readme ) ) {
1150 + $new_version_readme_data = $new_version->readme;
1151 + if ( isset( $new_version_readme_data->sections ) ) {
1152 + $new_version_readme_data->sections = (array) $new_version_readme_data->sections;
1153 + } else {
1154 + $new_version_readme_data->sections = array();
1155 + }
1156 +
1157 + if ( isset( $data->sections ) ) {
1158 + if ( isset( $data->sections['screenshots'] ) ) {
1159 + $new_version_readme_data->sections['screenshots'] = $data->sections['screenshots'];
1160 + }
1161 +
1162 + if ( isset( $data->sections['reviews'] ) ) {
1163 + $new_version_readme_data->sections['reviews'] = $data->sections['reviews'];
1164 + }
1165 + }
1166 +
1167 + if ( isset( $new_version_readme_data->banners ) ) {
1168 + $new_version_readme_data->banners = (array) $new_version_readme_data->banners;
1169 + } else if ( isset( $data->banners ) ) {
1170 + $new_version_readme_data->banners = $data->banners;
1171 + }
1172 +
1173 + $wp_org_sections = array(
1174 + 'author',
1175 + 'author_profile',
1176 + 'rating',
1177 + 'ratings',
1178 + 'num_ratings',
1179 + 'support_threads',
1180 + 'support_threads_resolved',
1181 + 'active_installs',
1182 + 'added',
1183 + 'homepage'
1184 + );
1185 +
1186 + foreach ( $wp_org_sections as $wp_org_section ) {
1187 + if ( isset( $data->{$wp_org_section} ) ) {
1188 + $new_version_readme_data->{$wp_org_section} = $data->{$wp_org_section};
1189 + }
1190 + }
1191 +
1192 + $data = $new_version_readme_data;
1193 + }
1194 + }
1195 +
1196 + if ( ! empty( $data->tested ) ) {
1197 + $data->tested = self::get_tested_wp_version( $data->tested );
1198 + }
1199 +
1200 + return $data;
1201 + }
1202 +
1203 + /**
1204 + * @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.
1205 + *
1206 + * @param string|null $tested_up_to
1207 + *
1208 + * @return string|null
1209 + */
1210 + private static function get_tested_wp_version( $tested_up_to ) {
1211 + $current_wp_version = get_bloginfo( 'version' );
1212 +
1213 + return ( ! empty($tested_up_to) && fs_starts_with( $current_wp_version, $tested_up_to . '.' ) ) ?
1214 + $current_wp_version :
1215 + $tested_up_to;
1216 + }
1217 +
1218 + /**
1219 + * @author Vova Feldman (@svovaf)
1220 + * @since 1.2.1.7
1221 + *
1222 + * @param number|bool $addon_id
1223 + * @param bool|string $newer_than Since 2.2.1
1224 + * @param bool|string $fetch_readme Since 2.2.1
1225 + *
1226 + * @return object
1227 + */
1228 + private function get_latest_download_details( $addon_id = false, $newer_than = false, $fetch_readme = true ) {
1229 + return $this->_fs->_fetch_latest_version( $addon_id, true, FS_Plugin_Updater::UPDATES_CHECK_CACHE_EXPIRATION, $newer_than, $fetch_readme );
1230 + }
1231 +
1232 + /**
1233 + * Checks if a given basename has a matching folder name
1234 + * with the current context plugin.
1235 + *
1236 + * @author Vova Feldman (@svovaf)
1237 + * @since 1.2.1.6
1238 + *
1239 + * @return bool
1240 + */
1241 + private function is_correct_folder_name() {
1242 + return ( $this->_fs->get_target_folder_name() == trim( dirname( $this->_fs->get_plugin_basename() ), '/\\' ) );
1243 + }
1244 +
1245 + /**
1246 + * This is a special after upgrade handler for migrating modules
1247 + * that didn't use the '-premium' suffix folder structure before
1248 + * the migration.
1249 + *
1250 + * @author Vova Feldman (@svovaf)
1251 + * @since 1.2.1.6
1252 + *
1253 + * @param bool $response Install response.
1254 + * @param array $hook_extra Extra arguments passed to hooked filters.
1255 + * @param array $result Installation result data.
1256 + *
1257 + * @return bool
1258 + */
1259 + function _maybe_update_folder_name( $response, $hook_extra, $result ) {
1260 + $basename = $this->_fs->get_plugin_basename();
1261 +
1262 + if ( true !== $response ||
1263 + empty( $hook_extra ) ||
1264 + empty( $hook_extra['plugin'] ) ||
1265 + $basename !== $hook_extra['plugin']
1266 + ) {
1267 + return $response;
1268 + }
1269 +
1270 + $active_plugins_basenames = get_option( 'active_plugins' );
1271 +
1272 + foreach ( $active_plugins_basenames as $key => $active_plugin_basename ) {
1273 + if ( $basename === $active_plugin_basename ) {
1274 + // Get filename including extension.
1275 + $filename = basename( $basename );
1276 +
1277 + $new_basename = plugin_basename(
1278 + trailingslashit( $this->_fs->is_premium() ? $this->_fs->get_premium_slug() : $this->_fs->get_slug() ) .
1279 + $filename
1280 + );
1281 +
1282 + // Verify that the expected correct path exists.
1283 + if ( file_exists( fs_normalize_path( WP_PLUGIN_DIR . '/' . $new_basename ) ) ) {
1284 + // Override active plugin name.
1285 + $active_plugins_basenames[ $key ] = $new_basename;
1286 + update_option( 'active_plugins', $active_plugins_basenames );
1287 + }
1288 +
1289 + break;
1290 + }
1291 + }
1292 +
1293 + return $response;
1294 + }
1295 +
1296 + #----------------------------------------------------------------------------------
1297 + #region Auto Activation
1298 + #----------------------------------------------------------------------------------
1299 +
1300 + /**
1301 + * Installs and active a plugin when explicitly requested that from a 3rd party service.
1302 + *
1303 + * This logic was inspired by the TGMPA GPL licensed library by Thomas Griffin.
1304 + *
1305 + * @link http://tgmpluginactivation.com/
1306 + *
1307 + * @author Vova Feldman
1308 + * @since 1.2.1.7
1309 + *
1310 + * @link https://make.wordpress.org/plugins/2017/03/16/clarification-of-guideline-8-executable-code-and-installs/
1311 + *
1312 + * @uses WP_Filesystem
1313 + * @uses WP_Error
1314 + * @uses WP_Upgrader
1315 + * @uses Plugin_Upgrader
1316 + * @uses Plugin_Installer_Skin
1317 + * @uses Plugin_Upgrader_Skin
1318 + *
1319 + * @param number|bool $plugin_id
1320 + *
1321 + * @return array
1322 + */
1323 + function install_and_activate_plugin( $plugin_id = false ) {
1324 + if ( ! empty( $plugin_id ) && ! FS_Plugin::is_valid_id( $plugin_id ) ) {
1325 + // Invalid plugin ID.
1326 + return array(
1327 + 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
1328 + 'code' => 'invalid_module_id',
1329 + );
1330 + }
1331 +
1332 + $is_addon = false;
1333 + if ( FS_Plugin::is_valid_id( $plugin_id ) &&
1334 + $plugin_id != $this->_fs->get_id()
1335 + ) {
1336 + $addon = $this->_fs->get_addon( $plugin_id );
1337 +
1338 + if ( ! is_object( $addon ) ) {
1339 + // Invalid add-on ID.
1340 + return array(
1341 + 'message' => $this->_fs->get_text_inline( 'Invalid module ID.', 'auto-install-error-invalid-id' ),
1342 + 'code' => 'invalid_module_id',
1343 + );
1344 + }
1345 +
1346 + $slug = $addon->slug;
1347 + $premium_slug = $addon->premium_slug;
1348 + $title = $addon->title . ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' );
1349 +
1350 + $is_addon = true;
1351 + } else {
1352 + $slug = $this->_fs->get_slug();
1353 + $premium_slug = $this->_fs->get_premium_slug();
1354 + $title = $this->_fs->get_plugin_title() .
1355 + ( $this->_fs->is_addon() ? ' ' . $this->_fs->get_text_inline( 'Add-On', 'addon' ) : '' );
1356 + }
1357 +
1358 + if ( $this->is_premium_plugin_active( $plugin_id ) ) {
1359 + // Premium version already activated.
1360 + return array(
1361 + 'message' => $is_addon ?
1362 + $this->_fs->get_text_inline( 'Premium add-on version already installed.', 'auto-install-error-premium-addon-activated' ) :
1363 + $this->_fs->get_text_inline( 'Premium version already active.', 'auto-install-error-premium-activated' ),
1364 + 'code' => 'premium_installed',
1365 + );
1366 + }
1367 +
1368 + $latest_version = $this->get_latest_download_details( $plugin_id, false, false );
1369 + $target_folder = $premium_slug;
1370 +
1371 + // Prep variables for Plugin_Installer_Skin class.
1372 + $extra = array();
1373 + $extra['slug'] = $target_folder;
1374 + $source = $latest_version->url;
1375 + $api = null;
1376 +
1377 + $install_url = add_query_arg(
1378 + array(
1379 + 'action' => 'install-plugin',
1380 + 'plugin' => urlencode( $slug ),
1381 + ),
1382 + 'update.php'
1383 + );
1384 +
1385 + if ( ! class_exists( 'Plugin_Upgrader', false ) ) {
1386 + // Include required resources for the installation.
1387 + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1388 + }
1389 +
1390 + $skin_args = array(
1391 + 'type' => 'web',
1392 + 'title' => sprintf( $this->_fs->get_text_inline( 'Installing plugin: %s', 'installing-plugin-x' ), $title ),
1393 + 'url' => esc_url_raw( $install_url ),
1394 + 'nonce' => 'install-plugin_' . $slug,
1395 + 'plugin' => '',
1396 + 'api' => $api,
1397 + 'extra' => $extra,
1398 + );
1399 +
1400 +// $skin = new Automatic_Upgrader_Skin( $skin_args );
1401 +// $skin = new Plugin_Installer_Skin( $skin_args );
1402 + $skin = new WP_Ajax_Upgrader_Skin( $skin_args );
1403 +
1404 + // Create a new instance of Plugin_Upgrader.
1405 + $upgrader = new Plugin_Upgrader( $skin );
1406 +
1407 + // Perform the action and install the plugin from the $source urldecode().
1408 + add_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1, 3 );
1409 +
1410 + $install_result = $upgrader->install( $source );
1411 +
1412 + remove_filter( 'upgrader_source_selection', array( 'FS_Plugin_Updater', '_maybe_adjust_source_dir' ), 1 );
1413 +
1414 + if ( is_wp_error( $install_result ) ) {
1415 + return array(
1416 + 'message' => $install_result->get_error_message(),
1417 + 'code' => $install_result->get_error_code(),
1418 + );
1419 + } elseif ( is_wp_error( $skin->result ) ) {
1420 + return array(
1421 + 'message' => $skin->result->get_error_message(),
1422 + 'code' => $skin->result->get_error_code(),
1423 + );
1424 + } elseif ( $skin->get_errors()->get_error_code() ) {
1425 + return array(
1426 + 'message' => $skin->get_error_messages(),
1427 + 'code' => 'unknown',
1428 + );
1429 + } elseif ( is_null( $install_result ) ) {
1430 + global $wp_filesystem;
1431 +
1432 + $error_code = 'unable_to_connect_to_filesystem';
1433 + $error_message = $this->_fs->get_text_inline( 'Unable to connect to the filesystem. Please confirm your credentials.' );
1434 +
1435 + // Pass through the error from WP_Filesystem if one was raised.
1436 + if ( $wp_filesystem instanceof WP_Filesystem_Base &&
1437 + is_wp_error( $wp_filesystem->errors ) &&
1438 + $wp_filesystem->errors->get_error_code()
1439 + ) {
1440 + $error_message = $wp_filesystem->errors->get_error_message();
1441 + }
1442 +
1443 + return array(
1444 + 'message' => $error_message,
1445 + 'code' => $error_code,
1446 + );
1447 + }
1448 +
1449 + // Grab the full path to the main plugin's file.
1450 + $plugin_activate = $upgrader->plugin_info();
1451 +
1452 + // Try to activate the plugin.
1453 + $activation_result = $this->try_activate_plugin( $plugin_activate );
1454 +
1455 + if ( is_wp_error( $activation_result ) ) {
1456 + return array(
1457 + 'message' => $activation_result->get_error_message(),
1458 + 'code' => $activation_result->get_error_code(),
1459 + );
1460 + }
1461 +
1462 + return $skin->get_upgrade_messages();
1463 + }
1464 +
1465 + /**
1466 + * Tries to activate a plugin. If fails, returns the error.
1467 + *
1468 + * @author Vova Feldman
1469 + * @since 1.2.1.7
1470 + *
1471 + * @param string $file_path Path within wp-plugins/ to main plugin file.
1472 + * This determines the styling of the output messages.
1473 + *
1474 + * @return bool|WP_Error
1475 + */
1476 + protected function try_activate_plugin( $file_path ) {
1477 + $activate = activate_plugin( $file_path, '', $this->_fs->is_network_active() );
1478 +
1479 + return is_wp_error( $activate ) ?
1480 + $activate :
1481 + true;
1482 + }
1483 +
1484 + /**
1485 + * Check if a premium module version is already active.
1486 + *
1487 + * @author Vova Feldman
1488 + * @since 1.2.1.7
1489 + *
1490 + * @param number|bool $plugin_id
1491 + *
1492 + * @return bool
1493 + */
1494 + private function is_premium_plugin_active( $plugin_id = false ) {
1495 + if ( $plugin_id != $this->_fs->get_id() ) {
1496 + return $this->_fs->is_addon_activated( $plugin_id, true );
1497 + }
1498 +
1499 + return is_plugin_active( $this->_fs->premium_plugin_basename() );
1500 + }
1501 +
1502 + /**
1503 + * Store the basename since it's not always available in the `_maybe_adjust_source_dir` method below.
1504 + *
1505 + * @author Leo Fajardo (@leorw)
1506 + * @since 2.2.1
1507 + *
1508 + * @param bool|WP_Error $response Response.
1509 + * @param array $hook_extra Extra arguments passed to hooked filters.
1510 + *
1511 + * @return bool|WP_Error
1512 + */
1513 + static function _store_basename_for_source_adjustment( $response, $hook_extra ) {
1514 + if ( isset( $hook_extra['plugin'] ) ) {
1515 + self::$_upgrade_basename = $hook_extra['plugin'];
1516 + } else if ( isset( $hook_extra['theme'] ) ) {
1517 + self::$_upgrade_basename = $hook_extra['theme'];
1518 + } else {
1519 + self::$_upgrade_basename = null;
1520 + }
1521 +
1522 + return $response;
1523 + }
1524 +
1525 + /**
1526 + * Adjust the plugin directory name if necessary.
1527 + * Assumes plugin has a folder (not a single file plugin).
1528 + *
1529 + * The final destination directory of a plugin is based on the subdirectory name found in the
1530 + * (un)zipped source. In some cases this subdirectory name is not the same as the expected
1531 + * slug and the plugin will not be recognized as installed. This is fixed by adjusting
1532 + * the temporary unzipped source subdirectory name to the expected plugin slug.
1533 + *
1534 + * @author Vova Feldman
1535 + * @since 1.2.1.7
1536 + * @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.
1537 + *
1538 + * @param string $source Path to upgrade/zip-file-name.tmp/subdirectory/.
1539 + * @param string $remote_source Path to upgrade/zip-file-name.tmp.
1540 + * @param \WP_Upgrader $upgrader Instance of the upgrader which installs the plugin.
1541 + *
1542 + * @return string|WP_Error
1543 + */
1544 + static function _maybe_adjust_source_dir( $source, $remote_source, $upgrader ) {
1545 + if ( ! is_object( $GLOBALS['wp_filesystem'] ) ) {
1546 + return $source;
1547 + }
1548 +
1549 + $basename = self::$_upgrade_basename;
1550 + $is_theme = false;
1551 +
1552 + // Figure out what the slug is supposed to be.
1553 + if ( isset( $upgrader->skin->options['extra'] ) ) {
1554 + // Set by the auto-install logic.
1555 + $desired_slug = $upgrader->skin->options['extra']['slug'];
1556 + } else if ( ! empty( $basename ) ) {
1557 + /**
1558 + * If it doesn't end with ".php", it's a theme.
1559 + *
1560 + * @author Leo Fajardo (@leorw)
1561 + * @since 2.2.1
1562 + */
1563 + $is_theme = ( ! fs_ends_with( $basename, '.php' ) );
1564 +
1565 + $desired_slug = ( ! $is_theme ) ?
1566 + dirname( $basename ) :
1567 + // Theme slug
1568 + $basename;
1569 + } else {
1570 + // Can't figure out the desired slug, stop the execution.
1571 + return $source;
1572 + }
1573 +
1574 + if ( is_multisite() ) {
1575 + /**
1576 + * If we are running in a multisite environment and the product is not network activated,
1577 + * the instance will not exist anyway. Therefore, try to update the source if necessary
1578 + * regardless if the Freemius instance of the product exists or not.
1579 + *
1580 + * @author Vova Feldman
1581 + */
1582 + } else if ( ! empty( $basename ) ) {
1583 + $fs = Freemius::get_instance_by_file(
1584 + $basename,
1585 + $is_theme ?
1586 + WP_FS__MODULE_TYPE_THEME :
1587 + WP_FS__MODULE_TYPE_PLUGIN
1588 + );
1589 +
1590 + if ( ! is_object( $fs ) ) {
1591 + /**
1592 + * If the Freemius instance does not exist on a non-multisite network environment, it means that:
1593 + * 1. The product is not powered by Freemius; OR
1594 + * 2. The product is not activated, therefore, we don't mind if after the update the folder name will change.
1595 + *
1596 + * @author Leo Fajardo (@leorw)
1597 + * @since 2.2.1
1598 + */
1599 + return $source;
1600 + }
1601 + }
1602 +
1603 + $subdir_name = untrailingslashit( str_replace( trailingslashit( $remote_source ), '', $source ) );
1604 +
1605 + if ( ! empty( $subdir_name ) && $subdir_name !== $desired_slug ) {
1606 + $from_path = untrailingslashit( $source );
1607 + $to_path = trailingslashit( $remote_source ) . $desired_slug;
1608 +
1609 + if ( true === $GLOBALS['wp_filesystem']->move( $from_path, $to_path ) ) {
1610 + return trailingslashit( $to_path );
1611 + }
1612 +
1613 + return new WP_Error(
1614 + 'rename_failed',
1615 + 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' ),
1616 + array(
1617 + 'found' => $subdir_name,
1618 + 'expected' => $desired_slug
1619 + )
1620 + );
1621 + }
1622 +
1623 + return $source;
1624 + }
1625 +
1626 + #endregion
1627 + }