get_products(); if ( empty( $products ) ) { return $tabs; } $show_licenses_tab = apply_filters( 'charitable_show_old_license_tab', false ); if ( $show_licenses_tab ) : $tabs = charitable_add_settings_tab( $tabs, 'licenses', __( 'Licenses', 'charitable' ), array( 'index' => 4, ) ); endif; return $tabs; } /** * Add the licenses tab settings fields. * * @since 1.0.0 * * @return array */ public function add_licenses_fields() { if ( ! charitable_is_settings_view( 'licenses' ) ) { return array(); } $fields = array( 'section' => array( 'title' => '', 'type' => 'hidden', 'priority' => 10000, 'value' => 'licenses', 'save' => false, ), 'licenses' => array( 'title' => false, 'callback' => array( $this, 'render_licenses_table' ), 'priority' => 4, ), ); foreach ( charitable_get_helper( 'licenses' )->get_products() as $key => $product ) { $fields[ $key ] = array( 'type' => 'text', 'render' => false, 'priority' => 6, ); } return $fields; } /** * Add the licenses group. * * @since 1.0.0 * * @param string[] $groups Settings groups. * @return string[] */ public function add_licenses_group( $groups ) { $groups['licenses'] = array(); return $groups; } /** * Render the licenses table. * * @since 1.0.0 * * @return void */ public function render_licenses_table() { charitable_admin_view( 'settings/licenses' ); } /** * Check if a license is expiring in x seconds. * * @since 1.8.1.12 * * @param int $seconds The number of seconds to check for. * @return bool */ public function is_license_expiring( $seconds = 1209600 ) { if ( $this->is_license_expired() ) { return false; } // If the 'CHARITABLE_FORCE_EXPIRING_LICENSE' is defined, then return true. if ( defined( 'CHARITABLE_FORCE_EXPIRING_LICENSE' ) && CHARITABLE_FORCE_EXPIRING_LICENSE ) { return true; } $expire_date = $this->get_license_expire_date( 'timestamp' ); if ( false === $expire_date ) { return false; } // expire_date must be a timestamp, if not then return false. if ( ! is_numeric( $expire_date ) ) { return false; } $current_date = time(); // If the license is expiring in x seconds, return true. if ( $expire_date - $current_date < $seconds ) { return true; } return false; } /** * Check if a license has expired, * * @since 1.8.1.12 * * @return bool */ public function is_license_expired() { // If the 'CHARITABLE_FORCE_EXPIRED_LICENSE' is defined, then return true. For troubleshooting. if ( defined( 'CHARITABLE_FORCE_EXPIRED_LICENSE' ) && CHARITABLE_FORCE_EXPIRED_LICENSE ) { return true; } $expire_date = $this->get_license_expire_date( 'timestamp' ); if ( false === $expire_date ) { return false; } // expire_date must be a timestamp, if not then return false. if ( ! is_numeric( $expire_date ) ) { return false; } $current_date = time(); // If the license is expiring in x seconds, return true. if ( $expire_date - $current_date < 0 ) { return true; } return false; } /** * If a license has expired, do a license request (to make sure we have the most updated informatino, especially when it comes to license renewals). * * @since 1.8.3.2 * * @return void */ public function maybe_check_if_license_expired() { // We need to be on the settings general page, otherwise bail. if ( ! charitable_is_settings_view( 'general' ) ) { return; } // Determine if the license is expired. if ( ! $this->is_license_expired() ) { return; } // Determine when (if ever) we last checked. $settings = get_option( 'charitable_settings' ); $license_settings = $settings['licenses']; if ( false === $license_settings || false === $license_settings['charitable-v2'] || ! isset( $license_settings['checked'] ) || time() > $license_settings['checked'] ) { /* * Check and see if this check recently happened. * If it has within the last day (and the license is still expired) then try again for a while. */ if ( ! empty( $license_settings['charitable-v2']['license'] ) ) : $license_data = charitable_get_helper( 'licenses' )->verify_license( 'charitable', esc_html( $license_settings['charitable-v2']['license'] ), true, false ); // if the license "keys" don't match, abort. if ( empty( $license_data ) || ! isset( $license_data['license'] ) || $license_data['license'] !== $license_settings['charitable-v2']['license'] ) { // phpcs:ignore // do nothing. } else { // Check the expiration_date in the license data - if it's not the same as the current expiration date, update it. if ( isset( $license_data['expiration_date'] ) && $license_data['expiration_date'] !== $license_settings['charitable-v2']['expiration_date'] ) { $license_settings['charitable-v2']['expiration_date'] = $license_data['expiration_date']; } } endif; // We've done the check, so update the updated time for one day in the future. $license_settings['checked'] = strtotime( '+1 day' ); $settings['licenses'] = $license_settings; global $wpdb; // manually update the 'charitable_settings' option. $wpdb->update( // phpcs:ignore $wpdb->options, array( 'option_value' => maybe_serialize( $settings ), ), array( 'option_name' => 'charitable_settings', ) ); // redirect to the settings page. wp_safe_redirect( admin_url( 'admin.php?page=charitable-settings' ) ); exit; } } /** * If a license has expired, do a license request (to make sure we have the most updated informatino, especially when it comes to license renewals). * * @since 1.8.3.2 * * @return void */ public function maybe_check_if_license_expiring() { // We need to be on the settings general page, otherwise bail. if ( ! charitable_is_settings_view( 'general' ) ) { return; } // Determine if the license is expired. if ( ! $this->is_license_expiring() ) { return; } // Determine when (if ever) we last checked. $settings = get_option( 'charitable_settings' ); $license_settings = $settings['licenses']; if ( false === $license_settings || false === $license_settings['charitable-v2'] || ! isset( $license_settings['checked'] ) || time() > $license_settings['checked'] ) { /* * Check and see if this check recently happened. * If it has within the last day (and the license is still expired) then try again for a while. */ if ( ! empty( $license_settings['charitable-v2']['license'] ) ) : $license_data = charitable_get_helper( 'licenses' )->verify_license( 'charitable', esc_html( $license_settings['charitable-v2']['license'] ), true, false ); // if the license "keys" don't match, abort. if ( empty( $license_data ) || ! isset( $license_data['license'] ) || $license_data['license'] !== $license_settings['charitable-v2']['license'] ) { // phpcs:ignore // do nothing. } else { // Check the expiration_date in the license data - if it's not the same as the current expiration date, update it. if ( isset( $license_data['expiration_date'] ) && $license_data['expiration_date'] !== $license_settings['charitable-v2']['expiration_date'] ) { $license_settings['charitable-v2']['expiration_date'] = $license_data['expiration_date']; } } endif; // We've done the check, so update the updated time for one day in the future. $license_settings['checked'] = strtotime( '+1 day' ); $settings['licenses'] = $license_settings; global $wpdb; // manually update the 'charitable_settings' option. $wpdb->update( // phpcs:ignore $wpdb->options, array( 'option_value' => maybe_serialize( $settings ), ), array( 'option_name' => 'charitable_settings', ) ); // redirect to the settings page. wp_safe_redirect( admin_url( 'admin.php?page=charitable-settings' ) ); exit; } } /** * Get the expire data of a current license (checks for "is_pro" happen before this function). * * @since 1.8.1.12 * @version 1.8.1.14 added lifetime license check. * * @param mixed $timestamp Whether to return a timestamp or a formatted date. * * @return mixed[] */ public function get_license_expire_date( $timestamp = false ) { $settings = get_option( 'charitable_settings' ); if ( false === $settings ) { return false; } $license_key = ! empty( $settings['licenses']['charitable-v2']['license'] ) ? esc_html( $settings['licenses']['charitable-v2']['license'] ) : false; $license_expires = ! empty( $settings['licenses']['charitable-v2']['expiration_date'] ) ? esc_html( $settings['licenses']['charitable-v2']['expiration_date'] ) : false; if ( strtolower( $license_expires ) === 'lifetime' ) { // make the license expire date a long time from now. if ( $timestamp ) { return strtotime( '+100 years' ); } else { return date_i18n( get_option( 'date_format' ), strtotime( '+100 years' ) ); } } if ( false === $license_key || false === $license_expires ) { return false; } if ( $timestamp ) { return strtotime( $license_expires ); } return date_i18n( get_option( 'date_format' ), strtotime( $license_expires ) ); } /** * Add an extra button to the Licenses tab to re-check licenses. * * @since 1.6.0 * * @param string $button The button HTML. * @return string */ public function add_license_recheck_button( $button ) { $licenses = array_filter( charitable_get_helper( 'licenses' )->get_licenses(), 'is_array' ); if ( empty( $licenses ) ) { return $button; } $slug = Charitable_Addons_Directory::get_current_plan_slug(); if ( $slug === false || ( is_string( $slug ) && strtolower( $slug ) === 'lite' ) ) { // there is no valid updated license so allow this button to output. $html = '
'; return str_replace( '', $html, $button ); } return $button; } /** * Checks for updated license and invalidates status field if not set. * * @since 1.0.0 * @version 1.7.0.4 * @version 1.8.6.2 - clear transient _charitable_plugin_versions * @version 1.8.9.1 * * @param mixed[] $values The parsed values combining old values & new values. * @param mixed[] $new_values The newly submitted values. * @return mixed[] */ public function save_license( $values, $new_values ) { /* If we didn't just submit licenses, stop here. */ if ( ! isset( $new_values['licenses_legacy'] ) ) { return $values; } // phpcs:ignore WordPress.Security.NonceVerification.Missing $re_check = array_key_exists( 'recheck', $_POST ); $licenses = $new_values['licenses_legacy']; // Delete transients (related to plugin versions). delete_transient( '_charitable_plugin_versions' ); // Remember that legacy licenses are passed into values differently in this hook. $values[licenses_legacy]. foreach ( $licenses as $product_key => $license ) { $license = trim( $license ); if ( empty( $license ) ) { $values['licenses_legacy'][ $product_key ] = ''; continue; } $license_data = charitable_get_helper( 'licenses' )->verify_license( $product_key, $license, $re_check, true ); // the true added to make sure we let the server know we are attempting to vertify a legacy license. if ( empty( $license_data ) ) { continue; } $values['licenses'][ $product_key ] = $license_data; // this is ok because this follows previous versions of where the licenses would be going. } return $values; } /** * Outputs the "new" license HTML for the general settings tab. * * @since 1.7.0.4 * @version 1.8.3 added charitable_is_pro() check. * @version 1.8.5 added pro_connect flag. * * @param string $has_valid_license Allows us to control HTML based on valid license already in the system. * @return string */ public function generate_license_check_html( $has_valid_license = 'false' ) { $slug = (string) Charitable_Addons_Directory::get_current_plan_slug(); $is_legacy = Charitable_Addons_Directory::is_current_plan_legacy(); $pro_connect = $this->is_pro_connect(); $readonly = false; $show_license_form = true; // PRE-VALIDATION: Check for existing valid licenses before showing upgrade prompt $licenses_helper = charitable_get_helper( 'licenses' ); $has_stored_valid_license = false; // Check if there's already a valid license stored (prevents duplicate prompts) if ( $licenses_helper ) { // Check for v3 license (newer format) if ( method_exists( $licenses_helper, 'is_v3_license_valid' ) && $licenses_helper->is_v3_license_valid() ) { $has_stored_valid_license = true; } // Check for any valid license in the system elseif ( method_exists( $licenses_helper, 'has_valid_license' ) && $licenses_helper->has_valid_license( 'charitable' ) ) { $has_stored_valid_license = true; } } if ( $slug === false || strtolower( $slug ) === 'lite' ) { $has_valid_license = $has_stored_valid_license; // Use stored license status instead of always false } elseif ( charitable_is_pro() ) { $has_valid_license = true; } $output = ''; return $output; } /** * Checks if the pro_connect flag is set. This decides if entering a license key will trigger Charitable Connect, an improved migration path to Charitable Pro. * * @since 1.8.5 * @version 1.8.5.3 * * @return boolean */ public function is_pro_connect() { if ( defined( 'CHARITABLE_FORCE_PRO_CONNECT' ) && CHARITABLE_FORCE_PRO_CONNECT ) { return true; } if ( defined( 'CHARITABLE_MANUAL_LICENSE_ACTIVATION' ) && CHARITABLE_MANUAL_LICENSE_ACTIVATION ) { return false; } // If the pro plugin is already installed (but not activated), then we don't need to do anything. if ( file_exists( WP_PLUGIN_DIR . '/charitable-pro/charitable.php' ) ) { return false; } $charitable_activated = (array) get_option( 'charitable_activated', array() ); // If pro connect has been done before, it can be done again. if ( ! empty( $charitable_activated['pro_connect'] ) ) { return true; } // Allows special cases to be handled by other plugins. return apply_filters( 'charitable_allow_pro_connect', true ); } /** * Outputs a message for unlicnensed users for the general settings tab. * * @since 1.7.0.4 * * @param boolean $valid Valid license. * @param array $license_data Available license information. * @param boolean $error Error flag. * @return string */ public function get_unlicensed_message( $valid = false, $license_data = false, $error = true ) { $output = '' . esc_html__( 'You\'re using ', 'charitable' ); $output .= 'Charitable Lite'; $output .= esc_html__( ' - no license needed. Enjoy!', 'charitable' ) . ' 🙂
'; $output .= '' . sprintf( wp_kses( /* translators: %s - charitable.com upgrade URL. */ __( 'To unlock more features consider upgrading to PRO.', 'charitable' ), array( 'a' => array( 'href' => array(), 'class' => array(), 'target' => array(), 'rel' => array(), ), 'strong' => array(), ) ), esc_url( charitable_pro_upgrade_url( 'settings-upgrade' ) ) ) . '
'; $output .= '' . wp_kses( __( 'As a valued Charitable Lite user, you receive up to $300 off, automatically applied at checkout!', 'charitable' ), array( 'strong' => array(), 'br' => array(), ) ) . '
'; if ( $valid && false === $error ) { $output .= '' . esc_html__( 'Already registered? You might have an expired or invalid license. Reach out to us for support.', 'charitable' ) . '
'; } elseif ( ! $valid && isset( $license_data['license_limit'] ) && false !== $license_data['license_limit'] ) { $output .= '' . esc_html__( 'There was an error attempting to validate your license key. Check and see if you have exceeded your license activations.', 'charitable' ) . '
'; } elseif ( ! $valid && isset( $license_data['comm_success'] ) && false !== $license_data['comm_success'] ) { $output .= '' . esc_html__( 'There was an error attempting to contact the license server. Please try again later.', 'charitable' ) . '
'; // phpcs:disable WordPress.Security.NonceVerification.Recommended } elseif ( isset( $_GET['valid'] ) && 'invalid' === sanitize_text_field( wp_unslash( $_GET['valid'] ) ) && isset( $_GET['comm_success'] ) && 0 === intval( wp_unslash( $_GET['comm_success'] ) ) ) { $output .= '' . esc_html__( 'There was an error attempting to validate your license key. Please try again later.', 'charitable' ) . '
'; } elseif ( isset( $_GET['valid'] ) && 'invalid' === sanitize_text_field( wp_unslash( $_GET['valid'] ) ) && isset( $_GET['license_limit'] ) && false !== sanitize_text_field( wp_unslash( $_GET['license_limit'] ) ) ) { $output .= '' . esc_html__( 'There was an error attempting to validate your license key. Check and see if you have exceeded your license activations.', 'charitable' ) . '
'; } elseif ( isset( $_GET['valid'] ) && 'invalid' === sanitize_text_field( wp_unslash( $_GET['valid'] ) ) ) { $output .= '' . esc_html__( 'There was a problem attempting to validate your license key. Please try again later.', 'charitable' ) . '
'; // phpcs:enable WordPress.Security.NonceVerification.Recommended } else { $output .= '' . esc_html__( 'Already purchased? Simply enter your license key below to enable Charitable PRO!', 'charitable' ) . '
'; } return $output; } /** * Outputs a message for licnensed users for the general settings tab. * * @since 1.7.0.4 * @version 1.8.1.15 added license expiring message, account for no license expire date (lifetime licenses). * * @param boolean $force_valid Force valid license (not used). * @return string */ public function get_licensed_message( $force_valid = true ) { // phpcs:ignore $settings = get_option( 'charitable_settings' ); $price_id = intval( $settings['licenses']['charitable-v2']['plan_id'] ); $license_expires = ! empty( $settings['licenses']['charitable-v2']['expiration_date'] ) ? esc_html( $settings['licenses']['charitable-v2']['expiration_date'] ) : false; $valid = $settings['licenses']['charitable-v2']['valid']; $output = ''; if ( $valid ) { $plan_name = $this->get_license_label_from_plan_id( $price_id ); $output = '' . esc_html__( 'You\'re using ', 'charitable' ); $output .= 'Charitable ' . $plan_name . ''; if ( $license_expires ) : if ( 'lifetime' === $license_expires ) : $output .= esc_html__( '. You have a lifetime license', 'charitable' ); else : $output .= esc_html__( '. Your license expires on ', 'charitable' ); $output .= gmdate( 'M d, Y', strtotime( $license_expires ) ); endif; endif; $output .= esc_html__( '. Enjoy!', 'charitable' ) . ' 🙂
'; if ( $this->is_license_expiring() ) { $output .= '' . sprintf( wp_kses( /* translators: %s - charitable.com upgrade URL. */ __( 'Your license may be expiring soon. Please renew to continue receiving updates and support. Learn more.', 'charitable' ), array( 'a' => array( 'href' => array(), 'class' => array(), 'target' => array(), 'rel' => array(), ), 'br' => array(), 'strong' => array(), ) ), 'https://wpcharitable.com/documentation/expired-expiring-license' ) . '
'; } elseif ( $this->is_license_expired() ) { $output .= '' . sprintf( wp_kses( /* translators: %s - charitable.com upgrade URL. */ __( 'It appears your license may have expired. Please renew to continue receiving updates and support. Learn more.', 'charitable' ), array( 'a' => array( 'href' => array(), 'class' => array(), 'target' => array(), 'rel' => array(), ), 'br' => array(), 'strong' => array(), ) ), 'https://wpcharitable.com/documentation/expired-expiring-license' ) . '
'; } if ( defined( 'CHARITABLE_DEBUG_LICENSE' ) && CHARITABLE_DEBUG_LICENSE ) { $output .= ''; if ( charitable_is_debug() ) { error_log( 'get_licensed_message - valid' ); // phpcs:ignore error_log( print_r( $settings, true ) ); // phpcs:ignore error_log( print_r( $plan_name, true ) ); // phpcs:ignore } } } else { $plan_name = esc_html__( 'Lite', 'charitable' ); if ( intval( $price_id ) > 0 ) { $valid = esc_html__( 'Your license is not valid or has expired.', 'charitable' ); } else { $valid = false; } $output .= $this->get_unlicensed_message( $valid ); if ( defined( 'CHARITABLE_DEBUG_LICENSE' ) && CHARITABLE_DEBUG_LICENSE ) { if ( charitable_is_debug() ) { error_log( 'get_licensed_message - not valid' ); // phpcs:ignore error_log( print_r( $valid, true ) ); // phpcs:ignore } } } return $output; } /** * Outputs a message for deactivated licenses for the general settings tab. * * @since 1.8.5 * @return string */ public function get_deactivated_message() { return sprintf( '' ); } /** * Get license label from plan id. * * @since 1.8.0 * * @param boolean $plan_id Plan ID. * @return string */ public function get_license_label_from_plan_id( $plan_id = false ) { return charitable_get_license_label_from_plan_id( $plan_id ); } /** * Outputs a message for unlicnensed users for the general settings tab. * * @since 1.7.0.4 * @version 1.8.3.2 remove upsell message. * @return string */ public function get_legacy_licensed_message() { $output = '' . sprintf( wp_kses( /* translators: %s - charitable.com upgrade URL. */ __( 'You\'re using Charitable with one or more activated legacy licenses. Enjoy! 🙂', 'charitable' ), array( 'a' => array( 'href' => array(), 'class' => array(), 'target' => array(), 'rel' => array(), ), 'br' => array(), 'strong' => array(), ) ), esc_url( admin_url( 'admin.php?page=charitable-settings&tab=advanced' ) ) ) . '
'; return $output; } /** * Activates the pro plugin after a license is activated. * * @since 1.8.5.3 * @return void */ public function activate_pro_plugin_after_license_activation() { // Check query params. if ( ! isset( $_GET['valid'] ) || 'valid' !== $_GET['valid'] ) { // phpcs:ignore return; } if ( ! isset( $_GET['pro'] ) || 'activate' !== $_GET['pro'] ) { // phpcs:ignore return; } // Confirm pro plugin is installed. if ( ! file_exists( WP_PLUGIN_DIR . '/charitable-pro/charitable.php' ) ) { return; } // Activate the pro plugin. activate_plugin( 'charitable-pro/charitable.php' ); // Redirect to the settings page. wp_safe_redirect( admin_url( 'admin.php?page=charitable-settings&tab=general&valid=valid' ) ); exit; } } endif;