Completed
Push — master ( 841b7d...3ed1d3 )
by
unknown
13:44 queued 04:45
created

src/BasicBackend.php (3 issues)

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
/**
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   GNU General Public License, version 2 (or any later version)
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;
31
use Hooks;
32
use Parser;
33
use ParserOptions;
34
use Revision;
35
use TextContent;
36
use Title;
37
use User;
38
use WikiPage;
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
55 1
		parent::__construct( $messages );
56
57 1
		$this->registerHooks();
58
59 1
	}
60
61 17
	protected function registerHooks() {
62 17
		Hooks::register( 'ArticlePurge', [ $this, 'purgeCache' ] );
63 17
		Hooks::register( 'PageContentSave', [ $this, 'purgeCache' ] );
64 17
	}
65
66
	/**
67
	 * This function returns the next element. The element is an array of four
68
	 * strings: Term, Definition, Link, Source. For the Lingo\BasicBackend Link
69
	 * and Source are set to null. If there is no next element the function
70
	 * returns null.
71
	 *
72
	 * @return array | null
73
	 * @throws \MWException
74
	 */
75 7
	public function next() {
76
77 7
		static $term = null;
78 7
		static $definitions = [];
79 7
		static $ret = [];
80
81 7
		$this->collectDictionaryLines();
82
83
		// loop backwards: accumulate definitions until term found
84 7
		while ( ( count( $ret ) === 0 ) && ( $this->mArticleLines ) ) {
85
86 7
			$line = array_pop( $this->mArticleLines );
87
88 7
			if ( $this->isValidGlossaryLine( $line ) ) {
89
90 6
				list( $term, $definitions ) = $this->processNextGlossaryLine( $line, $term, $definitions );
91
92 6
				if ( $term !== null ) {
93 6
					$ret = $this->queueDefinitions( $definitions, $term );
94
				}
95
			}
96
		}
97
98 7
		return array_pop( $ret );
99
	}
100
101
	/**
102
	 * @param string $line
103
	 * @param string $term
104
	 * @param string[] $definitions
105
	 * @return array
106
	 */
107 9
	protected function processNextGlossaryLine( $line, $term, $definitions ) {
108
109 9
		$chunks = explode( ':', $line, 2 );
110
111
		// found a new definition?
112 9
		if ( count( $chunks ) === 2 ) {
113
114
			// wipe the data if it's a totally new term definition
115 9
			if ( !empty( $term ) && count( $definitions ) > 0 ) {
116 9
				$definitions = [];
117 9
				$term = null;
118
			}
119
120 9
			$definitions[] = trim( $chunks[ 1 ] );
121
		}
122
123
		// found a new term?
124 9
		if ( strlen( trim( $chunks[ 0 ] ) ) > 1 ) {
125 9
			$term = trim( substr( $chunks[ 0 ], 1 ) );
126
		}
127
128 9
		return [ $term, $definitions ];
129
	}
130
131
	/**
132
	 * @param $definitions
133
	 * @param $term
134
	 * @return array
135
	 */
136 9
	protected function queueDefinitions( $definitions, $term ) {
137 9
		$ret = [];
138
139 9
		foreach ( $definitions as $definition ) {
140 9
			$ret[] = [
141 9
				Element::ELEMENT_TERM       => $term,
142 9
				Element::ELEMENT_DEFINITION => $definition,
143 9
				Element::ELEMENT_LINK       => null,
144 9
				Element::ELEMENT_SOURCE     => null
145
			];
146
		}
147
148 9
		return $ret;
149
	}
150
151
	/**
152
	 * @throws \MWException
153
	 */
154 14
	protected function collectDictionaryLines() {
155
156 14
		if ( $this->mArticleLines !== null ) {
157 6
			return;
158
		}
159
160
		// Get Terminology page
161 14
		$dictionaryPageName = $this->getLingoPageName();
162 14
		$dictionaryTitle = $this->getTitleFromText( $dictionaryPageName );
163
164 14
		if ( $dictionaryTitle->getInterwiki() !== '' ) {
165 1
			$this->getMessageLog()->addError( wfMessage( 'lingo-terminologypagenotlocal', $dictionaryPageName )->inContentLanguage()->text() );
166 1
			return;
167
		}
168
169 13
		$rawContent = $this->getRawDictionaryContent( $dictionaryTitle );
170
171
		// Expand templates and variables in the text, producing valid, static
172
		// wikitext. Have to use a new anonymous user to avoid any leakage as
173
		// Lingo is caching only one user-independent glossary
174 13
		$parser = new Parser;
175 13
		$content = $parser->preprocess( $rawContent, $dictionaryTitle, new ParserOptions( new User() ) );
176
177 13
		$this->mArticleLines = explode( "\n", $content );
178 13
	}
179
180
	/**
181
	 * @return string
182
	 */
183 15
	private function getLingoPageName() {
184 15
		global $wgexLingoPage;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
185 15
		return $wgexLingoPage ? $wgexLingoPage : wfMessage( 'lingo-terminologypagename' )->inContentLanguage()->text();
186
	}
187
188
	/**
189
	 * @param Title $dictionaryTitle
190
	 *
191
	 * @return null|string
192
	 */
193 13
	protected function getRawDictionaryContent( Title $dictionaryTitle ) {
194
195 13
		global $wgRequest;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
196
197
		// This is a hack special-casing the submitting of the terminology page
198
		// itself. In this case the Revision is not up to date when we get here,
199
		// i.e. $revision->getText() would return outdated Text. This hack takes the
200
		// text directly out of the data from the web request.
201 13
		if ( $wgRequest->getVal( 'action', 'view' ) === 'submit' &&
202 13
			$this->getTitleFromText( $wgRequest->getVal( 'title' ) )->getArticleID() === $dictionaryTitle->getArticleID()
203
		) {
204
205 1
			return $wgRequest->getVal( 'wpTextbox1' );
206
		}
207
208 12
		$revision = $this->getRevisionFromTitle( $dictionaryTitle );
209
210 12
		if ( $revision !== null ) {
211
212 11
			$content = $revision->getContent();
213
214 11
			if ( is_null( $content ) ) {
215 1
				return '';
216
			}
217
218 10
			if ( $content instanceof TextContent ) {
219 9
				return $content->getNativeData();
220
			}
221
222 1
			$this->getMessageLog()->addError( wfMessage( 'lingo-notatextpage', $dictionaryTitle->getFullText() )->inContentLanguage()->text() );
223
224
		} else {
225
226 1
			$this->getMessageLog()->addWarning( wfMessage( 'lingo-noterminologypage', $dictionaryTitle->getFullText() )->inContentLanguage()->text() );
227
		}
228
229 2
		return '';
230
	}
231
232
	/**
233
	 * Returns revision of the terms page.
234
	 *
235
	 * @param Title $title
236
	 * @return null|Revision
237
	 */
238 12
	protected function getRevisionFromTitle( Title $title ) {
239 12
		global $wgexLingoEnableApprovedRevs;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

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