Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
34.88% |
30 / 86 |
|
33.33% |
2 / 6 |
CRAP | |
0.00% |
0 / 1 |
RemoteApiActionExecutor | |
34.88% |
30 / 86 |
|
33.33% |
2 / 6 |
44.41 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
executeTestEditActionQuery | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
2 | |||
executeEditAction | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
2 | |||
executeUserRightsQuery | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
2 | |||
executeDeleteAction | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
6 | |||
statusFromApiResponse | |
80.00% |
8 / 10 |
|
0.00% |
0 / 1 |
4.13 |
1 | <?php |
2 | |
3 | namespace FileImporter\Remote\MediaWiki; |
4 | |
5 | use FileImporter\Data\SourceUrl; |
6 | use MediaWiki\User\User; |
7 | use StatusValue; |
8 | |
9 | /** |
10 | * @license GPL-2.0-or-later |
11 | */ |
12 | class 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 | } |