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