"SECRET_EXAMPLE"]);
}
// === Security settings ===
// Block duration in seconds (48 hours by default)
if (!defined('BLOCK_DURATION_SECONDS')) {
define('BLOCK_DURATION_SECONDS', 48 * 3600); // Duration of IP block after failures
}
} else {
// If the file was accessed directly, deny access
error_deny();
}
/**
* Sends a standard 403 Forbidden HTTP response with an HTML message
* and terminates the script.
*/
function error_deny() : void {
// Set HTTP response status code to 403
http_response_code(403);
// Explicitly send HTTP headers for 403 Forbidden and HTML content type
header('HTTP/1.1 403 Forbidden');
header('Content-Type: text/html; charset=utf-8');
// Output a simple HTML page explaining the forbidden access
echo "\n";
echo "
\n";
echo "403 Forbidden\n";
echo "\n";
echo "\n";
echo "Forbidden
\n";
echo "You do not have permission to access this document.\n";
echo "\n";
echo "
\n";
echo "\n";
echo "Web Server at " . $_SERVER['SERVER_NAME'] . "\n";
echo "\n";
echo "\n";
echo "\n";
// Exit the script with status code 403
exit(403);
}
/**
* Retrieves a request parameter from POST or GET with fallback to default.
*
* @param string $name The name of the parameter to retrieve.
* @param mixed $def The default value if the parameter is not found or empty.
* @return mixed The parameter value or the default.
*/
function getQuery(string $name, mixed $def): mixed {
// Check POST first, then GET, fallback to default
$val = $_POST[$name] ?? $_GET[$name] ?? $def;
// Return default if value is empty (including trimmed empty strings)
return isEmpty($val) ? $def : $val;
}
/**
* Retrieves multiple request parameters from POST or GET with fallback to default.
* Key matching is case-insensitive.
*
* @param array $params Array of parameters in the format:
* [
* 'KeyName' => defaultValue,
* ...
* ]
* @return array Associative array:
* [
* 'KeyName' => [
* 'value' => mixed,
* 'source' => 'post' | 'get' | 'default'
* ],
* ...
* ]
*/
function getQueryMulti(array $params): array {
$result = [];
// Lowercase copies of GET and POST for case-insensitive matching
$post = array_change_key_case($_POST, CASE_LOWER);
$get = array_change_key_case($_GET, CASE_LOWER);
foreach ($params as $key => $default) {
$keyLower = strtolower($key);
$value = $default;
$source = 'default';
if (isset($post[$keyLower]) && !isEmpty($post[$keyLower])) {
$value = $post[$keyLower];
$source = 'post';
} elseif (isset($get[$keyLower]) && !isEmpty($get[$keyLower])) {
$value = $get[$keyLower];
$source = 'get';
}
$result[$key] = [
'value' => $value,
'source' => $source
];
}
return $result;
}
/**
* Checks if a given value is considered "empty".
* - Returns true for null, empty strings, empty arrays, 0, "0", etc. (like PHP's `empty()`)
* - Additionally, for strings, also trims whitespace before checking emptiness
*
* @param mixed $val The value to evaluate
* @return bool True if the value is empty or whitespace-only, false otherwise
*/
function isEmpty(mixed $val): bool {
// Handle typical empty values (null, "", 0, [], false, etc.)
if (empty($val)) {
return true;
}
// If it's a string, trim and check again
if (is_string($val)) {
if (trim($val) === '') {
return true;
}
}
return false;
}
/**
* Loads and parses a JSON file into an associative array.
*
* Checks if the file exists and is readable before attempting to decode it.
* If the JSON is invalid, an error message is printed.
* If decoding fails or the result is not an array, an empty array is returned.
*
* @param string $filename Path to the JSON file
* @return array Parsed associative array or empty array on error
*/
function loadJson(string $filename): array {
$result = [];
// Attempt to read and decode the file if it exists and is readable
if (file_exists($filename) && is_readable($filename)) {
$result = json_decode(file_get_contents($filename), true);
// Log an error if JSON is invalid
if (json_last_error() !== JSON_ERROR_NONE) {
echo "Invalid JSON in $filename: " . json_last_error_msg() . "\n";
}
}
// Ensure the result is an array
if (!is_array($result)) {
$result = [];
}
return $result;
}
/**
* Saves a PHP array to a JSON file in pretty format.
*
* - Ensures the target file is writable if it already exists.
* - Automatically replaces null or empty input with an empty array.
* - Returns true on success, false on any failure.
*
* @param string $filename The path to the JSON file to write
* @param array|null $val The data to save (null or empty → [])
* @return bool True if saved successfully, false otherwise
*/
function saveJson(string $filename, ?array $val): bool {
// Default to empty array if null or empty
if (isEmpty($val)) {
$val = [];
}
// Encode to JSON
$encoded = json_encode($val, JSON_PRETTY_PRINT);
if ($encoded === false) {
echo "Failed to encode $filename: " . json_last_error_msg() . "\n";
return false;
}
// If file exists, check for write permissions
if (file_exists($filename)) {
if (!is_writable($filename)) {
echo "No access to write in $filename\n";
return false;
}
}
-+
// Try writing up to 10 times with 250ms delay
$success = false;
$attempt = 0;
for ($attempt = 1; $attempt <= 10; $attempt++) {
if (@file_put_contents($filename, $encoded) !== false) {
$success = true;
break;
}
usleep(250 * 1000); // 250 ms delay
}
if (!$success) {
echo "Failed to write to $filename after $attempt attempts\n";
}
return $success;
}