1083afc55SAndreas Gohr<?php 2083afc55SAndreas Gohr 3083afc55SAndreas Gohrnamespace plugin\struct\meta; 4d486d6d7SAndreas Gohr 5bbf3d6aaSAndreas Gohruse plugin\struct\types\AbstractBaseType; 6083afc55SAndreas Gohr 745560cc7SAndreas Gohrif(!defined('JSON_PRETTY_PRINT')) define('JSON_PRETTY_PRINT', 0); // PHP 5.3 compatibility 845560cc7SAndreas Gohr 97182938bSAndreas Gohr/** 107182938bSAndreas Gohr * Class Schema 117182938bSAndreas Gohr * 127182938bSAndreas Gohr * Represents the schema of a single data table and all its properties. It defines what can be stored in 137182938bSAndreas Gohr * the represented data table and how those contents are formatted. 147182938bSAndreas Gohr * 157182938bSAndreas Gohr * It can be initialized with a timestamp to access the schema as it looked at that particular point in time. 167182938bSAndreas Gohr * 177182938bSAndreas Gohr * @package plugin\struct\meta 187182938bSAndreas Gohr */ 19083afc55SAndreas Gohrclass Schema { 20083afc55SAndreas Gohr 21083afc55SAndreas Gohr /** @var \helper_plugin_sqlite|null */ 22083afc55SAndreas Gohr protected $sqlite; 23083afc55SAndreas Gohr 24083afc55SAndreas Gohr /** @var int The ID of this schema */ 25083afc55SAndreas Gohr protected $id = 0; 26083afc55SAndreas Gohr 27083afc55SAndreas Gohr /** @var string name of the associated table */ 28083afc55SAndreas Gohr protected $table = ''; 29083afc55SAndreas Gohr 30083afc55SAndreas Gohr /** 31083afc55SAndreas Gohr * @var string the current checksum of this schema 32083afc55SAndreas Gohr */ 33083afc55SAndreas Gohr protected $chksum = ''; 34083afc55SAndreas Gohr 351c502704SAndreas Gohr /** @var Column[] all the colums */ 36083afc55SAndreas Gohr protected $columns = array(); 37083afc55SAndreas Gohr 38083afc55SAndreas Gohr /** @var int */ 39083afc55SAndreas Gohr protected $maxsort = 0; 40083afc55SAndreas Gohr 41250c83c2SAndreas Gohr /** @var int */ 42250c83c2SAndreas Gohr protected $ts = 0; 43250c83c2SAndreas Gohr 44d486d6d7SAndreas Gohr /** @var string struct version info */ 45d486d6d7SAndreas Gohr protected $structversion = '?'; 46d486d6d7SAndreas Gohr 47083afc55SAndreas Gohr /** 48083afc55SAndreas Gohr * Schema constructor 497182938bSAndreas Gohr * 50083afc55SAndreas Gohr * @param string $table The table this schema is for 51083afc55SAndreas Gohr * @param int $ts The timestamp for when this schema was valid, 0 for current 52083afc55SAndreas Gohr */ 53083afc55SAndreas Gohr public function __construct($table, $ts = 0) { 54083afc55SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 55083afc55SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 56d486d6d7SAndreas Gohr $info = $helper->getInfo(); 57d486d6d7SAndreas Gohr $this->structversion = $info['date']; 58083afc55SAndreas Gohr $this->sqlite = $helper->getDB(); 59083afc55SAndreas Gohr if(!$this->sqlite) return; 60083afc55SAndreas Gohr 61083afc55SAndreas Gohr $table = self::cleanTableName($table); 62083afc55SAndreas Gohr $this->table = $table; 63250c83c2SAndreas Gohr $this->ts = $ts; 64083afc55SAndreas Gohr 65083afc55SAndreas Gohr // load info about the schema itself 66083afc55SAndreas Gohr if($ts) { 67083afc55SAndreas Gohr $sql = "SELECT * 68083afc55SAndreas Gohr FROM schemas 69083afc55SAndreas Gohr WHERE tbl = ? 70083afc55SAndreas Gohr AND ts <= ? 71083afc55SAndreas Gohr ORDER BY ts DESC 72083afc55SAndreas Gohr LIMIT 1"; 73083afc55SAndreas Gohr $opt = array($table, $ts); 74083afc55SAndreas Gohr } else { 75083afc55SAndreas Gohr $sql = "SELECT * 76083afc55SAndreas Gohr FROM schemas 77083afc55SAndreas Gohr WHERE tbl = ? 78083afc55SAndreas Gohr ORDER BY ts DESC 79083afc55SAndreas Gohr LIMIT 1"; 80083afc55SAndreas Gohr $opt = array($table); 81083afc55SAndreas Gohr } 82083afc55SAndreas Gohr $res = $this->sqlite->query($sql, $opt); 83083afc55SAndreas Gohr if($this->sqlite->res2count($res)) { 844e2abec0SMichael Große $schema = $this->sqlite->res2arr($res); 854e2abec0SMichael Große $result = array_shift($schema); 86083afc55SAndreas Gohr $this->id = $result['id']; 87083afc55SAndreas Gohr $this->chksum = $result['chksum']; 88083afc55SAndreas Gohr } 89083afc55SAndreas Gohr $this->sqlite->res_close($res); 90083afc55SAndreas Gohr if(!$this->id) return; 91083afc55SAndreas Gohr 92083afc55SAndreas Gohr // load existing columns 93083afc55SAndreas Gohr $sql = "SELECT SC.*, T.* 94083afc55SAndreas Gohr FROM schema_cols SC, 95083afc55SAndreas Gohr types T 961c502704SAndreas Gohr WHERE SC.sid = ? 971c502704SAndreas Gohr AND SC.tid = T.id 98083afc55SAndreas Gohr ORDER BY SC.sort"; 991c502704SAndreas Gohr $res = $this->sqlite->query($sql, $this->id); 100083afc55SAndreas Gohr $rows = $this->sqlite->res2arr($res); 101083afc55SAndreas Gohr $this->sqlite->res_close($res); 102083afc55SAndreas Gohr 103083afc55SAndreas Gohr foreach($rows as $row) { 1041c502704SAndreas Gohr $class = 'plugin\\struct\\types\\' . $row['class']; 10598eaa57dSAndreas Gohr if(!class_exists($class)) { 10698eaa57dSAndreas Gohr // This usually never happens, except during development 10798eaa57dSAndreas Gohr msg('Unknown type "' . hsc($row['class']) . '" falling back to Text', -1); 10898eaa57dSAndreas Gohr $class = 'plugin\\struct\\types\\Text'; 10998eaa57dSAndreas Gohr } 11098eaa57dSAndreas Gohr 111083afc55SAndreas Gohr $config = json_decode($row['config'], true); 112bbf3d6aaSAndreas Gohr /** @var AbstractBaseType $type */ 113bbf3d6aaSAndreas Gohr $type = new $class($config, $row['label'], $row['ismulti'], $row['tid']); 114bbf3d6aaSAndreas Gohr $column = new Column( 1151c502704SAndreas Gohr $row['sort'], 116bbf3d6aaSAndreas Gohr $type, 1171c502704SAndreas Gohr $row['colref'], 11863d51bbfSAndreas Gohr $row['enabled'], 11963d51bbfSAndreas Gohr $table 1201c502704SAndreas Gohr ); 121bbf3d6aaSAndreas Gohr $type->setContext($column); 1221c502704SAndreas Gohr 1237629557eSAndreas Gohr $this->columns[] = $column; 124083afc55SAndreas Gohr if($row['sort'] > $this->maxsort) $this->maxsort = $row['sort']; 125083afc55SAndreas Gohr } 126083afc55SAndreas Gohr } 127083afc55SAndreas Gohr 128083afc55SAndreas Gohr /** 129083afc55SAndreas Gohr * Cleans any unwanted stuff from table names 130083afc55SAndreas Gohr * 131083afc55SAndreas Gohr * @param string $table 132083afc55SAndreas Gohr * @return string 133083afc55SAndreas Gohr */ 134083afc55SAndreas Gohr static public function cleanTableName($table) { 1352af472dcSAndreas Gohr $table = strtolower($table); 136083afc55SAndreas Gohr $table = preg_replace('/[^a-z0-9_]+/', '', $table); 137083afc55SAndreas Gohr $table = preg_replace('/^[0-9_]+/', '', $table); 138083afc55SAndreas Gohr $table = trim($table); 139083afc55SAndreas Gohr return $table; 140083afc55SAndreas Gohr } 141083afc55SAndreas Gohr 142083afc55SAndreas Gohr /** 143097f4a53SAndreas Gohr * Gets a list of all available schemas 144097f4a53SAndreas Gohr * 145097f4a53SAndreas Gohr * @return string[] 146097f4a53SAndreas Gohr */ 147097f4a53SAndreas Gohr static public function getAll() { 148097f4a53SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 149097f4a53SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 150097f4a53SAndreas Gohr $db = $helper->getDB(); 151097f4a53SAndreas Gohr if(!$db) return array(); 152097f4a53SAndreas Gohr 153097f4a53SAndreas Gohr $res = $db->query("SELECT DISTINCT tbl FROM schemas ORDER BY tbl"); 154097f4a53SAndreas Gohr $tables = $db->res2arr($res); 155097f4a53SAndreas Gohr $db->res_close($res); 156097f4a53SAndreas Gohr 157097f4a53SAndreas Gohr $result = array(); 158097f4a53SAndreas Gohr foreach($tables as $row) { 159097f4a53SAndreas Gohr $result[] = $row['tbl']; 160097f4a53SAndreas Gohr } 161097f4a53SAndreas Gohr return $result; 162097f4a53SAndreas Gohr } 163097f4a53SAndreas Gohr 164097f4a53SAndreas Gohr /** 165*d5a1a6dcSAndreas Gohr * Delete all data associated with this schema 166*d5a1a6dcSAndreas Gohr * 167*d5a1a6dcSAndreas Gohr * This is really all data ever! Be careful! 168*d5a1a6dcSAndreas Gohr */ 169*d5a1a6dcSAndreas Gohr public function delete() { 170*d5a1a6dcSAndreas Gohr if(!$this->id) throw new StructException('can not delete unsaved schema'); 171*d5a1a6dcSAndreas Gohr 172*d5a1a6dcSAndreas Gohr $this->sqlite->query('BEGIN TRANSACTION'); 173*d5a1a6dcSAndreas Gohr 174*d5a1a6dcSAndreas Gohr $sql = "DROP TABLE ?"; 175*d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'data_'.$this->table); 176*d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'multi_'.$this->table); 177*d5a1a6dcSAndreas Gohr 178*d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments WHERE tbl = ?"; 179*d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 180*d5a1a6dcSAndreas Gohr 181*d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments_patterns WHERE tbl = ?"; 182*d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 183*d5a1a6dcSAndreas Gohr 184*d5a1a6dcSAndreas Gohr $sql = "SELECT T.id 185*d5a1a6dcSAndreas Gohr FROM types T, schema_cols SC, schemas S 186*d5a1a6dcSAndreas Gohr WHERE T.id = SC.tid 187*d5a1a6dcSAndreas Gohr AND SC.sid = S.id 188*d5a1a6dcSAndreas Gohr AND S.tbl = ?"; 189*d5a1a6dcSAndreas Gohr $sql = "DELETE FROM types WHERE id IN ($sql)"; 190*d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 191*d5a1a6dcSAndreas Gohr 192*d5a1a6dcSAndreas Gohr $sql = "SELECT id 193*d5a1a6dcSAndreas Gohr FROM schemas 194*d5a1a6dcSAndreas Gohr WHERE tbl = ?"; 195*d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_cols WHERE sid IN ($sql)"; 196*d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 197*d5a1a6dcSAndreas Gohr 198*d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schemas WHERE tbl = ?"; 199*d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 200*d5a1a6dcSAndreas Gohr 201*d5a1a6dcSAndreas Gohr $this->sqlite->query('COMMIT TRANSACTION'); 202*d5a1a6dcSAndreas Gohr $this->sqlite->query('VACUUM'); 203*d5a1a6dcSAndreas Gohr } 204*d5a1a6dcSAndreas Gohr 205*d5a1a6dcSAndreas Gohr /** 2061c502704SAndreas Gohr * @return string 2071c502704SAndreas Gohr */ 2081c502704SAndreas Gohr public function getChksum() { 2091c502704SAndreas Gohr return $this->chksum; 2101c502704SAndreas Gohr } 2111c502704SAndreas Gohr 2121c502704SAndreas Gohr /** 2131c502704SAndreas Gohr * @return int 2141c502704SAndreas Gohr */ 2151c502704SAndreas Gohr public function getId() { 2161c502704SAndreas Gohr return $this->id; 2171c502704SAndreas Gohr } 2181c502704SAndreas Gohr 2191c502704SAndreas Gohr /** 220ce206ec7SAndreas Gohr * Returns a list of columns in this schema 221ce206ec7SAndreas Gohr * 222ce206ec7SAndreas Gohr * @param bool $withDisabled if false, disabled columns will not be returned 223ce206ec7SAndreas Gohr * @return Column[] 2241c502704SAndreas Gohr */ 225ce206ec7SAndreas Gohr public function getColumns($withDisabled = true) { 226ce206ec7SAndreas Gohr if(!$withDisabled) { 227ce206ec7SAndreas Gohr return array_filter( 228ce206ec7SAndreas Gohr $this->columns, 229ce206ec7SAndreas Gohr function (Column $col) { 230ce206ec7SAndreas Gohr return $col->isEnabled(); 231ce206ec7SAndreas Gohr } 232ce206ec7SAndreas Gohr ); 233ce206ec7SAndreas Gohr } 234ce206ec7SAndreas Gohr 2351c502704SAndreas Gohr return $this->columns; 2361c502704SAndreas Gohr } 2371c502704SAndreas Gohr 238ae697e1fSAndreas Gohr /** 2395742aea9SAndreas Gohr * Find a column in the schema by its label 2405742aea9SAndreas Gohr * 2415742aea9SAndreas Gohr * Only enabled columns are returned! 2425742aea9SAndreas Gohr * 2435742aea9SAndreas Gohr * @param $name 2445742aea9SAndreas Gohr * @return bool|Column 2455742aea9SAndreas Gohr */ 2465742aea9SAndreas Gohr public function findColumn($name) { 2475742aea9SAndreas Gohr foreach($this->columns as $col) { 2485742aea9SAndreas Gohr if($col->isEnabled() && utf8_strtolower($col->getLabel()) == utf8_strtolower($name)) { 2495742aea9SAndreas Gohr return $col; 2505742aea9SAndreas Gohr } 2515742aea9SAndreas Gohr } 2525742aea9SAndreas Gohr return false; 2535742aea9SAndreas Gohr } 2545742aea9SAndreas Gohr 2555742aea9SAndreas Gohr /** 256ae697e1fSAndreas Gohr * @return string 257ae697e1fSAndreas Gohr */ 258ae697e1fSAndreas Gohr public function getTable() { 259ae697e1fSAndreas Gohr return $this->table; 260ae697e1fSAndreas Gohr } 2611c502704SAndreas Gohr 262ae697e1fSAndreas Gohr /** 263ae697e1fSAndreas Gohr * @return int the highest sort number used in this schema 264ae697e1fSAndreas Gohr */ 265ae697e1fSAndreas Gohr public function getMaxsort() { 266ae697e1fSAndreas Gohr return $this->maxsort; 267ae697e1fSAndreas Gohr } 2681c502704SAndreas Gohr 269d486d6d7SAndreas Gohr /** 270d486d6d7SAndreas Gohr * @return string the JSON representing this schema 271d486d6d7SAndreas Gohr */ 272d486d6d7SAndreas Gohr public function toJSON() { 273d486d6d7SAndreas Gohr $data = array( 274d486d6d7SAndreas Gohr 'structversion' => $this->structversion, 275d486d6d7SAndreas Gohr 'schema' => $this->getTable(), 276d486d6d7SAndreas Gohr 'id' => $this->getId(), 277d486d6d7SAndreas Gohr 'columns' => array() 278d486d6d7SAndreas Gohr ); 279d486d6d7SAndreas Gohr 280d486d6d7SAndreas Gohr foreach($this->columns as $column) { 281d486d6d7SAndreas Gohr $data['columns'][] = array( 282d486d6d7SAndreas Gohr 'colref' => $column->getColref(), 283d486d6d7SAndreas Gohr 'ismulti' => $column->isMulti(), 284d486d6d7SAndreas Gohr 'isenabled' => $column->isEnabled(), 285d486d6d7SAndreas Gohr 'sort' => $column->getSort(), 286d486d6d7SAndreas Gohr 'label' => $column->getLabel(), 287d486d6d7SAndreas Gohr 'class' => $column->getType()->getClass(), 288d486d6d7SAndreas Gohr 'config' => $column->getType()->getConfig(), 289d486d6d7SAndreas Gohr ); 290d486d6d7SAndreas Gohr } 291d486d6d7SAndreas Gohr 292d486d6d7SAndreas Gohr return json_encode($data, JSON_PRETTY_PRINT); 293d486d6d7SAndreas Gohr } 294083afc55SAndreas Gohr} 295