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 | namespace Wikibase\Repo\LinkedData; |
||
4 | |||
5 | use ApiFormatBase; |
||
6 | use ApiMain; |
||
7 | use ApiResult; |
||
8 | use DerivativeContext; |
||
9 | use DerivativeRequest; |
||
10 | use MWException; |
||
11 | use PageProps; |
||
12 | use RequestContext; |
||
13 | use Serializers\Serializer; |
||
14 | use SiteLookup; |
||
15 | use Wikibase\DataModel\Entity\EntityId; |
||
16 | use Wikibase\DataModel\Entity\EntityRedirect; |
||
17 | use Wikibase\DataModel\SerializerFactory; |
||
18 | use Wikibase\DataModel\Services\Lookup\EntityLookup; |
||
19 | use Wikibase\DataModel\Services\Lookup\PropertyDataTypeLookup; |
||
20 | use Wikibase\Lib\Store\EntityRevision; |
||
21 | use Wikibase\Lib\Store\EntityTitleLookup; |
||
22 | use Wikibase\Lib\Store\RedirectRevision; |
||
23 | use Wikibase\Repo\Api\ResultBuilder; |
||
24 | use Wikibase\Repo\Rdf\EntityRdfBuilderFactory; |
||
25 | use Wikibase\Repo\Rdf\HashDedupeBag; |
||
26 | use Wikibase\Repo\Rdf\RdfBuilder; |
||
27 | use Wikibase\Repo\Rdf\RdfProducer; |
||
28 | use Wikibase\Repo\Rdf\RdfVocabulary; |
||
29 | use Wikibase\Repo\Rdf\ValueSnakRdfBuilderFactory; |
||
30 | use Wikimedia\Purtle\RdfWriterFactory; |
||
31 | |||
32 | /** |
||
33 | * Service for serializing entity data. |
||
34 | * |
||
35 | * Note that we are using the API's serialization facility to ensure a consistent external |
||
36 | * representation of data entities. Using the ContentHandler to serialize the entity would expose |
||
37 | * internal implementation details. |
||
38 | * |
||
39 | * For RDF output, this relies on the RdfBuilder class. |
||
40 | * |
||
41 | * @license GPL-2.0-or-later |
||
42 | * @author Daniel Kinzler |
||
43 | * @author Thomas Pellissier Tanon |
||
44 | * @author Anja Jentzsch < [email protected] > |
||
45 | */ |
||
46 | class EntityDataSerializationService { |
||
47 | |||
48 | /** |
||
49 | * @var EntityLookup|null |
||
50 | */ |
||
51 | private $entityLookup = null; |
||
52 | |||
53 | /** |
||
54 | * @var EntityTitleLookup |
||
55 | */ |
||
56 | private $entityTitleLookup; |
||
57 | |||
58 | /** |
||
59 | * @var SerializerFactory |
||
60 | */ |
||
61 | private $serializerFactory; |
||
62 | |||
63 | /** |
||
64 | * @var Serializer |
||
65 | */ |
||
66 | private $entitySerializer; |
||
67 | |||
68 | /** |
||
69 | * @var PropertyDataTypeLookup |
||
70 | */ |
||
71 | private $propertyLookup; |
||
72 | |||
73 | /** |
||
74 | * @var EntityDataFormatProvider |
||
75 | */ |
||
76 | private $entityDataFormatProvider; |
||
77 | |||
78 | /** |
||
79 | * @var RdfWriterFactory |
||
80 | */ |
||
81 | private $rdfWriterFactory; |
||
82 | |||
83 | /** |
||
84 | * @var SiteLookup |
||
85 | */ |
||
86 | private $siteLookup; |
||
87 | |||
88 | /** |
||
89 | * @var RdfVocabulary |
||
90 | */ |
||
91 | private $rdfVocabulary; |
||
92 | |||
93 | /** |
||
94 | * @var ValueSnakRdfBuilderFactory |
||
95 | */ |
||
96 | private $valueSnakRdfBuilderFactory; |
||
97 | |||
98 | /** |
||
99 | * @var EntityRdfBuilderFactory |
||
100 | */ |
||
101 | private $entityRdfBuilderFactory; |
||
102 | |||
103 | public function __construct( |
||
104 | EntityLookup $entityLookup, |
||
105 | EntityTitleLookup $entityTitleLookup, |
||
106 | PropertyDataTypeLookup $propertyLookup, |
||
107 | ValueSnakRdfBuilderFactory $valueSnakRdfBuilderFactory, |
||
108 | EntityRdfBuilderFactory $entityRdfBuilderFactory, |
||
109 | EntityDataFormatProvider $entityDataFormatProvider, |
||
110 | SerializerFactory $serializerFactory, |
||
111 | Serializer $entitySerializer, |
||
112 | SiteLookup $siteLookup, |
||
113 | RdfVocabulary $rdfVocabulary |
||
114 | ) { |
||
115 | $this->entityLookup = $entityLookup; |
||
116 | $this->entityTitleLookup = $entityTitleLookup; |
||
117 | $this->propertyLookup = $propertyLookup; |
||
118 | $this->valueSnakRdfBuilderFactory = $valueSnakRdfBuilderFactory; |
||
119 | $this->entityRdfBuilderFactory = $entityRdfBuilderFactory; |
||
120 | $this->entityDataFormatProvider = $entityDataFormatProvider; |
||
121 | $this->serializerFactory = $serializerFactory; |
||
122 | $this->entitySerializer = $entitySerializer; |
||
123 | $this->siteLookup = $siteLookup; |
||
124 | $this->rdfVocabulary = $rdfVocabulary; |
||
125 | |||
126 | $this->rdfWriterFactory = new RdfWriterFactory(); |
||
127 | } |
||
128 | |||
129 | /** |
||
130 | * Output entity data. |
||
131 | * |
||
132 | * @param string $format The name (mime type of file extension) of the format to use |
||
133 | * @param EntityRevision $entityRevision The entity |
||
134 | * @param RedirectRevision|null $followedRedirect The redirect that led to the entity, or null |
||
135 | * @param EntityId[] $incomingRedirects Incoming redirects to include in the output |
||
136 | * @param string|null $flavor The type of the output provided by serializer |
||
137 | * |
||
138 | * @return array tuple of ( $data, $contentType ) |
||
139 | * @throws MWException |
||
140 | */ |
||
141 | public function getSerializedData( |
||
142 | $format, |
||
143 | EntityRevision $entityRevision, |
||
144 | RedirectRevision $followedRedirect = null, |
||
145 | array $incomingRedirects = [], |
||
146 | $flavor = null |
||
147 | ) { |
||
148 | |||
149 | $formatName = $this->entityDataFormatProvider->getFormatName( $format ); |
||
150 | |||
151 | if ( $formatName === null ) { |
||
152 | throw new MWException( "Unsupported format: $format" ); |
||
153 | } |
||
154 | |||
155 | $serializer = $this->createApiSerializer( $formatName ); |
||
156 | |||
157 | if ( $serializer !== null ) { |
||
158 | $data = $this->getApiSerialization( $entityRevision, $serializer ); |
||
159 | $contentType = $serializer->getIsHtml() ? 'text/html' : $serializer->getMimeType(); |
||
160 | } else { |
||
161 | $rdfBuilder = $this->createRdfBuilder( $formatName, $flavor ); |
||
162 | |||
163 | if ( $rdfBuilder === null ) { |
||
164 | throw new MWException( "Could not create serializer for $formatName" ); |
||
165 | } |
||
166 | |||
167 | $data = $this->rdfSerialize( $entityRevision, $followedRedirect, $incomingRedirects, $rdfBuilder, $flavor ); |
||
168 | |||
169 | $mimeTypes = $this->rdfWriterFactory->getMimeTypes( $formatName ); |
||
170 | $contentType = reset( $mimeTypes ); |
||
171 | } |
||
172 | |||
173 | return [ $data, $contentType ]; |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | * @param EntityRevision $entityRevision |
||
178 | * @param RedirectRevision|null $followedRedirect a redirect leading to the entity for use in the output |
||
179 | * @param EntityId[] $incomingRedirects Incoming redirects to include in the output |
||
180 | * @param RdfBuilder $rdfBuilder |
||
181 | * @param string|null $flavor The type of the output provided by serializer |
||
182 | * |
||
183 | * @return string RDF |
||
184 | */ |
||
185 | private function rdfSerialize( |
||
186 | EntityRevision $entityRevision, |
||
187 | ?RedirectRevision $followedRedirect, |
||
188 | array $incomingRedirects, |
||
189 | RdfBuilder $rdfBuilder, |
||
190 | $flavor = null |
||
191 | ) { |
||
192 | $rdfBuilder->startDocument(); |
||
193 | $redir = null; |
||
194 | |||
195 | if ( $followedRedirect ) { |
||
196 | $redir = $followedRedirect->getRedirect(); |
||
197 | $rdfBuilder->addEntityRedirect( $redir->getEntityId(), $redir->getTargetId() ); |
||
198 | |||
199 | if ( $followedRedirect->getRevisionId() > 0 ) { |
||
200 | $rdfBuilder->addEntityRevisionInfo( |
||
201 | $redir->getEntityId(), |
||
202 | $followedRedirect->getRevisionId(), |
||
203 | $followedRedirect->getTimestamp() |
||
204 | ); |
||
205 | } |
||
206 | } |
||
207 | |||
208 | if ( $followedRedirect && $flavor === 'dump' ) { |
||
209 | // For redirects, don't output the target entity data if the "dump" flavor is requested. |
||
210 | // @todo: In this case, avoid loading the Entity all together. |
||
211 | // However we want to output the revisions for redirects |
||
212 | } else { |
||
213 | $rdfBuilder->addEntityRevisionInfo( |
||
214 | $entityRevision->getEntity()->getId(), |
||
215 | $entityRevision->getRevisionId(), |
||
216 | $entityRevision->getTimestamp() |
||
217 | ); |
||
218 | |||
219 | $rdfBuilder->addEntityPageProps( $entityRevision->getEntity()->getId() ); |
||
0 ignored issues
–
show
|
|||
220 | |||
221 | $rdfBuilder->addEntity( $entityRevision->getEntity() ); |
||
222 | $rdfBuilder->resolveMentionedEntities( $this->entityLookup ); |
||
223 | } |
||
224 | |||
225 | if ( $flavor !== 'dump' ) { |
||
226 | // For $flavor === 'dump' we don't need to output incoming redirects. |
||
227 | |||
228 | $targetId = $entityRevision->getEntity()->getId(); |
||
229 | $this->addIncomingRedirects( $targetId, $redir, $incomingRedirects, $rdfBuilder ); |
||
230 | } |
||
231 | |||
232 | $rdfBuilder->finishDocument(); |
||
233 | |||
234 | return $rdfBuilder->getRDF(); |
||
235 | } |
||
236 | |||
237 | /** |
||
238 | * @param EntityId $targetId |
||
239 | * @param EntityRedirect|null $followedRedirect The followed redirect, will be omitted from the |
||
240 | * output. |
||
241 | * @param EntityId[] $incomingRedirects |
||
242 | * @param RdfBuilder $rdfBuilder |
||
243 | */ |
||
244 | private function addIncomingRedirects( |
||
245 | EntityId $targetId, |
||
246 | ?EntityRedirect $followedRedirect, |
||
247 | array $incomingRedirects, |
||
248 | RdfBuilder $rdfBuilder |
||
249 | ) { |
||
250 | foreach ( $incomingRedirects as $rId ) { |
||
251 | // don't add the followed redirect again |
||
252 | if ( !$followedRedirect || !$followedRedirect->getEntityId()->equals( $rId ) ) { |
||
253 | $rdfBuilder->addEntityRedirect( $rId, $targetId ); |
||
254 | } |
||
255 | } |
||
256 | } |
||
257 | |||
258 | /** |
||
259 | * Returns an ApiMain module that acts as a context for the formatting and serialization. |
||
260 | * |
||
261 | * @param string $format The desired output format, as a format name that ApiBase understands. |
||
262 | * |
||
263 | * @return ApiMain |
||
264 | */ |
||
265 | private function newApiMain( $format ) { |
||
266 | // Fake request params to ApiMain, with forced format parameters. |
||
267 | // We can override additional parameters here, as needed. |
||
268 | $params = [ |
||
269 | 'format' => $format, |
||
270 | ]; |
||
271 | |||
272 | $context = new DerivativeContext( RequestContext::getMain() ); //XXX: ugly |
||
273 | |||
274 | $req = new DerivativeRequest( $context->getRequest(), $params ); |
||
275 | $context->setRequest( $req ); |
||
276 | |||
277 | $api = new ApiMain( $context ); |
||
278 | return $api; |
||
279 | } |
||
280 | |||
281 | /** |
||
282 | * Creates an API printer that can generate the given output format. |
||
283 | * |
||
284 | * @param string $formatName The desired serialization format, |
||
285 | * as a format name understood by ApiBase or RdfWriterFactory. |
||
286 | * |
||
287 | * @return ApiFormatBase|null A suitable result printer, or null |
||
288 | * if the given format is not supported by the API. |
||
289 | */ |
||
290 | private function createApiSerializer( $formatName ) { |
||
291 | //MediaWiki formats |
||
292 | $api = $this->newApiMain( $formatName ); |
||
293 | $formatNames = $api->getModuleManager()->getNames( 'format' ); |
||
294 | if ( $formatName !== null && in_array( $formatName, $formatNames ) ) { |
||
295 | return $api->createPrinterByName( $formatName ); |
||
296 | } |
||
297 | |||
298 | return null; |
||
299 | } |
||
300 | |||
301 | /** |
||
302 | * Get the producer setting for current data format |
||
303 | * |
||
304 | * @param string|null $flavorName |
||
305 | * |
||
306 | * @return int |
||
307 | * @throws MWException |
||
308 | */ |
||
309 | private function getFlavor( $flavorName ) { |
||
310 | switch ( $flavorName ) { |
||
311 | case 'simple': |
||
312 | return RdfProducer::PRODUCE_TRUTHY_STATEMENTS |
||
313 | | RdfProducer::PRODUCE_SITELINKS |
||
314 | | RdfProducer::PRODUCE_VERSION_INFO; |
||
315 | case 'dump': |
||
316 | return RdfProducer::PRODUCE_ALL_STATEMENTS |
||
317 | | RdfProducer::PRODUCE_TRUTHY_STATEMENTS |
||
318 | | RdfProducer::PRODUCE_QUALIFIERS |
||
319 | | RdfProducer::PRODUCE_REFERENCES |
||
320 | | RdfProducer::PRODUCE_SITELINKS |
||
321 | | RdfProducer::PRODUCE_FULL_VALUES |
||
322 | | RdfProducer::PRODUCE_PAGE_PROPS |
||
323 | | RdfProducer::PRODUCE_NORMALIZED_VALUES |
||
324 | | RdfProducer::PRODUCE_VERSION_INFO; |
||
325 | case 'long': |
||
326 | return RdfProducer::PRODUCE_ALL_STATEMENTS |
||
327 | | RdfProducer::PRODUCE_QUALIFIERS |
||
328 | | RdfProducer::PRODUCE_REFERENCES |
||
329 | | RdfProducer::PRODUCE_SITELINKS |
||
330 | | RdfProducer::PRODUCE_VERSION_INFO; |
||
331 | case 'full': |
||
332 | case null: |
||
333 | return RdfProducer::PRODUCE_ALL; |
||
334 | } |
||
335 | |||
336 | throw new MWException( "Unsupported flavor: $flavorName" ); |
||
337 | } |
||
338 | |||
339 | /** |
||
340 | * Creates an Rdf Serializer that can generate the given output format. |
||
341 | * |
||
342 | * @param string $format The desired serialization format, as a format name understood by ApiBase or RdfWriterFactory |
||
343 | * @param string|null $flavorName Flavor name (used for RDF output) |
||
344 | * |
||
345 | * @return RdfBuilder|null A suitable result printer, or null |
||
346 | * if the given format is not supported. |
||
347 | */ |
||
348 | private function createRdfBuilder( $format, $flavorName = null ) { |
||
349 | $canonicalFormat = $this->rdfWriterFactory->getFormatName( $format ); |
||
350 | |||
351 | if ( !$canonicalFormat ) { |
||
352 | return null; |
||
353 | } |
||
354 | |||
355 | $rdfWriter = $this->rdfWriterFactory->getWriter( $format ); |
||
356 | |||
357 | $rdfBuilder = new RdfBuilder( |
||
358 | $this->rdfVocabulary, |
||
359 | $this->valueSnakRdfBuilderFactory, |
||
360 | $this->propertyLookup, |
||
361 | $this->entityRdfBuilderFactory, |
||
362 | $this->getFlavor( $flavorName ), |
||
363 | $rdfWriter, |
||
364 | new HashDedupeBag(), |
||
365 | $this->entityTitleLookup |
||
366 | ); |
||
367 | |||
368 | $rdfBuilder->setPageProps( PageProps::getInstance() ); |
||
369 | |||
370 | return $rdfBuilder; |
||
371 | } |
||
372 | |||
373 | /** |
||
374 | * Pushes the given $entity into the ApiResult held by the ApiMain module |
||
375 | * returned by newApiMain(). Calling $printer->execute() later will output this |
||
376 | * result, if $printer was generated from that same ApiMain module, as |
||
377 | * createApiPrinter() does. |
||
378 | * |
||
379 | * @param EntityRevision $entityRevision The entity to convert ot an ApiResult |
||
380 | * @param ApiFormatBase $printer The output printer that will be used for serialization. |
||
381 | * Used to provide context for generating the ApiResult, and may also be manipulated |
||
382 | * to fine-tune the output. |
||
383 | * |
||
384 | * @return ApiResult |
||
385 | */ |
||
386 | private function generateApiResult( EntityRevision $entityRevision, ApiFormatBase $printer ) { |
||
387 | $res = $printer->getResult(); |
||
388 | |||
389 | // Make sure result is empty. May still be full if this |
||
390 | // function gets called multiple times during testing, etc. |
||
391 | $res->reset(); |
||
392 | |||
393 | $resultBuilder = new ResultBuilder( |
||
394 | $res, |
||
395 | $this->entityTitleLookup, |
||
396 | $this->serializerFactory, |
||
397 | $this->entitySerializer, |
||
398 | $this->siteLookup, |
||
399 | $this->propertyLookup, |
||
400 | true // include metadata for the API result printer |
||
401 | ); |
||
402 | $resultBuilder->addEntityRevision( null, $entityRevision ); |
||
403 | |||
404 | return $res; |
||
405 | } |
||
406 | |||
407 | /** |
||
408 | * Serialize the entity data using the provided format. |
||
409 | * |
||
410 | * Note that we are using the API's serialization facility to ensure a consistent external |
||
411 | * representation of data entities. Using the ContentHandler to serialize the entity would |
||
412 | * expose internal implementation details. |
||
413 | * |
||
414 | * @param EntityRevision $entityRevision the entity to output. |
||
415 | * @param ApiFormatBase $printer the printer to use to generate the output |
||
416 | * |
||
417 | * @return string the serialized data |
||
418 | */ |
||
419 | private function getApiSerialization( |
||
420 | EntityRevision $entityRevision, |
||
421 | ApiFormatBase $printer |
||
422 | ) { |
||
423 | // NOTE: The way the ApiResult is provided to $printer is somewhat |
||
424 | // counter-intuitive. Basically, the relevant ApiResult object |
||
425 | // is owned by the ApiMain module provided by newApiMain(). |
||
426 | |||
427 | // Pushes $entity into the ApiResult held by the ApiMain module |
||
428 | // TODO: where to put the followed redirect? |
||
429 | // TODO: where to put the incoming redirects? See T98039 |
||
430 | $this->generateApiResult( $entityRevision, $printer ); |
||
431 | |||
432 | $printer->initPrinter(); |
||
433 | |||
434 | // Outputs the ApiResult held by the ApiMain module, which is hopefully the one we added the entity data to. |
||
435 | //NOTE: this can and will mess with the HTTP response! |
||
436 | $printer->execute(); |
||
437 | $data = $printer->getBuffer(); |
||
438 | |||
439 | $printer->disable(); |
||
440 | |||
441 | return $data; |
||
442 | } |
||
443 | |||
444 | } |
||
445 |
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: