MediaWiki  master
WikitextContent.php
Go to the documentation of this file.
1 <?php
30 
37  private $redirectTargetAndText = null;
38 
43  private $hadSignature = false;
44 
48  private $previousParseStackTrace = null;
49 
50  public function __construct( $text ) {
51  parent::__construct( $text, CONTENT_MODEL_WIKITEXT );
52  }
53 
61  public function getSection( $sectionId ) {
62  $text = $this->getText();
63  $sect = MediaWikiServices::getInstance()->getParser()
64  ->getSection( $text, $sectionId, false );
65 
66  if ( $sect === false ) {
67  return false;
68  } else {
69  return new static( $sect );
70  }
71  }
72 
83  public function replaceSection( $sectionId, Content $with, $sectionTitle = '' ) {
84  $myModelId = $this->getModel();
85  $sectionModelId = $with->getModel();
86 
87  if ( $sectionModelId != $myModelId ) {
88  throw new MWException( "Incompatible content model for section: " .
89  "document uses $myModelId but " .
90  "section uses $sectionModelId." );
91  }
93  '@phan-var self $with';
94 
95  $oldtext = $this->getText();
96  $text = $with->getText();
97 
98  if ( strval( $sectionId ) === '' ) {
99  return $with; # XXX: copy first?
100  }
101 
102  if ( $sectionId === 'new' ) {
103  # Inserting a new section
104  $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' )
105  ->plaintextParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : '';
106  if ( Hooks::run( 'PlaceNewSection', [ $this, $oldtext, $subject, &$text ] ) ) {
107  $text = strlen( trim( $oldtext ) ) > 0
108  ? "{$oldtext}\n\n{$subject}{$text}"
109  : "{$subject}{$text}";
110  }
111  } else {
112  # Replacing an existing section; roll out the big guns
113  $text = MediaWikiServices::getInstance()->getParser()
114  ->replaceSection( $oldtext, $sectionId, $text );
115  }
116 
117  $newContent = new static( $text );
118 
119  return $newContent;
120  }
121 
130  public function addSectionHeader( $header ) {
131  $text = wfMessage( 'newsectionheaderdefaultlevel' )
132  ->rawParams( $header )->inContentLanguage()->text();
133  $text .= "\n\n";
134  $text .= $this->getText();
135 
136  return new static( $text );
137  }
138 
149  public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
150  $text = $this->getText();
151 
152  $parser = MediaWikiServices::getInstance()->getParser();
153  $pst = $parser->preSaveTransform( $text, $title, $user, $popts );
154 
155  if ( $text === $pst ) {
156  return $this;
157  }
158 
159  $ret = new static( $pst );
160 
161  if ( $parser->getOutput()->getFlag( 'user-signature' ) ) {
162  $ret->hadSignature = true;
163  }
164 
165  return $ret;
166  }
167 
178  public function preloadTransform( Title $title, ParserOptions $popts, $params = [] ) {
179  $text = $this->getText();
180  $plt = MediaWikiServices::getInstance()->getParser()
181  ->getPreloadText( $text, $title, $popts, $params );
182 
183  return new static( $plt );
184  }
185 
195  protected function getRedirectTargetAndText() {
196  global $wgMaxRedirects;
197 
198  if ( $this->redirectTargetAndText !== null ) {
200  }
201 
202  if ( $wgMaxRedirects < 1 ) {
203  // redirects are disabled, so quit early
204  $this->redirectTargetAndText = [ null, $this->getText() ];
206  }
207 
208  $redir = MediaWikiServices::getInstance()->getMagicWordFactory()->get( 'redirect' );
209  $text = ltrim( $this->getText() );
210  if ( $redir->matchStartAndRemove( $text ) ) {
211  // Extract the first link and see if it's usable
212  // Ensure that it really does come directly after #REDIRECT
213  // Some older redirects included a colon, so don't freak about that!
214  $m = [];
215  if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}\s*!', $text, $m ) ) {
216  // Strip preceding colon used to "escape" categories, etc.
217  // and URL-decode links
218  if ( strpos( $m[1], '%' ) !== false ) {
219  // Match behavior of inline link parsing here;
220  $m[1] = rawurldecode( ltrim( $m[1], ':' ) );
221  }
222  $title = Title::newFromText( $m[1] );
223  // If the title is a redirect to bad special pages or is invalid, return null
224  if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
225  $this->redirectTargetAndText = [ null, $this->getText() ];
227  }
228 
229  $this->redirectTargetAndText = [ $title, substr( $text, strlen( $m[0] ) ) ];
231  }
232  }
233 
234  $this->redirectTargetAndText = [ null, $this->getText() ];
236  }
237 
245  public function getRedirectTarget() {
246  list( $title, ) = $this->getRedirectTargetAndText();
247 
248  return $title;
249  }
250 
263  public function updateRedirect( Title $target ) {
264  if ( !$this->isRedirect() ) {
265  return $this;
266  }
267 
268  # Fix the text
269  # Remember that redirect pages can have categories, templates, etc.,
270  # so the regex has to be fairly general
271  $newText = preg_replace( '/ \[ \[ [^\]]* \] \] /x',
272  '[[' . $target->getFullText() . ']]',
273  $this->getText(), 1 );
274 
275  return new static( $newText );
276  }
277 
289  public function isCountable( $hasLinks = null, Title $title = null ) {
290  global $wgArticleCountMethod;
291 
292  if ( $this->isRedirect() ) {
293  return false;
294  }
295 
296  if ( $wgArticleCountMethod === 'link' ) {
297  if ( $hasLinks === null ) { # not known, find out
298  if ( !$title ) {
300  $title = $context->getTitle();
301  }
302 
303  $po = $this->getParserOutput( $title, null, null, false );
304  $links = $po->getLinks();
305  $hasLinks = !empty( $links );
306  }
307 
308  return $hasLinks;
309  }
310 
311  return true;
312  }
313 
318  public function getTextForSummary( $maxlength = 250 ) {
319  $truncatedtext = parent::getTextForSummary( $maxlength );
320 
321  # clean up unfinished links
322  # XXX: make this optional? wasn't there in autosummary, but required for
323  # deletion summary.
324  $truncatedtext = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $truncatedtext );
325 
326  return $truncatedtext;
327  }
328 
341  protected function fillParserOutput( Title $title, $revId,
342  ParserOptions $options, $generateHtml, ParserOutput &$output
343  ) {
344  $stackTrace = ( new RuntimeException() )->getTraceAsString();
345  if ( $this->previousParseStackTrace ) {
346  // NOTE: there may be legitimate changes to re-parse the same WikiText content,
347  // e.g. if predicted revision ID for the REVISIONID magic word mismatched.
348  // But that should be rare.
349  $logger = LoggerFactory::getInstance( 'DuplicateParse' );
350  $logger->debug(
351  __METHOD__ . ': Possibly redundant parse!',
352  [
353  'title' => $title->getPrefixedDBkey(),
354  'rev' => $revId,
355  'options-hash' => $options->optionsHash(
357  $title
358  ),
359  'trace' => $stackTrace,
360  'previous-trace' => $this->previousParseStackTrace,
361  ]
362  );
363  }
364  $this->previousParseStackTrace = $stackTrace;
365 
366  list( $redir, $text ) = $this->getRedirectTargetAndText();
367  $output = MediaWikiServices::getInstance()->getParser()
368  ->parse( $text, $title, $options, true, true, $revId );
369 
370  // Add redirect indicator at the top
371  if ( $redir ) {
372  // Make sure to include the redirect link in pagelinks
373  $output->addLink( $redir );
374  if ( $generateHtml ) {
375  $chain = $this->getRedirectChain();
376  $output->setText(
377  Article::getRedirectHeaderHtml( $title->getPageLanguage(), $chain, false ) .
378  $output->getRawText()
379  );
380  $output->addModuleStyles( 'mediawiki.action.view.redirectPage' );
381  }
382  }
383 
384  // Pass along user-signature flag
385  if ( $this->hadSignature ) {
386  $output->setFlag( 'user-signature' );
387  }
388  }
389 
393  protected function getHtml() {
394  throw new MWException(
395  "getHtml() not implemented for wikitext. "
396  . "Use getParserOutput()->getText()."
397  );
398  }
399 
409  public function matchMagicWord( MagicWord $word ) {
410  return $word->match( $this->getText() );
411  }
412 
413 }
$wgArticleCountMethod
Method used to determine if a page in a content namespace should be counted as a valid article...
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:215
$context
Definition: load.php:45
addModuleStyles( $modules)
setText( $text)
getRedirectTargetAndText()
Extract the redirect target and the remaining text on the page.
optionsHash( $forOptions, $title=null)
Generate a hash string with the values set on these ParserOptions for the keys given in the array...
bool $hadSignature
Tracks if the parser set the user-signature flag when creating this content, which would make it expi...
getText()
Returns the text represented by this Content object, as a string.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
updateRedirect(Title $target)
This implementation replaces the first link on the page with the given new target if this Content obj...
getRedirectTarget()
Implement redirect extraction for wikitext.
preloadTransform(Title $title, ParserOptions $popts, $params=[])
Returns a Content object with preload transformations applied (or this object if no transformations a...
static getMain()
Get the RequestContext object associated with the main request.
$wgMaxRedirects
Max number of redirects to follow when resolving redirects.
setFlag( $flag)
Attach a flag to the output so that it can be checked later to handle special cases.
addSectionHeader( $header)
Returns a new WikitextContent object with the given section heading prepended.
array null $previousParseStackTrace
Stack trace of the previous parse.
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1713
match( $text)
Returns true if the text contains the word.
Definition: MagicWord.php:300
matchMagicWord(MagicWord $word)
This implementation calls $word->match() on the this TextContent object&#39;s text.
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with &#39;#&#39;)
Definition: Title.php:1882
getTextForSummary( $maxlength=250)
$header
Content object for wiki text pages.
replaceSection( $sectionId, Content $with, $sectionTitle='')
getRawText()
Get the cacheable text with <mw:editsection> markers still in it.
static allCacheVaryingOptions()
Return all option keys that vary the options hash.
preSaveTransform(Title $title, User $user, ParserOptions $popts)
Returns a Content object with pre-save transformations applied using Parser::preSaveTransform().
getSection( $sectionId)
getPageLanguage()
Get the language in which the content of this page is written in wikitext.
Definition: Title.php:4628
addLink(Title $title, $id=null)
Record a local or interwiki inline link for saving in future link tables.
isCountable( $hasLinks=null, Title $title=null)
Returns true if this content is not a redirect, and this content&#39;s text is countable according to the...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
getModel()
Returns the ID of the content model used by this Content object.
getParserOutput(Title $title, $revId=null, ParserOptions $options=null, $generateHtml=true)
Returns a ParserOutput object containing information derived from this content.
fillParserOutput(Title $title, $revId, ParserOptions $options, $generateHtml, ParserOutput &$output)
Returns a ParserOutput object resulting from parsing the content&#39;s text using the global Parser servi...
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
getPrefixedDBkey()
Get the prefixed database key form.
Definition: Title.php:1846
This class encapsulates "magic words" such as "#redirect", NOTOC, etc.
Definition: MagicWord.php:57
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319