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