Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
66.92% covered (warning)
66.92%
87 / 130
46.15% covered (danger)
46.15%
6 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialMIMESearch
67.44% covered (warning)
67.44%
87 / 129
46.15% covered (danger)
46.15%
6 / 13
41.26
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 isExpensive
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isSyndicated
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isCacheable
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 linkParameters
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getQueryInfo
97.37% covered (success)
97.37%
37 / 38
0.00% covered (danger)
0.00%
0 / 1
3
 getOrderFields
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPageHeader
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
1
 getSuggestionsForTypes
54.84% covered (warning)
54.84%
17 / 31
0.00% covered (danger)
0.00%
0 / 1
5.47
 execute
66.67% covered (warning)
66.67%
8 / 12
0.00% covered (danger)
0.00%
0 / 1
7.33
 formatResult
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
2
 preprocessResults
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Specials;
8
9use MediaWiki\FileRepo\File\File;
10use MediaWiki\FileRepo\File\FileSelectQueryBuilder;
11use MediaWiki\HTMLForm\HTMLForm;
12use MediaWiki\Language\ILanguageConverter;
13use MediaWiki\Languages\LanguageConverterFactory;
14use MediaWiki\Linker\Linker;
15use MediaWiki\MainConfigNames;
16use MediaWiki\MediaWikiServices;
17use MediaWiki\Page\LinkBatchFactory;
18use MediaWiki\Skin\Skin;
19use MediaWiki\SpecialPage\QueryPage;
20use MediaWiki\Title\Title;
21use stdClass;
22use Wikimedia\HtmlArmor\HtmlArmor;
23use Wikimedia\Rdbms\IConnectionProvider;
24
25/**
26 * Search the database for files of the requested MIME type, comparing this with the
27 * 'img_major_mime' and 'img_minor_mime' fields in the image table.
28 *
29 * @ingroup SpecialPage
30 * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
31 */
32class SpecialMIMESearch extends QueryPage {
33    /** @var string */
34    protected $major;
35    /** @var string */
36    protected $minor;
37    /** @var string */
38    protected $mime;
39
40    private ILanguageConverter $languageConverter;
41
42    public function __construct(
43        IConnectionProvider $dbProvider,
44        LinkBatchFactory $linkBatchFactory,
45        LanguageConverterFactory $languageConverterFactory
46    ) {
47        parent::__construct( 'MIMEsearch' );
48        $this->setDatabaseProvider( $dbProvider );
49        $this->setLinkBatchFactory( $linkBatchFactory );
50        $this->languageConverter = $languageConverterFactory->getLanguageConverter( $this->getContentLanguage() );
51    }
52
53    /** @inheritDoc */
54    public function isExpensive() {
55        return false;
56    }
57
58    /** @inheritDoc */
59    public function isSyndicated() {
60        return false;
61    }
62
63    /** @inheritDoc */
64    public function isCacheable() {
65        return false;
66    }
67
68    /** @inheritDoc */
69    protected function linkParameters() {
70        return [ 'mime' => "{$this->major}/{$this->minor}" ];
71    }
72
73    /** @inheritDoc */
74    public function getQueryInfo() {
75        $minorType = [];
76        if ( $this->minor !== '*' ) {
77            // Allow wildcard searching
78            $minorType['img_minor_mime'] = $this->minor;
79        }
80        $fileQuery = FileSelectQueryBuilder::newForFile( $this->getDatabaseProvider()->getReplicaDatabase() )
81            ->getQueryInfo();
82        $qi = [
83            'tables' => $fileQuery['tables'],
84            'fields' => [
85                'namespace' => NS_FILE,
86                'title' => 'img_name',
87                // Still have a value field just in case,
88                // but it isn't actually used for sorting.
89                'value' => 'img_name',
90                'img_size',
91                'img_width',
92                'img_height',
93                'img_timestamp'
94            ],
95            'conds' => [
96                'img_major_mime' => $this->major,
97                // This is in order to trigger using
98                // the img_media_mime index in "range" mode.
99                // @todo how is order defined? use MimeAnalyzer::getMediaTypes?
100                'img_media_type' => [
101                    MEDIATYPE_BITMAP,
102                    MEDIATYPE_DRAWING,
103                    MEDIATYPE_AUDIO,
104                    MEDIATYPE_VIDEO,
105                    MEDIATYPE_MULTIMEDIA,
106                    MEDIATYPE_UNKNOWN,
107                    MEDIATYPE_OFFICE,
108                    MEDIATYPE_TEXT,
109                    MEDIATYPE_EXECUTABLE,
110                    MEDIATYPE_ARCHIVE,
111                    MEDIATYPE_3D,
112                ],
113            ] + $minorType,
114            'join_conds' => $fileQuery['join_conds'],
115        ];
116
117        if ( isset( $fileQuery['fields']['img_user_text'] ) ) {
118            $qi['fields']['img_user_text'] = $fileQuery['fields']['img_user_text'];
119        } else {
120            // file read new
121            $qi['fields'][] = 'img_user_text';
122        }
123
124        return $qi;
125    }
126
127    /**
128     * The index is on (img_media_type, img_major_mime, img_minor_mime)
129     * which unfortunately doesn't have img_name at the end for sorting.
130     * So tell db to sort it however it wishes (Its not super important
131     * that this report gives results in a logical order). As an additional
132     * note, mysql seems to by default order things by img_name ASC, which
133     * is what we ideally want, so everything works out fine anyhow.
134     * @return array
135     */
136    protected function getOrderFields() {
137        return [];
138    }
139
140    /**
141     * Generate and output the form
142     * @return string
143     */
144    protected function getPageHeader() {
145        $formDescriptor = [
146            'mime' => [
147                'type' => 'combobox',
148                'options' => $this->getSuggestionsForTypes(),
149                'name' => 'mime',
150                'label-message' => 'mimetype',
151                'required' => true,
152                'default' => $this->mime,
153            ],
154        ];
155
156        HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
157            ->setSubmitTextMsg( 'ilsubmit' )
158            ->setTitle( $this->getPageTitle() )
159            ->setMethod( 'get' )
160            ->prepareForm()
161            ->displayForm( false );
162        return '';
163    }
164
165    protected function getSuggestionsForTypes(): array {
166        $queryBuilder = $this->getDatabaseProvider()->getReplicaDatabase()->newSelectQueryBuilder();
167        $migrationStage = MediaWikiServices::getInstance()->getMainConfig()->get(
168            MainConfigNames::FileSchemaMigrationStage
169        );
170        if ( $migrationStage & SCHEMA_COMPAT_READ_OLD ) {
171            $queryBuilder
172                // We ignore img_media_type, but using it in the query is needed for MySQL to choose a
173                // sensible execution plan
174                ->select( [ 'img_media_type', 'img_major_mime', 'img_minor_mime' ] )
175                ->from( 'image' )
176                ->groupBy( [ 'img_media_type', 'img_major_mime', 'img_minor_mime' ] );
177        } else {
178            $queryBuilder->select(
179                [
180                    'img_media_type' => 'ft_media_type',
181                    'img_major_mime' => 'ft_major_mime',
182                    'img_minor_mime' => 'ft_minor_mime',
183                ]
184            )
185                ->from( 'filetypes' );
186        }
187
188        $result = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
189
190        $lastMajor = null;
191        $suggestions = [];
192        foreach ( $result as $row ) {
193            $major = $row->img_major_mime;
194            $minor = $row->img_minor_mime;
195            $suggestions[ "$major/$minor" ] = "$major/$minor";
196            if ( $lastMajor === $major ) {
197                // If there are at least two with the same major mime type, also include the wildcard
198                $suggestions[ "$major/*" ] = "$major/*";
199            }
200            $lastMajor = $major;
201        }
202        ksort( $suggestions );
203        return $suggestions;
204    }
205
206    /** @inheritDoc */
207    public function execute( $par ) {
208        $this->addHelpLink( 'Help:Managing_files' );
209        $this->mime = $par ?: $this->getRequest()->getText( 'mime' );
210        $this->mime = trim( $this->mime );
211        [ $this->major, $this->minor ] = File::splitMime( $this->mime );
212        $mimeAnalyzer = MediaWikiServices::getInstance()->getMimeAnalyzer();
213
214        if ( $this->major == '' || $this->minor == '' || $this->minor == 'unknown' ||
215            !$mimeAnalyzer->isValidMajorMimeType( $this->major )
216        ) {
217            $this->setHeaders();
218            $this->outputHeader();
219            $this->getPageHeader();
220            return;
221        }
222
223        parent::execute( $par );
224    }
225
226    /**
227     * @param Skin $skin
228     * @param stdClass $result Result row
229     * @return string
230     */
231    public function formatResult( $skin, $result ) {
232        $linkRenderer = $this->getLinkRenderer();
233        $nt = Title::makeTitle( $result->namespace, $result->title );
234
235        $text = $this->languageConverter->convertHtml( $nt->getText() );
236        $plink = $linkRenderer->makeLink(
237            Title::newFromText( $nt->getPrefixedText() ),
238            new HtmlArmor( $text )
239        );
240
241        $download = Linker::makeMediaLinkObj( $nt, $this->msg( 'download' )->escaped() );
242        $download = $this->msg( 'parentheses' )->rawParams( $download )->escaped();
243        $lang = $this->getLanguage();
244        $bytes = htmlspecialchars( $lang->formatSize( $result->img_size ) );
245        $dimensions = $this->msg( 'widthheight' )->numParams( $result->img_width,
246            $result->img_height )->escaped();
247        $user = $linkRenderer->makeLink(
248            Title::makeTitle( NS_USER, $result->img_user_text ),
249            $result->img_user_text
250        );
251
252        $time = $lang->userTimeAndDate( $result->img_timestamp, $this->getUser() );
253        $time = htmlspecialchars( $time );
254
255        return "$download $plink . . $dimensions . . $bytes . . $user . . $time";
256    }
257
258    /** @inheritDoc */
259    public function preprocessResults( $db, $res ) {
260        $this->executeLBFromResultWrapper( $res );
261    }
262
263    /** @inheritDoc */
264    protected function getGroupName() {
265        return 'media';
266    }
267}
268
269/**
270 * Retain the old class name for backwards compatibility.
271 * @deprecated since 1.41
272 */
273class_alias( SpecialMIMESearch::class, 'SpecialMIMESearch' );