Jake Vanderwerf
2026-08-28 a9654f00c8fbc9934289d761843ddd071025013f
inc/managers/CustomTable.php
@@ -2,6 +2,7 @@
namespace JVBase\managers;
use Exception;
use wpdb;
if (!defined('ABSPATH')) {
   exit;
@@ -19,7 +20,9 @@
 */
class CustomTable
{
   protected \wpdb $wpdb;
   private static ?wpdb $wpdb = null;
   private static string $prefix;
   protected string $tableName;
   protected string $definition;
   protected string $fullTableName;
@@ -93,20 +96,20 @@
      protected function ensureUserTable():void
      {
         if (!isset(static::$userTable)) {
            static::$userTable = is_multisite() ? $this->getMultisiteUsersTable() : $this->wpdb->users;
            static::$userTable = is_multisite() ? $this->getMultisiteUsersTable() : self::$wpdb->users;
         }
      }
         protected function getMultisiteUsersTable():string
         {
            $siteUsersTable = $this->wpdb->prefix . 'users';
            $siteExists = $this->wpdb->get_var(
               $this->wpdb->prepare("SHOW TABLES LIKE %s", $siteUsersTable)
            $siteUsersTable = self::$wpdb->prefix . 'users';
            $siteExists = self::$wpdb->get_var(
               self::$wpdb->prepare("SHOW TABLES LIKE %s", $siteUsersTable)
            );
            if ($siteExists) {
               return $siteUsersTable;
            }
            //fallback to main one
            return $this->wpdb->users;
            return self::$wpdb->users;
         }
      protected function ensureUserIDType():void
      {
@@ -119,27 +122,27 @@
      protected function ensureTermIDType():void
      {
         if (!isset(static::$termIDType)) {
            static::$termIDType = $this->getColumnType($this->wpdb->terms, 'term_id');
            static::$termIDType = $this->getColumnType(self::$wpdb->terms, 'term_id');
         }
      }
      protected function ensureTermTable():void
      {
         if (!isset(static::$termTable)) {
            static::$termTable = $this->wpdb->terms;
            static::$termTable = self::$wpdb->terms;
         }
      }
      protected function ensurePostIDType():void
      {
         if (!isset(static::$postIDType)) {
            static::$postIDType = $this->getColumnType($this->wpdb->posts, 'ID');
            static::$postIDType = $this->getColumnType(self::$wpdb->posts, 'ID');
         }
      }
      protected function ensurePostTable():void
      {
         if (!isset(static::$postTable)) {
            static::$postTable = $this->wpdb->posts;
            static::$postTable = self::$wpdb->posts;
         }
      }
@@ -238,29 +241,29 @@
         error_log('[CustomTable]No definition set for '.$this->tableName);
         return;
      }
      if ($this->wpdb->get_var("SHOW TABLES LIKE '{$this->fullTableName}'")) {
      if (self::$wpdb->get_var("SHOW TABLES LIKE '{$this->fullTableName}'")) {
         return;
      }
      require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
      error_log('Creating Database Table: '. $this->tableName);
      $charset = self::$charsetCollate;
      $schema = "CREATE TABLE IF NOT EXISTS {$this->fullTableName} {$this->definition} {$charset}";
      $this->wpdb->flush();
      self::$wpdb->flush();
      $hasForeignKey = stripos($schema, 'FOREIGN KEY') !== false;
      if ($hasForeignKey) {
         $result = $this->wpdb->query($schema);
         $success = ($result !== false && !$this->wpdb->last_error);
         $result = self::$wpdb->query($schema);
         $success = ($result !== false && !self::$wpdb->last_error);
      } else {
         dbDelta($schema.';');
         $success = !$this->wpdb->last_error;
         $success = !self::$wpdb->last_error;
      }
      if (!$success) {
         $error_msg = "SQL Error creating table {$this->tableName}: " . $this->wpdb->last_error;
         $error_msg = "SQL Error creating table {$this->tableName}: " . self::$wpdb->last_error;
         error_log($error_msg);
         error_log("Failed SQL Query: " . $schema);
      } elseif ($this->wpdb->get_var("SHOW TABLES LIKE '{$this->fullTableName}'")) {
      } elseif (self::$wpdb->get_var("SHOW TABLES LIKE '{$this->fullTableName}'")) {
         error_log("Successfully created table: {$this->fullTableName}");
      }
   }
@@ -273,16 +276,30 @@
      self::$instances = [];
   }
   protected function checkWPDB():void
   {
      if (is_null(self::$wpdb)) {
         global $wpdb;
         self::$wpdb = $wpdb;
         self::$prefix = $wpdb->prefix;
         $usersStatus = self::$wpdb->get_row("SHOW TABLE STATUS LIKE '{self::$wpdb->users}'");
         $parentCollation = $usersStatus->Collation ?? 'utf8mb4_general_ci';
         self::$charsetCollate = "DEFAULT CHARACTER SET utf8mb4 COLLATE {$parentCollation}";
      }
   }
   /**
    * @param string $tableName Table name without prefix/BASE (e.g., 'favourites', 'notifications')
    * @param bool $useTransactions Whether to auto-wrap operations in transactions
    */
   public function __construct(string $tableName, bool $user = false, bool $auth = false, bool $useTransactions = false)
   private function __construct(string $tableName, bool $user = false, bool $auth = false, bool $useTransactions = false)
   {
      global $wpdb;
      $this->wpdb = $wpdb;
      $this->checkWPDB();
      $this->tableName = $tableName;
      $this->fullTableName = $wpdb->prefix . apply_filters('jvb_base', BASE) . $tableName;
      $this->fullTableName = self::$prefix . apply_filters('jvb_base', BASE) . $tableName;
      $this->useTransactions = $useTransactions;
      $this->cache = Cache::for($tableName);
@@ -293,9 +310,6 @@
         $this->cache->auth();
      }
      $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}";
   }
@@ -479,7 +493,7 @@
         $data['created_at'] = current_time('mysql');
      }
      $result = $this->wpdb->insert(
      $result = self::$wpdb->insert(
         $this->fullTableName,
         $data,
         $format
@@ -492,7 +506,7 @@
      $this->cache->flush();
      return $this->wpdb->insert_id;
      return self::$wpdb->insert_id;
   }
   /**
@@ -586,7 +600,7 @@
              (" . implode(',', $columns_escaped) . ")
              VALUES " . implode(',', $placeholders);
      $result = $this->wpdb->query($this->wpdb->prepare($query, $values));
      $result = self::$wpdb->query(self::$wpdb->prepare($query, $values));
      if ($result === false) {
         $this->logError('bulkInsert', ['rows' => count($rows)]);
@@ -620,7 +634,7 @@
            $query .= " WHERE {$where['sql']}";
            $values = $where['values'];
            return $this->wpdb->get_row($this->wpdb->prepare($query, $values), $output);
            return self::$wpdb->get_row(self::$wpdb->prepare($query, $values), $output);
         }
      );
   }
@@ -680,10 +694,10 @@
            }
            if (empty($values)) {
               return $this->wpdb->get_results($query, $output);
               return self::$wpdb->get_results($query, $output);
            }
            return $this->wpdb->get_results($this->wpdb->prepare($query, $values), $output);
            return self::$wpdb->get_results(self::$wpdb->prepare($query, $values), $output);
         }
      );
@@ -776,10 +790,10 @@
            }
            if (empty($values)) {
               return (int) $this->wpdb->get_var($query);
               return (int) self::$wpdb->get_var($query);
            }
            return (int) $this->wpdb->get_var($this->wpdb->prepare($query, $values));
            return (int) self::$wpdb->get_var(self::$wpdb->prepare($query, $values));
         }
      );
@@ -822,7 +836,7 @@
         $data['updated_at'] = current_time('mysql');
      }
      $result = $this->wpdb->update(
      $result = self::$wpdb->update(
         $this->fullTableName,
         $data,
         $where,
@@ -854,7 +868,7 @@
    */
   public function delete(array $where, ?array $where_format = null): int|false
   {
      $result = $this->wpdb->delete(
      $result = self::$wpdb->delete(
         $this->fullTableName,
         $where,
         $where_format
@@ -877,7 +891,7 @@
    *
    * @param string $query SQL query (use {table} as placeholder)
    * @param array $values Values for prepare()
    * @return mixed Query result
    * @return int|bool|\mysqli_result|null Query result
    *
    * @example
    * $results = $table->query(
@@ -885,15 +899,15 @@
    *     [1, 'pending', 'active']
    * );
    */
   public function query(string $query, array $values = []): mixed
   public function query(string $query, array $values = []): int|bool|null|\mysqli_result
   {
      $query = str_replace('{table}', $this->fullTableName, $query);
      if (empty($values)) {
         return $this->wpdb->query($query);
         return self::$wpdb->query($query);
      }
      return $this->wpdb->query($this->wpdb->prepare($query, $values));
      return self::$wpdb->query(self::$wpdb->prepare($query, $values));
   }
   /**
@@ -909,10 +923,10 @@
      $query = str_replace('{table}', $this->fullTableName, $query);
      if (empty($values)) {
         return $this->wpdb->get_results($query, $output);
         return self::$wpdb->get_results($query, $output);
      }
      return $this->wpdb->get_results($this->wpdb->prepare($query, $values), $output);
      return self::$wpdb->get_results(self::$wpdb->prepare($query, $values), $output);
   }
   /**
@@ -920,17 +934,17 @@
    *
    * @param string $query SQL query (use {table} as placeholder)
    * @param array $values Values for prepare()
    * @return mixed
    * @return string|null
    */
   public function queryVar(string $query, array $values = []): mixed
   public function queryVar(string $query, array $values = []): ?string
   {
      $query = str_replace('{table}', $this->fullTableName, $query);
      if (empty($values)) {
         return $this->wpdb->get_var($query);
         return self::$wpdb->get_var($query);
      }
      return $this->wpdb->get_var($this->wpdb->prepare($query, $values));
      return self::$wpdb->get_var(self::$wpdb->prepare($query, $values));
   }
   // =========================================================================
@@ -942,7 +956,7 @@
    */
   public function startTransaction(): void
   {
      $this->wpdb->query('START TRANSACTION');
      self::$wpdb->query('START TRANSACTION');
   }
   /**
@@ -950,7 +964,7 @@
    */
   public function commit(): void
   {
      $this->wpdb->query('COMMIT');
      self::$wpdb->query('COMMIT');
   }
   /**
@@ -958,7 +972,7 @@
    */
   public function rollback(): void
   {
      $this->wpdb->query('ROLLBACK');
      self::$wpdb->query('ROLLBACK');
   }
   /**
@@ -1007,7 +1021,7 @@
    */
   public function getInsertId(): int
   {
      return $this->wpdb->insert_id;
      return self::$wpdb->insert_id;
   }
   /**
@@ -1015,7 +1029,7 @@
    */
   public function getLastError(): string
   {
      return $this->wpdb->last_error;
      return self::$wpdb->last_error;
   }
   /**
@@ -1023,7 +1037,7 @@
    */
   public function getAffectedRows(): int
   {
      return $this->wpdb->rows_affected;
      return self::$wpdb->rows_affected;
   }
   public function getUserIDType():string
@@ -1061,16 +1075,16 @@
   // PRIVATE HELPERS
   // =========================================================================
   private function getColumnType(string $table, string $column):string|false {
      $tableExists = $this->wpdb->get_var(
         $this->wpdb->prepare("SHOW TABLES LIKE %s", $table)
      $tableExists = self::$wpdb->get_var(
         self::$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(
      $result = self::$wpdb->get_row(
         self::$wpdb->prepare(
            "SELECT COLUMN_TYPE
            FROM INFORMATION_SCHEMA.COLUMNS
            WHERE TABLE_SCHEMA = DATABASE()
@@ -1089,7 +1103,7 @@
   private function tableExists():bool
   {
      return !is_null($this->wpdb->get_var($this->wpdb->prepare("SHOW TABLES LIKE %s", $this->fullTableName)));
      return !is_null(self::$wpdb->get_var(self::$wpdb->prepare("SHOW TABLES LIKE %s", $this->fullTableName)));
   }
   /**
    * Build WHERE clause from associative array
@@ -1165,7 +1179,7 @@
      static $cache = [];
      if (!isset($cache[$this->tableName])) {
         $columns = $this->wpdb->get_col("DESCRIBE {$this->fullTableName}");
         $columns = self::$wpdb->get_col("DESCRIBE {$this->fullTableName}");
         $cache[$this->tableName] = array_flip($columns);
      }
@@ -1178,14 +1192,13 @@
   private function logError(string $operation, array $context = []): void
   {
      if (function_exists('JVB')) {
         JVB()->error()->log(
         Errors::getInstance()->log(
            $this->tableName,
            "CustomTable {$operation} failed: " . $this->wpdb->last_error,
            $context,
            'error'
            "CustomTable {$operation} failed: " . self::$wpdb->last_error,
            $context
         );
      } else {
         error_log("[CustomTable:{$this->tableName}] {$operation} failed: " . $this->wpdb->last_error);
         error_log("[CustomTable:{$this->tableName}] {$operation} failed: " . self::$wpdb->last_error);
      }
   }