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