MediaWiki  1.34.0
SpecialReplaceText.php
Go to the documentation of this file.
1 <?php
22 
24  private $target;
25  private $replacement;
26  private $use_regex;
27  private $category;
28  private $prefix;
29  private $edit_pages;
30  private $move_pages;
32  private $doAnnounce;
33 
34  public function __construct() {
35  parent::__construct( 'ReplaceText', 'replacetext' );
36  }
37 
41  public function doesWrites() {
42  return true;
43  }
44 
48  function execute( $query ) {
50 
51  if ( !$this->getUser()->isAllowed( 'replacetext' ) ) {
52  throw new PermissionsError( 'replacetext' );
53  }
54 
55  $out = $this->getOutput();
56  // Replace Text can't be run with certain settings, due to the
57  // changes they make to the DB storage setup.
58  if ( $wgCompressRevisions ) {
59  $errorMsg = "Error: text replacements cannot be run if \$wgCompressRevisions is set to true.";
60  $out->addWikiTextAsContent( "<div class=\"errorbox\">$errorMsg</div>" );
61  return;
62  }
63  if ( !empty( $wgExternalStores ) ) {
64  $errorMsg = "Error: text replacements cannot be run if \$wgExternalStores is non-empty.";
65  $out->addWikiTextAsContent( "<div class=\"errorbox\">$errorMsg</div>" );
66  return;
67  }
68 
69  $this->setHeaders();
70  if ( !is_null( $out->getResourceLoader()->getModule( 'mediawiki.special' ) ) ) {
71  $out->addModuleStyles( 'mediawiki.special' );
72  }
73  $this->doSpecialReplaceText();
74  }
75 
79  function getSelectedNamespaces() {
80  if ( class_exists( MediaWikiServices::class ) ) {
81  // MW 1.27+
82  $all_namespaces = MediaWikiServices::getInstance()->getSearchEngineConfig()
83  ->searchableNamespaces();
84  } else {
86  $all_namespaces = SearchEngine::searchableNamespaces();
87  }
89  foreach ( $all_namespaces as $ns => $name ) {
90  if ( $this->getRequest()->getCheck( 'ns' . $ns ) ) {
91  $selected_namespaces[] = $ns;
92  }
93  }
94  return $selected_namespaces;
95  }
96 
100  function doSpecialReplaceText() {
101  $out = $this->getOutput();
102  $request = $this->getRequest();
103 
104  $this->target = $request->getText( 'target' );
105  $this->replacement = $request->getText( 'replacement' );
106  $this->use_regex = $request->getBool( 'use_regex' );
107  $this->category = $request->getText( 'category' );
108  $this->prefix = $request->getText( 'prefix' );
109  $this->edit_pages = $request->getBool( 'edit_pages' );
110  $this->move_pages = $request->getBool( 'move_pages' );
111  $this->doAnnounce = $request->getBool( 'doAnnounce' );
112  $this->selected_namespaces = $this->getSelectedNamespaces();
113 
114  if ( $request->getCheck( 'continue' ) && $this->target === '' ) {
115  $this->showForm( 'replacetext_givetarget' );
116  return;
117  }
118 
119  if ( $request->getCheck( 'replace' ) ) {
120 
121  // check for CSRF
122  $user = $this->getUser();
123  if ( !$user->matchEditToken( $request->getVal( 'token' ) ) ) {
124  $out->addWikiMsg( 'sessionfailure' );
125  return;
126  }
127 
128  $jobs = $this->createJobsForTextReplacements();
129  JobQueueGroup::singleton()->push( $jobs );
130 
131  $count = $this->getLanguage()->formatNum( count( $jobs ) );
132  $out->addWikiMsg(
133  'replacetext_success',
134  "<code><nowiki>{$this->target}</nowiki></code>",
135  "<code><nowiki>{$this->replacement}</nowiki></code>",
136  $count
137  );
138 
139  // Link back
140  $out->addHTML(
142  $this->getPageTitle(),
143  $this->msg( 'replacetext_return' )->text()
144  )
145  );
146  return;
147  }
148 
149  if ( $request->getCheck( 'target' ) ) {
150  // check for CSRF
151  $user = $this->getUser();
152  if ( !$user->matchEditToken( $request->getVal( 'token' ) ) ) {
153  $out->addWikiMsg( 'sessionfailure' );
154  return;
155  }
156 
157  // first, check that at least one namespace has been
158  // picked, and that either editing or moving pages
159  // has been selected
160  if ( count( $this->selected_namespaces ) == 0 ) {
161  $this->showForm( 'replacetext_nonamespace' );
162  return;
163  }
164  if ( !$this->edit_pages && !$this->move_pages ) {
165  $this->showForm( 'replacetext_editormove' );
166  return;
167  }
168 
169  // If user is replacing text within pages...
170  $titles_for_edit = $titles_for_move = $unmoveable_titles = [];
171  if ( $this->edit_pages ) {
172  $titles_for_edit = $this->getTitlesForEditingWithContext();
173  }
174  if ( $this->move_pages ) {
175  list( $titles_for_move, $unmoveable_titles ) = $this->getTitlesForMoveAndUnmoveableTitles();
176  }
177 
178  // If no results were found, check to see if a bad
179  // category name was entered.
180  if ( count( $titles_for_edit ) == 0 && count( $titles_for_move ) == 0 ) {
181  $bad_cat_name = false;
182 
183  if ( !empty( $this->category ) ) {
184  $category_title = Title::makeTitleSafe( NS_CATEGORY, $this->category );
185  if ( !$category_title->exists() ) {
186  $bad_cat_name = true;
187  }
188  }
189 
190  if ( $bad_cat_name ) {
191  $link = ReplaceTextUtils::link(
192  $category_title,
193  ucfirst( $this->category )
194  );
195  $out->addHTML(
196  $this->msg( 'replacetext_nosuchcategory' )->rawParams( $link )->escaped()
197  );
198  } else {
199  if ( $this->edit_pages ) {
200  $out->addWikiMsg(
201  'replacetext_noreplacement', "<code><nowiki>{$this->target}</nowiki></code>"
202  );
203  }
204 
205  if ( $this->move_pages ) {
206  $out->addWikiMsg( 'replacetext_nomove', "<code><nowiki>{$this->target}</nowiki></code>" );
207  }
208  }
209  // link back to starting form
210  $out->addHTML(
211  '<p>' .
213  $this->getPageTitle(),
214  $this->msg( 'replacetext_return' )->text()
215  )
216  . '</p>'
217  );
218  } else {
219  $warning_msg = $this->getAnyWarningMessageBeforeReplace( $titles_for_edit, $titles_for_move );
220  if ( !is_null( $warning_msg ) ) {
221  $out->addWikiTextAsContent(
222  "<div class=\"errorbox\">$warning_msg</div><br clear=\"both\" />"
223  );
224  }
225 
226  $this->pageListForm( $titles_for_edit, $titles_for_move, $unmoveable_titles );
227  }
228  return;
229  }
230 
231  // If we're still here, show the starting form.
232  $this->showForm();
233  }
234 
241  global $wgReplaceTextUser;
242 
243  $replacement_params = [];
244  if ( $wgReplaceTextUser != null ) {
246  } else {
247  $user = $this->getUser();
248  }
249 
250  $replacement_params['user_id'] = $user->getId();
251  $replacement_params['target_str'] = $this->target;
252  $replacement_params['replacement_str'] = $this->replacement;
253  $replacement_params['use_regex'] = $this->use_regex;
254  $replacement_params['edit_summary'] = $this->msg(
255  'replacetext_editsummary',
256  $this->target, $this->replacement
257  )->inContentLanguage()->plain();
258  $replacement_params['create_redirect'] = false;
259  $replacement_params['watch_page'] = false;
260  $replacement_params['doAnnounce'] = $this->doAnnounce;
261 
262  $request = $this->getRequest();
263  foreach ( $request->getValues() as $key => $value ) {
264  if ( $key == 'create-redirect' && $value == '1' ) {
265  $replacement_params['create_redirect'] = true;
266  } elseif ( $key == 'watch-pages' && $value == '1' ) {
267  $replacement_params['watch_page'] = true;
268  }
269  }
270 
271  $jobs = [];
272  foreach ( $request->getValues() as $key => $value ) {
273  if ( $value == '1' && $key !== 'replace' && $key !== 'use_regex' ) {
274  if ( strpos( $key, 'move-' ) !== false ) {
275  $title = Title::newFromID( substr( $key, 5 ) );
276  $replacement_params['move_page'] = true;
277  } else {
278  $title = Title::newFromID( $key );
279  }
280  if ( $title !== null ) {
281  $jobs[] = new ReplaceTextJob( $title, $replacement_params );
282  }
283  }
284  }
285 
286  return $jobs;
287  }
288 
296  $titles_for_edit = [];
297 
299  $this->target,
300  $this->selected_namespaces,
301  $this->category,
302  $this->prefix,
303  $this->use_regex
304  );
305 
306  foreach ( $res as $row ) {
307  $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
308  if ( $title == null ) {
309  continue;
310  }
311  $context = $this->extractContext( $row->old_text, $this->target, $this->use_regex );
312  $titles_for_edit[] = [ $title, $context ];
313  }
314 
315  return $titles_for_edit;
316  }
317 
327  $titles_for_move = [];
328  $unmoveable_titles = [];
329 
331  $this->target,
332  $this->selected_namespaces,
333  $this->category,
334  $this->prefix,
335  $this->use_regex
336  );
337 
338  foreach ( $res as $row ) {
339  $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
340  if ( $title == null ) {
341  continue;
342  }
343 
345  $title,
346  $this->target,
347  $this->replacement,
348  $this->use_regex
349  );
350 
351  $mvPage = new MovePage( $title, $new_title );
352  $moveStatus = $mvPage->isValidMove();
353  $permissionStatus = $mvPage->checkPermissions( $this->getUser(), null );
354 
355  if ( $permissionStatus->isOK() && $moveStatus->isOK() ) {
356  $titles_for_move[] = $title;
357  } else {
358  $unmoveable_titles[] = $title;
359  }
360  }
361 
362  return [ $titles_for_move, $unmoveable_titles ];
363  }
364 
374  function getAnyWarningMessageBeforeReplace( $titles_for_edit, $titles_for_move ) {
375  if ( $this->replacement === '' ) {
376  return $this->msg( 'replacetext_blankwarning' )->text();
377  } elseif ( $this->use_regex ) {
378  // If it's a regex, don't bother checking for existing
379  // pages - if the replacement string includes wildcards,
380  // it's a meaningless check.
381  return null;
382  } elseif ( count( $titles_for_edit ) > 0 ) {
384  $this->replacement,
385  $this->selected_namespaces,
386  $this->category,
387  $this->prefix,
388  $this->use_regex
389  );
390  $count = $res->numRows();
391  if ( $count > 0 ) {
392  return $this->msg( 'replacetext_warning' )->numParams( $count )
393  ->params( "<code><nowiki>{$this->replacement}</nowiki></code>" )->text();
394  }
395  } elseif ( count( $titles_for_move ) > 0 ) {
397  $this->replacement,
398  $this->selected_namespaces,
399  $this->category,
400  $this->prefix,
401  $this->use_regex
402  );
403  $count = $res->numRows();
404  if ( $count > 0 ) {
405  return $this->msg( 'replacetext_warning' )->numParams( $count )
406  ->params( $this->replacement )->text();
407  }
408  }
409 
410  return null;
411  }
412 
416  function showForm( $warning_msg = null ) {
417  $out = $this->getOutput();
418 
419  $out->addHTML(
421  'form',
422  [
423  'id' => 'powersearch',
424  'action' => $this->getPageTitle()->getFullURL(),
425  'method' => 'post'
426  ]
427  ) . "\n" .
428  Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
429  Html::hidden( 'continue', 1 ) .
430  Html::hidden( 'token', $out->getUser()->getEditToken() )
431  );
432  if ( is_null( $warning_msg ) ) {
433  $out->addWikiMsg( 'replacetext_docu' );
434  } else {
435  $out->wrapWikiMsg(
436  "<div class=\"errorbox\">\n$1\n</div><br clear=\"both\" />",
437  $warning_msg
438  );
439  }
440 
441  $out->addHTML( '<table><tr><td style="vertical-align: top;">' );
442  $out->addWikiMsg( 'replacetext_originaltext' );
443  $out->addHTML( '</td><td>' );
444  // 'width: auto' style is needed to override MediaWiki's
445  // normal 'width: 100%', which causes the textarea to get
446  // zero width in IE
447  $out->addHTML(
448  Xml::textarea( 'target', $this->target, 100, 5, [ 'style' => 'width: auto;' ] )
449  );
450  $out->addHTML( '</td></tr><tr><td style="vertical-align: top;">' );
451  $out->addWikiMsg( 'replacetext_replacementtext' );
452  $out->addHTML( '</td><td>' );
453  $out->addHTML(
454  Xml::textarea( 'replacement', $this->replacement, 100, 5, [ 'style' => 'width: auto;' ] )
455  );
456  $out->addHTML( '</td></tr></table>' );
457 
458  // MSSQL/SQLServer and SQLite unfortunately lack a REGEXP
459  // function or operator by default, so disable regex(p)
460  // searches for both these DB types.
461  $dbr = wfGetDB( DB_REPLICA );
462  if ( $dbr->getType() != 'sqlite' && $dbr->getType() != 'mssql' ) {
463  $out->addHTML( Xml::tags( 'p', null,
465  $this->msg( 'replacetext_useregex' )->text(),
466  'use_regex', 'use_regex'
467  )
468  ) . "\n" .
469  Xml::element( 'p',
470  [ 'style' => 'font-style: italic' ],
471  $this->msg( 'replacetext_regexdocu' )->text()
472  )
473  );
474  }
475 
476  // The interface is heavily based on the one in Special:Search.
477  if ( class_exists( MediaWikiServices::class ) ) {
478  // MW 1.27+
479  $namespaces = MediaWikiServices::getInstance()->getSearchEngineConfig()
480  ->searchableNamespaces();
481  } else {
483  $namespaces = SearchEngine::searchableNamespaces();
484  }
485  $tables = $this->namespaceTables( $namespaces );
486  $out->addHTML(
487  "<div class=\"mw-search-formheader\"></div>\n" .
488  "<fieldset id=\"mw-searchoptions\">\n" .
489  Xml::tags( 'h4', null, $this->msg( 'powersearch-ns' )->parse() )
490  );
491  // The ability to select/unselect groups of namespaces in the
492  // search interface exists only in some skins, like Vector -
493  // check for the presence of the 'powersearch-togglelabel'
494  // message to see if we can use this functionality here.
495  if ( $this->msg( 'powersearch-togglelabel' )->isDisabled() ) {
496  // do nothing
497  } else {
498  $out->addHTML(
499  Html::element(
500  'div',
501  [ 'id' => 'mw-search-togglebox' ]
502  )
503  );
504  }
505  $out->addHTML(
506  Xml::element( 'div', [ 'class' => 'divider' ], '', false ) .
507  "$tables\n</fieldset>"
508  );
509  // @todo FIXME: raw html messages
510  $category_search_label = $this->msg( 'replacetext_categorysearch' )->escaped();
511  $prefix_search_label = $this->msg( 'replacetext_prefixsearch' )->escaped();
512  $rcPage = SpecialPage::getTitleFor( 'Recentchanges' );
513  $rcPageName = $rcPage->getPrefixedText();
514  $out->addHTML(
515  "<fieldset id=\"mw-searchoptions\">\n" .
516  Xml::tags( 'h4', null, $this->msg( 'replacetext_optionalfilters' )->parse() ) .
517  Xml::element( 'div', [ 'class' => 'divider' ], '', false ) .
518  "<p>$category_search_label\n" .
519  Xml::input( 'category', 20, $this->category, [ 'type' => 'text' ] ) . '</p>' .
520  "<p>$prefix_search_label\n" .
521  Xml::input( 'prefix', 20, $this->prefix, [ 'type' => 'text' ] ) . '</p>' .
522  "</fieldset>\n" .
523  "<p>\n" .
525  $this->msg( 'replacetext_editpages' )->text(), 'edit_pages', 'edit_pages', true
526  ) . '<br />' .
528  $this->msg( 'replacetext_movepages' )->text(), 'move_pages', 'move_pages'
529  ) . '<br />' .
531  $this->msg( 'replacetext_announce', $rcPageName )->text(), 'doAnnounce', 'doAnnounce', true
532  ) .
533  "</p>\n" .
534  Xml::submitButton( $this->msg( 'replacetext_continue' )->text() ) .
535  Xml::closeElement( 'form' )
536  );
537  $out->addModules( 'ext.ReplaceText' );
538  }
539 
547  function namespaceTables( $namespaces, $rowsPerTable = 3 ) {
548  global $wgContLang;
549  // Group namespaces into rows according to subject.
550  // Try not to make too many assumptions about namespace numbering.
551  $rows = [];
552  $tables = "";
553  foreach ( $namespaces as $ns => $name ) {
554  $subj = MWNamespace::getSubject( $ns );
555  if ( !array_key_exists( $subj, $rows ) ) {
556  $rows[$subj] = "";
557  }
558  $name = str_replace( '_', ' ', $name );
559  if ( '' == $name ) {
560  $name = $this->msg( 'blanknamespace' )->text();
561  }
562  $rows[$subj] .= Xml::openElement( 'td', [ 'style' => 'white-space: nowrap' ] ) .
563  Xml::checkLabel( $name, "ns{$ns}", "mw-search-ns{$ns}", in_array( $ns, $namespaces ) ) .
564  Xml::closeElement( 'td' ) . "\n";
565  }
566  $rows = array_values( $rows );
567  $numRows = count( $rows );
568  // Lay out namespaces in multiple floating two-column tables so they'll
569  // be arranged nicely while still accommodating different screen widths
570  // Float to the right on RTL wikis
571  $tableStyle = $wgContLang->isRTL() ?
572  'float: right; margin: 0 0 0em 1em' : 'float: left; margin: 0 1em 0em 0';
573  // Build the final HTML table...
574  for ( $i = 0; $i < $numRows; $i += $rowsPerTable ) {
575  $tables .= Xml::openElement( 'table', [ 'style' => $tableStyle ] );
576  for ( $j = $i; $j < $i + $rowsPerTable && $j < $numRows; $j++ ) {
577  $tables .= "<tr>\n" . $rows[$j] . "</tr>";
578  }
579  $tables .= Xml::closeElement( 'table' ) . "\n";
580  }
581  return $tables;
582  }
583 
589  function pageListForm( $titles_for_edit, $titles_for_move, $unmoveable_titles ) {
590  global $wgLang;
591 
592  $out = $this->getOutput();
593 
594  $formOpts = [
595  'id' => 'choose_pages',
596  'method' => 'post',
597  'action' => $this->getPageTitle()->getFullUrl()
598  ];
599  $out->addHTML(
600  Xml::openElement( 'form', $formOpts ) . "\n" .
601  Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
602  Html::hidden( 'target', $this->target ) .
603  Html::hidden( 'replacement', $this->replacement ) .
604  Html::hidden( 'use_regex', $this->use_regex ) .
605  Html::hidden( 'move_pages', $this->move_pages ) .
606  Html::hidden( 'edit_pages', $this->edit_pages ) .
607  Html::hidden( 'doAnnounce', $this->doAnnounce ) .
608  Html::hidden( 'replace', 1 ) .
609  Html::hidden( 'token', $out->getUser()->getEditToken() )
610  );
611 
612  foreach ( $this->selected_namespaces as $ns ) {
613  $out->addHTML( Html::hidden( 'ns' . $ns, 1 ) );
614  }
615 
616  $out->addModules( "ext.ReplaceText" );
617  $out->addModuleStyles( "ext.ReplaceTextStyles" );
618  // Needed for bolding of search term.
619  $out->addModuleStyles( "mediawiki.special.search.styles" );
620 
621  if ( count( $titles_for_edit ) > 0 ) {
622  $out->addWikiMsg(
623  'replacetext_choosepagesforedit',
624  "<code><nowiki>{$this->target}</nowiki></code>",
625  "<code><nowiki>{$this->replacement}</nowiki></code>",
626  $wgLang->formatNum( count( $titles_for_edit ) )
627  );
628 
629  foreach ( $titles_for_edit as $title_and_context ) {
633  list( $title, $context ) = $title_and_context;
634  $out->addHTML(
635  Xml::check( $title->getArticleID(), true ) .
637  " - <small>$context</small><br />\n"
638  );
639  }
640  $out->addHTML( '<br />' );
641  }
642 
643  if ( count( $titles_for_move ) > 0 ) {
644  $out->addWikiMsg(
645  'replacetext_choosepagesformove',
646  $this->target, $this->replacement, $wgLang->formatNum( count( $titles_for_move ) )
647  );
648  foreach ( $titles_for_move as $title ) {
649  $out->addHTML(
650  Xml::check( 'move-' . $title->getArticleID(), true ) .
651  ReplaceTextUtils::link( $title ) . "<br />\n"
652  );
653  }
654  $out->addHTML( '<br />' );
655  $out->addWikiMsg( 'replacetext_formovedpages' );
656  $rcPage = SpecialPage::getTitleFor( 'Recentchanges' );
657  $rcPageName = $rcPage->getPrefixedText();
658  $out->addHTML(
660  $this->msg( 'replacetext_savemovedpages' )->text(),
661  'create-redirect', 'create-redirect', true ) . "<br />\n" .
663  $this->msg( 'replacetext_watchmovedpages' )->text(),
664  'watch-pages', 'watch-pages', false ) . '<br />'
665  );
666  $out->addHTML( '<br />' );
667  }
668 
669  $out->addHTML(
670  "<br />\n" .
671  Xml::submitButton( $this->msg( 'replacetext_replace' )->text() ) . "\n"
672  );
673 
674  // Only show "invert selections" link if there are more than
675  // five pages.
676  if ( count( $titles_for_edit ) + count( $titles_for_move ) > 5 ) {
677  $buttonOpts = [
678  'type' => 'button',
679  'value' => $this->msg( 'replacetext_invertselections' )->text(),
680  'disabled' => true,
681  'id' => 'replacetext-invert',
682  'class' => 'mw-replacetext-invert'
683  ];
684 
685  $out->addHTML(
686  Xml::element( 'input', $buttonOpts )
687  );
688  }
689 
690  $out->addHTML( '</form>' );
691 
692  if ( count( $unmoveable_titles ) > 0 ) {
693  $out->addWikiMsg( 'replacetext_cannotmove', $wgLang->formatNum( count( $unmoveable_titles ) ) );
694  $text = "<ul>\n";
695  foreach ( $unmoveable_titles as $title ) {
696  $text .= "<li>" . ReplaceTextUtils::link( $title ) . "<br />\n";
697  }
698  $text .= "</ul>\n";
699  $out->addHTML( $text );
700  }
701  }
702 
712  function extractContext( $text, $target, $use_regex = false ) {
713  global $wgLang;
714 
715  $cw = $this->getUser()->getOption( 'contextchars', 40 );
716 
717  // Get all indexes
718  if ( $use_regex ) {
719  preg_match_all( "/$target/Uu", $text, $matches, PREG_OFFSET_CAPTURE );
720  } else {
721  $targetq = preg_quote( $target, '/' );
722  preg_match_all( "/$targetq/", $text, $matches, PREG_OFFSET_CAPTURE );
723  }
724 
725  $poss = [];
726  foreach ( $matches[0] as $_ ) {
727  $poss[] = $_[1];
728  }
729 
730  $cuts = [];
731  // @codingStandardsIgnoreStart
732  for ( $i = 0; $i < count( $poss ); $i++ ) {
733  // @codingStandardsIgnoreEnd
734  $index = $poss[$i];
735  $len = strlen( $target );
736 
737  // Merge to the next if possible
738  while ( isset( $poss[$i + 1] ) ) {
739  if ( $poss[$i + 1] < $index + $len + $cw * 2 ) {
740  $len += $poss[$i + 1] - $poss[$i];
741  $i++;
742  } else {
743  // Can't merge, exit the inner loop
744  break;
745  }
746  }
747  $cuts[] = [ $index, $len ];
748  }
749 
750  $context = '';
751  foreach ( $cuts as $_ ) {
752  list( $index, $len, ) = $_;
753  $contextBefore = substr( $text, 0, $index );
754  $contextAfter = substr( $text, $index + $len );
755  if ( !is_callable( [ $wgLang, 'truncateForDatabase' ] ) ) {
756  // Backwards compatibility code; remove once MW 1.30 is
757  // no longer supported.
758  $contextBefore =
759  $wgLang->truncate( $contextBefore, - $cw, '...', false );
760  $contextAfter =
761  $wgLang->truncate( $contextAfter, $cw, '...', false );
762  } else {
763  $contextBefore =
764  $wgLang->truncateForDatabase( $contextBefore, - $cw, '...', false );
765  $contextAfter =
766  $wgLang->truncateForDatabase( $contextAfter, $cw, '...', false );
767  }
768  $context .= $this->convertWhiteSpaceToHTML( $contextBefore );
769  $snippet = $this->convertWhiteSpaceToHTML( substr( $text, $index, $len ) );
770  if ( $use_regex ) {
771  $targetStr = "/$target/Uu";
772  } else {
773  $targetq = preg_quote( $this->convertWhiteSpaceToHTML( $target ), '/' );
774  $targetStr = "/$targetq/i";
775  }
776  $context .= preg_replace( $targetStr, '<span class="searchmatch">\0</span>', $snippet );
777 
778  $context .= $this->convertWhiteSpaceToHTML( $contextAfter );
779  }
780  return $context;
781  }
782 
783  private function convertWhiteSpaceToHTML( $message ) {
784  $msg = htmlspecialchars( $message );
785  $msg = preg_replace( '/^ /m', '&#160; ', $msg );
786  $msg = preg_replace( '/ $/m', ' &#160;', $msg );
787  $msg = preg_replace( '/ /', '&#160; ', $msg );
788  # $msg = str_replace( "\n", '<br />', $msg );
789  return $msg;
790  }
791 
795  protected function getGroupName() {
796  return 'wiki';
797  }
798 }
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:672
SpecialReplaceText\doSpecialReplaceText
doSpecialReplaceText()
Do the actual display and logic of Special:ReplaceText.
Definition: SpecialReplaceText.php:100
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:792
SpecialReplaceText\extractContext
extractContext( $text, $target, $use_regex=false)
Extract context and highlights search text.
Definition: SpecialReplaceText.php:712
$wgExternalStores
$wgExternalStores
External stores allow including content from non database sources following URL links.
Definition: DefaultSettings.php:2213
SpecialReplaceText\$target
$target
Definition: SpecialReplaceText.php:24
SpecialReplaceText\execute
execute( $query)
Definition: SpecialReplaceText.php:48
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:719
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:117
SpecialReplaceText\$doAnnounce
$doAnnounce
Definition: SpecialReplaceText.php:32
SpecialReplaceText\pageListForm
pageListForm( $titles_for_edit, $titles_for_move, $unmoveable_titles)
Definition: SpecialReplaceText.php:589
SpecialReplaceText\$category
$category
Definition: SpecialReplaceText.php:27
SpecialReplaceText\$edit_pages
$edit_pages
Definition: SpecialReplaceText.php:29
true
return true
Definition: router.php:92
SpecialReplaceText\$use_regex
$use_regex
Definition: SpecialReplaceText.php:26
Xml\textarea
static textarea( $name, $content, $cols=40, $rows=5, $attribs=[])
Shortcut for creating textareas.
Definition: Xml.php:635
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:515
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:83
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:30
SpecialPage\getLanguage
getLanguage()
Shortcut to get user's language.
Definition: SpecialPage.php:749
$res
$res
Definition: testCompression.php:52
SpecialReplaceText\__construct
__construct()
Definition: SpecialReplaceText.php:34
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:108
SpecialReplaceText\$prefix
$prefix
Definition: SpecialReplaceText.php:28
$dbr
$dbr
Definition: testCompression.php:50
SpecialReplaceText\showForm
showForm( $warning_msg=null)
Definition: SpecialReplaceText.php:416
ReplaceTextUtils\link
static link(Title $title, $text=null)
Shim for compatibility.
Definition: ReplaceTextUtils.php:31
SpecialReplaceText\getSelectedNamespaces
getSelectedNamespaces()
Definition: SpecialReplaceText.php:79
MovePage
Handles the backend logic of moving a page from one title to another.
Definition: MovePage.php:36
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2575
$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:323
$wgLang
$wgLang
Definition: Setup.php:881
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
$title
$title
Definition: testCompression.php:34
SpecialPage\setHeaders
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
Definition: SpecialPage.php:537
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:729
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:74
$wgCompressRevisions
$wgCompressRevisions
We can also compress text stored in the 'text' table.
Definition: DefaultSettings.php:2200
SpecialReplaceText\getTitlesForMoveAndUnmoveableTitles
getTitlesForMoveAndUnmoveableTitles()
Returns two lists: the set of titles that would be moved/renamed by the current text replacement,...
Definition: SpecialReplaceText.php:326
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:613
SpecialReplaceText\$replacement
$replacement
Definition: SpecialReplaceText.php:25
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:37
Xml\tags
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:130
SpecialReplaceText\createJobsForTextReplacements
createJobsForTextReplacements()
Returns the set of MediaWiki jobs that will do all the actual replacements.
Definition: SpecialReplaceText.php:240
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:709
SpecialReplaceText\$selected_namespaces
$selected_namespaces
Definition: SpecialReplaceText.php:31
$context
$context
Definition: load.php:45
ReplaceTextSearch\getReplacedTitle
static getReplacedTitle(Title $title, $search, $replacement, $regex)
Do a replacement on a title.
Definition: ReplaceTextSearch.php:178
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:117
JobQueueGroup\singleton
static singleton( $domain=false)
Definition: JobQueueGroup.php:70
ReplaceTextSearch\doSearchQuery
static doSearchQuery( $search, $namespaces, $category, $prefix, $use_regex=false)
Definition: ReplaceTextSearch.php:34
SpecialReplaceText\$move_pages
$move_pages
Definition: SpecialReplaceText.php:30
Xml\input
static input( $name, $size=false, $value=false, $attribs=[])
Convenience function to build an HTML text input field.
Definition: Xml.php:274
SpecialReplaceText
Definition: SpecialReplaceText.php:23
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:374
SpecialReplaceText\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: SpecialReplaceText.php:795
MWNamespace\getSubject
static getSubject( $index)
Get the subject namespace index for a given namespace Special namespaces (NS_MEDIA,...
Definition: MWNamespace.php:76
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:467
$wgContLang
$wgContLang
Definition: Setup.php:801
SpecialReplaceText\namespaceTables
namespaceTables( $namespaces, $rowsPerTable=3)
Copied almost exactly from MediaWiki's SpecialSearch class, i.e.
Definition: SpecialReplaceText.php:547
$wgReplaceTextUser
$wgReplaceTextUser
Definition: ReplaceText.php:73
SpecialReplaceText\doesWrites
doesWrites()
Indicates whether this special page may perform database writes.bool 1.27
Definition: SpecialReplaceText.php:41
SpecialReplaceText\getTitlesForEditingWithContext
getTitlesForEditingWithContext()
Returns the set of Titles whose contents would be modified by this replacement, along with the "searc...
Definition: SpecialReplaceText.php:295
ReplaceTextSearch\getMatchingTitles
static getMatchingTitles( $str, $namespaces, $category, $prefix, $use_regex=false)
Definition: ReplaceTextSearch.php:122
SpecialReplaceText\convertWhiteSpaceToHTML
convertWhiteSpaceToHTML( $message)
Definition: SpecialReplaceText.php:783
Xml\submitButton
static submitButton( $value, $attribs=[])
Convenience function to build an HTML submit button When $wgUseMediaWikiUIEverywhere is true it will ...
Definition: Xml.php:459
Xml\checkLabel
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition: Xml.php:419