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