1f411d872SAndreas Gohr<?php 2f411d872SAndreas Gohr 3f411d872SAndreas Gohrnamespace dokuwiki\plugin\struct\meta; 4f411d872SAndreas Gohr 5f411d872SAndreas Gohrabstract class AccessTable { 6f411d872SAndreas Gohr 7f411d872SAndreas Gohr /** @var Schema */ 8f411d872SAndreas Gohr protected $schema; 9f411d872SAndreas Gohr protected $pid; 100ceefd5cSAnna Dabrowska protected $rid; 11f411d872SAndreas Gohr protected $labels = array(); 12f411d872SAndreas Gohr protected $ts = 0; 13f411d872SAndreas Gohr /** @var \helper_plugin_sqlite */ 14f411d872SAndreas Gohr protected $sqlite; 15f411d872SAndreas Gohr 1690421550SAndreas Gohr // options on how to retrieve data 17f411d872SAndreas Gohr protected $opt_skipempty = false; 18f411d872SAndreas Gohr 19f411d872SAndreas Gohr /** 20b9d35ff2SAnna Dabrowska * Factory method returning the appropriate data accessor (page, lookup or serial) 21f411d872SAndreas Gohr * 22f411d872SAndreas Gohr * @param Schema $schema schema to load 2386a40c1eSAnna Dabrowska * @param string $pid Page id to access 24897aef42SAndreas Gohr * @param int $ts Time at which the data should be read or written, 0 for now 2586a40c1eSAnna Dabrowska * @param int $rid Row id, 0 for page type data, otherwise autoincrement 26b9d35ff2SAnna Dabrowska * @return AccessTableData|AccessTableLookup|AccessTableSerial 27f411d872SAndreas Gohr */ 280ceefd5cSAnna Dabrowska public static function bySchema(Schema $schema, $pid, $ts = 0, $rid = 0) { 290ceefd5cSAnna Dabrowska if (!$pid && $ts === 0) { 300ceefd5cSAnna Dabrowska return new AccessTableLookup($schema, $pid, $ts, $rid); 31f411d872SAndreas Gohr } 32b9d35ff2SAnna Dabrowska if ($pid && $ts === 0) { 33b9d35ff2SAnna Dabrowska return new AccessTableSerial($schema, $pid, $ts, $rid); 34b9d35ff2SAnna Dabrowska } 350ceefd5cSAnna Dabrowska return new AccessTableData($schema, $pid, $ts, $rid); 36f411d872SAndreas Gohr } 37f411d872SAndreas Gohr 38f411d872SAndreas Gohr /** 39f411d872SAndreas Gohr * Factory Method to access a data or lookup table 40f411d872SAndreas Gohr * 41f411d872SAndreas Gohr * @param string $tablename schema to load 4286a40c1eSAnna Dabrowska * @param string $pid Page id to access 43897aef42SAndreas Gohr * @param int $ts Time at which the data should be read or written, 0 for now 4486a40c1eSAnna Dabrowska * @param int $rid Row id, 0 for page type data, otherwise autoincrement 4594c9aa4cSAndreas Gohr * @return AccessTableData|AccessTableLookup 46f411d872SAndreas Gohr */ 470ceefd5cSAnna Dabrowska public static function byTableName($tablename, $pid, $ts = 0, $rid = 0) { 48f411d872SAndreas Gohr $schema = new Schema($tablename, $ts); 490ceefd5cSAnna Dabrowska return self::bySchema($schema, $pid, $ts, $rid); 50f411d872SAndreas Gohr } 51f411d872SAndreas Gohr 52f411d872SAndreas Gohr /** 53f411d872SAndreas Gohr * AccessTable constructor 54f411d872SAndreas Gohr * 55897aef42SAndreas Gohr * @param Schema $schema The schema valid at $ts 5686a40c1eSAnna Dabrowska * @param string $pid Page id 57897aef42SAndreas Gohr * @param int $ts Time at which the data should be read or written, 0 for now 580ceefd5cSAnna Dabrowska * @param int $rid Row id: 0 for pages, autoincremented for other types 59f411d872SAndreas Gohr */ 600ceefd5cSAnna Dabrowska public function __construct(Schema $schema, $pid, $ts = 0, $rid = 0) { 61f411d872SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 62f411d872SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 63f411d872SAndreas Gohr $this->sqlite = $helper->getDB(); 64f411d872SAndreas Gohr 65f411d872SAndreas Gohr if(!$schema->getId()) { 66f411d872SAndreas Gohr throw new StructException('Schema does not exist. Only data of existing schemas can be accessed'); 67f411d872SAndreas Gohr } 68f411d872SAndreas Gohr 69f411d872SAndreas Gohr $this->schema = $schema; 70f411d872SAndreas Gohr $this->pid = $pid; 710ceefd5cSAnna Dabrowska $this->rid = $rid; 72897aef42SAndreas Gohr $this->setTimestamp($ts); 73f411d872SAndreas Gohr foreach($this->schema->getColumns() as $col) { 74f411d872SAndreas Gohr $this->labels[$col->getColref()] = $col->getType()->getLabel(); 75f411d872SAndreas Gohr } 76f411d872SAndreas Gohr } 77f411d872SAndreas Gohr 78f411d872SAndreas Gohr /** 79f411d872SAndreas Gohr * gives access to the schema 80f411d872SAndreas Gohr * 81f411d872SAndreas Gohr * @return Schema 82f411d872SAndreas Gohr */ 83f411d872SAndreas Gohr public function getSchema() { 84f411d872SAndreas Gohr return $this->schema; 85f411d872SAndreas Gohr } 86f411d872SAndreas Gohr 87f411d872SAndreas Gohr /** 88f107f479SAndreas Gohr * The current pid 89f107f479SAndreas Gohr * 9086a40c1eSAnna Dabrowska * @return string 91f107f479SAndreas Gohr */ 92f107f479SAndreas Gohr public function getPid() { 93f107f479SAndreas Gohr return $this->pid; 94f107f479SAndreas Gohr } 95f107f479SAndreas Gohr 96f107f479SAndreas Gohr /** 970ceefd5cSAnna Dabrowska * The current rid 980ceefd5cSAnna Dabrowska * 9986a40c1eSAnna Dabrowska * @return int 1000ceefd5cSAnna Dabrowska */ 1010ceefd5cSAnna Dabrowska public function getRid() { 1020ceefd5cSAnna Dabrowska return $this->rid; 1030ceefd5cSAnna Dabrowska } 1040ceefd5cSAnna Dabrowska 1050ceefd5cSAnna Dabrowska /** 106f411d872SAndreas Gohr * Should remove the current data, by either deleting or ovewriting it 107f411d872SAndreas Gohr * 108f411d872SAndreas Gohr * @return bool if the delete succeeded 109f411d872SAndreas Gohr */ 110f411d872SAndreas Gohr abstract public function clearData(); 111f411d872SAndreas Gohr 112f411d872SAndreas Gohr /** 113f411d872SAndreas Gohr * Save the data to the database. 114f411d872SAndreas Gohr * 115f411d872SAndreas Gohr * We differentiate between single-value-column and multi-value-column by the value to the respective column-name, 116f411d872SAndreas Gohr * i.e. depending on if that is a string or an array, respectively. 117f411d872SAndreas Gohr * 118f411d872SAndreas Gohr * @param array $data typelabel => value for single fields or typelabel => array(value, value, ...) for multi fields 119f411d872SAndreas Gohr * @return bool success of saving the data to the database 120f411d872SAndreas Gohr */ 121f411d872SAndreas Gohr abstract public function saveData($data); 122f411d872SAndreas Gohr 123f411d872SAndreas Gohr /** 124f411d872SAndreas Gohr * Should empty or invisible (inpage) fields be returned? 125f411d872SAndreas Gohr * 126f411d872SAndreas Gohr * Defaults to false 127f411d872SAndreas Gohr * 128f411d872SAndreas Gohr * @param null|bool $set new value, null to read only 129f411d872SAndreas Gohr * @return bool current value (after set) 130f411d872SAndreas Gohr */ 131f411d872SAndreas Gohr public function optionSkipEmpty($set = null) { 132f411d872SAndreas Gohr if(!is_null($set)) { 133f411d872SAndreas Gohr $this->opt_skipempty = $set; 134f411d872SAndreas Gohr } 135f411d872SAndreas Gohr return $this->opt_skipempty; 136f411d872SAndreas Gohr } 137f411d872SAndreas Gohr 138f411d872SAndreas Gohr /** 139f411d872SAndreas Gohr * Get the value of a single column 140f411d872SAndreas Gohr * 141f411d872SAndreas Gohr * @param Column $column 142f411d872SAndreas Gohr * @return Value|null 143f411d872SAndreas Gohr */ 144f411d872SAndreas Gohr public function getDataColumn($column) { 145f411d872SAndreas Gohr $data = $this->getData(); 146f411d872SAndreas Gohr foreach($data as $value) { 147f411d872SAndreas Gohr if($value->getColumn() == $column) { 148f411d872SAndreas Gohr return $value; 149f411d872SAndreas Gohr } 150f411d872SAndreas Gohr } 151f411d872SAndreas Gohr return null; 152f411d872SAndreas Gohr } 153f411d872SAndreas Gohr 154f411d872SAndreas Gohr /** 155f411d872SAndreas Gohr * returns the data saved for the page 156f411d872SAndreas Gohr * 157f411d872SAndreas Gohr * @return Value[] a list of values saved for the current page 158f411d872SAndreas Gohr */ 159f411d872SAndreas Gohr public function getData() { 160f411d872SAndreas Gohr $data = $this->getDataFromDB(); 161f411d872SAndreas Gohr $data = $this->consolidateData($data, false); 162f411d872SAndreas Gohr return $data; 163f411d872SAndreas Gohr } 164f411d872SAndreas Gohr 165f411d872SAndreas Gohr /** 166f411d872SAndreas Gohr * returns the data saved for the page as associative array 167f411d872SAndreas Gohr * 168f411d872SAndreas Gohr * The array returned is in the same format as used in @see saveData() 169f411d872SAndreas Gohr * 17090421550SAndreas Gohr * It always returns raw Values! 17190421550SAndreas Gohr * 172f411d872SAndreas Gohr * @return array 173f411d872SAndreas Gohr */ 174f411d872SAndreas Gohr public function getDataArray() { 175f411d872SAndreas Gohr $data = $this->getDataFromDB(); 176f411d872SAndreas Gohr $data = $this->consolidateData($data, true); 177f411d872SAndreas Gohr return $data; 178f411d872SAndreas Gohr } 179f411d872SAndreas Gohr 180f411d872SAndreas Gohr /** 181f411d872SAndreas Gohr * Return the data in pseudo syntax 182f411d872SAndreas Gohr */ 183f411d872SAndreas Gohr public function getDataPseudoSyntax() { 184f411d872SAndreas Gohr $result = ''; 185a0a1d14eSAndreas Gohr $data = $this->getData(); 186a0a1d14eSAndreas Gohr 187a0a1d14eSAndreas Gohr foreach($data as $value) { 188a0a1d14eSAndreas Gohr $key = $value->getColumn()->getFullQualifiedLabel(); 189a0a1d14eSAndreas Gohr $value = $value->getDisplayValue(); 190f411d872SAndreas Gohr if(is_array($value)) $value = join(', ', $value); 191f411d872SAndreas Gohr $result .= sprintf("% -20s : %s\n", $key, $value); 192f411d872SAndreas Gohr } 193f411d872SAndreas Gohr return $result; 194f411d872SAndreas Gohr } 195f411d872SAndreas Gohr 196f411d872SAndreas Gohr /** 197f411d872SAndreas Gohr * retrieve the data saved for the page from the database. Usually there is no need to call this function. 198f411d872SAndreas Gohr * Call @see SchemaData::getData instead. 199f411d872SAndreas Gohr */ 200f411d872SAndreas Gohr protected function getDataFromDB() { 201f411d872SAndreas Gohr list($sql, $opt) = $this->buildGetDataSQL(); 202f411d872SAndreas Gohr 203f411d872SAndreas Gohr $res = $this->sqlite->query($sql, $opt); 204f411d872SAndreas Gohr $data = $this->sqlite->res2arr($res); 2059c00b26cSAndreas Gohr $this->sqlite->res_close($res); 206f411d872SAndreas Gohr return $data; 207f411d872SAndreas Gohr } 208f411d872SAndreas Gohr 209f411d872SAndreas Gohr /** 210f411d872SAndreas Gohr * Creates a proper result array from the database data 211f411d872SAndreas Gohr * 212f411d872SAndreas Gohr * @param array $DBdata the data as it is retrieved from the database, i.e. by SchemaData::getDataFromDB 213f411d872SAndreas Gohr * @param bool $asarray return data as associative array (true) or as array of Values (false) 214f411d872SAndreas Gohr * @return array|Value[] 215f411d872SAndreas Gohr */ 216f411d872SAndreas Gohr protected function consolidateData($DBdata, $asarray = false) { 217f411d872SAndreas Gohr $data = array(); 218f411d872SAndreas Gohr 219f411d872SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 220f411d872SAndreas Gohr 221f411d872SAndreas Gohr foreach($this->schema->getColumns(false) as $col) { 222f411d872SAndreas Gohr 22390421550SAndreas Gohr // if no data saved yet, return empty strings 224f411d872SAndreas Gohr if($DBdata) { 225bab52340SAndreas Gohr $val = $DBdata[0]['out' . $col->getColref()]; 226f411d872SAndreas Gohr } else { 227f411d872SAndreas Gohr $val = ''; 228f411d872SAndreas Gohr } 229f411d872SAndreas Gohr 230f411d872SAndreas Gohr // multi val data is concatenated 231f411d872SAndreas Gohr if($col->isMulti()) { 232f411d872SAndreas Gohr $val = explode($sep, $val); 233f411d872SAndreas Gohr $val = array_filter($val); 234f411d872SAndreas Gohr } 235f411d872SAndreas Gohr 23690421550SAndreas Gohr $value = new Value($col, $val); 237f411d872SAndreas Gohr 23890421550SAndreas Gohr if($this->opt_skipempty && $value->isEmpty()) continue; 23990421550SAndreas Gohr if($this->opt_skipempty && !$col->isVisibleInPage()) continue; //FIXME is this a correct assumption? 24090421550SAndreas Gohr 24190421550SAndreas Gohr // for arrays, we return the raw value only 242f411d872SAndreas Gohr if($asarray) { 24390421550SAndreas Gohr $data[$col->getLabel()] = $value->getRawValue(); 244f411d872SAndreas Gohr } else { 2456e54daafSMichael Große $data[$col->getLabel()] = $value; 246f411d872SAndreas Gohr } 247f411d872SAndreas Gohr } 248f411d872SAndreas Gohr 249f411d872SAndreas Gohr return $data; 250f411d872SAndreas Gohr } 251f411d872SAndreas Gohr 252f411d872SAndreas Gohr /** 253f411d872SAndreas Gohr * Builds the SQL statement to select the data for this page and schema 254f411d872SAndreas Gohr * 255f411d872SAndreas Gohr * @return array Two fields: the SQL string and the parameters array 256f411d872SAndreas Gohr */ 257*6fd73b4bSAnna Dabrowska protected function buildGetDataSQL($idColumn = 'pid') { 258f411d872SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 259f411d872SAndreas Gohr $stable = 'data_' . $this->schema->getTable(); 260f411d872SAndreas Gohr $mtable = 'multi_' . $this->schema->getTable(); 261f411d872SAndreas Gohr 262f411d872SAndreas Gohr $QB = new QueryBuilder(); 263f411d872SAndreas Gohr $QB->addTable($stable, 'DATA'); 264*6fd73b4bSAnna Dabrowska $QB->addSelectColumn('DATA', $idColumn, strtoupper($idColumn)); 265*6fd73b4bSAnna Dabrowska $QB->addGroupByStatement("DATA.$idColumn"); 266f411d872SAndreas Gohr 267f411d872SAndreas Gohr foreach($this->schema->getColumns(false) as $col) { 268f411d872SAndreas Gohr 269f411d872SAndreas Gohr $colref = $col->getColref(); 270f411d872SAndreas Gohr $colname = 'col' . $colref; 271bab52340SAndreas Gohr $outname = 'out' . $colref; 272f411d872SAndreas Gohr 273f411d872SAndreas Gohr if($col->getType()->isMulti()) { 274f411d872SAndreas Gohr $tn = 'M' . $colref; 275f411d872SAndreas Gohr $QB->addLeftJoin( 276f411d872SAndreas Gohr 'DATA', 277f411d872SAndreas Gohr $mtable, 278f411d872SAndreas Gohr $tn, 279*6fd73b4bSAnna Dabrowska "DATA.$idColumn = $tn.$idColumn AND DATA.rev = $tn.rev AND $tn.colref = $colref" 280f411d872SAndreas Gohr ); 281bab52340SAndreas Gohr $col->getType()->select($QB, $tn, 'value', $outname); 282bab52340SAndreas Gohr $sel = $QB->getSelectStatement($outname); 283bab52340SAndreas Gohr $QB->addSelectStatement("GROUP_CONCAT($sel, '$sep')", $outname); 284f411d872SAndreas Gohr } else { 285bab52340SAndreas Gohr $col->getType()->select($QB, 'DATA', $colname, $outname); 286bab52340SAndreas Gohr $QB->addGroupByStatement($outname); 287f411d872SAndreas Gohr } 288f411d872SAndreas Gohr } 289f411d872SAndreas Gohr 290*6fd73b4bSAnna Dabrowska $pl = $QB->addValue($this->{$idColumn}); 291*6fd73b4bSAnna Dabrowska $QB->filters()->whereAnd("DATA.$idColumn = $pl"); 292897aef42SAndreas Gohr $pl = $QB->addValue($this->getLastRevisionTimestamp()); 293f411d872SAndreas Gohr $QB->filters()->whereAnd("DATA.rev = $pl"); 294f411d872SAndreas Gohr 295f411d872SAndreas Gohr return $QB->getSQL(); 296f411d872SAndreas Gohr } 297f411d872SAndreas Gohr 298f411d872SAndreas Gohr /** 29913eddb0fSAndreas Gohr * @param int $ts 30013eddb0fSAndreas Gohr */ 30113eddb0fSAndreas Gohr public function setTimestamp($ts) { 302897aef42SAndreas Gohr if($ts && $ts < $this->schema->getTimeStamp()) { 303897aef42SAndreas Gohr throw new StructException('Given timestamp is not valid for current Schema'); 304897aef42SAndreas Gohr } 305897aef42SAndreas Gohr 30613eddb0fSAndreas Gohr $this->ts = $ts; 30713eddb0fSAndreas Gohr } 30813eddb0fSAndreas Gohr 30913eddb0fSAndreas Gohr /** 310897aef42SAndreas Gohr * Return the last time an edit happened for this table for the currently set 311897aef42SAndreas Gohr * time and pid. When the current timestamp is 0, the newest revision is 312897aef42SAndreas Gohr * returned. Used in @see buildGetDataSQL() 313f411d872SAndreas Gohr * 314897aef42SAndreas Gohr * @return int 315f411d872SAndreas Gohr */ 316897aef42SAndreas Gohr abstract protected function getLastRevisionTimestamp(); 31787dc1344SAndreas Gohr 31887dc1344SAndreas Gohr /** 31987dc1344SAndreas Gohr * Check if the given data validates against the current types. 32087dc1344SAndreas Gohr * 32187dc1344SAndreas Gohr * @param array $data 32293ca6f4fSAndreas Gohr * @return AccessDataValidator 32387dc1344SAndreas Gohr */ 32487dc1344SAndreas Gohr public function getValidator($data) { 32593ca6f4fSAndreas Gohr return new AccessDataValidator($this, $data); 32687dc1344SAndreas Gohr } 327f411d872SAndreas Gohr} 328f411d872SAndreas Gohr 329f411d872SAndreas Gohr 330