1<?php 2 3namespace dokuwiki\plugin\struct\meta; 4 5abstract class AccessTable { 6 7 /** @var Schema */ 8 protected $schema; 9 protected $pid; 10 protected $labels = array(); 11 protected $ts = 0; 12 /** @var \helper_plugin_sqlite */ 13 protected $sqlite; 14 15 // options on how to retieve data 16 protected $opt_skipempty = false; 17 protected $opt_rawvalue = false; 18 19 /** 20 * Factory Method to access a data or lookup table 21 * 22 * @param Schema $schema schema to load 23 * @param string|int $pid Page or row id to access 24 * @param int $ts Time at which the data should be read or written, 0 for now 25 * @return AccessTableData|AccessTableLookup 26 */ 27 public static function bySchema(Schema $schema, $pid, $ts = 0) { 28 if($schema->isLookup()) { 29 return new AccessTableLookup($schema, $pid, $ts); 30 } else { 31 return new AccessTableData($schema, $pid, $ts); 32 } 33 } 34 35 /** 36 * Factory Method to access a data or lookup table 37 * 38 * @param string $tablename schema to load 39 * @param string|int $pid Page or row id to access 40 * @param int $ts Time at which the data should be read or written, 0 for now 41 * @return AccessTableData|AccessTableLookup 42 */ 43 public static function byTableName($tablename, $pid, $ts = 0) { 44 $schema = new Schema($tablename, $ts); 45 return self::bySchema($schema, $pid, $ts); 46 } 47 48 /** 49 * AccessTable constructor 50 * 51 * @param Schema $schema The schema valid at $ts 52 * @param string|int $pid 53 * @param int $ts Time at which the data should be read or written, 0 for now 54 */ 55 public function __construct(Schema $schema, $pid, $ts = 0) { 56 /** @var \helper_plugin_struct_db $helper */ 57 $helper = plugin_load('helper', 'struct_db'); 58 $this->sqlite = $helper->getDB(); 59 if(!$this->sqlite) { 60 throw new StructException('Sqlite plugin required'); 61 } 62 63 if(!$schema->getId()) { 64 throw new StructException('Schema does not exist. Only data of existing schemas can be accessed'); 65 } 66 67 $this->schema = $schema; 68 $this->pid = $pid; 69 $this->setTimestamp($ts); 70 foreach($this->schema->getColumns() as $col) { 71 $this->labels[$col->getColref()] = $col->getType()->getLabel(); 72 } 73 } 74 75 /** 76 * gives access to the schema 77 * 78 * @return Schema 79 */ 80 public function getSchema() { 81 return $this->schema; 82 } 83 84 /** 85 * Should remove the current data, by either deleting or ovewriting it 86 * 87 * @return bool if the delete succeeded 88 */ 89 abstract public function clearData(); 90 91 /** 92 * Save the data to the database. 93 * 94 * We differentiate between single-value-column and multi-value-column by the value to the respective column-name, 95 * i.e. depending on if that is a string or an array, respectively. 96 * 97 * @param array $data typelabel => value for single fields or typelabel => array(value, value, ...) for multi fields 98 * @return bool success of saving the data to the database 99 */ 100 abstract public function saveData($data); 101 102 /** 103 * Should empty or invisible (inpage) fields be returned? 104 * 105 * Defaults to false 106 * 107 * @param null|bool $set new value, null to read only 108 * @return bool current value (after set) 109 */ 110 public function optionSkipEmpty($set = null) { 111 if(!is_null($set)) { 112 $this->opt_skipempty = $set; 113 } 114 return $this->opt_skipempty; 115 } 116 117 /** 118 * Should the values be returned raw or are complex returns okay? 119 * 120 * Defaults to false = complex values okay 121 * 122 * @param null|bool $set new value, null to read only 123 * @return bool current value (after set) 124 */ 125 public function optionRawValue($set = null) { 126 if(!is_null($set)) { 127 $this->opt_rawvalue = $set; 128 } 129 return $this->opt_rawvalue; 130 } 131 132 133 /** 134 * Get the value of a single column 135 * 136 * @param Column $column 137 * @return Value|null 138 */ 139 public function getDataColumn($column) { 140 $data = $this->getData(); 141 foreach($data as $value) { 142 if($value->getColumn() == $column) { 143 return $value; 144 } 145 } 146 return null; 147 } 148 149 /** 150 * returns the data saved for the page 151 * 152 * @return Value[] a list of values saved for the current page 153 */ 154 public function getData() { 155 $this->getLastRevisionTimestamp($this->pid, $this->ts); 156 $data = $this->getDataFromDB(); 157 $data = $this->consolidateData($data, false); 158 return $data; 159 } 160 161 /** 162 * returns the data saved for the page as associative array 163 * 164 * The array returned is in the same format as used in @see saveData() 165 * 166 * @return array 167 */ 168 public function getDataArray() { 169 $this->getLastRevisionTimestamp($this->pid, $this->ts); 170 $data = $this->getDataFromDB(); 171 $data = $this->consolidateData($data, true); 172 return $data; 173 } 174 175 /** 176 * Return the data in pseudo syntax 177 */ 178 public function getDataPseudoSyntax() { 179 $result = ''; 180 $data = $this->getDataArray(); 181 foreach($data as $key => $value) { 182 $key = $this->schema->getTable() . ".$key"; 183 if(is_array($value)) $value = join(', ', $value); 184 $result .= sprintf("% -20s : %s\n", $key, $value); 185 } 186 return $result; 187 } 188 189 /** 190 * retrieve the data saved for the page from the database. Usually there is no need to call this function. 191 * Call @see SchemaData::getData instead. 192 */ 193 protected function getDataFromDB() { 194 list($sql, $opt) = $this->buildGetDataSQL(); 195 196 $res = $this->sqlite->query($sql, $opt); 197 $data = $this->sqlite->res2arr($res); 198 199 return $data; 200 } 201 202 /** 203 * Creates a proper result array from the database data 204 * 205 * @param array $DBdata the data as it is retrieved from the database, i.e. by SchemaData::getDataFromDB 206 * @param bool $asarray return data as associative array (true) or as array of Values (false) 207 * @return array|Value[] 208 */ 209 protected function consolidateData($DBdata, $asarray = false) { 210 $data = array(); 211 212 $sep = Search::CONCAT_SEPARATOR; 213 214 foreach($this->schema->getColumns(false) as $col) { 215 216 // if no data saved, yet return empty strings 217 if($DBdata) { 218 $val = $DBdata[0]['col'.$col->getColref()]; 219 } else { 220 $val = ''; 221 } 222 223 // multi val data is concatenated 224 if($col->isMulti()) { 225 $val = explode($sep, $val); 226 if($this->opt_rawvalue) { 227 $val = array_map( 228 function ($val) use ($col) { // FIXME requires PHP 5.4+ 229 return $col->getType()->rawValue($val); 230 }, 231 $val 232 ); 233 } 234 $val = array_filter($val); 235 } else { 236 if($this->opt_rawvalue) { 237 $val = $col->getType()->rawValue($val); 238 } 239 } 240 241 if($this->opt_skipempty && ($val === '' || $val == array())) continue; 242 if($this->opt_skipempty && !$col->isVisibleInPage()) continue; 243 244 if($asarray) { 245 $data[$col->getLabel()] = $val; 246 } else { 247 $data[] = new Value($col, $val); 248 } 249 } 250 251 return $data; 252 } 253 254 /** 255 * Builds the SQL statement to select the data for this page and schema 256 * 257 * @return array Two fields: the SQL string and the parameters array 258 */ 259 protected function buildGetDataSQL() { 260 $sep = Search::CONCAT_SEPARATOR; 261 $stable = 'data_' . $this->schema->getTable(); 262 $mtable = 'multi_' . $this->schema->getTable(); 263 264 $QB = new QueryBuilder(); 265 $QB->addTable($stable, 'DATA'); 266 $QB->addSelectColumn('DATA', 'pid', 'PID'); 267 $QB->addGroupByStatement('DATA.pid'); 268 269 foreach($this->schema->getColumns(false) as $col) { 270 271 $colref = $col->getColref(); 272 $colname = 'col'.$colref; 273 274 if($col->getType()->isMulti()) { 275 $tn = 'M' . $colref; 276 $QB->addLeftJoin( 277 'DATA', 278 $mtable, 279 $tn, 280 "DATA.pid = $tn.pid AND DATA.rev = $tn.rev AND $tn.colref = $colref" 281 ); 282 $col->getType()->select($QB, $tn, 'value', $colname); 283 $sel = $QB->getSelectStatement($colname); 284 $QB->addSelectStatement("GROUP_CONCAT($sel, '$sep')", $colname); 285 } else { 286 $col->getType()->select($QB, 'DATA', $colname, $colname); 287 $QB->addGroupByStatement($colname); 288 } 289 } 290 291 $pl = $QB->addValue($this->pid); 292 $QB->filters()->whereAnd("DATA.pid = $pl"); 293 $pl = $QB->addValue($this->getLastRevisionTimestamp()); 294 $QB->filters()->whereAnd("DATA.rev = $pl"); 295 296 return $QB->getSQL(); 297 } 298 299 /** 300 * @param int $ts 301 */ 302 public function setTimestamp($ts) { 303 if($ts && $ts < $this->schema->getTimeStamp()) { 304 throw new StructException('Given timestamp is not valid for current Schema'); 305 } 306 307 $this->ts = $ts; 308 } 309 310 311 /** 312 * Return the last time an edit happened for this table for the currently set 313 * time and pid. When the current timestamp is 0, the newest revision is 314 * returned. Used in @see buildGetDataSQL() 315 * 316 * @return int 317 */ 318 abstract protected function getLastRevisionTimestamp(); 319} 320 321 322