parent
3aa76d8054
commit
2fae0452e9
@ -0,0 +1,142 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace CodeHydrater\bootstrap; |
||||
|
||||
/** |
||||
* @author http://php-fig.org/ <info@php-fig.org> |
||||
* @copyright Copyright (c) 2013-2017 PHP Framework Interop Group |
||||
* @license MIT |
||||
* @site https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md |
||||
*/ |
||||
class Psr4AutoloaderClass { |
||||
|
||||
/** |
||||
* An associative array where the key is a namespace prefix and the value |
||||
* is an array of base directories for classes in that namespace. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $prefixes = []; |
||||
protected $loaded_files = []; |
||||
|
||||
/** |
||||
* Register loader with SPL autoloader stack. |
||||
* |
||||
* @return void |
||||
*/ |
||||
public function register() { |
||||
spl_autoload_register(array($this, 'load_class')); |
||||
} |
||||
|
||||
public function is_loaded(string $prefix): bool { |
||||
$prefix = trim($prefix, '\\') . '\\'; |
||||
return (isset($this->prefixes[$prefix])) ? true : false; |
||||
} |
||||
|
||||
public function get_list(): array { |
||||
return $this->prefixes; |
||||
} |
||||
|
||||
public function get_files_list(): array { |
||||
return $this->loaded_files; |
||||
} |
||||
|
||||
/** |
||||
* Adds a base directory for a namespace prefix. |
||||
* |
||||
* @param string $prefix The namespace prefix. |
||||
* @param string $base_dir A base directory for class files in the |
||||
* namespace. |
||||
* @param bool $prepend If true, prepend the base directory to the stack |
||||
* instead of appending it; this causes it to be searched first rather |
||||
* than last. |
||||
* @return void |
||||
*/ |
||||
public function add_namespace(string $prefix, string $base_dir, bool $prepend = false): void { |
||||
$prefix = trim($prefix, '\\') . '\\'; |
||||
$base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/'; |
||||
if (isset($this->prefixes[$prefix]) === false) { |
||||
$this->prefixes[$prefix] = array(); |
||||
} |
||||
if ($prepend) { |
||||
array_unshift($this->prefixes[$prefix], $base_dir); |
||||
} else { |
||||
array_push($this->prefixes[$prefix], $base_dir); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Loads the class file for a given class name. |
||||
* |
||||
* @param string $class The fully-qualified class name. |
||||
* @return mixed The mapped file name on success, or boolean false on |
||||
* failure. |
||||
*/ |
||||
public function load_class(string $class): false|string { |
||||
if (! strrpos($class, '\\')) { |
||||
$ret = ($this->load_mapped_file($class . '\\', $class)); |
||||
if ($ret !== false) { |
||||
return $ret; |
||||
} |
||||
} |
||||
$prefix = $class; |
||||
while (false !== $pos = strrpos($prefix, '\\')) { |
||||
// retain the trailing namespace separator in the prefix |
||||
$prefix = substr($class, 0, $pos + 1); |
||||
$relative_class = substr($class, $pos + 1); |
||||
$mapped_file = $this->load_mapped_file($prefix, $relative_class); |
||||
if ($mapped_file) { |
||||
return $mapped_file; |
||||
} |
||||
// remove the trailing namespace separator for the next iteration |
||||
// of strrpos() |
||||
$prefix = rtrim($prefix, '\\'); |
||||
} |
||||
// never found a mapped file |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Load the mapped file for a namespace prefix and relative class. |
||||
* |
||||
* @param string $prefix The namespace prefix. |
||||
* @param string $relative_class The relative class name. |
||||
* @return mixed Boolean false if no mapped file can be loaded, or the |
||||
* name of the mapped file that was loaded. |
||||
*/ |
||||
protected function load_mapped_file(string $prefix, string $relative_class): false | string { |
||||
// are there any base directories for this namespace prefix? |
||||
if (isset($this->prefixes[$prefix]) === false) { |
||||
return false; |
||||
} |
||||
// look through base directories for this namespace prefix |
||||
foreach ($this->prefixes[$prefix] as $base_dir) { |
||||
$file = str_replace('\\', '/', $relative_class) . '.php'; |
||||
if ($this->require_file($base_dir, $file)) { |
||||
return $file; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* If a file exists, require it from the file system. |
||||
* |
||||
* @param string $file The file to require. |
||||
* @return bool True if the file exists, false if not. |
||||
*/ |
||||
private function require_file(string $path, string $file): bool { |
||||
$safer_file = requires::safer_file_exists($file, $path); |
||||
if ($safer_file !== false) { |
||||
if (! isset($this->loaded_files[$safer_file])) { |
||||
require $safer_file; |
||||
$this->loaded_files[$safer_file] = true; |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,296 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater\bootstrap; |
||||
|
||||
final class common { |
||||
|
||||
protected function __construct() { |
||||
|
||||
} |
||||
|
||||
public static function return_bool_as_int_bit(bool $b_data): int { |
||||
return ($b_data) ? 1 : 0; // if true=1, else =0 |
||||
} |
||||
|
||||
/** |
||||
* |
||||
* @param type $ret option to check for false error condition |
||||
* @return bool true if false/error found |
||||
*/ |
||||
public static function is_error($ret): bool { |
||||
$lr = self::string_to_lowercase(trim($ret)); |
||||
return ($ret === false || $lr === 'false') ? true : false; |
||||
} |
||||
|
||||
public static function return_bool_as_yes_no(bool $b_data): string { |
||||
return ($b_data) ? 'y' : 'n'; // if true=y, else =n |
||||
} |
||||
|
||||
public static function get_bool($bool, $throw = true): bool { |
||||
if (is_bool($bool)) { |
||||
return $bool; |
||||
} |
||||
if (is_string($bool)) { |
||||
$bool = self::string_to_lowercase(trim($bool)); |
||||
} |
||||
switch ($bool) { |
||||
case '0': |
||||
case 'false': |
||||
case ':false': |
||||
case ':null': |
||||
case null: |
||||
case 'no': |
||||
case 'n': |
||||
case 'disable': |
||||
case 'disabled': |
||||
return false; |
||||
case '1': |
||||
case 'true': |
||||
case ':true': |
||||
case 'yes': |
||||
case 'y': |
||||
case 'enable': |
||||
case 'enabled': |
||||
return true; |
||||
default: |
||||
if ($throw === true) { |
||||
throw new \CodeHydrater\exceptions\Bool_Exception("Value: ({$bool})"); |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static function get_count($i): int { |
||||
return (is_array($i) || is_object($i)) ? count($i) : 0; |
||||
} |
||||
|
||||
// Begin Strings Functions here:: |
||||
|
||||
public static function string_to_lowercase(string $string, $encoding = null): string { |
||||
if (null === $encoding) { |
||||
$encoding = mb_internal_encoding(); |
||||
} |
||||
|
||||
return (extension_loaded('mbstring')) ? mb_strtolower($string, $encoding) : strtolower($string); |
||||
} |
||||
|
||||
public static function string_to_uppercase(string $string, $encoding = null): string { |
||||
if (null === $encoding) { |
||||
$encoding = mb_internal_encoding(); |
||||
} |
||||
|
||||
return (extension_loaded('mbstring')) ? mb_strtoupper($string, $encoding) : strtoupper($string); |
||||
} |
||||
|
||||
public static function string_length(string $string, $encoding = null) { |
||||
if (null === $encoding) { |
||||
$encoding = mb_internal_encoding(); |
||||
} |
||||
|
||||
return (extension_loaded('mbstring')) ? mb_strlen($string, $encoding) : strlen($string); |
||||
} |
||||
|
||||
public static function string_position(string $string, string $needle, int $offset = 0, $encoding = null) { |
||||
if (null === $encoding) { |
||||
$encoding = mb_internal_encoding(); |
||||
} |
||||
|
||||
return (extension_loaded('mbstring')) ? mb_strpos($string, $needle, $offset, $encoding) : strpos($string, $needle, $offset); |
||||
} |
||||
|
||||
public static function string_last_position(string $string, string $needle, int $offset = 0, $encoding = null) { |
||||
if (null === $encoding) { |
||||
$encoding = mb_internal_encoding(); |
||||
} |
||||
|
||||
return (extension_loaded('mbstring')) ? mb_strrpos($string, $needle, $offset, $encoding) : strrpos($string, $needle, $offset); |
||||
} |
||||
|
||||
public static function string_trim($string, $charlist = null) { |
||||
if (is_null($charlist)) { |
||||
return trim($string); |
||||
} else { |
||||
$charlist = preg_quote($charlist, '/'); |
||||
return preg_replace("/(^[$charlist]+)|([$charlist]+$)/us", '', $string); |
||||
} |
||||
} |
||||
|
||||
public static function string_rtrim($string, $charlist = null) { |
||||
if (is_null($charlist)) { |
||||
return rtrim($string); |
||||
} else { |
||||
$charlist = preg_quote($charlist, '/'); |
||||
return preg_replace("/([$charlist]+$)/us", '', $string); |
||||
} |
||||
} |
||||
|
||||
public static function string_ltrim($string, $charlist = null) { |
||||
if (is_null($charlist)) { |
||||
return ltrim($string); |
||||
} else { |
||||
$charlist = preg_quote($charlist, '/'); |
||||
return preg_replace("/(^[$charlist]+)/us", '', $string); |
||||
} |
||||
} |
||||
|
||||
public static function string_cmp($str1, $str2, $encoding = null) { |
||||
if (null === $encoding) { |
||||
$encoding = mb_internal_encoding(); |
||||
} |
||||
return strcmp(mb_strtoupper($str1, $encoding), mb_strtoupper($str2, $encoding)); |
||||
} |
||||
|
||||
/* |
||||
* Case-Insensitive Find |
||||
*/ |
||||
|
||||
public static function string_first_position(string $string, string $needle, int $offset = 0, $encoding = null) { |
||||
if (null === $encoding) { |
||||
$encoding = mb_internal_encoding(); |
||||
} |
||||
|
||||
return (extension_loaded('mbstring')) ? mb_stripos($string, $needle, $offset, $encoding) : stripos($string, $needle, $offset); |
||||
} |
||||
|
||||
public static function string_sub_part(string $string, int $offset = 0, int $length = null, $encoding = null) { |
||||
if (null === $encoding) { |
||||
$encoding = mb_internal_encoding(); |
||||
} |
||||
|
||||
if ($length === null) { |
||||
return (extension_loaded('mbstring')) ? mb_substr($string, $offset, self::string_length($string), $encoding) : substr($string, $offset, strlen($string)); |
||||
} else { |
||||
return (extension_loaded('mbstring')) ? mb_substr($string, $offset, $length, $encoding) : substr($string, $offset, $length); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Not really needed as str_contains($string, $needle) now exists! |
||||
* Will determine if data was found in string find and returns true if found. |
||||
* @param string $data |
||||
* @param string $find |
||||
* @retval bool |
||||
*/ |
||||
public static function is_string_found(string $data, string $find): bool { |
||||
return (self::string_first_position($data, $find) !== false); |
||||
} |
||||
|
||||
/** |
||||
* Will get only left part of string by length. |
||||
* @param string $str |
||||
* @param int $length |
||||
* @retval type string or false |
||||
*/ |
||||
public static function get_string_left(string $str, int $length): false | string { |
||||
return self::string_sub_part($str, 0, $length); |
||||
} |
||||
|
||||
public static function is_json(string $maybeJSON): bool { |
||||
$version = (float) phpversion(); |
||||
if ($version >= 8.3) { |
||||
return json_validate($maybeJSON); |
||||
} else { |
||||
$obj = json_decode($maybeJSON); |
||||
return (json_last_error() === JSON_ERROR_NONE) ? true : false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Will get only the right part of string by length. |
||||
* @param string $str |
||||
* @param int $length |
||||
* @retval type string or false |
||||
*/ |
||||
public static function get_string_right(string $str, int $length): false | string { |
||||
return self::string_sub_part($str, -$length); |
||||
} |
||||
|
||||
public static function real_time_output(): void { |
||||
header("Content-type: text/plain"); |
||||
// Turn off output buffering |
||||
ini_set('output_buffering', 'off'); |
||||
// Turn off PHP output compression |
||||
ini_set('zlib.output_compression', false); |
||||
|
||||
// Implicitly flush the buffer(s) |
||||
ini_set('implicit_flush', true); |
||||
ob_implicit_flush(true); |
||||
while (ob_get_level() > 0) { |
||||
// Get the curent level |
||||
$level = ob_get_level(); |
||||
// End the buffering |
||||
ob_end_clean(); |
||||
// If the current level has not changed, abort |
||||
if (ob_get_level() == $level) break; |
||||
} |
||||
} |
||||
/** |
||||
* Clear out from memory given variable by Reference! |
||||
* @param type $sensitive_data |
||||
*/ |
||||
public static function wipe(& $sensitive_data): void { |
||||
if (function_exists("sodium_memzero")) { |
||||
sodium_memzero($sensitive_data); |
||||
} |
||||
unset($sensitive_data); |
||||
} |
||||
|
||||
/** |
||||
* Variable Dump and exit |
||||
* Configure of security for show_dumps must be true for debugging. |
||||
* @param var - any type will display type and value of contents |
||||
* @param bool end - if true ends the script |
||||
*/ |
||||
public static function dump($var = 'nothing', $end = true): void { |
||||
if (\main_tts\configure::get('security', 'show_dumps') !== true) { |
||||
return; |
||||
} |
||||
if (!is_object($var)) { |
||||
var_dump($var); |
||||
echo '<br>'; |
||||
} |
||||
|
||||
if ($var === false) { |
||||
echo 'It is FALSE!'; |
||||
} elseif ($var === true) { |
||||
echo 'It is TRUE!'; |
||||
} elseif (is_resource($var)) { |
||||
echo 'VAR IS a RESOURCE'; |
||||
} elseif (is_array($var) && self::get_count($var) == 0) { |
||||
echo 'VAR IS an EMPTY ARRAY!'; |
||||
} elseif (is_numeric($var)) { |
||||
echo 'VAR is a NUMBER = ' . $var; |
||||
} elseif (empty($var) && !is_null($var)) { |
||||
echo 'VAR IS EMPTY!'; |
||||
} elseif ($var == 'nothing') { |
||||
echo 'MISSING VAR!'; |
||||
} elseif (is_null($var)) { |
||||
echo 'VAR IS NULL!'; |
||||
} elseif (is_string($var)) { |
||||
echo 'VAR is a STRING = ' . $var; |
||||
} else { |
||||
echo "<pre style=\"border: 1px solid #000; overflow: auto; margin: 0.5em;\">"; |
||||
print_r($var); |
||||
echo '</pre>'; |
||||
} |
||||
echo '<br><br>'; |
||||
|
||||
if ($end === true) { |
||||
exit; |
||||
} |
||||
} |
||||
|
||||
public static function nl2br(string $text): string { |
||||
return strtr($text, array("\r\n" => '<br />', "\r" => '<br />', "\n" => '<br />')); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,121 @@ |
||||
<?php |
||||
|
||||
namespace CodeHydrater\bootstrap; |
||||
|
||||
// Configuration |
||||
if (! defined('BaseDir')) { |
||||
define('LOG_FILE', __DIR__ . '/protected/logs/error_log.txt'); |
||||
} else { |
||||
define('LOG_FILE', BaseDir . '/protected/logs/error_log.txt'); |
||||
} |
||||
|
||||
if (! defined('ENVIRONMENT')) { |
||||
define('ENVIRONMENT', 'production'); // 'production' or 'development' |
||||
} |
||||
/** |
||||
* Format message with appropriate colors based on environment |
||||
*/ |
||||
function formatMessage($message, $type = 'error') { |
||||
if (PHP_SAPI === 'cli') { |
||||
// CLI color formatting |
||||
$colors = [ |
||||
'error' => "\033[31m", // red |
||||
'warning' => "\033[33m", // yellow |
||||
'notice' => "\033[36m", // cyan |
||||
'reset' => "\033[0m" // reset |
||||
]; |
||||
$color = $colors[$type] ?? $colors['error']; |
||||
return $color . $message . $colors['reset'] . PHP_EOL; |
||||
} else { |
||||
// Web HTML formatting |
||||
$styles = [ |
||||
'error' => 'color:red;', |
||||
'warning' => 'color:orange;', |
||||
'notice' => 'color:blue;' |
||||
]; |
||||
$style = $styles[$type] ?? $styles['error']; |
||||
return "<div style='{$style}padding:10px;border:1px solid #f99;margin:10px;'>$message</div>"; |
||||
} |
||||
} |
||||
|
||||
// Custom error handler |
||||
set_error_handler(function($errno, $errstr, $errfile, $errline) { |
||||
// Skip if error reporting is turned off |
||||
if (!(error_reporting() & $errno)) { |
||||
return false; |
||||
} |
||||
|
||||
$errorTypes = [ |
||||
E_ERROR => ['ERROR', 'error'], |
||||
E_WARNING => ['WARNING', 'warning'], |
||||
E_PARSE => ['PARSE ERROR', 'error'], |
||||
E_NOTICE => ['NOTICE', 'notice'], |
||||
E_CORE_ERROR => ['CORE ERROR', 'error'], |
||||
E_CORE_WARNING => ['CORE WARNING', 'warning'], |
||||
E_COMPILE_ERROR => ['COMPILE ERROR', 'error'], |
||||
E_COMPILE_WARNING => ['COMPILE WARNING', 'warning'], |
||||
E_USER_ERROR => ['USER ERROR', 'error'], |
||||
E_USER_WARNING => ['USER WARNING', 'warning'], |
||||
E_USER_NOTICE => ['USER NOTICE', 'notice'], |
||||
E_STRICT => ['STRICT', 'notice'], |
||||
E_RECOVERABLE_ERROR=> ['RECOVERABLE ERROR', 'error'], |
||||
E_DEPRECATED => ['DEPRECATED', 'warning'], |
||||
E_USER_DEPRECATED => ['USER DEPRECATED', 'warning'] |
||||
]; |
||||
|
||||
$errorInfo = $errorTypes[$errno] ?? ['UNKNOWN', 'error']; |
||||
$errorType = $errorInfo[0]; |
||||
$errorCategory = $errorInfo[1]; |
||||
|
||||
$logMessage = date('[Y-m-d H:i:s]') . " [$errorType] $errstr in $errfile on line $errline" . PHP_EOL; |
||||
$displayMessage = "$errorType: $errstr in $errfile on line $errline"; |
||||
|
||||
// Log to file |
||||
file_put_contents(LOG_FILE, $logMessage, FILE_APPEND); |
||||
|
||||
// Display in development environment |
||||
if (ENVIRONMENT === 'development') { |
||||
echo formatMessage($displayMessage, $errorCategory); |
||||
} |
||||
|
||||
// Prevent PHP's default error handler |
||||
return true; |
||||
}); |
||||
|
||||
// Handle exceptions |
||||
set_exception_handler(function($e) { |
||||
$logMessage = date('[Y-m-d H:i:s]') . " [EXCEPTION] " . $e->getMessage() . |
||||
" in " . $e->getFile() . " on line " . $e->getLine() . PHP_EOL; |
||||
$displayMessage = "UNCAUGHT EXCEPTION: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine(); |
||||
|
||||
file_put_contents(LOG_FILE, $logMessage, FILE_APPEND); |
||||
|
||||
if (ENVIRONMENT === 'development') { |
||||
echo formatMessage($displayMessage, 'error'); |
||||
} else { |
||||
// In production, show user-friendly message |
||||
echo PHP_SAPI === 'cli' |
||||
? "An error occurred. Our team has been notified." . PHP_EOL |
||||
: "An error occurred. Our team has been notified."; |
||||
} |
||||
}); |
||||
|
||||
// Handle fatal errors |
||||
register_shutdown_function(function() { |
||||
$error = error_get_last(); |
||||
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) { |
||||
$logMessage = date('[Y-m-d H:i:s]') . " [FATAL] {$error['message']} in {$error['file']} on line {$error['line']}" . PHP_EOL; |
||||
$displayMessage = "FATAL ERROR: {$error['message']} in {$error['file']} on line {$error['line']}"; |
||||
|
||||
file_put_contents(LOG_FILE, $logMessage, FILE_APPEND); |
||||
|
||||
if (ENVIRONMENT === 'development') { |
||||
echo formatMessage($displayMessage, 'error'); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
// Test the error handler (uncomment to test) |
||||
// trigger_error("This is a test warning", E_USER_WARNING); |
||||
// throw new Exception("This is a test exception"); |
||||
// nonexistentFunction(); // Will trigger a fatal error in shutdown handler |
||||
@ -0,0 +1,45 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts <Bob_586@Yahoo.com> |
||||
* @copyright (c) 2025, Robert Strutts |
||||
* @license MIT |
||||
*/ |
||||
namespace CodeHydrater\bootstrap; |
||||
|
||||
final class loadAll { |
||||
|
||||
public static function init(string $path): void { |
||||
$config_path = $path . "configs"; |
||||
if (is_dir($config_path)) { |
||||
$cdir = scandir($config_path); |
||||
if ($cdir !== false) { |
||||
self::doLoop($cdir, $config_path); |
||||
} |
||||
} |
||||
$service_path = $path . "services"; |
||||
if (is_dir($service_path)) { |
||||
$sdir = scandir($service_path); |
||||
if ($sdir !== false) { |
||||
self::doLoop($sdir, $service_path); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private static function doLoop(array $cdir, string $path): void { |
||||
foreach($cdir as $key => $file) { |
||||
$file = trim($file); |
||||
if ($file === '..' || $file === '.') { |
||||
continue; |
||||
} |
||||
$on = substr($file, 0, 3); // grab first three letters |
||||
if ($on !== 'on_') { |
||||
continue; // It's not ON, so skip it! |
||||
} |
||||
$file_with_path = $path ."/" . $file; |
||||
requires::secure_include($file_with_path, UseDir::FIXED); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,297 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater\bootstrap; |
||||
|
||||
// Polyfill for PHP 4 to 7 |
||||
if (!function_exists('str_contains')) { |
||||
function str_contains($haystack, $needle) { |
||||
return $needle !== '' && mb_strpos($haystack, $needle) !== false; |
||||
} |
||||
} |
||||
|
||||
unset($_REQUEST); // Request is dangerious as order of Vars is not Known |
||||
unset($_GET); // Super Globals are not Filtered, so unset them |
||||
unset($_POST); |
||||
|
||||
$mem_baseline = memory_get_usage(); |
||||
|
||||
require_once CodeHydrater_FRAMEWORK . 'bootstrap/errors.php'; |
||||
|
||||
final class views { |
||||
public static function ob_start(): void { |
||||
if (extension_loaded('mbstring')) { |
||||
ob_start('mb_output_handler'); |
||||
} else { |
||||
ob_start(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (siteHelper::get_testing() === false) { |
||||
views::ob_start(); |
||||
} |
||||
|
||||
final class errors { |
||||
private static $handle_global_errors = true; |
||||
private static $handle_shutdown_errors = true; |
||||
private static $handle_exceptions = true; |
||||
|
||||
public static function set_handle_shutdown_errors(bool $do_handle_errors): void { |
||||
self::$handle_shutdown_errors = $do_handle_errors; |
||||
} |
||||
public static function get_handle_shutdown_errors(): bool { |
||||
return self::$handle_shutdown_errors; |
||||
} |
||||
public static function set_handle_exceptions(bool $do_handle_errors): void { |
||||
self::$handle_exceptions = $do_handle_errors; |
||||
} |
||||
public static function get_handle_exceptions(): bool { |
||||
return self::$handle_exceptions; |
||||
} |
||||
public static function set_handle_global_errors(bool $do_handle_errors): void { |
||||
self::$handle_global_errors = $do_handle_errors; |
||||
} |
||||
public static function get_handle_global_errors(): bool { |
||||
return self::$handle_global_errors; |
||||
} |
||||
} |
||||
|
||||
final class configure { |
||||
|
||||
private static $config = []; |
||||
|
||||
protected function __construct() { |
||||
|
||||
} |
||||
|
||||
public static function exists() { // an Alias to has |
||||
return self::has(func_get_args()); |
||||
} |
||||
|
||||
public static function has(string $name, $key = false): bool { |
||||
if ($key === false) { |
||||
return (isset(self::$config[strtolower($name)])) ? true : false; |
||||
} |
||||
return (isset(self::$config[strtolower($name)][strtolower($key)])) ? true : false; |
||||
} |
||||
|
||||
public static function get(string $name, $key = false) { |
||||
if (isset(self::$config[strtolower($name)])) { |
||||
$a = self::$config[strtolower($name)]; |
||||
if ($key === false) { |
||||
return $a; |
||||
} |
||||
if (isset($a[$key])) { |
||||
return $a[$key]; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public static function update() { // an Alias to set |
||||
return self::set(func_get_args()); |
||||
} |
||||
|
||||
public static function set(string $name, $value): void { |
||||
self::$config[strtolower($name)] = $value; |
||||
} |
||||
|
||||
public static function set_key(string $name, string $key, $value): void { |
||||
self::$config[strtolower($name)][strtolower($key)] = $value; |
||||
} |
||||
|
||||
public static function add_to_key(string $name, string $key, $value): void { |
||||
self::$config[strtolower($name)][strtolower($key)][] = $value; |
||||
} |
||||
|
||||
public static function wipe(string $name, $key = false): void { |
||||
if (! self::exists($name, $key)) { |
||||
return; |
||||
} |
||||
if ($key === false) { |
||||
common::wipe(self::$config[strtolower($name)]); |
||||
} |
||||
common::wipe(self::$config[strtolower($name)][strtolower($key)]); |
||||
} |
||||
|
||||
public static function load_array(array $a): void { |
||||
if (isset($a) && is_array($a)) { |
||||
foreach ($a as $name => $value) { |
||||
self::$config[$name] = $value; |
||||
} |
||||
} |
||||
unset($a); |
||||
} |
||||
|
||||
} |
||||
|
||||
final class registry { |
||||
|
||||
private static $registry = []; |
||||
|
||||
protected function __construct() { |
||||
|
||||
} |
||||
|
||||
public static function get(string $name, $key = false) { |
||||
if (isset(self::$registry[strtolower($name)])) { |
||||
$a = self::$registry[strtolower($name)]; |
||||
if ($key === false) { |
||||
return $a; |
||||
} |
||||
if (isset($a[$key])) { |
||||
return $a[$key]; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public static function set(string $name, $value): bool { |
||||
if (array_key_exists(strtolower($name), self::$registry)) { |
||||
return false; |
||||
} |
||||
self::$registry[strtolower($name)] = $value; |
||||
return true; |
||||
} |
||||
|
||||
} |
||||
|
||||
final class di { |
||||
|
||||
protected $services = []; |
||||
|
||||
public function register(string $service_name, callable $callable): void { |
||||
$this->services[$service_name] = $callable; |
||||
} |
||||
|
||||
public function has(string $service_name): bool { |
||||
return (array_key_exists($service_name, $this->services)); |
||||
} |
||||
|
||||
public function exists(string $service_name) { // an Alias to has |
||||
return $this->has($service_name); |
||||
} |
||||
|
||||
/* Note args may be an object or an array maybe more...! |
||||
* This will Call/Execute the service |
||||
*/ |
||||
public function get_service( |
||||
string $service_name, |
||||
$args = [], |
||||
...$more |
||||
) { |
||||
if ($this->has($service_name) ) { |
||||
return $this->services[$service_name]($args, $more); |
||||
} |
||||
return $this->resolve($service_name); // Try to Auto-Wire |
||||
} |
||||
|
||||
public function get_auto(string $service_name) { |
||||
if ($this->has($service_name) ) { |
||||
return $this->services[$service_name]($this); |
||||
} |
||||
return $this->resolve($service_name); // Try to Auto-Wire |
||||
} |
||||
|
||||
public function __set(string $service_name, callable $callable): void { |
||||
$this->register($service_name, $callable); |
||||
} |
||||
|
||||
public function __get(string $service_name) { |
||||
return $this->get_service($service_name); |
||||
} |
||||
|
||||
public function list_services_as_array(): array { |
||||
return array_keys($this->services); |
||||
} |
||||
|
||||
public function list_services_as_string(): string { |
||||
return implode(',', array_keys($this->services)); |
||||
} |
||||
|
||||
public function resolve(string $service_name) { |
||||
try { |
||||
$reflection_class = new \ReflectionClass($service_name); |
||||
} catch (\ReflectionException $e) { |
||||
if (! is_live()) { |
||||
var_dump($e->getTrace()); |
||||
echo $e->getMessage(); |
||||
exit; |
||||
} else { |
||||
throw new \Exception("Failed to resolve resource: {$service_name}!"); |
||||
} |
||||
} |
||||
if (! $reflection_class->isInstantiable()) { |
||||
throw new \Exception("The Service class: {$service_name} is not instantiable."); |
||||
} |
||||
$constructor = $reflection_class->getConstructor(); |
||||
if (! $constructor) { |
||||
return new $service_name; |
||||
} |
||||
$parameters = $constructor->getParameters(); |
||||
if (! $parameters) { |
||||
return new $service_name; |
||||
} |
||||
$dependencies = array_map( |
||||
function(\ReflectionParameter $param) { |
||||
$name = $param->getName(); |
||||
$type = $param->getType(); |
||||
if (! $type) { |
||||
throw new \Exception("Failed to resolve class: {$service_name} becasue param {$name} is missing a type hint."); |
||||
} |
||||
if ($type instanceof \ReflectionUnionType) { |
||||
throw new \Exception("Failed to resolve class: {$service_name} because of union type for param {$name}."); |
||||
} |
||||
if ($type instanceof \ReflectionNamedType && ! $type->isBuiltin()) { |
||||
return $this->get_auto($type->getName()); |
||||
} |
||||
|
||||
throw new \Exception("Failed to resolve class {$service_name} because of invalid param {$param}."); |
||||
}, $parameters |
||||
); |
||||
return $reflection_class->newInstanceArgs($dependencies); |
||||
} |
||||
|
||||
} |
||||
|
||||
// Initialize our Dependency Injector |
||||
registry::set('di', new di()); |
||||
|
||||
// Setup php for working with Unicode data, if possible |
||||
if (extension_loaded('mbstring')) { |
||||
mb_internal_encoding('UTF-8'); |
||||
mb_http_output('UTF-8'); |
||||
mb_language('uni'); |
||||
mb_regex_encoding('UTF-8'); |
||||
setlocale(LC_ALL, "en_US.UTF-8"); |
||||
} |
||||
|
||||
function is_live() { |
||||
return (common::get_bool(configure::get('CodeHydrater', 'live'))); |
||||
} |
||||
|
||||
require_once CodeHydrater_FRAMEWORK . 'bootstrap/common.php'; |
||||
require_once CodeHydrater_FRAMEWORK . 'bootstrap/requires.php'; |
||||
require_once CodeHydrater_FRAMEWORK . 'bootstrap/autoLoader.php'; |
||||
|
||||
registry::set('loader', new Psr4AutoloaderClass); |
||||
registry::get('loader')->register(); |
||||
registry::get('loader')->add_namespace("CodeHydrater\bootstrap", CodeHydrater_FRAMEWORK . "bootstrap"); |
||||
registry::get('loader')->add_namespace("CodeHydrater", CodeHydrater_FRAMEWORK . "classes"); |
||||
registry::get('loader')->add_namespace("Project", CodeHydrater_PROJECT); |
||||
|
||||
loadAll::init(CodeHydrater_PROJECT); |
||||
|
||||
$returned_route = \CodeHydrater\router::execute(); |
||||
if ($returned_route["found"] === false) { |
||||
$app = new \CodeHydrater\app(); |
||||
$app->load_controller(); |
||||
} |
||||
@ -0,0 +1,194 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater\bootstrap; |
||||
|
||||
use CodeHydrater\bootstrap\common; |
||||
|
||||
enum UseDir: string { |
||||
case FIXED = "Fixed"; |
||||
case FRAMEWORK = "Framework"; |
||||
case PROJECT = "Project"; |
||||
case ONERROR = "OnError"; |
||||
} |
||||
|
||||
final class requires { |
||||
|
||||
public static function is_valid_file(string $filename): bool { |
||||
if (is_string($filename) && strlen($filename) < 64) { |
||||
return (self::is_dangerous($filename) === false) ? true : false; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public static function is_dangerous(string $file): bool { |
||||
// Make sure the file does not contain null bytes to avoid PHAR file attacks |
||||
if (strpos($file, "\x00") !== false) { |
||||
return true; |
||||
} |
||||
|
||||
// Remove non-visible characters |
||||
$file = preg_replace('/[\x00-\x1F\x7F]/u', '', $file); |
||||
|
||||
if (strpos($file, "..") !== false || strpos($file, "./") !== false) { |
||||
return true; // .. Too dangerious, up path attack |
||||
} |
||||
|
||||
/* |
||||
* :// Too dangerious, PHAR file execution of serialized code injection, etc... |
||||
* Also, prevent remote code execution from http://, ftp:// |
||||
*/ |
||||
if (strpos($file, "://") !== false) { |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public static function filter_file_name(string $file): string { |
||||
return preg_replace('/[^-a-z0-9._\\/\s]+/i', "", $file); |
||||
} |
||||
|
||||
/** |
||||
* Do not allow periods in folder names! |
||||
* @param string $dir |
||||
* @return string |
||||
*/ |
||||
public static function filter_dir_path(string $dir): string { |
||||
return preg_replace('/[^a-z0-9_\\/]+/i', "", $dir); |
||||
} |
||||
|
||||
private static function get_PHP_Version_for_file(string $file): string|bool { |
||||
if (file_exists($file . PHP_MAJOR_VERSION . PHP_MINOR_VERSION) && is_readable($file . PHP_MAJOR_VERSION . PHP_MINOR_VERSION)) { |
||||
return $file . PHP_MAJOR_VERSION . PHP_MINOR_VERSION; |
||||
} elseif (file_exists($file . PHP_MAJOR_VERSION) && is_readable($file . PHP_MAJOR_VERSION)) { |
||||
return $file . PHP_MAJOR_VERSION; |
||||
} elseif (file_exists($file) && is_readable($file)) { |
||||
return $file; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @return Cleaned DIR or False |
||||
*/ |
||||
public static function safer_dir_exists(string $dir): string|bool { |
||||
if (self::is_dangerous($dir) || empty($dir)) { |
||||
return false; |
||||
} |
||||
$dir = str_replace('_DS_', '/', $dir); |
||||
$dir = self::filter_dir_path($dir); |
||||
|
||||
$realpath = realpath($dir); |
||||
if ($realpath === false) { |
||||
return false; |
||||
} |
||||
|
||||
$dir = escapeshellcmd($realpath); |
||||
return (file_exists($dir)) ? $dir : false; |
||||
} |
||||
|
||||
/** |
||||
* @return Cleaned FileName or False |
||||
*/ |
||||
public static function safer_file_exists(string $file, string $dir=""): string|bool { |
||||
if (self::is_dangerous($file)) { |
||||
return false; |
||||
} |
||||
if (self::is_dangerous($dir)) { |
||||
return false; |
||||
} |
||||
if (empty($file)) { |
||||
return false; |
||||
} |
||||
|
||||
$pos = common::string_last_position($file, "."); |
||||
if ($pos === false) { |
||||
$file_type = ".php"; |
||||
$file_name = $file; |
||||
} else { |
||||
$file_name = common::get_string_left($file, $pos); |
||||
$file_kind = common::get_string_right($file, common::string_length($file) - $pos); |
||||
$file_type = match ($file_kind) { |
||||
".tpl" => ".tpl", |
||||
default => ".php", |
||||
}; |
||||
} |
||||
|
||||
$filtered_file = self::filter_file_name($file_name); |
||||
if (empty($dir)) { |
||||
$file_plus_dir = $filtered_file; |
||||
} else { |
||||
$filtered_dir = rtrim(self::filter_dir_path($dir), '/') . '/'; |
||||
$file_plus_dir = $filtered_dir . $filtered_file; |
||||
} |
||||
$escaped_file = escapeshellcmd($file_plus_dir . $file_type); |
||||
return self::get_PHP_Version_for_file($escaped_file); |
||||
} |
||||
|
||||
private static function ob_starter() { |
||||
if (extension_loaded('mbstring')) { |
||||
ob_start('mb_output_handler'); |
||||
} else { |
||||
ob_start(); |
||||
} |
||||
} |
||||
|
||||
private static function secure_file(bool $return_contents, string $file, UseDir $path, $local = null, array $args = array(), bool $load_once = true) { |
||||
$dir = match ($path) { |
||||
UseDir::FIXED => "", |
||||
UseDir::FRAMEWORK => CodeHydrater_FRAMEWORK, |
||||
UseDir::ONERROR => CodeHydrater_PROJECT . "views/on_error/", |
||||
default => CodeHydrater_PROJECT, |
||||
}; |
||||
$versioned_file = self::safer_file_exists($file, $dir); |
||||
if ($versioned_file === false) { |
||||
return false; |
||||
} |
||||
|
||||
if (is_array($args)) { |
||||
if (isset($args["return_contents"]) |
||||
|| isset($args["script_output"]) |
||||
|| isset($args["versioned_file"]) |
||||
) { |
||||
return false; // Args are Dangerious!! Abort |
||||
} |
||||
extract($args, EXTR_PREFIX_SAME, "dup"); |
||||
} |
||||
|
||||
if ($return_contents) { |
||||
$script_output = (string) ob_get_clean(); |
||||
self::ob_starter(); |
||||
include $versioned_file; |
||||
$script_output .= (string) ob_get_clean(); |
||||
return $script_output; |
||||
} else { |
||||
return ($load_once) ? include_once($versioned_file) : include($versioned_file); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Use secure_include instead of include. |
||||
* @param string $file script |
||||
* @param UseDir $path (framework or project) |
||||
* @param type $local ($this) |
||||
* @param array $args ($var to pass along) |
||||
* @param bool $load_once (default true) |
||||
* @return results of include or false if failed |
||||
*/ |
||||
public static function secure_include(string $file, UseDir $path = UseDir::PROJECT, $local = null, array $args = array(), bool $load_once = true) { |
||||
return self::secure_file(false, $file, $path, $local, $args, $load_once); |
||||
} |
||||
|
||||
public static function secure_get_content(string $file, UseDir $path = UseDir::PROJECT, $local = null, array $args = array(), bool $load_once = true) { |
||||
return self::secure_file(true, $file, $path, $local, $args, $load_once); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,628 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license https://mit-license.org/ |
||||
*/ |
||||
|
||||
/* Sanitize Input, Validate data and Escape output. |
||||
* 1) In web development to sanitize means that you remove unsafe |
||||
* characters from the input. Makes safer DB inserts/selects, etc...! |
||||
* 2) Validation is not sanitization, this step does not remove any bad data, |
||||
* validation confirms that the info that is coming to your application meets |
||||
* the criteria you want. |
||||
* 3) Escape output - to get free from something, or to avoid something. Pay |
||||
* attention to not escape the data more than once, you must escape only when |
||||
* you received it or when you need to output it. I recommend it on all output only. |
||||
* |
||||
* Don’t try to sanitize input. Escape output. Perhaps more importantly, |
||||
* it gives a false sense of security. What does “unsafe” mean? In what context? |
||||
* Sure, <>& are unsafe characters for HTML, but what about CSS, JSON, SQL, or |
||||
* even shell scripts? Those have a completely different set of unsafe characters. |
||||
* |
||||
* Every so often developers talk about “sanitizing user input” to prevent |
||||
* cross-site scripting attacks. This is well-intentioned, but leads to a |
||||
* false sense of security, and sometimes mangles perfectly good input. |
||||
*/ |
||||
|
||||
namespace CodeHydrater\bootstrap; |
||||
|
||||
use \CodeHydrater\enums\FIELD_FILTER; // Defined in enum\safer_io_enums |
||||
use \CodeHydrater\enums\DB_FILTER; |
||||
use \CodeHydrater\enums\HTML_FLAG; |
||||
use \CodeHydrater\enums\INPUTS; |
||||
|
||||
/** |
||||
* use_io defines public members to be used on safer_io INPUTS |
||||
*/ |
||||
final class use_io { |
||||
public $input_var; |
||||
public $input_type; |
||||
public $field_filter; |
||||
public $escape_html; |
||||
public $validation_rule; |
||||
public $validation_message; |
||||
public $skip_the_db; |
||||
public $use_db_filter; |
||||
} |
||||
|
||||
/** |
||||
* use_iol is to Auto-Wire Input Output Logic controllers |
||||
* in standard paths defined below |
||||
*/ |
||||
final class use_iol { |
||||
public static function auto_wire( |
||||
string $root_folder, |
||||
string $file, |
||||
string $method = 'index', |
||||
string $db_service= 'db_mocker' |
||||
) { |
||||
new \CodeHydrater\enums\safer_io_enums(); // Auto load |
||||
|
||||
registry::set('db', \main_tts\registry::get('di')->get_service($db_service) ); |
||||
|
||||
$class_name = "\\Project\\inputs\\{$root_folder}\\{$file}_in"; |
||||
$input = $class_name::$method(); |
||||
|
||||
$class_name = "\\Project\\logic\\{$root_folder}\\{$file}_logic"; |
||||
$class_name::$method($input); |
||||
|
||||
$class_name = "\\Project\\outputs\\{$root_folder}\\{$file}_out"; |
||||
return $class_name::$method($input); |
||||
} |
||||
} |
||||
|
||||
final class saferIO { |
||||
private static string $string_of_POST_data = ""; |
||||
private static array $DATA_INPUTS = []; |
||||
|
||||
protected function __construct() { |
||||
|
||||
} |
||||
// Allow anything to set_data_inputs is desired here |
||||
public static function set_data_input(string $var_name, mixed $data_in): void { |
||||
if (! isset(self::$DATA_INPUTS[$var_name])) { |
||||
self::$DATA_INPUTS[$var_name] = $data_in; |
||||
} |
||||
} |
||||
// Do not allow anyone out-side of this class to get this un-filtered input |
||||
private static function get_data_input(string $var_name) { |
||||
return (isset(self::$DATA_INPUTS[$var_name])) ? |
||||
self::$DATA_INPUTS[$var_name] : null; |
||||
} |
||||
|
||||
public static function grab_all_post_data( |
||||
int $bytes_limit = 650000, |
||||
int $max_params = 400 |
||||
): void { |
||||
if ($stream = fopen("php://input", 'r')) { |
||||
if ($bytes_limit === 0) { |
||||
$post_data = stream_get_contents($stream); |
||||
} else { |
||||
$post_data = stream_get_contents($stream, $bytes_limit); |
||||
} |
||||
|
||||
fclose($stream); |
||||
if ($bytes_limit > 0 && strlen($post_data) == $bytes_limit) { |
||||
throw new \Exception("Too much input data!"); |
||||
} |
||||
$count_params = substr_count($post_data, "&"); |
||||
if ($max_params > 0 && $count_params > $max_params) { |
||||
throw new \Exception("Too many input parameters!"); |
||||
} |
||||
self::$string_of_POST_data = $post_data; |
||||
} |
||||
} |
||||
|
||||
public static function clear_post_data() { |
||||
self::$string_of_POST_data = ""; |
||||
} |
||||
|
||||
public static function convert_to_utf8(string $in_str): string { |
||||
if (! extension_loaded('mbstring')) { |
||||
return $in_str; |
||||
} |
||||
$cur_encoding = mb_detect_encoding($in_str); |
||||
if($cur_encoding == "UTF-8" && mb_check_encoding($in_str,"UTF-8")) { |
||||
return $in_str; |
||||
} else { |
||||
return mb_convert_encoding($in_str, 'UTF-8', $cur_encoding); |
||||
} |
||||
} |
||||
|
||||
// Escape HTML output |
||||
public static function h(string $string): string { |
||||
$utf8 = self::convert_to_utf8($string); |
||||
return htmlspecialchars($utf8, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8'); |
||||
} |
||||
|
||||
// Reverse encode of HTML |
||||
public static function html_decode(string $string): string { |
||||
return htmlspecialchars_decode($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5); |
||||
} |
||||
|
||||
// HTML Purify library |
||||
public static function p(string $string): string { |
||||
$purifer = registry::get('di')->get_service('html_filter'); |
||||
if (!$purifer->has_loaded()) { |
||||
$purifer->set_defaults(); |
||||
} |
||||
return $purifer->purify($string); |
||||
} |
||||
|
||||
// Escape JavaScript output |
||||
public static function j($input, int $levels_deep = 512): mixed { |
||||
try { |
||||
return json_encode($input, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR, $levels_deep); |
||||
} catch (\JsonException $ex) { |
||||
return $ex; |
||||
} |
||||
} |
||||
|
||||
public static function json_decode(string $string, bool $return_as_an_array = true, int $levels_deep = 512): mixed { |
||||
try { |
||||
return json_decode($string, $return_as_an_array, $levels_deep, JSON_THROW_ON_ERROR); |
||||
} catch (\JsonException $ex) { |
||||
return $ex; |
||||
} |
||||
} |
||||
|
||||
public static function has_json_error($object): bool { |
||||
return ($object instanceof \JsonException); |
||||
} |
||||
|
||||
// Escape URL output |
||||
public static function u(string $string): string { |
||||
return urlencode($string); |
||||
} |
||||
|
||||
/* |
||||
* Encode HTML kindof... The problem with htmlentities() is that it is not |
||||
* very powerful, in fact, it does not escape single quotes, cannot detect |
||||
* the character set and does not validate HTML as well. |
||||
*/ |
||||
public static function e(string $string): string { |
||||
$utf8 = self::convert_to_utf8($string); |
||||
return htmlentities($utf8, ENT_QUOTES, 'UTF-8'); |
||||
} |
||||
|
||||
public static function de(string $data): string { |
||||
return html_entity_decode($data); |
||||
} |
||||
|
||||
/* |
||||
* Note: Generally, "strip_tags" is just the wrong function. |
||||
* Never use it. And if you do, absolutely never use the second parameter, |
||||
* because sooner or later someone will abuse it. |
||||
*/ |
||||
|
||||
public static function get_clean_server_var(string $var): mixed { |
||||
return filter_input(INPUT_SERVER, $var, FILTER_UNSAFE_RAW); |
||||
} |
||||
|
||||
public static function get_bool($in): bool { |
||||
return (filter_var($in, FILTER_VALIDATE_BOOLEAN)); |
||||
} |
||||
|
||||
/** |
||||
* Purpose: To decode JQuery encoded objects, arrays, strings, int, bool types. |
||||
* The content must be of application/json. |
||||
* Note: It will return null if not valid json. false is not application/json |
||||
*/ |
||||
private static function get_json_post_data( |
||||
string $input_field_name, |
||||
bool $return_as_array = true, |
||||
int $levels_deep = 512 |
||||
) { |
||||
$ret_json = self::json_decode( |
||||
self::$string_of_POST_data, |
||||
$return_as_array, |
||||
$levels_deep |
||||
); |
||||
if (self::has_json_error($ret_json)) { |
||||
return false; |
||||
} |
||||
if (isset($ret_json[$input_field_name])) { |
||||
return $ret_json[$input_field_name]; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private static function get_post_data(): \Generator { |
||||
$pairs = explode("&", self::$string_of_POST_data); |
||||
while(true) { |
||||
$pair = array_pop($pairs); |
||||
if ($pair === null) { |
||||
break; |
||||
} |
||||
$nv = explode("=", $pair); |
||||
$n = $nv[0] ?? false; |
||||
$v = $nv[1] ?? ""; |
||||
unset($nv); |
||||
if ($n === false || empty($n)) { |
||||
continue; |
||||
} |
||||
$cmd = (yield urldecode($n) => urldecode($v)); |
||||
if ($cmd == "stop") { |
||||
break; |
||||
} |
||||
} |
||||
unset($n); |
||||
unset($v); |
||||
unset($pairs); |
||||
} |
||||
|
||||
private static function safer_html(string $input, HTML_FLAG $safety_level = HTML_FLAG::escape): string { |
||||
switch ($safety_level) { |
||||
case HTML_FLAG::raw : |
||||
throw new \Exception('Raw HTML not supported!'); |
||||
case HTML_FLAG::strip : |
||||
return strip_tags($input); |
||||
case HTML_FLAG::encode : |
||||
return self::e($input); |
||||
case HTML_FLAG::purify : |
||||
return self::p($input); |
||||
case HTML_FLAG::escape : |
||||
default: |
||||
return self::h($input); |
||||
} |
||||
} |
||||
|
||||
private static function t($item, bool $do_trim = true) { |
||||
if ($do_trim) { |
||||
if (is_string($item)) { |
||||
return trim($item); |
||||
} |
||||
if (\bs_tts\common::get_count($data)) { |
||||
$ret = []; |
||||
foreach($data as $text) { |
||||
if (is_bool($text) || is_int($text)) { |
||||
$ret[] = $text; |
||||
continue; |
||||
} |
||||
if (! is_string($text)) { |
||||
continue; // Deny Arrays and Objects here! |
||||
} |
||||
$ret[] = trim($text); |
||||
} |
||||
return $ret; |
||||
} |
||||
} |
||||
return $item; |
||||
} |
||||
|
||||
private static function find_post_field(string $input_field_name): mixed { |
||||
$content_type = self::get_clean_server_var('CONTENT_TYPE'); |
||||
if ($content_type === null) { |
||||
return false; |
||||
} |
||||
if (str_contains($content_type, "application/json")) { |
||||
return self::get_json_post_data($input_field_name); |
||||
} |
||||
if (str_contains($content_type, "application/x-www-form-urlencoded")) { |
||||
$post = self::get_post_data(); |
||||
foreach($post as $key => $data) { |
||||
if ($key === $input_field_name) { |
||||
$post->send("stop"); // Break loop in Generator |
||||
return $data; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private static function find_get_field(string $input_field_name): mixed { |
||||
if (isset($_SERVER['QUERY_STRING'])) { |
||||
$query = self::get_clean_server_var('QUERY_STRING'); |
||||
$get = []; |
||||
parse_str($query, $get); |
||||
if (isset($get[$input_field_name])) { |
||||
return $get[$input_field_name]; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private static function get_input_by_type( |
||||
string $input_field_name, |
||||
INPUTS $input_type, |
||||
): mixed { |
||||
/* Must return here to avoid Failing Resolve later on in this FN |
||||
* as input types variable, debugging, and json will not Resolve! |
||||
*/ |
||||
if ($input_type == INPUTS::variable) { |
||||
return self::get_data_input($input_field_name); |
||||
} |
||||
if ($input_type == INPUTS::debugging) { |
||||
$rd = self::find_post_field($input_field_name); |
||||
if ($rd !== false) { |
||||
return $rd; |
||||
} |
||||
|
||||
$is_set = filter_has_var(INPUT_POST, $input_field_name); |
||||
if ($is_set) { |
||||
return filter_input(INPUT_POST, $input_field_name); |
||||
} |
||||
if (!self::find_get_field("debugging")) { |
||||
return null; |
||||
} |
||||
$get_var = self::find_get_field($input_field_name); |
||||
if ($get_var !== false) { |
||||
return $get_var; |
||||
} |
||||
$is_get_set = filter_has_var(INPUT_GET, $input_field_name); |
||||
if ($is_get_set) { |
||||
return filter_input(INPUT_GET, $input_field_name); |
||||
} |
||||
return null; |
||||
} |
||||
if ($input_type === INPUTS::json) { |
||||
$rd = self::find_post_field($input_field_name); |
||||
if ($rd !== false) { |
||||
return $rd; |
||||
} |
||||
return null; |
||||
} |
||||
if ($input_type === INPUTS::get) { |
||||
$get_var = self::find_get_field($input_field_name); |
||||
if ($get_var !== false) { |
||||
return $get_var; |
||||
} |
||||
} |
||||
$resolve_input = $input_type->resolve(); |
||||
$is_set = filter_has_var($resolve_input, $input_field_name); |
||||
if ($is_set) { |
||||
return filter_input($resolve_input, $input_field_name); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* |
||||
* @param string $data |
||||
* @param array $a['html'] of type HTML_FLAG |
||||
* @return string|bool |
||||
*/ |
||||
private static function get_safer_string(string $data, use_io $a): string | bool { |
||||
if (isset($a->escape_html) && $a->escape_html instanceof \UnitEnum) { |
||||
return self::safer_html($data, $a->escape_html); |
||||
} |
||||
return self::safer_html($data); |
||||
} |
||||
|
||||
private static function get_safer_html($data, use_io $a) { |
||||
if (is_string($data)) { |
||||
return self::get_safer_string($data, $a); |
||||
} else if (common::get_count($data)) { |
||||
$ret = []; |
||||
foreach($data as $text) { |
||||
if (is_bool($text) || is_int($text)) { |
||||
$ret[] = $text; |
||||
continue; |
||||
} |
||||
if (! is_string($text)) { |
||||
continue; // Deny Arrays and Objects here! |
||||
} |
||||
$ret[] = self::get_safer_string($text, $a); |
||||
} |
||||
return $ret; |
||||
} |
||||
return $data; |
||||
} |
||||
|
||||
public static function required_fields_were_NOT_all_submitted(array $data): bool { |
||||
$field = $data['name'] ?? false; |
||||
$empty = $data['meta'][$field]['empty'] ?? true; |
||||
$required = $data['meta'][$field]['validation_rules_set'] ?? false; |
||||
return ($empty && $required); |
||||
} |
||||
|
||||
private static function sanitize_helper( |
||||
string $from, |
||||
string $input_field_name, |
||||
use_io $a, |
||||
FIELD_FILTER $default_filter = FIELD_FILTER::raw_string, |
||||
bool $trim = true, |
||||
) : array { |
||||
|
||||
$meta = []; |
||||
$meta['missing'] = []; |
||||
$safer_data = ""; |
||||
$rules = []; |
||||
$messages = []; |
||||
|
||||
if (isset($a->field_filter) && $a->field_filter instanceof \UnitEnum) { |
||||
$field_type = $a->field_filter; |
||||
} else { |
||||
$field_type = $default_filter; |
||||
} |
||||
|
||||
if (isset($a->input_var)) { |
||||
$user_text = $a->input_var; |
||||
} elseif (isset($a->input_type) && $a->input_type instanceof \UnitEnum) { |
||||
$user_text = self::get_input_by_type($input_field_name, $a->input_type); |
||||
} else { |
||||
$ret['name'] = $input_field_name; |
||||
$ret['meta']['missing'][] = $input_field_name; |
||||
$ret['errors'][$input_field_name] = "Missing Field $input_field_name"; |
||||
$ret['html'] = null; |
||||
$ret['db'] = false; |
||||
$ret['logic'] = false; |
||||
return $ret; |
||||
} |
||||
|
||||
$safer_data = false; // needs to be false to fail the validator |
||||
$safer_html_data = null; // should be null for ?? operator to work with it.... |
||||
|
||||
if (isset($a->validation_rule)) { |
||||
$rules[$input_field_name] = $a->validation_rule; |
||||
} |
||||
|
||||
if (isset($a->validation_message) && isset($a->validation_rule)) { |
||||
$messages[$input_field_name] = $a->validation_message; |
||||
} |
||||
|
||||
$meta[$input_field_name]['validation_rules_set'] = (count($rules)) ? true : false; |
||||
|
||||
$db = (isset($a->skip_the_db)) ? $a->skip_the_db : false; |
||||
$meta[$input_field_name]['type'] = $field_type->name; |
||||
$meta[$input_field_name]['skip_db'] = $db; |
||||
|
||||
if ($user_text === null) { |
||||
$safer_data = null; |
||||
$safer_db_data = null; |
||||
$safer_html_data = null; |
||||
$meta[$input_field_name]['empty'] = true; |
||||
} else { |
||||
$field_filter_resolved = $field_type->resolve(); |
||||
|
||||
$meta[$input_field_name]['empty'] = false; |
||||
|
||||
$safer_data = $user_text; |
||||
if ($field_type == FIELD_FILTER::email) { |
||||
$safer_data = substr($safer_data, 0, 254); |
||||
} |
||||
|
||||
$safer_data = filter_var($safer_data, FILTER_DEFAULT, $field_filter_resolved); |
||||
|
||||
// FallBack: These field types should never allow arrays anyways |
||||
if ($field_type == FIELD_FILTER::raw_string || |
||||
$field_type == FIELD_FILTER::raw |
||||
) { |
||||
if (common::get_count($safer_data)) { |
||||
$safer_data = $safer_data[0]; |
||||
} |
||||
} |
||||
|
||||
if ($from === "html") { |
||||
$safer_html = self::get_safer_html($safer_data, $a); |
||||
if ($safer_html !== false) { |
||||
$safer_html_data = $safer_html; |
||||
} |
||||
|
||||
if (isset($safer_html_data)) { |
||||
$safer_html_data = self::t($safer_html_data, $trim); |
||||
} |
||||
} else { |
||||
$safer_data = self::t($safer_data, $trim); |
||||
} |
||||
|
||||
if ($field_type == FIELD_FILTER::integer_number) { |
||||
$safer_data = intval($safer_data); |
||||
} |
||||
if ($field_type == FIELD_FILTER::floating_point) { |
||||
$safer_data = floatval($safer_data); |
||||
} |
||||
if ($from === "db") { |
||||
if ($field_type == FIELD_FILTER::integer_number || $field_type == FIELD_FILTER::floating_point) { |
||||
$safer_db_data = $safer_data; |
||||
} else { |
||||
if (isset($a->use_db_filter) && $a->use_db_filter == DB_FILTER::ON) { |
||||
$safe_for_db = \tts\extras\safer_sql::get_safer_sql_text($safer_data); |
||||
$text = $safe_for_db["text"]; |
||||
$meta[$input_field_name]['db_filter_status'] = $safe_for_db["status"] ?? \tts\SQL_SAFETY_FLAG::filtered; |
||||
} else { |
||||
$text = $safer_data; |
||||
} |
||||
$safer_db_data = $text; |
||||
} |
||||
} |
||||
} |
||||
$ret['name'] = $input_field_name; |
||||
$ret['meta'] = $meta; |
||||
if ($from === "db") { |
||||
$ret['db'] = $safer_db_data; |
||||
$data[$input_field_name] = $safer_db_data; |
||||
} elseif ($from === "logic") { |
||||
$ret['logic'] = $safer_data; |
||||
$data[$input_field_name] = $safer_data; |
||||
} elseif ($from === "html") { |
||||
$ret['html'] = $safer_html_data; |
||||
$data[$input_field_name] = $safer_html_data; |
||||
} |
||||
$ret['errors'] = (count($rules)) ? \CodeHydrater\validator::validate($data, $rules, $messages) : []; |
||||
return $ret; |
||||
} |
||||
|
||||
/** |
||||
* As PHP uses the underlying C functions for filesystem related operations, |
||||
* it may handle null bytes in a quite unexpected way. |
||||
* As null bytes denote the end of a string in C, strings |
||||
* containing them won't be considered entirely but rather |
||||
* only until a null byte occurs. So, clean it out to |
||||
* avoid vulnerable code. |
||||
*/ |
||||
public static function remove_null_byte(string $input): string { |
||||
return str_replace(chr(0), '', $input); |
||||
} |
||||
|
||||
public static function db_sanitize( |
||||
array $inputs, |
||||
FIELD_FILTER $default_filter = FIELD_FILTER::raw_string, |
||||
bool $trim = true, |
||||
) : \Generator { |
||||
foreach ($inputs as $input_field_name => $a) { |
||||
if (! $a instanceof use_io) { |
||||
continue; |
||||
} |
||||
$yield = static::sanitize_helper( |
||||
"db", |
||||
$input_field_name, |
||||
$a, |
||||
$default_filter, |
||||
$trim |
||||
); |
||||
yield $yield; |
||||
} |
||||
} |
||||
|
||||
public static function logic_sanitize( |
||||
array $inputs, |
||||
FIELD_FILTER $default_filter = FIELD_FILTER::raw_string, |
||||
bool $trim = true, |
||||
) : \Generator { |
||||
foreach ($inputs as $input_field_name => $a) { |
||||
if (! $a instanceof use_io) { |
||||
continue; |
||||
} |
||||
$yield = static::sanitize_helper( |
||||
"logic", |
||||
$input_field_name, |
||||
$a, |
||||
$default_filter, |
||||
$trim |
||||
); |
||||
yield $yield; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sanitize the inputs based on the rules an optionally trim the string |
||||
* @param FIELD_FILTER $default_filter FILTER_SANITIZE_STRING |
||||
* @param bool $trim |
||||
* @return Generator |
||||
*/ |
||||
public static function html_escape_and_sanitize( |
||||
array $inputs, |
||||
FIELD_FILTER $default_filter = FIELD_FILTER::raw_string, |
||||
bool $trim = true, |
||||
) : \Generator { |
||||
foreach ($inputs as $input_field_name => $a) { |
||||
if (! $a instanceof use_io) { |
||||
continue; |
||||
} |
||||
$yield = static::sanitize_helper( |
||||
"html", |
||||
$input_field_name, |
||||
$a, |
||||
$default_filter, |
||||
$trim |
||||
); |
||||
yield $yield; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,222 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts <Bob_586@Yahoo.com> |
||||
* @copyright (c) 2025, Robert Strutts |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater\bootstrap; |
||||
|
||||
final class siteHelper { |
||||
|
||||
private static $ROOT; |
||||
private static $ROUTE; |
||||
private static $PRJ; |
||||
private static $FW_DIST; |
||||
private static $REQUEST_URI; |
||||
private static $REQUEST_METHOD; |
||||
private static $USE_SECURE = true; |
||||
private static $TESTING; |
||||
private static $queryParams; |
||||
private static $DEFAULT_PROJECT; |
||||
private static $all_projects = []; |
||||
private static $local_site_domains = ['localhost']; |
||||
private static $Private_IPs_allowed = ['127.0.0.1', '::1']; |
||||
private static $Public_IPs_allowed = []; |
||||
|
||||
public static function set_local_site_domains(string|array $domain_name): void { |
||||
if (is_array($domain_name)) { |
||||
foreach($domain_name as $domain) { |
||||
self::$local_site_domains[] = $domain; |
||||
} |
||||
} elseif (is_string($domain_name)) { |
||||
self::$local_site_domains[] = $domain_name; |
||||
} |
||||
} |
||||
|
||||
public static function set_allowed_Private_IPs(string|array $IP_addresses): void { |
||||
if (is_array($IP_addresses)) { |
||||
foreach($IP_addresses as $IP) { |
||||
$s_ip = \tts\security::get_valid_ip($IP); |
||||
if ($s_ip === false) { |
||||
continue; |
||||
} |
||||
self::$Private_IPs_allowed[] = $IP; |
||||
} |
||||
} elseif (is_string($IP_addresses)) { |
||||
$s_ip = \tts\security::get_valid_ip($IP); |
||||
if ($s_ip === false) { |
||||
return; |
||||
} |
||||
self::$Private_IPs_allowed[] = $IP_addresses; |
||||
} |
||||
} |
||||
|
||||
public static function set_allowed_Public_IPs(string|array $IP_addresses): void { |
||||
if (is_array($IP_addresses)) { |
||||
foreach($IP_addresses as $IP) { |
||||
$s_ip = \tts\security::get_valid_public_ip($IP); |
||||
if ($s_ip === false) { |
||||
continue; |
||||
} |
||||
self::$Public_IPs_allowed[] = $s_ip; |
||||
} |
||||
} elseif (is_string($IP_addresses)) { |
||||
$s_ip = \tts\security::get_valid_public_ip($IP); |
||||
if ($s_ip === false) { |
||||
return; |
||||
} |
||||
self::$Public_IPs_allowed[] = $IP_addresses; |
||||
} |
||||
} |
||||
|
||||
public static function get_route(): string { |
||||
return self::$ROUTE; |
||||
} |
||||
|
||||
public static function get_root(): ?string { |
||||
return self::$ROOT; |
||||
} |
||||
|
||||
public static function get_testing() { |
||||
return self::$TESTING; |
||||
} |
||||
|
||||
public static function get_uri(): string { |
||||
return self::$REQUEST_URI; |
||||
} |
||||
|
||||
public static function get_method(): string { |
||||
return strtoupper(self::$REQUEST_METHOD); |
||||
} |
||||
|
||||
public static function get_params() { |
||||
return self::$queryParams; |
||||
} |
||||
|
||||
public static function get_use_secure(): bool { |
||||
return self::$USE_SECURE; |
||||
} |
||||
|
||||
/** |
||||
* Because $_SERVER['REQUEST_URI'] May only available on Apache, |
||||
* we generate an equivalent using other environment variables. |
||||
* @return string |
||||
*/ |
||||
public static function request_uri() { |
||||
if (self::$REQUEST_URI !== null && !empty(self::$REQUEST_URI)) { |
||||
$uri = self::$REQUEST_URI; |
||||
} else if (isset($_SERVER['REQUEST_URI'])) { |
||||
$uri = saferIO::get_clean_server_var('REQUEST_URI'); |
||||
} else { |
||||
if (isset($_SERVER['argv'])) { |
||||
$uri = saferIO::get_clean_server_var('SCRIPT_NAME') . '?' . $_SERVER['argv'][0]; |
||||
} elseif (isset($_SERVER['QUERY_STRING'])) { |
||||
$uri = saferIO::get_clean_server_var('SCRIPT_NAME') . '?' . \bs_tts\safer_io::get_clean_server_var('QUERY_STRING'); |
||||
} else { |
||||
$uri = saferIO::get_clean_server_var('SCRIPT_NAME'); |
||||
} |
||||
} |
||||
// Prevent multiple slashes to avoid cross site requests via the Form API. |
||||
$uri = '/' . ltrim($uri, '/'); |
||||
|
||||
return $uri; |
||||
} |
||||
|
||||
public static function get_clean_server_var(string $var): mixed { |
||||
return filter_input(INPUT_SERVER, $var, FILTER_UNSAFE_RAW); |
||||
} |
||||
|
||||
public static function site_url(): string { |
||||
$server_port = self::get_clean_server_var('SERVER_PORT'); |
||||
$secure_port_on = self::get_clean_server_var('HTTPS'); |
||||
$use_secure = ($server_port == '443' || $secure_port_on == 'on'); |
||||
self::$USE_SECURE = $use_secure; |
||||
$protocol = ($use_secure) ? 'https://' : 'http://'; |
||||
define('TTS_PROTOCOL', $protocol); |
||||
$domainName = self::get_clean_server_var('HTTP_HOST'); |
||||
|
||||
return $protocol . $domainName . "/"; |
||||
} |
||||
|
||||
public static function resolve($action, ...$params) { |
||||
if (is_callable($action)) { |
||||
return call_user_func($action, $params); |
||||
} |
||||
|
||||
if (!is_array($action)) { |
||||
return false; |
||||
} |
||||
|
||||
[$class, $method] = $action; |
||||
$call_class = "\\" . $class; |
||||
|
||||
if (class_exists($call_class)) { |
||||
$auto_class = registry::get('di')->get_auto($call_class); |
||||
if (method_exists($call_class, $method)) { |
||||
return call_user_func_array([$auto_class, $method], $params); |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private static function set_route(): void { |
||||
// Get just route |
||||
$pos = strpos(self::$REQUEST_URI, "?"); |
||||
$uri = ($pos !== false) ? substr(self::$REQUEST_URI, 0, $pos) : self::$REQUEST_URI; |
||||
$root = str_replace(self::$ROOT, "", $uri); |
||||
$routes = explode('/', trim($root, '/')); |
||||
self::$ROUTE = implode('/', $routes); |
||||
} |
||||
|
||||
private static function set_params(): void { |
||||
// Get just query string |
||||
$pos = strpos(self::$REQUEST_URI, "?"); |
||||
$uri = ($pos !== false) ? substr(self::$REQUEST_URI, $pos + 1) : ""; |
||||
if (empty($uri)) { |
||||
return; |
||||
} |
||||
$queryParams = []; |
||||
parse_str($uri, $queryParams); |
||||
self::$queryParams = $queryParams; |
||||
} |
||||
|
||||
public static function restrictSite(): void { |
||||
if ($_SERVER['HTTP_REFERER'] != $_SERVER['HTTP_HOST']) { |
||||
die("Form may not be used outside of parent site!"); |
||||
} |
||||
} |
||||
|
||||
public static function get_cli_args(): string { |
||||
$argv = (isset($GLOBALS['argv'])) ? $GLOBALS['argv'] : []; |
||||
$args = array_shift($argv); // POP out the SCRIPT_NAME!! |
||||
if ($args === null) { |
||||
return ""; // NO Args |
||||
} |
||||
$route = $argv[0] ?? ""; // Keep the Route |
||||
$args = array_shift($argv); // POP out the ROUTE!! |
||||
if ($args === null) { |
||||
return $route; |
||||
} |
||||
return $route . "?" . ltrim(implode('&', $argv), "&"); |
||||
} |
||||
|
||||
public static function init(string $ROOT, string $REQUEST_URI, string $REQUEST_METHOD, bool $testing = false) { |
||||
self::$ROOT = $ROOT; |
||||
self::$REQUEST_URI = $REQUEST_URI; |
||||
self::$REQUEST_METHOD = $REQUEST_METHOD; |
||||
self::$TESTING = $testing; |
||||
self::set_route(); |
||||
self::set_params(); |
||||
|
||||
if (! defined("ASSETS_BASE_REF")) { |
||||
define('ASSETS_BASE_REF', "/assets/"); |
||||
} |
||||
define('SITE_URL', self::site_url()); |
||||
define("BROWSER", self::get_clean_server_var('HTTP_USER_AGENT')); |
||||
define("ASSETS_DIR", "/public/assets/"); |
||||
} |
||||
} |
||||
@ -0,0 +1,354 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts <Robert@TryingToScale.com> |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater; |
||||
|
||||
class api { |
||||
const CONTINUE_STATUS = "Continue"; // 100 |
||||
const SWITCHING_PROTOCOLS = "Switching Protocols"; // 101 |
||||
const OK = "OK"; // 200 |
||||
const CREATED = "Created"; // 201 |
||||
const ACCEPTED = "Accepted"; // 202 |
||||
const NON_AUTHORITATIVE = "Non-Authoritative Information"; // 203 |
||||
const NO_CONTENT = "No Content"; // 204 |
||||
const RESET_CONTENT = "Reset Content"; // 205 |
||||
const PARTIAL_CONTENT = "Partial Content"; // 206 |
||||
const ALREADY_REPORTED = "Already Reported"; // 208 |
||||
const MULTI_STATUS = "Multiple Choices"; // 300 |
||||
const MOVED_PERMANENTLY = "Moved Permanently"; // 301 |
||||
const MOVED_TEMPORARILY = "Moved Temporarily"; // 302 |
||||
const SEE_OTHER = "See Other"; // 303 |
||||
const NOT_MODIFIED = "Not Modified"; // 304 |
||||
const USE_PROXY = "Use Proxy"; // 305 |
||||
const TEMP_REDIRECT = "Temporary Redirect"; // 307 |
||||
const BAD_REQUEST = "The request cannot be fulfilled due to bad syntax."; // 400 |
||||
const UNAUTHORIZED = "The authorization details given appear to be invalid."; // 401 |
||||
const PAYMENT_REQUIRED = "Payment Required"; // 402 |
||||
const FORBIDDEN = "The requested resource is not accessible."; // 403 |
||||
const NOT_FOUND = "The requested resource does not exist."; // 404 |
||||
const METHOD_NOT_ALLOWED = "Method Not Allowed"; // 405 |
||||
const NOT_ACCEPTABLE = "Not Acceptable"; // 406 |
||||
const PROXY_AUTH_REQUIRED = "Proxy Authentication Required"; // 407 |
||||
const REQUEST_TIME_OUT = "Request Time-out"; // 408 |
||||
const CONFLICT = "Conflict"; // 409 |
||||
const GONE = "Gone"; // 410 |
||||
const LENGTH_REQUIRED = "Length Required"; // 411 |
||||
const PRECONDITION_FAILED = "Precondition Failed"; // 412 |
||||
const REQUEST_ENTITY_TOO_LARGE = "Request Entity Too Large"; // 413 |
||||
const REQUEST_URI_TOO_LARGE = "Request-URI Too Large"; // 414 |
||||
const UNSUPPORTED_FORMAT = "The format requested is not supported by the server."; // 415 |
||||
const EXPECTATION_FAILED = "Expectation Failed"; // 417 |
||||
const INTERNAL_ERROR = "An unexpected error occured."; // 500 |
||||
const NOT_IMPLEMENTED = "Not Implemented"; // 501 |
||||
const BAD_GATEWAY = "Bad Gateway"; // 502 |
||||
const MAINTENANCE_MODE = "The requested resource is currently unavailable due to maintenance."; // 503 |
||||
const GATEWAY_TIME_OUT = "Gateway Time-out"; // 504 |
||||
const HTTP_VERSION_NOT_SUPPORTED = "HTTP Version not supported"; // 505 |
||||
|
||||
/** |
||||
* Use Encoder, default JSON |
||||
* @param type $data |
||||
* @param type $status_code |
||||
*/ |
||||
public static function encode($data, $status_code): void { |
||||
$response_type = misc::request_var('return'); |
||||
switch ($response_type) { |
||||
case 'xml': |
||||
self::xml_encode($data, $status_code, null); |
||||
break; |
||||
case 'php': |
||||
self::php_encode($data, $status_code); |
||||
break; |
||||
case 'json': |
||||
default: |
||||
self::json_encode($data, $status_code); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* See: HTTP_status_codes.txt |
||||
* @link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes |
||||
* HTTP $code to try, use $default if not valid |
||||
*/ |
||||
public static function get_code_number(int $code, int $default): int { |
||||
return ($code > 99 && $code < 600) ? $code : $default; |
||||
} |
||||
|
||||
/** |
||||
* Encode Error |
||||
* @param array $data |
||||
*/ |
||||
public static function error(array $data): void { |
||||
$error_code = (isset($data['code'])) ? $data['code'] : 400; |
||||
|
||||
$code = self::get_code_number($error_code, 400); |
||||
$data['result'] = false; |
||||
|
||||
if (isset($data['response'])) { |
||||
switch ($data['response']) { |
||||
case self::CONTINUE_STATUS: |
||||
$long_code = "100 Continue"; |
||||
$data['message'] = self::CONTINUE_STATUS; |
||||
break; |
||||
case self::SWITCHING_PROTOCOLS: |
||||
$long_code = "101 Switching Protocols"; |
||||
$data['message'] = self::SWITCHING_PROTOCOLS; |
||||
break; |
||||
case self::MULTI_STATUS: |
||||
$long_code = "300 Multiple Choices"; |
||||
$data['message'] = self::MULTI_STATUS; |
||||
break; |
||||
case self::MOVED_PERMANENTLY: |
||||
$long_code = "301 Moved Permanently"; |
||||
$data['message'] = self::MOVED_PERMANENTLY; |
||||
break; |
||||
case self::MOVED_TEMPORARILY: |
||||
$long_code = "302 Moved Temporarily"; |
||||
$data['message'] = self::MOVED_TEMPORARILY; |
||||
break; |
||||
case self::SEE_OTHER: |
||||
$long_code = "303 See Other"; |
||||
$data['message'] = self::SEE_OTHER; |
||||
break; |
||||
case self::NOT_MODIFIED: |
||||
$long_code = "304 Not Modified"; |
||||
$data['message'] = self::NOT_MODIFIED; |
||||
break; |
||||
case self::USE_PROXY: |
||||
$long_code = "305 Use Proxy"; |
||||
$data['message'] = self::USE_PROXY; |
||||
break; |
||||
case self::TEMP_REDIRECT: |
||||
$long_code = "307 Temporary Redirect"; |
||||
$data['message'] = self::TEMP_REDIRECT; |
||||
case self::BAD_REQUEST: |
||||
$long_code = "400 Bad Request"; |
||||
$data['message'] = self::BAD_REQUEST; |
||||
break; |
||||
case self::UNAUTHORIZED: |
||||
$long_code = "401 Unauthorized"; |
||||
$data['message'] = self::UNAUTHORIZED; |
||||
break; |
||||
case self::PAYMENT_REQUIRED: |
||||
$long_code = "402 Payment Required"; |
||||
$data['message'] = self::PAYMENT_REQUIRED; |
||||
break; |
||||
case self::FORBIDDEN: |
||||
$long_code = "403 Forbidden"; |
||||
$data['message'] = self::FORBIDDEN; |
||||
break; |
||||
case self::NOT_FOUND: |
||||
$long_code = "404 Not Found"; |
||||
$data['message'] = self::NOT_FOUND; |
||||
break; |
||||
case self::METHOD_NOT_ALLOWED: |
||||
$long_code = "405 Method Not Allowed"; |
||||
$data['message'] = self::METHOD_NOT_ALLOWED; |
||||
break; |
||||
case self::NOT_ACCEPTABLE: |
||||
$long_code = "406 Bad Request"; |
||||
$data['message'] = self::NOT_ACCEPTABLE; |
||||
break; |
||||
case self::PROXY_AUTH_REQUIRED: |
||||
$long_code = "407 Proxy Authentication Required"; |
||||
$data['message'] = self::PROXY_AUTH_REQUIRED; |
||||
break; |
||||
case self::REQUEST_TIME_OUT: |
||||
$long_code = "408 Request Time-out"; |
||||
$data['message'] = self::REQUEST_TIME_OUT; |
||||
break; |
||||
case self::CONFLICT: |
||||
$long_code = "409 Bad Request"; |
||||
$data['message'] = self::CONFLICT; |
||||
break; |
||||
case self::GONE: |
||||
$long_code = "410 Gone"; |
||||
$data['message'] = self::GONE; |
||||
break; |
||||
case self::LENGTH_REQUIRED: |
||||
$long_code = "411 Length Required"; |
||||
$data['message'] = self::LENGTH_REQUIRED; |
||||
break; |
||||
case self::PRECONDITION_FAILED: |
||||
$long_code = "412 Precondition Failed"; |
||||
$data['message'] = self::PRECONDITION_FAILED; |
||||
break; |
||||
case self::REQUEST_ENTITY_TOO_LARGE: |
||||
$long_code = "413 Request Entity Too Large"; |
||||
$data['message'] = self::REQUEST_ENTITY_TOO_LARGE; |
||||
break; |
||||
case self::REQUEST_URI_TOO_LARGE: |
||||
$long_code = "414 Request-URI Too Large"; |
||||
$data['message'] = self::REQUEST_URI_TOO_LARGE; |
||||
break; |
||||
case self::UNSUPPORTED_FORMAT: |
||||
$long_code = "415 Unsupported Media Type"; |
||||
$data['message'] = self::UNSUPPORTED_FORMAT; |
||||
break; |
||||
case self::EXPECTATION_FAILED: |
||||
$long_code = "417 Expectation Failed"; |
||||
$data['message'] = self::EXPECTATION_FAILED; |
||||
break; |
||||
case self::INTERNAL_ERROR: |
||||
$long_code = "500 Internal Server Error"; |
||||
$data['message'] = self::INTERNAL_ERROR; |
||||
break; |
||||
case self::NOT_IMPLEMENTED: |
||||
$long_code = "501 Not Implemented"; |
||||
$data['message'] = self::NOT_IMPLEMENTED; |
||||
break; |
||||
case self::BAD_GATEWAY: |
||||
$long_code = "502 Bad Gateway"; |
||||
$data['message'] = self::BAD_GATEWAY; |
||||
break; |
||||
case self::MAINTENANCE_MODE: |
||||
$long_code = "503 Service Unavailable"; |
||||
$data['message'] = self::MAINTENANCE_MODE; |
||||
break; |
||||
case self::GATEWAY_TIME_OUT: |
||||
$long_code = "504 Gateway Time-out"; |
||||
$data['message'] = self::GATEWAY_TIME_OUT; |
||||
break; |
||||
case self::HTTP_VERSION_NOT_SUPPORTED: |
||||
$long_code = "505 HTTP Version not supported"; |
||||
$data['message'] = self::HTTP_VERSION_NOT_SUPPORTED; |
||||
break; |
||||
default: |
||||
$long_code = $code; |
||||
break; |
||||
} |
||||
} else { |
||||
$long_code = $code; |
||||
} |
||||
|
||||
$data['code'] = $long_code; |
||||
|
||||
$memory_check = bootstrap\common::get_bool(\tts\misc::request_var('debug')); |
||||
if ($memory_check) { |
||||
$echo = false; |
||||
$data['memory_used'] = memory_usage::get_memory_stats($echo); |
||||
} |
||||
|
||||
self::encode($data, $long_code); |
||||
} |
||||
|
||||
/** |
||||
* Encode ok |
||||
* @param array $data |
||||
*/ |
||||
public static function ok(array $data = array()): void { |
||||
$data['result'] = true; |
||||
$code = 200; // OK |
||||
|
||||
$memory_check = bootstrap\common::get_bool(misc::request_var('debug')); |
||||
if ($memory_check) { |
||||
$echo = false; |
||||
$data['memory_used'] = memory_usage::get_memory_stats($echo); |
||||
} |
||||
|
||||
if (isset($data['code'])) { |
||||
if ($data['code'] > 199 && $data['code'] < 209) { |
||||
$code = $data['code']; |
||||
} |
||||
unset($data['code']); |
||||
} |
||||
|
||||
if (isset($data['response'])) { |
||||
switch ($data['response']) { |
||||
case self::CREATED: $long_code = "201 Created"; break; |
||||
case self::ACCEPTED: $long_code = "202 Accepted"; break; |
||||
case self::NON_AUTHORITATIVE: $long_code = "203 Non-Authoritative Information"; break; |
||||
case self::NO_CONTENT: $long_code = "204 No Content"; break; |
||||
case self::RESET_CONTENT: $long_code = "205 Reset Content"; break; |
||||
case self::PARTIAL_CONTENT: $long_code = "206 Partial Content"; break; |
||||
case self::ALREADY_REPORTED: $long_code = "208 Already Reported"; break; |
||||
case self::OK: $long_code = "200 OK"; break; |
||||
default: $long_code = $code; break; |
||||
} |
||||
} else { |
||||
$long_code = $code; |
||||
} |
||||
|
||||
self::encode($data, $long_code); |
||||
} |
||||
|
||||
public static function xml_encode($data, $status_code, $object = null, string $start_tag = 'root') { |
||||
if (is_null($object)) { |
||||
$xml = new \SimpleXMLElement('<?xml version="1.0"?><'.$start_tag.'/>');
|
||||
self::xml_encode($status_code, $xml, $data); |
||||
|
||||
if (!headers_sent()) { |
||||
header($_SERVER['SERVER_PROTOCOL'] . " " . $status_code); |
||||
header("Access-Control-Allow-Origin: *"); |
||||
header("Access-Control-Allow-Methods: *"); |
||||
header('Content-Type: text/xml; charset=utf-8', true, intval($status_code)); |
||||
} |
||||
|
||||
echo $xml->asXML(); |
||||
exit; |
||||
} else { |
||||
foreach ($data as $key => $value) { |
||||
if (is_array($value)) { |
||||
$new_object = $object->addChild($key); |
||||
self::xml_encode($value, $status_code, $new_object); |
||||
} else { |
||||
$object->addChild($key, $value); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* |
||||
* Purpose to decode XML into an array |
||||
*/ |
||||
|
||||
public static function xml_decode(string $xmlstring) { |
||||
$xml = simplexml_load_string($xmlstring); |
||||
$json = json_encode($xml); |
||||
return json_decode($json, true); |
||||
} |
||||
|
||||
public static function xml_parse(string $htmlStr): string { |
||||
$xmlStr = str_replace('<', '<', $htmlStr); |
||||
$xmlStr = str_replace('>', '>', $xmlStr); |
||||
$xmlStr = str_replace('"', '"', $xmlStr); |
||||
$xmlStr = str_replace("'", ''', $xmlStr); |
||||
$xmlStr = str_replace("&", '&', $xmlStr); |
||||
return $xmlStr; |
||||
} |
||||
|
||||
public static function json_encode($data, $status_code): void { |
||||
if (!headers_sent()) { |
||||
header($_SERVER['SERVER_PROTOCOL'] . " " . $status_code); |
||||
/* |
||||
* Allow JavaScript from anywhere. CORS - Cross Origin Resource Sharing |
||||
* @link https://manning-content.s3.amazonaws.com/download/f/54fa960-332e-4a8c-8e7f-1eb213831e5a/CORS_ch01.pdf |
||||
*/ |
||||
header("Access-Control-Allow-Origin: *"); |
||||
header("Access-Control-Allow-Methods: *"); |
||||
header('Content-Type: application/json; charset=utf-8', true, intval($status_code)); |
||||
} |
||||
echo json_encode($data); |
||||
exit; |
||||
} |
||||
|
||||
public static function php_encode($data, $status_code): void { |
||||
if (!headers_sent()) { |
||||
header($_SERVER['SERVER_PROTOCOL'] . " " . $status_code); |
||||
header("Access-Control-Allow-Origin: *"); |
||||
header("Access-Control-Allow-Methods: *"); |
||||
header('Content-Type: text/php; charset=utf-8', true, intval($status_code)); |
||||
} |
||||
echo serialize($data); |
||||
exit; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,205 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts <Robert@TryingToScale.com> |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater; |
||||
|
||||
use Exception; |
||||
|
||||
/** |
||||
* |
||||
* @todo Ensure tts JS error reporting works |
||||
* @todo Finish Session MGT, Encrypted Sessions |
||||
* @todo Make Cached Sessions |
||||
* @todo Force Database methods to try Cache First and Save Cache Data |
||||
*/ |
||||
|
||||
class app { |
||||
private $file; |
||||
private $class; |
||||
private $method; |
||||
private $params; |
||||
|
||||
private function get_first_chunks(string $input): string { |
||||
$parts = explode('/', $input); |
||||
$last = array_pop($parts); |
||||
$parts = array(implode('/', $parts), $last); |
||||
return $parts[0]; |
||||
} |
||||
|
||||
private function get_last_part(string $input): string { |
||||
$reversedParts = explode('/', strrev($input), 2); |
||||
return strrev($reversedParts[0]); |
||||
} |
||||
|
||||
/** |
||||
* Do not declare a return type here, as it will Error out!! |
||||
*/ |
||||
public function __construct() { |
||||
$full_route = bootstrap\siteHelper::get_route(); |
||||
$no_hmtl = str_replace(".html", "", $full_route); |
||||
|
||||
// Find the Route |
||||
$route = $this->get_first_chunks($no_hmtl); |
||||
|
||||
// Find the Method |
||||
$the_method = $this->get_last_part($no_hmtl); |
||||
|
||||
$params = bootstrap\siteHelper::get_params(); |
||||
|
||||
// Now load Route |
||||
$is_from_the_controller = true; // TRUE for from Constructor... |
||||
$this->router($route, $the_method, $params, $is_from_the_controller); |
||||
} |
||||
|
||||
private function get_ctrl_dir(): string { |
||||
$ctrl = (consoleApp::is_cli()) ? "cli_" : ""; |
||||
return (bootstrap\siteHelper::get_testing()) ? "test_" : $ctrl; |
||||
} |
||||
|
||||
/** |
||||
* Gets route and checks if valid |
||||
* @param string $route |
||||
* @param string $method |
||||
* @param bool $is_controller |
||||
* @retval action |
||||
*/ |
||||
public function router(?string $route, ?string $method, $params, bool $is_controller = false) { |
||||
$ROOT = bootstrap\siteHelper::get_root(); |
||||
$file = ""; |
||||
$class = ""; |
||||
|
||||
if (misc::is_empty($route)) { |
||||
$uri = '/app/' . bootstrap\configure::get('CodeHydrater', 'default_project'); |
||||
} else { |
||||
$uri = $route; |
||||
} |
||||
|
||||
try { |
||||
$filtered_uri = security::filter_uri($uri); |
||||
} catch (Exception $ex) { |
||||
$this->local404(); // Route Un-Safe URI to Local 404 Page |
||||
} |
||||
|
||||
$safe_folders = bootstrap\requires::filter_dir_path( |
||||
$this->get_first_chunks($filtered_uri) |
||||
); |
||||
if (bootstrap\requires::is_dangerous($safe_folders)) { |
||||
$this->local404(); |
||||
} |
||||
|
||||
$safe_folders = rtrim($safe_folders, '/'); |
||||
if (empty($ROOT)) { |
||||
$this->local404(); |
||||
} |
||||
|
||||
$safe_file = bootstrap\requires::filter_dir_path( |
||||
$this->get_last_part($filtered_uri) |
||||
); |
||||
if (bootstrap\requires::is_dangerous($safe_file)) { |
||||
$this->local404(); |
||||
} |
||||
|
||||
$test = $this->get_ctrl_dir(); |
||||
$dir = bootstrap\requires::safer_dir_exists($ROOT . "{$test}controllers/" . $safe_folders); |
||||
|
||||
//check for default site controller first |
||||
if ($dir === false) { |
||||
$this->local404(); |
||||
} else { |
||||
$file = bootstrap\requires::safer_file_exists(basename($safe_file) . '_ctrl.php', $dir); |
||||
if ($file !== false) { |
||||
$class = security::filter_class($safe_folders) . "\\" . security::filter_class($safe_file . "_ctrl"); |
||||
} else { |
||||
$this->local404(); |
||||
} |
||||
} |
||||
|
||||
if (misc::is_empty($method)) { |
||||
$method = ""; // Clear out null if exists |
||||
} |
||||
|
||||
if (substr($method, 0, 2) == '__') { |
||||
$method = ""; // Stop any magical methods being called |
||||
} |
||||
|
||||
if ($is_controller === true) { |
||||
$this->file = $file; |
||||
$this->class = $class; |
||||
$this->method = $method; |
||||
$this->params = $params; |
||||
} else { |
||||
return $this->action($file, $class, $method, $params); |
||||
} |
||||
} |
||||
|
||||
private function local404() { |
||||
page_not_found::error404(); |
||||
} |
||||
|
||||
/** |
||||
* Do controller action |
||||
* @param string $file |
||||
* @param string $class |
||||
* @param string $method |
||||
* @retval type |
||||
*/ |
||||
private function action(string $file, string $class, string $method, $params) { |
||||
$safer_file = bootstrap\requires::safer_file_exists($file); |
||||
if (! $safer_file) { |
||||
$this->local404(); |
||||
} |
||||
if (empty($class)) { |
||||
$this->local404(); |
||||
} |
||||
|
||||
$use_api = misc::is_api(); |
||||
$test = $this->get_ctrl_dir(); |
||||
|
||||
$call_class = "\\Project\\" . $test . 'controllers\\' . $class; |
||||
$controller = new $call_class(); |
||||
|
||||
if ($method === "error" && str_contains($class, "app") && |
||||
method_exists($controller, $method) === false |
||||
) { |
||||
CodeHydrater_broken_error(); |
||||
return false; |
||||
} |
||||
|
||||
if ($use_api) { |
||||
if (empty($method)) { |
||||
$method = "index"; |
||||
} |
||||
$method .= "_api"; |
||||
if (method_exists($controller, $method)) { |
||||
return $controller->$method($params); |
||||
} else { |
||||
page_not_found::error404_cli(); |
||||
} |
||||
} else { |
||||
if (!empty($method) && method_exists($controller, $method)) { |
||||
return $controller->$method($params); |
||||
} else { |
||||
if (empty($method) && method_exists($controller, 'index')) { |
||||
return $controller->index($params); |
||||
} else { |
||||
$this->local404(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Does load controller by calling action |
||||
*/ |
||||
public function load_controller() { |
||||
return $this->action($this->file, $this->class, $this->method, $this->params); |
||||
} |
||||
|
||||
} // end of app |
||||
@ -0,0 +1,47 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts <Robert@TryingToScale.com> |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater; |
||||
|
||||
final class consoleApp { |
||||
public static $is_cli = false; |
||||
|
||||
public static function is_cli() { |
||||
if (static::$is_cli) { |
||||
return true; |
||||
} |
||||
|
||||
if (defined('STDIN')) { |
||||
return true; |
||||
} |
||||
|
||||
if (php_sapi_name() === 'cli') { |
||||
return true; |
||||
} |
||||
|
||||
if (array_key_exists('SHELL', $_ENV)) { |
||||
return true; |
||||
} |
||||
|
||||
// $argv = $_SERVER['argv'] ?? []; |
||||
// && count($argv) > 0 |
||||
|
||||
if (!isset($_SERVER['REMOTE_ADDR']) && !isset($_SERVER['HTTP_USER_AGENT'])) { |
||||
return true; |
||||
} |
||||
|
||||
if (!array_key_exists('REQUEST_METHOD', $_SERVER)) { |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,88 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater\enums; |
||||
|
||||
class safer_io_enums {} // Needed to auto-load |
||||
|
||||
enum HTML_FLAG { |
||||
case raw; // Dangerious XSS attacks... |
||||
case strip; |
||||
case encode; |
||||
case purify; // Allow safe whitelisted HTML elements/tags |
||||
case escape; // safely Escape HTML |
||||
} |
||||
|
||||
enum INPUTS: int { |
||||
case variable = 998; // User Defined VAR |
||||
case debugging = 999; // check POST and then if debugging is set, check GET |
||||
case json = 1000; // uses JSON on raw POST BODY |
||||
case post = 0; // INPUT_POST; |
||||
case get = 1; // INPUT_GET; |
||||
case cookie = 2; //INPUT_COOKIE; |
||||
case env = 4; // INPUT_ENV; |
||||
case server = 5; // INPUT_SERVER; |
||||
|
||||
public function resolve(): int { |
||||
return match($this) { |
||||
self::post => INPUT_POST, |
||||
self::get => INPUT_GET, |
||||
self::cookie => INPUT_COOKIE, |
||||
self::env => INPUT_ENV, |
||||
self::server => INPUT_SERVER, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
enum DB_FILTER { |
||||
case ON; // Tries to Filter out SQL from User Input |
||||
case OFF; // Normal pass thourgh... |
||||
} |
||||
|
||||
enum FIELD_FILTER: string { |
||||
case raw_string = "string"; |
||||
case array_of_strings = "strings"; |
||||
case email = "email-address"; |
||||
case url = "site-url"; |
||||
case raw = "unfiltered-non-sanitized"; |
||||
case integer_number = "integer"; |
||||
case array_of_ints = "integers"; |
||||
case floating_point = "float"; |
||||
case array_of_floats = "floats"; |
||||
|
||||
public function resolve() { |
||||
return match($this) { |
||||
self::raw_string => FILTER_UNSAFE_RAW, |
||||
self::array_of_strings => [ |
||||
'filter' => FILTER_UNSAFE_RAW, |
||||
'flags' => FILTER_REQUIRE_ARRAY |
||||
], |
||||
self::email => FILTER_SANITIZE_EMAIL, |
||||
self::url => FILTER_SANITIZE_URL, |
||||
self::raw => FILTER_DEFAULT, // Unfiltered, non-sanitized!!! |
||||
self::integer_number => [ |
||||
'filter' => FILTER_SANITIZE_NUMBER_INT, |
||||
'flags' => FILTER_REQUIRE_SCALAR |
||||
], |
||||
self::array_of_ints => [ |
||||
'filter' => FILTER_SANITIZE_NUMBER_INT, |
||||
'flags' => FILTER_REQUIRE_ARRAY |
||||
], |
||||
self::floating_point => [ |
||||
'filter' => FILTER_SANITIZE_NUMBER_FLOAT, |
||||
'flags' => FILTER_FLAG_ALLOW_FRACTION |
||||
], |
||||
self::array_of_floats => [ |
||||
'filter' => FILTER_SANITIZE_NUMBER_FLOAT, |
||||
'flags' => FILTER_REQUIRE_ARRAY |
||||
], |
||||
}; |
||||
} |
||||
} |
||||
@ -0,0 +1,14 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace CodeHydrater\exceptions; |
||||
|
||||
class Bool_Exception extends \Exception { |
||||
|
||||
public function errorMessage() { |
||||
$errorMsg = $this->getMessage() . ' is not a valid bool type.'; |
||||
return $errorMsg; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,103 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace CodeHydrater\exceptions; |
||||
|
||||
enum HANDLE_FAILURE: int { |
||||
case static = 0; |
||||
case always_throw = 1; |
||||
case halt = 2; |
||||
case just_return = 3; |
||||
} |
||||
|
||||
enum EX_OUTPUT: int { |
||||
case static = 0; |
||||
case html = 1; |
||||
case ajax = 2; |
||||
} |
||||
|
||||
enum DEBUGGER: int { |
||||
case static = 0; |
||||
case basic = 1; |
||||
case extended = 2; |
||||
} |
||||
|
||||
class DB_Exception extends \Exception { |
||||
public static $debug = DEBUGGER::basic; |
||||
public static $handle = HANDLE_FAILURE::always_throw; |
||||
public static $ouput = EX_OUTPUT::html; |
||||
public static $error_message = "Please try again, later..."; |
||||
|
||||
/** |
||||
* @param \Exception $e |
||||
* @param string $message Your Custom Error Message |
||||
* @param DEBUGGER $debug (static, basic, extended) |
||||
* @param string $do |
||||
* ( |
||||
* "static" = throw on static do_throw is true and no-results, |
||||
* "always_throw" = throw always if no-results were found, |
||||
* "return" = always return array of results or false and count if none... |
||||
* ) |
||||
* |
||||
* @return array can be used with was_empty |
||||
* @throws \Exception |
||||
*/ |
||||
public static function customMessage( |
||||
\Exception $e, |
||||
string $message = "", |
||||
DEBUGGER $debug = DEBUGGER::static, |
||||
HANDLE_FAILURE $handle = HANDLE_FAILURE::static, |
||||
EX_OUTPUT $output = EX_OUTPUT::static |
||||
): string { |
||||
if (empty($message)) { |
||||
$message = self::$error_message; |
||||
} |
||||
$live = (\main_tts\is_live()); |
||||
if ($live === false || \tts\session_management::has_user_right('debugger')) { |
||||
$msg = ($debug == DEBUGGER::basic || |
||||
(self::$debug === DEBUGGER::basic && |
||||
$debug === DEBUGGER::static) |
||||
) ? |
||||
$message . PHP_EOL : |
||||
$message . " " . $e->getMessage() .PHP_EOL; |
||||
} else { |
||||
$msg = $message; |
||||
} |
||||
|
||||
$ajax = ($output === EX_OUTPUT::ajax || |
||||
(self::$ouput === EX_OUTPUT::static && |
||||
self::$ouput === EX_OUTPUT::ajax) |
||||
) ? true : false; |
||||
|
||||
$status_code = 500; |
||||
if (!headers_sent() && $ajax) { |
||||
header($_SERVER['SERVER_PROTOCOL'] . " " . $status_code); |
||||
header("Access-Control-Allow-Origin: *"); |
||||
header("Access-Control-Allow-Methods: *"); |
||||
header("Content-Type: application/json; charset=UTF-8", true, intval($status_code)); |
||||
} |
||||
|
||||
$json = []; |
||||
$json['had_error'] = '1'; |
||||
$json['success'] = false; |
||||
$json['user_message'] = $msg; |
||||
if ($ajax) { |
||||
echo json_encode($json); |
||||
} |
||||
|
||||
if ($handle == HANDLE_FAILURE::always_throw || |
||||
(self::$handle === HANDLE_FAILURE::always_throw && |
||||
$handle === HANDLE_FAILURE::static) |
||||
) { |
||||
throw new \Exception($msg); |
||||
} |
||||
if ($handle === HANDLE_FAILURE::halt || |
||||
(self::$handle === HANDLE_FAILURE::halt && |
||||
$handle === HANDLE_FAILURE::static)) { |
||||
die(); |
||||
} |
||||
return self::$msg; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,130 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater; |
||||
|
||||
final class html { |
||||
|
||||
/** |
||||
* Purpose: To output all HTML select (drop down) option values. |
||||
* @param array $options array of key=>value |
||||
* @param string $default Item to select on. |
||||
* @param string $select_by ['value'] for most of your needs. |
||||
* @param array $a as optional options |
||||
* @param bool $escape XSS prevention... |
||||
* @retval string of option values... |
||||
*/ |
||||
public static function do_options( |
||||
array $options, |
||||
string $default = '', |
||||
string $select_by = 'text', |
||||
array $a = array(), |
||||
bool $escape = true |
||||
): string { |
||||
$more = ''; |
||||
if (count($a)) { |
||||
foreach ($a as $k => $v) { |
||||
if ($escape) { |
||||
$more .= " ". bootstrap\saferIO::h($k) . "=\"" . bootstrap\saferIO::h($v) . "\""; |
||||
} else { |
||||
$more .= " {$k}=\"{$v}\""; |
||||
} |
||||
} |
||||
} |
||||
|
||||
$values = ''; |
||||
foreach ($options as $value => $text) { |
||||
$compair_to = ($select_by == 'text') ? $text : $value; |
||||
$selected = (!empty($default) && $default == $compair_to) ? 'selected' : ''; |
||||
if ($escape) { |
||||
$value = bootstrap\saferIO::h($value); |
||||
$text = bootstrap\saferIO::h($text); |
||||
} |
||||
$values .= "<option value=\"{$value}\" " . |
||||
$selected . " " . $more . ">{$text}</option>"; |
||||
} |
||||
return $values; |
||||
} |
||||
|
||||
/** |
||||
* Used by Memory_Usage script. |
||||
* Displays a table from input arrays -- fields |
||||
* @param array $header_fields - TH |
||||
* @param array $fields - TD |
||||
* @param bool $escape XSS prevention... |
||||
* @retval void |
||||
*/ |
||||
public static function show_table( |
||||
array $header_fields, |
||||
array $fields, |
||||
bool $escape = true |
||||
): void { |
||||
$nl = PHP_EOL; |
||||
echo "{$nl}<table border='1' cellpadding='1' cellspacing='1'>{$nl}"; |
||||
echo "\t<tr>{$nl}"; |
||||
foreach($header_fields as $header_field) { |
||||
$field = ($escape) ? \bs_tts\safer_io::h($header_field) : $header_field; |
||||
echo "\t\t<th>{$field}</th>{$nl}"; |
||||
} |
||||
echo "\t</tr>{$nl}"; |
||||
|
||||
foreach($fields as $field) { |
||||
echo "\t<tr>{$nl}"; |
||||
foreach($field as $td) { |
||||
$cell = ($escape) ? \bs_tts\safer_io::h($td) : $td; |
||||
echo "\t\t<td>{$cell}</td>{$nl}"; |
||||
} |
||||
echo "\t</tr>{$nl}"; |
||||
} |
||||
echo "</table>{$nl}"; |
||||
} |
||||
|
||||
/** |
||||
* Generators use Memory in an Effient way!!!! So use this. |
||||
* @param array $header_fields |
||||
* @param array $db_field_names |
||||
* @param \Iterator $records |
||||
* @param bool $escape |
||||
* @return void |
||||
*/ |
||||
public static function show_table_from_generator( |
||||
array $header_fields, |
||||
\Iterator $records, |
||||
bool $escape = true |
||||
): void { |
||||
$nl = PHP_EOL; |
||||
echo "{$nl}<table border='1' cellpadding='1' cellspacing='1'>{$nl}"; |
||||
echo "\t<tr>{$nl}"; |
||||
foreach($header_fields as $header_field) { |
||||
$field = ($escape) ? \bs_tts\safer_io::h($header_field) : $header_field; |
||||
echo "\t\t<th>{$field}</th>{$nl}"; |
||||
} |
||||
echo "\t</tr>{$nl}"; |
||||
|
||||
foreach($records as $record) { |
||||
echo "\t<tr>{$nl}"; |
||||
foreach($record as $key => $value) { |
||||
if (! is_string($key)) { |
||||
continue; // Remove Duplicate records |
||||
} |
||||
$td = $value ?? ""; |
||||
if (is_string($td)) { |
||||
$cell = ($escape) ? \bs_tts\safer_io::h($td) : $td; |
||||
} else { |
||||
$cell = (string) $td; |
||||
} |
||||
echo "\t\t<td>{$cell}</td>{$nl}"; |
||||
} |
||||
echo "\t</tr>{$nl}"; |
||||
} |
||||
echo "</table>{$nl}"; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,31 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts <Bob_586@Yahoo.com> |
||||
* @copyright (c) 2025, Robert Strutts |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater; |
||||
|
||||
use HydraterLicense\MakeLicense; |
||||
|
||||
const PrivatePEM = BaseDir."/keydata/private.pem"; |
||||
const PublicPEM = BaseDir."/keydata/public.pem"; |
||||
const AESKeysFile = BaseDir."/src/aeskeys.php"; |
||||
const LicenseFile = BaseDir."/keydata/license.json"; |
||||
|
||||
if (! class_exists("HydraterLicense\MakeLicense")) { |
||||
die("Sorry, this extenstion is not availiable!"); |
||||
} |
||||
|
||||
$license_maker->generateLicense( |
||||
Array_For_Files, |
||||
ALLOWED_DOMAINS, |
||||
PrivatePEM, |
||||
PublicPEM, |
||||
AESKeysFile, |
||||
LicenseFile |
||||
); |
||||
@ -0,0 +1,71 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater; |
||||
|
||||
final class memory_usage { |
||||
|
||||
/** |
||||
* Used by get_memory_stats to get appropriate units of bytes |
||||
* @param type $size in bytes |
||||
* @retval string of unit size for bytes |
||||
*/ |
||||
public static function convert_bytes($size) { |
||||
$unit=array('b','kb','mb','gb','tb','pb'); |
||||
return @round($size/pow(1024,($i=floor(log($size,1024)))),2).' '.$unit[$i]; |
||||
} |
||||
|
||||
/** |
||||
* Display/Returns Current Memory Usage |
||||
* @param bool $echo (default of true) - true: displays, false: returns data |
||||
*/ |
||||
public static function get_memory_stats($echo = true) { |
||||
global $mem_baseline; |
||||
|
||||
$check = bootstrap\common::get_bool(misc::request_var('debug')); |
||||
|
||||
if ($check || defined('DEBUG') && DEBUG === true) { |
||||
$now_mem = memory_get_usage(); |
||||
$diff_mem = $now_mem - $mem_baseline; |
||||
|
||||
$s_current = self::convert_bytes($now_mem); |
||||
$s_diff = self::convert_bytes($diff_mem); |
||||
$s_startup_mem = self::convert_bytes($mem_baseline); |
||||
|
||||
$peak = memory_get_peak_usage(); |
||||
$s_peak = self::convert_bytes($peak); |
||||
$diff_peak = $peak - $mem_baseline; |
||||
$s_diff_peak = self::convert_bytes($diff_peak); |
||||
|
||||
if (! $echo) { |
||||
$a_fields['start-up'] = $s_startup_mem; |
||||
$a_fields['currently'] = $s_current; |
||||
$a_fields['total-diff'] = $s_diff; |
||||
$a_fields['peak'] = $s_peak; |
||||
$a_fields['total-diff-peak'] = $s_diff_peak; |
||||
} else { |
||||
$a_headers = array('Memory Item', 'Size'); |
||||
$a_fields[] = array('On-StartUp', $s_startup_mem); |
||||
$a_fields[] = array('Currently', $s_current); |
||||
$a_fields[] = array('<b>Total Diff</b>', "<b>{$s_diff}</b>"); |
||||
$a_fields[] = array(' ', ' '); |
||||
$a_fields[] = array('PEAK', $s_peak); |
||||
$a_fields[] = array('<i>Total Diff PEAK</i>', "<i>{$s_diff_peak}</i>"); |
||||
} |
||||
|
||||
if ($echo === true) { |
||||
html::show_table($a_headers, $a_fields, false); |
||||
} else { |
||||
return $a_fields; |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,370 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts <Robert@TryingToScale.com> |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater; |
||||
|
||||
final class misc { |
||||
|
||||
public static function post_var(string $var, int $filter = FILTER_UNSAFE_RAW): mixed { |
||||
return filter_input(INPUT_POST, $var, $filter); |
||||
} |
||||
|
||||
public static function get_var(string $var, int $filter = FILTER_UNSAFE_RAW): mixed { |
||||
return filter_input(INPUT_GET, $var, $filter); |
||||
} |
||||
|
||||
public static function request_var(string $var, int $filter = FILTER_UNSAFE_RAW): mixed { |
||||
if (filter_has_var(INPUT_POST, $var)) { |
||||
return self::post_var($var, $filter); |
||||
} |
||||
if (filter_has_var(INPUT_GET, $var)) { |
||||
return self::get_var($var, $filter); |
||||
} |
||||
return ""; |
||||
} |
||||
|
||||
/** |
||||
* Please note that not all web servers support: HTTP_X_REQUESTED_WITH |
||||
* So, you need to code more checks! |
||||
* @retval boolean true if AJAX request by JQuery, etc... |
||||
*/ |
||||
public static function is_ajax(): bool { |
||||
$http_x_requested_with = bootstrap\saferIO::get_clean_server_var('HTTP_X_REQUESTED_WITH'); |
||||
return (self::compair_it($http_x_requested_with, 'xmlhttprequest')); |
||||
} |
||||
|
||||
/** |
||||
* Checks if two strings match, case-insensitive |
||||
* @param string $var |
||||
* @param string $var2 |
||||
* @retval bool true match |
||||
*/ |
||||
public static function compair_it(?string $var, ?string $var2): bool { |
||||
if ($var === null || $var2 === null) { |
||||
return false; |
||||
} |
||||
return (bootstrap\common::string_to_lowercase(trim($var)) === bootstrap\common::string_to_lowercase(trim($var2))); |
||||
} |
||||
|
||||
/** |
||||
* Multi-Bytes Safe Lowercase and Trim data |
||||
* @param string $var |
||||
* @retval string |
||||
*/ |
||||
public static function safe_lowercase_trim(string $var): string { |
||||
return (bootstrap\common::string_to_lowercase(trim($var))); |
||||
} |
||||
|
||||
/** |
||||
* Is valid ID > zero |
||||
* @param int $var |
||||
* @retval bool is valid ID |
||||
*/ |
||||
public static function is_valid_id(int $var): bool { |
||||
return ($var > 0) ? true : false; |
||||
} |
||||
|
||||
/** |
||||
* Is NOT a valid ID... (ID < 1) |
||||
* @param int $var |
||||
* @retval bool true if not valid ID |
||||
*/ |
||||
public static function is_not_valid_id(int $var): bool { |
||||
return ($var < 1) ? true : false; |
||||
} |
||||
|
||||
/** |
||||
* Check if string is not empty |
||||
* @param string $var |
||||
* @retval bool |
||||
*/ |
||||
public static function is_not_empty(?string $var): bool { |
||||
return ($var !== null && !empty(trim($var))); |
||||
} |
||||
|
||||
/** |
||||
* Check if string is empty |
||||
* @param string $var |
||||
* @retval bool |
||||
*/ |
||||
public static function is_empty(?string $var): bool { |
||||
return ($var === null || empty(trim($var)) ); |
||||
} |
||||
|
||||
/** |
||||
* RESTFUL method detection for API |
||||
*/ |
||||
public static function get_request_method(): array { |
||||
$method = filter_input(INPUT_SERVER, 'REQUEST_METHOD', FILTER_SANITIZE_ENCODED); |
||||
switch (strtoupper($method)) { |
||||
case 'GET': // only retrieve data |
||||
$crud = 'read'; |
||||
return ['method' => $method, 'crud' => $crud, 'resetful' => true, 'valid' => true]; |
||||
case 'HEAD': // Has no response body |
||||
$crud = 'read'; |
||||
return ['method' => $method, 'crud' => $crud, 'resetful' => true, 'valid' => true]; |
||||
case 'POST': |
||||
$crud = 'create'; |
||||
return ['method' => $method, 'crud' => $crud, 'resetful' => true, 'valid' => true]; |
||||
case 'PUT': |
||||
$crud = 'replace'; |
||||
return ['method' => $method, 'crud' => $crud, 'resetful' => true, 'valid' => true]; |
||||
case 'PATCH': |
||||
$crud = 'modify'; |
||||
return ['method' => $method, 'crud' => $crud, 'resetful' => true, 'valid' => true]; |
||||
case 'DELETE': |
||||
$crud = 'delete'; |
||||
return ['method' => $method, 'crud' => $crud, 'resetful' => true, 'valid' => true]; |
||||
case 'CONNECT': |
||||
case 'COPY': |
||||
case 'OPTIONS': |
||||
case 'TRACE': |
||||
case 'LINK': |
||||
case 'UNLINK': |
||||
case 'PURGE': |
||||
case 'LOCK': |
||||
case 'UNLOCK': |
||||
case 'PROPFIND': |
||||
case 'VIEW': |
||||
return ['method' => $method, 'crud' => false, 'restful' => false, 'valid' => true]; |
||||
default: |
||||
return ['method' => $method, 'crud' => false, 'resetful' => false, 'valid' => false]; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Fetch File |
||||
* @param string $var |
||||
* @retval content or :null |
||||
*/ |
||||
public static function file_var(string $var) { |
||||
return (isset($_FILES[$var])) ? $_FILES[$var] : ':null'; |
||||
} |
||||
|
||||
/** |
||||
* How to prevent CRLF injection (Http response HEADER splitting) in php |
||||
* https://stackoverflow.com/questions/31318151/how-to-prevent-crlf-injection-http-response-splitting-in-php |
||||
* https://medium.com/@tomnomnom/crlf-injection-into-phps-curl-options-e2e0d7cfe545 |
||||
*/ |
||||
public static function abort_on_crlf(string $data): string { |
||||
// if (self::$_safe_replacement_for_crlf) { |
||||
$data = str_ireplace(["%0d", "%0a"], "", $data); |
||||
// } |
||||
|
||||
$cr = "/%0D|%0d/"; |
||||
$lf = "/%0A|%0a/"; |
||||
|
||||
$cr_check = preg_match($cr, $data); |
||||
$lf_check = preg_match($lf, $data); |
||||
|
||||
if ($cr_check > 0 || $lf_check > 0) { |
||||
throw new \Exception('Attempted: CRLF header hack detected. Aborting.'); |
||||
} |
||||
return $data; |
||||
} |
||||
|
||||
public static function filter_two_decimal_number($number): string { |
||||
return number_format(filter_var($number, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION), 2); |
||||
} |
||||
|
||||
/** |
||||
* Filter/Sanitize Email data |
||||
* @param string $data |
||||
* @retval string of sanitized email data or :false |
||||
*/ |
||||
public static function clean_email(string $data): string { |
||||
$email = filter_var(trim($data), FILTER_SANITIZE_EMAIL); |
||||
return (filter_var($email, FILTER_VALIDATE_EMAIL)) ? $email : ':false'; |
||||
} |
||||
|
||||
/** |
||||
* Encode and Filter/Sanitize URL data |
||||
* @param string $data |
||||
* @retval string of sanitized url data |
||||
*/ |
||||
public static function encode_url_var(string $data): string { |
||||
return urlencode(filter_var(trim($data), FILTER_SANITIZE_URL)); |
||||
} |
||||
|
||||
public static function is_url(string $url): bool { |
||||
return filter_var($url, FILTER_VALIDATE_URL); |
||||
} |
||||
|
||||
/** |
||||
* Decode URL data |
||||
* @param string $data |
||||
* @retval string of url data |
||||
*/ |
||||
public static function decode_url_var(string $data): string { |
||||
return trim(urldecode($data)); |
||||
} |
||||
|
||||
public static function is_regex($exp): bool { |
||||
return (filter_var($exp, FILTER_VALIDATE_REGEXP)); |
||||
} |
||||
|
||||
public static function email_hyper_link(string $address, string $subject, string $body): string { |
||||
$mail_to_uri = 'mailto:' . rawurlencode($address) . '?subject=' . rawurlencode($subject) . '&body=' . rawurlencode($body); |
||||
return htmlspecialchars($mail_to_uri); |
||||
} |
||||
|
||||
// GET requests should not make changes |
||||
// Only POST requests should make changes |
||||
|
||||
public static function request_is_get() { |
||||
return $_SERVER['REQUEST_METHOD'] === 'GET'; |
||||
} |
||||
|
||||
public static function request_is_post() { |
||||
return $_SERVER['REQUEST_METHOD'] === 'POST'; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Helper function for safe_url -> does URL encode |
||||
* @param string $q |
||||
* @retval string |
||||
*/ |
||||
private static function _parm_encode(string $q): string { |
||||
if (substr_count($q, "=") > 0) { |
||||
$parms = explode("=", $q); |
||||
$parm = self::encode_url_var($parms[0]); |
||||
$value = self::encode_url_var($parms[1]); |
||||
return $parm . "=" . $value . "&"; |
||||
} else { |
||||
return self::encode_url_var($q); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Encode and make safe URL |
||||
* @param string $vars |
||||
* @retval string the safe url |
||||
*/ |
||||
public static function safe_url(string $vars): string { |
||||
$new = ''; |
||||
|
||||
if (substr_count($vars, "&") > 0) { |
||||
$qs = explode("&", $vars); |
||||
foreach ($qs as $q) { |
||||
$new .= self::_parm_encode($q); |
||||
} |
||||
$new = rtrim($new, "&"); |
||||
} else { |
||||
$new = rtrim(self::_parm_encode($vars), "&"); |
||||
} |
||||
|
||||
return $new; |
||||
} |
||||
|
||||
/** |
||||
* Purpose to auto create url based on if short url is needed |
||||
* @param string $project - Which folder |
||||
* @param string $route |
||||
* @param string $method |
||||
* @param type $vars |
||||
* @retval string url |
||||
*/ |
||||
public static function get_url(string $route, string $method, $vars = ''): string { |
||||
$route = ltrim($route, "/"); |
||||
|
||||
if (is_array($vars)) { |
||||
$vars = http_build_query($vars); |
||||
} elseif (is_string($vars) && !empty($vars)) { |
||||
$vars = self::safe_url($vars); |
||||
} |
||||
|
||||
if (bootstrap\configure::get('CodeHydrater', 'short_url') === true) { |
||||
$vars = (!empty($vars)) ? "?{$vars}" : ''; |
||||
return PROJECT_BASE_REF . "/{$route}/{$method}.html{$vars}"; |
||||
} else { |
||||
$vars = (!empty($vars)) ? "&{$vars}" : ''; |
||||
return PROJECT_BASE_REF . "?route={$route}&m={$method}{$vars}"; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Purpose to auto create API url based on if short url is needed |
||||
* @param string $route |
||||
* @param string $method |
||||
* @param type $vars |
||||
* @retval string API url |
||||
*/ |
||||
public static function get_api_url(string $route, string $method, $vars = ''): string { |
||||
$route = ltrim($route, "/"); |
||||
|
||||
if (is_array($vars)) { |
||||
$vars = http_build_query($vars); |
||||
} elseif (is_string($vars) && !empty($vars)) { |
||||
$vars = self::safe_url($vars); |
||||
} |
||||
|
||||
if (bootstrap\configure::get("CodeHydrater", 'short_url') === true) { |
||||
$vars = (!empty($vars)) ? "?{$vars}" : '?x=0'; |
||||
return PROJECT_BASE_REF . "/api/{$route}/{$method}{$vars}&api=true"; |
||||
} else { |
||||
$vars = (!empty($vars)) ? "&{$vars}" : ''; |
||||
return PROJECT_BASE_REF . "?route={$route}&m={$method}&code=/api/{$vars}&api=true"; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Checks if current URI is from an API call |
||||
* @retval bool |
||||
*/ |
||||
public static function is_api(): bool { |
||||
$uri = bootstrap\siteHelper::request_uri(); |
||||
if (strlen($uri) > 2) { |
||||
return ((substr_count($uri, "/api/") == 1 && self::get_var('api') == 'true') || (substr_count($uri, "/api2/") == 1 && self::get_var('api2') == 'true')) ? true : false; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* site http://php.net/manual/en/function.base64-encode.php |
||||
*/ |
||||
public static function base64url_encode(string $data): string { |
||||
return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); |
||||
} |
||||
|
||||
public static function base64url_decode(string $data): string { |
||||
//return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT)); |
||||
return base64_decode( strtr( $data, '-_', '+/') . str_repeat('=', 3 - ( 3 + strlen( $data )) % 4 )); |
||||
} |
||||
|
||||
public static function get_globals(array $skip = ['route', 'm'], array $only_these = []): string { |
||||
$the_request = ''; |
||||
|
||||
$getArray = filter_input_array(INPUT_GET) ?? []; |
||||
$postArray = filter_input_array(INPUT_POST) ?? []; |
||||
$my_requests = array_merge($getArray, $postArray); |
||||
|
||||
foreach ($my_requests as $key => $value) { |
||||
if (count($skip) && in_array($key, $skip)) { |
||||
continue; |
||||
} |
||||
|
||||
$safe_key = filter_var(trim($key), FILTER_UNSAFE_RAW); |
||||
$url_key = self::encode_url_var($safe_key); |
||||
|
||||
$data = filter_var($key); |
||||
$safe_value = filter_var(trim($data), FILTER_UNSAFE_RAW); |
||||
$url_value = self::encode_url_var($safe_value); |
||||
|
||||
if (in_array($key, $only_these) || !count($only_these)) { |
||||
$the_request .= '&' . $url_key . '=' . $url_value; |
||||
} |
||||
} |
||||
|
||||
return strip_tags($the_request); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,69 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater; |
||||
|
||||
class page_not_found { |
||||
|
||||
/** |
||||
* Command Line Route - Invalid Error |
||||
*/ |
||||
public static function error404_cli(): void { |
||||
$err = "CLI 404 Page not found! Invalid Route/Method." . PHP_EOL; |
||||
$argv = (isset($GLOBALS['argv'])) ? $GLOBALS['argv'] : array(); |
||||
$num_args = count($argv); |
||||
if ( $num_args > 1 ) { |
||||
$err .= $argv[1]; |
||||
} |
||||
|
||||
$exists = bootstrap\registry::get('di')->exists('log'); |
||||
if ($exists) { |
||||
$log = bootstrap\registry::get('di')->get_service('log', ['CLI_404s']); |
||||
$log->write($err); |
||||
} |
||||
|
||||
echo $err; |
||||
|
||||
exit(1); |
||||
} |
||||
|
||||
/** |
||||
* Displays 404 Page not Found |
||||
*/ |
||||
public static function error404(): void { |
||||
if (consoleApp::is_cli()) { |
||||
self::error404_cli(); |
||||
} else { |
||||
$use_api = misc::is_api(); |
||||
} |
||||
|
||||
if ($use_api === true) { |
||||
self::api_method_not_found(); |
||||
} |
||||
// Show 404, Page Not Found Error Page! |
||||
if (bootstrap\requires::secure_include('404_page.php', bootstrap\UseDir::ONERROR) === false) { |
||||
$loaded = bootstrap\requires::secure_include('views/on_error/404_page.php', bootstrap\UseDir::FRAMEWORK); |
||||
if ($loaded === false) { |
||||
echo "<h1>404 Page Not Found!</h1>"; |
||||
} |
||||
} |
||||
exit(1); |
||||
} |
||||
|
||||
/** |
||||
* API Method was not found do API 400 Error |
||||
*/ |
||||
private static function api_method_not_found(): void { |
||||
$status = 400; // Bad Request |
||||
$bad_request = api::BAD_REQUEST; |
||||
api::error(array('response' => $bad_request, 'code' => $status, 'reason' => 'Command not found')); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,487 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @license MIT |
||||
* @copyright (c) 2018, Patrik Mokrý |
||||
* @author Patrik Mokrý |
||||
* @link https://github.com/MokryPatrik/PHP-Router |
||||
*/ |
||||
|
||||
namespace CodeHydrater; |
||||
|
||||
class router |
||||
{ |
||||
private static $instance = null; |
||||
public static $URL = null; |
||||
public static $REQUEST = null; |
||||
public static $params = []; |
||||
private static $queryParams = []; |
||||
private static $routes = []; // All routes |
||||
public static $route = ""; // Return current route |
||||
private static $last = null; // Last route added |
||||
|
||||
private static $prefix = null; |
||||
private static $name = null; |
||||
private static $doNotIncludeInParams = []; |
||||
private static $shortcuts = [ |
||||
'i' => '(\d+)', // Any Number |
||||
's' => '(\w+)', // Any Word |
||||
'locale' => '(sk|en)->en' |
||||
]; |
||||
|
||||
/** |
||||
* Init new self instance |
||||
*/ |
||||
public static function init() { |
||||
self::$instance = new self(); |
||||
} |
||||
|
||||
/** |
||||
* Assign name to route |
||||
* |
||||
* @param $name |
||||
*/ |
||||
public function name($name) |
||||
{ |
||||
$route = self::$routes[self::$last]; |
||||
unset(self::$routes[self::$last]); |
||||
self::$routes[self::$name . $name] = $route; |
||||
} |
||||
|
||||
/** |
||||
* Add custom shortcut |
||||
* shortcut with default value -> $regex = (val1|val2)->val1; |
||||
* |
||||
* @param $shortcut |
||||
* @param $regex |
||||
*/ |
||||
public static function addShortcut($shortcut, $regex) |
||||
{ |
||||
self::$shortcuts[$shortcut] = $regex; |
||||
} |
||||
|
||||
/** |
||||
* Get link from route name and params |
||||
* |
||||
* @param $route |
||||
* @param array $params |
||||
* @param $absolute |
||||
* @return null |
||||
*/ |
||||
public static function link($route, $params = [], $absolute = true) |
||||
{ |
||||
if (!isset(self::$routes[$route])) return null; |
||||
$route = self::$routes[$route]; |
||||
|
||||
$link = ""; |
||||
foreach ($route['params'] as $key => $param) { |
||||
if (isset($param['real'])) { |
||||
$link .= $param['pattern'] . '/'; |
||||
} else if (isset($params[$param['name']])) { |
||||
// Chcek if param has default |
||||
if (isset($param['default']) && $param['default'] !== $params[$param['name']]) { |
||||
$link .= $params[$param['name']] . '/'; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Add absolute path |
||||
if ($absolute) { |
||||
$link = self::$URL . $link; |
||||
} |
||||
|
||||
// Cut slash at the end |
||||
return rtrim($link, '/'); |
||||
} |
||||
|
||||
/** |
||||
* Redirect to specific route or defined location |
||||
* |
||||
* @param $route |
||||
* @param array $params |
||||
*/ |
||||
public static function redirect($route, $params = []) |
||||
{ |
||||
if (isset(self::$routes[$route])) { |
||||
$route = self::link($route, $params); |
||||
} |
||||
header('Location: ' . $route, true); |
||||
die(); |
||||
} |
||||
|
||||
/** |
||||
* Create prefixed routes |
||||
* |
||||
* @param $prefix |
||||
* @param $callback |
||||
* @param string $name |
||||
* @param array $doNotIncludeInParams |
||||
*/ |
||||
public static function prefix($prefix, $callback, $name = '', $doNotIncludeInParams = []) |
||||
{ |
||||
self::$prefix = $prefix; |
||||
self::$name = $name; |
||||
self::$doNotIncludeInParams = $doNotIncludeInParams; |
||||
call_user_func($callback); |
||||
self::$prefix = null; |
||||
self::$name = null; |
||||
self::$doNotIncludeInParams = []; |
||||
} |
||||
|
||||
/** |
||||
* Create route |
||||
* |
||||
* @param $route |
||||
* @param $action |
||||
* @param array $method |
||||
* @return Router |
||||
*/ |
||||
public static function route($route, $action, $method = ["POST", "GET"]) |
||||
{ |
||||
$prefix = self::$prefix; |
||||
if (empty($route) && !empty(self::$prefix)) { |
||||
$prefix = rtrim(self::$prefix, '/'); |
||||
} |
||||
|
||||
|
||||
$explodedRoute = explode("/", $prefix . $route); |
||||
$params = []; |
||||
$pattern = ""; |
||||
|
||||
// create route |
||||
foreach ($explodedRoute as $key => $r) { |
||||
if (strpos($r, '}?') !== false) { |
||||
$r = self::dynamic($r, $params, true); |
||||
$pattern = substr($pattern, 0, -1); |
||||
$dyn = true; |
||||
} else if (strpos($r, '}') !== false) { |
||||
$r = self::dynamic($r, $params, false); |
||||
$pattern = substr($pattern, 0, -1); |
||||
$dyn = true; |
||||
} else { |
||||
$params[] = [ |
||||
'pattern' => $r, |
||||
'real' => false |
||||
]; |
||||
} |
||||
$pattern .= ($key == 0 && !isset($dyn) ? '/' : '') . $r . '/'; |
||||
} |
||||
|
||||
// Create pattern |
||||
$pattern = substr($pattern, 0, -1) . '/?'; |
||||
$pattern = '~^' . str_replace('/', '\/', $pattern) . '$~'; |
||||
|
||||
// Save data to static property |
||||
$name = uniqid(); |
||||
self::$routes[$name] = [ |
||||
'route' => $route, |
||||
'pattern' => $pattern, |
||||
'params' => $params, |
||||
'action' => $action, |
||||
'method' => $method, |
||||
'doNotIncludeInParams' => self::$doNotIncludeInParams |
||||
]; |
||||
// Set last added |
||||
self::$last = $name; |
||||
|
||||
// return self instance |
||||
if (self::$instance == null) { |
||||
self::init(); |
||||
} |
||||
return self::$instance; |
||||
} |
||||
|
||||
public static function get($route, $action) { |
||||
return self::route($route, $action, ['GET']); |
||||
} |
||||
|
||||
public static function post($route, $action) { |
||||
return self::route($route, $action, ['POST']); |
||||
} |
||||
|
||||
public static function put($route, $action) { |
||||
return self::route($route, $action, ['PUT']); |
||||
} |
||||
|
||||
public static function delete($route, $action) { |
||||
return self::route($route, $action, ['DELETE']); |
||||
} |
||||
|
||||
public static function any($route, $action) { |
||||
return self::route($route, $action, ['GET', 'POST', 'PUT', 'DELETE']); |
||||
} |
||||
|
||||
/** |
||||
* Create all routes for resource (CRUD) |
||||
* |
||||
* @param $route |
||||
* @param $controller |
||||
* @param string $name |
||||
* @param string $shortcut |
||||
*/ |
||||
public static function resource($route, $controller, $name = null, $shortcut = 's') |
||||
{ |
||||
self::$name = self::$name . $name; |
||||
|
||||
self::get($route . '/{page::paginator}?', $controller . "@index")->name('index'); |
||||
self::get($route . "/create", $controller . "@create")->name('create'); |
||||
self::post($route, $controller . "@store")->name('store'); |
||||
self::get($route . "/{id::" . $shortcut . "}", $controller . "@show")->name('show'); |
||||
self::get($route . "/{id::" . $shortcut . "}/edit", $controller . "@edit")->name('edit'); |
||||
self::put($route . "/{id::" . $shortcut . "}", $controller . "@update")->name('update'); |
||||
self::delete($route . "/{id::" . $shortcut . "}", $controller . "@destroy")->name('destroy'); |
||||
self::get($route . "/{id::" . $shortcut . "}/delete", $controller . "@destroy")->name('destroy_'); |
||||
|
||||
self::$name = str_replace($name, '', self::$name); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Handle dynamic parameter in route |
||||
* |
||||
* @param string $route |
||||
* @param array $params |
||||
* @param boolean $optional |
||||
* @return mixed |
||||
*/ |
||||
private static function dynamic($route, &$params, $optional = false) |
||||
{ |
||||
$shortcut = self::rules($route); |
||||
|
||||
$name = str_replace('{', '', $route); |
||||
if (!$optional) { |
||||
$name = str_replace('}', '', $name); |
||||
} else { |
||||
$name = str_replace('}?', '', $name); |
||||
} |
||||
|
||||
if (strpos($name, '::')) { |
||||
$name = substr($name, 0, strpos($name, "::")); |
||||
} |
||||
|
||||
if (array_search($name, array_column($params, 'name')) === false) { |
||||
|
||||
$pattern = str_replace('(', '(/', $shortcut['shortcut']); |
||||
$pattern = str_replace('|', '|/', $pattern); |
||||
|
||||
// If is optional add ? at the end of pattern |
||||
if ($optional) { |
||||
$pattern .= '?'; |
||||
} |
||||
|
||||
$params[] = [ |
||||
'name' => $name, |
||||
'pattern' => $pattern, |
||||
'default' => $shortcut['default'] |
||||
]; |
||||
} else { |
||||
throw new \Exception('Parameter with name ' . $name . ' has been already defined'); |
||||
} |
||||
return $pattern; |
||||
} |
||||
|
||||
/** |
||||
* Dynamic route rules |
||||
* |
||||
* @param $route |
||||
* @return mixed |
||||
*/ |
||||
private static function rules($route) |
||||
{ |
||||
if (preg_match('~::(.*?)}~', $route, $match)) { |
||||
list(, $shortcut) = $match; |
||||
if (isset(self::$shortcuts[$shortcut])) { |
||||
// Try to get default value from shortcut |
||||
$default = false; |
||||
$shortcut = self::$shortcuts[$shortcut]; |
||||
if (preg_match('~->(.*?)$~', $shortcut, $match)) { |
||||
$default = $match[1]; |
||||
$shortcut = str_replace($match[0], '', $shortcut); |
||||
} |
||||
// Return shortcut and default value |
||||
return [ |
||||
'shortcut' => $shortcut, |
||||
'default' => $default |
||||
]; |
||||
} |
||||
} |
||||
return [ |
||||
'shortcut' => self::$shortcuts['s'], |
||||
'default' => false |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* get_all_routes -> Added by Robert Strutts to auto load routes |
||||
* Namespace must start with Project |
||||
* Needs a routes folder, then |
||||
* either an routes.php or test_routes.php files must exists |
||||
* with a method called get! |
||||
*/ |
||||
private static function get_all_routes(bool $testing = false) { |
||||
$route_name = (consoleApp::is_cli()) ? "cli_routes" : "routes"; |
||||
$routes = ($testing) ? "test_routes" : $route_name; |
||||
$routes_class = "\\Project\\routes\\{$routes}"; |
||||
|
||||
if (class_exists($routes_class)) { |
||||
if (method_exists($routes_class, "get")) { |
||||
$callback = "{$routes_class}::get"; |
||||
call_user_func($callback); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Execute router |
||||
* @todo add Kernel/Requests... |
||||
*/ |
||||
public static function execute() { |
||||
$ROOT = bootstrap\siteHelper::get_root(); |
||||
$request_uri = bootstrap\siteHelper::get_uri(); |
||||
$request_method = bootstrap\siteHelper::get_method(); |
||||
$testing = bootstrap\siteHelper::get_testing(); |
||||
|
||||
// Generate request and absolute path |
||||
self::generateURL($ROOT, $request_uri); |
||||
// Fecth all Routes from the Project |
||||
self::get_all_routes($testing); |
||||
|
||||
// Get query string |
||||
$request = explode('?', $request_uri); |
||||
$queryParams = []; |
||||
if (isset($request[1])) { |
||||
$queryParams = $request[1]; |
||||
parse_str($queryParams, $queryParams); |
||||
self::$queryParams = $queryParams; |
||||
} |
||||
|
||||
// Modify request |
||||
$request = '/' . trim(self::$REQUEST, '/'); |
||||
|
||||
// Find route |
||||
foreach (self::$routes as $routeKey => $route) { |
||||
$post_method = \tts\misc::post_var("_method"); |
||||
$matchMethod = in_array($request_method, $route['method']) || ($post_method !== null |
||||
&& in_array($post_method, $route['method'])); |
||||
if (preg_match($route['pattern'], $request, $match) && $matchMethod) { |
||||
|
||||
// Default variables |
||||
$explodedRequest = explode('/', ltrim($request, '/')); |
||||
$routeParams = $route['params']; |
||||
|
||||
// Match request params with params in array - static params |
||||
foreach ($explodedRequest as $key => $value) { |
||||
foreach ($routeParams as $k => $routeParam) { |
||||
// Go to the next request part |
||||
if (isset($routeParam['real']) && $routeParam['pattern'] == $value) { |
||||
unset($routeParams[$k]); |
||||
unset($explodedRequest[$key]); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
$number = "(/\d+)"; |
||||
$params = []; |
||||
// Match request params with params in array - dynamic params |
||||
foreach ($routeParams as $k => $routeParam) { |
||||
$value = $explodedRequest[$k] ?? false; |
||||
$default = $routeParam['default'] ?? true; |
||||
$pattern = $routeParam['pattern'] ?? ""; |
||||
$wild = str_contains($pattern, "?"); |
||||
if (! $wild && ($value === false && $default)) { |
||||
$params[$routeParam['name']] = null; |
||||
} else if ($wild && ($value === false && ! $default)) { |
||||
// Let the default function params be used by not setting anything here! |
||||
} else if (preg_match('~' . $pattern . '~', '/' . $value, $match)) { |
||||
if (str_contains($pattern, $number)) { |
||||
$params[$routeParam['name']] = (int) $value; |
||||
} else { |
||||
$params[$routeParam['name']] = $value; |
||||
} |
||||
unset($routeParams[$k]); |
||||
} |
||||
} |
||||
|
||||
// Merge with query params |
||||
self::$params = array_merge($params, $queryParams); |
||||
|
||||
// Setup default route and url |
||||
self::$route = $route; |
||||
|
||||
foreach ($params as $key => $value) { |
||||
if (in_array($key, $route['doNotIncludeInParams']) && !is_numeric($key)) { |
||||
unset($params[$key]); |
||||
} |
||||
} |
||||
|
||||
// Call action |
||||
if (is_callable($route['action'])) { |
||||
$returned = call_user_func_array($route['action'], $params); |
||||
return ["found"=> true, "returned"=> $returned]; |
||||
} else if (strpos($route['action'], '@') !== false) { |
||||
|
||||
// call controller |
||||
list($controller, $method) = explode('@', $route['action']); |
||||
|
||||
// init new controller |
||||
$controller = new $controller; |
||||
|
||||
// Check if class has parent |
||||
$parentControllers = class_parents($controller); |
||||
if (!empty($parentControllers)) { |
||||
end($parentControllers); |
||||
$parentController = $parentControllers[key($parentControllers)]; |
||||
$parentController = new $parentController; |
||||
|
||||
// Add properties to parent class |
||||
foreach ($params as $key => $value) { |
||||
$parentController::$params[$key] = $value; |
||||
} |
||||
} |
||||
|
||||
//Call method |
||||
if (method_exists($controller, $method)) { |
||||
$returned = call_user_func_array([$controller, $method], $params); |
||||
return ["found"=> true, "returned"=> $returned]; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return ["found"=>false]; |
||||
} |
||||
|
||||
/** |
||||
* Generate URL |
||||
* |
||||
* @param $ROOT |
||||
*/ |
||||
private static function generateURL(string $ROOT, string $request_uri) |
||||
{ |
||||
$https = bootstrap\saferIO::get_clean_server_var('HTTPS'); |
||||
$baseLink = ($https === 'on') ? "https" : "http"; |
||||
|
||||
$server_name = bootstrap\saferIO::get_clean_server_var('SERVER_NAME'); |
||||
$baseLink .= "://" . $server_name; |
||||
|
||||
$port = bootstrap\saferIO::get_clean_server_var('SERVER_PORT'); |
||||
$baseLink .= ($port !== '80') ? ':' . $port : ':'; |
||||
|
||||
$baseRequest = ''; |
||||
|
||||
$request = $request_uri; |
||||
foreach (explode('/', $ROOT) as $key => $value) { |
||||
if ($value == '') continue; |
||||
if (preg_match('~/' . $value . '~', $request)) { |
||||
$baseRequest .= $value . '/'; |
||||
} |
||||
$request = preg_replace('~/' . $value . '~', '', $request, 1); |
||||
} |
||||
|
||||
self::$URL = $baseLink . '/' . $baseRequest; |
||||
self::$REQUEST = explode('?', $request)[0]; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,264 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace CodeHydrater; |
||||
|
||||
final class security { |
||||
|
||||
use traits\security\csrf_token_functions; |
||||
use traits\security\session_hijacking_functions; |
||||
|
||||
/** |
||||
* Get unique IDs for database |
||||
* @return int |
||||
*/ |
||||
public static function get_unique_number(): int { |
||||
return abs(crc32(microtime())); |
||||
} |
||||
|
||||
/** |
||||
* Get token |
||||
* @return string |
||||
*/ |
||||
public static function get_unique_id(): string { |
||||
$more_entropy = true; |
||||
$prefix = ""; // Blank is a rand string |
||||
return md5(uniqid($prefix, $more_entropy)); |
||||
} |
||||
|
||||
public static function use_hmac(string $algo, string $pepper) { |
||||
if (!function_exists("hash_hmac_algos")) { |
||||
throw new \Exception("hash_hmac not installed!"); |
||||
} |
||||
if (strpos($algo, "md") !== false) { |
||||
throw new \Exception("MD is too weak!"); |
||||
} |
||||
if (strpos($algo, "sha1") !== false) { |
||||
throw new \Exception("sha1 is too weak!"); |
||||
} |
||||
$allowed = hash_hmac_algos(); |
||||
if (in_array(strtolower($algo), $allowed)) { |
||||
return hash_hmac($algo, $pepper); |
||||
} |
||||
throw new \Exception("hmac algo not found!"); |
||||
} |
||||
|
||||
/* |
||||
* Consider MD5 and SHA1 which are fast and efficient, |
||||
* making them ideal for check summing and file verification. |
||||
* However, their speed makes them unsuitable for hashing a |
||||
* user’s password. With today’s computational power of |
||||
* modern CPUs/GPUs and cloud computing, a hashed password |
||||
* can be cracked by brute force in a matter of minutes. |
||||
* It’s fairly trivial to quickly generate billions of MD5 |
||||
* hashes from random words until a match is found, thereby |
||||
* revealing the original plain-text password. Instead, |
||||
* intentionally slower hashing algorithms such as bcrypt |
||||
* or Argon2 should be used. |
||||
*/ |
||||
|
||||
public static function find_default_hash_algo() { |
||||
if (defined("PASSWORD_ARGON2ID")) |
||||
return PASSWORD_ARGON2ID; |
||||
if (defined("PASSWORD_ARGON2")) |
||||
return PASSWORD_ARGON2; |
||||
if (defined("PASSWORD_DEFAULT")) |
||||
return PASSWORD_DEFAULT; |
||||
if (defined("PASSWORD_BCRYPT")) |
||||
return PASSWORD_BCRYPT; |
||||
return false; |
||||
} |
||||
|
||||
/* |
||||
* The password_hash() function not only uses a secure |
||||
* one-way hashing algorithm, but it automatically handles |
||||
* salt and prevents time based side-channel attacks. |
||||
*/ |
||||
|
||||
public static function do_password_hash(string $password): bool | string { |
||||
$pwd_peppered = self::make_hash($password); |
||||
$hash_algo = \main_tts\configure::get( |
||||
"security", |
||||
"hash_algo" |
||||
); |
||||
if (! empty($hash_algo)) { |
||||
return password_hash($pwd_peppered, $hash_algo); |
||||
} |
||||
return password_hash($pwd_peppered, self::find_default_hash_algo()); |
||||
} |
||||
|
||||
public static function do_password_verify( |
||||
string $input_pwd, $db_password |
||||
): bool { |
||||
$pwd_peppered = self::make_hash($input_pwd); |
||||
return password_verify($pwd_peppered, $db_password); |
||||
} |
||||
|
||||
/** |
||||
* Make a secure Hash |
||||
* @param string $text to encode |
||||
* @param string $level (weak, low, high, max) |
||||
* @return string new Hashed |
||||
*/ |
||||
public static function make_hash(string $text): string { |
||||
$level = bootstrap\configure::get('security', 'hash_level'); |
||||
if (empty($level)) { |
||||
$level = "normal"; |
||||
} |
||||
$pepper = bootstrap\configure::get('security', 'pepper_pwd'); |
||||
if (strlen($pepper) < 12) { |
||||
throw new \Exception("Pepper Password, too short!"); |
||||
} |
||||
$salt = bootstrap\configure::get('security', 'salt_pwd'); |
||||
if (strlen($salt) < 5) { |
||||
throw new \Exception("Salt Password, too short!"); |
||||
} |
||||
|
||||
switch (strtolower($level)) { |
||||
case 'max': |
||||
// Prefer computing using HMAC |
||||
if (function_exists("hash_hmac")) { |
||||
return hash_hmac("sha512", $text, $pepper); |
||||
} |
||||
// Sha512 hash is the next best thing |
||||
if (function_exists("hash")) { |
||||
return hash("sha512", $salt . $text . $pepper); |
||||
} |
||||
case 'normal': |
||||
// Prefer computing using HMAC |
||||
if (function_exists("hash_hmac")) { |
||||
return hash_hmac("sha256", $text, $pepper); |
||||
} |
||||
// Sha256 hash is the next best thing |
||||
if (function_exists("hash")) { |
||||
return hash("sha256", $salt . $text . $pepper); |
||||
} |
||||
case 'weak': |
||||
throw \Exception("Too weak of a Hash FN"); |
||||
// return sha1($salt . $text . $pepper); |
||||
case 'low': |
||||
throw \Exception("Too weak of a Hash FN"); |
||||
// return md5($salt . md5($text . $pepper)); |
||||
default: |
||||
break; |
||||
} |
||||
return self::use_hmac($level, $pepper); |
||||
} |
||||
|
||||
/** |
||||
* @method filter_class |
||||
* @param type $class |
||||
* Please NEVER add a period or SLASH as it will allow BAD things! |
||||
* IT should be a-zA-Z0-9_ and that's it. |
||||
* @retval string of safe class name |
||||
*/ |
||||
public static function filter_class(string $class): string { |
||||
if (bootstrap\requires::is_dangerous($class)) { |
||||
throw new \Exception("Dangerious URI!"); |
||||
} |
||||
return preg_replace('/[^a-zA-Z0-9_]/', '', $class); |
||||
} |
||||
|
||||
/** |
||||
* Filter possible unsafe URI, prevent ../up-level hackers |
||||
* @param string $uri |
||||
* @return string Safe URI |
||||
*/ |
||||
public static function filter_uri(string $uri): string { |
||||
if (bootstrap\requires::is_dangerous($uri) === true) { |
||||
throw new \Exception("Dangerious URI!"); |
||||
} |
||||
return bootstrap\requires::filter_file_name($uri); |
||||
} |
||||
|
||||
public static function id_hash(): string { |
||||
return crc32($_SESSION['user_id']); |
||||
} |
||||
|
||||
public static function get_valid_ip(string $ip) { |
||||
return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4|FILTER_FLAG_IPV6)); |
||||
} |
||||
public static function get_valid_public_ip(string $ip) { |
||||
return (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|FILTER_FLAG_NO_PRIV_RANGE)); |
||||
} |
||||
|
||||
/** |
||||
* Is the server on the local test domain name |
||||
* @return bool SERVER Domain name is on whitelist |
||||
*/ |
||||
public static function is_server_name_on_domain_list(array $whitelist): bool { |
||||
if (!isset($_SERVER['SERVER_NAME'])) { |
||||
return false; |
||||
} |
||||
return (in_array($_SERVER['SERVER_NAME'], $whitelist)); |
||||
} |
||||
|
||||
/** |
||||
* Check if same Domain as Server |
||||
* @return bool |
||||
*/ |
||||
public static function request_is_same_domain(): bool { |
||||
if (!isset($_SERVER['HTTP_REFERER'])) { |
||||
// No referer send, so can't be same domain! |
||||
return false; |
||||
} else { |
||||
$referer_host = parse_url($_SERVER['HTTP_REFERER'] . PHP_URL_HOST); |
||||
if ($referer_host === false) { |
||||
return false; // Malformed URL |
||||
} |
||||
$refed_host = $referer_host['host'] ?? ""; |
||||
|
||||
$server_host = $_SERVER['HTTP_HOST']; |
||||
return ($refed_host === $server_host); |
||||
} |
||||
} |
||||
|
||||
|
||||
public static function get_client_ip_address() { |
||||
$ipaddress = ''; |
||||
if (isset($_SERVER['HTTP_CLIENT_IP'])) { |
||||
$ipaddress = $_SERVER['HTTP_CLIENT_IP']; |
||||
} else if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { |
||||
$ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR']; |
||||
} else if (isset($_SERVER['HTTP_X_FORWARDED'])) { |
||||
$ipaddress = $_SERVER['HTTP_X_FORWARDED']; |
||||
} else if (isset($_SERVER['HTTP_FORWARDED_FOR'])) { |
||||
$ipaddress = $_SERVER['HTTP_FORWARDED_FOR']; |
||||
} else if (isset($_SERVER['HTTP_FORWARDED'])) { |
||||
$ipaddress = $_SERVER['HTTP_FORWARDED']; |
||||
} else if (isset($_SERVER['REMOTE_ADDR'])) { |
||||
$ipaddress = $_SERVER['REMOTE_ADDR']; |
||||
} else { |
||||
$ipaddress = 'UNKNOWN'; |
||||
} |
||||
return $ipaddress; |
||||
} |
||||
|
||||
/** |
||||
* Make sure uploads (LIKE Images, etc...) do NOT run PHP code!! |
||||
* Checks for PHP tags inside of file. |
||||
* @param string $file |
||||
* @return bool true if PHP was found |
||||
*/ |
||||
public static function file_contains_php(string $file): bool { |
||||
$file_handle = fopen($file, "r"); |
||||
while (!feof($file_handle)) { |
||||
$line = fgets($file_handle); |
||||
$pos = strpos($line, '<?php'); |
||||
if ($pos !== false) { |
||||
fclose($file_handle); |
||||
return true; |
||||
} |
||||
} |
||||
fclose($file_handle); |
||||
return false; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,73 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace tts\traits\database; |
||||
|
||||
trait run_sql { |
||||
|
||||
public function run($sql, $bind=""): int { |
||||
$pdostmt = $this->pdo->prepare(trim($sql)); |
||||
if (\bs_tts\common::get_count($bind) > 0) { |
||||
$exec = $pdostmt->execute($bind); |
||||
} else { |
||||
$exec = $pdostmt->execute(); |
||||
} |
||||
return $pdostmt->rowCount(); |
||||
} |
||||
|
||||
public function run_select($sql) { |
||||
$pdostmt = $this->pdo->prepare(trim($sql)); |
||||
$exec = $pdostmt->execute(); |
||||
return $pdostmt->fetchAll(\PDO::FETCH_ASSOC); |
||||
} |
||||
|
||||
private function describe_table(string $table) { |
||||
$driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); |
||||
$tick_table = (strrpos($table, "`") === false) ? "`{$table}`" : $table; |
||||
if ($driver == 'sqlite') { |
||||
$sql = "PRAGMA table_info({$tick_table});"; |
||||
$key = "name"; |
||||
} elseif ($driver == 'mysql') { |
||||
$sql = "DESCRIBE {$tick_table};"; |
||||
$key = "Field"; |
||||
} elseif ($driver == 'pgsql') { |
||||
$sql = "SELECT column_name FROM information_schema.columns WHERE table_name = '{$table}';"; |
||||
$key = "column_name"; |
||||
} else { |
||||
return false; |
||||
} |
||||
return ['sql'=>$sql, 'key'=>$key]; |
||||
} |
||||
|
||||
public function get_fields(string $table) { |
||||
$describe = $this->describe_table($table); |
||||
if (false !== ($list = $this->run_select($describe['sql']))) { |
||||
$fields = array(); |
||||
if (count($list) == 0) { |
||||
return array(); |
||||
} |
||||
foreach ($list as $record) { |
||||
$fields[] = $record[$describe['key']]; |
||||
} |
||||
return $fields; |
||||
} |
||||
return array(); |
||||
} |
||||
|
||||
private function filter(string $table, array $info): array { |
||||
$describe = $this->describe_table($table); |
||||
if ($describe === false) { |
||||
return array_keys($info); |
||||
} |
||||
if (false !== ($list = $this->run_select($describe['sql']))) { |
||||
$fields = array(); |
||||
foreach ($list as $record) { |
||||
$fields[] = $record[$describe['key']]; |
||||
} |
||||
return array_values(array_intersect($fields, array_keys($info))); |
||||
} |
||||
return array(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,204 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts <Robert@TryingToScale.com> |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
namespace tts\traits\database; |
||||
|
||||
trait validation { |
||||
|
||||
/** |
||||
* Validate current class members |
||||
* @retval bool true valid, false failed tests |
||||
*/ |
||||
public function validate_mysql(): bool { |
||||
$tbl = (\bs_tts\common::is_string_found($this->table, "`")) ? $this->table : "`{$this->table}`"; |
||||
foreach ($this->members as $field => $value) { |
||||
if ($field == $this->primary_key) { |
||||
continue; |
||||
} |
||||
|
||||
$validation_field = $this->get_vaildation_member($field); |
||||
if (isset($validation_field['native_type']) && isset($validation_field['len'])) { |
||||
$type = strtoupper($validation_field['native_type']); |
||||
$len = intval($validation_field['len']); |
||||
$meta = $validation_field; |
||||
} else { |
||||
$meta = $this->get_MySQL_meta_field($field, $this->table); |
||||
$type = (isset($meta['native_type']) ? $meta['native_type'] : ''); |
||||
$len = $meta['len']; |
||||
} |
||||
|
||||
switch ($type) { //This should be all uppercase input. |
||||
case 'SHORT': //Small INT |
||||
case 'INT24': //MED INT |
||||
case 'LONGLONG': //BIG INT or SERIAL is an alias for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE. |
||||
case 'LONG': // Integers |
||||
if (!preg_match('/^[0-9]*$/', $value)) { |
||||
$this->do_verr("Failed Validation: NOT a digit {$type} {$field}"); |
||||
return false; |
||||
} // Does not allow decimal numbers!! |
||||
if (strlen($value) > $len) { |
||||
$this->do_verr("Failed Validation: too long {$type} {$field}"); |
||||
return false; |
||||
} |
||||
break; |
||||
case 'FLOAT': |
||||
if (strlen($value) > $len) { |
||||
$this->do_verr("Failed Validation: too long {$type} {$field}"); |
||||
return false; |
||||
} |
||||
if (!is_float($value)) { |
||||
$this->do_verr("Failed Validation: NOT a float {$type} {$field}"); |
||||
return false; |
||||
} |
||||
break; |
||||
case 'NEWDECIMAL': |
||||
$prec = intval($meta['precision']); |
||||
$maxlen = $len - $prec; |
||||
if (!\bs_tts\common::is_string_found($value, '.')) { |
||||
$this->do_verr("Failed Validation: No Decimal Found in field {$field}"); |
||||
return false; |
||||
} |
||||
$x = explode('.', $value); |
||||
if (strlen($x[0]) >= ($maxlen - 1) || strlen($x[1]) > $prec) { |
||||
$this->do_verr("Failed Validation: too long {$type} {$field}"); |
||||
return false; |
||||
} |
||||
break; |
||||
case 'DOUBLE': |
||||
if (strlen($value) > $len) { |
||||
$this->do_verr("Failed Validation: too long {$type} {$field}"); |
||||
return false; |
||||
} |
||||
if (!is_double($value)) { |
||||
$this->do_verr("Failed Validation: NOT a double {$type} {$field}"); |
||||
return false; |
||||
} |
||||
break; |
||||
case 'BLOB': // Text |
||||
if ($len == '4294967295' || $len == '16777215') |
||||
continue 2; //Too Big to process, 16777215 MEDIUMTEXT |
||||
if (strlen($value) > $len) { |
||||
$this->do_verr("Failed Validation: too long {$type} {$field}"); |
||||
return false; |
||||
} |
||||
break; |
||||
case 'VAR_STRING': // VARCHAR or VARBINARY |
||||
case 'STRING': //CHAR or BINARY |
||||
if (strlen($value) > $len) { |
||||
$this->do_verr("Failed Validation: too long {$type} {$field}"); |
||||
return false; |
||||
} |
||||
break; |
||||
case 'DATE': |
||||
if (!$this->is_valid_mysql_date($value)) { |
||||
$this->do_verr("Failed Validation: invalid date in {$field}"); |
||||
return false; |
||||
} |
||||
break; |
||||
case 'TIME': |
||||
if (!$this->is_valid_mysql_time($value)) { |
||||
$this->do_verr("Failed Validation: invalid time in {$field}"); |
||||
return false; |
||||
} |
||||
break; |
||||
case 'TIMESTAMP': |
||||
case 'DATETIME': |
||||
if (strlen($value) > $len) { |
||||
$this->do_verr("Failed Validation: too long {$type} {$field}"); |
||||
return false; |
||||
} |
||||
if (!$this->is_valid_mysql_timestamp($value)) { |
||||
$this->do_verr("Failed Validation: invalid timestamp in {$field}"); |
||||
return false; |
||||
} |
||||
break; |
||||
default: //TINYINT, Bit, Bool, or Year is the default for no meta data |
||||
//if (!is_Digits($value)) return false; //This fails so its commented out. |
||||
if ($len == 3) { // Tiny INT |
||||
if (intval($value) > 255) { |
||||
$this->do_verr("Failed Validation: too long {$type} {$field}"); |
||||
return false; |
||||
} |
||||
if (intval($value) < -127) { |
||||
$this->do_verr("Failed Validation: too short {$type} {$field}"); |
||||
return false; |
||||
} |
||||
} elseif ($len == 1) { // Bit or Bool |
||||
if (intval($value) > 9) { |
||||
$this->do_verr("Failed Validation: too long {$type} {$field}"); |
||||
return false; |
||||
} |
||||
if (intval($value) < 0) { |
||||
$this->do_verr("Failed Validation: too short {$type} {$field}"); |
||||
return false; |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
public function get_MySQL_meta_field(string $field, string $table): array { |
||||
$query = "SELECT `{$field}` FROM {$table} LIMIT 1"; |
||||
$pdo_stmt = $this->pdo->prepare($query); |
||||
$pdo_stmt->execute(); |
||||
return $pdo_stmt->getColumnMeta(0); |
||||
} |
||||
|
||||
/** |
||||
* Check if valid timestamp/datetime |
||||
* @param type $Str |
||||
* @return type |
||||
*/ |
||||
public function is_valid_mysql_timestamp(string $Str): bool { |
||||
$Stamp = strtotime($Str); |
||||
$Month = date('m', $Stamp); |
||||
$Day = date('d', $Stamp); |
||||
$Year = date('Y', $Stamp); |
||||
|
||||
return checkdate($Month, $Day, $Year); |
||||
} |
||||
|
||||
public function is_valid_mysql_date(string $str): bool { |
||||
$date_parts = explode('-', $str); |
||||
if (\main_tts\common::count($date_parts) != 3) |
||||
return false; |
||||
if ((strlen($date_parts[0]) != 4) || (!is_numeric($date_parts[0]))) |
||||
return false; |
||||
if ((strlen($date_parts[1]) != 2) || (!is_numeric($date_parts[1]))) |
||||
return false; |
||||
if ((strlen($date_parts[2]) != 2) || (!is_numeric($date_parts[2]))) |
||||
return false; |
||||
if (!checkdate($date_parts[1], $date_parts[2], $date_parts[0])) |
||||
return false; |
||||
return true; |
||||
} |
||||
|
||||
public function is_valid_mysql_time(string $str): bool { |
||||
return (strtotime($str) === false) ? false : true; |
||||
} |
||||
|
||||
/** |
||||
* Helper function for validate mysql |
||||
* Will echo if not live error message |
||||
* If enabled will also log the error. |
||||
* @param string $msg error message |
||||
*/ |
||||
private function do_verr(string $msg): void { |
||||
$this->error_message = $msg; |
||||
$exists = \main_tts\registry::get('di')->exists('log'); |
||||
if ($exists && \main_tts\configure::get('database', 'log_validation_errors') === true) { |
||||
$log = \main_tts\registry::get('di')->get_service('log', ['validation_errors']); |
||||
$log->write($msg); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,87 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
namespace CodeHydrater\traits\security; |
||||
|
||||
trait csrf_token_functions { |
||||
|
||||
/** |
||||
* Get an Cross-Site Request Forge - Prevention Token |
||||
* @return string |
||||
*/ |
||||
public static function csrf_token(): string { |
||||
return self::get_unique_id(); |
||||
} |
||||
|
||||
/** |
||||
* Set Session to use CSRF Token |
||||
* @return string CSRF Token |
||||
*/ |
||||
public static function create_csrf_token(): string { |
||||
$token = self::csrf_token(); |
||||
$_SESSION['csrf_token'] = $token; |
||||
$_SESSION['csrf_token_time'] = time(); |
||||
return $token; |
||||
} |
||||
|
||||
/** |
||||
* Destroy CSRF Token from Session |
||||
* @return bool success |
||||
*/ |
||||
public static function destroy_csrf_token(): bool { |
||||
$_SESSION['csrf_token'] = null; |
||||
$_SESSION['csrf_token_time'] = null; |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Get CSRF Token for use with HTML Form |
||||
* @return string Hidden Form with token set |
||||
*/ |
||||
public static function csrf_token_tag(): string { |
||||
$token = self::create_csrf_token(); |
||||
return "<input type=\"hidden\" name=\"csrf_token\" value=\"" . $token . "\">"; |
||||
} |
||||
|
||||
/** |
||||
* Check if POST data CSRF Token is Valid |
||||
* @return bool is valid |
||||
*/ |
||||
public static function csrf_token_is_valid(): bool { |
||||
$is_csrf = filter_has_var(INPUT_POST, 'csrf_token'); |
||||
if ($is_csrf) { |
||||
$user_token = \tts\misc::post_var('csrf_token'); |
||||
$stored_token = $_SESSION['csrf_token'] ?? ''; |
||||
if (empty($stored_token)) { |
||||
return false; |
||||
} |
||||
return \tts\misc::compair_it($user_token, $stored_token); |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Optional check to see if token is also recent |
||||
* @return bool |
||||
*/ |
||||
public static function csrf_token_is_recent(): bool { |
||||
$max_elapsed = intval(\main_tts\configure::get( |
||||
'security', |
||||
'max_token_age' |
||||
)); |
||||
if ($max_elapsed < 30) { |
||||
$max_elapsed = 60 * 60 * 24; // 1 day |
||||
} |
||||
|
||||
if (isset($_SESSION['csrf_token_time'])) { |
||||
$stored_time = $_SESSION['csrf_token_time']; |
||||
return ($stored_time + $max_elapsed) >= time(); |
||||
} else { |
||||
// Remove expired token |
||||
self::destroy_csrf_token(); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,149 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
namespace CodeHydrater\traits\security; |
||||
|
||||
/** |
||||
* author site did not work to contact him July 30th, 2023 |
||||
* @link https://gist.github.com/ohidxy/cc361bb40fb3fbb3fe9112c99d51f69e |
||||
* Codes to prevent SESSION Hijacking and Fixation in PHP |
||||
*/ |
||||
|
||||
trait session_hijacking_functions { |
||||
|
||||
public static function init() { |
||||
$my_sess = \CodeHydrater\main\configure::get("site", "session_name"); |
||||
session_name($my_sess); |
||||
session_start(); |
||||
} |
||||
|
||||
// Function to forcibly end the session |
||||
public static function end_session() { |
||||
// Use both for compatibility with all browsers |
||||
// and all versions of PHP. |
||||
session_unset(); |
||||
session_destroy(); |
||||
} |
||||
|
||||
// Does the request IP match the stored value? |
||||
public static function request_ip_matches_session() { |
||||
// return false if either value is not set |
||||
if (!isset($_SESSION['ip']) || !isset($_SERVER['REMOTE_ADDR'])) { |
||||
return false; |
||||
} |
||||
if ($_SESSION['ip'] === $_SERVER['REMOTE_ADDR']) { |
||||
return true; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// Does the request user agent match the stored value? |
||||
public static function request_user_agent_matches_session() { |
||||
// return false if either value is not set |
||||
if (!isset($_SESSION['user_agent']) || !isset($_SERVER['HTTP_USER_AGENT'])) { |
||||
return false; |
||||
} |
||||
if ($_SESSION['user_agent'] === $_SERVER['HTTP_USER_AGENT']) { |
||||
return true; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// Has too much time passed since the last login? |
||||
public static function last_login_is_recent() { |
||||
$max_elapsed = intval(\CodeHydrater\main\configure::get( |
||||
'security', |
||||
'max_last_login_age' |
||||
)); |
||||
if ($max_elapsed < 30) { |
||||
$max_elapsed = 60 * 60 * 24; // 1 day |
||||
} |
||||
|
||||
// return false if value is not set |
||||
if (!isset($_SESSION['last_login'])) { |
||||
return false; |
||||
} |
||||
if (($_SESSION['last_login'] + $max_elapsed) >= time()) { |
||||
return true; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// Should the session be considered valid? |
||||
public static function is_session_valid() { |
||||
$check_ip = true; |
||||
$check_user_agent = true; |
||||
$check_last_login = true; |
||||
|
||||
if ($check_ip && !self::request_ip_matches_session()) { |
||||
return false; |
||||
} |
||||
if ($check_user_agent && !self::request_user_agent_matches_session()) { |
||||
return false; |
||||
} |
||||
if ($check_last_login && !self::last_login_is_recent()) { |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
// If session is not valid, end and redirect to login page. |
||||
public static function confirm_session_is_valid() { |
||||
if (!self::is_session_valid()) { |
||||
self::end_session(); |
||||
// Note that header redirection requires output buffering |
||||
// to be turned on or requires nothing has been output |
||||
// (not even whitespace). |
||||
header("Location: login.php"); |
||||
exit; |
||||
} |
||||
} |
||||
|
||||
// Is user logged in already? |
||||
public static function is_logged_in() { |
||||
return (isset($_SESSION['logged_in']) && $_SESSION['logged_in']); |
||||
} |
||||
|
||||
// If user is not logged in, end and redirect to login page. |
||||
public static function confirm_user_logged_in() { |
||||
if (!self::is_logged_in()) { |
||||
self::end_session(); |
||||
// Note that header redirection requires output buffering |
||||
// to be turned on or requires nothing has been output |
||||
// (not even whitespace). |
||||
header("Location: login.php"); |
||||
exit; |
||||
} |
||||
} |
||||
|
||||
// Actions to preform after every successful login |
||||
public static function after_successful_login() { |
||||
// Regenerate session ID to invalidate the old one. |
||||
// Super important to prevent session hijacking/fixation. |
||||
session_regenerate_id(); |
||||
|
||||
$_SESSION['logged_in'] = true; |
||||
|
||||
// Save these values in the session, even when checks aren't enabled |
||||
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR']; |
||||
$_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT']; |
||||
$_SESSION['last_login'] = time(); |
||||
} |
||||
|
||||
// Actions to preform after every successful logout |
||||
public static function after_successful_logout() { |
||||
$_SESSION['logged_in'] = false; |
||||
self::end_session(); |
||||
} |
||||
|
||||
// Actions to preform before giving access to any |
||||
// access-restricted page. |
||||
public static function before_every_protected_page() { |
||||
self::confirm_user_logged_in(); |
||||
self::confirm_session_is_valid(); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,234 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* Modified from phptutorial.net |
||||
* @link https://www.phptutorial.net/php-tutorial/php-validation/ |
||||
*/ |
||||
|
||||
namespace CodeHydrater; |
||||
|
||||
class validator { |
||||
|
||||
const DEFAULT_VALIDATION_ERRORS = [ |
||||
'required' => 'Please enter the %s', |
||||
'email' => 'The %s is not a valid email address', |
||||
'less_than' => 'The %s number must be less than %d', |
||||
'greater_than' => 'The %s number must be greater than %d', |
||||
'number_range' => 'The %s number must be in range of %d to %d', |
||||
'min' => 'The %s must have at least %s characters', |
||||
'max' => 'The %s must have at most %s characters', |
||||
'between' => 'The %s must have between %d and %d characters', |
||||
'same' => 'The %s must match with %s', |
||||
'alphanumeric' => 'The %s should have only letters and numbers', |
||||
'secure' => 'The %s must have between 8 and 64 characters and contain at least one number, one upper case letter, one lower case letter and one special character', |
||||
'valid_email_domain' => 'The %s email address is not active', |
||||
'valid_domain' => 'The %s domain name is not active', |
||||
]; |
||||
|
||||
private static function make_arrays(array $data, $field): array { |
||||
$dataset = []; |
||||
if (isset($data[$field])) { |
||||
if (bootstrap\common::get_count($data[$field])) { |
||||
foreach($data[$field] as $the_data) { |
||||
$dataset[] = $the_data; |
||||
} |
||||
} else { |
||||
$dataset[] = $data[$field]; |
||||
} |
||||
} else { |
||||
$dataset[] = null; // If field is null, force null set |
||||
} |
||||
return $dataset; |
||||
} |
||||
|
||||
public static function validate( |
||||
array $data, |
||||
array $fields, |
||||
array $messages = [] |
||||
): array { |
||||
// Split the array by a separator, trim each element |
||||
// and return the array |
||||
$split = fn($str, $separator) => array_map('trim', explode($separator, $str)); |
||||
|
||||
// get the message rules |
||||
$rule_messages = array_filter($messages, fn($message) => is_string($message)); |
||||
// overwrite the default message |
||||
$validation_errors = array_merge(self::DEFAULT_VALIDATION_ERRORS, $rule_messages); |
||||
|
||||
$errors = []; |
||||
|
||||
foreach ($fields as $field => $option) { |
||||
foreach(self::make_arrays($data, $field) as $index=>$v) { |
||||
$data[$field] = $v; // Force update on arrays |
||||
|
||||
$rules = $split($option, '|'); |
||||
|
||||
foreach ($rules as $rule) { |
||||
// get rule name params |
||||
$params = []; |
||||
// if the rule has parameters e.g., min: 1 |
||||
if (strpos($rule, ':')) { |
||||
[$rule_name, $param_str] = $split($rule, ':'); |
||||
$params = $split($param_str, ','); |
||||
} else { |
||||
$rule_name = trim($rule); |
||||
} |
||||
// by convention, the callback should be is_<rule> e.g.,is_required |
||||
$fn = 'is_' . $rule_name; |
||||
|
||||
$callable = self::class . "::{$fn}"; |
||||
if (is_callable($callable)) { |
||||
$pass = $callable($data, $field, ...$params); |
||||
if (!$pass) { |
||||
// get the error message for a specific field and rule if exists |
||||
// otherwise get the error message from the $validation_errors |
||||
$errors[$field] = sprintf( |
||||
$messages[$field][$rule_name] ?? $validation_errors[$rule_name], |
||||
$field, |
||||
...$params |
||||
); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return $errors; |
||||
} |
||||
|
||||
private static function is_required(array $data, string $field): bool { |
||||
if (isset($data[$field])) { |
||||
if (bootstrap\common::get_count($data[$field])) { |
||||
return false; // Should not be an array here |
||||
} |
||||
if (is_string($data[$field])) { |
||||
return (trim($data[$field]) !== ''); |
||||
} |
||||
if (is_int($data[$field])) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private static function is_email(array $data, string $field): bool { |
||||
if (empty($data[$field])) { |
||||
return true; |
||||
} |
||||
return filter_var($data[$field], FILTER_VALIDATE_EMAIL); |
||||
} |
||||
|
||||
private static function is_min(array $data, string $field, string $min): bool { |
||||
if (!isset($data[$field])) { |
||||
return true; |
||||
} |
||||
|
||||
return mb_strlen($data[$field]) >= intval($min); |
||||
} |
||||
|
||||
private static function is_max(array $data, string $field, string $max): bool { |
||||
if (!isset($data[$field])) { |
||||
return true; |
||||
} |
||||
|
||||
return mb_strlen($data[$field]) <= intval($max); |
||||
} |
||||
|
||||
private static function is_greater_than(array $data, string $field, string $min): bool { |
||||
if (!isset($data[$field])) { |
||||
return true; |
||||
} |
||||
|
||||
return intval($data[$field]) > intval($min); |
||||
} |
||||
|
||||
private static function is_less_than(array $data, string $field, string $max): bool { |
||||
if (!isset($data[$field])) { |
||||
return true; |
||||
} |
||||
|
||||
return intval($data[$field]) < intval($max); |
||||
} |
||||
|
||||
private static function is_number_range(array $data, string $field, string $min, string $max): bool { |
||||
if (!isset($data[$field])) { |
||||
return true; |
||||
} |
||||
|
||||
$no = intval($data[$field]); |
||||
return $no >= intval($min) && $no <= intval($max); |
||||
} |
||||
|
||||
private static function is_between(array $data, string $field, string $min, string $max): bool { |
||||
if (!isset($data[$field])) { |
||||
return true; |
||||
} |
||||
|
||||
$len = mb_strlen($data[$field]); |
||||
return $len >= intval($min) && $len <= intval($max); |
||||
} |
||||
|
||||
private static function is_same(array $data, string $field, string $other): bool { |
||||
if (isset($data[$field]) && isset($data[$other])) { |
||||
return $data[$field] === $data[$other]; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
private static function is_alphanumeric(array $data, string $field): bool { |
||||
if (!isset($data[$field])) { |
||||
return true; |
||||
} |
||||
|
||||
return ctype_alnum($data[$field]); |
||||
} |
||||
|
||||
private static function is_secure(array $data, string $field): bool { |
||||
if (!isset($data[$field])) { |
||||
return false; |
||||
} |
||||
// Is 8 to 64 CHRs |
||||
$pattern = "#.*^(?=.{8,64})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*\W).*$#"; |
||||
return preg_match($pattern, $data[$field]); |
||||
} |
||||
|
||||
private static function is_valid_email_domain(array $data, string $field): bool { |
||||
if (!isset($data[$field])) { |
||||
return false; |
||||
} |
||||
|
||||
$domain = ltrim(stristr($data[$field], '@'), '@') . '.'; |
||||
return checkdnsrr($domain, 'MX'); |
||||
} |
||||
|
||||
private static function is_valid_domain(array $data, string $field): bool { |
||||
if (!isset($data[$field])) { |
||||
return false; |
||||
} |
||||
|
||||
return checkdnsrr($data[$field], 'A') |
||||
|| checkdnsrr($data[$field], 'AAAA') |
||||
|| checkdnsrr($data[$field], 'CNAME'); |
||||
} |
||||
|
||||
} |
||||
|
||||
/* |
||||
$data = ['email'=>'jim@aol.com']; |
||||
$fields = ['email' => 'required | email']; |
||||
|
||||
$errors = \CodeHydrater\validator::validate($data, $fields); |
||||
print_r($errors); |
||||
*/ |
||||
|
||||
/* |
||||
* 'firstname' => 'required | max:255', |
||||
'lastname' => 'required | max: 255', |
||||
'address' => 'required | min: 10 | max:255', |
||||
'zipcode' => 'between: 5,6', |
||||
'username' => 'required | alphanumeric | between: 3,255', |
||||
'email' => 'required | email | valid_email_domain', |
||||
*/ |
||||
@ -0,0 +1,52 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
$protocol = "HTTP/1.0"; |
||||
if ( "HTTP/1.1" == $_SERVER["SERVER_PROTOCOL"] ) { |
||||
$protocol = "HTTP/1.1"; |
||||
} |
||||
|
||||
header( "{$protocol} 404 Not Found", true, 404 ); |
||||
header('Content-type: text/html; charset=utf-8'); |
||||
?> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<meta name="language" content="english"> |
||||
<meta name="robots" content="no-follow"> |
||||
<link rel="shortcut icon" href="/assets/favicon/favicon.ico"> |
||||
<title>404 Page not found!</title> |
||||
<style> |
||||
@media only screen and (max-width: 600px) { |
||||
#nopage { |
||||
height: 150px; |
||||
width: 300px; |
||||
} |
||||
} |
||||
@media only screen and (min-width: 600px) { |
||||
#nopage { |
||||
height: 500px; |
||||
width: 1500px; |
||||
} |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
<div id="wrap"> |
||||
<img src="/assets/images/404page.jpg" alt="Page not found." id="nopage"/> |
||||
<header><h1>404 Page not found!<h1></header> |
||||
<h3>Our apologies for the temporary inconvenience.</h3> |
||||
</div> |
||||
</body> |
||||
</html> |
||||
<?php |
||||
exit; |
||||
@ -0,0 +1,23 @@ |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<meta name="keywords" content=""> |
||||
<meta name="description" content=""> |
||||
<meta name="author" content="Robert Strutts"> |
||||
<meta name="language" content="english"> |
||||
<meta name="robots" content=""> |
||||
<meta name="copyright" content="2014-<?= date('Y'); ?>">
|
||||
<title>DEV ERROR!</title> |
||||
<link rel="shortcut icon" href="/assets/favicon/favicon.ico"> |
||||
</head> |
||||
<body id="my-page"> |
||||
|
||||
<div id="wrap"> |
||||
<div id="autosavemessage"></div> |
||||
<?= $local->page_output; ?> |
||||
</div> <!-- end wrap --> |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,43 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* @author Robert Strutts <Robert@TryingToScale.com> |
||||
* @copyright Copyright (c) 2022, Robert Strutts. |
||||
* @license MIT |
||||
*/ |
||||
|
||||
define('PRODUCTION', 600); |
||||
define('MAINTENACE', 3600); // 1 hour = 3600 seconds |
||||
define('RETRY_AFTER', PRODUCTION); |
||||
|
||||
if(! headers_sent()) { |
||||
header('HTTP/1.1 503 Service Temporarily Unavailable'); |
||||
header('Status: 503 Service Temporarily Unavailable'); |
||||
header('Retry-After: ' . RETRY_AFTER); |
||||
} |
||||
?> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<meta name="author" content="Robert Strutts"> |
||||
<meta name="language" content="english"> |
||||
<meta name="robots" content="NOINDEX, NOFOLLOW"> |
||||
<meta name="copyright" content="2014-<?php echo date('Y'); ?>">
|
||||
<link rel="shortcut icon" href="/assets/favicon/favicon.ico"> |
||||
<title>Sorry, we had an error...</title> |
||||
|
||||
<style> |
||||
body { padding: 20px; background: #C00; color: white; font-size: 40px; } |
||||
</style> |
||||
</head> |
||||
<body> |
||||
|
||||
<h1>Sorry, we had an error...</h1> |
||||
<p>We apologize for any inconvenience this may cause.<p> |
||||
|
||||
</body> |
||||
</html> |
||||
<?php exit; |
||||
Loading…
Reference in new issue