Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 140 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
LintErrorsPager | |
0.00% |
0 / 140 |
|
0.00% |
0 / 8 |
2652 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
20 | |||
fillQueryBuilder | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
156 | |||
getQueryInfo | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
doBatchLookups | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
isFieldSortable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
formatValue | |
0.00% |
0 / 72 |
|
0.00% |
0 / 1 |
756 | |||
getDefaultSort | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getFieldNames | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\Linter; |
22 | |
23 | use IContextSource; |
24 | use InvalidArgumentException; |
25 | use MediaWiki\Cache\LinkCache; |
26 | use MediaWiki\Html\Html; |
27 | use MediaWiki\Linker\LinkRenderer; |
28 | use MediaWiki\MediaWikiServices; |
29 | use MediaWiki\Pager\TablePager; |
30 | use MediaWiki\Permissions\PermissionManager; |
31 | use MediaWiki\Title\Title; |
32 | use MediaWiki\Title\TitleValue; |
33 | use Wikimedia\Rdbms\IExpression; |
34 | use Wikimedia\Rdbms\LikeValue; |
35 | use Wikimedia\Rdbms\SelectQueryBuilder; |
36 | |
37 | class LintErrorsPager extends TablePager { |
38 | |
39 | private CategoryManager $categoryManager; |
40 | private LinkCache $linkCache; |
41 | private LinkRenderer $linkRenderer; |
42 | private PermissionManager $permissionManager; |
43 | |
44 | private ?string $category; |
45 | /** @var mixed */ |
46 | private $categoryId; |
47 | private array $namespaces; |
48 | private bool $exactMatch; |
49 | private string $title; |
50 | private string $tag; |
51 | |
52 | /** |
53 | * Allowed values are keys 'all', 'with' or 'without' |
54 | */ |
55 | private string $throughTemplate; |
56 | |
57 | /** |
58 | * @param IContextSource $context |
59 | * @param CategoryManager $categoryManager |
60 | * @param LinkCache $linkCache |
61 | * @param LinkRenderer $linkRenderer |
62 | * @param PermissionManager $permissionManager |
63 | * @param ?string $category |
64 | * @param array $namespaces |
65 | * @param bool $exactMatch |
66 | * @param string $title |
67 | * @param string $throughTemplate |
68 | * @param string $tag |
69 | */ |
70 | public function __construct( |
71 | IContextSource $context, |
72 | CategoryManager $categoryManager, |
73 | LinkCache $linkCache, |
74 | LinkRenderer $linkRenderer, |
75 | PermissionManager $permissionManager, |
76 | ?string $category, |
77 | array $namespaces, |
78 | bool $exactMatch, |
79 | string $title, |
80 | string $throughTemplate, |
81 | string $tag |
82 | ) { |
83 | $this->categoryManager = $categoryManager; |
84 | $this->linkCache = $linkCache; |
85 | $this->linkRenderer = $linkRenderer; |
86 | $this->permissionManager = $permissionManager; |
87 | |
88 | $this->category = $category; |
89 | if ( $category !== null ) { |
90 | $this->categoryId = $categoryManager->getCategoryId( $category ); |
91 | } else { |
92 | $this->categoryId = array_values( $this->categoryManager->getCategoryIds( |
93 | $this->categoryManager->getVisibleCategories() |
94 | ) ); |
95 | } |
96 | |
97 | $this->namespaces = $namespaces; |
98 | $this->exactMatch = $exactMatch; |
99 | $this->title = $title; |
100 | $this->throughTemplate = $throughTemplate ?: 'all'; |
101 | $this->tag = $tag ?: 'all'; |
102 | parent::__construct( $context ); |
103 | } |
104 | |
105 | private function fillQueryBuilder( SelectQueryBuilder $queryBuilder ): void { |
106 | $mainConfig = MediaWikiServices::getInstance()->getMainConfig(); |
107 | $queryBuilder |
108 | ->table( 'page' ) |
109 | ->join( 'linter', null, 'page_id=linter_page' ) |
110 | ->fields( LinkCache::getSelectFields() ) |
111 | ->fields( [ |
112 | 'page_namespace', 'page_title', |
113 | 'linter_id', 'linter_params', |
114 | 'linter_start', 'linter_end', |
115 | 'linter_cat' |
116 | ] ) |
117 | ->where( [ 'linter_cat' => $this->categoryId ] ); |
118 | |
119 | if ( $this->title !== '' ) { |
120 | $namespaces = $this->namespaces ?: [ NS_MAIN ]; |
121 | // Specify page_namespace so that the index can be used (T360865) |
122 | $queryBuilder->where( [ 'page_namespace' => $namespaces ] ); |
123 | if ( $mainConfig->get( 'LinterUseNamespaceColumnStage' ) ) { |
124 | // Also put a condition on linter_namespace, in case the DB |
125 | // decides to put the linter table first |
126 | $queryBuilder->where( [ 'linter_namespace' => $namespaces ] ); |
127 | } |
128 | if ( $this->exactMatch ) { |
129 | $queryBuilder->where( [ |
130 | 'page_title' => $this->title |
131 | ] ); |
132 | } else { |
133 | $queryBuilder->where( $this->mDb->expr( |
134 | 'page_title', IExpression::LIKE, new LikeValue( $this->title, $this->mDb->anyString() ) |
135 | ) ); |
136 | } |
137 | } elseif ( $this->namespaces ) { |
138 | $namespaceCol = $mainConfig->get( 'LinterUseNamespaceColumnStage' ) |
139 | ? "linter_namespace" : "page_namespace"; |
140 | $queryBuilder->where( [ $namespaceCol => $this->namespaces ] ); |
141 | } |
142 | |
143 | if ( $mainConfig->get( 'LinterUserInterfaceTagAndTemplateStage' ) ) { |
144 | if ( $this->throughTemplate !== 'all' ) { |
145 | $op = ( $this->throughTemplate === 'with' ) ? '!=' : '='; |
146 | $queryBuilder->where( $this->mDb->expr( 'linter_template', $op, '' ) ); |
147 | } |
148 | if ( $this->tag !== 'all' && ( new HtmlTags( $this ) )->checkAllowedHTMLTags( $this->tag ) ) { |
149 | $queryBuilder->where( [ 'linter_tag' => $this->tag ] ); |
150 | } |
151 | } |
152 | } |
153 | |
154 | /** |
155 | * @inheritDoc |
156 | */ |
157 | public function getQueryInfo() { |
158 | $queryBuilder = $this->mDb->newSelectQueryBuilder(); |
159 | $this->fillQueryBuilder( $queryBuilder ); |
160 | return $queryBuilder->getQueryInfo(); |
161 | } |
162 | |
163 | protected function doBatchLookups() { |
164 | foreach ( $this->mResult as $row ) { |
165 | $titleValue = new TitleValue( (int)$row->page_namespace, $row->page_title ); |
166 | $this->linkCache->addGoodLinkObjFromRow( $titleValue, $row ); |
167 | } |
168 | } |
169 | |
170 | /** @inheritDoc */ |
171 | public function isFieldSortable( $field ) { |
172 | return false; |
173 | } |
174 | |
175 | /** |
176 | * @param string $name |
177 | * @param string $value |
178 | * @return string |
179 | * @throws InvalidArgumentException |
180 | */ |
181 | public function formatValue( $name, $value ) { |
182 | $row = $this->mCurrentRow; |
183 | |
184 | // To support multiple lint errors of varying types for a single page, the |
185 | // category is set each time based on the category set in the lint error $row |
186 | // not by the class when lints are being reported by type for many pages |
187 | $category = $this->category; |
188 | if ( $category === null ) { |
189 | // Assert $row->linter_cat !== null ? |
190 | $category = $this->categoryManager->getCategoryName( $row->linter_cat ); |
191 | } else { |
192 | $row->linter_cat = $this->categoryId; |
193 | } |
194 | $lintError = Database::makeLintError( $this->categoryManager, $row ); |
195 | |
196 | if ( !$lintError ) { |
197 | return ''; |
198 | } |
199 | |
200 | switch ( $name ) { |
201 | case 'title': |
202 | $title = Title::makeTitle( $row->page_namespace, $row->page_title ); |
203 | $viewLink = $this->linkRenderer->makeLink( $title ); |
204 | $editMsgKey = $this->permissionManager->quickUserCan( 'edit', $this->getUser(), $title ) ? |
205 | 'linter-page-edit' : 'linter-page-viewsource'; |
206 | $editLink = $this->linkRenderer->makeLink( |
207 | $title, |
208 | $this->msg( $editMsgKey )->text(), |
209 | [], |
210 | [ 'action' => 'edit', 'lintid' => $lintError->lintId, ] |
211 | ); |
212 | |
213 | $historyLink = $this->linkRenderer->makeLink( |
214 | $title, |
215 | $this->msg( 'linter-page-history' )->text(), |
216 | [], |
217 | [ 'action' => 'history' ] |
218 | ); |
219 | |
220 | $editHistLinks = $this->getLanguage()->pipeList( [ $editLink, $historyLink ] ); |
221 | return $this->msg( 'linter-page-title-edit' ) |
222 | ->rawParams( $viewLink, $editHistLinks ) |
223 | ->escaped(); |
224 | case 'details': |
225 | if ( $category !== null && $this->categoryManager->hasNameParam( $category ) && |
226 | isset( $lintError->params['name'] ) ) { |
227 | return Html::element( 'code', [], $lintError->params['name'] ); |
228 | } elseif ( $category === 'bogus-image-options' && isset( $lintError->params['items'] ) ) { |
229 | $list = array_map( static function ( $in ) { |
230 | return Html::element( 'code', [], $in ); |
231 | }, $lintError->params['items'] ); |
232 | return $this->getLanguage()->commaList( $list ); |
233 | } elseif ( $category === 'pwrap-bug-workaround' && |
234 | isset( $lintError->params['root'] ) && |
235 | isset( $lintError->params['child'] ) ) { |
236 | return Html::element( 'code', [], |
237 | $lintError->params['root'] . " > " . $lintError->params['child'] ); |
238 | } elseif ( $category === 'tidy-whitespace-bug' && |
239 | isset( $lintError->params['node'] ) && |
240 | isset( $lintError->params['sibling'] ) ) { |
241 | return Html::element( 'code', [], |
242 | $lintError->params['node'] . " + " . $lintError->params['sibling'] ); |
243 | } elseif ( $category === 'multi-colon-escape' && |
244 | isset( $lintError->params['href'] ) ) { |
245 | return Html::element( 'code', [], $lintError->params['href'] ); |
246 | } elseif ( $category === 'multiline-html-table-in-list' ) { |
247 | /* ancestor and name will be set */ |
248 | return Html::element( 'code', [], |
249 | $lintError->params['ancestorName'] . " > " . $lintError->params['name'] ); |
250 | } elseif ( $category === 'misc-tidy-replacement-issues' ) { |
251 | /* There will be a 'subtype' param to disambiguate */ |
252 | return Html::element( 'code', [], $lintError->params['subtype'] ); |
253 | } |
254 | return ''; |
255 | case 'template': |
256 | if ( !$lintError->templateInfo ) { |
257 | return '—'; |
258 | } |
259 | |
260 | if ( isset( $lintError->templateInfo['multiPartTemplateBlock'] ) ) { |
261 | return $this->msg( 'multi-part-template-block' )->escaped(); |
262 | } else { |
263 | // @phan-suppress-next-line PhanTypeArraySuspiciousNullable Null checked above |
264 | $templateName = $lintError->templateInfo['name']; |
265 | // Parsoid provides us with fully qualified template title |
266 | // So, fallback to the default main namespace |
267 | $templateTitle = Title::newFromText( $templateName ); |
268 | if ( !$templateTitle ) { |
269 | // Shouldn't be possible...??? |
270 | return '—'; |
271 | } |
272 | } |
273 | |
274 | return $this->linkRenderer->makeLink( |
275 | $templateTitle |
276 | ); |
277 | case 'category': |
278 | return Html::element( 'code', [], $category ?? '' ); |
279 | default: |
280 | throw new InvalidArgumentException( "Unexpected name: $name" ); |
281 | } |
282 | } |
283 | |
284 | /** @inheritDoc */ |
285 | public function getDefaultSort() { |
286 | return 'linter_id'; |
287 | } |
288 | |
289 | /** |
290 | * @return string[] |
291 | */ |
292 | public function getFieldNames() { |
293 | $names = [ |
294 | 'title' => $this->msg( 'linter-pager-title-header' )->text(), |
295 | ]; |
296 | if ( !$this->category ) { |
297 | $names['category'] = $this->msg( 'linter-pager-category-header' )->text(); |
298 | $names['details'] = $this->msg( "linter-pager-details-header" )->text(); |
299 | } elseif ( !$this->categoryManager->hasNoParams( $this->category ) ) { |
300 | $names['details'] = $this->msg( "linter-pager-{$this->category}-details" )->text(); |
301 | } |
302 | $names['template'] = $this->msg( "linter-pager-template-header" )->text(); |
303 | return $names; |
304 | } |
305 | } |