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