<?php
/**
 * Calculate PHP Tool.
 *
 * Execute PHP code in a sandboxed environment to perform calculations.
 * Uses PHPSandbox library for AST-based security validation with whitelisted
 * functions and disabled dangerous operations.
 *
 * @package   Agenistrator
 * @since     1.0.0
 * @author    Alex Ruban <alex@maximum.guru>
 * @author    Maximum Software Inc <contact@maximum.software>
 * @license   GPL-3.0-only
 * @link      https://agenistrator.com/
 *
 * SPDX-FileCopyrightText: 2025-2026 Alex Ruban <alex@maximum.guru>
 * SPDX-FileCopyrightText: 2025-2026 Maximum Software Inc <contact@maximum.software>
 * SPDX-License-Identifier: GPL-3.0-only
 */

namespace Agenistrator;

use PHPSandbox\PHPSandbox;

// Direct access prevention
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Calculate_PHP_Tool class.
 *
 * Security Model:
 * PHPSandbox validates every function call against whitelist at the AST level.
 * We use a WHITELIST approach: only explicitly listed safe functions are allowed.
 * This is the most secure approach - new PHP extensions or future PHP versions
 * cannot introduce dangerous functions that bypass the sandbox.
 *
 * Whitelisted function categories:
 *   - String functions (strlen, substr, str_replace, etc.)
 *   - Array functions (array_map, array_filter, sort, etc.)
 *   - Math functions (abs, ceil, floor, sin, cos, rand, etc.)
 *   - Type functions (is_array, intval, gettype, etc.)
 *   - Date/Time functions (date, time, strtotime, etc.)
 *   - JSON functions (json_encode, json_decode)
 *   - PCRE functions (preg_match, preg_replace, etc.)
 *   - Multibyte string functions (mb_*)
 *   - Grapheme functions (grapheme_strlen, grapheme_substr, etc.)
 *   - Intl functions (all procedural wrappers: collator_*, datefmt_*, numfmt_*, locale_*, etc.)
 *   - BC Math functions (bcadd, bcmul, etc.)
 *   - GMP functions (gmp_add, gmp_mul, etc.)
 *   - Hash functions (hash, md5, sha1, etc.)
 *   - Password functions (password_hash, password_verify)
 *   - Character type functions (ctype_*)
 *   - Filter functions (filter_var)
 *   - URL/Query string parsing (parse_url, http_build_query, parse_str on PHP 8.0+ only)
 *   - Path string manipulation (basename, dirname, pathinfo) - pure string ops, no filesystem access
 *   - CSV parsing (str_getcsv)
 *   - XML parsing (simplexml_load_string, DOMDocument, etc.) - safe, see notes below
 *
 * Whitelisted class categories:
 *   - DateTime classes (DateTime, DateTimeZone, DateInterval, etc.)
 *   - Iterator/SPL classes (ArrayIterator, SplStack, SplHeap, etc.)
 *   - Intl classes (21 classes: NumberFormatter, Collator, Locale, IntlCalendar, etc. - NOT ResourceBundle)
 *   - Random classes (Random\Randomizer, Random\Engine\*, etc.) - PHP 8.2+
 *   - XML classes (SimpleXMLElement, DOMDocument, etc.) - safe with constant restrictions
 *
 * Whitelisted constant categories:
 *   - HTML entity constants (ENT_QUOTES, ENT_HTML5, etc.)
 *   - String/array constants (STR_PAD_*, SORT_*, CASE_*, etc.)
 *   - JSON constants (JSON_PRETTY_PRINT, JSON_THROW_ON_ERROR, etc.)
 *   - Math constants (M_PI, M_E, INF, NAN, PHP_INT_MAX, etc.)
 *   - PCRE constants (PREG_PATTERN_ORDER, PREG_OFFSET_CAPTURE, etc.)
 *   - Date format constants (DATE_ATOM, DATE_RFC3339, etc.)
 *   - Filter constants (FILTER_VALIDATE_*, FILTER_SANITIZE_*, etc.)
 *   - Password constants (PASSWORD_DEFAULT, PASSWORD_BCRYPT, etc.)
 *   - Grapheme constants (GRAPHEME_EXTR_*)
 *   - LIBXML safe constants (formatting/behavior flags only - no XXE risk)
 *   - PATHINFO constants (PATHINFO_DIRNAME, PATHINFO_BASENAME, etc.)
 *   - Note: Class constants (e.g., NumberFormatter::CURRENCY) are auto-allowed when class is whitelisted
 *
 * XML Parsing Security:
 *   XML parsing functions are safe because:
 *   1. We use a whitelist approach for constants - only safe constants are allowed
 *      (PHPSandbox checkConst() case 1: whitelist exists → only whitelisted allowed)
 *   2. LIBXML_NOENT, LIBXML_DTDLOAD, LIBXML_DTDATTR, LIBXML_DTDVALID, LIBXML_XINCLUDE
 *      are NOT whitelisted (XXE attack vectors)
 *   3. Safe LIBXML constants ARE whitelisted (LIBXML_NOCDATA, LIBXML_NOBLANKS, etc.)
 *      - these only control formatting/output behavior, not entity loading
 *   4. File/URL loading functions are NOT whitelisted (simplexml_load_file, etc.)
 *   5. PHP 8.0+ with libxml 2.9.0+ disables external entities by default
 *   DO NOT: whitelist simplexml_load_file, XMLReader, or dangerous LIBXML_* constants
 *   DO NOT: call blacklistConst() - it switches PHPSandbox to "allow all except blacklisted"
 *
 * PHPSandbox checkConst() logic (for reference):
 *   Case 1: If whitelist exists → only whitelisted constants allowed (our approach)
 *   Case 2: If blacklist exists (no whitelist) → only blacklisted blocked, ALL OTHERS ALLOWED
 *   Case 3: If neither → ALL constants blocked
 *   Calling blacklistConst() would switch to case 2, enabling all non-blacklisted constants!
 *
 * PHPSandbox Settings:
 *
 * ENABLED (safe for calculations):
 *   allow_functions        - Declare helper functions (calls still validated against whitelist)
 *   allow_closures         - Use callbacks with array_map, array_filter, usort, etc.
 *   allow_constants        - Define constants for readability
 *   allow_casting          - Type casting like (int), (float), (string)
 *   allow_error_suppressing - Use @ operator for minor convenience
 *   allow_variables        - Create variables (default: true)
 *   allow_static_variables - Static vars in functions (default: true)
 *   allow_objects          - Create objects of whitelisted classes only (default: true)
 *   allow_generators       - Use generators (default: true)
 *   allow_references       - Reference assignment (default: true)
 *
 * DISABLED (security risks):
 *   allow_globals          - Access global scope variables
 *   allow_namespaces       - Declare namespaces
 *   allow_aliases          - Namespace aliases
 *   allow_classes          - Declare classes (potential for abuse)
 *   allow_interfaces       - Declare interfaces
 *   allow_traits           - Declare traits
 *   allow_escaping         - Escape to HTML
 *   allow_backticks        - Shell execution via backticks
 *   allow_halting          - Halt PHP compiler
 *
 * Security Testing:
 *   Run from project root. Use these templates to test functions/constants/classes:
 *
 *   php << 'EOFPHP'
 *   <?php
 *   require_once "vendor/autoload.php";
 *   use PHPSandbox\PHPSandbox;
 *   $sandbox = new PHPSandbox();
 *   $sandbox->whitelistFunc(["FUNC_TO_TEST", "var_dump"]);
 *   // $sandbox->whitelistConst(["CONST_TO_TEST"]);
 *   // $sandbox->whitelistClass(["Class_To_Test"]);
 *   $code = 'return FUNC_TO_TEST(...);';
 *   try {
 *       $result = $sandbox->execute($code);
 *       echo "Result: "; var_dump($result);
 *   } catch (Exception $e) {
 *       echo "BLOCKED: " . $e->getMessage() . "\n";
 *   }
 *   EOFPHP
 *
 *   Key things to verify:
 *   - Cannot access superglobals ($_GET, $_POST, $_SERVER, etc.)
 *   - Cannot access sandbox internals or file paths
 *   - Cannot call non-whitelisted functions (even via callbacks)
 *   - Cannot instantiate non-whitelisted classes
 *   - No DoS vectors (sleep, infinite loops, memory exhaustion)
 */
class Calculate_PHP_Tool extends Tool {

	private const WHITELISTED_FUNCTIONS = array(
		// Function introspection
		'func_num_args',
		'func_get_arg',
		'func_get_args',

		// String functions - basic
		'strlen',
		'strcmp',
		'strncmp',
		'strcasecmp',
		'strncasecmp',
		'strnatcmp',
		'strnatcasecmp',
		'substr_compare',
		'strpos',
		'stripos',
		'strrpos',
		'strripos',
		'strstr',
		'stristr',
		'strrchr',
		'strpbrk',
		'substr',
		'substr_count',
		'substr_replace',
		'str_replace',
		'str_ireplace',
		'str_repeat',
		'str_pad',
		'str_split',
		'str_word_count',
		'str_shuffle',
		'str_contains',
		'str_starts_with',
		'str_ends_with',
		'str_increment',
		'str_decrement',

		// String functions - transformation
		'strtolower',
		'strtoupper',
		'ucfirst',
		'lcfirst',
		'ucwords',
		'strrev',
		'chunk_split',
		'wordwrap',
		'nl2br',
		'trim',
		'ltrim',
		'rtrim',
		'chop',

		// String functions - formatting
		'sprintf',
		'vsprintf',
		'sscanf',
		'number_format',
		'money_format',
		'printf',
		'vprintf',

		// String functions - encoding
		'ord',
		'chr',
		'bin2hex',
		'hex2bin',
		'convert_uuencode',
		'convert_uudecode',
		'quoted_printable_encode',
		'quoted_printable_decode',

		// String functions - splitting/joining
		'explode',
		'implode',
		'join',
		'strtok',

		// String functions - comparison/analysis
		'similar_text',
		'levenshtein',
		'soundex',
		'metaphone',
		'count_chars',

		// String functions - HTML/URL encoding
		'htmlspecialchars',
		'htmlspecialchars_decode',
		'htmlentities',
		'html_entity_decode',
		'strip_tags',
		'addslashes',
		'stripslashes',
		'addcslashes',
		'stripcslashes',
		'quotemeta',
		'urlencode',
		'urldecode',
		'rawurlencode',
		'rawurldecode',
		'base64_encode',
		'base64_decode',

		// Array functions - creation
		'array_fill',
		'array_fill_keys',
		'array_combine',
		'range',

		// Array functions - information
		'count',
		'sizeof',
		'array_count_values',
		'array_key_exists',
		'key_exists',
		'in_array',
		'array_search',
		'array_keys',
		'array_values',
		'array_unique',

		// Array functions - manipulation
		'array_push',
		'array_pop',
		'array_shift',
		'array_unshift',
		'array_slice',
		'array_splice',
		'array_merge',
		'array_merge_recursive',
		'array_replace',
		'array_replace_recursive',
		'array_chunk',
		'array_pad',
		'array_flip',
		'array_reverse',
		'array_column',

		// Array functions - sorting
		'sort',
		'rsort',
		'asort',
		'arsort',
		'ksort',
		'krsort',
		'natsort',
		'natcasesort',
		'usort',
		'uasort',
		'uksort',
		'array_multisort',
		'shuffle',

		// Array functions - iteration
		'current',
		'pos',
		'key',
		'next',
		'prev',
		'reset',
		'end',
		'each',

		// Array functions - comparison
		'array_diff',
		'array_diff_key',
		'array_diff_assoc',
		'array_diff_ukey',
		'array_diff_uassoc',
		'array_udiff',
		'array_udiff_assoc',
		'array_udiff_uassoc',
		'array_intersect',
		'array_intersect_key',
		'array_intersect_assoc',
		'array_intersect_ukey',
		'array_intersect_uassoc',
		'array_uintersect',
		'array_uintersect_assoc',
		'array_uintersect_uassoc',

		// Array functions - aggregation/transformation
		'array_sum',
		'array_product',
		'max',
		'min',
		'array_reduce',
		'array_map',
		'array_filter',
		'array_walk',
		'array_walk_recursive',
		'compact',
		'get_defined_vars',

		// Array functions - PHP 8.1+ / 8.4+
		'array_is_list',
		'array_all',
		'array_any',
		'array_find',
		'array_find_key',

		// Math functions - basic
		'abs',
		'ceil',
		'floor',
		'round',
		'fmod',
		'intdiv',
		'fdiv',

		// Math functions - power/logarithm
		'pow',
		'sqrt',
		'exp',
		'expm1',
		'log',
		'log10',
		'log1p',

		// Math functions - trigonometry
		'sin',
		'cos',
		'tan',
		'asin',
		'acos',
		'atan',
		'atan2',
		'sinh',
		'cosh',
		'tanh',
		'asinh',
		'acosh',
		'atanh',

		// Math functions - conversion
		'deg2rad',
		'rad2deg',
		'base_convert',
		'bindec',
		'octdec',
		'hexdec',
		'decbin',
		'decoct',
		'dechex',

		// Math functions - random
		'rand',
		'srand',
		'getrandmax',
		'mt_rand',
		'mt_srand',
		'mt_getrandmax',
		'random_int',
		'random_bytes',

		// Math functions - other
		'hypot',
		'pi',
		'is_nan',
		'is_infinite',
		'is_finite',

		// Type functions - checking
		'gettype',
		'is_array',
		'is_bool',
		'is_callable',
		'is_countable',
		'is_float',
		'is_double',
		'is_real',
		'is_int',
		'is_integer',
		'is_long',
		'is_null',
		'is_numeric',
		'is_object',
		'is_resource',
		'is_scalar',
		'is_string',
		'is_iterable',

		// Type functions - casting
		'intval',
		'floatval',
		'doubleval',
		'strval',
		'boolval',
		'settype',

		// Type functions - variable info
		'var_dump',
		'print_r',
		'var_export',

		// Date/Time functions - core
		// NOTE: date_default_timezone_set is NOT whitelisted - it modifies the process-wide
		// default timezone, affecting all code that runs after the sandbox within the same request.
		// Use DateTimeZone objects instead: new DateTime('now', new DateTimeZone('America/New_York'))
		'time',
		'mktime',
		'gmmktime',
		'strtotime',
		'date',
		'gmdate',
		'strftime',
		'gmstrftime',
		'idate',
		'getdate',
		'localtime',
		'gettimeofday',
		'microtime',
		'checkdate',
		'date_parse',
		'date_parse_from_format',
		'date_default_timezone_get',

		// Date/Time functions - DateTime procedural wrappers
		'date_add',
		'date_create',
		'date_create_from_format',
		'date_create_immutable',
		'date_create_immutable_from_format',
		'date_date_set',
		'date_diff',
		'date_format',
		'date_get_last_errors',
		'date_interval_create_from_date_string',
		'date_interval_format',
		'date_isodate_set',
		'date_modify',
		'date_offset_get',
		'date_sub',
		'date_sun_info',
		'date_sunrise',
		'date_sunset',
		'date_time_set',
		'date_timestamp_get',
		'date_timestamp_set',
		'date_timezone_get',
		'date_timezone_set',

		// Date/Time functions - timezone procedural wrappers
		'timezone_abbreviations_list',
		'timezone_identifiers_list',
		'timezone_location_get',
		'timezone_name_from_abbr',
		'timezone_name_get',
		'timezone_offset_get',
		'timezone_open',
		'timezone_transitions_get',
		'timezone_version_get',

		// JSON functions
		'json_encode',
		'json_decode',
		'json_last_error',
		'json_last_error_msg',
		'json_validate',

		// PCRE functions
		'preg_match',
		'preg_match_all',
		'preg_replace',
		'preg_replace_callback',
		'preg_replace_callback_array',
		'preg_filter',
		'preg_split',
		'preg_grep',
		'preg_quote',
		'preg_last_error',
		'preg_last_error_msg',

		// Multibyte string functions
		'mb_strlen',
		'mb_strpos',
		'mb_stripos',
		'mb_strrpos',
		'mb_strripos',
		'mb_strstr',
		'mb_stristr',
		'mb_strrchr',
		'mb_substr',
		'mb_substr_count',
		'mb_strtolower',
		'mb_strtoupper',
		'mb_convert_case',
		'mb_convert_encoding',
		'mb_detect_encoding',
		'mb_check_encoding',
		'mb_encoding_aliases',
		'mb_list_encodings',
		'mb_internal_encoding',
		'mb_language',
		'mb_ord',
		'mb_chr',
		'mb_scrub',
		'mb_str_split',
		'mb_ucfirst',
		'mb_lcfirst',
		'mb_trim',
		'mb_ltrim',
		'mb_rtrim',

		// Grapheme functions (Unicode grapheme cluster handling)
		'grapheme_strlen',
		'grapheme_strpos',
		'grapheme_stripos',
		'grapheme_strrpos',
		'grapheme_strripos',
		'grapheme_substr',
		'grapheme_strstr',
		'grapheme_stristr',
		'grapheme_str_split',
		'grapheme_extract',

		// Intl error functions
		'intl_get_error_code',
		'intl_get_error_message',
		'intl_is_failure',
		'intl_error_name',

		// IDN functions (internationalized domain names)
		'idn_to_ascii',
		'idn_to_utf8',

		// Collator procedural functions
		'collator_asort',
		'collator_compare',
		'collator_create',
		'collator_get_attribute',
		'collator_get_error_code',
		'collator_get_error_message',
		'collator_get_locale',
		'collator_get_sort_key',
		'collator_get_strength',
		'collator_set_attribute',
		'collator_set_strength',
		'collator_sort',
		'collator_sort_with_sort_keys',

		// IntlDateFormatter procedural functions
		'datefmt_create',
		'datefmt_format',
		'datefmt_format_object',
		'datefmt_get_calendar',
		'datefmt_get_calendar_object',
		'datefmt_get_datetype',
		'datefmt_get_error_code',
		'datefmt_get_error_message',
		'datefmt_get_locale',
		'datefmt_get_pattern',
		'datefmt_get_timetype',
		'datefmt_get_timezone',
		'datefmt_get_timezone_id',
		'datefmt_is_lenient',
		'datefmt_localtime',
		'datefmt_parse',
		'datefmt_set_calendar',
		'datefmt_set_lenient',
		'datefmt_set_pattern',
		'datefmt_set_timezone',

		// IntlCalendar procedural functions
		'intlcal_add',
		'intlcal_after',
		'intlcal_before',
		'intlcal_clear',
		'intlcal_create_instance',
		'intlcal_equals',
		'intlcal_field_difference',
		'intlcal_from_date_time',
		'intlcal_get',
		'intlcal_get_actual_maximum',
		'intlcal_get_actual_minimum',
		'intlcal_get_available_locales',
		'intlcal_get_day_of_week_type',
		'intlcal_get_error_code',
		'intlcal_get_error_message',
		'intlcal_get_first_day_of_week',
		'intlcal_get_greatest_minimum',
		'intlcal_get_keyword_values_for_locale',
		'intlcal_get_least_maximum',
		'intlcal_get_locale',
		'intlcal_get_maximum',
		'intlcal_get_minimal_days_in_first_week',
		'intlcal_get_minimum',
		'intlcal_get_now',
		'intlcal_get_repeated_wall_time_option',
		'intlcal_get_skipped_wall_time_option',
		'intlcal_get_time',
		'intlcal_get_time_zone',
		'intlcal_get_type',
		'intlcal_get_weekend_transition',
		'intlcal_in_daylight_time',
		'intlcal_is_equivalent_to',
		'intlcal_is_lenient',
		'intlcal_is_set',
		'intlcal_is_weekend',
		'intlcal_roll',
		'intlcal_set',
		'intlcal_set_first_day_of_week',
		'intlcal_set_lenient',
		'intlcal_set_minimal_days_in_first_week',
		'intlcal_set_repeated_wall_time_option',
		'intlcal_set_skipped_wall_time_option',
		'intlcal_set_time',
		'intlcal_set_time_zone',
		'intlcal_to_date_time',

		// IntlGregorianCalendar procedural functions
		'intlgregcal_create_instance',
		'intlgregcal_get_gregorian_change',
		'intlgregcal_is_leap_year',
		'intlgregcal_set_gregorian_change',

		// IntlTimeZone procedural functions
		'intltz_count_equivalent_ids',
		'intltz_create_default',
		'intltz_create_enumeration',
		'intltz_create_time_zone',
		'intltz_create_time_zone_id_enumeration',
		'intltz_from_date_time_zone',
		'intltz_get_canonical_id',
		'intltz_get_display_name',
		'intltz_get_dst_savings',
		'intltz_get_equivalent_id',
		'intltz_get_error_code',
		'intltz_get_error_message',
		'intltz_get_gmt',
		'intltz_get_iana_id',
		'intltz_get_id',
		'intltz_get_id_for_windows_id',
		'intltz_get_offset',
		'intltz_get_raw_offset',
		'intltz_get_region',
		'intltz_get_tz_data_version',
		'intltz_get_unknown',
		'intltz_get_windows_id',
		'intltz_has_same_rules',
		'intltz_to_date_time_zone',
		'intltz_use_daylight_time',

		// Locale procedural functions
		'locale_accept_from_http',
		'locale_canonicalize',
		'locale_compose',
		'locale_filter_matches',
		'locale_get_all_variants',
		'locale_get_default',
		'locale_get_display_language',
		'locale_get_display_name',
		'locale_get_display_region',
		'locale_get_display_script',
		'locale_get_display_variant',
		'locale_get_keywords',
		'locale_get_primary_language',
		'locale_get_region',
		'locale_get_script',
		'locale_lookup',
		'locale_parse',
		'locale_set_default',

		// MessageFormatter procedural functions
		'msgfmt_create',
		'msgfmt_format',
		'msgfmt_format_message',
		'msgfmt_get_error_code',
		'msgfmt_get_error_message',
		'msgfmt_get_locale',
		'msgfmt_get_pattern',
		'msgfmt_parse',
		'msgfmt_parse_message',
		'msgfmt_set_pattern',

		// Normalizer procedural functions
		'normalizer_get_raw_decomposition',
		'normalizer_is_normalized',
		'normalizer_normalize',

		// NumberFormatter procedural functions
		'numfmt_create',
		'numfmt_format',
		'numfmt_format_currency',
		'numfmt_get_attribute',
		'numfmt_get_error_code',
		'numfmt_get_error_message',
		'numfmt_get_locale',
		'numfmt_get_pattern',
		'numfmt_get_symbol',
		'numfmt_get_text_attribute',
		'numfmt_parse',
		'numfmt_parse_currency',
		'numfmt_set_attribute',
		'numfmt_set_pattern',
		'numfmt_set_symbol',
		'numfmt_set_text_attribute',

		// NOTE: ResourceBundle functions (resourcebundle_*) are NOT whitelisted
		// because they can access filesystem paths to load .res files.

		// Transliterator procedural functions
		'transliterator_create',
		'transliterator_create_from_rules',
		'transliterator_create_inverse',
		'transliterator_get_error_code',
		'transliterator_get_error_message',
		'transliterator_list_ids',
		'transliterator_transliterate',

		// Extension checking (safe - only returns true/false)
		'extension_loaded',

		// BC Math functions
		'bcadd',
		'bcsub',
		'bcmul',
		'bcdiv',
		'bcmod',
		'bcpow',
		'bcpowmod',
		'bcsqrt',
		'bcscale',
		'bccomp',

		// GMP functions
		'gmp_init',
		'gmp_intval',
		'gmp_strval',
		'gmp_add',
		'gmp_sub',
		'gmp_mul',
		'gmp_div_q',
		'gmp_div_r',
		'gmp_div_qr',
		'gmp_mod',
		'gmp_divexact',
		'gmp_neg',
		'gmp_abs',
		'gmp_fact',
		'gmp_sqrt',
		'gmp_sqrtrem',
		'gmp_pow',
		'gmp_powm',
		'gmp_root',
		'gmp_rootrem',
		'gmp_gcd',
		'gmp_gcdext',
		'gmp_lcm',
		'gmp_invert',
		'gmp_jacobi',
		'gmp_legendre',
		'gmp_kronecker',
		'gmp_cmp',
		'gmp_sign',
		'gmp_random_bits',
		'gmp_random_range',
		'gmp_and',
		'gmp_or',
		'gmp_xor',
		'gmp_com',
		'gmp_setbit',
		'gmp_clrbit',
		'gmp_testbit',
		'gmp_scan0',
		'gmp_scan1',
		'gmp_popcount',
		'gmp_hamdist',
		'gmp_nextprime',
		'gmp_binomial',
		'gmp_perfect_square',
		'gmp_perfect_power',
		'gmp_prob_prime',

		// Character type functions
		'ctype_alnum',
		'ctype_alpha',
		'ctype_cntrl',
		'ctype_digit',
		'ctype_graph',
		'ctype_lower',
		'ctype_print',
		'ctype_punct',
		'ctype_space',
		'ctype_upper',
		'ctype_xdigit',

		// Filter functions
		'filter_var',
		'filter_var_array',
		'filter_list',
		'filter_id',

		// Hash functions
		'hash',
		'hash_algos',
		'hash_hmac',
		'hash_hmac_algos',
		'hash_init',
		'hash_update',
		'hash_final',
		'hash_copy',
		'hash_equals',
		'hash_pbkdf2',
		'hash_hkdf',
		'md5',
		'sha1',
		'crc32',

		// Password functions
		'password_hash',
		'password_verify',
		'password_needs_rehash',
		'password_get_info',
		'password_algos',

		// iconv functions
		'iconv',
		'iconv_strlen',
		'iconv_strpos',
		'iconv_strrpos',
		'iconv_substr',
		'iconv_mime_encode',
		'iconv_mime_decode',
		'iconv_mime_decode_headers',
		'iconv_get_encoding',
		'iconv_set_encoding',

		// SPL functions
		'class_implements',
		'class_parents',
		'class_uses',
		'spl_classes',
		'spl_object_hash',
		'spl_object_id',
		'iterator_count',
		'iterator_to_array',
		'iterator_apply',

		// Class/Object introspection functions
		'class_exists',
		'enum_exists',
		'get_class',
		'get_called_class',
		'get_parent_class',
		'get_class_methods',
		'get_class_vars',
		'get_object_vars',
		'get_mangled_object_vars',
		'interface_exists',
		'is_a',
		'is_subclass_of',
		'method_exists',
		'property_exists',
		'trait_exists',

		// Function handling (sandbox validates at runtime)
		'function_exists',
		'call_user_func',
		'call_user_func_array',
		'forward_static_call',
		'forward_static_call_array',

		// Variable handling
		'debug_zval_dump',
		'get_debug_type',
		'get_resource_id',
		'get_resource_type',
		'serialize',

		// Compression functions - safe (pure in-memory operations)
		// NOTE: gz* file functions (gzopen, gzread, gzwrite, etc.) are NOT whitelisted
		'gzcompress',
		'gzdecode',
		'gzdeflate',
		'gzencode',
		'gzinflate',
		'gzuncompress',
		'zlib_decode',
		'zlib_encode',
		'zlib_get_coding_type',
		'bzcompress',
		'bzdecompress',
		'bzerrno',
		'bzerror',
		'bzerrstr',

		// NOTE: Memory functions (memory_get_usage, memory_get_peak_usage, hrtime) are NOT
		// whitelisted - no legitimate calculation use case, information disclosure risk.
		// NOTE: GC functions (gc_collect_cycles, gc_enable, gc_disable, etc.) are NOT
		// whitelisted - no legitimate calculation use case, could cause performance issues.

		// Miscellaneous safe functions
		'pack',
		'unpack',
		'version_compare',
		'constant',
		'defined',

		// URL/Query string parsing functions
		// NOTE: parse_str is NOT included here - it's conditionally added in execute() for PHP 8.0+ only.
		// In PHP 7.x, parse_str's second parameter is optional:
		//   parse_str('foo=bar') creates $foo='bar' in the current scope (variable injection)
		// This allows untrusted LLM-generated code to overwrite variables in the sandbox scope,
		// potentially manipulating control flow or return values.
		// In PHP 8.0+, the second parameter is mandatory, making it safe:
		//   parse_str('foo=bar', $result) only populates $result array
		// Note: parse_str('...', $_GET) is blocked because superglobals are validated at AST level.
		'parse_url',
		'http_build_query',

		// Path string manipulation functions
		// SAFETY: These functions operate naively on input strings and are NOT aware of the
		// actual filesystem. They do NOT access the filesystem, resolve symlinks, or check
		// file existence. They are pure string manipulation functions useful for URL path
		// parsing and similar tasks.
		// NOTE: realpath() is NOT whitelisted - it actually accesses the filesystem.
		'basename',
		'dirname',
		'pathinfo',

		// CSV parsing
		'str_getcsv',

		// XML parsing functions
		// SAFETY: XML parsing is safe here because:
		// 1. PHPSandbox blocks ALL constants when no whitelist/blacklist is set (checkConst case 3)
		// 2. This prevents LIBXML_NOENT, LIBXML_DTDLOAD, etc. which enable XXE attacks
		// 3. File/URL loading functions are NOT whitelisted (simplexml_load_file, etc.)
		// 4. PHP 8.0+ with libxml 2.9.0+ disables external entities by default anyway
		// WARNING: Do NOT whitelist simplexml_load_file, DOMDocument::load(), or XMLReader
		// WARNING: Do NOT call blacklistConst() - it enables all non-blacklisted constants!
		'simplexml_load_string',
		'simplexml_import_dom',
		'dom_import_simplexml',
	);

	/**
	 * Whitelisted classes - only these classes can be instantiated in the sandbox.
	 */
	private const WHITELISTED_CLASSES = array(
		// Standard classes
		'stdClass',
		'Exception',
		'ErrorException',
		'Error',
		'TypeError',
		'ArgumentCountError',
		'ArithmeticError',
		'DivisionByZeroError',

		// DateTime classes
		'DateTime',
		'DateTimeImmutable',
		'DateTimeZone',
		'DateInterval',
		'DatePeriod',

		// DateTime exception classes (PHP 8.3+)
		'DateError',
		'DateException',
		'DateInvalidOperationException',
		'DateInvalidTimeZoneException',
		'DateMalformedIntervalStringException',
		'DateMalformedPeriodStringException',
		'DateMalformedStringException',
		'DateObjectError',
		'DateRangeError',

		// Iterator classes
		'ArrayIterator',
		'RecursiveArrayIterator',
		'ArrayObject',
		'EmptyIterator',
		'InfiniteIterator',
		'LimitIterator',
		'NoRewindIterator',
		'MultipleIterator',
		'CachingIterator',
		'RecursiveCachingIterator',
		'FilterIterator',
		'CallbackFilterIterator',
		'RecursiveFilterIterator',
		'RecursiveCallbackFilterIterator',
		'ParentIterator',
		'RegexIterator',
		'RecursiveRegexIterator',
		'AppendIterator',
		'RecursiveIteratorIterator',
		'IteratorIterator',

		// SPL data structures
		'SplDoublyLinkedList',
		'SplStack',
		'SplQueue',
		'SplHeap',
		'SplMinHeap',
		'SplMaxHeap',
		'SplPriorityQueue',
		'SplFixedArray',
		'SplObjectStorage',

		// Other safe classes
		'Closure',
		'Generator',
		'WeakReference',
		'WeakMap',

		// XML parsing classes
		// SAFETY: See notes above WHITELISTED_FUNCTIONS XML section
		// These are safe because constants are blocked (no LIBXML_NOENT, etc.)
		'SimpleXMLElement',
		'DOMDocument',
		'DOMXPath',
		'DOMElement',
		'DOMNode',
		'DOMNodeList',
		'DOMAttr',
		'DOMText',
		'DOMComment',
		'DOMCdataSection',
		'DOMProcessingInstruction',

		// Intl formatting classes
		'NumberFormatter',
		'MessageFormatter',
		'IntlDateFormatter',
		'IntlDatePatternGenerator',
		'Locale',

		// Intl string/character classes
		'Collator',
		'Normalizer',
		'IntlChar',
		'Transliterator',
		'Spoofchecker',
		'UConverter',

		// Intl calendar/timezone classes
		'IntlCalendar',
		'IntlGregorianCalendar',
		'IntlTimeZone',

		// Intl iterator classes
		'IntlIterator',
		'IntlBreakIterator',
		'IntlRuleBasedBreakIterator',
		'IntlCodePointBreakIterator',
		'IntlPartsIterator',

		// Intl exceptions
		'IntlException',

		// NOTE: ResourceBundle is NOT whitelisted - it can access filesystem paths.

		// Random classes (PHP 8.2+)
		'Random\\Randomizer',
		'Random\\Engine\\Mt19937',
		'Random\\Engine\\PcgOneseq128XslRr64',
		'Random\\Engine\\Xoshiro256StarStar',
		'Random\\Engine\\Secure',
		'Random\\IntervalBoundary',
		'Random\\RandomError',
		'Random\\BrokenRandomEngineError',
		'Random\\RandomException',

		// Additional exception/error classes
		'JsonException',
		'ValueError',
		'UnhandledMatchError',
	);

	/**
	 * Whitelisted constants - only these constants can be used in the sandbox.
	 *
	 * SECURITY NOTE: Do NOT whitelist:
	 * - LIBXML_* constants (enable XXE attacks)
	 * - GLOB_* constants (filesystem traversal)
	 * - PHP_BINARY, PHP_SAPI, etc. (information disclosure)
	 */
	private const WHITELISTED_CONSTANTS = array(
		// HTML entity constants (for htmlentities, htmlspecialchars, etc.)
		'ENT_COMPAT',
		'ENT_QUOTES',
		'ENT_NOQUOTES',
		'ENT_IGNORE',
		'ENT_SUBSTITUTE',
		'ENT_DISALLOWED',
		'ENT_HTML401',
		'ENT_XML1',
		'ENT_XHTML',
		'ENT_HTML5',

		// String padding constants (for str_pad)
		'STR_PAD_LEFT',
		'STR_PAD_RIGHT',
		'STR_PAD_BOTH',

		// phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- Documentation comment listing PHP functions that use these constants.
		// Case constants (for array_change_key_case, mb_convert_case)
		'CASE_LOWER',
		'CASE_UPPER',
		'MB_CASE_UPPER',
		'MB_CASE_LOWER',
		'MB_CASE_TITLE',
		'MB_CASE_FOLD',
		'MB_CASE_UPPER_SIMPLE',
		'MB_CASE_LOWER_SIMPLE',
		'MB_CASE_TITLE_SIMPLE',
		'MB_CASE_FOLD_SIMPLE',

		// Sort constants (for sort, usort, array_multisort, etc.)
		'SORT_REGULAR',
		'SORT_NUMERIC',
		'SORT_STRING',
		'SORT_LOCALE_STRING',
		'SORT_NATURAL',
		'SORT_FLAG_CASE',

		// Array constants
		'ARRAY_FILTER_USE_KEY',
		'ARRAY_FILTER_USE_BOTH',
		'COUNT_NORMAL',
		'COUNT_RECURSIVE',

		// JSON encoding constants
		'JSON_HEX_TAG',
		'JSON_HEX_AMP',
		'JSON_HEX_APOS',
		'JSON_HEX_QUOT',
		'JSON_FORCE_OBJECT',
		'JSON_NUMERIC_CHECK',
		'JSON_PRETTY_PRINT',
		'JSON_UNESCAPED_SLASHES',
		'JSON_UNESCAPED_UNICODE',
		'JSON_PARTIAL_OUTPUT_ON_ERROR',
		'JSON_PRESERVE_ZERO_FRACTION',
		'JSON_UNESCAPED_LINE_TERMINATORS',
		'JSON_OBJECT_AS_ARRAY',
		'JSON_BIGINT_AS_STRING',
		'JSON_THROW_ON_ERROR',
		'JSON_INVALID_UTF8_IGNORE',
		'JSON_INVALID_UTF8_SUBSTITUTE',

		// JSON error constants
		'JSON_ERROR_NONE',
		'JSON_ERROR_DEPTH',
		'JSON_ERROR_STATE_MISMATCH',
		'JSON_ERROR_CTRL_CHAR',
		'JSON_ERROR_SYNTAX',
		'JSON_ERROR_UTF8',
		'JSON_ERROR_RECURSION',
		'JSON_ERROR_INF_OR_NAN',
		'JSON_ERROR_UNSUPPORTED_TYPE',
		'JSON_ERROR_INVALID_PROPERTY_NAME',
		'JSON_ERROR_UTF16',

		// Math constants
		'M_PI',
		'M_E',
		'M_LOG2E',
		'M_LOG10E',
		'M_LN2',
		'M_LN10',
		'M_PI_2',
		'M_PI_4',
		'M_1_PI',
		'M_2_PI',
		'M_SQRTPI',
		'M_2_SQRTPI',
		'M_SQRT2',
		'M_SQRT3',
		'M_SQRT1_2',
		'M_LNPI',
		'M_EULER',
		'INF',
		'NAN',

		// PHP rounding constants
		'PHP_ROUND_HALF_UP',
		'PHP_ROUND_HALF_DOWN',
		'PHP_ROUND_HALF_EVEN',
		'PHP_ROUND_HALF_ODD',

		// PHP integer/float constants (safe numeric limits)
		'PHP_INT_MAX',
		'PHP_INT_MIN',
		'PHP_INT_SIZE',
		'PHP_FLOAT_DIG',
		'PHP_FLOAT_EPSILON',
		'PHP_FLOAT_MIN',
		'PHP_FLOAT_MAX',

		// PCRE pattern constants
		'PREG_PATTERN_ORDER',
		'PREG_SET_ORDER',
		'PREG_OFFSET_CAPTURE',
		'PREG_UNMATCHED_AS_NULL',
		'PREG_SPLIT_NO_EMPTY',
		'PREG_SPLIT_DELIM_CAPTURE',
		'PREG_SPLIT_OFFSET_CAPTURE',
		'PREG_GREP_INVERT',

		// PCRE error constants
		'PREG_NO_ERROR',
		'PREG_INTERNAL_ERROR',
		'PREG_BACKTRACK_LIMIT_ERROR',
		'PREG_RECURSION_LIMIT_ERROR',
		'PREG_BAD_UTF8_ERROR',
		'PREG_BAD_UTF8_OFFSET_ERROR',
		'PREG_JIT_STACKLIMIT_ERROR',

		// Date/Time format constants
		'DATE_ATOM',
		'DATE_COOKIE',
		'DATE_ISO8601',
		'DATE_ISO8601_EXPANDED',
		'DATE_RFC822',
		'DATE_RFC850',
		'DATE_RFC1036',
		'DATE_RFC1123',
		'DATE_RFC2822',
		'DATE_RFC3339',
		'DATE_RFC3339_EXTENDED',
		'DATE_RFC7231',
		'DATE_RSS',
		'DATE_W3C',

		// Filter validation constants
		'FILTER_VALIDATE_BOOLEAN',
		'FILTER_VALIDATE_BOOL',
		'FILTER_VALIDATE_EMAIL',
		'FILTER_VALIDATE_FLOAT',
		'FILTER_VALIDATE_INT',
		'FILTER_VALIDATE_IP',
		'FILTER_VALIDATE_MAC',
		'FILTER_VALIDATE_REGEXP',
		'FILTER_VALIDATE_URL',
		'FILTER_VALIDATE_DOMAIN',

		// Filter sanitize constants
		'FILTER_SANITIZE_EMAIL',
		'FILTER_SANITIZE_ENCODED',
		'FILTER_SANITIZE_NUMBER_FLOAT',
		'FILTER_SANITIZE_NUMBER_INT',
		'FILTER_SANITIZE_SPECIAL_CHARS',
		'FILTER_SANITIZE_FULL_SPECIAL_CHARS',
		'FILTER_SANITIZE_URL',
		'FILTER_SANITIZE_ADD_SLASHES',
		'FILTER_UNSAFE_RAW',
		'FILTER_DEFAULT',

		// Filter flag constants
		'FILTER_FLAG_STRIP_LOW',
		'FILTER_FLAG_STRIP_HIGH',
		'FILTER_FLAG_STRIP_BACKTICK',
		'FILTER_FLAG_ALLOW_FRACTION',
		'FILTER_FLAG_ALLOW_THOUSAND',
		'FILTER_FLAG_ALLOW_SCIENTIFIC',
		'FILTER_FLAG_NO_ENCODE_QUOTES',
		'FILTER_FLAG_ENCODE_LOW',
		'FILTER_FLAG_ENCODE_HIGH',
		'FILTER_FLAG_ENCODE_AMP',
		'FILTER_FLAG_IPV4',
		'FILTER_FLAG_IPV6',
		'FILTER_FLAG_HOSTNAME',
		'FILTER_FLAG_EMAIL_UNICODE',
		'FILTER_NULL_ON_FAILURE',
		'FILTER_REQUIRE_SCALAR',
		'FILTER_REQUIRE_ARRAY',
		'FILTER_FORCE_ARRAY',

		// Boolean constants
		'TRUE',
		'FALSE',
		'NULL',

		// Password hashing constants
		'PASSWORD_DEFAULT',
		'PASSWORD_BCRYPT',
		'PASSWORD_ARGON2I',
		'PASSWORD_ARGON2ID',
		'PASSWORD_BCRYPT_DEFAULT_COST',

		// Grapheme constants (global constants, not class constants)
		'GRAPHEME_EXTR_COUNT',
		'GRAPHEME_EXTR_MAXBYTES',
		'GRAPHEME_EXTR_MAXCHARS',

		// Compression (zlib) constants
		'ZLIB_ENCODING_RAW',
		'ZLIB_ENCODING_GZIP',
		'ZLIB_ENCODING_DEFLATE',
		'ZLIB_FILTERED',
		'ZLIB_HUFFMAN_ONLY',
		'ZLIB_RLE',
		'ZLIB_FIXED',
		'ZLIB_DEFAULT_STRATEGY',
		'ZLIB_BLOCK',
		'ZLIB_NO_FLUSH',
		'ZLIB_PARTIAL_FLUSH',
		'ZLIB_SYNC_FLUSH',
		'ZLIB_FULL_FLUSH',
		'ZLIB_FINISH',

		// LIBXML safe constants (formatting/behavior flags - NO XXE risk)
		// SAFETY: These constants only control parser behavior for formatting, error handling,
		// and output options. They do NOT enable external entity loading or network access.
		// NOTE: LIBXML_NOENT, LIBXML_DTDLOAD, LIBXML_DTDATTR, LIBXML_DTDVALID, LIBXML_XINCLUDE
		// are NOT whitelisted - they enable XXE attacks or external file inclusion.
		'LIBXML_NOCDATA',         // Merge CDATA as text nodes
		'LIBXML_NOBLANKS',        // Remove blank/whitespace nodes
		'LIBXML_NOERROR',         // Suppress error reports
		'LIBXML_NOWARNING',       // Suppress warning reports
		'LIBXML_COMPACT',         // Optimize small node allocation
		'LIBXML_NOXMLDECL',       // Drop XML declaration on save
		'LIBXML_NOEMPTYTAG',      // Expand empty tags (e.g., <br/> to <br></br>)
		'LIBXML_NSCLEAN',         // Remove redundant namespace declarations
		'LIBXML_PEDANTIC',        // Enable pedantic error reporting
		'LIBXML_BIGLINES',        // Allow line numbers > 65535
		'LIBXML_NONET',           // Disable network access (defensive)
		'LIBXML_HTML_NOIMPLIED',  // Don't add implied html/body elements
		'LIBXML_HTML_NODEFDTD',   // Don't add default doctype
		'LIBXML_ERR_NONE',        // Error level: no errors
		'LIBXML_ERR_WARNING',     // Error level: warning
		'LIBXML_ERR_ERROR',       // Error level: recoverable error
		'LIBXML_ERR_FATAL',       // Error level: fatal error

		// Path info constants (for use with pathinfo() function)
		'PATHINFO_DIRNAME',
		'PATHINFO_BASENAME',
		'PATHINFO_EXTENSION',
		'PATHINFO_FILENAME',

		// Note: Class constants (e.g., NumberFormatter::CURRENCY, IntlDateFormatter::FULL)
		// are automatically available when the class is whitelisted - no need to list them here.
	);

	/**
	 * Get the tool ID.
	 *
	 * @return string The tool ID.
	 * @since     1.0.0
	 */
	public function get_id() {
		return 'calculate_php';
	}

	/**
	 * Get the tool name.
	 *
	 * @return string The tool name.
	 * @since     1.0.0
	 */
	public function get_name() {
		return 'Calculate PHP';
	}

	/**
	 * Get the tool description.
	 *
	 * @return string The tool description.
	 * @since     1.0.0
	 */
	public function get_description() {
		return 'Execute PHP code in a safe sandbox for calculations and data processing.';
	}

	/**
	 * Get additional description instructions for the LLM.
	 *
	 * @return string Additional instructions appended to description.
	 * @since     1.0.0
	 */
	public function get_description_instructions() {
		return 'Use for: math calculations (arbitrary precision with BCMath/GMP), date/time operations (parsing, formatting, timezone conversions), string processing (regex, multibyte, Unicode normalization), array manipulation, JSON/XML data transformation, number/currency formatting (Intl), compression (gzip/bzip2), hashing, and complex multi-step computations.';
	}

	/**
	 * Get usage instructions for the LLM.
	 *
	 * @return string Instructions for using this tool.
	 * @since     1.0.0
	 */
	public function get_instructions() {
		return 'PHP Version: ' . PHP_VERSION . PHP_EOL . '
SANDBOX RULES:
- Code runs in isolated sandbox with NO access to: filesystem, network, database, WordPress functions, sessions, or environment variables
- No <?php tags. Use "return" statement for output (e.g., "return $result;")
- Maximum execution time: 5 seconds. Memory limit enforced

ALLOWED CAPABILITIES:
- Math: +, -, *, /, %, **, all math functions (sin, cos, sqrt, log, etc.), BCMath (bcadd, bcmul, bcdiv, etc.), GMP (arbitrary precision integers)
- Strings: All string functions, preg_* (regex), mb_* (multibyte), iconv, grapheme_* (Unicode)
- Arrays: All array functions (map, filter, reduce, sort, etc.)
- Date/Time: DateTime, DateTimeImmutable, DateTimeZone, DateInterval, DatePeriod, date(), strtotime(), etc. Note: date_default_timezone_set() is blocked; use DateTimeZone objects instead
- JSON: json_encode, json_decode, json_validate (PHP 8.3+)
- Hashing: hash, hash_hmac, md5, sha1, crc32, password_hash, password_verify
- Intl: NumberFormatter (currency/number formatting), IntlDateFormatter, Collator, Transliterator, Normalizer, IntlChar (Unicode properties)
- Compression: gzcompress, gzuncompress, gzencode, gzdecode, bzcompress, bzdecompress (in-memory only)
- XML: SimpleXMLElement, DOMDocument, DOMXPath (string parsing only, no file loading)
- Data structures: SplFixedArray, SplHeap, SplStack, SplQueue, SplPriorityQueue, ArrayObject, iterators
- Types: All type-checking functions (is_*, gettype), casting, serialize (not unserialize)

NOT ALLOWED (blocked):
- File operations: fopen, file_get_contents, include, require, glob, etc.
- Network: curl_*, fsockopen, stream_socket_*, file_get_contents with URLs
- Execution: exec, shell_exec, system, passthru, proc_open, eval, create_function
- Database: mysqli_*, PDO, wpdb, any DB functions
- WordPress: No WP functions, hooks, globals, or options
- Reflection: ReflectionClass, ReflectionMethod, etc.
- Superglobals: $_GET, $_POST, $_SERVER, $_ENV, $_SESSION, etc.
- Deserialization: unserialize (security risk)
- Process state: ini_set, putenv, set_time_limit, error_reporting

EXAMPLES:
1. Calculate compound interest: return pow(1 + 0.05/12, 12*10) * 1000;
2. Parse and format date: return (new DateTime("2024-03-15"))->format("l, F j, Y");
3. Format currency: $fmt = new NumberFormatter("en_US", NumberFormatter::CURRENCY); return $fmt->formatCurrency(1234.56, "USD");
4. Regex extraction: preg_match_all("/\d+/", "abc123def456", $m); return $m[0];
5. Hash password: return password_hash("secret", PASSWORD_DEFAULT);';
	}

	/**
	 * Check if this tool is read-only.
	 *
	 * @return bool True if the tool only reads data.
	 * @since     1.0.0
	 */
	public function is_read_only() {
		return true;
	}

	/**
	 * Get the parameters schema for this tool.
	 *
	 * @return array Parameters schema in JSON Schema format.
	 * @since     1.0.0
	 */
	public function get_parameters() {
		return array(
			'type'       => 'object',
			'properties' => array(
				'code' => array(
					'type'        => 'string',
					'description' => 'PHP code to execute. No <?php tags. Use return for output.',
				),
			),
			'required'   => array( 'code' ),
		);
	}

	/**
	 * Check if the current user can execute this tool.
	 *
	 * @return bool True if the current user has permission.
	 * @since     1.0.0
	 */
	public function current_user_can_execute() {
		return current_user_can( 'read' );
	}

	/**
	 * Execute the tool with the given arguments.
	 *
	 * @param array $args The tool arguments.
	 *
	 * @return array The tool execution result.
	 * @since     1.0.0
	 */
	protected function execute( $args ) {
		$code = $args['code'];

		$sandbox = new PHPSandbox();
		$sandbox->whitelistFunc( self::WHITELISTED_FUNCTIONS );
		$sandbox->whitelistClass( self::WHITELISTED_CLASSES );
		$sandbox->whitelistType( self::WHITELISTED_CLASSES ); // Types are checked separately from classes
		$sandbox->whitelistConst( self::WHITELISTED_CONSTANTS );

		// parse_str is only safe in PHP 8.0+ where the second parameter is mandatory.
		// In PHP 7.x, calling parse_str('foo=bar') without second param creates $foo in current scope.
		if ( PHP_VERSION_ID >= 80000 ) {
			$sandbox->whitelistFunc( 'parse_str' );
		}

		// SECURITY NOTE: Do NOT call blacklistConst() here!
		// PHPSandbox's checkConst() logic is:
		//   1. If whitelist exists → only whitelisted constants allowed (current state)
		//   2. If blacklist exists (no whitelist) → only blacklisted blocked, ALL OTHERS ALLOWED
		//   3. If neither → ALL constants blocked
		// We use case 1 (whitelist) to allow safe constants while blocking LIBXML_NOENT, etc.
		// Calling blacklistConst() would switch to case 2, enabling all non-blacklisted constants!

		$sandbox->allow_functions         = true;
		$sandbox->allow_closures          = true;
		$sandbox->allow_constants         = true;
		$sandbox->allow_casting           = true;
		$sandbox->allow_error_suppressing = true;
		$sandbox->allow_variables         = true;
		$sandbox->allow_static_variables  = true;
		$sandbox->allow_objects           = true;
		$sandbox->allow_generators        = true;
		$sandbox->allow_references        = true;
		$sandbox->convert_errors          = true;

		try {
			ob_start();
			$return_value = $sandbox->execute( $code );
			$output       = ob_get_clean();
		} catch ( \Throwable $e ) {
			ob_end_clean();
			return $this->error_response( $e->getMessage() );
		}

		$result = array();
		if ( '' !== $output ) {
			$result['output'] = $output;
		}
		if ( null !== $return_value ) {
			$result['return_value'] = $return_value;
		}

		return $this->success_response( 'Calculated', $result );
	}
}
