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