MediaWiki  1.33.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 
227  global $wgReplaceTextUser;
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 
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 
333  $title,
334  $this->target,
335  $this->replacement,
336  $this->use_regex
337  );
338 
339  $err = $title->isValidMoveOperation( $new_title );
340 
341  if ( $title->userCan( 'move' ) && !is_array( $err ) ) {
342  $titles_for_move[] = $title;
343  } else {
344  $unmoveable_titles[] = $title;
345  }
346  }
347 
348  return [ $titles_for_move, $unmoveable_titles ];
349  }
350 
360  function getAnyWarningMessageBeforeReplace( $titles_for_edit, $titles_for_move ) {
361  if ( $this->replacement === '' ) {
362  return $this->msg( 'replacetext_blankwarning' )->text();
363  } elseif ( $this->use_regex ) {
364  // If it's a regex, don't bother checking for existing
365  // pages - if the replacement string includes wildcards,
366  // it's a meaningless check.
367  return null;
368  } elseif ( count( $titles_for_edit ) > 0 ) {
370  $this->replacement,
371  $this->selected_namespaces,
372  $this->category,
373  $this->prefix,
374  $this->use_regex
375  );
376  $count = $res->numRows();
377  if ( $count > 0 ) {
378  return $this->msg( 'replacetext_warning' )->numParams( $count )
379  ->params( "<code><nowiki>{$this->replacement}</nowiki></code>" )->text();
380  }
381  } elseif ( count( $titles_for_move ) > 0 ) {
383  $this->replacement,
384  $this->selected_namespaces,
385  $this->category,
386  $this->prefix,
387  $this->use_regex
388  );
389  $count = $res->numRows();
390  if ( $count > 0 ) {
391  return $this->msg( 'replacetext_warning' )->numParams( $count )
392  ->params( $this->replacement )->text();
393  }
394  }
395 
396  return null;
397  }
398 
402  function showForm( $warning_msg = null ) {
403  $out = $this->getOutput();
404 
405  $out->addHTML(
407  'form',
408  [
409  'id' => 'powersearch',
410  'action' => $this->getPageTitle()->getFullURL(),
411  'method' => 'post'
412  ]
413  ) . "\n" .
414  Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
415  Html::hidden( 'continue', 1 ) .
416  Html::hidden( 'token', $out->getUser()->getEditToken() )
417  );
418  if ( is_null( $warning_msg ) ) {
419  $out->addWikiMsg( 'replacetext_docu' );
420  } else {
421  $out->wrapWikiMsg(
422  "<div class=\"errorbox\">\n$1\n</div><br clear=\"both\" />",
423  $warning_msg
424  );
425  }
426 
427  $out->addHTML( '<table><tr><td style="vertical-align: top;">' );
428  $out->addWikiMsg( 'replacetext_originaltext' );
429  $out->addHTML( '</td><td>' );
430  // 'width: auto' style is needed to override MediaWiki's
431  // normal 'width: 100%', which causes the textarea to get
432  // zero width in IE
433  $out->addHTML(
434  Xml::textarea( 'target', $this->target, 100, 5, [ 'style' => 'width: auto;' ] )
435  );
436  $out->addHTML( '</td></tr><tr><td style="vertical-align: top;">' );
437  $out->addWikiMsg( 'replacetext_replacementtext' );
438  $out->addHTML( '</td><td>' );
439  $out->addHTML(
440  Xml::textarea( 'replacement', $this->replacement, 100, 5, [ 'style' => 'width: auto;' ] )
441  );
442  $out->addHTML( '</td></tr></table>' );
443 
444  // MSSQL/SQLServer and SQLite unfortunately lack a REGEXP
445  // function or operator by default, so disable regex(p)
446  // searches for both these DB types.
447  $dbr = wfGetDB( DB_REPLICA );
448  if ( $dbr->getType() != 'sqlite' && $dbr->getType() != 'mssql' ) {
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(
478  Html::element(
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  $out->addModules( 'ext.ReplaceText' );
517  }
518 
526  function namespaceTables( $namespaces, $rowsPerTable = 3 ) {
527  global $wgContLang;
528  // Group namespaces into rows according to subject.
529  // Try not to make too many assumptions about namespace numbering.
530  $rows = [];
531  $tables = "";
532  foreach ( $namespaces as $ns => $name ) {
533  $subj = MWNamespace::getSubject( $ns );
534  if ( !array_key_exists( $subj, $rows ) ) {
535  $rows[$subj] = "";
536  }
537  $name = str_replace( '_', ' ', $name );
538  if ( '' == $name ) {
539  $name = $this->msg( 'blanknamespace' )->text();
540  }
541  $rows[$subj] .= Xml::openElement( 'td', [ 'style' => 'white-space: nowrap' ] ) .
542  Xml::checkLabel( $name, "ns{$ns}", "mw-search-ns{$ns}", in_array( $ns, $namespaces ) ) .
543  Xml::closeElement( 'td' ) . "\n";
544  }
545  $rows = array_values( $rows );
546  $numRows = count( $rows );
547  // Lay out namespaces in multiple floating two-column tables so they'll
548  // be arranged nicely while still accommodating different screen widths
549  // Float to the right on RTL wikis
550  $tableStyle = $wgContLang->isRTL() ?
551  'float: right; margin: 0 0 0em 1em' : 'float: left; margin: 0 1em 0em 0';
552  // Build the final HTML table...
553  for ( $i = 0; $i < $numRows; $i += $rowsPerTable ) {
554  $tables .= Xml::openElement( 'table', [ 'style' => $tableStyle ] );
555  for ( $j = $i; $j < $i + $rowsPerTable && $j < $numRows; $j++ ) {
556  $tables .= "<tr>\n" . $rows[$j] . "</tr>";
557  }
558  $tables .= Xml::closeElement( 'table' ) . "\n";
559  }
560  return $tables;
561  }
562 
568  function pageListForm( $titles_for_edit, $titles_for_move, $unmoveable_titles ) {
569  global $wgLang;
570 
571  $out = $this->getOutput();
572 
573  $formOpts = [
574  'id' => 'choose_pages',
575  'method' => 'post',
576  'action' => $this->getPageTitle()->getFullUrl()
577  ];
578  $out->addHTML(
579  Xml::openElement( 'form', $formOpts ) . "\n" .
580  Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) .
581  Html::hidden( 'target', $this->target ) .
582  Html::hidden( 'replacement', $this->replacement ) .
583  Html::hidden( 'use_regex', $this->use_regex ) .
584  Html::hidden( 'move_pages', $this->move_pages ) .
585  Html::hidden( 'edit_pages', $this->edit_pages ) .
586  Html::hidden( 'doAnnounce', $this->doAnnounce ) .
587  Html::hidden( 'replace', 1 ) .
588  Html::hidden( 'token', $out->getUser()->getEditToken() )
589  );
590 
591  foreach ( $this->selected_namespaces as $ns ) {
592  $out->addHTML( Html::hidden( 'ns' . $ns, 1 ) );
593  }
594 
595  $out->addModules( "ext.ReplaceText" );
596  $out->addModuleStyles( "ext.ReplaceTextStyles" );
597  // Needed for bolding of search term.
598  $out->addModuleStyles( "mediawiki.special.search.styles" );
599 
600  if ( count( $titles_for_edit ) > 0 ) {
601  $out->addWikiMsg(
602  'replacetext_choosepagesforedit',
603  "<code><nowiki>{$this->target}</nowiki></code>",
604  "<code><nowiki>{$this->replacement}</nowiki></code>",
605  $wgLang->formatNum( count( $titles_for_edit ) )
606  );
607 
608  foreach ( $titles_for_edit as $title_and_context ) {
612  list( $title, $context ) = $title_and_context;
613  $out->addHTML(
614  Xml::check( $title->getArticleID(), true ) .
616  " - <small>$context</small><br />\n"
617  );
618  }
619  $out->addHTML( '<br />' );
620  }
621 
622  if ( count( $titles_for_move ) > 0 ) {
623  $out->addWikiMsg(
624  'replacetext_choosepagesformove',
625  $this->target, $this->replacement, $wgLang->formatNum( count( $titles_for_move ) )
626  );
627  foreach ( $titles_for_move as $title ) {
628  $out->addHTML(
629  Xml::check( 'move-' . $title->getArticleID(), true ) .
630  ReplaceTextUtils::link( $title ) . "<br />\n"
631  );
632  }
633  $out->addHTML( '<br />' );
634  $out->addWikiMsg( 'replacetext_formovedpages' );
635  $rcPage = SpecialPage::getTitleFor( 'Recentchanges' );
636  $rcPageName = $rcPage->getPrefixedText();
637  $out->addHTML(
639  $this->msg( 'replacetext_savemovedpages' )->text(),
640  'create-redirect', 'create-redirect', true ) . "<br />\n" .
642  $this->msg( 'replacetext_watchmovedpages' )->text(),
643  'watch-pages', 'watch-pages', false ) . '<br />'
644  );
645  $out->addHTML( '<br />' );
646  }
647 
648  $out->addHTML(
649  "<br />\n" .
650  Xml::submitButton( $this->msg( 'replacetext_replace' )->text() ) . "\n"
651  );
652 
653  // Only show "invert selections" link if there are more than
654  // five pages.
655  if ( count( $titles_for_edit ) + count( $titles_for_move ) > 5 ) {
656  $buttonOpts = [
657  'type' => 'button',
658  'value' => $this->msg( 'replacetext_invertselections' )->text(),
659  'disabled' => true,
660  'id' => 'replacetext-invert',
661  'class' => 'mw-replacetext-invert'
662  ];
663 
664  $out->addHTML(
665  Xml::element( 'input', $buttonOpts )
666  );
667  }
668 
669  $out->addHTML( '</form>' );
670 
671  if ( count( $unmoveable_titles ) > 0 ) {
672  $out->addWikiMsg( 'replacetext_cannotmove', $wgLang->formatNum( count( $unmoveable_titles ) ) );
673  $text = "<ul>\n";
674  foreach ( $unmoveable_titles as $title ) {
675  $text .= "<li>" . ReplaceTextUtils::link( $title ) . "<br />\n";
676  }
677  $text .= "</ul>\n";
678  $out->addHTML( $text );
679  }
680  }
681 
691  function extractContext( $text, $target, $use_regex = false ) {
692  global $wgLang;
693 
694  $cw = $this->getUser()->getOption( 'contextchars', 40 );
695 
696  // Get all indexes
697  if ( $use_regex ) {
698  preg_match_all( "/$target/Uu", $text, $matches, PREG_OFFSET_CAPTURE );
699  } else {
700  $targetq = preg_quote( $target, '/' );
701  preg_match_all( "/$targetq/", $text, $matches, PREG_OFFSET_CAPTURE );
702  }
703 
704  $poss = [];
705  foreach ( $matches[0] as $_ ) {
706  $poss[] = $_[1];
707  }
708 
709  $cuts = [];
710  // @codingStandardsIgnoreStart
711  for ( $i = 0; $i < count( $poss ); $i++ ) {
712  // @codingStandardsIgnoreEnd
713  $index = $poss[$i];
714  $len = strlen( $target );
715 
716  // Merge to the next if possible
717  while ( isset( $poss[$i + 1] ) ) {
718  if ( $poss[$i + 1] < $index + $len + $cw * 2 ) {
719  $len += $poss[$i + 1] - $poss[$i];
720  $i++;
721  } else {
722  // Can't merge, exit the inner loop
723  break;
724  }
725  }
726  $cuts[] = [ $index, $len ];
727  }
728 
729  $context = '';
730  foreach ( $cuts as $_ ) {
731  list( $index, $len, ) = $_;
732  $contextBefore = substr( $text, 0, $index );
733  $contextAfter = substr( $text, $index + $len );
734  if ( !is_callable( [ $wgLang, 'truncateForDatabase' ] ) ) {
735  // Backwards compatibility code; remove once MW 1.30 is
736  // no longer supported.
737  $contextBefore =
738  $wgLang->truncate( $contextBefore, - $cw, '...', false );
739  $contextAfter =
740  $wgLang->truncate( $contextAfter, $cw, '...', false );
741  } else {
742  $contextBefore =
743  $wgLang->truncateForDatabase( $contextBefore, - $cw, '...', false );
744  $contextAfter =
745  $wgLang->truncateForDatabase( $contextAfter, $cw, '...', false );
746  }
747  $context .= $this->convertWhiteSpaceToHTML( $contextBefore );
748  $snippet = $this->convertWhiteSpaceToHTML( substr( $text, $index, $len ) );
749  if ( $use_regex ) {
750  $targetStr = "/$target/Uu";
751  } else {
752  $targetq = preg_quote( $this->convertWhiteSpaceToHTML( $target ), '/' );
753  $targetStr = "/$targetq/i";
754  }
755  $context .= preg_replace( $targetStr, '<span class="searchmatch">\0</span>', $snippet );
756 
757  $context .= $this->convertWhiteSpaceToHTML( $contextAfter );
758  }
759  return $context;
760  }
761 
762  private function convertWhiteSpaceToHTML( $msg ) {
763  $msg = htmlspecialchars( $msg );
764  $msg = preg_replace( '/^ /m', '&#160; ', $msg );
765  $msg = preg_replace( '/ $/m', ' &#160;', $msg );
766  $msg = preg_replace( '/ /', '&#160; ', $msg );
767  # $msg = str_replace( "\n", '<br />', $msg );
768  return $msg;
769  }
770 
774  protected function getGroupName() {
775  return 'wiki';
776  }
777 }
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:678
SpecialReplaceText\doSpecialReplaceText
doSpecialReplaceText()
Do the actual display and logic of Special:ReplaceText.
Definition: SpecialReplaceText.php:91
$user
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1476
SpecialPage\msg
msg( $key)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:796
SpecialReplaceText\extractContext
extractContext( $text, $target, $use_regex=false)
Extract context and highlights search text.
Definition: SpecialReplaceText.php:691
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
$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:2636
$wgExternalStores
$wgExternalStores
External stores allow including content from non database sources following URL links.
Definition: DefaultSettings.php:2204
SpecialReplaceText\$target
$target
Definition: SpecialReplaceText.php:22
SpecialReplaceText\execute
execute( $query)
Definition: SpecialReplaceText.php:46
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:725
SpecialReplaceText\$doAnnounce
$doAnnounce
Definition: SpecialReplaceText.php:30
SpecialReplaceText\pageListForm
pageListForm( $titles_for_edit, $titles_for_move, $unmoveable_titles)
Definition: SpecialReplaceText.php:568
captcha-old.count
count
Definition: captcha-old.py:249
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:740
SpecialReplaceText\$use_regex
$use_regex
Definition: SpecialReplaceText.php:24
$namespaces
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:925
$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 When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password 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:780
Xml\textarea
static textarea( $name, $content, $cols=40, $rows=5, $attribs=[])
Shortcut for creating textareas.
Definition: Xml.php:635
$tables
this hook is for auditing only 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:979
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:585
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
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:755
SpecialReplaceText\__construct
__construct()
Definition: SpecialReplaceText.php:32
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:108
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:402
$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:1588
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:925
SpecialReplaceText\getSelectedNamespaces
getSelectedNamespaces()
Definition: SpecialReplaceText.php:77
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2636
$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:875
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
SpecialPage\setHeaders
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
Definition: SpecialPage.php:531
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:735
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:78
$wgCompressRevisions
$wgCompressRevisions
We can also compress text stored in the 'text' table.
Definition: DefaultSettings.php:2191
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
$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:2636
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:604
$value
$value
Definition: styleTest.css.php:49
SpecialReplaceText\$replacement
$replacement
Definition: SpecialReplaceText.php:23
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:36
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:226
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:715
SpecialReplaceText\$selected_namespaces
$selected_namespaces
Definition: SpecialReplaceText.php:29
text
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
Definition: All_system_messages.txt:1267
ReplaceTextSearch\getReplacedTitle
static getReplacedTitle(Title $title, $search, $replacement, $regex)
Do a replacement on a title.
Definition: ReplaceTextSearch.php:176
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
$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:2636
ReplaceTextSearch\doSearchQuery
static doSearchQuery( $search, $namespaces, $category, $prefix, $use_regex=false)
Definition: ReplaceTextSearch.php:34
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:3053
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:1985
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:274
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:360
SpecialReplaceText\getGroupName
getGroupName()
@inheritDoc
Definition: SpecialReplaceText.php:774
MWNamespace\getSubject
static getSubject( $index)
Get the subject namespace index for a given namespace Special namespaces (NS_MEDIA,...
Definition: MWNamespace.php:144
SpecialReplaceText\convertWhiteSpaceToHTML
convertWhiteSpaceToHTML( $msg)
Definition: SpecialReplaceText.php:762
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:457
$wgContLang
$wgContLang
Definition: Setup.php:790
SpecialReplaceText\namespaceTables
namespaceTables( $namespaces, $rowsPerTable=3)
Copied almost exactly from MediaWiki's SpecialSearch class, i.e.
Definition: SpecialReplaceText.php:526
$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
ReplaceTextSearch\getMatchingTitles
static getMatchingTitles( $str, $namespaces, $category, $prefix, $use_regex=false)
Definition: ReplaceTextSearch.php:120
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