xref: /plugin/struct/meta/AccessTable.php (revision 6a819106d9214b25a316f4707f497180f2b71449)
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{
1417a3a578SAndreas Gohr    public const DEFAULT_REV = 0;
1517a3a578SAndreas Gohr    public const DEFAULT_LATEST = 1;
16efe74305SAnna Dabrowska
17f411d872SAndreas Gohr    /** @var  Schema */
18f411d872SAndreas Gohr    protected $schema;
19f411d872SAndreas Gohr    protected $pid;
200ceefd5cSAnna Dabrowska    protected $rid;
21aeb8444cSAnna Dabrowska    protected $labels = [];
22f411d872SAndreas Gohr    protected $ts = 0;
23da62ec9cSAnna Dabrowska    protected $published;
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
30d680cb37SAnna Dabrowska    protected $optQueries = [];
31d680cb37SAnna Dabrowska
32f411d872SAndreas Gohr    /**
33aeb8444cSAnna Dabrowska     * @var string Name of single-value table
34aeb8444cSAnna Dabrowska     */
35aeb8444cSAnna Dabrowska    protected $stable;
36aeb8444cSAnna Dabrowska
37aeb8444cSAnna Dabrowska    /**
38aeb8444cSAnna Dabrowska     * @var string Name of multi-value table
39aeb8444cSAnna Dabrowska     */
40aeb8444cSAnna Dabrowska    protected $mtable;
41aeb8444cSAnna Dabrowska
42aeb8444cSAnna Dabrowska    /**
43aeb8444cSAnna Dabrowska     * @var array Column names for the single-value insert/update
44aeb8444cSAnna Dabrowska     */
45aeb8444cSAnna Dabrowska    protected $singleCols;
46aeb8444cSAnna Dabrowska
47aeb8444cSAnna Dabrowska    /**
48aeb8444cSAnna Dabrowska     * @var array Input values for the single-value insert/update
49aeb8444cSAnna Dabrowska     */
50aeb8444cSAnna Dabrowska    protected $singleValues;
51aeb8444cSAnna Dabrowska
52aeb8444cSAnna Dabrowska    /**
53aeb8444cSAnna Dabrowska     * @var array Input values for the multi-value inserts/updates
54aeb8444cSAnna Dabrowska     */
55aeb8444cSAnna Dabrowska    protected $multiValues;
56aeb8444cSAnna Dabrowska
574cd5cc28SAnna Dabrowska    public static function getPageAccess($tablename, $pid, $ts = 0)
584cd5cc28SAnna Dabrowska    {
594cd5cc28SAnna Dabrowska        $schema = new Schema($tablename, $ts);
60308cc83fSAndreas Gohr        return new AccessTablePage($schema, $pid, $ts, 0);
614cd5cc28SAnna Dabrowska    }
624cd5cc28SAnna Dabrowska
634cd5cc28SAnna Dabrowska    public static function getSerialAccess($tablename, $pid, $rid = 0)
644cd5cc28SAnna Dabrowska    {
654cd5cc28SAnna Dabrowska        $schema = new Schema($tablename, 0);
664cd5cc28SAnna Dabrowska        return new AccessTableSerial($schema, $pid, 0, $rid);
674cd5cc28SAnna Dabrowska    }
684cd5cc28SAnna Dabrowska
69308cc83fSAndreas Gohr    public static function getGlobalAccess($tablename, $rid = 0)
704cd5cc28SAnna Dabrowska    {
714cd5cc28SAnna Dabrowska        $schema = new Schema($tablename, 0);
72308cc83fSAndreas Gohr        return new AccessTableGlobal($schema, '', 0, $rid);
734cd5cc28SAnna Dabrowska    }
74aeb8444cSAnna Dabrowska
75aeb8444cSAnna Dabrowska    /**
76308cc83fSAndreas Gohr     * Factory method returning the appropriate data accessor (page, global or serial)
77f411d872SAndreas Gohr     *
78f411d872SAndreas Gohr     * @param Schema $schema schema to load
7986a40c1eSAnna Dabrowska     * @param string $pid Page id to access
80a14cf85dSAnna Dabrowska     * @param int $ts Time at which the data should be read or written
8186a40c1eSAnna Dabrowska     * @param int $rid Row id, 0 for page type data, otherwise autoincrement
82308cc83fSAndreas Gohr     * @return AccessTablePage|AccessTableGlobal
83308cc83fSAndreas Gohr     * @deprecated
84f411d872SAndreas Gohr     */
85d6d97f60SAnna Dabrowska    public static function bySchema(Schema $schema, $pid, $ts = 0, $rid = 0)
86d6d97f60SAnna Dabrowska    {
874cd5cc28SAnna Dabrowska        if (self::isTypePage($pid, $ts)) {
88308cc83fSAndreas Gohr            return new AccessTablePage($schema, $pid, $ts, $rid);
89f411d872SAndreas Gohr        }
90308cc83fSAndreas Gohr        return new AccessTableGlobal($schema, $pid, $ts, $rid);
91aeb8444cSAnna Dabrowska    }
92f411d872SAndreas Gohr
93f411d872SAndreas Gohr    /**
94c73fba38SAnna Dabrowska     * Factory Method to access data
95f411d872SAndreas Gohr     *
96f411d872SAndreas Gohr     * @param string $tablename schema to load
9786a40c1eSAnna Dabrowska     * @param string $pid Page id to access
98a14cf85dSAnna Dabrowska     * @param int $ts Time at which the data should be read or written
9986a40c1eSAnna Dabrowska     * @param int $rid Row id, 0 for page type data, otherwise autoincrement
100308cc83fSAndreas Gohr     * @return AccessTablePage|AccessTableGlobal
101308cc83fSAndreas Gohr     * @deprecated  Use specific methods since we can no longer
102308cc83fSAndreas Gohr     *              guarantee instantiating the required descendant class
103f411d872SAndreas Gohr     */
104d6d97f60SAnna Dabrowska    public static function byTableName($tablename, $pid, $ts = 0, $rid = 0)
105d6d97f60SAnna Dabrowska    {
106984b4e7bSAnna Dabrowska        // force loading the latest schema for anything other than page data,
107984b4e7bSAnna Dabrowska        // for which we might actually need the history
1084cd5cc28SAnna Dabrowska        if (!self::isTypePage($pid, $ts)) {
109984b4e7bSAnna Dabrowska            $schema = new Schema($tablename, time());
110984b4e7bSAnna Dabrowska        } else {
111f411d872SAndreas Gohr            $schema = new Schema($tablename, $ts);
112984b4e7bSAnna Dabrowska        }
1130ceefd5cSAnna Dabrowska        return self::bySchema($schema, $pid, $ts, $rid);
114f411d872SAndreas Gohr    }
115f411d872SAndreas Gohr
116f411d872SAndreas Gohr    /**
117f411d872SAndreas Gohr     * AccessTable constructor
118f411d872SAndreas Gohr     *
119897aef42SAndreas Gohr     * @param Schema $schema The schema valid at $ts
12086a40c1eSAnna Dabrowska     * @param string $pid Page id
121897aef42SAndreas Gohr     * @param int $ts Time at which the data should be read or written, 0 for now
1220ceefd5cSAnna Dabrowska     * @param int $rid Row id: 0 for pages, autoincremented for other types
123f411d872SAndreas Gohr     */
1244cd5cc28SAnna Dabrowska    public function __construct($schema, $pid, $ts = 0, $rid = 0)
125d6d97f60SAnna Dabrowska    {
126f411d872SAndreas Gohr        /** @var \helper_plugin_struct_db $helper */
127f411d872SAndreas Gohr        $helper = plugin_load('helper', 'struct_db');
128f411d872SAndreas Gohr        $this->sqlite = $helper->getDB();
129f411d872SAndreas Gohr
130f411d872SAndreas Gohr        if (!$schema->getId()) {
131f411d872SAndreas Gohr            throw new StructException('Schema does not exist. Only data of existing schemas can be accessed');
132f411d872SAndreas Gohr        }
133f411d872SAndreas Gohr
134f411d872SAndreas Gohr        $this->schema = $schema;
135f411d872SAndreas Gohr        $this->pid = $pid;
1360ceefd5cSAnna Dabrowska        $this->rid = $rid;
137897aef42SAndreas Gohr        $this->setTimestamp($ts);
138f411d872SAndreas Gohr        foreach ($this->schema->getColumns() as $col) {
139f411d872SAndreas Gohr            $this->labels[$col->getColref()] = $col->getType()->getLabel();
140f411d872SAndreas Gohr        }
141f411d872SAndreas Gohr    }
142f411d872SAndreas Gohr
143f411d872SAndreas Gohr    /**
144f411d872SAndreas Gohr     * gives access to the schema
145f411d872SAndreas Gohr     *
146f411d872SAndreas Gohr     * @return Schema
147f411d872SAndreas Gohr     */
148d6d97f60SAnna Dabrowska    public function getSchema()
149d6d97f60SAnna Dabrowska    {
150f411d872SAndreas Gohr        return $this->schema;
151f411d872SAndreas Gohr    }
152f411d872SAndreas Gohr
153f411d872SAndreas Gohr    /**
154f107f479SAndreas Gohr     * The current pid
155f107f479SAndreas Gohr     *
15686a40c1eSAnna Dabrowska     * @return string
157f107f479SAndreas Gohr     */
158d6d97f60SAnna Dabrowska    public function getPid()
159d6d97f60SAnna Dabrowska    {
160f107f479SAndreas Gohr        return $this->pid;
161f107f479SAndreas Gohr    }
162f107f479SAndreas Gohr
163f107f479SAndreas Gohr    /**
1640ceefd5cSAnna Dabrowska     * The current rid
1650ceefd5cSAnna Dabrowska     *
16686a40c1eSAnna Dabrowska     * @return int
1670ceefd5cSAnna Dabrowska     */
168d6d97f60SAnna Dabrowska    public function getRid()
169d6d97f60SAnna Dabrowska    {
1700ceefd5cSAnna Dabrowska        return $this->rid;
1710ceefd5cSAnna Dabrowska    }
1720ceefd5cSAnna Dabrowska
1730ceefd5cSAnna Dabrowska    /**
174da62ec9cSAnna Dabrowska     * Published status
175da62ec9cSAnna Dabrowska     *
176da62ec9cSAnna Dabrowska     * @return int|null
177da62ec9cSAnna Dabrowska     */
178da62ec9cSAnna Dabrowska    public function getPublished()
179da62ec9cSAnna Dabrowska    {
180da62ec9cSAnna Dabrowska        return $this->published;
181da62ec9cSAnna Dabrowska    }
182da62ec9cSAnna Dabrowska
183da62ec9cSAnna Dabrowska    /**
184f411d872SAndreas Gohr     * Should remove the current data, by either deleting or ovewriting it
185f411d872SAndreas Gohr     *
186f411d872SAndreas Gohr     * @return bool if the delete succeeded
187f411d872SAndreas Gohr     */
188f411d872SAndreas Gohr    abstract public function clearData();
189f411d872SAndreas Gohr
190f411d872SAndreas Gohr    /**
191f411d872SAndreas Gohr     * Save the data to the database.
192f411d872SAndreas Gohr     *
193f411d872SAndreas Gohr     * We differentiate between single-value-column and multi-value-column by the value to the respective column-name,
194f411d872SAndreas Gohr     * i.e. depending on if that is a string or an array, respectively.
195f411d872SAndreas Gohr     *
196f411d872SAndreas Gohr     * @param array $data typelabel => value for single fields or typelabel => array(value, value, ...) for multi fields
197f411d872SAndreas Gohr     * @return bool success of saving the data to the database
198f411d872SAndreas Gohr     */
199aeb8444cSAnna Dabrowska    public function saveData($data)
200aeb8444cSAnna Dabrowska    {
201aeb8444cSAnna Dabrowska        if (!$this->validateTypeData($data)) {
202aeb8444cSAnna Dabrowska            return false;
203aeb8444cSAnna Dabrowska        }
204aeb8444cSAnna Dabrowska
205aeb8444cSAnna Dabrowska        $this->stable = 'data_' . $this->schema->getTable();
206aeb8444cSAnna Dabrowska        $this->mtable = 'multi_' . $this->schema->getTable();
207aeb8444cSAnna Dabrowska
208aeb8444cSAnna Dabrowska        $colrefs = array_flip($this->labels);
209aeb8444cSAnna Dabrowska
210aeb8444cSAnna Dabrowska        foreach ($data as $colname => $value) {
211aeb8444cSAnna Dabrowska            if (!isset($colrefs[$colname])) {
212aeb8444cSAnna Dabrowska                throw new StructException("Unknown column %s in schema.", hsc($colname));
213aeb8444cSAnna Dabrowska            }
214aeb8444cSAnna Dabrowska
215aeb8444cSAnna Dabrowska            $this->singleCols[] = 'col' . $colrefs[$colname];
216aeb8444cSAnna Dabrowska            if (is_array($value)) {
217aeb8444cSAnna Dabrowska                foreach ($value as $index => $multivalue) {
218aeb8444cSAnna Dabrowska                    $this->multiValues[] = [$colrefs[$colname], $index + 1, $multivalue];
219aeb8444cSAnna Dabrowska                }
220aeb8444cSAnna Dabrowska                // copy first value to the single column
221aeb8444cSAnna Dabrowska                if (isset($value[0])) {
222aeb8444cSAnna Dabrowska                    $this->singleValues[] = $value[0];
223f6cf0d85SAnna Dabrowska                    if ($value[0] === '') {
224d680cb37SAnna Dabrowska                        $this->handleEmptyMulti($this->pid, $this->rid, $colrefs[$colname]);
225d680cb37SAnna Dabrowska                    }
226aeb8444cSAnna Dabrowska                } else {
227aeb8444cSAnna Dabrowska                    $this->singleValues[] = null;
228aeb8444cSAnna Dabrowska                }
229aeb8444cSAnna Dabrowska            } else {
230aeb8444cSAnna Dabrowska                $this->singleValues[] = $value;
231aeb8444cSAnna Dabrowska            }
232aeb8444cSAnna Dabrowska        }
233aeb8444cSAnna Dabrowska
234aeb8444cSAnna Dabrowska        $this->sqlite->query('BEGIN TRANSACTION');
235aeb8444cSAnna Dabrowska
236aeb8444cSAnna Dabrowska        $ok = $this->beforeSave();
237aeb8444cSAnna Dabrowska
238aeb8444cSAnna Dabrowska        // insert single values
239aeb8444cSAnna Dabrowska        $ok = $ok && $this->sqlite->query(
240aeb8444cSAnna Dabrowska            $this->getSingleSql(),
241aeb8444cSAnna Dabrowska            array_merge($this->getSingleNoninputValues(), $this->singleValues)
242aeb8444cSAnna Dabrowska        );
243aeb8444cSAnna Dabrowska
244aeb8444cSAnna Dabrowska        $ok = $ok && $this->afterSingleSave();
245aeb8444cSAnna Dabrowska
246aeb8444cSAnna Dabrowska        // insert multi values
247aeb8444cSAnna Dabrowska        if ($ok && $this->multiValues) {
248aeb8444cSAnna Dabrowska            $multisql = $this->getMultiSql();
249aeb8444cSAnna Dabrowska            $multiNoninputValues = $this->getMultiNoninputValues();
250aeb8444cSAnna Dabrowska            foreach ($this->multiValues as $value) {
251aeb8444cSAnna Dabrowska                $ok = $ok && $this->sqlite->query(
252aeb8444cSAnna Dabrowska                    $multisql,
253aeb8444cSAnna Dabrowska                    array_merge($multiNoninputValues, $value)
254aeb8444cSAnna Dabrowska                );
255aeb8444cSAnna Dabrowska            }
256aeb8444cSAnna Dabrowska        }
257aeb8444cSAnna Dabrowska
258d680cb37SAnna Dabrowska        $ok = $ok && $this->afterSave();
259d680cb37SAnna Dabrowska
260aeb8444cSAnna Dabrowska        if (!$ok) {
261aeb8444cSAnna Dabrowska            $this->sqlite->query('ROLLBACK TRANSACTION');
262aeb8444cSAnna Dabrowska            return false;
263aeb8444cSAnna Dabrowska        }
264aeb8444cSAnna Dabrowska        $this->sqlite->query('COMMIT TRANSACTION');
265aeb8444cSAnna Dabrowska        return true;
266aeb8444cSAnna Dabrowska    }
267aeb8444cSAnna Dabrowska
268aeb8444cSAnna Dabrowska    /**
269aeb8444cSAnna Dabrowska     * Check whether all required data is present
270aeb8444cSAnna Dabrowska     *
271aeb8444cSAnna Dabrowska     * @param array $data
272aeb8444cSAnna Dabrowska     * @return bool
273aeb8444cSAnna Dabrowska     */
274aeb8444cSAnna Dabrowska    abstract protected function validateTypeData($data);
275aeb8444cSAnna Dabrowska
276aeb8444cSAnna Dabrowska    /**
277aeb8444cSAnna Dabrowska     * Names of non-input columns to be inserted into SQL query
278aeb8444cSAnna Dabrowska     *
279aeb8444cSAnna Dabrowska     * @return array
280aeb8444cSAnna Dabrowska     */
281aeb8444cSAnna Dabrowska    abstract protected function getSingleNoninputCols();
282aeb8444cSAnna Dabrowska
283aeb8444cSAnna Dabrowska    /**
284aeb8444cSAnna Dabrowska     * Values for non-input columns to be inserted into SQL query
285aeb8444cSAnna Dabrowska     * for single-value tables
286aeb8444cSAnna Dabrowska     *
287aeb8444cSAnna Dabrowska     * @return array
288aeb8444cSAnna Dabrowska     */
289aeb8444cSAnna Dabrowska    abstract protected function getSingleNoninputValues();
290aeb8444cSAnna Dabrowska
291aeb8444cSAnna Dabrowska    /**
292aeb8444cSAnna Dabrowska     * String template for single-value table
293aeb8444cSAnna Dabrowska     *
294aeb8444cSAnna Dabrowska     * @return string
295aeb8444cSAnna Dabrowska     */
296aeb8444cSAnna Dabrowska    protected function getSingleSql()
297aeb8444cSAnna Dabrowska    {
298aeb8444cSAnna Dabrowska        $cols = array_merge($this->getSingleNoninputCols(), $this->singleCols);
299aeb8444cSAnna Dabrowska        $cols = join(',', $cols);
300aeb8444cSAnna Dabrowska        $vals = array_merge($this->getSingleNoninputValues(), $this->singleValues);
301aeb8444cSAnna Dabrowska
302aeb8444cSAnna Dabrowska        return "INSERT INTO $this->stable ($cols) VALUES (" . trim(str_repeat('?,', count($vals)), ',') . ');';
303aeb8444cSAnna Dabrowska    }
304aeb8444cSAnna Dabrowska
305aeb8444cSAnna Dabrowska    /**
306aeb8444cSAnna Dabrowska     * Optional operations to be executed before saving data
307aeb8444cSAnna Dabrowska     *
308aeb8444cSAnna Dabrowska     * @return bool False if any of the operations failed and transaction should be rolled back
309aeb8444cSAnna Dabrowska     */
310aeb8444cSAnna Dabrowska    protected function beforeSave()
311aeb8444cSAnna Dabrowska    {
312aeb8444cSAnna Dabrowska        return true;
313aeb8444cSAnna Dabrowska    }
314aeb8444cSAnna Dabrowska
315aeb8444cSAnna Dabrowska    /**
316aeb8444cSAnna Dabrowska     * Optional operations to be executed after saving data to single-value table,
317aeb8444cSAnna Dabrowska     * before saving multivalues
318aeb8444cSAnna Dabrowska     *
319aeb8444cSAnna Dabrowska     * @return bool False if anything goes wrong and transaction should be rolled back
320aeb8444cSAnna Dabrowska     */
321aeb8444cSAnna Dabrowska    protected function afterSingleSave()
322aeb8444cSAnna Dabrowska    {
323aeb8444cSAnna Dabrowska        return true;
324aeb8444cSAnna Dabrowska    }
325aeb8444cSAnna Dabrowska
326aeb8444cSAnna Dabrowska    /**
327d680cb37SAnna Dabrowska     * Executes final optional queries.
328d680cb37SAnna Dabrowska     *
329d680cb37SAnna Dabrowska     * @return bool False if anything goes wrong and transaction should be rolled back
330d680cb37SAnna Dabrowska     */
331d680cb37SAnna Dabrowska    protected function afterSave()
332d680cb37SAnna Dabrowska    {
333d680cb37SAnna Dabrowska        $ok = true;
334d680cb37SAnna Dabrowska        foreach ($this->optQueries as $query) {
335d680cb37SAnna Dabrowska            $ok = $ok && $this->sqlite->query(array_shift($query), $query);
336d680cb37SAnna Dabrowska        }
337d680cb37SAnna Dabrowska        return $ok;
338d680cb37SAnna Dabrowska    }
339d680cb37SAnna Dabrowska
340d680cb37SAnna Dabrowska    /**
341aeb8444cSAnna Dabrowska     * String template for multi-value table
342aeb8444cSAnna Dabrowska     *
343aeb8444cSAnna Dabrowska     * @return string
344aeb8444cSAnna Dabrowska     */
345aeb8444cSAnna Dabrowska    abstract protected function getMultiSql();
346aeb8444cSAnna Dabrowska
347aeb8444cSAnna Dabrowska    /**
348aeb8444cSAnna Dabrowska     * Values for non-input columns to be inserted into SQL query
349aeb8444cSAnna Dabrowska     * for multi-value tables
350aeb8444cSAnna Dabrowska     * @return array
351aeb8444cSAnna Dabrowska     */
352aeb8444cSAnna Dabrowska    abstract protected function getMultiNoninputValues();
353aeb8444cSAnna Dabrowska
354f411d872SAndreas Gohr
355f411d872SAndreas Gohr    /**
356f411d872SAndreas Gohr     * Should empty or invisible (inpage) fields be returned?
357f411d872SAndreas Gohr     *
358f411d872SAndreas Gohr     * Defaults to false
359f411d872SAndreas Gohr     *
360f411d872SAndreas Gohr     * @param null|bool $set new value, null to read only
361f411d872SAndreas Gohr     * @return bool current value (after set)
362f411d872SAndreas Gohr     */
363d6d97f60SAnna Dabrowska    public function optionSkipEmpty($set = null)
364d6d97f60SAnna Dabrowska    {
365f411d872SAndreas Gohr        if (!is_null($set)) {
366f411d872SAndreas Gohr            $this->opt_skipempty = $set;
367f411d872SAndreas Gohr        }
368f411d872SAndreas Gohr        return $this->opt_skipempty;
369f411d872SAndreas Gohr    }
370f411d872SAndreas Gohr
371f411d872SAndreas Gohr    /**
372f411d872SAndreas Gohr     * Get the value of a single column
373f411d872SAndreas Gohr     *
374f411d872SAndreas Gohr     * @param Column $column
375f411d872SAndreas Gohr     * @return Value|null
376f411d872SAndreas Gohr     */
377d6d97f60SAnna Dabrowska    public function getDataColumn($column)
378d6d97f60SAnna Dabrowska    {
379f411d872SAndreas Gohr        $data = $this->getData();
380f411d872SAndreas Gohr        foreach ($data as $value) {
381f411d872SAndreas Gohr            if ($value->getColumn() == $column) {
382f411d872SAndreas Gohr                return $value;
383f411d872SAndreas Gohr            }
384f411d872SAndreas Gohr        }
385f411d872SAndreas Gohr        return null;
386f411d872SAndreas Gohr    }
387f411d872SAndreas Gohr
388f411d872SAndreas Gohr    /**
389f411d872SAndreas Gohr     * returns the data saved for the page
390f411d872SAndreas Gohr     *
391f411d872SAndreas Gohr     * @return Value[] a list of values saved for the current page
392f411d872SAndreas Gohr     */
393d6d97f60SAnna Dabrowska    public function getData()
394d6d97f60SAnna Dabrowska    {
395f411d872SAndreas Gohr        $data = $this->getDataFromDB();
396f411d872SAndreas Gohr        $data = $this->consolidateData($data, false);
397f411d872SAndreas Gohr        return $data;
398f411d872SAndreas Gohr    }
399f411d872SAndreas Gohr
400f411d872SAndreas Gohr    /**
401f411d872SAndreas Gohr     * returns the data saved for the page as associative array
402f411d872SAndreas Gohr     *
4030549dcc5SAndreas Gohr     * The array returned is in the same format as used in @return array
4040549dcc5SAndreas Gohr     * @see saveData()
405f411d872SAndreas Gohr     *
40690421550SAndreas Gohr     * It always returns raw Values!
40790421550SAndreas Gohr     *
408da62ec9cSAnna Dabrowska     * @return array
409f411d872SAndreas Gohr     */
410d6d97f60SAnna Dabrowska    public function getDataArray()
411d6d97f60SAnna Dabrowska    {
412f411d872SAndreas Gohr        $data = $this->getDataFromDB();
413f411d872SAndreas Gohr        $data = $this->consolidateData($data, true);
414f411d872SAndreas Gohr        return $data;
415f411d872SAndreas Gohr    }
416f411d872SAndreas Gohr
417f411d872SAndreas Gohr    /**
418f411d872SAndreas Gohr     * Return the data in pseudo syntax
419f411d872SAndreas Gohr     */
420d6d97f60SAnna Dabrowska    public function getDataPseudoSyntax()
421d6d97f60SAnna Dabrowska    {
422f411d872SAndreas Gohr        $result = '';
423a0a1d14eSAndreas Gohr        $data = $this->getData();
424a0a1d14eSAndreas Gohr
425a0a1d14eSAndreas Gohr        foreach ($data as $value) {
426a0a1d14eSAndreas Gohr            $key = $value->getColumn()->getFullQualifiedLabel();
427a0a1d14eSAndreas Gohr            $value = $value->getDisplayValue();
428f411d872SAndreas Gohr            if (is_array($value)) $value = join(', ', $value);
429f411d872SAndreas Gohr            $result .= sprintf("% -20s : %s\n", $key, $value);
430f411d872SAndreas Gohr        }
431f411d872SAndreas Gohr        return $result;
432f411d872SAndreas Gohr    }
433f411d872SAndreas Gohr
434f411d872SAndreas Gohr    /**
435f411d872SAndreas Gohr     * retrieve the data saved for the page from the database. Usually there is no need to call this function.
436f411d872SAndreas Gohr     * Call @see SchemaData::getData instead.
437f411d872SAndreas Gohr     */
438d6d97f60SAnna Dabrowska    protected function getDataFromDB()
439d6d97f60SAnna Dabrowska    {
4404cd5cc28SAnna Dabrowska        $idColumn = self::isTypePage($this->pid, $this->ts) ? 'pid' : 'rid';
441efe74305SAnna Dabrowska        list($sql, $opt) = $this->buildGetDataSQL($idColumn);
442f411d872SAndreas Gohr
443f411d872SAndreas Gohr        $res = $this->sqlite->query($sql, $opt);
444f411d872SAndreas Gohr        $data = $this->sqlite->res2arr($res);
4459c00b26cSAndreas Gohr        $this->sqlite->res_close($res);
446f411d872SAndreas Gohr        return $data;
447f411d872SAndreas Gohr    }
448f411d872SAndreas Gohr
449f411d872SAndreas Gohr    /**
450f411d872SAndreas Gohr     * Creates a proper result array from the database data
451f411d872SAndreas Gohr     *
452f411d872SAndreas Gohr     * @param array $DBdata the data as it is retrieved from the database, i.e. by SchemaData::getDataFromDB
453f411d872SAndreas Gohr     * @param bool $asarray return data as associative array (true) or as array of Values (false)
454f411d872SAndreas Gohr     * @return array|Value[]
455f411d872SAndreas Gohr     */
456d6d97f60SAnna Dabrowska    protected function consolidateData($DBdata, $asarray = false)
457d6d97f60SAnna Dabrowska    {
458f411d872SAndreas Gohr        $data = array();
459f411d872SAndreas Gohr
460f411d872SAndreas Gohr        $sep = Search::CONCAT_SEPARATOR;
461f411d872SAndreas Gohr
462f411d872SAndreas Gohr        foreach ($this->schema->getColumns(false) as $col) {
46390421550SAndreas Gohr            // if no data saved yet, return empty strings
464f411d872SAndreas Gohr            if ($DBdata) {
465*6a819106SAndreas Gohr                $val = (string) $DBdata[0]['out' . $col->getColref()];
466f411d872SAndreas Gohr            } else {
467f411d872SAndreas Gohr                $val = '';
468f411d872SAndreas Gohr            }
469f411d872SAndreas Gohr
470f411d872SAndreas Gohr            // multi val data is concatenated
471f411d872SAndreas Gohr            if ($col->isMulti()) {
472f411d872SAndreas Gohr                $val = explode($sep, $val);
473f411d872SAndreas Gohr                $val = array_filter($val);
474f411d872SAndreas Gohr            }
475f411d872SAndreas Gohr
47690421550SAndreas Gohr            $value = new Value($col, $val);
477f411d872SAndreas Gohr
47890421550SAndreas Gohr            if ($this->opt_skipempty && $value->isEmpty()) continue;
47990421550SAndreas Gohr            if ($this->opt_skipempty && !$col->isVisibleInPage()) continue; //FIXME is this a correct assumption?
48090421550SAndreas Gohr
48190421550SAndreas Gohr            // for arrays, we return the raw value only
482f411d872SAndreas Gohr            if ($asarray) {
48390421550SAndreas Gohr                $data[$col->getLabel()] = $value->getRawValue();
484f411d872SAndreas Gohr            } else {
4856e54daafSMichael Große                $data[$col->getLabel()] = $value;
486f411d872SAndreas Gohr            }
487f411d872SAndreas Gohr        }
488f411d872SAndreas Gohr
489f411d872SAndreas Gohr        return $data;
490f411d872SAndreas Gohr    }
491f411d872SAndreas Gohr
492f411d872SAndreas Gohr    /**
493f411d872SAndreas Gohr     * Builds the SQL statement to select the data for this page and schema
494f411d872SAndreas Gohr     *
495f411d872SAndreas Gohr     * @return array Two fields: the SQL string and the parameters array
496f411d872SAndreas Gohr     */
497d6d97f60SAnna Dabrowska    protected function buildGetDataSQL($idColumn = 'pid')
498d6d97f60SAnna Dabrowska    {
499f411d872SAndreas Gohr        $sep = Search::CONCAT_SEPARATOR;
500f411d872SAndreas Gohr        $stable = 'data_' . $this->schema->getTable();
501f411d872SAndreas Gohr        $mtable = 'multi_' . $this->schema->getTable();
502f411d872SAndreas Gohr
503f411d872SAndreas Gohr        $QB = new QueryBuilder();
504f411d872SAndreas Gohr        $QB->addTable($stable, 'DATA');
5056fd73b4bSAnna Dabrowska        $QB->addSelectColumn('DATA', $idColumn, strtoupper($idColumn));
5066fd73b4bSAnna Dabrowska        $QB->addGroupByStatement("DATA.$idColumn");
507f411d872SAndreas Gohr
508f411d872SAndreas Gohr        foreach ($this->schema->getColumns(false) as $col) {
509f411d872SAndreas Gohr            $colref = $col->getColref();
510f411d872SAndreas Gohr            $colname = 'col' . $colref;
511bab52340SAndreas Gohr            $outname = 'out' . $colref;
512f411d872SAndreas Gohr
513f411d872SAndreas Gohr            if ($col->getType()->isMulti()) {
514f411d872SAndreas Gohr                $tn = 'M' . $colref;
515f411d872SAndreas Gohr                $QB->addLeftJoin(
516f411d872SAndreas Gohr                    'DATA',
517f411d872SAndreas Gohr                    $mtable,
518f411d872SAndreas Gohr                    $tn,
5196fd73b4bSAnna Dabrowska                    "DATA.$idColumn = $tn.$idColumn AND DATA.rev = $tn.rev AND $tn.colref = $colref"
520f411d872SAndreas Gohr                );
521bab52340SAndreas Gohr                $col->getType()->select($QB, $tn, 'value', $outname);
522bab52340SAndreas Gohr                $sel = $QB->getSelectStatement($outname);
523bab52340SAndreas Gohr                $QB->addSelectStatement("GROUP_CONCAT($sel, '$sep')", $outname);
524f411d872SAndreas Gohr            } else {
525bab52340SAndreas Gohr                $col->getType()->select($QB, 'DATA', $colname, $outname);
526bab52340SAndreas Gohr                $QB->addGroupByStatement($outname);
527f411d872SAndreas Gohr            }
528f411d872SAndreas Gohr        }
529f411d872SAndreas Gohr
5306fd73b4bSAnna Dabrowska        $pl = $QB->addValue($this->{$idColumn});
5316fd73b4bSAnna Dabrowska        $QB->filters()->whereAnd("DATA.$idColumn = $pl");
532897aef42SAndreas Gohr        $pl = $QB->addValue($this->getLastRevisionTimestamp());
533f411d872SAndreas Gohr        $QB->filters()->whereAnd("DATA.rev = $pl");
534f411d872SAndreas Gohr
535f411d872SAndreas Gohr        return $QB->getSQL();
536f411d872SAndreas Gohr    }
537f411d872SAndreas Gohr
538f411d872SAndreas Gohr    /**
53913eddb0fSAndreas Gohr     * @param int $ts
54013eddb0fSAndreas Gohr     */
541d6d97f60SAnna Dabrowska    public function setTimestamp($ts)
542d6d97f60SAnna Dabrowska    {
543897aef42SAndreas Gohr        if ($ts && $ts < $this->schema->getTimeStamp()) {
544897aef42SAndreas Gohr            throw new StructException('Given timestamp is not valid for current Schema');
545897aef42SAndreas Gohr        }
546897aef42SAndreas Gohr
54713eddb0fSAndreas Gohr        $this->ts = $ts;
54813eddb0fSAndreas Gohr    }
54913eddb0fSAndreas Gohr
55013eddb0fSAndreas Gohr    /**
55169f7ec8fSAnna Dabrowska     * Returns the timestamp from the current data
55269f7ec8fSAnna Dabrowska     * @return int
55369f7ec8fSAnna Dabrowska     */
55469f7ec8fSAnna Dabrowska    public function getTimestamp()
55569f7ec8fSAnna Dabrowska    {
55669f7ec8fSAnna Dabrowska        return $this->ts;
55769f7ec8fSAnna Dabrowska    }
55869f7ec8fSAnna Dabrowska
55969f7ec8fSAnna Dabrowska    /**
560897aef42SAndreas Gohr     * Return the last time an edit happened for this table for the currently set
561da62ec9cSAnna Dabrowska     * time and pid. Used in
5620549dcc5SAndreas Gohr     * @see buildGetDataSQL()
563f411d872SAndreas Gohr     *
564da62ec9cSAnna Dabrowska     * @return int
565f411d872SAndreas Gohr     */
566897aef42SAndreas Gohr    abstract protected function getLastRevisionTimestamp();
56787dc1344SAndreas Gohr
56887dc1344SAndreas Gohr    /**
56987dc1344SAndreas Gohr     * Check if the given data validates against the current types.
57087dc1344SAndreas Gohr     *
57187dc1344SAndreas Gohr     * @param array $data
57293ca6f4fSAndreas Gohr     * @return AccessDataValidator
57387dc1344SAndreas Gohr     */
574d6d97f60SAnna Dabrowska    public function getValidator($data)
575d6d97f60SAnna Dabrowska    {
57693ca6f4fSAndreas Gohr        return new AccessDataValidator($this, $data);
57787dc1344SAndreas Gohr    }
578c73fba38SAnna Dabrowska
579c73fba38SAnna Dabrowska    /**
580c73fba38SAnna Dabrowska     * Returns true if data is of type "page"
581c73fba38SAnna Dabrowska     *
582c73fba38SAnna Dabrowska     * @param string $pid
583c73fba38SAnna Dabrowska     * @param int $rev
584c73fba38SAnna Dabrowska     * @param int $rid
585c73fba38SAnna Dabrowska     * @return bool
586c73fba38SAnna Dabrowska     */
5874cd5cc28SAnna Dabrowska    public static function isTypePage($pid, $rev)
588c73fba38SAnna Dabrowska    {
589c73fba38SAnna Dabrowska        return $rev > 0;
590c73fba38SAnna Dabrowska    }
591c73fba38SAnna Dabrowska
592c73fba38SAnna Dabrowska    /**
593308cc83fSAndreas Gohr     * Returns true if data is of type "global"
594c73fba38SAnna Dabrowska     *
595c73fba38SAnna Dabrowska     * @param string $pid
596c73fba38SAnna Dabrowska     * @param int $rev
597c73fba38SAnna Dabrowska     * @param int $rid
598c73fba38SAnna Dabrowska     * @return bool
599c73fba38SAnna Dabrowska     */
600308cc83fSAndreas Gohr    public static function isTypeGlobal($pid, $rev)
601c73fba38SAnna Dabrowska    {
602c73fba38SAnna Dabrowska        return $pid === '';
603c73fba38SAnna Dabrowska    }
604c73fba38SAnna Dabrowska
605c73fba38SAnna Dabrowska    /**
606c73fba38SAnna Dabrowska     * Returns true if data is of type "serial"
607c73fba38SAnna Dabrowska     *
608c73fba38SAnna Dabrowska     * @param string $pid
609c73fba38SAnna Dabrowska     * @param int $rev
610c73fba38SAnna Dabrowska     * @param int $rid
611c73fba38SAnna Dabrowska     * @return bool
612c73fba38SAnna Dabrowska     */
6134cd5cc28SAnna Dabrowska    public static function isTypeSerial($pid, $rev)
614c73fba38SAnna Dabrowska    {
615c73fba38SAnna Dabrowska        return $pid !== '' && $rev === 0;
616c73fba38SAnna Dabrowska    }
617d680cb37SAnna Dabrowska
618d680cb37SAnna Dabrowska    /**
619d680cb37SAnna Dabrowska     * Global and serial data require additional queries. They are put into query queue
620a09ff24aSAnna Dabrowska     * in descendants of this method.
621d680cb37SAnna Dabrowska     *
622d680cb37SAnna Dabrowska     * @param string $pid
623d680cb37SAnna Dabrowska     * @param int $rid
624d680cb37SAnna Dabrowska     * @param int $colref
625d680cb37SAnna Dabrowska     */
626d680cb37SAnna Dabrowska    protected function handleEmptyMulti($pid, $rid, $colref)
627d680cb37SAnna Dabrowska    {
628d680cb37SAnna Dabrowska    }
629a09ff24aSAnna Dabrowska
630a09ff24aSAnna Dabrowska    /**
631a09ff24aSAnna Dabrowska     * Clears all multi_ values for the current row.
632a09ff24aSAnna Dabrowska     * Executed when updating global and serial data. Otherwise removed (deselected) values linger in database.
633a09ff24aSAnna Dabrowska     *
634a09ff24aSAnna Dabrowska     * @return bool|\SQLiteResult
635a09ff24aSAnna Dabrowska     */
636a09ff24aSAnna Dabrowska    protected function clearMulti()
637a09ff24aSAnna Dabrowska    {
638a09ff24aSAnna Dabrowska        $colrefs = array_unique(array_map(function ($val) {
639a09ff24aSAnna Dabrowska            return $val[0];
640a09ff24aSAnna Dabrowska        }, $this->multiValues));
641a09ff24aSAnna Dabrowska        return $this->sqlite->query(
642a09ff24aSAnna Dabrowska            "DELETE FROM $this->mtable WHERE pid = ? AND rid = $this->rid AND rev = 0 AND colref IN (" .
643a09ff24aSAnna Dabrowska            implode(',', $colrefs) . ")",
644a09ff24aSAnna Dabrowska            $this->pid
645a09ff24aSAnna Dabrowska        );
646a09ff24aSAnna Dabrowska    }
647f411d872SAndreas Gohr}
648