Failed Conditions
Push — master ( b2c9cd...43d3f0 )
by Andreas
12:59 queued 09:00
created

PageCLI::main()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 36
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 32
nc 5
nop 1
dl 0
loc 36
rs 8.439
c 0
b 0
f 0
1
#!/usr/bin/php
2
<?php
3
4
use splitbrain\phpcli\CLI;
5
use splitbrain\phpcli\Options;
6
7
if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../') . '/');
8
define('NOSESSION', 1);
9
require_once(DOKU_INC . 'inc/init.php');
10
11
/**
12
 * Checkout and commit pages from the command line while maintaining the history
13
 */
14
class 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);
0 ignored issues
show
Documentation Bug introduced by
It seems like $options->getOpt('force', false) can also be of type string or array<integer,string>. However, the property $force is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
133
        $this->username = $options->getOpt('user', $this->getUser());
0 ignored issues
show
Documentation Bug introduced by
It seems like $options->getOpt('user', $this->getUser()) can also be of type boolean or array<integer,string>. However, the property $username is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
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));
0 ignored issues
show
Security Debugging Code introduced by
var_dump(checklock($wiki_id)); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
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