Completed
Push — master ( 0b1d89...ca460c )
by Stephan
05:55 queued 02:40
created

SRFArray::deliverSingleManyValuesData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 0
cts 4
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 3
crap 6
1
<?php
2
/**
3
 * Query format for arrays with features for Extensions 'Arrays' and 'HashTables'
4
 * @file
5
 * @ingroup SemanticResultFormats
6
 * @author Daniel Werner < [email protected] >
7
 * 
8
 * Doesn't require 'Arrays' nor 'HashTables' exytensions but has additional features
9
 * ('name' parameter in either result format) if they are available.
10
 * 
11
 * Arrays 2.0+ and HashTables 1.0+ are recommended but not necessary.
12
 */
13
14
/**
15
 * Array format
16
 */
17
class SRFArray extends SMWResultPrinter {
18
	
19
	protected static $mDefaultSeps = [];
20
	protected $mSep;
21
	protected $mPropSep;
22
	protected $mManySep;
23
	protected $mRecordSep;
24
	protected $mHeaderSep;
25
	protected $mArrayName = null;
26
	protected $mShowPageTitles;
27
	
28
	protected $mHideRecordGaps;
29
	protected $mHidePropertyGaps;
30
	
31
	/**
32
	 * @var Boolean true if 'mainlabel' parameter is set to '-'
33
	 */
34
	protected $mMainLabelHack = false;
35
	
36 2
	public function __construct( $format, $inline = true, $useValidator = true ) {
37 2
		parent::__construct( $format, $inline, $useValidator );
38
		//overwrite parent default behavior for linking:
39 2
		$this->mLinkFirst = false;
40 2
		$this->mLinkOthers = false;
41 2
	}
42
43
	public function getQueryMode($context) {
44
		return SMWQuery::MODE_INSTANCES;
45
	}
46
47
	public function getName() {
48
		// Give grep a chance to find the usages:
49
		// srf_printername_array, srf_printername_hash
50
		return wfMessage( 'srf_printername_' . $this->mFormat )->text();
51
	}
52
	
53
	/*
54
	// By overwriting this function, we disable default searchlabel handling?
55
	public function getResult( SMWQueryResult $results, array $params, $outputmode ) {
56
		$this->handleParameters( $params, $outputmode );
57
		return $this->getResultText( $results, $outputmode );
58
	}
59
	*/
60
61
	protected function getResultText( SMWQueryResult $res, $outputmode ) {
62
		/*
63
		 * @todo
64
		 * labels of requested properties could define default values. Seems not possible at the moment because
65
		 * SMWPrintRequest::getLable() always returns the property name even if no specific label is defined.
66
		 */
67
		 
68
		$perPage_items = [];
69
		
70
		//for each page:
71
		while( $row = $res->getNext() ) {
72
			$perProperty_items = [];
73
			
74
			/**
75
			 * first field is always the page title, except, mainlabel is set to '-'
76
			 * @todo Is there some other way to check the data value directly for being the
77
			 *        page title or not? SMWs behavior could change on mainlabel handling...
78
			 */
79
			$isPageTitle = !$this->mMainLabelHack;
80
			
81
			//for each property on that page:
82
			foreach( $row as $field ) { // $row is array(), $field of type SMWResultArray
83
				$manyValue_items = [];
84
				$isMissingProperty = false;
85
				
86
				$manyValues = $field->getContent();
87
				
88
				//If property is not set (has no value) on a page:
89
				if( empty( $manyValues ) ) {
90
					$delivery = $this->deliverMissingProperty( $field );
91
					$manyValue_items = $this->fillDeliveryArray( $manyValue_items, $delivery );
92
					$isMissingProperty = true;
93
				} else
94
				//otherwise collect property value (potentially many values):
95
				while( $obj = $field->getNextDataValue() ) {
96
					
97
					$value_items = [];					
98
					$isRecord = false;
99
					
100
					// handle page Title:
101
					if( $isPageTitle ) {						
102
						if( ! $this->mShowPageTitles ) {
103
							$isPageTitle = false;
104
							continue 2; //next property
105
						}						
106
						$value_items = $this->fillDeliveryArray( $value_items, $this->deliverPageTitle( $obj, $this->mLinkFirst ) );
107
					}
108
					// handle record values:
109
					elseif( $obj instanceof SMWRecordValue ) {												
110
						$recordItems = $obj->getDataItems();
111
						// walk all single values of the record set:
112
						foreach( $recordItems as $dataItem ) {							
113
							$recordField = $dataItem !== null ? SMWDataValueFactory::newDataItemValue( $dataItem, null ) : null;
114
							$value_items = $this->fillDeliveryArray( $value_items, $this->deliverRecordField( $recordField, $this->mLinkOthers ) );
115
						}
116
						$isRecord = true;
117
					}
118
					// handle normal data values:
119
					else {						
120
						$value_items = $this->fillDeliveryArray( $value_items, $this->deliverSingleValue( $obj, $this->mLinkOthers ) );
121
					}
122
					$delivery = $this->deliverSingleManyValuesData( $value_items, $isRecord, $isPageTitle );
123
					$manyValue_items = $this->fillDeliveryArray( $manyValue_items, $delivery );
124
				} // foreach...
125
				$delivery = $this->deliverPropertiesManyValues( $manyValue_items, $isMissingProperty, $isPageTitle, $field );
126
				$perProperty_items = $this->fillDeliveryArray( $perProperty_items, $delivery );
127
				$isPageTitle = false; // next one could be record or normal value
128
			} // foreach...			
129
			$delivery = $this->deliverPageProperties( $perProperty_items );
130
			$perPage_items = $this->fillDeliveryArray( $perPage_items, $delivery );
131
		} // while...
132
133
		$output = $this->deliverQueryResultPages( $perPage_items );
134
		
135
		return $output;
136
	}
137
	
138
	protected function fillDeliveryArray( $array = [], $value = null ) {
139
		if( ! is_null( $value ) ) { //don't create any empty entries
140
			$array[] = $value;
141
		}
142
		return $array;
143
	}
144
145
	protected function deliverPageTitle( $value, $link = false ) {
146
		return $this->deliverSingleValue( $value, $link );
147
	}
148
	protected function deliverRecordField( $value, $link = false ) {
149
		if( $value !== null ) // contains value
150
			return $this->deliverSingleValue( $value, $link );
151
		elseif( $this->mHideRecordGaps )
152
			return null; // hide gap
153
		else
154
			return ''; // empty string will make sure that record value separators are generated
155
	}
156
	protected function deliverSingleValue( $value, $link = false ) {
157
		//return trim( $value->getShortWikiText( $link ) );
158
		return trim( Sanitizer::decodeCharReferences( $value->getShortWikiText( $link ) ) ); // decode: better for further processing with array extension
159
	}
160
	// Property not declared on a page:
161
	protected function deliverMissingProperty( SMWResultArray $field ) {
162
		if( $this->mHidePropertyGaps )
163
			return null;
164
		else
165
			return ''; //empty string will make sure that array separator will be generated
166
			/** @ToDo: System for Default values?... **/
167
	}
168
	//represented by an array of record fields or just a single array value:
169
	protected function deliverSingleManyValuesData( $value_items, $containsRecord, $isPageTitle ) {
170
		if( empty( $value_items ) ) //happens when one of the higher functions delivers null
171
			return null;
172
		return implode( $this->mRecordSep, $value_items );
173
	}
174
	protected function deliverPropertiesManyValues( $manyValue_items, $isMissingProperty, $isPageTitle, SMWResultArray $data ) {
175
		if( empty( $manyValue_items ) )
176
			return null;
177
		
178
		$text = implode( $this->mManySep, $manyValue_items );
179
		
180
		// if property names should be displayed and this is not the page titles value:
181
		if(  $this->mShowHeaders != SMW_HEADERS_HIDE && ! $isPageTitle ) {
182
			$linker = $this->mShowHeaders == SMW_HEADERS_PLAIN ? null : $this->mLinker;
183
			$text = $data->getPrintRequest()->getText( SMW_OUTPUT_WIKI, $linker ) . $this->mHeaderSep . $text;
184
		}
185
		return $text;
186
	}
187
	protected function deliverPageProperties( $perProperty_items ) {
188
		if( empty( $perProperty_items ) )
189
			return null;
190
		return implode( $this->mPropSep, $perProperty_items );
191
	}
192
	protected function deliverQueryResultPages( $perPage_items ) {
193
		if( $this->mArrayName !== null ) {
194
			$this->createArray( $perPage_items ); //create Array
195
			return '';
196
		} else {
197
			return implode( $this->mSep, $perPage_items );
198
		}
199
	}
200
	
201
	/**
202
	 * Helper function to create a new Array within 'Arrays' extension. Takes care of different versions
203
	 * as well as the old 'ArrayExtension'.
204
	 */
205
	protected function createArray( $array ) {
206
		global $wgArrayExtension;
207
		
208
		$arrayId = $this->mArrayName;
209
		
210
		if( defined( 'ExtArrays::VERSION' ) ) {
211
			// 'Arrays' extension 2+
212
			global $wgParser; /** ToDo: is there a way to get the actual parser which has started the query? */
213
			ExtArrays::get( $wgParser )->createArray( $arrayId, $array );
214
			return true;
215
		}
216
		
217
		// compatbility to 'ArrayExtension' extension before 2.0:
218
		
219
		if( ! isset( $wgArrayExtension ) ) {
220
			//Hash extension is not installed in this wiki
221
			return false;
222
		}
223
		$version = null;		
224
		if( defined( 'ArrayExtension::VERSION' ) ) {
225
			$version = ArrayExtension::VERSION;
226
		} elseif( defined( 'ExtArrayExtension::VERSION' ) ) {
227
			$version = ExtArrayExtension::VERSION;
228
		}
229
		if( $version !== null && version_compare( $version, '1.3.2', '>=' ) ) {
230
			// ArrayExtension 1.3.2+
231
			$wgArrayExtension->createArray( $arrayId, $array );
232
		} else {
233
			// dirty way
234
			$wgArrayExtension->mArrays[ trim( $arrayId ) ] = $array;
235
		}
236
		return true;
237
	}
238
	
239
	protected function initializeCfgValue( $dfltVal, $dfltCacheKey ) {		
240
		$cache = &self::$mDefaultSeps[ $dfltCacheKey ];
241
		if( ! isset( $cache ) ) {
242
			$cache = $this->getCfgSepText( $dfltVal );			
243
			if( $cache === null ) {
244
				// cache can't be initialized, propably function-reference in userconfig
245
				// but format is not used in inline context, use fallback in this case:
246
				global $srfgArraySepTextualFallbacks;
247
				$cache = $srfgArraySepTextualFallbacks[ $dfltCacheKey ];
248
			}
249
		}
250
		return $cache;
251
	}
252
	protected function getCfgSepText( $obj ) {		
253
		if( is_array( $obj ) ) {
254
			// invalid definition:
255
			if( ! array_key_exists( 0, $obj ) )
256
				return null;
257
258
			// check for config-defined arguments to pass to the page before processing it:			
259
			if( array_key_exists( 'args', $obj ) && is_array( $obj['args'] ) )
260
				$params = $obj['args'];
261
			else
262
				$params = []; // no arguments
263
			
264
			// create title of page whose text should be used as separator:
265
			$obj = Title::newFromText( $obj[0], ( array_key_exists( 1, $obj ) ? $obj[1] : NS_MAIN ) );
266
		}
267
		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...
268
			$article = new Article( $obj );
269
		} 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...
270
			$article = $obj;
271
		} else {
272
			return $obj; //only text
273
		}
274
		
275
		global $wgParser;		
276
		/*
277
		 * Feature to use page value as separator only works if Parser::parse() is running!
278
		 * That's not the case on semantic search special page for example!
279
		 */
280
		// can't use $this->mInline here since SMW 1.6.2 had a bug setting it to false in most cases!		
281
		if( ! isset( $wgParser->mOptions ) ) {
282
		//if( ! $this->mInline ) {
283
			return null;
284
		}
285
		
286
		/*
287
		 * parse page as if it were included like a template. Never use Parser::recursiveTagParse() or similar 
288
		 * for this since it would call hooks we don't want to call and won't return wiki text for inclusion!
289
		 */
290
		$frame = $wgParser->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...
291
		// compatibility for 1.19, getContent() was implemented in 1.21.
292
		// FIXME: Remove when support for MediaWiki 1.19 is dropped
293
		if ( method_exists( $article, 'getContent' ) ) {
294
			$content = $article->getContent( Revision::RAW )->getNativeData();
295
		} else {
296
			$content = $article->getRawText();
297
		}
298
		$text = $wgParser->preprocessToDom( $content, Parser::PTD_FOR_INCLUSION );
299
		$text = trim( $frame->expand( $text ) );
300
		
301
		return $text;
302
	}
303
	
304
	protected function handleParameters( array $params, $outputmode ) {
305
		// does the link parameter:
306
		parent::handleParameters( $params, $outputmode );
307
		
308
		//separators:
309
		$this->mSep       = $params['sep'];
310
		$this->mPropSep   = $params['propsep'];
311
		$this->mManySep   = $params['manysep'];
312
		$this->mRecordSep = $params['recordsep'];
313
		$this->mHeaderSep = $params['headersep'];
314
		
315
		// only use this in inline mode, if text is given. Since SMW 1.6.2 '' is given, so if
316
		// we wouldn't check, we would always end up with an array instead of visible output
317
		if( $params['name'] !== false && ( $this->mInline || trim( $params['name'] ) !== '' ) ) {
318
			$this->mArrayName = trim( $params['name'] );
319
			$this->createArray( [] ); //create empty array in case we get no result so we won't have an undefined array in the end.
320
		}
321
		
322
		// if mainlabel set to '-', this will cause the titles not to appear, so make sure we catch this!
323
		$this->mMainLabelHack = trim( $params['mainlabel'] ) === '-';
324
		
325
		// whether or not to display the page title:
326
		$this->mShowPageTitles = strtolower( $params['titles'] ) != 'hide';
327
		
328
		switch( strtolower( $params['hidegaps'] ) ) {
329
			case 'none':
330
				$this->mHideRecordGaps = false;
331
				$this->mHidePropertyGaps = false;
332
				break;
333
			case 'all':
334
				$this->mHideRecordGaps = true;
335
				$this->mHidePropertyGaps = true;
336
				break;
337
			case 'property': case 'prop': case 'attribute': case 'attr':
338
				$this->mHideRecordGaps = false;
339
				$this->mHidePropertyGaps = true;
340
				break;
341
			case 'record': case 'rec': case 'rcrd': case 'n-ary': case 'nary':
342
				$this->mHideRecordGaps = true;
343
				$this->mHidePropertyGaps = false;
344
				break;
345
		}
346
	}
347
348
	/**
349
	 * @see SMWResultPrinter::getParamDefinitions
350
	 *
351
	 * @since 1.8
352
	 *
353
	 * @param $definitions array of IParamDefinition
354
	 *
355
	 * @return array of IParamDefinition|array
356
	 */
357
	public function getParamDefinitions( array $definitions ) {
0 ignored issues
show
Coding Style introduced by
getParamDefinitions uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
358
		$params = parent::getParamDefinitions( $definitions );
359
360
		### adjusted basic SMW params: ###
361
362
		$definitions['limit']->setDefault( $GLOBALS['smwgQMaxInlineLimit'] );
363
		$definitions['link']->setDefault( 'none' );
364
		$definitions['headers']->setDefault( 'hide' );
365
		
366
		### new params: ###
367
		
368
		$params['titles'] = [
369
			'message' => 'srf_paramdesc_pagetitle',
370
			'values' => [ 'show', 'hide' ],
371
			'aliases' => [ 'pagetitle', 'pagetitles' ],
372
			'default' => 'show',
373
		];
374
375
		$params['hidegaps'] = [
376
			'message' => 'srf_paramdesc_hidegaps',
377
			'values' => [ 'none', 'all', 'property', 'record' ],
378
			'default' => 'none',
379
		];
380
381
		$params['name'] = [
382
			'message' => 'srf_paramdesc_arrayname',
383
			'default' => false,
384
			'manipulatedefault' => false,
385
		];
386
387
		// separators (default values are defined in the following globals:)
388
		global $srfgArraySep, $srfgArrayPropSep, $srfgArrayManySep, $srfgArrayRecordSep, $srfgArrayHeaderSep;
389
390
		$params['sep'] = [
391
			'message' => 'smw_paramdesc_sep',
392
			'default' => $this->initializeCfgValue( $srfgArraySep, 'sep' ),
393
		];
394
395
		$params['propsep'] = [
396
			'message' => 'smw_paramdesc_propsep',
397
			'default' => $this->initializeCfgValue( $srfgArrayPropSep, 'propsep' ),
398
		];
399
400
		$params['manysep'] = [
401
			'message' => 'srf_paramdesc_manysep',
402
			'default' => $this->initializeCfgValue( $srfgArrayManySep, 'manysep' ),
403
		];
404
405
		$params['recordsep'] = [
406
			'message' => 'srf_paramdesc_recordsep',
407
			'default' => $this->initializeCfgValue( $srfgArrayRecordSep, 'recordsep' ),
408
			'aliases' => [ 'narysep', 'rcrdsep', 'recsep' ],
409
		];
410
411
		$params['headersep'] = [
412
			'message' => 'srf_paramdesc_headersep',
413
			'default' => $this->initializeCfgValue( $srfgArrayHeaderSep, 'headersep' ),
414
			'aliases' => [ 'narysep', 'rcrdsep', 'recsep' ],
415
		];
416
417
		return $params;
418
	}
419
420
}
421
422
423
424