LinkTitles extension for MediaWiki
Automatically add links to existing pages.
Special.php
1 <?php
24 namespace LinkTitles;
26 
28 if ( !defined( 'MEDIAWIKI' ) ) {
29  die( 'Not an entry point.' );
30 }
32 
39 class Special extends \SpecialPage {
40  private $config;
41 
45  function __construct() {
46  // the second parameter in the following function call ensures that only
47  // users who have the 'linktitles-batch' right get to see this page (by
48  // default, this are all sysop users).
49  parent::__construct( 'LinkTitles', 'linktitles-batch' );
50  $this->config = new Config();
51  }
52 
53  function getGroupName() {
54  return 'pagetools';
55  }
56 
57 
62  function execute( $par ) {
63  // Prevent non-authorized users from executing the batch processing.
64  if ( !$this->userCanExecute( $this->getUser() ) ) {
65  $this->displayRestrictionError();
66  return;
67  }
68 
69  $request = $this->getRequest();
70  $output = $this->getOutput();
71  $this->setHeaders();
72 
73  // Determine whether this page was requested via GET or POST.
74  // If GET, display information and a button to start linking.
75  // If POST, start or continue the linking process.
76  if ( $request->wasPosted() ) {
77  if ( array_key_exists( 's', $request->getValues() ) ) {
78  $this->process( $request, $output );
79  }
80  else
81  {
82  $this->buildInfoPage( $request, $output );
83  }
84  }
85  else
86  {
87  $this->buildInfoPage( $request, $output );
88  }
89  }
90 
98  private function process( \WebRequest &$request, \OutputPage &$output) {
99  // get our Namespaces
100  $namespacesClause = str_replace( '_', ' ','(' . implode( ', ',$this->config->sourceNamespaces ) . ')' );
101 
102  // Start the stopwatch
103  $startTime = microtime( true );
104 
105  // Connect to the database
106  $dbr = wfGetDB( DB_SLAVE );
107 
108  // Fetch the start index and max number of records from the POST
109  // request.
110  $postValues = $request->getValues();
111 
112  // Convert the start index to an integer; this helps preventing
113  // SQL injection attacks via forged POST requests.
114  $start = intval( $postValues['s'] );
115 
116  // If an end index was given, we don't need to query the database
117  if ( array_key_exists( 'e', $postValues ) ) {
118  $end = intval( $postValues['e'] );
119  }
120  else
121  {
122  // No end index was given. Therefore, count pages now.
123  $end = $this->countPages( $dbr, $namespacesClause );
124  };
125 
126  array_key_exists( 'r', $postValues ) ? $reloads = $postValues['r'] : $reloads = 0;
127 
128  // Retrieve page names from the database.
129  $res = $dbr->select(
130  'page',
131  array('page_title', 'page_namespace'),
132  array(
133  'page_namespace IN ' . $namespacesClause,
134  ),
135  __METHOD__,
136  array(
137  'LIMIT' => 999999999,
138  'OFFSET' => $start
139  )
140  );
141 
142  // Iterate through the pages; break if a time limit is exceeded.
143  foreach ( $res as $row ) {
144  $curTitle = \Title::makeTitleSafe( $row->page_namespace, $row->page_title);
145  Extension::processPage( $curTitle, $this->getContext() );
146  $start += 1;
147 
148  // Check if the time limit is exceeded
149  if ( microtime( true ) - $startTime > $config->specialPageReloadAfter )
150  {
151  break;
152  }
153  }
154 
155  $this->addProgressInfo( $output, $curTitle, $start, $end );
156 
157  // If we have not reached the last page yet, produce code to reload
158  // the extension's special page.
159  if ( $start < $end )
160  {
161  $reloads += 1;
162  // Build a form with hidden values and output JavaScript code that
163  // immediately submits the form in order to continue the process.
164  $output->addHTML( $this->getReloaderForm( $request->getRequestURL(),
165  $start, $end, $reloads) );
166  }
167  else // Last page has been processed
168  {
169  $this->addCompletedInfo( $output, $start, $end, $reloads );
170  }
171  }
172 
173  /*
174  * Adds WikiText to the output containing information about the extension
175  * and a form and button to start linking.
176  */
177  private function buildInfoPage( &$request, &$output ) {
178  $output->addWikiMsg( 'linktitles-special-info', Extension::URL );
179  $url = $request->getRequestURL();
180  $submitButtonLabel = $this->msg( 'linktitles-special-submit' );
181  $output->addHTML(
182 <<<EOF
183 <form method="post" action="${url}">
184  <input type="submit" value="$submitButtonLabel" />
185  <input type="hidden" name="s" value="0" />
186 </form>
187 EOF
188  );
189  }
190 
191  /*
192  * Produces informative output in WikiText format to show while working.
193  * @param $output Output object.
194  * @param $curTitle Title of the currently processed page.
195  * @param $index Index of the currently processed page.
196  * @param $end Last index that will be processed (i.e., number of pages).
197  */
198  private function addProgressInfo( &$output, $curTitle, $index, $end ) {
199  $progress = $index / $end * 100;
200  $percent = sprintf("%01.1f", $progress);
201 
202  $output->addWikiMsg( 'linktitles-special-progress', Extension::URL, $curTitle );
203  $pageInfo = $this->msg( 'linktitles-page-count', $index, $end );
204  $output->addWikiMsg( 'linktitles-special-page-count', $index, $end );
205  $output->addHTML( // TODO: do not use the style attribute (to make it work with CSP-enabled sites)
206 <<<EOF
207 <div style="width:100%; padding:2px; border:1px solid #000; position: relative; margin-bottom:16px;">
208  <span style="position: absolute; left: 50%; font-weight:bold; color:#555;">${percent}%</span>
209  <div style="width:${progress}%; background-color:#bbb; height:20px; margin:0;"></div>
210 </div>
211 EOF
212  );
213  $output->addWikiMsg( 'linktitles-special-cancel-notice' );
214  }
215 
225  private function getReloaderForm( $url, $start, $end, $reloads ) {
226  return
227 <<<EOF
228 <form method="post" name="linktitles" action="${url}">
229  <input type="hidden" name="s" value="${start}" />
230  <input type="hidden" name="e" value="${end}" />
231  <input type="hidden" name="r" value="${reloads}" />
232 </form>
233 <script type="text/javascript">
234  document.linktitles.submit();
235 </script>
236 EOF
237  ;
238  }
239 
248  private function addCompletedInfo( &$output, $start, $end, $reloads ) {
249  $pagesPerReload = sprintf('%0.1f', $end / $reloads);
250  $output->addWikiMsg( 'linktitltes-special-completed-info', $end,
251  $config->specialPageReloadAfter, $reloads, $pagesPerReload
252  );
253  }
254 
260  private function countPages( &$dbr, $namespacesClause ) {
261  $res = $dbr->select(
262  'page',
263  array('pagecount' => "COUNT(page_id)"),
264  array(
265  'page_namespace IN ' . $namespacesClause,
266  ),
267  __METHOD__
268  );
269 
270  return $res->current()->pagecount;
271  }
272 }
273 
274 // vim: ts=2:sw=2:noet:comments^=\:///
The LinkTitles class holds configuration for the LinkTitles extension.
Definition: Config.php:23