1083afc55SAndreas Gohr<?php 2083afc55SAndreas Gohr 3ba766201SAndreas Gohrnamespace dokuwiki\plugin\struct\meta; 4d486d6d7SAndreas Gohr 5ba766201SAndreas Gohruse dokuwiki\plugin\struct\types\AbstractBaseType; 6083afc55SAndreas Gohr 77182938bSAndreas Gohr/** 87182938bSAndreas Gohr * Class Schema 97182938bSAndreas Gohr * 107182938bSAndreas Gohr * Represents the schema of a single data table and all its properties. It defines what can be stored in 117182938bSAndreas Gohr * the represented data table and how those contents are formatted. 127182938bSAndreas Gohr * 137182938bSAndreas Gohr * It can be initialized with a timestamp to access the schema as it looked at that particular point in time. 147182938bSAndreas Gohr * 15ba766201SAndreas Gohr * @package dokuwiki\plugin\struct\meta 167182938bSAndreas Gohr */ 17d6d97f60SAnna Dabrowskaclass Schema 18d6d97f60SAnna Dabrowska{ 19f800af69SMichael Große use TranslationUtilities; 20f800af69SMichael Große 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 33083afc55SAndreas Gohr /** 34083afc55SAndreas Gohr * @var string the current checksum of this schema 35083afc55SAndreas Gohr */ 36083afc55SAndreas Gohr protected $chksum = ''; 37083afc55SAndreas Gohr 381c502704SAndreas Gohr /** @var Column[] all the colums */ 39083afc55SAndreas Gohr protected $columns = array(); 40083afc55SAndreas Gohr 41083afc55SAndreas Gohr /** @var int */ 42083afc55SAndreas Gohr protected $maxsort = 0; 43083afc55SAndreas Gohr 44250c83c2SAndreas Gohr /** @var int */ 45250c83c2SAndreas Gohr protected $ts = 0; 46250c83c2SAndreas Gohr 47d486d6d7SAndreas Gohr /** @var string struct version info */ 48d486d6d7SAndreas Gohr protected $structversion = '?'; 49d486d6d7SAndreas Gohr 50127d6bacSMichael Große /** @var array config array with label translations */ 51127d6bacSMichael Große protected $config = array(); 52e2c90eebSAndreas Gohr 53083afc55SAndreas Gohr /** 54083afc55SAndreas Gohr * Schema constructor 557182938bSAndreas Gohr * 56083afc55SAndreas Gohr * @param string $table The table this schema is for 57083afc55SAndreas Gohr * @param int $ts The timestamp for when this schema was valid, 0 for current 58083afc55SAndreas Gohr */ 59d6d97f60SAnna Dabrowska public function __construct($table, $ts = 0) 60d6d97f60SAnna Dabrowska { 61127d6bacSMichael Große $baseconfig = array('allowed editors' => ''); 62127d6bacSMichael Große 63083afc55SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 64083afc55SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 65d486d6d7SAndreas Gohr $info = $helper->getInfo(); 66d486d6d7SAndreas Gohr $this->structversion = $info['date']; 67083afc55SAndreas Gohr $this->sqlite = $helper->getDB(); 68083afc55SAndreas Gohr $table = self::cleanTableName($table); 69083afc55SAndreas Gohr $this->table = $table; 70250c83c2SAndreas Gohr $this->ts = $ts; 71083afc55SAndreas Gohr 72083afc55SAndreas Gohr // load info about the schema itself 73083afc55SAndreas Gohr if ($ts) { 74083afc55SAndreas Gohr $sql = "SELECT * 75083afc55SAndreas Gohr FROM schemas 76083afc55SAndreas Gohr WHERE tbl = ? 77083afc55SAndreas Gohr AND ts <= ? 78083afc55SAndreas Gohr ORDER BY ts DESC 79083afc55SAndreas Gohr LIMIT 1"; 80083afc55SAndreas Gohr $opt = array($table, $ts); 81083afc55SAndreas Gohr } else { 82083afc55SAndreas Gohr $sql = "SELECT * 83083afc55SAndreas Gohr FROM schemas 84083afc55SAndreas Gohr WHERE tbl = ? 85083afc55SAndreas Gohr ORDER BY ts DESC 86083afc55SAndreas Gohr LIMIT 1"; 87083afc55SAndreas Gohr $opt = array($table); 88083afc55SAndreas Gohr } 89083afc55SAndreas Gohr $res = $this->sqlite->query($sql, $opt); 90127d6bacSMichael Große $config = array(); 91083afc55SAndreas Gohr if ($this->sqlite->res2count($res)) { 924e2abec0SMichael Große $schema = $this->sqlite->res2arr($res); 934e2abec0SMichael Große $result = array_shift($schema); 94083afc55SAndreas Gohr $this->id = $result['id']; 95fa7b96aaSMichael Grosse $this->user = $result['user']; 969007da58SMichael Große $this->chksum = isset($result['chksum']) ? $result['chksum'] : ''; 97587e314dSAndreas Gohr $this->ts = $result['ts']; 98127d6bacSMichael Große $config = json_decode($result['config'], true); 99083afc55SAndreas Gohr } 100083afc55SAndreas Gohr $this->sqlite->res_close($res); 101127d6bacSMichael Große $this->config = array_merge($baseconfig, $config); 102f800af69SMichael Große $this->initTransConfig(array('label')); 103083afc55SAndreas Gohr if (!$this->id) return; 104083afc55SAndreas Gohr 105083afc55SAndreas Gohr // load existing columns 106083afc55SAndreas Gohr $sql = "SELECT SC.*, T.* 107083afc55SAndreas Gohr FROM schema_cols SC, 108083afc55SAndreas Gohr types T 1091c502704SAndreas Gohr WHERE SC.sid = ? 1101c502704SAndreas Gohr AND SC.tid = T.id 111083afc55SAndreas Gohr ORDER BY SC.sort"; 1121c502704SAndreas Gohr $res = $this->sqlite->query($sql, $this->id); 113083afc55SAndreas Gohr $rows = $this->sqlite->res2arr($res); 114083afc55SAndreas Gohr $this->sqlite->res_close($res); 115083afc55SAndreas Gohr 116636c8abaSAndreas Gohr $typeclasses = Column::allTypes(); 117083afc55SAndreas Gohr foreach ($rows as $row) { 118328db41bSAndreas Gohr if ($row['class'] == 'Integer') { 119328db41bSAndreas Gohr $row['class'] = 'Decimal'; 120328db41bSAndreas Gohr } 121328db41bSAndreas Gohr 122636c8abaSAndreas Gohr $class = $typeclasses[$row['class']]; 12398eaa57dSAndreas Gohr if (!class_exists($class)) { 12498eaa57dSAndreas Gohr // This usually never happens, except during development 12598eaa57dSAndreas Gohr msg('Unknown type "' . hsc($row['class']) . '" falling back to Text', -1); 126ba766201SAndreas Gohr $class = 'dokuwiki\\plugin\\struct\\types\\Text'; 12798eaa57dSAndreas Gohr } 12898eaa57dSAndreas Gohr 129083afc55SAndreas Gohr $config = json_decode($row['config'], true); 130bbf3d6aaSAndreas Gohr /** @var AbstractBaseType $type */ 131bbf3d6aaSAndreas Gohr $type = new $class($config, $row['label'], $row['ismulti'], $row['tid']); 132bbf3d6aaSAndreas Gohr $column = new Column( 1331c502704SAndreas Gohr $row['sort'], 134bbf3d6aaSAndreas Gohr $type, 1351c502704SAndreas Gohr $row['colref'], 13663d51bbfSAndreas Gohr $row['enabled'], 13763d51bbfSAndreas Gohr $table 1381c502704SAndreas Gohr ); 139bbf3d6aaSAndreas Gohr $type->setContext($column); 1401c502704SAndreas Gohr 1417629557eSAndreas Gohr $this->columns[] = $column; 142083afc55SAndreas Gohr if ($row['sort'] > $this->maxsort) $this->maxsort = $row['sort']; 143083afc55SAndreas Gohr } 144083afc55SAndreas Gohr } 145083afc55SAndreas Gohr 146083afc55SAndreas Gohr /** 14767641668SAndreas Gohr * @return string identifer for debugging purposes 14867641668SAndreas Gohr */ 149748e747fSAnna Dabrowska public function __toString() 150d6d97f60SAnna Dabrowska { 1515b808f9fSAnna Dabrowska return __CLASS__ . ' ' . $this->table . ' (' . $this->id . ') '; 15267641668SAndreas Gohr } 15367641668SAndreas Gohr 15467641668SAndreas Gohr /** 155083afc55SAndreas Gohr * Cleans any unwanted stuff from table names 156083afc55SAndreas Gohr * 157083afc55SAndreas Gohr * @param string $table 158083afc55SAndreas Gohr * @return string 159083afc55SAndreas Gohr */ 160d6d97f60SAnna Dabrowska public static function cleanTableName($table) 161d6d97f60SAnna Dabrowska { 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 169127d6bacSMichael Große 170127d6bacSMichael Große /** 171097f4a53SAndreas Gohr * Gets a list of all available schemas 172097f4a53SAndreas Gohr * 1737c080d69SAndreas Gohr * @return \string[] 174097f4a53SAndreas Gohr */ 1755b808f9fSAnna Dabrowska public static function getAll() 176d6d97f60SAnna Dabrowska { 177097f4a53SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 178097f4a53SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 1797cbcfbdbSAndreas Gohr $db = $helper->getDB(false); 180097f4a53SAndreas Gohr if (!$db) return array(); 181097f4a53SAndreas Gohr 1825b808f9fSAnna Dabrowska $res = $db->query("SELECT DISTINCT tbl FROM schemas ORDER BY tbl"); 183097f4a53SAndreas Gohr $tables = $db->res2arr($res); 184097f4a53SAndreas Gohr $db->res_close($res); 185097f4a53SAndreas Gohr 186097f4a53SAndreas Gohr $result = array(); 187097f4a53SAndreas Gohr foreach ($tables as $row) { 188097f4a53SAndreas Gohr $result[] = $row['tbl']; 189097f4a53SAndreas Gohr } 190097f4a53SAndreas Gohr return $result; 191097f4a53SAndreas Gohr } 192097f4a53SAndreas Gohr 193097f4a53SAndreas Gohr /** 194d5a1a6dcSAndreas Gohr * Delete all data associated with this schema 195d5a1a6dcSAndreas Gohr * 196d5a1a6dcSAndreas Gohr * This is really all data ever! Be careful! 197d5a1a6dcSAndreas Gohr */ 198d6d97f60SAnna Dabrowska public function delete() 199d6d97f60SAnna Dabrowska { 200d5a1a6dcSAndreas Gohr if (!$this->id) throw new StructException('can not delete unsaved schema'); 201d5a1a6dcSAndreas Gohr 202d5a1a6dcSAndreas Gohr $this->sqlite->query('BEGIN TRANSACTION'); 203d5a1a6dcSAndreas Gohr 204d5a1a6dcSAndreas Gohr $sql = "DROP TABLE ?"; 205d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'data_' . $this->table); 206d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'multi_' . $this->table); 207d5a1a6dcSAndreas Gohr 208d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments WHERE tbl = ?"; 209d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 210d5a1a6dcSAndreas Gohr 211d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments_patterns WHERE tbl = ?"; 212d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 213d5a1a6dcSAndreas Gohr 214d5a1a6dcSAndreas Gohr $sql = "SELECT T.id 215d5a1a6dcSAndreas Gohr FROM types T, schema_cols SC, schemas S 216d5a1a6dcSAndreas Gohr WHERE T.id = SC.tid 217d5a1a6dcSAndreas Gohr AND SC.sid = S.id 218d5a1a6dcSAndreas Gohr AND S.tbl = ?"; 219d5a1a6dcSAndreas Gohr $sql = "DELETE FROM types WHERE id IN ($sql)"; 220d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 221d5a1a6dcSAndreas Gohr 222d5a1a6dcSAndreas Gohr $sql = "SELECT id 223d5a1a6dcSAndreas Gohr FROM schemas 224d5a1a6dcSAndreas Gohr WHERE tbl = ?"; 225d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_cols WHERE sid IN ($sql)"; 226d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 227d5a1a6dcSAndreas Gohr 228d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schemas WHERE tbl = ?"; 229d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 230d5a1a6dcSAndreas Gohr 231d5a1a6dcSAndreas Gohr $this->sqlite->query('COMMIT TRANSACTION'); 232d5a1a6dcSAndreas Gohr $this->sqlite->query('VACUUM'); 233f9f13d8cSAndreas Gohr 234f9f13d8cSAndreas Gohr // a deleted schema should not be used anymore, but let's make sure it's somewhat sane anyway 235f9f13d8cSAndreas Gohr $this->id = 0; 236f9f13d8cSAndreas Gohr $this->chksum = ''; 237f9f13d8cSAndreas Gohr $this->columns = array(); 238f9f13d8cSAndreas Gohr $this->maxsort = 0; 239f9f13d8cSAndreas Gohr $this->ts = 0; 240d5a1a6dcSAndreas Gohr } 241d5a1a6dcSAndreas Gohr 24279c83e06SMichael Große 24379c83e06SMichael Große /** 24479c83e06SMichael Große * Clear all data of a schema, but retain the schema itself 24579c83e06SMichael Große */ 246d6d97f60SAnna Dabrowska public function clear() 247d6d97f60SAnna Dabrowska { 24879c83e06SMichael Große if (!$this->id) throw new StructException('can not clear data of unsaved schema'); 24979c83e06SMichael Große 25079c83e06SMichael Große $this->sqlite->query('BEGIN TRANSACTION'); 25179c83e06SMichael Große $sql = 'DELETE FROM ?'; 25279c83e06SMichael Große $this->sqlite->query($sql, 'data_' . $this->table); 25379c83e06SMichael Große $this->sqlite->query($sql, 'multi_' . $this->table); 25479c83e06SMichael Große $this->sqlite->query('COMMIT TRANSACTION'); 25579c83e06SMichael Große $this->sqlite->query('VACUUM'); 25679c83e06SMichael Große } 25779c83e06SMichael Große 258d5a1a6dcSAndreas Gohr /** 2591c502704SAndreas Gohr * @return string 2601c502704SAndreas Gohr */ 261d6d97f60SAnna Dabrowska public function getChksum() 262d6d97f60SAnna Dabrowska { 2631c502704SAndreas Gohr return $this->chksum; 2641c502704SAndreas Gohr } 2651c502704SAndreas Gohr 2661c502704SAndreas Gohr /** 2671c502704SAndreas Gohr * @return int 2681c502704SAndreas Gohr */ 269d6d97f60SAnna Dabrowska public function getId() 270d6d97f60SAnna Dabrowska { 2711c502704SAndreas Gohr return $this->id; 2721c502704SAndreas Gohr } 2731c502704SAndreas Gohr 2741c502704SAndreas Gohr /** 275587e314dSAndreas Gohr * @return int returns the timestamp this Schema was created at 276f411d872SAndreas Gohr */ 277d6d97f60SAnna Dabrowska public function getTimeStamp() 278d6d97f60SAnna Dabrowska { 279f411d872SAndreas Gohr return $this->ts; 280f411d872SAndreas Gohr } 281f411d872SAndreas Gohr 282f411d872SAndreas Gohr /** 283fa7b96aaSMichael Grosse * @return string 284fa7b96aaSMichael Grosse */ 285d6d97f60SAnna Dabrowska public function getUser() 286d6d97f60SAnna Dabrowska { 287fa7b96aaSMichael Grosse return $this->user; 288fa7b96aaSMichael Grosse } 289fa7b96aaSMichael Grosse 290d6d97f60SAnna Dabrowska public function getConfig() 291d6d97f60SAnna Dabrowska { 292127d6bacSMichael Große return $this->config; 293e2c90eebSAndreas Gohr } 294e2c90eebSAndreas Gohr 295fa7b96aaSMichael Grosse /** 296f800af69SMichael Große * Returns the translated label for this schema 297f800af69SMichael Große * 298f800af69SMichael Große * Uses the current language as determined by $conf['lang']. Falls back to english 299f800af69SMichael Große * and then to the Schema label 300f800af69SMichael Große * 301f800af69SMichael Große * @return string 302f800af69SMichael Große */ 303d6d97f60SAnna Dabrowska public function getTranslatedLabel() 304d6d97f60SAnna Dabrowska { 305f800af69SMichael Große return $this->getTranslatedKey('label', $this->table); 306f800af69SMichael Große } 307f800af69SMichael Große 308f800af69SMichael Große /** 3096ebbbb8eSAndreas Gohr * Checks if the current user may edit data in this schema 3106ebbbb8eSAndreas Gohr * 3116ebbbb8eSAndreas Gohr * @return bool 3126ebbbb8eSAndreas Gohr */ 313d6d97f60SAnna Dabrowska public function isEditable() 314d6d97f60SAnna Dabrowska { 3156ebbbb8eSAndreas Gohr global $USERINFO; 316*ecf2cba2SAndreas Gohr global $INPUT; 317127d6bacSMichael Große if ($this->config['allowed editors'] === '') return true; 318*ecf2cba2SAndreas Gohr if ($INPUT->server->str('REMOTE_USER') === '') return false; 3196ebbbb8eSAndreas Gohr if (auth_isadmin()) return true; 320*ecf2cba2SAndreas Gohr return auth_isMember($this->config['allowed editors'], $INPUT->server->str('REMOTE_USER'), $USERINFO['grps']); 3216ebbbb8eSAndreas Gohr } 3226ebbbb8eSAndreas Gohr 3236ebbbb8eSAndreas Gohr /** 324ce206ec7SAndreas Gohr * Returns a list of columns in this schema 325ce206ec7SAndreas Gohr * 326ce206ec7SAndreas Gohr * @param bool $withDisabled if false, disabled columns will not be returned 327ce206ec7SAndreas Gohr * @return Column[] 3281c502704SAndreas Gohr */ 329d6d97f60SAnna Dabrowska public function getColumns($withDisabled = true) 330d6d97f60SAnna Dabrowska { 331ce206ec7SAndreas Gohr if (!$withDisabled) { 332ce206ec7SAndreas Gohr return array_filter( 333ce206ec7SAndreas Gohr $this->columns, 334ce206ec7SAndreas Gohr function (Column $col) { 335ce206ec7SAndreas Gohr return $col->isEnabled(); 336ce206ec7SAndreas Gohr } 337ce206ec7SAndreas Gohr ); 338ce206ec7SAndreas Gohr } 339ce206ec7SAndreas Gohr 3401c502704SAndreas Gohr return $this->columns; 3411c502704SAndreas Gohr } 3421c502704SAndreas Gohr 343ae697e1fSAndreas Gohr /** 3445742aea9SAndreas Gohr * Find a column in the schema by its label 3455742aea9SAndreas Gohr * 3465742aea9SAndreas Gohr * Only enabled columns are returned! 3475742aea9SAndreas Gohr * 3485742aea9SAndreas Gohr * @param $name 3495742aea9SAndreas Gohr * @return bool|Column 3505742aea9SAndreas Gohr */ 351d6d97f60SAnna Dabrowska public function findColumn($name) 352d6d97f60SAnna Dabrowska { 3535742aea9SAndreas Gohr foreach ($this->columns as $col) { 3545742aea9SAndreas Gohr if ($col->isEnabled() && utf8_strtolower($col->getLabel()) == utf8_strtolower($name)) { 3555742aea9SAndreas Gohr return $col; 3565742aea9SAndreas Gohr } 3575742aea9SAndreas Gohr } 3585742aea9SAndreas Gohr return false; 3595742aea9SAndreas Gohr } 3605742aea9SAndreas Gohr 3615742aea9SAndreas Gohr /** 362ae697e1fSAndreas Gohr * @return string 363ae697e1fSAndreas Gohr */ 364d6d97f60SAnna Dabrowska public function getTable() 365d6d97f60SAnna Dabrowska { 366ae697e1fSAndreas Gohr return $this->table; 367ae697e1fSAndreas Gohr } 3681c502704SAndreas Gohr 369ae697e1fSAndreas Gohr /** 370ae697e1fSAndreas Gohr * @return int the highest sort number used in this schema 371ae697e1fSAndreas Gohr */ 372d6d97f60SAnna Dabrowska public function getMaxsort() 373d6d97f60SAnna Dabrowska { 374ae697e1fSAndreas Gohr return $this->maxsort; 375ae697e1fSAndreas Gohr } 3761c502704SAndreas Gohr 377d486d6d7SAndreas Gohr /** 378d486d6d7SAndreas Gohr * @return string the JSON representing this schema 379d486d6d7SAndreas Gohr */ 380d6d97f60SAnna Dabrowska public function toJSON() 381d6d97f60SAnna Dabrowska { 382d486d6d7SAndreas Gohr $data = array( 383d486d6d7SAndreas Gohr 'structversion' => $this->structversion, 384d486d6d7SAndreas Gohr 'schema' => $this->getTable(), 385d486d6d7SAndreas Gohr 'id' => $this->getId(), 38645313413SMichael Grosse 'user' => $this->getUser(), 387127d6bacSMichael Große 'config' => $this->getConfig(), 388d486d6d7SAndreas Gohr 'columns' => array() 389d486d6d7SAndreas Gohr ); 390d486d6d7SAndreas Gohr 391d486d6d7SAndreas Gohr foreach ($this->columns as $column) { 392d486d6d7SAndreas Gohr $data['columns'][] = array( 393d486d6d7SAndreas Gohr 'colref' => $column->getColref(), 394d486d6d7SAndreas Gohr 'ismulti' => $column->isMulti(), 395d486d6d7SAndreas Gohr 'isenabled' => $column->isEnabled(), 396d486d6d7SAndreas Gohr 'sort' => $column->getSort(), 397d486d6d7SAndreas Gohr 'label' => $column->getLabel(), 398d486d6d7SAndreas Gohr 'class' => $column->getType()->getClass(), 399d486d6d7SAndreas Gohr 'config' => $column->getType()->getConfig(), 400d486d6d7SAndreas Gohr ); 401d486d6d7SAndreas Gohr } 402d486d6d7SAndreas Gohr 403d486d6d7SAndreas Gohr return json_encode($data, JSON_PRETTY_PRINT); 404d486d6d7SAndreas Gohr } 405083afc55SAndreas Gohr} 406