PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.10
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.10
4.4.10 4.4.9.1 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 All 142 releases
learnpress / inc / Services / AddonService.php

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

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