xref: /plugin/struct/meta/AccessTable.php (revision a14cf85d0dc6d9c91c1c733090beb24cfe2e1aa3)
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 */
12f411d872SAndreas Gohrabstract class AccessTable {
13f411d872SAndreas Gohr
14efe74305SAnna Dabrowska    const DEFAULT_REV = 0;
15efe74305SAnna Dabrowska    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;
23f411d872SAndreas Gohr    /** @var \helper_plugin_sqlite */
24f411d872SAndreas Gohr    protected $sqlite;
25f411d872SAndreas Gohr
2690421550SAndreas Gohr    // options on how to retrieve data
27f411d872SAndreas Gohr    protected $opt_skipempty = false;
28f411d872SAndreas Gohr
29f411d872SAndreas Gohr    /**
30aeb8444cSAnna Dabrowska     * @var string Name of single-value table
31aeb8444cSAnna Dabrowska     */
32aeb8444cSAnna Dabrowska    protected $stable;
33aeb8444cSAnna Dabrowska
34aeb8444cSAnna Dabrowska    /**
35aeb8444cSAnna Dabrowska     * @var string Name of multi-value table
36aeb8444cSAnna Dabrowska     */
37aeb8444cSAnna Dabrowska    protected $mtable;
38aeb8444cSAnna Dabrowska
39aeb8444cSAnna Dabrowska    /**
40aeb8444cSAnna Dabrowska     * @var array Column names for the single-value insert/update
41aeb8444cSAnna Dabrowska     */
42aeb8444cSAnna Dabrowska    protected $singleCols;
43aeb8444cSAnna Dabrowska
44aeb8444cSAnna Dabrowska    /**
45aeb8444cSAnna Dabrowska     * @var array Input values for the single-value insert/update
46aeb8444cSAnna Dabrowska     */
47aeb8444cSAnna Dabrowska    protected $singleValues;
48aeb8444cSAnna Dabrowska
49aeb8444cSAnna Dabrowska    /**
50aeb8444cSAnna Dabrowska     * @var array Input values for the multi-value inserts/updates
51aeb8444cSAnna Dabrowska     */
52aeb8444cSAnna Dabrowska    protected $multiValues;
53aeb8444cSAnna Dabrowska
54aeb8444cSAnna Dabrowska
55aeb8444cSAnna Dabrowska    /**
56b9d35ff2SAnna Dabrowska     * Factory method returning the appropriate data accessor (page, lookup or serial)
57f411d872SAndreas Gohr     *
58f411d872SAndreas Gohr     * @param Schema $schema schema to load
5986a40c1eSAnna Dabrowska     * @param string $pid Page id to access
60*a14cf85dSAnna Dabrowska     * @param int $ts Time at which the data should be read or written
6186a40c1eSAnna Dabrowska     * @param int $rid Row id, 0 for page type data, otherwise autoincrement
62aeb8444cSAnna Dabrowska     * @return AccessTableData|AccessTableLookup
63f411d872SAndreas Gohr     */
640ceefd5cSAnna Dabrowska    public static function bySchema(Schema $schema, $pid, $ts = 0, $rid = 0) {
65aeb8444cSAnna Dabrowska        if (self::isTypePage($pid, $ts, $rid)) {
660ceefd5cSAnna Dabrowska            return new AccessTableData($schema, $pid, $ts, $rid);
67f411d872SAndreas Gohr        }
68aeb8444cSAnna Dabrowska        return new AccessTableLookup($schema, $pid, $ts, $rid);
69aeb8444cSAnna Dabrowska    }
70f411d872SAndreas Gohr
71f411d872SAndreas Gohr    /**
72c73fba38SAnna Dabrowska     * Factory Method to access data
73f411d872SAndreas Gohr     *
74f411d872SAndreas Gohr     * @param string $tablename schema to load
7586a40c1eSAnna Dabrowska     * @param string $pid Page id to access
76*a14cf85dSAnna Dabrowska     * @param int $ts Time at which the data should be read or written
7786a40c1eSAnna Dabrowska     * @param int $rid Row id, 0 for page type data, otherwise autoincrement
78aeb8444cSAnna Dabrowska     * @return AccessTableData|AccessTableLookup
79f411d872SAndreas Gohr     */
800ceefd5cSAnna Dabrowska    public static function byTableName($tablename, $pid, $ts = 0, $rid = 0) {
81f411d872SAndreas Gohr        $schema = new Schema($tablename, $ts);
820ceefd5cSAnna Dabrowska        return self::bySchema($schema, $pid, $ts, $rid);
83f411d872SAndreas Gohr    }
84f411d872SAndreas Gohr
85f411d872SAndreas Gohr    /**
86f411d872SAndreas Gohr     * AccessTable constructor
87f411d872SAndreas Gohr     *
88897aef42SAndreas Gohr     * @param Schema $schema The schema valid at $ts
8986a40c1eSAnna Dabrowska     * @param string $pid Page id
90897aef42SAndreas Gohr     * @param int $ts Time at which the data should be read or written, 0 for now
910ceefd5cSAnna Dabrowska     * @param int $rid Row id: 0 for pages, autoincremented for other types
92f411d872SAndreas Gohr     */
930ceefd5cSAnna Dabrowska    public function __construct(Schema $schema, $pid, $ts = 0, $rid = 0) {
94f411d872SAndreas Gohr        /** @var \helper_plugin_struct_db $helper */
95f411d872SAndreas Gohr        $helper = plugin_load('helper', 'struct_db');
96f411d872SAndreas Gohr        $this->sqlite = $helper->getDB();
97f411d872SAndreas Gohr
98f411d872SAndreas Gohr        if(!$schema->getId()) {
99f411d872SAndreas Gohr            throw new StructException('Schema does not exist. Only data of existing schemas can be accessed');
100f411d872SAndreas Gohr        }
101f411d872SAndreas Gohr
102f411d872SAndreas Gohr        $this->schema = $schema;
103f411d872SAndreas Gohr        $this->pid = $pid;
1040ceefd5cSAnna Dabrowska        $this->rid = $rid;
105897aef42SAndreas Gohr        $this->setTimestamp($ts);
106f411d872SAndreas Gohr        foreach($this->schema->getColumns() as $col) {
107f411d872SAndreas Gohr            $this->labels[$col->getColref()] = $col->getType()->getLabel();
108f411d872SAndreas Gohr        }
109f411d872SAndreas Gohr    }
110f411d872SAndreas Gohr
111f411d872SAndreas Gohr    /**
112f411d872SAndreas Gohr     * gives access to the schema
113f411d872SAndreas Gohr     *
114f411d872SAndreas Gohr     * @return Schema
115f411d872SAndreas Gohr     */
116f411d872SAndreas Gohr    public function getSchema() {
117f411d872SAndreas Gohr        return $this->schema;
118f411d872SAndreas Gohr    }
119f411d872SAndreas Gohr
120f411d872SAndreas Gohr    /**
121f107f479SAndreas Gohr     * The current pid
122f107f479SAndreas Gohr     *
12386a40c1eSAnna Dabrowska     * @return string
124f107f479SAndreas Gohr     */
125f107f479SAndreas Gohr    public function getPid() {
126f107f479SAndreas Gohr        return $this->pid;
127f107f479SAndreas Gohr    }
128f107f479SAndreas Gohr
129f107f479SAndreas Gohr    /**
1300ceefd5cSAnna Dabrowska     * The current rid
1310ceefd5cSAnna Dabrowska     *
13286a40c1eSAnna Dabrowska     * @return int
1330ceefd5cSAnna Dabrowska     */
1340ceefd5cSAnna Dabrowska    public function getRid() {
1350ceefd5cSAnna Dabrowska        return $this->rid;
1360ceefd5cSAnna Dabrowska    }
1370ceefd5cSAnna Dabrowska
1380ceefd5cSAnna Dabrowska    /**
139f411d872SAndreas Gohr     * Should remove the current data, by either deleting or ovewriting it
140f411d872SAndreas Gohr     *
141f411d872SAndreas Gohr     * @return bool if the delete succeeded
142f411d872SAndreas Gohr     */
143f411d872SAndreas Gohr    abstract public function clearData();
144f411d872SAndreas Gohr
145f411d872SAndreas Gohr    /**
146f411d872SAndreas Gohr     * Save the data to the database.
147f411d872SAndreas Gohr     *
148f411d872SAndreas Gohr     * We differentiate between single-value-column and multi-value-column by the value to the respective column-name,
149f411d872SAndreas Gohr     * i.e. depending on if that is a string or an array, respectively.
150f411d872SAndreas Gohr     *
151f411d872SAndreas Gohr     * @param array $data typelabel => value for single fields or typelabel => array(value, value, ...) for multi fields
152f411d872SAndreas Gohr     * @return bool success of saving the data to the database
153f411d872SAndreas Gohr     */
154aeb8444cSAnna Dabrowska    public function saveData($data)
155aeb8444cSAnna Dabrowska    {
156aeb8444cSAnna Dabrowska        if (!$this->validateTypeData($data)) {
157aeb8444cSAnna Dabrowska            return false;
158aeb8444cSAnna Dabrowska        }
159aeb8444cSAnna Dabrowska
160aeb8444cSAnna Dabrowska        $this->stable = 'data_' . $this->schema->getTable();
161aeb8444cSAnna Dabrowska        $this->mtable = 'multi_' . $this->schema->getTable();
162aeb8444cSAnna Dabrowska
163aeb8444cSAnna Dabrowska        $colrefs = array_flip($this->labels);
164aeb8444cSAnna Dabrowska
165aeb8444cSAnna Dabrowska        foreach ($data as $colname => $value) {
166aeb8444cSAnna Dabrowska            if(!isset($colrefs[$colname])) {
167aeb8444cSAnna Dabrowska                throw new StructException("Unknown column %s in schema.", hsc($colname));
168aeb8444cSAnna Dabrowska            }
169aeb8444cSAnna Dabrowska
170aeb8444cSAnna Dabrowska            $this->singleCols[] = 'col' . $colrefs[$colname];
171aeb8444cSAnna Dabrowska            if (is_array($value)) {
172aeb8444cSAnna Dabrowska                foreach ($value as $index => $multivalue) {
173aeb8444cSAnna Dabrowska                    $this->multiValues[] = [$colrefs[$colname], $index + 1, $multivalue];
174aeb8444cSAnna Dabrowska                }
175aeb8444cSAnna Dabrowska                // copy first value to the single column
176aeb8444cSAnna Dabrowska                if(isset($value[0])) {
177aeb8444cSAnna Dabrowska                    $this->singleValues[] = $value[0];
178aeb8444cSAnna Dabrowska                } else {
179aeb8444cSAnna Dabrowska                    $this->singleValues[] = null;
180aeb8444cSAnna Dabrowska                }
181aeb8444cSAnna Dabrowska            } else {
182aeb8444cSAnna Dabrowska                $this->singleValues[] = $value;
183aeb8444cSAnna Dabrowska            }
184aeb8444cSAnna Dabrowska        }
185aeb8444cSAnna Dabrowska
186aeb8444cSAnna Dabrowska        $this->sqlite->query('BEGIN TRANSACTION');
187aeb8444cSAnna Dabrowska
188aeb8444cSAnna Dabrowska        $ok = $this->beforeSave();
189aeb8444cSAnna Dabrowska
190aeb8444cSAnna Dabrowska        // insert single values
191aeb8444cSAnna Dabrowska        $ok = $ok && $this->sqlite->query(
192aeb8444cSAnna Dabrowska                $this->getSingleSql(),
193aeb8444cSAnna Dabrowska            array_merge($this->getSingleNoninputValues(), $this->singleValues)
194aeb8444cSAnna Dabrowska            );
195aeb8444cSAnna Dabrowska
196aeb8444cSAnna Dabrowska        $ok = $ok && $this->afterSingleSave();
197aeb8444cSAnna Dabrowska
198aeb8444cSAnna Dabrowska        // insert multi values
199aeb8444cSAnna Dabrowska        if ($ok && $this->multiValues) {
200aeb8444cSAnna Dabrowska            $multisql = $this->getMultiSql();
201aeb8444cSAnna Dabrowska            $multiNoninputValues = $this->getMultiNoninputValues();
202aeb8444cSAnna Dabrowska            foreach ($this->multiValues as $value) {
203aeb8444cSAnna Dabrowska                $ok = $ok && $this->sqlite->query(
204aeb8444cSAnna Dabrowska                        $multisql,
205aeb8444cSAnna Dabrowska                        array_merge($multiNoninputValues, $value)
206aeb8444cSAnna Dabrowska                    );
207aeb8444cSAnna Dabrowska            }
208aeb8444cSAnna Dabrowska        }
209aeb8444cSAnna Dabrowska
210aeb8444cSAnna Dabrowska        if (!$ok) {
211aeb8444cSAnna Dabrowska            $this->sqlite->query('ROLLBACK TRANSACTION');
212aeb8444cSAnna Dabrowska            return false;
213aeb8444cSAnna Dabrowska        }
214aeb8444cSAnna Dabrowska        $this->sqlite->query('COMMIT TRANSACTION');
215aeb8444cSAnna Dabrowska        return true;
216aeb8444cSAnna Dabrowska    }
217aeb8444cSAnna Dabrowska
218aeb8444cSAnna Dabrowska    /**
219aeb8444cSAnna Dabrowska     * Check whether all required data is present
220aeb8444cSAnna Dabrowska     *
221aeb8444cSAnna Dabrowska     * @param array $data
222aeb8444cSAnna Dabrowska     * @return bool
223aeb8444cSAnna Dabrowska     */
224aeb8444cSAnna Dabrowska    abstract protected function validateTypeData($data);
225aeb8444cSAnna Dabrowska
226aeb8444cSAnna Dabrowska    /**
227aeb8444cSAnna Dabrowska     * Names of non-input columns to be inserted into SQL query
228aeb8444cSAnna Dabrowska     *
229aeb8444cSAnna Dabrowska     * @return array
230aeb8444cSAnna Dabrowska     */
231aeb8444cSAnna Dabrowska    abstract protected function getSingleNoninputCols();
232aeb8444cSAnna Dabrowska
233aeb8444cSAnna Dabrowska    /**
234aeb8444cSAnna Dabrowska     * Values for non-input columns to be inserted into SQL query
235aeb8444cSAnna Dabrowska     * for single-value tables
236aeb8444cSAnna Dabrowska     *
237aeb8444cSAnna Dabrowska     * @return array
238aeb8444cSAnna Dabrowska     */
239aeb8444cSAnna Dabrowska    abstract protected function getSingleNoninputValues();
240aeb8444cSAnna Dabrowska
241aeb8444cSAnna Dabrowska    /**
242aeb8444cSAnna Dabrowska     * String template for single-value table
243aeb8444cSAnna Dabrowska     *
244aeb8444cSAnna Dabrowska     * @return string
245aeb8444cSAnna Dabrowska     */
246aeb8444cSAnna Dabrowska    protected function getSingleSql()
247aeb8444cSAnna Dabrowska    {
248aeb8444cSAnna Dabrowska        $cols = array_merge($this->getSingleNoninputCols(), $this->singleCols);
249aeb8444cSAnna Dabrowska        $cols = join(',', $cols);
250aeb8444cSAnna Dabrowska        $vals = array_merge($this->getSingleNoninputValues(), $this->singleValues);
251aeb8444cSAnna Dabrowska
252aeb8444cSAnna Dabrowska        return "INSERT INTO $this->stable ($cols) VALUES (" . trim(str_repeat('?,', count($vals)),',') . ');';
253aeb8444cSAnna Dabrowska    }
254aeb8444cSAnna Dabrowska
255aeb8444cSAnna Dabrowska    /**
256aeb8444cSAnna Dabrowska     * Optional operations to be executed before saving data
257aeb8444cSAnna Dabrowska     *
258aeb8444cSAnna Dabrowska     * @return bool False if any of the operations failed and transaction should be rolled back
259aeb8444cSAnna Dabrowska     */
260aeb8444cSAnna Dabrowska    protected function beforeSave()
261aeb8444cSAnna Dabrowska    {
262aeb8444cSAnna Dabrowska        return true;
263aeb8444cSAnna Dabrowska    }
264aeb8444cSAnna Dabrowska
265aeb8444cSAnna Dabrowska    /**
266aeb8444cSAnna Dabrowska     * Optional operations to be executed after saving data to single-value table,
267aeb8444cSAnna Dabrowska     * before saving multivalues
268aeb8444cSAnna Dabrowska     *
269aeb8444cSAnna Dabrowska     * @return bool False if anything goes wrong and transaction should be rolled back
270aeb8444cSAnna Dabrowska     */
271aeb8444cSAnna Dabrowska    protected function afterSingleSave()
272aeb8444cSAnna Dabrowska    {
273aeb8444cSAnna Dabrowska        return true;
274aeb8444cSAnna Dabrowska    }
275aeb8444cSAnna Dabrowska
276aeb8444cSAnna Dabrowska    /**
277aeb8444cSAnna Dabrowska     * String template for multi-value table
278aeb8444cSAnna Dabrowska     *
279aeb8444cSAnna Dabrowska     * @return string
280aeb8444cSAnna Dabrowska     */
281aeb8444cSAnna Dabrowska    abstract protected function getMultiSql();
282aeb8444cSAnna Dabrowska
283aeb8444cSAnna Dabrowska    /**
284aeb8444cSAnna Dabrowska     * Values for non-input columns to be inserted into SQL query
285aeb8444cSAnna Dabrowska     * for multi-value tables
286aeb8444cSAnna Dabrowska     * @return array
287aeb8444cSAnna Dabrowska     */
288aeb8444cSAnna Dabrowska    abstract protected function getMultiNoninputValues();
289aeb8444cSAnna Dabrowska
290f411d872SAndreas Gohr
291f411d872SAndreas Gohr    /**
292f411d872SAndreas Gohr     * Should empty or invisible (inpage) fields be returned?
293f411d872SAndreas Gohr     *
294f411d872SAndreas Gohr     * Defaults to false
295f411d872SAndreas Gohr     *
296f411d872SAndreas Gohr     * @param null|bool $set new value, null to read only
297f411d872SAndreas Gohr     * @return bool current value (after set)
298f411d872SAndreas Gohr     */
299f411d872SAndreas Gohr    public function optionSkipEmpty($set = null) {
300f411d872SAndreas Gohr        if(!is_null($set)) {
301f411d872SAndreas Gohr            $this->opt_skipempty = $set;
302f411d872SAndreas Gohr        }
303f411d872SAndreas Gohr        return $this->opt_skipempty;
304f411d872SAndreas Gohr    }
305f411d872SAndreas Gohr
306f411d872SAndreas Gohr    /**
307f411d872SAndreas Gohr     * Get the value of a single column
308f411d872SAndreas Gohr     *
309f411d872SAndreas Gohr     * @param Column $column
310f411d872SAndreas Gohr     * @return Value|null
311f411d872SAndreas Gohr     */
312f411d872SAndreas Gohr    public function getDataColumn($column) {
313f411d872SAndreas Gohr        $data = $this->getData();
314f411d872SAndreas Gohr        foreach($data as $value) {
315f411d872SAndreas Gohr            if($value->getColumn() == $column) {
316f411d872SAndreas Gohr                return $value;
317f411d872SAndreas Gohr            }
318f411d872SAndreas Gohr        }
319f411d872SAndreas Gohr        return null;
320f411d872SAndreas Gohr    }
321f411d872SAndreas Gohr
322f411d872SAndreas Gohr    /**
323f411d872SAndreas Gohr     * returns the data saved for the page
324f411d872SAndreas Gohr     *
325f411d872SAndreas Gohr     * @return Value[] a list of values saved for the current page
326f411d872SAndreas Gohr     */
327f411d872SAndreas Gohr    public function getData() {
328f411d872SAndreas Gohr        $data = $this->getDataFromDB();
329f411d872SAndreas Gohr        $data = $this->consolidateData($data, false);
330f411d872SAndreas Gohr        return $data;
331f411d872SAndreas Gohr    }
332f411d872SAndreas Gohr
333f411d872SAndreas Gohr    /**
334f411d872SAndreas Gohr     * returns the data saved for the page as associative array
335f411d872SAndreas Gohr     *
336f411d872SAndreas Gohr     * The array returned is in the same format as used in @see saveData()
337f411d872SAndreas Gohr     *
33890421550SAndreas Gohr     * It always returns raw Values!
33990421550SAndreas Gohr     *
340f411d872SAndreas Gohr     * @return array
341f411d872SAndreas Gohr     */
342f411d872SAndreas Gohr    public function getDataArray() {
343f411d872SAndreas Gohr        $data = $this->getDataFromDB();
344f411d872SAndreas Gohr        $data = $this->consolidateData($data, true);
345f411d872SAndreas Gohr        return $data;
346f411d872SAndreas Gohr    }
347f411d872SAndreas Gohr
348f411d872SAndreas Gohr    /**
349f411d872SAndreas Gohr     * Return the data in pseudo syntax
350f411d872SAndreas Gohr     */
351f411d872SAndreas Gohr    public function getDataPseudoSyntax() {
352f411d872SAndreas Gohr        $result = '';
353a0a1d14eSAndreas Gohr        $data = $this->getData();
354a0a1d14eSAndreas Gohr
355a0a1d14eSAndreas Gohr        foreach($data as $value) {
356a0a1d14eSAndreas Gohr            $key = $value->getColumn()->getFullQualifiedLabel();
357a0a1d14eSAndreas Gohr            $value = $value->getDisplayValue();
358f411d872SAndreas Gohr            if(is_array($value)) $value = join(', ', $value);
359f411d872SAndreas Gohr            $result .= sprintf("% -20s : %s\n", $key, $value);
360f411d872SAndreas Gohr        }
361f411d872SAndreas Gohr        return $result;
362f411d872SAndreas Gohr    }
363f411d872SAndreas Gohr
364f411d872SAndreas Gohr    /**
365f411d872SAndreas Gohr     * retrieve the data saved for the page from the database. Usually there is no need to call this function.
366f411d872SAndreas Gohr     * Call @see SchemaData::getData instead.
367f411d872SAndreas Gohr     */
368f411d872SAndreas Gohr    protected function getDataFromDB() {
369efe74305SAnna Dabrowska        $idColumn = self::isTypePage($this->pid, $this->ts, $this->rid) ? 'pid' : 'rid';
370efe74305SAnna Dabrowska        list($sql, $opt) = $this->buildGetDataSQL($idColumn);
371f411d872SAndreas Gohr
372f411d872SAndreas Gohr        $res = $this->sqlite->query($sql, $opt);
373f411d872SAndreas Gohr        $data = $this->sqlite->res2arr($res);
3749c00b26cSAndreas Gohr        $this->sqlite->res_close($res);
375f411d872SAndreas Gohr        return $data;
376f411d872SAndreas Gohr    }
377f411d872SAndreas Gohr
378f411d872SAndreas Gohr    /**
379f411d872SAndreas Gohr     * Creates a proper result array from the database data
380f411d872SAndreas Gohr     *
381f411d872SAndreas Gohr     * @param array $DBdata the data as it is retrieved from the database, i.e. by SchemaData::getDataFromDB
382f411d872SAndreas Gohr     * @param bool $asarray return data as associative array (true) or as array of Values (false)
383f411d872SAndreas Gohr     * @return array|Value[]
384f411d872SAndreas Gohr     */
385f411d872SAndreas Gohr    protected function consolidateData($DBdata, $asarray = false) {
386f411d872SAndreas Gohr        $data = array();
387f411d872SAndreas Gohr
388f411d872SAndreas Gohr        $sep = Search::CONCAT_SEPARATOR;
389f411d872SAndreas Gohr
390f411d872SAndreas Gohr        foreach($this->schema->getColumns(false) as $col) {
391f411d872SAndreas Gohr
39290421550SAndreas Gohr            // if no data saved yet, return empty strings
393f411d872SAndreas Gohr            if($DBdata) {
394bab52340SAndreas Gohr                $val = $DBdata[0]['out' . $col->getColref()];
395f411d872SAndreas Gohr            } else {
396f411d872SAndreas Gohr                $val = '';
397f411d872SAndreas Gohr            }
398f411d872SAndreas Gohr
399f411d872SAndreas Gohr            // multi val data is concatenated
400f411d872SAndreas Gohr            if($col->isMulti()) {
401f411d872SAndreas Gohr                $val = explode($sep, $val);
402f411d872SAndreas Gohr                $val = array_filter($val);
403f411d872SAndreas Gohr            }
404f411d872SAndreas Gohr
40590421550SAndreas Gohr            $value = new Value($col, $val);
406f411d872SAndreas Gohr
40790421550SAndreas Gohr            if($this->opt_skipempty && $value->isEmpty()) continue;
40890421550SAndreas Gohr            if($this->opt_skipempty && !$col->isVisibleInPage()) continue; //FIXME is this a correct assumption?
40990421550SAndreas Gohr
41090421550SAndreas Gohr            // for arrays, we return the raw value only
411f411d872SAndreas Gohr            if($asarray) {
41290421550SAndreas Gohr                $data[$col->getLabel()] = $value->getRawValue();
413f411d872SAndreas Gohr            } else {
4146e54daafSMichael Große                $data[$col->getLabel()] = $value;
415f411d872SAndreas Gohr            }
416f411d872SAndreas Gohr        }
417f411d872SAndreas Gohr
418f411d872SAndreas Gohr        return $data;
419f411d872SAndreas Gohr    }
420f411d872SAndreas Gohr
421f411d872SAndreas Gohr    /**
422f411d872SAndreas Gohr     * Builds the SQL statement to select the data for this page and schema
423f411d872SAndreas Gohr     *
424f411d872SAndreas Gohr     * @return array Two fields: the SQL string and the parameters array
425f411d872SAndreas Gohr     */
4266fd73b4bSAnna Dabrowska    protected function buildGetDataSQL($idColumn = 'pid') {
427f411d872SAndreas Gohr        $sep = Search::CONCAT_SEPARATOR;
428f411d872SAndreas Gohr        $stable = 'data_' . $this->schema->getTable();
429f411d872SAndreas Gohr        $mtable = 'multi_' . $this->schema->getTable();
430f411d872SAndreas Gohr
431f411d872SAndreas Gohr        $QB = new QueryBuilder();
432f411d872SAndreas Gohr        $QB->addTable($stable, 'DATA');
4336fd73b4bSAnna Dabrowska        $QB->addSelectColumn('DATA', $idColumn, strtoupper($idColumn));
4346fd73b4bSAnna Dabrowska        $QB->addGroupByStatement("DATA.$idColumn");
435f411d872SAndreas Gohr
436f411d872SAndreas Gohr        foreach($this->schema->getColumns(false) as $col) {
437f411d872SAndreas Gohr
438f411d872SAndreas Gohr            $colref = $col->getColref();
439f411d872SAndreas Gohr            $colname = 'col' . $colref;
440bab52340SAndreas Gohr            $outname = 'out' . $colref;
441f411d872SAndreas Gohr
442f411d872SAndreas Gohr            if($col->getType()->isMulti()) {
443f411d872SAndreas Gohr                $tn = 'M' . $colref;
444f411d872SAndreas Gohr                $QB->addLeftJoin(
445f411d872SAndreas Gohr                    'DATA',
446f411d872SAndreas Gohr                    $mtable,
447f411d872SAndreas Gohr                    $tn,
4486fd73b4bSAnna Dabrowska                    "DATA.$idColumn = $tn.$idColumn AND DATA.rev = $tn.rev AND $tn.colref = $colref"
449f411d872SAndreas Gohr                );
450bab52340SAndreas Gohr                $col->getType()->select($QB, $tn, 'value', $outname);
451bab52340SAndreas Gohr                $sel = $QB->getSelectStatement($outname);
452bab52340SAndreas Gohr                $QB->addSelectStatement("GROUP_CONCAT($sel, '$sep')", $outname);
453f411d872SAndreas Gohr            } else {
454bab52340SAndreas Gohr                $col->getType()->select($QB, 'DATA', $colname, $outname);
455bab52340SAndreas Gohr                $QB->addGroupByStatement($outname);
456f411d872SAndreas Gohr            }
457f411d872SAndreas Gohr        }
458f411d872SAndreas Gohr
4596fd73b4bSAnna Dabrowska        $pl = $QB->addValue($this->{$idColumn});
4606fd73b4bSAnna Dabrowska        $QB->filters()->whereAnd("DATA.$idColumn = $pl");
461897aef42SAndreas Gohr        $pl = $QB->addValue($this->getLastRevisionTimestamp());
462f411d872SAndreas Gohr        $QB->filters()->whereAnd("DATA.rev = $pl");
463f411d872SAndreas Gohr
464f411d872SAndreas Gohr        return $QB->getSQL();
465f411d872SAndreas Gohr    }
466f411d872SAndreas Gohr
467f411d872SAndreas Gohr    /**
46813eddb0fSAndreas Gohr     * @param int $ts
46913eddb0fSAndreas Gohr     */
47013eddb0fSAndreas Gohr    public function setTimestamp($ts) {
471897aef42SAndreas Gohr        if($ts && $ts < $this->schema->getTimeStamp()) {
472897aef42SAndreas Gohr            throw new StructException('Given timestamp is not valid for current Schema');
473897aef42SAndreas Gohr        }
474897aef42SAndreas Gohr
47513eddb0fSAndreas Gohr        $this->ts = $ts;
47613eddb0fSAndreas Gohr    }
47713eddb0fSAndreas Gohr
47813eddb0fSAndreas Gohr    /**
47969f7ec8fSAnna Dabrowska     * Returns the timestamp from the current data
48069f7ec8fSAnna Dabrowska     * @return int
48169f7ec8fSAnna Dabrowska     */
48269f7ec8fSAnna Dabrowska    public function getTimestamp()
48369f7ec8fSAnna Dabrowska    {
48469f7ec8fSAnna Dabrowska        return $this->ts;
48569f7ec8fSAnna Dabrowska    }
48669f7ec8fSAnna Dabrowska
48769f7ec8fSAnna Dabrowska    /**
488897aef42SAndreas Gohr     * Return the last time an edit happened for this table for the currently set
489*a14cf85dSAnna Dabrowska     * time and pid. Used in @see buildGetDataSQL()
490f411d872SAndreas Gohr     *
491897aef42SAndreas Gohr     * @return int
492f411d872SAndreas Gohr     */
493897aef42SAndreas Gohr    abstract protected function getLastRevisionTimestamp();
49487dc1344SAndreas Gohr
49587dc1344SAndreas Gohr    /**
49687dc1344SAndreas Gohr     * Check if the given data validates against the current types.
49787dc1344SAndreas Gohr     *
49887dc1344SAndreas Gohr     * @param array $data
49993ca6f4fSAndreas Gohr     * @return AccessDataValidator
50087dc1344SAndreas Gohr     */
50187dc1344SAndreas Gohr    public function getValidator($data) {
50293ca6f4fSAndreas Gohr        return new AccessDataValidator($this, $data);
50387dc1344SAndreas Gohr    }
504c73fba38SAnna Dabrowska
505c73fba38SAnna Dabrowska    /**
506c73fba38SAnna Dabrowska     * Returns true if data is of type "page"
507c73fba38SAnna Dabrowska     *
508c73fba38SAnna Dabrowska     * @param string $pid
509c73fba38SAnna Dabrowska     * @param int $rev
510c73fba38SAnna Dabrowska     * @param int $rid
511c73fba38SAnna Dabrowska     * @return bool
512c73fba38SAnna Dabrowska     */
513c73fba38SAnna Dabrowska    public static function isTypePage($pid, $rev, $rid)
514c73fba38SAnna Dabrowska    {
515c73fba38SAnna Dabrowska        return $rev > 0;
516c73fba38SAnna Dabrowska    }
517c73fba38SAnna Dabrowska
518c73fba38SAnna Dabrowska    /**
519c73fba38SAnna Dabrowska     * Returns true if data is of type "lookup"
520c73fba38SAnna Dabrowska     *
521c73fba38SAnna Dabrowska     * @param string $pid
522c73fba38SAnna Dabrowska     * @param int $rev
523c73fba38SAnna Dabrowska     * @param int $rid
524c73fba38SAnna Dabrowska     * @return bool
525c73fba38SAnna Dabrowska     */
526c73fba38SAnna Dabrowska    public static function isTypeLookup($pid, $rev, $rid)
527c73fba38SAnna Dabrowska    {
528c73fba38SAnna Dabrowska        return $pid === '';
529c73fba38SAnna Dabrowska    }
530c73fba38SAnna Dabrowska
531c73fba38SAnna Dabrowska    /**
532c73fba38SAnna Dabrowska     * Returns true if data is of type "serial"
533c73fba38SAnna Dabrowska     *
534c73fba38SAnna Dabrowska     * @param string $pid
535c73fba38SAnna Dabrowska     * @param int $rev
536c73fba38SAnna Dabrowska     * @param int $rid
537c73fba38SAnna Dabrowska     * @return bool
538c73fba38SAnna Dabrowska     */
539c73fba38SAnna Dabrowska    public static function isTypeSerial($pid, $rev, $rid)
540c73fba38SAnna Dabrowska    {
541c73fba38SAnna Dabrowska        return $pid !== '' && $rev === 0;
542c73fba38SAnna Dabrowska    }
543f411d872SAndreas Gohr}
544f411d872SAndreas Gohr
545f411d872SAndreas Gohr
546