Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 135 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
SpecialDisambiguationPageLinks | |
0.00% |
0 / 135 |
|
0.00% |
0 / 12 |
756 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
isExpensive | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isSyndicated | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getQueryInfo | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
2 | |||
getOrderFields | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
sortDescending | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
formatResult | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
12 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
recache | |
0.00% |
0 / 51 |
|
0.00% |
0 / 1 |
90 | |||
execute | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
fetchFromCache | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
20 | |||
preprocessResults | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * DisambiguationPageLinks SpecialPage for Disambiguator extension |
4 | * This page lists all pages that link to disambiguation pages. |
5 | * |
6 | * @file |
7 | * @ingroup Extensions |
8 | */ |
9 | |
10 | namespace MediaWiki\Extension\Disambiguator\Specials; |
11 | |
12 | use Exception; |
13 | use MediaWiki\Cache\LinkBatchFactory; |
14 | use MediaWiki\Content\IContentHandlerFactory; |
15 | use MediaWiki\Linker\LinksMigration; |
16 | use MediaWiki\SpecialPage\QueryPage; |
17 | use MediaWiki\Title\NamespaceInfo; |
18 | use MediaWiki\Title\Title; |
19 | use Wikimedia\Rdbms\DBError; |
20 | use Wikimedia\Rdbms\IConnectionProvider; |
21 | use Wikimedia\Rdbms\IDatabase; |
22 | use Wikimedia\Rdbms\IResultWrapper; |
23 | use Wikimedia\Rdbms\SelectQueryBuilder; |
24 | |
25 | class SpecialDisambiguationPageLinks extends QueryPage { |
26 | |
27 | /** @var NamespaceInfo */ |
28 | private $namespaceInfo; |
29 | |
30 | /** @var LinkBatchFactory */ |
31 | private $linkBatchFactory; |
32 | |
33 | /** @var IContentHandlerFactory */ |
34 | private $contentHandlerFactory; |
35 | private IConnectionProvider $dbProvider; |
36 | private LinksMigration $linksMigration; |
37 | |
38 | /** |
39 | * @param NamespaceInfo $namespaceInfo |
40 | * @param LinkBatchFactory $linkBatchFactory |
41 | * @param IContentHandlerFactory $contentHandlerFactory |
42 | * @param IConnectionProvider $dbProvider |
43 | * @param LinksMigration $linksMigration |
44 | */ |
45 | public function __construct( |
46 | NamespaceInfo $namespaceInfo, |
47 | LinkBatchFactory $linkBatchFactory, |
48 | IContentHandlerFactory $contentHandlerFactory, |
49 | IConnectionProvider $dbProvider, |
50 | LinksMigration $linksMigration |
51 | ) { |
52 | parent::__construct( 'DisambiguationPageLinks' ); |
53 | $this->namespaceInfo = $namespaceInfo; |
54 | $this->linkBatchFactory = $linkBatchFactory; |
55 | $this->contentHandlerFactory = $contentHandlerFactory; |
56 | $this->dbProvider = $dbProvider; |
57 | $this->linksMigration = $linksMigration; |
58 | } |
59 | |
60 | public function isExpensive() { |
61 | return true; |
62 | } |
63 | |
64 | public function isSyndicated() { |
65 | return false; |
66 | } |
67 | |
68 | public function getQueryInfo() { |
69 | [ $blNamespace, $blTitle ] = $this->linksMigration->getTitleFields( 'pagelinks' ); |
70 | $queryInfo = $this->linksMigration->getQueryInfo( 'pagelinks', 'pagelinks' ); |
71 | return [ |
72 | 'tables' => array_merge( $queryInfo['tables'], [ |
73 | 'p1' => 'page', |
74 | 'p2' => 'page', |
75 | 'page_props' |
76 | ] ), |
77 | // The fields we are selecting correspond with fields in the |
78 | // querycachetwo table so that the results are cachable. |
79 | 'fields' => [ |
80 | 'value' => 'pl_from', |
81 | 'namespace' => 'p2.page_namespace', |
82 | 'title' => 'p2.page_title', |
83 | 'to_namespace' => 'p1.page_namespace', |
84 | 'to_title' => 'p1.page_title', |
85 | ], |
86 | 'conds' => [ |
87 | 'pp_propname' => 'disambiguation', |
88 | 'p2.page_namespace' => $this->namespaceInfo->getContentNamespaces(), |
89 | 'p2.page_is_redirect != 1' |
90 | ], |
91 | 'join_conds' => array_merge( $queryInfo['joins'], [ |
92 | 'p1' => [ 'JOIN', [ "$blNamespace = p1.page_namespace", "$blTitle = p1.page_title" ] ], |
93 | 'page_props' => [ 'JOIN', [ 'p1.page_id = pp_page' ] ], |
94 | 'p2' => [ 'JOIN', [ 'p2.page_id = pl_from' ] ] |
95 | ] ) |
96 | ]; |
97 | } |
98 | |
99 | /** |
100 | * Order the results by ID of the linking page (value), namespace of the |
101 | * linked page, and title of the linked page. This function only affects |
102 | * ordering when not using $wgMiserMode. |
103 | * |
104 | * @return array |
105 | */ |
106 | public function getOrderFields() { |
107 | return [ 'value', 'to_namespace', 'to_title' ]; |
108 | } |
109 | |
110 | public function sortDescending() { |
111 | return false; |
112 | } |
113 | |
114 | public function formatResult( $skin, $result ) { |
115 | $fromTitle = Title::makeTitle( $result->namespace, $result->title ); |
116 | $toTitle = Title::makeTitle( $result->to_namespace, $result->to_title ); |
117 | |
118 | $linkRenderer = $this->getLinkRenderer(); |
119 | $from = $linkRenderer->makeKnownLink( $fromTitle ); |
120 | $arr = $this->getLanguage()->getArrow(); |
121 | $to = $linkRenderer->makeKnownLink( $toTitle ); |
122 | |
123 | // Check if user is allowed to edit |
124 | if ( |
125 | $this->getAuthority()->isAllowed( 'edit' ) && |
126 | $this->contentHandlerFactory->getContentHandler( $fromTitle->getContentModel() )->supportsDirectEditing() |
127 | ) { |
128 | $edit = $linkRenderer->makeLink( |
129 | $fromTitle, |
130 | $this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->text(), |
131 | [], |
132 | [ 'redirect' => 'no', 'action' => 'edit' ] |
133 | ); |
134 | return "$from $edit $arr $to"; |
135 | } |
136 | |
137 | return "$from $arr $to"; |
138 | } |
139 | |
140 | protected function getGroupName() { |
141 | return 'pages'; |
142 | } |
143 | |
144 | /** |
145 | * Clear the cache and save new results |
146 | * |
147 | * @param int|bool $limit Limit for SQL statement |
148 | * @param bool $ignoreErrors Whether to ignore database errors |
149 | * @return bool|int |
150 | * @throws DBError|Exception |
151 | */ |
152 | public function recache( $limit, $ignoreErrors = true ) { |
153 | if ( !$this->isCacheable() ) { |
154 | return 0; |
155 | } |
156 | |
157 | $fname = get_class( $this ) . '::recache'; |
158 | $dbw = $this->dbProvider->getPrimaryDatabase(); |
159 | |
160 | try { |
161 | // Do query |
162 | $res = $this->reallyDoQuery( $limit, false ); |
163 | $num = false; |
164 | if ( $res ) { |
165 | $num = $res->numRows(); |
166 | // Fetch results |
167 | $vals = []; |
168 | foreach ( $res as $row ) { |
169 | if ( isset( $row->value ) ) { |
170 | if ( $this->usesTimestamps() ) { |
171 | $value = wfTimestamp( TS_UNIX, $row->value ); |
172 | } else { |
173 | $value = intval( $row->value ); |
174 | } |
175 | } else { |
176 | $value = 0; |
177 | } |
178 | |
179 | $vals[] = [ |
180 | 'qcc_type' => $this->getName(), |
181 | 'qcc_value' => $value, |
182 | 'qcc_namespace' => $row->namespace, |
183 | 'qcc_title' => $row->title, |
184 | 'qcc_namespacetwo' => $row->to_namespace, |
185 | 'qcc_titletwo' => $row->to_title, |
186 | ]; |
187 | } |
188 | |
189 | $dbw->startAtomic( __METHOD__ ); |
190 | // Clear out any old cached data |
191 | $dbw->newDeleteQueryBuilder() |
192 | ->deleteFrom( 'querycachetwo' ) |
193 | ->where( [ 'qcc_type' => $this->getName() ] ) |
194 | ->caller( $fname ) |
195 | ->execute(); |
196 | // Save results into the querycachetwo table on the master |
197 | if ( count( $vals ) ) { |
198 | $dbw->newInsertQueryBuilder() |
199 | ->insertInto( 'querycachetwo' ) |
200 | ->rows( $vals ) |
201 | ->caller( $fname ) |
202 | ->execute(); |
203 | } |
204 | // Update the querycache_info record for the page |
205 | $dbw->newDeleteQueryBuilder() |
206 | ->deleteFrom( 'querycache_info' ) |
207 | ->where( [ 'qci_type' => $this->getName() ] ) |
208 | ->caller( $fname ) |
209 | ->execute(); |
210 | $dbw->newInsertQueryBuilder() |
211 | ->insertInto( 'querycache_info' ) |
212 | ->row( [ 'qci_type' => $this->getName(), 'qci_timestamp' => $dbw->timestamp() ] ) |
213 | ->caller( $fname ) |
214 | ->execute(); |
215 | $dbw->endAtomic( __METHOD__ ); |
216 | } |
217 | } catch ( DBError $e ) { |
218 | if ( !$ignoreErrors ) { |
219 | // Report query error |
220 | throw $e; |
221 | } |
222 | // Set result to false to indicate error |
223 | $num = false; |
224 | } |
225 | |
226 | return $num; |
227 | } |
228 | |
229 | public function execute( $par ) { |
230 | $this->addHelpLink( 'Extension:Disambiguator' ); |
231 | parent::execute( $par ); |
232 | } |
233 | |
234 | /** |
235 | * Fetch the query results from the query cache |
236 | * |
237 | * @param int|bool $limit Numerical limit or false for no limit |
238 | * @param int|bool $offset Numerical offset or false for no offset |
239 | * @return IResultWrapper |
240 | */ |
241 | public function fetchFromCache( $limit, $offset = false ) { |
242 | $dbr = $this->dbProvider->getReplicaDatabase(); |
243 | |
244 | $queryBuilder = $dbr->newSelectQueryBuilder() |
245 | ->select( [ |
246 | 'value' => 'qcc_value', |
247 | 'namespace' => 'qcc_namespace', |
248 | 'title' => 'qcc_title', |
249 | 'to_namespace' => 'qcc_namespacetwo', |
250 | 'to_title' => 'qcc_titletwo', |
251 | ] ) |
252 | ->from( 'querycachetwo' ) |
253 | ->where( [ 'qcc_type' => $this->getName() ] ) |
254 | ->caller( __METHOD__ ); |
255 | |
256 | if ( $limit !== false ) { |
257 | $queryBuilder->limit( intval( $limit ) ); |
258 | } |
259 | if ( $offset !== false ) { |
260 | $queryBuilder->offset( intval( $offset ) ); |
261 | } |
262 | // Set sort order. This should match the ordering in getOrderFields(). |
263 | $queryBuilder->orderBy( [ 'qcc_value', 'qcc_namespacetwo' ] ); |
264 | if ( $this->sortDescending() ) { |
265 | $queryBuilder->orderBy( 'qcc_titletwo', SelectQueryBuilder::SORT_DESC ); |
266 | } else { |
267 | $queryBuilder->orderBy( 'qcc_titletwo', SelectQueryBuilder::SORT_ASC ); |
268 | } |
269 | |
270 | return $queryBuilder->fetchResultSet(); |
271 | } |
272 | |
273 | /** |
274 | * Cache page content model for performance |
275 | * |
276 | * @param IDatabase $db |
277 | * @param IResultWrapper $res |
278 | */ |
279 | public function preprocessResults( $db, $res ) { |
280 | if ( !$res->numRows() ) { |
281 | return; |
282 | } |
283 | |
284 | $batch = $this->linkBatchFactory->newLinkBatch(); |
285 | foreach ( $res as $row ) { |
286 | $batch->add( $row->namespace, $row->title ); |
287 | $batch->add( $row->to_namespace, $row->to_title ); |
288 | } |
289 | $batch->execute(); |
290 | |
291 | $res->seek( 0 ); |
292 | } |
293 | } |