From 772462eeca3002a1d52508aeba485aab2b4742ad Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 03 Mar 2026 19:06:19 +0000
Subject: [PATCH] =MAJOR OVERHAUL. Likely should have made a new branch ages ago. Key changes: Registrar.php is the base for custom post types, taxonomies, and user roles. Replaces JVB_CONTENT, JVB_TAXONOMY, and JVB_USER constants, eliminates most of Features.php (except for JVB_SITE, JVB_MEMBERSHIP), and has built in sanitizing and validation via sub-classes. Also started a major overhaul of the Schema output. Created a shit ton of property traits and classes to help sanitize and ensure proper data for different schema types. Still a bunch to do, but better to be starting committing changes here on this other branch.

---
 inc/managers/CustomTable.php |  226 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 225 insertions(+), 1 deletions(-)

diff --git a/inc/managers/CustomTable.php b/inc/managers/CustomTable.php
index 893e5ad..4299b71 100644
--- a/inc/managers/CustomTable.php
+++ b/inc/managers/CustomTable.php
@@ -21,11 +21,21 @@
 {
 	protected \wpdb $wpdb;
 	protected string $tableName;
+	protected string $definition;
 	protected string $fullTableName;
 	protected bool $useTransactions;
+	protected array $columns;
+	protected array $keys = [];
+	protected array $constraints =[];
 
 	/** @var array<string, self> Instance cache for fluent interface */
 	protected static array $instances = [];
+	protected static string $charsetCollate;
+
+	protected static string $userTable;
+	protected static string $userIDType;
+	protected static string $termIDType;
+	protected static string $postIDType;
 
 	/**
 	 * Fluent factory method
@@ -41,9 +51,167 @@
 			self::$instances[$tableName] = new self($tableName);
 		}
 
+
 		return self::$instances[$tableName];
 	}
 
+	public function ensureDefined() {
+		$this->ensureUserTable();
+		$this->ensureUserIDType();
+		$this->ensureTermIDType();
+		$this->ensurePostIDType();
+	}
+		protected function ensureUserTable():void
+		{
+			if (!isset(static::$userTable)) {
+				static::$userTable = is_multisite() ? $this->getMultisiteUsersTable() : $this->wpdb->users;
+			}
+		}
+			protected function getMultisiteUsersTable():string
+			{
+				$siteUsersTable = $this->wpdb->prefix . 'users';
+				$siteExists = $this->wpdb->get_var(
+					$this->wpdb->prepare("SHOW TABLES LIKE %s", $siteUsersTable)
+				);
+				if ($siteExists) {
+					return $siteUsersTable;
+				}
+				//fallback to main one
+				return $this->wpdb->users;
+			}
+		protected function ensureUserIDType():void
+		{
+			if (!isset(static::$userIDType)) {
+				$this->ensureUserTable();
+				static::$userIDType = $this->getColumnType(static::$userTable, 'ID');
+			}
+		}
+
+		protected function ensureTermIDType():void
+		{
+			if (!isset(static::$termIDType)) {
+				static::$termIDType = $this->getColumnType($this->wpdb->terms, 'term_id');
+			}
+		}
+
+		protected function ensurePostIDType():void
+		{
+			if (!isset(static::$postIDType)) {
+				static::$postIDType = $this->getColumnType($this->wpdb->posts, 'ID');
+			}
+		}
+
+	/**
+	 *
+	 * @param array $columns An array of $columnName => $columnDefinition
+	 * @return $this
+	 */
+	public function setColumns(array $columns):self
+	{
+		$this->columns = $columns;
+		return $this;
+	}
+
+	/**
+	 * @param array $keys An array of {string} $keys. If a $key is an array, you can define a custom $key => $value, example: 'UNIQUE' => $value, or 'PRIMARY' => $value
+	 * @return $this
+	 */
+	public function setKeys(array $keys):self
+	{
+		$this->keys = $keys;
+		return $this;
+	}
+
+	/**
+	 * @param array $constraints an array of arrays, each value a $constraint => $references
+	 * @return $this
+	 */
+	public function setConstraints(array $constraints):self
+	{
+		$this->constraints = $constraints;
+		return $this;
+	}
+
+	public function defineTable(): self
+	{
+		if (empty($this->columns)) {
+			error_log('[CustomTable] No columns defined for ' . $this->tableName);
+			return $this;
+		}
+
+		$parts = [];
+
+		// Columns
+		foreach ($this->columns as $name => $type) {
+			$parts[] = "`{$name}` {$type}";
+		}
+
+		// Keys
+		if (empty($this->keys)) {
+			$parts[] = 'PRIMARY KEY (`' . array_key_first($this->columns) . '`)';
+		} else {
+			foreach ($this->keys as $key) {
+				if (is_array($key)) {
+					$value = $key['value'];
+					// Ensure value is wrapped in parentheses
+					if (!str_starts_with(trim($value), '(')) {
+						$value = '(`' . $value . '`)';
+					}
+					$parts[] = $key['key'] . ' KEY ' . $value;
+				} else {
+					$parts[] = 'KEY ' . $key;
+				}
+			}
+		}
+
+		// Constraints
+		foreach ($this->constraints as $constraint => $references) {
+			$parts[] = "CONSTRAINT {$constraint} REFERENCES {$references}";
+		}
+
+		$this->definition = "(\n    " . implode(",\n    ", $parts) . "\n)";
+		return $this;
+	}
+
+	public static function ensureTables():void
+	{
+		require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
+
+		foreach (self::$instances as $instance) {
+			if (!$instance->wpdb->get_var("SHOW TABLES LIKE '{$instance->fullTableName}'")) {
+				$instance->createTable();
+			}
+		}
+	}
+
+	protected function createTable():void
+	{
+		if (!$this->definition && empty($this->definition)) {
+			error_log('[CustomTable]No definition set for '.$this->tableName);
+			return;
+		}
+		$charset = self::$charsetCollate;
+		$schema = "CREATE TABLE IF NOT EXISTS {$this->fullTableName} {$this->definition} {$charset}";
+		$this->wpdb->flush();
+
+		$hasForeignKey = stripos($schema, 'FOREIGN KEY') !== false;
+		if ($hasForeignKey) {
+			$result = $this->wpdb->query($schema);
+			$success = ($result !== false && !$this->wpdb->last_error);
+		} else {
+			dbDelta($schema.';');
+			$success = !$this->wpdb->last_error;
+		}
+
+		if (!$success) {
+			$error_msg = "SQL Error creating table {$this->tableName}: " . $this->wpdb->last_error;
+			error_log($error_msg);
+			error_log("Failed SQL Query: " . $schema);
+		} elseif ($this->wpdb->get_var("SHOW TABLES LIKE '{$this->fullTableName}'")) {
+			error_log("Successfully created table: {$this->fullTableName}");
+		}
+	}
+
 	/**
 	 * Clear instance cache (useful for testing)
 	 */
@@ -61,8 +229,13 @@
 		global $wpdb;
 		$this->wpdb = $wpdb;
 		$this->tableName = $tableName;
-		$this->fullTableName = $wpdb->prefix . BASE . $tableName;
+		$this->fullTableName = $wpdb->prefix . apply_filters('jvb_base', BASE) . $tableName;
 		$this->useTransactions = $useTransactions;
+
+		$usersStatus = $this->wpdb->get_row("SHOW TABLE STATUS LIKE '{$this->wpdb->users}'");
+		$parentCollation = $usersStatus->Collation ?? 'utf8mb4_general_ci';
+		self::$charsetCollate = "DEFAULT CHARACTER SET utf8mb4 COLLATE {$parentCollation}";
+
 	}
 
 	// =========================================================================
@@ -678,10 +851,61 @@
 		return $this->wpdb->rows_affected;
 	}
 
+	public function getUserIDType():string
+	{
+		$this->ensureUserIdType();
+		return static::$userIDType;
+	}
+	public function getUserTable():string
+	{
+		$this->ensureUserTable();
+		return static::$userTable;
+	}
+	public function getTermIDType():string
+	{
+		$this->ensureTermIDType();
+		return static::$termIDType;
+	}
+	public function getPostIDType():string
+	{
+		$this->ensurePostIDType();
+		return static::$postIDType;
+	}
+
 	// =========================================================================
 	// PRIVATE HELPERS
 	// =========================================================================
+	private function getColumnType(string $table, string $column):string|false {
+		$tableExists = $this->wpdb->get_var(
+			$this->wpdb->prepare("SHOW TABLES LIKE %s", $table)
+		);
+		if (!$tableExists) {
+			error_log("[CustomTable] Table {$table} does not exist for getColumnType");
+			return 'bigint(20) unsigned'; // safe fallback
+		}
 
+		$result = $this->wpdb->get_row(
+			$this->wpdb->prepare(
+				"SELECT COLUMN_TYPE
+            FROM INFORMATION_SCHEMA.COLUMNS
+            WHERE TABLE_SCHEMA = DATABASE()
+            AND TABLE_NAME = %s
+            AND COLUMN_NAME = %s",
+				$table,
+				$column
+			)
+		);
+		if ($result && isset($result->COLUMN_TYPE)) {
+			return $result->COLUMN_TYPE;
+		}
+		error_log("[CustomTable] Could not determine column type for {$table}.{$column}");
+		return 'bigint(20) unsigned';
+	}
+
+	private function tableExists():bool
+	{
+		return !is_null($this->wpdb->get_var($this->wpdb->prepare("SHOW TABLES LIKE %s", $this->fullTableName)));
+	}
 	/**
 	 * Build WHERE clause from associative array
 	 */

--
Gitblit v1.10.0