Completed
Branch master (ecb46d)
by Tobias
01:39
created

AbstractComponent::prepareInput()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 16
cts 16
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 13
nc 4
nop 2
crap 4
1
<?php
2
/**
3
 * Contains base class for all components.
4
 *
5
 * @copyright (C) 2018, Tobias Oetterer, Paderborn University
6
 * @license       https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 (or later)
7
 *
8
 * This file is part of the MediaWiki extension BootstrapComponents.
9
 * The BootstrapComponents extension is free software: you can redistribute it
10
 * and/or modify it under the terms of the GNU General Public License as published
11
 * by the Free Software Foundation, either version 3 of the License, or
12
 * (at your option) any later version.
13
 *
14
 * The BootstrapComponents extension is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
21
 *
22
 * @file
23
 * @ingroup       BootstrapComponents
24
 * @author        Tobias Oetterer
25
 */
26
27
namespace BootstrapComponents;
28
29
use \MWException;
30
31
/**
32
 * Class AbstractComponent
33
 *
34
 * Abstract class for all component classes
35
 *
36
 * @since 1.0
37
 */
38
abstract class AbstractComponent implements NestableInterface {
39
	/**
40
	 * Holds a reference of the component's attribute manger.
41
	 *
42
	 * @var AttributeManager $attributeManager
43
	 */
44
	private $attributeManager;
45
46
	/**
47
	 * @var ComponentLibrary $componentLibrary
48
	 */
49
	private $componentLibrary;
50
51
	/**
52
	 * The (html) id of this component. Not available before the component was opened.
53
	 *
54
	 * @var string $id
55
	 */
56
	private $id;
57
58
	/**
59
	 * Name of the component
60
	 *
61
	 * @var string $name
62
	 */
63
	private $name;
64
65
	/**
66
	 * @var NestingController $nestingController
67
	 */
68
	private $nestingController;
69
70
	/**
71
	 * @var NestableInterface|false $parentComponent
72
	 */
73
	private $parentComponent;
74
75
	/**
76
	 * @var ParserOutputHelper $parserOutputHelper
77
	 */
78
	private $parserOutputHelper;
79
80
	/**
81
	 * @var ParserRequest $parserRequest
82
	 */
83
	private $parserRequest;
84
85
	/**
86
	 * For every of my registered attributes holds a value. false, if not valid in supplied
87
	 * parserRequest.
88
	 *
89
	 * @var array $sanitizedAttributes
90
	 */
91
	private $sanitizedAttributes;
92
93
	/**
94
	 * Does the actual work in the individual components.
95
	 *
96
	 * @param string $input
97
	 *
98
	 * @return string|array
99
	 */
100
	abstract protected function placeMe( $input );
101
102
	/**
103
	 * Component constructor.
104
	 *
105
	 * @param ComponentLibrary   $componentLibrary
106
	 * @param ParserOutputHelper $parserOutputHelper
107
	 * @param NestingController  $nestingController
108
	 *
109
	 * @throws MWException cascading {@see \BootstrapComponents\ComponentLibrary::getNameFor}
110
	 *                      or {@see \BootstrapComponents\Component::extractAttribute}
111
	 */
112 42
	public function __construct( $componentLibrary, $parserOutputHelper, $nestingController ) {
113 42
		$this->componentLibrary = $componentLibrary;
114 42
		$this->parserOutputHelper = $parserOutputHelper;
115 42
		$this->nestingController = $nestingController;
116 42
		$this->name = $componentLibrary->getNameFor(
117 42
			get_class( $this )
118 42
		);
119 42
		$this->attributeManager = ApplicationFactory::getInstance()->getAttributeManager(
120 42
			$this->componentLibrary->getAttributesFor( $this->getComponentName() )
121 42
		);
122 42
		$this->storeParentComponent(
123 42
			$this->getNestingController()->getCurrentElement()
124 42
		);
125 42
	}
126
127
	/**
128
	 * Returns the name of the component.
129
	 *
130
	 * @return string
131
	 */
132 42
	public function getComponentName() {
133 42
		return $this->name;
134
	}
135
136
	/**
137
	 * Note that id is only present after {@see AbstractComponent::parseComponent} starts execution.
138
	 *
139
	 * @return string
140
	 */
141 30
	public function getId() {
142 30
		return $this->id;
143
	}
144
145
	/**
146
	 * @param ParserRequest $parserRequest ;
147
	 *
148
	 * @throws \MWException self or cascading from {@see \BootstrapComponents\Component::processArguments}
149
	 *                      or {@see \BootstrapComponents\NestingController::close}
150
	 * @return string|array
151
	 */
152 40
	public function parseComponent( $parserRequest ) {
153 40
		if ( !is_a( $parserRequest, 'BootstrapComponents\ParserRequest' ) ) {
154 11
			throw new MWException( 'Invalid ParserRequest supplied to component ' . $this->getComponentName() . '!' );
155
		}
156 29
		$this->getNestingController()->open( $this );
157 29
		$this->initComponentData( $parserRequest );
158
159 29
		$input = $this->prepareInput( $parserRequest );
160
161 29
		$ret = $this->placeMe( $input );
162 29
		$this->getNestingController()->close( $this->getId() );
163 29
		return $ret;
164
	}
165
166
	/**
167
	 * Converts the input array to a string using glue. Removes invalid (false) entries beforehand.
168
	 *
169
	 * @param array|false $array
170
	 * @param string      $glue
171
	 *
172
	 * @return false|string returns false on empty array, string otherwise
173
	 */
174 27
	protected function arrayToString( $array, $glue ) {
175 27
		if ( empty( $array ) ) {
176 17
			return false;
177
		}
178 27
		foreach ( (array) $array as $key => $item ) {
179 27
			if ( $item === false || $item === '' ) {
180
				unset( $array[$key] );
181
			}
182 27
		}
183 27
		return count( $array ) ? implode( $glue, $array ) : false;
184 1
	}
185
186
	/**
187
	 * @return AttributeManager
188
	 */
189 29
	protected function getAttributeManager() {
190 29
		return $this->attributeManager;
191
	}
192
193
	/**
194
	 * @return ComponentLibrary
195
	 */
196 29
	protected function getComponentLibrary() {
197 29
		return $this->componentLibrary;
198
	}
199
200
	/**
201
	 * @return NestingController
202
	 */
203 42
	protected function getNestingController() {
204 42
		return $this->nestingController;
205
	}
206
207
	/**
208
	 * @return NestableInterface|false
209
	 */
210 5
	protected function getParentComponent() {
211 5
		return $this->parentComponent;
212
	}
213
214
	/**
215
	 * @return ParserOutputHelper
216
	 */
217 29
	protected function getParserOutputHelper() {
218 29
		return $this->parserOutputHelper;
219
	}
220
221
	/**
222
	 * Returns the original parser request supplied to this component.
223
	 * Note, that none of the attributes nor the input were parsed with
224
	 * {@see \Parser::recursiveTagParse}.
225
	 *
226
	 * @return ParserRequest
227
	 */
228 6
	protected function getParserRequest() {
229 6
		return $this->parserRequest;
230
	}
231
232
	/**
233
	 * If attribute is registered, this returns the verified and parsed value for it. If not, or the
234
	 * verified value is false, this returns the fallback.
235
	 *
236
	 * @param string      $attribute
237
	 * @param bool|string $fallback
238
	 *
239
	 * @return bool|string
240
	 */
241 29
	protected function getValueFor( $attribute, $fallback = false ) {
242 29
		if ( !isset( $this->sanitizedAttributes[$attribute] ) || $this->sanitizedAttributes[$attribute] === false ) {
243 29
			return $fallback;
244
		}
245 27
		return $this->sanitizedAttributes[$attribute];
246
	}
247
248
	/**
249
	 * Parses input text from parser request. Does also some fixes to let parser detect paragraphs in content.
250
	 *
251
	 * @param ParserRequest $parserRequest
252
	 * @param bool          $fullParse
253
	 *
254
	 * @since 1.1.0
255
	 *
256
	 * @return string
257
	 */
258 29
	protected function prepareInput( $parserRequest, $fullParse = false ) {
259 29
		$parser = $parserRequest->getParser();
260 29
		if ( $fullParse ) {
261 2
			$input = $parser->recursiveTagParseFully(
262 2
				$parserRequest->getInput(),
263 2
				$parserRequest->getFrame()
264 2
			);
265 2
		} else {
266 29
			$input = $parser->recursiveTagParse(
267 29
				$parserRequest->getInput(),
268 29
				$parserRequest->getFrame()
269 29
			);
270
		}
271 29
		if ( preg_match( '/\n\n/', $input ) || preg_match( '/<p/', $input ) ) {
272
			// if there are paragraph marker we prefix input with a new line so the parser recognizes two paragraphs.
273 2
			$input = "\n" . $input . "\n";
274 2
		}
275 29
		return $input;
276
	}
277
278
	/**
279
	 * Takes your class and style string and appends them with corresponding data from user (if present)
280
	 * passed in attributes.
281
	 *
282
	 * @param string|array $class
283
	 * @param string|array $style
284
	 *
285
	 * @return array[] containing (array)$class and (array)$style
286
	 */
287 27
	protected function processCss( $class, $style ) {
288 27
		$class = (array) $class;
289 27
		$style = (array) $style;
290 27
		if ( $newClass = $this->getValueFor( 'class' ) ) {
291 25
			$class[] = $newClass;
292 25
		}
293 27
		if ( $newStyle = $this->getValueFor( 'style' ) ) {
294 25
			$style[] = $newStyle;
295 25
		}
296 27
		return [ $class, $style ];
297
	}
298
299
	/**
300
	 * Performs all the mandatory actions on the parser output for the component class.
301
	 */
302 29
	private function augmentParserOutput() {
303 29
		$this->getParserOutputHelper()->addTrackingCategory();
304 29
		$this->getParserOutputHelper()->loadBootstrapModules();
305 29
		$modules = $this->getComponentLibrary()->getModulesFor(
306 29
			$this->getComponentName(),
307 29
			$this->getParserOutputHelper()->getNameOfActiveSkin()
308 29
		);
309 29
		$this->getParserOutputHelper()->addModules( $modules );
310 29
	}
311
312
	/**
313
	 * Initializes the attributes, id and stores the original parser request.
314
	 *
315
	 * @param ParserRequest $parserRequest
316
	 */
317 29
	private function initComponentData( $parserRequest ) {
318 29
		$this->parserRequest = $parserRequest;
319 29
		$this->sanitizedAttributes = $this->sanitizeAttributes(
320 29
			$parserRequest->getParser(),
321 29
			$parserRequest->getAttributes()
322 29
		);
323 29
		$this->id = $this->getValueFor( 'id' ) != false
324 29
			? (string) $this->getValueFor( 'id' )
325 29
			: $this->getNestingController()->generateUniqueId( $this->getComponentName() );
326 29
		$this->augmentParserOutput();
327 29
	}
328
329
	/**
330
	 * For every registered attribute, sanitizes (parses and verifies) the corresponding value in supplied attributes.
331
	 *
332
	 * @param \Parser  $parser
333
	 * @param string[] $attributes
334
	 *
335
	 * @return array
336
	 */
337 29
	private function sanitizeAttributes( $parser, $attributes ) {
338
339 29
		$parsedAttributes = [];
340 29
		foreach ( $attributes as $attribute => $unParsedValue ) {
341 28
			$parsedAttributes[$attribute] = $parser->recursiveTagParse( $unParsedValue );
342 29
		}
343 29
		return $this->getAttributeManager()->verifyAttributes( $parsedAttributes );
344
	}
345
346
	/**
347
	 * @param NestableInterface|false $parentComponent
348
	 */
349 42
	private function storeParentComponent( $parentComponent ) {
350 42
		$this->parentComponent = $parentComponent;
351 42
	}
352
}
353