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