SRFArray   F
last analyzed

Complexity

Total Complexity 69

Size/Duplication

Total Lines 449
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 2

Test Coverage

Coverage 2.43%

Importance

Changes 0
Metric Value
wmc 69
lcom 2
cbo 2
dl 0
loc 449
ccs 5
cts 206
cp 0.0243
rs 2.88
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A getQueryMode() 0 3 1
A getName() 0 5 1
A fillDeliveryArray() 0 6 2
A deliverPageTitle() 0 3 1
A deliverRecordField() 0 11 3
A deliverSingleValue() 0 6 1
A deliverMissingProperty() 0 8 2
A deliverSingleManyValuesData() 0 7 2
A deliverPropertiesManyValues() 0 14 5
A deliverPageProperties() 0 6 2
A deliverQueryResultPages() 0 8 2
B createArray() 0 34 7
A initializeCfgValue() 0 13 3
B getCfgSepText() 0 53 10
C handleParameters() 0 52 15
B getParamDefinitions() 0 62 1
C getResultText() 0 91 10

How to fix   Complexity   

Complex Class

Complex classes like SRFArray often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SRFArray, and based on these observations, apply Extract Interface, too.

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
/**
16
 * Array format
17
 */
18
class SRFArray extends SMWResultPrinter {
19
20
	protected static $mDefaultSeps = [];
21
	protected $mSep;
22
	protected $mPropSep;
23
	protected $mManySep;
24
	protected $mRecordSep;
25
	protected $mHeaderSep;
26
	protected $mArrayName = null;
27
	protected $mShowPageTitles;
28
29
	protected $mHideRecordGaps;
30
	protected $mHidePropertyGaps;
31
32
	/**
33
	 * @var Boolean true if 'mainlabel' parameter is set to '-'
34
	 */
35
	protected $mMainLabelHack = false;
36
37 2
	public function __construct( $format, $inline = true ) {
38 2
		parent::__construct( $format, $inline );
39
		//overwrite parent default behavior for linking:
40 2
		$this->mLinkFirst = false;
41 2
		$this->mLinkOthers = false;
42 2
	}
43
44
	public function getQueryMode( $context ) {
45
		return SMWQuery::MODE_INSTANCES;
46
	}
47
48
	public function getName() {
49
		// Give grep a chance to find the usages:
50
		// srf_printername_array, srf_printername_hash
51
		return wfMessage( 'srf_printername_' . $this->mFormat )->text();
52
	}
53
54
	/*
55
	// By overwriting this function, we disable default searchlabel handling?
56
	public function getResult( SMWQueryResult $results, array $params, $outputmode ) {
57
		$this->handleParameters( $params, $outputmode );
58
		return $this->getResultText( $results, $outputmode );
59
	}
60
	*/
61
62
	protected function getResultText( SMWQueryResult $res, $outputmode ) {
63
		/*
64
		 * @todo
65
		 * labels of requested properties could define default values. Seems not possible at the moment because
66
		 * SMWPrintRequest::getLable() always returns the property name even if no specific label is defined.
67
		 */
68
69
		$perPage_items = [];
70
71
		//for each page:
72
		while ( $row = $res->getNext() ) {
73
			$perProperty_items = [];
74
75
			/**
76
			 * first field is always the page title, except, mainlabel is set to '-'
77
			 *
78
			 * @todo Is there some other way to check the data value directly for being the
79
			 *        page title or not? SMWs behavior could change on mainlabel handling...
80
			 */
81
			$isPageTitle = !$this->mMainLabelHack;
82
83
			//for each property on that page:
84
			foreach ( $row as $field ) { // $row is array(), $field of type SMWResultArray
85
				$manyValue_items = [];
86
				$isMissingProperty = false;
87
88
				$manyValues = $field->getContent();
89
90
				//If property is not set (has no value) on a page:
91
				if ( empty( $manyValues ) ) {
92
					$delivery = $this->deliverMissingProperty( $field );
93
					$manyValue_items = $this->fillDeliveryArray( $manyValue_items, $delivery );
94
					$isMissingProperty = true;
95
				} else //otherwise collect property value (potentially many values):
96
				{
97
					while ( $obj = $field->getNextDataValue() ) {
98
99
						$value_items = [];
100
						$isRecord = false;
101
102
						// handle page Title:
103
						if ( $isPageTitle ) {
104
							if ( !$this->mShowPageTitles ) {
105
								$isPageTitle = false;
106
								continue 2; //next property
107
							}
108
							$value_items = $this->fillDeliveryArray(
109
								$value_items,
110
								$this->deliverPageTitle( $obj, $this->mLinkFirst )
111
							);
112
						} // handle record values:
113
						elseif ( $obj instanceof SMWRecordValue ) {
114
							$recordItems = $obj->getDataItems();
115
							// walk all single values of the record set:
116
							foreach ( $recordItems as $dataItem ) {
117
								$recordField = $dataItem !== null ? SMWDataValueFactory::getInstance(
118
								)->newDataValueByItem( $dataItem, null ) : null;
119
								$value_items = $this->fillDeliveryArray(
120
									$value_items,
121
									$this->deliverRecordField( $recordField, $this->mLinkOthers )
122
								);
123
							}
124
							$isRecord = true;
125
						} // handle normal data values:
126
						else {
127
							$value_items = $this->fillDeliveryArray(
128
								$value_items,
129
								$this->deliverSingleValue( $obj, $this->mLinkOthers )
130
							);
131
						}
132
						$delivery = $this->deliverSingleManyValuesData( $value_items, $isRecord, $isPageTitle );
133
						$manyValue_items = $this->fillDeliveryArray( $manyValue_items, $delivery );
134
					}
135
				} // foreach...
136
				$delivery = $this->deliverPropertiesManyValues(
137
					$manyValue_items,
138
					$isMissingProperty,
139
					$isPageTitle,
140
					$field
141
				);
142
				$perProperty_items = $this->fillDeliveryArray( $perProperty_items, $delivery );
143
				$isPageTitle = false; // next one could be record or normal value
144
			} // foreach...			
145
			$delivery = $this->deliverPageProperties( $perProperty_items );
146
			$perPage_items = $this->fillDeliveryArray( $perPage_items, $delivery );
147
		} // while...
148
149
		$output = $this->deliverQueryResultPages( $perPage_items );
150
151
		return $output;
152
	}
153
154
	protected function fillDeliveryArray( $array = [], $value = null ) {
155
		if ( !is_null( $value ) ) { //don't create any empty entries
156
			$array[] = $value;
157
		}
158
		return $array;
159
	}
160
161
	protected function deliverPageTitle( $value, $link = false ) {
162
		return $this->deliverSingleValue( $value, $link );
163
	}
164
165
	protected function deliverRecordField( $value, $link = false ) {
166
		if ( $value !== null ) // contains value
167
		{
168
			return $this->deliverSingleValue( $value, $link );
169
		} elseif ( $this->mHideRecordGaps ) {
170
			return null;
171
		} // hide gap
172
		else {
173
			return '';
174
		} // empty string will make sure that record value separators are generated
175
	}
176
177
	protected function deliverSingleValue( $value, $link = false ) {
178
		//return trim( $value->getShortWikiText( $link ) );
179
		return trim(
180
			Sanitizer::decodeCharReferences( $value->getShortWikiText( $link ) )
181
		); // decode: better for further processing with array extension
182
	}
183
184
	// Property not declared on a page:
185
	protected function deliverMissingProperty( SMWResultArray $field ) {
186
		if ( $this->mHidePropertyGaps ) {
187
			return null;
188
		} else {
189
			return '';
190
		} //empty string will make sure that array separator will be generated
191
		/** @ToDo: System for Default values?... * */
192
	}
193
194
	//represented by an array of record fields or just a single array value:
195
	protected function deliverSingleManyValuesData( $value_items, $containsRecord, $isPageTitle ) {
196
		if ( empty( $value_items ) ) //happens when one of the higher functions delivers null
197
		{
198
			return null;
199
		}
200
		return implode( $this->mRecordSep, $value_items );
201
	}
202
203
	protected function deliverPropertiesManyValues( $manyValue_items, $isMissingProperty, $isPageTitle, SMWResultArray $data ) {
204
		if ( empty( $manyValue_items ) ) {
205
			return null;
206
		}
207
208
		$text = implode( $this->mManySep, $manyValue_items );
209
210
		// if property names should be displayed and this is not the page titles value:
211
		if ( $this->mShowHeaders != SMW_HEADERS_HIDE && !$isPageTitle ) {
212
			$linker = $this->mShowHeaders == SMW_HEADERS_PLAIN ? null : $this->mLinker;
213
			$text = $data->getPrintRequest()->getText( SMW_OUTPUT_WIKI, $linker ) . $this->mHeaderSep . $text;
214
		}
215
		return $text;
216
	}
217
218
	protected function deliverPageProperties( $perProperty_items ) {
219
		if ( empty( $perProperty_items ) ) {
220
			return null;
221
		}
222
		return implode( $this->mPropSep, $perProperty_items );
223
	}
224
225
	protected function deliverQueryResultPages( $perPage_items ) {
226
		if ( $this->mArrayName !== null ) {
227
			$this->createArray( $perPage_items ); //create Array
228
			return '';
229
		} else {
230
			return implode( $this->mSep, $perPage_items );
231
		}
232
	}
233
234
	/**
235
	 * Helper function to create a new Array within 'Arrays' extension. Takes care of different versions
236
	 * as well as the old 'ArrayExtension'.
237
	 */
238
	protected function createArray( $array ) {
239
		global $wgArrayExtension;
240
241
		$arrayId = $this->mArrayName;
242
243
		if ( defined( 'ExtArrays::VERSION' ) ) {
244
			// 'Arrays' extension 2+
245
			global $wgParser;
246
			/** ToDo: is there a way to get the actual parser which has started the query? */
247
			ExtArrays::get( $wgParser )->createArray( $arrayId, $array );
248
			return true;
249
		}
250
251
		// compatbility to 'ArrayExtension' extension before 2.0:
252
253
		if ( !isset( $wgArrayExtension ) ) {
254
			//Hash extension is not installed in this wiki
255
			return false;
256
		}
257
		$version = null;
258
		if ( defined( 'ArrayExtension::VERSION' ) ) {
259
			$version = ArrayExtension::VERSION;
260
		} elseif ( defined( 'ExtArrayExtension::VERSION' ) ) {
261
			$version = ExtArrayExtension::VERSION;
262
		}
263
		if ( $version !== null && version_compare( $version, '1.3.2', '>=' ) ) {
264
			// ArrayExtension 1.3.2+
265
			$wgArrayExtension->createArray( $arrayId, $array );
266
		} else {
267
			// dirty way
268
			$wgArrayExtension->mArrays[trim( $arrayId )] = $array;
269
		}
270
		return true;
271
	}
272
273
	protected function initializeCfgValue( $dfltVal, $dfltCacheKey ) {
274
		$cache = &self::$mDefaultSeps[$dfltCacheKey];
275
		if ( !isset( $cache ) ) {
276
			$cache = $this->getCfgSepText( $dfltVal );
277
			if ( $cache === null ) {
278
				// cache can't be initialized, propably function-reference in userconfig
279
				// but format is not used in inline context, use fallback in this case:
280
				global $srfgArraySepTextualFallbacks;
281
				$cache = $srfgArraySepTextualFallbacks[$dfltCacheKey];
282
			}
283
		}
284
		return $cache;
285
	}
286
287
	protected function getCfgSepText( $obj ) {
288
		if ( is_array( $obj ) ) {
289
			// invalid definition:
290
			if ( !array_key_exists( 0, $obj ) ) {
291
				return null;
292
			}
293
294
			// check for config-defined arguments to pass to the page before processing it:			
295
			if ( array_key_exists( 'args', $obj ) && is_array( $obj['args'] ) ) {
296
				$params = $obj['args'];
297
			} else {
298
				$params = [];
299
			} // no arguments
300
301
			// create title of page whose text should be used as separator:
302
			$obj = Title::newFromText( $obj[0], ( array_key_exists( 1, $obj ) ? $obj[1] : NS_MAIN ) );
303
		}
304
		if ( $obj instanceof Title ) {
0 ignored issues
show
Bug introduced by danwe
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...
305
			$article = new Article( $obj );
306
		} elseif ( $obj instanceof Article ) {
0 ignored issues
show
Bug introduced by danwe
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...
307
			$article = $obj;
308
		} else {
309
			return $obj; //only text
310
		}
311
312
		global $wgParser;
313
		/*
314
		 * Feature to use page value as separator only works if Parser::parse() is running!
315
		 * That's not the case on semantic search special page for example!
316
		 */
317
		// can't use $this->mInline here since SMW 1.6.2 had a bug setting it to false in most cases!		
318
		if ( !isset( $wgParser->mOptions ) ) {
319
			//if( ! $this->mInline ) {
320
			return null;
321
		}
322
323
		/*
324
		 * parse page as if it were included like a template. Never use Parser::recursiveTagParse() or similar 
325
		 * for this since it would call hooks we don't want to call and won't return wiki text for inclusion!
326
		 */
327
		$frame = $wgParser->getPreprocessor()->newCustomFrame( $params );
0 ignored issues
show
Bug introduced by Florian
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...
328
		// compatibility for 1.19, getContent() was implemented in 1.21.
329
		// FIXME: Remove when support for MediaWiki 1.19 is dropped
330
		if ( method_exists( $article, 'getContent' ) ) {
331
			$content = $article->getContent( Revision::RAW )->getNativeData();
332
		} else {
333
			$content = $article->getRawText();
334
		}
335
		$text = $wgParser->preprocessToDom( $content, Parser::PTD_FOR_INCLUSION );
336
		$text = trim( $frame->expand( $text ) );
337
338
		return $text;
339
	}
340
341
	protected function handleParameters( array $params, $outputmode ) {
342
		// does the link parameter:
343
		parent::handleParameters( $params, $outputmode );
344
345
		//separators:
346
		$this->mSep = $params['sep'];
347
		$this->mPropSep = $params['propsep'];
348
		$this->mManySep = $params['manysep'];
349
		$this->mRecordSep = $params['recordsep'];
350
		$this->mHeaderSep = $params['headersep'];
351
352
		// only use this in inline mode, if text is given. Since SMW 1.6.2 '' is given, so if
353
		// we wouldn't check, we would always end up with an array instead of visible output
354
		if ( $params['name'] !== false && ( $this->mInline || trim( $params['name'] ) !== '' ) ) {
355
			$this->mArrayName = trim( $params['name'] );
356
			$this->createArray(
357
				[]
358
			); //create empty array in case we get no result so we won't have an undefined array in the end.
359
		}
360
361
		// if mainlabel set to '-', this will cause the titles not to appear, so make sure we catch this!
362
		$this->mMainLabelHack = trim( $params['mainlabel'] ) === '-';
363
364
		// whether or not to display the page title:
365
		$this->mShowPageTitles = strtolower( $params['titles'] ) != 'hide';
366
367
		switch ( strtolower( $params['hidegaps'] ) ) {
368
			case 'none':
369
				$this->mHideRecordGaps = false;
370
				$this->mHidePropertyGaps = false;
371
				break;
372
			case 'all':
373
				$this->mHideRecordGaps = true;
374
				$this->mHidePropertyGaps = true;
375
				break;
376
			case 'property':
377
			case 'prop':
378
			case 'attribute':
379
			case 'attr':
380
				$this->mHideRecordGaps = false;
381
				$this->mHidePropertyGaps = true;
382
				break;
383
			case 'record':
384
			case 'rec':
385
			case 'rcrd':
386
			case 'n-ary':
387
			case 'nary':
388
				$this->mHideRecordGaps = true;
389
				$this->mHidePropertyGaps = false;
390
				break;
391
		}
392
	}
393
394
	/**
395
	 * @see SMWResultPrinter::getParamDefinitions
396
	 *
397
	 * @since 1.8
398
	 *
399
	 * @param $definitions array of IParamDefinition
400
	 *
401
	 * @return array of IParamDefinition|array
402
	 */
403
	public function getParamDefinitions( array $definitions ) {
404
		$params = parent::getParamDefinitions( $definitions );
405
406
		### adjusted basic SMW params: ###
407
408
		$definitions['limit']->setDefault( $GLOBALS['smwgQMaxInlineLimit'] );
409
		$definitions['link']->setDefault( 'none' );
410
		$definitions['headers']->setDefault( 'hide' );
411
412
		### new params: ###
413
414
		$params['titles'] = [
415
			'message' => 'srf_paramdesc_pagetitle',
416
			'values' => [ 'show', 'hide' ],
417
			'aliases' => [ 'pagetitle', 'pagetitles' ],
418
			'default' => 'show',
419
		];
420
421
		$params['hidegaps'] = [
422
			'message' => 'srf_paramdesc_hidegaps',
423
			'values' => [ 'none', 'all', 'property', 'record' ],
424
			'default' => 'none',
425
		];
426
427
		$params['name'] = [
428
			'message' => 'srf_paramdesc_arrayname',
429
			'default' => false,
430
			'manipulatedefault' => false,
431
		];
432
433
		// separators (default values are defined in the following globals:)
434
		global $srfgArraySep, $srfgArrayPropSep, $srfgArrayManySep, $srfgArrayRecordSep, $srfgArrayHeaderSep;
435
436
		$params['sep'] = [
437
			'message' => 'smw-paramdesc-sep',
438
			'default' => $this->initializeCfgValue( $srfgArraySep, 'sep' ),
439
		];
440
441
		$params['propsep'] = [
442
			'message' => 'srf_paramdesc_propsep',
443
			'default' => $this->initializeCfgValue( $srfgArrayPropSep, 'propsep' ),
444
		];
445
446
		$params['manysep'] = [
447
			'message' => 'srf_paramdesc_manysep',
448
			'default' => $this->initializeCfgValue( $srfgArrayManySep, 'manysep' ),
449
		];
450
451
		$params['recordsep'] = [
452
			'message' => 'srf_paramdesc_recordsep',
453
			'default' => $this->initializeCfgValue( $srfgArrayRecordSep, 'recordsep' ),
454
			'aliases' => [ 'narysep', 'rcrdsep', 'recsep' ],
455
		];
456
457
		$params['headersep'] = [
458
			'message' => 'srf_paramdesc_headersep',
459
			'default' => $this->initializeCfgValue( $srfgArrayHeaderSep, 'headersep' ),
460
			'aliases' => [ 'narysep', 'rcrdsep', 'recsep' ],
461
		];
462
463
		return $params;
464
	}
465
466
}
467
468
469
470