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