1<?php
2
3use dokuwiki\plugin\sqlite\SQLiteDB;
4
5/**
6 * DokuWiki Plugin structstatus (Action Component)
7 *
8 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
9 * @author  Andreas Gohr <dokuwiki@cosmocode.de>
10 */
11class action_plugin_structstatus extends DokuWiki_Action_Plugin {
12
13    /**
14     * Registers a callback function for a given event
15     *
16     * @param Doku_Event_Handler $controller DokuWiki's event controller object
17     * @return void
18     */
19    public function register(Doku_Event_Handler $controller) {
20
21       $controller->register_hook('PLUGIN_STRUCT_TYPECLASS_INIT', 'BEFORE', $this, 'handleInit');
22       $controller->register_hook('PLUGIN_STRUCT_TYPECLASS_INIT', 'AFTER', $this, 'handleMigrations');
23
24    }
25
26    /**
27     * @param Doku_Event $event
28     * @return void
29     */
30
31    public function handleInit(Doku_Event $event) {
32        $event->data['Status'] = 'dokuwiki\\plugin\\structstatus\\Status';
33    }
34
35    /**
36     * Call our custom migrations when defined
37     *
38     * @param Doku_Event $event
39     * @return bool
40     */
41    public function handleMigrations(Doku_Event $event)
42    {
43        /** @var \helper_plugin_struct_db $helper */
44        $helper = plugin_load('helper', 'struct_db');
45        $sqlite = $helper->getDB();
46
47        // check whether we are already up-to-date
48        list($dbVersionStruct, $dbVersionStructStatus) = $this->getDbVersions($sqlite);
49        if (isset($dbVersionStructStatus) && $dbVersionStructStatus === $dbVersionStruct) {
50            return true;
51        }
52
53        // check whether we have any pending migrations for the current version of struct db
54        $pending = array_filter(array_map(function ($version) use ($dbVersionStruct) {
55            return $version >= $dbVersionStruct &&
56                is_callable([$this, "migration$version"]) ? $version : null;
57        }, $this->diffVersions($dbVersionStruct, $dbVersionStructStatus)));
58        if (empty($pending)) {
59            return true;
60        }
61
62        // execute the migrations
63        $sql = "SELECT MAX(id) AS id, tbl FROM schemas GROUP BY tbl";
64        $schemas= $sqlite->queryAll($sql);
65
66        $sqlite->getPdo()->beginTransaction();
67        try {
68
69            foreach ($pending as $version) {
70                $call = 'migration' . $version;
71                foreach ($schemas as $schema) {
72                    $this->$call($sqlite, $schema);
73                }
74            }
75
76            // update migration status in struct database
77            $sql = 'REPLACE INTO opts(opt,val) VALUES ("dbversion_structstatus", ' . $version . ')';
78            $sqlite->query($sql);
79
80            $sqlite->getPdo()->commit();
81        } catch (Exception $e) {
82            $sqlite->getPdo()->rollBack();
83            return false;
84        }
85
86        return true;
87    }
88
89    /**
90     * Detect which migrations should be executed. Start conservatively with version 1.
91     *
92     * @param int $dbVersionStruct Current version of struct DB as found in 'opts' table
93     * @param int|null $dbVersionStructStatus Current version in 'opts', may not exist yet
94     * @return int[]
95     */
96    protected function diffVersions($dbVersionStruct, $dbVersionStructStatus)
97    {
98        $pluginDbVersion = $dbVersionStructStatus ?: 1;
99        return range($pluginDbVersion, $dbVersionStruct);
100    }
101
102    /**
103     * Converts integer ids used in struct before dbversion 17
104     * to composite ids ["",int]
105     *
106     * @param SQLiteDB $sqlite
107     * @param array $schema
108     * @return bool
109     */
110    protected function migration17($sqlite, $schema)
111    {
112        $name = $schema['tbl'];
113        $sid = $schema['id'];
114
115        $s = $this->getLookupColsSql($sid);
116        $cols = $sqlite->queryAll($s);
117
118        if ($cols) {
119            foreach ($cols as $col) {
120                $colno = $col['COL'];
121                $s = "UPDATE data_$name SET col$colno = '[" . '""' . ",'||col$colno||']' WHERE col$colno != '' AND CAST(col$colno AS DECIMAL) = col$colno";
122                $ok = true && $sqlite->query($s);
123                if (!$ok) return false;
124                // multi_
125                $s = "UPDATE multi_$name SET value = '[" . '""' . ",'||value||']' WHERE colref=$colno AND CAST(value AS DECIMAL) = value";
126                $ok = $ok && $sqlite->query($s);
127                if (!$ok) return false;
128            }
129        }
130
131        return true;
132    }
133
134    /**
135     * @param SQLiteDB $sqlite
136     * @return array
137     */
138    protected function getDbVersions($sqlite)
139    {
140        $dbVersionStruct = null;
141        $dbVersionStructStatus = null;
142
143        $sql = 'SELECT opt, val FROM opts WHERE opt=? OR opt=?';
144        $vals = $sqlite->queryAll($sql, ['dbversion', 'dbversion_structstatus']);
145
146        foreach ($vals as $val) {
147            if ($val['opt'] === 'dbversion') {
148                $dbVersionStruct = $val['val'];
149            }
150            if ($val['opt'] === 'dbversion_structstatus') {
151                $dbVersionStructStatus = $val['val'];
152            }
153        }
154         return [$dbVersionStruct, $dbVersionStructStatus];
155    }
156
157    /**
158     * Returns a select statement to fetch our columns in the current schema
159     *
160     * @param int $sid Id of the schema
161     * @return string SQL statement
162     */
163    protected function getLookupColsSql($sid)
164    {
165        return "SELECT C.colref AS COL, T.class AS TYPE
166                FROM schema_cols AS C
167                LEFT OUTER JOIN types AS T
168                    ON C.tid = T.id
169                WHERE C.sid = $sid
170                AND (TYPE = 'Status')
171            ";
172    }
173}
174
175// vim:ts=4:sw=4:et:
176