xref: /dokuwiki/bin/dwpage.php (revision a38bc7e6c4a8b1362de2bae7c3f9b01bf79df38f)
1#!/usr/bin/php
2<?php
3if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__).'/../').'/');
4define('NOSESSION', 1);
5require_once(DOKU_INC.'inc/init.php');
6
7/**
8 * Checkout and commit pages from the command line while maintaining the history
9 */
10class PageCLI extends DokuCLI {
11
12    protected $force = false;
13    protected $username = '';
14
15    /**
16     * Register options and arguments on the given $options object
17     *
18     * @param DokuCLI_Options $options
19     * @return void
20     */
21    protected function setup(DokuCLI_Options $options) {
22        /* global */
23        $options->registerOption(
24            'force',
25            'force obtaining a lock for the page (generally bad idea)',
26            'f'
27        );
28        $options->registerOption(
29            'user',
30            'work as this user. defaults to current CLI user',
31            'u',
32            'username'
33        );
34        $options->setHelp(
35            'Utility to help command line Dokuwiki page editing, allow '.
36            'pages to be checked out for editing then committed after changes'
37        );
38
39        /* checkout command */
40        $options->registerCommand(
41            'checkout',
42            'Checks out a file from the repository, using the wiki id and obtaining '.
43            'a lock for the page. '."\n".
44            'If a working_file is specified, this is where the page is copied to. '.
45            'Otherwise defaults to the same as the wiki page in the current '.
46            'working directory.'
47        );
48        $options->registerArgument(
49            'wikipage',
50            'The wiki page to checkout',
51            true,
52            'checkout'
53        );
54        $options->registerArgument(
55            'workingfile',
56            'How to name the local checkout',
57            false,
58            'checkout'
59        );
60
61        /* commit command */
62        $options->registerCommand(
63            'commit',
64            'Checks in the working_file into the repository using the specified '.
65            'wiki id, archiving the previous version.'
66        );
67        $options->registerArgument(
68            'workingfile',
69            'The local file to commit',
70            true,
71            'commit'
72        );
73        $options->registerArgument(
74            'wikipage',
75            'The wiki page to create or update',
76            true,
77            'commit'
78        );
79        $options->registerOption(
80            'message',
81            'Summary describing the change (required)',
82            'm',
83            'summary',
84            'commit'
85        );
86        $options->registerOption(
87            'trivial',
88            'minor change',
89            't',
90            false,
91            'commit'
92        );
93
94        /* lock command */
95        $options->registerCommand(
96            'lock',
97            'Obtains or updates a lock for a wiki page'
98        );
99        $options->registerArgument(
100            'wikipage',
101            'The wiki page to lock',
102            true,
103            'lock'
104        );
105
106        /* unlock command */
107        $options->registerCommand(
108            'unlock',
109            'Removes a lock for a wiki page.'
110        );
111        $options->registerArgument(
112            'wikipage',
113            'The wiki page to unlock',
114            true,
115            'unlock'
116        );
117    }
118
119    /**
120     * Your main program
121     *
122     * Arguments and options have been parsed when this is run
123     *
124     * @param DokuCLI_Options $options
125     * @return void
126     */
127    protected function main(DokuCLI_Options $options) {
128        $this->force    = $options->getOpt('force', false);
129        $this->username = $options->getOpt('user', $this->getUser());
130
131        $command = $options->getCmd();
132        switch($command) {
133            case 'checkout':
134                $wiki_id   = array_shift($options->args);
135                $localfile = array_shift($options->args);
136                $this->commandCheckout($wiki_id, $localfile);
137                break;
138            case 'commit':
139                $localfile = array_shift($options->args);
140                $wiki_id   = array_shift($options->args);
141                $this->commandCommit(
142                    $localfile,
143                    $wiki_id,
144                    $options->getOpt('message', ''),
145                    $options->getOpt('trivial', false)
146                );
147                break;
148            case 'lock':
149                $wiki_id = array_shift($options->args);
150                $this->obtainLock($wiki_id);
151                $this->success("$wiki_id locked");
152                break;
153            case 'unlock':
154                $wiki_id = array_shift($options->args);
155                $this->clearLock($wiki_id);
156                $this->success("$wiki_id unlocked");
157                break;
158            default:
159                echo $options->help();
160        }
161    }
162
163    /**
164     * Check out a file
165     *
166     * @param string $wiki_id
167     * @param string $localfile
168     */
169    protected function commandCheckout($wiki_id, $localfile) {
170        global $conf;
171
172        $wiki_id = cleanID($wiki_id);
173        $wiki_fn = wikiFN($wiki_id);
174
175        if(!file_exists($wiki_fn)) {
176            $this->fatal("$wiki_id does not yet exist");
177        }
178
179        if(empty($localfile)) {
180            $localfile = getcwd().'/'.utf8_basename($wiki_fn);
181        }
182
183        if(!file_exists(dirname($localfile))) {
184            $this->fatal("Directory ".dirname($localfile)." does not exist");
185        }
186
187        if(stristr(realpath(dirname($localfile)), realpath($conf['datadir'])) !== false) {
188            $this->fatal("Attempt to check out file into data directory - not allowed");
189        }
190
191        $this->obtainLock($wiki_id);
192
193        if(!copy($wiki_fn, $localfile)) {
194            $this->clearLock($wiki_id);
195            $this->fatal("Unable to copy $wiki_fn to $localfile");
196        }
197
198        $this->success("$wiki_id > $localfile");
199    }
200
201    /**
202     * Save a file as a new page revision
203     *
204     * @param string $localfile
205     * @param string $wiki_id
206     * @param string $message
207     * @param bool   $minor
208     */
209    protected function commandCommit($localfile, $wiki_id, $message, $minor) {
210        $wiki_id = cleanID($wiki_id);
211        $message = trim($message);
212
213        if(!file_exists($localfile)) {
214            $this->fatal("$localfile does not exist");
215        }
216
217        if(!is_readable($localfile)) {
218            $this->fatal("Cannot read from $localfile");
219        }
220
221        if(!$message) {
222            $this->fatal("Summary message required");
223        }
224
225        $this->obtainLock($wiki_id);
226
227        saveWikiText($wiki_id, file_get_contents($localfile), $message, $minor);
228
229        $this->clearLock($wiki_id);
230
231        $this->success("$localfile > $wiki_id");
232    }
233
234    /**
235     * Lock the given page or exit
236     *
237     * @param string $wiki_id
238     */
239    protected function obtainLock($wiki_id) {
240        if($this->force) $this->deleteLock($wiki_id);
241
242        $_SERVER['REMOTE_USER'] = $this->username;
243
244        if(checklock($wiki_id)) {
245            $this->error("Page $wiki_id is already locked by another user");
246            exit(1);
247        }
248
249        lock($wiki_id);
250
251        if(checklock($wiki_id)) {
252            $this->error("Unable to obtain lock for $wiki_id ");
253            var_dump(checklock($wiki_id));
254            exit(1);
255        }
256    }
257
258    /**
259     * Clear the lock on the given page
260     *
261     * @param string $wiki_id
262     */
263    protected function clearLock($wiki_id) {
264        if($this->force) $this->deleteLock($wiki_id);
265
266        $_SERVER['REMOTE_USER'] = $this->username;
267        if(checklock($wiki_id)) {
268            $this->error("Page $wiki_id is locked by another user");
269            exit(1);
270        }
271
272        unlock($wiki_id);
273
274        if(file_exists(wikiLockFN($wiki_id))) {
275            $this->error("Unable to clear lock for $wiki_id");
276            exit(1);
277        }
278    }
279
280    /**
281     * Forcefully remove a lock on the page given
282     *
283     * @param string $wiki_id
284     */
285    protected function deleteLock($wiki_id) {
286        $wikiLockFN = wikiLockFN($wiki_id);
287
288        if(file_exists($wikiLockFN)) {
289            if(!unlink($wikiLockFN)) {
290                $this->error("Unable to delete $wikiLockFN");
291                exit(1);
292            }
293        }
294    }
295
296    /**
297     * Get the current user's username from the environment
298     *
299     * @return string
300     */
301    protected function getUser() {
302        $user = getenv('USER');
303        if(empty ($user)) {
304            $user = getenv('USERNAME');
305        } else {
306            return $user;
307        }
308        if(empty ($user)) {
309            $user = 'admin';
310        }
311        return $user;
312    }
313}
314
315
316// Main
317$cli = new PageCLI();
318$cli->run();