Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
34.88% covered (danger)
34.88%
30 / 86
33.33% covered (danger)
33.33%
2 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
RemoteApiActionExecutor
34.88% covered (danger)
34.88%
30 / 86
33.33% covered (danger)
33.33%
2 / 6
44.41
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 executeTestEditActionQuery
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
2
 executeEditAction
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
2
 executeUserRightsQuery
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
2
 executeDeleteAction
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
6
 statusFromApiResponse
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
4.13
1<?php
2
3namespace FileImporter\Remote\MediaWiki;
4
5use FileImporter\Data\SourceUrl;
6use MediaWiki\User\User;
7use StatusValue;
8
9/**
10 * @license GPL-2.0-or-later
11 */
12class RemoteApiActionExecutor {
13
14    public const CHANGE_TAG = 'fileimporter-remote';
15
16    private RemoteApiRequestExecutor $remoteApiRequestExecutor;
17
18    public function __construct( RemoteApiRequestExecutor $remoteApiRequestExecutor ) {
19        $this->remoteApiRequestExecutor = $remoteApiRequestExecutor;
20    }
21
22    /**
23     * @return StatusValue ok if the user can edit the page
24     */
25    public function executeTestEditActionQuery( SourceUrl $sourceUrl, User $user, string $title ): StatusValue {
26        // Expected return values:
27        // { "query": { "pages": [ { "actions": { "edit": true }, …
28        // { "query": { "pages": [ { "actions": { "edit": false }, …
29        $result = $this->remoteApiRequestExecutor->execute(
30            $sourceUrl,
31            $user,
32            [
33                'action' => 'query',
34                'errorformat' => 'plaintext',
35                'format' => 'json',
36                'formatversion' => 2,
37                'prop' => 'info',
38                'titles' => $title,
39                'intestactions' => 'edit',
40            ],
41            true
42        );
43
44        $status = $this->statusFromApiResponse( $result );
45        $canEdit = $result['query']['pages'][0]['actions']['edit'] ?? false;
46        $status->setOK( $canEdit );
47
48        return $status;
49    }
50
51    /**
52     * @param SourceUrl $sourceUrl
53     * @param User $user
54     * @param string $title
55     * @param array $params At least one of the parameters "text", "appendtext", "prependtext" and
56     *  "undo" is required.
57     * @param string $editSummary
58     */
59    public function executeEditAction(
60        SourceUrl $sourceUrl,
61        User $user,
62        string $title,
63        array $params,
64        string $editSummary
65    ): StatusValue {
66        $token = $this->remoteApiRequestExecutor->getCsrfToken( $sourceUrl, $user );
67        if ( $token === null ) {
68            return $this->statusFromApiResponse();
69        }
70
71        $result = $this->remoteApiRequestExecutor->execute(
72            $sourceUrl,
73            $user,
74            array_merge(
75                [
76                    'action' => 'edit',
77                    'token' => $token,
78                    'format' => 'json',
79                    'formatversion' => 2,
80                    'title' => $title,
81                    'summary' => $editSummary,
82                    'tags' => self::CHANGE_TAG,
83                ],
84                $params
85            ),
86            true
87        );
88        return $this->statusFromApiResponse( $result );
89    }
90
91    /**
92     * @return StatusValue ok if the user is allowed to delete pages
93     */
94    public function executeUserRightsQuery( SourceUrl $sourceUrl, User $user ): StatusValue {
95        // Expected return values:
96        // { "query": { "userinfo": { "rights": [ "delete", …
97        $result = $this->remoteApiRequestExecutor->execute(
98            $sourceUrl,
99            $user,
100            [
101                'action' => 'query',
102                'errorformat' => 'plaintext',
103                'format' => 'json',
104                'formatversion' => 2,
105                'meta' => 'userinfo',
106                'uiprop' => 'rights',
107            ]
108        );
109
110        $status = $this->statusFromApiResponse( $result );
111        $rights = $result['query']['userinfo']['rights'] ?? [];
112        $canDelete = in_array( 'delete', $rights );
113        $status->setOK( $canDelete );
114
115        return $status;
116    }
117
118    public function executeDeleteAction(
119        SourceUrl $sourceUrl,
120        User $user,
121        string $title,
122        string $deletionReason
123    ): StatusValue {
124        $token = $this->remoteApiRequestExecutor->getCsrfToken( $sourceUrl, $user );
125        if ( $token === null ) {
126            return $this->statusFromApiResponse();
127        }
128
129        $result = $this->remoteApiRequestExecutor->execute(
130            $sourceUrl,
131            $user,
132            [
133                'action' => 'delete',
134                'errorformat' => 'plaintext',
135                'format' => 'json',
136                'formatversion' => 2,
137                'title' => $title,
138                'reason' => $deletionReason,
139                'tags' => self::CHANGE_TAG,
140                'token' => $token,
141            ],
142            true
143        );
144        return $this->statusFromApiResponse( $result );
145    }
146
147    private function statusFromApiResponse( ?array $apiResponse = null ): StatusValue {
148        $status = StatusValue::newGood();
149
150        if ( !$apiResponse ) {
151            $status->setOK( false );
152            return $status;
153        }
154
155        // TODO: Simplify once all requests are updated.
156        // It's an array of "errors" with errorformat=plaintext, but a single "error" without.
157        $errors = $apiResponse['errors'] ?? [];
158        if ( isset( $apiResponse['error'] ) ) {
159            $errors[] = $apiResponse['error'];
160        }
161        foreach ( $errors as $error ) {
162            // Errors contain "code" and "info" with formatversion=2, but "code" and "*" without.
163            $status->error( $error['code'] );
164        }
165
166        return $status;
167    }
168
169}