1<?php
2/**
3 * Move Plugin Page Rename Functionality
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Andreas Gohr <gohr@cosmocode.de>
7 */
8// must be run within Dokuwiki
9if(!defined('DOKU_INC')) die();
10
11/**
12 * Class action_plugin_move_rename
13 */
14class action_plugin_move_rename extends DokuWiki_Action_Plugin {
15
16    /**
17     * Register event handlers.
18     *
19     * @param Doku_Event_Handler $controller The plugin controller
20     */
21    public function register(Doku_Event_Handler $controller) {
22        $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, 'handle_init');
23
24        // TODO: DEPRECATED JAN 2018
25        $controller->register_hook('TEMPLATE_PAGETOOLS_DISPLAY', 'BEFORE', $this, 'handle_pagetools');
26
27        $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'addsvgbutton', array());
28        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax');
29        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handleAjaxMediaManager');
30    }
31
32    /**
33     * set JavaScript info if renaming of current page is possible
34     */
35    public function handle_init() {
36        global $JSINFO;
37        global $INFO;
38        global $INPUT;
39        global $USERINFO;
40
41        if (isset($INFO['id'])) {
42            $JSINFO['move_renameokay'] = $this->renameOkay($INFO['id']);
43        } else {
44            $JSINFO['move_renameokay'] = false;
45        }
46
47        $JSINFO['move_allowrename'] = auth_isMember(
48            $this->getConf('allowrename'),
49            $INPUT->server->str('REMOTE_USER'),
50            $USERINFO['grps'] ?? []
51        );
52    }
53
54    /**
55     * Adds a button to the default template
56     *
57     * TODO: DEPRECATED JAN 2018
58     *
59     * @param Doku_Event $event
60     */
61    public function handle_pagetools(Doku_Event $event) {
62        if($event->data['view'] != 'main') return;
63        if (!$this->getConf('pagetools_integration')) {
64            return;
65        }
66
67        $newitem = '<li class="plugin_move_page"><a href=""><span>' . $this->getLang('renamepage') . '</span></a></li>';
68        $offset = count($event->data['items']) - 1;
69        $event->data['items'] =
70            array_slice($event->data['items'], 0, $offset, true) +
71            array('plugin_move' => $newitem) +
72            array_slice($event->data['items'], $offset, null, true);
73    }
74
75    /**
76     * Add 'rename' button to page tools, new SVG based mechanism
77     *
78     * @param Doku_Event $event
79     */
80    public function addsvgbutton(Doku_Event $event) {
81        global $INFO, $JSINFO;
82        if(
83            $event->data['view'] !== 'page' ||
84            !$this->getConf('pagetools_integration') ||
85            empty($JSINFO['move_renameokay'])
86        ) {
87            return;
88        }
89        if(!$INFO['exists']) {
90            return;
91        }
92        array_splice($event->data['items'], -1, 0, array(new \dokuwiki\plugin\move\MenuItem()));
93    }
94
95    /**
96     * Rename a single page
97     *
98     * This creates a plan and executes it right away. If the user selected to move media with the page,
99     * all media files used in the original page that are located in the same namespace are moved with the page
100     * to the new namespace.
101     *
102     */
103    public function handle_ajax(Doku_Event $event) {
104        if($event->data != 'plugin_move_rename') return;
105        $event->preventDefault();
106        $event->stopPropagation();
107
108        global $MSG;
109        global $INPUT;
110
111        $src = cleanID($INPUT->str('id'));
112        $dst = cleanID($INPUT->str('newid'));
113        $doMedia = $INPUT->bool('media');
114
115        header('Content-Type: application/json');
116
117        if(!$this->renameOkay($src)) {
118            echo json_encode(['error' => $this->getLang('cantrename')]);
119            return;
120        }
121
122        if(!$dst || $dst == $src) {
123            echo json_encode(['error' => $this->getLang('nodst')]);
124            return;
125        }
126
127        /** @var helper_plugin_move_plan $plan */
128        $plan = plugin_load('helper', 'move_plan');
129        if($plan->isCommited()) {
130            echo json_encode(['error' => $this->getLang('cantrename')]);
131            return;
132        }
133        $plan->setOption('autorewrite', true);
134        $plan->addPageMove($src, $dst); // add the page move to the plan
135
136        if($doMedia) { // move media with the page?
137            $srcNS = getNS($src);
138            $dstNS = getNS($dst);
139            $srcNSLen = strlen($srcNS);
140            // we don't do this for root namespace or if namespace hasn't changed
141            if ($srcNS != '' && $srcNS != $dstNS) {
142                $media = p_get_metadata($src, 'relation media');
143                if (is_array($media)) {
144                    foreach ($media as $file => $exists) {
145                        if(!$exists) continue;
146                        $mediaNS = getNS($file);
147                        if ($mediaNS == $srcNS) {
148                            $plan->addMediaMove($file, $dstNS . substr($file, $srcNSLen));
149                        }
150                    }
151                }
152            }
153        }
154
155        try {
156            // commit and execute the plan
157            $plan->commit();
158            do {
159                $next = $plan->nextStep();
160                if ($next === false) throw new \Exception('Move plan failed');
161            } while ($next > 0);
162            echo json_encode(array('redirect_url' => wl($dst, '', true, '&')));
163        } catch (\Exception $e) {
164            // error should be in $MSG
165            if(isset($MSG[0])) {
166                $error = $MSG[0]; // first error
167            } else {
168                $error = $this->getLang('cantrename') . ' ' . $e->getMessage();
169            }
170            echo json_encode(['error' => $error]);
171        }
172    }
173
174    /**
175     * Handle media renames in media manager
176     *
177     * @param Doku_Event $event
178     * @return void
179     */
180    public function handleAjaxMediaManager(Doku_Event $event)
181    {
182        if ($event->data !== 'plugin_move_rename_mediamanager') return;
183
184        if (!checkSecurityToken()) {
185            throw new \Exception('Security token did not match');
186        }
187
188        $event->preventDefault();
189        $event->stopPropagation();
190
191        global $INPUT;
192        global $MSG;
193        global $USERINFO;
194
195        $src = cleanID($INPUT->str('src'));
196        $dst = cleanID($INPUT->str('dst'));
197
198        /** @var helper_plugin_move_op $moveOperator */
199        $moveOperator = plugin_load('helper', 'move_op');
200
201        if ($src && $dst) {
202            header('Content-Type: application/json');
203
204            $response = [];
205
206            // check user/group restrictions
207            if (
208                !auth_isMember($this->getConf('allowrename'), $INPUT->server->str('REMOTE_USER'), (array) $USERINFO['grps'])
209            ) {
210                $response['error'] = $this->getLang('notallowed');
211                echo json_encode($response);
212                return;
213            }
214
215            $response['success'] = $moveOperator->moveMedia($src, $dst);
216
217            if ($response['success']) {
218                $ns = getNS($dst);
219                $response['redirect_url'] = wl($dst, ['do' => 'media', 'ns' => $ns], true, '&');
220            } else {
221                $response['error'] = sprintf($this->getLang('mediamoveerror'), $src);
222                if (isset($MSG)) {
223                    foreach ($MSG as $msg) {
224                        $response['error'] .= ' ' . $msg['msg'];
225                    }
226                }
227            }
228
229            echo json_encode($response);
230        }
231    }
232
233    /**
234     * Determines if it would be okay to show a rename page button for the given page and current user
235     *
236     * @param $id
237     * @return bool
238     */
239    public function renameOkay($id) {
240        global $conf;
241        global $ACT;
242        global $USERINFO;
243        if(!($ACT == 'show' || empty($ACT))) return false;
244        if(!page_exists($id)) return false;
245        if(auth_quickaclcheck($id) < AUTH_EDIT) return false;
246        if(checklock($id) !== false || @file_exists(wikiLockFN($id))) return false;
247        if(!$conf['useacl']) return true;
248        if(!isset($_SERVER['REMOTE_USER'])) return false;
249        if(!auth_isMember($this->getConf('allowrename'), $_SERVER['REMOTE_USER'], (array) $USERINFO['grps'])) return false;
250
251        return true;
252    }
253
254    /**
255     * Use this in your template to add a simple "move this page" link
256     *
257     * Alternatively give anything the class "plugin_move_page" - it will automatically be hidden and shown and
258     * trigger the page move dialog.
259     */
260    public function tpl() {
261        echo '<a href="" class="plugin_move_page">';
262        echo $this->getLang('renamepage');
263        echo '</a>';
264    }
265
266}
267