parent
951ac7ee20
commit
434883baa8
@ -1,4 +1,5 @@ |
|||||||
# IOcornerstone PHP 8.5 Framework |
# IOcornerstone PHP 8.5 Framework |
||||||
|
|
||||||
Author Robert Strutts |
## Author Robert Strutts |
||||||
Copyright (c) 2010-2026 MIT |
## Copyright (c) 2010-2026 Robert Strutts |
||||||
|
## License MIT |
||||||
|
|||||||
@ -0,0 +1,102 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
namespace IOcornerstone\Framework\Arrays; |
||||||
|
|
||||||
|
class CommonStuff { |
||||||
|
|
||||||
|
public static function getOrdinalNumberSuffix(int $number): string { |
||||||
|
$number = abs($number); |
||||||
|
if (in_array(($number % 100),array(11,12,13))){ |
||||||
|
return 'th'; |
||||||
|
} |
||||||
|
switch ($number % 10) { |
||||||
|
case 1: return 'st'; |
||||||
|
case 2: return 'nd'; |
||||||
|
case 3: return 'rd'; |
||||||
|
default: return 'th'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Fetch Months |
||||||
|
* @return array of months |
||||||
|
*/ |
||||||
|
public static function months(): array { |
||||||
|
return array( |
||||||
|
'1' => 'January', |
||||||
|
'2' => 'February', |
||||||
|
'3' => 'March', |
||||||
|
'4' => 'April', |
||||||
|
'5' => 'May', |
||||||
|
'6' => 'June', |
||||||
|
'7' => 'July', |
||||||
|
'8' => 'August', |
||||||
|
'9' => 'September', |
||||||
|
'10' => 'October', |
||||||
|
'11' => 'November', |
||||||
|
'12' => 'December' |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Fetch States in USA |
||||||
|
* @return array of states |
||||||
|
*/ |
||||||
|
public static function statesArray(): array { |
||||||
|
return array( |
||||||
|
'AL' => 'Alabama', |
||||||
|
'AK' => 'Alaska', |
||||||
|
'AZ' => 'Arizona', |
||||||
|
'AR' => 'Arkansas', |
||||||
|
'CA' => 'California', |
||||||
|
'CO' => 'Colorado', |
||||||
|
'CT' => 'Connecticut', |
||||||
|
'DE' => 'Delaware', |
||||||
|
'DC' => 'District of Columbia', |
||||||
|
'FL' => 'Florida', |
||||||
|
'GA' => 'Georgia', |
||||||
|
'HI' => 'Hawaii', |
||||||
|
'ID' => 'Idaho', |
||||||
|
'IL' => 'Illinois', |
||||||
|
'IN' => 'Indiana', |
||||||
|
'IA' => 'Iowa', |
||||||
|
'KS' => 'Kansas', |
||||||
|
'KY' => 'Kentucky', |
||||||
|
'LA' => 'Louisiana', |
||||||
|
'ME' => 'Maine', |
||||||
|
'MD' => 'Maryland', |
||||||
|
'MA' => 'Massachusetts', |
||||||
|
'MI' => 'Michigan', |
||||||
|
'MN' => 'Minnesota', |
||||||
|
'MS' => 'Mississippi', |
||||||
|
'MO' => 'Missouri', |
||||||
|
'MT' => 'Montana', |
||||||
|
'NE' => 'Nebraska', |
||||||
|
'NV' => 'Nevada', |
||||||
|
'NH' => 'New Hampshire', |
||||||
|
'NJ' => 'New Jersey', |
||||||
|
'NM' => 'New Mexico', |
||||||
|
'NY' => 'New York', |
||||||
|
'NC' => 'North Carolina', |
||||||
|
'ND' => 'North Dakota', |
||||||
|
'OH' => 'Ohio', |
||||||
|
'OK' => 'Oklahoma', |
||||||
|
'OR' => 'Oregon', |
||||||
|
'PA' => 'Pennsylvania', |
||||||
|
'RI' => 'Rhode Island', |
||||||
|
'SC' => 'South Carolina', |
||||||
|
'SD' => 'South Dakota', |
||||||
|
'TN' => 'Tennessee', |
||||||
|
'TX' => 'Texas', |
||||||
|
'UT' => 'Utah', |
||||||
|
'VT' => 'Vermont', |
||||||
|
'VA' => 'Virginia', |
||||||
|
'WA' => 'Washington', |
||||||
|
'WV' => 'West Virginia', |
||||||
|
'WI' => 'Wisconsin', |
||||||
|
'WY' => 'Wyoming', |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,262 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework\Arrays; |
||||||
|
|
||||||
|
class Countries { |
||||||
|
|
||||||
|
/** |
||||||
|
* Fetch countries |
||||||
|
* @return array of countries |
||||||
|
*/ |
||||||
|
public static function countriesArray(): array { |
||||||
|
return array( |
||||||
|
'AX' => 'Aland Islands', |
||||||
|
'AF' => 'Afghanistan', |
||||||
|
'AL' => 'Albania', |
||||||
|
'DZ' => 'Algeria', |
||||||
|
'AS' => 'American Samoa', |
||||||
|
'AD' => 'Andorra', |
||||||
|
'AO' => 'Angola', |
||||||
|
'AI' => 'Anguilla', |
||||||
|
'AQ' => 'Antarctica', |
||||||
|
'AG' => 'Antigua And Barbuda', |
||||||
|
'AR' => 'Argentina', |
||||||
|
'AM' => 'Armenia', |
||||||
|
'AW' => 'Aruba', |
||||||
|
'AU' => 'Australia', |
||||||
|
'AT' => 'Austria', |
||||||
|
'AZ' => 'Azerbaijan', |
||||||
|
'BS' => 'Bahamas', |
||||||
|
'BH' => 'Bahrain', |
||||||
|
'BD' => 'Bangladesh', |
||||||
|
'BB' => 'Barbados', |
||||||
|
'BY' => 'Belarus', |
||||||
|
'BE' => 'Belgium', |
||||||
|
'BZ' => 'Belize', |
||||||
|
'BJ' => 'Benin', |
||||||
|
'BM' => 'Bermuda', |
||||||
|
'BT' => 'Bhutan', |
||||||
|
'BO' => 'Bolivia', |
||||||
|
'BA' => 'Bosnia And Herzegovina', |
||||||
|
'BW' => 'Botswana', |
||||||
|
'BV' => 'Bouvet Island', |
||||||
|
'BR' => 'Brazil', |
||||||
|
'IO' => 'British Indian Ocean Territory', |
||||||
|
'BN' => 'Brunei', |
||||||
|
'BG' => 'Bulgaria', |
||||||
|
'BF' => 'Burkina Faso', |
||||||
|
'AR' => 'Burma', |
||||||
|
'BI' => 'Burundi', |
||||||
|
'KH' => 'Cambodia', |
||||||
|
'CM' => 'Cameroon', |
||||||
|
'CA' => 'Canada', |
||||||
|
'CV' => 'Cape Verde', |
||||||
|
'KY' => 'Cayman Islands', |
||||||
|
'CF' => 'Central African Republic', |
||||||
|
'TD' => 'Chad', |
||||||
|
'CL' => 'Chile', |
||||||
|
'CN' => 'China', |
||||||
|
'CX' => 'Christmas Island', |
||||||
|
'CC' => 'Cocos (Keeling) Islands', |
||||||
|
'CO' => 'Columbia', |
||||||
|
'KM' => 'Comoros', |
||||||
|
'CG' => 'Congo', |
||||||
|
'CK' => 'Cook Islands', |
||||||
|
'CR' => 'Costa Rica', |
||||||
|
'CI' => 'Cote D\'Ivorie (Ivory Coast)', |
||||||
|
'HR' => 'Croatia (Hrvatska)', |
||||||
|
'CU' => 'Cuba', |
||||||
|
'CY' => 'Cyprus', |
||||||
|
'CZ' => 'Czech Republic static', |
||||||
|
'CD' => 'Democratic Republic Of Congo (Zaire)', |
||||||
|
'DK' => 'Denmark', |
||||||
|
'DJ' => 'Djibouti', |
||||||
|
'DM' => 'Dominica', |
||||||
|
'DO' => 'Dominican Republic', |
||||||
|
'TP' => 'East Timor', |
||||||
|
'EC' => 'Ecuador', |
||||||
|
'EG' => 'Egypt', |
||||||
|
'SV' => 'El Salvador', |
||||||
|
'GB' => 'England', |
||||||
|
'GQ' => 'Equatorial Guinea', |
||||||
|
'ER' => 'Eritrea', |
||||||
|
'EE' => 'Estonia', |
||||||
|
'ET' => 'Ethiopia', |
||||||
|
'EU' => 'European Union', |
||||||
|
'FK' => 'Falkland Islands (Malvinas)', |
||||||
|
'FO' => 'Faroe Islands', |
||||||
|
'FJ' => 'Fiji', |
||||||
|
'FI' => 'Finland', |
||||||
|
'FR' => 'France', |
||||||
|
'FX' => 'France, Metropolitan', |
||||||
|
'GF' => 'French Guinea', |
||||||
|
'PF' => 'French Polynesia', |
||||||
|
'TF' => 'French Southern Territories', |
||||||
|
'GA' => 'Gabon', |
||||||
|
'GM' => 'Gambia', |
||||||
|
'GE' => 'Georgia', |
||||||
|
'DE' => 'Germany', |
||||||
|
'GH' => 'Ghana', |
||||||
|
'GI' => 'Gibraltar', |
||||||
|
'GR' => 'Greece', |
||||||
|
'GL' => 'Greenland', |
||||||
|
'GD' => 'Grenada', |
||||||
|
'GP' => 'Guadeloupe', |
||||||
|
'GU' => 'Guam', |
||||||
|
'GT' => 'Guatemala', |
||||||
|
'GN' => 'Guinea', |
||||||
|
'GW' => 'Guinea-Bissau', |
||||||
|
'GY' => 'Guyana', |
||||||
|
'HT' => 'Haiti', |
||||||
|
'HM' => 'Heard And McDonald Islands', |
||||||
|
'HN' => 'Honduras', |
||||||
|
'HK' => 'Hong Kong', |
||||||
|
'HU' => 'Hungary', |
||||||
|
'IS' => 'Iceland', |
||||||
|
'IN' => 'India', |
||||||
|
'ID' => 'Indonesia', |
||||||
|
'IR' => 'Iran', |
||||||
|
'IQ' => 'Iraq', |
||||||
|
'IE' => 'Ireland', |
||||||
|
'IL' => 'Israel', |
||||||
|
'IT' => 'Italy', |
||||||
|
'JM' => 'Jamaica', |
||||||
|
'JP' => 'Japan', |
||||||
|
'JO' => 'Jordan', |
||||||
|
'KZ' => 'Kazakhstan', |
||||||
|
'KE' => 'Kenya', |
||||||
|
'KI' => 'Kiribati', |
||||||
|
'KW' => 'Kuwait', |
||||||
|
'KG' => 'Kyrgyzstan', |
||||||
|
'LA' => 'Laos', |
||||||
|
'LV' => 'Latvia', |
||||||
|
'LB' => 'Lebanon', |
||||||
|
'LS' => 'Lesotho', |
||||||
|
'LR' => 'Liberia', |
||||||
|
'LY' => 'Libya', |
||||||
|
'LI' => 'Liechtenstein', |
||||||
|
'LT' => 'Lithuania', |
||||||
|
'LU' => 'Luxembourg', |
||||||
|
'MO' => 'Macau', |
||||||
|
'MK' => 'Macedonia', |
||||||
|
'MG' => 'Madagascar', |
||||||
|
'MW' => 'Malawi', |
||||||
|
'MY' => 'Malaysia', |
||||||
|
'MV' => 'Maldives', |
||||||
|
'ML' => 'Mali', |
||||||
|
'MT' => 'Malta', |
||||||
|
'MH' => 'Marshall Islands', |
||||||
|
'MQ' => 'Martinique', |
||||||
|
'MR' => 'Mauritania', |
||||||
|
'MU' => 'Mauritius', |
||||||
|
'YT' => 'Mayotte', |
||||||
|
'MX' => 'Mexico', |
||||||
|
'FM' => 'Micronesia', |
||||||
|
'MD' => 'Moldova', |
||||||
|
'MC' => 'Monaco', |
||||||
|
'MN' => 'Mongolia', |
||||||
|
'ME' => 'Montenegro', |
||||||
|
'MS' => 'Montserrat', |
||||||
|
'MA' => 'Morocco', |
||||||
|
'MZ' => 'Mozambique', |
||||||
|
'MM' => 'Myanmar (Burma)', |
||||||
|
'NA' => 'Namibia', |
||||||
|
'NR' => 'Nauru', |
||||||
|
'NP' => 'Nepal', |
||||||
|
'NL' => 'Netherlands', |
||||||
|
'AN' => 'Netherlands Antilles', |
||||||
|
'NC' => 'New Caledonia', |
||||||
|
'NZ' => 'New Zealand', |
||||||
|
'NI' => 'Nicaragua', |
||||||
|
'NE' => 'Niger', |
||||||
|
'NG' => 'Nigeria', |
||||||
|
'NU' => 'Niue', |
||||||
|
'NF' => 'Norfolk Island', |
||||||
|
'KP' => 'North Korea', |
||||||
|
'MP' => 'Northern Mariana Islands', |
||||||
|
'NO' => 'Norway', |
||||||
|
'OM' => 'Oman', |
||||||
|
'PK' => 'Pakistan', |
||||||
|
'PW' => 'Palau', |
||||||
|
'PS' => 'Palestine', |
||||||
|
'PA' => 'Panama', |
||||||
|
'PG' => 'Papua New Guinea', |
||||||
|
'PY' => 'Paraguay', |
||||||
|
'PE' => 'Peru', |
||||||
|
'PH' => 'Philippines', |
||||||
|
'PN' => 'Pitcairn', |
||||||
|
'PL' => 'Poland', |
||||||
|
'PT' => 'Portugal', |
||||||
|
'PR' => 'Puerto Rico', |
||||||
|
'QA' => 'Qatar', |
||||||
|
'RE' => 'Reunion', |
||||||
|
'RO' => 'Romania', |
||||||
|
'RU' => 'Russia', |
||||||
|
'RW' => 'Rwanda', |
||||||
|
'SH' => 'Saint Helena', |
||||||
|
'KN' => 'Saint Kitts And Nevis', |
||||||
|
'LC' => 'Saint Lucia', |
||||||
|
'PM' => 'Saint Pierre And Miquelon', |
||||||
|
'VC' => 'Saint Vincent And The Grenadines', |
||||||
|
'SM' => 'San Marino', |
||||||
|
'ST' => 'Sao Tome And Principe', |
||||||
|
'SA' => 'Saudi Arabia', |
||||||
|
'SN' => 'Senegal', |
||||||
|
'SC' => 'Seychelles', |
||||||
|
'SL' => 'Sierra Leone', |
||||||
|
'SG' => 'Singapore', |
||||||
|
'SK' => 'Slovakia', |
||||||
|
'SI' => 'Slovenia', |
||||||
|
'SB' => 'Solomon Islands', |
||||||
|
'SO' => 'Somalia', |
||||||
|
'ZA' => 'South Africa', |
||||||
|
'GS' => 'South Georgia And South Sandwich Islands', |
||||||
|
'KR' => 'South Korea', |
||||||
|
'ES' => 'Spain', |
||||||
|
'LK' => 'Sri Lanka', |
||||||
|
'SD' => 'Sudan', |
||||||
|
'SR' => 'Suriname', |
||||||
|
'SJ' => 'Svalbard And Jan Mayen', |
||||||
|
'SZ' => 'Swaziland', |
||||||
|
'SE' => 'Sweden', |
||||||
|
'CH' => 'Switzerland', |
||||||
|
'SY' => 'Syria', |
||||||
|
'TW' => 'Taiwan', |
||||||
|
'TJ' => 'Tajikistan', |
||||||
|
'TZ' => 'Tanzania', |
||||||
|
'TH' => 'Thailand', |
||||||
|
'TG' => 'Togo', |
||||||
|
'TK' => 'Tokelau', |
||||||
|
'TO' => 'Tonga', |
||||||
|
'TT' => 'Trinidad And Tobago', |
||||||
|
'TN' => 'Tunisia', |
||||||
|
'TR' => 'Turkey', |
||||||
|
'TM' => 'Turkmenistan', |
||||||
|
'TC' => 'Turks And Caicos Islands', |
||||||
|
'TV' => 'Tuvalu', |
||||||
|
'UG' => 'Uganda', |
||||||
|
'UA' => 'Ukraine', |
||||||
|
'AE' => 'United Arab Emirates', |
||||||
|
'UK' => 'United Kingdom', |
||||||
|
'US' => 'United States', |
||||||
|
'UM' => 'United States Minor Outlying Islands', |
||||||
|
'UY' => 'Uruguay', |
||||||
|
'UZ' => 'Uzbekistan', |
||||||
|
'VU' => 'Vanuatu', |
||||||
|
'VA' => 'Vatican City', |
||||||
|
'VE' => 'Venezuela', |
||||||
|
'VN' => 'Vietnam', |
||||||
|
'VG' => 'Virgin Islands (British)', |
||||||
|
'VI' => 'Virgin Islands (US)', |
||||||
|
'WF' => 'Wallis And Futuna Islands', |
||||||
|
'EH' => 'Western Sahara', |
||||||
|
'WS' => 'Western Samoa', |
||||||
|
'YE' => 'Yemen', |
||||||
|
'YU' => 'Yugoslavia', |
||||||
|
'ZM' => 'Zambia', |
||||||
|
'ZW' => 'Zimbabwe' |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,166 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework\Arrays; |
||||||
|
|
||||||
|
class Mimes { |
||||||
|
|
||||||
|
/** |
||||||
|
* Fetch Mime Types |
||||||
|
* @return array of mime types |
||||||
|
*/ |
||||||
|
public static function mimesArray(): array { |
||||||
|
return [ |
||||||
|
'text' => [ |
||||||
|
'txt' => 'text/plain', |
||||||
|
], |
||||||
|
'web' => [ |
||||||
|
'htm' => 'text/html', |
||||||
|
'html' => 'text/html', |
||||||
|
'css' => 'text/css', |
||||||
|
'json' => 'application/json', |
||||||
|
'xml' => 'application/xml' |
||||||
|
], |
||||||
|
'images' => [ |
||||||
|
'png' => 'image/png', |
||||||
|
'jpe' => 'image/jpeg', |
||||||
|
'jpeg' => 'image/jpeg', |
||||||
|
'jpg' => 'image/jpeg', |
||||||
|
'gif' => 'image/gif', |
||||||
|
'bmp' => 'image/bmp', |
||||||
|
'ico' => 'image/vnd.microsoft.icon', |
||||||
|
'tiff' => 'image/tiff', |
||||||
|
'tif' => 'image/tiff', |
||||||
|
'svg' => 'image/svg+xml', |
||||||
|
'svgz' => 'image/svg+xml', |
||||||
|
'webp' => 'image/webp' |
||||||
|
], |
||||||
|
'archives' => [ |
||||||
|
'zip' => 'application/zip', |
||||||
|
'rar' => 'application/x-rar-compressed', |
||||||
|
'cab' => 'application/vnd.ms-cab-compressed', |
||||||
|
'gz' => 'application/gzip', |
||||||
|
'tgz' => 'application/x-compressed', |
||||||
|
'tar' => 'application/x-tar', |
||||||
|
'iso' => 'application/x-iso9660-image', |
||||||
|
'bz2' => 'application/x-bzip2', |
||||||
|
'lz' => 'application/x-lzip', |
||||||
|
'xz' => 'application/x-xz', |
||||||
|
'7z' => 'application/x-7z-compressed', |
||||||
|
], |
||||||
|
'audio' => [ |
||||||
|
'oga' => 'audio/ogg', 'ogg' => 'audio/ogg', 'opus' => 'audio/ogg', 'spx' => 'audio/ogg', |
||||||
|
'mp3' => 'audio/mpeg', |
||||||
|
'wav' => 'audio/x-wav' |
||||||
|
], |
||||||
|
'video' => [ |
||||||
|
'mpeg' => 'video/mpeg', 'mpg' => 'video/mpeg', 'mpe' => 'video/mpeg', |
||||||
|
'mp4' => 'video/mp4', |
||||||
|
'qt' => 'video/quicktime', |
||||||
|
'mov' => 'video/quicktime', |
||||||
|
'ogv' => 'video/ogg' |
||||||
|
], |
||||||
|
'adobe' => [ |
||||||
|
'pdf' => 'application/pdf', |
||||||
|
'psd' => 'image/vnd.adobe.photoshop', |
||||||
|
'ai' => 'application/postscript', |
||||||
|
'eps' => 'application/postscript', |
||||||
|
'ps' => 'application/postscript' |
||||||
|
], |
||||||
|
'office' => [ |
||||||
|
'doc' => 'application/msword', |
||||||
|
'rtf' => 'application/rtf', |
||||||
|
'xls' => 'application/vnd.ms-excel', |
||||||
|
'ppt' => 'application/vnd.ms-powerpoint', |
||||||
|
'odt' => 'application/vnd.oasis.opendocument.text', |
||||||
|
'ods' => 'application/vnd.oasis.opendocument.spreadsheet' |
||||||
|
] |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
public static function mimesDangerious(): array { |
||||||
|
return [ |
||||||
|
// Programming |
||||||
|
'js' => 'application/javascript', |
||||||
|
'php' => 'application/x-php-code', |
||||||
|
'py' => 'application/x-python-code', |
||||||
|
'pyc' => 'application/x-python-code', |
||||||
|
'pyo' => 'application/x-python-code', |
||||||
|
'torrent' => 'application/x-bittorrent', |
||||||
|
// Linux |
||||||
|
'deb' => 'application/vnd.debian.binary-package', |
||||||
|
'rpm' => 'application/x-redhat-package-manager', |
||||||
|
'bash' => 'application/x-shellscript', |
||||||
|
'sh' => 'application/x-sh', |
||||||
|
'jar' => 'application/java', |
||||||
|
'java' => 'application/java', |
||||||
|
// Windows |
||||||
|
'swf' => 'application/x-shockwave-flash', 'swfl' => 'application/x-shockwave-flash', |
||||||
|
'reg' => 'application/registry', |
||||||
|
'lnk' => 'application/shortcut', |
||||||
|
'inf' => 'application/config', |
||||||
|
'scf' => 'application/explorer', |
||||||
|
// A Monad script file. Monad was later renamed PowerShell. |
||||||
|
'msh' => 'shell', 'msh1' => 'shell', 'msh2' => 'shell', 'mshxml' => 'shell', 'msh1xml' => 'shell', 'msh2xml' => 'shell', |
||||||
|
// Power Shell |
||||||
|
'ps1' => 'shell', 'ps1xml' => 'shell', 'ps2' => 'shell', 'ps2xml' => 'shell', 'psc1' => 'shell', 'psc2' => 'shell', |
||||||
|
'wsc' => 'application/scripting-component', 'wsh' => 'application/scripting-host', 'ws' => 'application/scripting', 'wsf' => 'application/scripting', |
||||||
|
'jse' => 'application/javascript-encrypted', |
||||||
|
'vbe' => 'application/vbscript-encrypted', |
||||||
|
'vb' => 'application/visualbasic', |
||||||
|
'vbs' => 'application/visualbasic', |
||||||
|
'bat' => 'application/batch', |
||||||
|
'cmd' => 'application/batch', |
||||||
|
'exe' => 'application/x-msdownload', |
||||||
|
'pif' => 'application/executiable', |
||||||
|
'application' => 'application/oneclick', |
||||||
|
'gadget' => 'vista/desktop', |
||||||
|
'msi' => 'application/x-msdownload', |
||||||
|
'msp' => 'application/patch', |
||||||
|
'com' => 'application/dos', |
||||||
|
'scr' => 'application/screen-saver', |
||||||
|
'hta' => 'html/application', |
||||||
|
'cpl' => 'application/control-panel', |
||||||
|
'msc' => 'application/m-console', |
||||||
|
// Office 2007 Macros |
||||||
|
'docm' => 'macro/msword', |
||||||
|
'dotm' => 'macro/template', |
||||||
|
'xlsm' => 'macro/excel', |
||||||
|
'xltm' => 'macro', 'xlam' => 'macro', 'pptm' => 'macro', 'potm' => 'macro', 'ppam' => 'macro', 'ppsm' => 'macro', 'sldm' => 'marco' |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
public static function getMimeType(string $filename, array $allowed = ['all']) { |
||||||
|
$ext = strtolower(array_pop(explode('.', $filename))); |
||||||
|
$bad = self::mimesDangerious(); |
||||||
|
|
||||||
|
if (array_key_exists($ext, $bad)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
$mime_types = self::mimesArray(); |
||||||
|
|
||||||
|
$everyone = in_array('all', $allowed); |
||||||
|
|
||||||
|
foreach ($mime_types as $key => $mime) { |
||||||
|
if (($everyone || in_array($key, $allowed) ) && array_key_exists($ext, $mime)) { |
||||||
|
return $mime[$ext]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ($everyone) { |
||||||
|
if (function_exists('finfo_open')) { |
||||||
|
$finfo = finfo_open(FILEINFO_MIME); |
||||||
|
$mimetype = finfo_file($finfo, $filename); |
||||||
|
finfo_close($finfo); |
||||||
|
return $mimetype; |
||||||
|
} else { |
||||||
|
return 'application/octet-stream'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,45 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace IOcornerstone\Arrays\Mocking; |
||||||
|
|
||||||
|
use IOcornerstone\Framework\Common; |
||||||
|
|
||||||
|
class Phone { |
||||||
|
|
||||||
|
const TOLLFREE = [800, 844, 855, 866, 877, 888]; |
||||||
|
const AREACODES = [201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 212, 213, 214, 215, 216, 217, 218, 219, 220, 224, 225, 226, 228, 229, 231, 234, 236, 239, 240, 242, 246, 248, 249, 250, 251, 252, 253, 254, 256, 260, 262, 264, 267, 268, 269, 270, 272, 276, 281, 284, 289, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 323, 325, 330, 331, 334, 336, 337, 339, 340, 343, 345, 346, 347, 351, 352, 360, 361, 364, 365, 380, 385, 386, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 412, 413, 414, 415, 416, 417, 418, 419, 423, 424, 425, 430, 431, 432, 434, 435, 437, 438, 440, 441, 442, 443, 450, 458, 469, 470, 473, 475, 478, 479, 480, 484, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 512, 513, 514, 515, 516, 517, 518, 519, 520, 530, 531, 534, 539, 540, 541, 548, 551, 559, 561, 562, 563, 567, 570, 571, 573, 574, 575, 579, 580, 581, 585, 586, 587, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 612, 613, 614, 615, 616, 617, 618, 619, 620, 623, 626, 628, 629, 630, 631, 636, 639, 641, 646, 647, 649, 650, 651, 657, 660, 661, 662, 664, 667, 669, 670, 671, 678, 681, 682, 684, 701, 702, 703, 704, 705, 706, 707, 708, 709, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 724, 725, 727, 731, 732, 734, 737, 740, 743, 747, 754, 757, 758, 760, 762, 763, 765, 767, 769, 770, 772, 773, 774, 775, 778, 779, 780, 781, 782, 784, 785, 786, 787, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 812, 813, 814, 815, 816, 817, 818, 819, 825, 828, 829, 830, 831, 832, 843, 845, 847, 848, 849, 850, 854, 856, 857, 858, 859, 860, 862, 863, 864, 865, 867, 868, 869, 870, 872, 873, 876, 878, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 912, 913, 914, 915, 916, 917, 918, 919, 920, 925, 928, 929, 930, 931, 934, 936, 937, 938, 939, 940, 941, 947, 949, 951, 952, 954, 956, 959, 970, 971, 972, 973, 978, 979, 980, 984, 985, 989]; |
||||||
|
|
||||||
|
private static function rndA(array $a) { |
||||||
|
$array_count = Common::getCount($a); |
||||||
|
return $a[rand(0, $array_count - 1)]; |
||||||
|
} |
||||||
|
|
||||||
|
public static function getPhone(int $rows = 100, bool $formatted = true, bool $tollfree = false): array { |
||||||
|
$phoneNumbers = []; |
||||||
|
for($i =0; $i < $rows; $i++) { |
||||||
|
$use_tollfree = ($tollfree && rand(0,9) === 4) ? true : false; |
||||||
|
$randomNumber = (string) rand(1000, 9999); |
||||||
|
$prefix = (string) rand(111,899); |
||||||
|
$toll = ""; |
||||||
|
if ($use_tollfree) { |
||||||
|
$toll = (string) self::rndA(self::TOLLFREE); |
||||||
|
if ($formatted) { |
||||||
|
$phoneNumbers[] = "1-(" . $toll . ")" . $prefix . "-" . $randomNumber; |
||||||
|
} else { |
||||||
|
$phoneNumbers[] = "1" . $toll . $prefix . $randomNumber; |
||||||
|
} |
||||||
|
} else { |
||||||
|
$areacode = (string) self::rndA(self::AREACODES); |
||||||
|
if ($formatted) { |
||||||
|
$phoneNumbers[] = "(" . $areacode . ")" . $prefix . "-". $randomNumber; |
||||||
|
} else { |
||||||
|
$phoneNumbers[] = $areacode . $prefix . $randomNumber; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return $phoneNumbers; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,29 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework\Arrays; |
||||||
|
|
||||||
|
final class Shortn { |
||||||
|
|
||||||
|
public static function shortStreetSuffix($text) { |
||||||
|
$uspsAbbreviations = [ |
||||||
|
'AVENUE' => 'Ave', |
||||||
|
'BOULEVARD' => 'Blvd', |
||||||
|
'DRIVE' => 'Dr', |
||||||
|
'LANE' => 'Ln', |
||||||
|
'MOUNT' => 'Mt', |
||||||
|
'ROAD' => 'Rd', |
||||||
|
'STREET' => 'St', |
||||||
|
' NORTH ' => ' N. ', |
||||||
|
' SOUTH ' => ' S. ', |
||||||
|
' EAST ' => ' E. ', |
||||||
|
' WEST ' => ' W. ' |
||||||
|
]; |
||||||
|
foreach ($uspsAbbreviations as $key => $value) { |
||||||
|
$text = str_ireplace($key, $value, $text); |
||||||
|
} |
||||||
|
return $text; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,185 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework\Arrays; |
||||||
|
|
||||||
|
class ZipCodes { |
||||||
|
|
||||||
|
public static function getState(string $zipcode): array { |
||||||
|
|
||||||
|
/* Ensure we have exactly 5 characters to parse */ |
||||||
|
if (strlen($zipcode) !== 5) { |
||||||
|
return 'Must pass a 5-digit $zipcode.'; |
||||||
|
} |
||||||
|
|
||||||
|
$st = ''; |
||||||
|
$state = ''; |
||||||
|
|
||||||
|
/* Code cases alphabetized by state */ |
||||||
|
if ($zipcode >= 35000 && $zipcode <= 36999) { |
||||||
|
$st = 'AL'; |
||||||
|
$state = 'Alabama'; |
||||||
|
} else if ($zipcode >= 99500 && $zipcode <= 99999) { |
||||||
|
$st = 'AK'; |
||||||
|
$state = 'Alaska'; |
||||||
|
} else if ($zipcode >= 85000 && $zipcode <= 86999) { |
||||||
|
$st = 'AZ'; |
||||||
|
$state = 'Arizona'; |
||||||
|
} else if ($zipcode >= 71600 && $zipcode <= 72999) { |
||||||
|
$st = 'AR'; |
||||||
|
$state = 'Arkansas'; |
||||||
|
} else if ($zipcode >= 90000 && $zipcode <= 96699) { |
||||||
|
$st = 'CA'; |
||||||
|
$state = 'California'; |
||||||
|
} else if ($zipcode >= 80000 && $zipcode <= 81999) { |
||||||
|
$st = 'CO'; |
||||||
|
$state = 'Colorado'; |
||||||
|
} else if (($zipcode >= 6000 && $zipcode <= 6389) || ($zipcode >= 6391 && $zipcode <= 6999)) { |
||||||
|
$st = 'CT'; |
||||||
|
$state = 'Connecticut'; |
||||||
|
} else if ($zipcode >= 19700 && $zipcode <= 19999) { |
||||||
|
$st = 'DE'; |
||||||
|
$state = 'Delaware'; |
||||||
|
} else if ($zipcode >= 32000 && $zipcode <= 34999) { |
||||||
|
$st = 'FL'; |
||||||
|
$state = 'Florida'; |
||||||
|
} else if (($zipcode >= 30000 && $zipcode <= 31999) || ($zipcode >= 39800 && $zipcode <= 39999)) { |
||||||
|
$st = 'GA'; |
||||||
|
$state = 'Georgia'; |
||||||
|
} else if ($zipcode >= 96700 && $zipcode <= 96999) { |
||||||
|
$st = 'HI'; |
||||||
|
$state = 'Hawaii'; |
||||||
|
} else if ($zipcode >= 83200 && $zipcode <= 83999) { |
||||||
|
$st = 'ID'; |
||||||
|
$state = 'Idaho'; |
||||||
|
} else if ($zipcode >= 60000 && $zipcode <= 62999) { |
||||||
|
$st = 'IL'; |
||||||
|
$state = 'Illinois'; |
||||||
|
} else if ($zipcode >= 46000 && $zipcode <= 47999) { |
||||||
|
$st = 'IN'; |
||||||
|
$state = 'Indiana'; |
||||||
|
} else if ($zipcode >= 50000 && $zipcode <= 52999) { |
||||||
|
$st = 'IA'; |
||||||
|
$state = 'Iowa'; |
||||||
|
} else if ($zipcode >= 66000 && $zipcode <= 67999) { |
||||||
|
$st = 'KS'; |
||||||
|
$state = 'Kansas'; |
||||||
|
} else if ($zipcode >= 40000 && $zipcode <= 42999) { |
||||||
|
$st = 'KY'; |
||||||
|
$state = 'Kentucky'; |
||||||
|
} else if ($zipcode >= 70000 && $zipcode <= 71599) { |
||||||
|
$st = 'LA'; |
||||||
|
$state = 'Louisiana'; |
||||||
|
} else if ($zipcode >= 3900 && $zipcode <= 4999) { |
||||||
|
$st = 'ME'; |
||||||
|
$state = 'Maine'; |
||||||
|
} else if ($zipcode >= 20600 && $zipcode <= 21999) { |
||||||
|
$st = 'MD'; |
||||||
|
$state = 'Maryland'; |
||||||
|
} else if (($zipcode >= 1000 && $zipcode <= 2799) || ($zipcode == 5501) || ($zipcode == 5544 )) { |
||||||
|
$st = 'MA'; |
||||||
|
$state = 'Massachusetts'; |
||||||
|
} else if ($zipcode >= 48000 && $zipcode <= 49999) { |
||||||
|
$st = 'MI'; |
||||||
|
$state = 'Michigan'; |
||||||
|
} else if ($zipcode >= 55000 && $zipcode <= 56899) { |
||||||
|
$st = 'MN'; |
||||||
|
$state = 'Minnesota'; |
||||||
|
} else if ($zipcode >= 38600 && $zipcode <= 39999) { |
||||||
|
$st = 'MS'; |
||||||
|
$state = 'Mississippi'; |
||||||
|
} else if ($zipcode >= 63000 && $zipcode <= 65999) { |
||||||
|
$st = 'MO'; |
||||||
|
$state = 'Missouri'; |
||||||
|
} else if ($zipcode >= 59000 && $zipcode <= 59999) { |
||||||
|
$st = 'MT'; |
||||||
|
$state = 'Montana'; |
||||||
|
} else if ($zipcode >= 27000 && $zipcode <= 28999) { |
||||||
|
$st = 'NC'; |
||||||
|
$state = 'North Carolina'; |
||||||
|
} else if ($zipcode >= 58000 && $zipcode <= 58999) { |
||||||
|
$st = 'ND'; |
||||||
|
$state = 'North Dakota'; |
||||||
|
} else if ($zipcode >= 68000 && $zipcode <= 69999) { |
||||||
|
$st = 'NE'; |
||||||
|
$state = 'Nebraska'; |
||||||
|
} else if ($zipcode >= 88900 && $zipcode <= 89999) { |
||||||
|
$st = 'NV'; |
||||||
|
$state = 'Nevada'; |
||||||
|
} else if ($zipcode >= 3000 && $zipcode <= 3899) { |
||||||
|
$st = 'NH'; |
||||||
|
$state = 'New Hampshire'; |
||||||
|
} else if ($zipcode >= 7000 && $zipcode <= 8999) { |
||||||
|
$st = 'NJ'; |
||||||
|
$state = 'New Jersey'; |
||||||
|
} else if ($zipcode >= 87000 && $zipcode <= 88499) { |
||||||
|
$st = 'NM'; |
||||||
|
$state = 'New Mexico'; |
||||||
|
} else if (($zipcode >= 10000 && $zipcode <= 14999) || ($zipcode == 6390) || ($zipcode == 501) || ($zipcode == 544)) { |
||||||
|
$st = 'NY'; |
||||||
|
$state = 'New York'; |
||||||
|
} else if ($zipcode >= 43000 && $zipcode <= 45999) { |
||||||
|
$st = 'OH'; |
||||||
|
$state = 'Ohio'; |
||||||
|
} else if (($zipcode >= 73000 && $zipcode <= 73199) || ($zipcode >= 73400 && $zipcode <= 74999)) { |
||||||
|
$st = 'OK'; |
||||||
|
$state = 'Oklahoma'; |
||||||
|
} else if ($zipcode >= 97000 && $zipcode <= 97999) { |
||||||
|
$st = 'OR'; |
||||||
|
$state = 'Oregon'; |
||||||
|
} else if ($zipcode >= 15000 && $zipcode <= 19699) { |
||||||
|
$st = 'PA'; |
||||||
|
$state = 'Pennsylvania'; |
||||||
|
} else if ($zipcode >= 300 && $zipcode <= 999) { |
||||||
|
$st = 'PR'; |
||||||
|
$state = 'Puerto Rico'; |
||||||
|
} else if ($zipcode >= 2800 && $zipcode <= 2999) { |
||||||
|
$st = 'RI'; |
||||||
|
$state = 'Rhode Island'; |
||||||
|
} else if ($zipcode >= 29000 && $zipcode <= 29999) { |
||||||
|
$st = 'SC'; |
||||||
|
$state = 'South Carolina'; |
||||||
|
} else if ($zipcode >= 57000 && $zipcode <= 57999) { |
||||||
|
$st = 'SD'; |
||||||
|
$state = 'South Dakota'; |
||||||
|
} else if ($zipcode >= 37000 && $zipcode <= 38599) { |
||||||
|
$st = 'TN'; |
||||||
|
$state = 'Tennessee'; |
||||||
|
} else if (($zipcode >= 75000 && $zipcode <= 79999) || ($zipcode >= 73301 && $zipcode <= 73399) || ($zipcode >= 88500 && $zipcode <= 88599)) { |
||||||
|
$st = 'TX'; |
||||||
|
$state = 'Texas'; |
||||||
|
} else if ($zipcode >= 84000 && $zipcode <= 84999) { |
||||||
|
$st = 'UT'; |
||||||
|
$state = 'Utah'; |
||||||
|
} else if ($zipcode >= 5000 && $zipcode <= 5999) { |
||||||
|
$st = 'VT'; |
||||||
|
$state = 'Vermont'; |
||||||
|
} else if (($zipcode >= 20100 && $zipcode <= 20199) || ($zipcode >= 22000 && $zipcode <= 24699) || ($zipcode == 20598)) { |
||||||
|
$st = 'VA'; |
||||||
|
$state = 'Virgina'; |
||||||
|
} else if (($zipcode >= 20000 && $zipcode <= 20099) || ($zipcode >= 20200 && $zipcode <= 20599) || ($zipcode >= 56900 && $zipcode <= 56999)) { |
||||||
|
$st = 'DC'; |
||||||
|
$state = 'Washington DC'; |
||||||
|
} else if ($zipcode >= 98000 && $zipcode <= 99499) { |
||||||
|
$st = 'WA'; |
||||||
|
$state = 'Washington'; |
||||||
|
} else if ($zipcode >= 24700 && $zipcode <= 26999) { |
||||||
|
$st = 'WV'; |
||||||
|
$state = 'West Virginia'; |
||||||
|
} else if ($zipcode >= 53000 && $zipcode <= 54999) { |
||||||
|
$st = 'WI'; |
||||||
|
$state = 'Wisconsin'; |
||||||
|
} else if ($zipcode >= 82000 && $zipcode <= 83199) { |
||||||
|
$st = 'WY'; |
||||||
|
$state = 'Wyoming'; |
||||||
|
} else { |
||||||
|
$st = 'none'; |
||||||
|
$state = 'none'; |
||||||
|
return 'No state found matching' . $zipcode; |
||||||
|
} |
||||||
|
|
||||||
|
return ['abbr' => $st, 'state' => $state]; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,267 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
use IOcornerstone\Framework\{ |
||||||
|
Security, |
||||||
|
Common, |
||||||
|
String\StringFacade, |
||||||
|
}; |
||||||
|
|
||||||
|
class Assets |
||||||
|
{ |
||||||
|
public static function getVer(string $filename): string { |
||||||
|
if (self::attemptsRootDir($filename)) { |
||||||
|
return ""; |
||||||
|
} |
||||||
|
|
||||||
|
$safe_file = Security::filterUri($filename); |
||||||
|
if (! file_exists($safe_file)) { |
||||||
|
return "?ver=false"; |
||||||
|
} |
||||||
|
return "?ver=" . date('Y.m.d_H.i.s', filemtime($safe_file)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Check for / or absolute path not belonging to /var/www or PRJ:ROOT |
||||||
|
* @param string|null $path |
||||||
|
* @return bool |
||||||
|
*/ |
||||||
|
private static function attemptsRootDir(?string $path): bool { |
||||||
|
// up from src and back up to public |
||||||
|
$open_base_dir_path = dirname(BaseDir, 1) . '/public/'; |
||||||
|
if ($path === null || $path === '') { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (Common::getStringLeft($path, strlen($open_base_dir_path)) == $open_base_dir_path) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (Common::getStringLeft($path, 1) == '/') { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public static function getAjaxFiles(string $ajax_folder): string { |
||||||
|
if (self::attemptsRootDir($ajax_folder)) { |
||||||
|
return ""; |
||||||
|
} |
||||||
|
$safe_folder = Security::filterUri($ajax_folder); |
||||||
|
|
||||||
|
$ret = "var assets_files = [];" . PHP_EOL; |
||||||
|
$js = glob($safe_folder . "*.{js,css}", GLOB_BRACE); |
||||||
|
foreach($js as $file) { |
||||||
|
$ret .= 'assets_files.push({ filename: "' . basename($file) . '", ts: "' . self::getVer($file) . '" });' . PHP_EOL; |
||||||
|
} |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
public static function loadallCss(array $files, string $scope='project'): string { |
||||||
|
$ret = ''; |
||||||
|
foreach($files as $file=>$a_media) { |
||||||
|
$ret .= self::wrapCss($file, $scope, $a_media); |
||||||
|
} |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
public static function loadallJs(array $files, string $scope='project'): string { |
||||||
|
$ret = ''; |
||||||
|
foreach($files as $file=>$a) { |
||||||
|
$ret .= self::wrapJs($file, $scope, $a); |
||||||
|
} |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
public static function image(string $file, string $scope = '', array $a = array()): string { |
||||||
|
$more = ''; |
||||||
|
if (count($a)) { |
||||||
|
foreach ($a as $k => $v) { |
||||||
|
$more .= " {$k}=\"{$v}\""; |
||||||
|
} |
||||||
|
} |
||||||
|
$file = self::wrapAsset($file, $scope); |
||||||
|
if ($file === false) { |
||||||
|
return ''; |
||||||
|
} |
||||||
|
return "<img src=\"{$file}\" {$more}/>\r\n"; |
||||||
|
} |
||||||
|
|
||||||
|
public static function alert($msg) { |
||||||
|
return self::inlineJs('alert("'.$msg.'");'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Wrapper for JS/CSS assets for the page. |
||||||
|
* @param string $file or CDN |
||||||
|
* @param string $scope (project/framework/cdn) |
||||||
|
* @retval boolean|string false is not found, else Asset REF |
||||||
|
*/ |
||||||
|
public static function wrapAsset(string $file, string $scope = 'project') { |
||||||
|
$scope = StringFacade::strtolower($scope); |
||||||
|
if ($scope === 'cdn') { |
||||||
|
$proto = bootstrap\safer_io::get_clean_server_var('SERVER_PROTOCOL'); |
||||||
|
if ($proto === null) { |
||||||
|
$protocol = '://'; |
||||||
|
} else { |
||||||
|
$protocol = strtolower(substr($proto, 0, strpos($proto, '/'))) . '://'; |
||||||
|
} |
||||||
|
return (str_contains($file, '://') === true) ? $file : $protocol . $file; |
||||||
|
} |
||||||
|
if ($scope === 'project' || $scope === 'app') { |
||||||
|
$path = PROJECT_ASSETS_DIR . "/"; |
||||||
|
$ism = self::isMinified($path, $file); |
||||||
|
return ($ism !== false) ? PROJECT_ASSETS_BASE_REF . "/" . $ism : false; |
||||||
|
} |
||||||
|
if ($scope === 'assets') { |
||||||
|
$path = ASSETS_DIR . "/"; |
||||||
|
$ism = self::isMinified($path, $file); |
||||||
|
return ($ism !== false) ? ASSETS_BASE_REF . "/" . $ism : false; |
||||||
|
} |
||||||
|
return $file; |
||||||
|
} |
||||||
|
/** |
||||||
|
* Fetch Version of file if exists |
||||||
|
* @param string $file |
||||||
|
* @param string $scope |
||||||
|
* @return string|bool |
||||||
|
*/ |
||||||
|
private static function projectPaths(string $file, string $scope = 'project'): string | bool { |
||||||
|
$scope = stringFacade::strtolower($scope); |
||||||
|
if ($scope === 'cdn') { |
||||||
|
return ""; |
||||||
|
} else if ($scope === 'project' || $scope === 'app') { |
||||||
|
$path = PROJECT_ASSETS_DIR . "/"; |
||||||
|
} else if ($scope === 'assets') { |
||||||
|
$path = ASSETS_DIR . "/"; |
||||||
|
} else { |
||||||
|
return ""; |
||||||
|
} |
||||||
|
$check = self::is_minified($path, $file); |
||||||
|
return ($check !==false) ? self::get_ver($path . $check) : false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Wrapper to return the CSS href=file.. stylesheet code for use in page. |
||||||
|
* @param string $file - CSS file |
||||||
|
* @param string $media - default of all media |
||||||
|
* @retval string |
||||||
|
*/ |
||||||
|
public static function wrapCss(string $file, string $scope = 'project', array $a_media = array()): string { |
||||||
|
$more = ''; |
||||||
|
if (count($a_media)) { |
||||||
|
foreach ($a_media as $k => $v) { |
||||||
|
$more .= " {$k}=\"{$v}\""; |
||||||
|
} |
||||||
|
} else { |
||||||
|
$more .= " media=\"all\""; |
||||||
|
} |
||||||
|
|
||||||
|
$ver = self::projectPaths($file, $scope); |
||||||
|
$wrap_file = self::wrapAsset($file, $scope); |
||||||
|
if ($ver === false || $wrap_file === false) { |
||||||
|
return "<!-- CSS: {$file} not Found -->"; |
||||||
|
} |
||||||
|
return "<link rel=\"stylesheet\" href=\"{$wrap_file}{$ver}\" type=\"text/css\"{$more}/>\r\n"; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Wrapper to return the JavaScript src=file... code for use with page. |
||||||
|
* @param type $file - external JS file. |
||||||
|
* @retval string of script src=file |
||||||
|
*/ |
||||||
|
public static function wrapJs(string $file, string $scope = 'project', array $a = array()): string { |
||||||
|
$more = ''; |
||||||
|
if (count($a)) { |
||||||
|
foreach ($a as $k => $v) { |
||||||
|
$more .= (isset($k) && $k !=0 ) ? " {$k}=\"{$v}\"" : " {$v}"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$ver = self::projectPaths($file, $scope); |
||||||
|
$wrap_file = self::wrapAsset($file, $scope); |
||||||
|
if ($ver === false || $wrap_file === false) { |
||||||
|
return "<!-- Script: {$file} not Found -->"; |
||||||
|
} |
||||||
|
return "<script src=\"{$wrap_file}{$ver}\"{$more}></script>\r\n"; |
||||||
|
//return "<script type=\"text/javascript\">js_loader(\"{$file}\");</script>"; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Purpose: To do inline JavaScript. |
||||||
|
* @param type $code string of code to inline into page. |
||||||
|
* @retval type |
||||||
|
*/ |
||||||
|
public static function inlineJs(string $code): string { |
||||||
|
return "<script type=\"text/javascript\">\r\n//<![CDATA[\r\n {$code}\r\n //]]> \r\n </script>\r\n"; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Purpose: To execute this JavaScript code once JQuery is Ready(). |
||||||
|
* @param string $code to do once JQuery is Ready |
||||||
|
* @retval string wrapped in ready code... |
||||||
|
*/ |
||||||
|
public static function jqueryLoad(string $code): string { |
||||||
|
return "\r\n$(function() { \r\n \t {$code} \r\n }); \r\n"; |
||||||
|
} |
||||||
|
|
||||||
|
public static function isMinified(string $path, string $file) { |
||||||
|
if (self::attemptsRootDir($path)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
$safe_path = Security::filterUri($path); |
||||||
|
$safe_file = Security::filterUri($file); |
||||||
|
|
||||||
|
if (str_contains($safe_file, '.auto.js')) { |
||||||
|
$production = (isLive()); |
||||||
|
$safe_file = str_replace('.auto.js', '', $safe_file); |
||||||
|
if ( $production && file_exists($safe_path . $safe_file . '.min.js') ) { |
||||||
|
return $safe_file . '.min.js'; |
||||||
|
} |
||||||
|
return (file_exists($safe_path . $safe_file . '.js')) ? $safe_file . '.js' : false; |
||||||
|
} |
||||||
|
|
||||||
|
if (str_contains($safe_file, '.auto.css')) { |
||||||
|
$production = (isLive()); |
||||||
|
$safe_file = str_replace('.auto.css', '', $safe_file); |
||||||
|
if ( $production && file_exists($safe_path . $safe_file . '.min.css') ) { |
||||||
|
return $safe_file . '.min.css'; |
||||||
|
} |
||||||
|
return (file_exists($safe_path . $safe_file . '.css')) ? $safe_file . '.css' : false; |
||||||
|
} |
||||||
|
return ( file_exists($safe_path . $safe_file) ) ? $safe_file : false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* meta redirect when headers are already sent... |
||||||
|
* @param string url - site to do redirect on |
||||||
|
* @reval none |
||||||
|
*/ |
||||||
|
public static function gotoUrl(string $url): void { |
||||||
|
echo '<META http-equiv="refresh" content="0;URL=' . $url . '">'; |
||||||
|
exit; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Rediect to url and attempt to send via header. |
||||||
|
* @param string $url - site to do redirect for |
||||||
|
* @retval none - exits once done |
||||||
|
*/ |
||||||
|
public static function redirectUrl(string $url): void { |
||||||
|
$url = str_replace(array('&', "\n", "\r"), array('&', '', ''), $url); |
||||||
|
if (!headers_sent()) { |
||||||
|
header('Location: ' . $url); |
||||||
|
} else { |
||||||
|
self::goto_url($url); |
||||||
|
} |
||||||
|
exit; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,229 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornetstone\Framework\Attributes\Validators; |
||||||
|
|
||||||
|
use \Attribute; |
||||||
|
|
||||||
|
enum DEFAULT_VALUE { |
||||||
|
case do_error_on_null; |
||||||
|
case do_not_error_on_null; |
||||||
|
} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class Positive {} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class Required {} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class Email {} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class Min { |
||||||
|
public function __construct(public int|float $value) {} |
||||||
|
} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class Max { |
||||||
|
public function __construct(public int|float $value) {} |
||||||
|
} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class GreaterThan { |
||||||
|
public function __construct(public int|float $value) {} |
||||||
|
} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class LessThan { |
||||||
|
public function __construct(public int|float $value) {} |
||||||
|
} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class NumberRange { |
||||||
|
public function __construct( |
||||||
|
public int|float $min, |
||||||
|
public int|float $max |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class Between { |
||||||
|
public function __construct( |
||||||
|
public int|float $min, |
||||||
|
public int|float $max |
||||||
|
) {} |
||||||
|
} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class Same { |
||||||
|
public function __construct(public string $other) {} |
||||||
|
} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class Alphanumeric {} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class Secure {} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class ValidEmailDomain {} |
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||||
|
class ValidDomain {} |
||||||
|
|
||||||
|
class MyValidator { |
||||||
|
|
||||||
|
use \IOcornerstone\Framework\Trait\FormValidator; |
||||||
|
|
||||||
|
private array $errors = []; |
||||||
|
|
||||||
|
private function handleErrors(string $rule_name, string $name, $value, $params = [], array $messages = []) { |
||||||
|
// get the message rules |
||||||
|
$ruleMessages = array_filter($messages, fn($message) => is_string($message)); |
||||||
|
// overwrite the default message |
||||||
|
$validationErrors = array_merge(self::DEFAULT_VALIDATION_ERRORS, $ruleMessages); |
||||||
|
|
||||||
|
$fn = 'is_' . $rule_name; |
||||||
|
$field = $name; |
||||||
|
$data[$name] = $value; |
||||||
|
|
||||||
|
$callable = self::class . "::{$fn}"; |
||||||
|
|
||||||
|
$string_params = array_map('strval', $params); |
||||||
|
|
||||||
|
if (is_callable($callable)) { |
||||||
|
$pass = $callable($data, $field, ...$string_params); |
||||||
|
if (!$pass) { |
||||||
|
$lookfor = $messages[$field][$rule_name] ?? $validationErrors[$rule_name]; |
||||||
|
$new_params = array_values($params); |
||||||
|
$this->errors[$field] = sprintf( |
||||||
|
$lookfor, |
||||||
|
$field, |
||||||
|
...$params |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @param object $object |
||||||
|
* @param array $messages defined in trait |
||||||
|
* @param type $default null avoids an error on no-entry, false gives errors on no-entry |
||||||
|
* @return void |
||||||
|
*/ |
||||||
|
|
||||||
|
public function validate(object $object, array $messages = [], DEFAULT_VALUE $default_value = DEFAULT_VALUE::do_not_error_on_null): void { |
||||||
|
|
||||||
|
$handlers = [ |
||||||
|
Positive::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$this->handleErrors("positive", $name, $value, messages: $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
Required::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$this->handleErrors("required", $name, $value, messages: $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
Email::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$this->handleErrors("email", $name, $value, messages: $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
GreaterThan::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$params[] = $attr->value; |
||||||
|
$this->handleErrors("greaterThan", $name, $value, $params, $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
LessThan::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$params[] = $attr->value; |
||||||
|
$this->handleErrors("lessThan", $name, $value, $params, $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
Min::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$params[] = $attr->value; |
||||||
|
$this->handleErrors("min", $name, $value, $params, $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
Max::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$params[] = $attr->value; |
||||||
|
$this->handleErrors("max", $name, $value, $params, $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
NumberRange::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$params[] = $attr->min; |
||||||
|
$params[] = $attr->max; |
||||||
|
|
||||||
|
$this->handleErrors("numberRange", $name, $value, $params, $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
Between::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$params[] = $attr->min; |
||||||
|
$params[] = $attr->max; |
||||||
|
|
||||||
|
$this->handleErrors("between", $name, $value, $params, $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
Same::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$params[] = $attr->value; |
||||||
|
$this->handleErrors("same", $name, $value, $params, $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
Alphanumeric::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$this->handleErrors("alphanumeric", $name, $value, messages: $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
Secure::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$this->handleErrors("secure", $name, $value, messages: $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
ValidEmailDomain::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$this->handleErrors("validEmailDomain", $name, $value, messages: $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
ValidDomain::class => function (string $name, $value, $attr, array $messages) { |
||||||
|
$this->handleErrors("validDomain", $name, $value, messages: $messages); |
||||||
|
}, |
||||||
|
|
||||||
|
]; |
||||||
|
|
||||||
|
$default = match($default_value) { |
||||||
|
DEFAULT_VALUE::do_not_error_on_null => null, |
||||||
|
// Fail validation as user requested false on default |
||||||
|
DEFAULT_VALUE::do_error_on_null => false, |
||||||
|
default => null, // Pass validation as null or value unset |
||||||
|
}; |
||||||
|
|
||||||
|
$ref = new \ReflectionObject($object); |
||||||
|
|
||||||
|
foreach ($ref->getProperties() as $property) { |
||||||
|
$name = $property->getName(); |
||||||
|
$property->setAccessible(true); |
||||||
|
$value = $property->getValue($object) ?? $default; |
||||||
|
|
||||||
|
foreach ($property->getAttributes() as $attribute) { |
||||||
|
$class = $attribute->getName(); |
||||||
|
|
||||||
|
if (!isset($handlers[$class])) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
$handlers[$class]( |
||||||
|
$name, |
||||||
|
$value, |
||||||
|
$attribute->newInstance(), |
||||||
|
$messages |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function getErrors(): array { |
||||||
|
return $this->errors; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,176 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @link https://github.com/genert/bbcode/blob/master/src/Parser/BBCodeParser.php |
||||||
|
* @license The MIT License (MIT) |
||||||
|
* @copyright (c) Genert 2017 - present. |
||||||
|
* |
||||||
|
* Take BB code 2 HTML, to display output |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
final class BbCodeParser |
||||||
|
{ |
||||||
|
protected $parsers = [ |
||||||
|
'h1' => [ |
||||||
|
'pattern' => '/\[h1\](.*?)\[\/h1\]/s', |
||||||
|
'replace' => '<h1>$1</h1>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'h2' => [ |
||||||
|
'pattern' => '/\[h2\](.*?)\[\/h2\]/s', |
||||||
|
'replace' => '<h2>$1</h2>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'h3' => [ |
||||||
|
'pattern' => '/\[h3\](.*?)\[\/h3\]/s', |
||||||
|
'replace' => '<h3>$1</h3>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'h4' => [ |
||||||
|
'pattern' => '/\[h4\](.*?)\[\/h4\]/s', |
||||||
|
'replace' => '<h4>$1</h4>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'h5' => [ |
||||||
|
'pattern' => '/\[h5\](.*?)\[\/h5\]/s', |
||||||
|
'replace' => '<h5>$1</h5>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'h6' => [ |
||||||
|
'pattern' => '/\[h6\](.*?)\[\/h6\]/s', |
||||||
|
'replace' => '<h6>$1</h6>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'bold' => [ |
||||||
|
'pattern' => '/\[b\](.*?)\[\/b\]/s', |
||||||
|
'replace' => '<b>$1</b>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'italic' => [ |
||||||
|
'pattern' => '/\[i\](.*?)\[\/i\]/s', |
||||||
|
'replace' => '<i>$1</i>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'underline' => [ |
||||||
|
'pattern' => '/\[u\](.*?)\[\/u\]/s', |
||||||
|
'replace' => '<u>$1</u>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'strikethrough' => [ |
||||||
|
'pattern' => '/\[s\](.*?)\[\/s\]/s', |
||||||
|
'replace' => '<s>$1</s>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'quote' => [ |
||||||
|
'pattern' => '/\[quote\](.*?)\[\/quote\]/s', |
||||||
|
'replace' => '<blockquote>$1</blockquote>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'link' => [ |
||||||
|
'pattern' => '/\[url\](.*?)\[\/url\]/s', |
||||||
|
'replace' => '<a href="$1">$1</a>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'namedlink' => [ |
||||||
|
'pattern' => '/\[url\=(.*?)\](.*?)\[\/url\]/s', |
||||||
|
'replace' => '<a href="$1">$2</a>', |
||||||
|
'content' => '$2' |
||||||
|
], |
||||||
|
'image' => [ |
||||||
|
'pattern' => '/\[img\](.*?)\[\/img\]/s', |
||||||
|
'replace' => '<img src="$1">', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'orderedlistnumerical' => [ |
||||||
|
'pattern' => '/\[list=1\](.*?)\[\/list\]/s', |
||||||
|
'replace' => '<ol>$1</ol>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'orderedlistalpha' => [ |
||||||
|
'pattern' => '/\[list=a\](.*?)\[\/list\]/s', |
||||||
|
'replace' => '<ol type="a">$1</ol>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'unorderedlist' => [ |
||||||
|
'pattern' => '/\[list\](.*?)\[\/list\]/s', |
||||||
|
'replace' => '<ul>$1</ul>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'listitem' => [ |
||||||
|
'pattern' => '/\[\*\](.*)/', |
||||||
|
'replace' => '<li>$1</li>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'code' => [ |
||||||
|
'pattern' => '/\[code\](.*?)\[\/code\]/s', |
||||||
|
'replace' => '<code>$1</code>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'youtube' => [ |
||||||
|
'pattern' => '/\[youtube\](.*?)\[\/youtube\]/s', |
||||||
|
'replace' => '<iframe width="560" height="315" src="//www.youtube-nocookie.com/embed/$1" frameborder="0" allowfullscreen></iframe>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'sub' => [ |
||||||
|
'pattern' => '/\[sub\](.*?)\[\/sub\]/s', |
||||||
|
'replace' => '<sub>$1</sub>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'sup' => [ |
||||||
|
'pattern' => '/\[sup\](.*?)\[\/sup\]/s', |
||||||
|
'replace' => '<sup>$1</sup>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'small' => [ |
||||||
|
'pattern' => '/\[small\](.*?)\[\/small\]/s', |
||||||
|
'replace' => '<small>$1</small>', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'table' => [ |
||||||
|
'pattern' => '/\[table\](.*?)\[\/table\]/s', |
||||||
|
'replace' => '<table>$1</table>', |
||||||
|
'content' => '$1', |
||||||
|
], |
||||||
|
'table-row' => [ |
||||||
|
'pattern' => '/\[tr\](.*?)\[\/tr\]/s', |
||||||
|
'replace' => '<tr>$1</tr>', |
||||||
|
'content' => '$1', |
||||||
|
], |
||||||
|
'table-data' => [ |
||||||
|
'pattern' => '/\[td\](.*?)\[\/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 stripTags(string $source): string { |
||||||
|
foreach ($this->parsers as $name => $parser) { |
||||||
|
$source = $this->searchAndReplace($parser['pattern'] . 'i', $parser['content'], $source); |
||||||
|
} |
||||||
|
|
||||||
|
return $source; |
||||||
|
} |
||||||
|
|
||||||
|
public function parse(string $source, $caseInsensitive = null): string { |
||||||
|
$caseInsensitive = $caseInsensitive === self::CASE_INSENSITIVE ? true : false; |
||||||
|
|
||||||
|
foreach ($this->parsers as $name => $parser) { |
||||||
|
$pattern = ($caseInsensitive) ? $parser['pattern'] . 'i' : $parser['pattern']; |
||||||
|
|
||||||
|
$source = $this->searchAndReplace($pattern, $parser['replace'], $source); |
||||||
|
} |
||||||
|
|
||||||
|
return $source; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
/** |
||||||
|
* @template TItem |
||||||
|
*/ |
||||||
|
final readonly class Collection |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @param array<int, TItem> $items |
||||||
|
*/ |
||||||
|
public function __construct( |
||||||
|
private array $items |
||||||
|
) { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return array<int, TItem> |
||||||
|
*/ |
||||||
|
public function all(): array { |
||||||
|
return $this->items; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
class Dollars |
||||||
|
{ |
||||||
|
private function moneyAsFloat(Number $money): float { |
||||||
|
// Remove any existing currency symbols and thousands separators |
||||||
|
$t = preg_replace('/[^\d.-]/', '', $money); |
||||||
|
return (float)$t; |
||||||
|
} |
||||||
|
|
||||||
|
public function formatAsUSD(Number $money): string { |
||||||
|
return '$' . number_format($this->moneyAsFloat($money), 2, '.', ','); |
||||||
|
} |
||||||
|
|
||||||
|
public function intl_USD(Number $money): string|false { |
||||||
|
if (class_exists('NumberFormatter')) { |
||||||
|
$formatter = new \NumberFormatter('en_US', \NumberFormatter::CURRENCY); |
||||||
|
return $formatter->formatCurrency($this->moneyAsFloat($money), 'USD'); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/* |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework\Enum; |
||||||
|
|
||||||
|
enum CompressionMethod: string |
||||||
|
{ |
||||||
|
case GZIP = 'gzip'; |
||||||
|
case DEFLATE = 'deflate'; |
||||||
|
case ZLIB = 'zlib'; |
||||||
|
} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/* |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework\Enum; |
||||||
|
|
||||||
|
enum ViewType: string |
||||||
|
{ |
||||||
|
case LIQUID = ".tpl"; |
||||||
|
case TWIG = ".twig"; |
||||||
|
case PHP = ".php"; |
||||||
|
|
||||||
|
public static function get_file_extension_for(ViewType $type): string { |
||||||
|
return $type->value; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,53 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
use IOcornerstone\Framework\Enum\CompressionMethod as Method; |
||||||
|
|
||||||
|
final class GzCompression |
||||||
|
{ |
||||||
|
|
||||||
|
public function __construct( |
||||||
|
public Method $method = Method::DEFLATE, |
||||||
|
public int $compressionLevel = 4, |
||||||
|
public bool $useCompression = true |
||||||
|
) |
||||||
|
{} |
||||||
|
|
||||||
|
public function compress(string $data): string|false |
||||||
|
{ |
||||||
|
if ($this->useCompression === false) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
$level = ($this->compressionLevel < 10 && $this->compressionLevel > 0) ? $this->compressionLevel : 4; |
||||||
|
|
||||||
|
$c = match ($this->method) { |
||||||
|
Method::DEFLATE => gzdeflate($data, $level), |
||||||
|
Method::GZIP => gzencode($data, $level), |
||||||
|
Method::ZLIB => gzcompress($data, $level), |
||||||
|
}; |
||||||
|
return base64_encode($c); |
||||||
|
} |
||||||
|
|
||||||
|
public function decompress(string $compressed_data): string|false |
||||||
|
{ |
||||||
|
if ($this->useCompression === false) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
$d = base64_decode($compressed_data); |
||||||
|
return match ($this->method) { |
||||||
|
Method::DEFLATE => gzinflate($d), |
||||||
|
Method::GZIP => gzdecode($d), |
||||||
|
Method::ZLIB => gzuncompress($d), |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,229 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
final class Html |
||||||
|
{ |
||||||
|
const ECHO = true; |
||||||
|
|
||||||
|
// Escape HTML output |
||||||
|
private static function h(string $string): string { |
||||||
|
return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8'); |
||||||
|
} |
||||||
|
|
||||||
|
public static function doEcho(?string $input, bool $echo = self::ECHO): ?string { |
||||||
|
if ($echo) { |
||||||
|
echo $input; |
||||||
|
return null; |
||||||
|
} |
||||||
|
return $input; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 doOptions( |
||||||
|
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 .= " ". self::h($k) . "=\"" . self::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 = self::h($value); |
||||||
|
$text = self::h($text); |
||||||
|
} |
||||||
|
$values .= "<option value=\"{$value}\" " . |
||||||
|
$selected . " " . $more . ">{$text}</option>"; |
||||||
|
} |
||||||
|
return $values; |
||||||
|
} |
||||||
|
|
||||||
|
private static function makeHeadersOnTable(array $header_fields, array $options = []) { |
||||||
|
$echo = $options['echo'] ?? self::ECHO; |
||||||
|
$nl = PHP_EOL; |
||||||
|
$default_table_setup = "border='1' cellpadding='1' cellspacing='1'"; |
||||||
|
$table = $options['table_setup'] ?? $default_table_setup; |
||||||
|
$escape = $options['escape'] ?? true; |
||||||
|
$th = $options['th'] ?? ""; |
||||||
|
$th_class = (! empty($th)) ? " class=\"$th\" " : ""; |
||||||
|
|
||||||
|
$ret = self::doEcho("{$nl}<table {$table}>{$nl}", $echo); |
||||||
|
$ret .= self::doEcho("\t<tr>{$nl}", $echo); |
||||||
|
foreach($header_fields as $name=>$header_field) { |
||||||
|
$skipping = $options['skip_output'][$name] ?? false; |
||||||
|
if ($skipping) continue; |
||||||
|
|
||||||
|
$field = ($escape) ? self::h($header_field) : $header_field; |
||||||
|
$ret .= self::doEcho("\t\t<th{$th_class}>{$field}</th>{$nl}", $echo); |
||||||
|
} |
||||||
|
$ret .= self::doEcho("\t</tr>{$nl}", $echo); |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 showTable( |
||||||
|
array $header_fields, |
||||||
|
array $fields, |
||||||
|
array $options = [] |
||||||
|
): ?string { |
||||||
|
$echo = $options['echo'] ?? self::ECHO; |
||||||
|
$nl = PHP_EOL; |
||||||
|
$ret = self::makeHeadersOnTable($header_fields, $options); |
||||||
|
$escape = $options['escape'] ?? true; |
||||||
|
foreach($fields as $field) { |
||||||
|
$ret .= self::doEcho("\t<tr>{$nl}", $echo); |
||||||
|
foreach($field as $td) { |
||||||
|
$cell = ($escape) ? self::h($td) : $td; |
||||||
|
$ret .= self::doEcho("\t\t<td>{$cell}</td>{$nl}", $echo); |
||||||
|
} |
||||||
|
$ret .= self::doEcho("\t</tr>{$nl}", $echo); |
||||||
|
} |
||||||
|
$ret .= self::doEcho("</table>{$nl}", $echo); |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
private static function makeCellTableHelperForIO(\Iterator $record, array $options = []): array { |
||||||
|
$echo = $options['echo'] ?? self::ECHO; |
||||||
|
$escape = $options['escape'] ?? true; |
||||||
|
$cell_class = $options['cell_class'] ?? ""; |
||||||
|
$row_class = $options['row_class'] ?? ""; |
||||||
|
$cell_class_tag = (! empty($cell_class)) ? " class=\"$cell_class\" " : ""; |
||||||
|
$row_class_tag = (! empty($row_class)) ? " class=\"$row_class\" " : ""; |
||||||
|
$nl = PHP_EOL; |
||||||
|
$ret = self::doEcho("\t<tr{$row_class_tag}>{$nl}", $echo); |
||||||
|
|
||||||
|
$errors = []; |
||||||
|
foreach($record as $html) { |
||||||
|
$key = $html['name'] ?? ""; |
||||||
|
$value = $html['html']; |
||||||
|
|
||||||
|
if (\CodeHydrater\common::get_count($html['errors'])) { |
||||||
|
$errors[$key] = $html['errors'][$key]; |
||||||
|
} |
||||||
|
|
||||||
|
$td = $value ?? ""; |
||||||
|
if (is_string($td)) { |
||||||
|
$cell = ($escape) ? self::h($td) : $td; |
||||||
|
} else { |
||||||
|
$cell = (string) $td; |
||||||
|
} |
||||||
|
$ret .= self::doEcho("\t\t<td{$cell_class_tag}>{$cell}</td>{$nl}", $echo); |
||||||
|
} |
||||||
|
$ret .= self::doEcho("\t</tr>{$nl}", $echo); |
||||||
|
return ['errors' => $errors, 'data' => $ret]; |
||||||
|
} |
||||||
|
|
||||||
|
private static function makeCellTableHelper(array $record, array $options = []): ?string { |
||||||
|
$echo = $options['echo'] ?? self::ECHO; |
||||||
|
$escape = $options['escape'] ?? true; |
||||||
|
$cell_class = $options['cell_class'] ?? ""; |
||||||
|
$row_class = $options['row_class'] ?? ""; |
||||||
|
$cell_class_tag = (! empty($cell_class)) ? " class=\"$cell_class\" " : ""; |
||||||
|
$row_class_tag = (! empty($row_class)) ? " class=\"$row_class\" " : ""; |
||||||
|
$nl = PHP_EOL; |
||||||
|
$ret = self::doEcho("\t<tr{$row_class_tag}>{$nl}", $echo); |
||||||
|
foreach($record as $key => $value) { |
||||||
|
$skipping = $options['skip_output'][$key] ?? false; |
||||||
|
if ($skipping) continue; |
||||||
|
|
||||||
|
if (! is_string($key)) { |
||||||
|
continue; // Remove Duplicate records |
||||||
|
} |
||||||
|
$td = $value ?? ""; |
||||||
|
if (is_string($td)) { |
||||||
|
$cell = ($escape) ? self::h($td) : $td; |
||||||
|
} else { |
||||||
|
$cell = (string) $td; |
||||||
|
} |
||||||
|
$ret .= self::doEcho("\t\t<td{$cell_class_tag}>{$cell}</td>{$nl}", $echo); |
||||||
|
} |
||||||
|
$ret .= self::doEcho("\t</tr>{$nl}", $echo); |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generators use Memory in an Effient way!!!! So use this. |
||||||
|
* @param array $header_fields |
||||||
|
* @param array $db_field_names |
||||||
|
* @param $records, note: \Iterator not always can be used here |
||||||
|
* @param bool $escape |
||||||
|
* @return void |
||||||
|
*/ |
||||||
|
public static function showTableFrom( |
||||||
|
array $header_fields, |
||||||
|
$records, |
||||||
|
array $options = [], |
||||||
|
) { |
||||||
|
$echo = $options['echo'] ?? self::ECHO; |
||||||
|
$ret = self::makeHeadersOnTable($header_fields, $options); |
||||||
|
switch($options['helper']) { |
||||||
|
case "single-row": |
||||||
|
$ret .= self::makeCellTableHelper($records, $options); |
||||||
|
break; |
||||||
|
case "io": |
||||||
|
$ret = self::makeCellTableHelperForIO($records, $options); |
||||||
|
$ret['data'] .= self::doEcho($ret['data'] . "</table>" . PHP_EOL); |
||||||
|
return $ret; |
||||||
|
default: |
||||||
|
foreach($records as $record) { |
||||||
|
self::makeCellTableHelper($record, $options); |
||||||
|
} |
||||||
|
} |
||||||
|
$ret .= self::doEcho("</table>" . PHP_EOL, $echo); |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
public static function showErrors( |
||||||
|
?array $errors = [], |
||||||
|
array $options = [] |
||||||
|
): ?string { |
||||||
|
$echo = $options['echo'] ?? self::ECHO; |
||||||
|
$ret = ""; |
||||||
|
if ($errors) { |
||||||
|
$message = "Please correct the following errors: "; |
||||||
|
$i = 0; |
||||||
|
foreach($errors as $error) { |
||||||
|
$i++; |
||||||
|
$message .= "<br/>{$i}) " . $error; |
||||||
|
} |
||||||
|
$ret .= self::doEcho($message, $echo); |
||||||
|
} |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,435 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
use IOcornerstone\Framework\{ |
||||||
|
Assets, |
||||||
|
Configure, |
||||||
|
Common |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* Description of htmlDocument |
||||||
|
* |
||||||
|
* @author Robert Strutts |
||||||
|
*/ |
||||||
|
class HtmlDocument |
||||||
|
{ |
||||||
|
private $title = ''; |
||||||
|
private $author = ''; |
||||||
|
private $description = ''; |
||||||
|
private $keywords = ''; |
||||||
|
private $robots = ''; |
||||||
|
private $head = ''; |
||||||
|
private $header = ''; |
||||||
|
private $body = ''; |
||||||
|
private $footer = ''; |
||||||
|
private $jsOnReady = ''; |
||||||
|
private $styles = ''; |
||||||
|
private $scripts = ''; |
||||||
|
private $mainStyles = ''; |
||||||
|
private $mainScripts = ''; |
||||||
|
private $activeCrumb = ''; |
||||||
|
private $breadcrumb = array(); |
||||||
|
|
||||||
|
public function __construct() { |
||||||
|
$this->title = Configure::get('html', 'title') ?? ''; |
||||||
|
$this->author = Configure::get('html', 'author') ?? ''; |
||||||
|
$this->footer = Configure::get('html', 'footer') ?? ''; |
||||||
|
$this->keywords = Configure::get('html', 'keywords') ?? ''; |
||||||
|
$this->description = Configure::get('html', 'description') ?? ''; |
||||||
|
$this->robots = Configure::get('html', 'robots'); |
||||||
|
$css = Configure::get('html', 'css'); |
||||||
|
if (Common::getCount($css) > 0) { |
||||||
|
foreach($css as $file=>$path) { |
||||||
|
if (is_array($file)) continue; |
||||||
|
if (is_array($path)) { |
||||||
|
if (isset($path['path'])) { |
||||||
|
$pathType = $path['path']; |
||||||
|
unset($path['path']); |
||||||
|
} else { |
||||||
|
$pathType = "project"; |
||||||
|
} |
||||||
|
$this->addCss($file, $pathType, $path); |
||||||
|
} else { |
||||||
|
$this->addCss($file, $path); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
$js = Configure::get('html', 'javascript'); |
||||||
|
if (Common::getCount($js) >0) { |
||||||
|
foreach($js as $file=>$path) { |
||||||
|
if (is_array($file)) continue; |
||||||
|
if (is_array($path)) { |
||||||
|
if (isset($path['path'])) { |
||||||
|
$pathType = $path['path']; |
||||||
|
unset($path['path']); |
||||||
|
} else { |
||||||
|
$pathType = "project"; |
||||||
|
} |
||||||
|
$this->addJs($file, $pathType, $path); |
||||||
|
} else { |
||||||
|
$this->addJs($file, $path); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function clearCss(): void { |
||||||
|
$this->styles = ''; |
||||||
|
} |
||||||
|
|
||||||
|
public function clearJs(): void { |
||||||
|
$this->scripts = ''; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set both Title and Header for HTML |
||||||
|
* @param string $title |
||||||
|
*/ |
||||||
|
public function setTitleAndHeader(string $title): void { |
||||||
|
$this->title = $title; |
||||||
|
$this->header = $title; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set Author for HTML |
||||||
|
* @param string $title |
||||||
|
*/ |
||||||
|
public function setAuthor(string $author): void { |
||||||
|
$this->author = $author; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set Title for HTML |
||||||
|
* @param string $title |
||||||
|
*/ |
||||||
|
public function setTitle(string $title): void { |
||||||
|
$this->title = $title; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set Header for HTML |
||||||
|
* @param string $header |
||||||
|
*/ |
||||||
|
public function setHeader(string $header): void { |
||||||
|
$this->header = $header; |
||||||
|
} |
||||||
|
|
||||||
|
public function setHead(string $head): void { |
||||||
|
$this->head = $head; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set Footer for HTML |
||||||
|
* @param string $footer |
||||||
|
*/ |
||||||
|
public function setFooter(string $footer): void { |
||||||
|
// $this->add_css('footer.css', 'project'); |
||||||
|
$this->footer = $footer; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set Description for HTML |
||||||
|
* @param string $description |
||||||
|
*/ |
||||||
|
public function setDescription(string $description): void { |
||||||
|
$this->description = $description; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set Keywords for HTML |
||||||
|
* @param string $keywords |
||||||
|
*/ |
||||||
|
public function setKeywords(string $keywords): void { |
||||||
|
$this->keywords = $keywords; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set Robots for HTML |
||||||
|
* @param string $robot |
||||||
|
*/ |
||||||
|
public function setRobots(string $robot): void { |
||||||
|
$this->robots = $robot; |
||||||
|
} |
||||||
|
|
||||||
|
public function setBody(string $body): void { |
||||||
|
$this->body = $body; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set Active BreadCrumb in HTML |
||||||
|
* @param string $active |
||||||
|
*/ |
||||||
|
public function setActiveCrumb(string $active): void { |
||||||
|
$this->activeCrumb = $active; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set BreadCrumbs using array (HyperLink => Name of Crumb) |
||||||
|
* @param array $crumbs Array(href => name) |
||||||
|
*/ |
||||||
|
public function setBreadcrumbs(array $crumbs): void { |
||||||
|
$this->breadcrumb = $crumbs; |
||||||
|
} |
||||||
|
|
||||||
|
public function setAssetsFromArray(array $files, string $which, string $scope = 'project'): void { |
||||||
|
foreach($files as $file => $a) { |
||||||
|
switch($which) { |
||||||
|
case 'main_css': |
||||||
|
$this->addMainCss($file, $scope, $a); |
||||||
|
break; |
||||||
|
case 'css': |
||||||
|
$this->addCss($file, $scope, $a); |
||||||
|
break; |
||||||
|
case 'main_js': |
||||||
|
$this->addMainJs($file, $scope, $a); |
||||||
|
break; |
||||||
|
case 'js': |
||||||
|
$this->addJs($file, $scope, $a); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function missingFile(string $file, string $scope, string $kind) { |
||||||
|
$failed = strtoupper($kind) . " filename of {$file} - {$scope} Asset Failed to Load!"; |
||||||
|
$this->addToJavascript("console.log(\"%c {$failed}\", \"color: red\")"); |
||||||
|
$comment = "<!-- {$failed} -->"; |
||||||
|
if ($kind === "css") { |
||||||
|
$this->styles .= $comment; |
||||||
|
} else if ($kind === "main_css") { |
||||||
|
$this->mainStyles .= $comment; |
||||||
|
} else if ($kind === "js") { |
||||||
|
$this->scripts .= $comment; |
||||||
|
} else if ($kind === "main_js") { |
||||||
|
$this->mainScripts .= $comment; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add CSS stylesheet to HTML under main CSS |
||||||
|
* @param string $file |
||||||
|
* @param string $scope (project, framework, cdn) |
||||||
|
* @return bool was successful |
||||||
|
*/ |
||||||
|
public function addCss(string $file, string $scope = 'project', array $a = array()): bool { |
||||||
|
$css = Assets::wrapAsset($file, $scope); |
||||||
|
if ($css === false) { |
||||||
|
$this->missingFile($file, $scope, "css"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
$this->styles .= Assets::wrapCss($file, $scope, $a); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add JS JavaScript to HTML under main JS |
||||||
|
* @param string $file |
||||||
|
* @param string $scope (project, framework, cdn) |
||||||
|
* @return bool was successful |
||||||
|
*/ |
||||||
|
public function addJs(string $file, string $scope = 'project', array $a = array()): bool { |
||||||
|
$js = Assets::wrapAsset($file, $scope); |
||||||
|
if ($js === false) { |
||||||
|
$this->js_log($file . " - {$scope} Asset Failed to Load!"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
$this->scripts .= Assets::wrapJs($file, $scope, $a); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add CSS stylesheet to HTML towards top of HTML for CSS |
||||||
|
* @param string $file |
||||||
|
* @param string $scope (project, framework, cdn) |
||||||
|
* @return bool was successful |
||||||
|
*/ |
||||||
|
public function addMainCss(string $file, string $scope = 'project', array $a = array()): bool { |
||||||
|
$css = Assets::wrapAsset($file, $scope); |
||||||
|
if ($css === false) { |
||||||
|
$this->js_log($file . " - {$scope} Asset Failed to Load!"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
$this->mainStyles .= Assets::wrapCss($file, $scope, $a); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add JavaScript to HTML towards top of HTML for JS |
||||||
|
* @param string $file |
||||||
|
* @param string $scope (project, framework, cdn) |
||||||
|
* @return bool was successful |
||||||
|
*/ |
||||||
|
public function addMainJs(string $file, string $scope = 'project', array $a = array()): bool { |
||||||
|
$js = Assets::wrapAsset($file, $scope); |
||||||
|
if ($js === false) { |
||||||
|
$this->js_log($file . " - {$scope} Asset Failed to Load!"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
$this->mainScripts .= Assets::wrapJs($file, $scope, $a); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds JavaScript code to called after JQuery is ready. |
||||||
|
* @param string $code |
||||||
|
*/ |
||||||
|
//public function add_js_onready(string $code): void { |
||||||
|
// $this->js_onready .= \px_inline_js(\px_jquery_load($code)); |
||||||
|
//} |
||||||
|
|
||||||
|
/** |
||||||
|
* Log to JavaScript Console under Chrome Browser |
||||||
|
* @param string $log |
||||||
|
*/ |
||||||
|
public function jsLog(string $log): void { |
||||||
|
$this->addToJavascript("console.log('{$log}');"); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Place JavaScript in HTML |
||||||
|
* @param string $js |
||||||
|
*/ |
||||||
|
public function addToJavascript(string $js): void { |
||||||
|
if (! empty($js)) { |
||||||
|
$this->scripts .= Assets::inlineJs($js); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Use CSS/JS for Database SSP |
||||||
|
public function datatables_code(): void { |
||||||
|
$this->addCss('datatables/datatables.min.css', 'cl'); |
||||||
|
$this->addJs('datatables/datatables_no_jquery.min.js', 'cl'); |
||||||
|
} |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML Author |
||||||
|
* @return string HTML Author |
||||||
|
*/ |
||||||
|
public function getAuthor(): string { |
||||||
|
return $this->author; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML TITLE |
||||||
|
* @return string HTML TITLE |
||||||
|
*/ |
||||||
|
public function getTitle(): string { |
||||||
|
return $this->title; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML Header |
||||||
|
* @return string HTML Header |
||||||
|
*/ |
||||||
|
public function getHeader(): string { |
||||||
|
return $this->header; |
||||||
|
} |
||||||
|
|
||||||
|
public function getBody(): string { |
||||||
|
return $this->body; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML Footer |
||||||
|
* @return string HTML Footer |
||||||
|
*/ |
||||||
|
public function getFooter(): string { |
||||||
|
return $this->footer; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML Meta data for Description |
||||||
|
* @return string HTML Meta Description |
||||||
|
*/ |
||||||
|
public function getDescription(): string { |
||||||
|
return $this->description; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML Meta data for Keywords |
||||||
|
* @return string HTML Meta Keywords |
||||||
|
*/ |
||||||
|
public function getKeywords(): string { |
||||||
|
return $this->keywords; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML Meta data for Robots |
||||||
|
* @return string HTML Meta Robots |
||||||
|
*/ |
||||||
|
public function getRobots(): ?string { |
||||||
|
return $this->robots; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML CSS |
||||||
|
* @return string HTML CSS |
||||||
|
*/ |
||||||
|
public function getStyles(): string { |
||||||
|
return $this->styles; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML JavaScripts |
||||||
|
* @return string HTML JS |
||||||
|
*/ |
||||||
|
public function getScripts(): string { |
||||||
|
return $this->scripts; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML main CSS @Top |
||||||
|
* @return string HTML CSS |
||||||
|
*/ |
||||||
|
public function getMainStyles(): string { |
||||||
|
return $this->mainStyles; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML main JS @Top |
||||||
|
* @return string HTML JavaScript |
||||||
|
*/ |
||||||
|
public function getMainScripts(): string { |
||||||
|
return $this->mainScripts; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML JS after main JS |
||||||
|
* @return string HTML JS |
||||||
|
*/ |
||||||
|
public function getJsOnReady(): string { |
||||||
|
return $this->jsOnReady; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML Active BreadCrumb |
||||||
|
* @return string HTML Active BreadCrumb |
||||||
|
*/ |
||||||
|
public function getActiveCrumb(): string { |
||||||
|
return $this->activeCrumb; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used by Template file to render HTML BreadCrumbs |
||||||
|
* @return string HTML BreadCrumbs |
||||||
|
*/ |
||||||
|
public function getBreadcrumbs(): array { |
||||||
|
return $this->breadcrumb; |
||||||
|
} |
||||||
|
|
||||||
|
public function getHead(): string { |
||||||
|
return $this->head; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,175 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @link https://github.com/genert/bbcode/blob/master/src/Parser/HTMLParser.php |
||||||
|
* @license The MIT License (MIT) |
||||||
|
* @copyright (c) Genert 2017 - present. |
||||||
|
* |
||||||
|
* Take HTML 2 BB code .. to save into db |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
final class HtmlParser |
||||||
|
{ |
||||||
|
protected $parsers = [ |
||||||
|
'h1' => [ |
||||||
|
'pattern' => '/<h1>(.*?)<\/h1>/s', |
||||||
|
'replace' => '[h1]$1[/h1]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'h2' => [ |
||||||
|
'pattern' => '/<h2>(.*?)<\/h2>/s', |
||||||
|
'replace' => '[h2]$1[/h2]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'h3' => [ |
||||||
|
'pattern' => '/<h3>(.*?)<\/h3>/s', |
||||||
|
'replace' => '[h3]$1[/h3]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'h4' => [ |
||||||
|
'pattern' => '/<h4>(.*?)<\/h4>/s', |
||||||
|
'replace' => '[h4]$1[/h4]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'h5' => [ |
||||||
|
'pattern' => '/<h5>(.*?)<\/h5>/s', |
||||||
|
'replace' => '[h5]$1[/h5]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'h6' => [ |
||||||
|
'pattern' => '/<h6>(.*?)<\/h6>/s', |
||||||
|
'replace' => '[h6]$1[/h6]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'bold' => [ |
||||||
|
'pattern' => '/<b>(.*?)<\/b>/s', |
||||||
|
'replace' => '[b]$1[/b]', |
||||||
|
'content' => '$1', |
||||||
|
], |
||||||
|
'strong' => [ |
||||||
|
'pattern' => '/<strong>(.*?)<\/strong>/s', |
||||||
|
'replace' => '[b]$1[/b]', |
||||||
|
'content' => '$1', |
||||||
|
], |
||||||
|
'italic' => [ |
||||||
|
'pattern' => '/<i>(.*?)<\/i>/s', |
||||||
|
'replace' => '[i]$1[/i]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'em' => [ |
||||||
|
'pattern' => '/<em>(.*?)<\/em>/s', |
||||||
|
'replace' => '[i]$1[/i]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'underline' => [ |
||||||
|
'pattern' => '/<u>(.*?)<\/u>/s', |
||||||
|
'replace' => '[u]$1[/u]', |
||||||
|
'content' => '$1', |
||||||
|
], |
||||||
|
'strikethrough' => [ |
||||||
|
'pattern' => '/<s>(.*?)<\/s>/s', |
||||||
|
'replace' => '[s]$1[/s]', |
||||||
|
'content' => '$1', |
||||||
|
], |
||||||
|
'del' => [ |
||||||
|
'pattern' => '/<del>(.*?)<\/del>/s', |
||||||
|
'replace' => '[s]$1[/s]', |
||||||
|
'content' => '$1', |
||||||
|
], |
||||||
|
'code' => [ |
||||||
|
'pattern' => '/<code>(.*?)<\/code>/s', |
||||||
|
'replace' => '[code]$1[/code]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'orderedlistnumerical' => [ |
||||||
|
'pattern' => '/<ol>(.*?)<\/ol>/s', |
||||||
|
'replace' => '[list=1]$1[/list]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'unorderedlist' => [ |
||||||
|
'pattern' => '/<ul>(.*?)<\/ul>/s', |
||||||
|
'replace' => '[list]$1[/list]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'listitem' => [ |
||||||
|
'pattern' => '/<li>(.*?)<\/li>/s', |
||||||
|
'replace' => '[*]$1', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'link' => [ |
||||||
|
'pattern' => '/<a href="(.*?)">(.*?)<\/a>/s', |
||||||
|
'replace' => '[url=$1]$2[/url]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'quote' => [ |
||||||
|
'pattern' => '/<blockquote>(.*?)<\/blockquote>/s', |
||||||
|
'replace' => '[quote]$1[/quote]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'image' => [ |
||||||
|
'pattern' => '/<img src="(.*?)">/s', |
||||||
|
'replace' => '[img]$1[/img]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'youtube' => [ |
||||||
|
'pattern' => '/<iframe width="560" height="315" src="\/\/www\.youtube\.com\/embed\/(.*?)" frameborder="0" allowfullscreen><\/iframe>/s', |
||||||
|
'replace' => '[youtube]$1[/youtube]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'linebreak' => [ |
||||||
|
'pattern' => '/<br\s*\/?>/', |
||||||
|
'replace' => '/\r\n/', |
||||||
|
'content' => '', |
||||||
|
], |
||||||
|
'sub' => [ |
||||||
|
'pattern' => '/<sub>(.*?)<\/sub>/s', |
||||||
|
'replace' => '[sub]$1[/sub]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'sup' => [ |
||||||
|
'pattern' => '/<sup>(.*?)<\/sup>/s', |
||||||
|
'replace' => '[sup]$1[/sup]', |
||||||
|
'content' => '$1' |
||||||
|
], |
||||||
|
'small' => [ |
||||||
|
'pattern' => '/<small>(.*?)<\/small>/s', |
||||||
|
'replace' => '[small]$1[/small]', |
||||||
|
'content' => '$1', |
||||||
|
], |
||||||
|
'table' => [ |
||||||
|
'pattern' => '/<table>(.*?)<\/table>/s', |
||||||
|
'replace' => '[table]$1[/table]', |
||||||
|
'content' => '$1', |
||||||
|
], |
||||||
|
'table-row' => [ |
||||||
|
'pattern' => '/<tr>(.*?)<\/tr>/s', |
||||||
|
'replace' => '[tr]$1[/tr]', |
||||||
|
'content' => '$1', |
||||||
|
], |
||||||
|
'table-data' => [ |
||||||
|
'pattern' => '/<td>(.*?)<\/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; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/* |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework\Http\Contract; |
||||||
|
|
||||||
|
interface MiddlewareAwareInterface |
||||||
|
{ |
||||||
|
public function getMiddleware(): array; |
||||||
|
} |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/* |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework\Http\Contract; |
||||||
|
|
||||||
|
interface MiddlewareInterface |
||||||
|
{ |
||||||
|
public function process( |
||||||
|
ServerRequestInterface $request, |
||||||
|
RequestHandlerInterface $handler |
||||||
|
): ResponseInterface; |
||||||
|
} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework\Http\Exception; |
||||||
|
|
||||||
|
class RouteMiddlewareNotFoundException extends \Exception |
||||||
|
{ |
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework\Http\Routing; |
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface; |
||||||
|
use Psr\Http\Server\RequestHandlerInterface; |
||||||
|
use Psr\Http\Message\ResponseInterface; |
||||||
|
use Psr\Container\ContainerInterface; |
||||||
|
use IOcornerstone\Framework\Http\Exception\RouteMiddlewareNotFoundException; |
||||||
|
|
||||||
|
class RouterMiddlewareHandler implements RequestHandlerInterface |
||||||
|
{ |
||||||
|
public function __construct( |
||||||
|
private array $middleware, |
||||||
|
private ContainerInterface $container |
||||||
|
) {} |
||||||
|
|
||||||
|
public function handle(ServerRequestInterface $request): ResponseInterface |
||||||
|
{ |
||||||
|
$middleware = array_shift($this->middleware); |
||||||
|
|
||||||
|
// Resolve middleware from container if it's a string |
||||||
|
if (is_string($middleware)) { |
||||||
|
$middleware = $this->container->get($middleware); |
||||||
|
} |
||||||
|
|
||||||
|
if (is_callable($middleware)) { |
||||||
|
if (method_exists($middleware, "process")) { |
||||||
|
return $middleware->process($request, $this); |
||||||
|
} |
||||||
|
|
||||||
|
return $middleware($request, $this); |
||||||
|
} |
||||||
|
|
||||||
|
throw new RouteMiddlewareNotFoundException(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,103 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
/** |
||||||
|
* @template TKey |
||||||
|
* @template TValue |
||||||
|
* @implements \IteratorAggregate<TKey, TValue> |
||||||
|
*/ |
||||||
|
class LazyCollection |
||||||
|
{ |
||||||
|
/** @var \Closure|self|array<TKey, TValue>|null */ |
||||||
|
protected $source; |
||||||
|
|
||||||
|
/** |
||||||
|
* @param \Closure(): iterable<TKey, TValue>|self<TKey, TValue>|iterable<TKey, TValue>|null $source |
||||||
|
*/ |
||||||
|
public function __construct($source = null) |
||||||
|
{ |
||||||
|
if ($source instanceof \Closure || $source instanceof self) { |
||||||
|
$this->source = $source; |
||||||
|
} elseif (is_null($source)) { |
||||||
|
$this->source = static::empty(); |
||||||
|
} else { |
||||||
|
$this->source = $this->getArrayableItems($source); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return \Traversable<TKey, TValue> |
||||||
|
*/ |
||||||
|
public function getIterator(): \Traversable |
||||||
|
{ |
||||||
|
return $this->makeIterator($this->source); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param \Closure(): iterable<TKey, TValue>|self<TKey, TValue>|array<TKey, TValue> $source |
||||||
|
* @return \Traversable<TKey, TValue> |
||||||
|
*/ |
||||||
|
protected function makeIterator($source) |
||||||
|
{ |
||||||
|
if ($source instanceof \Closure) { |
||||||
|
$source = $source(); |
||||||
|
} |
||||||
|
|
||||||
|
if (is_array($source)) { |
||||||
|
return new \ArrayIterator($source); |
||||||
|
} |
||||||
|
|
||||||
|
return $source; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @template TNewKey |
||||||
|
* @template TNewValue |
||||||
|
* @param \Closure(): iterable<TNewKey, TNewValue>|iterable<TNewKey, TNewValue>|null $source |
||||||
|
* @return self<TNewKey, TNewValue> |
||||||
|
*/ |
||||||
|
public static function make($source = null) |
||||||
|
{ |
||||||
|
return new static($source instanceof \Closure ? $source() : $source); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param callable(TValue, TKey): bool $callback |
||||||
|
* @return self<TKey, TValue> |
||||||
|
*/ |
||||||
|
public function each(callable $callback): lazy_collection |
||||||
|
{ |
||||||
|
foreach ($this->getIterator() as $key => $value) { |
||||||
|
if ($callback($value, $key) === false) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param mixed $items |
||||||
|
* @return array<TKey, TValue> |
||||||
|
*/ |
||||||
|
protected function getArrayableItems($items): array |
||||||
|
{ |
||||||
|
if (is_array($items)) { |
||||||
|
return $items; |
||||||
|
} elseif ($items instanceof \Traversable) { |
||||||
|
return iterator_to_array($items); |
||||||
|
} elseif (is_object($items) && method_exists($items, 'toArray')) { |
||||||
|
return $items->toArray(); |
||||||
|
} |
||||||
|
|
||||||
|
return (array) $items; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
class LazyObject |
||||||
|
{ |
||||||
|
public function __construct(public string $className) {} |
||||||
|
|
||||||
|
public function proxy(mixed ...$args): Object { |
||||||
|
$className = $this->className; |
||||||
|
if (!class_exists($className, true)) { |
||||||
|
throw new \InvalidArgumentException("Class {$className} does not exist"); |
||||||
|
} |
||||||
|
$reflector = new \ReflectionClass($className); |
||||||
|
if (!$reflector->isInstantiable()) { |
||||||
|
throw new \InvalidArgumentException("Class {$className} cannot be instantiated"); |
||||||
|
} |
||||||
|
|
||||||
|
$initializer = static function () use ($className, $args) { |
||||||
|
return new $className(...$args); |
||||||
|
}; |
||||||
|
|
||||||
|
return $reflector->newLazyProxy($initializer); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
final class MemoryUsage |
||||||
|
{ |
||||||
|
/** |
||||||
|
* 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 convertBytes($size) { |
||||||
|
if ($size === null) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
$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 getMemoryStats($echo = true) { |
||||||
|
if (defined("Memory_Baseline")) { |
||||||
|
$mem_baseline = Memory_Baseline; |
||||||
|
} else { |
||||||
|
$mem_baseline = 0; |
||||||
|
} |
||||||
|
|
||||||
|
$check = $_GET['debug'] ?? false; |
||||||
|
|
||||||
|
if ($check || defined('DEBUG') && DEBUG === true) { |
||||||
|
$now_mem = memory_get_usage(); |
||||||
|
$diff_mem = $now_mem - $mem_baseline; |
||||||
|
|
||||||
|
$s_current = self::convertBytes($now_mem); |
||||||
|
$s_diff = self::convertBytes($diff_mem); |
||||||
|
$s_startup_mem = self::convertBytes($mem_baseline); |
||||||
|
|
||||||
|
$peak = memory_get_peak_usage(); |
||||||
|
$s_peak = self::convertBytes($peak); |
||||||
|
$diff_peak = $peak - $mem_baseline; |
||||||
|
$s_diff_peak = self::convertBytes($diff_peak); |
||||||
|
|
||||||
|
$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>"); |
||||||
|
|
||||||
|
$options['escape'] = false; |
||||||
|
$options['echo'] = $echo; |
||||||
|
return Html::showTable($a_headers, $a_fields, $options); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,46 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
use IOcornerstone\Framework\Requires; |
||||||
|
|
||||||
|
/** |
||||||
|
* May be used by the Liquid Template Engine |
||||||
|
*/ |
||||||
|
|
||||||
|
class PhpFileCache |
||||||
|
{ |
||||||
|
protected $cache_path; |
||||||
|
|
||||||
|
public function __construct($path) { |
||||||
|
$is_safe_path = Requires::isDangerous($path); |
||||||
|
if ($is_safe_path === false) { |
||||||
|
throw new \Exception("Bad cache path"); |
||||||
|
} |
||||||
|
$safer_path = Requires::filterDirPath($path); |
||||||
|
$this->cache_path = rtrim($safer_path, '/') . '/'; |
||||||
|
if (!is_dir($this->cache_path)) { |
||||||
|
mkdir($this->cache_path, 0775, true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function get($key) { |
||||||
|
$file = $this->cache_path . md5($key) . '.cache.php'; |
||||||
|
if (file_exists($file)) { |
||||||
|
return unserialize(file_get_contents($file)); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public function set($key, $value) { |
||||||
|
$file = $this->cache_path . md5($key) . '.cache.php'; |
||||||
|
file_put_contents($file, serialize($value)); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,106 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
use IOcornerstone\Framework\Common; |
||||||
|
|
||||||
|
/** |
||||||
|
* random_engine - Provides a high-level API to the randomness |
||||||
|
* provided by an Random\Engine OR uses the next Fall Back FN. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
class RandomEngine |
||||||
|
{ |
||||||
|
private $engine = false; |
||||||
|
|
||||||
|
public function __construct() { |
||||||
|
$version = (float) phpversion(); |
||||||
|
if ($version > 8.1) { |
||||||
|
$this->engine = new \Random\Randomizer(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function getBytes(int $bytes_length = 16): string { |
||||||
|
return ($this->engine) ? $this->engine->getBytes($bytes_length) : |
||||||
|
random_bytes($bytes_length); |
||||||
|
} |
||||||
|
|
||||||
|
public function getInt(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 bigRand(int $len = 18 ): int { |
||||||
|
$rand = ''; |
||||||
|
while( !( isset( $rand[$len-1] ) ) ) { |
||||||
|
$rand .= mt_rand( ); |
||||||
|
} |
||||||
|
return (int) substr( $rand , 0 , $len ); |
||||||
|
} |
||||||
|
|
||||||
|
public function getNextBigPostiveInt(): int { |
||||||
|
if ($this->engine) { |
||||||
|
return $this->engine->nextInt(); |
||||||
|
} |
||||||
|
return $this->bigRand(); |
||||||
|
} |
||||||
|
|
||||||
|
private function selectFromArray(array $a, int $num ): array { |
||||||
|
$array_count = Common::getCount($a) - 1; |
||||||
|
if ($array_count < 1) { |
||||||
|
return []; |
||||||
|
} |
||||||
|
$ret = []; |
||||||
|
for($i=0; $i<$num; $i++) { |
||||||
|
$ret[] = $a[$this->getInt(0, $array_count)]; |
||||||
|
} |
||||||
|
return $ret; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Pick random keys from an Array |
||||||
|
*/ |
||||||
|
public function getArrayKeys(array $a, int $num): array { |
||||||
|
if ($this->engine) { |
||||||
|
return $this->engine->pickArrayKeys($a, $num); |
||||||
|
} |
||||||
|
return $this->selectFromArray($a, $num); |
||||||
|
} |
||||||
|
|
||||||
|
public function getShuffledArray(array $a): array { |
||||||
|
if ($this->engine) { |
||||||
|
return $this->engine->shuffleArray($a); |
||||||
|
} |
||||||
|
shuffle($a); |
||||||
|
return $a; |
||||||
|
} |
||||||
|
|
||||||
|
public function getShuffledBytes(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); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,135 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
class RssFeed |
||||||
|
{ |
||||||
|
public static function generateRssFeed(array $root, array $items) { |
||||||
|
|
||||||
|
// Create the XML document |
||||||
|
$rss = new \DOMDocument('1.0', 'UTF-8'); |
||||||
|
$rss->formatOutput = true; |
||||||
|
|
||||||
|
// Create the RSS root element |
||||||
|
$rssElement = $rss->createElement('rss'); |
||||||
|
$rssElement->setAttribute('version', '2.0'); |
||||||
|
$rssElement->setAttribute('xmlns:atom', 'http://www.w3.org/2005/Atom'); |
||||||
|
$rssRoot = $rss->appendChild($rssElement); |
||||||
|
|
||||||
|
// Create the channel element |
||||||
|
$channel = $rss->createElement('channel'); |
||||||
|
$channel = $rssRoot->appendChild($channel); |
||||||
|
|
||||||
|
// Add atom:link for self-reference |
||||||
|
$atomLink = $rss->createElement('atom:link'); |
||||||
|
$rss_feed_link = $root['feed_link'] ?? false; |
||||||
|
if ($rss_feed_link === false) { |
||||||
|
throw new \Exception("Unknown Feed Link"); |
||||||
|
} |
||||||
|
$atomLink->setAttribute('href', $rss_feed_link); |
||||||
|
$atomLink->setAttribute('rel', 'self'); |
||||||
|
$atomLink->setAttribute('type', 'application/rss+xml'); |
||||||
|
$channel->appendChild($atomLink); |
||||||
|
|
||||||
|
// Add required channel elements |
||||||
|
$title = $root["title"] ?? "News"; |
||||||
|
$channel->appendChild($rss->createElement('title', $title)); |
||||||
|
$site_link = $root["site_link"] ?? false; |
||||||
|
if ($site_link === false) { |
||||||
|
throw new \Exception("Unknown Site Link"); |
||||||
|
} |
||||||
|
$channel->appendChild($rss->createElement('link', $site_link)); |
||||||
|
$desc = $root["description"] ?? "Latest news from My Website"; |
||||||
|
$channel->appendChild($rss->createElement('description', $desc)); |
||||||
|
|
||||||
|
// Optional channel elements |
||||||
|
$lang = $root['lang'] ?? "en-us"; |
||||||
|
$channel->appendChild($rss->createElement('language', $lang)); |
||||||
|
$pub_date = $root['pub_date'] ?? date(DATE_RSS); |
||||||
|
$channel->appendChild($rss->createElement('pubDate', $pub_date)); |
||||||
|
$build_date = $root['last_build_date'] ?? date(DATE_RSS); |
||||||
|
$channel->appendChild($rss->createElement('lastBuildDate', $build_date)); |
||||||
|
$channel->appendChild($rss->createElement('generator', 'RSS Generator')); |
||||||
|
|
||||||
|
// Add items to the channel |
||||||
|
foreach ($items as $itemData) { |
||||||
|
$item = $rss->createElement('item'); |
||||||
|
$item = $channel->appendChild($item); |
||||||
|
|
||||||
|
$item->appendChild($rss->createElement('title', $itemData['title'])); |
||||||
|
$item->appendChild($rss->createElement('link', $itemData['link'])); |
||||||
|
$item->appendChild($rss->createElement('description', $itemData['description'])); |
||||||
|
$item->appendChild($rss->createElement('pubDate', $itemData['pubDate'])); |
||||||
|
|
||||||
|
// GUID should be a permalink |
||||||
|
$guid = $rss->createElement('guid', $itemData['guid']); |
||||||
|
$guid->setAttribute('isPermaLink', 'true'); |
||||||
|
$item->appendChild($guid); |
||||||
|
|
||||||
|
// Optional item elements |
||||||
|
if (isset($itemData['author'])) { |
||||||
|
$item->appendChild($rss->createElement('author', $itemData['author'])); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $rss; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public static function saveToXmlFile(\DOMDocument $rss, string $filename = "feed") { |
||||||
|
if (! defined("BaseDir") ) { |
||||||
|
throw new \Exception("BaseDir not Set!"); |
||||||
|
} |
||||||
|
$safe_file = BaseDir . "/public/" . preg_replace('/[^A-Za-z0-9]/', '', $filename) . ".xml"; |
||||||
|
|
||||||
|
$result = $rss->save($safe_file); |
||||||
|
if ($result === false) { |
||||||
|
throw new \Exception("Failed to save RSS feed to file"); |
||||||
|
} |
||||||
|
|
||||||
|
// echo "Saved XML file: " . $safe_file; |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public static function outputRss(\DOMDocument $rss): void { |
||||||
|
// Set the content type to XML |
||||||
|
header('Content-Type: application/rss+xml; charset=utf-8'); |
||||||
|
echo $rss->saveXML(); |
||||||
|
} |
||||||
|
|
||||||
|
public static function getRss(\DOMDocument $rss): string { |
||||||
|
return $rss->saveXML(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
// Sample items - in a real application, these would come from a database |
||||||
|
$items = [ |
||||||
|
[ |
||||||
|
'title' => 'First Article', |
||||||
|
'link' => 'http://example.com/article1', |
||||||
|
'description' => 'This is the description of the first article.', |
||||||
|
'pubDate' => date(DATE_RSS, strtotime('-2 days')), |
||||||
|
'guid' => 'http://example.com/article1', |
||||||
|
'author' => 'author@example.com (John Doe)' |
||||||
|
], |
||||||
|
[ |
||||||
|
'title' => 'Second Article', |
||||||
|
'link' => 'http://example.com/article2', |
||||||
|
'description' => 'This is the description of the second article.', |
||||||
|
'pubDate' => date(DATE_RSS, strtotime('-1 day')), |
||||||
|
'guid' => 'http://example.com/article2', |
||||||
|
'author' => 'author@example.com (Jane Smith)' |
||||||
|
] |
||||||
|
]; |
||||||
|
*/ |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
class Scalar |
||||||
|
{ |
||||||
|
public function intoString(mixed $name): string { |
||||||
|
if (is_scalar($name) || (is_object($name) && method_exists($name, '__toString'))) { |
||||||
|
return (string)$name; |
||||||
|
} else { |
||||||
|
throw new \Exception("Cannot convert to string!"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,94 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
use IOcornerstone\Framework\{ |
||||||
|
Configure, |
||||||
|
Registry |
||||||
|
}; |
||||||
|
|
||||||
|
final class SessionManagement |
||||||
|
{ |
||||||
|
public static function start( |
||||||
|
array $options = [], |
||||||
|
string $type = "", |
||||||
|
$enc = false |
||||||
|
): void { |
||||||
|
if (empty($type)) { |
||||||
|
$type = Configure::get('sessions', 'type'); |
||||||
|
} |
||||||
|
if ($enc === false) { |
||||||
|
$exists = Registry::get('di')->exists('session_encryption'); |
||||||
|
if ($exists) { |
||||||
|
$enc = Registry::get('di')->get_service('session_encryption'); |
||||||
|
} |
||||||
|
} |
||||||
|
if ($type === "none" || $type === "php") { |
||||||
|
self::makeSessionStarted(); |
||||||
|
return; |
||||||
|
} |
||||||
|
$handler = match($type) { |
||||||
|
'redis' => new services\sessions\redis_session_handler($enc, $options), |
||||||
|
'files' => new services\sessions\file_session_handler($enc, $options), |
||||||
|
default => new services\sessions\cookie_session_handler($enc, $options), |
||||||
|
}; |
||||||
|
session_set_save_handler($handler, true); |
||||||
|
self::makeSessionStarted(); |
||||||
|
} |
||||||
|
|
||||||
|
private static function makeSessionStarted(bool $force_secure = false) { |
||||||
|
if ((function_exists('session_status') && session_status() !== PHP_SESSION_ACTIVE) || !session_id()) { |
||||||
|
$name = Configure::get('sessions', 'session_name'); |
||||||
|
if ($name !== null) { |
||||||
|
session_name($name); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @todo Fix ForceSecure....to detect mode |
||||||
|
*/ |
||||||
|
|
||||||
|
if (! headers_sent()) { |
||||||
|
$use_secure = 1; // (site_helper::get_use_secure()) ? 1 : 0; |
||||||
|
$use_secure = ($force_secure) ? 1 : $use_secure; |
||||||
|
session_start([ |
||||||
|
'cookie_lifetime' => 0, // until browser is closed |
||||||
|
'cookie_secure' => $use_secure, // require secure cookies if HTTPS is used |
||||||
|
'use_only_cookies' => 1, // should be 1 to prevent URL attacks |
||||||
|
'cookie_httponly' => 1, // should be 1 to disable JavaScript access |
||||||
|
'cookie_samesite' => 'Strict', // should be Strict to prevent XSS |
||||||
|
// So you need it when you do not want to allow a user to pre-define the session ID value. You normally want to prevent that to reduce the attack surface. |
||||||
|
'use_strict_mode' => 1, // Note: Enabling session.use_strict_mode is mandatory for general session security. All sites are advised to enable this. |
||||||
|
'use_trans_sid' => 0, // should be kept at the default of 0: URL based session management has additional security risks |
||||||
|
]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static function hasUserRight(string $right): bool { |
||||||
|
$rights = (isset($_SESSION['users_rights'])) ? $_SESSION['users_rights'] : false; |
||||||
|
if ($rights === false) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
if (! common::is_json($right)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
$assoc = true; // Use Array format |
||||||
|
$a_rights = json_decode($rights, $assoc); |
||||||
|
if (in_array($right, $a_rights)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public static function getUserId(): int { |
||||||
|
$sid = (isset($_SESSION['user_id'])) ? $_SESSION['user_id'] : 0; |
||||||
|
return intval($sid); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
use IOcornerstone\Framework\String as SF; |
||||||
|
|
||||||
|
final class TagMatches |
||||||
|
{ |
||||||
|
|
||||||
|
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 checkTags(string $page): array |
||||||
|
{ |
||||||
|
$alert = ''; |
||||||
|
$output = ''; |
||||||
|
$lowercasePage = SF\StringFacade::strtolower($page); |
||||||
|
unset($page); |
||||||
|
|
||||||
|
$assets = "/assets/uikit/css/uikit.gradient.min.css"; |
||||||
|
$ui = '<link rel="stylesheet" href="' . $assets . '/uikit/css/uikit.gradient.min.css" type="text/css" media="all" />'; |
||||||
|
$ui .= '<div class="uk-alert uk-alert-danger"><b>'; |
||||||
|
$ui_end = '</b></div>'; |
||||||
|
|
||||||
|
foreach (self::TAGS_TO_CHECK as $tagName) { |
||||||
|
if (str_contains($tagName, '*')) { |
||||||
|
$tagName = str_replace('*', '', $tagName); |
||||||
|
$otag = "<{$tagName}>"; // Open Tag |
||||||
|
$open = substr_count($lowercasePage, $otag); // Count open tags in page |
||||||
|
$otag = "<{$tagName} "; /* Open Tag with space */ |
||||||
|
$open += substr_count($lowercasePage, $otag); // Count open tags in page |
||||||
|
} else { |
||||||
|
$otag = "<{$tagName}"; // Open Tag |
||||||
|
$open = substr_count($lowercasePage, $otag); // Count open tags in page |
||||||
|
} |
||||||
|
|
||||||
|
$ctag = "</{$tagName}>"; // Close Tag |
||||||
|
$closed = substr_count($lowercasePage, $ctag); // Count Close tags in page |
||||||
|
$totalStillOpen = $open - $closed; // Difference of open vs. closed.... |
||||||
|
|
||||||
|
if ($totalStillOpen > 0) { |
||||||
|
$msg = "{$totalStillOpen} possibly MISSING closing {$tagName} !!!"; |
||||||
|
$alert .= "console.log('{$msg}');\r\n"; |
||||||
|
$output .= (isLive()) ? "<!-- {$msg} -->\r\n" : "{$ui}{$msg}{$ui_end}\r\n"; |
||||||
|
} elseif ($totalStillOpen < 0) { |
||||||
|
$msg = abs($totalStillOpen) . " possibly MISSING opening {$tagName} !!!"; |
||||||
|
$alert .= "console.log('{$msg}');\r\n"; |
||||||
|
$output .= (isLive()) ? "<!-- {$msg} -->\r\n" : "{$ui}{$msg}{$ui_end}\r\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
return array('output' => $output, 'alert' => $alert); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @folked from https://gist.github.com/Xeoncross/1204255 |
||||||
|
* @site https://gist.github.com/vrdriver/659562a150ba955063ad849e44dac2cc |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
class TimeZoneSelection |
||||||
|
{ |
||||||
|
protected $timezones = array(); |
||||||
|
|
||||||
|
public function __construct() { |
||||||
|
|
||||||
|
$regions = array( |
||||||
|
'America' => \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 '<label>Select Your Timezone</label><br/><select id="timezone">'; |
||||||
|
foreach ($this->timezones as $region => $list) { |
||||||
|
echo '<optgroup label="' . $region . '">' . "\n"; |
||||||
|
foreach ($list as $timezone => $name) { |
||||||
|
echo '<option value="' . $timezone . '">' . $name . '</option>' . "\n"; |
||||||
|
} |
||||||
|
echo '<optgroup>' . "\n"; |
||||||
|
} |
||||||
|
echo '</select>'; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,287 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
use IOcornerstone\Framework\String as SF; |
||||||
|
|
||||||
|
final class TimeZones |
||||||
|
{ |
||||||
|
|
||||||
|
/** |
||||||
|
* MySQL time conversion! |
||||||
|
* @param type $field time |
||||||
|
* @param type $time_zone_name, leave blank for users time zone! |
||||||
|
* @return type |
||||||
|
*/ |
||||||
|
public static function dbConvertTz($field, $time_zone_name = '') |
||||||
|
{ |
||||||
|
$session_time_zone = (isset($_SESSION['time_zone'])) ? $_SESSION['time_zone'] : 'America/Detroit'; |
||||||
|
$tz = (!empty($time_zone_name)) ? $time_zone_name : $session_time_zone; |
||||||
|
$to_tz = self::currentTimeForTimeZone($tz); |
||||||
|
return "CONVERT_TZ({$field}, '+00:00', '{$to_tz}')"; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Purpose: To convert time zone to offset time |
||||||
|
* @param type $time_zone_name |
||||||
|
* @return offset |
||||||
|
*/ |
||||||
|
public static function currentTimeForTimeZone($time_zone_name) |
||||||
|
{ |
||||||
|
$time = new \DateTime('now', new \DateTimeZone($time_zone_name)); |
||||||
|
return $time->format('P'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Purpose: To make a temporary valid timestamp for a database |
||||||
|
*/ |
||||||
|
public static function expiresInHours(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 (SF\StringFacade::strtolower($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 (SF\StringFacade::strtolower($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 (SF\StringFacade::strtolower($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 dtFormat($userTime, $format, $country) |
||||||
|
{ |
||||||
|
switch (SF\StringFacade::strtolower($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 (SF\StringFacade::strtolower($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 convertTimeZone($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::getOffset($offset) : self::getOffsetBy($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::dtFormat($userTime, $format, $country); |
||||||
|
} |
||||||
|
|
||||||
|
public static function humanDate($input_date) |
||||||
|
{ |
||||||
|
if (empty($input_date)) { |
||||||
|
return ''; |
||||||
|
} |
||||||
|
$today = self::convertTimeZone(array('format' => 'm/d/Y')); |
||||||
|
$date = strtotime($input_date); |
||||||
|
if (date('m/d/Y', $date) == $today) { |
||||||
|
return date('g:i', $date) . "<span style=\"font-size: 0.65em; vertical-align: text-top; margin-left: 1px; text-transform: lowercase;\">" . date('A', $date) . "</span>"; |
||||||
|
} elseif (date('Y', $date) == date('Y')) { |
||||||
|
return "<span style=\"font-weight: 500;\">" . date('M', $date) . " " . date('j', $date) . "<span style=\"font-size: 0.65em; vertical-align: text-top; margin-left: 1px;\">" . date('S', $date) . "</span></span>"; |
||||||
|
} else { |
||||||
|
return date('m/d/y', $date); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static function isValidOffset($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 getOffset($offset) |
||||||
|
{ |
||||||
|
return (self::isValidOffset($offset)) ? strtotime($offset) : $offset; |
||||||
|
} |
||||||
|
|
||||||
|
private static function getOffsetBy($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::isValidOffset($offset)) ? strtotime($offset, $db_time) : $db_time; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,79 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOCornerstone\Framework\Trait\Database; |
||||||
|
|
||||||
|
use IOcornerstone\Framework\Common; |
||||||
|
|
||||||
|
class RunSql |
||||||
|
{ |
||||||
|
public function run($sql, $bind=""): int { |
||||||
|
$pdostmt = $this->pdo->prepare(trim($sql)); |
||||||
|
if (Common::getCount($bind) > 0) { |
||||||
|
$exec = $pdostmt->execute($bind); |
||||||
|
} else { |
||||||
|
$exec = $pdostmt->execute(); |
||||||
|
} |
||||||
|
return $pdostmt->rowCount(); |
||||||
|
} |
||||||
|
|
||||||
|
public function runSelect($sql) { |
||||||
|
$pdostmt = $this->pdo->prepare(trim($sql)); |
||||||
|
$exec = $pdostmt->execute(); |
||||||
|
return $pdostmt->fetchAll(\PDO::FETCH_ASSOC); |
||||||
|
} |
||||||
|
|
||||||
|
private function describeTable(string $table) { |
||||||
|
$driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); |
||||||
|
$tickTable = (strrpos($table, "`") === false) ? "`{$table}`" : $table; |
||||||
|
if ($driver == 'sqlite') { |
||||||
|
$sql = "PRAGMA table_info({$tickTable});"; |
||||||
|
$key = "name"; |
||||||
|
} elseif ($driver == 'mysql') { |
||||||
|
$sql = "DESCRIBE {$tickTable};"; |
||||||
|
$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 getFields(string $table) { |
||||||
|
$describe = $this->describeTable($table); |
||||||
|
if (false !== ($list = $this->runSelect($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->describeTable($table); |
||||||
|
if ($describe === false) { |
||||||
|
return array_keys($info); |
||||||
|
} |
||||||
|
if (false !== ($list = $this->runSelect($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,208 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOCornerstone\Framework\Trait\Database; |
||||||
|
|
||||||
|
use IOcornerstone\Framework\{ |
||||||
|
Configure, |
||||||
|
Common, |
||||||
|
Registry |
||||||
|
}; |
||||||
|
|
||||||
|
trait Validation |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Validate current class members |
||||||
|
* @retval bool true valid, false failed tests |
||||||
|
*/ |
||||||
|
public function validateMysql(): bool { |
||||||
|
$tbl = (str_contains($this->table, "`")) ? $this->table : "`{$this->table}`"; |
||||||
|
foreach ($this->members as $field => $value) { |
||||||
|
if ($field == $this->primaryKey) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
$validationField = $this->getVaildationMember($field); |
||||||
|
if (isset($validationField['native_type']) && isset($validationField['len'])) { |
||||||
|
$type = strtoupper($validationField['native_type']); |
||||||
|
$len = intval($validationField['len']); |
||||||
|
$meta = $validationField; |
||||||
|
} else { |
||||||
|
$meta = $this->getMySQLMetaField($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 (!str_contains($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->isValidMysqlDate($value)) { |
||||||
|
$this->do_verr("Failed Validation: invalid date in {$field}"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'TIME': |
||||||
|
if (!$this->isValidMysqlTime($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->isValidMysqlTimestamp($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 getMySQLMetaField(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 isValidMysqlTimestamp(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 isValidMysqlDate(string $str): bool { |
||||||
|
$dateParts = explode('-', $str); |
||||||
|
if (Common::getCount($dateParts) != 3) |
||||||
|
return false; |
||||||
|
if ((strlen($dateParts[0]) != 4) || (!is_numeric($dateParts[0]))) |
||||||
|
return false; |
||||||
|
if ((strlen($dateParts[1]) != 2) || (!is_numeric($dateParts[1]))) |
||||||
|
return false; |
||||||
|
if ((strlen($dateParts[2]) != 2) || (!is_numeric($dateParts[2]))) |
||||||
|
return false; |
||||||
|
if (!checkdate($dateParts[1], $dateParts[2], $dateParts[0])) |
||||||
|
return false; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
public function isValidMysqlTime(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 = Registry::get('di')->exists('log'); |
||||||
|
if ($exists && Configure::get('database', 'log_validation_errors') === true) { |
||||||
|
$log = Registry::get('di')->getService('log', ['validation_errors']); |
||||||
|
$log->write($msg); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,156 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/* |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework\Trait; |
||||||
|
|
||||||
|
use IOcornerstone\Framework\Common; |
||||||
|
|
||||||
|
trait FormValidator |
||||||
|
{ |
||||||
|
const DEFAULT_VALIDATION_ERRORS = [ |
||||||
|
'positive' => 'The %s is not a positive number', |
||||||
|
'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', |
||||||
|
]; |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @param array $data |
||||||
|
* @param string $field |
||||||
|
* @param mixed return: null avoids an error on no-entry; false gives errors on no-entry; default is the value. DO NOT MESS with this... |
||||||
|
*/ |
||||||
|
private static function checkIfNullOrFalse(array $data, string $field) { |
||||||
|
$d = $data[$field] ?? null; |
||||||
|
return match($d) { |
||||||
|
null => true, // Pass validation as null or value unset |
||||||
|
false => false, // Fail validation as user requested false on default |
||||||
|
default => $d // Use value |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
private static function isPositive(array $data, string $field): bool { |
||||||
|
$r = self::checkIfNullOrFalse($data, $field); |
||||||
|
if (is_bool($r)) return $r; |
||||||
|
return (intval($r) >= 0) ? true : false; |
||||||
|
} |
||||||
|
|
||||||
|
private static function isRequired(array $data, string $field): bool { |
||||||
|
// Don't use check_if_null_or_false here, it should always fail on false or null! |
||||||
|
$r = $data[$field] ?? null; |
||||||
|
if ($r === null || $r === false) { |
||||||
|
return false; // It's a required field, so fail |
||||||
|
} |
||||||
|
|
||||||
|
if (common::get_count($r)) { |
||||||
|
return false; // Should not be an array here |
||||||
|
} |
||||||
|
if (is_string($r)) { |
||||||
|
return (trim($r) !== ''); |
||||||
|
} |
||||||
|
if (is_int($r)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static function isEmail(array $data, string $field): bool { |
||||||
|
$r = self::checkIfNullOrFalse($data, $field); |
||||||
|
if (is_bool($r)) return $r; |
||||||
|
return (filter_var($r, FILTER_VALIDATE_EMAIL) === false) ? false : true; |
||||||
|
} |
||||||
|
|
||||||
|
private static function isMin(array $data, string $field, string $min): bool { |
||||||
|
$r = self::checkIfNullOrFalse($data, $field); |
||||||
|
if (is_bool($r)) return $r; |
||||||
|
return mb_strlen($r) >= intval($min); |
||||||
|
} |
||||||
|
|
||||||
|
private static function isMax(array $data, string $field, string $max): bool { |
||||||
|
$r = self::checkIfNullOrFalse($data, $field); |
||||||
|
if (is_bool($r)) return $r; |
||||||
|
return mb_strlen($r) <= intval($max); |
||||||
|
} |
||||||
|
|
||||||
|
private static function isGreaterThan(array $data, string $field, string $min): bool { |
||||||
|
$r = self::checkIfNullOrFalse($data, $field); |
||||||
|
if (is_bool($r)) return $r; |
||||||
|
return intval($r) > intval($min); |
||||||
|
} |
||||||
|
|
||||||
|
private static function isLessThan(array $data, string $field, string $max): bool { |
||||||
|
$r = self::checkIfNullOrFalse($data, $field); |
||||||
|
if (is_bool($r)) return $r; |
||||||
|
return intval($r) < intval($max); |
||||||
|
} |
||||||
|
|
||||||
|
private static function isNumberRange(array $data, string $field, string $min, string $max): bool { |
||||||
|
$r = self::checkIfNullOrFalse($data, $field); |
||||||
|
if (is_bool($r)) return $r; |
||||||
|
$no = intval($r); |
||||||
|
return $no >= intval($min) && $no <= intval($max); |
||||||
|
} |
||||||
|
|
||||||
|
private static function isBetween(array $data, string $field, string $min, string $max): bool { |
||||||
|
$r = self::checkIfNullOrFalse($data, $field); |
||||||
|
if (is_bool($r)) return $r; |
||||||
|
$len = mb_strlen($r); |
||||||
|
return $len >= intval($min) && $len <= intval($max); |
||||||
|
} |
||||||
|
|
||||||
|
private static function isSame(array $data, string $field, string $other): bool { |
||||||
|
$r = self::checkIfNullOrFalse($data, $field); |
||||||
|
if (is_bool($r)) return $r; |
||||||
|
|
||||||
|
if (isset($data[$other])) { |
||||||
|
return $r === $data[$other]; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
private static function isAlphanumeric(array $data, string $field): bool { |
||||||
|
$r = self::checkIfNullOrFalse($data, $field); |
||||||
|
if (is_bool($r)) return $r; |
||||||
|
return ctype_alnum($r); |
||||||
|
} |
||||||
|
|
||||||
|
private static function isSecure(array $data, string $field): bool { |
||||||
|
$r = self::checkIfNullOrFalse($data, $field); |
||||||
|
if (is_bool($r)) return $r; |
||||||
|
// Is 8 to 64 CHRs |
||||||
|
$pattern = "#.*^(?=.{8,64})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*\W).*$#"; |
||||||
|
return preg_match($pattern, $r); |
||||||
|
} |
||||||
|
|
||||||
|
private static function isValidEmailDomain(array $data, string $field): bool { |
||||||
|
$r = self::checkIfNullOrFalse($data, $field); |
||||||
|
if (is_bool($r)) return $r; |
||||||
|
$domain = ltrim(stristr($r, '@'), '@') . '.'; |
||||||
|
return checkdnsrr($domain, 'MX'); |
||||||
|
} |
||||||
|
|
||||||
|
private static function isValidDomain(array $data, string $field): bool { |
||||||
|
$r = self::checkIfNullOrFalse($data, $field); |
||||||
|
if (is_bool($r)) return $r; |
||||||
|
return checkdnsrr($r, 'A') |
||||||
|
|| checkdnsrr($r, 'AAAA') |
||||||
|
|| checkdnsrr($r, 'CNAME'); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,64 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/* |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework\Trait; |
||||||
|
|
||||||
|
trait Macroable |
||||||
|
{ |
||||||
|
protected static array $macros = []; |
||||||
|
|
||||||
|
/** |
||||||
|
* Register a custom macro. |
||||||
|
*/ |
||||||
|
public static function macro(string $name, mixed $macro): void { |
||||||
|
static::$macros[$name] = $macro; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if macro is registered. |
||||||
|
*/ |
||||||
|
public static function hasMacro(string $name): bool { |
||||||
|
return isset(static::$macros[$name]); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Dynamically handle calls to the class. |
||||||
|
*/ |
||||||
|
public function __call(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($macro->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); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework\Uuids; |
||||||
|
|
||||||
|
const BASE62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; |
||||||
|
|
||||||
|
class Base62 |
||||||
|
{ |
||||||
|
public static function encodeId(int $num): string { |
||||||
|
if ($num === 0) return BASE62[0]; |
||||||
|
$base = strlen(BASE62); |
||||||
|
$encoded = ''; |
||||||
|
while ($num > 0) { |
||||||
|
$encoded = BASE62[$num % $base] . $encoded; |
||||||
|
$num = intdiv($num, $base); |
||||||
|
} |
||||||
|
return $encoded; |
||||||
|
} |
||||||
|
|
||||||
|
public static function decodeId(string $str): int { |
||||||
|
$base = strlen(BASE62); |
||||||
|
$len = strlen($str); |
||||||
|
$num = 0; |
||||||
|
for ($i = 0; $i < $len; $i++) { |
||||||
|
$num = $num * $base + strpos(BASE62, $str[$i]); |
||||||
|
} |
||||||
|
return $num; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,113 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework\Uuids; |
||||||
|
|
||||||
|
class UuidV7 |
||||||
|
{ |
||||||
|
public static function base32EncodedUuidV7(): string { |
||||||
|
$uuid = self::generateUuidV7(); |
||||||
|
$uuidHex = str_replace('-', '', $uuid); |
||||||
|
return self::base32Encode($uuidHex); |
||||||
|
} |
||||||
|
|
||||||
|
public static function generateUuidV7(): string { |
||||||
|
$timestamp = (int)(microtime(true) * 1000); // Real Unix 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($randomHex, 0, 3), // UUIDv7 |
||||||
|
substr($randomHex, 3, 4), |
||||||
|
substr($randomHex, 7, 12) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* NOTE: the absence of O like Orange and I like Internet, so |
||||||
|
* Zero and One are present but not IO... |
||||||
|
*/ |
||||||
|
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; |
||||||
|
} |
||||||
|
|
||||||
|
public static function base32Decode(string $base32): string { |
||||||
|
$base32Chars = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; |
||||||
|
$binary = ''; |
||||||
|
|
||||||
|
// Convert each base32 character to 5-bit binary |
||||||
|
foreach (str_split(strtoupper($base32)) as $char) { |
||||||
|
$pos = strpos($base32Chars, $char); |
||||||
|
if ($pos === false) { |
||||||
|
throw new InvalidArgumentException("Invalid base32 character: $char"); |
||||||
|
} |
||||||
|
$binary .= str_pad(decbin($pos), 5, '0', STR_PAD_LEFT); |
||||||
|
} |
||||||
|
|
||||||
|
// Split binary string into 4-bit chunks and convert to hex |
||||||
|
$hex = ''; |
||||||
|
foreach (str_split($binary, 4) as $chunk) { |
||||||
|
// Skip incomplete chunks at the end (padding) |
||||||
|
if (strlen($chunk) < 4) break; |
||||||
|
$hex .= dechex(bindec($chunk)); |
||||||
|
} |
||||||
|
|
||||||
|
return $hex; |
||||||
|
} |
||||||
|
|
||||||
|
public static function getTimestampFromUuidV7(string $uuid): int { |
||||||
|
// Remove hyphens and extract the time parts |
||||||
|
$cleaned = str_replace('-', '', $uuid); |
||||||
|
|
||||||
|
// Reconstruct the full 12-character timestamp hex |
||||||
|
$timeHex = |
||||||
|
substr($cleaned, 0, 8) . // First 8 chars (time_low) |
||||||
|
substr($cleaned, 8, 4); // Next 4 chars (time_mid) |
||||||
|
|
||||||
|
// Convert to decimal (custom scaled time) |
||||||
|
$customTime = hexdec($timeHex); |
||||||
|
|
||||||
|
// Reverse the scaling: divide by 1000 to get seconds |
||||||
|
$unixSeconds = (int)($customTime / 1000); |
||||||
|
|
||||||
|
return $unixSeconds; |
||||||
|
} |
||||||
|
|
||||||
|
public static function getFormatedDate(int $timestamp) { |
||||||
|
return date('Y-m-d H:i:s', $timestamp); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @example Database SQL: |
||||||
|
CREATE TABLE test_table ( |
||||||
|
id CHAR(36) PRIMARY KEY, -- IMPORTANT PART, to use generateUUIDv7 (UUIDv7) |
||||||
|
-- OR CHAR(26) for base32_encoded_UUIDv7 (BASE32 UUIDv7)... |
||||||
|
name VARCHAR(255) NOT NULL, -- BLA BLA |
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP |
||||||
|
); |
||||||
|
*/ |
||||||
|
} |
||||||
@ -0,0 +1,121 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types = 1); |
||||||
|
|
||||||
|
/** |
||||||
|
* Modified from phptutorial.net |
||||||
|
* @link https://www.phptutorial.net/php-tutorial/php-validation/ |
||||||
|
*/ |
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
enum DEFAULT_VALUE { |
||||||
|
case do_error_on_null; |
||||||
|
case do_not_error_on_null; |
||||||
|
} |
||||||
|
|
||||||
|
class Validator |
||||||
|
{ |
||||||
|
use \IOcornerstone\Framework\Trait\FormValidator; |
||||||
|
|
||||||
|
private static function makeArrays( |
||||||
|
array $data, |
||||||
|
$field, |
||||||
|
DEFAULT_VALUE $default_value = DEFAULT_VALUE::do_not_error_on_null |
||||||
|
): array { |
||||||
|
$dataset = []; |
||||||
|
if (isset($data[$field])) { |
||||||
|
if (common::get_count($data[$field])) { |
||||||
|
foreach($data[$field] as $the_data) { |
||||||
|
$dataset[] = $the_data; |
||||||
|
} |
||||||
|
} else { |
||||||
|
$dataset[] = $data[$field]; |
||||||
|
} |
||||||
|
} else { |
||||||
|
|
||||||
|
$default = match($default_value) { |
||||||
|
DEFAULT_VALUE::do_not_error_on_null => null, |
||||||
|
// Fail validation as user requested false on default |
||||||
|
DEFAULT_VALUE::do_error_on_null => false, |
||||||
|
default => null, // Pass validation as null or value unset |
||||||
|
}; |
||||||
|
$dataset[] = $default; // How to handle nulls |
||||||
|
} |
||||||
|
return $dataset; |
||||||
|
} |
||||||
|
|
||||||
|
public static function validate( |
||||||
|
array $data, |
||||||
|
array $fields, |
||||||
|
array $messages = [], |
||||||
|
DEFAULT_VALUE $default_value = DEFAULT_VALUE::do_not_error_on_null |
||||||
|
): 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::makeArrays($data, $field, $default_value) 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 |
||||||
|
$lookfor = $messages[$field][$rule_name] ?? $validation_errors[$rule_name]; |
||||||
|
$errors[$field] = sprintf( |
||||||
|
$lookfor, |
||||||
|
$field, |
||||||
|
...$params |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $errors; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
$data = ['email'=>'jim@aol.com']; |
||||||
|
$fields = ['email' => 'required | email']; |
||||||
|
|
||||||
|
$errors = Validator::validate($data, $fields, default_value: DEFAULT_VALUE::do_error_on_null); |
||||||
|
|
||||||
|
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,282 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Robert Strutts |
||||||
|
* @copyright (c) 2026, Robert Strutts |
||||||
|
* @license MIT |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace IOcornerstone\Framework; |
||||||
|
|
||||||
|
use IOcornerstone\Framework\Enum\ViewType; |
||||||
|
use IOcornerstone\Framework\{ |
||||||
|
Common, |
||||||
|
Configure, |
||||||
|
Requires, |
||||||
|
Registry, |
||||||
|
}; |
||||||
|
|
||||||
|
final class View |
||||||
|
{ |
||||||
|
|
||||||
|
public $white_space_control = false; |
||||||
|
public $page_output; |
||||||
|
private $vars = []; |
||||||
|
private $files = []; |
||||||
|
private $template = false; |
||||||
|
private $use_template_engine = false; |
||||||
|
private $use_template_engine_twig = false; |
||||||
|
private $use_template_engine_liquid = false; |
||||||
|
private $template_type = 'tpl'; |
||||||
|
protected $tempalte_engine_twig = null; |
||||||
|
protected $tempalte_engine_liquid = null; |
||||||
|
|
||||||
|
private function getFile(string $view_file, string $default): string |
||||||
|
{ |
||||||
|
$file = (empty($default)) ? "views/{$view_file}" : "views/{$default}/{$view_file}"; |
||||||
|
$path = IO_CORNERSTONE_PROJECT; |
||||||
|
$vf = $path . $file; |
||||||
|
if (Requires::saferFileExists($vf) !== false) { |
||||||
|
return $file; |
||||||
|
} |
||||||
|
return ''; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Alias to set_view |
||||||
|
*/ |
||||||
|
public function include(string $file, ViewType $type = ViewType::PHP): void |
||||||
|
{ |
||||||
|
$this->setView($file, $type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Alias to set_view |
||||||
|
*/ |
||||||
|
public function addView(string $file, ViewType $type = ViewType::PHP): void |
||||||
|
{ |
||||||
|
$this->setView($file, $type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Check the $_GET['render'] for which folder to use to render the view |
||||||
|
* @param string $viewFile |
||||||
|
* @return file | false |
||||||
|
*/ |
||||||
|
private function findViewPath(string $viewFile): string|false |
||||||
|
{ |
||||||
|
$found = false; |
||||||
|
|
||||||
|
$defaultPaths = Configure::get('view_mode', 'default_paths'); |
||||||
|
$get = $_GET['render']; |
||||||
|
if (in_array($get, $defaultPaths)) { |
||||||
|
if (($key = array_search($get, $defaultPaths)) !== false) { |
||||||
|
unset($defaultPaths[$key]); // Remove as we'll make it first later... |
||||||
|
} |
||||||
|
array_unshift($defaultPaths, $get); // Make First in Array! |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($defaultPaths as $default) { |
||||||
|
$file = $this->getFile($viewFile, $default); |
||||||
|
if (!empty($file)) { |
||||||
|
$found = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
return ($found) ? $file : false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Use View File |
||||||
|
* @param string $viewFile |
||||||
|
* @param string $render_path |
||||||
|
* @throws Exception |
||||||
|
*/ |
||||||
|
public function setView(?string $viewFile = null, ViewType $type = ViewType::PHP): void |
||||||
|
{ |
||||||
|
if ($viewFile === null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
$file_ext = ViewType::get_file_extension_for($type); |
||||||
|
$file = $this->findViewPath($viewFile . $file_ext); |
||||||
|
if ($type == ViewType::TWIG) { |
||||||
|
$this->use_template_engine_twig = true; |
||||||
|
$this->template_type = str_replace('.', '', $file_ext); |
||||||
|
$path = IO_CORNERSTONE_PROJECT; |
||||||
|
if ($file !== false) { |
||||||
|
$file = str_replace("views/twig", "", $file); // Remove Path, as it is defined in the service file |
||||||
|
} |
||||||
|
} |
||||||
|
if ($type == ViewType::LIQUID) { |
||||||
|
$this->use_template_engine_liquid = true; |
||||||
|
$this->template_type = str_replace('.', '', $file_ext); |
||||||
|
if ($file !== false) { |
||||||
|
$file = ltrim(str_replace("views/liquid", "", $file), "/"); // Remove Path, as it is defined in the service file |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ($file === false) { |
||||||
|
echo "No view file exists for: {$viewFile}!"; |
||||||
|
throw new \Exception("View File does not exist: " . $viewFile); |
||||||
|
} else { |
||||||
|
$this->files[] = array('file' => $file, 'path' => bootstrap\UseDir::PROJECT, 'file_type' => $file_ext); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function clearTemplate(): void |
||||||
|
{ |
||||||
|
$this->template = false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Use PHP Template with view |
||||||
|
* @param string $render_page |
||||||
|
* @return bool was found |
||||||
|
*/ |
||||||
|
public function setPhpTemplate(string $render_page): bool |
||||||
|
{ |
||||||
|
if (!empty($render_page)) { |
||||||
|
$render_page = str_replace('.php', '', $render_page); |
||||||
|
$templ = "/templates/{$render_page}"; |
||||||
|
if (bootstrap\requires::safer_file_exists(IO_CORNERSTONE_PROJECT . $templ . '.php') !== false) { |
||||||
|
$this->template = $templ; |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
$this->template = false; |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
private function clearOb(int $saved_ob_level): void |
||||||
|
{ |
||||||
|
while (ob_get_level() > $saved_ob_level) { |
||||||
|
ob_end_flush(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function obStart(): void |
||||||
|
{ |
||||||
|
if (extension_loaded('mbstring')) { |
||||||
|
ob_start('mb_output_handler'); |
||||||
|
} else { |
||||||
|
ob_start(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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, mixed $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, ViewType $type = ViewType::PHP) |
||||||
|
{ |
||||||
|
echo $this->fetch($local, $file, $type); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Outputs view |
||||||
|
* @param type $local = $this |
||||||
|
* @param $file (optional view file) |
||||||
|
*/ |
||||||
|
public function fetch($local, ?string $file = null, ViewType $type = ViewType::PHP): string |
||||||
|
{ |
||||||
|
$page_output = ob_get_clean(); // Get echos before View |
||||||
|
$this->obStart(); |
||||||
|
$saved_ob_level = ob_get_level(); |
||||||
|
$this->setView($file, $type); |
||||||
|
|
||||||
|
unset($file); |
||||||
|
|
||||||
|
if (!is_object($local)) { |
||||||
|
$local = $this; // FALL Back, please use fetch($this); |
||||||
|
} |
||||||
|
|
||||||
|
if ($this->use_template_engine_liquid) { |
||||||
|
$this->tempalte_engine_liquid = Registry::get('di')->get_service('liquid', [$this->template_type]); |
||||||
|
if ($this->white_space_control) { |
||||||
|
$this->tempalte_engine->whitespace_control(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ($this->use_template_engine_twig) { |
||||||
|
$this->tempalte_engine_twig = Registry::get('di')->get_service('twig', [$this->template_type]); |
||||||
|
} |
||||||
|
|
||||||
|
if (count($this->files) > 0) { |
||||||
|
foreach ($this->files as $view_file) { |
||||||
|
if ($view_file['file_type'] == '.php') { |
||||||
|
Requires::secureInclude($view_file['file'], UseDir::PROJECT, $local, $this->vars); // Include the PHP file |
||||||
|
} else if ($view_file['file_type'] == '.tpl') { |
||||||
|
// Liquid uses .tpl by default, so remove that File Ext |
||||||
|
$template_file = str_replace('.tpl', '', $view_file['file']); |
||||||
|
$this->tempalte_engine_liquid->parse_file($template_file); |
||||||
|
|
||||||
|
$assigns = $this->vars['template_assigns'] ?? []; |
||||||
|
$filters = $this->vars['template_filters'] ?? null; |
||||||
|
$registers = $this->vars['template_registers'] ?? []; |
||||||
|
$assigns['production'] = (isLive()); |
||||||
|
echo $this->tempalte_engine_liquid->render($assigns, $filters, $registers); |
||||||
|
} else if ($view_file['file_type'] == '.twig') { |
||||||
|
$fns = $this->vars['twig_functions'] ?? []; |
||||||
|
if (Common::getCount($fns)) { |
||||||
|
foreach ($fns as $fn_label => $fn_name) { |
||||||
|
$this->tempalte_engine_twig->addFunction(new \Twig\TwigFunction($fn_label, $fn_name)); |
||||||
|
} |
||||||
|
} |
||||||
|
$twig_data = $this->vars['twig_data'] ?? []; |
||||||
|
echo $this->tempalte_engine_twig->render($view_file['file'], $twig_data); |
||||||
|
} else { |
||||||
|
throw new \Exception("Unable View File Type"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
$this->clear_ob($saved_ob_level); |
||||||
|
$page_output .= ob_get_clean(); |
||||||
|
|
||||||
|
if (Configure::get('CodeHydrater', 'check_HTML_tags') === true) { |
||||||
|
$tags = TagMatches::checkTags($page_output); |
||||||
|
if (!empty($tags['output'])) { |
||||||
|
$page_output .= $tags['output']; |
||||||
|
$page_output .= '<script type="text/javascript">' . $tags['alert'] . '</script>'; |
||||||
|
foreach ($this->files as $bad) { |
||||||
|
$page_output .= "<script type=\"text/javascript\">console.log'In view file:{$bad['file']}');\r\n</script>"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ($this->template !== false && Requires::saferFileExists(IO_CORNERSTONE_PROJECT . $this->template . ".php") !== false) { |
||||||
|
$this->obStart(); |
||||||
|
$saved_ob_level = ob_get_level(); |
||||||
|
$local->page_output = $page_output; |
||||||
|
Requires::secureInclude($this->template, UseDir::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; |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue