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