1f411d872SAndreas Gohr<?php 2f411d872SAndreas Gohr 3f411d872SAndreas Gohrnamespace dokuwiki\plugin\struct\meta; 4f411d872SAndreas Gohr 5c73fba38SAnna Dabrowska/** 6c73fba38SAnna Dabrowska * Class AccessTable 7c73fba38SAnna Dabrowska * 8c73fba38SAnna Dabrowska * Base class for data accessors 9c73fba38SAnna Dabrowska * 10c73fba38SAnna Dabrowska * @package dokuwiki\plugin\struct\meta 11c73fba38SAnna Dabrowska */ 12f411d872SAndreas Gohrabstract class AccessTable { 13f411d872SAndreas Gohr 14*efe74305SAnna Dabrowska const DEFAULT_REV = 0; 15*efe74305SAnna Dabrowska const DEFAULT_LATEST = 1; 16*efe74305SAnna Dabrowska 17f411d872SAndreas Gohr /** @var Schema */ 18f411d872SAndreas Gohr protected $schema; 19f411d872SAndreas Gohr protected $pid; 200ceefd5cSAnna Dabrowska protected $rid; 21f411d872SAndreas Gohr protected $labels = array(); 22f411d872SAndreas Gohr protected $ts = 0; 23f411d872SAndreas Gohr /** @var \helper_plugin_sqlite */ 24f411d872SAndreas Gohr protected $sqlite; 25f411d872SAndreas Gohr 2690421550SAndreas Gohr // options on how to retrieve data 27f411d872SAndreas Gohr protected $opt_skipempty = false; 28f411d872SAndreas Gohr 29f411d872SAndreas Gohr /** 30b9d35ff2SAnna Dabrowska * Factory method returning the appropriate data accessor (page, lookup or serial) 31f411d872SAndreas Gohr * 32f411d872SAndreas Gohr * @param Schema $schema schema to load 3386a40c1eSAnna Dabrowska * @param string $pid Page id to access 34897aef42SAndreas Gohr * @param int $ts Time at which the data should be read or written, 0 for now 3586a40c1eSAnna Dabrowska * @param int $rid Row id, 0 for page type data, otherwise autoincrement 36b9d35ff2SAnna Dabrowska * @return AccessTableData|AccessTableLookup|AccessTableSerial 37f411d872SAndreas Gohr */ 380ceefd5cSAnna Dabrowska public static function bySchema(Schema $schema, $pid, $ts = 0, $rid = 0) { 39c73fba38SAnna Dabrowska if (self::isTypeLookup($pid, $ts, $rid)) { 400ceefd5cSAnna Dabrowska return new AccessTableLookup($schema, $pid, $ts, $rid); 41f411d872SAndreas Gohr } 42c73fba38SAnna Dabrowska if (self::isTypeSerial($pid, $ts, $rid)) { 43b9d35ff2SAnna Dabrowska return new AccessTableSerial($schema, $pid, $ts, $rid); 44b9d35ff2SAnna Dabrowska } 450ceefd5cSAnna Dabrowska return new AccessTableData($schema, $pid, $ts, $rid); 46f411d872SAndreas Gohr } 47f411d872SAndreas Gohr 48f411d872SAndreas Gohr /** 49c73fba38SAnna Dabrowska * Factory Method to access data 50f411d872SAndreas Gohr * 51f411d872SAndreas Gohr * @param string $tablename schema to load 5286a40c1eSAnna Dabrowska * @param string $pid Page id to access 53897aef42SAndreas Gohr * @param int $ts Time at which the data should be read or written, 0 for now 5486a40c1eSAnna Dabrowska * @param int $rid Row id, 0 for page type data, otherwise autoincrement 55c73fba38SAnna Dabrowska * @return AccessTableData|AccessTableLookup|AccessTableSerial 56f411d872SAndreas Gohr */ 570ceefd5cSAnna Dabrowska public static function byTableName($tablename, $pid, $ts = 0, $rid = 0) { 58f411d872SAndreas Gohr $schema = new Schema($tablename, $ts); 590ceefd5cSAnna Dabrowska return self::bySchema($schema, $pid, $ts, $rid); 60f411d872SAndreas Gohr } 61f411d872SAndreas Gohr 62f411d872SAndreas Gohr /** 63f411d872SAndreas Gohr * AccessTable constructor 64f411d872SAndreas Gohr * 65897aef42SAndreas Gohr * @param Schema $schema The schema valid at $ts 6686a40c1eSAnna Dabrowska * @param string $pid Page id 67897aef42SAndreas Gohr * @param int $ts Time at which the data should be read or written, 0 for now 680ceefd5cSAnna Dabrowska * @param int $rid Row id: 0 for pages, autoincremented for other types 69f411d872SAndreas Gohr */ 700ceefd5cSAnna Dabrowska public function __construct(Schema $schema, $pid, $ts = 0, $rid = 0) { 71f411d872SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 72f411d872SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 73f411d872SAndreas Gohr $this->sqlite = $helper->getDB(); 74f411d872SAndreas Gohr 75f411d872SAndreas Gohr if(!$schema->getId()) { 76f411d872SAndreas Gohr throw new StructException('Schema does not exist. Only data of existing schemas can be accessed'); 77f411d872SAndreas Gohr } 78f411d872SAndreas Gohr 79f411d872SAndreas Gohr $this->schema = $schema; 80f411d872SAndreas Gohr $this->pid = $pid; 810ceefd5cSAnna Dabrowska $this->rid = $rid; 82897aef42SAndreas Gohr $this->setTimestamp($ts); 83f411d872SAndreas Gohr foreach($this->schema->getColumns() as $col) { 84f411d872SAndreas Gohr $this->labels[$col->getColref()] = $col->getType()->getLabel(); 85f411d872SAndreas Gohr } 86f411d872SAndreas Gohr } 87f411d872SAndreas Gohr 88f411d872SAndreas Gohr /** 89f411d872SAndreas Gohr * gives access to the schema 90f411d872SAndreas Gohr * 91f411d872SAndreas Gohr * @return Schema 92f411d872SAndreas Gohr */ 93f411d872SAndreas Gohr public function getSchema() { 94f411d872SAndreas Gohr return $this->schema; 95f411d872SAndreas Gohr } 96f411d872SAndreas Gohr 97f411d872SAndreas Gohr /** 98f107f479SAndreas Gohr * The current pid 99f107f479SAndreas Gohr * 10086a40c1eSAnna Dabrowska * @return string 101f107f479SAndreas Gohr */ 102f107f479SAndreas Gohr public function getPid() { 103f107f479SAndreas Gohr return $this->pid; 104f107f479SAndreas Gohr } 105f107f479SAndreas Gohr 106f107f479SAndreas Gohr /** 1070ceefd5cSAnna Dabrowska * The current rid 1080ceefd5cSAnna Dabrowska * 10986a40c1eSAnna Dabrowska * @return int 1100ceefd5cSAnna Dabrowska */ 1110ceefd5cSAnna Dabrowska public function getRid() { 1120ceefd5cSAnna Dabrowska return $this->rid; 1130ceefd5cSAnna Dabrowska } 1140ceefd5cSAnna Dabrowska 1150ceefd5cSAnna Dabrowska /** 116f411d872SAndreas Gohr * Should remove the current data, by either deleting or ovewriting it 117f411d872SAndreas Gohr * 118f411d872SAndreas Gohr * @return bool if the delete succeeded 119f411d872SAndreas Gohr */ 120f411d872SAndreas Gohr abstract public function clearData(); 121f411d872SAndreas Gohr 122f411d872SAndreas Gohr /** 123f411d872SAndreas Gohr * Save the data to the database. 124f411d872SAndreas Gohr * 125f411d872SAndreas Gohr * We differentiate between single-value-column and multi-value-column by the value to the respective column-name, 126f411d872SAndreas Gohr * i.e. depending on if that is a string or an array, respectively. 127f411d872SAndreas Gohr * 128f411d872SAndreas Gohr * @param array $data typelabel => value for single fields or typelabel => array(value, value, ...) for multi fields 129f411d872SAndreas Gohr * @return bool success of saving the data to the database 130f411d872SAndreas Gohr */ 131f411d872SAndreas Gohr abstract public function saveData($data); 132f411d872SAndreas Gohr 133f411d872SAndreas Gohr /** 134f411d872SAndreas Gohr * Should empty or invisible (inpage) fields be returned? 135f411d872SAndreas Gohr * 136f411d872SAndreas Gohr * Defaults to false 137f411d872SAndreas Gohr * 138f411d872SAndreas Gohr * @param null|bool $set new value, null to read only 139f411d872SAndreas Gohr * @return bool current value (after set) 140f411d872SAndreas Gohr */ 141f411d872SAndreas Gohr public function optionSkipEmpty($set = null) { 142f411d872SAndreas Gohr if(!is_null($set)) { 143f411d872SAndreas Gohr $this->opt_skipempty = $set; 144f411d872SAndreas Gohr } 145f411d872SAndreas Gohr return $this->opt_skipempty; 146f411d872SAndreas Gohr } 147f411d872SAndreas Gohr 148f411d872SAndreas Gohr /** 149f411d872SAndreas Gohr * Get the value of a single column 150f411d872SAndreas Gohr * 151f411d872SAndreas Gohr * @param Column $column 152f411d872SAndreas Gohr * @return Value|null 153f411d872SAndreas Gohr */ 154f411d872SAndreas Gohr public function getDataColumn($column) { 155f411d872SAndreas Gohr $data = $this->getData(); 156f411d872SAndreas Gohr foreach($data as $value) { 157f411d872SAndreas Gohr if($value->getColumn() == $column) { 158f411d872SAndreas Gohr return $value; 159f411d872SAndreas Gohr } 160f411d872SAndreas Gohr } 161f411d872SAndreas Gohr return null; 162f411d872SAndreas Gohr } 163f411d872SAndreas Gohr 164f411d872SAndreas Gohr /** 165f411d872SAndreas Gohr * returns the data saved for the page 166f411d872SAndreas Gohr * 167f411d872SAndreas Gohr * @return Value[] a list of values saved for the current page 168f411d872SAndreas Gohr */ 169f411d872SAndreas Gohr public function getData() { 170f411d872SAndreas Gohr $data = $this->getDataFromDB(); 171f411d872SAndreas Gohr $data = $this->consolidateData($data, false); 172f411d872SAndreas Gohr return $data; 173f411d872SAndreas Gohr } 174f411d872SAndreas Gohr 175f411d872SAndreas Gohr /** 176f411d872SAndreas Gohr * returns the data saved for the page as associative array 177f411d872SAndreas Gohr * 178f411d872SAndreas Gohr * The array returned is in the same format as used in @see saveData() 179f411d872SAndreas Gohr * 18090421550SAndreas Gohr * It always returns raw Values! 18190421550SAndreas Gohr * 182f411d872SAndreas Gohr * @return array 183f411d872SAndreas Gohr */ 184f411d872SAndreas Gohr public function getDataArray() { 185f411d872SAndreas Gohr $data = $this->getDataFromDB(); 186f411d872SAndreas Gohr $data = $this->consolidateData($data, true); 187f411d872SAndreas Gohr return $data; 188f411d872SAndreas Gohr } 189f411d872SAndreas Gohr 190f411d872SAndreas Gohr /** 191f411d872SAndreas Gohr * Return the data in pseudo syntax 192f411d872SAndreas Gohr */ 193f411d872SAndreas Gohr public function getDataPseudoSyntax() { 194f411d872SAndreas Gohr $result = ''; 195a0a1d14eSAndreas Gohr $data = $this->getData(); 196a0a1d14eSAndreas Gohr 197a0a1d14eSAndreas Gohr foreach($data as $value) { 198a0a1d14eSAndreas Gohr $key = $value->getColumn()->getFullQualifiedLabel(); 199a0a1d14eSAndreas Gohr $value = $value->getDisplayValue(); 200f411d872SAndreas Gohr if(is_array($value)) $value = join(', ', $value); 201f411d872SAndreas Gohr $result .= sprintf("% -20s : %s\n", $key, $value); 202f411d872SAndreas Gohr } 203f411d872SAndreas Gohr return $result; 204f411d872SAndreas Gohr } 205f411d872SAndreas Gohr 206f411d872SAndreas Gohr /** 207f411d872SAndreas Gohr * retrieve the data saved for the page from the database. Usually there is no need to call this function. 208f411d872SAndreas Gohr * Call @see SchemaData::getData instead. 209f411d872SAndreas Gohr */ 210f411d872SAndreas Gohr protected function getDataFromDB() { 211*efe74305SAnna Dabrowska $idColumn = self::isTypePage($this->pid, $this->ts, $this->rid) ? 'pid' : 'rid'; 212*efe74305SAnna Dabrowska list($sql, $opt) = $this->buildGetDataSQL($idColumn); 213f411d872SAndreas Gohr 214f411d872SAndreas Gohr $res = $this->sqlite->query($sql, $opt); 215f411d872SAndreas Gohr $data = $this->sqlite->res2arr($res); 2169c00b26cSAndreas Gohr $this->sqlite->res_close($res); 217f411d872SAndreas Gohr return $data; 218f411d872SAndreas Gohr } 219f411d872SAndreas Gohr 220f411d872SAndreas Gohr /** 221f411d872SAndreas Gohr * Creates a proper result array from the database data 222f411d872SAndreas Gohr * 223f411d872SAndreas Gohr * @param array $DBdata the data as it is retrieved from the database, i.e. by SchemaData::getDataFromDB 224f411d872SAndreas Gohr * @param bool $asarray return data as associative array (true) or as array of Values (false) 225f411d872SAndreas Gohr * @return array|Value[] 226f411d872SAndreas Gohr */ 227f411d872SAndreas Gohr protected function consolidateData($DBdata, $asarray = false) { 228f411d872SAndreas Gohr $data = array(); 229f411d872SAndreas Gohr 230f411d872SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 231f411d872SAndreas Gohr 232f411d872SAndreas Gohr foreach($this->schema->getColumns(false) as $col) { 233f411d872SAndreas Gohr 23490421550SAndreas Gohr // if no data saved yet, return empty strings 235f411d872SAndreas Gohr if($DBdata) { 236bab52340SAndreas Gohr $val = $DBdata[0]['out' . $col->getColref()]; 237f411d872SAndreas Gohr } else { 238f411d872SAndreas Gohr $val = ''; 239f411d872SAndreas Gohr } 240f411d872SAndreas Gohr 241f411d872SAndreas Gohr // multi val data is concatenated 242f411d872SAndreas Gohr if($col->isMulti()) { 243f411d872SAndreas Gohr $val = explode($sep, $val); 244f411d872SAndreas Gohr $val = array_filter($val); 245f411d872SAndreas Gohr } 246f411d872SAndreas Gohr 24790421550SAndreas Gohr $value = new Value($col, $val); 248f411d872SAndreas Gohr 24990421550SAndreas Gohr if($this->opt_skipempty && $value->isEmpty()) continue; 25090421550SAndreas Gohr if($this->opt_skipempty && !$col->isVisibleInPage()) continue; //FIXME is this a correct assumption? 25190421550SAndreas Gohr 25290421550SAndreas Gohr // for arrays, we return the raw value only 253f411d872SAndreas Gohr if($asarray) { 25490421550SAndreas Gohr $data[$col->getLabel()] = $value->getRawValue(); 255f411d872SAndreas Gohr } else { 2566e54daafSMichael Große $data[$col->getLabel()] = $value; 257f411d872SAndreas Gohr } 258f411d872SAndreas Gohr } 259f411d872SAndreas Gohr 260f411d872SAndreas Gohr return $data; 261f411d872SAndreas Gohr } 262f411d872SAndreas Gohr 263f411d872SAndreas Gohr /** 264f411d872SAndreas Gohr * Builds the SQL statement to select the data for this page and schema 265f411d872SAndreas Gohr * 266f411d872SAndreas Gohr * @return array Two fields: the SQL string and the parameters array 267f411d872SAndreas Gohr */ 2686fd73b4bSAnna Dabrowska protected function buildGetDataSQL($idColumn = 'pid') { 269f411d872SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 270f411d872SAndreas Gohr $stable = 'data_' . $this->schema->getTable(); 271f411d872SAndreas Gohr $mtable = 'multi_' . $this->schema->getTable(); 272f411d872SAndreas Gohr 273f411d872SAndreas Gohr $QB = new QueryBuilder(); 274f411d872SAndreas Gohr $QB->addTable($stable, 'DATA'); 2756fd73b4bSAnna Dabrowska $QB->addSelectColumn('DATA', $idColumn, strtoupper($idColumn)); 2766fd73b4bSAnna Dabrowska $QB->addGroupByStatement("DATA.$idColumn"); 277f411d872SAndreas Gohr 278f411d872SAndreas Gohr foreach($this->schema->getColumns(false) as $col) { 279f411d872SAndreas Gohr 280f411d872SAndreas Gohr $colref = $col->getColref(); 281f411d872SAndreas Gohr $colname = 'col' . $colref; 282bab52340SAndreas Gohr $outname = 'out' . $colref; 283f411d872SAndreas Gohr 284f411d872SAndreas Gohr if($col->getType()->isMulti()) { 285f411d872SAndreas Gohr $tn = 'M' . $colref; 286f411d872SAndreas Gohr $QB->addLeftJoin( 287f411d872SAndreas Gohr 'DATA', 288f411d872SAndreas Gohr $mtable, 289f411d872SAndreas Gohr $tn, 2906fd73b4bSAnna Dabrowska "DATA.$idColumn = $tn.$idColumn AND DATA.rev = $tn.rev AND $tn.colref = $colref" 291f411d872SAndreas Gohr ); 292bab52340SAndreas Gohr $col->getType()->select($QB, $tn, 'value', $outname); 293bab52340SAndreas Gohr $sel = $QB->getSelectStatement($outname); 294bab52340SAndreas Gohr $QB->addSelectStatement("GROUP_CONCAT($sel, '$sep')", $outname); 295f411d872SAndreas Gohr } else { 296bab52340SAndreas Gohr $col->getType()->select($QB, 'DATA', $colname, $outname); 297bab52340SAndreas Gohr $QB->addGroupByStatement($outname); 298f411d872SAndreas Gohr } 299f411d872SAndreas Gohr } 300f411d872SAndreas Gohr 3016fd73b4bSAnna Dabrowska $pl = $QB->addValue($this->{$idColumn}); 3026fd73b4bSAnna Dabrowska $QB->filters()->whereAnd("DATA.$idColumn = $pl"); 303897aef42SAndreas Gohr $pl = $QB->addValue($this->getLastRevisionTimestamp()); 304f411d872SAndreas Gohr $QB->filters()->whereAnd("DATA.rev = $pl"); 305f411d872SAndreas Gohr 306f411d872SAndreas Gohr return $QB->getSQL(); 307f411d872SAndreas Gohr } 308f411d872SAndreas Gohr 309f411d872SAndreas Gohr /** 31013eddb0fSAndreas Gohr * @param int $ts 31113eddb0fSAndreas Gohr */ 31213eddb0fSAndreas Gohr public function setTimestamp($ts) { 313897aef42SAndreas Gohr if($ts && $ts < $this->schema->getTimeStamp()) { 314897aef42SAndreas Gohr throw new StructException('Given timestamp is not valid for current Schema'); 315897aef42SAndreas Gohr } 316897aef42SAndreas Gohr 31713eddb0fSAndreas Gohr $this->ts = $ts; 31813eddb0fSAndreas Gohr } 31913eddb0fSAndreas Gohr 32013eddb0fSAndreas Gohr /** 32169f7ec8fSAnna Dabrowska * Returns the timestamp from the current data 32269f7ec8fSAnna Dabrowska * @return int 32369f7ec8fSAnna Dabrowska */ 32469f7ec8fSAnna Dabrowska public function getTimestamp() 32569f7ec8fSAnna Dabrowska { 32669f7ec8fSAnna Dabrowska return $this->ts; 32769f7ec8fSAnna Dabrowska } 32869f7ec8fSAnna Dabrowska 32969f7ec8fSAnna Dabrowska /** 330897aef42SAndreas Gohr * Return the last time an edit happened for this table for the currently set 331897aef42SAndreas Gohr * time and pid. When the current timestamp is 0, the newest revision is 332897aef42SAndreas Gohr * returned. Used in @see buildGetDataSQL() 333f411d872SAndreas Gohr * 334897aef42SAndreas Gohr * @return int 335f411d872SAndreas Gohr */ 336897aef42SAndreas Gohr abstract protected function getLastRevisionTimestamp(); 33787dc1344SAndreas Gohr 33887dc1344SAndreas Gohr /** 33987dc1344SAndreas Gohr * Check if the given data validates against the current types. 34087dc1344SAndreas Gohr * 34187dc1344SAndreas Gohr * @param array $data 34293ca6f4fSAndreas Gohr * @return AccessDataValidator 34387dc1344SAndreas Gohr */ 34487dc1344SAndreas Gohr public function getValidator($data) { 34593ca6f4fSAndreas Gohr return new AccessDataValidator($this, $data); 34687dc1344SAndreas Gohr } 347c73fba38SAnna Dabrowska 348c73fba38SAnna Dabrowska /** 349c73fba38SAnna Dabrowska * Returns true if data is of type "page" 350c73fba38SAnna Dabrowska * 351c73fba38SAnna Dabrowska * @param string $pid 352c73fba38SAnna Dabrowska * @param int $rev 353c73fba38SAnna Dabrowska * @param int $rid 354c73fba38SAnna Dabrowska * @return bool 355c73fba38SAnna Dabrowska */ 356c73fba38SAnna Dabrowska public static function isTypePage($pid, $rev, $rid) 357c73fba38SAnna Dabrowska { 358c73fba38SAnna Dabrowska return $rev > 0; 359c73fba38SAnna Dabrowska } 360c73fba38SAnna Dabrowska 361c73fba38SAnna Dabrowska /** 362c73fba38SAnna Dabrowska * Returns true if data is of type "lookup" 363c73fba38SAnna Dabrowska * 364c73fba38SAnna Dabrowska * @param string $pid 365c73fba38SAnna Dabrowska * @param int $rev 366c73fba38SAnna Dabrowska * @param int $rid 367c73fba38SAnna Dabrowska * @return bool 368c73fba38SAnna Dabrowska */ 369c73fba38SAnna Dabrowska public static function isTypeLookup($pid, $rev, $rid) 370c73fba38SAnna Dabrowska { 371c73fba38SAnna Dabrowska return $pid === ''; 372c73fba38SAnna Dabrowska } 373c73fba38SAnna Dabrowska 374c73fba38SAnna Dabrowska /** 375c73fba38SAnna Dabrowska * Returns true if data is of type "serial" 376c73fba38SAnna Dabrowska * 377c73fba38SAnna Dabrowska * @param string $pid 378c73fba38SAnna Dabrowska * @param int $rev 379c73fba38SAnna Dabrowska * @param int $rid 380c73fba38SAnna Dabrowska * @return bool 381c73fba38SAnna Dabrowska */ 382c73fba38SAnna Dabrowska public static function isTypeSerial($pid, $rev, $rid) 383c73fba38SAnna Dabrowska { 384c73fba38SAnna Dabrowska return $pid !== '' && $rev === 0; 385c73fba38SAnna Dabrowska } 386f411d872SAndreas Gohr} 387f411d872SAndreas Gohr 388f411d872SAndreas Gohr 389