Completed
Push — master ( 7aacb2...2b39b6 )
by
unknown
06:55
created

AbstractComponent   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 331
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 98.17%

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 6
dl 0
loc 331
ccs 107
cts 109
cp 0.9817
rs 9.6
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
placeMe() 0 1 ?
A __construct() 0 13 1
A getComponentName() 0 3 1
A getId() 0 3 1
A parseComponent() 0 13 2
A arrayToString() 0 11 6
A getAttributeManager() 0 3 1
A getComponentLibrary() 0 3 1
A getNestingController() 0 3 1
A getParentComponent() 0 3 1
A getParserOutputHelper() 0 8 2
A getParserRequest() 0 3 1
A getValueFor() 0 6 2
A hasValueFor() 0 3 1
A prepareInput() 0 19 4
A processCss() 0 11 3
A augmentParserOutput() 0 9 1
A initComponentData() 0 11 2
A sanitizeAttributes() 0 16 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 function Couchbase\defaultDecoder;
30
use \MWException;
31
32
/**
33
 * Class AbstractComponent
34
 *
35
 * Abstract class for all component classes
36
 *
37
 * @since 1.0
38
 */
39
abstract class AbstractComponent implements NestableInterface {
40
	/**
41
	 * Holds a reference of the component's attribute manger.
42
	 *
43
	 * @var AttributeManager $attributeManager
44
	 */
45
	private $attributeManager;
46
47
	/**
48
	 * @var ComponentLibrary $componentLibrary
49
	 */
50
	private $componentLibrary;
51
52
	/**
53
	 * The (html) id of this component. Not available before the component was opened.
54
	 *
55
	 * @var string $id
56
	 */
57
	private $id;
58
59
	/**
60
	 * Name of the component
61
	 *
62
	 * @var string $name
63
	 */
64
	private $name;
65
66
	/**
67
	 * @var NestingController $nestingController
68
	 */
69
	private $nestingController;
70
71
	/**
72
	 * @var NestableInterface|false $parentComponent
73
	 */
74
	private $parentComponent;
75
76
	/**
77
	 * @var ParserOutputHelper $parserOutputHelper
78
	 */
79
	private $parserOutputHelper;
80
81
	/**
82
	 * @var ParserRequest $parserRequest
83
	 */
84
	private $parserRequest;
85
86
	/**
87
	 * For every of my registered attributes holds a value. false, if not valid in supplied
88
	 * parserRequest.
89
	 *
90
	 * @var array $sanitizedAttributes
91
	 */
92
	private $sanitizedAttributes;
93
94
	/**
95
	 * Does the actual work in the individual components.
96
	 *
97
	 * @param string $input
98
	 *
99
	 * @return string|array
100
	 */
101
	abstract protected function placeMe( $input );
102
103
	/**
104
	 * Component constructor.
105
	 *
106
	 * @param ComponentLibrary   $componentLibrary
107
	 * @param ParserOutputHelper $parserOutputHelper
108
	 * @param NestingController  $nestingController
109
	 *
110
	 * @throws MWException cascading {@see \BootstrapComponents\ComponentLibrary::getNameFor}
111
	 *                      or {@see \BootstrapComponents\Component::extractAttribute}
112
	 */
113 42
	public function __construct( $componentLibrary, $parserOutputHelper, $nestingController ) {
114 42
		$this->componentLibrary = $componentLibrary;
115 42
		$this->parserOutputHelper = $parserOutputHelper;
116 42
		$this->nestingController = $nestingController;
117 42
		$this->name = $componentLibrary->getNameFor(
118 42
			get_class( $this )
119 42
		);
120 42
		$this->attributeManager = ApplicationFactory::getInstance()->getNewAttributeManager(
121 42
			$this->componentLibrary->getAttributesFor( $this->getComponentName() ),
122 42
			$this->componentLibrary->getAliasesFor( $this->getComponentName() )
123 42
		);
124 42
		$this->parentComponent = $this->getNestingController()->getCurrentElement();
125 42
	}
126 42
127
	/**
128
	 * Returns the name of the component.
129
	 *
130
	 * @return string
131
	 */
132
	public function getComponentName() {
133 42
		return $this->name;
134 42
	}
135
136
	/**
137
	 * Note that id is only present after {@see AbstractComponent::parseComponent} starts execution.
138
	 *
139
	 * @return string
140
	 */
141
	public function getId() {
142 30
		return $this->id;
143 30
	}
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
	public function parseComponent( $parserRequest ) {
153 40
		if ( !is_a( $parserRequest, ParserRequest::class ) ) {
154 40
			throw new MWException( 'Invalid ParserRequest supplied to component ' . $this->getComponentName() . '!' );
155 11
		}
156
		$this->getNestingController()->open( $this );
157 29
		$this->initComponentData( $parserRequest );
158 29
159
		$input = $this->prepareInput( $parserRequest );
160 29
161
		$ret = $this->placeMe( $input );
162 29
		$this->getNestingController()->close( $this->getId() );
163 29
		return $ret;
164 29
	}
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
	protected function arrayToString( $array, $glue ) {
175 28
		if ( empty( $array ) ) {
176 27
			return false;
177 17
		}
178
		foreach ( (array) $array as $key => $item ) {
179 27
			if ( $item === false || $item === '' ) {
180 27
				unset( $array[$key] );
181
			}
182
		}
183 27
		return count( $array ) ? implode( $glue, $array ) : false;
184 28
	}
185
186
	/**
187
	 * @return AttributeManager
188
	 */
189
	protected function getAttributeManager() {
190 29
		return $this->attributeManager;
191 29
	}
192
193
	/**
194
	 * @return ComponentLibrary
195
	 */
196
	protected function getComponentLibrary() {
197 29
		return $this->componentLibrary;
198 29
	}
199
200
	/**
201
	 * @return NestingController
202
	 */
203
	protected function getNestingController() {
204 42
		return $this->nestingController;
205 42
	}
206
207
	/**
208
	 * @return NestableInterface|false
209
	 */
210
	protected function getParentComponent() {
211 5
		return $this->parentComponent;
212 5
	}
213
214
	/**
215
	 * @return ParserOutputHelper
216
	 */
217
	protected function getParserOutputHelper() {
218 29
		if ( !defined( 'BSC_INTEGRATION_TEST' ) ) {
219 29
			#@fixme this is foobar to make modals work in integration tests. find a better solution
220
			# see also \BootstrapComponents\Tests\Integration\BootstrapComponentsJsonTestCaseScriptRunnerTest::setUp
221
			return $this->parserOutputHelper;
222 12
		}
223
		return ApplicationFactory::getInstance()->getParserOutputHelper();
224 17
	}
225
226
	/**
227
	 * Returns the original parser request supplied to this component.
228
	 * Note, that none of the attributes nor the input were parsed with
229
	 * {@see \Parser::recursiveTagParse}.
230
	 *
231
	 * @return ParserRequest
232
	 */
233
	protected function getParserRequest() {
234 6
		return $this->parserRequest;
235 6
	}
236
237
	/**
238
	 * If attribute is registered, this returns the verified and parsed value for it. If not, or the
239
	 * verified value is false, this returns the fallback.
240
	 *
241
	 * @param string      $attribute
242
	 * @param bool|string $fallback
243
	 *
244
	 * @return bool|string
245
	 */
246
	protected function getValueFor( $attribute, $fallback = false ) {
247 29
		if ( !$this->hasValueFor( $attribute ) ) {
248 29
			return $fallback;
249 29
		}
250
		return $this->sanitizedAttributes[$attribute];
251 27
	}
252
253
	/**
254
	 * Checks, if component has a value supplied for $attribute. Note, that $value can be empty string or evaluate to false.
255
	 *
256
	 * @param string $attribute
257
	 *
258
	 * @return bool
259
	 */
260
	protected function hasValueFor( $attribute ) {
261
		return isset( $this->sanitizedAttributes[$attribute] );
262
	}
263
264 29
	/**
265 29
	 * Parses input text from parser request. Does also some fixes to let parser detect paragraphs in content.
266 29
	 *
267 2
	 * @param ParserRequest $parserRequest
268 2
	 * @param bool          $fullParse
269 2
	 *
270 2
	 * @return string
271 2
	 * @since 1.1.0
272 29
	 *
273 29
	 */
274 29
	protected function prepareInput( $parserRequest, $fullParse = false ) {
275 29
		$parser = $parserRequest->getParser();
276
		if ( $fullParse ) {
277 29
			$input = $parser->recursiveTagParseFully(
278
				$parserRequest->getInput(),
279 2
				$parserRequest->getFrame()
280 2
			);
281 29
		} else {
282
			$input = $parser->recursiveTagParse(
283
				$parserRequest->getInput(),
284
				$parserRequest->getFrame()
285
			);
286
		}
287
		if ( preg_match( '/\n\n/', $input ) || preg_match( '/<p/', $input ) ) {
288
			// if there are paragraph marker we prefix input with a new line so the parser recognizes two paragraphs.
289
			$input = "\n" . $input . "\n";
290
		}
291
		return $input;
292
	}
293 27
294 27
	/**
295 27
	 * Takes your class and style string and appends them with corresponding data from user (if present)
296 27
	 * passed in attributes.
297 25
	 *
298 25
	 * @param string|array $class
299 27
	 * @param string|array $style
300 25
	 *
301 25
	 * @return array[] containing (array)$class and (array)$style
302 27
	 */
303
	protected function processCss( $class, $style ) {
304
		$class = (array) $class;
305
		$style = (array) $style;
306
		if ( $newClass = $this->getValueFor( 'class' ) ) {
307
			$class[] = $newClass;
308 29
		}
309 29
		if ( $newStyle = $this->getValueFor( 'style' ) ) {
310 29
			$style[] = $newStyle;
311 29
		}
312 29
		return [ $class, $style ];
313 29
	}
314 29
315 29
	/**
316 29
	 * Performs all the mandatory actions on the parser output for the component class.
317
	 */
318
	private function augmentParserOutput() {
319
		$this->getParserOutputHelper()->addTrackingCategory();
320
		$this->getParserOutputHelper()->loadBootstrapModules();
321
		$modules = $this->getComponentLibrary()->getModulesFor(
322
			$this->getComponentName(),
323 29
			$this->getParserOutputHelper()->getNameOfActiveSkin()
324 29
		);
325 29
		$this->getParserOutputHelper()->addModules( $modules );
326 29
	}
327 29
328 29
	/**
329 29
	 * Initializes the attributes, id and stores the original parser request.
330 29
	 *
331 29
	 * @param ParserRequest $parserRequest
332 29
	 */
333 29
	private function initComponentData( $parserRequest ) {
334
		$this->parserRequest = $parserRequest;
335
		$this->sanitizedAttributes = $this->sanitizeAttributes(
336
			$parserRequest->getParser(),
337
			$parserRequest->getAttributes()
338
		);
339
		$this->id = $this->getValueFor( 'id' ) != false
340
			? (string) $this->getValueFor( 'id' )
341
			: $this->getNestingController()->generateUniqueId( $this->getComponentName() );
342
		$this->augmentParserOutput();
343 29
	}
344
345 29
	/**
346 29
	 * For every registered attribute, sanitizes (parses and verifies) the corresponding value in supplied attributes.
347 28
	 *
348 29
	 * @param \Parser $parser
349 29
	 * @param array   $attributes
350
	 *
351
	 * @return array
352
	 */
353
	private function sanitizeAttributes( $parser, $attributes ) {
354
		$parsedAttributes = [];
355 42
		foreach ( $attributes as $attribute => $unParsedValue ) {
356 42
			if ( !$this->getAttributeManager()->isValid( $attribute ) ) {
357 42
				continue;
358
			}
359
			list( $attribute, $verifiedValue ) = $this->getAttributeManager()->validateAttributeAndValue(
360
				$attribute,
361
				$parser->recursiveTagParse( $unParsedValue )
362
			);
363
			if ( !is_null( $verifiedValue ) ) {
364
				$parsedAttributes[$attribute] = $verifiedValue;
365
			}
366
		}
367
		return $parsedAttributes;
368
	}
369
}
370