1f411d872SAndreas Gohr<?php 2f411d872SAndreas Gohr 3f411d872SAndreas Gohrnamespace dokuwiki\plugin\struct\meta; 4f411d872SAndreas Gohr 5*c73fba38SAnna Dabrowska/** 6*c73fba38SAnna Dabrowska * Class AccessTable 7*c73fba38SAnna Dabrowska * 8*c73fba38SAnna Dabrowska * Base class for data accessors 9*c73fba38SAnna Dabrowska * 10*c73fba38SAnna Dabrowska * @package dokuwiki\plugin\struct\meta 11*c73fba38SAnna Dabrowska */ 12f411d872SAndreas Gohrabstract class AccessTable { 13f411d872SAndreas Gohr 14f411d872SAndreas Gohr /** @var Schema */ 15f411d872SAndreas Gohr protected $schema; 16f411d872SAndreas Gohr protected $pid; 170ceefd5cSAnna Dabrowska protected $rid; 18f411d872SAndreas Gohr protected $labels = array(); 19f411d872SAndreas Gohr protected $ts = 0; 20f411d872SAndreas Gohr /** @var \helper_plugin_sqlite */ 21f411d872SAndreas Gohr protected $sqlite; 22f411d872SAndreas Gohr 2390421550SAndreas Gohr // options on how to retrieve data 24f411d872SAndreas Gohr protected $opt_skipempty = false; 25f411d872SAndreas Gohr 26f411d872SAndreas Gohr /** 27b9d35ff2SAnna Dabrowska * Factory method returning the appropriate data accessor (page, lookup or serial) 28f411d872SAndreas Gohr * 29f411d872SAndreas Gohr * @param Schema $schema schema to load 3086a40c1eSAnna Dabrowska * @param string $pid Page id to access 31897aef42SAndreas Gohr * @param int $ts Time at which the data should be read or written, 0 for now 3286a40c1eSAnna Dabrowska * @param int $rid Row id, 0 for page type data, otherwise autoincrement 33b9d35ff2SAnna Dabrowska * @return AccessTableData|AccessTableLookup|AccessTableSerial 34f411d872SAndreas Gohr */ 350ceefd5cSAnna Dabrowska public static function bySchema(Schema $schema, $pid, $ts = 0, $rid = 0) { 36*c73fba38SAnna Dabrowska if (self::isTypeLookup($pid, $ts, $rid)) { 370ceefd5cSAnna Dabrowska return new AccessTableLookup($schema, $pid, $ts, $rid); 38f411d872SAndreas Gohr } 39*c73fba38SAnna Dabrowska if (self::isTypeSerial($pid, $ts, $rid)) { 40b9d35ff2SAnna Dabrowska return new AccessTableSerial($schema, $pid, $ts, $rid); 41b9d35ff2SAnna Dabrowska } 420ceefd5cSAnna Dabrowska return new AccessTableData($schema, $pid, $ts, $rid); 43f411d872SAndreas Gohr } 44f411d872SAndreas Gohr 45f411d872SAndreas Gohr /** 46*c73fba38SAnna Dabrowska * Factory Method to access data 47f411d872SAndreas Gohr * 48f411d872SAndreas Gohr * @param string $tablename schema to load 4986a40c1eSAnna Dabrowska * @param string $pid Page id to access 50897aef42SAndreas Gohr * @param int $ts Time at which the data should be read or written, 0 for now 5186a40c1eSAnna Dabrowska * @param int $rid Row id, 0 for page type data, otherwise autoincrement 52*c73fba38SAnna Dabrowska * @return AccessTableData|AccessTableLookup|AccessTableSerial 53f411d872SAndreas Gohr */ 540ceefd5cSAnna Dabrowska public static function byTableName($tablename, $pid, $ts = 0, $rid = 0) { 55f411d872SAndreas Gohr $schema = new Schema($tablename, $ts); 560ceefd5cSAnna Dabrowska return self::bySchema($schema, $pid, $ts, $rid); 57f411d872SAndreas Gohr } 58f411d872SAndreas Gohr 59f411d872SAndreas Gohr /** 60f411d872SAndreas Gohr * AccessTable constructor 61f411d872SAndreas Gohr * 62897aef42SAndreas Gohr * @param Schema $schema The schema valid at $ts 6386a40c1eSAnna Dabrowska * @param string $pid Page id 64897aef42SAndreas Gohr * @param int $ts Time at which the data should be read or written, 0 for now 650ceefd5cSAnna Dabrowska * @param int $rid Row id: 0 for pages, autoincremented for other types 66f411d872SAndreas Gohr */ 670ceefd5cSAnna Dabrowska public function __construct(Schema $schema, $pid, $ts = 0, $rid = 0) { 68f411d872SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 69f411d872SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 70f411d872SAndreas Gohr $this->sqlite = $helper->getDB(); 71f411d872SAndreas Gohr 72f411d872SAndreas Gohr if(!$schema->getId()) { 73f411d872SAndreas Gohr throw new StructException('Schema does not exist. Only data of existing schemas can be accessed'); 74f411d872SAndreas Gohr } 75f411d872SAndreas Gohr 76f411d872SAndreas Gohr $this->schema = $schema; 77f411d872SAndreas Gohr $this->pid = $pid; 780ceefd5cSAnna Dabrowska $this->rid = $rid; 79897aef42SAndreas Gohr $this->setTimestamp($ts); 80f411d872SAndreas Gohr foreach($this->schema->getColumns() as $col) { 81f411d872SAndreas Gohr $this->labels[$col->getColref()] = $col->getType()->getLabel(); 82f411d872SAndreas Gohr } 83f411d872SAndreas Gohr } 84f411d872SAndreas Gohr 85f411d872SAndreas Gohr /** 86f411d872SAndreas Gohr * gives access to the schema 87f411d872SAndreas Gohr * 88f411d872SAndreas Gohr * @return Schema 89f411d872SAndreas Gohr */ 90f411d872SAndreas Gohr public function getSchema() { 91f411d872SAndreas Gohr return $this->schema; 92f411d872SAndreas Gohr } 93f411d872SAndreas Gohr 94f411d872SAndreas Gohr /** 95f107f479SAndreas Gohr * The current pid 96f107f479SAndreas Gohr * 9786a40c1eSAnna Dabrowska * @return string 98f107f479SAndreas Gohr */ 99f107f479SAndreas Gohr public function getPid() { 100f107f479SAndreas Gohr return $this->pid; 101f107f479SAndreas Gohr } 102f107f479SAndreas Gohr 103f107f479SAndreas Gohr /** 1040ceefd5cSAnna Dabrowska * The current rid 1050ceefd5cSAnna Dabrowska * 10686a40c1eSAnna Dabrowska * @return int 1070ceefd5cSAnna Dabrowska */ 1080ceefd5cSAnna Dabrowska public function getRid() { 1090ceefd5cSAnna Dabrowska return $this->rid; 1100ceefd5cSAnna Dabrowska } 1110ceefd5cSAnna Dabrowska 1120ceefd5cSAnna Dabrowska /** 113f411d872SAndreas Gohr * Should remove the current data, by either deleting or ovewriting it 114f411d872SAndreas Gohr * 115f411d872SAndreas Gohr * @return bool if the delete succeeded 116f411d872SAndreas Gohr */ 117f411d872SAndreas Gohr abstract public function clearData(); 118f411d872SAndreas Gohr 119f411d872SAndreas Gohr /** 120f411d872SAndreas Gohr * Save the data to the database. 121f411d872SAndreas Gohr * 122f411d872SAndreas Gohr * We differentiate between single-value-column and multi-value-column by the value to the respective column-name, 123f411d872SAndreas Gohr * i.e. depending on if that is a string or an array, respectively. 124f411d872SAndreas Gohr * 125f411d872SAndreas Gohr * @param array $data typelabel => value for single fields or typelabel => array(value, value, ...) for multi fields 126f411d872SAndreas Gohr * @return bool success of saving the data to the database 127f411d872SAndreas Gohr */ 128f411d872SAndreas Gohr abstract public function saveData($data); 129f411d872SAndreas Gohr 130f411d872SAndreas Gohr /** 131f411d872SAndreas Gohr * Should empty or invisible (inpage) fields be returned? 132f411d872SAndreas Gohr * 133f411d872SAndreas Gohr * Defaults to false 134f411d872SAndreas Gohr * 135f411d872SAndreas Gohr * @param null|bool $set new value, null to read only 136f411d872SAndreas Gohr * @return bool current value (after set) 137f411d872SAndreas Gohr */ 138f411d872SAndreas Gohr public function optionSkipEmpty($set = null) { 139f411d872SAndreas Gohr if(!is_null($set)) { 140f411d872SAndreas Gohr $this->opt_skipempty = $set; 141f411d872SAndreas Gohr } 142f411d872SAndreas Gohr return $this->opt_skipempty; 143f411d872SAndreas Gohr } 144f411d872SAndreas Gohr 145f411d872SAndreas Gohr /** 146f411d872SAndreas Gohr * Get the value of a single column 147f411d872SAndreas Gohr * 148f411d872SAndreas Gohr * @param Column $column 149f411d872SAndreas Gohr * @return Value|null 150f411d872SAndreas Gohr */ 151f411d872SAndreas Gohr public function getDataColumn($column) { 152f411d872SAndreas Gohr $data = $this->getData(); 153f411d872SAndreas Gohr foreach($data as $value) { 154f411d872SAndreas Gohr if($value->getColumn() == $column) { 155f411d872SAndreas Gohr return $value; 156f411d872SAndreas Gohr } 157f411d872SAndreas Gohr } 158f411d872SAndreas Gohr return null; 159f411d872SAndreas Gohr } 160f411d872SAndreas Gohr 161f411d872SAndreas Gohr /** 162f411d872SAndreas Gohr * returns the data saved for the page 163f411d872SAndreas Gohr * 164f411d872SAndreas Gohr * @return Value[] a list of values saved for the current page 165f411d872SAndreas Gohr */ 166f411d872SAndreas Gohr public function getData() { 167f411d872SAndreas Gohr $data = $this->getDataFromDB(); 168f411d872SAndreas Gohr $data = $this->consolidateData($data, false); 169f411d872SAndreas Gohr return $data; 170f411d872SAndreas Gohr } 171f411d872SAndreas Gohr 172f411d872SAndreas Gohr /** 173f411d872SAndreas Gohr * returns the data saved for the page as associative array 174f411d872SAndreas Gohr * 175f411d872SAndreas Gohr * The array returned is in the same format as used in @see saveData() 176f411d872SAndreas Gohr * 17790421550SAndreas Gohr * It always returns raw Values! 17890421550SAndreas Gohr * 179f411d872SAndreas Gohr * @return array 180f411d872SAndreas Gohr */ 181f411d872SAndreas Gohr public function getDataArray() { 182f411d872SAndreas Gohr $data = $this->getDataFromDB(); 183f411d872SAndreas Gohr $data = $this->consolidateData($data, true); 184f411d872SAndreas Gohr return $data; 185f411d872SAndreas Gohr } 186f411d872SAndreas Gohr 187f411d872SAndreas Gohr /** 188f411d872SAndreas Gohr * Return the data in pseudo syntax 189f411d872SAndreas Gohr */ 190f411d872SAndreas Gohr public function getDataPseudoSyntax() { 191f411d872SAndreas Gohr $result = ''; 192a0a1d14eSAndreas Gohr $data = $this->getData(); 193a0a1d14eSAndreas Gohr 194a0a1d14eSAndreas Gohr foreach($data as $value) { 195a0a1d14eSAndreas Gohr $key = $value->getColumn()->getFullQualifiedLabel(); 196a0a1d14eSAndreas Gohr $value = $value->getDisplayValue(); 197f411d872SAndreas Gohr if(is_array($value)) $value = join(', ', $value); 198f411d872SAndreas Gohr $result .= sprintf("% -20s : %s\n", $key, $value); 199f411d872SAndreas Gohr } 200f411d872SAndreas Gohr return $result; 201f411d872SAndreas Gohr } 202f411d872SAndreas Gohr 203f411d872SAndreas Gohr /** 204f411d872SAndreas Gohr * retrieve the data saved for the page from the database. Usually there is no need to call this function. 205f411d872SAndreas Gohr * Call @see SchemaData::getData instead. 206f411d872SAndreas Gohr */ 207f411d872SAndreas Gohr protected function getDataFromDB() { 208f411d872SAndreas Gohr list($sql, $opt) = $this->buildGetDataSQL(); 209f411d872SAndreas Gohr 210f411d872SAndreas Gohr $res = $this->sqlite->query($sql, $opt); 211f411d872SAndreas Gohr $data = $this->sqlite->res2arr($res); 2129c00b26cSAndreas Gohr $this->sqlite->res_close($res); 213f411d872SAndreas Gohr return $data; 214f411d872SAndreas Gohr } 215f411d872SAndreas Gohr 216f411d872SAndreas Gohr /** 217f411d872SAndreas Gohr * Creates a proper result array from the database data 218f411d872SAndreas Gohr * 219f411d872SAndreas Gohr * @param array $DBdata the data as it is retrieved from the database, i.e. by SchemaData::getDataFromDB 220f411d872SAndreas Gohr * @param bool $asarray return data as associative array (true) or as array of Values (false) 221f411d872SAndreas Gohr * @return array|Value[] 222f411d872SAndreas Gohr */ 223f411d872SAndreas Gohr protected function consolidateData($DBdata, $asarray = false) { 224f411d872SAndreas Gohr $data = array(); 225f411d872SAndreas Gohr 226f411d872SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 227f411d872SAndreas Gohr 228f411d872SAndreas Gohr foreach($this->schema->getColumns(false) as $col) { 229f411d872SAndreas Gohr 23090421550SAndreas Gohr // if no data saved yet, return empty strings 231f411d872SAndreas Gohr if($DBdata) { 232bab52340SAndreas Gohr $val = $DBdata[0]['out' . $col->getColref()]; 233f411d872SAndreas Gohr } else { 234f411d872SAndreas Gohr $val = ''; 235f411d872SAndreas Gohr } 236f411d872SAndreas Gohr 237f411d872SAndreas Gohr // multi val data is concatenated 238f411d872SAndreas Gohr if($col->isMulti()) { 239f411d872SAndreas Gohr $val = explode($sep, $val); 240f411d872SAndreas Gohr $val = array_filter($val); 241f411d872SAndreas Gohr } 242f411d872SAndreas Gohr 24390421550SAndreas Gohr $value = new Value($col, $val); 244f411d872SAndreas Gohr 24590421550SAndreas Gohr if($this->opt_skipempty && $value->isEmpty()) continue; 24690421550SAndreas Gohr if($this->opt_skipempty && !$col->isVisibleInPage()) continue; //FIXME is this a correct assumption? 24790421550SAndreas Gohr 24890421550SAndreas Gohr // for arrays, we return the raw value only 249f411d872SAndreas Gohr if($asarray) { 25090421550SAndreas Gohr $data[$col->getLabel()] = $value->getRawValue(); 251f411d872SAndreas Gohr } else { 2526e54daafSMichael Große $data[$col->getLabel()] = $value; 253f411d872SAndreas Gohr } 254f411d872SAndreas Gohr } 255f411d872SAndreas Gohr 256f411d872SAndreas Gohr return $data; 257f411d872SAndreas Gohr } 258f411d872SAndreas Gohr 259f411d872SAndreas Gohr /** 260f411d872SAndreas Gohr * Builds the SQL statement to select the data for this page and schema 261f411d872SAndreas Gohr * 262f411d872SAndreas Gohr * @return array Two fields: the SQL string and the parameters array 263f411d872SAndreas Gohr */ 2646fd73b4bSAnna Dabrowska protected function buildGetDataSQL($idColumn = 'pid') { 265f411d872SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 266f411d872SAndreas Gohr $stable = 'data_' . $this->schema->getTable(); 267f411d872SAndreas Gohr $mtable = 'multi_' . $this->schema->getTable(); 268f411d872SAndreas Gohr 269f411d872SAndreas Gohr $QB = new QueryBuilder(); 270f411d872SAndreas Gohr $QB->addTable($stable, 'DATA'); 2716fd73b4bSAnna Dabrowska $QB->addSelectColumn('DATA', $idColumn, strtoupper($idColumn)); 2726fd73b4bSAnna Dabrowska $QB->addGroupByStatement("DATA.$idColumn"); 273f411d872SAndreas Gohr 274f411d872SAndreas Gohr foreach($this->schema->getColumns(false) as $col) { 275f411d872SAndreas Gohr 276f411d872SAndreas Gohr $colref = $col->getColref(); 277f411d872SAndreas Gohr $colname = 'col' . $colref; 278bab52340SAndreas Gohr $outname = 'out' . $colref; 279f411d872SAndreas Gohr 280f411d872SAndreas Gohr if($col->getType()->isMulti()) { 281f411d872SAndreas Gohr $tn = 'M' . $colref; 282f411d872SAndreas Gohr $QB->addLeftJoin( 283f411d872SAndreas Gohr 'DATA', 284f411d872SAndreas Gohr $mtable, 285f411d872SAndreas Gohr $tn, 2866fd73b4bSAnna Dabrowska "DATA.$idColumn = $tn.$idColumn AND DATA.rev = $tn.rev AND $tn.colref = $colref" 287f411d872SAndreas Gohr ); 288bab52340SAndreas Gohr $col->getType()->select($QB, $tn, 'value', $outname); 289bab52340SAndreas Gohr $sel = $QB->getSelectStatement($outname); 290bab52340SAndreas Gohr $QB->addSelectStatement("GROUP_CONCAT($sel, '$sep')", $outname); 291f411d872SAndreas Gohr } else { 292bab52340SAndreas Gohr $col->getType()->select($QB, 'DATA', $colname, $outname); 293bab52340SAndreas Gohr $QB->addGroupByStatement($outname); 294f411d872SAndreas Gohr } 295f411d872SAndreas Gohr } 296f411d872SAndreas Gohr 2976fd73b4bSAnna Dabrowska $pl = $QB->addValue($this->{$idColumn}); 2986fd73b4bSAnna Dabrowska $QB->filters()->whereAnd("DATA.$idColumn = $pl"); 299897aef42SAndreas Gohr $pl = $QB->addValue($this->getLastRevisionTimestamp()); 300f411d872SAndreas Gohr $QB->filters()->whereAnd("DATA.rev = $pl"); 301f411d872SAndreas Gohr 302f411d872SAndreas Gohr return $QB->getSQL(); 303f411d872SAndreas Gohr } 304f411d872SAndreas Gohr 305f411d872SAndreas Gohr /** 30613eddb0fSAndreas Gohr * @param int $ts 30713eddb0fSAndreas Gohr */ 30813eddb0fSAndreas Gohr public function setTimestamp($ts) { 309897aef42SAndreas Gohr if($ts && $ts < $this->schema->getTimeStamp()) { 310897aef42SAndreas Gohr throw new StructException('Given timestamp is not valid for current Schema'); 311897aef42SAndreas Gohr } 312897aef42SAndreas Gohr 31313eddb0fSAndreas Gohr $this->ts = $ts; 31413eddb0fSAndreas Gohr } 31513eddb0fSAndreas Gohr 31613eddb0fSAndreas Gohr /** 317897aef42SAndreas Gohr * Return the last time an edit happened for this table for the currently set 318897aef42SAndreas Gohr * time and pid. When the current timestamp is 0, the newest revision is 319897aef42SAndreas Gohr * returned. Used in @see buildGetDataSQL() 320f411d872SAndreas Gohr * 321897aef42SAndreas Gohr * @return int 322f411d872SAndreas Gohr */ 323897aef42SAndreas Gohr abstract protected function getLastRevisionTimestamp(); 32487dc1344SAndreas Gohr 32587dc1344SAndreas Gohr /** 32687dc1344SAndreas Gohr * Check if the given data validates against the current types. 32787dc1344SAndreas Gohr * 32887dc1344SAndreas Gohr * @param array $data 32993ca6f4fSAndreas Gohr * @return AccessDataValidator 33087dc1344SAndreas Gohr */ 33187dc1344SAndreas Gohr public function getValidator($data) { 33293ca6f4fSAndreas Gohr return new AccessDataValidator($this, $data); 33387dc1344SAndreas Gohr } 334*c73fba38SAnna Dabrowska 335*c73fba38SAnna Dabrowska /** 336*c73fba38SAnna Dabrowska * Returns true if data is of type "page" 337*c73fba38SAnna Dabrowska * 338*c73fba38SAnna Dabrowska * @param string $pid 339*c73fba38SAnna Dabrowska * @param int $rev 340*c73fba38SAnna Dabrowska * @param int $rid 341*c73fba38SAnna Dabrowska * @return bool 342*c73fba38SAnna Dabrowska */ 343*c73fba38SAnna Dabrowska public static function isTypePage($pid, $rev, $rid) 344*c73fba38SAnna Dabrowska { 345*c73fba38SAnna Dabrowska return $rev > 0; 346*c73fba38SAnna Dabrowska } 347*c73fba38SAnna Dabrowska 348*c73fba38SAnna Dabrowska /** 349*c73fba38SAnna Dabrowska * Returns true if data is of type "lookup" 350*c73fba38SAnna Dabrowska * 351*c73fba38SAnna Dabrowska * @param string $pid 352*c73fba38SAnna Dabrowska * @param int $rev 353*c73fba38SAnna Dabrowska * @param int $rid 354*c73fba38SAnna Dabrowska * @return bool 355*c73fba38SAnna Dabrowska */ 356*c73fba38SAnna Dabrowska public static function isTypeLookup($pid, $rev, $rid) 357*c73fba38SAnna Dabrowska { 358*c73fba38SAnna Dabrowska return $pid === ''; 359*c73fba38SAnna Dabrowska } 360*c73fba38SAnna Dabrowska 361*c73fba38SAnna Dabrowska /** 362*c73fba38SAnna Dabrowska * Returns true if data is of type "serial" 363*c73fba38SAnna Dabrowska * 364*c73fba38SAnna Dabrowska * @param string $pid 365*c73fba38SAnna Dabrowska * @param int $rev 366*c73fba38SAnna Dabrowska * @param int $rid 367*c73fba38SAnna Dabrowska * @return bool 368*c73fba38SAnna Dabrowska */ 369*c73fba38SAnna Dabrowska public static function isTypeSerial($pid, $rev, $rid) 370*c73fba38SAnna Dabrowska { 371*c73fba38SAnna Dabrowska return $pid !== '' && $rev === 0; 372*c73fba38SAnna Dabrowska } 373f411d872SAndreas Gohr} 374f411d872SAndreas Gohr 375f411d872SAndreas Gohr 376