PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
charitable / vendor / wpcharitable / charitable-licenses / src / CharitableLicenses.php

CharitableLicenses.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.13, at vendor/wpcharitable/charitable-licenses/src/CharitableLicenses.php

1,321 lines 36.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace CharitableLicenses;
4
5 /**
6 * Class CharitableLicenses
7 */
8 class CharitableLicenses {
9
10 /* @var string */
11 const UPDATE_URL = 'https://wpcharitable.com';
12
13 /**
14 * The single instance of this class.
15 *
16 * @var CharitableLicenses|null
17 */
18 private static $instance = null;
19
20 /**
21 * All the registered products requiring licensing.
22 *
23 * @var array
24 */
25 private $products;
26
27 /**
28 * All the stored licenses.
29 *
30 * @var array
31 */
32 private $licenses;
33
34 /**
35 * The key for the failed request cache.
36 *
37 * @var string
38 */
39 private $failed_request_cache_key = 'wpc_edd_sl_failed_plugin_versions';
40
41 /**
42 * Cached update data.
43 *
44 * @var array
45 */
46 private $update_data;
47
48 /**
49 * The pro plugin.
50 *
51 * @var string
52 */
53 const PRO_PLUGIN = 'charitable-pro/charitable-pro.php';
54
55 /**
56 * Private constructor to prevent multiple instances.
57 */
58 private function __construct() {
59
60 $this->products = array();
61 $this->update_data = array();
62
63 add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_for_updates' ) );
64 add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
65 add_action( 'charitable_deactivate_license', array( $this, 'deactivate_license' ) );
66
67 add_action( 'init', array( $this, 'ajax_license_check' ) );
68 add_action( 'wp_ajax_charitable_license_check', array( $this, 'ajax_license_check' ) );
69 add_action( 'wp_ajax_charitable_license_check', array( $this, 'ajax_license_deactivate' ) );
70 }
71
72 /**
73 * Returns and/or create the single instance of this class.
74 *
75 * @since 1.2.0
76 *
77 * @return Charitable_Licenses
78 */
79 public static function get_instance() {
80 if ( is_null( self::$instance ) ) {
81 self::$instance = new self();
82 }
83
84 return self::$instance;
85 }
86
87 /**
88 * Checks licenses (new and legacy) to see if any are valid.
89 *
90 * @since 1.7.0
91 *
92 * @return boolean
93 */
94 public function is_pro() {
95
96 if ( charitable_is_debug( 'vendor' ) ) {
97 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: is_pro()' );
98 }
99
100 $products = $this->get_products();
101
102 if ( is_multisite() ) {
103
104 // get the main blog id to check for license.
105 $network = get_network();
106 if ( ! $network ) {
107 return 0;
108 }
109
110 $blog_id = $network->site_id;
111
112 switch_to_blog( $blog_id );
113
114 if ( $this->is_v3_license_valid() ) {
115 restore_current_blog();
116 return true;
117 }
118
119 $licenses = charitable_get_option( 'licenses', array() );
120
121 // go through licenses - if there is at least ONE VALID ADDON LICNESE then it's pro.
122 foreach ( $licenses as $slug => $license ) {
123 if ( isset( $license['valid'] ) && 1 === intval( $license['valid'] ) ) {
124 restore_current_blog();
125 return true;
126 }
127 }
128
129 restore_current_blog();
130
131 return false;
132
133 } else {
134
135 if ( empty( $products ) ) {
136 return false;
137 }
138
139 if ( $this->is_v3_license_valid() ) {
140 return true;
141 }
142
143 // fallback: go through licenses - if there is at least ONE VALID ADDON LICNESE then it's pro.
144 foreach ( $products as $slug => $item ) {
145 $valid = $this->has_valid_license( $slug );
146 if ( $valid ) {
147 return true;
148 }
149 }
150 }
151
152 return false;
153 }
154
155 /**
156 * Checks the "v3" license (starting Nov 2022) to see if it's valid.
157 * If valid, normally doesn't require other license/pro checks.
158 *
159 * @since 1.7.0.4
160 *
161 * @return boolean
162 */
163 public function is_v3_license_valid() {
164
165 if ( charitable_is_debug( 'vendor' ) ) {
166 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: is_v3_license_valid()' );
167 }
168
169 $settings = get_option( 'charitable_settings' );
170 if ( false === $settings || ! isset( $settings['licenses']['charitable-v2'] ) || ! isset( $settings['licenses']['charitable-v2']['valid'] ) ) {
171 return false;
172 }
173 $valid = $settings['licenses']['charitable-v2']['valid'];
174 if ( 1 === intval( $valid ) ) {
175 return true;
176 }
177
178 return false;
179 }
180
181 /**
182 * Checks for any Charitable extensions with updates.
183 *
184 * @since 1.4.0
185 *
186 * @param array $_transient_data The plugin updates data.
187 * @return array
188 */
189 public function check_for_updates( $_transient_data ) {
190 global $pagenow;
191
192 if ( charitable_is_debug( 'vendor' ) ) {
193 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: check_for_updates()' );
194 }
195
196 if ( ! is_object( $_transient_data ) ) {
197 $_transient_data = new \stdClass();
198 }
199
200 if ( 'plugins.php' == $pagenow && is_multisite() ) {
201 return $_transient_data;
202 }
203
204 /* Loop over our licensed products and check whether any are missing transient data. */
205 $missing_data = array();
206
207 foreach ( $this->get_products() as $product ) {
208 if ( $this->is_missing_version_info( $product, $_transient_data ) ) {
209 $missing_data[] = $product;
210 }
211 }
212
213 /* If we are missing data for any of our products, check whether any have an update. */
214 if ( ! empty( $missing_data ) ) {
215
216 $versions = $this->get_versions();
217
218 unset( $versions['request_speed'] );
219
220 if ( ! empty( $versions ) ) {
221
222 $versions_name_lookup = wp_list_pluck( $versions, 'name' );
223
224 foreach ( $missing_data as $product ) {
225
226 if ( ! in_array( $product['name'], $versions_name_lookup ) ) {
227 continue;
228 }
229
230 $plugin_file = plugin_basename( $product['file'] );
231 $product_key = array_search( $product['name'], wp_list_pluck( $this->get_products(), 'name' ) );
232 $version_info = $versions[ array_search( $product['name'], $versions_name_lookup ) ];
233 $version_info['license'] = $this->get_license_details( $product_key );
234
235 if ( version_compare( $product['version'], $version_info['new_version'], '<' ) ) {
236
237 if ( isset( $version_info['sections'] ) ) {
238 $version_info['sections'] = maybe_unserialize( $version_info['sections'] );
239 }
240
241 $can_update = $this->able_to_update( $version_info );
242
243 if ( is_array( $can_update ) ) {
244 $version_info['package'] = $can_update['reason_code'];
245 $version_info['download_link'] = $can_update['reason_code'];
246 $version_info['package_download_restriction'] = $can_update['description'];
247 }
248
249 $_transient_data->response[ $plugin_file ] = (object) $version_info;
250 }
251
252 $_transient_data->last_checked = time();
253 $_transient_data->checked[ $plugin_file ] = $product['version'];
254
255 }//end foreach
256 }//end if
257 }//end if
258
259 return $_transient_data;
260 }
261
262 /**
263 * Updates information on the "View version x.x details" page with custom data.
264 *
265 * @uses api_request()
266 *
267 * @since 1.4.20
268 *
269 * @param mixed $_data Default set of data.
270 * @param string $_action The current action.
271 * @param object $_args Request args.
272 * @return object $_data
273 */
274 public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
275
276 if ( charitable_is_debug( 'vendor' ) ) {
277 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: plugins_api_filter()' );
278 }
279
280 if ( 'plugin_information' != $_action ) {
281 return $_data;
282 }
283
284 if ( ! isset( $_args->slug ) ) {
285 return $_data;
286 }
287
288 $plugin_key = str_replace( '-', '_', $_args->slug );
289
290 if ( 'charitable' === $plugin_key || ! array_key_exists( $plugin_key, $this->products ) ) {
291 return $_data;
292 }
293
294 $version_info = $this->get_version_info( plugin_basename( $this->products[ $plugin_key ]['file'] ) );
295
296 if ( $version_info ) {
297 $_data = $version_info;
298 }
299
300 return $_data;
301 }
302
303 /**
304 * Return whether a particular plugin is missing version info.
305 *
306 * @since 1.4.20
307 *
308 * @param array $product Product details array.
309 * @param false|object $update_cache Optional argument to pass update cache.
310 * @return boolean
311 */
312 public function is_missing_version_info( $product, $update_cache = false ) {
313
314 if ( charitable_is_debug( 'vendor' ) ) {
315 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: is_missing_version_info()' );
316 }
317
318 return ! $this->get_version_info( plugin_basename( $product['file'] ), $update_cache );
319 }
320
321 /**
322 * Return the version update info for a particular plugin.
323 *
324 * @since 1.4.20
325 *
326 * @param string $slug The plugin slug.
327 * @param false|object $update_cache Optional argument to pass update cache.
328 * @return array|false Array if an update is available. False otherwise.
329 */
330 public function get_version_info( $slug, $update_cache = false ) {
331
332 if ( charitable_is_debug( 'vendor' ) ) {
333 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: get_version_info()' );
334 }
335
336 if ( ! $update_cache ) {
337 $update_cache = get_site_transient( 'update_plugins' );
338 }
339
340 if ( ! is_object( $update_cache ) || empty( $update_cache->response ) || ! array_key_exists( $slug, $update_cache->response ) ) {
341 return false;
342 }
343
344 return $update_cache->response[ $slug ];
345 }
346
347 /**
348 * Checks whether the given plugin can be updated.
349 *
350 * @since 1.6.14
351 *
352 * @param array $version_info Version information.
353 * @return true|array If it can be updated, returns true. Otherwise
354 * returns an array with a reason_code and description.
355 */
356 protected function able_to_update( $version_info ) {
357
358 if ( charitable_is_debug( 'vendor' ) ) {
359 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: able_to_update()' );
360 }
361
362 $changelog_link = self_admin_url( 'index.php?edd_sl_action=view_plugin_changelog&plugin=' . $version_info['name'] . '&slug=' . $version_info['slug'] . '&TB_iframe=true&width=772&height=911' );
363
364 switch ( $version_info['package'] ) {
365
366 case 'missing_license':
367 return array(
368 'reason_code' => 'missing_license',
369 'description' => sprintf(
370 // translators: %1$s is the plugin name, %2$s is the URL to activate the license, %3$s is the URL to view the changelog, %4$s is the new version number.
371 __( '<p>There is a new version of %1$s available but you have not activated your license. <a target="_top" href="%2$s">Activate your license</a> or <a target="_blank" class="thickbox" href="%3$s">view version %4$s details</a>.</p>', 'charitable' ),
372 esc_html( $version_info['name'] ),
373 admin_url( 'admin.php?page=charitable-settings&tab=general' ),
374 esc_url( $changelog_link ),
375 esc_html( $version_info['new_version'] )
376 ),
377 );
378
379 case 'expired_license':
380 $base_renewal_url = isset( $version_info['renewal_link'] ) ? $version_info['renewal_link'] : 'https://www.wpcharitable.com/account';
381 $base_renewal_url = function_exists( 'charitable_ga_url' ) ? charitable_ga_url( $base_renewal_url, urlencode( 'Expired License Notice' ), urlencode( 'Renew your license' ) ) : $base_renewal_url;
382
383 return array(
384 'reason_code' => 'expired_license',
385 'description' => sprintf(
386 // translators: %1$s is the plugin name, %2$s is the URL to renew the license, %3$s is the URL to view the changelog, %4$s is the new version number.
387 __( '<p>There is a new version of %1$s available but your license has expired. <a target="_blank" href="%2$s">Renew your license</a> or <a target="_blank" class="thickbox" href="%3$s">view version %4$s details</a>.</p>', 'charitable' ),
388 esc_html( $version_info['name'] ),
389 $base_renewal_url,
390 esc_url( $changelog_link ),
391 esc_html( $version_info['new_version'] )
392 ),
393 );
394
395 default:
396 if ( ! isset( $version_info['requirements'] ) ) {
397 return true;
398 }
399
400 $messages = array();
401
402 foreach ( $version_info['requirements'] as $type => $details ) {
403
404 switch ( $type ) {
405 case 'php':
406 if ( version_compare( phpversion(), $details, '<' ) ) {
407 $messages[] = esc_html(
408 sprintf(
409 __( '<li>Requires PHP version %s or greater.</li>' ),
410 $details
411 )
412 );
413 }
414 break;
415
416 case 'charitable':
417 if ( version_compare( charitable()->get_version(), $details, '<' ) ) {
418 $messages[] = esc_html(
419 sprintf(
420 __( 'Requires Charitable version %s or greater.' ),
421 $details
422 )
423 );
424 }
425 break;
426 }
427
428 if ( empty( $messages ) ) {
429 return true;
430 }
431
432 return array(
433 'reason_code' => 'missing_requirements',
434 'description' => sprintf(
435 __( '<p>There is a new version of %1$s available but you are missing the following minimum requirements:</p>%2$s', 'charitable' ),
436 esc_html( $version_info['name'] ),
437 '<ul>' . implode( '<br/>', $messages ) . '</ul>'
438 ),
439 );
440 }
441
442 return true;
443 }
444 }
445
446 /**
447 * Return the package string for an expired license.
448 *
449 * @since 1.4.20
450 *
451 * @param string $plugin Plugin basename.
452 * @return string
453 */
454 public function get_expired_license_package( $plugin ) {
455
456 if ( charitable_is_debug( 'vendor' ) ) {
457 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: get_expired_license_package()' );
458 }
459
460 $version_info = $this->get_version_info( $plugin );
461 $base_renewal_url = isset( $version_info->renewal_link ) ? $version_info->renewal_link : 'https://www.wpcharitable.com/account';
462
463 return sprintf(
464 'expired_license:%s',
465 charitable_ga_url(
466 $base_renewal_url,
467 urlencode( 'WordPress Dashboard' ),
468 urlencode( 'Expired License' )
469 )
470 );
471 }
472
473 /**
474 * Return the package string for a plugin missing requirements.
475 *
476 * @since 1.6.14
477 *
478 * @param string $plugin Plugin basename.
479 * @return string
480 */
481 public function get_missing_requirements_package( $plugin ) {
482
483 if ( charitable_is_debug( 'vendor' ) ) {
484 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: get_missing_requirements_package()' );
485 }
486
487 $version_info = $this->get_version_info( $plugin );
488
489 return sprintf(
490 'missing_requirements:%s',
491 $version_info->package_download_restriction
492 );
493 }
494
495 /**
496 * Register a product that requires licensing.
497 *
498 * @since 1.0.0
499 *
500 * @param string $item_name The title of the product.
501 * @param string $author The author of the product.
502 * @param string $version The current product version we have installed.
503 * @param string $file The path to the plugin file.
504 * @param string $url The base URL where the plugin is licensed. Defaults to Charitable_Licenses::UPDATE_URL.
505 * @return void
506 */
507 public function register_licensed_product( $item_name, $author, $version, $file, $url = false ) {
508
509 if ( charitable_is_debug( 'vendor' ) ) {
510 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: register_licensed_product()' );
511 }
512
513 if ( ! $url ) {
514 $url = self::UPDATE_URL;
515 }
516
517 $product_key = $this->get_item_key( $item_name );
518
519 $this->products[ $product_key ] = array(
520 'name' => $item_name,
521 'author' => $author,
522 'version' => $version,
523 'url' => $url,
524 'file' => $file,
525 );
526
527 $licenses = $this->get_licenses();
528 $license = isset( $licenses[ $product_key ]['license'] ) ? trim( $licenses[ $product_key ]['license'] ) : '';
529
530 new \Charitable_Checker(
531 $url,
532 $file,
533 array(
534 'version' => $version,
535 'license' => $license,
536 'item_name' => $item_name,
537 'author' => $author,
538 )
539 );
540 }
541
542 /**
543 * Return the list of products requiring licensing.
544 *
545 * @since 1.0.0
546 *
547 * @return array[]
548 */
549 public function get_products() {
550 if ( charitable_is_debug( 'vendor' ) ) {
551 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: get_products()' );
552 }
553 return $this->products;
554 }
555
556 /**
557 * Return a specific product's details.
558 *
559 * @since 1.0.0
560 *
561 * @param string $item The item for which we are getting product details.
562 * @return string[]
563 */
564 public function get_product_license_details( $item ) {
565 if ( charitable_is_debug( 'vendor' ) ) {
566 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: get_product_license_details()' );
567 }
568 return isset( $this->products[ $item ] ) ? $this->products[ $item ] : false;
569 }
570
571 /**
572 * Returns whether the given product has a valid license.
573 *
574 * @since 1.0.0
575 *
576 * @param string $item The item to check.
577 * @return boolean
578 */
579 public function has_valid_license( $item ) {
580 if ( charitable_is_debug( 'vendor' ) ) {
581 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: has_valid_license()' );
582 }
583 $license = $this->get_license_details( $item );
584
585 if ( ! $license || ! isset( $license['valid'] ) ) {
586 return false;
587 }
588
589 return $license['valid'];
590 }
591
592 /**
593 * Returns the license details for the given product.
594 *
595 * @since 1.0.0
596 *
597 * @param string $item The item to get the license for.
598 * @return mixed[]
599 */
600 public function get_license( $item ) {
601 if ( charitable_is_debug( 'vendor' ) ) {
602 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: get_license()' );
603 }
604 $license = $this->get_license_details( $item );
605
606 if ( ! $license || ! is_array( $license ) ) {
607 return false;
608 }
609
610 return $license['license'];
611 }
612
613 /**
614 * Returns the active license details for the given product.
615 *
616 * @since 1.0.0
617 *
618 * @param string $item The item to get active licensing details for.
619 * @return mixed[]
620 */
621 public function get_license_details( $item ) {
622 if ( charitable_is_debug( 'vendor' ) ) {
623 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: get_license_details()' );
624 }
625 $licenses = $this->get_licenses();
626
627 if ( ! isset( $licenses[ $item ] ) ) {
628 return false;
629 }
630
631 return $licenses[ $item ];
632 }
633
634 /**
635 * Return the list of licenses.
636 *
637 * Note: The licenses are not necessarily valid. If a user enters an invalid
638 * license, the license will be stored but it will be flagged as invalid.
639 *
640 * @since 1.0.0
641 *
642 * @return array[]
643 */
644 public function get_licenses() {
645 if ( charitable_is_debug( 'vendor' ) ) {
646 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: get_licenses()' );
647 }
648 if ( ! isset( $this->licenses ) ) {
649 $this->licenses = charitable_get_option( 'licenses', array() );
650 }
651
652 return $this->licenses;
653 }
654
655 /**
656 * Verify a license.
657 *
658 * @since 1.0.0
659 *
660 * @param string $item The item to verify.
661 * @param string $license The license key for the item.
662 * @param boolean $force Whether to force the verification check.
663 * @param boolean $legacy Whether to use the legacy API (?).
664 * @return mixed[]
665 */
666 public function verify_license( $item, $license, $force = false, $legacy = false ) {
667
668 if ( charitable_is_debug( 'vendor' ) ) {
669 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: verify_license()' );
670 }
671
672 $license = trim( $license );
673
674 if ( $license === $this->get_license( $item ) && ! $force ) {
675 return $this->get_license_details( $item );
676 }
677
678 $product_details = $this->get_product_license_details( $item );
679
680 /* This product was not correctly registered. */
681 if ( ! $product_details ) {
682 return;
683 }
684
685 /* Data to send in our API request */
686 $api_params = array(
687 'edd_action' => 'activate_license',
688 'license' => $license,
689 'legacy' => $legacy,
690 'item_name' => urlencode( $product_details['name'] ),
691 'url' => home_url(),
692 );
693
694 /* Call the custom API */
695 $response = wp_remote_post(
696 $product_details['url'],
697 array(
698 'timeout' => 15,
699 'sslverify' => false,
700 'body' => $api_params,
701 )
702 );
703
704 if ( charitable_is_debug() ) {
705 error_log( 'verify_license' );
706 error_log( print_r( $api_params, true ) );
707 error_log( print_r( $product_details, true ) );
708 error_log( print_r( $response, true ) );
709 }
710
711 /* Make sure the response came back okay */
712 if ( is_wp_error( $response ) ) {
713 return;
714 }
715
716 $this->flush_update_cache();
717
718 $license_data = json_decode( wp_remote_retrieve_body( $response ) );
719
720 $comm_success = isset( $license_data ) && ! empty( $license_data ) ? true : false;
721
722 // if the license is not valid, attempt to discover why - in this case, maybe the activation limit was exceeded.
723
724 $license_limit = isset( $license_data->license_limit ) ? intval( $license_data->license_limit ) : false;
725 $site_count = isset( $license_data->site_count ) ? intval( $license_data->site_count ) : false;
726 $limit_exceeded = ( $license_limit > 0 && $site_count >= $license_limit ) ? true : false;
727
728 return array(
729 'license' => $license,
730 'expiration_date' => isset( $license_data->expires ) ? $license_data->expires : false,
731 'plan_id' => isset( $license_data->price_id ) ? $license_data->price_id : false,
732 'valid' => ( isset( $license_data->license ) && 'valid' === $license_data->license ),
733 'license_limit' => $limit_exceeded,
734 'comm_success' => $comm_success,
735 );
736 }
737
738 /**
739 * Return the URL to deactivate a specific license.
740 *
741 * @since 1.0.0
742 *
743 * @param string $item The item to deactivate.
744 * @return string
745 */
746 public function get_license_deactivation_url( $item ) {
747 if ( charitable_is_debug( 'vendor' ) ) {
748 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: get_license_deactivation_url()' );
749 }
750 return esc_url(
751 add_query_arg(
752 array(
753 'charitable_action' => 'deactivate_license',
754 'product_key' => $item,
755 '_nonce' => wp_create_nonce( 'license' ),
756 ),
757 admin_url( 'admin.php?page=charitable-settings&tab=advanced' )
758 )
759 );
760 }
761
762 /**
763 * Deactivate a license.
764 *
765 * @since 1.0.0
766 *
767 * @return void
768 */
769 public function deactivate_license() {
770
771 if ( charitable_is_debug( 'vendor' ) ) {
772 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: deactivate_license()' );
773 }
774
775 if ( ! wp_verify_nonce( $_REQUEST['_nonce'], 'license' ) ) {
776 wp_die( esc_attr__( 'Cheatin\' eh?!', 'charitable' ) );
777 }
778
779 $product_key = isset( $_REQUEST['product_key'] ) ? $_REQUEST['product_key'] : false;
780
781 /* Product key must be set */
782 if ( false === $product_key ) {
783 wp_die( esc_attr__( 'Missing product key', 'charitable' ) );
784 }
785
786 $product = $this->get_product_license_details( $product_key );
787
788 /* Make sure we have a valid product with a valid license. */
789 if ( ! $product || ! $this->has_valid_license( $product_key ) ) {
790 wp_die( esc_attr__( 'This product is not valid or does not have a valid license key.', 'charitable' ) );
791 }
792
793 $license = $this->get_license( $product_key );
794
795 /* Data to send to wpcharitable.com to deactivate the license. */
796 $api_params = array(
797 'edd_action' => 'deactivate_license',
798 'license' => $license,
799 'item_name' => urlencode( $product['name'] ),
800 'url' => home_url(),
801 );
802
803 /* Call the custom API. */
804 $response = wp_remote_post(
805 $product['url'],
806 array(
807 'timeout' => 15,
808 'sslverify' => false,
809 'body' => $api_params,
810 )
811 );
812
813 /* Make sure the response came back okay */
814 if ( is_wp_error( $response ) ) {
815 return;
816 }
817
818 $this->flush_update_cache();
819
820 /* Decode the license data */
821 $license_data = json_decode( wp_remote_retrieve_body( $response ) );
822
823 $settings = get_option( 'charitable_settings' );
824
825 unset( $settings['licenses'][ $product_key ] );
826
827 update_option( 'charitable_settings', $settings );
828
829 // Delete transients (related to plugin versions).
830 delete_transient( '_charitable_plugin_versions' );
831
832 wp_redirect( add_query_arg( array( 'tab' => 'advanced' ), admin_url( 'admin.php?page=charitable-settings' ) ) );
833 exit;
834 }
835
836 /**
837 * Flush the version update cache.
838 *
839 * @since 1.4.20
840 * @since 1.8.1.1 - Remove site option. Other items in this function are depreciated.
841 *
842 * @return void
843 */
844 protected function flush_update_cache() {
845
846 if ( charitable_is_debug( 'vendor' ) ) {
847 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: flush_update_cache()' );
848 }
849
850 delete_site_option( 'wpc_plugin_versions' );
851 delete_transient( '_charitable_plugin_versions' );
852
853 // Create an empty update transient object instead of null
854 $empty_transient = new \stdClass();
855 set_site_transient( 'update_plugins', $empty_transient );
856 }
857
858 /**
859 * Return a key for the item, based on the item name.
860 *
861 * @since 1.0.0
862 *
863 * @param string $item_name Name of the item.
864 * @return string
865 */
866 protected function get_item_key( $item_name ) {
867 if ( charitable_is_debug( 'vendor' ) ) {
868 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: get_item_key()' );
869 }
870 return strtolower( str_replace( ' ', '_', $item_name ) );
871 }
872
873 /**
874 * Return the latest versions of Charitable plugins.
875 *
876 * @since 1.4.0
877 * @since 1.8.1.1 - Removed cache_get method and site a site option instead.
878 * @since 1.8.1.3 - Added request_recently_failed check and log_failed_request call.
879 * @since 1.8.9.7 - Increased version cache TTL from 1 hour to 12 hours.
880 *
881 * @return array
882 */
883 protected function get_versions() {
884
885 if ( charitable_is_debug( 'vendor' ) ) {
886 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: wpc_plugin_versions()' );
887 }
888
889 $versions = get_site_option( 'wpc_plugin_versions' );
890
891 if ( false === $versions || ! isset( $versions['expires'] ) || time() > $versions['expires'] || ! isset( $versions['data'] ) ) {
892 /*
893 * Check and see if this request recently failed.
894 * Most likely because the site is down or didn't return a 200 response.
895 * Regardless this means we're allowed to try again for a while.
896 */
897 if ( $this->request_recently_failed() ) {
898 if ( charitable_is_debug() ) {
899 error_log( 'WPCharitable Debug Error: get_versions (licenses) API call aborted because it recently failed' );
900 }
901 return false;
902 }
903
904 $licenses = array();
905
906 foreach ( $this->get_licenses() as $license ) {
907 if ( isset( $license['license'] ) ) {
908 $licenses[] = $license['license'];
909 }
910 }
911
912 $response = wp_remote_post(
913 self::UPDATE_URL . '/edd-api/versions-v2/',
914 array(
915 'sslverify' => false,
916 'timeout' => 15,
917 'body' => array(
918 'licenses' => $licenses,
919 'url' => home_url(),
920 ),
921 )
922 );
923
924 // check the response code... if it's bad, then return what we have if anything.
925 if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
926
927 // Log this failure so we don't keep trying.
928 $this->log_failed_request();
929
930 if ( defined( 'CHARITABLE_DEBUG_LICENSE' ) && CHARITABLE_DEBUG_LICENSE ) {
931 error_log( 'WPCharitable Debug Error: get_versions (licenses) API call failed' );
932 error_log( print_r( $response, true ) );
933 }
934
935 return ( false !== $versions && isset( $versions['data'] ) ) ? $versions['data'] : array();
936 }
937
938 $versions = wp_remote_retrieve_body( $response );
939
940 $versions = json_decode( $versions, true );
941
942 update_site_option(
943 'wpc_plugin_versions',
944 array(
945 'expires' => strtotime( '+12 hours', time() ),
946 'data' => $versions,
947 )
948 );
949
950 if ( defined( 'CHARITABLE_DEBUG_LICENSE' ) && CHARITABLE_DEBUG_LICENSE ) {
951 error_log( 'WPCharitable Debug Notice v1.8.2: get_versions (licenses) API call successful in licensing and added to site option.' );
952 error_log(
953 print_r(
954 array(
955 'expires' => strtotime( '+12 hours', time() ),
956 'data' => $versions,
957 ),
958 true
959 )
960 );
961 }
962
963 } else {
964 $versions = $versions['data'];
965 }
966
967 return $versions;
968 }
969
970
971 /**
972 * Logs a failed HTTP request for this API URL.
973 *
974 * We set a timestamp for 2 hours from now. This prevents future API requests from being
975 * made to this domain for 2 hours. Once the timestamp is in the past, API requests
976 * will be allowed again. This way if the site is down for some reason we don't bombard
977 * it with failed API requests.
978 *
979 * @see EDD_SL_Plugin_Updater::request_recently_failed
980 *
981 * @since 1.8.1.3
982 * @since 1.8.9.7 - Increased failed request backoff from 1 hour to 2 hours.
983 */
984 private function log_failed_request() {
985 if ( charitable_is_debug( 'vendor' ) ) {
986 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: log_failed_request()' );
987 }
988 update_option( $this->failed_request_cache_key, strtotime( '+2 hours' ) );
989 }
990
991 /**
992 * Determines if a request has recently failed.
993 *
994 * @since 1.8.1.3
995 *
996 * @return bool
997 */
998 private function request_recently_failed() {
999 if ( charitable_is_debug( 'vendor' ) ) {
1000 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: request_recently_failed()' );
1001 }
1002 $failed_request_details = get_option( $this->failed_request_cache_key );
1003
1004 // Request has never failed.
1005 if ( empty( $failed_request_details ) || ! is_numeric( $failed_request_details ) ) {
1006 return false;
1007 }
1008
1009 /*
1010 * Request previously failed, but the timeout has expired.
1011 * This means we're allowed to try again.
1012 */
1013 if ( time() > $failed_request_details ) {
1014 delete_option( $this->failed_request_cache_key );
1015
1016 return false;
1017 }
1018
1019 return true;
1020 }
1021
1022 /**
1023 * AJAX handler for license checks.
1024 *
1025 * @since 1.0.0
1026 * @since 1.8.1.8 - Added nonce and permissions check.
1027 *
1028 * @return void
1029 */
1030 public function ajax_license_check() {
1031
1032 if ( charitable_is_debug( 'vendor' ) ) {
1033 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: ajax_license_check()' ); // phpcs:ignore
1034 }
1035
1036 if ( ! is_admin() ) {
1037 return;
1038 }
1039
1040 // check for nonce.
1041 if ( ! isset( $_REQUEST['nonce'] ) || ! wp_verify_nonce( $_REQUEST['nonce'], 'charitable_settings-options' ) ) { // phpcs:ignore
1042 return;
1043 }
1044
1045 // Check for permissions.
1046 if ( ! charitable_current_user_can( 'manage_options' ) ) {
1047 return;
1048 }
1049
1050 if ( ! isset( $_REQUEST['license'] ) ) {
1051 return;
1052 }
1053
1054 if ( ! isset( $_REQUEST['action'] ) && 'charitable_license_check' !== $_REQUEST['action'] ) {
1055 return;
1056 }
1057
1058 if ( ! isset( $_REQUEST['charitable_action'] ) || 'verify' !== $_REQUEST['charitable_action'] ) {
1059 return;
1060 }
1061
1062 $download_pro = isset( $_REQUEST['download_pro'] ) && 'true' === $_REQUEST['download_pro'] ? true : false;
1063
1064 // to-do: adjust spelling of charitable_action?
1065
1066 $product_key = 'charitable';
1067 $re_check = true;
1068 $license = trim( esc_html( $_REQUEST['license'] ) ); // phpcs:ignore
1069 $license_data = $this->verify_license( $product_key, $license, $re_check );
1070
1071 $this->flush_update_cache();
1072
1073 $settings = get_option( 'charitable_settings' );
1074
1075 $settings['licenses'][ $product_key . '-v2' ] = array(
1076 'license' => $license,
1077 'expiration_date' => isset( $license_data['expiration_date'] ) ? $license_data['expiration_date'] : false,
1078 'plan_id' => isset( $license_data['plan_id'] ) ? $license_data['plan_id'] : false,
1079 'valid' => isset( $license_data['plan_id'] ) ? 1 === intval( $license_data['valid'] ) : false,
1080 );
1081
1082 if ( charitable_is_debug() ) {
1083 error_log( 'ajax_license_check: settings' );
1084 error_log( print_r( $settings, true ) );
1085 error_log( print_r( $_POST, true ) );
1086 error_log( print_r( $_REQUEST, true ) );
1087 }
1088
1089 update_option( 'charitable_settings', $settings );
1090
1091 if ( charitable_is_debug() ) {
1092 error_log( 'download_pro: ' . $download_pro );
1093 }
1094
1095 if ( $download_pro ) {
1096 // Get the download url from the server.
1097 $response = wp_remote_get(
1098 'https://wpcharitable.com/',
1099 array(
1100 'timeout' => 15,
1101 'sslverify' => false,
1102 'body' => array(
1103 'edd_action' => 'get_version',
1104 'item_id' => '13035026',
1105 'license' => $license,
1106 'url' => site_url(),
1107 ),
1108 )
1109 );
1110
1111 if ( charitable_is_debug() ) {
1112 error_log( 'ajax_license_check: download pro response' );
1113 error_log( print_r( $response, true ) );
1114 }
1115
1116 // Check if the response is HTTP valid and not an error.
1117 if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
1118 if ( charitable_is_debug() ) {
1119 error_log( 'ajax_license_check: download pro response error' );
1120 error_log( print_r( $response, true ) );
1121 }
1122 return;
1123 }
1124
1125 // Get the body of the response.
1126 $body = wp_remote_retrieve_body( $response );
1127
1128 if ( charitable_is_debug() ) {
1129 error_log( 'ajax_license_check: download pro body' );
1130 error_log( print_r( $body, true ) );
1131 }
1132
1133 // Get the download link from the response.
1134 $download_link = json_decode( $body )->download_link;
1135
1136 // If the download link is a real url, then download the plugin.
1137 if ( $download_link ) {
1138 $this->download_pro( $download_link, $license );
1139 }
1140 }
1141
1142 if ( isset( $license_data['valid'] ) && intval( $license_data['valid'] ) === 1 ) {
1143 $license_data['message'] = \Charitable_Licenses_Settings::get_instance()->get_licensed_message();
1144 // Allow main plugin to add upgrade_url (or show_manual_upgrade for localhost) so Verify redirects to upgrade site.
1145 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
1146 error_log( 'CHARITABLE DEBUG: ajax_license_check applying filter charitable_license_verify_success_data' );
1147 }
1148 $license_data = apply_filters( 'charitable_license_verify_success_data', $license_data, $license );
1149 } else {
1150 $license_data['message'] = \Charitable_Licenses_Settings::get_instance()->get_unlicensed_message( false, $license_data );
1151 }
1152
1153 wp_send_json_success( $license_data );
1154 }
1155
1156 /**
1157 * Download the pro plugin.
1158 *
1159 * @since 1.8.5
1160 *
1161 * @return string
1162 */
1163 public function download_pro( $download_link = false, $license = false ) {
1164
1165 if ( ! $download_link ) {
1166 error_log( 'download_pro: no download link provided' );
1167 return array(
1168 'success' => false,
1169 'message' => \esc_html__( 'No download link provided.', 'charitable' ),
1170 );
1171 }
1172
1173 // Include required WordPress core files
1174 require_once ABSPATH . 'wp-admin/includes/file.php';
1175 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1176 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1177 require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
1178 require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
1179 require_once plugin_dir_path( CHARITABLE_DIRECTORY_PATH ) . 'charitable/includes/utilities/Skin.php';
1180
1181 // Set the current screen to avoid undefined notices.
1182 // require_once ABSPATH . 'wp-admin/includes/screen.php';
1183 // \set_current_screen( 'charitable_page_charitable-settings' );
1184
1185 // Prepare variables.
1186 $url = \esc_url_raw(
1187 \add_query_arg(
1188 array( 'page' => 'charitable-settings' ),
1189 \admin_url( 'admin.php' )
1190 )
1191 );
1192
1193 // Verify pro not activated.
1194 if ( \function_exists( 'charitable_is_pro_addon_version' ) && \charitable_is_pro_addon_version() ) {
1195 error_log( 'download_pro: pro addon version already installed and activated' );
1196 return array(
1197 'success' => false,
1198 'message' => \esc_html__( 'Plugin already installed.', 'charitable' ),
1199 );
1200 }
1201
1202 // Verify the plugin is not installed.
1203 if (!\function_exists('get_plugins')) {
1204 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1205 }
1206
1207 $all_plugins = \get_plugins();
1208 $is_installed = array_key_exists(self::PRO_PLUGIN, $all_plugins);
1209
1210 if ($is_installed) {
1211 error_log( 'download_pro: pro plugin already installed' );
1212 return array(
1213 'success' => false,
1214 'message' => \esc_html__( 'Plugin already installed.', 'charitable' ),
1215 );
1216 }
1217
1218 require_once ABSPATH . 'wp-admin/includes/file.php';
1219 $creds = \request_filesystem_credentials( $url, '', false, false );
1220
1221 // Check for file system permissions.
1222 if ( $creds === false || ! \WP_Filesystem( $creds ) ) {
1223 \wp_send_json_error(
1224 \esc_html__( 'There was an error while installing an upgrade. Please check file system permissions and try again. Also, you can download the plugin from charitable.com and install it manually.', 'charitable' )
1225 );
1226 }
1227
1228 /*
1229 * We do not need any extra credentials if we have gotten this far, so let's install the plugin.
1230 */
1231
1232 // Do not allow WordPress to search/download translations, as this will break JS output.
1233 \remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
1234
1235 // We do not need any extra credentials if we have gotten this far, so let's install the plugin.
1236 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1237 require_once plugin_dir_path( CHARITABLE_DIRECTORY_PATH ) . 'charitable/includes/utilities/Skin.php';
1238
1239 // Create the plugin upgrader with our custom skin.
1240 $installer = new \Plugin_Upgrader( new \Charitable_Skin() );
1241
1242 // Error check.
1243 if ( ! method_exists( $installer, 'install' ) ) {
1244 wp_send_json_error( esc_html__( 'No install method found.', 'charitable' ) );
1245 }
1246
1247 $installer->install( $download_link ); // phpcs:ignore
1248
1249 if ( charitable_is_debug() ) {
1250 error_log( 'Charitable Admin Connect process install' ); // phpcs:ignore
1251 error_log( $url ); // phpcs:ignore
1252 error_log( print_r( $installer->plugin_info(), true ) ); // phpcs:ignore
1253 }
1254
1255 // Flush the cache and return the newly installed plugin basename.
1256 wp_cache_flush();
1257
1258 $plugin_basename = $installer->plugin_info();
1259
1260 return $plugin_basename;
1261 }
1262
1263
1264 /**
1265 * AJAX handler for license deactivation.
1266 *
1267 * @since 1.8.5
1268 *
1269 * @return void
1270 */
1271 public function ajax_license_deactivate() {
1272
1273 if ( charitable_is_debug( 'vendor' ) ) {
1274 error_log( 'CHARITABLE: NEW VENDOR CALL RECEIVED: ajax_license_deactivate()' ); // phpcs:ignore
1275 }
1276
1277 if ( ! is_admin() ) {
1278 return;
1279 }
1280
1281 // check for nonce.
1282 if ( ! isset( $_REQUEST['nonce'] ) || ! wp_verify_nonce( $_REQUEST['nonce'], 'charitable_settings-options' ) ) { // phpcs:ignore
1283 return;
1284 }
1285
1286 // Check for permissions.
1287 if ( ! charitable_current_user_can( 'manage_options' ) ) {
1288 return;
1289 }
1290
1291 if ( ! isset( $_REQUEST['license'] ) ) {
1292 return;
1293 }
1294
1295 if ( ! isset( $_REQUEST['action'] ) && 'charitable_license_check' !== $_REQUEST['action'] ) {
1296 return;
1297 }
1298
1299 if ( ! isset( $_REQUEST['charitable_action'] ) || 'deactivate' !== $_REQUEST['charitable_action'] ) {
1300 return;
1301 }
1302
1303 $this->flush_update_cache();
1304
1305 $settings = get_option( 'charitable_settings' );
1306
1307 $product_key = 'charitable';
1308 $settings['licenses'][ $product_key . '-v2' ] = array();
1309
1310 update_option( 'charitable_settings', $settings );
1311
1312 // Delete transients (related to plugin versions).
1313 delete_transient( '_charitable_plugin_versions' );
1314
1315 $license_data['message'] = \Charitable_Licenses_Settings::get_instance()->get_deactivated_message();
1316
1317 wp_send_json_success( $license_data );
1318
1319 }
1320 }
1321