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