MediaWiki  1.32.5
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(
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 ) {
528  global $wgContLang;
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, ) = $_;
733  $contextBefore = substr( $text, 0, $index );
734  $contextAfter = substr( $text, $index + $len );
735  if ( !is_callable( [ $wgLang, 'truncateForDatabase' ] ) ) {
736  // Backwards compatibility code; remove once MW 1.30 is
737  // no longer supported.
738  $contextBefore =
739  $wgLang->truncate( $contextBefore, - $cw, '...', false );
740  $contextAfter =
741  $wgLang->truncate( $contextAfter, $cw, '...', false );
742  } else {
743  $contextBefore =
744  $wgLang->truncateForDatabase( $contextBefore, - $cw, '...', false );
745  $contextAfter =
746  $wgLang->truncateForDatabase( $contextAfter, $cw, '...', false );
747  }
748  $context .= $this->convertWhiteSpaceToHTML( $contextBefore );
749  $snippet = $this->convertWhiteSpaceToHTML( substr( $text, $index, $len ) );
750  if ( $use_regex ) {
751  $targetStr = "/$target/Uu";
752  } else {
753  $targetq = preg_quote( $this->convertWhiteSpaceToHTML( $target ), '/' );
754  $targetStr = "/$targetq/i";
755  }
756  $context .= preg_replace( $targetStr, '<span class="searchmatch">\0</span>', $snippet );
757 
758  $context .= $this->convertWhiteSpaceToHTML( $contextAfter );
759  }
760  return $context;
761  }
762 
763  private function convertWhiteSpaceToHTML( $msg ) {
764  $msg = htmlspecialchars( $msg );
765  $msg = preg_replace( '/^ /m', '&#160; ', $msg );
766  $msg = preg_replace( '/ $/m', ' &#160;', $msg );
767  $msg = preg_replace( '/ /', '&#160; ', $msg );
768  # $msg = str_replace( "\n", '<br />', $msg );
769  return $msg;
770  }
771 
775  protected function getGroupName() {
776  return 'wiki';
777  }
778 }
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
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:796
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
$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:2683
$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:569
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:752
SpecialReplaceText\$use_regex
$use_regex
Definition: SpecialReplaceText.php:24
$namespaces
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:964
Xml\textarea
static textarea( $name, $content, $cols=40, $rows=5, $attribs=[])
Shortcut for creating textareas.
Definition: Xml.php:637
$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:1018
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:592
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:110
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:1627
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:964
SpecialReplaceText\getSelectedNamespaces
getSelectedNamespaces()
Definition: SpecialReplaceText.php:77
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2693
$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:325
$wgLang
$wgLang
Definition: Setup.php:910
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:2683
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:795
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:573
$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:132
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:119
JobQueueGroup\singleton
static singleton( $domain=false)
Definition: JobQueueGroup.php:69
$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:2683
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:3098
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:2044
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:276
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
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:232
SpecialReplaceText\getGroupName
getGroupName()
@inheritDoc
Definition: SpecialReplaceText.php:775
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:763
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:427
$wgContLang
$wgContLang
Definition: Setup.php:809
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
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:461
Xml\checkLabel
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition: Xml.php:421
$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:813