These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | use SMW\DataTypeRegistry; |
||
4 | use SMW\DIWikiPage; |
||
5 | use SMW\Query\Language\ClassDescription; |
||
6 | use SMW\Query\Language\ConceptDescription; |
||
7 | use SMW\Query\Language\Description; |
||
8 | use SMW\Query\Language\NamespaceDescription; |
||
9 | use SMW\Query\Language\SomeProperty; |
||
10 | use SMW\Query\Language\ThingDescription; |
||
11 | use SMW\Query\Parser\DescriptionProcessor; |
||
12 | |||
13 | /** |
||
14 | * Objects of this class are in charge of parsing a query string in order |
||
15 | * to create an SMWDescription. The class and methods are not static in order |
||
16 | * to more cleanly store the intermediate state and progress of the parser. |
||
17 | * @ingroup SMWQuery |
||
18 | * @author Markus Krötzsch |
||
19 | */ |
||
20 | class SMWQueryParser { |
||
21 | |||
22 | private $separatorStack; // list of open blocks ("parentheses") that need closing at current step |
||
23 | private $currentString; // remaining string to be parsed (parsing eats query string from the front) |
||
24 | |||
25 | private $defaultNamespace; // description of the default namespace restriction, or NULL if not used |
||
26 | |||
27 | private $categoryPrefix; // cache label of category namespace . ':' |
||
28 | private $conceptPrefix; // cache label of concept namespace . ':' |
||
29 | private $categoryPrefixCannonical; // cache canonnical label of category namespace . ':' |
||
30 | private $conceptPrefixCannonical; // cache canonnical label of concept namespace . ':' |
||
31 | private $queryFeatures; // query features to be supported, format similar to $smwgQFeatures |
||
32 | |||
33 | /** |
||
34 | * @var DescriptionProcessor |
||
35 | */ |
||
36 | private $descriptionProcessor; |
||
37 | |||
38 | 214 | public function __construct( $queryFeatures = false ) { |
|
39 | 214 | global $wgContLang, $smwgQFeatures; |
|
40 | |||
41 | 214 | $this->categoryPrefix = $wgContLang->getNsText( NS_CATEGORY ) . ':'; |
|
42 | 214 | $this->conceptPrefix = $wgContLang->getNsText( SMW_NS_CONCEPT ) . ':'; |
|
43 | 214 | $this->categoryPrefixCannonical = 'Category:'; |
|
44 | 214 | $this->conceptPrefixCannonical = 'Concept:'; |
|
45 | |||
46 | 214 | $this->defaultNamespace = null; |
|
47 | 214 | $this->queryFeatures = $queryFeatures === false ? $smwgQFeatures : $queryFeatures; |
|
48 | 214 | $this->descriptionProcessor = new DescriptionProcessor( $this->queryFeatures ); |
|
49 | 214 | } |
|
50 | |||
51 | /** |
||
52 | * @since 2.4 |
||
53 | * |
||
54 | * @param DIWikiPage|null $contextPage |
||
55 | */ |
||
56 | 108 | public function setContextPage( DIWikiPage $contextPage = null ) { |
|
57 | 108 | $this->descriptionProcessor->setContextPage( $contextPage ); |
|
58 | 108 | } |
|
59 | |||
60 | /** |
||
61 | * Provide an array of namespace constants that are used as default restrictions. |
||
62 | * If NULL is given, no such default restrictions will be added (faster). |
||
63 | */ |
||
64 | 109 | public function setDefaultNamespaces( $namespaceArray ) { |
|
65 | 109 | $this->defaultNamespace = null; |
|
66 | |||
67 | 109 | if ( !is_null( $namespaceArray ) ) { |
|
68 | 1 | foreach ( $namespaceArray as $ns ) { |
|
69 | 1 | $this->defaultNamespace = $this->descriptionProcessor->constructDisjunctiveCompoundDescriptionFrom( |
|
70 | 1 | $this->defaultNamespace, |
|
71 | 1 | new NamespaceDescription( $ns ) |
|
72 | ); |
||
73 | } |
||
74 | } |
||
75 | 109 | } |
|
76 | |||
77 | /** |
||
78 | * Compute an SMWDescription from a query string. Returns whatever descriptions could be |
||
79 | * wrestled from the given string (the most general result being SMWThingDescription if |
||
80 | * no meaningful condition was extracted). |
||
81 | * |
||
82 | * @param string $queryString |
||
83 | * |
||
84 | * @return Description |
||
85 | */ |
||
86 | 170 | public function getQueryDescription( $queryString ) { |
|
87 | |||
88 | 170 | $this->descriptionProcessor->clear(); |
|
89 | 170 | $this->currentString = $queryString; |
|
90 | 170 | $this->separatorStack = array(); |
|
91 | 170 | $setNS = false; |
|
92 | 170 | $result = $this->getSubqueryDescription( $setNS ); |
|
93 | |||
94 | 170 | if ( !$setNS ) { // add default namespaces if applicable |
|
95 | 169 | $result = $this->descriptionProcessor->constructConjunctiveCompoundDescriptionFrom( $this->defaultNamespace, $result ); |
|
96 | } |
||
97 | |||
98 | 170 | if ( is_null( $result ) ) { // parsing went wrong, no default namespaces |
|
99 | 1 | $result = new ThingDescription(); |
|
100 | } |
||
101 | |||
102 | |||
103 | 170 | return $result; |
|
104 | } |
||
105 | |||
106 | /** |
||
107 | * Return array of error messages (possibly empty). |
||
108 | * |
||
109 | * @return array |
||
110 | */ |
||
111 | 109 | public function getErrors() { |
|
112 | 109 | return $this->descriptionProcessor->getErrors(); |
|
113 | } |
||
114 | |||
115 | /** |
||
116 | * Return error message or empty string if no error occurred. |
||
117 | * |
||
118 | * @return string |
||
119 | */ |
||
120 | public function getErrorString() { |
||
121 | return smwfEncodeMessages( $this->getErrors() ); |
||
122 | } |
||
123 | |||
124 | /** |
||
125 | * Compute an SMWDescription for current part of a query, which should |
||
126 | * be a standalone query (the main query or a subquery enclosed within |
||
127 | * "\<q\>...\</q\>". Recursively calls similar methods and returns NULL upon error. |
||
128 | * |
||
129 | * The call-by-ref parameter $setNS is a boolean. Its input specifies whether |
||
130 | * the query should set the current default namespace if no namespace restrictions |
||
131 | * were given. If false, the calling super-query is happy to set the required |
||
132 | * NS-restrictions by itself if needed. Otherwise the subquery has to impose the defaults. |
||
133 | * This is so, since outermost queries and subqueries of disjunctions will have to set |
||
134 | * their own default restrictions. |
||
135 | * |
||
136 | * The return value of $setNS specifies whether or not the subquery has a namespace |
||
137 | * specification in place. This might happen automatically if the query string imposes |
||
138 | * such restrictions. The return value is important for those callers that otherwise |
||
139 | * set up their own restrictions. |
||
140 | * |
||
141 | * Note that $setNS is no means to switch on or off default namespaces in general, |
||
142 | * but just controls query generation. For general effect, the default namespaces |
||
143 | * should be set to NULL. |
||
144 | * |
||
145 | * @return Description|null |
||
146 | */ |
||
147 | 170 | private function getSubqueryDescription( &$setNS ) { |
|
148 | 170 | $conjunction = null; // used for the current inner conjunction |
|
149 | 170 | $disjuncts = array(); // (disjunctive) array of subquery conjunctions |
|
150 | 170 | $hasNamespaces = false; // does the current $conjnuction have its own namespace restrictions? |
|
151 | 170 | $mustSetNS = $setNS; // must NS restrictions be set? (may become true even if $setNS is false) |
|
152 | |||
153 | 170 | $continue = ( $chunk = $this->readChunk() ) !== ''; // skip empty subquery completely, thorwing an error |
|
154 | |||
155 | 170 | while ( $continue ) { |
|
156 | 170 | $setsubNS = false; |
|
157 | |||
158 | switch ( $chunk ) { |
||
159 | 170 | case '[[': // start new link block |
|
160 | 170 | $ld = $this->getLinkDescription( $setsubNS ); |
|
161 | |||
162 | 170 | if ( !is_null( $ld ) ) { |
|
163 | 170 | $conjunction = $this->descriptionProcessor->constructConjunctiveCompoundDescriptionFrom( $conjunction, $ld ); |
|
164 | } |
||
165 | 170 | break; |
|
166 | 170 | case 'AND': |
|
167 | 170 | case '<q>': // enter new subquery, currently irrelevant but possible |
|
168 | 14 | $this->pushDelimiter( '</q>' ); |
|
169 | 14 | $conjunction = $this->descriptionProcessor->constructConjunctiveCompoundDescriptionFrom( $conjunction, $this->getSubqueryDescription( $setsubNS ) ); |
|
170 | 14 | break; |
|
171 | 170 | case 'OR': |
|
172 | 170 | case '||': |
|
173 | 170 | case '': |
|
174 | 26 | case '</q>': // finish disjunction and maybe subquery |
|
175 | 170 | if ( !is_null( $this->defaultNamespace ) ) { // possibly add namespace restrictions |
|
176 | 1 | if ( $hasNamespaces && !$mustSetNS ) { |
|
177 | // add NS restrictions to all earlier conjunctions (all of which did not have them yet) |
||
178 | 1 | $mustSetNS = true; // enforce NS restrictions from now on |
|
179 | 1 | $newdisjuncts = array(); |
|
180 | |||
181 | 1 | foreach ( $disjuncts as $conj ) { |
|
182 | $newdisjuncts[] = $this->descriptionProcessor->constructConjunctiveCompoundDescriptionFrom( $conj, $this->defaultNamespace ); |
||
183 | } |
||
184 | |||
185 | 1 | $disjuncts = $newdisjuncts; |
|
186 | 1 | } elseif ( !$hasNamespaces && $mustSetNS ) { |
|
187 | // add ns restriction to current result |
||
188 | $conjunction = $this->descriptionProcessor->constructConjunctiveCompoundDescriptionFrom( $conjunction, $this->defaultNamespace ); |
||
189 | } |
||
190 | } |
||
191 | |||
192 | 170 | $disjuncts[] = $conjunction; |
|
193 | // start anew |
||
194 | 170 | $conjunction = null; |
|
195 | 170 | $hasNamespaces = false; |
|
196 | |||
197 | // finish subquery? |
||
198 | 170 | if ( $chunk == '</q>' ) { |
|
199 | 24 | if ( $this->popDelimiter( '</q>' ) ) { |
|
200 | 24 | $continue = false; // leave the loop |
|
201 | } else { |
||
202 | $this->descriptionProcessor->addErrorWithMsgKey( 'smw_toomanyclosing', $chunk ); |
||
203 | 24 | return null; |
|
204 | } |
||
205 | 170 | } elseif ( $chunk === '' ) { |
|
206 | 170 | $continue = false; |
|
207 | } |
||
208 | 170 | break; |
|
209 | 2 | case '+': // "... AND true" (ignore) |
|
210 | break; |
||
211 | default: // error: unexpected $chunk |
||
212 | 2 | $this->descriptionProcessor->addErrorWithMsgKey( 'smw_unexpectedpart', $chunk ); |
|
213 | // return null; // Try to go on, it can only get better ... |
||
214 | } |
||
215 | |||
216 | 170 | if ( $setsubNS ) { // namespace restrictions encountered in current conjunct |
|
217 | 48 | $hasNamespaces = true; |
|
218 | } |
||
219 | |||
220 | 170 | if ( $continue ) { // read on only if $continue remained true |
|
221 | 170 | $chunk = $this->readChunk(); |
|
222 | } |
||
223 | } |
||
224 | |||
225 | 170 | if ( count( $disjuncts ) > 0 ) { // make disjunctive result |
|
226 | 170 | $result = null; |
|
227 | |||
228 | 170 | foreach ( $disjuncts as $d ) { |
|
229 | 170 | if ( is_null( $d ) ) { |
|
230 | 1 | $this->descriptionProcessor->addErrorWithMsgKey( 'smw_emptysubquery' ); |
|
231 | 1 | $setNS = false; |
|
232 | 1 | return null; |
|
233 | } else { |
||
234 | 170 | $result = $this->descriptionProcessor->constructDisjunctiveCompoundDescriptionFrom( $result, $d ); |
|
235 | } |
||
236 | } |
||
237 | } else { |
||
238 | $this->descriptionProcessor->addErrorWithMsgKey( 'smw_emptysubquery' ); |
||
239 | $setNS = false; |
||
240 | return null; |
||
241 | } |
||
242 | |||
243 | 170 | $setNS = $mustSetNS; // NOTE: also false if namespaces were given but no default NS descs are available |
|
244 | |||
245 | 170 | return $result; |
|
246 | } |
||
247 | |||
248 | /** |
||
249 | * Compute an SMWDescription for current part of a query, which should |
||
250 | * be the content of "[[ ... ]]". Returns NULL upon error. |
||
251 | * |
||
252 | * Parameters $setNS has the same use as in getSubqueryDescription(). |
||
253 | */ |
||
254 | 170 | private function getLinkDescription( &$setNS ) { |
|
255 | // This method is called when we encountered an opening '[['. The following |
||
256 | // block could be a Category-statement, fixed object, or property statement. |
||
257 | 170 | $chunk = $this->readChunk( '', true, false ); // NOTE: untrimmed, initial " " escapes prop. chains |
|
258 | |||
259 | 170 | if ( in_array( smwfNormalTitleText( $chunk ), |
|
260 | 170 | array( $this->categoryPrefix, $this->conceptPrefix, $this->categoryPrefixCannonical, $this->conceptPrefixCannonical ) ) ) { |
|
261 | 66 | return $this->getClassDescription( $setNS, ( |
|
262 | 66 | smwfNormalTitleText( $chunk ) == $this->categoryPrefix || smwfNormalTitleText( $chunk ) == $this->categoryPrefixCannonical |
|
263 | ) ); |
||
264 | } else { // fixed subject, namespace restriction, property query, or subquery |
||
265 | 155 | $sep = $this->readChunk( '', false ); // do not consume hit, "look ahead" |
|
266 | |||
267 | 155 | if ( ( $sep == '::' ) || ( $sep == ':=' ) ) { |
|
268 | 125 | if ( $chunk{0} != ':' ) { // property statement |
|
269 | 125 | return $this->getPropertyDescription( $chunk, $setNS ); |
|
270 | } else { // escaped article description, read part after :: to get full contents |
||
271 | $chunk .= $this->readChunk( '\[\[|\]\]|\|\||\|' ); |
||
272 | return $this->getArticleDescription( trim( $chunk ), $setNS ); |
||
273 | } |
||
274 | } else { // Fixed article/namespace restriction. $sep should be ]] or || |
||
275 | 48 | return $this->getArticleDescription( trim( $chunk ), $setNS ); |
|
276 | } |
||
277 | } |
||
278 | } |
||
279 | |||
280 | /** |
||
281 | * Parse a category description (the part of an inline query that |
||
282 | * is in between "[[Category:" and the closing "]]" and create a |
||
283 | * suitable description. |
||
284 | */ |
||
285 | 66 | private function getClassDescription( &$setNS, $category = true ) { |
|
286 | // note: no subqueries allowed here, inline disjunction allowed, wildcards allowed |
||
287 | 66 | $result = null; |
|
288 | 66 | $continue = true; |
|
289 | |||
290 | 66 | while ( $continue ) { |
|
291 | 66 | $chunk = $this->readChunk(); |
|
292 | |||
293 | 66 | if ( $chunk == '+' ) { |
|
294 | $description = new NamespaceDescription( $category ? NS_CATEGORY : SMW_NS_CONCEPT ); |
||
295 | $result = $this->descriptionProcessor->constructDisjunctiveCompoundDescriptionFrom( $result, $description ); |
||
296 | } else { // assume category/concept title |
||
297 | /// NOTE: we add m_c...prefix to prevent problems with, e.g., [[Category:Template:Test]] |
||
298 | 66 | $title = Title::newFromText( ( $category ? $this->categoryPrefix : $this->conceptPrefix ) . $chunk ); |
|
299 | |||
300 | 66 | if ( !is_null( $title ) ) { |
|
301 | 66 | $diWikiPage = new SMWDIWikiPage( $title->getDBkey(), $title->getNameSpace(), '' ); |
|
302 | 66 | $desc = $category ? new ClassDescription( $diWikiPage ) : new ConceptDescription( $diWikiPage ); |
|
303 | 66 | $result = $this->descriptionProcessor->constructDisjunctiveCompoundDescriptionFrom( $result, $desc ); |
|
304 | } |
||
305 | } |
||
306 | |||
307 | 66 | $chunk = $this->readChunk(); |
|
308 | 66 | $continue = ( $chunk == '||' ) && $category; // disjunctions only for cateories |
|
309 | } |
||
310 | |||
311 | 66 | return $this->finishLinkDescription( $chunk, false, $result, $setNS ); |
|
312 | } |
||
313 | |||
314 | /** |
||
315 | * Parse a property description (the part of an inline query that |
||
316 | * is in between "[[Some property::" and the closing "]]" and create a |
||
317 | * suitable description. The "::" is the first chunk on the current |
||
318 | * string. |
||
319 | */ |
||
320 | 125 | private function getPropertyDescription( $propertyName, &$setNS ) { |
|
321 | 125 | $this->readChunk(); // consume separator ":=" or "::" |
|
322 | |||
323 | // first process property chain syntax (e.g. "property1.property2::value"), escaped by initial " ": |
||
324 | 125 | $propertynames = ( $propertyName{0} == ' ' ) ? array( $propertyName ) : explode( '.', $propertyName ); |
|
325 | 125 | $properties = array(); |
|
326 | 125 | $typeid = '_wpg'; |
|
327 | 125 | $inverse = false; |
|
328 | |||
329 | 125 | foreach ( $propertynames as $name ) { |
|
330 | 125 | if ( !$this->isPagePropertyType( $typeid ) ) { // non-final property in chain was no wikipage: not allowed |
|
331 | $this->descriptionProcessor->addErrorWithMsgKey( 'smw_valuesubquery', $name ); |
||
332 | return null; ///TODO: read some more chunks and try to finish [[ ]] |
||
333 | } |
||
334 | |||
335 | 125 | $property = SMWPropertyValue::makeUserProperty( $name ); |
|
336 | |||
337 | 125 | if ( !$property->isValid() ) { // illegal property identifier |
|
338 | $this->descriptionProcessor->addError( $property->getErrors() ); |
||
339 | return null; ///TODO: read some more chunks and try to finish [[ ]] |
||
340 | } |
||
341 | |||
342 | 125 | $typeid = $property->getDataItem()->findPropertyTypeID(); |
|
343 | 125 | $inverse = $property->isInverse(); |
|
344 | 125 | $properties[] = $property; |
|
345 | } ///NOTE: after iteration, $property and $typeid correspond to last value |
||
346 | |||
347 | 125 | $innerdesc = null; |
|
348 | 125 | $continue = true; |
|
349 | |||
350 | 125 | while ( $continue ) { |
|
351 | 125 | $chunk = $this->readChunk(); |
|
352 | |||
353 | switch ( $chunk ) { |
||
354 | 125 | case '+': // wildcard, add namespaces for page-type properties |
|
355 | 42 | if ( !is_null( $this->defaultNamespace ) && ( $this->isPagePropertyType( $typeid ) || $inverse ) ) { |
|
356 | $innerdesc = $this->descriptionProcessor->constructDisjunctiveCompoundDescriptionFrom( $innerdesc, $this->defaultNamespace ); |
||
357 | } else { |
||
358 | 42 | $innerdesc = $this->descriptionProcessor->constructDisjunctiveCompoundDescriptionFrom( $innerdesc, new ThingDescription() ); |
|
359 | } |
||
360 | 42 | $chunk = $this->readChunk(); |
|
361 | 42 | break; |
|
362 | 98 | case '<q>': // subquery, set default namespaces |
|
363 | 12 | if ( $this->isPagePropertyType( $typeid ) || $inverse ) { |
|
364 | 12 | $this->pushDelimiter( '</q>' ); |
|
365 | 12 | $setsubNS = true; |
|
366 | 12 | $innerdesc = $this->descriptionProcessor->constructDisjunctiveCompoundDescriptionFrom( $innerdesc, $this->getSubqueryDescription( $setsubNS ) ); |
|
367 | } else { // no subqueries allowed for non-pages |
||
368 | $this->descriptionProcessor->addErrorWithMsgKey( 'smw_valuesubquery', end( $propertynames ) ); |
||
369 | $innerdesc = $this->descriptionProcessor->constructDisjunctiveCompoundDescriptionFrom( $innerdesc, new ThingDescription() ); |
||
370 | } |
||
371 | 12 | $chunk = $this->readChunk(); |
|
372 | 12 | break; |
|
373 | default: // normal object value |
||
374 | // read value(s), possibly with inner [[...]] |
||
375 | 98 | $open = 1; |
|
376 | 98 | $value = $chunk; |
|
377 | 98 | $continue2 = true; |
|
378 | // read value with inner [[, ]], || |
||
379 | 98 | while ( ( $open > 0 ) && ( $continue2 ) ) { |
|
380 | 98 | $chunk = $this->readChunk( '\[\[|\]\]|\|\||\|' ); |
|
381 | switch ( $chunk ) { |
||
382 | 98 | case '[[': // open new [[ ]] |
|
383 | $open++; |
||
384 | break; |
||
385 | 98 | case ']]': // close [[ ]] |
|
386 | 98 | $open--; |
|
387 | 98 | break; |
|
388 | 11 | case '|': |
|
389 | 10 | case '||': // terminates only outermost [[ ]] |
|
390 | 10 | if ( $open == 1 ) { |
|
391 | 10 | $open = 0; |
|
392 | } |
||
393 | 10 | break; |
|
394 | 1 | case '': ///TODO: report error; this is not good right now |
|
395 | $continue2 = false; |
||
396 | break; |
||
397 | } |
||
398 | 98 | if ( $open != 0 ) { |
|
399 | 1 | $value .= $chunk; |
|
400 | } |
||
401 | } ///NOTE: at this point, we normally already read one more chunk behind the value |
||
402 | |||
403 | 98 | $innerdesc = $this->descriptionProcessor->constructDisjunctiveCompoundDescriptionFrom( |
|
404 | $innerdesc, |
||
405 | 98 | $this->descriptionProcessor->constructDescriptionForPropertyObjectValue( $property->getDataItem(), $value ) |
|
0 ignored issues
–
show
$property->getDataItem() of type object<SMWDataItem> is not a sub-type of object<SMW\DIProperty> . It seems like you assume a child class of the class SMWDataItem to be always present.
This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass. Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.
Loading history...
|
|||
406 | ); |
||
407 | |||
408 | } |
||
409 | 125 | $continue = ( $chunk == '||' ); |
|
410 | } |
||
411 | |||
412 | 125 | if ( is_null( $innerdesc ) ) { // make a wildcard search |
|
413 | $innerdesc = ( !is_null( $this->defaultNamespace ) && $this->isPagePropertyType( $typeid ) ) ? |
||
414 | $this->descriptionProcessor->constructDisjunctiveCompoundDescriptionFrom( $innerdesc, $this->defaultNamespace ) : |
||
415 | $this->descriptionProcessor->constructDisjunctiveCompoundDescriptionFrom( $innerdesc, new ThingDescription() ); |
||
416 | $this->descriptionProcessor->addErrorWithMsgKey( 'smw_propvalueproblem', $property->getWikiValue() ); |
||
417 | } |
||
418 | |||
419 | 125 | $properties = array_reverse( $properties ); |
|
420 | |||
421 | 125 | foreach ( $properties as $property ) { |
|
422 | 125 | $innerdesc = new SomeProperty( $property->getDataItem(), $innerdesc ); |
|
423 | } |
||
424 | |||
425 | 125 | $result = $innerdesc; |
|
426 | |||
427 | 125 | return $this->finishLinkDescription( $chunk, false, $result, $setNS ); |
|
0 ignored issues
–
show
The variable
$chunk does not seem to be defined for all execution paths leading up to this point.
If you define a variable conditionally, it can happen that it is not defined for all execution paths. Let’s take a look at an example: function myFunction($a) {
switch ($a) {
case 'foo':
$x = 1;
break;
case 'bar':
$x = 2;
break;
}
// $x is potentially undefined here.
echo $x;
}
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined. Available Fixes
Loading history...
|
|||
428 | } |
||
429 | |||
430 | /** |
||
431 | * Parse an article description (the part of an inline query that |
||
432 | * is in between "[[" and the closing "]]" assuming it is not specifying |
||
433 | * a category or property) and create a suitable description. |
||
434 | * The first chunk behind the "[[" has already been read and is |
||
435 | * passed as a parameter. |
||
436 | */ |
||
437 | 48 | private function getArticleDescription( $firstChunk, &$setNS ) { |
|
438 | 48 | $chunk = $firstChunk; |
|
439 | 48 | $result = null; |
|
440 | 48 | $continue = true; |
|
441 | |||
442 | 48 | while ( $continue ) { |
|
443 | 48 | if ( $chunk == '<q>' ) { // no subqueries of the form [[<q>...</q>]] (not needed) |
|
444 | |||
445 | $this->descriptionProcessor->addErrorWithMsgKey( 'smw_misplacedsubquery' ); |
||
446 | return null; |
||
447 | } |
||
448 | |||
449 | 48 | $list = preg_split( '/:/', $chunk, 3 ); // ":Category:Foo" "User:bar" ":baz" ":+" |
|
450 | |||
451 | 48 | if ( ( $list[0] === '' ) && ( count( $list ) == 3 ) ) { |
|
452 | 2 | $list = array_slice( $list, 1 ); |
|
453 | } |
||
454 | 48 | if ( ( count( $list ) == 2 ) && ( $list[1] == '+' ) ) { // try namespace restriction |
|
455 | |||
456 | 4 | $idx = \SMW\Localizer::getInstance()->getNamespaceIndexByName( $list[0] ); |
|
457 | |||
458 | 4 | if ( $idx !== false ) { |
|
459 | 4 | $result = $this->descriptionProcessor->constructDisjunctiveCompoundDescriptionFrom( $result, new NamespaceDescription( $idx ) ); |
|
460 | } |
||
461 | } else { |
||
462 | 45 | $result = $this->descriptionProcessor->constructDisjunctiveCompoundDescriptionFrom( |
|
463 | $result, |
||
464 | 45 | $this->descriptionProcessor->constructDescriptionForWikiPageValueChunk( $chunk ) |
|
465 | ); |
||
466 | } |
||
467 | |||
468 | 48 | $chunk = $this->readChunk( '\[\[|\]\]|\|\||\|' ); |
|
469 | |||
470 | 48 | if ( $chunk == '||' ) { |
|
471 | $chunk = $this->readChunk( '\[\[|\]\]|\|\||\|' ); |
||
472 | $continue = true; |
||
473 | } else { |
||
474 | 48 | $continue = false; |
|
475 | } |
||
476 | } |
||
477 | |||
478 | 48 | return $this->finishLinkDescription( $chunk, true, $result, $setNS ); |
|
479 | } |
||
480 | |||
481 | 170 | private function finishLinkDescription( $chunk, $hasNamespaces, $result, &$setNS ) { |
|
482 | 170 | if ( is_null( $result ) ) { // no useful information or concrete error found |
|
483 | $this->descriptionProcessor->addErrorWithMsgKey( 'smw_unexpectedpart', $chunk ); // was smw_badqueryatom |
||
484 | 170 | } elseif ( !$hasNamespaces && $setNS && !is_null( $this->defaultNamespace ) ) { |
|
485 | $result = $this->descriptionProcessor->constructConjunctiveCompoundDescriptionFrom( $result, $this->defaultNamespace ); |
||
486 | $hasNamespaces = true; |
||
487 | } |
||
488 | |||
489 | 170 | $setNS = $hasNamespaces; |
|
490 | |||
491 | 170 | if ( $chunk == '|' ) { // skip content after single |, but report a warning |
|
492 | // Note: Using "|label" in query atoms used to be a way to set the mainlabel in SMW <1.0; no longer supported now |
||
493 | 1 | $chunk = $this->readChunk( '\]\]' ); |
|
494 | 1 | $labelpart = '|'; |
|
495 | 1 | if ( $chunk != ']]' ) { |
|
496 | 1 | $labelpart .= $chunk; |
|
497 | 1 | $chunk = $this->readChunk( '\]\]' ); |
|
498 | } |
||
499 | 1 | $this->descriptionProcessor->addErrorWithMsgKey( 'smw_unexpectedpart', $labelpart ); |
|
500 | } |
||
501 | |||
502 | 170 | if ( $chunk != ']]' ) { |
|
503 | // What happended? We found some chunk that could not be processed as |
||
504 | // link content (as in [[Category:Test<q>]]), or the closing ]] are |
||
505 | // just missing entirely. |
||
506 | 2 | if ( $chunk !== '' ) { |
|
507 | 1 | $this->descriptionProcessor->addErrorWithMsgKey( 'smw_misplacedsymbol', $chunk ); |
|
508 | |||
509 | // try to find a later closing ]] to finish this misshaped subpart |
||
510 | 1 | $chunk = $this->readChunk( '\]\]' ); |
|
511 | |||
512 | 1 | if ( $chunk != ']]' ) { |
|
513 | 1 | $chunk = $this->readChunk( '\]\]' ); |
|
514 | } |
||
515 | } |
||
516 | 2 | if ( $chunk === '' ) { |
|
517 | 1 | $this->descriptionProcessor->addErrorWithMsgKey( 'smw_noclosingbrackets' ); |
|
518 | } |
||
519 | } |
||
520 | |||
521 | 170 | return $result; |
|
522 | } |
||
523 | |||
524 | /** |
||
525 | * Get the next unstructured string chunk from the query string. |
||
526 | * Chunks are delimited by any of the special strings used in inline queries |
||
527 | * (such as [[, ]], <q>, ...). If the string starts with such a delimiter, |
||
528 | * this delimiter is returned. Otherwise the first string in front of such a |
||
529 | * delimiter is returned. |
||
530 | * Trailing and initial spaces are ignored if $trim is true, and chunks |
||
531 | * consisting only of spaces are not returned. |
||
532 | * If there is no more qurey string left to process, the empty string is |
||
533 | * returned (and in no other case). |
||
534 | * |
||
535 | * The stoppattern can be used to customise the matching, especially in order to |
||
536 | * overread certain special symbols. |
||
537 | * |
||
538 | * $consume specifies whether the returned chunk should be removed from the |
||
539 | * query string. |
||
540 | */ |
||
541 | 170 | private function readChunk( $stoppattern = '', $consume = true, $trim = true ) { |
|
542 | 170 | if ( $stoppattern === '' ) { |
|
543 | $stoppattern = '\[\[|\]\]|::|:=|<q>|<\/q>' . |
||
544 | 170 | '|^' . $this->categoryPrefix . '|^' . $this->categoryPrefixCannonical . |
|
545 | 170 | '|^' . $this->conceptPrefix . '|^' . $this->conceptPrefixCannonical . |
|
546 | 170 | '|\|\||\|'; |
|
547 | } |
||
548 | 170 | $chunks = preg_split( '/[\s]*(' . $stoppattern . ')/iu', $this->currentString, 2, PREG_SPLIT_DELIM_CAPTURE ); |
|
549 | 170 | if ( count( $chunks ) == 1 ) { // no matches anymore, strip spaces and finish |
|
550 | 170 | if ( $consume ) { |
|
551 | 170 | $this->currentString = ''; |
|
552 | } |
||
553 | |||
554 | 170 | return $trim ? trim( $chunks[0] ) : $chunks[0]; |
|
555 | 170 | } elseif ( count( $chunks ) == 3 ) { // this should generally happen if count is not 1 |
|
556 | 170 | if ( $chunks[0] === '' ) { // string started with delimiter |
|
557 | 170 | if ( $consume ) { |
|
558 | 170 | $this->currentString = $chunks[2]; |
|
559 | } |
||
560 | |||
561 | 170 | return $trim ? trim( $chunks[1] ) : $chunks[1]; |
|
562 | } else { |
||
563 | 169 | if ( $consume ) { |
|
564 | 169 | $this->currentString = $chunks[1] . $chunks[2]; |
|
565 | } |
||
566 | |||
567 | 169 | return $trim ? trim( $chunks[0] ) : $chunks[0]; |
|
568 | } |
||
569 | } else { |
||
570 | return false; |
||
571 | } // should never happen |
||
572 | } |
||
573 | |||
574 | /** |
||
575 | * Enter a new subblock in the query, which must at some time be terminated by the |
||
576 | * given $endstring delimiter calling popDelimiter(); |
||
577 | */ |
||
578 | 24 | private function pushDelimiter( $endstring ) { |
|
579 | 24 | array_push( $this->separatorStack, $endstring ); |
|
580 | 24 | } |
|
581 | |||
582 | /** |
||
583 | * Exit a subblock in the query ending with the given delimiter. |
||
584 | * If the delimiter does not match the top-most open block, false |
||
585 | * will be returned. Otherwise return true. |
||
586 | */ |
||
587 | 24 | private function popDelimiter( $endstring ) { |
|
588 | 24 | $topdelim = array_pop( $this->separatorStack ); |
|
589 | 24 | return ( $topdelim == $endstring ); |
|
590 | } |
||
591 | |||
592 | 125 | private function isPagePropertyType( $typeid ) { |
|
593 | 125 | return $typeid == '_wpg' || DataTypeRegistry::getInstance()->isSubDataType( $typeid ); |
|
594 | } |
||
595 | |||
596 | } |
||
597 |
If you define a variable conditionally, it can happen that it is not defined for all execution paths.
Let’s take a look at an example:
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.
Available Fixes
Check for existence of the variable explicitly:
Define a default value for the variable:
Add a value for the missing path: