PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.7.4
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.7.4
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
visualizer / vendor / codeinwp / themeisle-sdk / src / Modules / Licenser.php

Licenser.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.7.4, at vendor/codeinwp/themeisle-sdk/src/Modules/Licenser.php

1,050 lines 31.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The main loader class for license handling.
4 *
5 * @package ThemeIsleSDK
6 * @subpackage Modules
7 * @copyright Copyright (c) 2017, Marius Cristea
8 * @license http://opensource.org/licenses/gpl-3.0.php GNU Public License
9 * @since 1.0.0
10 */
11
12 namespace ThemeisleSDK\Modules;
13
14 // Exit if accessed directly.
15 use ThemeisleSDK\Common\Abstract_Module;
16 use ThemeisleSDK\Loader;
17 use ThemeisleSDK\Product;
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit;
21 }
22
23 /**
24 * Licenser module for ThemeIsle SDK.
25 */
26 class Licenser extends Abstract_Module {
27
28 /**
29 * Number of max failed checks before showing the license message.
30 *
31 * @var int $max_failed Maximum failed checks allowed before show the notice
32 */
33 private static $max_failed = 2;
34 /**
35 * License key string.
36 *
37 * @var string $license_key The license key string
38 */
39 public $license_key;
40 /**
41 * This ensures that the custom API request only runs on the second time that WP fires the update check.
42 *
43 * @var bool $do_check Flag for request.
44 */
45 private $do_check = false;
46 /**
47 * Number of failed checks to the api endpoint.
48 *
49 * @var bool $failed_checks
50 */
51 private $failed_checks = 0;
52 /**
53 * The product update response key.
54 *
55 * @var string $product_key Product key.
56 */
57 private $product_key;
58
59 /**
60 * Holds local license object.
61 *
62 * @var null Local license object.
63 */
64 private $license_local = null;
65 /**
66 * Product namespace, used for fixed name filters/cli commands.
67 *
68 * @var string $namespace Product namespace.
69 */
70 private $namespace = null;
71
72 /**
73 * Disable wporg updates for premium products.
74 *
75 * @param string $r Update payload.
76 * @param string $url The api url.
77 *
78 * @return mixed List of themes to check for update.
79 */
80 public function disable_wporg_update( $r, $url ) {
81
82 if ( 0 !== strpos( $url, 'https://api.wordpress.org/themes/update-check/' ) ) {
83 return $r;
84 }
85
86 // Decode the JSON response.
87 $themes = json_decode( $r['body']['themes'] );
88
89 unset( $themes->themes->{$this->product->get_slug()} );
90
91 // Encode the updated JSON response.
92 $r['body']['themes'] = wp_json_encode( $themes );
93
94 return $r;
95 }
96
97 /**
98 * Register the setting for the license of the product.
99 *
100 * @return bool
101 */
102 public function register_settings() {
103 if ( ! is_admin() ) {
104 return false;
105 }
106 if ( apply_filters( $this->product->get_key() . '_hide_license_field', false ) ) {
107 return;
108 }
109 add_settings_field(
110 $this->product->get_key() . '_license',
111 $this->product->get_name() . ' license',
112 array( $this, 'license_view' ),
113 'general'
114 );
115 }
116
117 /**
118 * The license view field.
119 */
120 public function license_view() {
121 $status = $this->get_license_status();
122 $value = $this->license_key;
123
124 $activate_string = apply_filters( $this->product->get_key() . '_lc_activate_string', 'Activate' );
125 $deactivate_string = apply_filters( $this->product->get_key() . '_lc_deactivate_string', 'Deactivate' );
126 $valid_string = apply_filters( $this->product->get_key() . '_lc_valid_string', 'Valid' );
127 $invalid_string = apply_filters( $this->product->get_key() . '_lc_invalid_string', 'Invalid' );
128 $license_message = apply_filters( $this->product->get_key() . '_lc_license_message', 'Enter your license from %s purchase history in order to get %s updates' );
129 $error_message = $this->get_error();
130 ?>
131 <style type="text/css">
132 input.themeisle-sdk-text-input-valid {
133 border: 1px solid #7ad03a;
134 }
135
136 input.themeisle-sdk-license-input {
137 width: 300px;
138 padding: 0 8px;
139 line-height: 2;
140 min-height: 30px;
141 }
142
143 .themeisle-sdk-license-deactivate-cta {
144 color: #fff;
145 background: #7ad03a;
146 display: inline-block;
147 text-decoration: none;
148 font-size: 13px;
149 line-height: 30px;
150 height: 26px;
151 margin-left: 5px;
152 padding: 0 10px 3px;
153 -webkit-border-radius: 3px;
154 border-radius: 3px;
155 }
156
157 .themeisle-sdk-license-activate-cta {
158 color: #fff;
159 background: #dd3d36;
160 display: inline-block;
161 text-decoration: none;
162 font-size: 13px;
163 line-height: 30px;
164 height: 26px;
165 margin-left: 5px;
166 padding: 0 10px 3px;
167 -webkit-border-radius: 3px;
168 border-radius: 3px;
169 }
170
171 button.button.themeisle-sdk-licenser-button-cta {
172 line-height: 26px;
173 height: 29px;
174 vertical-align: top;
175 }
176
177 </style>
178 <?php
179 echo sprintf(
180 '<p>%s<input class="themeisle-sdk-license-input %s" type="text" id="%s_license" name="%s_license" value="%s" /><a class="%s">%s</a>&nbsp;&nbsp;&nbsp;<button name="%s_btn_trigger" class="button button-primary themeisle-sdk-licenser-button-cta" value="yes" type="submit" >%s</button></p><p class="description">%s</p>%s',
181 ( ( 'valid' === $status ) ? sprintf( '<input type="hidden" value="%s" name="%s_license" />', esc_attr( $value ), esc_attr( $this->product->get_key() ) ) : '' ),
182 ( ( 'valid' === $status ) ? 'themeisle-sdk-text-input-valid' : '' ),
183 esc_attr( $this->product->get_key() ),
184 esc_attr( ( ( 'valid' === $status ) ? $this->product->get_key() . '_mask' : $this->product->get_key() ) ),
185 esc_attr( ( ( 'valid' === $status ) ? ( str_repeat( '*', 30 ) . substr( $value, - 5 ) ) : $value ) ),
186 esc_attr( ( 'valid' === $status ? 'themeisle-sdk-license-deactivate-cta' : 'themeisle-sdk-license-activate-cta' ) ),
187 esc_attr( 'valid' === $status ? $valid_string : $invalid_string ),
188 esc_attr( $this->product->get_key() ),
189 esc_attr( 'valid' === $status ? $deactivate_string : $activate_string ),
190 sprintf( wp_kses_data( $license_message ), '<a href="' . esc_url( $this->get_api_url() ) . '">' . esc_attr( $this->get_distributor_name() ) . '</a> ', esc_attr( $this->product->get_type() ) ),
191 wp_kses_data( empty( $error_message ) ? '' : sprintf( '<p style="color:#dd3d36">%s</p>', ( $error_message ) ) )
192 ) . wp_nonce_field( $this->product->get_key() . 'nonce', $this->product->get_key() . 'nonce_field', false, false );//phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
193
194 }
195
196 /**
197 * Return the license status.
198 *
199 * @param bool $check_expiration Should check if license is valid, but expired.
200 *
201 * @return string The License status.
202 */
203 public function get_license_status( $check_expiration = false ) {
204
205 $license_data = get_option( $this->product->get_key() . '_license_data', '' );
206
207 if ( '' === $license_data ) {
208 return get_option( $this->product->get_key() . '_license_status', 'not_active' );
209 }
210 $status = isset( $license_data->license ) ? $license_data->license : get_option( $this->product->get_key() . '_license_status', 'not_active' );
211 if ( false === $check_expiration ) {
212 return $status;
213 }
214
215 return ( 'valid' === $status && isset( $license_data->is_expired ) && 'yes' === $license_data->is_expired ) ? 'active_expired' : $status;
216 }
217
218 /**
219 * Return the last error message.
220 *
221 * @return mixed Error message.
222 */
223 public function get_error() {
224 return get_transient( $this->product->get_key() . 'act_err' );
225 }
226
227 /**
228 * Get remote api url.
229 *
230 * @return string Remote api url.
231 */
232 public function get_api_url() {
233 if ( $this->is_from_partner( $this->product ) ) {
234 return 'https://themeisle.com';
235 }
236
237 return $this->product->get_store_url();
238 }
239
240 /**
241 * Get remote api url.
242 *
243 * @return string Remote api url.
244 */
245 public function get_distributor_name() {
246 if ( $this->is_from_partner( $this->product ) ) {
247 return 'Themeisle';
248 }
249
250 return $this->product->get_store_name();
251 }
252
253 /**
254 * License price id.
255 *
256 * @return int License plan.
257 */
258 public function get_plan() {
259 $license_data = get_option( $this->product->get_key() . '_license_data', '' );
260 if ( ! isset( $license_data->price_id ) ) {
261 return - 1;
262 }
263
264 return (int) $license_data->price_id;
265 }
266
267 /**
268 * Show the admin notice regarding the license status.
269 *
270 * @return bool Should we show the notice ?
271 */
272 public function show_notice() {
273 if ( ! is_admin() ) {
274 return false;
275 }
276
277 if ( apply_filters( $this->product->get_key() . '_hide_license_notices', false ) ) {
278 return false;
279 }
280
281 $status = $this->get_license_status( true );
282 $no_activations_string = apply_filters( $this->product->get_key() . '_lc_no_activations_string', 'No more activations left for %s. You need to upgrade your plan in order to use %s on more websites. If you need assistance, please get in touch with %s staff.' );
283 $no_valid_string = apply_filters( $this->product->get_key() . '_lc_no_valid_string', 'In order to benefit from updates and support for %s, please add your license code from your <a href="%s" target="_blank">purchase history</a> and validate it <a href="%s">here</a>. ' );
284 $expired_license_string = apply_filters( $this->product->get_key() . '_lc_expired_string', 'Your %s\'s License Key has expired. In order to continue receiving support and software updates you must <a href="%s" target="_blank">renew</a> your license key.' );
285 // No activations left for this license.
286 if ( 'valid' != $status && $this->check_activation() ) {
287 ?>
288 <div class="error">
289 <p><strong>
290 <?php
291 echo sprintf(
292 wp_kses_data( $no_activations_string ),
293 esc_attr( $this->product->get_name() ),
294 esc_attr( $this->product->get_name() ),
295 '<a href="' . esc_url( $this->get_api_url() ) . '" target="_blank">' . esc_attr( $this->get_distributor_name() ) . '</a>'
296 );
297 ?>
298 </strong>
299 </p>
300 </div>
301 <?php
302 return false;
303 }
304
305 // Invalid license key.
306 if ( 'active_expired' === $status ) {
307 ?>
308 <div class="error">
309 <p>
310 <strong><?php echo sprintf( wp_kses_data( $expired_license_string ), esc_attr( $this->product->get_name() . ' ' . $this->product->get_type() ), esc_url( $this->get_api_url() . '?license=' . $this->license_key ) ); ?> </strong>
311 </p>
312 </div>
313 <?php
314
315 return false;
316 }
317 // Invalid license key.
318 if ( 'valid' != $status ) {
319 ?>
320 <div class="error">
321 <p>
322 <strong><?php echo sprintf( wp_kses_data( $no_valid_string ), esc_attr( $this->product->get_name() . ' ' . $this->product->get_type() ), esc_url( $this->get_api_url() ), esc_url( admin_url( 'options-general.php' ) . '#' . $this->product->get_key() . '_license' ) ); ?> </strong>
323 </p>
324 </div>
325 <?php
326
327 return false;
328 }
329
330 return true;
331 }
332
333 /**
334 * Check if the license is active or not.
335 *
336 * @return bool
337 */
338 public function check_activation() {
339 $license_data = get_option( $this->product->get_key() . '_license_data', '' );
340 if ( '' === $license_data ) {
341 return false;
342 }
343
344 return isset( $license_data->license ) ? ( 'no_activations_left' == $license_data->license ) : false;
345
346 }
347
348 /**
349 * Check if the license is about to expire in the next month.
350 *
351 * @return bool
352 */
353 public function check_expiration() {
354 $license_data = get_option( $this->product->get_key() . '_license_data', '' );
355 if ( '' === $license_data ) {
356 return false;
357 }
358 if ( ! isset( $license_data->expires ) ) {
359 return false;
360 }
361 if ( strtotime( $license_data->expires ) - time() > 30 * 24 * 3600 ) {
362 return false;
363 }
364
365 return true;
366 }
367
368 /**
369 * Return the renew url from the store used.
370 *
371 * @return string The renew url.
372 */
373 public function renew_url() {
374 $license_data = get_option( $this->product->get_key() . '_license_data', '' );
375 if ( '' === $license_data ) {
376 return $this->get_api_url();
377 }
378 if ( ! isset( $license_data->download_id ) || ! isset( $license_data->key ) ) {
379 return $this->get_api_url();
380 }
381
382 return $this->get_api_url() . '/checkout/?edd_license_key=' . $license_data->key . '&download_id=' . $license_data->download_id;
383 }
384
385 /**
386 * Run the license check call.
387 */
388 public function product_valid() {
389 if ( false !== ( $license = get_transient( $this->product->get_key() . '_license_data' ) ) ) { //phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure
390 return;
391 }
392 $license = $this->check_license();
393 set_transient( $this->product->get_key() . '_license_data', $license, 12 * HOUR_IN_SECONDS );
394 update_option( $this->product->get_key() . '_license_data', $license );
395 }
396
397 /**
398 * Check the license status.
399 *
400 * @return object The license data.
401 */
402 public function check_license() {
403 $status = $this->get_license_status();
404 if ( 'not_active' === $status ) {
405 $license_data = new \stdClass();
406 $license_data->license = 'not_active';
407
408 return $license_data;
409 }
410 $license = trim( $this->license_key );
411
412 $response = $this->do_license_process( $license, 'check' );
413
414 if ( is_wp_error( $response ) ) {
415 $license_data = new \stdClass();
416 $license_data->license = 'invalid';
417 } else {
418 $license_data = $response;
419 }
420
421 $license_old = get_option( $this->product->get_key() . '_license_data', '' );
422 if ( 'valid' === $license_old->license && ( $license_data->license !== $license_old->license ) && $this->failed_checks <= self::$max_failed ) {
423 $this->increment_failed_checks();
424
425 return $license_old;
426 }
427
428 if ( ! isset( $license_data->key ) ) {
429 $license_data->key = isset( $license_old->key ) ? $license_old->key : '';
430 }
431 $this->reset_failed_checks();
432
433 return $license_data;
434
435 }
436
437 /**
438 * Do license activation/deactivation.
439 *
440 * @param string $license License key.
441 * @param string $action What do to.
442 *
443 * @return bool|\WP_Error
444 */
445 public function do_license_process( $license, $action = 'toggle' ) {
446 if ( strlen( $license ) < 10 ) {
447 return new \WP_Error( 'themeisle-license-invalid-format', 'Invalid license.' );
448 }
449 $status = $this->get_license_status();
450
451 if ( 'valid' === $status && 'activate' === $action ) {
452 return new \WP_Error( 'themeisle-license-already-active', 'License is already active.' );
453 }
454 if ( 'valid' !== $status && 'deactivate' === $action ) {
455 return new \WP_Error( 'themeisle-license-already-deactivate', 'License not active.' );
456 }
457
458 if ( 'toggle' === $action ) {
459 $action = ( 'valid' !== $status ? ( 'activate' ) : ( 'deactivate' ) );
460 }
461
462 // Call the custom API.
463 if ( 'check' === $action ) {
464 $response = $this->safe_get( sprintf( '%slicense/check/%s/%s/%s/%s', Product::API_URL, rawurlencode( $this->product->get_name() ), $license, rawurlencode( home_url() ), Loader::get_cache_token() ) );
465 } else {
466 $response = wp_remote_post(
467 sprintf( '%slicense/%s/%s/%s', Product::API_URL, $action, rawurlencode( $this->product->get_name() ), $license ),
468 array(
469 'body' => wp_json_encode(
470 array(
471 'url' => rawurlencode( home_url() ),
472 )
473 ),
474 'headers' => array(
475 'Content-Type' => 'application/json',
476 ),
477 )
478 );
479 }
480
481 // make sure the response came back okay.
482 if ( is_wp_error( $response ) ) {
483 return new \WP_Error( 'themeisle-license-500', sprintf( 'ERROR: Failed to connect to the license service. Please try again later. Reason: %s', $response->get_error_message() ) );
484 }
485
486 $license_data = json_decode( wp_remote_retrieve_body( $response ) );
487
488 if ( ! is_object( $license_data ) ) {
489 return new \WP_Error( 'themeisle-license-404', 'ERROR: Failed to validate license. Please try again in one minute.' );
490 }
491 if ( 'check' === $action ) {
492 return $license_data;
493 }
494
495 Loader::clear_cache_token();
496
497 if ( ! isset( $license_data->license ) ) {
498 $license_data->license = 'invalid';
499 }
500
501 if ( ! isset( $license_data->key ) ) {
502 $license_data->key = $license;
503 }
504 if ( 'valid' === $license_data->license ) {
505 $this->reset_failed_checks();
506 }
507
508 if ( 'deactivate' === $action ) {
509
510 delete_option( $this->product->get_key() . '_license_data' );
511 delete_option( $this->product->get_key() . '_license_plan' );
512 delete_transient( $this->product->get_key() . '_license_data' );
513
514 return true;
515 }
516 if ( isset( $license_data->plan ) ) {
517 update_option( $this->product->get_key() . '_license_plan', $license_data->plan );
518 }
519 update_option( $this->product->get_key() . '_license_data', $license_data );
520 set_transient( $this->product->get_key() . '_license_data', $license_data, 12 * HOUR_IN_SECONDS );
521 if ( 'activate' === $action && 'valid' !== $license_data->license ) {
522 return new \WP_Error( 'themeisle-license-invalid', 'ERROR: Invalid license provided.' );
523 }
524
525 return true;
526 }
527
528 /**
529 * Reset the failed checks
530 */
531 private function reset_failed_checks() {
532 $this->failed_checks = 1;
533 update_option( $this->product->get_key() . '_failed_checks', $this->failed_checks );
534 }
535
536 /**
537 * Increment the failed checks.
538 */
539 private function increment_failed_checks() {
540 $this->failed_checks ++;
541 update_option( $this->product->get_key() . '_failed_checks', $this->failed_checks );
542 }
543
544 /**
545 * Activate the license remotely.
546 */
547 public function process_license() {
548 // listen for our activate button to be clicked.
549 if ( ! isset( $_POST[ $this->product->get_key() . '_btn_trigger' ] ) ) {
550 return;
551 }
552 if ( ! isset( $_POST[ $this->product->get_key() . 'nonce_field' ] )
553 || ! wp_verify_nonce( $_POST[ $this->product->get_key() . 'nonce_field' ], $this->product->get_key() . 'nonce' ) //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
554 ) {
555 return;
556 }
557 if ( ! current_user_can( 'manage_options' ) ) {
558 return;
559 }
560 $license = isset( $_POST[ $this->product->get_key() . '_license' ] )
561 ? sanitize_text_field( $_POST[ $this->product->get_key() . '_license' ] )
562 : '';
563
564 $response = $this->do_license_process( $license, 'toggle' );
565 if ( is_wp_error( $response ) ) {
566 $this->set_error( $response->get_error_message() );
567
568 return;
569 }
570 if ( true === $response ) {
571 $this->set_error( '' );
572 }
573 }
574
575 /**
576 * Set license validation error message.
577 *
578 * @param string $message Error message.
579 */
580 public function set_error( $message = '' ) {
581 set_transient( $this->product->get_key() . 'act_err', $message, MINUTE_IN_SECONDS );
582
583 }
584
585 /**
586 * Load the Themes screen.
587 */
588 public function load_themes_screen() {
589 add_thickbox();
590 add_action( 'admin_notices', array( &$this, 'update_nag' ) );
591 }
592
593 /**
594 * Alter the nag for themes update.
595 */
596 public function update_nag() {
597 $theme = wp_get_theme( $this->product->get_slug() );
598 $api_response = get_transient( $this->product_key );
599 if ( false === $api_response || ! isset( $api_response->new_version ) ) {
600 return;
601 }
602 $update_url = wp_nonce_url( 'update.php?action=upgrade-theme&amp;theme=' . urlencode( $this->product->get_slug() ), 'upgrade-theme_' . $this->product->get_slug() );
603 $update_message = apply_filters( 'themeisle_sdk_license_update_message', 'Updating this theme will lose any customizations you have made. Cancel to stop, OK to update.' );
604 $update_onclick = ' onclick="if ( confirm(\'' . esc_js( $update_message ) . '\') ) {return true;}return false;"';
605 if ( version_compare( $this->product->get_version(), $api_response->new_version, '<' ) ) {
606 echo '<div id="update-nag">';
607 printf(
608 '<strong>%1$s %2$s</strong> is available. <a href="%3$s" class="thickbox" title="%4s">Check out what\'s new</a> or <a href="%5$s"%6$s>update now</a>.',
609 esc_attr( $theme->get( 'Name' ) ),
610 esc_attr( $api_response->new_version ),
611 esc_url( sprintf( '%s&TB_iframe=true&amp;width=1024&amp;height=800', $this->product->get_changelog() ) ),
612 esc_attr( $theme->get( 'Name' ) ),
613 esc_url( $update_url ),
614 $update_onclick // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped, Already escaped.
615 );
616 echo '</div>';
617 echo '<div id="' . esc_attr( $this->product->get_slug() ) . '_changelog" style="display:none;">';
618 echo wp_kses_data( wpautop( $api_response->sections['changelog'] ) );
619 echo '</div>';
620 }
621 }
622
623 /**
624 * Alter update transient.
625 *
626 * @param mixed $value The transient data.
627 *
628 * @return mixed
629 */
630 public function theme_update_transient( $value ) {
631 $update_data = $this->check_for_update();
632 if ( empty( $value ) ) {
633 return $value;
634 }
635
636 if ( ! isset( $value->response ) ) {
637 return $value;
638 }
639
640 if ( ! $update_data ) {
641 return $value;
642 }
643
644 $value->response[ $this->product->get_slug() ] = $update_data;
645 return $value;
646 }
647
648 /**
649 * Check for updates
650 *
651 * @return array|bool Either the update data or false in case of failure.
652 */
653 public function check_for_update() {
654 $update_data = get_transient( $this->product_key );
655
656 if ( false === $update_data ) {
657 $failed = false;
658 $update_data = $this->get_version_data();
659 if ( empty( $update_data ) ) {
660 $failed = true;
661 }
662 // If the response failed, try again in 30 minutes.
663 if ( $failed ) {
664 $data = new \stdClass();
665 $data->new_version = $this->product->get_version();
666 set_transient( $this->product_key, $data, 30 * MINUTE_IN_SECONDS );
667
668 return false;
669 }
670 $update_data->sections = isset( $update_data->sections ) ? maybe_unserialize( $update_data->sections ) : null;
671
672 set_transient( $this->product_key, $update_data, 12 * HOUR_IN_SECONDS );
673 }
674 if ( ! isset( $update_data->new_version ) ) {
675 return false;
676 }
677 if ( version_compare( $this->product->get_version(), $update_data->new_version, '>=' ) ) {
678 return false;
679 }
680
681 return (array) $update_data;
682 }
683
684 /**
685 * Check remote api for latest version.
686 *
687 * @return bool|mixed Update api response.
688 */
689 private function get_version_data() {
690
691 $response = $this->safe_get(
692 sprintf(
693 '%slicense/version/%s/%s/%s/%s',
694 Product::API_URL,
695 rawurlencode( $this->product->get_name() ),
696 ( empty( $this->license_key ) ? 'free' : $this->license_key ),
697 $this->product->get_version(),
698 rawurlencode( home_url() )
699 ),
700 array(
701 'timeout' => 15, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout, Inherited by wp_remote_get only, for vip environment we use defaults.
702 'sslverify' => false,
703 )
704 );
705 if ( is_wp_error( $response ) || 200 != wp_remote_retrieve_response_code( $response ) ) {
706 return false;
707 }
708 $update_data = json_decode( wp_remote_retrieve_body( $response ) );
709 if ( ! is_object( $update_data ) ) {
710 return false;
711 }
712 if ( isset( $update_data->slug ) ) {
713 $update_data->slug = $this->product->get_slug();
714 }
715 if ( isset( $update_data->icons ) ) {
716 $update_data->icons = (array) $update_data->icons;
717 }
718 if ( isset( $update_data->banners ) ) {
719 $update_data->banners = (array) $update_data->banners;
720 }
721
722 return $update_data;
723 }
724
725 /**
726 * Delete the update transient
727 */
728 public function delete_theme_update_transient() {
729 return delete_transient( $this->product_key );
730 }
731
732 /**
733 * Check for Updates at the defined API endpoint and modify the update array.
734 *
735 * @param array $_transient_data Update array build by WordPress.
736 *
737 * @return mixed Modified update array with custom plugin data.
738 */
739 public function pre_set_site_transient_update_plugins_filter( $_transient_data ) {
740 if ( empty( $_transient_data ) || ! $this->do_check ) {
741 $this->do_check = true;
742
743 return $_transient_data;
744 }
745 $api_response = $this->api_request();
746 if ( false !== $api_response && is_object( $api_response ) && isset( $api_response->new_version ) ) {
747 if ( ! isset( $api_response->plugin ) ) {
748 $api_response->plugin = $this->product->get_slug() . '/' . $this->product->get_file();
749 }
750 if ( version_compare( $this->product->get_version(), $api_response->new_version, '<' ) ) {
751 $_transient_data->response[ $this->product->get_slug() . '/' . $this->product->get_file() ] = $api_response;
752 } else {
753 $_transient_data->no_update[ $this->product->get_slug() . '/' . $this->product->get_file() ] = $api_response;
754 }
755 }
756
757 return $_transient_data;
758 }
759
760 /**
761 * Calls the API and, if successfull, returns the object delivered by the API.
762 *
763 * @param string $_action The requested action.
764 * @param array $_data Parameters for the API action.
765 *
766 * @return false||object
767 */
768 private function api_request( $_action = '', $_data = '' ) {
769 $update_data = $this->get_version_data();
770 if ( empty( $update_data ) ) {
771 return false;
772 }
773 if ( $update_data && isset( $update_data->sections ) ) {
774 $update_data->sections = maybe_unserialize( $update_data->sections );
775 }
776
777 return $update_data;
778 }
779
780 /**
781 * Updates information on the "View version x.x details" page with custom data.
782 *
783 * @param mixed $_data Plugin data.
784 * @param string $_action Action to send.
785 * @param object $_args Arguments to use.
786 *
787 * @return object $_data
788 */
789 public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
790 if ( ( 'plugin_information' !== $_action ) || ! isset( $_args->slug ) || ( $_args->slug !== $this->product->get_slug() ) ) {
791 return $_data;
792 }
793 $api_response = $this->api_request();
794 if ( false !== $api_response ) {
795 $_data = $api_response;
796 }
797
798 return $_data;
799 }
800
801 /**
802 * Disable SSL verification in order to prevent download update failures.
803 *
804 * @param array $args Http args.
805 * @param string $url Url to check.
806 *
807 * @return array $array
808 */
809 public function http_request_args( $args, $url ) {
810 // If it is an https request and we are performing a package download, disable ssl verification.
811 if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
812 $args['sslverify'] = false;
813 }
814
815 return $args;
816 }
817
818 /**
819 * Check if we should load the module for this product.
820 *
821 * @param Product $product Product data.
822 *
823 * @return bool Should we load the module?
824 */
825 public function can_load( $product ) {
826
827 if ( $product->is_wordpress_available() ) {
828 return false;
829 }
830
831 return ( apply_filters( $product->get_key() . '_enable_licenser', true ) === true );
832
833 }
834
835 /**
836 * Load module logic.
837 *
838 * @param Product $product Product to load the module for.
839 *
840 * @return Licenser Module object.
841 */
842 public function load( $product ) {
843 $this->product = $product;
844
845 $this->product_key = $this->product->get_key() . '-update-response';
846
847 $this->license_key = $this->product->get_license();
848 if ( $this->product->requires_license() ) {
849 $this->failed_checks = intval( get_option( $this->product->get_key() . '_failed_checks', 0 ) );
850 $this->register_license_hooks();
851 }
852
853 $namespace = apply_filters( 'themesle_sdk_namespace_' . md5( $product->get_basefile() ), false );
854
855 if ( false !== $namespace ) {
856 $this->namespace = $namespace;
857 add_filter( 'themeisle_sdk_license_process_' . $namespace, [ $this, 'do_license_process' ], 10, 2 );
858 add_filter( 'product_' . $namespace . '_license_status', [ $this, 'get_license_status' ], PHP_INT_MAX );
859 add_filter( 'product_' . $namespace . '_license_key', [ $this->product, 'get_license' ] );
860 add_filter( 'product_' . $namespace . '_license_plan', [ $this, 'get_plan' ], PHP_INT_MAX );
861 if ( defined( 'WP_CLI' ) && WP_CLI ) {
862 \WP_CLI::add_command( $namespace . ' activate', [ $this, 'cli_activate' ] );
863 \WP_CLI::add_command( $namespace . ' deactivate', [ $this, 'cli_deactivate' ] );
864 \WP_CLI::add_command( $namespace . ' is-active', [ $this, 'cli_is_active' ] );
865 }
866 }
867
868 add_action( 'admin_head', [ $this, 'auto_activate' ] );
869 if ( $this->product->is_plugin() ) {
870 add_filter(
871 'pre_set_site_transient_update_plugins',
872 [
873 $this,
874 'pre_set_site_transient_update_plugins_filter',
875 ]
876 );
877 add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
878 add_filter( 'http_request_args', array( $this, 'http_request_args' ), 10, 2 ); //phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args
879
880 return $this;
881 }
882 if ( $this->product->is_theme() ) {
883 add_filter( 'site_transient_update_themes', array( &$this, 'theme_update_transient' ) );
884 add_action( 'delete_site_transient_update_themes', array( &$this, 'delete_theme_update_transient' ) );
885 add_action( 'load-update-core.php', array( &$this, 'delete_theme_update_transient' ) );
886 add_action( 'load-themes.php', array( &$this, 'delete_theme_update_transient' ) );
887 add_action( 'load-themes.php', array( &$this, 'load_themes_screen' ) );
888 add_filter( 'http_request_args', array( $this, 'disable_wporg_update' ), 5, 2 ); //phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args
889
890 return $this;
891
892 }
893
894 return $this;
895 }
896
897 /**
898 * Register license fields for the products.
899 */
900 public function register_license_hooks() {
901 add_action( 'admin_init', array( $this, 'register_settings' ) );
902 add_action( 'admin_init', array( $this, 'process_license' ) );
903 add_action( 'admin_init', array( $this, 'product_valid' ), 99999999 );
904 add_action( 'admin_notices', array( $this, 'show_notice' ) );
905 add_filter( $this->product->get_key() . '_license_status', array( $this, 'get_license_status' ) );
906 }
907
908 /**
909 * Check license on filesystem.
910 *
911 * @return mixed License key.
912 */
913 public function get_file_license() {
914
915 $license_file = dirname( $this->product->get_basefile() ) . '/license.json';
916
917 global $wp_filesystem;
918 if ( ! is_file( $license_file ) ) {
919 return false;
920 }
921
922 require_once ABSPATH . '/wp-admin/includes/file.php';
923 \WP_Filesystem();
924 $content = json_decode( $wp_filesystem->get_contents( $license_file ) );
925 if ( ! is_object( $content ) ) {
926 return false;
927 }
928 if ( ! isset( $content->key ) ) {
929 return false;
930 }
931 return $content->key;
932 }
933 /**
934 * Run license activation on plugin activate.
935 */
936 public function auto_activate() {
937 $status = $this->get_license_status();
938 if ( 'not_active' !== $status ) {
939 return false;
940 }
941
942 if ( ! empty( $this->namespace ) ) {
943 $license_key = apply_filters( 'product_' . $this->namespace . '_license_key_constant', '' );
944 }
945
946 if ( empty( $license_key ) ) {
947 $license_key = $this->get_file_license();
948 }
949 if ( empty( $license_key ) ) {
950 return;
951 }
952
953
954 $this->license_local = $license_key;
955 $lock_key = $this->product->get_key() . '_autoactivated';
956
957 if ( 'yes' === get_option( $lock_key, '' ) ) {
958 return;
959 }
960 if ( 'yes' === get_transient( $lock_key ) ) {
961 return;
962 }
963 $response = $this->do_license_process( $license_key, 'activate' );
964
965 set_transient( $lock_key, 'yes', 6 * HOUR_IN_SECONDS );
966
967 if ( apply_filters( $this->product->get_key() . '_hide_license_notices', false ) ) {
968 return;
969 }
970
971 if ( true === $response ) {
972 add_action( 'admin_notices', [ $this, 'autoactivate_notice' ] );
973 }
974 }
975
976 /**
977 * Show auto-activate notice.
978 */
979 public function autoactivate_notice() {
980 ?>
981 <div class="notice notice-success is-dismissible">
982 <p><?php echo sprintf( '<strong>%s</strong> has been successfully activated using <strong>%s</strong> license !', esc_attr( $this->product->get_name() ), esc_attr( str_repeat( '*', 20 ) . substr( $this->license_local, - 10 ) ) ); ?></p>
983 </div>
984 <?php
985 }
986
987 /**
988 * Activate product license on this site.
989 *
990 * ## OPTIONS
991 *
992 * @param array $args Command args.
993 *
994 * [<license-key>]
995 * : Product license key.
996 */
997 public function cli_activate( $args ) {
998 $license_key = isset( $args[0] ) ? trim( $args[0] ) : '';
999 $response = $this->do_license_process( $license_key, 'activate' );
1000 if ( true !== $response ) {
1001 \WP_CLI::error( $response->get_error_message() );
1002
1003 return;
1004 }
1005
1006 \WP_CLI::success( 'Product successfully activated.' );
1007 }
1008
1009 /**
1010 * Deactivate product license on this site.
1011 *
1012 * @param array $args Command args.
1013 *
1014 * ## OPTIONS
1015 *
1016 * [<license-key>]
1017 * : Product license key.
1018 */
1019 public function cli_deactivate( $args ) {
1020 $license_key = isset( $args[0] ) ? trim( $args[0] ) : '';
1021 $response = $this->do_license_process( $license_key, 'deactivate' );
1022 if ( true !== $response ) {
1023 \WP_CLI::error( $response->get_error_message() );
1024
1025 return;
1026 }
1027
1028 \WP_CLI::success( 'Product successfully deactivated.' );
1029 }
1030
1031 /**
1032 * Checks if product has license activated.
1033 *
1034 * @param array $args Command args.
1035 *
1036 * @subcommand is-active
1037 */
1038 public function cli_is_active( $args ) {
1039
1040 $status = $this->get_license_status();
1041 if ( 'valid' === $status ) {
1042 \WP_CLI::halt( 0 );
1043
1044 return;
1045 }
1046
1047 \WP_CLI::halt( 1 );
1048 }
1049 }
1050