PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.9
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.9
4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 All 140 releases
learnpress / inc / Services / AddonService.php

AddonService.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.9, at inc/Services/AddonService.php

557 lines 14.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace LearnPress\Services;
4
5 use DateTime;
6 use Exception;
7 use LearnPress\Helpers\LPDateTime;
8 use LearnPress\Helpers\Singleton;
9 use LP_Cache;
10 use LP_Debug;
11 use LP_Helper;
12 use LP_Settings;
13 use LP_WP_Filesystem;
14 use Plugin_Upgrader;
15 use stdClass;
16 use Throwable;
17 use WP_Ajax_Upgrader_Skin;
18
19 defined( 'ABSPATH' ) || exit;
20
21 /**
22 * Class AddonService
23 *
24 * Manage LearnPress add-ons: list, install, update, activate, deactivate,
25 * purchase code validation.
26 *
27 * @package LearnPress\Services
28 * @change from LP_Manager_Addons class old
29 * @since 4.2.1
30 * @version 1.0.1
31 */
32 class AddonService {
33 use Singleton;
34
35 /**
36 * @var string list addons config on the github
37 */
38 public $url_list_addons = 'https://learnpress.github.io/learnpress/version-addons.json';
39 /**
40 * @var string Link download plugin from ThimPress.
41 */
42 private $link_addon_action = 'https://updates.thimpress.com/thim-addon-market/download-addon';
43 /**
44 * @var string Link get info addons purchased.
45 */
46 public $link_addons_purchased = 'https://updates.thimpress.com/thim-addon-market/info-addons-purchased';
47 /**
48 * @var string Link active site.
49 */
50 private $link_active_site = 'https://updates.thimpress.com/thim-addon-market/active-site';
51 /**
52 * @var string Link download plugin from org.
53 */
54 public $link_org = 'https://downloads.wordpress.org/plugin/';
55 /**
56 * @var WP_Ajax_Upgrader_Skin $upgrader_skin
57 */
58 public $upgrader_skin;
59 /**
60 * @var Plugin_Upgrader $plugin_upgrader
61 */
62 public $plugin_upgrader;
63 /**
64 * @var string Option key stores purchase codes of addons.
65 */
66 public $key_purchase_addons = 'purchase_addons';
67
68 /**
69 * Constructor
70 */
71 protected function __construct() {
72 include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
73 include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
74
75 $this->upgrader_skin = new WP_Ajax_Upgrader_Skin();
76 $this->plugin_upgrader = new Plugin_Upgrader( $this->upgrader_skin );
77 }
78
79 /**
80 * Init hooks.
81 *
82 * @return void
83 */
84 public function init(): void {}
85
86 /**
87 * Get addons data from the remote source.
88 *
89 * @return object
90 */
91 public function get_remote_data(): object {
92 $data = new stdClass();
93
94 try {
95 $lp_cache = new LP_Cache( true );
96 $key_cache = 'lp_addons_data';
97 $dat_cache = $lp_cache->get_cache( $key_cache );
98 if ( false !== $dat_cache ) {
99 return json_decode( wp_json_encode( $dat_cache ) );
100 }
101
102 if ( ! empty( $this->url_list_addons ) ) {
103 $response = wp_remote_get(
104 $this->url_list_addons,
105 array(
106 'timeout' => 30,
107 )
108 );
109
110 if ( ! is_wp_error( $response )
111 && 200 === wp_remote_retrieve_response_code( $response ) ) {
112 $data = LP_Helper::json_decode( wp_remote_retrieve_body( $response ) );
113 $lp_cache->set_cache( $key_cache, $data, 5 * MINUTE_IN_SECONDS );
114 } else {
115 throw new Exception( $response->get_error_message() );
116 }
117 }
118 } catch ( Throwable $e ) {
119 LP_Debug::error_log( $e );
120 }
121
122 return $data;
123 }
124
125 /**
126 * Get list addons from file local - for test.
127 *
128 * @return object
129 * @throws Exception When the local data file cannot be read.
130 */
131 public function get_local_data(): object {
132 $data = new stdClass();
133 $file = LP_PLUGIN_PATH . 'config/addons-data.json';
134
135 if ( ! file_exists( $file ) || ! is_readable( $file ) ) {
136 return $data;
137 }
138
139 $content = file_get_contents( $file );
140 if ( false === $content ) {
141 return $data;
142 }
143
144 return LP_Helper::json_decode( $content );
145 }
146
147 /**
148 * Get list addons with purchase info attached.
149 *
150 * @return object List addons (stdClass map keyed by slug).
151 * @throws Exception
152 */
153 public function get_addons(): object {
154 //$addons = $this->get_local_data();
155 $addons = $this->get_remote_data();
156
157 $addons = $this->get_addons_purchased( $addons );
158
159 return apply_filters( 'learn-press/data/addons', $addons );
160 }
161
162 /**
163 * Get list addons purchased and attach purchase_info to each addon.
164 *
165 * @param object $addons List addons keyed by slug.
166 *
167 * @return object
168 * @throws Exception
169 */
170 public function get_addons_purchased( object $addons ): object {
171 $addons_purchase = LP_Settings::get_option( $this->key_purchase_addons, [] );
172 if ( empty( $addons_purchase ) ) {
173 return $addons;
174 }
175
176 $lp_cache = new LP_Cache( true );
177 $key_cache = 'addons_purchased_info';
178 $addons_cache = $lp_cache->get_cache( $key_cache );
179 if ( false !== $addons_cache ) {
180 return json_decode( wp_json_encode( $addons_cache ) );
181 }
182
183 $args = [
184 'method' => 'POST',
185 'body' => [
186 'addons_purchase' => $addons_purchase,
187 ],
188 'timeout' => 30,
189 'user-agent' => site_url(),
190 ];
191
192 $result = wp_remote_post( $this->link_addons_purchased, $args );
193 if ( is_wp_error( $result ) ) {
194 throw new Exception( $result->get_error_message() );
195 }
196
197 $data_str = wp_remote_retrieve_body( $result );
198 if ( preg_match( '/^Error.*/', $data_str ) ) {
199 throw new Exception( $data_str );
200 }
201
202 $data = LP_Helper::json_decode( $data_str );
203
204 foreach ( $addons as $key => $addon ) {
205 if ( isset( $data->{$key} ) ) {
206 $addons->{$key}->purchase_info = $data->{$key};
207 }
208 }
209
210 $lp_cache->set_cache( $key_cache, $addons );
211
212 return $addons;
213 }
214
215 /**
216 * Validate a purchase code with the add-ons service before storing it.
217 *
218 * @param string $addon_slug Add-on slug.
219 * @param string $purchase_code Purchase code.
220 *
221 * @return object Purchase information returned by the service.
222 * @throws Exception When the code cannot be validated.
223 */
224 public function validate_and_save_purchase_code( string $addon_slug, string $purchase_code ): object {
225 $purchase_code = trim( $purchase_code );
226
227 if ( empty( $addon_slug ) || empty( $purchase_code ) ) {
228 throw new Exception( __( 'Purchase code is invalid!', 'learnpress' ) );
229 }
230
231 $response = wp_remote_post(
232 $this->link_addons_purchased,
233 array(
234 'method' => 'POST',
235 'body' => array(
236 'addons_purchase' => array(
237 $addon_slug => $purchase_code,
238 ),
239 ),
240 'timeout' => 30,
241 'user-agent' => site_url(),
242 )
243 );
244
245 if ( is_wp_error( $response ) ) {
246 throw new Exception( $response->get_error_message() );
247 }
248
249 $response_body = wp_remote_retrieve_body( $response );
250 if ( empty( $response_body ) || preg_match( '/^Error.*/', $response_body ) ) {
251 throw new Exception( __( 'Purchase code is invalid!', 'learnpress' ) );
252 }
253
254 $data = LP_Helper::json_decode( $response_body );
255 if ( ! is_object( $data ) || ! isset( $data->{$addon_slug} ) || ! is_object( $data->{$addon_slug} ) ) {
256 throw new Exception( __( 'Purchase code is invalid!', 'learnpress' ) );
257 }
258
259 $key_purchase = LP_Settings::get_option( $this->key_purchase_addons, array() );
260 $key_purchase[ $addon_slug ] = $purchase_code;
261 LP_Settings::update_option( $this->key_purchase_addons, $key_purchase );
262
263 $this->clear_addons_purchased_cache();
264
265 return $data->{$addon_slug};
266 }
267
268 /**
269 * Mask a purchase code with 16 asterisks, keeping its final four characters visible.
270 *
271 * @param string $purchase_code Purchase code.
272 *
273 * @return string
274 */
275 public static function mask_purchase_code( string $purchase_code ): string {
276 $purchase_code = trim( $purchase_code );
277
278 if ( empty( $purchase_code ) ) {
279 return '';
280 }
281
282 return str_repeat( '*', 16 ) . substr( $purchase_code, -4 );
283 }
284
285 /**
286 * Get the license status for an expiration date.
287 *
288 * @param string $date_expire Expiration date.
289 * @param string $current_date Current date in Y-m-d format.
290 *
291 * @return string
292 */
293 public static function get_license_status( string $date_expire, string $current_date = '' ): string {
294 if ( empty( $date_expire ) ) {
295 return 'active';
296 }
297
298 $current_date = $current_date ?: gmdate( 'Y-m-d' );
299
300 return strtotime( $date_expire ) <= strtotime( $current_date ) ? 'expired' : 'active';
301 }
302
303 /**
304 * Download addon from ThimPress.
305 *
306 * @param array $addon Addon data.
307 * @param string $purchase_code Purchase code.
308 *
309 * @return string Path to downloaded zip file.
310 * @throws Exception
311 */
312 public function download_from_thimpress( array $addon = [], string $purchase_code = '' ): string {
313 $lp_file_system = LP_WP_Filesystem::instance();
314 $link_download = $this->link_addon_action;
315 $args = [
316 'method' => 'POST',
317 'body' => [
318 'addon' => $addon['slug'],
319 'version' => 'lastest',
320 ],
321 'user-agent' => site_url(),
322 ];
323
324 // For addon must buy.
325 if ( 0 == $addon['is_free'] ) {
326 $key_purchase = LP_Settings::get_option( $this->key_purchase_addons, [] );
327 $key_purchase[ $addon['slug'] ] = $purchase_code;
328 LP_Settings::update_option( $this->key_purchase_addons, $key_purchase );
329
330 $args['body']['purchase_code'] = $purchase_code;
331 }
332
333 $result = wp_remote_post( $link_download, $args );
334 if ( is_wp_error( $result ) ) {
335 throw new Exception( $result->get_error_message() );
336 }
337
338 $data = wp_remote_retrieve_body( $result );
339 if ( preg_match( '/^Error.*/', $data ) ) {
340 throw new Exception( $data );
341 }
342
343 // Create file temp zip addon to install with.
344 $wp_upload_dir = wp_upload_dir( null, false );
345 $name = 'addon.zip';
346 $path_file = $wp_upload_dir['basedir'] . DIRECTORY_SEPARATOR . $name;
347 $lp_file_system->put_contents( $path_file, $data );
348
349 return $path_file;
350 }
351
352 /**
353 * Install plugin.
354 *
355 * @param array $addon Addon data.
356 * @param string $package The full local path or URI of the package.
357 *
358 * @return void
359 * @throws Exception
360 */
361 public function install( array $addon = [], string $package = '' ) {
362 $result_install = $this->plugin_upgrader->install( $package );
363 if ( is_wp_error( $result_install ) ) {
364 throw new Exception( $result_install->get_error_message() );
365 } elseif ( ! $result_install ) {
366 throw new Exception( __( 'Install failed!', 'learnpress' ) );
367 }
368
369 $result_active = activate_plugin( $addon['basename'] );
370 if ( is_wp_error( $result_active ) ) {
371 throw new Exception( $result_active->get_error_message() );
372 }
373
374 $this->clear_addons_purchased_cache();
375 }
376
377 /**
378 * Update plugin.
379 *
380 * @param array $addon Addon data.
381 * @param string $package The full local path or URI of the package.
382 *
383 * @throws Exception
384 */
385 public function update( array $addon = [], string $package = '' ) {
386 $is_activate = is_plugin_active( $addon['basename'] );
387 // Must call this function to upgrade success.
388 wp_update_plugins();
389
390 $args_upgrade = [
391 'package' => $package,
392 'destination' => WP_PLUGIN_DIR,
393 'clear_destination' => false,
394 'clear_working' => true,
395 'hook_extra' => [],
396 'abort_if_destination_exists' => false,
397 ];
398 $result = $this->plugin_upgrader->run( $args_upgrade );
399 if ( ! $result ) {
400 throw new Exception( __( 'Update failed!', 'learnpress' ) );
401 }
402
403 if ( $is_activate ) {
404 $this->activate( $addon );
405 }
406
407 $this->clear_addons_purchased_cache();
408 }
409
410 /**
411 * Activate plugin.
412 *
413 * @param array $addon Addon data.
414 *
415 * @return bool|int|true|\WP_Error
416 * @throws Exception
417 */
418 public function activate( array $addon = [] ) {
419 if ( isset( $addon['dependency'] ) ) {
420 foreach ( $addon['dependency'] as $addon_slug => $addon_label ) {
421 if ( ! is_plugin_active( $addon_slug ) ) {
422 throw new Exception( sprintf( 'Please activate "%s" plugin before activate this add-on', $addon_label ) );
423 }
424 }
425 }
426
427 $result_active = activate_plugin( $addon['basename'] ?? '' );
428 if ( is_wp_error( $result_active ) ) {
429 throw new Exception( $result_active->get_error_message() );
430 }
431
432 return $result_active;
433 }
434
435 /**
436 * Deactivate plugin.
437 *
438 * @param array $addon Addon data.
439 *
440 * @return void
441 */
442 public function deactivate( array $addon = [] ) {
443 deactivate_plugins( $addon['basename'] ?? '' );
444 }
445
446 /**
447 * Clear cache of addons purchased info.
448 *
449 * @return void
450 */
451 private function clear_addons_purchased_cache(): void {
452 $lp_cache = new LP_Cache( true );
453 $lp_cache->clear( 'addons_purchased_info' );
454 }
455
456 /**
457 * Active site if install plugin via upload zip has "purchase code".
458 *
459 * @param string $addon_slug Addon slug.
460 * @param string $purchase_code Purchase code.
461 *
462 * @return void
463 * @throws Exception
464 */
465 public function active_site( string $addon_slug, string $purchase_code ) {
466 $args = [
467 'method' => 'POST',
468 'body' => [
469 'addon' => $addon_slug,
470 'purchase_code' => $purchase_code,
471 ],
472 'user-agent' => site_url(),
473 ];
474
475 $result = wp_remote_post( $this->link_active_site, $args );
476 if ( is_wp_error( $result ) ) {
477 throw new Exception( $result->get_error_message() );
478 }
479
480 $data = wp_remote_retrieve_body( $result );
481 if ( preg_match( '/^Error.*/', $data ) ) {
482 throw new Exception( $data );
483 }
484
485 // Save keys purchase code of addons to table WP Options.
486 $key_purchases = LP_Settings::get_option( $this->key_purchase_addons, [] );
487 $key_purchases[ $addon_slug ] = $purchase_code;
488 LP_Settings::update_option( $this->key_purchase_addons, $key_purchases );
489 }
490
491 /**
492 * Get list addon have new version.
493 *
494 * @return array
495 */
496 public function list_addon_new_version(): array {
497 $addons_new_version = [];
498
499 try {
500 $addons = $this->get_addons();
501 $plugins = get_plugins();
502
503 foreach ( $addons as $addon ) {
504 if ( isset( $plugins[ $addon->basename ] ) ) {
505 $version = $plugins[ $addon->basename ]['Version'];
506 if ( version_compare( $version, $addon->version, '<' ) ) {
507 $addons_new_version[] = $addon;
508 }
509 }
510 }
511 } catch ( Throwable $e ) {
512 LP_Debug::error_log( $e );
513 }
514
515 return $addons_new_version;
516 }
517
518 /**
519 * Check addons purchased need extend.
520 *
521 * @return bool
522 * @since 4.2.5.9
523 * @version 1.0.1
524 */
525 public function check_addons_purchased_need_extend(): bool {
526 try {
527 $addons = $this->get_addons();
528
529 foreach ( $addons as $addon ) {
530 if ( isset( $addon->purchase_info ) ) {
531 $addon_purchased = $addon->purchase_info;
532 $date_expired_str = $addon_purchased->date_expire ?? '';
533 if ( empty( $date_expired_str ) ) {
534 continue;
535 }
536
537 $date_expired = new DateTime( $date_expired_str );
538 $date_now = new DateTime( gmdate( LPDateTime::FORMAT_MYSQL ) );
539 $date_diff = date_diff( $date_now, $date_expired );
540 $number_days_remaining = $date_diff->days;
541 if ( $date_diff->invert ) {
542 $number_days_remaining = 0;
543 }
544
545 if ( $number_days_remaining <= 60 ) {
546 return true;
547 }
548 }
549 }
550 } catch ( Throwable $e ) {
551 LP_Debug::var_dump( $e );
552 }
553
554 return false;
555 }
556 }
557