Passed
Push — master ( 7eba59...66bf08 )
by
unknown
71:31 queued 36:31
created

BasicBackend::getRawDictionaryContent()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 42
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 17
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 42
rs 8.8333
ccs 15
cts 15
cp 1
crap 7
1
<?php
2
3
/**
4
 * File holding the Lingo\Backend class
5
 *
6
 * This file is part of the MediaWiki extension Lingo.
7
 *
8
 * @copyright 2011 - 2018, Stephan Gambke
9
 * @license GPL-2.0-or-later
10
 *
11
 * The Lingo extension is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU General Public License as published by the Free
13
 * Software Foundation; either version 2 of the License, or (at your option) any
14
 * later version.
15
 *
16
 * The Lingo extension is distributed in the hope that it will be useful, but
17
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
19
 * details.
20
 *
21
 * You should have received a copy of the GNU General Public License along
22
 * with this program. If not, see <http://www.gnu.org/licenses/>.
23
 *
24
 * @author Stephan Gambke
25
 * @file
26
 * @ingroup Lingo
27
 */
28
namespace Lingo;
29
30
use ApprovedRevs;
0 ignored issues
show
Bug introduced by
The type ApprovedRevs was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
31
use Hooks;
0 ignored issues
show
Bug introduced by
The type Hooks was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
32
use Parser;
0 ignored issues
show
Bug introduced by
The type Parser was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
33
use ParserOptions;
0 ignored issues
show
Bug introduced by
The type ParserOptions was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
34
use Revision;
0 ignored issues
show
Bug introduced by
The type Revision was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
35
use TextContent;
0 ignored issues
show
Bug introduced by
The type TextContent was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
36
use Title;
0 ignored issues
show
Bug introduced by
The type Title was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
37
use User;
0 ignored issues
show
Bug introduced by
The type User was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
38
use WikiPage;
0 ignored issues
show
Bug introduced by
The type WikiPage was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
39
40
/**
41
 * The Lingo\BasicBackend class.
42
 *
43
 * @ingroup Lingo
44
 */
45
class BasicBackend extends Backend {
46
47
	protected $mArticleLines = null;
48
49
	/**
50
	 * Lingo\BasicBackend constructor.
51
	 * @param MessageLog|null $messages
52
	 */
53 1
	public function __construct( MessageLog &$messages = null ) {
54 1
		parent::__construct( $messages );
55
56 1
		$this->registerHooks();
57 1
	}
58
59 17
	protected function registerHooks() {
60 17
		Hooks::register( 'ArticlePurge', [ $this, 'purgeCache' ] );
61 17
		Hooks::register( 'PageContentSave', [ $this, 'purgeCache' ] );
62 17
	}
63
64
	/**
65
	 * This function returns the next element. The element is an array of four
66
	 * strings: Term, Definition, Link, Source. For the Lingo\BasicBackend Link
67
	 * and Source are set to null. If there is no next element the function
68
	 * returns null.
69
	 *
70
	 * @return array | null
71
	 * @throws \MWException
72
	 */
73 7
	public function next() {
74 7
		static $term = null;
75 7
		static $definitions = [];
76 7
		static $ret = [];
77
78 7
		$this->collectDictionaryLines();
79
80
		// loop backwards: accumulate definitions until term found
81 7
		while ( ( count( $ret ) === 0 ) && ( $this->mArticleLines ) ) {
82
83 7
			$line = array_pop( $this->mArticleLines );
84
85 7
			if ( $this->isValidGlossaryLine( $line ) ) {
86
87 6
				list( $term, $definitions ) = $this->processNextGlossaryLine( $line, $term, $definitions );
88
89 6
				if ( $term !== null ) {
90 6
					$ret = $this->queueDefinitions( $definitions, $term );
91
				}
92
			}
93
		}
94
95 7
		return array_pop( $ret );
96
	}
97
98
	/**
99
	 * @param string $line
100
	 * @param string $term
101
	 * @param string[] $definitions
102
	 * @return array
103
	 */
104 9
	protected function processNextGlossaryLine( $line, $term, $definitions ) {
105 9
		$chunks = explode( ':', $line, 2 );
106
107
		// found a new definition?
108 9
		if ( count( $chunks ) === 2 ) {
109
110
			// wipe the data if it's a totally new term definition
111 9
			if ( !empty( $term ) && count( $definitions ) > 0 ) {
112 9
				$definitions = [];
113 9
				$term = null;
114
			}
115
116 9
			$definitions[] = trim( $chunks[ 1 ] );
117
		}
118
119
		// found a new term?
120 9
		if ( strlen( trim( $chunks[ 0 ] ) ) > 1 ) {
121 9
			$term = trim( substr( $chunks[ 0 ], 1 ) );
122
		}
123
124 9
		return [ $term, $definitions ];
125
	}
126
127
	/**
128
	 * @param string[] $definitions
129
	 * @param string $term
130
	 * @return array
131
	 */
132 9
	protected function queueDefinitions( $definitions, $term ) {
133 9
		$ret = [];
134
135 9
		foreach ( $definitions as $definition ) {
136 9
			$ret[] = [
137 9
				Element::ELEMENT_TERM       => $term,
138 9
				Element::ELEMENT_DEFINITION => $definition,
139 9
				Element::ELEMENT_LINK       => null,
140 9
				Element::ELEMENT_SOURCE     => null
141
			];
142
		}
143
144 9
		return $ret;
145
	}
146
147
	/**
148
	 * @throws \MWException
149
	 */
150 14
	protected function collectDictionaryLines() {
151 14
		if ( $this->mArticleLines !== null ) {
152 6
			return;
153
		}
154
155
		// Get Terminology page
156 14
		$dictionaryPageName = $this->getLingoPageName();
157 14
		$dictionaryTitle = $this->getTitleFromText( $dictionaryPageName );
158
159 14
		if ( $dictionaryTitle->getInterwiki() !== '' ) {
160 1
			$this->getMessageLog()->addError( wfMessage( 'lingo-terminologypagenotlocal', $dictionaryPageName )->inContentLanguage()->text() );
0 ignored issues
show
Bug introduced by
The function wfMessage was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

160
			$this->getMessageLog()->addError( /** @scrutinizer ignore-call */ wfMessage( 'lingo-terminologypagenotlocal', $dictionaryPageName )->inContentLanguage()->text() );
Loading history...
161 1
			return;
162
		}
163
164 13
		$rawContent = $this->getRawDictionaryContent( $dictionaryTitle );
165
166
		// Expand templates and variables in the text, producing valid, static
167
		// wikitext. Have to use a new anonymous user to avoid any leakage as
168
		// Lingo is caching only one user-independent glossary
169 13
		$parser = new Parser;
170 13
		$content = $parser->preprocess( $rawContent, $dictionaryTitle, new ParserOptions( new User() ) );
171
172 13
		$this->mArticleLines = explode( "\n", $content );
173 13
	}
174
175
	/**
176
	 * @return string
177
	 */
178 15
	private function getLingoPageName() {
179 15
		global $wgexLingoPage;
180 15
		return $wgexLingoPage ? $wgexLingoPage : wfMessage( 'lingo-terminologypagename' )->inContentLanguage()->text();
0 ignored issues
show
Bug introduced by
The function wfMessage was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

180
		return $wgexLingoPage ? $wgexLingoPage : /** @scrutinizer ignore-call */ wfMessage( 'lingo-terminologypagename' )->inContentLanguage()->text();
Loading history...
181
	}
182
183
	/**
184
	 * @param Title $dictionaryTitle
185
	 *
186
	 * @return null|string
187
	 */
188 13
	protected function getRawDictionaryContent( Title $dictionaryTitle ) {
189 13
		global $wgRequest;
190
191
		// This is a hack special-casing the submitting of the terminology page
192
		// itself. In this case the Revision is not up to date when we get here,
193
		// i.e. $revision->getText() would return outdated Text. This hack takes the
194
		// text directly out of the data from the web request.
195 13
		if ( $wgRequest->getVal( 'action', 'view' ) === 'submit' &&
196 13
			$this->getTitleFromText( $wgRequest->getVal( 'title' ) )->getArticleID() === $dictionaryTitle->getArticleID()
197
		) {
198
199 1
			return $wgRequest->getVal( 'wpTextbox1' );
200
		}
201
202 12
		$revision = $this->getRevisionFromTitle( $dictionaryTitle );
203
204 12
		if ( $revision !== null ) {
205
206 11
			$content = $revision->getContent();
207
208 11
			if ( is_null( $content ) ) {
209 1
				return '';
210
			}
211
212 10
			if ( $content instanceof TextContent ) {
213 9
214
				// FIXME: getNativeData() is deprecated for MW 1.33+. Use getText().
0 ignored issues
show
Coding Style introduced by
Comment refers to a FIXME task "getNativeData() is deprecated for MW 1.33+. Use getText"
Loading history...
215
				if ( ! method_exists( $content, 'getText')) {
216 1
					return $content->getNativeData();
217
				}
218
219
				return $content->getText();
220 1
			}
221
222
			$this->getMessageLog()->addError( wfMessage( 'lingo-notatextpage', $dictionaryTitle->getFullText() )->inContentLanguage()->text() );
0 ignored issues
show
Bug introduced by
The function wfMessage was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

222
			$this->getMessageLog()->addError( /** @scrutinizer ignore-call */ wfMessage( 'lingo-notatextpage', $dictionaryTitle->getFullText() )->inContentLanguage()->text() );
Loading history...
223 2
224
		} else {
225
226
			$this->getMessageLog()->addWarning( wfMessage( 'lingo-noterminologypage', $dictionaryTitle->getFullText() )->inContentLanguage()->text() );
227
		}
228
229
		return '';
230
	}
231
232 12
	/**
233 12
	 * Returns revision of the terms page.
234
	 *
235 12
	 * @param Title $title
236
	 * @return null|Revision
237 2
	 */
238 1
	protected function getRevisionFromTitle( Title $title ) {
239
		global $wgexLingoEnableApprovedRevs;
240
241 1
		if ( $wgexLingoEnableApprovedRevs ) {
242
243
			if ( defined( 'APPROVED_REVS_VERSION' ) ) {
244 11
				return $this->getApprovedRevisionFromTitle( $title );
245
			}
246
247
			$this->getMessageLog()->addWarning( wfMessage( 'lingo-noapprovedrevs' )->inContentLanguage()->text() );
0 ignored issues
show
Bug introduced by
The function wfMessage was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

247
			$this->getMessageLog()->addWarning( /** @scrutinizer ignore-call */ wfMessage( 'lingo-noapprovedrevs' )->inContentLanguage()->text() );
Loading history...
248
		}
249
250
		return $this->getLatestRevisionFromTitle( $title );
251
	}
252
253 1
	/**
254 1
	 * Initiates the purging of the cache when the Terminology page was saved or purged.
255
	 *
256 1
	 * @param WikiPage $wikipage
257
	 * @return Bool
258
	 */
259 1
	public function purgeCache( WikiPage &$wikipage ) {
260
		if ( !is_null( $wikipage ) && ( $wikipage->getTitle()->getText() === $this->getLingoPageName() ) ) {
261
262
			$this->getLingoParser()->purgeGlossaryFromCache();
263
		}
264
265
		return true;
266
	}
267
268
	/**
269
	 * The basic backend is cache-enabled so this function returns true.
270 1
	 *
271 1
	 * Actual caching is done by the parser, the backend just calls
272
	 * Lingo\LingoParser::purgeCache when necessary.
273
	 *
274
	 * @return bool
275
	 */
276
	public function useCache() {
277
		return true;
278
	}
279
280
	/**
281
	 * @codeCoverageIgnore
282
	 * @param $dictionaryPage
283
	 * @return Title
284
	 */
285
	protected function getTitleFromText( $dictionaryPage ) {
286
		return Title::newFromTextThrow( $dictionaryPage );
287
	}
288
289
	/**
290
	 * @codeCoverageIgnore
291
	 * @param Title $title
292
	 * @return null|Revision
293
	 */
294
	protected function getApprovedRevisionFromTitle( Title $title ) {
295
		return Revision::newFromId( ApprovedRevs::getApprovedRevID( $title ) );
296
	}
297
298
	/**
299
	 * @codeCoverageIgnore
300
	 * @param Title $title
301
	 * @return null|Revision
302
	 */
303
	protected function getLatestRevisionFromTitle( Title $title ) {
304
		return Revision::newFromTitle( $title );
305 13
	}
306 13
307
	/**
308
	 * @param $line
309
	 * @return bool
310
	 */
311
	protected function isValidGlossaryLine( $line ) {
312
		return !empty( $line ) && ( $line[ 0 ] === ';' || $line[ 0 ] === ':' );
313
	}
314
315
}
316