Completed
Push — master ( eadb3e...25e675 )
by
unknown
10:12
created

AbstractComponent::arrayToString()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 6.3949

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 7
cts 9
cp 0.7778
rs 9.2222
c 0
b 0
f 0
cc 6
nc 7
nop 2
crap 6.3949
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()->getAttributeManager(
121 42
			$this->componentLibrary->getAttributesFor( $this->getComponentName() )
122 42
		);
123 42
		$this->storeParentComponent(
124 42
			$this->getNestingController()->getCurrentElement()
125 42
		);
126 42
	}
127
128
	/**
129
	 * Returns the name of the component.
130
	 *
131
	 * @return string
132
	 */
133 42
	public function getComponentName() {
134 42
		return $this->name;
135
	}
136
137
	/**
138
	 * Note that id is only present after {@see AbstractComponent::parseComponent} starts execution.
139
	 *
140
	 * @return string
141
	 */
142 30
	public function getId() {
143 30
		return $this->id;
144
	}
145
146
	/**
147
	 * @param ParserRequest $parserRequest ;
148
	 *
149
	 * @throws \MWException self or cascading from {@see \BootstrapComponents\Component::processArguments}
150
	 *                      or {@see \BootstrapComponents\NestingController::close}
151
	 * @return string|array
152
	 */
153 40
	public function parseComponent( $parserRequest ) {
154 40
		if ( !is_a( $parserRequest, 'BootstrapComponents\ParserRequest' ) ) {
155 11
			throw new MWException( 'Invalid ParserRequest supplied to component ' . $this->getComponentName() . '!' );
156
		}
157 29
		$this->getNestingController()->open( $this );
158 29
		$this->initComponentData( $parserRequest );
159
160 29
		$input = $this->prepareInput( $parserRequest );
161
162 29
		$ret = $this->placeMe( $input );
163 29
		$this->getNestingController()->close( $this->getId() );
164 29
		return $ret;
165
	}
166
167
	/**
168
	 * Converts the input array to a string using glue. Removes invalid (false) entries beforehand.
169
	 *
170
	 * @param array|false $array
171
	 * @param string      $glue
172
	 *
173
	 * @return false|string returns false on empty array, string otherwise
174
	 */
175 28
	protected function arrayToString( $array, $glue ) {
176 27
		if ( empty( $array ) ) {
177 17
			return false;
178
		}
179 27
		foreach ( (array) $array as $key => $item ) {
180 27
			if ( $item === false || $item === '' ) {
181
				unset( $array[$key] );
182
			}
183 27
		}
184 28
		return count( $array ) ? implode( $glue, $array ) : false;
185
	}
186
187
	/**
188
	 * @return AttributeManager
189
	 */
190 29
	protected function getAttributeManager() {
191 29
		return $this->attributeManager;
192
	}
193
194
	/**
195
	 * @return ComponentLibrary
196
	 */
197 29
	protected function getComponentLibrary() {
198 29
		return $this->componentLibrary;
199
	}
200
201
	/**
202
	 * @return NestingController
203
	 */
204 42
	protected function getNestingController() {
205 42
		return $this->nestingController;
206
	}
207
208
	/**
209
	 * @return NestableInterface|false
210
	 */
211 5
	protected function getParentComponent() {
212 5
		return $this->parentComponent;
213
	}
214
215
	/**
216
	 * @return ParserOutputHelper
217
	 */
218 29
	protected function getParserOutputHelper() {
219 29
		if ( !defined( 'BSC_INTEGRATION_TEST' ) ) {
220
			#@fixme this is foobar to make modals work in integration tests. find a better solution
221
			# see also \BootstrapComponents\Tests\Integration\BootstrapComponentsJsonTestCaseScriptRunnerTest::setUp
222 12
			return $this->parserOutputHelper;
223
		}
224 17
		return ApplicationFactory::getInstance()->getParserOutputHelper();
225
	}
226
227
	/**
228
	 * Returns the original parser request supplied to this component.
229
	 * Note, that none of the attributes nor the input were parsed with
230
	 * {@see \Parser::recursiveTagParse}.
231
	 *
232
	 * @return ParserRequest
233
	 */
234 6
	protected function getParserRequest() {
235 6
		return $this->parserRequest;
236
	}
237
238
	/**
239
	 * If attribute is registered, this returns the verified and parsed value for it. If not, or the
240
	 * verified value is false, this returns the fallback.
241
	 *
242
	 * @param string      $attribute
243
	 * @param bool|string $fallback
244
	 *
245
	 * @return bool|string
246
	 */
247 29
	protected function getValueFor( $attribute, $fallback = false ) {
248 29
		if ( !isset( $this->sanitizedAttributes[$attribute] ) || $this->sanitizedAttributes[$attribute] === false ) {
249 29
			return $fallback;
250
		}
251 27
		return $this->sanitizedAttributes[$attribute];
252
	}
253
254
	/**
255
	 * Parses input text from parser request. Does also some fixes to let parser detect paragraphs in content.
256
	 *
257
	 * @param ParserRequest $parserRequest
258
	 * @param bool          $fullParse
259
	 *
260
	 * @since 1.1.0
261
	 *
262
	 * @return string
263
	 */
264 29
	protected function prepareInput( $parserRequest, $fullParse = false ) {
265 29
		$parser = $parserRequest->getParser();
266 29
		if ( $fullParse ) {
267 2
			$input = $parser->recursiveTagParseFully(
268 2
				$parserRequest->getInput(),
269 2
				$parserRequest->getFrame()
270 2
			);
271 2
		} else {
272 29
			$input = $parser->recursiveTagParse(
273 29
				$parserRequest->getInput(),
274 29
				$parserRequest->getFrame()
275 29
			);
276
		}
277 29
		if ( preg_match( '/\n\n/', $input ) || preg_match( '/<p/', $input ) ) {
278
			// if there are paragraph marker we prefix input with a new line so the parser recognizes two paragraphs.
279 2
			$input = "\n" . $input . "\n";
280 2
		}
281 29
		return $input;
282
	}
283
284
	/**
285
	 * Takes your class and style string and appends them with corresponding data from user (if present)
286
	 * passed in attributes.
287
	 *
288
	 * @param string|array $class
289
	 * @param string|array $style
290
	 *
291
	 * @return array[] containing (array)$class and (array)$style
292
	 */
293 27
	protected function processCss( $class, $style ) {
294 27
		$class = (array) $class;
295 27
		$style = (array) $style;
296 27
		if ( $newClass = $this->getValueFor( 'class' ) ) {
297 25
			$class[] = $newClass;
298 25
		}
299 27
		if ( $newStyle = $this->getValueFor( 'style' ) ) {
300 25
			$style[] = $newStyle;
301 25
		}
302 27
		return [ $class, $style ];
303
	}
304
305
	/**
306
	 * Performs all the mandatory actions on the parser output for the component class.
307
	 */
308 29
	private function augmentParserOutput() {
309 29
		$this->getParserOutputHelper()->addTrackingCategory();
310 29
		$this->getParserOutputHelper()->loadBootstrapModules();
311 29
		$modules = $this->getComponentLibrary()->getModulesFor(
312 29
			$this->getComponentName(),
313 29
			$this->getParserOutputHelper()->getNameOfActiveSkin()
314 29
		);
315 29
		$this->getParserOutputHelper()->addModules( $modules );
316 29
	}
317
318
	/**
319
	 * Initializes the attributes, id and stores the original parser request.
320
	 *
321
	 * @param ParserRequest $parserRequest
322
	 */
323 29
	private function initComponentData( $parserRequest ) {
324 29
		$this->parserRequest = $parserRequest;
325 29
		$this->sanitizedAttributes = $this->sanitizeAttributes(
326 29
			$parserRequest->getParser(),
327 29
			$parserRequest->getAttributes()
328 29
		);
329 29
		$this->id = $this->getValueFor( 'id' ) != false
330 29
			? (string) $this->getValueFor( 'id' )
331 29
			: $this->getNestingController()->generateUniqueId( $this->getComponentName() );
332 29
		$this->augmentParserOutput();
333 29
	}
334
335
	/**
336
	 * For every registered attribute, sanitizes (parses and verifies) the corresponding value in supplied attributes.
337
	 *
338
	 * @param \Parser  $parser
339
	 * @param string[] $attributes
340
	 *
341
	 * @return array
342
	 */
343 29
	private function sanitizeAttributes( $parser, $attributes ) {
344
345 29
		$parsedAttributes = [];
346 29
		foreach ( $attributes as $attribute => $unParsedValue ) {
347 28
			$parsedAttributes[$attribute] = $parser->recursiveTagParse( $unParsedValue );
348 29
		}
349 29
		return $this->getAttributeManager()->verifyAttributes( $parsedAttributes );
350
	}
351
352
	/**
353
	 * @param NestableInterface|false $parentComponent
354
	 */
355 42
	private function storeParentComponent( $parentComponent ) {
356 42
		$this->parentComponent = $parentComponent;
357 42
	}
358
}
359