Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
52.27% |
23 / 44 |
|
66.67% |
2 / 3 |
CRAP | |
0.00% |
0 / 1 |
BadFileLookup | |
53.49% |
23 / 43 |
|
66.67% |
2 / 3 |
37.64 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
isBadFile | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
6 | |||
buildBadFilesList | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
72 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Page\File; |
4 | |
5 | use BagOStuff; |
6 | use MediaWiki\HookContainer\HookContainer; |
7 | use MediaWiki\HookContainer\HookRunner; |
8 | use MediaWiki\Linker\LinkTarget; |
9 | use MediaWiki\Title\MalformedTitleException; |
10 | use MediaWiki\Title\TitleParser; |
11 | use RepoGroup; |
12 | |
13 | class BadFileLookup { |
14 | /** @var callable Returns contents of bad file list (see comment for isBadFile()) */ |
15 | private $listCallback; |
16 | |
17 | /** @var BagOStuff Cache of parsed bad image list */ |
18 | private $cache; |
19 | |
20 | /** @var RepoGroup */ |
21 | private $repoGroup; |
22 | |
23 | /** @var TitleParser */ |
24 | private $titleParser; |
25 | |
26 | /** @var array<string,array<int,array<string,true>>>|null Parsed bad file list */ |
27 | private $badFiles; |
28 | |
29 | /** @var HookRunner */ |
30 | private $hookRunner; |
31 | |
32 | /** |
33 | * Do not call directly. Use MediaWikiServices. |
34 | * |
35 | * @param callable $listCallback Callback that returns wikitext of a bad file list |
36 | * @param BagOStuff $cache For caching parsed versions of the bad file list |
37 | * @param RepoGroup $repoGroup |
38 | * @param TitleParser $titleParser |
39 | * @param HookContainer $hookContainer |
40 | */ |
41 | public function __construct( |
42 | callable $listCallback, |
43 | BagOStuff $cache, |
44 | RepoGroup $repoGroup, |
45 | TitleParser $titleParser, |
46 | HookContainer $hookContainer |
47 | ) { |
48 | $this->listCallback = $listCallback; |
49 | $this->cache = $cache; |
50 | $this->repoGroup = $repoGroup; |
51 | $this->titleParser = $titleParser; |
52 | $this->hookRunner = new HookRunner( $hookContainer ); |
53 | } |
54 | |
55 | /** |
56 | * Determine if a file exists on the 'bad image list'. |
57 | * |
58 | * The format of MediaWiki:Bad_image_list is as follows: |
59 | * * Only list items (lines starting with "*") are considered |
60 | * * The first link on a line must be a link to a bad file |
61 | * * Any subsequent links on the same line are considered to be exceptions, |
62 | * i.e. articles where the file may occur inline. |
63 | * |
64 | * @param string $name The file name to check |
65 | * @param LinkTarget|null $contextTitle The page on which the file occurs, if known |
66 | * @return bool |
67 | */ |
68 | public function isBadFile( $name, LinkTarget $contextTitle = null ) { |
69 | // Handle redirects; callers almost always hit RepoGroup::findFile() anyway, |
70 | // so just use that method because it has a fast process cache. |
71 | $file = $this->repoGroup->findFile( $name ); |
72 | // XXX If we don't find the file we also don't replace spaces by underscores or otherwise |
73 | // validate or normalize the title, is this right? |
74 | if ( $file ) { |
75 | $name = $file->getTitle()->getDBkey(); |
76 | } |
77 | |
78 | // Run the extension hook |
79 | $bad = false; |
80 | if ( !$this->hookRunner->onBadImage( $name, $bad ) ) { |
81 | return (bool)$bad; |
82 | } |
83 | |
84 | if ( $this->badFiles === null ) { |
85 | $list = ( $this->listCallback )(); |
86 | $key = $this->cache->makeKey( 'bad-image-list', sha1( $list ) ); |
87 | $this->badFiles = $this->cache->getWithSetCallback( |
88 | $key, |
89 | BagOStuff::TTL_DAY, |
90 | function () use ( $list ) { |
91 | return $this->buildBadFilesList( $list ); |
92 | } |
93 | ); |
94 | } |
95 | |
96 | return isset( $this->badFiles[$name] ) && ( !$contextTitle || |
97 | !isset( $this->badFiles[$name][$contextTitle->getNamespace()][$contextTitle->getDBkey()] ) ); |
98 | } |
99 | |
100 | /** |
101 | * @param string $list |
102 | * @return array<string,array<int,array<string,true>>> |
103 | */ |
104 | private function buildBadFilesList( string $list ): array { |
105 | $ret = []; |
106 | $lines = explode( "\n", $list ); |
107 | foreach ( $lines as $line ) { |
108 | // List items only |
109 | if ( substr( $line, 0, 1 ) !== '*' ) { |
110 | continue; |
111 | } |
112 | |
113 | // Find all links |
114 | $m = []; |
115 | // XXX What is the ':?' doing in the regex? Why not let the TitleParser strip it? |
116 | if ( !preg_match_all( '/\[\[:?(.*?)\]\]/', $line, $m ) ) { |
117 | continue; |
118 | } |
119 | |
120 | $fileDBkey = null; |
121 | $exceptions = []; |
122 | foreach ( $m[1] as $i => $titleText ) { |
123 | try { |
124 | $title = $this->titleParser->parseTitle( $titleText ); |
125 | } catch ( MalformedTitleException $e ) { |
126 | continue; |
127 | } |
128 | if ( $i == 0 ) { |
129 | $fileDBkey = $title->getDBkey(); |
130 | } else { |
131 | $exceptions[$title->getNamespace()][$title->getDBkey()] = true; |
132 | } |
133 | } |
134 | |
135 | if ( $fileDBkey !== null ) { |
136 | $ret[$fileDBkey] = $exceptions; |
137 | } |
138 | } |
139 | return $ret; |
140 | } |
141 | } |
142 | |
143 | /** @deprecated class alias since 1.40 */ |
144 | class_alias( BadFileLookup::class, 'MediaWiki\\BadFileLookup' ); |