get_id(); } /** * Get clean WooCommerce price HTML. * * @param \WC_Product $product Product instance. * * @return string */ public static function get_price_html( $product ) { if ( ! $product instanceof \WC_Product ) { return ''; } $price_html = (string) $product->get_price_html(); // Remove accessibility-only text $price_html = preg_replace( '/]*class=["\'][^"\']*screen-reader-text[^"\']*["\'][^>]*>.*?<\/span>/is', '', $price_html ); // Fix broken / structure (common Woo issues) // Ensure closing tags exist if ( substr_count( $price_html, '' ) ) { $price_html .= ''; } if ( substr_count( $price_html, '' ) ) { $price_html .= ''; } // Fix unclosed span tags $open_spans = substr_count( $price_html, '' ); if ( $open_spans > $close_spans ) { $price_html .= str_repeat( '', $open_spans - $close_spans ); } // Final HTML balance if ( function_exists( 'force_balance_tags' ) ) { $price_html = force_balance_tags( $price_html ); } return trim( $price_html ); } /** * Get current product price formatted to store precision. * * @return string|false */ public static function get_product_value() { $product = function_exists('wc_get_product') ? wc_get_product() : 0; if (!is_object($product) || !method_exists($product, 'get_price')) { return false; } return self::format_price_with_precision($product->get_price()); } /** * Get current store currency. * * @return string */ public static function get_currency() { return function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : ''; } /** * Getting current order object * * @return mixed */ public static function get_order() { $order = function_exists('wc_get_order') ? wc_get_order() : false; if (!is_object($order) || !method_exists($order, 'get_id') || !$order->get_id()) { return self::get_order_thankyou(); } return $order; } /** * Sanitize product description for JSON-safe output * * @param string|null $desc * @return string */ public static function get_sanitize_description( $desc ) { // Ensure scalar input (some editors/meta can return arrays/objects). if ( is_array( $desc ) || is_object( $desc ) ) { $desc = ''; } $clean = (string) ( $desc ?? '' ); // Try to normalize to UTF-8 (helps when descriptions contain mixed encodings). if ( function_exists( 'mb_detect_encoding' ) && function_exists( 'mb_convert_encoding' ) ) { // Some PHP/mbstring builds don't support certain aliases (e.g. Windows-1255) and can throw ValueError. $candidates = array( 'UTF-8', 'CP1255', 'ISO-8859-8', 'ISO-8859-1' ); if ( function_exists( 'mb_list_encodings' ) ) { $supported = array_map( 'strtoupper', (array) mb_list_encodings() ); $candidates = array_values( array_filter( $candidates, static function ( $e ) use ( $supported ) { return in_array( strtoupper( $e ), $supported, true ); } ) ); } // If list is empty for some reason, fall back to UTF-8 only. if ( empty( $candidates ) ) { $candidates = array( 'UTF-8' ); } $enc = mb_detect_encoding( $clean, $candidates, true ); if ( $enc && 'UTF-8' !== strtoupper( $enc ) ) { $clean = mb_convert_encoding( $clean, 'UTF-8', $enc ); } } // Strip/replace invalid UTF-8 sequences. $clean = wp_check_invalid_utf8( $clean ); if ( false === $clean ) { $clean = ''; } // If description contains a meta description tag (escaped or raw), use its content ONLY // when the description is essentially just that meta tag (otherwise keep normal cleaning). $meta_src = html_entity_decode( $clean, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); // Work on decoded HTML so `` (even if originally `<meta`) can be detected/removed reliably. $clean = $meta_src; if ( preg_match( '/]*name\\s*=\\s*(?:\"|\')description(?:\"|\')\\s+[^>]*content\\s*=\\s*(?:\"|\')([^\"\\\']*)(?:\"|\\\')/iu', $meta_src, $m ) ) { $meta_desc = trim( (string) $m[1] ); $meta_desc = preg_replace( '/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/u', '', $meta_desc ); $meta_desc = preg_replace( '/\\s+/', ' ', $meta_desc ); // Remove the meta tag(s) and see if anything meaningful remains. $without_meta = preg_replace( '/]*>/iu', ' ', $meta_src ); $without_meta = wp_strip_all_tags( $without_meta ); $without_meta = html_entity_decode( (string) $without_meta, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); $without_meta = preg_replace( '/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]/u', '', $without_meta ); $without_meta = preg_replace( '/\\s+/', ' ', $without_meta ); $without_meta = trim( $without_meta ); // If there is no other content besides the meta tag, return meta content. if ( $without_meta === '' ) { return trim( $meta_desc ); } // Remove the meta fragment even if it's malformed (missing '>') by stripping up to the end of the content attribute. $clean = preg_replace( '/]*name\\s*=\\s*(?:\"|\')description(?:\"|\')\\s+[^>]*content\\s*=\\s*(?:\"|\')[^\"\\\']*(?:\"|\\\')/iu', ' ' . $meta_desc . ' ', $clean, 1 ); // Otherwise continue with normal sanitization (do not early return). } // Remove embedded media blocks (video/audio/images) from rich editors like Elementor. // Strip full blocks first to avoid leaving media URLs in the resulting plain text. $clean = preg_replace( '/]*class=[\"\\\'][^\"\\\']*wp-video[^\"\\\']*[\"\\\'][^>]*>.*?<\\/div>/is', ' ', $clean ); $clean = preg_replace( '/]*class=[\"\\\'][^\"\\\']*wp-block-(video|audio|embed)[^\"\\\']*[\"\\\'][^>]*>.*?<\\/figure>/is', ' ', $clean ); $clean = preg_replace( '/<(video|audio)\\b[^>]*>.*?<\\/(video|audio)>/is', ' ', $clean ); $clean = preg_replace( '/]*>/is', ' ', $clean ); $clean = preg_replace( '/]*>/is', ' ', $clean ); $clean = preg_replace( '/]*>/is', ' ', $clean ); $clean = preg_replace( '/<(noscript|script)\\b[^>]*>.*?<\\/(noscript|script)>/is', ' ', $clean ); // Remove anchor links entirely (href + linked text), so URLs don't leak into plain text. $clean = preg_replace( '/]*>.*?<\/a>/is', ' ', $clean ); // Remove all HTML tags but keep text $clean = wp_strip_all_tags( $clean ); // Decode HTML entities to plain text $clean = html_entity_decode( (string) $clean, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); // Then remove the marker when it precedes a number. $clean = preg_replace( '/[<<]\s*(?=\d)/u', '', $clean ); // Normalize real newlines $clean = str_replace( [ "\r\n", "\n", "\r" ], ' ', $clean ); // Strip ASCII control characters that can break some JSON parsers. $clean = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $clean ); // Remove WordPress shortcodes (e.g., [gallery], [video src=...]). $clean = preg_replace( '/\[[^\]]+\]/', ' ', $clean ); // Remove plain URLs. $clean = preg_replace( '#https?://[^\s<>"]+#i', ' ', $clean ); // Remove common www-style URLs. $clean = preg_replace( '#\bwww\.[^\s]+#i', ' ', $clean ); // Collapse whitespace. $clean = preg_replace( '/\s+/', ' ', $clean ); $clean = trim( $clean ); // Final guard: ensure the string can be safely JSON-encoded (no invalid UTF-8/control chars). // If encoding fails, remove problematic bytes and retry. if ( false === json_encode( array( 'd' => $clean ) ) ) { // Remove any remaining ASCII control chars. $clean = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $clean ); // Drop invalid UTF-8 sequences (WP helper). $tmp = wp_check_invalid_utf8( $clean ); if ( false !== $tmp ) { $clean = $tmp; } else { $clean = ''; } // As a last resort, ignore invalid bytes via iconv if available. if ( $clean !== '' && function_exists( 'iconv' ) ) { $iconv = @iconv( 'UTF-8', 'UTF-8//IGNORE', $clean ); if ( is_string( $iconv ) ) { $clean = $iconv; } } // Re-collapse whitespace after cleanup. $clean = preg_replace( '/\s+/', ' ', (string) $clean ); $clean = trim( $clean ); } return $clean; } /** * Resolve order object from "order-received" endpoint. * * @return \WC_Order|false */ public static function get_order_thankyou() { // Only in thankyou "Order-received" page if (!is_wc_endpoint_url('order-received')) { return false; } global $wp; // Get the order ID $order_id = absint($wp->query_vars['order-received']); if (empty($order_id)) { return false; } return wc_get_order($order_id); } /** * Resolve order object from GET params (order + key). * Read-only access; verified against order key. * * @return \WC_Order|false */ public static function get_order_by_get_params() { // check if the order ID exists and if is set the order key if (!isset($_GET['order'], $_GET['key'])) { return false; } $order_id = (int)$_GET['order']; //get order object $order = wc_get_order($order_id); //order exists check the order key passed is isset and if it is the same of the order $order_key = $order->get_order_key(); if (isset($_GET['key']) && $_GET['key'] !== $order_key) { return false; } return $order; } /** * Getting order total price * * @param $_order mixed; order id | order object WP_Post | order object instanceof WC_Abstract_Order * * @return bool|float */ public static function get_order_value($_order) { $order = function_exists('wc_get_order') ? wc_get_order($_order) : false; if (!is_object($order) || !method_exists($order, 'get_total')) { return false; } return $order->get_total(); } /** * Getting order total price without shipping * * @param $_order mixed; order id | order object WP_Post | order object instanceof WC_Abstract_Order * * @return bool|float */ public static function get_order_value_no_shipping($_order) { $order = function_exists('wc_get_order') ? wc_get_order($_order) : false; if ( !is_object($order) || !method_exists($order, 'get_total') || !method_exists($order, 'get_shipping_total') || !method_exists($order, 'get_shipping_tax') || !function_exists('wc_get_price_decimals') ) { return false; } return number_format((float)$order->get_total() - $order->get_shipping_total() - $order->get_shipping_tax(), wc_get_price_decimals(), '.', ''); } public static function resolve_value_for_inline_js($value) { $value = (string)$value; $value = '' === $value ? '""' : $value; $value = is_numeric($value) ? $value : "'{$value}'"; return $value; } // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid public static function get_shop_domain_computed() { $parts = wp_parse_url(home_url()); $shop_domain = isset($parts['host']) ? sanitize_text_field($parts['host']) : ''; $shop_path = isset($parts['path']) ? sanitize_text_field($parts['path']) : ''; // Strip common 'www.' prefix from host for normalization $prefix = 'www.'; if (0 === strpos($shop_domain, $prefix)) { $shop_domain = substr($shop_domain, strlen($prefix)); } // Normalize path (ensure no leading slash when concatenating) $shop_path = ltrim($shop_path, '/'); $domain_path = $shop_domain . ($shop_path !== '' ? '/' . $shop_path : ''); return str_replace('/', '_', $domain_path); } /** Get shop domain from options */ public static function get_shop_domain_from_option() { return get_option('adscale__shop_domain', ''); } /** Get shop user by configured login */ public static function get_shop_user_id() { return get_option('adscale__access_wp_user_id', ''); } /** Get shop user by configured login */ public static function get_shop_user() { $user_id = sanitize_user(Helper::get_shop_user_id()); return get_user($user_id); } /** Check if shop user exists */ public static function has_shop_user() { $shop_user = self::get_shop_user(); return $shop_user instanceof \WP_User; } /** Get shop user role */ public static function get_shop_user_role() { $shop_user = self::get_shop_user(); return is_array($shop_user->roles) ? array_first($shop_user->roles) : ''; } /** Check if shop has REST API access keys */ public static function has_shop_rest_api() { $user_id = sanitize_user(Helper::get_shop_user_id()); $exist_shop_keys_data = ShopKeys::get_shop_keys_data_by_user_id($user_id); return (bool)$exist_shop_keys_data; } /** Get shop domain, prefer option value over computed (for backward compatibility) */ public static function get_shop_domain() { $from_option = self::get_shop_domain_from_option(); $computed = self::get_shop_domain_computed(); // bc return $from_option ? $from_option : $computed; } /** Save computed shop domain to options */ public static function save_computed_shop_domain() { return update_option('adscale__shop_domain', self::get_shop_domain_computed()); } /** Get proxy endpoint from options */ public static function get_proxy_endpoint() { return get_option('adscale__proxy_endpoint', ''); } /** Save default proxy endpoint from config to options */ public static function save_default_proxy_endpoint() { $proxy_from_config = self::getConfigSetting('adscale_proxy_url_base', ''); return update_option('adscale__proxy_endpoint', $proxy_from_config); } /** Save Google Verification code to options */ public static function save_g_v_code($code) { update_option('adscale__gsv', $code); // google-site-verification } /** Get Google Verification code from options */ public static function get_g_v_code() { return get_option('adscale__gsv', ''); } /** Resolve and save Google Verification code if not already set */ public static function resolve_g_v_code() { if (!self::get_g_v_code()) { API_Manager::process_google_verification_code(); } } /** Save Facebook Verification code to options */ public static function save_f_v_code($code) { update_option('adscale__fdv', $code); // facebook-domain-verification } /** Get Facebook Verification code from options */ public static function get_f_v_code() { return get_option('adscale__fdv', ''); } /** * Is opt-in feature enabled in options? * @return bool */ public static function get_opt_in_enabled() { return get_option('adscale__optin_enabled') == 'on'; } /** Default opt-in state, possibly overridden by per-user meta. @return bool */ public static function get_opt_in_checked() { $option = get_option('adscale__optin_checked_by_default') == 'on'; if (is_user_logged_in()) { // date check users if exists field by user $optin = get_user_meta(get_current_user_id(), '_adscale__optin_date', true); $optout = get_user_meta(get_current_user_id(), '_adscale__optout_date', true); if ($optin && $optout) { $option = strtotime($optout) < strtotime($optin); } elseif ($optout) { $option = false; } elseif ($optin) { $option = true; } } return $option; } /** Get opt-in checkbox action hook from options. @return string */ public static function get_opt_in_action() { return get_option('adscale__optin_position') ?: 'woocommerce_review_order_before_submit'; } /** Get opt-in checkbox message from options. @return string */ public static function getOptInMessage() { return get_option('adscale__optin_message') ?: __('I agree to receive marketing materials by sms or email', 'adscale-ai'); } /** Resolve and save Facebook Verification code if not already set */ public static function resolveFVCode() { if (!self::get_f_v_code()) { API_Manager::process_facebook_verification_code(); } } /** * Get a configuration setting by its path. * * @param string $name Configuration setting path, e.g. 'mode/default'. * @param mixed $default Default value if setting not found. * @param bool $direct If true, load config directly from file instead of using App instance. * * @return mixed */ public static function getConfigSetting($name, $default, $direct = false) { $parts = explode('/', $name); $config = $direct ? apply_filters('adscale/config', require ADSCALE_PLUGIN_DIR . '/src/config/config.php') : App::instance()->getConfig(); if (!isset($config[$parts[0]])) { return $default; } $value = $config[array_shift($parts)]; foreach ($parts as $part) { if (is_array($value) && isset($value[$part])) { $value = $value[$part]; } else { return $default; } } return $value instanceof \Closure ? $value() : $value; } /** Generate a random value * @param int $length Length of the random string to generate. * * @return string Generated random string. */ public static function generate_rand_string($length = 32) { $chars = '123456789ABCDEFGHIJKLMNPQRSTUVWXYZ'; $chars_length = strlen($chars); $generatedValue = ''; for ($i = 1; $i <= $length; ++$i) { $generatedValue .= substr($chars, (int)floor(self::random_float(0, 1) * ($chars_length - 1)), 1); } return $generatedValue; } /** * Generate a random float between min and max. * * @param float $min Minimum value (inclusive). * @param float $max Maximum value (exclusive). * * @return float Random float in [min, max). */ public static function random_float($min = 0, $max = 1) { $min = (float)$min; $max = (float)$max; // Ensure a valid range if ($max <= $min) { $max = $min + 1.0; } // Use wp_rand() for a secure random integer, then scale to [0,1) $granularity = 1000000; // 1e6 steps for sufficient precision $rand = wp_rand(0, $granularity); $fraction = $rand / $granularity; return $min + $fraction * ($max - $min); } /** * Generate a random hash string. * * @param int $limit Length of the hash string to generate. * * @return string Generated random hash string. */ public static function generate_rand_hash($limit = 32) { if (!function_exists('openssl_random_pseudo_bytes')) { return self::generate_rand_string($limit); } $bytes = openssl_random_pseudo_bytes($limit); $hex = strtoupper(bin2hex($bytes)); return substr($hex, 0, $limit); } /** Get access key from POST body data */ public static function get_access_key() { return @hex2bin(self::get_post_body_data_value('key', 'sanitize_key')); } /** * Safely get a value from JSON POST body data with optional sanitization. * * @param string $key Key to retrieve from JSON body. * @param callable|null $sanitize_callback Optional sanitizer. If provided, it will be called with the raw (decoded) value. * Examples: 'sanitize_text_field', 'esc_url_raw', 'sanitize_email'. * @param mixed $default Default value if key is absent/invalid. * @return mixed */ public static function get_post_body_data_value($key, $sanitize_callback = null, $default = null) { // Retrieve raw JSON input securely $json_input = file_get_contents('php://input'); if ($json_input === false) { return $default; } $json_input = trim($json_input); // Basic guards: size & UTF-8 validity if ($json_input === '' || strlen($json_input) > 10000) { return $default; } if (function_exists('wp_check_invalid_utf8') && wp_check_invalid_utf8($json_input, true) === '') { return $default; } // Decode JSON as an associative array (no sanitization yet) $data = json_decode($json_input, true); if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) { return $default; } // Ensure the key exists and is scalar if (!array_key_exists($key, $data) || !is_scalar($data[$key])) { return $default; } // Unslash then sanitize $value = is_string($data[$key]) ? wp_unslash($data[$key]) : $data[$key]; // Apply specific sanitization when provided; otherwise apply a safe default if (is_callable($sanitize_callback)) { $value = call_user_func($sanitize_callback, $value); } else { // Default sanitization: // - Strings → sanitize_text_field // - Everything else → cast to string and sanitize if (is_string($value)) { $value = sanitize_text_field($value); } else { $value = sanitize_text_field((string)$value); } } return $value; } /** * Delete rewrite rules option to ensure WordPress regenerates rules * if a specific rule is not present. * * @param string $rewrite_rule_regex The regex of the rewrite rule to check. */ public static function del_to_ensure_rewrite_rule($rewrite_rule_regex) { $rewrite_rules = get_option('rewrite_rules'); if (is_array($rewrite_rules) && !array_key_exists($rewrite_rule_regex, $rewrite_rules)) { delete_option('rewrite_rules'); } } /** Get resolved activation email */ public static function get_resolved_activation_email() { $info_file_email = self::get_info_file_email(); return $info_file_email ? $info_file_email : self::get_site_email(); } /** Get site admin email */ public static function get_site_email() { return get_option('admin_email'); } /** Get email from Info File */ public static function get_info_file_email() { return self::get_info_file_value('email_on_download'); } /** Get value from Info File by key */ public static function get_info_file_value($key) { $data = self::get_info_file_data(); return isset($data[$key]) ? $data[$key] : null; } /** Get all data from Info File * * @return mixed * */ public static function get_info_file_data() { $info_file_path = ADSCALE_PLUGIN_DIR . '/info.php'; if (is_file($info_file_path)) { return include $info_file_path; } return []; } /** * Getting valid modes from config * * @return array */ public static function get_valid_modes() { return self::getConfigSetting('mode/valid', ['Full', 'Shopy']); } /** * Checking the 'mode' is valid * * @param $mode string * * @return bool */ public static function is_valid_mode($mode) { return in_array($mode, self::get_valid_modes(), true); } /** * Getting default mode from config * * @return string */ public static function get_default_mode() { return self::getConfigSetting('mode/default', 'Full'); } /** * Getting mode from options * * @return string */ public static function get_mode_from_option() { return get_option('adscale__mode', ''); } /** * Main getter mode method * return mode from options or default value otherwise */ public static function get_mode() { $mode_from_opt = self::get_mode_from_option(); return $mode_from_opt && self::is_valid_mode($mode_from_opt) ? $mode_from_opt : self::get_default_mode(); } /** * Saving the 'mode' to options if it valid * * @param $mode string * * @return bool true if mode is valid and saved in options, false - otherwise */ public static function save_mode($mode) { $valid = self::is_valid_mode($mode); if ($valid) { update_option('adscale__mode', $mode); } return $valid && get_option('adscale__mode') === $mode; } /** * Get mode from Info File */ public static function get_info_file_mode() { return self::get_info_file_value('mode_on_download'); } /** * Return mode from Info File or default value otherwise */ public static function get_resolved_mode() { $mode = self::get_info_file_mode(); $valid = self::is_valid_mode($mode); return $valid && $mode ? $mode : self::get_default_mode(); } /** * Save resolved mode to options * * @return bool */ public static function save_resolved_mode() { return self::save_mode(self::get_resolved_mode()); } /** * Checking the 'mode' is 'Full' * * @return bool */ public static function is_full_mode() { return self::get_mode() === 'Full'; } /** * Checking the 'mode' is 'Shopy' * * @return bool */ public static function is_shopy_mode() { return self::get_mode() === 'Shopy'; } /** * Checking the need for a redirect to Adscale on plugin activation * * @return bool */ public static function is_need_redirect_on_activation() { $is_need = self::get_info_file_value('redirect_on_activation'); $is_need_default = true; return null !== $is_need ? 1 === (int)$is_need : $is_need_default; } /** * Format Response with ADSCALE marks * * @param string $response * * @return string */ public static function format_response($response) { return "ADSCALE_START{$response}ADSCALE_END"; } /** * Send a JSON response, indicating success. * * @param mixed $data Data to encode as JSON, then print and die. * @param int $status_code * @param string $status_desc */ public static function send_response_json_success($data = null, $status_code = 200, $status_desc = '') { self::send_response_json($data, $status_code, $status_desc); } /** * Send a JSON response, indicating failure. * * @param mixed $data Data to encode as JSON, then print and die. * @param int $status_code The HTTP status code to output. * @param string $status_desc */ public static function send_response_json_error($data = null, $status_code = 500, $status_desc = '') { self::send_response_json($data, $status_code, $status_desc); } /** * Send a JSON response. * * @param mixed $response Data to encode as JSON, then print and die. * @param int $status_code The HTTP status code to output. * @param string $status_desc */ public static function send_response_json($response, $status_code = null, $status_desc = '') { self::send_response(json_encode($response), $status_code, $status_desc, 'application/json'); } /** * Send a Formatted response, indicating success. * * @param mixed $data Data to encode as JSON, then print and die. * @param int $status_code * @param string $status_desc */ public static function send_response_formatted_success($data = null, $status_code = 200, $status_desc = '') { self::send_response_formatted($data, $status_code, $status_desc); } /** * Send a Formatted response, indicating failure. * * @param mixed $data Data to encode as JSON, then print and die. * @param int $status_code The HTTP status code to output. * @param string $status_desc */ public static function send_response_formatted_error($data = null, $status_code = 500, $status_desc = '') { self::send_response_formatted($data, $status_code, $status_desc); } /** * Send a Formatted response. * * @param mixed $response Data to encode as JSON, then print and die. * @param int $status_code The HTTP status code to output. * @param string $status_desc */ public static function send_response_formatted($response, $status_code = null, $status_desc = '') { self::send_response(self::format_response(json_encode($response)), $status_code, $status_desc, 'text/plain'); } /** * Send a response and terminate execution. * * @param string $response The response body to send. * @param int|null $status_code The HTTP status code to output, or null to skip setting it. * @param string $status_desc Optional status description. * @param string $content_typ The Content-Type header value. */ public static function send_response($response, $status_code = null, $status_desc = '', $content_typ = 'text/plain') { if (!headers_sent()) { if (null !== $status_code) { \remove_all_filters('status_header', false); \status_header($status_code, $status_desc); } if (is_string($content_typ) && $content_typ) { header("Content-Type: {$content_typ}"); } } echo wp_kses($response, self::get_response_allowed_html_tags()); die(); } public static function get_response_allowed_html_tags() { return self::getConfigSetting('response_allowed_html_tags', []); } /** * Set a cookie - wrapper for setcookie using WP constants. * * @param string $name Name of the cookie being set. * @param string $value Value of the cookie. * @param integer $expire Expiry of the cookie. * @param bool $secure Whether the cookie should be served only over https. * @param bool $httponly Whether the cookie is only accessible over HTTP, not scripting languages like JavaScript. */ public static function set_cookie($name, $value, $expire = 0, $secure = false, $httponly = false) { if (!headers_sent()) { setcookie($name, $value, $expire, COOKIEPATH ? COOKIEPATH : '/', COOKIE_DOMAIN, $secure, $httponly); } else { headers_sent($file, $line); Logger::log("{$name} cookie cannot be set - headers already sent by {$file} on line {$line}", 'setCookie: failed: ', 'cookie'); } } /** * Safely get a cookie value with optional sanitization. * * @param string $name Cookie name. * @param mixed $default Default value if cookie is absent/invalid. * @param callable|null $sanitize_callback Optional sanitizer. If provided, it will be called with the raw (decoded) string. * Examples: 'sanitize_text_field', 'esc_url_raw', 'sanitize_email'. * @return mixed */ public static function get_cookie($name, $default = '', $sanitize_callback = null) { $cookie_name = is_string($name) ? sanitize_key($name) : ''; if ('' === $cookie_name) { return $default; } $raw_cookie = filter_input(INPUT_COOKIE, $cookie_name, FILTER_UNSAFE_RAW, FILTER_NULL_ON_FAILURE); if (null === $raw_cookie || false === $raw_cookie) { return $default; } // Ensure string and unslash from PHP/WP runtime. $value = is_string($raw_cookie) ? wp_unslash($raw_cookie) : ''; // If our setter used rawurlencode, decode here (safe if not encoded) $decoded = rawurldecode($value); if (is_string($decoded)) { $value = $decoded; } // Basic guards: size & UTF-8 validity if (strlen($value) > 4096) { // Clamp to a reasonable cookie length $value = substr($value, 0, 4096); } if (function_exists('wp_check_invalid_utf8') && wp_check_invalid_utf8($value, true) === '') { return $default; } // Apply specific sanitization when provided; otherwise apply a safe default if (is_callable($sanitize_callback)) { $sanitized = call_user_func($sanitize_callback, $value); return $sanitized; } // Sensible default: sanitize text return sanitize_text_field($value); } /** * Gets WC Session cookie - Contains a unique code for each customer * so that it knows where to find the cart data in the database for each customer. * * @return string */ public static function get_w_c_session_cookie_restored_value() { $wc_session_cookie_parts = WC()->session->get_session_cookie(); return is_array($wc_session_cookie_parts) ? rawurlencode(implode('||', $wc_session_cookie_parts)) : ''; } /** * Safely get a value from an array with optional callback processing. * * @param array $array The array to retrieve the value from. * @param string|int $key The key to look up in the array. * @param mixed $default Default value if key is absent. * @param callable|null $cb Optional callback to process the value if found. * * @return mixed */ public static function get_array_value($array, $key, $default = null, $cb = null) { if (!isset($array[$key])) { return $default; } return is_callable($cb) ? $cb($array[$key]) : $array[$key]; } /** * Safely get a property from an object with optional callback processing. * * @param object $obj The object to retrieve the property from. * @param string $prop The property name to look up in the object. * @param mixed $default Default value if property is absent. * @param callable|null $cb Optional callback to process the value if found. * * @return mixed */ public static function get_obj_property($obj, $prop, $default = null, $cb = null) { if (!isset($obj->$prop)) { return $default; } return is_callable($cb) ? $cb($obj->$prop) : $obj->$prop; } /** * Send no-cache headers */ public static function nocache_headers() { if (function_exists('wc_nocache_headers')) { wc_nocache_headers(); } else { nocache_headers(); } } /** * Get saved adscale_ad_link * * @param int $order_id * * @return string */ public static function get_saved_adscale_ad_link($order_id) { $order = wc_get_order($order_id); return (string)$order->get_meta('_adscale_ad_link', true); } /** * Get WooCommerce permalink structure * * @return array */ public static function get_wc_permalink_structure() { $saved_permalinks = (array)get_option('woocommerce_permalinks', array()); $permalinks = wp_parse_args( array_filter($saved_permalinks), array( 'product_base' => _x('product', 'slug', 'adscale-ai'), 'category_base' => _x('product-category', 'slug', 'adscale-ai'), 'tag_base' => _x('product-tag', 'slug', 'adscale-ai'), 'attribute_base' => '', 'use_verbose_page_rules' => false, ) ); $permalinks['product_rewrite_slug'] = untrailingslashit($permalinks['product_base']); $permalinks['category_rewrite_slug'] = untrailingslashit($permalinks['category_base']); $permalinks['tag_rewrite_slug'] = untrailingslashit($permalinks['tag_base']); $permalinks['attribute_rewrite_slug'] = untrailingslashit($permalinks['attribute_base']); return $permalinks; } /** * Save default 'consider pending orders' setting from config to options */ public static function save_default_consider_pending_orders() { $default_from_config = self::getConfigSetting('default_consider_pending_orders', true); return update_option('adscale__consider_pending_orders', $default_from_config); } /** * Get 'consider pending orders' setting from options * * @return bool */ public static function is_consider_pending_orders() { return filter_var(get_option('adscale__consider_pending_orders', false), FILTER_VALIDATE_BOOLEAN); } /** * Analog of WordPress function wp_timezone_string for wp<5.3 compatibility */ public static function wp_timezone_string() { $timezone_string = get_option('timezone_string'); if ($timezone_string) { return $timezone_string; } $offset = (float)get_option('gmt_offset'); $hours = (int)$offset; $minutes = ($offset - $hours); $sign = ($offset < 0) ? '-' : '+'; $abs_hour = abs($hours); $abs_mins = abs($minutes * 60); $tz_offset = sprintf('%s%02d:%02d', $sign, $abs_hour, $abs_mins); return $tz_offset; } /** * Get current HTTP method * * @return string */ public static function get_current_http_method() { return isset($_SERVER['REQUEST_METHOD']) ? strtoupper(sanitize_text_field($_SERVER['REQUEST_METHOD'])) : ''; } /** * Float formatting * * @param float|int $val * @param int $precision * @param int $mode * * @return string */ public static function format_price_with_precision($val, $precision = null, $mode = PHP_ROUND_HALF_UP) { $val = (float)$val; if ($precision === null) { $precision = (int)get_option('woocommerce_price_num_decimals', 2); } $val = round($val, $precision, $mode); return number_format($val, $precision, '.', ''); } /** * Validate Date * * @param string $date * @param string $format * * @return bool */ public static function validate_date($date, $format = 'Y-m-d H:i:s') { $d = \DateTime::createFromFormat($format, $date); return $d && $d->format($format) === $date; } /** * Reformat Date * * @param string $date * @param string $format_from * @param string $format_to * * @return bool|string */ public static function reformat_date($date, $format_from = 'dmY', $format_to = 'Y-m-d') { $d = \DateTime::createFromFormat($format_from, $date); return $d ? $d->format($format_to) : false; } /** * Return true if Id Valid, else - false * * @param int|string $id * * @return bool */ public static function check_id_is_valid($id) { return is_numeric($id) && $id && (string)abs((int)$id) === (string)$id; } /** * Get full continent name * * @param string $cc Continent code ('EU, SA, ...') * * @return string */ public static function get_full_continent_name($cc) { if (!$cc) { return $cc; } $continent = self::get_array_value(WC()->countries->get_continents(), $cc, []); return self::get_array_value($continent, 'name', $cc); } /** * Get full list of countries of the continent * * @param string $cc Continent code ('EU, SA, ...') * @param bool $code_to_name * * @return array */ public static function get_continent_countries($cc, $code_to_name = false) { if (!$cc) { return []; } $continent = self::get_array_value(WC()->countries->get_continents(), $cc, []); $continent_countries_codes = self::get_array_value($continent, 'countries', []); if (!$code_to_name) { return $continent_countries_codes; } $all_countries = WC()->countries->get_countries(); $continent_countries_names = []; foreach ($continent_countries_codes as $code) { $continent_countries_names[] = self::get_array_value($all_countries, $code, $code); } return $continent_countries_names; } /** * Get full country name * * @param string $cc Country code ('US, GB, ...') * * @return string */ public static function get_full_country_name($cc) { if (!$cc) { return $cc; } return self::get_array_value(WC()->countries->get_countries(), $cc, $cc); } /** * Get full state name * * @param string $country Country code ('US, GB, ...') * @param string $state State code ('CA, FL, ...') * * @return string */ public static function get_full_state_name($country, $state) { if (!$country || !$state) { return $state; } $states = WC()->countries->get_states($country); return self::get_array_value($states, $state, $state); } /** * Validate security key * * @param string $key * * @return bool */ public static function validate_sec_key($key) { if (!class_exists('\Adscale_Crypt_RSA')) { $lib_path = ADSCALE_PLUGIN_DIR . '/lib/customized/vendor/phpseclib'; set_include_path(get_include_path() . PATH_SEPARATOR . $lib_path); require_once 'AdscaleCrypt/RSA.php'; } $rsa = new \Adscale_Crypt_RSA(); $rsa->setSignatureMode(ADSCALE_CRYPT_RSA_SIGNATURE_PKCS1); $rsa->setEncryptionMode(ADSCALE_CRYPT_RSA_ENCRYPTION_PKCS1); $rsa->loadKey("-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCBMUfDGyQbPrNjBpZVkQ6Kabt4fo9W0fRlA7XKdbo2eg9P8i+TfBfeYBd6hlu/DlMnDL8AdElXKakNONT5B4y1/M15cBySmn1gDPLYs/u9rZrjmGLNZX/4sOomISUFihnDl2+3hGMrrGRQyuW78kY/ZJBpdqqybAJolJTe1J4R2wIDAQAB\n-----END PUBLIC KEY-----"); return @$rsa->verify(self::get_shop_domain(), $key); } public static function safe_external_redirect($url) { $host = wp_parse_url($url, PHP_URL_HOST); if ($host) { add_filter( 'allowed_redirect_hosts', static function ($hosts) use ($host) { $hosts[] = $host; return array_unique($hosts); } ); } wp_safe_redirect($url); } }