Completed
Push — master ( 35bd4c...c8e02c )
by
unknown
10:31 queued 10s
created

src/ParserOutputHelper.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Contains the class augmenting the parser output.
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 \ConfigException;
30
use \Html;
31
use \MediaWiki\MediaWikiServices;
32
use \ParserOutput;
33
use \RequestContext;
34
use \Title;
35
36
/**
37
 * Class ParserOutputHelper
38
 *
39
 * Performs all the adaptions on the ParserOutput
40
 *
41
 * @since 1.0
42
 */
43
class ParserOutputHelper {
44
45
	/**
46
	 * @var string
47
	 */
48
	const EXTENSION_DATA_DEFERRED_CONTENT_KEY = 'bsc_deferredContent';
49
50
	/**
51
	 * @var string
52
	 */
53
	const INJECTION_PREFIX = '<!-- injected by Extension:BootstrapComponents -->';
54
55
	/**
56
	 * @var string
57
	 */
58
	const INJECTION_SUFFIX = '<!-- /injected by Extension:BootstrapComponents -->';
59
60
	/**
61
	 * To make sure, we only add the tracking category once.
62
	 *
63
	 * @var bool $articleTracked
64
	 */
65
	private $articleTracked;
66
67
	/**
68
	 * To make sure, we only add the error tracking category once.
69
	 *
70
	 * @var bool $articleTrackedOnError
71
	 */
72
	private $articleTrackedOnError;
73
74
	/**
75
	 * Holds the name of the skin we use (or false, if there is no skin).
76
	 *
77
	 * @var string $nameOfActiveSkin
78
	 */
79
	private $nameOfActiveSkin;
80
81
	/**
82
	 * @var \Parser $parser
83
	 */
84
	private $parser;
85
86
87
	/**
88
	 * ParserOutputHelper constructor.
89
	 *
90
	 * Do not instantiate directly, but use {@see ApplicationFactory::getParserOutputHelper} instead.
91
	 *
92
	 * @param \Parser $parser
93
	 *
94
	 * @see ApplicationFactory::getParserOutputHelper
95
	 */
96 38
	public function __construct( $parser ) {
97 38
		$this->articleTracked = false;
98 38
		$this->articleTrackedOnError = false;
99 38
		$this->parser = $parser;
100 38
		$this->nameOfActiveSkin = $this->detectSkinInUse(
101 38
			defined( 'MW_NO_SESSION' )
102 38
		);
103 38
	}
104
105
	/**
106
	 * Adds the error tracking category to the current page if not done already.
107
	 */
108 13
	public function addErrorTrackingCategory() {
109 13
		if ( $this->articleTrackedOnError ) {
110 11
			return;
111
		}
112 13
		$this->placeTrackingCategory( 'bootstrap-components-error-tracking-category' );
113 13
		$this->articleTrackedOnError = true;
114 13
	}
115
116
	/**
117
	 * Adds the supplied modules to the parser output.
118
	 *
119
	 * @param array $modulesToAdd
120
	 */
121 20
	public function addModules( $modulesToAdd ) {
122 20
		$parserOutput = $this->getParser()->getOutput();
123 20
		if ( is_a( $parserOutput, ParserOutput::class ) ) {
124
			// Q: when do we expect \Parser->getOutput() no to be a \ParserOutput? A:During tests.
125 20
			$parserOutput->addModules( $modulesToAdd );
126 20
		}
127 20
	}
128
129
	/**
130
	 * Adds the tracking category to the current page if not done already.
131
	 */
132 18
	public function addTrackingCategory() {
133 18
		if ( $this->articleTracked ) {
134 18
			return;
135
		}
136 18
		$this->placeTrackingCategory( 'bootstrap-components-tracking-category' );
137 18
		$this->articleTracked = true;
138 18
	}
139
140
	/**
141
	 * Unless I find a solution for the integration test problem, I cannot use an instance of
142
	 * ParserOutputHelper in ImageModal to ascertain this. In integration tests, "we" use a
143
	 * different parser than the InternalParseBeforeLinks-Hook. At least, after I added
144
	 * Scribunto _unit_ tests. All messes up, I'm afraid. ImageModal better use global parser, and
145
	 * for the time being this method will be
146
	 * @deprecated
147
	 *
148
	 * @return bool|null
149
	 */
150
	public function areImageModalsSuppressed() {
151
		return $this->getParser()->getOutput()->getExtensionData( 'bsc_no_image_modal' );
152
	}
153
154
	/**
155
	 * Returns the raw html that is be inserted at the end of the page.
156
	 *
157
	 * @param \ParserOutput $parserOutput
158
	 *
159
	 * @return string
160
	 */
161 24
	public function getContentForLaterInjection( \ParserOutput $parserOutput ) {
162 24
		$deferredContent = $parserOutput->getExtensionData( self::EXTENSION_DATA_DEFERRED_CONTENT_KEY );
163
164 24
		if ( empty( $deferredContent ) || !is_array( $deferredContent ) ) {
165 23
			return '';
166
		}
167
168
		// clearing extension data for unit and integration tests to work
169 7
		$parserOutput->setExtensionData( self::EXTENSION_DATA_DEFERRED_CONTENT_KEY, null );
170 7
		return self::INJECTION_PREFIX . implode( array_values( $deferredContent ) ) . self::INJECTION_SUFFIX;
171
	}
172
173
	/**
174
	 * @return string
175
	 */
176 22
	public function getNameOfActiveSkin() {
177 22
		return $this->nameOfActiveSkin;
178
	}
179
180
	/**
181
	 * Allows for html fragments for a given container id to be stored, so that it can be added to the page at a later time.
182
	 *
183
	 * @param string $id
184
	 * @param string $rawHtml
185
	 *
186
	 * @return ParserOutputHelper $this (fluid)
187
	 */
188 10
	public function injectLater( $id, $rawHtml ) {
189 10
		if ( !empty( $rawHtml ) ) {
190 7
			$deferredContent = $this->getParser()->getOutput()->getExtensionData( self::EXTENSION_DATA_DEFERRED_CONTENT_KEY );
191 7
			if ( empty( $deferredContent ) ) {
192 7
				$deferredContent = [];
193 7
			}
194 7
			$deferredContent[$id] = $rawHtml;
195 7
			$this->getParser()->getOutput()->setExtensionData( self::EXTENSION_DATA_DEFERRED_CONTENT_KEY, $deferredContent );
196 7
		}
197 10
		return $this;
198
	}
199
200
	/**
201
	 * Adds the bootstrap modules and styles to the page, if not done already
202
	 */
203 20
	public function loadBootstrapModules() {
204 20
		$parserOutput = $this->getParser()->getOutput();
205 20
		if ( is_a( $parserOutput, ParserOutput::class ) ) {
206
			// Q: when do we expect \Parser->getOutput() no to be a \ParserOutput? A: During tests.
207 20
			$parserOutput->addModuleStyles( 'ext.bootstrap.styles' );
208 20
			$parserOutput->addModuleScripts( 'ext.bootstrap.scripts' );
209 20
			if ( $this->vectorSkinInUse() ) {
210 20
				$parserOutput->addModules( 'ext.bootstrapComponents.vector-fix' );
211 20
			}
212 20
		}
213 20
	}
214
215
	/**
216
	 * Formats a text as error text so it can be added to the output.
217
	 *
218
	 * @param string $errorMessageName
219
	 *
220
	 * @return string
221
	 */
222 16
	public function renderErrorMessage( $errorMessageName ) {
223 16
		if ( !$errorMessageName || !trim( $errorMessageName ) ) {
224 4
			return '';
225
		}
226 12
		$this->addErrorTrackingCategory();
227 12
		return Html::rawElement(
228 12
			'span',
229 12
			[ 'class' => 'error' ],
230 12
			wfMessage( trim( $errorMessageName ) )->inContentLanguage()->title( $this->parser->getTitle() )->parse()
231 12
		);
232
	}
233
234
	/**
235
	 * Returns true, if active skin is vector
236
	 *
237
	 * @return bool
238
	 */
239 21
	public function vectorSkinInUse() {
240 21
		return strtolower( $this->getNameOfActiveSkin() ) == 'vector';
241
	}
242
243
	/**
244
	 * @return \Parser
245
	 */
246 22
	protected function getParser() {
247 22
		return $this->parser;
248
	}
249
250
	/**
251
	 * @param bool $useConfig   set to true, if we can't rely on {@see \RequestContext::getSkin}
252
	 *
253
	 * @return string
254
	 */
255 38
	private function detectSkinInUse( $useConfig = false ) {
256 38
		if ( !$useConfig ) {
257 38
			$skin = RequestContext::getMain()->getSkin();
258 38
		}
259 38
		if ( !empty( $skin ) && is_a( $skin, 'Skin' ) ) {
260 38
			return $skin->getSkinName();
261
		}
262 1
		$mainConfig = MediaWikiServices::getInstance()->getMainConfig();
263
		try {
264 1
			$defaultSkin = $mainConfig->get( 'DefaultSkin' );
265 1
		} catch ( \ConfigException $e ) {
0 ignored issues
show
The class ConfigException does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
266
			$defaultSkin = 'unknown';
267
		}
268 1
		return empty( $defaultSkin ) ? 'unknown' : $defaultSkin;
269
	}
270
271
	/**
272
	 * Adds current page to the indicated tracking category, if not done already.
273
	 *
274
	 * @param String $trackingCategoryMessageName name of the message, containing the tracking category
275
	 */
276 21
	private function placeTrackingCategory( $trackingCategoryMessageName ) {
277 21
		$categoryMessage = wfMessage( $trackingCategoryMessageName )->inContentLanguage();
278 21
		$parserOutput = $this->parser->getOutput();
279 21
		if ( !$categoryMessage->isDisabled() && is_a( $parserOutput, ParserOutput::class ) ) {
280
			// Q: when do we expect \Parser->getOutput() no to be a \ParserOutput? A:During tests.
281 12
			$cat = Title::makeTitleSafe( NS_CATEGORY, $categoryMessage->text() );
282 12
			if ( $cat ) {
283 12
				$sort = (string) $parserOutput->getProperty( 'defaultsort' );
284 12
				$parserOutput->addCategory( $cat->getDBkey(), $sort );
285 12
			} else {
286
				wfDebug( __METHOD__ . ": [[MediaWiki:{$trackingCategoryMessageName}]] is not a valid title!\n" );
287
			}
288 12
		}
289 21
	}
290
}
291