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