MediaWiki  1.31.0
SpecialReplaceText.php
Go to the documentation of this file.
1 <?php
22  private $target;
23  private $replacement;
24  private $use_regex;
25  private $category;
26  private $prefix;
27  private $edit_pages;
28  private $move_pages;
30  private $doAnnounce;
31 
32  public function __construct() {
33  parent::__construct( 'ReplaceText', 'replacetext' );
34  }
35 
39  public function doesWrites() {
40  return true;
41  }
42 
46  function execute( $query ) {
48 
49  if ( !$this->getUser()->isAllowed( 'replacetext' ) ) {
50  throw new PermissionsError( 'replacetext' );
51  }
52 
53  // Replace Text can't be run with certain settings, due to the
54  // changes they make to the DB storage setup.
55  if ( $wgCompressRevisions ) {
56  $errorMsg = "Error: text replacements cannot be run if \$wgCompressRevisions is set to true.";
57  $this->getOutput()->addWikiText( "<div class=\"errorbox\">$errorMsg</div>" );
58  return;
59  }
60  if ( !empty( $wgExternalStores ) ) {
61  $errorMsg = "Error: text replacements cannot be run if \$wgExternalStores is non-empty.";
62  $this->getOutput()->addWikiText( "<div class=\"errorbox\">$errorMsg</div>" );
63  return;
64  }
65 
66  $this->setHeaders();
67  $out = $this->getOutput();
68  if ( !is_null( $out->getResourceLoader()->getModule( 'mediawiki.special' ) ) ) {
69  $out->addModuleStyles( 'mediawiki.special' );
70  }
71  $this->doSpecialReplaceText();
72  }
73 
77  function getSelectedNamespaces() {
78  $all_namespaces = SearchEngine::searchableNamespaces();
80  foreach ( $all_namespaces as $ns => $name ) {
81  if ( $this->getRequest()->getCheck( 'ns' . $ns ) ) {
82  $selected_namespaces[] = $ns;
83  }
84  }
85  return $selected_namespaces;
86  }
87 
91  function doSpecialReplaceText() {
92  $out = $this->getOutput();
93  $request = $this->getRequest();
94 
95  $this->target = $request->getText( 'target' );
96  $this->replacement = $request->getText( 'replacement' );
97  $this->use_regex = $request->getBool( 'use_regex' );
98  $this->category = $request->getText( 'category' );
99  $this->prefix = $request->getText( 'prefix' );
100  $this->edit_pages = $request->getBool( 'edit_pages' );
101  $this->move_pages = $request->getBool( 'move_pages' );
102  $this->doAnnounce = $request->getBool( 'doAnnounce' );
103  $this->selected_namespaces = $this->getSelectedNamespaces();
104 
105  if ( $request->getCheck( 'continue' ) && $this->target === '' ) {
106  $this->showForm( 'replacetext_givetarget' );
107  return;
108  }
109 
110  if ( $request->getCheck( 'replace' ) ) {
111 
112  // check for CSRF
113  $user = $this->getUser();
114  if ( !$user->matchEditToken( $request->getVal( 'token' ) ) ) {
115  $out->addWikiMsg( 'sessionfailure' );
116  return;
117  }
118 
119  $jobs = $this->createJobsForTextReplacements();
120  JobQueueGroup::singleton()->push( $jobs );
121 
122  $count = $this->getLanguage()->formatNum( count( $jobs ) );
123  $out->addWikiMsg(
124  'replacetext_success',
125  "<code><nowiki>{$this->target}</nowiki></code>",
126  "<code><nowiki>{$this->replacement}</nowiki></code>",
127  $count
128  );
129 
130  // Link back
131  $out->addHTML(
133  $this->getPageTitle(),
134  $this->msg( 'replacetext_return' )->escaped()
135  )
136  );
137  return;
138  }
139 
140  if ( $request->getCheck( 'target' ) ) {
141  // check for CSRF
142  $user = $this->getUser();
143  if ( !$user->matchEditToken( $request->getVal( 'token' ) ) ) {
144  $out->addWikiMsg( 'sessionfailure' );
145  return;
146  }
147 
148  // first, check that at least one namespace has been
149  // picked, and that either editing or moving pages
150  // has been selected
151  if ( count( $this->selected_namespaces ) == 0 ) {
152  $this->showForm( 'replacetext_nonamespace' );
153  return;
154  }
155  if ( ! $this->edit_pages && ! $this->move_pages ) {
156  $this->showForm( 'replacetext_editormove' );
157  return;
158  }
159 
160  // If user is replacing text within pages...
161  $titles_for_edit = $titles_for_move = $unmoveable_titles = [];
162  if ( $this->edit_pages ) {
163  $titles_for_edit = $this->getTitlesForEditingWithContext();
164  }
165  if ( $this->move_pages ) {
166  list( $titles_for_move, $unmoveable_titles ) = $this->getTitlesForMoveAndUnmoveableTitles();
167  }
168 
169  // If no results were found, check to see if a bad
170  // category name was entered.
171  if ( count( $titles_for_edit ) == 0 && count( $titles_for_move ) == 0 ) {
172  $bad_cat_name = false;
173 
174  if ( !empty( $this->category ) ) {
175  $category_title = Title::makeTitleSafe( NS_CATEGORY, $this->category );
176  if ( !$category_title->exists() ) {
177  $bad_cat_name = true;
178  }
179  }
180 
181  if ( $bad_cat_name ) {
182  $link = ReplaceTextUtils::link( $category_title,
183  htmlspecialchars( ucfirst( $this->category ) ) );
184  $out->addHTML(
185  $this->msg( 'replacetext_nosuchcategory' )->rawParams( $link )->escaped()
186  );
187  } else {
188  if ( $this->edit_pages ) {
189  $out->addWikiMsg(
190  'replacetext_noreplacement', "<code><nowiki>{$this->target}</nowiki></code>"
191  );
192  }
193 
194  if ( $this->move_pages ) {
195  $out->addWikiMsg( 'replacetext_nomove', "<code><nowiki>{$this->target}</nowiki></code>" );
196  }
197  }
198  // link back to starting form
199  $out->addHTML(
200  '<p>' .
202  $this->getPageTitle(),
203  $this->msg( 'replacetext_return' )->escaped() )
204  . '</p>'
205  );
206  } else {
207  $warning_msg = $this->getAnyWarningMessageBeforeReplace( $titles_for_edit, $titles_for_move );
208  if ( ! is_null( $warning_msg ) ) {
209  $out->addWikiText( "<div class=\"errorbox\">$warning_msg</div><br clear=\"both\" />" );
210  }
211 
212  $this->pageListForm( $titles_for_edit, $titles_for_move, $unmoveable_titles );
213  }
214  return;
215  }
216 
217  // If we're still here, show the starting form.
218  $this->showForm();
219  }
220 
228 
229  $replacement_params = [];
230  if ( $wgReplaceTextUser != null ) {
232  } else {
233  $user = $this->getUser();
234  }
235 
236  $replacement_params['user_id'] = $user->getId();
237  $replacement_params['target_str'] = $this->target;
238  $replacement_params['replacement_str'] = $this->replacement;
239  $replacement_params['use_regex'] = $this->use_regex;
240  $replacement_params['edit_summary'] = $this->msg(
241  'replacetext_editsummary',
242  $this->target, $this->replacement
243  )->inContentLanguage()->plain();
244  $replacement_params['create_redirect'] = false;
245  $replacement_params['watch_page'] = false;
246  $replacement_params['doAnnounce'] = $this->doAnnounce;
247 
248  $request = $this->getRequest();
249  foreach ( $request->getValues() as $key => $value ) {
250  if ( $key == 'create-redirect' && $value == '1' ) {
251  $replacement_params['create_redirect'] = true;
252  } elseif ( $key == 'watch-pages' && $value == '1' ) {
253  $replacement_params['watch_page'] = true;
254  }
255  }
256 
257  $jobs = [];
258  foreach ( $request->getValues() as $key => $value ) {
259  if ( $value == '1' && $key !== 'replace' && $key !== 'use_regex' ) {
260  if ( strpos( $key, 'move-' ) !== false ) {
261  $title = Title::newFromID( substr( $key, 5 ) );
262  $replacement_params['move_page'] = true;
263  } else {
264  $title = Title::newFromID( $key );
265  }
266  if ( $title !== null ) {
267  $jobs[] = new ReplaceTextJob( $title, $replacement_params );
268  }
269  }
270  }
271 
272  return $jobs;
273  }
274 
282  $titles_for_edit = [];
283 
285  $this->target,
286  $this->selected_namespaces,
287  $this->category,
288  $this->prefix,
289  $this->use_regex
290  );
291 
292  foreach ( $res as $row ) {
293  $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
294  if ( $title == null ) {
295  continue;
296  }
297  $context = $this->extractContext( $row->old_text, $this->target, $this->use_regex );
298  $titles_for_edit[] = [ $title, $context ];
299  }
300 
301  return $titles_for_edit;
302  }
303 
313  $titles_for_move = [];
314  $unmoveable_titles = [];
315 
316  $res = $this->getMatchingTitles(
317  $this->target,
318  $this->selected_namespaces,
319  $this->category,
320  $this->prefix,
321  $this->use_regex
322  );
323 
324  foreach ( $res as $row ) {
325  $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
326  if ( $title == null ) {
327  continue;
328  }
329  // See if this move can happen.
330  $cur_page_name = str_replace( '_', ' ', $row->page_title );
331 
332  if ( $this->use_regex ) {
333  $new_page_name =
334  preg_replace( "/" . $this->target . "/Uu", $this->replacement, $cur_page_name );
335  } else {
336  $new_page_name =
337  str_replace( $this->target, $this->replacement, $cur_page_name );
338  }
339 
340  $new_title = Title::makeTitleSafe( $row->page_namespace, $new_page_name );
341  $err = $title->isValidMoveOperation( $new_title );
342 
343  if ( $title->userCan( 'move' ) && !is_array( $err ) ) {
344  $titles_for_move[] = $title;
345  } else {
346  $unmoveable_titles[] = $title;
347  }
348  }
349 
350  return [ $titles_for_move, $unmoveable_titles ];
351  }
352 
362  function getAnyWarningMessageBeforeReplace( $titles_for_edit, $titles_for_move ) {
363  if ( $this->replacement === '' ) {
364  return $this->msg( 'replacetext_blankwarning' )->text();
365  } elseif ( $this->use_regex ) {
366  // If it's a regex, don't bother checking for existing
367  // pages - if the replacement string includes wildcards,
368  // it's a meaningless check.
369  return null;
370  } elseif ( count( $titles_for_edit ) > 0 ) {
372  $this->replacement,
373  $this->selected_namespaces,
374  $this->category,
375  $this->prefix,
376  $this->use_regex
377  );
378  $count = $res->numRows();
379  if ( $count > 0 ) {
380  return $this->msg( 'replacetext_warning' )->numParams( $count )
381  ->params( "<code><nowiki>{$this->replacement}</nowiki></code>" )->text();
382  }
383  } elseif ( count( $titles_for_move ) > 0 ) {
384  $res = $this->getMatchingTitles(
385  $this->replacement,
386  $this->selected_namespaces,
387  $this->category,
388  $this->prefix, $this->use_regex
389  );
390  $count = $res->numRows();
391  if ( $count > 0 ) {
392  return $this->msg( 'replacetext_warning' )->numParams( $count )
393  ->params( $this->replacement )->text();
394  }
395  }
396 
397  return null;
398  }
399 
403  function showForm( $warning_msg = null ) {
404  $out = $this->getOutput();
405 
406  $out->addHTML(
408  'form',
409  [
410  'id' => 'powersearch',
411  'action' => $this->getPageTitle()->getFullURL(),
412  'method' => 'post'
413  ]
414  ) . "\n" .
415  Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
416  Html::hidden( 'continue', 1 ) .
417  Html::hidden( 'token', $out->getUser()->getEditToken() )
418  );
419  if ( is_null( $warning_msg ) ) {
420  $out->addWikiMsg( 'replacetext_docu' );
421  } else {
422  $out->wrapWikiMsg(
423  "<div class=\"errorbox\">\n$1\n</div><br clear=\"both\" />",
424  $warning_msg
425  );
426  }
427 
428  $out->addHTML( '<table><tr><td style="vertical-align: top;">' );
429  $out->addWikiMsg( 'replacetext_originaltext' );
430  $out->addHTML( '</td><td>' );
431  // 'width: auto' style is needed to override MediaWiki's
432  // normal 'width: 100%', which causes the textarea to get
433  // zero width in IE
434  $out->addHTML(
435  Xml::textarea( 'target', $this->target, 100, 5, [ 'style' => 'width: auto;' ] )
436  );
437  $out->addHTML( '</td></tr><tr><td style="vertical-align: top;">' );
438  $out->addWikiMsg( 'replacetext_replacementtext' );
439  $out->addHTML( '</td><td>' );
440  $out->addHTML(
441  Xml::textarea( 'replacement', $this->replacement, 100, 5, [ 'style' => 'width: auto;' ] )
442  );
443  $out->addHTML( '</td></tr></table>' );
444 
445  // SQLite unfortunately lacks a REGEXP function or operator by
446  // default, so disable regex(p) searches for SQLite.
447  $dbr = wfGetDB( DB_REPLICA );
448  if ( $dbr->getType() != 'sqlite' ) {
449  $out->addHTML( Xml::tags( 'p', null,
451  $this->msg( 'replacetext_useregex' )->text(),
452  'use_regex', 'use_regex'
453  )
454  ) . "\n" .
455  Xml::element( 'p',
456  [ 'style' => 'font-style: italic' ],
457  $this->msg( 'replacetext_regexdocu' )->text()
458  )
459  );
460  }
461 
462  // The interface is heavily based on the one in Special:Search.
464  $tables = $this->namespaceTables( $namespaces );
465  $out->addHTML(
466  "<div class=\"mw-search-formheader\"></div>\n" .
467  "<fieldset id=\"mw-searchoptions\">\n" .
468  Xml::tags( 'h4', null, $this->msg( 'powersearch-ns' )->parse() )
469  );
470  // The ability to select/unselect groups of namespaces in the
471  // search interface exists only in some skins, like Vector -
472  // check for the presence of the 'powersearch-togglelabel'
473  // message to see if we can use this functionality here.
474  if ( $this->msg( 'powersearch-togglelabel' )->isDisabled() ) {
475  // do nothing
476  } else {
477  $out->addHTML(
479  'div',
480  [ 'id' => 'mw-search-togglebox' ]
481  )
482  );
483  }
484  $out->addHTML(
485  Xml::element( 'div', [ 'class' => 'divider' ], '', false ) .
486  "$tables\n</fieldset>"
487  );
488  // @todo FIXME: raw html messages
489  $category_search_label = $this->msg( 'replacetext_categorysearch' )->escaped();
490  $prefix_search_label = $this->msg( 'replacetext_prefixsearch' )->escaped();
491  $rcPage = SpecialPage::getTitleFor( 'Recentchanges' );
492  $rcPageName = $rcPage->getPrefixedText();
493  $out->addHTML(
494  "<fieldset id=\"mw-searchoptions\">\n" .
495  Xml::tags( 'h4', null, $this->msg( 'replacetext_optionalfilters' )->parse() ) .
496  Xml::element( 'div', [ 'class' => 'divider' ], '', false ) .
497  "<p>$category_search_label\n" .
498  Xml::input( 'category', 20, $this->category, [ 'type' => 'text' ] ) . '</p>' .
499  "<p>$prefix_search_label\n" .
500  Xml::input( 'prefix', 20, $this->prefix, [ 'type' => 'text' ] ) . '</p>' .
501  "</fieldset>\n" .
502  "<p>\n" .
504  $this->msg( 'replacetext_editpages' )->text(), 'edit_pages', 'edit_pages', true
505  ) . '<br />' .
507  $this->msg( 'replacetext_movepages' )->text(), 'move_pages', 'move_pages'
508  ) . '<br />' .
510  $this->msg( 'replacetext_announce', $rcPageName )->text(), 'doAnnounce', 'doAnnounce', true
511  ) .
512  "</p>\n" .
513  Xml::submitButton( $this->msg( 'replacetext_continue' )->text() ) .
514  Xml::closeElement( 'form' )
515  );
516  // Add Javascript specific to Special:Search
517  $out->addModules( 'mediawiki.special.search' );
518  }
519 
527  function namespaceTables( $namespaces, $rowsPerTable = 3 ) {
529  // Group namespaces into rows according to subject.
530  // Try not to make too many assumptions about namespace numbering.
531  $rows = [];
532  $tables = "";
533  foreach ( $namespaces as $ns => $name ) {
534  $subj = MWNamespace::getSubject( $ns );
535  if ( !array_key_exists( $subj, $rows ) ) {
536  $rows[$subj] = "";
537  }
538  $name = str_replace( '_', ' ', $name );
539  if ( '' == $name ) {
540  $name = $this->msg( 'blanknamespace' )->text();
541  }
542  $rows[$subj] .= Xml::openElement( 'td', [ 'style' => 'white-space: nowrap' ] ) .
543  Xml::checkLabel( $name, "ns{$ns}", "mw-search-ns{$ns}", in_array( $ns, $namespaces ) ) .
544  Xml::closeElement( 'td' ) . "\n";
545  }
546  $rows = array_values( $rows );
547  $numRows = count( $rows );
548  // Lay out namespaces in multiple floating two-column tables so they'll
549  // be arranged nicely while still accommodating different screen widths
550  // Float to the right on RTL wikis
551  $tableStyle = $wgContLang->isRTL() ?
552  'float: right; margin: 0 0 0em 1em' : 'float: left; margin: 0 1em 0em 0';
553  // Build the final HTML table...
554  for ( $i = 0; $i < $numRows; $i += $rowsPerTable ) {
555  $tables .= Xml::openElement( 'table', [ 'style' => $tableStyle ] );
556  for ( $j = $i; $j < $i + $rowsPerTable && $j < $numRows; $j++ ) {
557  $tables .= "<tr>\n" . $rows[$j] . "</tr>";
558  }
559  $tables .= Xml::closeElement( 'table' ) . "\n";
560  }
561  return $tables;
562  }
563 
569  function pageListForm( $titles_for_edit, $titles_for_move, $unmoveable_titles ) {
570  global $wgLang;
571 
572  $out = $this->getOutput();
573 
574  $formOpts = [
575  'id' => 'choose_pages',
576  'method' => 'post',
577  'action' => $this->getPageTitle()->getFullUrl()
578  ];
579  $out->addHTML(
580  Xml::openElement( 'form', $formOpts ) . "\n" .
581  Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
582  Html::hidden( 'target', $this->target ) .
583  Html::hidden( 'replacement', $this->replacement ) .
584  Html::hidden( 'use_regex', $this->use_regex ) .
585  Html::hidden( 'move_pages', $this->move_pages ) .
586  Html::hidden( 'edit_pages', $this->edit_pages ) .
587  Html::hidden( 'doAnnounce', $this->doAnnounce ) .
588  Html::hidden( 'replace', 1 ) .
589  Html::hidden( 'token', $out->getUser()->getEditToken() )
590  );
591 
592  foreach ( $this->selected_namespaces as $ns ) {
593  $out->addHTML( Html::hidden( 'ns' . $ns, 1 ) );
594  }
595 
596  $out->addModules( "ext.ReplaceText" );
597  $out->addModuleStyles( "ext.ReplaceTextStyles" );
598  // Needed for bolding of search term.
599  $out->addModuleStyles( "mediawiki.special.search.styles" );
600 
601  if ( count( $titles_for_edit ) > 0 ) {
602  $out->addWikiMsg(
603  'replacetext_choosepagesforedit',
604  "<code><nowiki>{$this->target}</nowiki></code>",
605  "<code><nowiki>{$this->replacement}</nowiki></code>",
606  $wgLang->formatNum( count( $titles_for_edit ) )
607  );
608 
609  foreach ( $titles_for_edit as $title_and_context ) {
613  list( $title, $context ) = $title_and_context;
614  $out->addHTML(
615  Xml::check( $title->getArticleID(), true ) .
617  " - <small>$context</small><br />\n"
618  );
619  }
620  $out->addHTML( '<br />' );
621  }
622 
623  if ( count( $titles_for_move ) > 0 ) {
624  $out->addWikiMsg(
625  'replacetext_choosepagesformove',
626  $this->target, $this->replacement, $wgLang->formatNum( count( $titles_for_move ) )
627  );
628  foreach ( $titles_for_move as $title ) {
629  $out->addHTML(
630  Xml::check( 'move-' . $title->getArticleID(), true ) .
631  ReplaceTextUtils::link( $title ) . "<br />\n"
632  );
633  }
634  $out->addHTML( '<br />' );
635  $out->addWikiMsg( 'replacetext_formovedpages' );
636  $rcPage = SpecialPage::getTitleFor( 'Recentchanges' );
637  $rcPageName = $rcPage->getPrefixedText();
638  $out->addHTML(
640  $this->msg( 'replacetext_savemovedpages' )->text(),
641  'create-redirect', 'create-redirect', true ) . "<br />\n" .
643  $this->msg( 'replacetext_watchmovedpages' )->text(),
644  'watch-pages', 'watch-pages', false ) . '<br />'
645  );
646  $out->addHTML( '<br />' );
647  }
648 
649  $out->addHTML(
650  "<br />\n" .
651  Xml::submitButton( $this->msg( 'replacetext_replace' )->text() ) . "\n"
652  );
653 
654  // Only show "invert selections" link if there are more than
655  // five pages.
656  if ( count( $titles_for_edit ) + count( $titles_for_move ) > 5 ) {
657  $buttonOpts = [
658  'type' => 'button',
659  'value' => $this->msg( 'replacetext_invertselections' )->text(),
660  'disabled' => true,
661  'id' => 'replacetext-invert',
662  'class' => 'mw-replacetext-invert'
663  ];
664 
665  $out->addHTML(
666  Xml::element( 'input', $buttonOpts )
667  );
668  }
669 
670  $out->addHTML( '</form>' );
671 
672  if ( count( $unmoveable_titles ) > 0 ) {
673  $out->addWikiMsg( 'replacetext_cannotmove', $wgLang->formatNum( count( $unmoveable_titles ) ) );
674  $text = "<ul>\n";
675  foreach ( $unmoveable_titles as $title ) {
676  $text .= "<li>" . ReplaceTextUtils::link( $title ) . "<br />\n";
677  }
678  $text .= "</ul>\n";
679  $out->addHTML( $text );
680  }
681  }
682 
692  function extractContext( $text, $target, $use_regex = false ) {
693  global $wgLang;
694 
695  $cw = $this->getUser()->getOption( 'contextchars', 40 );
696 
697  // Get all indexes
698  if ( $use_regex ) {
699  preg_match_all( "/$target/Uu", $text, $matches, PREG_OFFSET_CAPTURE );
700  } else {
701  $targetq = preg_quote( $target, '/' );
702  preg_match_all( "/$targetq/", $text, $matches, PREG_OFFSET_CAPTURE );
703  }
704 
705  $poss = [];
706  foreach ( $matches[0] as $_ ) {
707  $poss[] = $_[1];
708  }
709 
710  $cuts = [];
711  // @codingStandardsIgnoreStart
712  for ( $i = 0; $i < count( $poss ); $i++ ) {
713  // @codingStandardsIgnoreEnd
714  $index = $poss[$i];
715  $len = strlen( $target );
716 
717  // Merge to the next if possible
718  while ( isset( $poss[$i + 1] ) ) {
719  if ( $poss[$i + 1] < $index + $len + $cw * 2 ) {
720  $len += $poss[$i + 1] - $poss[$i];
721  $i++;
722  } else {
723  // Can't merge, exit the inner loop
724  break;
725  }
726  }
727  $cuts[] = [ $index, $len ];
728  }
729 
730  $context = '';
731  foreach ( $cuts as $_ ) {
732  list( $index, $len, ) = $_;
734  $wgLang->truncate( substr( $text, 0, $index ), - $cw, '...', false )
735  );
736  $snippet = $this->convertWhiteSpaceToHTML( substr( $text, $index, $len ) );
737  if ( $use_regex ) {
738  $targetStr = "/$target/Uu";
739  } else {
740  $targetq = preg_quote( $this->convertWhiteSpaceToHTML( $target ), '/' );
741  $targetStr = "/$targetq/i";
742  }
743  $context .= preg_replace( $targetStr, '<span class="searchmatch">\0</span>', $snippet );
744 
746  $wgLang->truncate( substr( $text, $index + $len ), $cw, '...', false )
747  );
748  }
749  return $context;
750  }
751 
752  private function convertWhiteSpaceToHTML( $msg ) {
753  $msg = htmlspecialchars( $msg );
754  $msg = preg_replace( '/^ /m', '&#160; ', $msg );
755  $msg = preg_replace( '/ $/m', ' &#160;', $msg );
756  $msg = preg_replace( '/ /', '&#160; ', $msg );
757  # $msg = str_replace( "\n", '<br />', $msg );
758  return $msg;
759  }
760 
761  private function getMatchingTitles( $str, $namespaces, $category, $prefix, $use_regex = false ) {
762  $dbr = wfGetDB( DB_REPLICA );
763 
764  $tables = [ 'page' ];
765  $vars = [ 'page_title', 'page_namespace' ];
766 
767  $str = str_replace( ' ', '_', $str );
768  if ( $use_regex ) {
769  $comparisonCond = ReplaceTextSearch::regexCond( $dbr, 'page_title', $str );
770  } else {
771  $any = $dbr->anyString();
772  $comparisonCond = 'page_title ' . $dbr->buildLike( $any, $str, $any );
773  }
774  $conds = [
775  $comparisonCond,
776  'page_namespace' => $namespaces,
777  ];
778 
781  $sort = [ 'ORDER BY' => 'page_namespace, page_title' ];
782 
783  return $dbr->select( $tables, $vars, $conds, __METHOD__, $sort );
784  }
785 
789  protected function getGroupName() {
790  return 'wiki';
791  }
792 }
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:629
SpecialReplaceText\doSpecialReplaceText
doSpecialReplaceText()
Do the actual display and logic of Special:ReplaceText.
Definition: SpecialReplaceText.php:91
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:244
SpecialPage\msg
msg( $key)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:747
SpecialReplaceText\extractContext
extractContext( $text, $target, $use_regex=false)
Extract context and highlights search text.
Definition: SpecialReplaceText.php:692
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
$tables
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist & $tables
Definition: hooks.txt:990
$context
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2604
$wgExternalStores
$wgExternalStores
External stores allow including content from non database sources following URL links.
Definition: DefaultSettings.php:2118
SpecialReplaceText\$target
$target
Definition: SpecialReplaceText.php:22
Xml\tags
static tags( $element, $attribs=null, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:131
SpecialReplaceText\execute
execute( $query)
Definition: SpecialReplaceText.php:46
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:676
ReplaceTextSearch\prefixCondition
static prefixCondition( $prefix, &$conds)
Definition: ReplaceTextSearch.php:83
SpecialReplaceText\$doAnnounce
$doAnnounce
Definition: SpecialReplaceText.php:30
SpecialReplaceText\pageListForm
pageListForm( $titles_for_edit, $titles_for_move, $unmoveable_titles)
Definition: SpecialReplaceText.php:569
captcha-old.count
count
Definition: captcha-old.py:249
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
SpecialReplaceText\$category
$category
Definition: SpecialReplaceText.php:25
SpecialReplaceText\$edit_pages
$edit_pages
Definition: SpecialReplaceText.php:27
SearchEngine\searchableNamespaces
static searchableNamespaces()
Make a list of searchable namespaces and their canonical names.
Definition: SearchEngine.php:641
SpecialReplaceText\$use_regex
$use_regex
Definition: SpecialReplaceText.php:24
$namespaces
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:934
Xml\textarea
static textarea( $name, $content, $cols=40, $rows=5, $attribs=[])
Shortcut for creating textareas.
Definition: Xml.php:636
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:591
SpecialPage\getTitleFor
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Definition: SpecialPage.php:82
$res
$res
Definition: database.txt:21
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:28
SpecialPage\getLanguage
getLanguage()
Shortcut to get user's language.
Definition: SpecialPage.php:706
ReplaceTextSearch\regexCond
static regexCond( $dbr, $column, $regex)
Definition: ReplaceTextSearch.php:103
SpecialReplaceText\__construct
__construct()
Definition: SpecialReplaceText.php:32
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:109
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
SpecialReplaceText\$prefix
$prefix
Definition: SpecialReplaceText.php:26
$dbr
$dbr
Definition: testCompression.php:50
SpecialReplaceText\showForm
showForm( $warning_msg=null)
Definition: SpecialReplaceText.php:403
ReplaceTextSearch\categoryCondition
static categoryCondition( $category, &$tables, &$conds)
Definition: ReplaceTextSearch.php:70
$query
null for the wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1591
ReplaceTextUtils\link
static link(Title $title, $text=null)
Shim for compatibility.
Definition: ReplaceTextUtils.php:31
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:934
SpecialReplaceText\getSelectedNamespaces
getSelectedNamespaces()
Definition: SpecialReplaceText.php:77
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2800
$matches
$matches
Definition: NoLocalSettings.php:24
ReplaceTextJob
Background job to replace text in a given page.
Definition: ReplaceTextJob.php:29
Xml\check
static check( $name, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox.
Definition: Xml.php:324
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:39
$wgLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as $wgLang
Definition: design.txt:56
SpecialPage\setHeaders
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
Definition: SpecialPage.php:484
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:686
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
$vars
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:2220
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:79
$wgCompressRevisions
$wgCompressRevisions
We can also compress text stored in the 'text' table.
Definition: DefaultSettings.php:2105
SpecialReplaceText\getTitlesForMoveAndUnmoveableTitles
getTitlesForMoveAndUnmoveableTitles()
Returns two lists: the set of titles that would be moved/renamed by the current text replacement,...
Definition: SpecialReplaceText.php:312
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
$sort
$sort
Definition: profileinfo.php:317
$request
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2604
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:774
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:562
$value
$value
Definition: styleTest.css.php:45
SpecialReplaceText\$replacement
$replacement
Definition: SpecialReplaceText.php:23
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:36
SpecialReplaceText\createJobsForTextReplacements
createJobsForTextReplacements()
Returns the set of MediaWiki jobs that will do all the actual replacements.
Definition: SpecialReplaceText.php:226
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:666
SpecialReplaceText\$selected_namespaces
$selected_namespaces
Definition: SpecialReplaceText.php:29
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:118
$rows
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction $rows
Definition: hooks.txt:2604
ReplaceTextSearch\doSearchQuery
static doSearchQuery( $search, $namespaces, $category, $prefix, $use_regex=false)
Definition: ReplaceTextSearch.php:34
JobQueueGroup\singleton
static singleton( $wiki=false)
Definition: JobQueueGroup.php:72
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
$link
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3005
true
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition: hooks.txt:1987
SpecialReplaceText\$move_pages
$move_pages
Definition: SpecialReplaceText.php:28
Xml\input
static input( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML text input field.
Definition: Xml.php:275
SpecialReplaceText\getMatchingTitles
getMatchingTitles( $str, $namespaces, $category, $prefix, $use_regex=false)
Definition: SpecialReplaceText.php:761
SpecialReplaceText
Definition: SpecialReplaceText.php:21
SpecialReplaceText\getAnyWarningMessageBeforeReplace
getAnyWarningMessageBeforeReplace( $titles_for_edit, $titles_for_move)
Get the warning message if the replacement string is either blank or found elsewhere on the wiki (sin...
Definition: SpecialReplaceText.php:362
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
SpecialReplaceText\getGroupName
getGroupName()
@inheritDoc
Definition: SpecialReplaceText.php:789
MWNamespace\getSubject
static getSubject( $index)
Get the subject namespace index for a given namespace Special namespaces (NS_MEDIA,...
Definition: MWNamespace.php:143
SpecialReplaceText\convertWhiteSpaceToHTML
convertWhiteSpaceToHTML( $msg)
Definition: SpecialReplaceText.php:752
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:416
SpecialReplaceText\namespaceTables
namespaceTables( $namespaces, $rowsPerTable=3)
Copied almost exactly from MediaWiki's SpecialSearch class, i.e.
Definition: SpecialReplaceText.php:527
$wgReplaceTextUser
$wgReplaceTextUser
Definition: ReplaceText.php:73
SpecialReplaceText\doesWrites
doesWrites()
@inheritDoc
Definition: SpecialReplaceText.php:39
SpecialReplaceText\getTitlesForEditingWithContext
getTitlesForEditingWithContext()
Returns the set of Titles whose contents would be modified by this replacement, along with the "searc...
Definition: SpecialReplaceText.php:281
Xml\submitButton
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button When $wgUseMediaWikiUIEverywhere is true it will ...
Definition: Xml.php:460
Xml\checkLabel
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition: Xml.php:420
$wgContLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the content language as $wgContLang
Definition: design.txt:56
$out
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:783