Completed
Push — master ( 0bc108...32d013 )
by Jeroen De
05:44
created

SRFArray::deliverMissingProperty()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 6
1
<?php
2
/**
3
 * Query format for arrays with features for Extensions 'Arrays' and 'HashTables'
4
 *
5
 * @file
6
 * @ingroup SemanticResultFormats
7
 * @author Daniel Werner < [email protected] >
8
 *
9
 * Doesn't require 'Arrays' nor 'HashTables' exytensions but has additional features
10
 * ('name' parameter in either result format) if they are available.
11
 *
12
 * Arrays 2.0+ and HashTables 1.0+ are recommended but not necessary.
13
 */
14
15
use MediaWiki\MediaWikiServices;
16
17
/**
18
 * Array format
19
 */
20
class SRFArray extends SMWResultPrinter {
21
22
	protected static $mDefaultSeps = [];
23
	protected $mSep;
24
	protected $mPropSep;
25
	protected $mManySep;
26
	protected $mRecordSep;
27
	protected $mHeaderSep;
28
	protected $mArrayName = null;
29
	protected $mShowPageTitles;
30
31
	protected $mHideRecordGaps;
32
	protected $mHidePropertyGaps;
33
34
	/**
35
	 * @var Boolean true if 'mainlabel' parameter is set to '-'
36
	 */
37
	protected $mMainLabelHack = false;
38
39 2
	public function __construct( $format, $inline = true ) {
40 2
		parent::__construct( $format, $inline );
41
		//overwrite parent default behavior for linking:
42 2
		$this->mLinkFirst = false;
43 2
		$this->mLinkOthers = false;
44 2
	}
45
46
	public function getQueryMode( $context ) {
47
		return SMWQuery::MODE_INSTANCES;
48
	}
49
50
	public function getName() {
51
		// Give grep a chance to find the usages:
52
		// srf_printername_array, srf_printername_hash
53
		return wfMessage( 'srf_printername_' . $this->mFormat )->text();
54
	}
55
56
	/*
57
	// By overwriting this function, we disable default searchlabel handling?
58
	public function getResult( SMWQueryResult $results, array $params, $outputmode ) {
59
		$this->handleParameters( $params, $outputmode );
60
		return $this->getResultText( $results, $outputmode );
61
	}
62
	*/
63
64
	protected function getResultText( SMWQueryResult $res, $outputmode ) {
65
		/*
66
		 * @todo
67
		 * labels of requested properties could define default values. Seems not possible at the moment because
68
		 * SMWPrintRequest::getLable() always returns the property name even if no specific label is defined.
69
		 */
70
71
		$perPage_items = [];
72
73
		//for each page:
74
		while ( $row = $res->getNext() ) {
75
			$perProperty_items = [];
76
77
			/**
78
			 * first field is always the page title, except, mainlabel is set to '-'
79
			 *
80
			 * @todo Is there some other way to check the data value directly for being the
81
			 *        page title or not? SMWs behavior could change on mainlabel handling...
82
			 */
83
			$isPageTitle = !$this->mMainLabelHack;
84
85
			//for each property on that page:
86
			foreach ( $row as $field ) { // $row is array(), $field of type SMWResultArray
87
				$manyValue_items = [];
88
				$isMissingProperty = false;
89
90
				$manyValues = $field->getContent();
91
92
				//If property is not set (has no value) on a page:
93
				if ( empty( $manyValues ) ) {
94
					$delivery = $this->deliverMissingProperty( $field );
95
					$manyValue_items = $this->fillDeliveryArray( $manyValue_items, $delivery );
96
					$isMissingProperty = true;
97
				} else //otherwise collect property value (potentially many values):
98
				{
99
					while ( $obj = $field->getNextDataValue() ) {
100
101
						$value_items = [];
102
						$isRecord = false;
103
104
						// handle page Title:
105
						if ( $isPageTitle ) {
106
							if ( !$this->mShowPageTitles ) {
107
								$isPageTitle = false;
108
								continue 2; //next property
109
							}
110
							$value_items = $this->fillDeliveryArray(
111
								$value_items,
112
								$this->deliverPageTitle( $obj, $this->mLinkFirst )
113
							);
114
						} // handle record values:
115
						elseif ( $obj instanceof SMWRecordValue ) {
116
							$recordItems = $obj->getDataItems();
117
							// walk all single values of the record set:
118
							foreach ( $recordItems as $dataItem ) {
119
								$recordField = $dataItem !== null ? SMWDataValueFactory::getInstance(
120
								)->newDataValueByItem( $dataItem, null ) : null;
121
								$value_items = $this->fillDeliveryArray(
122
									$value_items,
123
									$this->deliverRecordField( $recordField, $this->mLinkOthers )
124
								);
125
							}
126
							$isRecord = true;
127
						} // handle normal data values:
128
						else {
129
							$value_items = $this->fillDeliveryArray(
130
								$value_items,
131
								$this->deliverSingleValue( $obj, $this->mLinkOthers )
132
							);
133
						}
134
						$delivery = $this->deliverSingleManyValuesData( $value_items, $isRecord, $isPageTitle );
135
						$manyValue_items = $this->fillDeliveryArray( $manyValue_items, $delivery );
136
					}
137
				} // foreach...
138
				$delivery = $this->deliverPropertiesManyValues(
139
					$manyValue_items,
140
					$isMissingProperty,
141
					$isPageTitle,
142
					$field
143
				);
144
				$perProperty_items = $this->fillDeliveryArray( $perProperty_items, $delivery );
145
				$isPageTitle = false; // next one could be record or normal value
146
			} // foreach...			
147
			$delivery = $this->deliverPageProperties( $perProperty_items );
148
			$perPage_items = $this->fillDeliveryArray( $perPage_items, $delivery );
149
		} // while...
150
151
		$output = $this->deliverQueryResultPages( $perPage_items );
152
153
		return $output;
154
	}
155
156
	protected function fillDeliveryArray( $array = [], $value = null ) {
157
		if ( !is_null( $value ) ) { //don't create any empty entries
158
			$array[] = $value;
159
		}
160
		return $array;
161
	}
162
163
	protected function deliverPageTitle( $value, $link = false ) {
164
		return $this->deliverSingleValue( $value, $link );
165
	}
166
167
	protected function deliverRecordField( $value, $link = false ) {
168
		if ( $value !== null ) // contains value
169
		{
170
			return $this->deliverSingleValue( $value, $link );
171
		} elseif ( $this->mHideRecordGaps ) {
172
			return null;
173
		} // hide gap
174
		else {
175
			return '';
176
		} // empty string will make sure that record value separators are generated
177
	}
178
179
	protected function deliverSingleValue( $value, $link = false ) {
180
		//return trim( $value->getShortWikiText( $link ) );
181
		return trim(
182
			Sanitizer::decodeCharReferences( $value->getShortWikiText( $link ) )
183
		); // decode: better for further processing with array extension
184
	}
185
186
	// Property not declared on a page:
187
	protected function deliverMissingProperty( SMWResultArray $field ) {
188
		if ( $this->mHidePropertyGaps ) {
189
			return null;
190
		} else {
191
			return '';
192
		} //empty string will make sure that array separator will be generated
193
		/** @ToDo: System for Default values?... * */
194
	}
195
196
	//represented by an array of record fields or just a single array value:
197
	protected function deliverSingleManyValuesData( $value_items, $containsRecord, $isPageTitle ) {
198
		if ( empty( $value_items ) ) //happens when one of the higher functions delivers null
199
		{
200
			return null;
201
		}
202
		return implode( $this->mRecordSep, $value_items );
203
	}
204
205
	protected function deliverPropertiesManyValues( $manyValue_items, $isMissingProperty, $isPageTitle, SMWResultArray $data ) {
206
		if ( empty( $manyValue_items ) ) {
207
			return null;
208
		}
209
210
		$text = implode( $this->mManySep, $manyValue_items );
211
212
		// if property names should be displayed and this is not the page titles value:
213
		if ( $this->mShowHeaders != SMW_HEADERS_HIDE && !$isPageTitle ) {
214
			$linker = $this->mShowHeaders == SMW_HEADERS_PLAIN ? null : $this->mLinker;
215
			$text = $data->getPrintRequest()->getText( SMW_OUTPUT_WIKI, $linker ) . $this->mHeaderSep . $text;
216
		}
217
		return $text;
218
	}
219
220
	protected function deliverPageProperties( $perProperty_items ) {
221
		if ( empty( $perProperty_items ) ) {
222
			return null;
223
		}
224
		return implode( $this->mPropSep, $perProperty_items );
225
	}
226
227
	protected function deliverQueryResultPages( $perPage_items ) {
228
		if ( $this->mArrayName !== null ) {
229
			$this->createArray( $perPage_items ); //create Array
230
			return '';
231
		} else {
232
			return implode( $this->mSep, $perPage_items );
233
		}
234
	}
235
236
	/**
237
	 * Helper function to create a new Array within 'Arrays' extension. Takes care of different versions
238
	 * as well as the old 'ArrayExtension'.
239
	 */
240
	protected function createArray( $array ) {
241
		global $wgArrayExtension;
242
243
		$arrayId = $this->mArrayName;
244
245
		if ( defined( 'ExtArrays::VERSION' ) ) {
246
			// 'Arrays' extension 2+
247
			/** ToDo: is there a way to get the actual parser which has started the query? */
248
			$parser = MediaWikiServices::getInstance()->getParser();
249
			ExtArrays::get( $parser )->createArray( $arrayId, $array );
250
			return true;
251
		}
252
253
		// compatbility to 'ArrayExtension' extension before 2.0:
254
255
		if ( !isset( $wgArrayExtension ) ) {
256
			//Hash extension is not installed in this wiki
257
			return false;
258
		}
259
		$version = null;
260
		if ( defined( 'ArrayExtension::VERSION' ) ) {
261
			$version = ArrayExtension::VERSION;
262
		} elseif ( defined( 'ExtArrayExtension::VERSION' ) ) {
263
			$version = ExtArrayExtension::VERSION;
264
		}
265
		if ( $version !== null && version_compare( $version, '1.3.2', '>=' ) ) {
266
			// ArrayExtension 1.3.2+
267
			$wgArrayExtension->createArray( $arrayId, $array );
268
		} else {
269
			// dirty way
270
			$wgArrayExtension->mArrays[trim( $arrayId )] = $array;
271
		}
272
		return true;
273
	}
274
275
	protected function initializeCfgValue( $dfltVal, $dfltCacheKey ) {
276
		$cache = &self::$mDefaultSeps[$dfltCacheKey];
277
		if ( !isset( $cache ) ) {
278
			$cache = $this->getCfgSepText( $dfltVal );
279
			if ( $cache === null ) {
280
				// cache can't be initialized, propably function-reference in userconfig
281
				// but format is not used in inline context, use fallback in this case:
282
				global $srfgArraySepTextualFallbacks;
283
				$cache = $srfgArraySepTextualFallbacks[$dfltCacheKey];
284
			}
285
		}
286
		return $cache;
287
	}
288
289
	protected function getCfgSepText( $obj ) {
290
		if ( is_array( $obj ) ) {
291
			// invalid definition:
292
			if ( !array_key_exists( 0, $obj ) ) {
293
				return null;
294
			}
295
296
			// check for config-defined arguments to pass to the page before processing it:			
297
			if ( array_key_exists( 'args', $obj ) && is_array( $obj['args'] ) ) {
298
				$params = $obj['args'];
299
			} else {
300
				$params = [];
301
			} // no arguments
302
303
			// create title of page whose text should be used as separator:
304
			$obj = Title::newFromText( $obj[0], ( array_key_exists( 1, $obj ) ? $obj[1] : NS_MAIN ) );
305
		}
306
		if ( $obj instanceof Title ) {
0 ignored issues
show
Bug introduced by
The class Title does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
307
			$article = new Article( $obj );
308
		} elseif ( $obj instanceof Article ) {
0 ignored issues
show
Bug introduced by
The class Article does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
309
			$article = $obj;
310
		} else {
311
			return $obj; //only text
312
		}
313
314
		/*
315
		 * Feature to use page value as separator only works if Parser::parse() is running!
316
		 * That's not the case on semantic search special page for example!
317
		 */
318
		// can't use $this->mInline here since SMW 1.6.2 had a bug setting it to false in most cases!		
319
		$parser = MediaWikiServices::getInstance()->getParser();
320
		if ( !isset( $parser->mOptions ) ) {
321
			//if( ! $this->mInline ) {
322
			return null;
323
		}
324
325
		/*
326
		 * parse page as if it were included like a template. Never use Parser::recursiveTagParse() or similar 
327
		 * for this since it would call hooks we don't want to call and won't return wiki text for inclusion!
328
		 */
329
		$frame = $parser->getPreprocessor()->newCustomFrame( $params );
0 ignored issues
show
Bug introduced by
The variable $params 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

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
330
		$content = $article->getContent( Revision::RAW )->getNativeData();
331
		$text = $parser->preprocessToDom( $content, Parser::PTD_FOR_INCLUSION );
332
		$text = trim( $frame->expand( $text ) );
333
334
		return $text;
335
	}
336
337
	protected function handleParameters( array $params, $outputmode ) {
338
		// does the link parameter:
339
		parent::handleParameters( $params, $outputmode );
340
341
		//separators:
342
		$this->mSep = $params['sep'];
343
		$this->mPropSep = $params['propsep'];
344
		$this->mManySep = $params['manysep'];
345
		$this->mRecordSep = $params['recordsep'];
346
		$this->mHeaderSep = $params['headersep'];
347
348
		// only use this in inline mode, if text is given. Since SMW 1.6.2 '' is given, so if
349
		// we wouldn't check, we would always end up with an array instead of visible output
350
		if ( $params['name'] !== false && ( $this->mInline || trim( $params['name'] ) !== '' ) ) {
351
			$this->mArrayName = trim( $params['name'] );
352
			$this->createArray(
353
				[]
354
			); //create empty array in case we get no result so we won't have an undefined array in the end.
355
		}
356
357
		// if mainlabel set to '-', this will cause the titles not to appear, so make sure we catch this!
358
		$this->mMainLabelHack = trim( $params['mainlabel'] ) === '-';
359
360
		// whether or not to display the page title:
361
		$this->mShowPageTitles = strtolower( $params['titles'] ) != 'hide';
362
363
		switch ( strtolower( $params['hidegaps'] ) ) {
364
			case 'none':
365
				$this->mHideRecordGaps = false;
366
				$this->mHidePropertyGaps = false;
367
				break;
368
			case 'all':
369
				$this->mHideRecordGaps = true;
370
				$this->mHidePropertyGaps = true;
371
				break;
372
			case 'property':
373
			case 'prop':
374
			case 'attribute':
375
			case 'attr':
376
				$this->mHideRecordGaps = false;
377
				$this->mHidePropertyGaps = true;
378
				break;
379
			case 'record':
380
			case 'rec':
381
			case 'rcrd':
382
			case 'n-ary':
383
			case 'nary':
384
				$this->mHideRecordGaps = true;
385
				$this->mHidePropertyGaps = false;
386
				break;
387
		}
388
	}
389
390
	/**
391
	 * @see SMWResultPrinter::getParamDefinitions
392
	 *
393
	 * @since 1.8
394
	 *
395
	 * @param $definitions array of IParamDefinition
396
	 *
397
	 * @return array of IParamDefinition|array
398
	 */
399
	public function getParamDefinitions( array $definitions ) {
400
		$params = parent::getParamDefinitions( $definitions );
401
402
		### adjusted basic SMW params: ###
403
404
		$definitions['limit']->setDefault( $GLOBALS['smwgQMaxInlineLimit'] );
405
		$definitions['link']->setDefault( 'none' );
406
		$definitions['headers']->setDefault( 'hide' );
407
408
		### new params: ###
409
410
		$params['titles'] = [
411
			'message' => 'srf_paramdesc_pagetitle',
412
			'values' => [ 'show', 'hide' ],
413
			'aliases' => [ 'pagetitle', 'pagetitles' ],
414
			'default' => 'show',
415
		];
416
417
		$params['hidegaps'] = [
418
			'message' => 'srf_paramdesc_hidegaps',
419
			'values' => [ 'none', 'all', 'property', 'record' ],
420
			'default' => 'none',
421
		];
422
423
		$params['name'] = [
424
			'message' => 'srf_paramdesc_arrayname',
425
			'default' => false,
426
			'manipulatedefault' => false,
427
		];
428
429
		// separators (default values are defined in the following globals:)
430
		global $srfgArraySep, $srfgArrayPropSep, $srfgArrayManySep, $srfgArrayRecordSep, $srfgArrayHeaderSep;
431
432
		$params['sep'] = [
433
			'message' => 'smw-paramdesc-sep',
434
			'default' => $this->initializeCfgValue( $srfgArraySep, 'sep' ),
435
		];
436
437
		$params['propsep'] = [
438
			'message' => 'srf_paramdesc_propsep',
439
			'default' => $this->initializeCfgValue( $srfgArrayPropSep, 'propsep' ),
440
		];
441
442
		$params['manysep'] = [
443
			'message' => 'srf_paramdesc_manysep',
444
			'default' => $this->initializeCfgValue( $srfgArrayManySep, 'manysep' ),
445
		];
446
447
		$params['recordsep'] = [
448
			'message' => 'srf_paramdesc_recordsep',
449
			'default' => $this->initializeCfgValue( $srfgArrayRecordSep, 'recordsep' ),
450
			'aliases' => [ 'narysep', 'rcrdsep', 'recsep' ],
451
		];
452
453
		$params['headersep'] = [
454
			'message' => 'srf_paramdesc_headersep',
455
			'default' => $this->initializeCfgValue( $srfgArrayHeaderSep, 'headersep' ),
456
			'aliases' => [ 'narysep', 'rcrdsep', 'recsep' ],
457
		];
458
459
		return $params;
460
	}
461
462
}
463
464
465
466