Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
43.94% covered (danger)
43.94%
29 / 66
58.33% covered (warning)
58.33%
7 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
MediaInfoHandler
43.94% covered (danger)
43.94%
29 / 66
58.33% covered (warning)
58.33%
7 / 12
116.20
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
 makeEmptyEntity
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 newEntityContent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 makeEntityId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getEntityType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 showMissingEntity
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 canCreateWithCustomId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 allowAutomaticIds
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getContentDataForSearchIndex
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 getTitleForId
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getTitlesForIds
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 getIdForTitle
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace Wikibase\MediaInfo\Content;
4
5use MediaWiki\Content\Content;
6use MediaWiki\Context\IContextSource;
7use MediaWiki\Page\PageRecord;
8use MediaWiki\Page\PageStore;
9use MediaWiki\Title\Title;
10use MediaWiki\Title\TitleFactory;
11use Wikibase\DataModel\Entity\EntityId;
12use Wikibase\DataModel\Entity\EntityIdParser;
13use Wikibase\Lib\Store\EntityContentDataCodec;
14use Wikibase\Lib\Store\EntityIdLookup;
15use Wikibase\Lib\Store\NullEntityTermStoreWriter;
16use Wikibase\MediaInfo\DataModel\MediaInfo;
17use Wikibase\MediaInfo\DataModel\MediaInfoId;
18use Wikibase\MediaInfo\Services\FilePageLookup;
19use Wikibase\MediaInfo\Services\MediaInfoIdLookup;
20use Wikibase\Repo\Content\EntityHandler;
21use Wikibase\Repo\Content\EntityHolder;
22use Wikibase\Repo\Hooks\WikibaseTextForSearchIndexHook;
23use Wikibase\Repo\Search\Fields\FieldDefinitions;
24use Wikibase\Repo\Validators\EntityConstraintProvider;
25use Wikibase\Repo\Validators\ValidatorErrorLocalizer;
26use Wikibase\Search\Elastic\Fields\DescriptionsField;
27use Wikibase\Search\Elastic\Fields\LabelsField;
28use Wikimedia\Assert\Assert;
29
30/**
31 * @license GPL-2.0-or-later
32 * @author Bene* < benestar.wikimedia@gmail.com >
33 */
34class MediaInfoHandler extends EntityHandler {
35
36    /**
37     * @var MissingMediaInfoHandler
38     */
39    private $missingMediaInfoHandler;
40
41    /**
42     * @var EntityIdLookup
43     */
44    private $idLookup;
45
46    /**
47     * @var FilePageLookup
48     */
49    private $filePageLookup;
50
51    /**
52     * @var array<string,Title|null>
53     */
54    private $titleForIdCache = [];
55
56    /**
57     * @var PageStore
58     */
59    private $pageStore;
60
61    /**
62     * @var TitleFactory
63     */
64    private $titleFactory;
65
66    /**
67     * @var WikibaseTextForSearchIndexHook
68     */
69    private $hookRunner;
70
71    /**
72     * @param EntityContentDataCodec $contentCodec
73     * @param EntityConstraintProvider $constraintProvider
74     * @param ValidatorErrorLocalizer $errorLocalizer
75     * @param EntityIdParser $entityIdParser
76     * @param MissingMediaInfoHandler $missingMediaInfoHandler
77     * @param MediaInfoIdLookup $idLookup
78     * @param FilePageLookup $filePageLookup
79     * @param FieldDefinitions $mediaInfoFieldDefinitions
80     * @param PageStore $pageStore
81     * @param TitleFactory $titleFactory
82     * @param WikibaseTextForSearchIndexHook $hookRunner
83     * @param callable|null $legacyExportFormatDetector
84     */
85    public function __construct(
86        EntityContentDataCodec $contentCodec,
87        EntityConstraintProvider $constraintProvider,
88        ValidatorErrorLocalizer $errorLocalizer,
89        EntityIdParser $entityIdParser,
90        MissingMediaInfoHandler $missingMediaInfoHandler,
91        MediaInfoIdLookup $idLookup,
92        FilePageLookup $filePageLookup,
93        FieldDefinitions $mediaInfoFieldDefinitions,
94        PageStore $pageStore,
95        TitleFactory $titleFactory,
96        WikibaseTextForSearchIndexHook $hookRunner,
97        $legacyExportFormatDetector = null
98    ) {
99        parent::__construct(
100            MediaInfoContent::CONTENT_MODEL_ID,
101            new NullEntityTermStoreWriter(),
102            $contentCodec,
103            $constraintProvider,
104            $errorLocalizer,
105            $entityIdParser,
106            $mediaInfoFieldDefinitions,
107            $legacyExportFormatDetector
108        );
109        $this->missingMediaInfoHandler = $missingMediaInfoHandler;
110        $this->idLookup = $idLookup;
111        $this->filePageLookup = $filePageLookup;
112        $this->pageStore = $pageStore;
113        $this->titleFactory = $titleFactory;
114        $this->hookRunner = $hookRunner;
115    }
116
117    /**
118     * @return MediaInfo
119     */
120    public function makeEmptyEntity() {
121        return new MediaInfo();
122    }
123
124    /**
125     * @see EntityHandler::newEntityContent
126     *
127     * @param EntityHolder|null $entityHolder
128     *
129     * @return MediaInfoContent
130     */
131    public function newEntityContent( ?EntityHolder $entityHolder ) {
132        return new MediaInfoContent( $this->hookRunner, $entityHolder );
133    }
134
135    /**
136     * @param string $id
137     *
138     * @return MediaInfoId
139     */
140    public function makeEntityId( $id ) {
141        return new MediaInfoId( $id );
142    }
143
144    /**
145     * @return string
146     */
147    public function getEntityType() {
148        return MediaInfo::ENTITY_TYPE;
149    }
150
151    /**
152     * @see EntityHandler::showMissingEntity
153     *
154     * This is overwritten to show a dummy MediaInfo entity when appropriate.
155     *
156     * @see MissingMediaInfoHandler::showMissingMediaInfo
157     *
158     * @param Title $title
159     * @param IContextSource $context
160     */
161    public function showMissingEntity( Title $title, IContextSource $context ) {
162        $id = $this->missingMediaInfoHandler->getMediaInfoId( $title, $context );
163
164        if ( $id === null ) {
165            // No virtual MediaInfo for this title, fall back to the default behavior
166            // of displaying an error message.
167            parent::showMissingEntity( $title, $context );
168        } else {
169            // Show a virtual MediaInfo
170            $this->missingMediaInfoHandler->showVirtualMediaInfo( $id, $context );
171        }
172    }
173
174    /**
175     * @param EntityId $id
176     * @return bool
177     */
178    public function canCreateWithCustomId( EntityId $id ) {
179        return ( $id instanceof MediaInfoId )
180            && ( $this->filePageLookup->getFilePage( $id ) !== null );
181    }
182
183    /**
184     * @return bool
185     */
186    public function allowAutomaticIds() {
187        return false;
188    }
189
190    /**
191     * @param Content $content
192     * @return array
193     */
194    public function getContentDataForSearchIndex( Content $content ): array {
195        $fieldsData = parent::getContentDataForSearchIndex( $content );
196        if ( $content->isRedirect() || !( $content instanceof MediaInfoContent ) ) {
197            return $fieldsData;
198        }
199        $entity = $content->getEntity();
200        $fields = $this->fieldDefinitions->getFields();
201
202        foreach ( $fields as $fieldName => $field ) {
203            $fieldsData[$fieldName] = $field->getFieldData( $entity );
204        }
205
206        // Labels data is normally indexed for prefix matching.
207        // We don't need that for MediaInfo files, so swap labels data into descriptions
208        // instead so as not to overburden the search index
209        if ( isset( $fieldsData[ LabelsField::NAME ] ) ) {
210            $fieldsData[DescriptionsField::NAME] = $fieldsData[LabelsField::NAME];
211            $fieldsData[LabelsField::NAME] = null;
212        } else {
213            $fieldsData[DescriptionsField::NAME] = null;
214        }
215
216        return $fieldsData;
217    }
218
219    /**
220     * Returns the Title of the page in which this MediaInfoId is a slot
221     *
222     * @param EntityId $id
223     * @return Title|null
224     */
225    public function getTitleForId( EntityId $id ) {
226        '@phan-var MediaInfoId $id';
227        $idString = $id->getSerialization();
228        if ( !isset( $this->titleForIdCache[$idString] ) ) {
229            $this->titleForIdCache[$idString] = $this->titleFactory->newFromID( $id->getNumericId() );
230        }
231        return $this->titleForIdCache[$idString];
232    }
233
234    /**
235     * Returns an array of Titles of page in which these MediaInfoIds are slots, indexed
236     * by the MediaInfoId serialization
237     *
238     * @param EntityId[] $ids
239     * @return Title[]
240     */
241    public function getTitlesForIds( array $ids ) {
242        '@phan-var MediaInfoId[] $ids';
243        Assert::parameterElementType( MediaInfoId::class, $ids, '$ids' );
244
245        $titles = [];
246        $uncachedNumericIds = [];
247        // get whatever cached ids we can first
248        /** @var MediaInfoId $id */
249        foreach ( $ids as $id ) {
250            $idString = $id->getSerialization();
251            if ( isset( $this->titleForIdCache[$idString] ) ) {
252                $titles[$idString] = $this->titleForIdCache[$idString];
253            } else {
254                $uncachedNumericIds[] = $id->getNumericId();
255            }
256        }
257
258        $unindexedTitles = $this->pageStore
259            ->newSelectQueryBuilder()
260            ->wherePageIds( $uncachedNumericIds )
261            ->caller( __METHOD__ )
262            ->fetchPageRecords();
263
264        /** @var PageRecord $pageIdentity */
265        foreach ( $unindexedTitles as $pageIdentity ) {
266            $title = $this->titleFactory->castFromPageIdentity( $pageIdentity );
267
268            // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
269            $idString = $this->getIdForTitle( $title )->getSerialization();
270            $this->titleForIdCache[$idString] = $title;
271            $titles[$idString] = $title;
272        }
273        return $titles;
274    }
275
276    /**
277     * @param Title $target
278     * @return EntityId
279     */
280    public function getIdForTitle( Title $target ) {
281        $mediaInfoId = $this->idLookup->getEntityIdForTitle( $target );
282        if ( $mediaInfoId instanceof MediaInfoId ) {
283            return $mediaInfoId;
284        }
285
286        return parent::getIdForTitle( $target );
287    }
288
289}