*/ public static function positive_int_list($value): array { if (!is_array($value)) { if (is_string($value) && $value !== '') { $value = preg_split('/\s*,\s*/', sanitize_text_field($value)) ?: []; } elseif (is_numeric($value)) { $id = absint($value); return $id > 0 ? [$id] : []; } else { return []; } } $ids = []; foreach ($value as $item) { if (is_array($item)) { $item = $item['id'] ?? 0; } if (!is_numeric($item)) { continue; } $id = absint($item); if ($id > 0) { $ids[] = $id; } } return array_values(array_unique($ids)); } /** * List of positive integers from an array request value. * * @return list */ public static function int_list(string $source, string $key): array { foreach (self::input_types($source) as $type) { $array = filter_input($type, $key, FILTER_UNSAFE_RAW, FILTER_REQUIRE_ARRAY); if (is_array($array)) { return self::positive_int_list(wp_unslash($array)); } $scalar = filter_input($type, $key, FILTER_UNSAFE_RAW); if (is_string($scalar) || is_numeric($scalar)) { return self::positive_int_list(sanitize_text_field(wp_unslash((string) $scalar))); } } return []; } /** * Sanitize a JSON string payload and decode to an array. * * Returns `$default` when missing, not a string, or invalid JSON. * * @param array $default * @return array */ public static function json_array(string $source, string $key, array $default = []): array { $json = self::textarea($source, $key, ''); if ($json === '') { return $default; } $decoded = json_decode($json, true); return is_array($decoded) ? $decoded : $default; } /** * Y-m-d date string. Invalid dates return `$default`. */ public static function ymd_date(string $source, string $key, string $default = ''): string { return self::normalize_ymd_value(self::text($source, $key, ''), $default); } /** * Normalize a Y-m-d date value; invalid values return `$default`. * * Used by REST `prepare_*` helpers that already hold extracted params. * * @param mixed $value Candidate date. * @param string $default Fallback date. */ public static function normalize_ymd_value($value, string $default = ''): string { $value = sanitize_text_field((string) $value); if ($value === '' || !self::is_ymd_date($value)) { return $default; } return $value; } /** * Value must be in the allowlist after sanitization; otherwise `$default`. * * @param list $allowed */ public static function in_list(string $source, string $key, array $allowed, string $default = ''): string { $value = self::text($source, $key, ''); if ($value === '' || !in_array($value, $allowed, true)) { return $default; } return $value; } /** * Key must be in the allowlist after sanitize_key; otherwise `$default`. * * @param list $allowed */ public static function key_in_list(string $source, string $key, array $allowed, string $default = ''): string { $value = self::key($source, $key, ''); if ($value === '' || !in_array($value, $allowed, true)) { return $default; } return $value; } /** * Sanitize HTML from the request against an allowlist (kses). * * @param array $allowed_html */ public static function kses(string $source, string $key, array $allowed_html, string $default = ''): string { foreach (self::input_types($source) as $type) { $value = filter_input($type, $key, FILTER_UNSAFE_RAW); if (!is_string($value) && !is_numeric($value)) { continue; } return wp_kses(wp_unslash((string) $value), $allowed_html); } return $default; } /** * Sanitized array from the request (keys and string leaves cleaned). * * @param array $default * @return array */ public static function array(string $source, string $key, array $default = []): array { foreach (self::input_types($source) as $type) { $value = filter_input($type, $key, FILTER_UNSAFE_RAW, FILTER_REQUIRE_ARRAY); if (!is_array($value)) { continue; } return self::sanitize_array_deep(wp_unslash($value)); } return $default; } /** * Sanitize a cookie string. Invalid or missing values return `$default`. */ public static function cookie_text(string $key, string $default = ''): string { return self::text('cookie', $key, $default); } /** * Strict AJAX nonce check — dies with -1 on failure. */ public static function require_ajax_nonce(string $action, string $key = 'nonce'): void { check_ajax_referer($action, $key); /** * Fires after a plugin AJAX nonce has been verified. * * @param string $action Nonce action. * @param string $key Request key used for the nonce. */ do_action('bizdex_uplisting_ajax_nonce_verified', $action, $key); } /** * Fail-closed capability gate for admin AJAX handlers. */ public static function require_manage_options(): void { if (!current_user_can('manage_options')) { wp_send_json_error( ['message' => __('Unauthorized.', 'advanced-connector-for-uplisting')], 403 ); } /** * Fires after manage_options has been confirmed for an AJAX handler. */ do_action('bizdex_uplisting_ajax_authorized'); } /** * Admin AJAX gate: verify nonce, then require manage_options. * * @param string $action Nonce action. * @param string $key Request key used for the nonce. */ public static function require_ajax_security(string $action, string $key = 'nonce'): void { self::require_ajax_nonce($action, $key); self::require_manage_options(); } /** * Strict admin/form nonce check — dies on failure. */ public static function require_admin_nonce(string $action, string $key = '_wpnonce'): void { check_admin_referer($action, $key); } /** * Verify a nonce without dying. */ public static function verify(string $action, string $key = 'nonce', string $source = 'post'): bool { $nonce = self::text($source, $key); return $nonce !== '' && (bool) wp_verify_nonce($nonce, $action); } /** * Validate YYYY-MM-DD date string. */ public static function is_ymd_date(string $date): bool { if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) { return false; } $parts = explode('-', $date); return checkdate((int) $parts[1], (int) $parts[2], (int) $parts[0]); } /** * Recursively sanitize array keys and string values from a request payload. * * @param array $value * @return array */ private static function sanitize_array_deep(array $value): array { $clean = []; foreach ($value as $key => $item) { $clean_key = is_int($key) ? $key : sanitize_key((string) $key); if (is_array($item)) { $clean[$clean_key] = self::sanitize_array_deep($item); continue; } if (is_bool($item) || is_int($item) || is_float($item)) { $clean[$clean_key] = $item; continue; } $clean[$clean_key] = sanitize_text_field((string) $item); } return $clean; } /** * Map a logical source name to filter_input types (POST before GET for request). * * @return list */ private static function input_types(string $source): array { return match ($source) { 'post' => [INPUT_POST], 'get' => [INPUT_GET], 'cookie' => [INPUT_COOKIE], 'request' => [INPUT_POST, INPUT_GET], default => [], }; } }