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 */ 12d6d97f60SAnna Dabrowskaabstract class AccessTable 13d6d97f60SAnna Dabrowska{ 1417a3a578SAndreas Gohr public const DEFAULT_REV = 0; 1517a3a578SAndreas Gohr public const DEFAULT_LATEST = 1; 16efe74305SAnna Dabrowska 17f411d872SAndreas Gohr /** @var Schema */ 18f411d872SAndreas Gohr protected $schema; 19f411d872SAndreas Gohr protected $pid; 200ceefd5cSAnna Dabrowska protected $rid; 21aeb8444cSAnna Dabrowska protected $labels = []; 22f411d872SAndreas Gohr protected $ts = 0; 23da62ec9cSAnna Dabrowska protected $published; 24f411d872SAndreas Gohr /** @var \helper_plugin_sqlite */ 25f411d872SAndreas Gohr protected $sqlite; 26f411d872SAndreas Gohr 2790421550SAndreas Gohr // options on how to retrieve data 28f411d872SAndreas Gohr protected $opt_skipempty = false; 29f411d872SAndreas Gohr 30d680cb37SAnna Dabrowska protected $optQueries = []; 31d680cb37SAnna Dabrowska 32f411d872SAndreas Gohr /** 33aeb8444cSAnna Dabrowska * @var string Name of single-value table 34aeb8444cSAnna Dabrowska */ 35aeb8444cSAnna Dabrowska protected $stable; 36aeb8444cSAnna Dabrowska 37aeb8444cSAnna Dabrowska /** 38aeb8444cSAnna Dabrowska * @var string Name of multi-value table 39aeb8444cSAnna Dabrowska */ 40aeb8444cSAnna Dabrowska protected $mtable; 41aeb8444cSAnna Dabrowska 42aeb8444cSAnna Dabrowska /** 43aeb8444cSAnna Dabrowska * @var array Column names for the single-value insert/update 44aeb8444cSAnna Dabrowska */ 45aeb8444cSAnna Dabrowska protected $singleCols; 46aeb8444cSAnna Dabrowska 47aeb8444cSAnna Dabrowska /** 48aeb8444cSAnna Dabrowska * @var array Input values for the single-value insert/update 49aeb8444cSAnna Dabrowska */ 50aeb8444cSAnna Dabrowska protected $singleValues; 51aeb8444cSAnna Dabrowska 52aeb8444cSAnna Dabrowska /** 53aeb8444cSAnna Dabrowska * @var array Input values for the multi-value inserts/updates 54aeb8444cSAnna Dabrowska */ 55aeb8444cSAnna Dabrowska protected $multiValues; 56aeb8444cSAnna Dabrowska 574cd5cc28SAnna Dabrowska public static function getPageAccess($tablename, $pid, $ts = 0) 584cd5cc28SAnna Dabrowska { 594cd5cc28SAnna Dabrowska $schema = new Schema($tablename, $ts); 60308cc83fSAndreas Gohr return new AccessTablePage($schema, $pid, $ts, 0); 614cd5cc28SAnna Dabrowska } 624cd5cc28SAnna Dabrowska 634cd5cc28SAnna Dabrowska public static function getSerialAccess($tablename, $pid, $rid = 0) 644cd5cc28SAnna Dabrowska { 654cd5cc28SAnna Dabrowska $schema = new Schema($tablename, 0); 664cd5cc28SAnna Dabrowska return new AccessTableSerial($schema, $pid, 0, $rid); 674cd5cc28SAnna Dabrowska } 684cd5cc28SAnna Dabrowska 69308cc83fSAndreas Gohr public static function getGlobalAccess($tablename, $rid = 0) 704cd5cc28SAnna Dabrowska { 714cd5cc28SAnna Dabrowska $schema = new Schema($tablename, 0); 72308cc83fSAndreas Gohr return new AccessTableGlobal($schema, '', 0, $rid); 734cd5cc28SAnna Dabrowska } 74aeb8444cSAnna Dabrowska 75aeb8444cSAnna Dabrowska /** 76308cc83fSAndreas Gohr * Factory method returning the appropriate data accessor (page, global or serial) 77f411d872SAndreas Gohr * 78f411d872SAndreas Gohr * @param Schema $schema schema to load 7986a40c1eSAnna Dabrowska * @param string $pid Page id to access 80a14cf85dSAnna Dabrowska * @param int $ts Time at which the data should be read or written 8186a40c1eSAnna Dabrowska * @param int $rid Row id, 0 for page type data, otherwise autoincrement 82308cc83fSAndreas Gohr * @return AccessTablePage|AccessTableGlobal 83308cc83fSAndreas Gohr * @deprecated 84f411d872SAndreas Gohr */ 85d6d97f60SAnna Dabrowska public static function bySchema(Schema $schema, $pid, $ts = 0, $rid = 0) 86d6d97f60SAnna Dabrowska { 874cd5cc28SAnna Dabrowska if (self::isTypePage($pid, $ts)) { 88308cc83fSAndreas Gohr return new AccessTablePage($schema, $pid, $ts, $rid); 89f411d872SAndreas Gohr } 90308cc83fSAndreas Gohr return new AccessTableGlobal($schema, $pid, $ts, $rid); 91aeb8444cSAnna Dabrowska } 92f411d872SAndreas Gohr 93f411d872SAndreas Gohr /** 94c73fba38SAnna Dabrowska * Factory Method to access data 95f411d872SAndreas Gohr * 96f411d872SAndreas Gohr * @param string $tablename schema to load 9786a40c1eSAnna Dabrowska * @param string $pid Page id to access 98a14cf85dSAnna Dabrowska * @param int $ts Time at which the data should be read or written 9986a40c1eSAnna Dabrowska * @param int $rid Row id, 0 for page type data, otherwise autoincrement 100308cc83fSAndreas Gohr * @return AccessTablePage|AccessTableGlobal 101308cc83fSAndreas Gohr * @deprecated Use specific methods since we can no longer 102308cc83fSAndreas Gohr * guarantee instantiating the required descendant class 103f411d872SAndreas Gohr */ 104d6d97f60SAnna Dabrowska public static function byTableName($tablename, $pid, $ts = 0, $rid = 0) 105d6d97f60SAnna Dabrowska { 106984b4e7bSAnna Dabrowska // force loading the latest schema for anything other than page data, 107984b4e7bSAnna Dabrowska // for which we might actually need the history 1084cd5cc28SAnna Dabrowska if (!self::isTypePage($pid, $ts)) { 109984b4e7bSAnna Dabrowska $schema = new Schema($tablename, time()); 110984b4e7bSAnna Dabrowska } else { 111f411d872SAndreas Gohr $schema = new Schema($tablename, $ts); 112984b4e7bSAnna Dabrowska } 1130ceefd5cSAnna Dabrowska return self::bySchema($schema, $pid, $ts, $rid); 114f411d872SAndreas Gohr } 115f411d872SAndreas Gohr 116f411d872SAndreas Gohr /** 117f411d872SAndreas Gohr * AccessTable constructor 118f411d872SAndreas Gohr * 119897aef42SAndreas Gohr * @param Schema $schema The schema valid at $ts 12086a40c1eSAnna Dabrowska * @param string $pid Page id 121897aef42SAndreas Gohr * @param int $ts Time at which the data should be read or written, 0 for now 1220ceefd5cSAnna Dabrowska * @param int $rid Row id: 0 for pages, autoincremented for other types 123f411d872SAndreas Gohr */ 1244cd5cc28SAnna Dabrowska public function __construct($schema, $pid, $ts = 0, $rid = 0) 125d6d97f60SAnna Dabrowska { 126f411d872SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 127f411d872SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 128f411d872SAndreas Gohr $this->sqlite = $helper->getDB(); 129f411d872SAndreas Gohr 130f411d872SAndreas Gohr if (!$schema->getId()) { 131f411d872SAndreas Gohr throw new StructException('Schema does not exist. Only data of existing schemas can be accessed'); 132f411d872SAndreas Gohr } 133f411d872SAndreas Gohr 134f411d872SAndreas Gohr $this->schema = $schema; 135f411d872SAndreas Gohr $this->pid = $pid; 1360ceefd5cSAnna Dabrowska $this->rid = $rid; 137897aef42SAndreas Gohr $this->setTimestamp($ts); 138f411d872SAndreas Gohr foreach ($this->schema->getColumns() as $col) { 139f411d872SAndreas Gohr $this->labels[$col->getColref()] = $col->getType()->getLabel(); 140f411d872SAndreas Gohr } 141f411d872SAndreas Gohr } 142f411d872SAndreas Gohr 143f411d872SAndreas Gohr /** 144f411d872SAndreas Gohr * gives access to the schema 145f411d872SAndreas Gohr * 146f411d872SAndreas Gohr * @return Schema 147f411d872SAndreas Gohr */ 148d6d97f60SAnna Dabrowska public function getSchema() 149d6d97f60SAnna Dabrowska { 150f411d872SAndreas Gohr return $this->schema; 151f411d872SAndreas Gohr } 152f411d872SAndreas Gohr 153f411d872SAndreas Gohr /** 154f107f479SAndreas Gohr * The current pid 155f107f479SAndreas Gohr * 15686a40c1eSAnna Dabrowska * @return string 157f107f479SAndreas Gohr */ 158d6d97f60SAnna Dabrowska public function getPid() 159d6d97f60SAnna Dabrowska { 160f107f479SAndreas Gohr return $this->pid; 161f107f479SAndreas Gohr } 162f107f479SAndreas Gohr 163f107f479SAndreas Gohr /** 1640ceefd5cSAnna Dabrowska * The current rid 1650ceefd5cSAnna Dabrowska * 16686a40c1eSAnna Dabrowska * @return int 1670ceefd5cSAnna Dabrowska */ 168d6d97f60SAnna Dabrowska public function getRid() 169d6d97f60SAnna Dabrowska { 1700ceefd5cSAnna Dabrowska return $this->rid; 1710ceefd5cSAnna Dabrowska } 1720ceefd5cSAnna Dabrowska 1730ceefd5cSAnna Dabrowska /** 174da62ec9cSAnna Dabrowska * Published status 175da62ec9cSAnna Dabrowska * 176da62ec9cSAnna Dabrowska * @return int|null 177da62ec9cSAnna Dabrowska */ 178da62ec9cSAnna Dabrowska public function getPublished() 179da62ec9cSAnna Dabrowska { 180da62ec9cSAnna Dabrowska return $this->published; 181da62ec9cSAnna Dabrowska } 182da62ec9cSAnna Dabrowska 183da62ec9cSAnna Dabrowska /** 184f411d872SAndreas Gohr * Should remove the current data, by either deleting or ovewriting it 185f411d872SAndreas Gohr * 186f411d872SAndreas Gohr * @return bool if the delete succeeded 187f411d872SAndreas Gohr */ 188f411d872SAndreas Gohr abstract public function clearData(); 189f411d872SAndreas Gohr 190f411d872SAndreas Gohr /** 191f411d872SAndreas Gohr * Save the data to the database. 192f411d872SAndreas Gohr * 193f411d872SAndreas Gohr * We differentiate between single-value-column and multi-value-column by the value to the respective column-name, 194f411d872SAndreas Gohr * i.e. depending on if that is a string or an array, respectively. 195f411d872SAndreas Gohr * 196f411d872SAndreas Gohr * @param array $data typelabel => value for single fields or typelabel => array(value, value, ...) for multi fields 197f411d872SAndreas Gohr * @return bool success of saving the data to the database 198f411d872SAndreas Gohr */ 199aeb8444cSAnna Dabrowska public function saveData($data) 200aeb8444cSAnna Dabrowska { 201aeb8444cSAnna Dabrowska if (!$this->validateTypeData($data)) { 202aeb8444cSAnna Dabrowska return false; 203aeb8444cSAnna Dabrowska } 204aeb8444cSAnna Dabrowska 205aeb8444cSAnna Dabrowska $this->stable = 'data_' . $this->schema->getTable(); 206aeb8444cSAnna Dabrowska $this->mtable = 'multi_' . $this->schema->getTable(); 207aeb8444cSAnna Dabrowska 208aeb8444cSAnna Dabrowska $colrefs = array_flip($this->labels); 209aeb8444cSAnna Dabrowska 210aeb8444cSAnna Dabrowska foreach ($data as $colname => $value) { 211aeb8444cSAnna Dabrowska if (!isset($colrefs[$colname])) { 212aeb8444cSAnna Dabrowska throw new StructException("Unknown column %s in schema.", hsc($colname)); 213aeb8444cSAnna Dabrowska } 214aeb8444cSAnna Dabrowska 215aeb8444cSAnna Dabrowska $this->singleCols[] = 'col' . $colrefs[$colname]; 216aeb8444cSAnna Dabrowska if (is_array($value)) { 217aeb8444cSAnna Dabrowska foreach ($value as $index => $multivalue) { 218aeb8444cSAnna Dabrowska $this->multiValues[] = [$colrefs[$colname], $index + 1, $multivalue]; 219aeb8444cSAnna Dabrowska } 220aeb8444cSAnna Dabrowska // copy first value to the single column 221aeb8444cSAnna Dabrowska if (isset($value[0])) { 222aeb8444cSAnna Dabrowska $this->singleValues[] = $value[0]; 223f6cf0d85SAnna Dabrowska if ($value[0] === '') { 224d680cb37SAnna Dabrowska $this->handleEmptyMulti($this->pid, $this->rid, $colrefs[$colname]); 225d680cb37SAnna Dabrowska } 226aeb8444cSAnna Dabrowska } else { 227aeb8444cSAnna Dabrowska $this->singleValues[] = null; 228aeb8444cSAnna Dabrowska } 229aeb8444cSAnna Dabrowska } else { 230aeb8444cSAnna Dabrowska $this->singleValues[] = $value; 231aeb8444cSAnna Dabrowska } 232aeb8444cSAnna Dabrowska } 233aeb8444cSAnna Dabrowska 234aeb8444cSAnna Dabrowska $this->sqlite->query('BEGIN TRANSACTION'); 235aeb8444cSAnna Dabrowska 236aeb8444cSAnna Dabrowska $ok = $this->beforeSave(); 237aeb8444cSAnna Dabrowska 238aeb8444cSAnna Dabrowska // insert single values 239aeb8444cSAnna Dabrowska $ok = $ok && $this->sqlite->query( 240aeb8444cSAnna Dabrowska $this->getSingleSql(), 241aeb8444cSAnna Dabrowska array_merge($this->getSingleNoninputValues(), $this->singleValues) 242aeb8444cSAnna Dabrowska ); 243aeb8444cSAnna Dabrowska 244aeb8444cSAnna Dabrowska $ok = $ok && $this->afterSingleSave(); 245aeb8444cSAnna Dabrowska 246aeb8444cSAnna Dabrowska // insert multi values 247aeb8444cSAnna Dabrowska if ($ok && $this->multiValues) { 248aeb8444cSAnna Dabrowska $multisql = $this->getMultiSql(); 249aeb8444cSAnna Dabrowska $multiNoninputValues = $this->getMultiNoninputValues(); 250aeb8444cSAnna Dabrowska foreach ($this->multiValues as $value) { 251aeb8444cSAnna Dabrowska $ok = $ok && $this->sqlite->query( 252aeb8444cSAnna Dabrowska $multisql, 253aeb8444cSAnna Dabrowska array_merge($multiNoninputValues, $value) 254aeb8444cSAnna Dabrowska ); 255aeb8444cSAnna Dabrowska } 256aeb8444cSAnna Dabrowska } 257aeb8444cSAnna Dabrowska 258d680cb37SAnna Dabrowska $ok = $ok && $this->afterSave(); 259d680cb37SAnna Dabrowska 260aeb8444cSAnna Dabrowska if (!$ok) { 261aeb8444cSAnna Dabrowska $this->sqlite->query('ROLLBACK TRANSACTION'); 262aeb8444cSAnna Dabrowska return false; 263aeb8444cSAnna Dabrowska } 264aeb8444cSAnna Dabrowska $this->sqlite->query('COMMIT TRANSACTION'); 265aeb8444cSAnna Dabrowska return true; 266aeb8444cSAnna Dabrowska } 267aeb8444cSAnna Dabrowska 268aeb8444cSAnna Dabrowska /** 269aeb8444cSAnna Dabrowska * Check whether all required data is present 270aeb8444cSAnna Dabrowska * 271aeb8444cSAnna Dabrowska * @param array $data 272aeb8444cSAnna Dabrowska * @return bool 273aeb8444cSAnna Dabrowska */ 274aeb8444cSAnna Dabrowska abstract protected function validateTypeData($data); 275aeb8444cSAnna Dabrowska 276aeb8444cSAnna Dabrowska /** 277aeb8444cSAnna Dabrowska * Names of non-input columns to be inserted into SQL query 278aeb8444cSAnna Dabrowska * 279aeb8444cSAnna Dabrowska * @return array 280aeb8444cSAnna Dabrowska */ 281aeb8444cSAnna Dabrowska abstract protected function getSingleNoninputCols(); 282aeb8444cSAnna Dabrowska 283aeb8444cSAnna Dabrowska /** 284aeb8444cSAnna Dabrowska * Values for non-input columns to be inserted into SQL query 285aeb8444cSAnna Dabrowska * for single-value tables 286aeb8444cSAnna Dabrowska * 287aeb8444cSAnna Dabrowska * @return array 288aeb8444cSAnna Dabrowska */ 289aeb8444cSAnna Dabrowska abstract protected function getSingleNoninputValues(); 290aeb8444cSAnna Dabrowska 291aeb8444cSAnna Dabrowska /** 292aeb8444cSAnna Dabrowska * String template for single-value table 293aeb8444cSAnna Dabrowska * 294aeb8444cSAnna Dabrowska * @return string 295aeb8444cSAnna Dabrowska */ 296aeb8444cSAnna Dabrowska protected function getSingleSql() 297aeb8444cSAnna Dabrowska { 298aeb8444cSAnna Dabrowska $cols = array_merge($this->getSingleNoninputCols(), $this->singleCols); 299*7234bfb1Ssplitbrain $cols = implode(',', $cols); 300*7234bfb1Ssplitbrain 301aeb8444cSAnna Dabrowska $vals = array_merge($this->getSingleNoninputValues(), $this->singleValues); 302aeb8444cSAnna Dabrowska 303aeb8444cSAnna Dabrowska return "INSERT INTO $this->stable ($cols) VALUES (" . trim(str_repeat('?,', count($vals)), ',') . ');'; 304aeb8444cSAnna Dabrowska } 305aeb8444cSAnna Dabrowska 306aeb8444cSAnna Dabrowska /** 307aeb8444cSAnna Dabrowska * Optional operations to be executed before saving data 308aeb8444cSAnna Dabrowska * 309aeb8444cSAnna Dabrowska * @return bool False if any of the operations failed and transaction should be rolled back 310aeb8444cSAnna Dabrowska */ 311aeb8444cSAnna Dabrowska protected function beforeSave() 312aeb8444cSAnna Dabrowska { 313aeb8444cSAnna Dabrowska return true; 314aeb8444cSAnna Dabrowska } 315aeb8444cSAnna Dabrowska 316aeb8444cSAnna Dabrowska /** 317aeb8444cSAnna Dabrowska * Optional operations to be executed after saving data to single-value table, 318aeb8444cSAnna Dabrowska * before saving multivalues 319aeb8444cSAnna Dabrowska * 320aeb8444cSAnna Dabrowska * @return bool False if anything goes wrong and transaction should be rolled back 321aeb8444cSAnna Dabrowska */ 322aeb8444cSAnna Dabrowska protected function afterSingleSave() 323aeb8444cSAnna Dabrowska { 324aeb8444cSAnna Dabrowska return true; 325aeb8444cSAnna Dabrowska } 326aeb8444cSAnna Dabrowska 327aeb8444cSAnna Dabrowska /** 328d680cb37SAnna Dabrowska * Executes final optional queries. 329d680cb37SAnna Dabrowska * 330d680cb37SAnna Dabrowska * @return bool False if anything goes wrong and transaction should be rolled back 331d680cb37SAnna Dabrowska */ 332d680cb37SAnna Dabrowska protected function afterSave() 333d680cb37SAnna Dabrowska { 334d680cb37SAnna Dabrowska $ok = true; 335d680cb37SAnna Dabrowska foreach ($this->optQueries as $query) { 336438a804cSAnna Dabrowska $sql = array_shift($query); 337438a804cSAnna Dabrowska $ok = $ok && $this->sqlite->query($sql, $query); 338d680cb37SAnna Dabrowska } 339d680cb37SAnna Dabrowska return $ok; 340d680cb37SAnna Dabrowska } 341d680cb37SAnna Dabrowska 342d680cb37SAnna Dabrowska /** 343aeb8444cSAnna Dabrowska * String template for multi-value table 344aeb8444cSAnna Dabrowska * 345aeb8444cSAnna Dabrowska * @return string 346aeb8444cSAnna Dabrowska */ 347aeb8444cSAnna Dabrowska abstract protected function getMultiSql(); 348aeb8444cSAnna Dabrowska 349aeb8444cSAnna Dabrowska /** 350aeb8444cSAnna Dabrowska * Values for non-input columns to be inserted into SQL query 351aeb8444cSAnna Dabrowska * for multi-value tables 352aeb8444cSAnna Dabrowska * @return array 353aeb8444cSAnna Dabrowska */ 354aeb8444cSAnna Dabrowska abstract protected function getMultiNoninputValues(); 355aeb8444cSAnna Dabrowska 356f411d872SAndreas Gohr 357f411d872SAndreas Gohr /** 358f411d872SAndreas Gohr * Should empty or invisible (inpage) fields be returned? 359f411d872SAndreas Gohr * 360f411d872SAndreas Gohr * Defaults to false 361f411d872SAndreas Gohr * 362f411d872SAndreas Gohr * @param null|bool $set new value, null to read only 363f411d872SAndreas Gohr * @return bool current value (after set) 364f411d872SAndreas Gohr */ 365d6d97f60SAnna Dabrowska public function optionSkipEmpty($set = null) 366d6d97f60SAnna Dabrowska { 367f411d872SAndreas Gohr if (!is_null($set)) { 368f411d872SAndreas Gohr $this->opt_skipempty = $set; 369f411d872SAndreas Gohr } 370f411d872SAndreas Gohr return $this->opt_skipempty; 371f411d872SAndreas Gohr } 372f411d872SAndreas Gohr 373f411d872SAndreas Gohr /** 374f411d872SAndreas Gohr * Get the value of a single column 375f411d872SAndreas Gohr * 376f411d872SAndreas Gohr * @param Column $column 377f411d872SAndreas Gohr * @return Value|null 378f411d872SAndreas Gohr */ 379d6d97f60SAnna Dabrowska public function getDataColumn($column) 380d6d97f60SAnna Dabrowska { 381f411d872SAndreas Gohr $data = $this->getData(); 382f411d872SAndreas Gohr foreach ($data as $value) { 383f411d872SAndreas Gohr if ($value->getColumn() == $column) { 384f411d872SAndreas Gohr return $value; 385f411d872SAndreas Gohr } 386f411d872SAndreas Gohr } 387f411d872SAndreas Gohr return null; 388f411d872SAndreas Gohr } 389f411d872SAndreas Gohr 390f411d872SAndreas Gohr /** 391f411d872SAndreas Gohr * returns the data saved for the page 392f411d872SAndreas Gohr * 393f411d872SAndreas Gohr * @return Value[] a list of values saved for the current page 394f411d872SAndreas Gohr */ 395d6d97f60SAnna Dabrowska public function getData() 396d6d97f60SAnna Dabrowska { 397f411d872SAndreas Gohr $data = $this->getDataFromDB(); 398f411d872SAndreas Gohr $data = $this->consolidateData($data, false); 399f411d872SAndreas Gohr return $data; 400f411d872SAndreas Gohr } 401f411d872SAndreas Gohr 402f411d872SAndreas Gohr /** 403f411d872SAndreas Gohr * returns the data saved for the page as associative array 404f411d872SAndreas Gohr * 4050549dcc5SAndreas Gohr * The array returned is in the same format as used in @return array 4060549dcc5SAndreas Gohr * @see saveData() 407f411d872SAndreas Gohr * 40890421550SAndreas Gohr * It always returns raw Values! 40990421550SAndreas Gohr * 410da62ec9cSAnna Dabrowska * @return array 411f411d872SAndreas Gohr */ 412d6d97f60SAnna Dabrowska public function getDataArray() 413d6d97f60SAnna Dabrowska { 414f411d872SAndreas Gohr $data = $this->getDataFromDB(); 415f411d872SAndreas Gohr $data = $this->consolidateData($data, true); 416f411d872SAndreas Gohr return $data; 417f411d872SAndreas Gohr } 418f411d872SAndreas Gohr 419f411d872SAndreas Gohr /** 420f411d872SAndreas Gohr * Return the data in pseudo syntax 421f411d872SAndreas Gohr */ 422d6d97f60SAnna Dabrowska public function getDataPseudoSyntax() 423d6d97f60SAnna Dabrowska { 424f411d872SAndreas Gohr $result = ''; 425a0a1d14eSAndreas Gohr $data = $this->getData(); 426a0a1d14eSAndreas Gohr 427a0a1d14eSAndreas Gohr foreach ($data as $value) { 428a0a1d14eSAndreas Gohr $key = $value->getColumn()->getFullQualifiedLabel(); 429a0a1d14eSAndreas Gohr $value = $value->getDisplayValue(); 430*7234bfb1Ssplitbrain if (is_array($value)) $value = implode(', ', $value); 431f411d872SAndreas Gohr $result .= sprintf("% -20s : %s\n", $key, $value); 432f411d872SAndreas Gohr } 433f411d872SAndreas Gohr return $result; 434f411d872SAndreas Gohr } 435f411d872SAndreas Gohr 436f411d872SAndreas Gohr /** 437f411d872SAndreas Gohr * retrieve the data saved for the page from the database. Usually there is no need to call this function. 438f411d872SAndreas Gohr * Call @see SchemaData::getData instead. 439f411d872SAndreas Gohr */ 440d6d97f60SAnna Dabrowska protected function getDataFromDB() 441d6d97f60SAnna Dabrowska { 4424cd5cc28SAnna Dabrowska $idColumn = self::isTypePage($this->pid, $this->ts) ? 'pid' : 'rid'; 443*7234bfb1Ssplitbrain [$sql, $opt] = $this->buildGetDataSQL($idColumn); 444f411d872SAndreas Gohr 44579b29326SAnna Dabrowska return $this->sqlite->queryAll($sql, $opt); 446f411d872SAndreas Gohr } 447f411d872SAndreas Gohr 448f411d872SAndreas Gohr /** 449f411d872SAndreas Gohr * Creates a proper result array from the database data 450f411d872SAndreas Gohr * 451f411d872SAndreas Gohr * @param array $DBdata the data as it is retrieved from the database, i.e. by SchemaData::getDataFromDB 452f411d872SAndreas Gohr * @param bool $asarray return data as associative array (true) or as array of Values (false) 453f411d872SAndreas Gohr * @return array|Value[] 454f411d872SAndreas Gohr */ 455d6d97f60SAnna Dabrowska protected function consolidateData($DBdata, $asarray = false) 456d6d97f60SAnna Dabrowska { 457*7234bfb1Ssplitbrain $data = []; 458f411d872SAndreas Gohr 459f411d872SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 460f411d872SAndreas Gohr 461f411d872SAndreas Gohr foreach ($this->schema->getColumns(false) as $col) { 46290421550SAndreas Gohr // if no data saved yet, return empty strings 463f411d872SAndreas Gohr if ($DBdata) { 4646a819106SAndreas Gohr $val = (string) $DBdata[0]['out' . $col->getColref()]; 465f411d872SAndreas Gohr } else { 466f411d872SAndreas Gohr $val = ''; 467f411d872SAndreas Gohr } 468f411d872SAndreas Gohr 469f411d872SAndreas Gohr // multi val data is concatenated 470f411d872SAndreas Gohr if ($col->isMulti()) { 471f411d872SAndreas Gohr $val = explode($sep, $val); 472f411d872SAndreas Gohr $val = array_filter($val); 473f411d872SAndreas Gohr } 474f411d872SAndreas Gohr 47590421550SAndreas Gohr $value = new Value($col, $val); 476f411d872SAndreas Gohr 47790421550SAndreas Gohr if ($this->opt_skipempty && $value->isEmpty()) continue; 47890421550SAndreas Gohr if ($this->opt_skipempty && !$col->isVisibleInPage()) continue; //FIXME is this a correct assumption? 47990421550SAndreas Gohr 48090421550SAndreas Gohr // for arrays, we return the raw value only 481f411d872SAndreas Gohr if ($asarray) { 48290421550SAndreas Gohr $data[$col->getLabel()] = $value->getRawValue(); 483f411d872SAndreas Gohr } else { 4846e54daafSMichael Große $data[$col->getLabel()] = $value; 485f411d872SAndreas Gohr } 486f411d872SAndreas Gohr } 487f411d872SAndreas Gohr 488f411d872SAndreas Gohr return $data; 489f411d872SAndreas Gohr } 490f411d872SAndreas Gohr 491f411d872SAndreas Gohr /** 492f411d872SAndreas Gohr * Builds the SQL statement to select the data for this page and schema 493f411d872SAndreas Gohr * 494f411d872SAndreas Gohr * @return array Two fields: the SQL string and the parameters array 495f411d872SAndreas Gohr */ 496d6d97f60SAnna Dabrowska protected function buildGetDataSQL($idColumn = 'pid') 497d6d97f60SAnna Dabrowska { 498f411d872SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 499f411d872SAndreas Gohr $stable = 'data_' . $this->schema->getTable(); 500f411d872SAndreas Gohr $mtable = 'multi_' . $this->schema->getTable(); 501f411d872SAndreas Gohr 502f411d872SAndreas Gohr $QB = new QueryBuilder(); 503f411d872SAndreas Gohr $QB->addTable($stable, 'DATA'); 5046fd73b4bSAnna Dabrowska $QB->addSelectColumn('DATA', $idColumn, strtoupper($idColumn)); 5056fd73b4bSAnna Dabrowska $QB->addGroupByStatement("DATA.$idColumn"); 506f411d872SAndreas Gohr 507f411d872SAndreas Gohr foreach ($this->schema->getColumns(false) as $col) { 508f411d872SAndreas Gohr $colref = $col->getColref(); 509f411d872SAndreas Gohr $colname = 'col' . $colref; 510bab52340SAndreas Gohr $outname = 'out' . $colref; 511f411d872SAndreas Gohr 512f411d872SAndreas Gohr if ($col->getType()->isMulti()) { 513f411d872SAndreas Gohr $tn = 'M' . $colref; 514f411d872SAndreas Gohr $QB->addLeftJoin( 515f411d872SAndreas Gohr 'DATA', 516f411d872SAndreas Gohr $mtable, 517f411d872SAndreas Gohr $tn, 5186fd73b4bSAnna Dabrowska "DATA.$idColumn = $tn.$idColumn AND DATA.rev = $tn.rev AND $tn.colref = $colref" 519f411d872SAndreas Gohr ); 520bab52340SAndreas Gohr $col->getType()->select($QB, $tn, 'value', $outname); 521bab52340SAndreas Gohr $sel = $QB->getSelectStatement($outname); 522acc82d60SAndreas Gohr $QB->addSelectStatement("GROUP_CONCAT_DISTINCT($sel, '$sep')", $outname); 523f411d872SAndreas Gohr } else { 524bab52340SAndreas Gohr $col->getType()->select($QB, 'DATA', $colname, $outname); 525bab52340SAndreas Gohr $QB->addGroupByStatement($outname); 526f411d872SAndreas Gohr } 527f411d872SAndreas Gohr } 528f411d872SAndreas Gohr 5296fd73b4bSAnna Dabrowska $pl = $QB->addValue($this->{$idColumn}); 5306fd73b4bSAnna Dabrowska $QB->filters()->whereAnd("DATA.$idColumn = $pl"); 531897aef42SAndreas Gohr $pl = $QB->addValue($this->getLastRevisionTimestamp()); 532f411d872SAndreas Gohr $QB->filters()->whereAnd("DATA.rev = $pl"); 533f411d872SAndreas Gohr 534f411d872SAndreas Gohr return $QB->getSQL(); 535f411d872SAndreas Gohr } 536f411d872SAndreas Gohr 537f411d872SAndreas Gohr /** 53813eddb0fSAndreas Gohr * @param int $ts 53913eddb0fSAndreas Gohr */ 540d6d97f60SAnna Dabrowska public function setTimestamp($ts) 541d6d97f60SAnna Dabrowska { 542897aef42SAndreas Gohr if ($ts && $ts < $this->schema->getTimeStamp()) { 543897aef42SAndreas Gohr throw new StructException('Given timestamp is not valid for current Schema'); 544897aef42SAndreas Gohr } 545897aef42SAndreas Gohr 54613eddb0fSAndreas Gohr $this->ts = $ts; 54713eddb0fSAndreas Gohr } 54813eddb0fSAndreas Gohr 54913eddb0fSAndreas Gohr /** 55069f7ec8fSAnna Dabrowska * Returns the timestamp from the current data 55169f7ec8fSAnna Dabrowska * @return int 55269f7ec8fSAnna Dabrowska */ 55369f7ec8fSAnna Dabrowska public function getTimestamp() 55469f7ec8fSAnna Dabrowska { 55569f7ec8fSAnna Dabrowska return $this->ts; 55669f7ec8fSAnna Dabrowska } 55769f7ec8fSAnna Dabrowska 55869f7ec8fSAnna Dabrowska /** 559897aef42SAndreas Gohr * Return the last time an edit happened for this table for the currently set 560da62ec9cSAnna Dabrowska * time and pid. Used in 5610549dcc5SAndreas Gohr * @see buildGetDataSQL() 562f411d872SAndreas Gohr * 563da62ec9cSAnna Dabrowska * @return int 564f411d872SAndreas Gohr */ 565897aef42SAndreas Gohr abstract protected function getLastRevisionTimestamp(); 56687dc1344SAndreas Gohr 56787dc1344SAndreas Gohr /** 56887dc1344SAndreas Gohr * Check if the given data validates against the current types. 56987dc1344SAndreas Gohr * 57087dc1344SAndreas Gohr * @param array $data 57193ca6f4fSAndreas Gohr * @return AccessDataValidator 57287dc1344SAndreas Gohr */ 573d6d97f60SAnna Dabrowska public function getValidator($data) 574d6d97f60SAnna Dabrowska { 57593ca6f4fSAndreas Gohr return new AccessDataValidator($this, $data); 57687dc1344SAndreas Gohr } 577c73fba38SAnna Dabrowska 578c73fba38SAnna Dabrowska /** 579c73fba38SAnna Dabrowska * Returns true if data is of type "page" 580c73fba38SAnna Dabrowska * 581c73fba38SAnna Dabrowska * @param string $pid 582c73fba38SAnna Dabrowska * @param int $rev 583c73fba38SAnna Dabrowska * @param int $rid 584c73fba38SAnna Dabrowska * @return bool 585c73fba38SAnna Dabrowska */ 5864cd5cc28SAnna Dabrowska public static function isTypePage($pid, $rev) 587c73fba38SAnna Dabrowska { 588c73fba38SAnna Dabrowska return $rev > 0; 589c73fba38SAnna Dabrowska } 590c73fba38SAnna Dabrowska 591c73fba38SAnna Dabrowska /** 592308cc83fSAndreas Gohr * Returns true if data is of type "global" 593c73fba38SAnna Dabrowska * 594c73fba38SAnna Dabrowska * @param string $pid 595c73fba38SAnna Dabrowska * @param int $rev 596c73fba38SAnna Dabrowska * @param int $rid 597c73fba38SAnna Dabrowska * @return bool 598c73fba38SAnna Dabrowska */ 599308cc83fSAndreas Gohr public static function isTypeGlobal($pid, $rev) 600c73fba38SAnna Dabrowska { 601c73fba38SAnna Dabrowska return $pid === ''; 602c73fba38SAnna Dabrowska } 603c73fba38SAnna Dabrowska 604c73fba38SAnna Dabrowska /** 605c73fba38SAnna Dabrowska * Returns true if data is of type "serial" 606c73fba38SAnna Dabrowska * 607c73fba38SAnna Dabrowska * @param string $pid 608c73fba38SAnna Dabrowska * @param int $rev 609c73fba38SAnna Dabrowska * @param int $rid 610c73fba38SAnna Dabrowska * @return bool 611c73fba38SAnna Dabrowska */ 6124cd5cc28SAnna Dabrowska public static function isTypeSerial($pid, $rev) 613c73fba38SAnna Dabrowska { 614c73fba38SAnna Dabrowska return $pid !== '' && $rev === 0; 615c73fba38SAnna Dabrowska } 616d680cb37SAnna Dabrowska 617d680cb37SAnna Dabrowska /** 618d680cb37SAnna Dabrowska * Global and serial data require additional queries. They are put into query queue 619a09ff24aSAnna Dabrowska * in descendants of this method. 620d680cb37SAnna Dabrowska * 621d680cb37SAnna Dabrowska * @param string $pid 622d680cb37SAnna Dabrowska * @param int $rid 623d680cb37SAnna Dabrowska * @param int $colref 624d680cb37SAnna Dabrowska */ 625d680cb37SAnna Dabrowska protected function handleEmptyMulti($pid, $rid, $colref) 626d680cb37SAnna Dabrowska { 627d680cb37SAnna Dabrowska } 628a09ff24aSAnna Dabrowska 629a09ff24aSAnna Dabrowska /** 630a09ff24aSAnna Dabrowska * Clears all multi_ values for the current row. 631a09ff24aSAnna Dabrowska * Executed when updating global and serial data. Otherwise removed (deselected) values linger in database. 632a09ff24aSAnna Dabrowska * 633a09ff24aSAnna Dabrowska * @return bool|\SQLiteResult 634a09ff24aSAnna Dabrowska */ 635a09ff24aSAnna Dabrowska protected function clearMulti() 636a09ff24aSAnna Dabrowska { 637a09ff24aSAnna Dabrowska $colrefs = array_unique(array_map(function ($val) { 638a09ff24aSAnna Dabrowska return $val[0]; 639a09ff24aSAnna Dabrowska }, $this->multiValues)); 640a09ff24aSAnna Dabrowska return $this->sqlite->query( 641a09ff24aSAnna Dabrowska "DELETE FROM $this->mtable WHERE pid = ? AND rid = $this->rid AND rev = 0 AND colref IN (" . 642a09ff24aSAnna Dabrowska implode(',', $colrefs) . ")", 64379b29326SAnna Dabrowska [$this->pid] 644a09ff24aSAnna Dabrowska ); 645a09ff24aSAnna Dabrowska } 646f411d872SAndreas Gohr} 647