(.*?)<\/tr>/s',
+ 'replace' => '[tr]$1[/tr]',
+ 'content' => '$1',
+ ],
+ 'table-data' => [
+ 'pattern' => '/(.*?)<\/td>/s',
+ 'replace' => '[td]$1[/td]',
+ 'content' => '$1',
+ ],
+ ];
+
+ private function searchAndReplace(string $pattern, string $replace, string $source): string {
+ while (preg_match($pattern, $source)) {
+ $source = preg_replace($pattern, $replace, $source);
+ }
+
+ return $source;
+ }
+
+ public function parse(string $source): string {
+ foreach ($this->parsers as $name => $parser) {
+ $source = $this->searchAndReplace($parser['pattern'], $parser['replace'], $source);
+ }
+
+ return $source;
+ }
+}
diff --git a/src/classes/makeLicenseFiles.php b/src/classes/make_license_files.php
similarity index 100%
rename from src/classes/makeLicenseFiles.php
rename to src/classes/make_license_files.php
diff --git a/src/classes/mb_strings_fns.php b/src/classes/mb_strings_fns.php
new file mode 100644
index 0000000..5c8dc2c
--- /dev/null
+++ b/src/classes/mb_strings_fns.php
@@ -0,0 +1,44 @@
+
+ * @copyright (c) 2024, Robert Strutts
+ * @license MIT
+ */
+
+namespace CodeHydrater;
+
+class mb_strings_fns {
+
+ public static function isUTF8(string $string) {
+ return mb_check_encoding($string, 'UTF-8');
+ }
+
+ // Check if string contains multibyte characters
+ public static function has_multibyte_chars(string $string) {
+ return strlen($string) !== mb_strlen($string, 'UTF-8');
+ }
+
+ // Override to get the length of a string with multibyte support
+ public function strlen($string) {
+ return mb_strlen($string);
+ }
+
+ // Override to convert a string to lowercase with multibyte support
+ public function strtolower($string) {
+ return mb_strtolower($string);
+ }
+
+ // Override to convert a string to uppercase with multibyte support
+ public function strtoupper($string) {
+ return mb_strtoupper($string);
+ }
+
+ // Override to get a substring from a string with multibyte support
+ public function substr($string, $start, $length = null) {
+ return ($length !== null) ? mb_substr($string, $start, $length) : mb_substr($string, $start);
+ }
+}
+
diff --git a/src/classes/misc.php b/src/classes/misc.php
index 44633a3..72252f8 100644
--- a/src/classes/misc.php
+++ b/src/classes/misc.php
@@ -3,7 +3,7 @@
declare(strict_types=1);
/**
- * @author Robert Strutts
+ * @author Robert Strutts
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
@@ -36,7 +36,7 @@ final class misc {
* @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');
+ $http_x_requested_with = bootstrap\safer_io::get_clean_server_var('HTTP_X_REQUESTED_WITH');
return (self::compair_it($http_x_requested_with, 'xmlhttprequest'));
}
@@ -320,7 +320,7 @@ final class misc {
* @retval bool
*/
public static function is_api(): bool {
- $uri = bootstrap\siteHelper::request_uri();
+ $uri = bootstrap\site_helper::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 {
diff --git a/src/classes/page_not_found.php b/src/classes/page_not_found.php
index 8ba5e41..e0e921a 100644
--- a/src/classes/page_not_found.php
+++ b/src/classes/page_not_found.php
@@ -38,7 +38,7 @@ class page_not_found {
* Displays 404 Page not Found
*/
public static function error404(): void {
- if (consoleApp::is_cli()) {
+ if (console_app::is_cli()) {
self::error404_cli();
} else {
$use_api = misc::is_api();
diff --git a/src/classes/random_engine.php b/src/classes/random_engine.php
new file mode 100644
index 0000000..06fbfb7
--- /dev/null
+++ b/src/classes/random_engine.php
@@ -0,0 +1,106 @@
+ 8.1) {
+ $this->engine = new \Random\Randomizer();
+ }
+ }
+
+ public function get_bytes(int $bytes_length = 16): string {
+ return ($this->engine) ? $this->engine->getBytes($bytes_length) :
+ random_bytes($bytes_length);
+ }
+
+ public function get_int(int $min, int $max): int {
+ if ($this->engine) {
+ return $this->engine->getInt($min, $max);
+ }
+ if (function_exists('random_int')) {
+ return random_int($min, $max); // secure fallback
+ } elseif (function_exists('mt_rand')) {
+ return mt_rand($min, $max); // fast
+ }
+ return rand($min, $max); // old
+ }
+
+ // Took from source https://pageconfig.com/post/fixed-length-large-random-numbers-with-php
+ private function big_rand(int $len = 18 ): int {
+ $rand = '';
+ while( !( isset( $rand[$len-1] ) ) ) {
+ $rand .= mt_rand( );
+ }
+ return (int) substr( $rand , 0 , $len );
+ }
+
+ public function get_next_big_postive_int(): int {
+ if ($this->engine) {
+ return $this->engine->nextInt();
+ }
+ return $this->big_rand();
+ }
+
+ private function select_from_array(array $a, int $num ): array {
+ $array_count = \bs_tts\common::get_count($a) - 1;
+ if ($array_count < 1) {
+ return [];
+ }
+ $ret = [];
+ for($i=0; $i<$num; $i++) {
+ $ret[] = $a[$this->get_int(0, $array_count)];
+ }
+ return $ret;
+ }
+
+ /**
+ * Pick random keys from an Array
+ */
+ public function get_array_keys(array $a, int $num): array {
+ if ($this->engine) {
+ return $this->engine->pickArrayKeys($a, $num);
+ }
+ return $this->select_from_array($a, $num);
+ }
+
+ public function get_shuffled_array(array $a): array {
+ if ($this->engine) {
+ return $this->engine->shuffleArray($a);
+ }
+ shuffle($a);
+ return $a;
+ }
+
+ public function get_shuffled_bytes(string $bytes): string {
+ if ($this->engine) {
+ return $this->engine->shuffleBytes($bytes);
+ }
+ $len = mb_strlen($bytes);
+ $a = [];
+ while($len-- > 0) {
+ $a[] = mb_substr($bytes, $len, 1);
+ }
+ shuffle($a);
+ return join('', $a);
+ }
+
+}
diff --git a/src/classes/router.php b/src/classes/router.php
index 606f42e..974bdec 100644
--- a/src/classes/router.php
+++ b/src/classes/router.php
@@ -321,7 +321,7 @@ class router
* with a method called get!
*/
private static function get_all_routes(bool $testing = false) {
- $route_name = (consoleApp::is_cli()) ? "cli_routes" : "routes";
+ $route_name = (console_app::is_cli()) ? "cli_routes" : "routes";
$routes = ($testing) ? "test_routes" : $route_name;
$routes_class = "\\Project\\routes\\{$routes}";
@@ -338,10 +338,10 @@ class 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();
+ $ROOT = bootstrap\site_helper::get_root();
+ $request_uri = bootstrap\site_helper::get_uri();
+ $request_method = bootstrap\site_helper::get_method();
+ $testing = bootstrap\site_helper::get_testing();
// Generate request and absolute path
self::generateURL($ROOT, $request_uri);
@@ -460,13 +460,13 @@ class router
*/
private static function generateURL(string $ROOT, string $request_uri)
{
- $https = bootstrap\saferIO::get_clean_server_var('HTTPS');
+ $https = bootstrap\safer_io::get_clean_server_var('HTTPS');
$baseLink = ($https === 'on') ? "https" : "http";
- $server_name = bootstrap\saferIO::get_clean_server_var('SERVER_NAME');
+ $server_name = bootstrap\safer_io::get_clean_server_var('SERVER_NAME');
$baseLink .= "://" . $server_name;
- $port = bootstrap\saferIO::get_clean_server_var('SERVER_PORT');
+ $port = bootstrap\safer_io::get_clean_server_var('SERVER_PORT');
$baseLink .= ($port !== '80') ? ':' . $port : ':';
$baseRequest = '';
diff --git a/src/classes/security.php b/src/classes/security.php
index f29849a..18c6a79 100644
--- a/src/classes/security.php
+++ b/src/classes/security.php
@@ -220,6 +220,20 @@ final class security {
}
}
+ public static function safe_for_eval(string $s): string {
+ //new line check
+ $nl = chr(10);
+ if (strpos($s, $nl)) {
+ throw new \Exception("String CR/LF not permitted");
+ }
+ $meta = ['$','{','}','[',']','`',';'];
+ $escaped = ['$','{','}','[','`',';'];
+ // add slashed for quotes and blackslashes
+ $out = addslashes($s);
+ // replace php meta chrs
+ $out = str_repeat($meta, $escaped, $out);
+ return $out;
+ }
public static function get_client_ip_address() {
$ipaddress = '';
diff --git a/src/classes/services/log.php b/src/classes/services/log.php
new file mode 100644
index 0000000..78daf52
--- /dev/null
+++ b/src/classes/services/log.php
@@ -0,0 +1,115 @@
+handle = false; // Too dangerious, so return false
+ } else {
+ $log_dir = BaseDir . '/protected/logs';
+ if (! is_dir($log_dir)){
+ //Directory does not exist, so lets create it.
+ mkdir($log_dir, 0775);
+ }
+
+ $filename = preg_replace("|[^A-Za-z0-9_]|", "", $filename);
+ $filename = escapeshellcmd($filename);
+ $file = $log_dir . '/' . $filename . ".log.txt";
+
+ if ($max_count > 1) {
+ if ($this->get_lines($file) > $max_count) {
+ unlink($file);
+ }
+ }
+
+ $success = file_put_contents($file, "\n", FILE_APPEND);
+// $success = @touch($file);
+ if ($success === false) {
+ $this->handle = false;
+ \CodeHydrater\bootstrap\common::dump($file);
+ return false;
+ }
+
+ @chmod($file, 0660);
+ @chgrp($file, "www-data");
+ if (! is_writable($file)) {
+ $this->handle = false;
+ return false;
+ }
+ $this->handle = fopen($file, 'a');
+ }
+ }
+
+ /**
+ * Count number of lines in Log File
+ * @param string $file
+ * @return int line count
+ */
+ public function get_lines(string $file): int {
+ // No such file, so return zero for length.
+ if (! file_exists($file)) {
+ return 0;
+ }
+
+ $f = fopen($file, 'rb');
+ $lines = 0;
+
+ if ($f === false || !is_resource($f)) {
+ return 0;
+ }
+
+ while (!feof($f)) {
+ $line = fread($f, 8192);
+ if ($line === false) {
+ return 0;
+ }
+ $lines += substr_count($line, "\n");
+ }
+
+ fclose($f);
+
+ return $lines;
+ }
+
+ /**
+ * Write to Log File
+ * @param string $message to save
+ * @return bool able to write to log file
+ */
+ public function write(string $message): bool {
+ if ( $this->handle === false || ! is_resource($this->handle) ) {
+ return false;
+ }
+ $dt = new \DateTime();
+ $now = $dt->format('g:i A \o\n l jS F Y');
+ fwrite($this->handle, $now . ' - ' . print_r($message, true) . "\n");
+ return true;
+ }
+
+ /**
+ * Close Log File Handle
+ */
+ public function __destruct() {
+ if ($this->handle !== false && is_resource($this->handle)) {
+ fclose($this->handle);
+ }
+ }
+
+}
diff --git a/src/classes/services/paragon_crypto/crypto.php b/src/classes/services/paragon_crypto/crypto.php
new file mode 100644
index 0000000..9a2843d
--- /dev/null
+++ b/src/classes/services/paragon_crypto/crypto.php
@@ -0,0 +1,238 @@
+rnd = new \tts\random_engine();
+ }
+/*
+ * Secret key encryption (or symmetric encryption as it’s also
+ * known) uses a single key to both encrypt and decrypt data.
+ * In the past PHP relied on mcrypt and openssl for secret key
+ * encryption. PHP 7.2 introduced Sodium, which is more modern
+ * and widely considered more secure.
+ *
+ * In order to encrypt a value, first you’ll need an encryption
+ * key, which can be generated using the
+ * sodium_crypto_secretbox_keygen() function.
+ *
+ * $key = sodium_crypto_secretbox_keygen();
+ * You can also use the random_bytes() function with the
+ * SODIUM_CRYPTO_SECRETBOX_KEYBYTES integer constant for the
+ * key length, but using sodium_crypto_secretbox_keygen()
+ * ensures that the key length is always correct (i.e. not too
+ * short), and it’s easier.
+ *
+ * $key = random_bytes( SODIUM_CRYPTO_SECRETBOX_KEYBYTES );
+ * Either way, you’ll usually only do this once and store the
+ * result as an environment variable. Remember that this key
+ * must be kept secret at all costs. If the key is ever
+ * compromised, so is any data encrypted by using it.
+ */
+
+ public function encrypt(
+ #[\SensitiveParameter] string $message,
+ bool $key_usage = self::single_key
+ ): string {
+ $key = $this->key;
+
+ $nonce = $this->rnd->get_bytes(
+ SODIUM_CRYPTO_SECRETBOX_NONCEBYTES
+ );
+ $fn = ($key_usage == self::single_key) ? "sodium_crypto_secretbox" : "sodium_crypto_box";
+ $cipher = base64_encode(
+ $nonce .
+ $fn(
+ $message,
+ $nonce,
+ base64_decode($key)
+ )
+ );
+ sodium_memzero($message);
+ sodium_memzero($key);
+ return $cipher;
+ }
+
+ public function decrypt(
+ string $encrypted,
+ bool $key_usage = self::single_key
+ ): string | false {
+ $key = $this->key;
+
+ $decoded = base64_decode($encrypted);
+ if ($decoded === false) {
+ throw new Exception('Unable to decode');
+ }
+ if (mb_strlen($decoded, '8bit') < (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)) {
+ throw new Exception('Message was truncated');
+ }
+ $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
+ $ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
+
+ $fn = ($key_usage == self::single_key) ? "sodium_crypto_secretbox_open" : "sodium_crypto_box_open";
+ $plain = $fn(
+ $ciphertext,
+ $nonce,
+ base64_decode($key)
+ );
+ sodium_memzero($ciphertext);
+ sodium_memzero($key);
+ return $plain;
+ }
+
+ public static function make_user_box_kp() {
+ return sodium_crypto_box_keypair();
+ }
+
+ public static function make_user_sign_kp() {
+ return sodium_crypto_sign_keypair();
+ }
+
+ public static function get_user_box_secret_key(string $from_make_user_box_kp): string {
+ return base64_encode(sodium_crypto_box_secretkey($from_make_user_box_kp));
+ }
+
+ public static function get_user_box_public_key(string $from_make_user_box_kp): string {
+ return base64_encode(sodium_crypto_box_publickey($from_make_user_box_kp));
+ }
+
+ public static function get_user_sign_secret_key(string $from_make_user_sign_secret_kp): string {
+ return sodium_crypto_sign_secretkey($from_make_user_sign_secret_kp);
+ }
+
+ public static function get_user_sign_public_key(string $from_make_user_sign_public_kp): string {
+ return sodium_crypto_sign_secretkey($from_make_user_sign_public_kp);
+ }
+
+ public static function box_reassemble_keypair(
+ string $from_get_userA_box_secret_key,
+ string $from_get_userB_box_public_key
+ ): string {
+ return base64_encode(sodium_crypto_box_keypair_from_secretkey_and_publickey(
+ base64_decode($from_get_userA_box_secret_key),
+ base64_decode($from_get_userB_box_public_key)
+ ));
+ }
+
+ public static function sign_message(#[\SensitiveParameter] string $message, #[\SensitiveParameter] string $user_sign_secretkey): string {
+ return sodium_crypto_sign(
+ $message,
+ $user_sign_secretkey
+ );
+ }
+
+ public static function get_signed_message(#[\SensitiveParameter] string $signed_message, #[\SensitiveParameter] string $user_sign_publickey): string | false {
+ return sodium_crypto_sign_open(
+ $signed_message,
+ $user_sign_publickey
+ );
+ }
+
+ public static function make_just_signature(#[\SensitiveParameter] string $message, #[\SensitiveParameter] string $user_sign_secretkey): string {
+ return sodium_crypto_sign_detached(
+ $message,
+ $user_sign_secretkey
+ );
+ }
+
+ public static function is_a_valid_signature(
+ #[\SensitiveParameter] string $signature,
+ #[\SensitiveParameter] string $message,
+ #[\SensitiveParameter] string $user_sign_public_key
+ ): bool {
+ if ( sodium_crypto_sign_verify_detached(
+ $signature,
+ $message,
+ $user_sign_publickey
+ ) ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public static function a_single_key_maker(): string {
+ $rnd = new \tts\random_engine();
+ return base64_encode($rnd->get_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES));
+ }
+ /*
+ * Extract the public key from the secret key
+ */
+ public static function sign_publickey_from_secretkey(#[\SensitiveParameter] string $key) {
+ return sodium_crypto_sign_publickey_from_secretkey($key);
+ }
+
+ public static function increment_sequential_nonce($x) {
+ return sodium_increment($x); // The obtain the next nonce
+ }
+
+ // Should you Proceed with crypto_box decryption, if true go ahead do it.
+ public static function is_safe_to_decode($message_nonce, $expected_nonce): bool {
+ return (sodium_compare($message_nonce, $expected_nonce) === 0);
+ }
+
+ /*
+ * Sometimes you don't need to hide the contents of a message with encryption,
+ * but you still want to ensure that nobody on the network can tamper with
+ * it. For example, if you want to eschew server-side session storage and
+ * instead use HTTP cookies as your storage mechanism.
+ */
+ public static function sign_outbound_message_only(#[\SensitiveParameter] string $message, #[\SensitiveParameter] string $key): string {
+ $mac = sodium_crypto_auth($message, $key);
+ return $mac . "@@" . $message;
+ }
+
+ public static function is_valid_inbound_message(#[\SensitiveParameter] string $message, #[\SensitiveParameter] string $key): bool {
+ $a = explode("@@", $message, 2);
+ $mac = $a[0];
+ $old_message = $a[1];
+ $ret_bool = (sodium_crypto_auth_verify($mac, $old_message, $key));
+ if ($ret_bool === false) {
+ sodium_memzero($key);
+ throw new \Exception("Malformed message or invalid MAC");
+ }
+ return $ret_bool;
+ }
+
+}
+/*
+ * Usage:
+ *
+$key = tts\services\paragon_crypto\crypto::a_single_key_maker();
+$enc = tts\services\paragon_crypto\crypto::safe_encrypt('Encrypt This String...', $key, \tts\services\paragon_crypto\crypto::single_key);
+echo $enc . " ";
+$dec = \tts\services\paragon_crypto\crypto::safe_decrypt($enc, $key, \tts\services\paragon_crypto\crypto::single_key);
+echo $dec . " ";;
+// --------------------
+
+$key_pair_Alice = \tts\services\paragon_crypto\crypto::make_user_box_kp();
+$secret_Alice = \tts\services\paragon_crypto\crypto::get_user_box_secret_key($key_pair_Alice);
+$public_Alice = \tts\services\paragon_crypto\crypto::get_user_box_public_key($key_pair_Alice);
+
+$key_pair_Bob = \tts\services\paragon_crypto\crypto::make_user_box_kp();
+$secret_Bob = \tts\services\paragon_crypto\crypto::get_user_box_secret_key($key_pair_Bob);
+$public_Bob = \tts\services\paragon_crypto\crypto::get_user_box_public_key($key_pair_Bob);
+
+$kA = \tts\services\paragon_crypto\crypto::box_reassemble_keypair($secret_Alice, $public_Bob);
+$enc = \tts\services\paragon_crypto\crypto::safe_encrypt('Encrypt This String2...', $kA, \tts\services\paragon_crypto\crypto::multiple_keys);
+echo $enc . " ";
+
+$kB = \tts\services\paragon_crypto\crypto::box_reassemble_keypair($secret_Bob, $public_Alice);
+$dec = \tts\services\paragon_crypto\crypto::safe_decrypt($enc, $kB, \tts\services\paragon_crypto\crypto::multiple_keys);
+echo $dec;
+*/
diff --git a/src/classes/services/paragon_crypto/password_storage.php b/src/classes/services/paragon_crypto/password_storage.php
new file mode 100644
index 0000000..ee8a899
--- /dev/null
+++ b/src/classes/services/paragon_crypto/password_storage.php
@@ -0,0 +1,127 @@
+random_engine = new \tts\random_engine();
+ }
+
+ public function generate_a_key(): string {
+ return sodium_bin2hex($this->random_engine->get_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES));
+ }
+
+ /**
+ * Hash then encrypt a password
+ *
+ * @param string $password - The user's password
+ * @param string $secret_key - The main_magic key for all passwords
+ * @return string
+ */
+ public function hash(#[\SensitiveParameter] string $password, #[\SensitiveParameter] string $secret_key): string {
+ // First, let's calculate the hash
+ $hashed = sodium_crypto_pwhash_scryptsalsa208sha256_str(
+ $password,
+ SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_INTERACTIVE,
+ SODIUM_CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_INTERACTIVE
+ );
+ $salt = $this->random_engine->get_bytes(self::SALT_SIZE_IN_BYTES);
+ list ($enc_key, $auth_key) = $this->split_keys($secret_key, sodium_bin2hex($salt));
+ sodium_memzero($secret_key);
+ sodium_memzero($password);
+ $nonce = $this->random_engine->get_bytes(
+ SODIUM_CRYPTO_STREAM_NONCEBYTES
+ );
+ $cipher_text = sodium_crypto_stream_xor(
+ $hashed,
+ $nonce,
+ $enc_key
+ );
+
+ $mac = sodium_crypto_auth($nonce . $cipher_text, $auth_key);
+ sodium_memzero($enc_key);
+ sodium_memzero($auth_key);
+
+ return sodium_bin2hex($mac . $nonce . $salt . $cipher_text);
+ }
+
+ /**
+ * Decrypt then verify a password
+ *
+ * @param string $password - The user-provided password
+ * @param string $stored - The encrypted password hash
+ * @param string $secret_key - The main_magic key for all passwords
+ */
+ public function verify(#[\SensitiveParameter] string $password, string $stored, #[\SensitiveParameter] string $secret_key): bool {
+ $data = sodium_hex2bin($stored);
+ $mac = mb_substr(
+ $data,
+ 0,
+ SODIUM_CRYPTO_AUTH_BYTES,
+ '8bit'
+ );
+ $nonce = mb_substr(
+ $data,
+ SODIUM_CRYPTO_AUTH_BYTES,
+ SODIUM_CRYPTO_STREAM_NONCEBYTES,
+ '8bit'
+ );
+ $salt = mb_substr(
+ $data,
+ SODIUM_CRYPTO_AUTH_BYTES + SODIUM_CRYPTO_STREAM_NONCEBYTES,
+ self::SALT_SIZE_IN_BYTES,
+ '8bit'
+ );
+ $cipher_text = mb_substr(
+ $data,
+ SODIUM_CRYPTO_AUTH_BYTES + SODIUM_CRYPTO_STREAM_NONCEBYTES + self::SALT_SIZE_IN_BYTES,
+ null,
+ '8bit'
+ );
+ list ($enc_key, $auth_key) = $this->split_keys($secret_key, sodium_bin2hex($salt));
+
+ if (sodium_crypto_auth_verify($mac, $nonce . $cipher_text, $auth_key)) {
+ sodium_memzero($auth_key);
+ $hash_str = sodium_crypto_stream_xor($cipher_text, $nonce, $enc_key);
+ sodium_memzero($enc_key);
+ if ($hash_str !== false) {
+ $ret = sodium_crypto_pwhash_scryptsalsa208sha256_str_verify($hash_str, $password);
+ sodium_memzero($password);
+ return $ret;
+ }
+ } else {
+ sodium_memzero($auth_key);
+ sodium_memzero($enc_key);
+ }
+ throw new \Exception('Decryption failed.');
+ }
+
+ /**
+ * @return array(2) [encryption key, authentication key]
+ */
+ private function split_keys(#[\SensitiveParameter] string $secret_key, #[\SensitiveParameter] string $salt): array {
+ $enc_key = hash_hkdf('sha256', $secret_key, SODIUM_CRYPTO_STREAM_KEYBYTES, 'encryption', $salt);
+ $auth_key = hash_hkdf('sha256', $secret_key, SODIUM_CRYPTO_AUTH_KEYBYTES, 'authentication', $salt);
+ return [$enc_key, $auth_key];
+ }
+
+}
+
+/*
+$c = new \tts\services\paragon_crypto\password_storage();
+$k = $c->generate_a_key();
+$h = $c->hash("HelpMe", $k);
+var_dump( $c->verify("HelpMe", $h, $k) );
+ */
diff --git a/src/classes/services/paragon_crypto/sodium_storage.php b/src/classes/services/paragon_crypto/sodium_storage.php
new file mode 100644
index 0000000..5d8d391
--- /dev/null
+++ b/src/classes/services/paragon_crypto/sodium_storage.php
@@ -0,0 +1,133 @@
+random_engine = new \tts\random_engine();
+ }
+
+ public function encrypt(#[\SensitiveParameter] string $plain_text, string $item_name = ""): string {
+ $nonce = $this->random_engine->get_bytes(
+ SODIUM_CRYPTO_STREAM_NONCEBYTES
+ );
+ $salt = $this->random_engine->get_bytes(self::SALT_SIZE_IN_BYTES);
+ list ($enc_key, $auth_key) = $this->split_keys($item_name, sodium_bin2hex($salt));
+ $cipher_text = sodium_crypto_stream_xor(
+ $plain_text,
+ $nonce,
+ $enc_key
+ );
+ sodium_memzero($plain_text);
+
+ $mac = sodium_crypto_auth($nonce . $cipher_text, $auth_key);
+
+ sodium_memzero($enc_key);
+ sodium_memzero($auth_key);
+
+ return sodium_bin2hex($mac . $nonce . $salt . $cipher_text);
+ }
+
+ public function decrypt(string $cypher_data, string $item_name = ""): string {
+ $bin_data = sodium_hex2bin($cypher_data);
+
+ $mac = mb_substr(
+ $bin_data,
+ 0,
+ SODIUM_CRYPTO_AUTH_BYTES,
+ '8bit'
+ );
+ $nonce = mb_substr(
+ $bin_data,
+ SODIUM_CRYPTO_AUTH_BYTES,
+ SODIUM_CRYPTO_STREAM_NONCEBYTES,
+ '8bit'
+ );
+ $salt = mb_substr(
+ $bin_data,
+ SODIUM_CRYPTO_AUTH_BYTES + SODIUM_CRYPTO_STREAM_NONCEBYTES,
+ self::SALT_SIZE_IN_BYTES,
+ '8bit'
+ );
+ $cipher_text = mb_substr(
+ $bin_data,
+ SODIUM_CRYPTO_AUTH_BYTES + SODIUM_CRYPTO_STREAM_NONCEBYTES + self::SALT_SIZE_IN_BYTES,
+ null,
+ '8bit'
+ );
+
+ list ($enc_key, $auth_key) = $this->split_keys($item_name, sodium_bin2hex($salt));
+
+ if (sodium_crypto_auth_verify($mac, $nonce . $cipher_text, $auth_key)) {
+ sodium_memzero($auth_key);
+ $plaintext = sodium_crypto_stream_xor($cipher_text, $nonce, $enc_key);
+ sodium_memzero($enc_key);
+ if ($plaintext !== false) {
+ return $plaintext;
+ }
+ } else {
+ sodium_memzero($auth_key);
+ sodium_memzero($enc_key);
+ }
+ throw new \Exception('Decryption failed.');
+ }
+
+ /**
+ * @return array(2) [encryption key, authentication key]
+ */
+ private function split_keys(string $item_name, #[\SensitiveParameter] string $salt): array {
+ $enc_key = hash_hkdf('sha256', $this->key, SODIUM_CRYPTO_STREAM_KEYBYTES, md5('encryption' . $item_name), $salt);
+ $auth_key = hash_hkdf('sha256', $this->key, SODIUM_CRYPTO_AUTH_KEYBYTES, md5('authentication' . $item_name), $salt);
+ return [$enc_key, $auth_key];
+ }
+
+}
+
+/*
+ * Example:
+$secretkey = "78a5011b9997cd03a28a3412c66565b7c32715b35e055d7abfc228236308d3b2";
+$plain_text = "Hello World!";
+
+$sc = new \tts\services\paragon_crypto\sodium_storage($secretkey);
+$index = "sensitive";
+$encoded = $sc->encode($index, $plain_text);
+setcookie($index, $encoded);
+
+// On the next page load:
+try {
+ if (!array_key_exists($index, $_COOKIE)) {
+ throw new \Exception('No Cookie!');
+ }
+ $data = $_COOKIE[$index];
+ $plain_text = $sc->decode($index, $data);
+ echo $plain_text;
+} catch (Exception $ex) {
+ // Handle the exception here
+ echo $ex->getMessage();
+}
+*/
\ No newline at end of file
diff --git a/src/classes/string_fns.php b/src/classes/string_fns.php
new file mode 100644
index 0000000..c0d3b84
--- /dev/null
+++ b/src/classes/string_fns.php
@@ -0,0 +1,98 @@
+
+ * @copyright (c) 2024, Robert Strutts
+ * @license MIT
+ */
+
+namespace CodeHydrater;
+
+class String_fns {
+ public static function isUTF8(string $string) {
+ // Empty string is valid UTF-8
+ if ($string === '') {
+ return true;
+ }
+
+ // Convert string to array of bytes
+ $bytes = unpack('C*', $string);
+
+ // Pattern matching state
+ $state = 0;
+ $expectedBytes = 0;
+
+ foreach ($bytes as $byte) {
+ // Single byte character (0xxxxxxx)
+ if ($byte <= 0x7F) {
+ $state = 0;
+ continue;
+ }
+
+ // Start of multibyte sequence
+ if ($state === 0) {
+ // 2 bytes (110xxxxx)
+ if (($byte & 0xE0) === 0xC0) {
+ $expectedBytes = 1;
+ }
+ // 3 bytes (1110xxxx)
+ elseif (($byte & 0xF0) === 0xE0) {
+ $expectedBytes = 2;
+ }
+ // 4 bytes (11110xxx)
+ elseif (($byte & 0xF8) === 0xF0) {
+ $expectedBytes = 3;
+ }
+ // Invalid UTF-8 start byte
+ else {
+ return false;
+ }
+ $state = $expectedBytes;
+ continue;
+ }
+
+ // Continuation byte (10xxxxxx)
+ if (($byte & 0xC0) !== 0x80) {
+ return false;
+ }
+
+ $state--;
+ }
+
+ // Check if we finished the last multibyte sequence
+ return $state === 0;
+ }
+
+ // Check if string contains multibyte characters
+ public static function has_multibyte_chars(string $string) {
+ return (bool) preg_match('/[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2}/', $string);
+ }
+
+ // Get the length of a string
+ public function strlen($string) {
+ return strlen($string);
+ }
+
+ // Convert a string to lowercase
+ public function strtolower($string) {
+ return strtolower($string);
+ }
+
+ // Convert a string to uppercase
+ public function strtoupper($string) {
+ return strtoupper($string);
+ }
+
+ // Get a substring from a string
+ public function substr($string, $start, $length = null) {
+ return ($length !== null) ? substr($string, $start, $length) : substr($string, $start);
+ }
+
+ // Reverse a string
+ public function strrev($string) {
+ return strrev($string);
+ }
+}
+
diff --git a/src/classes/tag_matches.php b/src/classes/tag_matches.php
new file mode 100644
index 0000000..6e2882d
--- /dev/null
+++ b/src/classes/tag_matches.php
@@ -0,0 +1,63 @@
+
+ * @copyright Copyright (c) 2022, Robert Strutts.
+ * @license MIT
+ */
+
+namespace tts;
+
+final class tag_matches {
+
+const tags_to_check = array('div', 'span', 'form', 'i*', 'a*', 'h1', 'p*');
+
+/**
+ * Function checks tags to make sure they match.
+ * Used by view.php
+ * @param string $page
+ * @return array [output, alert]
+ */
+public static function check_tags(string $page): array {
+ $alert = '';
+ $output = '';
+ $l_page = \bs_tts\common::string_to_lowercase($page);
+ unset($page);
+
+ $assets = \bs_tts\site_helper::get_asset("uikit/css/uikit.gradient.min.css");
+ $ui = '';
+ $ui .= '';
+ $ui_end = ' ';
+
+ foreach (self::tags_to_check as $tag_name) {
+ if (\bs_tts\common::is_string_found($tag_name, '*')) {
+ $tag_name = str_replace('*', '', $tag_name);
+ $otag = "<{$tag_name}>"; // Open Tag
+ $open = substr_count($l_page, $otag); // Count open tags in page
+ $otag = "<{$tag_name} "; /* Open Tag with space */
+ $open += substr_count($l_page, $otag); // Count open tags in page
+ } else {
+ $otag = "<{$tag_name}"; // Open Tag
+ $open = substr_count($l_page, $otag); // Count open tags in page
+ }
+
+ $ctag = "{$tag_name}>"; // Close Tag
+ $closed = substr_count($l_page, $ctag); // Count Close tags in page
+ $total_still_open = $open - $closed; // Difference of open vs. closed....
+
+ if ($total_still_open > 0) {
+ $msg = "{$total_still_open} possibly MISSING closing {$tag_name} !!!";
+ $alert .= "console.log('{$msg}');\r\n";
+ $output .= (\main_tts\is_live()) ? "\r\n" : "{$ui}{$msg}{$ui_end}\r\n";
+ } elseif ($total_still_open < 0) {
+ $msg = abs($total_still_open) . " possibly MISSING opening {$tag_name} !!!";
+ $alert .= "console.log('{$msg}');\r\n";
+ $output .= (\main_tts\is_live()) ? "\r\n" : "{$ui}{$msg}{$ui_end}\r\n";
+ }
+ }
+ return array('output' => $output, 'alert' => $alert);
+ }
+
+}
diff --git a/src/classes/time_zone_selection.php b/src/classes/time_zone_selection.php
new file mode 100644
index 0000000..721f64a
--- /dev/null
+++ b/src/classes/time_zone_selection.php
@@ -0,0 +1,49 @@
+ \DateTimeZone::AMERICA,
+ 'Europe' => \DateTimeZone::EUROPE,
+ 'Africa' => \DateTimeZone::AFRICA,
+ 'Antarctica' => \DateTimeZone::ANTARCTICA,
+ 'Aisa' => \DateTimeZone::ASIA,
+ 'Atlantic' => \DateTimeZone::ATLANTIC,
+ 'Indian' => \DateTimeZone::INDIAN,
+ 'Pacific' => \DateTimeZone::PACIFIC
+ );
+
+ foreach ($regions as $name => $mask) {
+ $zones = \DateTimeZone::listIdentifiers($mask);
+ foreach ($zones as $timezone) {
+ $this->timezones[$name][$timezone] = substr($timezone, strlen($name) + 1);
+ }
+ }
+ }
+
+ public function view() {
+ echo ' ';
+ }
+
+}
diff --git a/src/classes/time_zones.php b/src/classes/time_zones.php
new file mode 100644
index 0000000..175ccf2
--- /dev/null
+++ b/src/classes/time_zones.php
@@ -0,0 +1,274 @@
+format('P');
+ }
+
+ /**
+ * Purpose: To make a temporary valid timestamp for a database
+ */
+ public static function expires_in_hours(int $hours = 1) {
+ $hours = ($hours > 0 && $hours < 10) ? $hours : 1;
+ $expires = new DateTime('NOW', new \DateTimeZone('UTC'));
+ $expires->add(new DateInterval("PT0{$hours}H"));
+ return $expires->format('Y-m-d H:i:s');
+ }
+
+ private static function mdy($userTime, $format) {
+ switch (bootstrap\common::string_to_lowercase($format)) {
+ case 'actions':
+ return $userTime->format('m-d-Y / h:iA');
+ case 'report':
+ return $userTime->format('l jS \of F\, Y\, h:i a');
+ case 'fancy':
+ return $userTime->format('l jS \of F Y h:i:s A');
+ case 'logging':
+ case 'log':
+ return $userTime->format('g:i A \o\n l jS F Y');
+ case 'legal_date':
+ return $userTime->format('n/j/Y');
+ case 'date':
+ return $userTime->format('m/d/Y');
+ case 'date-time':
+ return $userTime->format('m/d/Y h:i:s A');
+ case 'full':
+ return $userTime->format('m-d-Y h:i:s A');
+ case 'normal':
+ return $userTime->format('m/d/Y h:i A');
+ case 'default':
+ return $userTime->format('m-d-Y h:i A');
+ default:
+ return $userTime->format($format);
+ }
+ }
+
+ private static function dmy($userTime, $format) {
+ switch (bootstrap\common::string_to_lowercase($format)) {
+ case 'actions':
+ return $userTime->format('d-m-Y / h:iA');
+ case 'report':
+ return $userTime->format('l jS \of F\, Y\, h:i a');
+ case 'fancy':
+ return $userTime->format('l jS \of F Y h:i:s A');
+ case 'logging':
+ case 'log':
+ return $userTime->format('g:i A \o\n l jS F Y');
+ case 'legal_date':
+ return $userTime->format('j/n/Y');
+ case 'date':
+ return $userTime->format('d/m/Y');
+ case 'date-time':
+ return $userTime->format('d/m/Y h:i:s A');
+ case 'full':
+ return $userTime->format('d-m-Y h:i:s A');
+ case 'normal':
+ return $userTime->format('d/m/Y h:i A');
+ case 'default':
+ return $userTime->format('d-m-Y h:i A');
+ default:
+ return $userTime->format($format);
+ }
+ }
+
+ private static function ymd($userTime, $format) {
+ switch (bootstrap\common::string_to_lowercase($format)) {
+ case 'actions':
+ return $userTime->format('Y-m-d / h:iA');
+ case 'report':
+ return $userTime->format('l jS \of F\, Y\, h:i a');
+ case 'fancy':
+ return $userTime->format('l jS \of F Y h:i:s A');
+ case 'logging':
+ case 'log':
+ return $userTime->format('g:i A \o\n l jS F Y');
+ case 'legal_date':
+ return $userTime->format('Y/n/j');
+ case 'date':
+ return $userTime->format('Y/m/d');
+ case 'date-time':
+ return $userTime->format('Y/m/d h:i:s A');
+ case 'full':
+ return $userTime->format('Y-m-d h:i:s A');
+ case 'normal':
+ return $userTime->format('Y/m/d h:i A');
+ case 'default':
+ return $userTime->format('Y-m-d h:i A');
+ default:
+ return $userTime->format($format);
+ }
+ }
+
+ public static function dt_format($userTime, $format, $country) {
+ switch (bootstrap\common::string_to_lowercase($format)) {
+ case 'object':
+ return $userTime;
+ case 'unix':
+ return $userTime->format('U');
+ case 'day':
+ return $userTime->format('l');
+ case 'time':
+ return $userTime->format('h:i A');
+ case 'military':
+ return $userTime->format('H:i:s');
+ case 'atom':
+ return $userTime->format(DateTime::ATOM);
+ case 'cookie':
+ return $userTime->format(DateTime::COOKIE);
+ case 'iso8601':
+ case 'iso':
+ case '8601':
+ return $userTime->format(DateTime::ISO8601);
+ case 'rfc822':
+ return $userTime->format(DateTime::RFC822);
+ case 'rfc850':
+ return $userTime->format(DateTime::RFC850);
+ case 'rfc1036':
+ return $userTime->format(DateTime::RFC1036);
+ case 'rfc1123':
+ return $userTime->format(DateTime::RFC1123);
+ case 'rfc2822':
+ return $userTime->format(DateTime::RFC2822);
+ case 'rfc3339':
+ return $userTime->format(DateTime::RFC3339);
+ case 'rss':
+ return $userTime->format(DateTime::RSS);
+ case 'w3c':
+ return $userTime->format(DateTime::W3C);
+ case 'standard':
+ case 'computer':
+ case 'database':
+ return $userTime->format('Y-m-d H:i:s');
+ }
+
+ switch (bootstrap\common::string_to_lowercase($country)) {
+ case 'china':
+ return self::ymd($userTime, $format);
+ case 'int':
+ case 'other':
+ return self::dmy($userTime, $format);
+ default:
+ return self::mdy($userTime, $format);
+ }
+
+ }
+
+ /**
+ * Purpose: To convert a database timestamp into the users own Time Zone.
+ */
+ public static function convert_time_zone($options) {
+ $format = (isset($options['format'])) ? $options['format'] : 'normal';
+
+ $session_time_zone = (isset($_SESSION['time_zone'])) ? $_SESSION['time_zone'] : 'America/Detroit';
+ $tz = (isset($options['time_zone']) && !empty($options['time_zone'])) ? $options['time_zone'] : $session_time_zone;
+
+ $offset = (isset($options['offset'])) ? $options['offset'] : '';
+
+ $db_time = (isset($options['time'])) ? $options['time'] : '';
+ if ($db_time === '0000-00-00 00:00:00') {
+ return false;
+ }
+
+ $new_time = (empty($db_time)) ? self::get_offset($offset) : self::get_offset_by($offset, $db_time);
+
+ // Convert date("U"); unix timestamps to proper format for DateTime function...
+ if (substr_count($new_time, ':') == 0) {
+ $the_time = (empty($new_time) || $new_time == 'now' || $new_time == 'current') ? gmdate("Y-m-d H:i:s") : gmdate("Y-m-d H:i:s", $new_time);
+ }
+
+ $userTime = new \DateTime($the_time, new \DateTimeZone('UTC'));
+
+ // Set the users time_zone to their zone
+ if ($tz !== 'UTC') {
+ $userTime->setTimezone(new \DateTimeZone($tz));
+ }
+
+ $country = (isset($options['country'])) ? $options['country'] : 'usa';
+
+ return self::dt_format($userTime, $format, $country);
+ }
+
+ public static function human_date($input_date) {
+ if (empty($input_date)) {
+ return '';
+ }
+ $today = self::convert_time_zone(array('format' => 'm/d/Y'));
+ $date = strtotime($input_date);
+ if (date('m/d/Y', $date) == $today) {
+ return date('g:i', $date) . "" . date('A', $date) . "";
+ } elseif (date('Y', $date) == date('Y')) {
+ return "" . date('M', $date) . " " . date('j', $date) . "" . date('S', $date) . "";
+ } else {
+ return date('m/d/y', $date);
+ }
+ }
+
+ private static function is_valid_offset($offset) {
+ if (substr_count($offset, 'second') > 0) {
+ return true;
+ } elseif (substr_count($offset, 'minute') > 0) {
+ return true;
+ } elseif (substr_count($offset, 'hour') > 0) {
+ return true;
+ } elseif (substr_count($offset, 'day') > 0) {
+ return true;
+ } elseif (substr_count($offset, 'week') > 0) {
+ return true;
+ } elseif (substr_count($offset, 'month') > 0) {
+ return true;
+ } elseif (substr_count($offset, 'year') > 0) {
+ return true;
+ } elseif (substr_count($offset, 'next') > 0) {
+ return true;
+ } elseif (substr_count($offset, 'last') > 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private static function get_offset($offset) {
+ return (self::is_valid_offset($offset)) ? strtotime($offset) : $offset;
+ }
+
+ private static function get_offset_by($offset, $db_time) {
+ // strtotime requires a int timestamp
+ if (substr_count($db_time, ':') > 0) {
+ $UTC = new \DateTime($db_time, new \DateTimeZone('UTC'));
+ $db_time = $UTC->format('U');
+ }
+
+ return (self::is_valid_offset($offset)) ? strtotime($offset, $db_time) : $db_time;
+ }
+
+}
diff --git a/src/classes/traits/Macroable.php b/src/classes/traits/Macroable.php
new file mode 100644
index 0000000..f857a45
--- /dev/null
+++ b/src/classes/traits/Macroable.php
@@ -0,0 +1,60 @@
+bindTo($this, static::class), $parameters);
+ }
+
+ return call_user_func_array($macro, $parameters);
+ }
+
+ /**
+ * Dynamically handle static calls to the class.
+ */
+ public static function __callStatic(string $method, mixed $parameters): mixed
+ {
+ if (!static::hasMacro($method)) {
+ throw new \BadMethodCallException("Method {$method} does not exist.");
+ }
+
+ $macro = static::$macros[$method];
+
+ if ($macro instanceof \Closure) {
+ return call_user_func_array(\Closure::bind($macro, null, static::class), $parameters);
+ }
+
+ return call_user_func_array($macro, $parameters);
+ }
+}
diff --git a/src/classes/traits/security/csrf_token_functions.php b/src/classes/traits/security/csrf_token_functions.php
index 3740352..cdea594 100644
--- a/src/classes/traits/security/csrf_token_functions.php
+++ b/src/classes/traits/security/csrf_token_functions.php
@@ -50,12 +50,12 @@ trait csrf_token_functions {
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');
+ $user_token = \CodeHydrater\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);
+ return \CodeHydrater\misc::compair_it($user_token, $stored_token);
} else {
return false;
}
@@ -66,7 +66,7 @@ trait csrf_token_functions {
* @return bool
*/
public static function csrf_token_is_recent(): bool {
- $max_elapsed = intval(\main_tts\configure::get(
+ $max_elapsed = intval(\CodeHydrater\bootstrap\configure::get(
'security',
'max_token_age'
));
diff --git a/src/classes/uuidv7.php b/src/classes/uuidv7.php
new file mode 100644
index 0000000..586ca52
--- /dev/null
+++ b/src/classes/uuidv7.php
@@ -0,0 +1,68 @@
+
+ * @copyright (c) 2025, Robert Strutts
+ * @license MIT
+ */
+namespace CodeHydrater;
+
+/**
+ * Description of uuidv7
+ *
+ * @author Robert Strutts
+ */
+class uuidv7 {
+
+ public static function base32EncodedUUID(): string {
+ $uuid = self::generateUUIDv7();
+ $uuidHex = str_replace('-', '', $uuid);
+ return self::base32Encode($uuidHex);
+ }
+
+ public static function generateUUIDv7(): string {
+ $timestamp = microtime(true) * 10000; // current timestamp in milliseconds
+ $timeHex = str_pad(dechex((int)$timestamp), 12, '0', STR_PAD_LEFT);
+
+ $randomHex = bin2hex(random_bytes(10));
+
+ return sprintf(
+ '%s-%s-%s-%s-%s',
+ substr($timeHex, 0, 8),
+ substr($timeHex, 8, 4),
+ '7' . substr($timeHex, 12, 3) . substr($randomHex, 0, 1), // UUIDv7
+ substr($randomHex, 1, 4),
+ substr($randomHex, 5, 12)
+ );
+ }
+
+ public static function base32Encode(string $hex): string {
+ $base32Chars = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
+ $binary = '';
+
+ // Convert hex to binary
+ foreach (str_split($hex) as $hexChar) {
+ $binary .= str_pad(base_convert($hexChar, 16, 2), 4, '0', STR_PAD_LEFT);
+ }
+
+ // Split binary string into 5-bit chunks and convert to base32
+ $base32 = '';
+ foreach (str_split($binary, 5) as $chunk) {
+ $base32 .= $base32Chars[bindec(str_pad($chunk, 5, '0', STR_PAD_RIGHT))];
+ }
+
+ return $base32;
+ }
+
+/**
+ * @example Database SQL:
+CREATE TABLE test_table (
+ id CHAR(36) PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+*/
+
+}
diff --git a/src/classes/view.php b/src/classes/view.php
new file mode 100644
index 0000000..505a916
--- /dev/null
+++ b/src/classes/view.php
@@ -0,0 +1,243 @@
+project_dir}/views/{$view_file}" : "{$this->project_dir}/views/{$default}/{$view_file}";
+ $path = bootstrap\site_helper::get_root();
+ $vf = $path . $file;
+ if ( bootstrap\requires::safer_file_exists($vf) !== false) {
+ return $file;
+ }
+ return '';
+ }
+
+ /**
+ * Alias to set_view
+ */
+ public function include(string $file): void {
+ $this->set_view($file);
+ }
+
+ /**
+ * Alias to set_view
+ */
+ public function add_view(string $file): void {
+ $this->set_view($file);
+ }
+
+ private function find_view_path(string $view_file) {
+ $found = false;
+
+ $default_paths = bootstrap\configure::get('view_mode', 'default_paths');
+ $get = misc::request_var('render');
+ if (in_array($get, $default_paths)) {
+ if (($key = array_search($get, $default_paths)) !== false) {
+ unset($default_paths[$key]); // Remove as we'll make it first later...
+ }
+ array_unshift($default_paths, $get); // Make First in Array!
+ }
+
+ foreach ($default_paths as $default) {
+ $file = $this->get_file($view_file, $default);
+ if ( ! empty($file) ) {
+ $found = true;
+ break;
+ }
+ }
+ return ($found) ? $file : false;
+ }
+
+ private function find_template_path($tpl_file) {
+ $file = "{$this->project_dir}/views/includes/{$tpl_file}";
+ $path = bootstrap\site_helper::get_root();
+ $vf = $path . $file;
+ return bootstrap\requires::safer_file_exists($vf);
+ }
+
+ /**
+ * Use View File
+ * @param string $view_file
+ * @param string $render_path
+ * @throws Exception
+ */
+ public function set_view(string $view_file = null): void {
+ if ($view_file === null) {
+ return;
+ }
+
+ $file_ext = bootstrap\common::get_string_right($view_file, 4);
+ if (! bootstrap\common::is_string_found($file_ext, '.')) {
+ $file_ext = '.php';
+ } else if ($file_ext !== '.php') {
+ $this->use_template_engine = true;
+ $this->template_type = str_replace('.', '', $file_ext);
+ }
+
+ if ($file_ext === '.php') {
+ $file = $this->find_view_path($view_file);
+ } else {
+ $file = $this->find_template_path($view_file);
+ }
+
+ if ($file === false) {
+ echo "No view file exists for: {$view_file}!";
+ throw new \Exception("View File does not exist: " . $view_file);
+ } else {
+ $this->files[] = array('file'=>$file, 'path'=>"project", 'file_type'=>$file_ext);
+ }
+ }
+
+ public function clear_template(): void {
+ $this->template = false;
+ }
+
+ /**
+ * Use Template with view
+ * @param string $render_page
+ * @return bool was found
+ */
+ public function set_template(string $render_page): bool {
+ if (! empty($render_page)) {
+ $render_page = str_replace('.php', '', $render_page);
+ $templ = "{$this->project_dir}/templates/{$render_page}";
+ if (bootstrap\requires::safer_file_exists(bootstrap\site_helper::get_root() . $templ. '.php') !== false) {
+ $this->template = $templ;
+// echo $this->template;
+ return true;
+ }
+ }
+ $this->template = false;
+ return false;
+ }
+
+ private function clear_ob(int $saved_ob_level): void {
+ while (ob_get_level() > $saved_ob_level) {
+ ob_end_flush();
+ }
+ }
+
+ /**
+ * Sets a variable in this view with the given name and value
+ *
+ * @param mixed $name Name of the variable to set in the view, or an array of key/value pairs where each key is the variable and each value is the value to set.
+ * @param mixed $value Value of the variable to set in the view.
+ */
+ public function set($name, $value = null): void {
+ if (is_array($name)) {
+ foreach ($name as $var_name => $value) {
+ $this->vars[$var_name] = $value;
+ }
+ } else {
+ $this->vars[$name] = $value;
+ }
+ }
+
+ /**
+ * Alias to fetch with Built-in echo
+ * @param type $local
+ * @param string $file
+ */
+ public function render($local, string $file = null) {
+ echo $this->fetch($local, $file);
+ }
+
+ /**
+ * Outputs view
+ * @param type $local = $this
+ * @param $file (optional view file)
+ */
+ public function fetch($local, string $file = null): string {
+ $page_output = ob_get_clean(); // Get echos before View
+ bootstrap\views::ob_start();
+ $saved_ob_level = ob_get_level();
+ $this->set_view($file);
+
+ unset($file);
+
+ if (!is_object($local)) {
+ $local = $this; // FALL Back, please use fetch($this);
+ }
+
+ if ($this->use_template_engine) {
+ $this->tempalte_engine = bootstrap\registry::get('di')->get_service('templates', [$this->template_type]);
+ if ($this->white_space_control) {
+ $this->tempalte_engine->whitespace_control();
+ }
+ }
+
+ if (count($this->files) > 0) {
+ foreach ($this->files as $view_file) {
+ if ($view_file['file_type'] == '.php') {
+ bootstrap\requires::secure_include($view_file['file'], $view_file['path'], $local, $this->vars); // Include the file
+ } else {
+ $template_file = str_replace('.tpl', '', $view_file['file']);
+ $this->tempalte_engine->parse_file($template_file);
+ $assigns = $this->vars['template_assigns'] ?? [];
+ $filters = $this->vars['template_filters'] ?? null;
+ $registers = $this->vars['template_registers'] ?? [];
+ $assigns['production'] =(\main_tts\is_live());
+ echo $this->tempalte_engine->render($assigns, $filters, $registers);
+ }
+ }
+ }
+ $this->clear_ob($saved_ob_level);
+ $page_output .= ob_get_clean();
+
+ try {
+ if (bootstrap\common::get_bool(bootstrap\configure::get('tts', 'check_HTML_tags')) === true) {
+ $tags = \tts\tag_matches::check_tags($page_output);
+ if (! empty($tags['output'])) {
+ $page_output .= $tags['output'];
+ $page_output .= '';
+ foreach($this->files as $bad) {
+ $page_output .= "";
+ }
+ }
+ }
+ } catch (exceptions\Bool_Exception $e) {
+ if (bootstrap\configure::get('tts', 'live') === false) {
+ $page_output .= assets::alert('SET Config for tts: check_HTML_tags');
+ }
+ }
+
+ if ($this->template !== false && bootstrap\requires::safer_file_exists(bootstrap\site_helper::get_root() . $this->template . ".php") !== false) {
+ bootstrap\views::ob_start();
+ $saved_ob_level = ob_get_level();
+ $local->page_output = $page_output;
+ bootstrap\requires::secure_include($this->template, 'project', $local, $this->vars);
+ $this->clear_ob($saved_ob_level);
+ $page_output = ob_get_clean();
+ }
+
+ // Reset Files to zero, as they were outputed....
+ unset($this->files);
+ $this->files = [];
+
+ return $page_output;
+ }
+
+}
\ No newline at end of file
|