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