diff --git a/src/Bootstrap.php b/src/Bootstrap.php
index 53393bd..1baf824 100644
--- a/src/Bootstrap.php
+++ b/src/Bootstrap.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);
diff --git a/src/Framework/Assets.php b/src/Framework/Assets.php
index ffa0ce4..dcdb71a 100644
--- a/src/Framework/Assets.php
+++ b/src/Framework/Assets.php
@@ -233,6 +233,11 @@ class Assets
//return "";
}
+ public static function inlineCss(string $code): string
+ {
+ return "\r\n";
+ }
+
/**
* Purpose: To do inline JavaScript.
* @param type $code string of code to inline into page.
diff --git a/src/Framework/Common.php b/src/Framework/Common.php
index e33b6d3..00be7df 100644
--- a/src/Framework/Common.php
+++ b/src/Framework/Common.php
@@ -31,6 +31,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) {
diff --git a/src/Framework/Database/DummyData.php b/src/Framework/Database/DummyData.php
new file mode 100644
index 0000000..fee035a
--- /dev/null
+++ b/src/Framework/Database/DummyData.php
@@ -0,0 +1,105 @@
+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);
+ }
+ }
+
+
+}
diff --git a/src/Framework/Database/Model.php b/src/Framework/Database/Model.php
new file mode 100644
index 0000000..727bc5b
--- /dev/null
+++ b/src/Framework/Database/Model.php
@@ -0,0 +1,347 @@
+
+ * @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:
" . PHP_EOL;
+ $this->doDump($diff);
+ return true;
+ }
+ return false;
+ }
+
+ private function doDump(array $data) {
+ echo "
"; + print_r($data); + echo ''; + } + + 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 */ +} diff --git a/src/Framework/Database/Paginate.php b/src/Framework/Database/Paginate.php new file mode 100644 index 0000000..ccb5d2b --- /dev/null +++ b/src/Framework/Database/Paginate.php @@ -0,0 +1,283 @@ +_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 = '