1<?php
2
3/**
4 * DokuWiki Plugin struct (Action Component)
5 *
6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
7 * @author  Andreas Gohr, Michael Große <dokuwiki@cosmocode.de>
8 */
9
10use dokuwiki\plugin\struct\meta\Assignments;
11use dokuwiki\plugin\struct\meta\Column;
12use dokuwiki\plugin\struct\meta\Schema;
13use dokuwiki\plugin\struct\types\Lookup;
14use dokuwiki\plugin\struct\types\Media;
15use dokuwiki\plugin\struct\types\Page;
16
17class action_plugin_struct_move extends DokuWiki_Action_Plugin
18{
19    /** @var helper_plugin_sqlite */
20    protected $db = null;
21
22    /**
23     * Registers a callback function for a given event
24     *
25     * @param Doku_Event_Handler $controller DokuWiki's event controller object
26     * @return void
27     */
28    public function register(Doku_Event_Handler $controller)
29    {
30        $controller->register_hook('PLUGIN_MOVE_PAGE_RENAME', 'AFTER', $this, 'handleMove', true);
31        $controller->register_hook('PLUGIN_MOVE_MEDIA_RENAME', 'AFTER', $this, 'handleMove', false);
32    }
33
34    /**
35     * Renames all occurrences of a page ID in the database
36     *
37     * @param Doku_Event $event event object by reference
38     * @param bool $ispage is this a page move operation?
39     * @return bool
40     */
41    public function handleMove(Doku_Event $event, $ispage)
42    {
43        /** @var helper_plugin_struct_db $hlp */
44        $hlp = plugin_load('helper', 'struct_db');
45        $this->db = $hlp->getDB(false);
46        if (!$this->db) return false;
47        $old = $event->data['src_id'];
48        $new = $event->data['dst_id'];
49
50        // prepare work
51        $this->db->query('BEGIN TRANSACTION');
52
53        // general update of our meta tables
54        if ($ispage) {
55            $this->updateDataTablePIDs($old, $new);
56            $this->updateAssignments($old, $new);
57            $this->updateTitles($old, $new);
58        }
59
60        // apply updates to all columns in all schemas depending on type
61        $schemas = Schema::getAll();
62        foreach ($schemas as $table) {
63            $schema = new Schema($table);
64            foreach ($schema->getColumns() as $col) {
65                if ($ispage) {
66                    switch (get_class($col->getType())) {
67                        case Page::class:
68                            $this->updateColumnID($schema, $col, $old, $new, true);
69                            break;
70                        case Lookup::class:
71                            $this->updateColumnLookup($schema, $col, $old, $new);
72                            break;
73                    }
74                } else {
75                    switch (get_class($col->getType())) {
76                        case Media::class:
77                            $this->updateColumnID($schema, $col, $old, $new);
78                            break;
79                    }
80                }
81            }
82        }
83
84        // execute everything
85        $ok = $this->db->query('COMMIT TRANSACTION');
86        if (!$ok) {
87            $this->db->query('ROLLBACK TRANSACTION');
88            return false;
89        }
90
91        return true;
92    }
93
94    /**
95     * Update the pid column of ALL data tables
96     *
97     * (we don't trust the assigments are still there)
98     *
99     * @param string $old old page id
100     * @param string $new new page id
101     */
102    protected function updateDataTablePIDs($old, $new)
103    {
104        foreach (Schema::getAll() as $tbl) {
105            /** @noinspection SqlResolve */
106            $sql = "UPDATE data_$tbl SET pid = ? WHERE pid = ?";
107            $this->db->query($sql, array($new, $old));
108
109            /** @noinspection SqlResolve */
110            $sql = "UPDATE multi_$tbl SET pid = ? WHERE pid = ?";
111            $this->db->query($sql, array($new, $old));
112        }
113    }
114
115    /**
116     * Update the page-schema assignments
117     *
118     * @param string $old old page id
119     * @param string $new new page id
120     */
121    protected function updateAssignments($old, $new)
122    {
123        // assignments
124        $sql = "UPDATE schema_assignments SET pid = ? WHERE pid = ?";
125        $this->db->query($sql, array($new, $old));
126        // make sure assignments still match patterns;
127        $assignments = Assignments::getInstance();
128        $assignments->reevaluatePageAssignments($new);
129    }
130
131    /**
132     * Update the Title information for the moved page
133     *
134     * @param string $old old page id
135     * @param string $new new page id
136     */
137    protected function updateTitles($old, $new)
138    {
139        $sql = "UPDATE titles SET pid = ? WHERE pid = ?";
140        $this->db->query($sql, array($new, $old));
141    }
142
143    /**
144     * Update the ID in a given column
145     *
146     * @param Schema $schema
147     * @param Column $col
148     * @param string $old old page id
149     * @param string $new new page id
150     * @param bool $hashes could the ID have a hash part? (for Page type)
151     */
152    protected function updateColumnID(Schema $schema, Column $col, $old, $new, $hashes = false)
153    {
154        $colref = $col->getColref();
155        $table = $schema->getTable();
156
157        if ($col->isMulti()) {
158            /** @noinspection SqlResolve */
159            $sql = "UPDATE multi_$table
160                               SET value = REPLACE(value, ?, ?)
161                             WHERE value LIKE ?
162                               AND colref = $colref
163                               AND latest = 1";
164        } else {
165            /** @noinspection SqlResolve */
166            $sql = "UPDATE data_$table
167                               SET col$colref = REPLACE(col$colref, ?, ?)
168                             WHERE col$colref LIKE ?
169                               AND latest = 1";
170        }
171        $this->db->query($sql, $old, $new, $old); // exact match
172        if ($hashes) {
173            $this->db->query($sql, $old, $new, "$old#%"); // match with hashes
174        }
175        if (get_class($col->getType()) === Lookup::class) {
176            $this->db->query($sql, $old, $new, "[\"$old\",%]"); // match JSON string
177            if ($hashes) {
178                $this->db->query($sql, $old, $new, "[\"$old#%\",%]"); // match JSON string with hash
179            }
180        }
181    }
182
183    /**
184     * Update a Lookup type column
185     *
186     * Lookups contain a page id when the referenced schema is a data schema
187     *
188     * @param Schema $schema
189     * @param Column $col
190     * @param string $old old page id
191     * @param string $new new page id
192     */
193    protected function updateColumnLookup(Schema $schema, Column $col, $old, $new)
194    {
195        $tconf = $col->getType()->getConfig();
196        $ref = new Schema($tconf['schema']);
197        if (!$ref->getId()) return; // this schema does not exist
198        if (!$ref->getTimeStamp()) return; // a lookup is referenced, nothing to do
199        $this->updateColumnID($schema, $col, $old, $new);
200    }
201}
202