xref: /plugin/struct/meta/AccessTable.php (revision 984b4e7b722adaac36478a33bf21522518a14512)
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{
14f411d872SAndreas Gohr
15efe74305SAnna Dabrowska    const DEFAULT_REV = 0;
16efe74305SAnna Dabrowska    const DEFAULT_LATEST = 1;
17efe74305SAnna Dabrowska
18f411d872SAndreas Gohr    /** @var  Schema */
19f411d872SAndreas Gohr    protected $schema;
20f411d872SAndreas Gohr    protected $pid;
210ceefd5cSAnna Dabrowska    protected $rid;
22aeb8444cSAnna Dabrowska    protected $labels = [];
23f411d872SAndreas Gohr    protected $ts     = 0;
24f411d872SAndreas Gohr    /** @var \helper_plugin_sqlite */
25f411d872SAndreas Gohr    protected $sqlite;
26f411d872SAndreas Gohr
2790421550SAndreas Gohr    // options on how to retrieve data
28f411d872SAndreas Gohr    protected $opt_skipempty = false;
29f411d872SAndreas Gohr
30f411d872SAndreas Gohr    /**
31aeb8444cSAnna Dabrowska     * @var string Name of single-value table
32aeb8444cSAnna Dabrowska     */
33aeb8444cSAnna Dabrowska    protected $stable;
34aeb8444cSAnna Dabrowska
35aeb8444cSAnna Dabrowska    /**
36aeb8444cSAnna Dabrowska     * @var string Name of multi-value table
37aeb8444cSAnna Dabrowska     */
38aeb8444cSAnna Dabrowska    protected $mtable;
39aeb8444cSAnna Dabrowska
40aeb8444cSAnna Dabrowska    /**
41aeb8444cSAnna Dabrowska     * @var array Column names for the single-value insert/update
42aeb8444cSAnna Dabrowska     */
43aeb8444cSAnna Dabrowska    protected $singleCols;
44aeb8444cSAnna Dabrowska
45aeb8444cSAnna Dabrowska    /**
46aeb8444cSAnna Dabrowska     * @var array Input values for the single-value insert/update
47aeb8444cSAnna Dabrowska     */
48aeb8444cSAnna Dabrowska    protected $singleValues;
49aeb8444cSAnna Dabrowska
50aeb8444cSAnna Dabrowska    /**
51aeb8444cSAnna Dabrowska     * @var array Input values for the multi-value inserts/updates
52aeb8444cSAnna Dabrowska     */
53aeb8444cSAnna Dabrowska    protected $multiValues;
54aeb8444cSAnna Dabrowska
55aeb8444cSAnna Dabrowska
56aeb8444cSAnna Dabrowska    /**
57b9d35ff2SAnna Dabrowska     * Factory method returning the appropriate data accessor (page, lookup or serial)
58f411d872SAndreas Gohr     *
59f411d872SAndreas Gohr     * @param Schema $schema schema to load
6086a40c1eSAnna Dabrowska     * @param string $pid Page id to access
61a14cf85dSAnna Dabrowska     * @param int $ts Time at which the data should be read or written
6286a40c1eSAnna Dabrowska     * @param int $rid Row id, 0 for page type data, otherwise autoincrement
63aeb8444cSAnna Dabrowska     * @return AccessTableData|AccessTableLookup
64f411d872SAndreas Gohr     */
65d6d97f60SAnna Dabrowska    public static function bySchema(Schema $schema, $pid, $ts = 0, $rid = 0)
66d6d97f60SAnna Dabrowska    {
67aeb8444cSAnna Dabrowska        if (self::isTypePage($pid, $ts, $rid)) {
680ceefd5cSAnna Dabrowska            return new AccessTableData($schema, $pid, $ts, $rid);
69f411d872SAndreas Gohr        }
70aeb8444cSAnna Dabrowska        return new AccessTableLookup($schema, $pid, $ts, $rid);
71aeb8444cSAnna Dabrowska    }
72f411d872SAndreas Gohr
73f411d872SAndreas Gohr    /**
74c73fba38SAnna Dabrowska     * Factory Method to access data
75f411d872SAndreas Gohr     *
76f411d872SAndreas Gohr     * @param string $tablename schema to load
7786a40c1eSAnna Dabrowska     * @param string $pid Page id to access
78a14cf85dSAnna Dabrowska     * @param int $ts Time at which the data should be read or written
7986a40c1eSAnna Dabrowska     * @param int $rid Row id, 0 for page type data, otherwise autoincrement
80aeb8444cSAnna Dabrowska     * @return AccessTableData|AccessTableLookup
81f411d872SAndreas Gohr     */
82d6d97f60SAnna Dabrowska    public static function byTableName($tablename, $pid, $ts = 0, $rid = 0)
83d6d97f60SAnna Dabrowska    {
84*984b4e7bSAnna Dabrowska        // force loading the latest schema for anything other than page data,
85*984b4e7bSAnna Dabrowska        // for which we might actually need the history
86*984b4e7bSAnna Dabrowska        if (!self::isTypePage($pid, $ts, $rid)) {
87*984b4e7bSAnna Dabrowska            $schema = new Schema($tablename, time());
88*984b4e7bSAnna Dabrowska        } else {
89f411d872SAndreas Gohr            $schema = new Schema($tablename, $ts);
90*984b4e7bSAnna Dabrowska        }
910ceefd5cSAnna Dabrowska        return self::bySchema($schema, $pid, $ts, $rid);
92f411d872SAndreas Gohr    }
93f411d872SAndreas Gohr
94f411d872SAndreas Gohr    /**
95f411d872SAndreas Gohr     * AccessTable constructor
96f411d872SAndreas Gohr     *
97897aef42SAndreas Gohr     * @param Schema $schema The schema valid at $ts
9886a40c1eSAnna Dabrowska     * @param string $pid Page id
99897aef42SAndreas Gohr     * @param int $ts Time at which the data should be read or written, 0 for now
1000ceefd5cSAnna Dabrowska     * @param int $rid Row id: 0 for pages, autoincremented for other types
101f411d872SAndreas Gohr     */
102d6d97f60SAnna Dabrowska    public function __construct(Schema $schema, $pid, $ts = 0, $rid = 0)
103d6d97f60SAnna Dabrowska    {
104f411d872SAndreas Gohr        /** @var \helper_plugin_struct_db $helper */
105f411d872SAndreas Gohr        $helper = plugin_load('helper', 'struct_db');
106f411d872SAndreas Gohr        $this->sqlite = $helper->getDB();
107f411d872SAndreas Gohr
108f411d872SAndreas Gohr        if (!$schema->getId()) {
109f411d872SAndreas Gohr            throw new StructException('Schema does not exist. Only data of existing schemas can be accessed');
110f411d872SAndreas Gohr        }
111f411d872SAndreas Gohr
112f411d872SAndreas Gohr        $this->schema = $schema;
113f411d872SAndreas Gohr        $this->pid = $pid;
1140ceefd5cSAnna Dabrowska        $this->rid = $rid;
115897aef42SAndreas Gohr        $this->setTimestamp($ts);
116f411d872SAndreas Gohr        foreach ($this->schema->getColumns() as $col) {
117f411d872SAndreas Gohr            $this->labels[$col->getColref()] = $col->getType()->getLabel();
118f411d872SAndreas Gohr        }
119f411d872SAndreas Gohr    }
120f411d872SAndreas Gohr
121f411d872SAndreas Gohr    /**
122f411d872SAndreas Gohr     * gives access to the schema
123f411d872SAndreas Gohr     *
124f411d872SAndreas Gohr     * @return Schema
125f411d872SAndreas Gohr     */
126d6d97f60SAnna Dabrowska    public function getSchema()
127d6d97f60SAnna Dabrowska    {
128f411d872SAndreas Gohr        return $this->schema;
129f411d872SAndreas Gohr    }
130f411d872SAndreas Gohr
131f411d872SAndreas Gohr    /**
132f107f479SAndreas Gohr     * The current pid
133f107f479SAndreas Gohr     *
13486a40c1eSAnna Dabrowska     * @return string
135f107f479SAndreas Gohr     */
136d6d97f60SAnna Dabrowska    public function getPid()
137d6d97f60SAnna Dabrowska    {
138f107f479SAndreas Gohr        return $this->pid;
139f107f479SAndreas Gohr    }
140f107f479SAndreas Gohr
141f107f479SAndreas Gohr    /**
1420ceefd5cSAnna Dabrowska     * The current rid
1430ceefd5cSAnna Dabrowska     *
14486a40c1eSAnna Dabrowska     * @return int
1450ceefd5cSAnna Dabrowska     */
146d6d97f60SAnna Dabrowska    public function getRid()
147d6d97f60SAnna Dabrowska    {
1480ceefd5cSAnna Dabrowska        return $this->rid;
1490ceefd5cSAnna Dabrowska    }
1500ceefd5cSAnna Dabrowska
1510ceefd5cSAnna Dabrowska    /**
152f411d872SAndreas Gohr     * Should remove the current data, by either deleting or ovewriting it
153f411d872SAndreas Gohr     *
154f411d872SAndreas Gohr     * @return bool if the delete succeeded
155f411d872SAndreas Gohr     */
156f411d872SAndreas Gohr    abstract public function clearData();
157f411d872SAndreas Gohr
158f411d872SAndreas Gohr    /**
159f411d872SAndreas Gohr     * Save the data to the database.
160f411d872SAndreas Gohr     *
161f411d872SAndreas Gohr     * We differentiate between single-value-column and multi-value-column by the value to the respective column-name,
162f411d872SAndreas Gohr     * i.e. depending on if that is a string or an array, respectively.
163f411d872SAndreas Gohr     *
16428d21c10SAnna Dabrowska     * @fixme we need a flag here to disable per-save db transactions if this is executed in bulk
165f411d872SAndreas Gohr     * @param array $data typelabel => value for single fields or typelabel => array(value, value, ...) for multi fields
166f411d872SAndreas Gohr     * @return bool success of saving the data to the database
167f411d872SAndreas Gohr     */
168aeb8444cSAnna Dabrowska    public function saveData($data)
169aeb8444cSAnna Dabrowska    {
170aeb8444cSAnna Dabrowska        if (!$this->validateTypeData($data)) {
171aeb8444cSAnna Dabrowska            return false;
172aeb8444cSAnna Dabrowska        }
173aeb8444cSAnna Dabrowska
174aeb8444cSAnna Dabrowska        $this->stable = 'data_' . $this->schema->getTable();
175aeb8444cSAnna Dabrowska        $this->mtable = 'multi_' . $this->schema->getTable();
176aeb8444cSAnna Dabrowska
177aeb8444cSAnna Dabrowska        $colrefs = array_flip($this->labels);
178aeb8444cSAnna Dabrowska
179aeb8444cSAnna Dabrowska        foreach ($data as $colname => $value) {
180aeb8444cSAnna Dabrowska            if (!isset($colrefs[$colname])) {
181aeb8444cSAnna Dabrowska                throw new StructException("Unknown column %s in schema.", hsc($colname));
182aeb8444cSAnna Dabrowska            }
183aeb8444cSAnna Dabrowska
184aeb8444cSAnna Dabrowska            $this->singleCols[] = 'col' . $colrefs[$colname];
185aeb8444cSAnna Dabrowska            if (is_array($value)) {
186aeb8444cSAnna Dabrowska                foreach ($value as $index => $multivalue) {
187aeb8444cSAnna Dabrowska                    $this->multiValues[] = [$colrefs[$colname], $index + 1, $multivalue];
188aeb8444cSAnna Dabrowska                }
189aeb8444cSAnna Dabrowska                // copy first value to the single column
190aeb8444cSAnna Dabrowska                if (isset($value[0])) {
191aeb8444cSAnna Dabrowska                    $this->singleValues[] = $value[0];
192aeb8444cSAnna Dabrowska                } else {
193aeb8444cSAnna Dabrowska                    $this->singleValues[] = null;
194aeb8444cSAnna Dabrowska                }
195aeb8444cSAnna Dabrowska            } else {
196aeb8444cSAnna Dabrowska                $this->singleValues[] = $value;
197aeb8444cSAnna Dabrowska            }
198aeb8444cSAnna Dabrowska        }
199aeb8444cSAnna Dabrowska
200aeb8444cSAnna Dabrowska        $this->sqlite->query('BEGIN TRANSACTION');
201aeb8444cSAnna Dabrowska
202aeb8444cSAnna Dabrowska        $ok = $this->beforeSave();
203aeb8444cSAnna Dabrowska
204aeb8444cSAnna Dabrowska        // insert single values
205aeb8444cSAnna Dabrowska        $ok = $ok && $this->sqlite->query(
206aeb8444cSAnna Dabrowska            $this->getSingleSql(),
207aeb8444cSAnna Dabrowska            array_merge($this->getSingleNoninputValues(), $this->singleValues)
208aeb8444cSAnna Dabrowska        );
209aeb8444cSAnna Dabrowska
210aeb8444cSAnna Dabrowska        $ok = $ok && $this->afterSingleSave();
211aeb8444cSAnna Dabrowska
212aeb8444cSAnna Dabrowska        // insert multi values
213aeb8444cSAnna Dabrowska        if ($ok && $this->multiValues) {
214aeb8444cSAnna Dabrowska            $multisql = $this->getMultiSql();
215aeb8444cSAnna Dabrowska            $multiNoninputValues = $this->getMultiNoninputValues();
216aeb8444cSAnna Dabrowska            foreach ($this->multiValues as $value) {
217aeb8444cSAnna Dabrowska                $ok = $ok && $this->sqlite->query(
218aeb8444cSAnna Dabrowska                    $multisql,
219aeb8444cSAnna Dabrowska                    array_merge($multiNoninputValues, $value)
220aeb8444cSAnna Dabrowska                );
221aeb8444cSAnna Dabrowska            }
222aeb8444cSAnna Dabrowska        }
223aeb8444cSAnna Dabrowska
224aeb8444cSAnna Dabrowska        if (!$ok) {
225aeb8444cSAnna Dabrowska            $this->sqlite->query('ROLLBACK TRANSACTION');
226aeb8444cSAnna Dabrowska            return false;
227aeb8444cSAnna Dabrowska        }
228aeb8444cSAnna Dabrowska        $this->sqlite->query('COMMIT TRANSACTION');
229aeb8444cSAnna Dabrowska        return true;
230aeb8444cSAnna Dabrowska    }
231aeb8444cSAnna Dabrowska
232aeb8444cSAnna Dabrowska    /**
233aeb8444cSAnna Dabrowska     * Check whether all required data is present
234aeb8444cSAnna Dabrowska     *
235aeb8444cSAnna Dabrowska     * @param array $data
236aeb8444cSAnna Dabrowska     * @return bool
237aeb8444cSAnna Dabrowska     */
238aeb8444cSAnna Dabrowska    abstract protected function validateTypeData($data);
239aeb8444cSAnna Dabrowska
240aeb8444cSAnna Dabrowska    /**
241aeb8444cSAnna Dabrowska     * Names of non-input columns to be inserted into SQL query
242aeb8444cSAnna Dabrowska     *
243aeb8444cSAnna Dabrowska     * @return array
244aeb8444cSAnna Dabrowska     */
245aeb8444cSAnna Dabrowska    abstract protected function getSingleNoninputCols();
246aeb8444cSAnna Dabrowska
247aeb8444cSAnna Dabrowska    /**
248aeb8444cSAnna Dabrowska     * Values for non-input columns to be inserted into SQL query
249aeb8444cSAnna Dabrowska     * for single-value tables
250aeb8444cSAnna Dabrowska     *
251aeb8444cSAnna Dabrowska     * @return array
252aeb8444cSAnna Dabrowska     */
253aeb8444cSAnna Dabrowska    abstract protected function getSingleNoninputValues();
254aeb8444cSAnna Dabrowska
255aeb8444cSAnna Dabrowska    /**
256aeb8444cSAnna Dabrowska     * String template for single-value table
257aeb8444cSAnna Dabrowska     *
258aeb8444cSAnna Dabrowska     * @return string
259aeb8444cSAnna Dabrowska     */
260aeb8444cSAnna Dabrowska    protected function getSingleSql()
261aeb8444cSAnna Dabrowska    {
262aeb8444cSAnna Dabrowska        $cols = array_merge($this->getSingleNoninputCols(), $this->singleCols);
263aeb8444cSAnna Dabrowska        $cols = join(',', $cols);
264aeb8444cSAnna Dabrowska        $vals = array_merge($this->getSingleNoninputValues(), $this->singleValues);
265aeb8444cSAnna Dabrowska
266aeb8444cSAnna Dabrowska        return "INSERT INTO $this->stable ($cols) VALUES (" . trim(str_repeat('?,', count($vals)), ',') . ');';
267aeb8444cSAnna Dabrowska    }
268aeb8444cSAnna Dabrowska
269aeb8444cSAnna Dabrowska    /**
270aeb8444cSAnna Dabrowska     * Optional operations to be executed before saving data
271aeb8444cSAnna Dabrowska     *
272aeb8444cSAnna Dabrowska     * @return bool False if any of the operations failed and transaction should be rolled back
273aeb8444cSAnna Dabrowska     */
274aeb8444cSAnna Dabrowska    protected function beforeSave()
275aeb8444cSAnna Dabrowska    {
276aeb8444cSAnna Dabrowska        return true;
277aeb8444cSAnna Dabrowska    }
278aeb8444cSAnna Dabrowska
279aeb8444cSAnna Dabrowska    /**
280aeb8444cSAnna Dabrowska     * Optional operations to be executed after saving data to single-value table,
281aeb8444cSAnna Dabrowska     * before saving multivalues
282aeb8444cSAnna Dabrowska     *
283aeb8444cSAnna Dabrowska     * @return bool False if anything goes wrong and transaction should be rolled back
284aeb8444cSAnna Dabrowska     */
285aeb8444cSAnna Dabrowska    protected function afterSingleSave()
286aeb8444cSAnna Dabrowska    {
287aeb8444cSAnna Dabrowska        return true;
288aeb8444cSAnna Dabrowska    }
289aeb8444cSAnna Dabrowska
290aeb8444cSAnna Dabrowska    /**
291aeb8444cSAnna Dabrowska     * String template for multi-value table
292aeb8444cSAnna Dabrowska     *
293aeb8444cSAnna Dabrowska     * @return string
294aeb8444cSAnna Dabrowska     */
295aeb8444cSAnna Dabrowska    abstract protected function getMultiSql();
296aeb8444cSAnna Dabrowska
297aeb8444cSAnna Dabrowska    /**
298aeb8444cSAnna Dabrowska     * Values for non-input columns to be inserted into SQL query
299aeb8444cSAnna Dabrowska     * for multi-value tables
300aeb8444cSAnna Dabrowska     * @return array
301aeb8444cSAnna Dabrowska     */
302aeb8444cSAnna Dabrowska    abstract protected function getMultiNoninputValues();
303aeb8444cSAnna Dabrowska
304f411d872SAndreas Gohr
305f411d872SAndreas Gohr    /**
306f411d872SAndreas Gohr     * Should empty or invisible (inpage) fields be returned?
307f411d872SAndreas Gohr     *
308f411d872SAndreas Gohr     * Defaults to false
309f411d872SAndreas Gohr     *
310f411d872SAndreas Gohr     * @param null|bool $set new value, null to read only
311f411d872SAndreas Gohr     * @return bool current value (after set)
312f411d872SAndreas Gohr     */
313d6d97f60SAnna Dabrowska    public function optionSkipEmpty($set = null)
314d6d97f60SAnna Dabrowska    {
315f411d872SAndreas Gohr        if (!is_null($set)) {
316f411d872SAndreas Gohr            $this->opt_skipempty = $set;
317f411d872SAndreas Gohr        }
318f411d872SAndreas Gohr        return $this->opt_skipempty;
319f411d872SAndreas Gohr    }
320f411d872SAndreas Gohr
321f411d872SAndreas Gohr    /**
322f411d872SAndreas Gohr     * Get the value of a single column
323f411d872SAndreas Gohr     *
324f411d872SAndreas Gohr     * @param Column $column
325f411d872SAndreas Gohr     * @return Value|null
326f411d872SAndreas Gohr     */
327d6d97f60SAnna Dabrowska    public function getDataColumn($column)
328d6d97f60SAnna Dabrowska    {
329f411d872SAndreas Gohr        $data = $this->getData();
330f411d872SAndreas Gohr        foreach ($data as $value) {
331f411d872SAndreas Gohr            if ($value->getColumn() == $column) {
332f411d872SAndreas Gohr                return $value;
333f411d872SAndreas Gohr            }
334f411d872SAndreas Gohr        }
335f411d872SAndreas Gohr        return null;
336f411d872SAndreas Gohr    }
337f411d872SAndreas Gohr
338f411d872SAndreas Gohr    /**
339f411d872SAndreas Gohr     * returns the data saved for the page
340f411d872SAndreas Gohr     *
341f411d872SAndreas Gohr     * @return Value[] a list of values saved for the current page
342f411d872SAndreas Gohr     */
343d6d97f60SAnna Dabrowska    public function getData()
344d6d97f60SAnna Dabrowska    {
345f411d872SAndreas Gohr        $data = $this->getDataFromDB();
346f411d872SAndreas Gohr        $data = $this->consolidateData($data, false);
347f411d872SAndreas Gohr        return $data;
348f411d872SAndreas Gohr    }
349f411d872SAndreas Gohr
350f411d872SAndreas Gohr    /**
351f411d872SAndreas Gohr     * returns the data saved for the page as associative array
352f411d872SAndreas Gohr     *
353f411d872SAndreas Gohr     * The array returned is in the same format as used in @see saveData()
354f411d872SAndreas Gohr     *
35590421550SAndreas Gohr     * It always returns raw Values!
35690421550SAndreas Gohr     *
357f411d872SAndreas Gohr     * @return array
358f411d872SAndreas Gohr     */
359d6d97f60SAnna Dabrowska    public function getDataArray()
360d6d97f60SAnna Dabrowska    {
361f411d872SAndreas Gohr        $data = $this->getDataFromDB();
362f411d872SAndreas Gohr        $data = $this->consolidateData($data, true);
363f411d872SAndreas Gohr        return $data;
364f411d872SAndreas Gohr    }
365f411d872SAndreas Gohr
366f411d872SAndreas Gohr    /**
367f411d872SAndreas Gohr     * Return the data in pseudo syntax
368f411d872SAndreas Gohr     */
369d6d97f60SAnna Dabrowska    public function getDataPseudoSyntax()
370d6d97f60SAnna Dabrowska    {
371f411d872SAndreas Gohr        $result = '';
372a0a1d14eSAndreas Gohr        $data = $this->getData();
373a0a1d14eSAndreas Gohr
374a0a1d14eSAndreas Gohr        foreach ($data as $value) {
375a0a1d14eSAndreas Gohr            $key = $value->getColumn()->getFullQualifiedLabel();
376a0a1d14eSAndreas Gohr            $value = $value->getDisplayValue();
377f411d872SAndreas Gohr            if (is_array($value)) $value = join(', ', $value);
378f411d872SAndreas Gohr            $result .= sprintf("% -20s : %s\n", $key, $value);
379f411d872SAndreas Gohr        }
380f411d872SAndreas Gohr        return $result;
381f411d872SAndreas Gohr    }
382f411d872SAndreas Gohr
383f411d872SAndreas Gohr    /**
384f411d872SAndreas Gohr     * retrieve the data saved for the page from the database. Usually there is no need to call this function.
385f411d872SAndreas Gohr     * Call @see SchemaData::getData instead.
386f411d872SAndreas Gohr     */
387d6d97f60SAnna Dabrowska    protected function getDataFromDB()
388d6d97f60SAnna Dabrowska    {
389efe74305SAnna Dabrowska        $idColumn = self::isTypePage($this->pid, $this->ts, $this->rid) ? 'pid' : 'rid';
390efe74305SAnna Dabrowska        list($sql, $opt) = $this->buildGetDataSQL($idColumn);
391f411d872SAndreas Gohr
392f411d872SAndreas Gohr        $res = $this->sqlite->query($sql, $opt);
393f411d872SAndreas Gohr        $data = $this->sqlite->res2arr($res);
3949c00b26cSAndreas Gohr        $this->sqlite->res_close($res);
395f411d872SAndreas Gohr        return $data;
396f411d872SAndreas Gohr    }
397f411d872SAndreas Gohr
398f411d872SAndreas Gohr    /**
399f411d872SAndreas Gohr     * Creates a proper result array from the database data
400f411d872SAndreas Gohr     *
401f411d872SAndreas Gohr     * @param array $DBdata the data as it is retrieved from the database, i.e. by SchemaData::getDataFromDB
402f411d872SAndreas Gohr     * @param bool $asarray return data as associative array (true) or as array of Values (false)
403f411d872SAndreas Gohr     * @return array|Value[]
404f411d872SAndreas Gohr     */
405d6d97f60SAnna Dabrowska    protected function consolidateData($DBdata, $asarray = false)
406d6d97f60SAnna Dabrowska    {
407f411d872SAndreas Gohr        $data = array();
408f411d872SAndreas Gohr
409f411d872SAndreas Gohr        $sep = Search::CONCAT_SEPARATOR;
410f411d872SAndreas Gohr
411f411d872SAndreas Gohr        foreach ($this->schema->getColumns(false) as $col) {
41290421550SAndreas Gohr            // if no data saved yet, return empty strings
413f411d872SAndreas Gohr            if ($DBdata) {
414bab52340SAndreas Gohr                $val = $DBdata[0]['out' . $col->getColref()];
415f411d872SAndreas Gohr            } else {
416f411d872SAndreas Gohr                $val = '';
417f411d872SAndreas Gohr            }
418f411d872SAndreas Gohr
419f411d872SAndreas Gohr            // multi val data is concatenated
420f411d872SAndreas Gohr            if ($col->isMulti()) {
421f411d872SAndreas Gohr                $val = explode($sep, $val);
422f411d872SAndreas Gohr                $val = array_filter($val);
423f411d872SAndreas Gohr            }
424f411d872SAndreas Gohr
42590421550SAndreas Gohr            $value = new Value($col, $val);
426f411d872SAndreas Gohr
42790421550SAndreas Gohr            if ($this->opt_skipempty && $value->isEmpty()) continue;
42890421550SAndreas Gohr            if ($this->opt_skipempty && !$col->isVisibleInPage()) continue; //FIXME is this a correct assumption?
42990421550SAndreas Gohr
43090421550SAndreas Gohr            // for arrays, we return the raw value only
431f411d872SAndreas Gohr            if ($asarray) {
43290421550SAndreas Gohr                $data[$col->getLabel()] = $value->getRawValue();
433f411d872SAndreas Gohr            } else {
4346e54daafSMichael Große                $data[$col->getLabel()] = $value;
435f411d872SAndreas Gohr            }
436f411d872SAndreas Gohr        }
437f411d872SAndreas Gohr
438f411d872SAndreas Gohr        return $data;
439f411d872SAndreas Gohr    }
440f411d872SAndreas Gohr
441f411d872SAndreas Gohr    /**
442f411d872SAndreas Gohr     * Builds the SQL statement to select the data for this page and schema
443f411d872SAndreas Gohr     *
444f411d872SAndreas Gohr     * @return array Two fields: the SQL string and the parameters array
445f411d872SAndreas Gohr     */
446d6d97f60SAnna Dabrowska    protected function buildGetDataSQL($idColumn = 'pid')
447d6d97f60SAnna Dabrowska    {
448f411d872SAndreas Gohr        $sep = Search::CONCAT_SEPARATOR;
449f411d872SAndreas Gohr        $stable = 'data_' . $this->schema->getTable();
450f411d872SAndreas Gohr        $mtable = 'multi_' . $this->schema->getTable();
451f411d872SAndreas Gohr
452f411d872SAndreas Gohr        $QB = new QueryBuilder();
453f411d872SAndreas Gohr        $QB->addTable($stable, 'DATA');
4546fd73b4bSAnna Dabrowska        $QB->addSelectColumn('DATA', $idColumn, strtoupper($idColumn));
4556fd73b4bSAnna Dabrowska        $QB->addGroupByStatement("DATA.$idColumn");
456f411d872SAndreas Gohr
457f411d872SAndreas Gohr        foreach ($this->schema->getColumns(false) as $col) {
458f411d872SAndreas Gohr            $colref = $col->getColref();
459f411d872SAndreas Gohr            $colname = 'col' . $colref;
460bab52340SAndreas Gohr            $outname = 'out' . $colref;
461f411d872SAndreas Gohr
462f411d872SAndreas Gohr            if ($col->getType()->isMulti()) {
463f411d872SAndreas Gohr                $tn = 'M' . $colref;
464f411d872SAndreas Gohr                $QB->addLeftJoin(
465f411d872SAndreas Gohr                    'DATA',
466f411d872SAndreas Gohr                    $mtable,
467f411d872SAndreas Gohr                    $tn,
4686fd73b4bSAnna Dabrowska                    "DATA.$idColumn = $tn.$idColumn AND DATA.rev = $tn.rev AND $tn.colref = $colref"
469f411d872SAndreas Gohr                );
470bab52340SAndreas Gohr                $col->getType()->select($QB, $tn, 'value', $outname);
471bab52340SAndreas Gohr                $sel = $QB->getSelectStatement($outname);
472bab52340SAndreas Gohr                $QB->addSelectStatement("GROUP_CONCAT($sel, '$sep')", $outname);
473f411d872SAndreas Gohr            } else {
474bab52340SAndreas Gohr                $col->getType()->select($QB, 'DATA', $colname, $outname);
475bab52340SAndreas Gohr                $QB->addGroupByStatement($outname);
476f411d872SAndreas Gohr            }
477f411d872SAndreas Gohr        }
478f411d872SAndreas Gohr
4796fd73b4bSAnna Dabrowska        $pl = $QB->addValue($this->{$idColumn});
4806fd73b4bSAnna Dabrowska        $QB->filters()->whereAnd("DATA.$idColumn = $pl");
481897aef42SAndreas Gohr        $pl = $QB->addValue($this->getLastRevisionTimestamp());
482f411d872SAndreas Gohr        $QB->filters()->whereAnd("DATA.rev = $pl");
483f411d872SAndreas Gohr
484f411d872SAndreas Gohr        return $QB->getSQL();
485f411d872SAndreas Gohr    }
486f411d872SAndreas Gohr
487f411d872SAndreas Gohr    /**
48813eddb0fSAndreas Gohr     * @param int $ts
48913eddb0fSAndreas Gohr     */
490d6d97f60SAnna Dabrowska    public function setTimestamp($ts)
491d6d97f60SAnna Dabrowska    {
492897aef42SAndreas Gohr        if ($ts && $ts < $this->schema->getTimeStamp()) {
493897aef42SAndreas Gohr            throw new StructException('Given timestamp is not valid for current Schema');
494897aef42SAndreas Gohr        }
495897aef42SAndreas Gohr
49613eddb0fSAndreas Gohr        $this->ts = $ts;
49713eddb0fSAndreas Gohr    }
49813eddb0fSAndreas Gohr
49913eddb0fSAndreas Gohr    /**
50069f7ec8fSAnna Dabrowska     * Returns the timestamp from the current data
50169f7ec8fSAnna Dabrowska     * @return int
50269f7ec8fSAnna Dabrowska     */
50369f7ec8fSAnna Dabrowska    public function getTimestamp()
50469f7ec8fSAnna Dabrowska    {
50569f7ec8fSAnna Dabrowska        return $this->ts;
50669f7ec8fSAnna Dabrowska    }
50769f7ec8fSAnna Dabrowska
50869f7ec8fSAnna Dabrowska    /**
509897aef42SAndreas Gohr     * Return the last time an edit happened for this table for the currently set
510a14cf85dSAnna Dabrowska     * time and pid. Used in @see buildGetDataSQL()
511f411d872SAndreas Gohr     *
512897aef42SAndreas Gohr     * @return int
513f411d872SAndreas Gohr     */
514897aef42SAndreas Gohr    abstract protected function getLastRevisionTimestamp();
51587dc1344SAndreas Gohr
51687dc1344SAndreas Gohr    /**
51787dc1344SAndreas Gohr     * Check if the given data validates against the current types.
51887dc1344SAndreas Gohr     *
51987dc1344SAndreas Gohr     * @param array $data
52093ca6f4fSAndreas Gohr     * @return AccessDataValidator
52187dc1344SAndreas Gohr     */
522d6d97f60SAnna Dabrowska    public function getValidator($data)
523d6d97f60SAnna Dabrowska    {
52493ca6f4fSAndreas Gohr        return new AccessDataValidator($this, $data);
52587dc1344SAndreas Gohr    }
526c73fba38SAnna Dabrowska
527c73fba38SAnna Dabrowska    /**
528c73fba38SAnna Dabrowska     * Returns true if data is of type "page"
529c73fba38SAnna Dabrowska     *
530c73fba38SAnna Dabrowska     * @param string $pid
531c73fba38SAnna Dabrowska     * @param int $rev
532c73fba38SAnna Dabrowska     * @param int $rid
533c73fba38SAnna Dabrowska     * @return bool
534c73fba38SAnna Dabrowska     */
535c73fba38SAnna Dabrowska    public static function isTypePage($pid, $rev, $rid)
536c73fba38SAnna Dabrowska    {
537c73fba38SAnna Dabrowska        return $rev > 0;
538c73fba38SAnna Dabrowska    }
539c73fba38SAnna Dabrowska
540c73fba38SAnna Dabrowska    /**
541c73fba38SAnna Dabrowska     * Returns true if data is of type "lookup"
542c73fba38SAnna Dabrowska     *
543c73fba38SAnna Dabrowska     * @param string $pid
544c73fba38SAnna Dabrowska     * @param int $rev
545c73fba38SAnna Dabrowska     * @param int $rid
546c73fba38SAnna Dabrowska     * @return bool
547c73fba38SAnna Dabrowska     */
548c73fba38SAnna Dabrowska    public static function isTypeLookup($pid, $rev, $rid)
549c73fba38SAnna Dabrowska    {
550c73fba38SAnna Dabrowska        return $pid === '';
551c73fba38SAnna Dabrowska    }
552c73fba38SAnna Dabrowska
553c73fba38SAnna Dabrowska    /**
554c73fba38SAnna Dabrowska     * Returns true if data is of type "serial"
555c73fba38SAnna Dabrowska     *
556c73fba38SAnna Dabrowska     * @param string $pid
557c73fba38SAnna Dabrowska     * @param int $rev
558c73fba38SAnna Dabrowska     * @param int $rid
559c73fba38SAnna Dabrowska     * @return bool
560c73fba38SAnna Dabrowska     */
561c73fba38SAnna Dabrowska    public static function isTypeSerial($pid, $rev, $rid)
562c73fba38SAnna Dabrowska    {
563c73fba38SAnna Dabrowska        return $pid !== '' && $rev === 0;
564c73fba38SAnna Dabrowska    }
565f411d872SAndreas Gohr}
566