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 */ 19d6d97f60SAnna Dabrowskaclass Schema 20d6d97f60SAnna Dabrowska{ 21f800af69SMichael Große use TranslationUtilities; 22f800af69SMichael Große 23083afc55SAndreas Gohr /** @var \helper_plugin_sqlite|null */ 24083afc55SAndreas Gohr protected $sqlite; 25083afc55SAndreas Gohr 26083afc55SAndreas Gohr /** @var int The ID of this schema */ 27083afc55SAndreas Gohr protected $id = 0; 28083afc55SAndreas Gohr 29fa7b96aaSMichael Grosse /** @var string the user who last edited this schema */ 30fa7b96aaSMichael Grosse protected $user = ''; 31fa7b96aaSMichael Grosse 32083afc55SAndreas Gohr /** @var string name of the associated table */ 33083afc55SAndreas Gohr protected $table = ''; 34083afc55SAndreas Gohr 35083afc55SAndreas Gohr /** 36083afc55SAndreas Gohr * @var string the current checksum of this schema 37083afc55SAndreas Gohr */ 38083afc55SAndreas Gohr protected $chksum = ''; 39083afc55SAndreas Gohr 401c502704SAndreas Gohr /** @var Column[] all the colums */ 41083afc55SAndreas Gohr protected $columns = array(); 42083afc55SAndreas Gohr 43083afc55SAndreas Gohr /** @var int */ 44083afc55SAndreas Gohr protected $maxsort = 0; 45083afc55SAndreas Gohr 46250c83c2SAndreas Gohr /** @var int */ 47250c83c2SAndreas Gohr protected $ts = 0; 48250c83c2SAndreas Gohr 49d486d6d7SAndreas Gohr /** @var string struct version info */ 50d486d6d7SAndreas Gohr protected $structversion = '?'; 51d486d6d7SAndreas Gohr 52127d6bacSMichael Große /** @var array config array with label translations */ 53127d6bacSMichael Große protected $config = array(); 54e2c90eebSAndreas Gohr 55083afc55SAndreas Gohr /** 56083afc55SAndreas Gohr * Schema constructor 577182938bSAndreas Gohr * 58083afc55SAndreas Gohr * @param string $table The table this schema is for 59083afc55SAndreas Gohr * @param int $ts The timestamp for when this schema was valid, 0 for current 60083afc55SAndreas Gohr */ 61d6d97f60SAnna Dabrowska public function __construct($table, $ts = 0) 62d6d97f60SAnna Dabrowska { 63127d6bacSMichael Große $baseconfig = array('allowed editors' => ''); 64127d6bacSMichael Große 65083afc55SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 66083afc55SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 67d486d6d7SAndreas Gohr $info = $helper->getInfo(); 68d486d6d7SAndreas Gohr $this->structversion = $info['date']; 69083afc55SAndreas Gohr $this->sqlite = $helper->getDB(); 70083afc55SAndreas Gohr $table = self::cleanTableName($table); 71083afc55SAndreas Gohr $this->table = $table; 72250c83c2SAndreas Gohr $this->ts = $ts; 73083afc55SAndreas Gohr 74083afc55SAndreas Gohr // load info about the schema itself 75083afc55SAndreas Gohr if ($ts) { 76083afc55SAndreas Gohr $sql = "SELECT * 77083afc55SAndreas Gohr FROM schemas 78083afc55SAndreas Gohr WHERE tbl = ? 79083afc55SAndreas Gohr AND ts <= ? 80083afc55SAndreas Gohr ORDER BY ts DESC 81083afc55SAndreas Gohr LIMIT 1"; 82083afc55SAndreas Gohr $opt = array($table, $ts); 83083afc55SAndreas Gohr } else { 84083afc55SAndreas Gohr $sql = "SELECT * 85083afc55SAndreas Gohr FROM schemas 86083afc55SAndreas Gohr WHERE tbl = ? 87083afc55SAndreas Gohr ORDER BY ts DESC 88083afc55SAndreas Gohr LIMIT 1"; 89083afc55SAndreas Gohr $opt = array($table); 90083afc55SAndreas Gohr } 91083afc55SAndreas Gohr $res = $this->sqlite->query($sql, $opt); 92127d6bacSMichael Große $config = array(); 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']; 989007da58SMichael Große $this->chksum = isset($result['chksum']) ? $result['chksum'] : ''; 99587e314dSAndreas Gohr $this->ts = $result['ts']; 100127d6bacSMichael Große $config = json_decode($result['config'], true); 101083afc55SAndreas Gohr } 102083afc55SAndreas Gohr $this->sqlite->res_close($res); 103127d6bacSMichael Große $this->config = array_merge($baseconfig, $config); 104f800af69SMichael Große $this->initTransConfig(array('label')); 105083afc55SAndreas Gohr if (!$this->id) return; 106083afc55SAndreas Gohr 107083afc55SAndreas Gohr // load existing columns 108083afc55SAndreas Gohr $sql = "SELECT SC.*, T.* 109083afc55SAndreas Gohr FROM schema_cols SC, 110083afc55SAndreas Gohr types T 1111c502704SAndreas Gohr WHERE SC.sid = ? 1121c502704SAndreas Gohr AND SC.tid = T.id 113083afc55SAndreas Gohr ORDER BY SC.sort"; 1141c502704SAndreas Gohr $res = $this->sqlite->query($sql, $this->id); 115083afc55SAndreas Gohr $rows = $this->sqlite->res2arr($res); 116083afc55SAndreas Gohr $this->sqlite->res_close($res); 117083afc55SAndreas Gohr 118636c8abaSAndreas Gohr $typeclasses = Column::allTypes(); 119083afc55SAndreas Gohr foreach ($rows as $row) { 120328db41bSAndreas Gohr if ($row['class'] == 'Integer') { 121328db41bSAndreas Gohr $row['class'] = 'Decimal'; 122328db41bSAndreas Gohr } 123328db41bSAndreas Gohr 124636c8abaSAndreas Gohr $class = $typeclasses[$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 */ 151748e747fSAnna Dabrowska public function __toString() 152d6d97f60SAnna Dabrowska { 153*5b808f9fSAnna Dabrowska return __CLASS__ . ' ' . $this->table . ' (' . $this->id . ') '; 15467641668SAndreas Gohr } 15567641668SAndreas Gohr 15667641668SAndreas Gohr /** 157083afc55SAndreas Gohr * Cleans any unwanted stuff from table names 158083afc55SAndreas Gohr * 159083afc55SAndreas Gohr * @param string $table 160083afc55SAndreas Gohr * @return string 161083afc55SAndreas Gohr */ 162d6d97f60SAnna Dabrowska public static function cleanTableName($table) 163d6d97f60SAnna Dabrowska { 1642af472dcSAndreas Gohr $table = strtolower($table); 165083afc55SAndreas Gohr $table = preg_replace('/[^a-z0-9_]+/', '', $table); 166083afc55SAndreas Gohr $table = preg_replace('/^[0-9_]+/', '', $table); 167083afc55SAndreas Gohr $table = trim($table); 168083afc55SAndreas Gohr return $table; 169083afc55SAndreas Gohr } 170083afc55SAndreas Gohr 171127d6bacSMichael Große 172127d6bacSMichael Große /** 173097f4a53SAndreas Gohr * Gets a list of all available schemas 174097f4a53SAndreas Gohr * 1757c080d69SAndreas Gohr * @return \string[] 176097f4a53SAndreas Gohr */ 177*5b808f9fSAnna Dabrowska public static function getAll() 178d6d97f60SAnna Dabrowska { 179097f4a53SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 180097f4a53SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 1817cbcfbdbSAndreas Gohr $db = $helper->getDB(false); 182097f4a53SAndreas Gohr if (!$db) return array(); 183097f4a53SAndreas Gohr 184*5b808f9fSAnna Dabrowska $res = $db->query("SELECT DISTINCT tbl FROM schemas ORDER BY tbl"); 185097f4a53SAndreas Gohr $tables = $db->res2arr($res); 186097f4a53SAndreas Gohr $db->res_close($res); 187097f4a53SAndreas Gohr 188097f4a53SAndreas Gohr $result = array(); 189097f4a53SAndreas Gohr foreach ($tables as $row) { 190097f4a53SAndreas Gohr $result[] = $row['tbl']; 191097f4a53SAndreas Gohr } 192097f4a53SAndreas Gohr return $result; 193097f4a53SAndreas Gohr } 194097f4a53SAndreas Gohr 195097f4a53SAndreas Gohr /** 196d5a1a6dcSAndreas Gohr * Delete all data associated with this schema 197d5a1a6dcSAndreas Gohr * 198d5a1a6dcSAndreas Gohr * This is really all data ever! Be careful! 199d5a1a6dcSAndreas Gohr */ 200d6d97f60SAnna Dabrowska public function delete() 201d6d97f60SAnna Dabrowska { 202d5a1a6dcSAndreas Gohr if (!$this->id) throw new StructException('can not delete unsaved schema'); 203d5a1a6dcSAndreas Gohr 204d5a1a6dcSAndreas Gohr $this->sqlite->query('BEGIN TRANSACTION'); 205d5a1a6dcSAndreas Gohr 206d5a1a6dcSAndreas Gohr $sql = "DROP TABLE ?"; 207d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'data_' . $this->table); 208d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'multi_' . $this->table); 209d5a1a6dcSAndreas Gohr 210d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments WHERE tbl = ?"; 211d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 212d5a1a6dcSAndreas Gohr 213d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments_patterns WHERE tbl = ?"; 214d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 215d5a1a6dcSAndreas Gohr 216d5a1a6dcSAndreas Gohr $sql = "SELECT T.id 217d5a1a6dcSAndreas Gohr FROM types T, schema_cols SC, schemas S 218d5a1a6dcSAndreas Gohr WHERE T.id = SC.tid 219d5a1a6dcSAndreas Gohr AND SC.sid = S.id 220d5a1a6dcSAndreas Gohr AND S.tbl = ?"; 221d5a1a6dcSAndreas Gohr $sql = "DELETE FROM types WHERE id IN ($sql)"; 222d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 223d5a1a6dcSAndreas Gohr 224d5a1a6dcSAndreas Gohr $sql = "SELECT id 225d5a1a6dcSAndreas Gohr FROM schemas 226d5a1a6dcSAndreas Gohr WHERE tbl = ?"; 227d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_cols WHERE sid IN ($sql)"; 228d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 229d5a1a6dcSAndreas Gohr 230d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schemas WHERE tbl = ?"; 231d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 232d5a1a6dcSAndreas Gohr 233d5a1a6dcSAndreas Gohr $this->sqlite->query('COMMIT TRANSACTION'); 234d5a1a6dcSAndreas Gohr $this->sqlite->query('VACUUM'); 235f9f13d8cSAndreas Gohr 236f9f13d8cSAndreas Gohr // a deleted schema should not be used anymore, but let's make sure it's somewhat sane anyway 237f9f13d8cSAndreas Gohr $this->id = 0; 238f9f13d8cSAndreas Gohr $this->chksum = ''; 239f9f13d8cSAndreas Gohr $this->columns = array(); 240f9f13d8cSAndreas Gohr $this->maxsort = 0; 241f9f13d8cSAndreas Gohr $this->ts = 0; 242d5a1a6dcSAndreas Gohr } 243d5a1a6dcSAndreas Gohr 24479c83e06SMichael Große 24579c83e06SMichael Große /** 24679c83e06SMichael Große * Clear all data of a schema, but retain the schema itself 24779c83e06SMichael Große */ 248d6d97f60SAnna Dabrowska public function clear() 249d6d97f60SAnna Dabrowska { 25079c83e06SMichael Große if (!$this->id) throw new StructException('can not clear data of unsaved schema'); 25179c83e06SMichael Große 25279c83e06SMichael Große $this->sqlite->query('BEGIN TRANSACTION'); 25379c83e06SMichael Große $sql = 'DELETE FROM ?'; 25479c83e06SMichael Große $this->sqlite->query($sql, 'data_' . $this->table); 25579c83e06SMichael Große $this->sqlite->query($sql, 'multi_' . $this->table); 25679c83e06SMichael Große $this->sqlite->query('COMMIT TRANSACTION'); 25779c83e06SMichael Große $this->sqlite->query('VACUUM'); 25879c83e06SMichael Große } 25979c83e06SMichael Große 260d5a1a6dcSAndreas Gohr /** 2611c502704SAndreas Gohr * @return string 2621c502704SAndreas Gohr */ 263d6d97f60SAnna Dabrowska public function getChksum() 264d6d97f60SAnna Dabrowska { 2651c502704SAndreas Gohr return $this->chksum; 2661c502704SAndreas Gohr } 2671c502704SAndreas Gohr 2681c502704SAndreas Gohr /** 2691c502704SAndreas Gohr * @return int 2701c502704SAndreas Gohr */ 271d6d97f60SAnna Dabrowska public function getId() 272d6d97f60SAnna Dabrowska { 2731c502704SAndreas Gohr return $this->id; 2741c502704SAndreas Gohr } 2751c502704SAndreas Gohr 2761c502704SAndreas Gohr /** 277587e314dSAndreas Gohr * @return int returns the timestamp this Schema was created at 278f411d872SAndreas Gohr */ 279d6d97f60SAnna Dabrowska public function getTimeStamp() 280d6d97f60SAnna Dabrowska { 281f411d872SAndreas Gohr return $this->ts; 282f411d872SAndreas Gohr } 283f411d872SAndreas Gohr 284f411d872SAndreas Gohr /** 285fa7b96aaSMichael Grosse * @return string 286fa7b96aaSMichael Grosse */ 287d6d97f60SAnna Dabrowska public function getUser() 288d6d97f60SAnna Dabrowska { 289fa7b96aaSMichael Grosse return $this->user; 290fa7b96aaSMichael Grosse } 291fa7b96aaSMichael Grosse 292d6d97f60SAnna Dabrowska public function getConfig() 293d6d97f60SAnna Dabrowska { 294127d6bacSMichael Große return $this->config; 295e2c90eebSAndreas Gohr } 296e2c90eebSAndreas Gohr 297fa7b96aaSMichael Grosse /** 298f800af69SMichael Große * Returns the translated label for this schema 299f800af69SMichael Große * 300f800af69SMichael Große * Uses the current language as determined by $conf['lang']. Falls back to english 301f800af69SMichael Große * and then to the Schema label 302f800af69SMichael Große * 303f800af69SMichael Große * @return string 304f800af69SMichael Große */ 305d6d97f60SAnna Dabrowska public function getTranslatedLabel() 306d6d97f60SAnna Dabrowska { 307f800af69SMichael Große return $this->getTranslatedKey('label', $this->table); 308f800af69SMichael Große } 309f800af69SMichael Große 310f800af69SMichael Große /** 3116ebbbb8eSAndreas Gohr * Checks if the current user may edit data in this schema 3126ebbbb8eSAndreas Gohr * 3136ebbbb8eSAndreas Gohr * @return bool 3146ebbbb8eSAndreas Gohr */ 315d6d97f60SAnna Dabrowska public function isEditable() 316d6d97f60SAnna Dabrowska { 3176ebbbb8eSAndreas Gohr global $USERINFO; 318127d6bacSMichael Große if ($this->config['allowed editors'] === '') return true; 3196ebbbb8eSAndreas Gohr if (blank($_SERVER['REMOTE_USER'])) return false; 3206ebbbb8eSAndreas Gohr if (auth_isadmin()) return true; 321127d6bacSMichael Große return auth_isMember($this->config['allowed editors'], $_SERVER['REMOTE_USER'], $USERINFO['grps']); 3226ebbbb8eSAndreas Gohr } 3236ebbbb8eSAndreas Gohr 3246ebbbb8eSAndreas Gohr /** 325ce206ec7SAndreas Gohr * Returns a list of columns in this schema 326ce206ec7SAndreas Gohr * 327ce206ec7SAndreas Gohr * @param bool $withDisabled if false, disabled columns will not be returned 328ce206ec7SAndreas Gohr * @return Column[] 3291c502704SAndreas Gohr */ 330d6d97f60SAnna Dabrowska public function getColumns($withDisabled = true) 331d6d97f60SAnna Dabrowska { 332ce206ec7SAndreas Gohr if (!$withDisabled) { 333ce206ec7SAndreas Gohr return array_filter( 334ce206ec7SAndreas Gohr $this->columns, 335ce206ec7SAndreas Gohr function (Column $col) { 336ce206ec7SAndreas Gohr return $col->isEnabled(); 337ce206ec7SAndreas Gohr } 338ce206ec7SAndreas Gohr ); 339ce206ec7SAndreas Gohr } 340ce206ec7SAndreas Gohr 3411c502704SAndreas Gohr return $this->columns; 3421c502704SAndreas Gohr } 3431c502704SAndreas Gohr 344ae697e1fSAndreas Gohr /** 3455742aea9SAndreas Gohr * Find a column in the schema by its label 3465742aea9SAndreas Gohr * 3475742aea9SAndreas Gohr * Only enabled columns are returned! 3485742aea9SAndreas Gohr * 3495742aea9SAndreas Gohr * @param $name 3505742aea9SAndreas Gohr * @return bool|Column 3515742aea9SAndreas Gohr */ 352d6d97f60SAnna Dabrowska public function findColumn($name) 353d6d97f60SAnna Dabrowska { 3545742aea9SAndreas Gohr foreach ($this->columns as $col) { 3555742aea9SAndreas Gohr if ($col->isEnabled() && utf8_strtolower($col->getLabel()) == utf8_strtolower($name)) { 3565742aea9SAndreas Gohr return $col; 3575742aea9SAndreas Gohr } 3585742aea9SAndreas Gohr } 3595742aea9SAndreas Gohr return false; 3605742aea9SAndreas Gohr } 3615742aea9SAndreas Gohr 3625742aea9SAndreas Gohr /** 363ae697e1fSAndreas Gohr * @return string 364ae697e1fSAndreas Gohr */ 365d6d97f60SAnna Dabrowska public function getTable() 366d6d97f60SAnna Dabrowska { 367ae697e1fSAndreas Gohr return $this->table; 368ae697e1fSAndreas Gohr } 3691c502704SAndreas Gohr 370ae697e1fSAndreas Gohr /** 371ae697e1fSAndreas Gohr * @return int the highest sort number used in this schema 372ae697e1fSAndreas Gohr */ 373d6d97f60SAnna Dabrowska public function getMaxsort() 374d6d97f60SAnna Dabrowska { 375ae697e1fSAndreas Gohr return $this->maxsort; 376ae697e1fSAndreas Gohr } 3771c502704SAndreas Gohr 378d486d6d7SAndreas Gohr /** 379d486d6d7SAndreas Gohr * @return string the JSON representing this schema 380d486d6d7SAndreas Gohr */ 381d6d97f60SAnna Dabrowska public function toJSON() 382d6d97f60SAnna Dabrowska { 383d486d6d7SAndreas Gohr $data = array( 384d486d6d7SAndreas Gohr 'structversion' => $this->structversion, 385d486d6d7SAndreas Gohr 'schema' => $this->getTable(), 386d486d6d7SAndreas Gohr 'id' => $this->getId(), 38745313413SMichael Grosse 'user' => $this->getUser(), 388127d6bacSMichael Große 'config' => $this->getConfig(), 389d486d6d7SAndreas Gohr 'columns' => array() 390d486d6d7SAndreas Gohr ); 391d486d6d7SAndreas Gohr 392d486d6d7SAndreas Gohr foreach ($this->columns as $column) { 393d486d6d7SAndreas Gohr $data['columns'][] = array( 394d486d6d7SAndreas Gohr 'colref' => $column->getColref(), 395d486d6d7SAndreas Gohr 'ismulti' => $column->isMulti(), 396d486d6d7SAndreas Gohr 'isenabled' => $column->isEnabled(), 397d486d6d7SAndreas Gohr 'sort' => $column->getSort(), 398d486d6d7SAndreas Gohr 'label' => $column->getLabel(), 399d486d6d7SAndreas Gohr 'class' => $column->getType()->getClass(), 400d486d6d7SAndreas Gohr 'config' => $column->getType()->getConfig(), 401d486d6d7SAndreas Gohr ); 402d486d6d7SAndreas Gohr } 403d486d6d7SAndreas Gohr 404d486d6d7SAndreas Gohr return json_encode($data, JSON_PRETTY_PRINT); 405d486d6d7SAndreas Gohr } 406083afc55SAndreas Gohr} 407