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