1083afc55SAndreas Gohr<?php 2083afc55SAndreas Gohr 3ba766201SAndreas Gohrnamespace dokuwiki\plugin\struct\meta; 4d486d6d7SAndreas Gohr 5ba766201SAndreas Gohruse dokuwiki\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 * 17ba766201SAndreas Gohr * @package dokuwiki\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 27fa7b96aaSMichael Grosse /** @var string the user who last edited this schema */ 28fa7b96aaSMichael Grosse protected $user = ''; 29fa7b96aaSMichael Grosse 30083afc55SAndreas Gohr /** @var string name of the associated table */ 31083afc55SAndreas Gohr protected $table = ''; 32083afc55SAndreas Gohr 3388b7d2aaSAndreas Gohr /** @var bool is this a lookup schema? */ 3488b7d2aaSAndreas Gohr protected $islookup = false; 3588b7d2aaSAndreas Gohr 36083afc55SAndreas Gohr /** 37083afc55SAndreas Gohr * @var string the current checksum of this schema 38083afc55SAndreas Gohr */ 39083afc55SAndreas Gohr protected $chksum = ''; 40083afc55SAndreas Gohr 411c502704SAndreas Gohr /** @var Column[] all the colums */ 42083afc55SAndreas Gohr protected $columns = array(); 43083afc55SAndreas Gohr 44083afc55SAndreas Gohr /** @var int */ 45083afc55SAndreas Gohr protected $maxsort = 0; 46083afc55SAndreas Gohr 47250c83c2SAndreas Gohr /** @var int */ 48250c83c2SAndreas Gohr protected $ts = 0; 49250c83c2SAndreas Gohr 50d486d6d7SAndreas Gohr /** @var string struct version info */ 51d486d6d7SAndreas Gohr protected $structversion = '?'; 52d486d6d7SAndreas Gohr 53*e2c90eebSAndreas Gohr /** @var string comma separated list of allowed editors */ 54*e2c90eebSAndreas Gohr protected $editors = ''; 55*e2c90eebSAndreas Gohr 56083afc55SAndreas Gohr /** 57083afc55SAndreas Gohr * Schema constructor 587182938bSAndreas Gohr * 59083afc55SAndreas Gohr * @param string $table The table this schema is for 60083afc55SAndreas Gohr * @param int $ts The timestamp for when this schema was valid, 0 for current 6188b7d2aaSAndreas Gohr * @param bool $islookup only used when creating a new schema, makes the new schema a lookup 62083afc55SAndreas Gohr */ 6388b7d2aaSAndreas Gohr public function __construct($table, $ts = 0, $islookup = false) { 64083afc55SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 65083afc55SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 66d486d6d7SAndreas Gohr $info = $helper->getInfo(); 67d486d6d7SAndreas Gohr $this->structversion = $info['date']; 68083afc55SAndreas Gohr $this->sqlite = $helper->getDB(); 69083afc55SAndreas Gohr if(!$this->sqlite) return; 70083afc55SAndreas Gohr 71083afc55SAndreas Gohr $table = self::cleanTableName($table); 72083afc55SAndreas Gohr $this->table = $table; 73250c83c2SAndreas Gohr $this->ts = $ts; 74083afc55SAndreas Gohr 75083afc55SAndreas Gohr // load info about the schema itself 76083afc55SAndreas Gohr if($ts) { 77083afc55SAndreas Gohr $sql = "SELECT * 78083afc55SAndreas Gohr FROM schemas 79083afc55SAndreas Gohr WHERE tbl = ? 80083afc55SAndreas Gohr AND ts <= ? 81083afc55SAndreas Gohr ORDER BY ts DESC 82083afc55SAndreas Gohr LIMIT 1"; 83083afc55SAndreas Gohr $opt = array($table, $ts); 84083afc55SAndreas Gohr } else { 85083afc55SAndreas Gohr $sql = "SELECT * 86083afc55SAndreas Gohr FROM schemas 87083afc55SAndreas Gohr WHERE tbl = ? 88083afc55SAndreas Gohr ORDER BY ts DESC 89083afc55SAndreas Gohr LIMIT 1"; 90083afc55SAndreas Gohr $opt = array($table); 91083afc55SAndreas Gohr } 92083afc55SAndreas Gohr $res = $this->sqlite->query($sql, $opt); 93083afc55SAndreas Gohr if($this->sqlite->res2count($res)) { 944e2abec0SMichael Große $schema = $this->sqlite->res2arr($res); 954e2abec0SMichael Große $result = array_shift($schema); 96083afc55SAndreas Gohr $this->id = $result['id']; 97fa7b96aaSMichael Grosse $this->user = $result['user']; 98083afc55SAndreas Gohr $this->chksum = $result['chksum']; 9988b7d2aaSAndreas Gohr $this->islookup = $result['islookup']; 100587e314dSAndreas Gohr $this->ts = $result['ts']; 101*e2c90eebSAndreas Gohr $this->editors = $result['editors']; 10288b7d2aaSAndreas Gohr } else { 10388b7d2aaSAndreas Gohr $this->islookup = $islookup; 104083afc55SAndreas Gohr } 105083afc55SAndreas Gohr $this->sqlite->res_close($res); 106083afc55SAndreas Gohr if(!$this->id) return; 107083afc55SAndreas Gohr 108083afc55SAndreas Gohr // load existing columns 109083afc55SAndreas Gohr $sql = "SELECT SC.*, T.* 110083afc55SAndreas Gohr FROM schema_cols SC, 111083afc55SAndreas Gohr types T 1121c502704SAndreas Gohr WHERE SC.sid = ? 1131c502704SAndreas Gohr AND SC.tid = T.id 114083afc55SAndreas Gohr ORDER BY SC.sort"; 1151c502704SAndreas Gohr $res = $this->sqlite->query($sql, $this->id); 116083afc55SAndreas Gohr $rows = $this->sqlite->res2arr($res); 117083afc55SAndreas Gohr $this->sqlite->res_close($res); 118083afc55SAndreas Gohr 119083afc55SAndreas Gohr foreach($rows as $row) { 120328db41bSAndreas Gohr if($row['class'] == 'Integer') { 121328db41bSAndreas Gohr $row['class'] = 'Decimal'; 122328db41bSAndreas Gohr } 123328db41bSAndreas Gohr 124ba766201SAndreas Gohr $class = 'dokuwiki\\plugin\\struct\\types\\' . $row['class']; 12598eaa57dSAndreas Gohr if(!class_exists($class)) { 12698eaa57dSAndreas Gohr // This usually never happens, except during development 12798eaa57dSAndreas Gohr msg('Unknown type "' . hsc($row['class']) . '" falling back to Text', -1); 128ba766201SAndreas Gohr $class = 'dokuwiki\\plugin\\struct\\types\\Text'; 12998eaa57dSAndreas Gohr } 13098eaa57dSAndreas Gohr 131083afc55SAndreas Gohr $config = json_decode($row['config'], true); 132bbf3d6aaSAndreas Gohr /** @var AbstractBaseType $type */ 133bbf3d6aaSAndreas Gohr $type = new $class($config, $row['label'], $row['ismulti'], $row['tid']); 134bbf3d6aaSAndreas Gohr $column = new Column( 1351c502704SAndreas Gohr $row['sort'], 136bbf3d6aaSAndreas Gohr $type, 1371c502704SAndreas Gohr $row['colref'], 13863d51bbfSAndreas Gohr $row['enabled'], 13963d51bbfSAndreas Gohr $table 1401c502704SAndreas Gohr ); 141bbf3d6aaSAndreas Gohr $type->setContext($column); 1421c502704SAndreas Gohr 1437629557eSAndreas Gohr $this->columns[] = $column; 144083afc55SAndreas Gohr if($row['sort'] > $this->maxsort) $this->maxsort = $row['sort']; 145083afc55SAndreas Gohr } 146083afc55SAndreas Gohr } 147083afc55SAndreas Gohr 148083afc55SAndreas Gohr /** 14967641668SAndreas Gohr * @return string identifer for debugging purposes 15067641668SAndreas Gohr */ 15167641668SAndreas Gohr function __toString() { 15267641668SAndreas Gohr return __CLASS__.' '.$this->table.' ('.$this->id.') '.($this->islookup ? 'LOOKUP' : 'DATA'); 15367641668SAndreas Gohr } 15467641668SAndreas Gohr 15567641668SAndreas Gohr /** 156083afc55SAndreas Gohr * Cleans any unwanted stuff from table names 157083afc55SAndreas Gohr * 158083afc55SAndreas Gohr * @param string $table 159083afc55SAndreas Gohr * @return string 160083afc55SAndreas Gohr */ 161083afc55SAndreas Gohr static public function cleanTableName($table) { 1622af472dcSAndreas Gohr $table = strtolower($table); 163083afc55SAndreas Gohr $table = preg_replace('/[^a-z0-9_]+/', '', $table); 164083afc55SAndreas Gohr $table = preg_replace('/^[0-9_]+/', '', $table); 165083afc55SAndreas Gohr $table = trim($table); 166083afc55SAndreas Gohr return $table; 167083afc55SAndreas Gohr } 168083afc55SAndreas Gohr 169083afc55SAndreas Gohr /** 170097f4a53SAndreas Gohr * Gets a list of all available schemas 171097f4a53SAndreas Gohr * 1727c080d69SAndreas Gohr * @param string $filter either 'page' or 'lookup' 1737c080d69SAndreas Gohr * @return \string[] 174097f4a53SAndreas Gohr */ 1757c080d69SAndreas Gohr static public function getAll($filter = '') { 176097f4a53SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 177097f4a53SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 178097f4a53SAndreas Gohr $db = $helper->getDB(); 179097f4a53SAndreas Gohr if(!$db) return array(); 180097f4a53SAndreas Gohr 1817c080d69SAndreas Gohr if($filter == 'page') { 1827c080d69SAndreas Gohr $where = 'islookup = 0'; 1837c080d69SAndreas Gohr } elseif($filter == 'lookup') { 1847c080d69SAndreas Gohr $where = 'islookup = 1'; 1857c080d69SAndreas Gohr } else { 1867c080d69SAndreas Gohr $where = '1 = 1'; 1877c080d69SAndreas Gohr } 1887c080d69SAndreas Gohr 1897c080d69SAndreas Gohr $res = $db->query("SELECT DISTINCT tbl FROM schemas WHERE $where ORDER BY tbl"); 190097f4a53SAndreas Gohr $tables = $db->res2arr($res); 191097f4a53SAndreas Gohr $db->res_close($res); 192097f4a53SAndreas Gohr 193097f4a53SAndreas Gohr $result = array(); 194097f4a53SAndreas Gohr foreach($tables as $row) { 195097f4a53SAndreas Gohr $result[] = $row['tbl']; 196097f4a53SAndreas Gohr } 197097f4a53SAndreas Gohr return $result; 198097f4a53SAndreas Gohr } 199097f4a53SAndreas Gohr 200097f4a53SAndreas Gohr /** 201d5a1a6dcSAndreas Gohr * Delete all data associated with this schema 202d5a1a6dcSAndreas Gohr * 203d5a1a6dcSAndreas Gohr * This is really all data ever! Be careful! 204d5a1a6dcSAndreas Gohr */ 205d5a1a6dcSAndreas Gohr public function delete() { 206d5a1a6dcSAndreas Gohr if(!$this->id) throw new StructException('can not delete unsaved schema'); 207d5a1a6dcSAndreas Gohr 208d5a1a6dcSAndreas Gohr $this->sqlite->query('BEGIN TRANSACTION'); 209d5a1a6dcSAndreas Gohr 210d5a1a6dcSAndreas Gohr $sql = "DROP TABLE ?"; 211d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'data_' . $this->table); 212d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'multi_' . $this->table); 213d5a1a6dcSAndreas Gohr 214d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments WHERE tbl = ?"; 215d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 216d5a1a6dcSAndreas Gohr 217d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments_patterns WHERE tbl = ?"; 218d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 219d5a1a6dcSAndreas Gohr 220d5a1a6dcSAndreas Gohr $sql = "SELECT T.id 221d5a1a6dcSAndreas Gohr FROM types T, schema_cols SC, schemas S 222d5a1a6dcSAndreas Gohr WHERE T.id = SC.tid 223d5a1a6dcSAndreas Gohr AND SC.sid = S.id 224d5a1a6dcSAndreas Gohr AND S.tbl = ?"; 225d5a1a6dcSAndreas Gohr $sql = "DELETE FROM types WHERE id IN ($sql)"; 226d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 227d5a1a6dcSAndreas Gohr 228d5a1a6dcSAndreas Gohr $sql = "SELECT id 229d5a1a6dcSAndreas Gohr FROM schemas 230d5a1a6dcSAndreas Gohr WHERE tbl = ?"; 231d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_cols WHERE sid IN ($sql)"; 232d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 233d5a1a6dcSAndreas Gohr 234d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schemas WHERE tbl = ?"; 235d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 236d5a1a6dcSAndreas Gohr 237d5a1a6dcSAndreas Gohr $this->sqlite->query('COMMIT TRANSACTION'); 238d5a1a6dcSAndreas Gohr $this->sqlite->query('VACUUM'); 239f9f13d8cSAndreas Gohr 240f9f13d8cSAndreas Gohr // a deleted schema should not be used anymore, but let's make sure it's somewhat sane anyway 241f9f13d8cSAndreas Gohr $this->id = 0; 242f9f13d8cSAndreas Gohr $this->chksum = ''; 243f9f13d8cSAndreas Gohr $this->columns = array(); 244f9f13d8cSAndreas Gohr $this->maxsort = 0; 245f9f13d8cSAndreas Gohr $this->ts = 0; 246d5a1a6dcSAndreas Gohr } 247d5a1a6dcSAndreas Gohr 248d5a1a6dcSAndreas Gohr /** 2491c502704SAndreas Gohr * @return string 2501c502704SAndreas Gohr */ 2511c502704SAndreas Gohr public function getChksum() { 2521c502704SAndreas Gohr return $this->chksum; 2531c502704SAndreas Gohr } 2541c502704SAndreas Gohr 2551c502704SAndreas Gohr /** 2561c502704SAndreas Gohr * @return int 2571c502704SAndreas Gohr */ 2581c502704SAndreas Gohr public function getId() { 2591c502704SAndreas Gohr return $this->id; 2601c502704SAndreas Gohr } 2611c502704SAndreas Gohr 2621c502704SAndreas Gohr /** 263587e314dSAndreas Gohr * @return int returns the timestamp this Schema was created at 264f411d872SAndreas Gohr */ 265f411d872SAndreas Gohr public function getTimeStamp() { 266f411d872SAndreas Gohr return $this->ts; 267f411d872SAndreas Gohr } 268f411d872SAndreas Gohr 269f411d872SAndreas Gohr /** 27088b7d2aaSAndreas Gohr * @return bool is this a lookup schema? 27188b7d2aaSAndreas Gohr */ 27288b7d2aaSAndreas Gohr public function isLookup() { 27388b7d2aaSAndreas Gohr return $this->islookup; 27488b7d2aaSAndreas Gohr } 27588b7d2aaSAndreas Gohr 27688b7d2aaSAndreas Gohr /** 277fa7b96aaSMichael Grosse * @return string 278fa7b96aaSMichael Grosse */ 279fa7b96aaSMichael Grosse public function getUser() { 280fa7b96aaSMichael Grosse return $this->user; 281fa7b96aaSMichael Grosse } 282fa7b96aaSMichael Grosse 283*e2c90eebSAndreas Gohr public function getEditors() { 284*e2c90eebSAndreas Gohr return $this->editors; 285*e2c90eebSAndreas Gohr } 286*e2c90eebSAndreas Gohr 287fa7b96aaSMichael Grosse /** 288ce206ec7SAndreas Gohr * Returns a list of columns in this schema 289ce206ec7SAndreas Gohr * 290ce206ec7SAndreas Gohr * @param bool $withDisabled if false, disabled columns will not be returned 291ce206ec7SAndreas Gohr * @return Column[] 2921c502704SAndreas Gohr */ 293ce206ec7SAndreas Gohr public function getColumns($withDisabled = true) { 294ce206ec7SAndreas Gohr if(!$withDisabled) { 295ce206ec7SAndreas Gohr return array_filter( 296ce206ec7SAndreas Gohr $this->columns, 297ce206ec7SAndreas Gohr function (Column $col) { 298ce206ec7SAndreas Gohr return $col->isEnabled(); 299ce206ec7SAndreas Gohr } 300ce206ec7SAndreas Gohr ); 301ce206ec7SAndreas Gohr } 302ce206ec7SAndreas Gohr 3031c502704SAndreas Gohr return $this->columns; 3041c502704SAndreas Gohr } 3051c502704SAndreas Gohr 306ae697e1fSAndreas Gohr /** 3075742aea9SAndreas Gohr * Find a column in the schema by its label 3085742aea9SAndreas Gohr * 3095742aea9SAndreas Gohr * Only enabled columns are returned! 3105742aea9SAndreas Gohr * 3115742aea9SAndreas Gohr * @param $name 3125742aea9SAndreas Gohr * @return bool|Column 3135742aea9SAndreas Gohr */ 3145742aea9SAndreas Gohr public function findColumn($name) { 3155742aea9SAndreas Gohr foreach($this->columns as $col) { 3165742aea9SAndreas Gohr if($col->isEnabled() && utf8_strtolower($col->getLabel()) == utf8_strtolower($name)) { 3175742aea9SAndreas Gohr return $col; 3185742aea9SAndreas Gohr } 3195742aea9SAndreas Gohr } 3205742aea9SAndreas Gohr return false; 3215742aea9SAndreas Gohr } 3225742aea9SAndreas Gohr 3235742aea9SAndreas Gohr /** 324ae697e1fSAndreas Gohr * @return string 325ae697e1fSAndreas Gohr */ 326ae697e1fSAndreas Gohr public function getTable() { 327ae697e1fSAndreas Gohr return $this->table; 328ae697e1fSAndreas Gohr } 3291c502704SAndreas Gohr 330ae697e1fSAndreas Gohr /** 331ae697e1fSAndreas Gohr * @return int the highest sort number used in this schema 332ae697e1fSAndreas Gohr */ 333ae697e1fSAndreas Gohr public function getMaxsort() { 334ae697e1fSAndreas Gohr return $this->maxsort; 335ae697e1fSAndreas Gohr } 3361c502704SAndreas Gohr 337d486d6d7SAndreas Gohr /** 338d486d6d7SAndreas Gohr * @return string the JSON representing this schema 339d486d6d7SAndreas Gohr */ 340d486d6d7SAndreas Gohr public function toJSON() { 341d486d6d7SAndreas Gohr $data = array( 342d486d6d7SAndreas Gohr 'structversion' => $this->structversion, 343d486d6d7SAndreas Gohr 'schema' => $this->getTable(), 344d486d6d7SAndreas Gohr 'id' => $this->getId(), 34545313413SMichael Grosse 'user' => $this->getUser(), 346d486d6d7SAndreas Gohr 'columns' => array() 347d486d6d7SAndreas Gohr ); 348d486d6d7SAndreas Gohr 349d486d6d7SAndreas Gohr foreach($this->columns as $column) { 350d486d6d7SAndreas Gohr $data['columns'][] = array( 351d486d6d7SAndreas Gohr 'colref' => $column->getColref(), 352d486d6d7SAndreas Gohr 'ismulti' => $column->isMulti(), 353d486d6d7SAndreas Gohr 'isenabled' => $column->isEnabled(), 354d486d6d7SAndreas Gohr 'sort' => $column->getSort(), 355d486d6d7SAndreas Gohr 'label' => $column->getLabel(), 356d486d6d7SAndreas Gohr 'class' => $column->getType()->getClass(), 357d486d6d7SAndreas Gohr 'config' => $column->getType()->getConfig(), 358d486d6d7SAndreas Gohr ); 359d486d6d7SAndreas Gohr } 360d486d6d7SAndreas Gohr 361d486d6d7SAndreas Gohr return json_encode($data, JSON_PRETTY_PRINT); 362d486d6d7SAndreas Gohr } 363083afc55SAndreas Gohr} 364