get_path( 'admin' ) . 'views/'; /** * Filter the path to the view. * * @since 1.0.0 * * @param string $path The default path. * @param string $view The view. * @param array $view_args View args. */ $filename = apply_filters( 'charitable_admin_view_path', $base_path . $view . '.php', $view, $view_args ); if ( ! is_readable( $filename ) ) { charitable_get_deprecated()->doing_it_wrong( __FUNCTION__, sprintf( /* translators: %s: Filename of passed view */ __( 'Passed view (%s) not found or is not readable.', 'charitable' ), $filename ), '1.0.0' ); return false; } ob_start(); include $filename; if ( $return_html ) { $output = ob_get_contents(); ob_end_clean(); return $output; } ob_end_flush(); return true; } /** * Returns the Charitable_Settings helper. * * @since 1.0.0 * * @return Charitable_Settings */ function charitable_get_admin_settings() { return Charitable_Settings::get_instance(); } /** * Returns the Charitable_Reports helper. * * @since 1.8.1 * * @return Charitable_Reports */ function charitable_get_admin_reports() { return Charitable_Reports::get_instance(); } /** * Returns the Charitable_Dashboard_Legacy helper. * * @since 1.8.1 * * @return Charitable_Dashboard_Legacy */ function charitable_get_admin_dashboard() { return Charitable_Dashboard_Legacy::get_instance(); } /** * Returns the Charitable_Tools helper. * * @since 1.8.1.6 * * @return Charitable_Tools */ function charitable_get_admin_tools() { return Charitable_Tools::get_instance(); } /** * Returns the Charitable_Intergrations_WPCode helper. * * @since 1.8.1.6 * * @return Charitable_Intergrations_WPCode */ function charitable_get_intergration_wpcode() { return Charitable_Intergrations_WPCode::get_instance(); } /** * Returns the Charitable_Tools_System_Info helper. * * @since 1.8.1.6 * * @return Charitable_Tools_System_Info */ function charitable_get_system_info() { return Charitable_Tools_System_Info::get_instance(); } /** * Returns the Charitable_Admin_Notices helper. * * @since 1.4.6 * * @return Charitable_Admin_Notices */ function charitable_get_admin_notices() { return charitable()->registry()->get( 'admin_notices' ); } /** * Returns whether we are currently viewing the Charitable settings area. * * @since 1.2.0 * * @param string $tab Optional. If passed, the function will also check that we are on the given tab. * @return boolean */ function charitable_is_settings_view( $tab = '' ) { if ( ! empty( $_POST ) ) { // phpcs:ignore $is_settings = array_key_exists( 'option_page', $_POST ) && 'charitable_settings' === $_POST['option_page']; // phpcs:ignore if ( ! $is_settings || empty( $tab ) ) { return $is_settings; } return array_key_exists( 'charitable_settings', $_POST ) && array_key_exists( $tab, $_POST['charitable_settings'] ); // phpcs:ignore } $is_settings = isset( $_GET['page'] ) && 'charitable-settings' == $_GET['page']; // phpcs:ignore if ( ! $is_settings || empty( $tab ) ) { return $is_settings; } /* The general tab can be loaded when tab is not set. */ if ( 'general' == $tab ) { return ! isset( $_GET['tab'] ) || 'general' == $_GET['tab']; // phpcs:ignore } return isset( $_GET['tab'] ) && $tab == $_GET['tab']; // phpcs:ignore } /** * Print out the settings fields for a particular settings section. * * This is based on WordPress' do_settings_fields but allows the possibility * of leaving out a field lable/title, for fullwidth fields. * * @see do_settings_fields * * @since 1.0.0 * * @global $wp_settings_fields Storage array of settings fields and their pages/sections * * @param string $page Slug title of the admin page who's settings fields you want to show. * @param string $section Slug title of the settings section who's fields you want to show. * @return string */ function charitable_do_settings_fields( $page, $section ) { global $wp_settings_fields; if ( ! isset( $wp_settings_fields[ $page ][ $section ] ) ) { return; } foreach ( (array) $wp_settings_fields[ $page ][ $section ] as $field ) { $class = ''; if ( ! empty( $field['args']['class'] ) ) { $class = ' class="' . esc_attr( $field['args']['class'] ) . '"'; } echo "
' . sanitize_text_field( $field['args']['help'] ) . '
'; // phpcs:ignore } else { echo '' . sanitize_text_field( $field['args']['help'] ) . '
'; // phpcs:ignore } else { echo '' . print_r( map_deep( $response, 'wp_strip_all_tags' ), true ) . '', // phpcs:ignore ) ); } add_action( 'wp_ajax_charitable_verify_ssl', 'charitable_verify_ssl' ); /** * Deactivate addon. * * @since 1.0.0 * @since 1.6.2.3 Updated the permissions checking. */ function charitable_deactivate_addon() { // Run a security check. check_ajax_referer( 'charitable-admin', 'nonce' ); // Check for permissions. if ( ! current_user_can( 'deactivate_plugins' ) ) { wp_send_json_error( esc_html__( 'Plugin deactivation is disabled for you on this site.', 'charitable' ) ); } $type = empty( $_POST['type'] ) ? 'addon' : sanitize_key( $_POST['type'] ); if ( isset( $_POST['plugin'] ) ) { $plugin = sanitize_text_field( wp_unslash( $_POST['plugin'] ) ); if ( charitable_is_debug() ) { error_log( 'charitable_deactivate_addon' ); // phpcs:ignore error_log( print_r( $plugin, true ) ); // phpcs:ignore error_log( print_r( $_POST, true ) ); // phpcs:ignore error_log( print_r( $type, true ) ); // phpcs:ignore } deactivate_plugins( $plugin ); do_action( 'charitable_plugin_deactivated', $plugin ); if ( $type === 'plugin' ) { wp_send_json_success( esc_html__( 'Plugin deactivated.', 'charitable' ) ); } else { wp_send_json_success( esc_html__( 'Addon deactivated.', 'charitable' ) ); } } wp_send_json_error( esc_html__( 'Could not deactivate the addon. Please deactivate from the Plugins page.', 'charitable' ) ); } add_action( 'wp_ajax_charitable_deactivate_addon', 'charitable_deactivate_addon' ); /** * Activate addon. * * @since 1.0.0 * @since 1.6.2.3 Updated the permissions checking. */ function charitable_ajax_activate_addon() { // Run a security check. check_ajax_referer( 'charitable-admin', 'nonce' ); // Check for permissions. if ( ! current_user_can( 'activate_plugins' ) ) { wp_send_json_error( esc_html__( 'Plugin activation is disabled for you on this site.', 'charitable' ) ); } $type = 'addon'; if ( isset( $_POST['plugin'] ) ) { if ( ! empty( $_POST['type'] ) ) { $type = sanitize_key( $_POST['type'] ); } $plugin = sanitize_text_field( wp_unslash( $_POST['plugin'] ) ); $activate = activate_plugins( $plugin ); /** * Fire after plugin activating via the Charitable installer. * * @since 1.6.3.1 * * @param string $plugin Path to the plugin file relative to the plugins directory. */ do_action( 'charitable_plugin_activated', $plugin ); if ( ! is_wp_error( $activate ) ) { if ( $type === 'plugin' ) { wp_send_json_success( esc_html__( 'Plugin activated.', 'charitable' ) ); } else { wp_send_json_success( esc_html__( 'Addon activated.', 'charitable' ) ); } } } if ( $type === 'plugin' ) { wp_send_json_error( esc_html__( 'Could not activate the plugin. Please activate it on the Plugins page.', 'charitable' ) ); } wp_send_json_error( esc_html__( 'Could not activate the addon. Please activate it on the Plugins page.', 'charitable' ) ); } add_action( 'wp_ajax_charitable_activate_addon', 'charitable_ajax_activate_addon' ); /** * Installs an Charitable addon. * * @since 1.0.0 * @version 1.8.6.2 */ function charitable_ajax_install_addon() { // Run a security check first. check_admin_referer( 'charitable-admin', 'nonce' ); // Permission check. if ( ! current_user_can( 'install_plugins' ) ) { wp_send_json_error( esc_html__( 'Plugin install is disabled for you on this site.', 'charitable' ) ); } // Install the addon. if ( isset( $_POST['plugin'] ) ) { $download_url = esc_url_raw( wp_unslash( $_POST['plugin'] ) ); global $hook_suffix; // If the url doesn't start with 'https://www.wpcharitable.com/', then return an error. if ( strpos( $download_url, 'https://www.wpcharitable.com/' ) !== 0 ) { wp_send_json_error( array( 'basename' => $download_url, 'is_activated' => false, 'msg' => esc_html__( 'Addon not installed. Invalid download URL.', 'charitable' ), ) ); } if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) { if ( charitable_is_debug() ) { error_log( 'charitable_ajax_install_addon' ); // phpcs:ignore error_log( print_r( $_POST, true ) ); // phpcs:ignore error_log( print_r( $download_url, true ) ); // phpcs:ignore } } // Set the current screen to avoid undefined notices. set_current_screen(); // Prepare variables. $method = ''; $url = add_query_arg( array( 'page' => 'charitable-settings', ), admin_url( 'admin.php' ) ); $url = esc_url( $url ); // Start output bufferring to catch the filesystem form if credentials are needed. ob_start(); $creds = request_filesystem_credentials( $url, $method, false, false, null ); if ( false === $creds ) { $form = ob_get_clean(); echo wp_json_encode( array( 'form' => $form ) ); die; } if ( charitable_is_debug() ) { error_log( 'charitable_ajax_install_addon creds' ); // phpcs:ignore error_log( print_r( $creds, true ) ); // phpcs:ignore } // If we are not authenticated, make it happen now. if ( ! WP_Filesystem( $creds ) ) { ob_start(); request_filesystem_credentials( $url, $method, true, false, null ); $form = ob_get_clean(); echo wp_json_encode( array( 'form' => $form ) ); die; } // Do not allow WordPress to search/download translations, as this will break JS output. remove_action( 'upgrader_process_complete', [ 'Language_Pack_Upgrader', 'async_upgrade' ], 20 ); // We do not need any extra credentials if we have gotten this far, so let's install the plugin. require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; require_once plugin_dir_path( CHARITABLE_DIRECTORY_PATH ) . 'charitable/includes/utilities/Skin.php'; // Create the plugin upgrader with our custom skin. $skin = new Charitable_Skin(); $installer = new Plugin_Upgrader( $skin ); $installer->install( $download_url ); // Flush the cache and return the newly installed plugin basename. wp_cache_flush(); if ( $installer->plugin_info() ) { $plugin_basename = $installer->plugin_info(); $plugin = sanitize_text_field( wp_unslash( $_POST['plugin'] ) ); // attempt to activate the installed addon, save the user a step. $activate = activate_plugins( $plugin_basename ); if ( ! is_wp_error( $activate ) ) { $response = array( 'basename' => $plugin_basename, 'is_activated' => true, 'msg' => esc_html__( 'Addon installed and activated.', 'charitable' ), ); // When Charitable Pro is what was just installed and activated, point // the user at the Charitable dashboard. Activating Pro auto-deactivates // Lite, which means the addons screen they're currently on (served by // Lite) no longer exists in the menu — without a redirect they'd land // on a 404 / blank admin page once the AJAX returns. Pro's main file // lives at charitable-pro/charitable.php, so we match by directory. if ( 0 === strpos( $plugin_basename, 'charitable-pro/' ) ) { $response['redirect'] = admin_url( 'admin.php?page=charitable-dashboard' ); } wp_send_json_success( $response ); } else { wp_send_json_success( array( 'basename' => $plugin_basename, 'msg' => esc_html__( 'Addon installed.', 'charitable' ), ) ); } die; } } // Send back a response. echo wp_json_encode( true ); die; } add_action( 'wp_ajax_charitable_install_addon', 'charitable_ajax_install_addon' ); /** * Keep an option updated to know what/when a warning went out for a third party plugin, theme, etc. * * @since 1.7.0.8 */ function charitable_update_third_party_warning_option() { $third_party_warnings = get_option( 'charitable_third_party_warnings' ); $updated = false; // If the option is empty or wiped (which is possible), initialize it with an empty array. $third_party_warnings = ( false === $third_party_warnings ) ? array( 'plugins' => array() ) : $third_party_warnings = json_decode( $third_party_warnings, ARRAY_A ); if ( is_plugin_active( 'code-snippets/code-snippets.php' ) && ! array_key_exists( 'code-snippets/code-snippets.php', $third_party_warnings['plugins'] ) ) { $third_party_warnings['plugins']['code-snippets/code-snippets.php'] = 'noted'; $updated = true; } elseif ( ! is_plugin_active( 'code-snippets/code-snippets.php' ) && array_key_exists( 'code-snippets/code-snippets.php', $third_party_warnings['plugins'] ) && $third_party_warnings['plugins']['code-snippets/code-snippets.php'] === 'noted' ) { unset( $third_party_warnings['plugins']['code-snippets/code-snippets.php'] ); $updated = true; } $third_party_warnings = apply_filters( 'charitable_update_third_party_warning_option', $third_party_warnings ); if ( $updated ) { $result = update_option( 'charitable_third_party_warnings', wp_json_encode( $third_party_warnings ) ); } } add_action( 'admin_init', 'charitable_update_third_party_warning_option' ); /** * Get an option related to third party warning ( null, noted, dismissed ) * * @since 1.7.0.8 * * @param string $plugin_path Plugin path. * @param string $category Category. */ function charitable_get_third_party_warning_option( $plugin_path = false, $category = 'plugins' ) { if ( false === $plugin_path ) { return false; } $third_party_warnings = get_option( 'charitable_third_party_warnings' ); if ( false === $third_party_warnings ) { return false; } $third_party_warnings = json_decode( $third_party_warnings, ARRAY_A ); if ( ! isset( $third_party_warnings[ $category ][ $plugin_path ] ) ) { return false; } return esc_html( $third_party_warnings[ $category ][ $plugin_path ] ); } /** * Get an option related to third party warning ( null, noted, dismissed ) * * @since 1.7.0.8 * * @param string $plugin_path Plugin path. * @param string $value Value. * @param string $category Category. */ function charitable_set_third_party_warning_option( $plugin_path = false, $value = false, $category = 'plugins' ) { if ( false === $plugin_path ) { return; } $third_party_warnings = get_option( 'charitable_third_party_warnings' ); if ( false === $third_party_warnings ) { return; } $third_party_warnings = json_decode( $third_party_warnings, ARRAY_A ); $third_party_warnings[ $category ][ $plugin_path ] = $value; $result = update_option( 'charitable_third_party_warnings', wp_json_encode( $third_party_warnings ) ); return $result; } /** * Return the latest versions of Charitable plugins. * * @since 1.8.0 * * @param array $licenses Licenses. * @param string $update_url Update URL. * * @return array */ function charitable_get_addons_data_from_server( $licenses = false, $update_url = 'https://wpcharitable.com' ) { $versions = get_transient( '_charitable_plugin_versions' ); if ( false === $versions ) { if ( false === $licenses ) { $licenses = array(); foreach ( charitable_get_licenses() as $license ) { if ( isset( $license['license'] ) ) { $licenses[] = $license['license']; } } } $response = wp_remote_post( $update_url . '/edd-api/versions-v3/', array( 'sslverify' => false, 'timeout' => 15, 'body' => array( 'licenses' => $licenses, 'url' => home_url(), ), ) ); $response_body = wp_remote_retrieve_body( $response ); $response_code = wp_remote_retrieve_response_code( $response ); // Bail out early if there are any errors. if ( (int) $response_code !== 200 || is_wp_error( $response_body ) ) { return false; } $versions = json_decode( $response_body, true ); set_transient( '_charitable_plugin_versions', $versions, DAY_IN_SECONDS ); } // end if return $versions; } if ( ! function_exists( 'charitable_get_licenses' ) ) { /** * Return the list of licenses. * * Note: The licenses are not necessarily valid. If a user enters an invalid * license, the license will be stored but it will be flagged as invalid. * * @since 1.8.0 * * @return array[] */ function charitable_get_licenses() { return charitable_get_option( 'licenses', array() ); } } /** * Get license label from plan id. * * @since 1.8.0 * * @param boolean $plan_id Plan ID. * @return string */ function charitable_get_license_label_from_plan_id( $plan_id = false ) { if ( ! $plan_id ) { $settings = get_option( 'charitable_settings' ); $plan_id = ! empty( $settings['licenses']['charitable-v2']['plan_id'] ) ? intval( $settings['licenses']['charitable-v2']['plan_id'] ) : false; } if ( ! $plan_id ) { $plan_name = 'Lite'; } switch ( $plan_id ) { case 1: $plan_name = 'Basic'; break; case 2: $plan_name = 'Plus'; break; case 3: $plan_name = 'Pro'; break; case 4: $plan_name = 'Elite'; break; default: $plan_name = 'Lite'; break; } return $plan_name; } /** * Get license label from plan id. * * @since 1.8.0 * * @param boolean $plan_id Plan ID. * @return string */ function charitable_get_license_slug_from_plan_id( $plan_id = false ) { return sanitize_title( charitable_get_license_label_from_plan_id( $plan_id ) ); } /** * Hide non-Charitable warnings. * * @since 1.8.1.5 * @version 1.8.8.6 * * @return void */ function hide_non_charitable_warnings() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Function name is hooked via add_action and changing it would break existing functionality. // Bail if we're not on a charitable screen (another type of check). if ( false === charitable_is_admin_screen() ) { return; } charitable_filter_admin_notices( 'user_admin_notices', 'charitable' ); charitable_filter_admin_notices( 'admin_notices', 'charitable' ); charitable_filter_admin_notices( 'all_admin_notices', 'charitable' ); } /** * Filter notices. * * @since 1.8.1.5 * * @param string $notice_type Notice type. * @param string $prefix Prefix. * * @return void */ function charitable_filter_admin_notices( $notice_type, $prefix ) { global $wp_filter; if ( ! empty( $wp_filter[ $notice_type ]->callbacks ) && is_array( $wp_filter[ $notice_type ]->callbacks ) ) { foreach ( $wp_filter[ $notice_type ]->callbacks as $priority => $hooks ) { foreach ( $hooks as $name => $arr ) { if ( is_object( $arr['function'] ) && $arr['function'] instanceof Closure ) { unset( $wp_filter[ $notice_type ]->callbacks[ $priority ][ $name ] ); continue; } if ( ! empty( $arr['function'][0] ) && is_object( $arr['function'][0] ) && false !== strpos( strtolower( get_class( $arr['function'][0] ) ), $prefix ) ) { continue; } if ( ! empty( $name ) && false === strpos( $name, $prefix ) ) { unset( $wp_filter[ $notice_type ]->callbacks[ $priority ][ $name ] ); } } } } } add_action( 'admin_print_scripts', 'hide_non_charitable_warnings' ); add_action( 'admin_head', 'hide_non_charitable_warnings', PHP_INT_MAX ); /** * Remove a dashboard notification via AJAX. * * @since 1.8.2 * * @return void */ function charitable_disable_dashboard_notification_ajax() { // Run a security check. check_ajax_referer( 'charitable-admin', 'nonce' ); $notification_id = isset( $_POST['notification_id'] ) ? sanitize_text_field( wp_unslash( $_POST['notification_id'] ) ) : false; if ( ! $notification_id ) { wp_send_json_error( array( 'message' => esc_html__( 'Invalid notification ID.', 'charitable' ) ) ); } $notifications = (array) get_option( 'charitable_dashboard_notifications', array() ); if ( empty( $notifications ) ) { wp_send_json_error( array( 'message' => esc_html__( 'No notifications found.', 'charitable' ) ) ); } if ( ! empty( $notifications[ $notification_id ] ) ) { // add a 'dismissed' key to the notification with the current time. $notifications[ $notification_id ]['dismissed'] = time(); update_option( 'charitable_dashboard_notifications', $notifications ); /** * Fires after a dashboard rail notification has been dismissed. * * Listeners can attach side effects keyed off the dismissed slug — * e.g. the resume-setup-wizard module clears * `charitable_started_onboarding` when its banner is dismissed, * so the underlying onboarding state is also turned off, not just * the banner. * * @since 1.8.10.5 * * @param string $notification_id Slug of the dismissed notification. */ do_action( 'charitable_dashboard_notification_dismissed', $notification_id ); wp_send_json_success( array( 'message' => esc_html__( 'Notification removed.', 'charitable' ) ) ); } else { wp_send_json_error( array( 'message' => esc_html__( 'Notification not found.', 'charitable' ) ) ); } } add_action( 'wp_ajax_charitable_disable_dashboard_notification', 'charitable_disable_dashboard_notification_ajax' ); /** * Get a checkbox wrapped with markup to be displayed as a toggle. * * @since 1.8.2 * * @param bool $checked Is it checked or not. * @param string $name The name for the input. * @param string $description Field description (optional). * @param string|int $value Field value (optional). * @param string $label Field label (optional). * * @return string */ function charitable_get_checkbox_toggle( $checked, $name, $description = '', $value = '', $label = '' ) { $markup = ''; if ( ! empty( $label ) ) { $markup .= ''; } if ( ! empty( $description ) ) { $markup .= '
' . wp_kses_post( $description ) . '
'; } return $markup; } /** * Gets the unique site ID or token. * This is generated from the home URL and two random pieces of data * to create a hashed site ID that can anonymize data. * * @since 1.8.4 * * @return string */ function charitable_get_site_token() { $wpchar_site_token = get_option( 'wpchar_telemetry_uuid' ); if ( empty( $wpchar_site_token ) ) { $home_url = get_home_url(); $uuid = wp_generate_uuid4(); $today = gmdate( 'now' ); $wpchar_site_token = md5( $home_url . $uuid . $today ); update_option( 'wpchar_telemetry_uuid', $wpchar_site_token, false ); } return $wpchar_site_token; } if ( ! function_exists( 'charitable_onboarding_app_url' ) ) : /** * Base URL of the centrally hosted onboarding wizard, without a trailing slash. * * Filterable so a development site can be pointed at a local instance of the * wizard. The wizard has no staging environment, so without this the only way * to exercise the hand-off between it and a site is against production. * * add_filter( 'charitable_onboarding_app_url', function () { * return 'http://127.0.0.1:8131'; * } ); * * Unfiltered the return value is unchanged from the previously hardcoded * literal, so this is a no-op in production. * * @since 1.8.12.4 * * @return string */ function charitable_onboarding_app_url() { return untrailingslashit( apply_filters( 'charitable_onboarding_app_url', 'https://app.wpcharitable.com' ) ); } endif; if ( ! function_exists( 'charitable_get_onboarding_url' ) ) : /** * Get the URL for the first screen of the onboarding process. * * @since 1.8.4 * * @return string */ function charitable_get_onboarding_url() { $current_site_url = get_site_url(); return add_query_arg( array( 'token' => charitable_get_site_token(), 'version' => charitable()->get_version(), 'utm_campaign' => 'onboarding_charitable_lite', 'email' => get_option( 'admin_email' ), // generate a random session id to prevent caching. 'sessionid' => wp_rand( 10000000, 99999999 ), 'return' => rawurlencode( base64_encode( get_admin_url( null, 'admin.php') ) // phpcs:ignore ), 'update_to_pro_url' => 'https://app.charitable.com/upgrade-free-to-pro?api_token=REPLACE_API_TOKEN&license_key=REPLACE_LICENSE_KEY&oth=11ecbadab9561202d33b5ffb8405f9cb9b783af17b52c4b16e16bd8fbbd6cdccbd2a5445c2cb456cb11cdd555471c19e5e2ad446450df2f4e0fc70e410a814d4&endpoint=&siteurl=' . $current_site_url . '/wp-admin/', ), charitable_onboarding_app_url() . '/setup-wizard-charitable_lite' ); } endif; if ( ! function_exists( 'charitable_get_usage_tracking_setting' ) ) : /** * Get the usage tracking setting. * * @since 1.8.4 * * @return string */ function charitable_get_usage_tracking_setting() { return (int) get_option( 'charitable_usage_tracking', false ); } endif; if ( ! function_exists( 'charitable_update_usage_tracking_setting' ) ) : /** * Update the usage tracking setting. * * @since 1.8.4 * * @param int $value The new value (0 or 1). */ function charitable_update_usage_tracking_setting( $value ) { // update options. update_option( 'charitable_usage_tracking', $value ); // update Charitable settings to match. $settings = get_option( 'charitable_settings' ); $settings['charitable_usage_tracking'] = $value ? true : false; update_option( 'charitable_settings', $settings ); } endif; /** * Santitize setting labels to allow only