'.trim($code)); return ob_get_clean(); } /** * Buffers (gets) function output. * * A variable length of additional arguments are possible. * Additional parameters get passed into the ``$function``. * * @package s2Member\Utilities * @since 3.5 * * @param string $function Name of a function to call upon. * * @return string Output after call to function. * Any output is buffered and returned. */ public static function get($function = '') { $args = func_get_args(); $function = array_shift($args); if(is_string($function) && $function) { ob_start(); // Open output buffer. if(is_array($args) && !empty($args)) $return = call_user_func_array($function, $args); else // There are no additional arguments to pass. $return = call_user_func($function); $echo = ob_get_clean(); // Close buffer. return !strlen($echo) && strlen($return) ? $return : $echo; } return NULL; } /** * Builds a version checksum for this installation. * * @package s2Member\Utilities * @since 3.5 * * @return string String with `[version]-[pro version]-[consolidated checksum]`. */ public static function ver_checksum() { $checksum = WS_PLUGIN__S2MEMBER_VERSION; // Software version string. $checksum .= (c_ws_plugin__s2member_utils_conds::pro_is_installed()) ? '-'.WS_PLUGIN__S2MEMBER_PRO_VERSION : ''; // Pro version string? $checksum .= '-'.abs(crc32($GLOBALS['WS_PLUGIN__']['s2member']['c']['checksum'].$GLOBALS['WS_PLUGIN__']['s2member']['o']['options_checksum'].$GLOBALS['WS_PLUGIN__']['s2member']['o']['options_version'])); return $checksum; // (i.e., version-pro version-checksum) } /** * String with current time details. * * @package s2Member\Utilities * @since 130210 * * @return string String with time representation (in UTC time). */ public static function time_details() { $time = time(); // The time at this very moment. $details = date('D M jS, Y', $time).' @ precisely '.date('g:i a e', $time); return $details; // Return all details. } /** * String with all version details *(for PHP, WordPress, s2Member, and Pro)*. * * @package s2Member\Utilities * @since 3.5 * * @return string String with `PHP vX.XX :: WordPress vX.XX :: s2Member vX.XX :: s2Member Pro vX.XX`. */ public static function ver_details() { $details = 'PHP v'.PHP_VERSION.' :: WordPress v'.get_bloginfo('version').' :: s2Member v'.WS_PLUGIN__S2MEMBER_VERSION; $details .= (c_ws_plugin__s2member_utils_conds::pro_is_installed()) ? ' :: s2Member Pro v'.WS_PLUGIN__S2MEMBER_PRO_VERSION : ''; return $details; // Return all details. } /** * Generates s2Member Security Badge. * * @package s2Member\Utilities * @since 3.5 * * @param string $v A variation number to display. Defaults to `1`. * @param bool $no_cache Defaults to false. If true, the HTML markup will contain query string params that prevent caching. * @param bool $display_on_failure . Defaults to false. True if we need to display the 'NOT yet verified' version inside admin panels. * * @return string HTML markup for display of s2Member Security Badge. */ public static function s_badge_gen($v = '1', $no_cache = FALSE, $display_on_failure = FALSE) { if($v && file_exists(($template = dirname(dirname(__FILE__)).'/templates/badges/s-badge.php'))) { switch((int)$v) // Width/height based on variation. { case 1: // Variation number 1. $width_height_attrs = 'width="200" height="55"'; $width_height_styles = 'width:200px; height:55px;'; break; // Break switch loop. case 2: // Variation number 2. $width_height_attrs = 'width="180" height="58"'; $width_height_styles = 'width:180px; height:58px;'; break; // Break switch loop. case 3: // Variation number 3. $width_height_attrs = 'width="80" height="15"'; $width_height_styles = 'width:80px; height:15px;'; break; // Break switch loop. default: // Default case handler. $width_height_attrs = $width_height_styles = ''; break; // Break switch loop. } $badge = trim(c_ws_plugin__s2member_utilities::evl(file_get_contents($template))); $badge = preg_replace('/%%v%%/i', (string)(int)$v, $badge); $badge = preg_replace('/%%site_url%%/i', urlencode(home_url()), $badge); $badge = preg_replace('/%%no_cache%%/i', $no_cache ? '&no_cache='.urlencode(mt_rand()) : '', $badge); $badge = preg_replace('/%%display_on_failure%%/i', $display_on_failure ? '&display_on_failure=1' : '', $badge); $badge = preg_replace(array('/%%width_height_attrs%%/i', '/%%width_height_styles%%/i'), array($width_height_attrs, $width_height_styles), $badge); } return !empty($badge) ? $badge : ''; // Return Security Badge. } /** * Acquires information about memory usage. * * @package s2Member\Utilities * @since 110815 * * @return string String with `Memory x MB :: Real Memory x MB :: Peak Memory x MB :: Real Peak Memory x MB`. */ public static function mem_details() { $memory = number_format(memory_get_usage() / 1048576, 2, '.', ''); $real_memory = number_format(memory_get_usage(TRUE) / 1048576, 2, '.', ''); $peak_memory = number_format(memory_get_peak_usage() / 1048576, 2, '.', ''); $real_peak_memory = number_format(memory_get_peak_usage(TRUE) / 1048576, 2, '.', ''); $details = 'Memory '.$memory.' MB :: Real Memory '.$real_memory.' MB :: Peak Memory '.$peak_memory.' MB :: Real Peak Memory '.$real_peak_memory.' MB'; return $details; // Return all details. } /** * Acquires s2Member options for the Main Site of a Multisite Network. * * @package s2Member\Utilities * @since 110912 * * @return array Array of s2Member options for the Main Site. */ public static function mms_options() { return (is_multisite()) ? (array)get_site_option('ws_plugin__s2member_options') : array(); } /** * Builds an array of backtrace callers. * * @package s2Member\Utilities * @since 110912 * * @param array $debug_backtrace Optional. Defaults to ``debug_backtrace()``. * * @return array Array of backtrace callers (lowercase). */ public static function callers($debug_backtrace = NULL) { $callers = array(); // Initialize array. foreach(($debug_backtrace = is_array($debug_backtrace) ? $debug_backtrace : debug_backtrace()) as $caller) if(isset($caller['class'], $caller['function']) || (!isset($caller['class']) && isset($caller['function']))) $callers[] = isset($caller['class']) ? $caller['class'].'::'.$caller['function'] : $caller['function']; return array_map('strtolower', array_unique($callers)); } /** * Sends an email using WordPress' `wp_mail()`, supporting HTML formatting and auto From header. * * Automatically sets Content-Type to `text/html` if HTML markers are found. * If no `From:` header is provided, adds one using s2Member options via `email_config()` filters. * * @since 250605 * * @param string|string[] $to Recipient email address(es). * @param string $subject Email subject line. * @param string $message Email body. May contain HTML. * @param string|string[] $headers Optional. Additional headers. * @param string|string[] $attachments Optional. File paths to attach. * * @return bool True if the email was sent successfully, false otherwise. */ public static function mail($to, $subject, $message, $headers = '', $attachments = []) { // If From: not already in headers, add it using configured options. if (stripos($headers, 'From:') === false) { c_ws_plugin__s2member_email_configs::email_config(); // Setup From filters $from_email = isset($GLOBALS['WS_PLUGIN__']['s2member']['o']['reg_email_from_email']) ? $GLOBALS['WS_PLUGIN__']['s2member']['o']['reg_email_from_email'] : ''; $from_name = isset($GLOBALS['WS_PLUGIN__']['s2member']['o']['reg_email_from_name']) ? str_replace('"', "'", $GLOBALS['WS_PLUGIN__']['s2member']['o']['reg_email_from_name']) : ''; $headers .= (empty($headers) ? '' : "\r\n") . 'From: "' . $from_name . '" <' . $from_email . '>'; c_ws_plugin__s2member_email_configs::email_config_release(); // Remove filters. } // Check HTML tags. $is_html = false; foreach (['', ' if HTML emails are disabled. * Displays a soft warning if the database charset may strip emojis. * * @since 250610 * * @param string $option_key The s2Member option key (w/o prefix). * @param string $editor_id Optional. The DOM ID for the editor. Defaults to derived from option_key. * @param array $editor_args Optional. Extra arguments for `wp_editor()`. */ public static function editor($option_key, $editor_id = '', array $editor_args = []) { $value = isset($GLOBALS['WS_PLUGIN__']['s2member']['o'][$option_key]) ? $GLOBALS['WS_PLUGIN__']['s2member']['o'][$option_key] : ''; $editor_id = $editor_id ? $editor_id : strtr('ws_plugin__s2member_' . $option_key, ['_' => '-']); // If HTML emails are enabled, use wp_editor() $html_enabled = !empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['html_emails_enabled']); if ($html_enabled) { $args = wp_parse_args($editor_args, [ 'textarea_name' => 'ws_plugin__s2member_' . $option_key, 'textarea_rows' => 20, 'editor_height' => null, 'media_buttons' => false, 'teeny' => false, 'quicktags' => true, 'tinymce' => [ 'height' => 400, 'resize' => true, 'wp_autoresize_on' => false, 'toolbar1' => 'formatselect | bold italic underline strikethrough | bullist numlist blockquote | alignleft aligncenter alignright | link unlink | undo redo | removeformat', 'toolbar2' => '', ], ]); wp_editor($value, $editor_id, $args); } else { echo '
' . "\n"; } // Check charset and warn about emojis. global $wpdb; $collation = $wpdb->get_var( $wpdb->prepare( "SELECT COLLATION_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'option_value'", $wpdb->dbname, $wpdb->options ) ); $charset = $collation ? strtolower(preg_replace('/_.+$/', '', $collation)) : ''; if ($charset && $charset !== 'utf8mb4') { echo '
'; echo '⚠️ Note: Your database uses the ' . esc_html($charset) . ' charset for WordPress option values. Changes to your email will be lost if you use emojis, as they require the utf8mb4 charset.'; echo '
'; } } /** * Cancels a subscription using a gateway-aware dispatch routine. * * This is designed for replacement/modification flows where the old subscription must be * canceled after a new checkout begins. Callers should pass the previously captured * subscription details from before proxy/IPN processing updates the user's current profile. * * For PayPal-family subscriptions, this uses captured signup/proxy vars first when available * as a product hint, and then falls back to trying other configured cancellation routines * that are still plausible for the captured subscription context. //260407 * * Gateway/product handlers may no longer be configured or loaded by the current installation. * In those cases this routine fails safely and returns `FALSE` instead of calling unavailable code. * * @package s2Member\Utilities * @since 260407 * * @param string|bool $subscr_gateway Previously captured `s2member_subscr_gateway`. * @param string|bool $subscr_id Previously captured `s2member_subscr_id`. * @param string|bool $subscr_baid Previously captured `s2member_subscr_baid`. * @param string|bool $subscr_cid Previously captured `s2member_subscr_cid`. * @param array $ipn_signup_vars Previously captured signup/proxy vars for product-specific routing. * @param bool $immediate Optional. If false, cancel at period end where supported. * @param string $reason Optional human-readable cancellation reason for APIs that accept one. * * @return bool True if a cancellation request was accepted; else false. */ public static function cancel_gateway_subscription($subscr_gateway = FALSE, $subscr_id = FALSE, $subscr_baid = FALSE, $subscr_cid = FALSE, $ipn_signup_vars = array(), $immediate = TRUE, $reason = '') { $subscr_gateway = strtolower((string)$subscr_gateway); $subscr_id = (string)$subscr_id; $subscr_baid = (string)$subscr_baid; $subscr_cid = (string)$subscr_cid; $ipn_signup_vars = (is_array($ipn_signup_vars)) ? $ipn_signup_vars : array(); if(!$subscr_gateway || !$subscr_id) return FALSE; switch($subscr_gateway) { case 'stripe': if(!$subscr_cid || !class_exists('c_ws_plugin__s2member_pro_stripe_utilities')) return FALSE; //260407 Stripe replacement flows need the guard around old-subscription cancellation. c_ws_plugin__s2member_pro_stripe_utilities::set_replacement_cancellation_guard($subscr_id); if(!is_object(c_ws_plugin__s2member_pro_stripe_utilities::cancel_customer_subscription($subscr_cid, $subscr_id, !$immediate))) { c_ws_plugin__s2member_pro_stripe_utilities::clear_replacement_cancellation_guard($subscr_id); return FALSE; } return TRUE; case 'authnet': if(!class_exists('c_ws_plugin__s2member_pro_authnet_utilities')) return FALSE; return (($authnet = c_ws_plugin__s2member_pro_authnet_utilities::authnet_arb_response(array('x_method' => 'cancel', 'x_subscription_id' => $subscr_id))) && empty($authnet['__error'])) ? TRUE : FALSE; case 'paypal': $paypal_proxy_use = (!empty($ipn_signup_vars['s2member_paypal_proxy_use'])) ? (string)$ipn_signup_vars['s2member_paypal_proxy_use'] : ''; $paypal_invoice = (!empty($ipn_signup_vars['invoice'])) ? (string)$ipn_signup_vars['invoice'] : ''; $ppco_hint = ($paypal_proxy_use === 'paypal_checkout' || strpos($paypal_invoice, 's2mpf-') === 0); $can_try_ppco = FALSE; $can_try_standard = FALSE; $can_try_payflow = FALSE; $paypal_checkout_creds = c_ws_plugin__s2member_paypal_utilities::paypal_checkout_creds(); $can_try_ppco = (!empty($paypal_checkout_creds['client_id']) && !empty($paypal_checkout_creds['secret'])); $can_try_standard = (!empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_api_username']) && !empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_api_password']) && !empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_api_signature'])); $can_try_payflow = (!empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_payflow_api_username']) && !empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_payflow_api_partner']) && !empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_payflow_api_vendor']) && !empty($GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_payflow_api_password']) && class_exists('c_ws_plugin__s2member_pro_paypal_utilities')); //260819.0543 Use positive product provenance where available; I-* is ambiguous, while Payflow profile IDs use RP/RT prefixes. $payflow_hint = (bool)preg_match('/^R[PT]/i', $subscr_id); if($ppco_hint) $paypal_routes = array('ppco', 'standard', 'payflow'); else if($payflow_hint) $paypal_routes = array('payflow', 'standard', 'ppco'); else $paypal_routes = array('standard', 'ppco', 'payflow'); $reason = trim((string)$reason); if(!$reason) $reason = 'Cancelled by replacement checkout.'; foreach($paypal_routes as $_paypal_route) { switch($_paypal_route) { case 'ppco': if(!$can_try_ppco) break; if(($paypal_checkout = c_ws_plugin__s2member_paypal_utilities::paypal_checkout_subscription_cancel($subscr_id, $reason)) && !empty($paypal_checkout['code']) && ($paypal_checkout['code'] === 204 || ($paypal_checkout['code'] >= 200 && $paypal_checkout['code'] <= 299))) return TRUE; break; case 'standard': if(!$can_try_standard) break; if(($paypal_standard = c_ws_plugin__s2member_paypal_utilities::paypal_standard_subscription_cancel($subscr_id)) && empty($paypal_standard['__error'])) return TRUE; break; case 'payflow': if(!$can_try_payflow) break; if(c_ws_plugin__s2member_pro_paypal_utilities::payflow_cancel_profile($subscr_id, $subscr_baid)) return TRUE; break; } } } return FALSE; } } }