ApiSummaryCardContentParser::getCacheHelper()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.5

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 10
ccs 3
cts 6
cp 0.5
crap 2.5
rs 9.9332
c 0
b 0
f 0
1
<?php
2
3
namespace SUC;
4
5
use ParserOptions;
6
use ParserOutput;
7
use ApiBase;
8
use Title;
9
10
/**
11
 * Content parsing can be expensive especially if it contains a large set of
12
 * #ask queries/#if parser functions to form a summary card.
13
 *
14
 * This API module is to parse a text/template and if possible tries to retrieve
15
 * data from a persistent cache layer to avoid unnecessary content parsing.
16
 *
17
 * On the event of NewRevisionFromEditComplete a cached item will be evicted if
18
 * it matches the CacheHelper::getHashFrom.
19
 *
20
 * On the event that a template that was used for generating content  was modified
21
 * then a re-parse of the content is requested the next time instead of a cache
22
 * retrieval.
23
 *
24
 * @license GNU GPL v2+
25
 * @since 1.0
26
 *
27
 * @author mwjames
28
 */
29
class ApiSummaryCardContentParser extends ApiBase {
30
31
	/**
32
	 * @var CacheHelper
33
	 */
34
	private $cacheHelper;
35
36
	/**
37
	 * @since 1.0
38
	 *
39
	 * @param CacheHelper $cacheHelper
40
	 */
41 4
	public function setCacheHelper( CacheHelper $cacheHelper ) {
42 4
		$this->cacheHelper = $cacheHelper;
43 4
	}
44
45
	/**
46
	 * @since 1.0
47
	 *
48
	 * @return CacheHelper
49
	 */
50 2
	public function getCacheHelper() {
51
52 2
		if ( $this->cacheHelper !== null ) {
53 2
			return $this->cacheHelper;
54
		}
55
56
		return $this->cacheHelper = CacheHelper::newFromOptions(
57
			Options::newFromGlobals()
58
		);
59
	}
60
61
	/**
62
	 * ApiBase::execute
63
	 *
64
	 * @since 1.0
65
	 */
66 3
	public function execute() {
67
68 3
		$data = $this->getDataFrom(
69 3
			$this->extractRequestParams()
70 3
		);
71
72 3
 		$this->getResult()->addValue(
73 3
 			null,
74 3
 			$this->getModuleName(),
75
 			$data
76 3
 		);
77 3
	}
78
79
	/**
80
	 * ApiBase::getAllowedParams
81
	 *
82
	 * @since 1.0
83
	 */
84 3
	public function getAllowedParams() {
85
		return array(
86 3
			'text'         => array( ApiBase::PARAM_TYPE => 'string' ),
87 3
			'template'     => array( ApiBase::PARAM_TYPE => 'string' ),
88 3
			'title'        => array( ApiBase::PARAM_TYPE => 'string' ),
89 3
			'userlanguage' => array( ApiBase::PARAM_TYPE => 'string' )
90 3
		);
91 1
	}
92
93
	/**
94
	 * @codeCoverageIgnore
95
	 * @see ApiBase::getParamDescription
96
	 *
97
	 * @return array
98
	 */
99
	public function getParamDescription() {
100
		return array(
101
			'text'     => 'Contains the text/template to be parsed.',
102
			'template' => 'Contains the template to track possible changes.',
103
			'title'    => 'Subject to filter repeated requests.',
104
			'userlanguage' => 'The user language.'
105
		);
106
	}
107
108
	/**
109
	 * @codeCoverageIgnore
110
	 * @see ApiBase::getDescription
111
	 *
112
	 * @return array
113
	 */
114
	public function getDescription() {
115
		return array(
116
			'Module to parse raw text/templates and store returning results using a persistent cache, if available.'
117
		);
118
	}
119
120 3
	private function getDataFrom( array $params ) {
121
122 3
		$start = microtime( true );
123 3
		$isUntouchedTemplate = false;
124
125
		$data = array(
126 3
			'text' => '',
127
			'time' => false
128 3
		);
129
130 3
		if ( !isset( $params['text'] ) || !isset( $params['title'] ) ) {
131 1
			return $data;
132
		}
133
134 2
		$blobStore = $this->getCacheHelper()->getBlobStore();
135
136 2
		list( $templateKey, $templateTouched ) = $this->getTemplateInfoFrom(
137
			$params
138 2
		);
139
140 2
		$title = $this->getCacheHelper()->newTitleFromText(
141 2
			$params['title']
142 2
		);
143
144 2
		$hash = $this->getCacheHelper()->getHashFrom( $title );
145
146 2
		$container = $blobStore->read( $hash );
147
148
		// If the template was touched then re-parse the content to
149
		// avoid stalled data
150 2
		if ( $container->has( $templateKey ) ) {
151 1
			$isUntouchedTemplate = $container->get( $templateKey ) == $templateTouched;
152 1
		}
153
154
		// Split by lang and fragment into separate containers, while the cache
155
		// is stored on a per subject basis allowing all related containers to
156
		// be purged at once
157 2
		$contentByLanguageKey = 'L#' . $params['userlanguage'] . '#' . ( $title !== null ? $title->getFragment() : '' );
158
159 2
		if ( $isUntouchedTemplate && $hash !== '' && $container->has( $contentByLanguageKey ) ) {
160 1
			wfDebugLog( 'smw', 'SummaryCards API cache hit on ' . $hash );
161 1
			$data = $container->get( $contentByLanguageKey );
162 1
			$data['time']['cached'] = microtime( true ) - $start;
163 1
			return $data;
164
		}
165
166
		$data = array(
167 1
			'text' => $this->doParse( $title, $params['userlanguage'], $params['text'] ),
168
			'time' => array(
169 1
				'parse'  => microtime( true ) - $start
170 1
			)
171 1
		);
172
173
		// Only cache when a template is known
174 1
		if ( $hash !== '' && $templateKey !== '' ) {
175
			$container->set( $contentByLanguageKey, $data );
176
			$container->set( $templateKey, $templateTouched );
177
			$blobStore->save( $container );
178
		}
179
180 1
		return $data;
181
	}
182
183 2
	private function getTemplateInfoFrom( $params ) {
184
185 2
		$templateKey = '';
186 2
		$templateTouched = 0;
187
188 2
		if ( isset( $params['template'] ) ) {
189 1
			$template = Title::makeTitleSafe( NS_TEMPLATE, $params['template'] );
190 1
			$templateTouched = $template->getTouched();
191 1
			$templateKey = 'T#' . $params['template'] . '#' . $params['userlanguage'];
192 1
		}
193
194 2
		return array( $templateKey, $templateTouched );
195
	}
196
197 1
	private function doParse( $title, $userLanguage, $text ) {
198
199 1
		$parserOutput = $GLOBALS['wgParser']->parse(
200 1
			$text,
201 1
			$title,
202 1
			$this->makeParserOptions( $title, $userLanguage )
203 1
		);
204
205 1
		return $parserOutput instanceof ParserOutput ? $parserOutput->getText() : '' ;
0 ignored issues
show
Bug introduced by
The class ParserOutput 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...
206
	}
207
208 1
	private function makeParserOptions( $title, $userLanguage ) {
209
210 1
		$user = null;
211
212 1
		$parserOptions = new ParserOptions( $user );
213 1
		$parserOptions->setInterfaceMessage( true );
214 1
		$parserOptions->setUserLang( $userLanguage );
215
216
		// Ensure that no dependency processing occurs with the #ask being
217
		// bound to the template output and not to the context page
218 1
		$parserOptions->smwAskNoDependencyTracking = true;
219
220 1
		return $parserOptions;
221
	}
222
223
}
224