1<?php 2 3namespace plugin\struct\meta; 4 5use dokuwiki\Form\Form; 6use plugin\struct\types\AbstractBaseType; 7use plugin\struct\types\Text; 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 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 name of the associated table */ 28 protected $table = ''; 29 30 /** 31 * @var string the current checksum of this schema 32 */ 33 protected $chksum = ''; 34 35 /** @var Column[] all the colums */ 36 protected $columns = array(); 37 38 /** @var int */ 39 protected $maxsort = 0; 40 41 /** 42 * Schema constructor 43 * 44 * @param string $table The table this schema is for 45 * @param int $ts The timestamp for when this schema was valid, 0 for current 46 */ 47 public function __construct($table, $ts = 0) { 48 /** @var \helper_plugin_struct_db $helper */ 49 $helper = plugin_load('helper', 'struct_db'); 50 $this->sqlite = $helper->getDB(); 51 if(!$this->sqlite) return; 52 53 $table = self::cleanTableName($table); 54 $this->table = $table; 55 56 // load info about the schema itself 57 if($ts) { 58 $sql = "SELECT * 59 FROM schemas 60 WHERE tbl = ? 61 AND ts <= ? 62 ORDER BY ts DESC 63 LIMIT 1"; 64 $opt = array($table, $ts); 65 66 } else { 67 $sql = "SELECT * 68 FROM schemas 69 WHERE tbl = ? 70 ORDER BY ts DESC 71 LIMIT 1"; 72 $opt = array($table); 73 } 74 $res = $this->sqlite->query($sql, $opt); 75 if($this->sqlite->res2count($res)) { 76 $result = array_shift($this->sqlite->res2arr($res)); 77 $this->id = $result['id']; 78 $this->chksum = $result['chksum']; 79 80 } 81 $this->sqlite->res_close($res); 82 if(!$this->id) return; 83 84 // load existing columns 85 $sql = "SELECT SC.*, T.* 86 FROM schema_cols SC, 87 types T 88 WHERE SC.sid = ? 89 AND SC.tid = T.id 90 ORDER BY SC.sort"; 91 $res = $this->sqlite->query($sql, $this->id); 92 $rows = $this->sqlite->res2arr($res); 93 $this->sqlite->res_close($res); 94 95 foreach($rows as $row) { 96 $class = 'plugin\\struct\\types\\' . $row['class']; 97 $config = json_decode($row['config'], true); 98 $this->columns[$row['colref']] = 99 new Column( 100 $row['sort'], 101 new $class($config, $row['label'], $row['ismulti']), 102 $row['tid'], 103 $row['colref'], 104 $row['enabled'] 105 ); 106 107 if($row['sort'] > $this->maxsort) $this->maxsort = $row['sort']; 108 } 109 } 110 111 /** 112 * Cleans any unwanted stuff from table names 113 * 114 * @param string $table 115 * @return string 116 */ 117 static public function cleanTableName($table) { 118 $table = preg_replace('/[^a-z0-9_]+/', '', $table); 119 $table = preg_replace('/^[0-9_]+/', '', $table); 120 $table = trim($table); 121 return $table; 122 } 123 124 /** 125 * Returns the Admin Form to edit the schema 126 * 127 * This data is processed by the SchemaBuilder class 128 * 129 * @return string 130 * @see SchemaBuilder 131 * @todo it could be discussed if this editor should be part of the schema class it self or if that should be in a SchemaEditor class 132 */ 133 public function adminEditor() { 134 $form = new Form(array('method' => 'POST')); 135 $form->setHiddenField('do', 'admin'); 136 $form->setHiddenField('page', 'struct'); 137 $form->setHiddenField('table', $this->table); 138 $form->setHiddenField('schema[id]', $this->id); 139 140 $form->addHTML('<table class="inline">'); 141 $form->addHTML('<tr><th>Sort</th><th>Label</th><th>Multi-Input?</th><th>Configuration</th><th>Type</th></tr>'); // FIXME localize 142 143 foreach($this->columns as $key => $obj) { 144 $form->addHTML($this->adminColumn($key, $obj)); 145 } 146 147 // FIXME new one needs to be added dynamically, this is just for testing 148 $form->addHTML($this->adminColumn('new1', new Column($this->maxsort+10, new Text()), 'new')); 149 150 $form->addHTML('</table>'); 151 $form->addButton('save', 'Save')->attr('type','submit'); 152 return $form->toHTML(); 153 } 154 155 /** 156 * Returns the HTML to edit a single column definition of the schema 157 * 158 * @param string $column_id 159 * @param Column $col 160 * @param string $key The key to use in the form 161 * @return string 162 * @todo this should probably be reused for adding new columns via AJAX later? 163 * @todo as above this might be better fitted to a SchemaEditor class 164 */ 165 protected function adminColumn($column_id, Column $col, $key='cols') { 166 $base = 'schema['.$key.'][' . $column_id . ']'; // base name for all fields 167 168 $html = '<tr>'; 169 170 $html .= '<td>'; 171 $html .= '<input type="text" name="' . $base . '[sort]" value="' . hsc($col->getSort()) . '" size="3">'; 172 $html .= '</td>'; 173 174 $html .= '<td>'; 175 $html .= '<input type="text" name="' . $base . '[label]" value="' . hsc($col->getType()->getLabel()) . '">'; 176 $html .= '</td>'; 177 178 $html .= '<td>'; 179 $checked = $col->getType()->isMulti() ? 'checked="checked"' : ''; 180 $html .= '<input type="checkbox" name="' . $base . '[ismulti]" value="1" ' . $checked . '>'; 181 $html .= '</td>'; 182 183 $html .= '<td>'; 184 $config = json_encode($col->getType()->getConfig(), JSON_PRETTY_PRINT); 185 $html .= '<textarea name="' . $base . '[config]" cols="45" rows="10">' . hsc($config) . '</textarea>'; 186 $html .= '</td>'; 187 188 $types = \helper_plugin_struct_column::getTypes(); 189 $html .= '<td>'; 190 $html .= '<select name="' . $base . '[class]">'; 191 foreach($types as $type) { 192 $selected = ($col->getType()->getClass() == $type) ? 'selected="selected"' : ''; 193 $html .= '<option value="' . hsc($type) . '" ' . $selected . '>' . hsc($type) . '</option>'; 194 } 195 $html .= '</select>'; 196 $html .= '</td>'; 197 198 $html .= '</tr>'; 199 200 return $html; 201 } 202 203 /** 204 * @return string 205 */ 206 public function getChksum() { 207 return $this->chksum; 208 } 209 210 /** 211 * @return int 212 */ 213 public function getId() { 214 return $this->id; 215 } 216 217 /** 218 * @return \plugin\struct\meta\Column[] 219 */ 220 public function getColumns() { 221 return $this->columns; 222 } 223 224 225 226 227} 228