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