1<?php 2 3namespace plugin\struct\meta; 4 5use plugin\struct\types\AbstractBaseType; 6 7/** 8 * Class Schema 9 * 10 * Represents the schema of a single data table and all its properties. It defines what can be stored in 11 * the represented data table and how those contents are formatted. 12 * 13 * It can be initialized with a timestamp to access the schema as it looked at that particular point in time. 14 * 15 * @package plugin\struct\meta 16 */ 17class Schema { 18 19 /** @var \helper_plugin_sqlite|null */ 20 protected $sqlite; 21 22 /** @var int The ID of this schema */ 23 protected $id = 0; 24 25 /** @var string name of the associated table */ 26 protected $table = ''; 27 28 /** 29 * @var string the current checksum of this schema 30 */ 31 protected $chksum = ''; 32 33 /** @var Column[] all the colums */ 34 protected $columns = array(); 35 36 /** @var int */ 37 protected $maxsort = 0; 38 39 /** @var int */ 40 protected $ts = 0; 41 42 /** @var string struct version info */ 43 protected $structversion = '?'; 44 45 /** 46 * Schema constructor 47 * 48 * @param string $table The table this schema is for 49 * @param int $ts The timestamp for when this schema was valid, 0 for current 50 */ 51 public function __construct($table, $ts = 0) { 52 /** @var \helper_plugin_struct_db $helper */ 53 $helper = plugin_load('helper', 'struct_db'); 54 $info = $helper->getInfo(); 55 $this->structversion = $info['date']; 56 $this->sqlite = $helper->getDB(); 57 if(!$this->sqlite) return; 58 59 $table = self::cleanTableName($table); 60 $this->table = $table; 61 $this->ts = $ts; 62 63 // load info about the schema itself 64 if($ts) { 65 $sql = "SELECT * 66 FROM schemas 67 WHERE tbl = ? 68 AND ts <= ? 69 ORDER BY ts DESC 70 LIMIT 1"; 71 $opt = array($table, $ts); 72 } else { 73 $sql = "SELECT * 74 FROM schemas 75 WHERE tbl = ? 76 ORDER BY ts DESC 77 LIMIT 1"; 78 $opt = array($table); 79 } 80 $res = $this->sqlite->query($sql, $opt); 81 if($this->sqlite->res2count($res)) { 82 $schema = $this->sqlite->res2arr($res); 83 $result = array_shift($schema); 84 $this->id = $result['id']; 85 $this->chksum = $result['chksum']; 86 } 87 $this->sqlite->res_close($res); 88 if(!$this->id) return; 89 90 // load existing columns 91 $sql = "SELECT SC.*, T.* 92 FROM schema_cols SC, 93 types T 94 WHERE SC.sid = ? 95 AND SC.tid = T.id 96 ORDER BY SC.sort"; 97 $res = $this->sqlite->query($sql, $this->id); 98 $rows = $this->sqlite->res2arr($res); 99 $this->sqlite->res_close($res); 100 101 foreach($rows as $row) { 102 $class = 'plugin\\struct\\types\\' . $row['class']; 103 if(!class_exists($class)) { 104 // This usually never happens, except during development 105 msg('Unknown type "' . hsc($row['class']) . '" falling back to Text', -1); 106 $class = 'plugin\\struct\\types\\Text'; 107 } 108 109 $config = json_decode($row['config'], true); 110 /** @var AbstractBaseType $type */ 111 $type = new $class($config, $row['label'], $row['ismulti'], $row['tid']); 112 $column = new Column( 113 $row['sort'], 114 $type, 115 $row['colref'], 116 $row['enabled'], 117 $table 118 ); 119 $type->setContext($column); 120 121 $this->columns[$row['colref']] = $column; 122 if($row['sort'] > $this->maxsort) $this->maxsort = $row['sort']; 123 } 124 } 125 126 /** 127 * Cleans any unwanted stuff from table names 128 * 129 * @param string $table 130 * @return string 131 */ 132 static public function cleanTableName($table) { 133 $table = strtolower($table); 134 $table = preg_replace('/[^a-z0-9_]+/', '', $table); 135 $table = preg_replace('/^[0-9_]+/', '', $table); 136 $table = trim($table); 137 return $table; 138 } 139 140 /** 141 * Gets a list of all available schemas 142 * 143 * @return string[] 144 */ 145 static public function getAll() { 146 /** @var \helper_plugin_struct_db $helper */ 147 $helper = plugin_load('helper', 'struct_db'); 148 $db = $helper->getDB(); 149 if(!$db) return array(); 150 151 $res = $db->query("SELECT DISTINCT tbl FROM schemas ORDER BY tbl"); 152 $tables = $db->res2arr($res); 153 $db->res_close($res); 154 155 $result = array(); 156 foreach($tables as $row) { 157 $result[] = $row['tbl']; 158 } 159 return $result; 160 } 161 162 /** 163 * @return string 164 */ 165 public function getChksum() { 166 return $this->chksum; 167 } 168 169 /** 170 * @return int 171 */ 172 public function getId() { 173 return $this->id; 174 } 175 176 /** 177 * Returns a list of columns in this schema 178 * 179 * @param bool $withDisabled if false, disabled columns will not be returned 180 * @return Column[] 181 */ 182 public function getColumns($withDisabled = true) { 183 if(!$withDisabled) { 184 return array_filter( 185 $this->columns, 186 function (Column $col) { 187 return $col->isEnabled(); 188 } 189 ); 190 } 191 192 return $this->columns; 193 } 194 195 /** 196 * Find a column in the schema by its label 197 * 198 * Only enabled columns are returned! 199 * 200 * @param $name 201 * @return bool|Column 202 */ 203 public function findColumn($name) { 204 foreach($this->columns as $col) { 205 if($col->isEnabled() && utf8_strtolower($col->getLabel()) == utf8_strtolower($name)) { 206 return $col; 207 } 208 } 209 return false; 210 } 211 212 /** 213 * @return string 214 */ 215 public function getTable() { 216 return $this->table; 217 } 218 219 /** 220 * @return int the highest sort number used in this schema 221 */ 222 public function getMaxsort() { 223 return $this->maxsort; 224 } 225 226 /** 227 * @return string the JSON representing this schema 228 */ 229 public function toJSON() { 230 $data = array( 231 'structversion' => $this->structversion, 232 'schema' => $this->getTable(), 233 'id' => $this->getId(), 234 'columns' => array() 235 ); 236 237 foreach($this->columns as $column) { 238 $data['columns'][] = array( 239 'colref' => $column->getColref(), 240 'ismulti' => $column->isMulti(), 241 'isenabled' => $column->isEnabled(), 242 'sort' => $column->getSort(), 243 'label' => $column->getLabel(), 244 'class' => $column->getType()->getClass(), 245 'config' => $column->getType()->getConfig(), 246 ); 247 } 248 249 return json_encode($data, JSON_PRETTY_PRINT); 250 } 251} 252