Robert 3 days ago
parent 14b5a48bed
commit b1715e8f3e
  1. 7
      src/Bootstrap.php
  2. 5
      src/Framework/Assets.php
  3. 25
      src/Framework/Common.php
  4. 105
      src/Framework/Database/DummyData.php
  5. 347
      src/Framework/Database/Model.php
  6. 283
      src/Framework/Database/Paginate.php
  7. 7
      src/Framework/HtmlDocument.php
  8. 4
      src/Framework/Http/Kernel.php
  9. 2
      src/Framework/Http/Request.php
  10. 8
      src/Framework/Middleware/ErrorMiddleware.php
  11. 3
      src/Framework/Requires.php
  12. 2
      src/Framework/Security.php
  13. 19
      src/Framework/SiteHelper.php
  14. 42
      src/Framework/TagMatches.php
  15. 17
      src/Framework/TimeZoneSelection.php
  16. 36
      src/Framework/View.php

@ -49,6 +49,13 @@ $loader->addNamespace("Psr\Http\Server", [
SiteHelper::setupHTTP();
function ddd(...$args) {
$stringArgs = array_map(function($arg) {
return (string)$arg;
}, $args);
return dd(implode('', $stringArgs));
}
function dd($var = 'nothing', endDump $end = endDump::EXIT_AND_STOP)
{
Common::dump($var, $end);

@ -233,6 +233,11 @@ class Assets
//return "<script type=\"text/javascript\">js_loader(\"{$file}\");</script>";
}
public static function inlineCss(string $code): string
{
return "<style>{$code}\r\n</style>\r\n";
}
/**
* Purpose: To do inline JavaScript.
* @param type $code string of code to inline into page.

@ -32,6 +32,31 @@ final class Common
return array_keys($array) !== range(0, count($array) - 1);
}
public static function combineDataToString(object $o, ...$args): string
{
$stringArgs = array_map(function($arg) {
if (is_array($arg) || is_object($arg)) {
$arg = json_encode($arg);
}
return (string)$arg;
}, $args);
$seperator = $o->seperator ?? "|";
$combined = implode($seperator, $stringArgs);
$useLogger = $o->useLogger ?? false;
if ($useLogger) {
\IOcornerstone\doLogger()->alert($combined);
return $combined;
}
$doDump = $o->doDump ?? false;
if ($doDump) {
dump($combined);
}
return $combined;
}
public static function stringSubPart(string $string, int $offset = 0, ?int $length = null, $encoding = null) {
if ($length === null) {
return F::substr($string, $offset, strlen($string));

@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace IOcornerstone\Framework\Database;
use IOcornerstone\Framework\{
RandomEngine,
Common,
};
final class DummyData {
private $pdo;
private $randomEngine;
public function __construct(\PDO $pdo) {
$this->pdo = $pdo;
$this->randomEngine = new RandomEngine();
}
/*
* Helper for add_dummy_data and get_dummy_data.
* Purpose: To return ONE ROW of junk/dummy data from input data.
* @param array $data
* @retval array of random dummy data AKA one row worth of it.
*/
private function useDummyData(array $data): array {
$ret = [];
foreach ($data as $field => $array_values) {
$array_count = Common::getCount($array_values);
if ($array_count) {
$ret[$field] = $array_values[$this->randomEngine->getInt(0, $array_count - 1)];
}
}
return $ret;
}
/**
* Inserts Dummy Data to DB
* @param int $num_rows to add/make
* @param array $data sample data EX: array('fname'=>array('bob','kim','lisa', ect...), ...)
* @retval bool true
*/
public function addDummyData(string $table, int $num_rows, array $data): bool {
for ($i = 0; $i < $num_rows; $i++) {
$bind_data = $this->useDummyData($data);
$fields = [];
foreach ($data as $field => $array_values) {
$fields[] = $field;
}
$sql = "INSERT INTO `{$table}` "
. "(" . implode(", ", $fields) . ") "
. "VALUES (:" . implode(", :", $fields) . ");";
$bind = [];
foreach ($fields as $field) {
$bind[":$field"] = $bind_data[$field];
}
unset($bind_data);
unset($fields);
$pdo_stmt = $this->pdo->prepare($sql);
$exec = $pdo_stmt->execute($bind);
unset($bind);
}
return true;
}
/**
* Make an array of fields for a fake row of dummy data out of input data.
* @param int $num_rows to make
* @param array $data sample data
* @retval array of rows with dummy data in it.
*/
public function getDummyData(int $num_rows, array $data): array {
if ($num_rows > 100) {
throw new \LengthException("Generating too much dummy data via \$num_rows!");
}
$ret = [];
for ($i = 0; $i < $num_rows; $i++) {
$ret[] = $this->useDummyData($data);
}
return $ret;
}
public function getDummyDataGenerator(
int $num_rows,
array $data
): \Generator {
for ($i = 0; $i < $num_rows; $i++) {
yield $this->useDummyData($data);
}
}
}

@ -0,0 +1,347 @@
<?php
declare(strict_types=1);
/**
* @author Robert Strutts <Robert@TryingToScale.com>
* @copyright Copyright (c) 2022, Robert Strutts.
* @license MIT
*/
namespace IOcornerstone\Framework\Database;
use IOcornerstone\Framework\{
TimeZones,
Common,
};
class Model {
use \IOCornerstone\Framework\Trait\Database\RunSql;
use \IOCornerstone\Framework\Trait\Database\Validation;
const SUCCESSFUL_SAVE = 1;
const DUPLICATE_FOUND = 2;
const VALIDATION_ERROR = 3;
const PRE_SAVE_FAILED = 4;
const POST_SAVE_FAILED = 5;
private $members = [];
private $validationMembers = [];
private $missing = [];
private $errorMessage;
private $primaryKey = 'id';
private $dbSkiped = [];
public function saved(bool $worked = true): array {
$time = TimeZones::convertTimeZone(array('format' => 'g:i a'));
return ['time' => $time, 'saved' => $worked];
}
/**
* JSON API inform AJAX that save failed
*/
public function saveFailed($msg = 'Save Failed!'): array {
return ['saved' => false, 'msg' => $msg];
}
public function dumpTableFields(string $table) {
$fields = $this->get_fields($table);
$this->doDump($fields);
}
public function dumpDiff(): bool {
$fields = $this->get_fields($this->table);
$diff = array_diff($fields, array_keys($this->members));
if (($key = array_search('id', $diff)) !== false) {
unset($diff[$key]); // Who cares about IDs
}
foreach($this->dbSkiped as $skip) {
unset($diff[$skip]);
}
if (count($diff)) {
echo "Diff on fields:<br>" . PHP_EOL;
$this->doDump($diff);
return true;
}
return false;
}
private function doDump(array $data) {
echo "<pre style=\"border: 1px solid #000; overflow: auto; margin: 0.5em;\">";
print_r($data);
echo '</pre>';
}
public function setPrimaryKeyName(string $key): void {
$this->primaryKey = $key;
}
/**
* Do not use this one in production! AS anyone can override
* the DB security and Inject stuff into the DB via POST, etc...
*/
public function autoSetMembers(array $globals = [], array $skip = ['route', 'm'], array $onlyThese = []): void {
if (isLive()) {
return; // Bail if LIVE
}
foreach ($globals as $key => $data) {
if (count($skip) && in_array($key, $skip)) {
continue;
}
if (in_array($key, $onlyThese) || !count($onlyThese)) {
$this->members[$key] = (! empty($data)) ? $data : "";
}
}
}
public function getMissing() {
return $this->missing;
}
public function getMember(string $member) {
return $this->members[$member] ?? null;
}
public function getMembers() {
return $this->members;
}
public function setMember(string $member, string $data): void {
$this->members[$member] = $data;
}
public function hasMember($member): bool {
return isset($this->members[$member]);
}
public function getLastError(): string {
return $this->errorMessage;
}
/**
* Set Member for Validation
* @param string $key
* @param array $a
*/
public function setMemberForValidation(string $key, array $a): void {
$this->validationMembers[$key] = $a;
}
/**
* Get Validation Member
* @param string $key
* @return array
*/
public function getVaildationMember(string $key): array {
return $this->validationMembers[$key] ?? array();
}
/**
* Unset Validation Member
* @param string $key
*/
public function clearValidationMember(string $key): void {
unset($this->validationMembers[$key]);
}
private function cleanup($bind) {
if (!is_array($bind)) {
if (!empty($bind)) {
$bind = array($bind);
} else {
$bind = array();
}
}
return $bind;
}
public static function emptyGenerator(): \Generator {
yield from [];
}
/**
* Please use this fetch_lazy instead of fetch_all!!!
* To AVOID running out of Memory!!!!!!!!!!!!!!!!!!!!
* @param \PDOStatement $stmt
* @return \Generator
*/
public static function pdoFetchLazy(\PDOStatement $stmt): \Generator {
foreach($stmt as $record) {
yield $record;
}
}
public static function isValid(array $data): bool {
$error_count = (isset($data['errors'])) ? count($data['errors']) : 0;
return ($error_count === 0);
}
public function load(int $id): bool{
if (method_exists($this, 'pre_load')) {
$this->pre_load();
}
$sql = "SELECT * FROM {$this->table} WHERE {$this->primaryKey} = ? LIMIT 1";
$query = $this->pdo->prepare($sql);
$query->execute(array($id));
if ($query === false) {
return false;
}
$data = $query->fetch(\PDO::FETCH_ASSOC);
if ($data === false) {
return false;
}
$this->members = array_merge($this->members, $data);
if (method_exists($this, 'post_load')) {
$this->post_load();
}
return true;
}
public function insert($table, $info) {
$fields = $this->filter($table, $info);
if (strrpos($table, "`") === false) {
$table = "`{$table}`";
}
$sql = "INSERT INTO {$table} (" . implode(", ", $fields) . ") VALUES (:" . implode(", :", $fields) . ");";
$bind = array();
foreach ($fields as $field) {
$bind[":$field"] = $info[$field];
}
return $this->run($sql, $bind);
}
public function update($table, $info, $where, $bind = "") {
$bind = $this->cleanup($bind);
$fields = $this->filter($table, $info);
if (strrpos($table, "`") === false) {
$table = "`{$table}`";
}
$sql = "UPDATE {$table} SET ";
$f = 0;
foreach ($fields as $key => $value) {
if ($f > 0) {
$sql .= ", ";
}
$f++;
$value = trim($value);
if (strrpos($value, "`") === false) {
$cf = '`' . $value . '`';
} else {
$cf = $value;
}
$sql .= $cf . " = :update_" . $value;
$bind[":update_$value"] = $info[$value];
}
$sql .= " WHERE " . $where . ";";
return $this->run($sql, $bind);
}
public static function makeDbTimeStamp(): string {
return TimeZones::convertTimeZone(['format' => 'database', 'timezone' => 'UTC']);
}
/**
* Save
*
* @param object $lcoal_model Should be $this from the Model
*
* Insert if primary key not set
* Update if primary key set
*/
public function save(bool $validate = true): int {
if (method_exists($this, 'preSave')) {
$preWasSuccessfull = $this->preSave();
if (! $preWasSuccessfull) {
return self::PRE_SAVE_FAILED;
}
}
$name = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); // Get DB Driver
if ($name === 'mysql' && $validate) {
if (!$this->validateMysql()) {
return self::VALIDATION_ERROR;
}
}
if ($this->hasMember('modified')) {
$this->setMember('modified', self::makeDbTimeStamp());
}
if (empty($this->members[$this->primaryKey])) {
if (method_exists($this, 'duplicateCheck')) {
$dup = $this->duplicateCheck();
if ($dup) {
return self::DUPLICATE_FOUND;
}
}
if ($this->hasMember('created')) {
$this->setMember('created', self::makeDbTimeStamp());
}
$this->members[$this->primaryKey] = $this->insert($this->table, $this->members);
} else {
$this->setMember("row_count", $this->update($this->table, $this->members,
"`{$this->primaryKey}` = :search_key",
array(':search_key' => $this->members[$this->primaryKey])
));
}
if (method_exists($this, 'postSave')) {
$postSaveSuccessfull = $this->postSave();
if (! $postSaveSuccessfull) {
return self::POST_SAVE_FAILED;
}
}
return self::SUCCESSFUL_SAVE;
}
/**
* This FN requires SSL connection to the database.
* So, the KEY does not get exposed!!!!
*/
public function encodeMySQLusingAES(#[\SensitiveParameter] string $data, #[\SensitiveParameter] string $key, bool $bind = true) {
$safe_text = addslashes($data);
$safe_key = addslashes($key);
$parm = ($bind) ? ":{$safe_text}" : "'{$safe_text}'";
$ret = "HEX(AES_ENCRYPT($parm},'{$safe_key}'))";
Common::wipe($key);
Common::wipe($safe_key);
Common::wipe($data);
Common::wipe($safe_text);
Common::wipe($parm);
return $ret;
}
/**
* This FN requires SSL connection to the database.
* So, the KEY does not get exposed!!!!
*/
public function decodeMySQLusingAES(string $field_name, #[\SensitiveParameter] string $key, bool $add_as = false) {
$safe_field = addslashes($field_name);
$safe_key = addslashes($key);
$as = ($add_as === true) ? " AS `{$safe_field}`" : "";
return "CAST(AES_DECRYPT(UNHEX(`{$safe_field}`),'{$safe_key}') AS char){$as}";
}
/* End Model */
}

@ -0,0 +1,283 @@
<?php
declare(strict_types=1);
/**
* Modified from:
* @link https://code.tutsplus.com/tutorials/how-to-paginate-data-with-php--net-2928
*/
namespace IOcornerstone\Framework\Database;
class Paginate
{
private $_conn;
private int $_limit;
private int $_page;
private $_query;
private $_total;
private $_url;
private $_url_limit;
private $_url_page;
public $maxLimit = 500;
/*
* Query needs to be string for MySQL and array for MonogDB
*/
public function __construct($conn, string|array $query, string $dbType = "mysql")
{
$this->_conn = $conn;
$this->_query = $query;
if ($dbType === "mysql") {
$rs = $this->_conn->query($this->_query);
$this->_total = $rs->rowCount();
}
}
private function setLimit(int $limit = 10): int
{
if ($limit < 1) {
return 10;
}
return ($limit > $this->maxLimit) ? $this->maxLimit : $limit;
}
private function setPage(int $page = 1): int
{
return ($page < 1) ? 1 : $page;
}
public function mongoGetData(int $limit = 10, int $page = 1, array $options = [])
{
$this->_limit = $this->setLimit($limit); // Number of items per page
$this->_page = $this->setPage($page); // The current page number
$skip = (($this->_page - 1) * $this->_limit);
if ($this->_limit === 0) {
$db_options = $options;
} else {
$limits = ['limit' => $this->_limit, 'skip' => $skip];
$db_options = array_merge($limits, $options);
}
// NOTE _query is the WHERE condition as an array
$collection = $this->_conn;
$documents = $collection->find($this->_query, $db_options);
// Calculate the total number of documents in the collection
$this->_total = $collection->countDocuments($this->_query);
$result = new \stdClass();
$result->page = $this->_page;
$result->limit = $this->_limit;
$result->total = $this->_total;
$result->data = $documents;
return $result;
}
public function getData(int $limit = 10, int $page = 1)
{
$this->_limit = $this->setLimit($limit);
$this->_page = $this->setPage($page);
if ($this->_limit === 0) {
$query = $this->_query;
} else {
$query = $this->_query . " LIMIT " . (($this->_page - 1) * $this->_limit) . ", $this->_limit";
}
$rs = $this->_conn->query($query);
$result = new \stdClass();
$result->page = $this->_page;
$result->limit = $this->_limit;
$result->total = $this->_total;
$result->rs = $rs;
return $result;
}
public function useHashRoutes(string $url): void
{
$this->_url = $url;
$this->_url_limit = "/";
$this->_url_page = "/";
}
public function useGetRoutes(string $url = ""): void
{
$this->_url = $url;
$this->_url_limit = "?limit=";
$this->_url_page = "&page=";
}
private function do_href(int $limit, int $page): string
{
return 'href="' . $this->_url . $this->_url_limit . $limit . $this->_url_page . $page . '"';
}
public function createLinks(int $links = 7, string $list_class = "ui pagination menu", string $item = "item"): string
{
$last = ceil($this->_total / $this->_limit);
if ($this->_limit === 0 || $last < 2) {
return '';
}
$start = (($this->_page - $links) > 0) ? $this->_page - $links : 1;
$end = (($this->_page + $links) < $last) ? $this->_page + $links : $last;
$html = '<div class="' . $list_class . '">';
$class = ($this->_page == 1) ? "disabled" : "";
$item = " " . $item;
$html .= '<a class="' . $class . $item . '" ' . $this->do_href($this->_limit, $this->_page - 1) . '>&laquo;</a>';
if ($start > 1) {
$html .= '<a class="' . $item . '" ' . $this->do_href($this->_limit, 1) . '>1</a>';
$html .= '<div class="disabled' . $item . '"><span>...</span></div>';
}
for ($i = $start; $i <= $end; $i++) {
$class = ($this->_page == $i) ? "active" : "";
$html .= '<a class="' . $class . $item . '" ' . $this->do_href($this->_limit, $i) . '>' . $i . '</a>';
}
if ($end < $last) {
$html .= '<div class="disabled' . $item . '"><span>...</span></div>';
$html .= '<a class="' . $class . $item . '" ' . $this->do_href($this->_limit, $last) . '>' . $last . '</a>';
}
$class = ($this->_page == $last) ? "disabled" : "";
$html .= '<a class="' . $class . $item . '" ' . $this->do_href($this->_limit, $this->_page + 1) . '>&raquo;</a>';
$html .= '</div>';
return $html;
}
public function createJumpMenuWithLinks(int $links = 7, string $label = "Jump to ", string $end_label = " page.", string $item = "item"): string
{
if ($this->_limit === 0) {
return '';
}
$last = ceil($this->_total / $this->_limit);
$start = (($this->_page - $links) > 0) ? $this->_page - $links : 1;
$end = (($this->_page + $links) < $last) ? $this->_page + $links : $last;
// Prev. Page
$class = ($this->_page == 1) ? "disabled " : "";
$item = " " . $item;
$href = ($this->_page == 1) ? "" : $this->do_href($this->_limit, $this->_page - 1);
$html .= '<a class="' . $class . $item . '" ' . $href . '>&laquo;</a>';
$html .= $this->createJumpMenu();
// Next Page
$class = ($this->_page == $last) ? "disabled " : "";
$href = ($this->_page == $last) ? "" : $this->do_href($this->_limit, $this->_page + 1);
$html .= '<a class="' . $class . $item . '" ' . $href . '>&raquo;</a>';
return $html;
}
public function createJumpMenu(string $label = "Jump to ", string $end_label = " page.", string $item = "item"): string
{
$last = ceil($this->_total / $this->_limit);
$option = '';
for ($i = 1; $i <= $last; $i++) {
$option .= ($i == $this->_page) ? "<option value=\"{$i}\" selected>Page {$i}</option>\n" : "<option value=\"{$i}\">Page {$i}</option>\n";
}
return "<label>{$label}<select class=\"{$item}\" onchange=\"window.location='" . $this->_url . $this->_url_limit . $this->_limit . $this->_url_page . "'+this[this.selectedIndex].value;return false;\">"
. "{$option}</select>"
. "{$end_label}</label>\n";
}
public function createItemsPerPage(string $label = "Items ", string $end_label = " per page.", string $item = "item"): string
{
$items = '';
$ipp_array = array(3, 6, 12, 24, 50, 100);
$found = false;
foreach ($ipp_array as $ipp_opt) {
if ($ipp_opt == $this->_limit) {
$found = true;
}
$items .= ($ipp_opt == $this->_limit) ? "<option selected value=\"{$ipp_opt}\">{$ipp_opt}</option>\n" : "<option value=\"{$ipp_opt}\">{$ipp_opt}</option>\n";
}
if ($found === false) {
$items = "<option selected value=\"{$this->_limit}\">{$this->_limit}</option>\n" . $items;
}
$my_page = str_replace("&", "?", $this->_url_page);
$my_limit = str_replace("?", "&", $this->_url_limit);
return "<label>{$label}<select class=\"{$item}\" onchange=\"window.location='" . $my_page . "1" . $my_limit . "'+this[this.selectedIndex].value;return false;\">{$items}</select>"
. "{$end_label}</label>\n";
}
public function getCSS(): string
{
return ".pagination a {
color: #333;
padding: 2px 20px;
line-height: 1;
font-size: 14px;
text-decoration: none;
border-radius: 6px;
border: 1px solid #ddd;
background: #fff;
box-shadow: 0 1px 3px rgba(0,0,0,.1);
transition: all .2s ease;
}
.pagination a:hover:not(.active) {
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(0,0,0,.15);
}
.pagination a.active {
background: #4CAF50;
color: white;
border-color: #4CAF50;
}
.pagination .disabled {
display: inline-block;
padding: 4px 12px;
color: #999;
border: 1px solid #ddd;
border-radius: 4px;
background: #f8f8f8;
cursor: not-allowed;
pointer-events: none;
opacity: 0.6;
}
div.pagination.menu {
padding-top: 10px;
}";
}
}
/*
* mongo db
$limit = $_GET['limit'] ?? 18;
$page = $_GET['page'] ?? 1;
$url = "#pages/bill";
$query = [ 'user_id' => (string) $user_id ];
$mongo_options = [ 'projection' => [ 'billing' => 1 ]];
$pag = new Paginate($collection, $query, "mongodb");
$r = $pag->mongoGetData($limit, $page, $mongo_options);
$pag->useHashRoutes($url);
foreach ($r->data as $bill) {
echo $bill;
}
echo $pag->createLinks();
*/

@ -352,6 +352,13 @@ class HtmlDocument
}
}
public function addToCss(string $css): void
{
if (!empty($css)) {
$this->styles .= Assets::inlineCss($css);
}
}
/**
* Use CSS/JS for Database SSP
public function datatables_code(): void {

@ -121,7 +121,9 @@ class Kernel {
private function emit(Response $response): void
{
http_response_code($response->getStatusCode());
if (! Console::isConsole()) {
http_response_code($response->getStatusCode());
}
foreach ($response->getHeaders() as $name => $values) {
if (! is_array($values) && ! is_object($values)) {
header("$name: $values", false);

@ -6,7 +6,7 @@ use Psr\Http\Message\{
ServerRequestInterface,
UriInterface,
StreamInterface,
UploadedFileInterface
// UploadedFileInterface
};
use IOcornerstone\Framework\Trait\ServerRequestDelegation;
use IOcornerstone\Framework\ParameterBag;

@ -19,6 +19,7 @@ use IOcornerstone\Framework\{
final class ErrorMiddleware implements MiddlewareInterface
{
private $headers = [];
public function __construct(
private LoggerInterface $logger,
private bool $hideErrors = false
@ -43,8 +44,7 @@ final class ErrorMiddleware implements MiddlewareInterface
: 'Internal Server Error';
$stream = Stream::fromString($bodyString);
return new Response(500, ['Content-Type' => 'text/plain'], $stream);
return new Response(status: 500, body: $stream, headers: $this->headers);
}
}
@ -62,6 +62,7 @@ final class ErrorMiddleware implements MiddlewareInterface
if (Console::isConsole()) {
$codeNumber = $e->getCode() ?? 0;
$this->headers = ['Content-Type' => 'text/plain'];
return sprintf(
"Code# %d; %s\n\n%s",
$codeNumber,
@ -71,9 +72,10 @@ final class ErrorMiddleware implements MiddlewareInterface
}
if (Reg::get('error_handler')->isJsonRequest()) {
$this->headers = ['Content-Type' => 'application/json'];
Reg::get('error_handler')->getJsonDebug($e);
}
$this->headers = ['Content-Type' => 'text/html'];
return Reg::get('error_handler')->formatWebMessage($e);
}
}

@ -15,6 +15,7 @@ enum UseDir: string {
case FRAMEWORK = "Framework";
case PROJECT = "Project";
case ONERROR = "OnError";
case BASEDIR = "BaseDir";
}
final class Requires {
@ -132,6 +133,7 @@ final class Requires {
// Keep offset negitive, to get file kind...
$fileKind = self::stringSubPart($file, -(strlen($file) - $posLastOccurrence));
$fileType = match ($fileKind) {
".txt" => ".txt",
".twig" => ".twig",
".tpl" => ".tpl",
default => ".php",
@ -160,6 +162,7 @@ final class Requires {
private static function secureFile(bool $returnContents, string $file, UseDir $path, $local = null, array $args = array(), bool $loadOnce = true) {
$dir = match ($path) {
UseDir::FIXED => "",
UseDir::BASEDIR => BaseDir,
UseDir::FRAMEWORK => IO_CORNERSTONE_FRAMEWORK,
UseDir::ONERROR => IO_CORNERSTONE_PROJECT . "Views/OnError/",
default => IO_CORNERSTONE_PROJECT,

@ -119,7 +119,7 @@ class Security
* salt and prevents time based side-channel attacks.
*/
public static function do_password_hash(#[\SensitiveParameter] string $password): bool|string
public static function doPasswordHash(#[\SensitiveParameter] string $password): bool|string
{
$pwdPeppered = self::makeHash($password);
$hashAlgo = Configure::get(

@ -10,8 +10,10 @@ declare(strict_types=1);
namespace IOcornerstone\Framework;
use IOcornerstone\Framework\Security;
use IOcornerstone\Framework\{
Security,
Console,
};
/**
* Description of SiteHelper
* Checks if IP is allowed for LIVE DEBUGGING
@ -100,6 +102,19 @@ final class SiteHelper
public static function remoteNotAllowedForceLive(): bool
{
if (Console::isConsole()) {
return false; // false to show errors and dumps
}
$s = $_SESSION['usersRights'] ?? false;
if ($s !== false && strlen($s) > 4) {
$rights = json_decode($s, associative: true);
$flipped = array_flip($rights);
if (isset($flipped['developer'])) {
return false; // false for Developers to see Errors/Logs
}
}
return (!self::is_allowed());
}

@ -15,7 +15,9 @@ use IOcornerstone\Framework\String as SF;
final class TagMatches
{
const TAGS_TO_CHECK = array('div', 'span', 'form', 'i*', 'a*', 'h1', 'p*');
const TAGS_TO_CHECK = ['div', 'span', 'form',
'i*', 'a*', 'h?', 'p*', 'td', 'th*', 'tr'
];
/**
* Function checks tags to make sure they match.
@ -23,40 +25,50 @@ final class TagMatches
* @param string $page
* @return array [output, alert]
*/
public static function checkTags(string $page): array
public static function checkTags(
string $page,
array $tags = [],
object $objAlerts
): array
{
$array_of_tags = array_merge(self::TAGS_TO_CHECK, $tags);
$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 = '<link rel="stylesheet" href="' . $objAlerts->assets . '" type="text/css" media="all" />';
$ui .= '<div class="' . $objAlerts->class . '"><b>';
$ui_end = '</b></div>';
foreach (self::TAGS_TO_CHECK as $tagName) {
if (str_contains($tagName, '*')) {
$tagName = str_replace('*', '', $tagName);
$otag = "<{$tagName}>"; // Open Tag
foreach ($array_of_tags as $tagName) {
$slen = strlen($tagName);
$tagNameWithoutStar = str_replace('*', '', $tagName);
$justTheTag = str_replace('?', '', $tagNameWithoutStar);
if (substr($tagName, $slen - 1, 1) === '*') {
$otag = "<{$justTheTag}>"; // Open Tag
$open = substr_count($lowercasePage, $otag); // Count open tags in page
$otag = "<{$tagName} "; /* Open Tag with space */
$otag = "<{$justTheTag} "; /* Open Tag with space */
$open += substr_count($lowercasePage, $otag); // Count open tags in page
} else {
$otag = "<{$tagName}"; // Open Tag
$otag = "<{$justTheTag}"; // Open Tag
$open = substr_count($lowercasePage, $otag); // Count open tags in page
}
$ctag = "</{$tagName}>"; // Close Tag
if (substr($tagName, $slen - 1, 1) === '?') {
$ctag = "</{$justTheTag}"; // Close Tag IE h1,h2
} else {
$ctag = "</{$justTheTag}>"; // 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} !!!";
$msg = "{$totalStillOpen} possibly MISSING closing {$justTheTag} Tags!!!";
$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} !!!";
$msg = abs($totalStillOpen) . " possibly MISSING opening {$justTheTag} Tags!!!";
$alert .= "console.log('{$msg}');\r\n";
$output .= (isLive()) ? "<!-- {$msg} -->\r\n" : "{$ui}{$msg}{$ui_end}\r\n";
}

@ -29,20 +29,23 @@ class TimeZoneSelection
foreach ($regions as $name => $mask) {
$zones = \DateTimeZone::listIdentifiers($mask);
foreach ($zones as $timezone) {
$this->timezones[$name][$timezone] = substr($timezone, strlen($name) + 1);
$time = new \DateTime("now", new \DateTimeZone($timezone));
$this->timezones[$name][$timezone] = substr($timezone, strlen($name) + 1) . ' - ' . $time->format('g:i a');
}
}
}
public function view() {
echo '<label>Select Your Timezone</label><br/><select id="timezone">';
public function view(string $tzName = ""): string {
$ret = '<label>Select Your Timezone</label><br/><select id="timezone" name="timezone">';
foreach ($this->timezones as $region => $list) {
echo '<optgroup label="' . $region . '">' . "\n";
$ret .= '<optgroup label="' . $region . '">' . "\n";
foreach ($list as $timezone => $name) {
echo '<option value="' . $timezone . '">' . $name . '</option>' . "\n";
$selected = ($tzName === $timezone) ? "Selected" : "";
$ret .= '<option value="' . $timezone . '"' . $selected . '>' . $name . '</option>' . "\n";
}
echo '<optgroup>' . "\n";
$ret .= '<optgroup>' . "\n";
}
echo '</select>';
$ret .= '</select>';
return $ret;
}
}

@ -21,15 +21,18 @@ use IOcornerstone\Framework\{
final class View
{
public $whiteSpaceControl = false;
public $pageOutput;
private $vars = [];
private $files = [];
public bool $whiteSpaceControl = false;
public string $pageOutput;
private array $vars = [];
private array $files = [];
private array $tags = [];
private string $alertAssets = "/assets/uikit/css/uikit.gradient.min.css";
private string $alertClass = "uk-alert uk-alert-danger";
private $template = false;
private $useTemplateEngine = false;
private $useTemplateEngineTwig = false;
private $useTemplateEngineLiquid = false;
private $templateType = 'tpl';
private bool $useTemplateEngine = false;
private bool $useTemplateEngineTwig = false;
private bool $useTemplateEngineLiquid = false;
private string $templateType = 'tpl';
protected $tempalteEngineTwig = null;
protected $tempalteEngineLiquid = null;
@ -167,6 +170,17 @@ final class View
}
}
public function changeAlerts(string $alertAsset, string $alertClass): void
{
$this->alertAssets = $alertAsset;
$this->alertClass = $alertClass;
}
public function addTags(array $tags): void
{
$this->tags = $tags;
}
/**
* Sets a variable in this view with the given name and value
*
@ -259,7 +273,11 @@ final class View
$pageOutput .= ob_get_clean();
if (Configure::get('IOcornerstone', 'check_HTML_tags') === true) {
$tags = TagMatches::checkTags($pageOutput);
$obj = new \stdClass();
$obj->assets = $this->alertAssets;
$obj->class = $this->alertClass;
$tags = TagMatches::checkTags($pageOutput, $this->tags, $obj);
if (!empty($tags['output'])) {
$pageOutput .= $tags['output'];
$pageOutput .= '<script type="text/javascript">' . $tags['alert'] . '</script>';

Loading…
Cancel
Save