1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SMW\ParserFunctions; |
4
|
|
|
|
5
|
|
|
use SMW\ParserData; |
6
|
|
|
use SMW\MessageFormatter; |
7
|
|
|
use SMW\Utils\CircularReferenceGuard; |
8
|
|
|
use SMW\ApplicationFactory; |
9
|
|
|
use SMW\ProcessingErrorMsgHandler; |
10
|
|
|
use SMW\DIProperty; |
11
|
|
|
use Parser; |
12
|
|
|
use SMWQueryProcessor as QueryProcessor; |
13
|
|
|
use SMWQuery as Query; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Provides the {{#ask}} parser function |
17
|
|
|
* |
18
|
|
|
* @see http://www.semantic-mediawiki.org/wiki/Help:Ask |
19
|
|
|
* |
20
|
|
|
* @license GNU GPL v2+ |
21
|
|
|
* @since 1.9 |
22
|
|
|
* |
23
|
|
|
* @author Markus Krötzsch |
24
|
|
|
* @author Jeroen De Dauw |
25
|
|
|
* @author mwjames |
26
|
|
|
*/ |
27
|
|
|
class AskParserFunction { |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var ParserData |
31
|
|
|
*/ |
32
|
|
|
private $parserData; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var MessageFormatter |
36
|
|
|
*/ |
37
|
|
|
private $messageFormatter; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var CircularReferenceGuard |
41
|
|
|
*/ |
42
|
|
|
private $circularReferenceGuard; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var boolean |
46
|
|
|
*/ |
47
|
|
|
private $showMode = false; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var ApplicationFactory |
51
|
|
|
*/ |
52
|
|
|
private $applicationFactory; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @since 1.9 |
56
|
|
|
* |
57
|
|
|
* @param ParserData $parserData |
58
|
|
|
* @param MessageFormatter $messageFormatter |
59
|
|
|
* @param CircularReferenceGuard $circularReferenceGuard |
60
|
|
|
*/ |
61
|
114 |
|
public function __construct( ParserData $parserData, MessageFormatter $messageFormatter, CircularReferenceGuard $circularReferenceGuard ) { |
62
|
114 |
|
$this->parserData = $parserData; |
63
|
114 |
|
$this->messageFormatter = $messageFormatter; |
64
|
114 |
|
$this->circularReferenceGuard = $circularReferenceGuard; |
65
|
114 |
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Enable showMode (normally only invoked by {{#show}}) |
69
|
|
|
* |
70
|
|
|
* @since 1.9 |
71
|
|
|
* |
72
|
|
|
* @return AskParserFunction |
73
|
|
|
*/ |
74
|
14 |
|
public function setShowMode( $mode ) { |
75
|
14 |
|
$this->showMode = $mode; |
76
|
14 |
|
return $this; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* {{#ask}} is disabled (see $smwgQEnabled) |
81
|
|
|
* |
82
|
|
|
* @since 1.9 |
83
|
|
|
* |
84
|
|
|
* @return string|null |
85
|
|
|
*/ |
86
|
2 |
|
public function isQueryDisabled() { |
87
|
2 |
|
return $this->messageFormatter->addFromKey( 'smw_iq_disabled' )->getHtml(); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Parse parameters, return results from the query printer and update the |
92
|
|
|
* ParserOutput with meta data from the query |
93
|
|
|
* |
94
|
|
|
* FIXME $rawParams use IParameterFormatter -> QueryParameterFormatter class |
95
|
|
|
* Parse parameters and return query results to the ParserOutput |
96
|
|
|
* object and output result data from the QueryProcessor |
97
|
|
|
* |
98
|
|
|
* @todo $rawParams should be of IParameterFormatter |
99
|
|
|
* QueryParameterFormatter class |
100
|
|
|
* |
101
|
|
|
* @since 1.9 |
102
|
|
|
* |
103
|
|
|
* @param array $functionParams |
104
|
|
|
* |
105
|
|
|
* @return string|null |
106
|
|
|
*/ |
107
|
110 |
|
public function parse( array $functionParams ) { |
108
|
|
|
|
109
|
|
|
// Do we still need this? |
110
|
|
|
// Reference found in SRF_Exhibit.php, SRF_Ploticus.php, SRF_Timeline.php, SRF_JitGraph.php |
111
|
110 |
|
global $smwgIQRunningNumber; |
|
|
|
|
112
|
110 |
|
$smwgIQRunningNumber++; |
113
|
|
|
|
114
|
110 |
|
$this->applicationFactory = ApplicationFactory::getInstance(); |
115
|
|
|
|
116
|
110 |
|
$functionParams = $this->prepareFunctionParameters( |
117
|
|
|
$functionParams |
118
|
|
|
); |
119
|
|
|
|
120
|
110 |
|
$result = $this->doFetchResultsFromFunctionParameters( |
121
|
|
|
$functionParams |
122
|
|
|
); |
123
|
|
|
|
124
|
110 |
|
$this->parserData->pushSemanticDataToParserOutput(); |
125
|
|
|
|
126
|
|
|
// 1.23+ add options so changes are recognized in case of: |
127
|
|
|
// - 'userlang' will trigger a cache fragmentation by user language |
128
|
|
|
// - 'dateformat' will trigger a cache fragmentation by date preference |
129
|
110 |
|
if ( method_exists( $this->parserData->getOutput(), 'recordOption' ) ) { |
130
|
110 |
|
$this->parserData->getOutput()->recordOption( 'userlang' ); |
131
|
110 |
|
$this->parserData->getOutput()->recordOption( 'dateformat' ); |
132
|
|
|
} |
133
|
|
|
|
134
|
110 |
|
return $result; |
135
|
|
|
} |
136
|
|
|
|
137
|
110 |
|
private function prepareFunctionParameters( array $functionParams ) { |
138
|
|
|
|
139
|
|
|
// Remove parser object from parameters array |
140
|
110 |
|
if( isset( $functionParams[0] ) && $functionParams[0] instanceof Parser ) { |
|
|
|
|
141
|
91 |
|
array_shift( $functionParams ); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
// Filter invalid parameters |
145
|
110 |
|
foreach ( $functionParams as $key => $value ) { |
146
|
|
|
|
147
|
|
|
// First and marked printrequests |
148
|
109 |
|
if ( $key == 0 || ( $value !== '' && $value{0} === '?' ) ) { |
149
|
109 |
|
continue; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
// Filter parameters that can not be split into |
153
|
|
|
// argument=value |
154
|
89 |
|
if ( strpos( $value, '=' ) === false ) { |
155
|
89 |
|
unset( $functionParams[$key] ); |
156
|
|
|
} |
157
|
|
|
} |
158
|
|
|
|
159
|
110 |
|
return $functionParams; |
160
|
|
|
} |
161
|
|
|
|
162
|
110 |
|
private function doFetchResultsFromFunctionParameters( array $functionParams ) { |
|
|
|
|
163
|
|
|
|
164
|
110 |
|
$contextPage = $this->parserData->getSubject(); |
165
|
|
|
|
166
|
110 |
|
list( $query, $this->params ) = QueryProcessor::getQueryAndParamsFromFunctionParams( |
|
|
|
|
167
|
|
|
$functionParams, |
168
|
110 |
|
SMW_OUTPUT_WIKI, |
169
|
110 |
|
QueryProcessor::INLINE_QUERY, |
170
|
110 |
|
$this->showMode, |
171
|
|
|
$contextPage |
172
|
|
|
); |
173
|
|
|
|
174
|
110 |
|
$query->setContextPage( |
175
|
|
|
$contextPage |
176
|
|
|
); |
177
|
|
|
|
178
|
110 |
|
$queryHash = $query->getHash(); |
179
|
|
|
|
180
|
110 |
|
$this->circularReferenceGuard->mark( $queryHash ); |
181
|
|
|
|
182
|
|
|
// If we caught in a circular loop (due to a template referencing to itself) |
183
|
|
|
// then we stop here before the next query execution to avoid an infinite |
184
|
|
|
// self-reference |
185
|
110 |
|
if ( $this->circularReferenceGuard->isCircularByRecursionFor( $queryHash ) ) { |
186
|
2 |
|
return ''; |
187
|
|
|
} |
188
|
|
|
|
189
|
109 |
|
$result = QueryProcessor::getResultFromQuery( |
190
|
|
|
$query, |
191
|
109 |
|
$this->params, |
192
|
109 |
|
SMW_OUTPUT_WIKI, |
193
|
109 |
|
QueryProcessor::INLINE_QUERY |
194
|
|
|
); |
195
|
|
|
|
196
|
109 |
|
$format = $this->params['format']->getValue(); |
197
|
|
|
|
198
|
|
|
// FIXME Parser should be injected into the ResultPrinter |
199
|
|
|
// Enables specific formats to import its annotation data from |
200
|
|
|
// a recursive parse process in the result format |
201
|
|
|
// e.g. using ask query to search/set an invert property value |
202
|
109 |
|
if ( isset( $this->params['import-annotation'] ) && $this->params['import-annotation']->getValue() ) { |
203
|
4 |
|
$this->parserData->importFromParserOutput( $GLOBALS['wgParser']->getOutput() ); |
204
|
|
|
} |
205
|
|
|
|
206
|
109 |
|
$this->circularReferenceGuard->unmark( $queryHash ); |
207
|
|
|
|
208
|
|
|
// In case of an query error add a marker to the subject for discoverability |
209
|
|
|
// of a failed query, don't bail-out as we can have results and errors |
210
|
|
|
// at the same time |
211
|
109 |
|
$this->addProcessingError( $query->getErrors() ); |
212
|
|
|
|
213
|
109 |
|
$this->addQueryProfile( |
214
|
|
|
$query, |
215
|
|
|
$format |
216
|
|
|
); |
217
|
|
|
|
218
|
109 |
|
return $result; |
219
|
|
|
} |
220
|
|
|
|
221
|
109 |
|
private function addQueryProfile( $query, $format ) { |
222
|
|
|
|
223
|
|
|
$settings = $this->applicationFactory->getSettings(); |
224
|
109 |
|
|
225
|
1 |
|
// If the smwgQueryProfiler is marked with FALSE then just don't create a profile. |
226
|
|
|
if ( $settings->get( 'smwgQueryProfiler' ) === false ) { |
227
|
|
|
return; |
228
|
108 |
|
} |
229
|
108 |
|
|
230
|
108 |
|
$query->setOption( |
231
|
|
|
Query::PROC_QUERY_TIME, |
232
|
|
|
$settings->get( 'smwgQueryDurationEnabled' ) ? $query->getOption( Query::PROC_QUERY_TIME ) : 0 |
233
|
108 |
|
); |
234
|
|
|
|
235
|
108 |
|
$query->setOption( |
236
|
|
|
Query::OPT_PARAMETERS, |
237
|
|
|
$settings->has( 'smwgQueryParametersEnabled' ) ? $settings->get( 'smwgQueryParametersEnabled' ) : false |
238
|
|
|
); |
239
|
|
|
|
240
|
108 |
|
$profileAnnotatorFactory = $this->applicationFactory->getQueryFactory()->newProfileAnnotatorFactory(); |
241
|
108 |
|
|
242
|
|
|
$combinedProfileAnnotator = $profileAnnotatorFactory->newCombinedProfileAnnotator( |
243
|
108 |
|
$query, |
244
|
|
|
$format |
245
|
109 |
|
); |
246
|
|
|
|
247
|
109 |
|
$combinedProfileAnnotator->pushAnnotationsTo( |
248
|
105 |
|
$this->parserData->getSemanticData() |
249
|
|
|
); |
250
|
|
|
} |
251
|
6 |
|
|
252
|
6 |
|
private function addProcessingError( $errors ) { |
253
|
|
|
|
254
|
|
|
if ( $errors === array() ) { |
255
|
6 |
|
return; |
256
|
|
|
} |
257
|
6 |
|
|
258
|
6 |
|
$processingErrorMsgHandler = new ProcessingErrorMsgHandler( |
259
|
6 |
|
$this->parserData->getSubject() |
260
|
6 |
|
); |
261
|
|
|
|
262
|
|
|
$property = new DIProperty( '_ASK' ); |
263
|
6 |
|
|
264
|
|
|
foreach ( $errors as $error ) { |
265
|
|
|
$processingErrorMsgHandler->addToSemanticData( |
266
|
|
|
$this->parserData->getSemanticData(), |
267
|
|
|
$processingErrorMsgHandler->newErrorContainerFromMsg( $error, $property ) |
268
|
|
|
); |
269
|
|
|
} |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
} |
273
|
|
|
|
Instead of relying on
global
state, we recommend one of these alternatives:1. Pass all data via parameters
2. Create a class that maintains your state