PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/includes/content/WikitextContent.php

https://gitlab.com/link233/bootmw
PHP | 371 lines | 164 code | 48 blank | 159 comment | 24 complexity | 349ca37376ef104461c152852fc76574 MD5 | raw file
  1. <?php
  2. /**
  3. * Content object for wiki text pages.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @since 1.21
  21. *
  22. * @file
  23. * @ingroup Content
  24. *
  25. * @author Daniel Kinzler
  26. */
  27. /**
  28. * Content object for wiki text pages.
  29. *
  30. * @ingroup Content
  31. */
  32. class WikitextContent extends TextContent {
  33. private $redirectTargetAndText = null;
  34. public function __construct( $text ) {
  35. parent::__construct( $text, CONTENT_MODEL_WIKITEXT );
  36. }
  37. /**
  38. * @param string|number $sectionId
  39. *
  40. * @return Content|bool|null
  41. *
  42. * @see Content::getSection()
  43. */
  44. public function getSection( $sectionId ) {
  45. global $wgParser;
  46. $text = $this->getNativeData();
  47. $sect = $wgParser->getSection( $text, $sectionId, false );
  48. if ( $sect === false ) {
  49. return false;
  50. } else {
  51. return new static( $sect );
  52. }
  53. }
  54. /**
  55. * @param string|number|null|bool $sectionId
  56. * @param Content $with
  57. * @param string $sectionTitle
  58. *
  59. * @throws MWException
  60. * @return Content
  61. *
  62. * @see Content::replaceSection()
  63. */
  64. public function replaceSection( $sectionId, Content $with, $sectionTitle = '' ) {
  65. $myModelId = $this->getModel();
  66. $sectionModelId = $with->getModel();
  67. if ( $sectionModelId != $myModelId ) {
  68. throw new MWException( "Incompatible content model for section: " .
  69. "document uses $myModelId but " .
  70. "section uses $sectionModelId." );
  71. }
  72. $oldtext = $this->getNativeData();
  73. $text = $with->getNativeData();
  74. if ( strval( $sectionId ) === '' ) {
  75. return $with; # XXX: copy first?
  76. }
  77. if ( $sectionId === 'new' ) {
  78. # Inserting a new section
  79. $subject = $sectionTitle ? wfMessage( 'newsectionheaderdefaultlevel' )
  80. ->rawParams( $sectionTitle )->inContentLanguage()->text() . "\n\n" : '';
  81. if ( Hooks::run( 'PlaceNewSection', [ $this, $oldtext, $subject, &$text ] ) ) {
  82. $text = strlen( trim( $oldtext ) ) > 0
  83. ? "{$oldtext}\n\n{$subject}{$text}"
  84. : "{$subject}{$text}";
  85. }
  86. } else {
  87. # Replacing an existing section; roll out the big guns
  88. global $wgParser;
  89. $text = $wgParser->replaceSection( $oldtext, $sectionId, $text );
  90. }
  91. $newContent = new static( $text );
  92. return $newContent;
  93. }
  94. /**
  95. * Returns a new WikitextContent object with the given section heading
  96. * prepended.
  97. *
  98. * @param string $header
  99. *
  100. * @return Content
  101. */
  102. public function addSectionHeader( $header ) {
  103. $text = wfMessage( 'newsectionheaderdefaultlevel' )
  104. ->rawParams( $header )->inContentLanguage()->text();
  105. $text .= "\n\n";
  106. $text .= $this->getNativeData();
  107. return new static( $text );
  108. }
  109. /**
  110. * Returns a Content object with pre-save transformations applied using
  111. * Parser::preSaveTransform().
  112. *
  113. * @param Title $title
  114. * @param User $user
  115. * @param ParserOptions $popts
  116. *
  117. * @return Content
  118. */
  119. public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
  120. global $wgParser;
  121. $text = $this->getNativeData();
  122. $pst = $wgParser->preSaveTransform( $text, $title, $user, $popts );
  123. rtrim( $pst );
  124. return ( $text === $pst ) ? $this : new static( $pst );
  125. }
  126. /**
  127. * Returns a Content object with preload transformations applied (or this
  128. * object if no transformations apply).
  129. *
  130. * @param Title $title
  131. * @param ParserOptions $popts
  132. * @param array $params
  133. *
  134. * @return Content
  135. */
  136. public function preloadTransform( Title $title, ParserOptions $popts, $params = [] ) {
  137. global $wgParser;
  138. $text = $this->getNativeData();
  139. $plt = $wgParser->getPreloadText( $text, $title, $popts, $params );
  140. return new static( $plt );
  141. }
  142. /**
  143. * Extract the redirect target and the remaining text on the page.
  144. *
  145. * @note migrated here from Title::newFromRedirectInternal()
  146. *
  147. * @since 1.23
  148. *
  149. * @return array List of two elements: Title|null and string.
  150. */
  151. protected function getRedirectTargetAndText() {
  152. global $wgMaxRedirects;
  153. if ( $this->redirectTargetAndText !== null ) {
  154. return $this->redirectTargetAndText;
  155. }
  156. if ( $wgMaxRedirects < 1 ) {
  157. // redirects are disabled, so quit early
  158. $this->redirectTargetAndText = [ null, $this->getNativeData() ];
  159. return $this->redirectTargetAndText;
  160. }
  161. $redir = MagicWord::get( 'redirect' );
  162. $text = ltrim( $this->getNativeData() );
  163. if ( $redir->matchStartAndRemove( $text ) ) {
  164. // Extract the first link and see if it's usable
  165. // Ensure that it really does come directly after #REDIRECT
  166. // Some older redirects included a colon, so don't freak about that!
  167. $m = [];
  168. if ( preg_match( '!^\s*:?\s*\[{2}(.*?)(?:\|.*?)?\]{2}\s*!', $text, $m ) ) {
  169. // Strip preceding colon used to "escape" categories, etc.
  170. // and URL-decode links
  171. if ( strpos( $m[1], '%' ) !== false ) {
  172. // Match behavior of inline link parsing here;
  173. $m[1] = rawurldecode( ltrim( $m[1], ':' ) );
  174. }
  175. $title = Title::newFromText( $m[1] );
  176. // If the title is a redirect to bad special pages or is invalid, return null
  177. if ( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
  178. $this->redirectTargetAndText = [ null, $this->getNativeData() ];
  179. return $this->redirectTargetAndText;
  180. }
  181. $this->redirectTargetAndText = [ $title, substr( $text, strlen( $m[0] ) ) ];
  182. return $this->redirectTargetAndText;
  183. }
  184. }
  185. $this->redirectTargetAndText = [ null, $this->getNativeData() ];
  186. return $this->redirectTargetAndText;
  187. }
  188. /**
  189. * Implement redirect extraction for wikitext.
  190. *
  191. * @return Title|null
  192. *
  193. * @see Content::getRedirectTarget
  194. */
  195. public function getRedirectTarget() {
  196. list( $title, ) = $this->getRedirectTargetAndText();
  197. return $title;
  198. }
  199. /**
  200. * This implementation replaces the first link on the page with the given new target
  201. * if this Content object is a redirect. Otherwise, this method returns $this.
  202. *
  203. * @since 1.21
  204. *
  205. * @param Title $target
  206. *
  207. * @return Content
  208. *
  209. * @see Content::updateRedirect()
  210. */
  211. public function updateRedirect( Title $target ) {
  212. if ( !$this->isRedirect() ) {
  213. return $this;
  214. }
  215. # Fix the text
  216. # Remember that redirect pages can have categories, templates, etc.,
  217. # so the regex has to be fairly general
  218. $newText = preg_replace( '/ \[ \[ [^\]]* \] \] /x',
  219. '[[' . $target->getFullText() . ']]',
  220. $this->getNativeData(), 1 );
  221. return new static( $newText );
  222. }
  223. /**
  224. * Returns true if this content is not a redirect, and this content's text
  225. * is countable according to the criteria defined by $wgArticleCountMethod.
  226. *
  227. * @param bool|null $hasLinks If it is known whether this content contains
  228. * links, provide this information here, to avoid redundant parsing to
  229. * find out (default: null).
  230. * @param Title|null $title Optional title, defaults to the title from the current main request.
  231. *
  232. * @return bool
  233. */
  234. public function isCountable( $hasLinks = null, Title $title = null ) {
  235. global $wgArticleCountMethod;
  236. if ( $this->isRedirect() ) {
  237. return false;
  238. }
  239. switch ( $wgArticleCountMethod ) {
  240. case 'any':
  241. return true;
  242. case 'comma':
  243. $text = $this->getNativeData();
  244. return strpos( $text, ',' ) !== false;
  245. case 'link':
  246. if ( $hasLinks === null ) { # not known, find out
  247. if ( !$title ) {
  248. $context = RequestContext::getMain();
  249. $title = $context->getTitle();
  250. }
  251. $po = $this->getParserOutput( $title, null, null, false );
  252. $links = $po->getLinks();
  253. $hasLinks = !empty( $links );
  254. }
  255. return $hasLinks;
  256. }
  257. return false;
  258. }
  259. /**
  260. * @param int $maxlength
  261. * @return string
  262. */
  263. public function getTextForSummary( $maxlength = 250 ) {
  264. $truncatedtext = parent::getTextForSummary( $maxlength );
  265. # clean up unfinished links
  266. # XXX: make this optional? wasn't there in autosummary, but required for
  267. # deletion summary.
  268. $truncatedtext = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $truncatedtext );
  269. return $truncatedtext;
  270. }
  271. /**
  272. * Returns a ParserOutput object resulting from parsing the content's text
  273. * using $wgParser.
  274. *
  275. * @param Title $title
  276. * @param int $revId Revision to pass to the parser (default: null)
  277. * @param ParserOptions $options (default: null)
  278. * @param bool $generateHtml (default: true)
  279. * @param ParserOutput &$output ParserOutput representing the HTML form of the text,
  280. * may be manipulated or replaced.
  281. */
  282. protected function fillParserOutput( Title $title, $revId,
  283. ParserOptions $options, $generateHtml, ParserOutput &$output
  284. ) {
  285. global $wgParser;
  286. list( $redir, $text ) = $this->getRedirectTargetAndText();
  287. $output = $wgParser->parse( $text, $title, $options, true, true, $revId );
  288. // Add redirect indicator at the top
  289. if ( $redir ) {
  290. // Make sure to include the redirect link in pagelinks
  291. $output->addLink( $redir );
  292. if ( $generateHtml ) {
  293. $chain = $this->getRedirectChain();
  294. $output->setText(
  295. Article::getRedirectHeaderHtml( $title->getPageLanguage(), $chain, false ) .
  296. $output->getRawText()
  297. );
  298. $output->addModuleStyles( 'mediawiki.action.view.redirectPage' );
  299. }
  300. }
  301. }
  302. /**
  303. * @throws MWException
  304. */
  305. protected function getHtml() {
  306. throw new MWException(
  307. "getHtml() not implemented for wikitext. "
  308. . "Use getParserOutput()->getText()."
  309. );
  310. }
  311. /**
  312. * This implementation calls $word->match() on the this TextContent object's text.
  313. *
  314. * @param MagicWord $word
  315. *
  316. * @return bool
  317. *
  318. * @see Content::matchMagicWord()
  319. */
  320. public function matchMagicWord( MagicWord $word ) {
  321. return $word->match( $this->getNativeData() );
  322. }
  323. }