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