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 27083afc55SAndreas Gohr /** @var string name of the associated table */ 28083afc55SAndreas Gohr protected $table = ''; 29083afc55SAndreas Gohr 3088b7d2aaSAndreas Gohr /** @var bool is this a lookup schema? */ 3188b7d2aaSAndreas Gohr protected $islookup = false; 3288b7d2aaSAndreas 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 50083afc55SAndreas Gohr /** 51083afc55SAndreas Gohr * Schema constructor 527182938bSAndreas Gohr * 53083afc55SAndreas Gohr * @param string $table The table this schema is for 54083afc55SAndreas Gohr * @param int $ts The timestamp for when this schema was valid, 0 for current 5588b7d2aaSAndreas Gohr * @param bool $islookup only used when creating a new schema, makes the new schema a lookup 56083afc55SAndreas Gohr */ 5788b7d2aaSAndreas Gohr public function __construct($table, $ts = 0, $islookup = false) { 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 if(!$this->sqlite) return; 64083afc55SAndreas Gohr 65083afc55SAndreas Gohr $table = self::cleanTableName($table); 66083afc55SAndreas Gohr $this->table = $table; 67250c83c2SAndreas Gohr $this->ts = $ts; 68083afc55SAndreas Gohr 69083afc55SAndreas Gohr // load info about the schema itself 70083afc55SAndreas Gohr if($ts) { 71083afc55SAndreas Gohr $sql = "SELECT * 72083afc55SAndreas Gohr FROM schemas 73083afc55SAndreas Gohr WHERE tbl = ? 74083afc55SAndreas Gohr AND ts <= ? 75083afc55SAndreas Gohr ORDER BY ts DESC 76083afc55SAndreas Gohr LIMIT 1"; 77083afc55SAndreas Gohr $opt = array($table, $ts); 78083afc55SAndreas Gohr } else { 79083afc55SAndreas Gohr $sql = "SELECT * 80083afc55SAndreas Gohr FROM schemas 81083afc55SAndreas Gohr WHERE tbl = ? 82083afc55SAndreas Gohr ORDER BY ts DESC 83083afc55SAndreas Gohr LIMIT 1"; 84083afc55SAndreas Gohr $opt = array($table); 85083afc55SAndreas Gohr } 86083afc55SAndreas Gohr $res = $this->sqlite->query($sql, $opt); 87083afc55SAndreas Gohr if($this->sqlite->res2count($res)) { 884e2abec0SMichael Große $schema = $this->sqlite->res2arr($res); 894e2abec0SMichael Große $result = array_shift($schema); 90083afc55SAndreas Gohr $this->id = $result['id']; 91083afc55SAndreas Gohr $this->chksum = $result['chksum']; 9288b7d2aaSAndreas Gohr $this->islookup = $result['islookup']; 9388b7d2aaSAndreas Gohr } else { 9488b7d2aaSAndreas Gohr $this->islookup = $islookup; 95083afc55SAndreas Gohr } 96083afc55SAndreas Gohr $this->sqlite->res_close($res); 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 110083afc55SAndreas Gohr foreach($rows as $row) { 111ba766201SAndreas Gohr $class = 'dokuwiki\\plugin\\struct\\types\\' . $row['class']; 11298eaa57dSAndreas Gohr if(!class_exists($class)) { 11398eaa57dSAndreas Gohr // This usually never happens, except during development 11498eaa57dSAndreas Gohr msg('Unknown type "' . hsc($row['class']) . '" falling back to Text', -1); 115ba766201SAndreas Gohr $class = 'dokuwiki\\plugin\\struct\\types\\Text'; 11698eaa57dSAndreas Gohr } 11798eaa57dSAndreas Gohr 118083afc55SAndreas Gohr $config = json_decode($row['config'], true); 119bbf3d6aaSAndreas Gohr /** @var AbstractBaseType $type */ 120bbf3d6aaSAndreas Gohr $type = new $class($config, $row['label'], $row['ismulti'], $row['tid']); 121bbf3d6aaSAndreas Gohr $column = new Column( 1221c502704SAndreas Gohr $row['sort'], 123bbf3d6aaSAndreas Gohr $type, 1241c502704SAndreas Gohr $row['colref'], 12563d51bbfSAndreas Gohr $row['enabled'], 12663d51bbfSAndreas Gohr $table 1271c502704SAndreas Gohr ); 128bbf3d6aaSAndreas Gohr $type->setContext($column); 1291c502704SAndreas Gohr 1307629557eSAndreas Gohr $this->columns[] = $column; 131083afc55SAndreas Gohr if($row['sort'] > $this->maxsort) $this->maxsort = $row['sort']; 132083afc55SAndreas Gohr } 133083afc55SAndreas Gohr } 134083afc55SAndreas Gohr 135083afc55SAndreas Gohr /** 136083afc55SAndreas Gohr * Cleans any unwanted stuff from table names 137083afc55SAndreas Gohr * 138083afc55SAndreas Gohr * @param string $table 139083afc55SAndreas Gohr * @return string 140083afc55SAndreas Gohr */ 141083afc55SAndreas Gohr static public function cleanTableName($table) { 1422af472dcSAndreas Gohr $table = strtolower($table); 143083afc55SAndreas Gohr $table = preg_replace('/[^a-z0-9_]+/', '', $table); 144083afc55SAndreas Gohr $table = preg_replace('/^[0-9_]+/', '', $table); 145083afc55SAndreas Gohr $table = trim($table); 146083afc55SAndreas Gohr return $table; 147083afc55SAndreas Gohr } 148083afc55SAndreas Gohr 149083afc55SAndreas Gohr /** 150097f4a53SAndreas Gohr * Gets a list of all available schemas 151097f4a53SAndreas Gohr * 1527c080d69SAndreas Gohr * @param string $filter either 'page' or 'lookup' 1537c080d69SAndreas Gohr * @return \string[] 154097f4a53SAndreas Gohr */ 1557c080d69SAndreas Gohr static public function getAll($filter = '') { 156097f4a53SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 157097f4a53SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 158097f4a53SAndreas Gohr $db = $helper->getDB(); 159097f4a53SAndreas Gohr if(!$db) return array(); 160097f4a53SAndreas Gohr 1617c080d69SAndreas Gohr if($filter == 'page') { 1627c080d69SAndreas Gohr $where = 'islookup = 0'; 1637c080d69SAndreas Gohr } elseif($filter == 'lookup') { 1647c080d69SAndreas Gohr $where = 'islookup = 1'; 1657c080d69SAndreas Gohr } else { 1667c080d69SAndreas Gohr $where = '1 = 1'; 1677c080d69SAndreas Gohr } 1687c080d69SAndreas Gohr 1697c080d69SAndreas Gohr $res = $db->query("SELECT DISTINCT tbl FROM schemas WHERE $where ORDER BY tbl"); 170097f4a53SAndreas Gohr $tables = $db->res2arr($res); 171097f4a53SAndreas Gohr $db->res_close($res); 172097f4a53SAndreas Gohr 173097f4a53SAndreas Gohr $result = array(); 174097f4a53SAndreas Gohr foreach($tables as $row) { 175097f4a53SAndreas Gohr $result[] = $row['tbl']; 176097f4a53SAndreas Gohr } 177097f4a53SAndreas Gohr return $result; 178097f4a53SAndreas Gohr } 179097f4a53SAndreas Gohr 180097f4a53SAndreas Gohr /** 181d5a1a6dcSAndreas Gohr * Delete all data associated with this schema 182d5a1a6dcSAndreas Gohr * 183d5a1a6dcSAndreas Gohr * This is really all data ever! Be careful! 184d5a1a6dcSAndreas Gohr */ 185d5a1a6dcSAndreas Gohr public function delete() { 186d5a1a6dcSAndreas Gohr if(!$this->id) throw new StructException('can not delete unsaved schema'); 187d5a1a6dcSAndreas Gohr 188d5a1a6dcSAndreas Gohr $this->sqlite->query('BEGIN TRANSACTION'); 189d5a1a6dcSAndreas Gohr 190d5a1a6dcSAndreas Gohr $sql = "DROP TABLE ?"; 191d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'data_' . $this->table); 192d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, 'multi_' . $this->table); 193d5a1a6dcSAndreas Gohr 194d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments WHERE tbl = ?"; 195d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 196d5a1a6dcSAndreas Gohr 197d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_assignments_patterns WHERE tbl = ?"; 198d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 199d5a1a6dcSAndreas Gohr 200d5a1a6dcSAndreas Gohr $sql = "SELECT T.id 201d5a1a6dcSAndreas Gohr FROM types T, schema_cols SC, schemas S 202d5a1a6dcSAndreas Gohr WHERE T.id = SC.tid 203d5a1a6dcSAndreas Gohr AND SC.sid = S.id 204d5a1a6dcSAndreas Gohr AND S.tbl = ?"; 205d5a1a6dcSAndreas Gohr $sql = "DELETE FROM types WHERE id IN ($sql)"; 206d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 207d5a1a6dcSAndreas Gohr 208d5a1a6dcSAndreas Gohr $sql = "SELECT id 209d5a1a6dcSAndreas Gohr FROM schemas 210d5a1a6dcSAndreas Gohr WHERE tbl = ?"; 211d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schema_cols WHERE sid IN ($sql)"; 212d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 213d5a1a6dcSAndreas Gohr 214d5a1a6dcSAndreas Gohr $sql = "DELETE FROM schemas WHERE tbl = ?"; 215d5a1a6dcSAndreas Gohr $this->sqlite->query($sql, $this->table); 216d5a1a6dcSAndreas Gohr 217d5a1a6dcSAndreas Gohr $this->sqlite->query('COMMIT TRANSACTION'); 218d5a1a6dcSAndreas Gohr $this->sqlite->query('VACUUM'); 219f9f13d8cSAndreas Gohr 220f9f13d8cSAndreas Gohr // a deleted schema should not be used anymore, but let's make sure it's somewhat sane anyway 221f9f13d8cSAndreas Gohr $this->id = 0; 222f9f13d8cSAndreas Gohr $this->chksum = ''; 223f9f13d8cSAndreas Gohr $this->columns = array(); 224f9f13d8cSAndreas Gohr $this->maxsort = 0; 225f9f13d8cSAndreas Gohr $this->ts = 0; 226d5a1a6dcSAndreas Gohr } 227d5a1a6dcSAndreas Gohr 228d5a1a6dcSAndreas Gohr /** 2291c502704SAndreas Gohr * @return string 2301c502704SAndreas Gohr */ 2311c502704SAndreas Gohr public function getChksum() { 2321c502704SAndreas Gohr return $this->chksum; 2331c502704SAndreas Gohr } 2341c502704SAndreas Gohr 2351c502704SAndreas Gohr /** 2361c502704SAndreas Gohr * @return int 2371c502704SAndreas Gohr */ 2381c502704SAndreas Gohr public function getId() { 2391c502704SAndreas Gohr return $this->id; 2401c502704SAndreas Gohr } 2411c502704SAndreas Gohr 2421c502704SAndreas Gohr /** 243*f411d872SAndreas Gohr * @todo should this return the time stamp it was created at? 244*f411d872SAndreas Gohr * @return int returns the timestamp this Schema was initialized with 245*f411d872SAndreas Gohr */ 246*f411d872SAndreas Gohr public function getTimeStamp() { 247*f411d872SAndreas Gohr return $this->ts; 248*f411d872SAndreas Gohr } 249*f411d872SAndreas Gohr 250*f411d872SAndreas Gohr /** 25188b7d2aaSAndreas Gohr * @return bool is this a lookup schema? 25288b7d2aaSAndreas Gohr */ 25388b7d2aaSAndreas Gohr public function isLookup() { 25488b7d2aaSAndreas Gohr return $this->islookup; 25588b7d2aaSAndreas Gohr } 25688b7d2aaSAndreas Gohr 25788b7d2aaSAndreas Gohr /** 258ce206ec7SAndreas Gohr * Returns a list of columns in this schema 259ce206ec7SAndreas Gohr * 260ce206ec7SAndreas Gohr * @param bool $withDisabled if false, disabled columns will not be returned 261ce206ec7SAndreas Gohr * @return Column[] 2621c502704SAndreas Gohr */ 263ce206ec7SAndreas Gohr public function getColumns($withDisabled = true) { 264ce206ec7SAndreas Gohr if(!$withDisabled) { 265ce206ec7SAndreas Gohr return array_filter( 266ce206ec7SAndreas Gohr $this->columns, 267ce206ec7SAndreas Gohr function (Column $col) { 268ce206ec7SAndreas Gohr return $col->isEnabled(); 269ce206ec7SAndreas Gohr } 270ce206ec7SAndreas Gohr ); 271ce206ec7SAndreas Gohr } 272ce206ec7SAndreas Gohr 2731c502704SAndreas Gohr return $this->columns; 2741c502704SAndreas Gohr } 2751c502704SAndreas Gohr 276ae697e1fSAndreas Gohr /** 2775742aea9SAndreas Gohr * Find a column in the schema by its label 2785742aea9SAndreas Gohr * 2795742aea9SAndreas Gohr * Only enabled columns are returned! 2805742aea9SAndreas Gohr * 2815742aea9SAndreas Gohr * @param $name 2825742aea9SAndreas Gohr * @return bool|Column 2835742aea9SAndreas Gohr */ 2845742aea9SAndreas Gohr public function findColumn($name) { 2855742aea9SAndreas Gohr foreach($this->columns as $col) { 2865742aea9SAndreas Gohr if($col->isEnabled() && utf8_strtolower($col->getLabel()) == utf8_strtolower($name)) { 2875742aea9SAndreas Gohr return $col; 2885742aea9SAndreas Gohr } 2895742aea9SAndreas Gohr } 2905742aea9SAndreas Gohr return false; 2915742aea9SAndreas Gohr } 2925742aea9SAndreas Gohr 2935742aea9SAndreas Gohr /** 294ae697e1fSAndreas Gohr * @return string 295ae697e1fSAndreas Gohr */ 296ae697e1fSAndreas Gohr public function getTable() { 297ae697e1fSAndreas Gohr return $this->table; 298ae697e1fSAndreas Gohr } 2991c502704SAndreas Gohr 300ae697e1fSAndreas Gohr /** 301ae697e1fSAndreas Gohr * @return int the highest sort number used in this schema 302ae697e1fSAndreas Gohr */ 303ae697e1fSAndreas Gohr public function getMaxsort() { 304ae697e1fSAndreas Gohr return $this->maxsort; 305ae697e1fSAndreas Gohr } 3061c502704SAndreas Gohr 307d486d6d7SAndreas Gohr /** 308d486d6d7SAndreas Gohr * @return string the JSON representing this schema 309d486d6d7SAndreas Gohr */ 310d486d6d7SAndreas Gohr public function toJSON() { 311d486d6d7SAndreas Gohr $data = array( 312d486d6d7SAndreas Gohr 'structversion' => $this->structversion, 313d486d6d7SAndreas Gohr 'schema' => $this->getTable(), 314d486d6d7SAndreas Gohr 'id' => $this->getId(), 315d486d6d7SAndreas Gohr 'columns' => array() 316d486d6d7SAndreas Gohr ); 317d486d6d7SAndreas Gohr 318d486d6d7SAndreas Gohr foreach($this->columns as $column) { 319d486d6d7SAndreas Gohr $data['columns'][] = array( 320d486d6d7SAndreas Gohr 'colref' => $column->getColref(), 321d486d6d7SAndreas Gohr 'ismulti' => $column->isMulti(), 322d486d6d7SAndreas Gohr 'isenabled' => $column->isEnabled(), 323d486d6d7SAndreas Gohr 'sort' => $column->getSort(), 324d486d6d7SAndreas Gohr 'label' => $column->getLabel(), 325d486d6d7SAndreas Gohr 'class' => $column->getType()->getClass(), 326d486d6d7SAndreas Gohr 'config' => $column->getType()->getConfig(), 327d486d6d7SAndreas Gohr ); 328d486d6d7SAndreas Gohr } 329d486d6d7SAndreas Gohr 330d486d6d7SAndreas Gohr return json_encode($data, JSON_PRETTY_PRINT); 331d486d6d7SAndreas Gohr } 332083afc55SAndreas Gohr} 333