1<?php 2 3namespace dokuwiki\plugin\struct\meta; 4 5use dokuwiki\plugin\struct\types\AbstractBaseType; 6 7if(!defined('JSON_PRETTY_PRINT')) define('JSON_PRETTY_PRINT', 0); // PHP 5.3 compatibility 8 9/** 10 * Class Schema 11 * 12 * Represents the schema of a single data table and all its properties. It defines what can be stored in 13 * the represented data table and how those contents are formatted. 14 * 15 * It can be initialized with a timestamp to access the schema as it looked at that particular point in time. 16 * 17 * @package dokuwiki\plugin\struct\meta 18 */ 19class Schema { 20 21 /** @var \helper_plugin_sqlite|null */ 22 protected $sqlite; 23 24 /** @var int The ID of this schema */ 25 protected $id = 0; 26 27 /** @var string the user who last edited this schema */ 28 protected $user = ''; 29 30 /** @var string name of the associated table */ 31 protected $table = ''; 32 33 /** 34 * @var string the current checksum of this schema 35 */ 36 protected $chksum = ''; 37 38 /** @var Column[] all the colums */ 39 protected $columns = array(); 40 41 /** @var int */ 42 protected $maxsort = 0; 43 44 /** @var int */ 45 protected $ts = 0; 46 47 /** @var string struct version info */ 48 protected $structversion = '?'; 49 50 /** 51 * Schema constructor 52 * 53 * @param string $table The table this schema is for 54 * @param int $ts The timestamp for when this schema was valid, 0 for current 55 */ 56 public function __construct($table, $ts = 0) { 57 /** @var \helper_plugin_struct_db $helper */ 58 $helper = plugin_load('helper', 'struct_db'); 59 $info = $helper->getInfo(); 60 $this->structversion = $info['date']; 61 $this->sqlite = $helper->getDB(); 62 if(!$this->sqlite) return; 63 64 $table = self::cleanTableName($table); 65 $this->table = $table; 66 $this->ts = $ts; 67 68 // load info about the schema itself 69 if($ts) { 70 $sql = "SELECT * 71 FROM schemas 72 WHERE tbl = ? 73 AND ts <= ? 74 ORDER BY ts DESC 75 LIMIT 1"; 76 $opt = array($table, $ts); 77 } else { 78 $sql = "SELECT * 79 FROM schemas 80 WHERE tbl = ? 81 ORDER BY ts DESC 82 LIMIT 1"; 83 $opt = array($table); 84 } 85 $res = $this->sqlite->query($sql, $opt); 86 if($this->sqlite->res2count($res)) { 87 $schema = $this->sqlite->res2arr($res); 88 $result = array_shift($schema); 89 $this->id = $result['id']; 90 $this->user = $result['user']; 91 $this->chksum = $result['chksum']; 92 } 93 $this->sqlite->res_close($res); 94 if(!$this->id) return; 95 96 // load existing columns 97 $sql = "SELECT SC.*, T.* 98 FROM schema_cols SC, 99 types T 100 WHERE SC.sid = ? 101 AND SC.tid = T.id 102 ORDER BY SC.sort"; 103 $res = $this->sqlite->query($sql, $this->id); 104 $rows = $this->sqlite->res2arr($res); 105 $this->sqlite->res_close($res); 106 107 foreach($rows as $row) { 108 if($row['class'] == 'Integer') { 109 $row['class'] = 'Decimal'; 110 } 111 112 $class = 'dokuwiki\\plugin\\struct\\types\\' . $row['class']; 113 if(!class_exists($class)) { 114 // This usually never happens, except during development 115 msg('Unknown type "' . hsc($row['class']) . '" falling back to Text', -1); 116 $class = 'dokuwiki\\plugin\\struct\\types\\Text'; 117 } 118 119 $config = json_decode($row['config'], true); 120 /** @var AbstractBaseType $type */ 121 $type = new $class($config, $row['label'], $row['ismulti'], $row['tid']); 122 $column = new Column( 123 $row['sort'], 124 $type, 125 $row['colref'], 126 $row['enabled'], 127 $table 128 ); 129 $type->setContext($column); 130 131 $this->columns[] = $column; 132 if($row['sort'] > $this->maxsort) $this->maxsort = $row['sort']; 133 } 134 } 135 136 /** 137 * Cleans any unwanted stuff from table names 138 * 139 * @param string $table 140 * @return string 141 */ 142 static public function cleanTableName($table) { 143 $table = strtolower($table); 144 $table = preg_replace('/[^a-z0-9_]+/', '', $table); 145 $table = preg_replace('/^[0-9_]+/', '', $table); 146 $table = trim($table); 147 return $table; 148 } 149 150 /** 151 * Gets a list of all available schemas 152 * 153 * @return string[] 154 */ 155 static public function getAll() { 156 /** @var \helper_plugin_struct_db $helper */ 157 $helper = plugin_load('helper', 'struct_db'); 158 $db = $helper->getDB(); 159 if(!$db) return array(); 160 161 $res = $db->query("SELECT DISTINCT tbl FROM schemas ORDER BY tbl"); 162 $tables = $db->res2arr($res); 163 $db->res_close($res); 164 165 $result = array(); 166 foreach($tables as $row) { 167 $result[] = $row['tbl']; 168 } 169 return $result; 170 } 171 172 /** 173 * Delete all data associated with this schema 174 * 175 * This is really all data ever! Be careful! 176 */ 177 public function delete() { 178 if(!$this->id) throw new StructException('can not delete unsaved schema'); 179 180 $this->sqlite->query('BEGIN TRANSACTION'); 181 182 $sql = "DROP TABLE ?"; 183 $this->sqlite->query($sql, 'data_'.$this->table); 184 $this->sqlite->query($sql, 'multi_'.$this->table); 185 186 $sql = "DELETE FROM schema_assignments WHERE tbl = ?"; 187 $this->sqlite->query($sql, $this->table); 188 189 $sql = "DELETE FROM schema_assignments_patterns WHERE tbl = ?"; 190 $this->sqlite->query($sql, $this->table); 191 192 $sql = "SELECT T.id 193 FROM types T, schema_cols SC, schemas S 194 WHERE T.id = SC.tid 195 AND SC.sid = S.id 196 AND S.tbl = ?"; 197 $sql = "DELETE FROM types WHERE id IN ($sql)"; 198 $this->sqlite->query($sql, $this->table); 199 200 $sql = "SELECT id 201 FROM schemas 202 WHERE tbl = ?"; 203 $sql = "DELETE FROM schema_cols WHERE sid IN ($sql)"; 204 $this->sqlite->query($sql, $this->table); 205 206 $sql = "DELETE FROM schemas WHERE tbl = ?"; 207 $this->sqlite->query($sql, $this->table); 208 209 $this->sqlite->query('COMMIT TRANSACTION'); 210 $this->sqlite->query('VACUUM'); 211 212 // a deleted schema should not be used anymore, but let's make sure it's somewhat sane anyway 213 $this->id = 0; 214 $this->chksum = ''; 215 $this->columns = array(); 216 $this->maxsort = 0; 217 $this->ts = 0; 218 } 219 220 /** 221 * @return string 222 */ 223 public function getChksum() { 224 return $this->chksum; 225 } 226 227 /** 228 * @return int 229 */ 230 public function getId() { 231 return $this->id; 232 } 233 234 /** 235 * @return string 236 */ 237 public function getUser() { 238 return $this->user; 239 } 240 241 /** 242 * Returns a list of columns in this schema 243 * 244 * @param bool $withDisabled if false, disabled columns will not be returned 245 * @return Column[] 246 */ 247 public function getColumns($withDisabled = true) { 248 if(!$withDisabled) { 249 return array_filter( 250 $this->columns, 251 function (Column $col) { 252 return $col->isEnabled(); 253 } 254 ); 255 } 256 257 return $this->columns; 258 } 259 260 /** 261 * Find a column in the schema by its label 262 * 263 * Only enabled columns are returned! 264 * 265 * @param $name 266 * @return bool|Column 267 */ 268 public function findColumn($name) { 269 foreach($this->columns as $col) { 270 if($col->isEnabled() && utf8_strtolower($col->getLabel()) == utf8_strtolower($name)) { 271 return $col; 272 } 273 } 274 return false; 275 } 276 277 /** 278 * @return string 279 */ 280 public function getTable() { 281 return $this->table; 282 } 283 284 /** 285 * @return int the highest sort number used in this schema 286 */ 287 public function getMaxsort() { 288 return $this->maxsort; 289 } 290 291 /** 292 * @return string the JSON representing this schema 293 */ 294 public function toJSON() { 295 $data = array( 296 'structversion' => $this->structversion, 297 'schema' => $this->getTable(), 298 'id' => $this->getId(), 299 'user' => $this->getUser(), 300 'columns' => array() 301 ); 302 303 foreach($this->columns as $column) { 304 $data['columns'][] = array( 305 'colref' => $column->getColref(), 306 'ismulti' => $column->isMulti(), 307 'isenabled' => $column->isEnabled(), 308 'sort' => $column->getSort(), 309 'label' => $column->getLabel(), 310 'class' => $column->getType()->getClass(), 311 'config' => $column->getType()->getConfig(), 312 ); 313 } 314 315 return json_encode($data, JSON_PRETTY_PRINT); 316 } 317} 318