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