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