1f411d872SAndreas Gohr<?php 2f411d872SAndreas Gohr 3f411d872SAndreas Gohrnamespace dokuwiki\plugin\struct\meta; 4f411d872SAndreas Gohr 5f411d872SAndreas Gohrabstract class AccessTable { 6f411d872SAndreas Gohr 7f411d872SAndreas Gohr /** @var Schema */ 8f411d872SAndreas Gohr protected $schema; 9f411d872SAndreas Gohr protected $pid; 10f411d872SAndreas Gohr protected $labels = array(); 11f411d872SAndreas Gohr protected $ts = 0; 12f411d872SAndreas Gohr /** @var \helper_plugin_sqlite */ 13f411d872SAndreas Gohr protected $sqlite; 14f411d872SAndreas Gohr 15f411d872SAndreas Gohr // options on how to retieve data 16f411d872SAndreas Gohr protected $opt_skipempty = false; 17f411d872SAndreas Gohr protected $opt_rawvalue = false; 18f411d872SAndreas Gohr 19f411d872SAndreas Gohr /** 20f411d872SAndreas Gohr * Factory Method to access a data or lookup table 21f411d872SAndreas Gohr * 22f411d872SAndreas Gohr * @param Schema $schema schema to load 23f411d872SAndreas Gohr * @param string|int $pid Page or row id to access 24*94c9aa4cSAndreas Gohr * @return AccessTableData|AccessTableLookup 25f411d872SAndreas Gohr */ 26f411d872SAndreas Gohr public static function bySchema(Schema $schema, $pid) { 27f411d872SAndreas Gohr if($schema->isLookup()) { 28*94c9aa4cSAndreas Gohr return new AccessTableLookup($schema, $pid); 29f411d872SAndreas Gohr } else { 30*94c9aa4cSAndreas Gohr return new AccessTableData($schema, $pid); 31f411d872SAndreas Gohr } 32f411d872SAndreas Gohr } 33f411d872SAndreas Gohr 34f411d872SAndreas Gohr /** 35f411d872SAndreas Gohr * Factory Method to access a data or lookup table 36f411d872SAndreas Gohr * 37f411d872SAndreas Gohr * @param string $tablename schema to load 38f411d872SAndreas Gohr * @param string|int $pid Page or row id to access 39f411d872SAndreas Gohr * @param int $ts from when is the schema to access? 40*94c9aa4cSAndreas Gohr * @return AccessTableData|AccessTableLookup 41f411d872SAndreas Gohr */ 42f411d872SAndreas Gohr public static function byTableName($tablename, $pid, $ts = 0) { 43f411d872SAndreas Gohr $schema = new Schema($tablename, $ts); 44f411d872SAndreas Gohr return self::bySchema($schema, $pid); 45f411d872SAndreas Gohr } 46f411d872SAndreas Gohr 47f411d872SAndreas Gohr /** 48f411d872SAndreas Gohr * AccessTable constructor 49f411d872SAndreas Gohr * 50f411d872SAndreas Gohr * @param Schema $schema 51f411d872SAndreas Gohr * @param string $pid 52f411d872SAndreas Gohr */ 53f411d872SAndreas Gohr public function __construct(Schema $schema, $pid) { 54f411d872SAndreas Gohr /** @var \helper_plugin_struct_db $helper */ 55f411d872SAndreas Gohr $helper = plugin_load('helper', 'struct_db'); 56f411d872SAndreas Gohr $this->sqlite = $helper->getDB(); 57f411d872SAndreas Gohr if(!$this->sqlite) { 58f411d872SAndreas Gohr throw new StructException('Sqlite plugin required'); 59f411d872SAndreas Gohr } 60f411d872SAndreas Gohr 61f411d872SAndreas Gohr if(!$schema->getId()) { 62f411d872SAndreas Gohr throw new StructException('Schema does not exist. Only data of existing schemas can be accessed'); 63f411d872SAndreas Gohr } 64f411d872SAndreas Gohr 65f411d872SAndreas Gohr $this->schema = $schema; 66f411d872SAndreas Gohr $this->pid = $pid; 67f411d872SAndreas Gohr $this->ts = $this->schema->getTimeStamp(); 68f411d872SAndreas Gohr foreach($this->schema->getColumns() as $col) { 69f411d872SAndreas Gohr $this->labels[$col->getColref()] = $col->getType()->getLabel(); 70f411d872SAndreas Gohr } 71f411d872SAndreas Gohr } 72f411d872SAndreas Gohr 73f411d872SAndreas Gohr /** 74f411d872SAndreas Gohr * gives access to the schema 75f411d872SAndreas Gohr * 76f411d872SAndreas Gohr * @return Schema 77f411d872SAndreas Gohr */ 78f411d872SAndreas Gohr public function getSchema() { 79f411d872SAndreas Gohr return $this->schema; 80f411d872SAndreas Gohr } 81f411d872SAndreas Gohr 82f411d872SAndreas Gohr /** 83f411d872SAndreas Gohr * Should remove the current data, by either deleting or ovewriting it 84f411d872SAndreas Gohr * 85f411d872SAndreas Gohr * @return bool if the delete succeeded 86f411d872SAndreas Gohr */ 87f411d872SAndreas Gohr abstract public function clearData(); 88f411d872SAndreas Gohr 89f411d872SAndreas Gohr /** 90f411d872SAndreas Gohr * Save the data to the database. 91f411d872SAndreas Gohr * 92f411d872SAndreas Gohr * We differentiate between single-value-column and multi-value-column by the value to the respective column-name, 93f411d872SAndreas Gohr * i.e. depending on if that is a string or an array, respectively. 94f411d872SAndreas Gohr * 95f411d872SAndreas Gohr * @param array $data typelabel => value for single fields or typelabel => array(value, value, ...) for multi fields 96f411d872SAndreas Gohr * @return bool success of saving the data to the database 97f411d872SAndreas Gohr */ 98f411d872SAndreas Gohr abstract public function saveData($data); 99f411d872SAndreas Gohr 100f411d872SAndreas Gohr /** 101f411d872SAndreas Gohr * Should empty or invisible (inpage) fields be returned? 102f411d872SAndreas Gohr * 103f411d872SAndreas Gohr * Defaults to false 104f411d872SAndreas Gohr * 105f411d872SAndreas Gohr * @param null|bool $set new value, null to read only 106f411d872SAndreas Gohr * @return bool current value (after set) 107f411d872SAndreas Gohr */ 108f411d872SAndreas Gohr public function optionSkipEmpty($set = null) { 109f411d872SAndreas Gohr if(!is_null($set)) { 110f411d872SAndreas Gohr $this->opt_skipempty = $set; 111f411d872SAndreas Gohr } 112f411d872SAndreas Gohr return $this->opt_skipempty; 113f411d872SAndreas Gohr } 114f411d872SAndreas Gohr 115f411d872SAndreas Gohr /** 116f411d872SAndreas Gohr * Should the values be returned raw or are complex returns okay? 117f411d872SAndreas Gohr * 118f411d872SAndreas Gohr * Defaults to false = complex values okay 119f411d872SAndreas Gohr * 120f411d872SAndreas Gohr * @param null|bool $set new value, null to read only 121f411d872SAndreas Gohr * @return bool current value (after set) 122f411d872SAndreas Gohr */ 123f411d872SAndreas Gohr public function optionRawValue($set = null) { 124f411d872SAndreas Gohr if(!is_null($set)) { 125f411d872SAndreas Gohr $this->opt_rawvalue = $set; 126f411d872SAndreas Gohr } 127f411d872SAndreas Gohr return $this->opt_rawvalue; 128f411d872SAndreas Gohr } 129f411d872SAndreas Gohr 130f411d872SAndreas Gohr 131f411d872SAndreas Gohr /** 132f411d872SAndreas Gohr * Get the value of a single column 133f411d872SAndreas Gohr * 134f411d872SAndreas Gohr * @param Column $column 135f411d872SAndreas Gohr * @return Value|null 136f411d872SAndreas Gohr */ 137f411d872SAndreas Gohr public function getDataColumn($column) { 138f411d872SAndreas Gohr $data = $this->getData(); 139f411d872SAndreas Gohr foreach($data as $value) { 140f411d872SAndreas Gohr if($value->getColumn() == $column) { 141f411d872SAndreas Gohr return $value; 142f411d872SAndreas Gohr } 143f411d872SAndreas Gohr } 144f411d872SAndreas Gohr return null; 145f411d872SAndreas Gohr } 146f411d872SAndreas Gohr 147f411d872SAndreas Gohr /** 148f411d872SAndreas Gohr * returns the data saved for the page 149f411d872SAndreas Gohr * 150f411d872SAndreas Gohr * @return Value[] a list of values saved for the current page 151f411d872SAndreas Gohr */ 152f411d872SAndreas Gohr public function getData() { 153f411d872SAndreas Gohr $this->setCorrectTimestamp($this->pid, $this->ts); 154f411d872SAndreas Gohr $data = $this->getDataFromDB(); 155f411d872SAndreas Gohr $data = $this->consolidateData($data, false); 156f411d872SAndreas Gohr return $data; 157f411d872SAndreas Gohr } 158f411d872SAndreas Gohr 159f411d872SAndreas Gohr /** 160f411d872SAndreas Gohr * returns the data saved for the page as associative array 161f411d872SAndreas Gohr * 162f411d872SAndreas Gohr * The array returned is in the same format as used in @see saveData() 163f411d872SAndreas Gohr * 164f411d872SAndreas Gohr * @return array 165f411d872SAndreas Gohr */ 166f411d872SAndreas Gohr public function getDataArray() { 167f411d872SAndreas Gohr $this->setCorrectTimestamp($this->pid, $this->ts); 168f411d872SAndreas Gohr $data = $this->getDataFromDB(); 169f411d872SAndreas Gohr $data = $this->consolidateData($data, true); 170f411d872SAndreas Gohr return $data; 171f411d872SAndreas Gohr } 172f411d872SAndreas Gohr 173f411d872SAndreas Gohr /** 174f411d872SAndreas Gohr * Return the data in pseudo syntax 175f411d872SAndreas Gohr */ 176f411d872SAndreas Gohr public function getDataPseudoSyntax() { 177f411d872SAndreas Gohr $result = ''; 178f411d872SAndreas Gohr $data = $this->getDataArray(); 179f411d872SAndreas Gohr foreach($data as $key => $value) { 180f411d872SAndreas Gohr $key = $this->schema->getTable() . ".$key"; 181f411d872SAndreas Gohr if(is_array($value)) $value = join(', ', $value); 182f411d872SAndreas Gohr $result .= sprintf("% -20s : %s\n", $key, $value); 183f411d872SAndreas Gohr } 184f411d872SAndreas Gohr return $result; 185f411d872SAndreas Gohr } 186f411d872SAndreas Gohr 187f411d872SAndreas Gohr /** 188f411d872SAndreas Gohr * retrieve the data saved for the page from the database. Usually there is no need to call this function. 189f411d872SAndreas Gohr * Call @see SchemaData::getData instead. 190f411d872SAndreas Gohr */ 191f411d872SAndreas Gohr protected function getDataFromDB() { 192f411d872SAndreas Gohr list($sql, $opt) = $this->buildGetDataSQL(); 193f411d872SAndreas Gohr 194f411d872SAndreas Gohr $res = $this->sqlite->query($sql, $opt); 195f411d872SAndreas Gohr $data = $this->sqlite->res2arr($res); 196f411d872SAndreas Gohr 197f411d872SAndreas Gohr return $data; 198f411d872SAndreas Gohr } 199f411d872SAndreas Gohr 200f411d872SAndreas Gohr /** 201f411d872SAndreas Gohr * Creates a proper result array from the database data 202f411d872SAndreas Gohr * 203f411d872SAndreas Gohr * @param array $DBdata the data as it is retrieved from the database, i.e. by SchemaData::getDataFromDB 204f411d872SAndreas Gohr * @param bool $asarray return data as associative array (true) or as array of Values (false) 205f411d872SAndreas Gohr * @return array|Value[] 206f411d872SAndreas Gohr */ 207f411d872SAndreas Gohr protected function consolidateData($DBdata, $asarray = false) { 208f411d872SAndreas Gohr $data = array(); 209f411d872SAndreas Gohr 210f411d872SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 211f411d872SAndreas Gohr 212f411d872SAndreas Gohr foreach($this->schema->getColumns(false) as $col) { 213f411d872SAndreas Gohr 214f411d872SAndreas Gohr // if no data saved, yet return empty strings 215f411d872SAndreas Gohr if($DBdata) { 216f411d872SAndreas Gohr $val = $DBdata[0]['col'.$col->getColref()]; 217f411d872SAndreas Gohr } else { 218f411d872SAndreas Gohr $val = ''; 219f411d872SAndreas Gohr } 220f411d872SAndreas Gohr 221f411d872SAndreas Gohr // multi val data is concatenated 222f411d872SAndreas Gohr if($col->isMulti()) { 223f411d872SAndreas Gohr $val = explode($sep, $val); 224f411d872SAndreas Gohr if($this->opt_rawvalue) { 225f411d872SAndreas Gohr $val = array_map( 226f411d872SAndreas Gohr function ($val) use ($col) { // FIXME requires PHP 5.4+ 227f411d872SAndreas Gohr return $col->getType()->rawValue($val); 228f411d872SAndreas Gohr }, 229f411d872SAndreas Gohr $val 230f411d872SAndreas Gohr ); 231f411d872SAndreas Gohr } 232f411d872SAndreas Gohr $val = array_filter($val); 233f411d872SAndreas Gohr } else { 234f411d872SAndreas Gohr if($this->opt_rawvalue) { 235f411d872SAndreas Gohr $val = $col->getType()->rawValue($val); 236f411d872SAndreas Gohr } 237f411d872SAndreas Gohr } 238f411d872SAndreas Gohr 239f411d872SAndreas Gohr if($this->opt_skipempty && ($val === '' || $val == array())) continue; 240f411d872SAndreas Gohr if($this->opt_skipempty && !$col->isVisibleInPage()) continue; 241f411d872SAndreas Gohr 242f411d872SAndreas Gohr if($asarray) { 243f411d872SAndreas Gohr $data[$col->getLabel()] = $val; 244f411d872SAndreas Gohr } else { 245f411d872SAndreas Gohr $data[] = new Value($col, $val); 246f411d872SAndreas Gohr } 247f411d872SAndreas Gohr } 248f411d872SAndreas Gohr 249f411d872SAndreas Gohr return $data; 250f411d872SAndreas Gohr } 251f411d872SAndreas Gohr 252f411d872SAndreas Gohr /** 253f411d872SAndreas Gohr * Builds the SQL statement to select the data for this page and schema 254f411d872SAndreas Gohr * 255f411d872SAndreas Gohr * @return array Two fields: the SQL string and the parameters array 256f411d872SAndreas Gohr */ 257f411d872SAndreas Gohr protected function buildGetDataSQL() { 258f411d872SAndreas Gohr $sep = Search::CONCAT_SEPARATOR; 259f411d872SAndreas Gohr $stable = 'data_' . $this->schema->getTable(); 260f411d872SAndreas Gohr $mtable = 'multi_' . $this->schema->getTable(); 261f411d872SAndreas Gohr 262f411d872SAndreas Gohr $QB = new QueryBuilder(); 263f411d872SAndreas Gohr $QB->addTable($stable, 'DATA'); 264f411d872SAndreas Gohr $QB->addSelectColumn('DATA', 'pid', 'PID'); 265f411d872SAndreas Gohr $QB->addGroupByStatement('DATA.pid'); 266f411d872SAndreas Gohr 267f411d872SAndreas Gohr foreach($this->schema->getColumns(false) as $col) { 268f411d872SAndreas Gohr 269f411d872SAndreas Gohr $colref = $col->getColref(); 270f411d872SAndreas Gohr $colname = 'col'.$colref; 271f411d872SAndreas Gohr 272f411d872SAndreas Gohr if($col->getType()->isMulti()) { 273f411d872SAndreas Gohr $tn = 'M' . $colref; 274f411d872SAndreas Gohr $QB->addLeftJoin( 275f411d872SAndreas Gohr 'DATA', 276f411d872SAndreas Gohr $mtable, 277f411d872SAndreas Gohr $tn, 278f411d872SAndreas Gohr "DATA.pid = $tn.pid AND DATA.rev = $tn.rev AND $tn.colref = $colref" 279f411d872SAndreas Gohr ); 280f411d872SAndreas Gohr $col->getType()->select($QB, $tn, 'value', $colname); 281f411d872SAndreas Gohr $sel = $QB->getSelectStatement($colname); 282f411d872SAndreas Gohr $QB->addSelectStatement("GROUP_CONCAT($sel, '$sep')", $colname); 283f411d872SAndreas Gohr } else { 284f411d872SAndreas Gohr $col->getType()->select($QB, 'DATA', $colname, $colname); 285f411d872SAndreas Gohr $QB->addGroupByStatement($colname); 286f411d872SAndreas Gohr } 287f411d872SAndreas Gohr } 288f411d872SAndreas Gohr 289f411d872SAndreas Gohr $pl = $QB->addValue($this->pid); 290f411d872SAndreas Gohr $QB->filters()->whereAnd("DATA.pid = $pl"); 291f411d872SAndreas Gohr $pl = $QB->addValue($this->ts); 292f411d872SAndreas Gohr $QB->filters()->whereAnd("DATA.rev = $pl"); 293f411d872SAndreas Gohr 294f411d872SAndreas Gohr return $QB->getSQL(); 295f411d872SAndreas Gohr } 296f411d872SAndreas Gohr 297f411d872SAndreas Gohr /** 298f411d872SAndreas Gohr * Set $this->ts to an existing timestamp, which is either current timestamp if it exists 299f411d872SAndreas Gohr * or the next oldest timestamp that exists. If not timestamp is provided it is the newest timestamp that exists. 300f411d872SAndreas Gohr * 301f411d872SAndreas Gohr * @param $page 302f411d872SAndreas Gohr * @param int|null $ts 303f411d872SAndreas Gohr * @fixme clear up description 304f411d872SAndreas Gohr */ 305f411d872SAndreas Gohr abstract protected function setCorrectTimestamp($page, $ts = null); 306f411d872SAndreas Gohr} 307f411d872SAndreas Gohr 308f411d872SAndreas Gohr 309