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    public function getContentDataForSearchIndex( Content $content ): array {
191        $fieldsData = parent::getContentDataForSearchIndex( $content );
192        if ( $content->isRedirect() || !( $content instanceof MediaInfoContent ) ) {
193            return $fieldsData;
194        }
195        $entity = $content->getEntity();
196        $fields = $this->fieldDefinitions->getFields();
197
198        foreach ( $fields as $fieldName => $field ) {
199            $fieldsData[$fieldName] = $field->getFieldData( $entity );
200        }
201
202        // Labels data is normally indexed for prefix matching.
203        // We don't need that for MediaInfo files, so swap labels data into descriptions
204        // instead so as not to overburden the search index
205        if ( isset( $fieldsData[ LabelsField::NAME ] ) ) {
206            $fieldsData[DescriptionsField::NAME] = $fieldsData[LabelsField::NAME];
207            $fieldsData[LabelsField::NAME] = null;
208        } else {
209            $fieldsData[DescriptionsField::NAME] = null;
210        }
211
212        return $fieldsData;
213    }
214
215    /**
216     * Returns the Title of the page in which this MediaInfoId is a slot
217     *
218     * @param EntityId $id
219     * @return Title|null
220     */
221    public function getTitleForId( EntityId $id ) {
222        '@phan-var MediaInfoId $id';
223        $idString = $id->getSerialization();
224        if ( !isset( $this->titleForIdCache[$idString] ) ) {
225            // @phan-suppress-next-line PhanTypeMismatchProperty
226            $this->titleForIdCache[$idString] = $this->titleFactory->newFromID( $id->getNumericId() );
227        }
228        return $this->titleForIdCache[$idString];
229    }
230
231    /**
232     * Returns an array of Titles of page in which these MediaInfoIds are slots, indexed
233     * by the MediaInfoId serialization
234     *
235     * @param EntityId[] $ids
236     * @return Title[]
237     */
238    public function getTitlesForIds( array $ids ) {
239        '@phan-var MediaInfoId[] $ids';
240        Assert::parameterElementType( MediaInfoId::class, $ids, '$ids' );
241
242        $titles = [];
243        $uncachedNumericIds = [];
244        // get whatever cached ids we can first
245        /** @var MediaInfoId $id */
246        foreach ( $ids as $id ) {
247            $idString = $id->getSerialization();
248            if ( isset( $this->titleForIdCache[$idString] ) ) {
249                $titles[$idString] = $this->titleForIdCache[$idString];
250            } else {
251                $uncachedNumericIds[] = $id->getNumericId();
252            }
253        }
254
255        $unindexedTitles = $this->pageStore
256            ->newSelectQueryBuilder()
257            ->wherePageIds( $uncachedNumericIds )
258            ->caller( __METHOD__ )
259            ->fetchPageRecords();
260
261        /** @var PageRecord $pageIdentity */
262        foreach ( $unindexedTitles as $pageIdentity ) {
263            $title = $this->titleFactory->newFromPageIdentity( $pageIdentity );
264
265            $idString = $this->getIdForTitle( $title )->getSerialization();
266            $this->titleForIdCache[$idString] = $title;
267            $titles[$idString] = $title;
268        }
269        return $titles;
270    }
271
272    /**
273     * @param Title $target
274     * @return EntityId
275     */
276    public function getIdForTitle( Title $target ) {
277        $mediaInfoId = $this->idLookup->getEntityIdForTitle( $target );
278        if ( $mediaInfoId instanceof MediaInfoId ) {
279            return $mediaInfoId;
280        }
281
282        return parent::getIdForTitle( $target );
283    }
284
285}