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 ""; // phpcs:ignore if ( ! empty( $field['args']['label_for'] ) ) { echo ''; // phpcs:ignore echo ''; call_user_func( $field['callback'], $field['args'] ); echo ''; } elseif ( ! empty( $field['title'] ) ) { if ( $field['args']['type'] === 'heading' && isset( $field['args']['help'] ) ) { // if this is a heading display a "help" as a subheading. echo '

' . esc_html( $field['title'] ) . '

'; echo '

' . sanitize_text_field( $field['args']['help'] ) . '

'; // phpcs:ignore } else { echo '

' . charitable_santitize_setting_labels( $field['title'] ) . '

'; // phpcs:ignore } echo ''; if ( $field['args']['type'] !== 'heading' ) { echo ''; call_user_func( $field['callback'], $field['args'] ); echo ''; } } else { echo ''; call_user_func( $field['callback'], $field['args'] ); echo ''; } echo ''; } } /** * Add new tab to the Charitable settings area. * * @since 1.3.0 * * @param string[] $tabs The existing tabs. * @param string $key The key for the new tab. * @param string $name The name of the new tab. * @param mixed[] $args Additional arguments for the new tab. * @return string[] */ function charitable_add_settings_tab( $tabs, $key, $name, $args = array() ) { $defaults = array( 'index' => 3, ); $args = wp_parse_args( $args, $defaults ); $keys = array_keys( $tabs ); $values = array_values( $tabs ); array_splice( $keys, $args['index'], 0, $key ); array_splice( $values, $args['index'], 0, $name ); return array_combine( $keys, $values ); } /** * Returns whether we are currently viewing the Charitable tools area. * * @since 1.8.1.6 * * @param string $tab Optional. If passed, the function will also check that we are on the given tab. * @return boolean */ function charitable_is_tools_view( $tab = '' ) { if ( ! empty( $_POST ) ) { // phpcs:ignore $is_settings = array_key_exists( 'option_page', $_POST ) && 'charitable_tools' === $_POST['option_page']; // phpcs:ignore if ( ! $is_settings || empty( $tab ) ) { return $is_settings; } return array_key_exists( 'charitable_tools', $_POST ) && array_key_exists( $tab, $_POST['charitable_tools'] ); // phpcs:ignore } $is_settings = isset( $_GET['page'] ) && 'charitable-tools' === $_GET['page']; // phpcs:ignore if ( ! $is_settings || empty( $tab ) ) { return $is_settings; } /* Check for sub-tab format (e.g. 'import__charitable'). */ if ( false !== strpos( $tab, '__' ) ) { $tab_parts = explode( '__', $tab ); $main_tab = $tab_parts[0]; $sub_tab = $tab_parts[1]; return isset( $_GET['tab'] ) && $main_tab == $_GET['tab'] // phpcs:ignore && isset( $_GET['sub_tab'] ) && $sub_tab == $_GET['sub_tab']; // phpcs:ignore } /* 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.8.1.6 * * @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_tools_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 ""; // phpcs:ignore if ( ! empty( $field['args']['label_for'] ) ) { echo ''; // phpcs:ignore echo ''; call_user_func( $field['callback'], $field['args'] ); echo ''; } elseif ( ! empty( $field['title'] ) ) { if ( $field['args']['type'] === 'heading' && isset( $field['args']['help'] ) ) { // if this is a heading display a "help" as a subheading. echo '

' . esc_html( $field['title'] ) . '

'; echo '

' . sanitize_text_field( $field['args']['help'] ) . '

'; // phpcs:ignore } else { echo '

' . charitable_santitize_setting_labels( $field['title'] ) . '

'; // phpcs:ignore } echo ''; if ( $field['args']['type'] !== 'heading' ) { echo ''; call_user_func( $field['callback'], $field['args'] ); echo ''; } } else { echo ''; call_user_func( $field['callback'], $field['args'] ); echo ''; } echo ''; } } /** * Return the donation actions class. * * @since 1.5.0 * * @return Charitable_Donation_Admin_Actions */ function charitable_get_donation_actions() { return Charitable_Admin::get_instance()->get_donation_actions(); } /** * Adds the "Upgrade to Pro" menu item to the very end of the submenu. * * @since 1.7.0 */ function charitable_add_upgrade_item() { global $submenu; if ( charitable_is_pro() ) { return; } $submenu['charitable'][99] = array( // phpcs:ignore __( 'Upgrade to Pro', 'charitable' ), 'manage_options', charitable_ga_url( 'https://wpcharitable.com/lite-vs-pro/', urlencode( 'Admin Menu Link' ), urlencode( 'Upgrade to Pro' ) ), ); } add_action( 'admin_menu', 'charitable_add_upgrade_item' ); /** * Determines if Charitable allows legacy campaigns to be created. * * @since 1.8.2 * * @return bool */ function charitable_disable_legacy_campaigns() { $disable_legacy_campaign = charitable_get_option( 'disable_campaign_legacy_mode', false ) ? true : false; $disable_legacy_campaign = apply_filters( 'charitable_disable_legacy_campaign', $disable_legacy_campaign ); return $disable_legacy_campaign; } /** * Outputs "please rate" text. * * @since 1.7.0 * @version 1.8.8.6 * * @param string $footer_text Footer text. * @return string */ function charitable_add_footer_text( $footer_text ) { if ( ! charitable_is_admin_screen() ) { return $footer_text; } return sprintf( /* translators: %1$s Opening strong tag, do not translate. %2$s Closing strong tag, do not translate. %3$s Opening anchor tag, do not translate. %4$s Closing anchor tag, do not translate. */ __( 'Please rate %1$sCharitable%2$s %3$s★★★★★%4$s on %3$sWordPress.org%4$s to help us spread the word. Thank you from the Charitable team!', 'charitable' ), '', '', '', '' ); } add_filter( 'admin_footer_text', 'charitable_add_footer_text' ); /** * Check if the promotion footer should be displayed. * * @since 1.8.7.2 * * @return bool */ function charitable_show_promotion_footer() { // Some 3rd-party addons may use page slugs that start with `charitable-` // so we should define exact pages we want the footer to be displayed on instead // of targeting any page that looks like a WPForms page. $plugin_pages = [ 'charitable-dashboard', 'charitable-reports', 'charitable-tools', 'charitable-settings', 'charitable-about', 'charitable-education', 'charitable-growth-tools', 'charitable-intergration-wpcode', 'charitable-system-info', 'charitable-notices', ]; $allowed_page = false; // Is this a post_type page of 'campaign' or 'donation'? if ( isset( $_REQUEST['post_type'] ) && in_array( $_REQUEST['post_type'], [ 'campaign', 'donation' ], true ) ) { // phpcs:ignore WordPress.Security.NonceVerification $allowed_page = true; } if ( charitable_is_admin_screen() ) { $allowed_page = true; } if ( ! $allowed_page ) { // phpcs:ignore WordPress.Security.NonceVerification $current_page = isset( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : ''; if ( ! in_array( $current_page, $plugin_pages, true ) ) { $allowed_page = false; } } return apply_filters( 'charitable_show_promotion_footer', $allowed_page ); } /** * Pre-footer promotion block, displayed on all Charitable admin pages except Form Builder. * * @since 1.8.7.2 * @version 1.8.8 - Updated plugin footer links. */ function charitable_promote_footer() { if ( ! charitable_show_promotion_footer() ) { return; } $links = [ [ 'url' => charitable_is_pro() ? charitable_utm_link( 'https://charitable.com/account/support/', 'Plugin Footer', 'Contact Support' ) : 'https://wordpress.org/support/plugin/charitable/', 'text' => __( 'Support', 'charitable' ), 'target' => '_blank', ], [ 'url' => charitable_utm_link( 'https://wpcharitable.com/documentation/', 'Plugin Footer', 'Plugin Documentation' ), 'text' => __( 'Docs', 'charitable' ), 'target' => '_blank', ], [ 'url' => admin_url( 'admin.php?page=charitable-about' ), 'text' => __( 'Free Plugins', 'charitable' ), ], ]; echo charitable_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 'admin/templates/promotion', [ 'title' => sprintf( /* translators: %s: Heart icon */ __( 'Made with %s by the Charitable Team', 'charitable' ), '♥' ), 'links' => $links, ], true ); } add_action( 'in_admin_footer', 'charitable_promote_footer' ); /** * Check if a screen is a plugin admin view. * Returns the screen id if true, false (bool) if not. * * @since 1.7.0 * @since 1.8.1.5 Added another check and 'donation' to first check. * * @return string|bool */ function charitable_is_admin_screen() { $screen = \get_current_screen(); if ( ! $screen ) { return false; } if ( 'charitable' === $screen->post_type || 'campaign' === $screen->post_type || 'donation' === $screen->post_type || 'charitable' === $screen->parent_file ) { return 'charitable'; } if ( isset( $_GET['page'] ) ) { // phpcs:ignore if ( 'charitable' === trim( $_GET['page'] ) ) { // phpcs:ignore return 'charitable'; } } if ( isset( $_REQUEST['page'] ) && strpos( sanitize_text_field( $_REQUEST['page'] ), 'charitable' ) !== false ) { // phpcs:ignore return 'charitable'; } return false; } /** * Appends UTM parameters to a given URL. * * @since 1.7.0 * * @param string $base_url Base URL. * @param string $utm_medium utm_medium parameter. * @param string $utm_content Optional. utm_content parameter. * @return string $url Full Google Analytics campaign URL. */ function charitable_ga_url( $base_url, $utm_medium, $utm_content = false ) { /** * Filters the UTM campaign for generated links. * * @since 1.7.0 * * @param string $utm_campaign */ $utm_campaign = apply_filters( 'charitable_utm_campaign', 'WP+Charitable' ); $args = array( 'utm_source' => 'WordPress', 'utm_campaign' => $utm_campaign, 'utm_medium' => $utm_medium, ); if ( ! empty( $utm_content ) ) { $args['utm_content'] = $utm_content; } return esc_url( add_query_arg( $args, $base_url ) ); } /** * URL for upgrading to Pro (or another Pro licecnse). * * @since 1.7.0 * * @param string $utm_medium utm_medium parameter. * @param string $utm_content Optional. utm_content parameter. * @return string */ function charitable_pro_upgrade_url( $utm_medium, $utm_content = '' ) { return apply_filters( 'charitable_upgrade_link', charitable_ga_url( 'https://wpcharitable.com/lite-vs-pro/', urlencode( $utm_medium ), urlencode( $utm_content ) ), $utm_medium, $utm_content ); } /** * Get the current installation license type (always lowercase). * * @since 1.7.0.3 * * @return string|false */ function charitable_get_license_type() { $type = charitable_setting( 'type', '', 'charitable_license' ); if ( empty( $type ) || ! charitable()->is_pro() ) { return false; } return strtolower( $type ); } /** * Get when WPCharitable was first installed. * * @since 1.6.0 * * @param string $type Specific install type to check for. * * @return int|false Unix timestamp. False on failure. */ function charitable_get_activated_timestamp( $type = '' ) { $activated = (array) get_option( 'charitable_activated', [] ); if ( empty( $activated ) ) { return false; } // When a passed install type is empty, then get it from a DB. // If it is installed/activated first, it is saved first. $type = empty( $type ) ? (string) array_keys( $activated )[0] : $type; if ( ! empty( $activated[ $type ] ) ) { return absint( $activated[ $type ] ); } // Fallback. $types = array_diff( [ 'lite', 'pro' ], [ $type ] ); foreach ( $types as $_type ) { if ( ! empty( $activated[ $_type ] ) ) { return absint( $activated[ $_type ] ); } } return false; } /** * Get when WPCharitable was first installed. * * @since 8.1.0 * * @param string $type Specific install type to check for. * * @return int|false Unix timestamp. False on failure. */ function charitable_get_updated_timestamp( $type = '' ) { $activated = (array) get_option( 'charitable_activated', [] ); if ( empty( $activated ) ) { return false; } // When a passed install type is empty, then get it from a DB. // If it is installed/activated first, it is saved first. $type = empty( $type ) ? (string) array_keys( $activated )[0] : $type; if ( ! empty( $activated[ $type ] ) ) { return absint( $activated[ $type ] ); } // Fallback. $types = array_diff( [ 'lite', 'pro' ], [ $type ] ); foreach ( $types as $_type ) { if ( ! empty( $activated[ $_type ] ) ) { return absint( $activated[ $_type ] ); } } return false; } /** * Determines whether the current request is a WP CLI request. * * @since 1.7.0.3 * * @return bool */ function charitable_doing_wp_cli() { return defined( 'WP_CLI' ) && WP_CLI; } /** * Modify the default USer-Agent generated by wp_remote_*() to include additional information. * * @since 1.7.0.3 * * @return string */ function charitable_get_default_user_agent() { $wpcharitable_type = function_exists( 'charitable_is_pro' ) && charitable_is_pro() ? 'Paid' : 'Lite'; return 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ) . '; WPCharitable/' . $wpcharitable_type; } /** * Get the template paths. * * @since 1.8.0 * * @return array */ function charitable_theme_template_paths() { $template_dir = 'charitable'; $file_paths = array( 1 => trailingslashit( get_stylesheet_directory() ) . $template_dir, 10 => trailingslashit( get_template_directory() ) . $template_dir, 100 => trailingslashit( CHARITABLE_DIRECTORY_PATH ) . 'includes', ); $file_paths = apply_filters( 'charitable_helpers_templates_get_theme_template_paths', $file_paths ); // Sort the file paths based on priority. ksort( $file_paths, SORT_NUMERIC ); return array_map( 'trailingslashit', $file_paths ); } /** * Locate a template and return the path for inclusion. * * @since 1.7.0.3 * * @param string $template_name Template name. * * @return string */ function charitable_locate_template( $template_name ) { // Trim off any slashes from the template name. $template_name = ltrim( $template_name, '/' ); if ( empty( $template_name ) ) { return apply_filters( 'charitable_helpers_templates_locate', '', $template_name ); } $located = ''; // Try locating this template file by looping through the template paths. foreach ( charitable_theme_template_paths() as $template_path ) { if ( file_exists( $template_path . $template_name ) ) { $located = $template_path . $template_name; break; } } return apply_filters( 'charitable_helpers_templates_locate', $located, $template_name ); } /** * Include a template. * Use 'require' if $args are passed or 'load_template' if not. * * @since 1.7.0.3 * * @param string $template_name Template name. * @param array $args Arguments. * @param bool $extract Extract arguments. * * @throws \RuntimeException If extract() tries to modify the scope. */ function charitable_admin_include_html( $template_name, $args = array(), $extract = false ) { $template_name .= '.php'; // Allow 3rd party plugins to filter template file from their plugin. $located = apply_filters( 'charitable_helpers_templates_include_html_located', charitable_locate_template( $template_name ), $template_name, $args, $extract ); $args = apply_filters( 'charitable_helpers_templates_include_html_args', $args, $template_name, $extract ); if ( empty( $located ) || ! \is_readable( $located ) ) { return; } // Load template WP way if no arguments were passed. if ( empty( $args ) ) { load_template( $located, false ); return; } $extract = apply_filters( 'charitable_helpers_templates_include_html_extract_args', $extract, $template_name, $args ); if ( $extract && is_array( $args ) ) { // Extract variables for template, checking for conflicts to prevent scope modification. $created_vars_count = 0; foreach ( $args as $var_name => $var_value ) { // Only create variable if it doesn't already exist (equivalent to EXTR_SKIP). if ( ! isset( $$var_name ) ) { $$var_name = $var_value; $created_vars_count++; } } // Protecting existing scope from modification. if ( count( $args ) !== $created_vars_count ) { throw new \RuntimeException( 'Extraction failed: variable names are clashing with the existing ones.' ); } } require $located; } /** * Like self::include_html, but returns the HTML instead of including. * * @since 1.7.0.3 * * @param string $template_name Template name. * @param array $args Arguments. * @param bool $extract Extract arguments. * * @return string */ function charitable_admin_get_html( $template_name, $args = array(), $extract = false ) { ob_start(); charitable_admin_include_html( $template_name, $args, $extract ); return ob_get_clean(); } /** * Include a template - alias to \charitable\Helpers\Template::get_html. * Use 'require' if $args are passed or 'load_template' if not. * * @since 1.7.0.3 * * @param string $template_name Template name. * @param array $args Arguments. * @param bool $extract Extract arguments. * * @throws \RuntimeException If extract() tries to modify the scope. * * @return string Compiled HTML. */ function charitable_render( $template_name, $args = array(), $extract = false ) { return charitable_admin_get_html( $template_name, $args, $extract ); } /** * Determine if the plugin/addon installations are allowed. * * @since 1.6.2.3 * * @param string $type Should be `plugin` or `addon`. * * @return bool */ function charitable_can_install( $type ) { return charitable_can_do( 'install', $type ); } /** * Determine if the plugin/addon activations are allowed. * * @since 1.7.3 * * @param string $type Should be `plugin` or `addon`. * * @return bool */ function charitable_can_activate( $type ) { return charitable_can_do( 'activate', $type ); } /** * Determine if the plugin/addon installations/activations are allowed. * * @since 1.7.3 * @since 1.8.4.3 // Removed 'Addons require additional license checks' comments. * * @internal Use charitable_can_activate() or charitable_can_install() instead. * * @param string $what Should be 'activate' or 'install'. * @param string $type Should be `plugin` or `addon`. * * @return bool */ function charitable_can_do( $what, $type ) { if ( ! in_array( $what, [ 'install', 'activate' ], true ) ) { return false; } if ( ! in_array( $type, [ 'plugin', 'addon' ], true ) ) { return false; } $capability = $what . '_plugins'; if ( ! current_user_can( $capability ) ) { return false; } // Determine whether file modifications are allowed and it is activation permissions checking. if ( $what === 'install' && ! wp_is_file_mod_allowed( 'charitable_can_install' ) ) { return false; } // All plugin checks are done. if ( $type === 'plugin' ) { return true; } return true; } /** * Perform test connection to verify that the current web host can successfully * make outbound SSL connections. * * @since 1.4.5 */ function charitable_verify_ssl() { // Run a security check. check_ajax_referer( 'charitable-admin-tools', 'nonce' ); // Check for permissions. if ( ! charitable_current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'msg' => esc_html__( 'You do not have permission to perform this operation.', 'charitable' ), ) ); } $response = wp_remote_post( 'https://wpcharitable.com/connection-test.php' ); if ( 200 === wp_remote_retrieve_response_code( $response ) ) { wp_send_json_success( array( 'msg' => esc_html__( 'Success! Your server can make SSL connections.', 'charitable' ), ) ); } wp_send_json_error( array( 'msg' => esc_html__( 'There was an error and the connection failed. Please contact your web host with the technical details below.', 'charitable' ), 'debug' => '
' . 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 ( 0 !== intval( strpos( $download_url, 'https://www.wpcharitable.com' ) ) ) { 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_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/', ), 'https://app.wpcharitable.com/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
and tags and their respective attributes. * * @since 1.8.4 * * @param mixed $content The content to sanitize. * * @return string */ function charitable_santitize_setting_labels( $content ) { $allowed_tags = array( 'div' => array( 'class' => array(), 'id' => array(), ), 'span' => array( 'class' => array(), 'id' => array(), ), ); return wp_kses( $content, $allowed_tags ); } /** * Check if the pro plugin is installed. * * @since 1.8.5.3 * * @return bool */ function charitable_is_pro_installed() { $pro_plugin_path = 'charitable-pro/charitable.php'; if ( ! file_exists( WP_PLUGIN_DIR . '/' . $pro_plugin_path ) ) { return false; } return true; } /** * Display admin bar when active. * * @since 1.8.7.5 * @version 1.8.9.1 * * @param WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance, passed by reference. * * @return bool */ function charitable_show_test_mode_notice_in_admin_bar( $wp_admin_bar ) { $is_test_mode = charitable_get_option( 'test_mode' ) ? true : false; // Allow filtering to disable the admin bar notice $show_notice = apply_filters( 'charitable_show_test_mode_admin_bar_notice', true ); if ( ! current_user_can( 'manage_options' ) || ! $is_test_mode || ! $show_notice ) { return false; } // Add the main site admin menu item. $wp_admin_bar->add_menu( [ 'id' => 'charitable-test-notice', 'href' => admin_url( 'admin.php?page=charitable-settings&tab=gateways' ), 'parent' => 'top-secondary', 'title' => '' . esc_html__( 'Charitable Test Mode Active', 'charitable' ) . '', 'meta' => [ 'class' => 'charitable-test-mode-active', 'onclick' => 'sessionStorage.setItem("charitable_from_admin_bar", "true");', ], ] ); return true; } add_action( 'admin_bar_menu', 'charitable_show_test_mode_notice_in_admin_bar', 1000, 1 ); /** * Render the global upgrade CTA section. * * This function provides a consistent upgrade CTA that can be used across all admin pages. * It uses the same styling and content as the dashboard v2 upgrade section. * * @since 1.8.8 * * @param string $css_class Optional. Additional CSS classes to add to the section. * @param bool $echo Optional. Whether to echo the output or return it. Default is true. * @return string|void The HTML output if $echo is false, otherwise void. */ function charitable_render_global_upgrade_cta( $css_class = '', $echo = true ) { // Don't show upgrade CTA if user already has Pro license if ( charitable_is_pro() ) { return $echo ? '' : ''; } $output = ''; $output .= '
'; $output .= '

' . esc_html__( 'Upgrade to Pro to Unlock Powerful Donation Features', 'charitable' ) . '

'; $output .= '
'; $output .= '
'; $output .= charitable_render_global_upgrade_features_left(); $output .= '
'; $output .= '
'; $output .= charitable_render_global_upgrade_features_right(); $output .= '
'; $output .= '
'; $output .= ''; $output .= '
'; if ( $echo ) { echo $output; // phpcs:ignore } else { return $output; } } /** * Render left column upgrade features for global CTA. * * @since 1.8.8 * * @return string The HTML output for left column features. */ function charitable_render_global_upgrade_features_left() { $features = array( __( 'Recurring Donations', 'charitable' ), __( 'Peer-to-Peer Fundraising', 'charitable' ), __( 'Donor Database', 'charitable' ), __( 'Donor Dashboard', 'charitable' ), __( 'PDF & Annual Receipts', 'charitable' ) ); $output = ''; foreach ( $features as $feature ) { $output .= '
'; $output .= ''; $output .= ''; $output .= ''; $output .= '' . esc_html( $feature ) . ''; $output .= '
'; } return $output; } /** * Render right column upgrade features for global CTA. * * @since 1.8.8 * * @return string The HTML output for right column features. */ function charitable_render_global_upgrade_features_right() { $features = array( __( 'Anonymous Giving', 'charitable' ), __( 'Fee Coverage Option', 'charitable' ), __( 'Email Marketing Integration', 'charitable' ), __( 'Built-In Analytics & Reports', 'charitable' ), __( 'Flexible Payment Methods', 'charitable' ) ); $output = ''; foreach ( $features as $feature ) { $output .= '
'; $output .= ''; $output .= ''; $output .= ''; $output .= '' . esc_html( $feature ) . ''; $output .= '
'; } return $output; }