This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | declare( strict_types = 1 ); |
||
4 | |||
5 | namespace Wikibase\Repo\Hooks; |
||
6 | |||
7 | use IJobSpecification; |
||
8 | use JobQueueGroup; |
||
9 | use JobSpecification; |
||
10 | use MediaWiki\Hook\ArticleRevisionVisibilitySetHook; |
||
11 | use MediaWiki\Revision\RevisionLookup; |
||
12 | use MediaWiki\Revision\RevisionRecord; |
||
13 | use Title; |
||
14 | use TitleFactory; |
||
15 | use Wikibase\Lib\Changes\RepoRevisionIdentifier; |
||
16 | use Wikibase\Lib\Store\EntityNamespaceLookup; |
||
17 | use Wikibase\Repo\Content\EntityContentFactory; |
||
18 | use Wikibase\Repo\WikibaseRepo; |
||
19 | use Wikimedia\Timestamp\ConvertibleTimestamp; |
||
20 | |||
21 | /** |
||
22 | * Hook handler that propagates changes to the visibility of an article's revisions |
||
23 | * to clients. |
||
24 | * |
||
25 | * This schedules "ChangeVisibilityNotification" jobs on all client wikis (all as |
||
26 | * some wikis might no longer be subscribed) which will handle this on the clients. |
||
27 | * |
||
28 | * @license GPL-2.0-or-later |
||
29 | * @author Marius Hoch |
||
30 | */ |
||
31 | class ArticleRevisionVisibilitySetHookHandler implements ArticleRevisionVisibilitySetHook { |
||
32 | |||
33 | /** |
||
34 | * @var RevisionLookup |
||
35 | */ |
||
36 | private $revisionLookup; |
||
37 | |||
38 | /** |
||
39 | * @var EntityContentFactory |
||
40 | */ |
||
41 | private $entityContentFactory; |
||
42 | |||
43 | /** |
||
44 | * @var EntityNamespaceLookup |
||
45 | */ |
||
46 | private $entityNamespaceLookup; |
||
47 | |||
48 | /** |
||
49 | * @var TitleFactory |
||
50 | */ |
||
51 | private $titleFactory; |
||
52 | |||
53 | /** |
||
54 | * @var string[] |
||
55 | */ |
||
56 | private $localClientDatabases; |
||
57 | |||
58 | /** |
||
59 | * @var int |
||
60 | */ |
||
61 | private $clientRCMaxAge; |
||
62 | |||
63 | /** |
||
64 | * @var int |
||
65 | */ |
||
66 | private $jobBatchSize; |
||
67 | |||
68 | /** |
||
69 | * @var callable |
||
70 | */ |
||
71 | private $jobQueueGroupFactory; |
||
72 | |||
73 | public function __construct( |
||
74 | RevisionLookup $revisionLookup, |
||
75 | EntityContentFactory $entityContentFactory, |
||
76 | EntityNamespaceLookup $entityNamespaceLookup, |
||
77 | TitleFactory $titleFactory, |
||
78 | array $localClientDatabases, |
||
79 | callable $jobQueueGroupFactory, |
||
80 | int $clientRCMaxAge, |
||
81 | int $jobBatchSize |
||
82 | ) { |
||
83 | $this->revisionLookup = $revisionLookup; |
||
84 | $this->entityContentFactory = $entityContentFactory; |
||
85 | $this->entityNamespaceLookup = $entityNamespaceLookup; |
||
86 | $this->titleFactory = $titleFactory; |
||
87 | $this->localClientDatabases = $localClientDatabases; |
||
88 | $this->jobQueueGroupFactory = $jobQueueGroupFactory; |
||
89 | $this->clientRCMaxAge = $clientRCMaxAge; |
||
90 | $this->jobBatchSize = $jobBatchSize; |
||
91 | } |
||
92 | |||
93 | public static function factory( RevisionLookup $revisionLookup, TitleFactory $titleFactory ) { |
||
94 | $wbRepo = WikibaseRepo::getDefaultInstance(); |
||
95 | |||
96 | return new self( |
||
97 | $revisionLookup, |
||
98 | $wbRepo->getEntityContentFactory(), |
||
99 | $wbRepo->getLocalEntityNamespaceLookup(), |
||
100 | $titleFactory, |
||
101 | $wbRepo->getSettings()->getSetting( 'localClientDatabases' ), |
||
102 | 'JobQueueGroup::singleton', |
||
103 | $wbRepo->getSettings()->getSetting( 'changeVisibilityNotificationClientRCMaxAge' ), |
||
104 | $wbRepo->getSettings()->getSetting( 'changeVisibilityNotificationJobBatchSize' ) |
||
105 | ); |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * @param Title $title |
||
110 | * @param int[] $ids |
||
111 | * @param int[][] $visibilityChangeMap |
||
112 | */ |
||
113 | public function onArticleRevisionVisibilitySet( $title, $ids, $visibilityChangeMap ): void { |
||
114 | if ( $this->localClientDatabases === [] ) { |
||
115 | return; |
||
116 | } |
||
117 | |||
118 | // Check if $title is in a wikibase namespace |
||
119 | if ( !$this->entityNamespaceLookup->isEntityNamespace( $title->getNamespace() ) ) { |
||
120 | return; |
||
121 | } |
||
122 | |||
123 | /** @var RevisionRecord[][] $revisionsByNewBits */ |
||
124 | $revisionsByNewBits = []; |
||
125 | foreach ( $this->getEligibleRevisionsById( $ids ) as $id => $revision ) { |
||
126 | $revisionsByNewBits[$visibilityChangeMap[$id]['newBits']][] = $revision; |
||
127 | } |
||
128 | |||
129 | $jobSpecifications = []; |
||
130 | foreach ( $revisionsByNewBits as $newBits => $revisions ) { |
||
131 | $revisionIdentifiers = $this->getRepoRevisionIdentifiers( $revisions ); |
||
132 | |||
133 | foreach ( array_chunk( $revisionIdentifiers, $this->jobBatchSize ) as $revisionIdentifierChunk ) { |
||
134 | $jobSpecifications[] = $this->createJobSpecification( |
||
135 | $this->revisionIdentifiersToJson( $revisionIdentifierChunk ), |
||
136 | $newBits |
||
137 | ); |
||
138 | } |
||
139 | } |
||
140 | |||
141 | if ( !$jobSpecifications ) { |
||
0 ignored issues
–
show
|
|||
142 | return; |
||
143 | } |
||
144 | foreach ( $this->localClientDatabases as $clientDatabase ) { |
||
145 | $this->newJobQueueGroup( $clientDatabase )->push( $jobSpecifications ); |
||
146 | } |
||
147 | } |
||
148 | |||
149 | /** |
||
150 | * Gets all revisions with the given ids and returns those that are relevant |
||
151 | * (=> no older than self::clientRCMaxAge). |
||
152 | * |
||
153 | * @param int[] $ids |
||
154 | * |
||
155 | * @return RevisionRecord[] Indexed by revision id |
||
156 | */ |
||
157 | private function getEligibleRevisionsById( array $ids ): array { |
||
158 | $revisions = []; |
||
159 | |||
160 | foreach ( $ids as $id ) { |
||
161 | $revision = $this->revisionLookup->getRevisionById( $id ); |
||
162 | if ( !$revision ) { |
||
163 | continue; |
||
164 | } |
||
165 | |||
166 | $timeDiff = time() - $this->getRevisionAge( $revision ); |
||
167 | if ( $timeDiff > $this->clientRCMaxAge ) { |
||
168 | continue; |
||
169 | } |
||
170 | |||
171 | $revisions[$id] = $revision; |
||
172 | } |
||
173 | |||
174 | return $revisions; |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * @param RevisionRecord[] $revisions |
||
179 | * |
||
180 | * @return RepoRevisionIdentifier[] |
||
181 | */ |
||
182 | private function getRepoRevisionIdentifiers( array $revisions ): array { |
||
183 | $revisionIdentifiers = []; |
||
184 | |||
185 | foreach ( $revisions as $revision ) { |
||
186 | $repoRevisionIdentifier = $this->newRepoRevisionIdentifier( $revision ); |
||
0 ignored issues
–
show
Are you sure the assignment to
$repoRevisionIdentifier is correct as $this->newRepoRevisionIdentifier($revision) (which targets Wikibase\Repo\Hooks\Arti...epoRevisionIdentifier() ) seems to always return null.
This check looks for function or method calls that always return null and whose return value is assigned to a variable. class A
{
function getObject()
{
return null;
}
}
$a = new A();
$object = $a->getObject();
The method The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes. ![]() |
|||
187 | if ( $repoRevisionIdentifier ) { |
||
188 | $revisionIdentifiers[] = $repoRevisionIdentifier; |
||
189 | } |
||
190 | } |
||
191 | |||
192 | return $revisionIdentifiers; |
||
193 | } |
||
194 | |||
195 | /** |
||
196 | * @param RevisionRecord $revision |
||
197 | * |
||
198 | * @return RepoRevisionIdentifier|null |
||
199 | */ |
||
200 | private function newRepoRevisionIdentifier( RevisionRecord $revision ): ?RepoRevisionIdentifier { |
||
201 | $title = $this->titleFactory->newFromID( $revision->getPageId() ); |
||
202 | if ( !$title ) { |
||
203 | return null; |
||
204 | } |
||
205 | $entityId = $this->entityContentFactory->getEntityIdForTitle( $title ); |
||
206 | if ( !$entityId ) { |
||
207 | return null; |
||
208 | } |
||
209 | return new RepoRevisionIdentifier( |
||
210 | $entityId->getSerialization(), |
||
211 | $revision->getTimestamp(), |
||
212 | $revision->getId() |
||
213 | ); |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * @param RevisionRecord $revision |
||
218 | * |
||
219 | * @return int Age of the revision in seconds |
||
220 | */ |
||
221 | private function getRevisionAge( RevisionRecord $revision ): int { |
||
222 | return intval( |
||
223 | ( new ConvertibleTimestamp( $revision->getTimestamp() ) )->getTimestamp( TS_UNIX ) |
||
224 | ); |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * @param string $wikiId |
||
229 | * |
||
230 | * @return JobQueueGroup |
||
231 | */ |
||
232 | private function newJobQueueGroup( string $wikiId ): JobQueueGroup { |
||
233 | return call_user_func( $this->jobQueueGroupFactory, $wikiId ); |
||
234 | } |
||
235 | |||
236 | /** |
||
237 | * JSON encode the given RepoRevisionIdentifiers |
||
238 | * |
||
239 | * @param RepoRevisionIdentifier[] $revisionIdentifiers |
||
240 | * |
||
241 | * @return string JSON |
||
242 | */ |
||
243 | private function revisionIdentifiersToJson( array $revisionIdentifiers ): string { |
||
244 | return json_encode( |
||
245 | array_map( |
||
246 | function ( RepoRevisionIdentifier $revisionIdentifier ) { |
||
247 | return $revisionIdentifier->toArray(); |
||
248 | }, |
||
249 | $revisionIdentifiers |
||
250 | ) |
||
251 | ); |
||
252 | } |
||
253 | |||
254 | /** |
||
255 | * Returns a new job for updating a client. |
||
256 | * |
||
257 | * @param string $revisionIdentifiersJson |
||
258 | * @param int $visibilityBitFlag |
||
259 | * |
||
260 | * @return IJobSpecification |
||
261 | */ |
||
262 | private function createJobSpecification( string $revisionIdentifiersJson, int $visibilityBitFlag ): IJobSpecification { |
||
263 | return new JobSpecification( |
||
264 | 'ChangeVisibilityNotification', |
||
265 | [ |
||
266 | 'revisionIdentifiersJson' => $revisionIdentifiersJson, |
||
267 | 'visibilityBitFlag' => $visibilityBitFlag, |
||
268 | ] |
||
269 | |||
270 | ); |
||
271 | } |
||
272 | |||
273 | } |
||
274 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.