Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 99
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQueryDuplicateFiles
0.00% covered (danger)
0.00%
0 / 98
0.00% covered (danger)
0.00%
0 / 8
930
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCacheMode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 executeGenerator
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 run
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 1
552
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getHelpUrls
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Copyright © 2008 Roan Kattouw <roan.kattouw@gmail.com>
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace MediaWiki\Api;
10
11use MediaWiki\FileRepo\File\File;
12use MediaWiki\FileRepo\RepoGroup;
13use Wikimedia\ParamValidator\ParamValidator;
14use Wikimedia\ParamValidator\TypeDef\IntegerDef;
15
16/**
17 * A query module to list duplicates of the given file(s)
18 *
19 * @ingroup API
20 */
21class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
22
23    private RepoGroup $repoGroup;
24
25    public function __construct(
26        ApiQuery $query,
27        string $moduleName,
28        RepoGroup $repoGroup
29    ) {
30        parent::__construct( $query, $moduleName, 'df' );
31        $this->repoGroup = $repoGroup;
32    }
33
34    public function execute() {
35        $this->run();
36    }
37
38    /** @inheritDoc */
39    public function getCacheMode( $params ) {
40        return 'public';
41    }
42
43    /** @inheritDoc */
44    public function executeGenerator( $resultPageSet ) {
45        $this->run( $resultPageSet );
46    }
47
48    /**
49     * @param ApiPageSet|null $resultPageSet
50     */
51    private function run( $resultPageSet = null ) {
52        $params = $this->extractRequestParams();
53        $namespaces = $this->getPageSet()->getGoodAndMissingTitlesByNamespace();
54        if ( empty( $namespaces[NS_FILE] ) ) {
55            return;
56        }
57        $images = $namespaces[NS_FILE];
58
59        if ( $params['dir'] == 'descending' ) {
60            $images = array_reverse( $images );
61        }
62
63        $skipUntilThisDup = false;
64        if ( isset( $params['continue'] ) ) {
65            $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'string', 'string' ] );
66            $fromImage = $cont[0];
67            $skipUntilThisDup = $cont[1];
68            // Filter out any images before $fromImage
69            foreach ( $images as $image => $pageId ) {
70                if ( $image < $fromImage ) {
71                    unset( $images[$image] );
72                } else {
73                    break;
74                }
75            }
76        }
77
78        $filesToFind = array_keys( $images );
79        if ( $params['localonly'] ) {
80            $files = $this->repoGroup->getLocalRepo()->findFiles( $filesToFind );
81        } else {
82            $files = $this->repoGroup->findFiles( $filesToFind );
83        }
84
85        $fit = true;
86        $count = 0;
87        $titles = [];
88
89        $sha1s = [];
90        foreach ( $files as $file ) {
91            /** @var File $file */
92            $sha1s[$file->getName()] = $file->getSha1();
93        }
94
95        // find all files with the hashes, result format is:
96        // [ hash => [ dup1, dup2 ], hash1 => ... ]
97        $filesToFindBySha1s = array_unique( array_values( $sha1s ) );
98        if ( $params['localonly'] ) {
99            $filesBySha1s = $this->repoGroup->getLocalRepo()->findBySha1s( $filesToFindBySha1s );
100        } else {
101            $filesBySha1s = $this->repoGroup->findBySha1s( $filesToFindBySha1s );
102        }
103
104        // iterate over $images to handle continue param correct
105        foreach ( $images as $image => $pageId ) {
106            if ( !isset( $sha1s[$image] ) ) {
107                continue; // file does not exist
108            }
109            $sha1 = $sha1s[$image];
110            $dupFiles = $filesBySha1s[$sha1];
111            if ( $params['dir'] == 'descending' ) {
112                $dupFiles = array_reverse( $dupFiles );
113            }
114            /** @var File $dupFile */
115            foreach ( $dupFiles as $dupFile ) {
116                $dupName = $dupFile->getName();
117                if ( $image == $dupName && $dupFile->isLocal() ) {
118                    continue; // ignore the local file itself
119                }
120                if ( $skipUntilThisDup !== false && $dupName < $skipUntilThisDup ) {
121                    continue; // skip to pos after the image from continue param
122                }
123                $skipUntilThisDup = false;
124                if ( ++$count > $params['limit'] ) {
125                    $fit = false; // break outer loop
126                    // We're one over limit which shows that
127                    // there are additional images to be had. Stop here...
128                    $this->setContinueEnumParameter( 'continue', $image . '|' . $dupName );
129                    break;
130                }
131                if ( $resultPageSet !== null ) {
132                    $titles[] = $dupFile->getTitle();
133                } else {
134                    $r = [
135                        'name' => $dupName,
136                        'timestamp' => wfTimestamp( TS_ISO_8601, $dupFile->getTimestamp() ),
137                        'shared' => !$dupFile->isLocal(),
138                    ];
139                    $uploader = $dupFile->getUploader( File::FOR_PUBLIC );
140                    if ( $uploader ) {
141                        $r['user'] = $uploader->getName();
142                    }
143                    $fit = $this->addPageSubItem( $pageId, $r );
144                    if ( !$fit ) {
145                        $this->setContinueEnumParameter( 'continue', $image . '|' . $dupName );
146                        break;
147                    }
148                }
149            }
150            if ( !$fit ) {
151                break;
152            }
153        }
154        if ( $resultPageSet !== null ) {
155            $resultPageSet->populateFromTitles( $titles );
156        }
157    }
158
159    /** @inheritDoc */
160    public function getAllowedParams() {
161        return [
162            'limit' => [
163                ParamValidator::PARAM_DEFAULT => 10,
164                ParamValidator::PARAM_TYPE => 'limit',
165                IntegerDef::PARAM_MIN => 1,
166                IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
167                IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
168            ],
169            'continue' => [
170                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
171            ],
172            'dir' => [
173                ParamValidator::PARAM_DEFAULT => 'ascending',
174                ParamValidator::PARAM_TYPE => [
175                    'ascending',
176                    'descending'
177                ]
178            ],
179            'localonly' => false,
180        ];
181    }
182
183    /** @inheritDoc */
184    protected function getExamplesMessages() {
185        return [
186            'action=query&titles=File:Albert_Einstein_Head.jpg&prop=duplicatefiles'
187                => 'apihelp-query+duplicatefiles-example-simple',
188            'action=query&generator=allimages&prop=duplicatefiles'
189                => 'apihelp-query+duplicatefiles-example-generated',
190        ];
191    }
192
193    /** @inheritDoc */
194    public function getHelpUrls() {
195        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Duplicatefiles';
196    }
197}
198
199/** @deprecated class alias since 1.43 */
200class_alias( ApiQueryDuplicateFiles::class, 'ApiQueryDuplicateFiles' );