MediaWiki master
SpecialPrefixIndex.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Specials;
8
17
25
31 protected $stripPrefix = false;
32
34 protected $hideRedirects = false;
35
36 // Inherit $maxPerPage
37
38 // phpcs:ignore MediaWiki.Commenting.PropertyDocumentation.WrongStyle
39 private IConnectionProvider $dbProvider;
40 private LinkCache $linkCache;
41
42 public function __construct(
43 IConnectionProvider $dbProvider,
44 LinkCache $linkCache
45 ) {
46 parent::__construct( $dbProvider );
47 $this->mName = 'Prefixindex';
48 $this->dbProvider = $dbProvider;
49 $this->linkCache = $linkCache;
50 }
51
56 public function execute( $par ) {
57 $this->setHeaders();
58 $this->outputHeader();
59
60 $out = $this->getOutput();
61 $out->addModuleStyles( 'mediawiki.special' );
62
63 # GET values
64 $request = $this->getRequest();
65 $from = $request->getVal( 'from', '' );
66 $prefix = $request->getVal( 'prefix', '' );
67 $ns = $request->getIntOrNull( 'namespace' );
68 $namespace = (int)$ns; // if no namespace given, use 0 (NS_MAIN).
69 $this->hideRedirects = $request->getBool( 'hideredirects', $this->hideRedirects );
70 $this->stripPrefix = $request->getBool( 'stripprefix', $this->stripPrefix );
71
72 $namespaces = $this->getContentLanguage()->getNamespaces();
73 $out->setPageTitleMsg(
74 ( $namespace > 0 && array_key_exists( $namespace, $namespaces ) )
75 ? $this->msg( 'prefixindex-namespace' )->plaintextParams(
76 str_replace( '_', ' ', $namespaces[$namespace] )
77 )
78 : $this->msg( 'prefixindex' )
79 );
80
81 $showme = '';
82 if ( $par !== null ) {
83 $showme = $par;
84 } elseif ( $prefix != '' ) {
85 $showme = $prefix;
86 } elseif ( $from != '' && $ns === null ) {
87 // For back-compat with Special:Allpages
88 // Don't do this if namespace is passed, so paging works when doing NS views.
89 $showme = $from;
90 }
91
92 // T29864: if transcluded, show all pages instead of the form.
93 if ( $this->including() || $showme != '' || $ns !== null ) {
94 $this->showPrefixChunk( $namespace, $showme, $from );
95 } else {
96 $out->addHTML( $this->namespacePrefixForm( $namespace, '' )->getHTML( false ) );
97 }
98 }
99
106 protected function namespacePrefixForm( $namespace = NS_MAIN, $from = '' ): HTMLForm {
107 $formDescriptor = [
108 'prefix' => [
109 'label-message' => 'allpagesprefix',
110 'name' => 'prefix',
111 'id' => 'nsfrom',
112 'type' => 'text',
113 'size' => '30',
114 'default' => str_replace( '_', ' ', $from ),
115 ],
116 'namespace' => [
117 'type' => 'namespaceselect',
118 'name' => 'namespace',
119 'id' => 'namespace',
120 'label-message' => 'namespace',
121 'all' => null,
122 'default' => $namespace,
123 ],
124 'hidedirects' => [
125 'class' => HTMLCheckField::class,
126 'name' => 'hideredirects',
127 'label-message' => 'allpages-hide-redirects',
128 ],
129 'stripprefix' => [
130 'class' => HTMLCheckField::class,
131 'name' => 'stripprefix',
132 'label-message' => 'prefixindex-strip',
133 ],
134 ];
135
136 $this->getHookRunner()->onSpecialPrefixIndexGetFormFilters( $this->getContext(), $formDescriptor );
137
138 $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
139 ->setMethod( 'get' )
140 ->setTitle( $this->getPageTitle() ) // Remove subpage
141 ->setWrapperLegendMsg( 'prefixindex' )
142 ->setSubmitTextMsg( 'prefixindex-submit' );
143
144 return $htmlForm->prepareForm();
145 }
146
152 protected function showPrefixChunk( $namespace, $prefix, $from = null ) {
153 $from ??= $prefix;
154
155 $fromList = $this->getNamespaceKeyAndText( $namespace, $from );
156 $prefixList = $this->getNamespaceKeyAndText( $namespace, $prefix );
157 $namespaces = $this->getContentLanguage()->getNamespaces();
158 $res = null;
159 $n = 0;
160 $nextRow = null;
161 $preparedHtmlForm = $this->namespacePrefixForm( $namespace, $prefix );
162
163 if ( !$prefixList || !$fromList ) {
164 $out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
165 } elseif ( !array_key_exists( $namespace, $namespaces ) ) {
166 // Show errormessage and reset to NS_MAIN
167 $out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
168 $namespace = NS_MAIN;
169 } else {
170 [ $namespace, $prefixKey, $prefix ] = $prefixList;
171 [ /* $fromNS */, $fromKey, ] = $fromList;
172
173 # ## @todo FIXME: Should complain if $fromNs != $namespace
174
175 $dbr = $this->dbProvider->getReplicaDatabase();
176 $queryBuiler = $dbr->newSelectQueryBuilder()
177 ->select( LinkCache::getSelectFields() )
178 ->from( 'page' )
179 ->where( [
180 'page_namespace' => $namespace,
181 $dbr->expr(
182 'page_title',
183 IExpression::LIKE,
184 new LikeValue( $prefixKey, $dbr->anyString() )
185 ),
186 $dbr->expr( 'page_title', '>=', $fromKey ),
187 ] )
188 ->orderBy( 'page_title' )
189 ->limit( $this->maxPerPage + 1 )
190 ->useIndex( 'page_name_title' );
191
192 if ( $this->hideRedirects ) {
193 $queryBuiler->andWhere( [ 'page_is_redirect' => 0 ] );
194 }
195
196 $this->getHookRunner()->onSpecialPrefixIndexQuery( $preparedHtmlForm->mFieldData, $queryBuiler );
197
198 $res = $queryBuiler->caller( __METHOD__ )->fetchResultSet();
199
200 // @todo FIXME: Side link to previous
201
202 if ( $res->numRows() > 0 ) {
203 $out = Html::openElement( 'ul', [ 'class' => 'mw-prefixindex-list' ] );
204
205 $prefixLength = strlen( $prefix );
206 foreach ( $res as $row ) {
207 if ( $n >= $this->maxPerPage ) {
208 $nextRow = $row;
209 break;
210 }
211 $title = Title::newFromRow( $row );
212 // Make sure it gets into LinkCache
213 $this->linkCache->addGoodLinkObjFromRow( $title, $row );
214 $displayed = $title->getText();
215 // Try not to generate unclickable links
216 if ( $this->stripPrefix && $prefixLength !== strlen( $displayed ) ) {
217 $displayed = substr( $displayed, $prefixLength );
218 }
219 $link = ( $title->isRedirect() ? '<div class="allpagesredirect">' : '' ) .
220 $this->getLinkRenderer()->makeKnownLink(
221 $title,
222 $displayed
223 ) .
224 ( $title->isRedirect() ? '</div>' : '' );
225
226 $out .= "<li>$link</li>\n";
227 $n++;
228
229 }
230 $out .= Html::closeElement( 'ul' );
231
232 if ( $res->numRows() > 2 ) {
233 // Only apply CSS column styles if there are more than 2 entries.
234 // Otherwise, rendering is broken as "mw-prefixindex-body"'s CSS column count is 3.
235 $out = Html::rawElement( 'div', [ 'class' => 'mw-prefixindex-body' ], $out );
236 }
237 } else {
238 $out = '';
239 }
240 }
241
242 $output = $this->getOutput();
243
244 if ( $this->including() ) {
245 // We don't show the nav-links and the form when included in other
246 // pages, so let's just finish here.
247 $output->addHTML( $out );
248 return;
249 }
250
251 $topOut = $preparedHtmlForm->getHTML( false );
252
253 if ( $res && ( $n == $this->maxPerPage ) && $nextRow ) {
254 $query = [
255 'from' => $nextRow->page_title,
256 'prefix' => $prefix,
257 'hideredirects' => $this->hideRedirects,
258 'stripprefix' => $this->stripPrefix,
259 ];
260
261 if ( $namespace || $prefix == '' ) {
262 // Keep the namespace even if it's 0 for empty prefixes.
263 // This tells us we're not just a holdover from old links.
264 $query['namespace'] = $namespace;
265 }
266
267 $nextLink = $this->getLinkRenderer()->makeKnownLink(
268 $this->getPageTitle(),
269 $this->msg( 'nextpage', str_replace( '_', ' ', $nextRow->page_title ) )->text(),
270 [],
271 $query
272 );
273
274 // Link shown at the top of the page below the form
275 $topOut .= Html::rawElement( 'div',
276 [ 'class' => 'mw-prefixindex-nav' ],
277 $nextLink
278 );
279
280 // Link shown at the footer
281 $out .= "\n" . Html::element( 'hr' ) .
282 Html::rawElement(
283 'div',
284 [ 'class' => 'mw-prefixindex-nav' ],
285 $nextLink
286 );
287
288 }
289
290 $output->addHTML( $topOut . $out );
291 }
292
294 protected function getGroupName() {
295 return 'pages';
296 }
297}
298
303class_alias( SpecialPrefixIndex::class, 'SpecialPrefixindex' );
const NS_MAIN
Definition Defines.php:51
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:195
setMethod( $method='post')
Set the method used to submit the form.
This class is a collection of static functions that serve two purposes:
Definition Html.php:43
Page existence and metadata cache.
Definition LinkCache.php:54
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getPageTitle( $subpage=false)
Get a self-referential title object.
getContext()
Gets the context this SpecialPage is executed in.
getRequest()
Get the WebRequest being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
getContentLanguage()
Shortcut to get content language.
including( $x=null)
Whether the special page is being evaluated via transclusion.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages By default the message key is the canonical name of...
Implements Special:Allpages.
Implements Special:Prefixindex.
namespacePrefixForm( $namespace=NS_MAIN, $from='')
Prepared HTMLForm object for the top form.
__construct(IConnectionProvider $dbProvider, LinkCache $linkCache)
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
showPrefixChunk( $namespace, $prefix, $from=null)
bool $stripPrefix
Whether to remove the searched prefix from the displayed link.
execute( $par)
Entry point: initialise variables and call subfunctions.
Represents a title within MediaWiki.
Definition Title.php:69
Content of like value.
Definition LikeValue.php:14
Provide primary and replica IDatabase connections.
element(SerializerNode $parent, SerializerNode $node, $contents)