LinkTitles extension for MediaWiki
Automatically add links to existing pages.
 All Classes Files Functions Variables Modules Pages
SpecialLinkTitles.php
1 <?php
2 /*
3  * Copyright 2012-2014 Daniel Kraus <krada@gmx.net> ('bovender')
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18  * MA 02110-1301, USA.
19  */
21 
23  if ( !defined( 'MEDIAWIKI' ) ) {
24  die( 'Not an entry point.' );
25  }
27 
31 class SpecialLinkTitles extends SpecialPage {
32 
35  function __construct() {
36  // the second parameter in the following function call ensures that only
37  // users who have the 'linktitles-batch' right get to see this page (by
38  // default, this are all sysop users).
39  parent::__construct('LinkTitles', 'linktitles-batch');
40  }
41 
45  function execute($par) {
46  // Prevent non-authorized users from executing the batch processing.
47  if ( !$this->userCanExecute( $this->getUser() ) ) {
48  $this->displayRestrictionError();
49  return;
50  }
51 
52  $request = $this->getRequest();
53  $output = $this->getOutput();
54  $this->setHeaders();
55 
56  // Determine whether this page was requested via GET or POST.
57  // If GET, display information and a button to start linking.
58  // If POST, start or continue the linking process.
59  if ( $request->wasPosted() ) {
60  if ( array_key_exists('s', $request->getValues()) ) {
61  $this->process($request, $output);
62  }
63  else
64  {
65  $this->buildInfoPage($request, $output);
66  }
67  }
68  else
69  {
70  $this->buildInfoPage($request, $output);
71  }
72  }
73 
80  private function process( WebRequest &$request, OutputPage &$output) {
82 
83  // Start the stopwatch
84  $startTime = microtime(true);
85 
86  // Connect to the database
87  $dbr = wfGetDB( DB_SLAVE );
88 
89  // Fetch the start index and max number of records from the POST
90  // request.
91  $postValues = $request->getValues();
92 
93  // Convert the start index to an integer; this helps preventing
94  // SQL injection attacks via forged POST requests.
95  $start = intval($postValues['s']);
96 
97  // If an end index was given, we don't need to query the database
98  if ( array_key_exists('e', $postValues) ) {
99  $end = intval($postValues['e']);
100  }
101  else
102  {
103  // No end index was given. Therefore, count pages now.
104  $end = $this->countPages($dbr);
105  };
106 
107  array_key_exists('r', $postValues) ?
108  $reloads = $postValues['r'] :
109  $reloads = 0;
110 
111  // Retrieve page names from the database.
112  $res = $dbr->select(
113  'page',
114  'page_title',
115  array(
116  'page_namespace = 0',
117  ),
118  __METHOD__,
119  array(
120  'LIMIT' => 999999999,
121  'OFFSET' => $start
122  )
123  );
124 
125  // Iterate through the pages; break if a time limit is exceeded.
126  foreach ( $res as $row ) {
127  $curTitle = $row->page_title;
128  LinkTitles::processPage($curTitle, $this->getContext());
129  $start += 1;
130 
131  // Check if the time limit is exceeded
132  if ( microtime(true)-$startTime > $wgLinkTitlesTimeLimit )
133  {
134  break;
135  }
136  }
137 
138  $this->addProgressInfo($output, $curTitle, $start, $end);
139 
140  // If we have not reached the last page yet, produce code to reload
141  // the extension's special page.
142  if ( $start < $end )
143  {
144  $reloads += 1;
145  // Build a form with hidden values and output JavaScript code that
146  // immediately submits the form in order to continue the process.
147  $output->addHTML($this->getReloaderForm($request->getRequestURL(),
148  $start, $end, $reloads));
149  }
150  else // Last page has been processed
151  {
152  $this->addCompletedInfo($output, $start, $end, $reloads);
153  }
154  }
155 
158  private function buildInfoPage(&$request, &$output) {
159  $url = $request->getRequestURL();
160 
161  // TODO: Put the page contents in messages in the i18n file.
162  $output->addWikiText(
163 <<<EOF
164 LinkTitles extension: http://www.mediawiki.org/wiki/Extension:LinkTitles
165 
166 Source code: http://github.com/bovender/LinkTitles
167 
168 == Batch Linking ==
169 You can start a batch linking process by clicking on the button below.
170 This will go through every page in the normal namespace of your Wiki and
171 insert links automatically. This page will repeatedly reload itself, in
172 order to prevent blocking the server. To interrupt the process, simply
173 close this page.
174 EOF
175  );
176  $output->addHTML(
177 <<<EOF
178 <form method="post" action="${url}">
179  <input type="submit" value="Start linking" />
180  <input type="hidden" name="s" value="0" />
181 </form>
182 EOF
183  );
184  }
185 
192  private function addProgressInfo(&$output, $curTitle, $index, $end) {
193  $progress = $index / $end * 100;
194  $percent = sprintf("%01.1f", $progress);
195 
196  $output->addWikiText(
197 <<<EOF
198 == Processing pages... ==
199 The [http://www.mediawiki.org/wiki/Extension:LinkTitles LinkTitles]
200 extension is currently going through every page of your wiki, adding links to
201 existing pages as appropriate.
202 
203 === Current page: $curTitle ===
204 EOF
205  );
206  $output->addHTML(
207 <<<EOF
208 <p>Page ${index} of ${end}.</p>
209 <div style="width:100%; padding:2px; border:1px solid #000; position: relative;
210  margin-bottom:16px;">
211  <span style="position: absolute; left: 50%; font-weight:bold; color:#555;">
212  ${percent}%
213  </span>
214  <div style="width:${progress}%; background-color:#bbb; height:20px; margin:0;"></div>
215 </div>
216 EOF
217  );
218  $output->addWikiText(
219 <<<EOF
220 === To abort, close this page, or hit the 'Stop' button in your browser ===
221 [[Special:LinkTitles|Return to Special:LinkTitles.]]
222 EOF
223  );
224  }
225 
234  private function getReloaderForm($url, $start, $end, $reloads) {
235  return
236 <<<EOF
237 <form method="post" name="linktitles" action="${url}">
238  <input type="hidden" name="s" value="${start}" />
239  <input type="hidden" name="e" value="${end}" />
240  <input type="hidden" name="r" value="${reloads}" />
241 </form>
242 <script type="text/javascript">
243  document.linktitles.submit();
244 </script>
245 EOF
246  ;
247  }
248 
255  private function addCompletedInfo(&$output, $start, $end, $reloads) {
256  global $wgLinkTitlesTimeLimit;
257  $pagesPerReload = sprintf('%0.1f', $end / $reloads);
258  $output->addWikiText(
259 <<<EOF
260 == Batch processing completed! ==
261 {| class="wikitable"
262 |-
263 | total number of pages: || ${end}
264 |-
265 | timeout setting [s]: || ${wgLinkTitlesTimeLimit}
266 |-
267 | webpage reloads: || ${reloads}
268 |-
269 | pages scanned per reload interval: || ${pagesPerReload}
270 |}
271 EOF
272  );
273  }
274 
278  private function countPages(&$dbr) {
279  $res = $dbr->select(
280  'page',
281  'page_id',
282  array(
283  'page_namespace = 0',
284  ),
285  __METHOD__
286  );
287  return $res->numRows();
288  }
289 }
290 
291 // vim: ts=2:sw=2:noet:comments^=\:///
Central class of the extension.
execute($par)
Entry function of the special page class.
Provides a special page that can be used to batch-process all pages in the wiki.
$wgLinkTitlesTimeLimit
Time limit for online batch processing.
Definition: LinkTitles.php:189
__construct()
Constructor.
static processPage($title, RequestContext $context)
Automatically processes a single page, given a $title Title object.