StripState::merge()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 2
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
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
/**
25
 * @todo document, briefly.
26
 * @ingroup Parser
27
 */
28
class StripState {
29
	protected $prefix;
30
	protected $data;
31
	protected $regex;
32
33
	protected $tempType, $tempMergePrefix;
0 ignored issues
show
Coding Style introduced by
It is generally advisable to only define one property per statement.

Only declaring a single property per statement allows you to later on add doc comments more easily.

It is also recommended by PSR2, so it is a common style that many people expect.

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