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 331c502704SAndreas Gohr /** @var Column[] all the colums */ 34083afc55SAndreas Gohr protected $columns = array(); 35083afc55SAndreas Gohr 36083afc55SAndreas Gohr /** @var int */ 37083afc55SAndreas Gohr protected $maxsort = 0; 38083afc55SAndreas Gohr 39250c83c2SAndreas Gohr /** @var int */ 40250c83c2SAndreas Gohr protected $ts = 0; 41250c83c2SAndreas Gohr 42d486d6d7SAndreas Gohr /** @var string struct version info */ 43d486d6d7SAndreas Gohr protected $structversion = '?'; 44d486d6d7SAndreas Gohr 45127d6bacSMichael Große /** @var array config array with label translations */ 46127d6bacSMichael Große protected $config = array(); 47e2c90eebSAndreas Gohr 48083afc55SAndreas Gohr /** 49083afc55SAndreas Gohr * Schema constructor 507182938bSAndreas Gohr * 51083afc55SAndreas Gohr * @param string $table The table this schema is for 52083afc55SAndreas Gohr * @param int $ts The timestamp for when this schema was valid, 0 for current 53083afc55SAndreas Gohr */ 54d6d97f60SAnna Dabrowska public function __construct($table, $ts = 0) 55d6d97f60SAnna Dabrowska { 56*6c9d1a10SAnna Dabrowska $baseconfig = array('allowed editors' => '', 'internal' => false); 57127d6bacSMichael Große 58083afc55SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 59083afc55SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 60d486d6d7SAndreas Gohr $info = $helper->getInfo(); 61d486d6d7SAndreas Gohr $this->structversion = $info['date']; 62083afc55SAndreas Gohr $this->sqlite = $helper->getDB(); 63083afc55SAndreas Gohr $table = self::cleanTableName($table); 64083afc55SAndreas Gohr $this->table = $table; 65250c83c2SAndreas Gohr $this->ts = $ts; 66083afc55SAndreas Gohr 67083afc55SAndreas Gohr // load info about the schema itself 68083afc55SAndreas Gohr if ($ts) { 69083afc55SAndreas Gohr $sql = "SELECT * 70083afc55SAndreas Gohr FROM schemas 71083afc55SAndreas Gohr WHERE tbl = ? 72083afc55SAndreas Gohr AND ts <= ? 73083afc55SAndreas Gohr ORDER BY ts DESC 74083afc55SAndreas Gohr LIMIT 1"; 75083afc55SAndreas Gohr $opt = array($table, $ts); 76083afc55SAndreas Gohr } else { 77083afc55SAndreas Gohr $sql = "SELECT * 78083afc55SAndreas Gohr FROM schemas 79083afc55SAndreas Gohr WHERE tbl = ? 80083afc55SAndreas Gohr ORDER BY ts DESC 81083afc55SAndreas Gohr LIMIT 1"; 82083afc55SAndreas Gohr $opt = array($table); 83083afc55SAndreas Gohr } 84083afc55SAndreas Gohr $res = $this->sqlite->query($sql, $opt); 85127d6bacSMichael Große $config = array(); 86083afc55SAndreas Gohr if ($this->sqlite->res2count($res)) { 874e2abec0SMichael Große $schema = $this->sqlite->res2arr($res); 884e2abec0SMichael Große $result = array_shift($schema); 89083afc55SAndreas Gohr $this->id = $result['id']; 90fa7b96aaSMichael Grosse $this->user = $result['user']; 91587e314dSAndreas Gohr $this->ts = $result['ts']; 92127d6bacSMichael Große $config = json_decode($result['config'], true); 93083afc55SAndreas Gohr } 94083afc55SAndreas Gohr $this->sqlite->res_close($res); 95127d6bacSMichael Große $this->config = array_merge($baseconfig, $config); 96f800af69SMichael Große $this->initTransConfig(array('label')); 97083afc55SAndreas Gohr if (!$this->id) return; 98083afc55SAndreas Gohr 99083afc55SAndreas Gohr // load existing columns 100083afc55SAndreas Gohr $sql = "SELECT SC.*, T.* 101083afc55SAndreas Gohr FROM schema_cols SC, 102083afc55SAndreas Gohr types T 1031c502704SAndreas Gohr WHERE SC.sid = ? 1041c502704SAndreas Gohr AND SC.tid = T.id 105083afc55SAndreas Gohr ORDER BY SC.sort"; 1061c502704SAndreas Gohr $res = $this->sqlite->query($sql, $this->id); 107083afc55SAndreas Gohr $rows = $this->sqlite->res2arr($res); 108083afc55SAndreas Gohr $this->sqlite->res_close($res); 109083afc55SAndreas Gohr 110636c8abaSAndreas Gohr $typeclasses = Column::allTypes(); 111083afc55SAndreas Gohr foreach ($rows as $row) { 112328db41bSAndreas Gohr if ($row['class'] == 'Integer') { 113328db41bSAndreas Gohr $row['class'] = 'Decimal'; 114328db41bSAndreas Gohr } 115328db41bSAndreas Gohr 116636c8abaSAndreas Gohr $class = $typeclasses[$row['class']]; 11798eaa57dSAndreas Gohr if (!class_exists($class)) { 11898eaa57dSAndreas Gohr // This usually never happens, except during development 11998eaa57dSAndreas Gohr msg('Unknown type "' . hsc($row['class']) . '" falling back to Text', -1); 120ba766201SAndreas Gohr $class = 'dokuwiki\\plugin\\struct\\types\\Text'; 12198eaa57dSAndreas Gohr } 12298eaa57dSAndreas Gohr 123083afc55SAndreas Gohr $config = json_decode($row['config'], true); 124bbf3d6aaSAndreas Gohr /** @var AbstractBaseType $type */ 125bbf3d6aaSAndreas Gohr $type = new $class($config, $row['label'], $row['ismulti'], $row['tid']); 126bbf3d6aaSAndreas Gohr $column = new Column( 1271c502704SAndreas Gohr $row['sort'], 128bbf3d6aaSAndreas Gohr $type, 1291c502704SAndreas Gohr $row['colref'], 13063d51bbfSAndreas Gohr $row['enabled'], 13163d51bbfSAndreas Gohr $table 1321c502704SAndreas Gohr ); 133bbf3d6aaSAndreas Gohr $type->setContext($column); 1341c502704SAndreas Gohr 1357629557eSAndreas Gohr $this->columns[] = $column; 136083afc55SAndreas Gohr if ($row['sort'] > $this->maxsort) $this->maxsort = $row['sort']; 137083afc55SAndreas Gohr } 138083afc55SAndreas Gohr } 139083afc55SAndreas Gohr 140083afc55SAndreas Gohr /** 14167641668SAndreas Gohr * @return string identifer for debugging purposes 14267641668SAndreas Gohr */ 143748e747fSAnna Dabrowska public function __toString() 144d6d97f60SAnna Dabrowska { 1455b808f9fSAnna Dabrowska return __CLASS__ . ' ' . $this->table . ' (' . $this->id . ') '; 14667641668SAndreas Gohr } 14767641668SAndreas Gohr 14867641668SAndreas Gohr /** 149083afc55SAndreas Gohr * Cleans any unwanted stuff from table names 150083afc55SAndreas Gohr * 151083afc55SAndreas Gohr * @param string $table 152083afc55SAndreas Gohr * @return string 153083afc55SAndreas Gohr */ 154d6d97f60SAnna Dabrowska public static function cleanTableName($table) 155d6d97f60SAnna Dabrowska { 1562af472dcSAndreas Gohr $table = strtolower($table); 157083afc55SAndreas Gohr $table = preg_replace('/[^a-z0-9_]+/', '', $table); 158083afc55SAndreas Gohr $table = preg_replace('/^[0-9_]+/', '', $table); 159083afc55SAndreas Gohr $table = trim($table); 160083afc55SAndreas Gohr return $table; 161083afc55SAndreas Gohr } 162083afc55SAndreas Gohr 163127d6bacSMichael Große 164127d6bacSMichael Große /** 165097f4a53SAndreas Gohr * Gets a list of all available schemas 166097f4a53SAndreas Gohr * 1677c080d69SAndreas Gohr * @return \string[] 168097f4a53SAndreas Gohr */ 1695b808f9fSAnna Dabrowska public static function getAll() 170d6d97f60SAnna Dabrowska { 171097f4a53SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 172097f4a53SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 1737cbcfbdbSAndreas Gohr $db = $helper->getDB(false); 174097f4a53SAndreas Gohr if (!$db) return array(); 175097f4a53SAndreas Gohr 1765b808f9fSAnna Dabrowska $res = $db->query("SELECT DISTINCT tbl FROM schemas ORDER BY tbl"); 177097f4a53SAndreas Gohr $tables = $db->res2arr($res); 178097f4a53SAndreas Gohr $db->res_close($res); 179097f4a53SAndreas Gohr 180097f4a53SAndreas Gohr $result = array(); 181097f4a53SAndreas Gohr foreach ($tables as $row) { 182097f4a53SAndreas Gohr $result[] = $row['tbl']; 183097f4a53SAndreas Gohr } 184097f4a53SAndreas Gohr return $result; 185097f4a53SAndreas Gohr } 186097f4a53SAndreas Gohr 187097f4a53SAndreas Gohr /** 188d5a1a6dcSAndreas Gohr * Delete all data associated with this schema 189d5a1a6dcSAndreas Gohr * 190d5a1a6dcSAndreas Gohr * This is really all data ever! Be careful! 191d5a1a6dcSAndreas Gohr */ 192d6d97f60SAnna Dabrowska public function delete() 193d6d97f60SAnna Dabrowska { 194d5a1a6dcSAndreas Gohr if (!$this->id) throw new StructException('can not delete unsaved schema'); 195d5a1a6dcSAndreas Gohr 196d5a1a6dcSAndreas Gohr $this->sqlite->query('BEGIN TRANSACTION'); 197d5a1a6dcSAndreas Gohr 198d5a1a6dcSAndreas Gohr $sql = "DROP TABLE ?"; 199d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'data_' . $this->table); 200d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'multi_' . $this->table); 201d5a1a6dcSAndreas Gohr 202d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments WHERE tbl = ?"; 203d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 204d5a1a6dcSAndreas Gohr 205d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments_patterns WHERE tbl = ?"; 206d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 207d5a1a6dcSAndreas Gohr 208d5a1a6dcSAndreas Gohr $sql = "SELECT T.id 209d5a1a6dcSAndreas Gohr FROM types T, schema_cols SC, schemas S 210d5a1a6dcSAndreas Gohr WHERE T.id = SC.tid 211d5a1a6dcSAndreas Gohr AND SC.sid = S.id 212d5a1a6dcSAndreas Gohr AND S.tbl = ?"; 213d5a1a6dcSAndreas Gohr $sql = "DELETE FROM types WHERE id IN ($sql)"; 214d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 215d5a1a6dcSAndreas Gohr 216d5a1a6dcSAndreas Gohr $sql = "SELECT id 217d5a1a6dcSAndreas Gohr FROM schemas 218d5a1a6dcSAndreas Gohr WHERE tbl = ?"; 219d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_cols WHERE sid IN ($sql)"; 220d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 221d5a1a6dcSAndreas Gohr 222d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schemas WHERE tbl = ?"; 223d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 224d5a1a6dcSAndreas Gohr 225d5a1a6dcSAndreas Gohr $this->sqlite->query('COMMIT TRANSACTION'); 226d5a1a6dcSAndreas Gohr $this->sqlite->query('VACUUM'); 227f9f13d8cSAndreas Gohr 228f9f13d8cSAndreas Gohr // a deleted schema should not be used anymore, but let's make sure it's somewhat sane anyway 229f9f13d8cSAndreas Gohr $this->id = 0; 230f9f13d8cSAndreas Gohr $this->columns = array(); 231f9f13d8cSAndreas Gohr $this->maxsort = 0; 232f9f13d8cSAndreas Gohr $this->ts = 0; 233d5a1a6dcSAndreas Gohr } 234d5a1a6dcSAndreas Gohr 23579c83e06SMichael Große 23679c83e06SMichael Große /** 23779c83e06SMichael Große * Clear all data of a schema, but retain the schema itself 23879c83e06SMichael Große */ 239d6d97f60SAnna Dabrowska public function clear() 240d6d97f60SAnna Dabrowska { 24179c83e06SMichael Große if (!$this->id) throw new StructException('can not clear data of unsaved schema'); 24279c83e06SMichael Große 24379c83e06SMichael Große $this->sqlite->query('BEGIN TRANSACTION'); 24479c83e06SMichael Große $sql = 'DELETE FROM ?'; 24579c83e06SMichael Große $this->sqlite->query($sql, 'data_' . $this->table); 24679c83e06SMichael Große $this->sqlite->query($sql, 'multi_' . $this->table); 24779c83e06SMichael Große $this->sqlite->query('COMMIT TRANSACTION'); 24879c83e06SMichael Große $this->sqlite->query('VACUUM'); 24979c83e06SMichael Große } 25079c83e06SMichael Große 251d5a1a6dcSAndreas Gohr /** 2521c502704SAndreas Gohr * @return int 2531c502704SAndreas Gohr */ 254d6d97f60SAnna Dabrowska public function getId() 255d6d97f60SAnna Dabrowska { 2561c502704SAndreas Gohr return $this->id; 2571c502704SAndreas Gohr } 2581c502704SAndreas Gohr 2591c502704SAndreas Gohr /** 260587e314dSAndreas Gohr * @return int returns the timestamp this Schema was created at 261f411d872SAndreas Gohr */ 262d6d97f60SAnna Dabrowska public function getTimeStamp() 263d6d97f60SAnna Dabrowska { 264f411d872SAndreas Gohr return $this->ts; 265f411d872SAndreas Gohr } 266f411d872SAndreas Gohr 267f411d872SAndreas Gohr /** 268fa7b96aaSMichael Grosse * @return string 269fa7b96aaSMichael Grosse */ 270d6d97f60SAnna Dabrowska public function getUser() 271d6d97f60SAnna Dabrowska { 272fa7b96aaSMichael Grosse return $this->user; 273fa7b96aaSMichael Grosse } 274fa7b96aaSMichael Grosse 275d6d97f60SAnna Dabrowska public function getConfig() 276d6d97f60SAnna Dabrowska { 277127d6bacSMichael Große return $this->config; 278e2c90eebSAndreas Gohr } 279e2c90eebSAndreas Gohr 280fa7b96aaSMichael Grosse /** 281f800af69SMichael Große * Returns the translated label for this schema 282f800af69SMichael Große * 283f800af69SMichael Große * Uses the current language as determined by $conf['lang']. Falls back to english 284f800af69SMichael Große * and then to the Schema label 285f800af69SMichael Große * 286f800af69SMichael Große * @return string 287f800af69SMichael Große */ 288d6d97f60SAnna Dabrowska public function getTranslatedLabel() 289d6d97f60SAnna Dabrowska { 290f800af69SMichael Große return $this->getTranslatedKey('label', $this->table); 291f800af69SMichael Große } 292f800af69SMichael Große 293f800af69SMichael Große /** 2946ebbbb8eSAndreas Gohr * Checks if the current user may edit data in this schema 2956ebbbb8eSAndreas Gohr * 2966ebbbb8eSAndreas Gohr * @return bool 2976ebbbb8eSAndreas Gohr */ 298d6d97f60SAnna Dabrowska public function isEditable() 299d6d97f60SAnna Dabrowska { 3006ebbbb8eSAndreas Gohr global $USERINFO; 301ecf2cba2SAndreas Gohr global $INPUT; 302127d6bacSMichael Große if ($this->config['allowed editors'] === '') return true; 303ecf2cba2SAndreas Gohr if ($INPUT->server->str('REMOTE_USER') === '') return false; 3046ebbbb8eSAndreas Gohr if (auth_isadmin()) return true; 305ecf2cba2SAndreas Gohr return auth_isMember($this->config['allowed editors'], $INPUT->server->str('REMOTE_USER'), $USERINFO['grps']); 3066ebbbb8eSAndreas Gohr } 3076ebbbb8eSAndreas Gohr 3086ebbbb8eSAndreas Gohr /** 309*6c9d1a10SAnna Dabrowska * 310*6c9d1a10SAnna Dabrowska * @return bool 311*6c9d1a10SAnna Dabrowska */ 312*6c9d1a10SAnna Dabrowska public function isInternal() 313*6c9d1a10SAnna Dabrowska { 314*6c9d1a10SAnna Dabrowska return (bool) $this->config['internal']; 315*6c9d1a10SAnna Dabrowska } 316*6c9d1a10SAnna Dabrowska 317*6c9d1a10SAnna Dabrowska /** 318ce206ec7SAndreas Gohr * Returns a list of columns in this schema 319ce206ec7SAndreas Gohr * 320ce206ec7SAndreas Gohr * @param bool $withDisabled if false, disabled columns will not be returned 321ce206ec7SAndreas Gohr * @return Column[] 3221c502704SAndreas Gohr */ 323d6d97f60SAnna Dabrowska public function getColumns($withDisabled = true) 324d6d97f60SAnna Dabrowska { 325ce206ec7SAndreas Gohr if (!$withDisabled) { 326ce206ec7SAndreas Gohr return array_filter( 327ce206ec7SAndreas Gohr $this->columns, 328ce206ec7SAndreas Gohr function (Column $col) { 329ce206ec7SAndreas Gohr return $col->isEnabled(); 330ce206ec7SAndreas Gohr } 331ce206ec7SAndreas Gohr ); 332ce206ec7SAndreas Gohr } 333ce206ec7SAndreas Gohr 3341c502704SAndreas Gohr return $this->columns; 3351c502704SAndreas Gohr } 3361c502704SAndreas Gohr 337ae697e1fSAndreas Gohr /** 3385742aea9SAndreas Gohr * Find a column in the schema by its label 3395742aea9SAndreas Gohr * 3405742aea9SAndreas Gohr * Only enabled columns are returned! 3415742aea9SAndreas Gohr * 3425742aea9SAndreas Gohr * @param $name 3435742aea9SAndreas Gohr * @return bool|Column 3445742aea9SAndreas Gohr */ 345d6d97f60SAnna Dabrowska public function findColumn($name) 346d6d97f60SAnna Dabrowska { 3475742aea9SAndreas Gohr foreach ($this->columns as $col) { 3485742aea9SAndreas Gohr if ($col->isEnabled() && utf8_strtolower($col->getLabel()) == utf8_strtolower($name)) { 3495742aea9SAndreas Gohr return $col; 3505742aea9SAndreas Gohr } 3515742aea9SAndreas Gohr } 3525742aea9SAndreas Gohr return false; 3535742aea9SAndreas Gohr } 3545742aea9SAndreas Gohr 3555742aea9SAndreas Gohr /** 356ae697e1fSAndreas Gohr * @return string 357ae697e1fSAndreas Gohr */ 358d6d97f60SAnna Dabrowska public function getTable() 359d6d97f60SAnna Dabrowska { 360ae697e1fSAndreas Gohr return $this->table; 361ae697e1fSAndreas Gohr } 3621c502704SAndreas Gohr 363ae697e1fSAndreas Gohr /** 364ae697e1fSAndreas Gohr * @return int the highest sort number used in this schema 365ae697e1fSAndreas Gohr */ 366d6d97f60SAnna Dabrowska public function getMaxsort() 367d6d97f60SAnna Dabrowska { 368ae697e1fSAndreas Gohr return $this->maxsort; 369ae697e1fSAndreas Gohr } 3701c502704SAndreas Gohr 371d486d6d7SAndreas Gohr /** 372d486d6d7SAndreas Gohr * @return string the JSON representing this schema 373d486d6d7SAndreas Gohr */ 374d6d97f60SAnna Dabrowska public function toJSON() 375d6d97f60SAnna Dabrowska { 376d486d6d7SAndreas Gohr $data = array( 377d486d6d7SAndreas Gohr 'structversion' => $this->structversion, 378d486d6d7SAndreas Gohr 'schema' => $this->getTable(), 379d486d6d7SAndreas Gohr 'id' => $this->getId(), 38045313413SMichael Grosse 'user' => $this->getUser(), 381127d6bacSMichael Große 'config' => $this->getConfig(), 382d486d6d7SAndreas Gohr 'columns' => array() 383d486d6d7SAndreas Gohr ); 384d486d6d7SAndreas Gohr 385d486d6d7SAndreas Gohr foreach ($this->columns as $column) { 386d486d6d7SAndreas Gohr $data['columns'][] = array( 387d486d6d7SAndreas Gohr 'colref' => $column->getColref(), 388d486d6d7SAndreas Gohr 'ismulti' => $column->isMulti(), 389d486d6d7SAndreas Gohr 'isenabled' => $column->isEnabled(), 390d486d6d7SAndreas Gohr 'sort' => $column->getSort(), 391d486d6d7SAndreas Gohr 'label' => $column->getLabel(), 392d486d6d7SAndreas Gohr 'class' => $column->getType()->getClass(), 393d486d6d7SAndreas Gohr 'config' => $column->getType()->getConfig(), 394d486d6d7SAndreas Gohr ); 395d486d6d7SAndreas Gohr } 396d486d6d7SAndreas Gohr 397d486d6d7SAndreas Gohr return json_encode($data, JSON_PRETTY_PRINT); 398d486d6d7SAndreas Gohr } 399083afc55SAndreas Gohr} 400