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{ 14*17a3a578SAndreas Gohr public const DEFAULT_REV = 0; 15*17a3a578SAndreas 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; 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 29d680cb37SAnna Dabrowska protected $optQueries = []; 30d680cb37SAnna Dabrowska 31f411d872SAndreas Gohr /** 32aeb8444cSAnna Dabrowska * @var string Name of single-value table 33aeb8444cSAnna Dabrowska */ 34aeb8444cSAnna Dabrowska protected $stable; 35aeb8444cSAnna Dabrowska 36aeb8444cSAnna Dabrowska /** 37aeb8444cSAnna Dabrowska * @var string Name of multi-value table 38aeb8444cSAnna Dabrowska */ 39aeb8444cSAnna Dabrowska protected $mtable; 40aeb8444cSAnna Dabrowska 41aeb8444cSAnna Dabrowska /** 42aeb8444cSAnna Dabrowska * @var array Column names for the single-value insert/update 43aeb8444cSAnna Dabrowska */ 44aeb8444cSAnna Dabrowska protected $singleCols; 45aeb8444cSAnna Dabrowska 46aeb8444cSAnna Dabrowska /** 47aeb8444cSAnna Dabrowska * @var array Input values for the single-value insert/update 48aeb8444cSAnna Dabrowska */ 49aeb8444cSAnna Dabrowska protected $singleValues; 50aeb8444cSAnna Dabrowska 51aeb8444cSAnna Dabrowska /** 52aeb8444cSAnna Dabrowska * @var array Input values for the multi-value inserts/updates 53aeb8444cSAnna Dabrowska */ 54aeb8444cSAnna Dabrowska protected $multiValues; 55aeb8444cSAnna Dabrowska 564cd5cc28SAnna Dabrowska public static function getPageAccess($tablename, $pid, $ts = 0) 574cd5cc28SAnna Dabrowska { 584cd5cc28SAnna Dabrowska $schema = new Schema($tablename, $ts); 59308cc83fSAndreas Gohr return new AccessTablePage($schema, $pid, $ts, 0); 604cd5cc28SAnna Dabrowska } 614cd5cc28SAnna Dabrowska 624cd5cc28SAnna Dabrowska public static function getSerialAccess($tablename, $pid, $rid = 0) 634cd5cc28SAnna Dabrowska { 644cd5cc28SAnna Dabrowska $schema = new Schema($tablename, 0); 654cd5cc28SAnna Dabrowska return new AccessTableSerial($schema, $pid, 0, $rid); 664cd5cc28SAnna Dabrowska } 674cd5cc28SAnna Dabrowska 68308cc83fSAndreas Gohr public static function getGlobalAccess($tablename, $rid = 0) 694cd5cc28SAnna Dabrowska { 704cd5cc28SAnna Dabrowska $schema = new Schema($tablename, 0); 71308cc83fSAndreas Gohr return new AccessTableGlobal($schema, '', 0, $rid); 724cd5cc28SAnna Dabrowska } 73aeb8444cSAnna Dabrowska 74aeb8444cSAnna Dabrowska /** 75308cc83fSAndreas Gohr * Factory method returning the appropriate data accessor (page, global or serial) 76f411d872SAndreas Gohr * 77f411d872SAndreas Gohr * @param Schema $schema schema to load 7886a40c1eSAnna Dabrowska * @param string $pid Page id to access 79a14cf85dSAnna Dabrowska * @param int $ts Time at which the data should be read or written 8086a40c1eSAnna Dabrowska * @param int $rid Row id, 0 for page type data, otherwise autoincrement 81308cc83fSAndreas Gohr * @return AccessTablePage|AccessTableGlobal 82308cc83fSAndreas Gohr * @deprecated 83f411d872SAndreas Gohr */ 84d6d97f60SAnna Dabrowska public static function bySchema(Schema $schema, $pid, $ts = 0, $rid = 0) 85d6d97f60SAnna Dabrowska { 864cd5cc28SAnna Dabrowska if (self::isTypePage($pid, $ts)) { 87308cc83fSAndreas Gohr return new AccessTablePage($schema, $pid, $ts, $rid); 88f411d872SAndreas Gohr } 89308cc83fSAndreas Gohr return new AccessTableGlobal($schema, $pid, $ts, $rid); 90aeb8444cSAnna Dabrowska } 91f411d872SAndreas Gohr 92f411d872SAndreas Gohr /** 93c73fba38SAnna Dabrowska * Factory Method to access data 94f411d872SAndreas Gohr * 95f411d872SAndreas Gohr * @param string $tablename schema to load 9686a40c1eSAnna Dabrowska * @param string $pid Page id to access 97a14cf85dSAnna Dabrowska * @param int $ts Time at which the data should be read or written 9886a40c1eSAnna Dabrowska * @param int $rid Row id, 0 for page type data, otherwise autoincrement 99308cc83fSAndreas Gohr * @return AccessTablePage|AccessTableGlobal 100308cc83fSAndreas Gohr * @deprecated Use specific methods since we can no longer 101308cc83fSAndreas Gohr * guarantee instantiating the required descendant class 102f411d872SAndreas Gohr */ 103d6d97f60SAnna Dabrowska public static function byTableName($tablename, $pid, $ts = 0, $rid = 0) 104d6d97f60SAnna Dabrowska { 105984b4e7bSAnna Dabrowska // force loading the latest schema for anything other than page data, 106984b4e7bSAnna Dabrowska // for which we might actually need the history 1074cd5cc28SAnna Dabrowska if (!self::isTypePage($pid, $ts)) { 108984b4e7bSAnna Dabrowska $schema = new Schema($tablename, time()); 109984b4e7bSAnna Dabrowska } else { 110f411d872SAndreas Gohr $schema = new Schema($tablename, $ts); 111984b4e7bSAnna Dabrowska } 1120ceefd5cSAnna Dabrowska return self::bySchema($schema, $pid, $ts, $rid); 113f411d872SAndreas Gohr } 114f411d872SAndreas Gohr 115f411d872SAndreas Gohr /** 116f411d872SAndreas Gohr * AccessTable constructor 117f411d872SAndreas Gohr * 118897aef42SAndreas Gohr * @param Schema $schema The schema valid at $ts 11986a40c1eSAnna Dabrowska * @param string $pid Page id 120897aef42SAndreas Gohr * @param int $ts Time at which the data should be read or written, 0 for now 1210ceefd5cSAnna Dabrowska * @param int $rid Row id: 0 for pages, autoincremented for other types 122f411d872SAndreas Gohr */ 1234cd5cc28SAnna Dabrowska public function __construct($schema, $pid, $ts = 0, $rid = 0) 124d6d97f60SAnna Dabrowska { 125f411d872SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 126f411d872SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 127f411d872SAndreas Gohr $this->sqlite = $helper->getDB(); 128f411d872SAndreas Gohr 129f411d872SAndreas Gohr if (!$schema->getId()) { 130f411d872SAndreas Gohr throw new StructException('Schema does not exist. Only data of existing schemas can be accessed'); 131f411d872SAndreas Gohr } 132f411d872SAndreas Gohr 133f411d872SAndreas Gohr $this->schema = $schema; 134f411d872SAndreas Gohr $this->pid = $pid; 1350ceefd5cSAnna Dabrowska $this->rid = $rid; 136897aef42SAndreas Gohr $this->setTimestamp($ts); 137f411d872SAndreas Gohr foreach ($this->schema->getColumns() as $col) { 138f411d872SAndreas Gohr $this->labels[$col->getColref()] = $col->getType()->getLabel(); 139f411d872SAndreas Gohr } 140f411d872SAndreas Gohr } 141f411d872SAndreas Gohr 142f411d872SAndreas Gohr /** 143f411d872SAndreas Gohr * gives access to the schema 144f411d872SAndreas Gohr * 145f411d872SAndreas Gohr * @return Schema 146f411d872SAndreas Gohr */ 147d6d97f60SAnna Dabrowska public function getSchema() 148d6d97f60SAnna Dabrowska { 149f411d872SAndreas Gohr return $this->schema; 150f411d872SAndreas Gohr } 151f411d872SAndreas Gohr 152f411d872SAndreas Gohr /** 153f107f479SAndreas Gohr * The current pid 154f107f479SAndreas Gohr * 15586a40c1eSAnna Dabrowska * @return string 156f107f479SAndreas Gohr */ 157d6d97f60SAnna Dabrowska public function getPid() 158d6d97f60SAnna Dabrowska { 159f107f479SAndreas Gohr return $this->pid; 160f107f479SAndreas Gohr } 161f107f479SAndreas Gohr 162f107f479SAndreas Gohr /** 1630ceefd5cSAnna Dabrowska * The current rid 1640ceefd5cSAnna Dabrowska * 16586a40c1eSAnna Dabrowska * @return int 1660ceefd5cSAnna Dabrowska */ 167d6d97f60SAnna Dabrowska public function getRid() 168d6d97f60SAnna Dabrowska { 1690ceefd5cSAnna Dabrowska return $this->rid; 1700ceefd5cSAnna Dabrowska } 1710ceefd5cSAnna Dabrowska 1720ceefd5cSAnna Dabrowska /** 173f411d872SAndreas Gohr * Should remove the current data, by either deleting or ovewriting it 174f411d872SAndreas Gohr * 175f411d872SAndreas Gohr * @return bool if the delete succeeded 176f411d872SAndreas Gohr */ 177f411d872SAndreas Gohr abstract public function clearData(); 178f411d872SAndreas Gohr 179f411d872SAndreas Gohr /** 180f411d872SAndreas Gohr * Save the data to the database. 181f411d872SAndreas Gohr * 182f411d872SAndreas Gohr * We differentiate between single-value-column and multi-value-column by the value to the respective column-name, 183f411d872SAndreas Gohr * i.e. depending on if that is a string or an array, respectively. 184f411d872SAndreas Gohr * 185f411d872SAndreas Gohr * @param array $data typelabel => value for single fields or typelabel => array(value, value, ...) for multi fields 186f411d872SAndreas Gohr * @return bool success of saving the data to the database 187f411d872SAndreas Gohr */ 188aeb8444cSAnna Dabrowska public function saveData($data) 189aeb8444cSAnna Dabrowska { 190aeb8444cSAnna Dabrowska if (!$this->validateTypeData($data)) { 191aeb8444cSAnna Dabrowska return false; 192aeb8444cSAnna Dabrowska } 193aeb8444cSAnna Dabrowska 194aeb8444cSAnna Dabrowska $this->stable = 'data_' . $this->schema->getTable(); 195aeb8444cSAnna Dabrowska $this->mtable = 'multi_' . $this->schema->getTable(); 196aeb8444cSAnna Dabrowska 197aeb8444cSAnna Dabrowska $colrefs = array_flip($this->labels); 198aeb8444cSAnna Dabrowska 199aeb8444cSAnna Dabrowska foreach ($data as $colname => $value) { 200aeb8444cSAnna Dabrowska if (!isset($colrefs[$colname])) { 201aeb8444cSAnna Dabrowska throw new StructException("Unknown column %s in schema.", hsc($colname)); 202aeb8444cSAnna Dabrowska } 203aeb8444cSAnna Dabrowska 204aeb8444cSAnna Dabrowska $this->singleCols[] = 'col' . $colrefs[$colname]; 205aeb8444cSAnna Dabrowska if (is_array($value)) { 206aeb8444cSAnna Dabrowska foreach ($value as $index => $multivalue) { 207aeb8444cSAnna Dabrowska $this->multiValues[] = [$colrefs[$colname], $index + 1, $multivalue]; 208aeb8444cSAnna Dabrowska } 209aeb8444cSAnna Dabrowska // copy first value to the single column 210aeb8444cSAnna Dabrowska if (isset($value[0])) { 211aeb8444cSAnna Dabrowska $this->singleValues[] = $value[0]; 212f6cf0d85SAnna Dabrowska if ($value[0] === '') { 213d680cb37SAnna Dabrowska $this->handleEmptyMulti($this->pid, $this->rid, $colrefs[$colname]); 214d680cb37SAnna Dabrowska } 215aeb8444cSAnna Dabrowska } else { 216aeb8444cSAnna Dabrowska $this->singleValues[] = null; 217aeb8444cSAnna Dabrowska } 218aeb8444cSAnna Dabrowska } else { 219aeb8444cSAnna Dabrowska $this->singleValues[] = $value; 220aeb8444cSAnna Dabrowska } 221aeb8444cSAnna Dabrowska } 222aeb8444cSAnna Dabrowska 223aeb8444cSAnna Dabrowska $this->sqlite->query('BEGIN TRANSACTION'); 224aeb8444cSAnna Dabrowska 225aeb8444cSAnna Dabrowska $ok = $this->beforeSave(); 226aeb8444cSAnna Dabrowska 227aeb8444cSAnna Dabrowska // insert single values 228aeb8444cSAnna Dabrowska $ok = $ok && $this->sqlite->query( 229aeb8444cSAnna Dabrowska $this->getSingleSql(), 230aeb8444cSAnna Dabrowska array_merge($this->getSingleNoninputValues(), $this->singleValues) 231aeb8444cSAnna Dabrowska ); 232aeb8444cSAnna Dabrowska 233aeb8444cSAnna Dabrowska $ok = $ok && $this->afterSingleSave(); 234aeb8444cSAnna Dabrowska 235aeb8444cSAnna Dabrowska // insert multi values 236aeb8444cSAnna Dabrowska if ($ok && $this->multiValues) { 237aeb8444cSAnna Dabrowska $multisql = $this->getMultiSql(); 238aeb8444cSAnna Dabrowska $multiNoninputValues = $this->getMultiNoninputValues(); 239aeb8444cSAnna Dabrowska foreach ($this->multiValues as $value) { 240aeb8444cSAnna Dabrowska $ok = $ok && $this->sqlite->query( 241aeb8444cSAnna Dabrowska $multisql, 242aeb8444cSAnna Dabrowska array_merge($multiNoninputValues, $value) 243aeb8444cSAnna Dabrowska ); 244aeb8444cSAnna Dabrowska } 245aeb8444cSAnna Dabrowska } 246aeb8444cSAnna Dabrowska 247d680cb37SAnna Dabrowska $ok = $ok && $this->afterSave(); 248d680cb37SAnna Dabrowska 249aeb8444cSAnna Dabrowska if (!$ok) { 250aeb8444cSAnna Dabrowska $this->sqlite->query('ROLLBACK TRANSACTION'); 251aeb8444cSAnna Dabrowska return false; 252aeb8444cSAnna Dabrowska } 253aeb8444cSAnna Dabrowska $this->sqlite->query('COMMIT TRANSACTION'); 254aeb8444cSAnna Dabrowska return true; 255aeb8444cSAnna Dabrowska } 256aeb8444cSAnna Dabrowska 257aeb8444cSAnna Dabrowska /** 258aeb8444cSAnna Dabrowska * Check whether all required data is present 259aeb8444cSAnna Dabrowska * 260aeb8444cSAnna Dabrowska * @param array $data 261aeb8444cSAnna Dabrowska * @return bool 262aeb8444cSAnna Dabrowska */ 263aeb8444cSAnna Dabrowska abstract protected function validateTypeData($data); 264aeb8444cSAnna Dabrowska 265aeb8444cSAnna Dabrowska /** 266aeb8444cSAnna Dabrowska * Names of non-input columns to be inserted into SQL query 267aeb8444cSAnna Dabrowska * 268aeb8444cSAnna Dabrowska * @return array 269aeb8444cSAnna Dabrowska */ 270aeb8444cSAnna Dabrowska abstract protected function getSingleNoninputCols(); 271aeb8444cSAnna Dabrowska 272aeb8444cSAnna Dabrowska /** 273aeb8444cSAnna Dabrowska * Values for non-input columns to be inserted into SQL query 274aeb8444cSAnna Dabrowska * for single-value tables 275aeb8444cSAnna Dabrowska * 276aeb8444cSAnna Dabrowska * @return array 277aeb8444cSAnna Dabrowska */ 278aeb8444cSAnna Dabrowska abstract protected function getSingleNoninputValues(); 279aeb8444cSAnna Dabrowska 280aeb8444cSAnna Dabrowska /** 281aeb8444cSAnna Dabrowska * String template for single-value table 282aeb8444cSAnna Dabrowska * 283aeb8444cSAnna Dabrowska * @return string 284aeb8444cSAnna Dabrowska */ 285aeb8444cSAnna Dabrowska protected function getSingleSql() 286aeb8444cSAnna Dabrowska { 287aeb8444cSAnna Dabrowska $cols = array_merge($this->getSingleNoninputCols(), $this->singleCols); 288aeb8444cSAnna Dabrowska $cols = join(',', $cols); 289aeb8444cSAnna Dabrowska $vals = array_merge($this->getSingleNoninputValues(), $this->singleValues); 290aeb8444cSAnna Dabrowska 291aeb8444cSAnna Dabrowska return "INSERT INTO $this->stable ($cols) VALUES (" . trim(str_repeat('?,', count($vals)), ',') . ');'; 292aeb8444cSAnna Dabrowska } 293aeb8444cSAnna Dabrowska 294aeb8444cSAnna Dabrowska /** 295aeb8444cSAnna Dabrowska * Optional operations to be executed before saving data 296aeb8444cSAnna Dabrowska * 297aeb8444cSAnna Dabrowska * @return bool False if any of the operations failed and transaction should be rolled back 298aeb8444cSAnna Dabrowska */ 299aeb8444cSAnna Dabrowska protected function beforeSave() 300aeb8444cSAnna Dabrowska { 301aeb8444cSAnna Dabrowska return true; 302aeb8444cSAnna Dabrowska } 303aeb8444cSAnna Dabrowska 304aeb8444cSAnna Dabrowska /** 305aeb8444cSAnna Dabrowska * Optional operations to be executed after saving data to single-value table, 306aeb8444cSAnna Dabrowska * before saving multivalues 307aeb8444cSAnna Dabrowska * 308aeb8444cSAnna Dabrowska * @return bool False if anything goes wrong and transaction should be rolled back 309aeb8444cSAnna Dabrowska */ 310aeb8444cSAnna Dabrowska protected function afterSingleSave() 311aeb8444cSAnna Dabrowska { 312aeb8444cSAnna Dabrowska return true; 313aeb8444cSAnna Dabrowska } 314aeb8444cSAnna Dabrowska 315aeb8444cSAnna Dabrowska /** 316d680cb37SAnna Dabrowska * Executes final optional queries. 317d680cb37SAnna Dabrowska * 318d680cb37SAnna Dabrowska * @return bool False if anything goes wrong and transaction should be rolled back 319d680cb37SAnna Dabrowska */ 320d680cb37SAnna Dabrowska protected function afterSave() 321d680cb37SAnna Dabrowska { 322d680cb37SAnna Dabrowska $ok = true; 323d680cb37SAnna Dabrowska foreach ($this->optQueries as $query) { 324d680cb37SAnna Dabrowska $ok = $ok && $this->sqlite->query(array_shift($query), $query); 325d680cb37SAnna Dabrowska } 326d680cb37SAnna Dabrowska return $ok; 327d680cb37SAnna Dabrowska } 328d680cb37SAnna Dabrowska 329d680cb37SAnna Dabrowska /** 330aeb8444cSAnna Dabrowska * String template for multi-value table 331aeb8444cSAnna Dabrowska * 332aeb8444cSAnna Dabrowska * @return string 333aeb8444cSAnna Dabrowska */ 334aeb8444cSAnna Dabrowska abstract protected function getMultiSql(); 335aeb8444cSAnna Dabrowska 336aeb8444cSAnna Dabrowska /** 337aeb8444cSAnna Dabrowska * Values for non-input columns to be inserted into SQL query 338aeb8444cSAnna Dabrowska * for multi-value tables 339aeb8444cSAnna Dabrowska * @return array 340aeb8444cSAnna Dabrowska */ 341aeb8444cSAnna Dabrowska abstract protected function getMultiNoninputValues(); 342aeb8444cSAnna Dabrowska 343f411d872SAndreas Gohr 344f411d872SAndreas Gohr /** 345f411d872SAndreas Gohr * Should empty or invisible (inpage) fields be returned? 346f411d872SAndreas Gohr * 347f411d872SAndreas Gohr * Defaults to false 348f411d872SAndreas Gohr * 349f411d872SAndreas Gohr * @param null|bool $set new value, null to read only 350f411d872SAndreas Gohr * @return bool current value (after set) 351f411d872SAndreas Gohr */ 352d6d97f60SAnna Dabrowska public function optionSkipEmpty($set = null) 353d6d97f60SAnna Dabrowska { 354f411d872SAndreas Gohr if (!is_null($set)) { 355f411d872SAndreas Gohr $this->opt_skipempty = $set; 356f411d872SAndreas Gohr } 357f411d872SAndreas Gohr return $this->opt_skipempty; 358f411d872SAndreas Gohr } 359f411d872SAndreas Gohr 360f411d872SAndreas Gohr /** 361f411d872SAndreas Gohr * Get the value of a single column 362f411d872SAndreas Gohr * 363f411d872SAndreas Gohr * @param Column $column 364f411d872SAndreas Gohr * @return Value|null 365f411d872SAndreas Gohr */ 366d6d97f60SAnna Dabrowska public function getDataColumn($column) 367d6d97f60SAnna Dabrowska { 368f411d872SAndreas Gohr $data = $this->getData(); 369f411d872SAndreas Gohr foreach ($data as $value) { 370f411d872SAndreas Gohr if ($value->getColumn() == $column) { 371f411d872SAndreas Gohr return $value; 372f411d872SAndreas Gohr } 373f411d872SAndreas Gohr } 374f411d872SAndreas Gohr return null; 375f411d872SAndreas Gohr } 376f411d872SAndreas Gohr 377f411d872SAndreas Gohr /** 378f411d872SAndreas Gohr * returns the data saved for the page 379f411d872SAndreas Gohr * 380f411d872SAndreas Gohr * @return Value[] a list of values saved for the current page 381f411d872SAndreas Gohr */ 382d6d97f60SAnna Dabrowska public function getData() 383d6d97f60SAnna Dabrowska { 384f411d872SAndreas Gohr $data = $this->getDataFromDB(); 385f411d872SAndreas Gohr $data = $this->consolidateData($data, false); 386f411d872SAndreas Gohr return $data; 387f411d872SAndreas Gohr } 388f411d872SAndreas Gohr 389f411d872SAndreas Gohr /** 390f411d872SAndreas Gohr * returns the data saved for the page as associative array 391f411d872SAndreas Gohr * 3920549dcc5SAndreas Gohr * The array returned is in the same format as used in @return array 3930549dcc5SAndreas Gohr * @see saveData() 394f411d872SAndreas Gohr * 39590421550SAndreas Gohr * It always returns raw Values! 39690421550SAndreas Gohr * 397f411d872SAndreas Gohr */ 398d6d97f60SAnna Dabrowska public function getDataArray() 399d6d97f60SAnna Dabrowska { 400f411d872SAndreas Gohr $data = $this->getDataFromDB(); 401f411d872SAndreas Gohr $data = $this->consolidateData($data, true); 402f411d872SAndreas Gohr return $data; 403f411d872SAndreas Gohr } 404f411d872SAndreas Gohr 405f411d872SAndreas Gohr /** 406f411d872SAndreas Gohr * Return the data in pseudo syntax 407f411d872SAndreas Gohr */ 408d6d97f60SAnna Dabrowska public function getDataPseudoSyntax() 409d6d97f60SAnna Dabrowska { 410f411d872SAndreas Gohr $result = ''; 411a0a1d14eSAndreas Gohr $data = $this->getData(); 412a0a1d14eSAndreas Gohr 413a0a1d14eSAndreas Gohr foreach ($data as $value) { 414a0a1d14eSAndreas Gohr $key = $value->getColumn()->getFullQualifiedLabel(); 415a0a1d14eSAndreas Gohr $value = $value->getDisplayValue(); 416f411d872SAndreas Gohr if (is_array($value)) $value = join(', ', $value); 417f411d872SAndreas Gohr $result .= sprintf("% -20s : %s\n", $key, $value); 418f411d872SAndreas Gohr } 419f411d872SAndreas Gohr return $result; 420f411d872SAndreas Gohr } 421f411d872SAndreas Gohr 422f411d872SAndreas Gohr /** 423f411d872SAndreas Gohr * retrieve the data saved for the page from the database. Usually there is no need to call this function. 424f411d872SAndreas Gohr * Call @see SchemaData::getData instead. 425f411d872SAndreas Gohr */ 426d6d97f60SAnna Dabrowska protected function getDataFromDB() 427d6d97f60SAnna Dabrowska { 4284cd5cc28SAnna Dabrowska $idColumn = self::isTypePage($this->pid, $this->ts) ? 'pid' : 'rid'; 429efe74305SAnna Dabrowska list($sql, $opt) = $this->buildGetDataSQL($idColumn); 430f411d872SAndreas Gohr 431f411d872SAndreas Gohr $res = $this->sqlite->query($sql, $opt); 432f411d872SAndreas Gohr $data = $this->sqlite->res2arr($res); 4339c00b26cSAndreas Gohr $this->sqlite->res_close($res); 434f411d872SAndreas Gohr return $data; 435f411d872SAndreas Gohr } 436f411d872SAndreas Gohr 437f411d872SAndreas Gohr /** 438f411d872SAndreas Gohr * Creates a proper result array from the database data 439f411d872SAndreas Gohr * 440f411d872SAndreas Gohr * @param array $DBdata the data as it is retrieved from the database, i.e. by SchemaData::getDataFromDB 441f411d872SAndreas Gohr * @param bool $asarray return data as associative array (true) or as array of Values (false) 442f411d872SAndreas Gohr * @return array|Value[] 443f411d872SAndreas Gohr */ 444d6d97f60SAnna Dabrowska protected function consolidateData($DBdata, $asarray = false) 445d6d97f60SAnna Dabrowska { 446f411d872SAndreas Gohr $data = array(); 447f411d872SAndreas Gohr 448f411d872SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 449f411d872SAndreas Gohr 450f411d872SAndreas Gohr foreach ($this->schema->getColumns(false) as $col) { 45190421550SAndreas Gohr // if no data saved yet, return empty strings 452f411d872SAndreas Gohr if ($DBdata) { 453bab52340SAndreas Gohr $val = $DBdata[0]['out' . $col->getColref()]; 454f411d872SAndreas Gohr } else { 455f411d872SAndreas Gohr $val = ''; 456f411d872SAndreas Gohr } 457f411d872SAndreas Gohr 458f411d872SAndreas Gohr // multi val data is concatenated 459f411d872SAndreas Gohr if ($col->isMulti()) { 460f411d872SAndreas Gohr $val = explode($sep, $val); 461f411d872SAndreas Gohr $val = array_filter($val); 462f411d872SAndreas Gohr } 463f411d872SAndreas Gohr 46490421550SAndreas Gohr $value = new Value($col, $val); 465f411d872SAndreas Gohr 46690421550SAndreas Gohr if ($this->opt_skipempty && $value->isEmpty()) continue; 46790421550SAndreas Gohr if ($this->opt_skipempty && !$col->isVisibleInPage()) continue; //FIXME is this a correct assumption? 46890421550SAndreas Gohr 46990421550SAndreas Gohr // for arrays, we return the raw value only 470f411d872SAndreas Gohr if ($asarray) { 47190421550SAndreas Gohr $data[$col->getLabel()] = $value->getRawValue(); 472f411d872SAndreas Gohr } else { 4736e54daafSMichael Große $data[$col->getLabel()] = $value; 474f411d872SAndreas Gohr } 475f411d872SAndreas Gohr } 476f411d872SAndreas Gohr 477f411d872SAndreas Gohr return $data; 478f411d872SAndreas Gohr } 479f411d872SAndreas Gohr 480f411d872SAndreas Gohr /** 481f411d872SAndreas Gohr * Builds the SQL statement to select the data for this page and schema 482f411d872SAndreas Gohr * 483f411d872SAndreas Gohr * @return array Two fields: the SQL string and the parameters array 484f411d872SAndreas Gohr */ 485d6d97f60SAnna Dabrowska protected function buildGetDataSQL($idColumn = 'pid') 486d6d97f60SAnna Dabrowska { 487f411d872SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 488f411d872SAndreas Gohr $stable = 'data_' . $this->schema->getTable(); 489f411d872SAndreas Gohr $mtable = 'multi_' . $this->schema->getTable(); 490f411d872SAndreas Gohr 491f411d872SAndreas Gohr $QB = new QueryBuilder(); 492f411d872SAndreas Gohr $QB->addTable($stable, 'DATA'); 4936fd73b4bSAnna Dabrowska $QB->addSelectColumn('DATA', $idColumn, strtoupper($idColumn)); 4946fd73b4bSAnna Dabrowska $QB->addGroupByStatement("DATA.$idColumn"); 495f411d872SAndreas Gohr 496f411d872SAndreas Gohr foreach ($this->schema->getColumns(false) as $col) { 497f411d872SAndreas Gohr $colref = $col->getColref(); 498f411d872SAndreas Gohr $colname = 'col' . $colref; 499bab52340SAndreas Gohr $outname = 'out' . $colref; 500f411d872SAndreas Gohr 501f411d872SAndreas Gohr if ($col->getType()->isMulti()) { 502f411d872SAndreas Gohr $tn = 'M' . $colref; 503f411d872SAndreas Gohr $QB->addLeftJoin( 504f411d872SAndreas Gohr 'DATA', 505f411d872SAndreas Gohr $mtable, 506f411d872SAndreas Gohr $tn, 5076fd73b4bSAnna Dabrowska "DATA.$idColumn = $tn.$idColumn AND DATA.rev = $tn.rev AND $tn.colref = $colref" 508f411d872SAndreas Gohr ); 509bab52340SAndreas Gohr $col->getType()->select($QB, $tn, 'value', $outname); 510bab52340SAndreas Gohr $sel = $QB->getSelectStatement($outname); 511bab52340SAndreas Gohr $QB->addSelectStatement("GROUP_CONCAT($sel, '$sep')", $outname); 512f411d872SAndreas Gohr } else { 513bab52340SAndreas Gohr $col->getType()->select($QB, 'DATA', $colname, $outname); 514bab52340SAndreas Gohr $QB->addGroupByStatement($outname); 515f411d872SAndreas Gohr } 516f411d872SAndreas Gohr } 517f411d872SAndreas Gohr 5186fd73b4bSAnna Dabrowska $pl = $QB->addValue($this->{$idColumn}); 5196fd73b4bSAnna Dabrowska $QB->filters()->whereAnd("DATA.$idColumn = $pl"); 520897aef42SAndreas Gohr $pl = $QB->addValue($this->getLastRevisionTimestamp()); 521f411d872SAndreas Gohr $QB->filters()->whereAnd("DATA.rev = $pl"); 522f411d872SAndreas Gohr 523f411d872SAndreas Gohr return $QB->getSQL(); 524f411d872SAndreas Gohr } 525f411d872SAndreas Gohr 526f411d872SAndreas Gohr /** 52713eddb0fSAndreas Gohr * @param int $ts 52813eddb0fSAndreas Gohr */ 529d6d97f60SAnna Dabrowska public function setTimestamp($ts) 530d6d97f60SAnna Dabrowska { 531897aef42SAndreas Gohr if ($ts && $ts < $this->schema->getTimeStamp()) { 532897aef42SAndreas Gohr throw new StructException('Given timestamp is not valid for current Schema'); 533897aef42SAndreas Gohr } 534897aef42SAndreas Gohr 53513eddb0fSAndreas Gohr $this->ts = $ts; 53613eddb0fSAndreas Gohr } 53713eddb0fSAndreas Gohr 53813eddb0fSAndreas Gohr /** 53969f7ec8fSAnna Dabrowska * Returns the timestamp from the current data 54069f7ec8fSAnna Dabrowska * @return int 54169f7ec8fSAnna Dabrowska */ 54269f7ec8fSAnna Dabrowska public function getTimestamp() 54369f7ec8fSAnna Dabrowska { 54469f7ec8fSAnna Dabrowska return $this->ts; 54569f7ec8fSAnna Dabrowska } 54669f7ec8fSAnna Dabrowska 54769f7ec8fSAnna Dabrowska /** 548897aef42SAndreas Gohr * Return the last time an edit happened for this table for the currently set 5490549dcc5SAndreas Gohr * time and pid. Used in @return int 5500549dcc5SAndreas Gohr * @see buildGetDataSQL() 551f411d872SAndreas Gohr * 552f411d872SAndreas Gohr */ 553897aef42SAndreas Gohr abstract protected function getLastRevisionTimestamp(); 55487dc1344SAndreas Gohr 55587dc1344SAndreas Gohr /** 55687dc1344SAndreas Gohr * Check if the given data validates against the current types. 55787dc1344SAndreas Gohr * 55887dc1344SAndreas Gohr * @param array $data 55993ca6f4fSAndreas Gohr * @return AccessDataValidator 56087dc1344SAndreas Gohr */ 561d6d97f60SAnna Dabrowska public function getValidator($data) 562d6d97f60SAnna Dabrowska { 56393ca6f4fSAndreas Gohr return new AccessDataValidator($this, $data); 56487dc1344SAndreas Gohr } 565c73fba38SAnna Dabrowska 566c73fba38SAnna Dabrowska /** 567c73fba38SAnna Dabrowska * Returns true if data is of type "page" 568c73fba38SAnna Dabrowska * 569c73fba38SAnna Dabrowska * @param string $pid 570c73fba38SAnna Dabrowska * @param int $rev 571c73fba38SAnna Dabrowska * @param int $rid 572c73fba38SAnna Dabrowska * @return bool 573c73fba38SAnna Dabrowska */ 5744cd5cc28SAnna Dabrowska public static function isTypePage($pid, $rev) 575c73fba38SAnna Dabrowska { 576c73fba38SAnna Dabrowska return $rev > 0; 577c73fba38SAnna Dabrowska } 578c73fba38SAnna Dabrowska 579c73fba38SAnna Dabrowska /** 580308cc83fSAndreas Gohr * Returns true if data is of type "global" 581c73fba38SAnna Dabrowska * 582c73fba38SAnna Dabrowska * @param string $pid 583c73fba38SAnna Dabrowska * @param int $rev 584c73fba38SAnna Dabrowska * @param int $rid 585c73fba38SAnna Dabrowska * @return bool 586c73fba38SAnna Dabrowska */ 587308cc83fSAndreas Gohr public static function isTypeGlobal($pid, $rev) 588c73fba38SAnna Dabrowska { 589c73fba38SAnna Dabrowska return $pid === ''; 590c73fba38SAnna Dabrowska } 591c73fba38SAnna Dabrowska 592c73fba38SAnna Dabrowska /** 593c73fba38SAnna Dabrowska * Returns true if data is of type "serial" 594c73fba38SAnna Dabrowska * 595c73fba38SAnna Dabrowska * @param string $pid 596c73fba38SAnna Dabrowska * @param int $rev 597c73fba38SAnna Dabrowska * @param int $rid 598c73fba38SAnna Dabrowska * @return bool 599c73fba38SAnna Dabrowska */ 6004cd5cc28SAnna Dabrowska public static function isTypeSerial($pid, $rev) 601c73fba38SAnna Dabrowska { 602c73fba38SAnna Dabrowska return $pid !== '' && $rev === 0; 603c73fba38SAnna Dabrowska } 604d680cb37SAnna Dabrowska 605d680cb37SAnna Dabrowska /** 606d680cb37SAnna Dabrowska * Global and serial data require additional queries. They are put into query queue 607a09ff24aSAnna Dabrowska * in descendants of this method. 608d680cb37SAnna Dabrowska * 609d680cb37SAnna Dabrowska * @param string $pid 610d680cb37SAnna Dabrowska * @param int $rid 611d680cb37SAnna Dabrowska * @param int $colref 612d680cb37SAnna Dabrowska */ 613d680cb37SAnna Dabrowska protected function handleEmptyMulti($pid, $rid, $colref) 614d680cb37SAnna Dabrowska { 615d680cb37SAnna Dabrowska } 616a09ff24aSAnna Dabrowska 617a09ff24aSAnna Dabrowska /** 618a09ff24aSAnna Dabrowska * Clears all multi_ values for the current row. 619a09ff24aSAnna Dabrowska * Executed when updating global and serial data. Otherwise removed (deselected) values linger in database. 620a09ff24aSAnna Dabrowska * 621a09ff24aSAnna Dabrowska * @return bool|\SQLiteResult 622a09ff24aSAnna Dabrowska */ 623a09ff24aSAnna Dabrowska protected function clearMulti() 624a09ff24aSAnna Dabrowska { 625a09ff24aSAnna Dabrowska $colrefs = array_unique(array_map(function ($val) { 626a09ff24aSAnna Dabrowska return $val[0]; 627a09ff24aSAnna Dabrowska }, $this->multiValues)); 628a09ff24aSAnna Dabrowska return $this->sqlite->query( 629a09ff24aSAnna Dabrowska "DELETE FROM $this->mtable WHERE pid = ? AND rid = $this->rid AND rev = 0 AND colref IN (" . 630a09ff24aSAnna Dabrowska implode(',', $colrefs) . ")", 631a09ff24aSAnna Dabrowska $this->pid 632a09ff24aSAnna Dabrowska ); 633a09ff24aSAnna Dabrowska } 634f411d872SAndreas Gohr} 635