StripState.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. <?php
  2. /**
  3. * Holder for stripped items when parsing wiki markup.
  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. * @file
  21. * @ingroup Parser
  22. */
  23. /**
  24. * @todo document, briefly.
  25. * @ingroup Parser
  26. */
  27. class StripState {
  28. protected $prefix;
  29. protected $data;
  30. protected $regex;
  31. protected $tempType, $tempMergePrefix;
  32. protected $circularRefGuard;
  33. protected $recursionLevel = 0;
  34. const UNSTRIP_RECURSION_LIMIT = 20;
  35. /**
  36. * @param string|null $prefix
  37. * @since 1.26 The prefix argument should be omitted, as the strip marker
  38. * prefix string is now a constant.
  39. */
  40. public function __construct( $prefix = null ) {
  41. if ( $prefix !== null ) {
  42. wfDeprecated( __METHOD__ . ' with called with $prefix argument' .
  43. ' (call with no arguments instead)', '1.26' );
  44. }
  45. $this->data = [
  46. 'nowiki' => [],
  47. 'general' => []
  48. ];
  49. $this->regex = '/' . Parser::MARKER_PREFIX . "([^\x7f<>&'\"]+)" . Parser::MARKER_SUFFIX . '/';
  50. $this->circularRefGuard = [];
  51. }
  52. /**
  53. * Add a nowiki strip item
  54. * @param string $marker
  55. * @param string $value
  56. */
  57. public function addNoWiki( $marker, $value ) {
  58. $this->addItem( 'nowiki', $marker, $value );
  59. }
  60. /**
  61. * @param string $marker
  62. * @param string $value
  63. */
  64. public function addGeneral( $marker, $value ) {
  65. $this->addItem( 'general', $marker, $value );
  66. }
  67. /**
  68. * @throws MWException
  69. * @param string $type
  70. * @param string $marker
  71. * @param string $value
  72. */
  73. protected function addItem( $type, $marker, $value ) {
  74. if ( !preg_match( $this->regex, $marker, $m ) ) {
  75. throw new MWException( "Invalid marker: $marker" );
  76. }
  77. $this->data[$type][$m[1]] = $value;
  78. }
  79. /**
  80. * @param string $text
  81. * @return mixed
  82. */
  83. public function unstripGeneral( $text ) {
  84. return $this->unstripType( 'general', $text );
  85. }
  86. /**
  87. * @param string $text
  88. * @return mixed
  89. */
  90. public function unstripNoWiki( $text ) {
  91. return $this->unstripType( 'nowiki', $text );
  92. }
  93. /**
  94. * @param string $text
  95. * @return mixed
  96. */
  97. public function unstripBoth( $text ) {
  98. $text = $this->unstripType( 'general', $text );
  99. $text = $this->unstripType( 'nowiki', $text );
  100. return $text;
  101. }
  102. /**
  103. * @param string $type
  104. * @param string $text
  105. * @return mixed
  106. */
  107. protected function unstripType( $type, $text ) {
  108. // Shortcut
  109. if ( !count( $this->data[$type] ) ) {
  110. return $text;
  111. }
  112. $oldType = $this->tempType;
  113. $this->tempType = $type;
  114. $text = preg_replace_callback( $this->regex, [ $this, 'unstripCallback' ], $text );
  115. $this->tempType = $oldType;
  116. return $text;
  117. }
  118. /**
  119. * @param array $m
  120. * @return array
  121. */
  122. protected function unstripCallback( $m ) {
  123. $marker = $m[1];
  124. if ( isset( $this->data[$this->tempType][$marker] ) ) {
  125. if ( isset( $this->circularRefGuard[$marker] ) ) {
  126. return '<span class="error">'
  127. . wfMessage( 'parser-unstrip-loop-warning' )->inContentLanguage()->text()
  128. . '</span>';
  129. }
  130. if ( $this->recursionLevel >= self::UNSTRIP_RECURSION_LIMIT ) {
  131. return '<span class="error">' .
  132. wfMessage( 'parser-unstrip-recursion-limit' )
  133. ->numParams( self::UNSTRIP_RECURSION_LIMIT )->inContentLanguage()->text() .
  134. '</span>';
  135. }
  136. $this->circularRefGuard[$marker] = true;
  137. $this->recursionLevel++;
  138. $value = $this->data[$this->tempType][$marker];
  139. if ( $value instanceof Closure ) {
  140. $value = $value();
  141. }
  142. $ret = $this->unstripType( $this->tempType, $value );
  143. $this->recursionLevel--;
  144. unset( $this->circularRefGuard[$marker] );
  145. return $ret;
  146. } else {
  147. return $m[0];
  148. }
  149. }
  150. /**
  151. * Get a StripState object which is sufficient to unstrip the given text.
  152. * It will contain the minimum subset of strip items necessary.
  153. *
  154. * @param string $text
  155. *
  156. * @return StripState
  157. */
  158. public function getSubState( $text ) {
  159. $subState = new StripState();
  160. $pos = 0;
  161. while ( true ) {
  162. $startPos = strpos( $text, Parser::MARKER_PREFIX, $pos );
  163. $endPos = strpos( $text, Parser::MARKER_SUFFIX, $pos );
  164. if ( $startPos === false || $endPos === false ) {
  165. break;
  166. }
  167. $endPos += strlen( Parser::MARKER_SUFFIX );
  168. $marker = substr( $text, $startPos, $endPos - $startPos );
  169. if ( !preg_match( $this->regex, $marker, $m ) ) {
  170. continue;
  171. }
  172. $key = $m[1];
  173. if ( isset( $this->data['nowiki'][$key] ) ) {
  174. $subState->data['nowiki'][$key] = $this->data['nowiki'][$key];
  175. } elseif ( isset( $this->data['general'][$key] ) ) {
  176. $subState->data['general'][$key] = $this->data['general'][$key];
  177. }
  178. $pos = $endPos;
  179. }
  180. return $subState;
  181. }
  182. /**
  183. * Merge another StripState object into this one. The strip marker keys
  184. * will not be preserved. The strings in the $texts array will have their
  185. * strip markers rewritten, the resulting array of strings will be returned.
  186. *
  187. * @param StripState $otherState
  188. * @param array $texts
  189. * @return array
  190. */
  191. public function merge( $otherState, $texts ) {
  192. $mergePrefix = wfRandomString( 16 );
  193. foreach ( $otherState->data as $type => $items ) {
  194. foreach ( $items as $key => $value ) {
  195. $this->data[$type]["$mergePrefix-$key"] = $value;
  196. }
  197. }
  198. $this->tempMergePrefix = $mergePrefix;
  199. $texts = preg_replace_callback( $otherState->regex, [ $this, 'mergeCallback' ], $texts );
  200. $this->tempMergePrefix = null;
  201. return $texts;
  202. }
  203. /**
  204. * @param array $m
  205. * @return string
  206. */
  207. protected function mergeCallback( $m ) {
  208. $key = $m[1];
  209. return Parser::MARKER_PREFIX . $this->tempMergePrefix . '-' . $key . Parser::MARKER_SUFFIX;
  210. }
  211. /**
  212. * Remove any strip markers found in the given text.
  213. *
  214. * @param string $text Input string
  215. * @return string
  216. */
  217. public function killMarkers( $text ) {
  218. return preg_replace( $this->regex, '', $text );
  219. }
  220. }