Completed
Push — zero ( 982509 )
by mw
02:56
created

ExtraPropertyAnnotator   C

Complexity

Total Complexity 76

Size/Duplication

Total Lines 368
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 44.86%

Importance

Changes 9
Bugs 1 Features 0
Metric Value
wmc 76
c 9
b 1
f 0
lcom 1
cbo 10
dl 0
loc 368
ccs 96
cts 214
cp 0.4486
rs 5.488

24 Methods

Rating   Name   Duplication   Size   Complexity  
A getSemanticData() 0 3 1
A hasRegisteredPropertyId() 0 4 2
A isUserPage() 0 3 1
A isFilePage() 0 3 1
A __construct() 0 5 1
B addAnnotation() 0 15 6
A getWikiPage() 0 8 2
B addPropertyValues() 0 30 5
C createDataItemById() 0 52 15
A makeFirstAuthorDataItem() 0 7 2
A makeNumberOfPageViewsDataItem() 0 13 3
A getPageViewCount() 0 11 3
B addPropertyValuesForPageContributors() 0 20 5
A getPageRevisionsForId() 0 12 2
A makePageIdDataItem() 0 7 3
A makeRevisionIdDataItem() 0 7 3
A makeNumberOfRevisionsDataItem() 0 9 3
A makeNumberOfTalkPageRevisionsDataItem() 0 9 3
A addPropertyValuesForMIMEAndMediaType() 0 21 2
A addPropertyValuesForSubPages() 0 12 2
A addPropertyValuesForExifData() 0 5 2
A addPropertyValuesForShortUrl() 0 8 2
A makeUserRegistrationDataItem() 0 23 3
A makeUserEditCountDataItem() 0 14 4

How to fix   Complexity   

Complex Class

Complex classes like ExtraPropertyAnnotator 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 ExtraPropertyAnnotator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SESP\Annotator;
4
5
use SESP\PropertyRegistry;
6
use SESP\AppFactory;
7
8
use SMW\SemanticData;
9
use SMW\DIProperty;
10
use SMW\DIWikiPage;
11
use SMW\Store;
12
13
use SMWDataItem as DataItem;
14
use SMWDIBlob as DIBlob;
15
use SMWDIBoolean as DIBoolean;
16
use SMWDITime as DITime;
17
use SMWDINumber as DINumber;
18
19
use WikiPage;
20
use User;
21
use RuntimeException;
22
23
/**
24
 * @ingroup SESP
25
 *
26
 * @license GNU GPL v2+
27
 * @since 1.0
28
 *
29
 * @author mwjames
30
 * @author rotsee
31
 */
32
class ExtraPropertyAnnotator {
33
34
	/**
35
	 * @var SemanticData
36
	 */
37
	protected $semanticData = null;
38
39
	/**
40
	 * @var AppFactory
41
	 */
42
	private $appFactory = null;
43
44
45
	protected $configuration = null;
46
	private $dbConnection = null;
47
	private $page = null;
48
49
	/**
50
	 * @since 1.0
51
	 *
52
	 * @param SemanticData $semanticData
53
	 * @param Factory $factory
0 ignored issues
show
Documentation introduced by
There is no parameter named $factory. Did you maybe mean $appFactory?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
54
	 * @param array $configuration
55
	 */
56 9
	public function __construct( SemanticData $semanticData, AppFactory $appFactory, array $configuration ) {
57 9
		$this->semanticData = $semanticData;
58 9
		$this->appFactory = $appFactory;
59 9
		$this->configuration = $configuration;
60 9
	}
61
62
	/**
63
	 * @since 1.0
64
	 *
65
	 * @return boolean
66
	 * @throws RuntimeException
67
	 *
68
	 * @return boolean
69
	 */
70 8
	public function addAnnotation() {
71
72 8
		$subject = $this->semanticData->getSubject();
73
74 8
		if ( $subject === null || $subject->getTitle() === null || $subject->getTitle()->isSpecialPage() ) {
75 1
			return false;
76
		}
77
78 7
		if ( isset( $this->configuration['sespSpecialProperties'] ) &&
79 7
			is_array( $this->configuration['sespSpecialProperties'] ) ) {
80 6
			return $this->addPropertyValues();
81
		}
82
83 1
		throw new RuntimeException( "Expected a 'sespSpecialProperties' configuration array" );
84
	}
85
86
	/**
87
	 * @since 1.0
88
	 *
89
	 * @return SemanticData
90
	 */
91 6
	public function getSemanticData() {
92 6
		return $this->semanticData;
93
	}
94
95
	/**
96
	 * @since 1.0
97
	 *
98
	 * @return WikiPage
99
	 */
100 6
	public function getWikiPage() {
101
102 6
		if ( $this->page === null ) {
103 6
			$this->page = $this->appFactory->newWikiPage( $this->semanticData->getSubject()->getTitle() ); //$this->loadRegisteredObject( 'WikiPage' );
0 ignored issues
show
Bug introduced by
It seems like $this->semanticData->getSubject()->getTitle() can be null; however, newWikiPage() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
104 6
		}
105
106 6
		return $this->page;
107
	}
108
109 6
	protected function addPropertyValues() {
110
111 6
		$cachedProperties = array();
112
113 6
		foreach ( $this->configuration['sespSpecialProperties'] as $externalId ) {
114
115 6
			$propertyId = PropertyRegistry::getInstance()->getPropertyId( $externalId );
116
117 6
			if ( $this->hasRegisteredPropertyId( $propertyId, $cachedProperties ) ) {
118
				continue;
119
			}
120
121 6
			$propertyDI = new DIProperty( $propertyId );
122
123 6
			if ( $this->getSemanticData()->getPropertyValues( $propertyDI ) !== array() ) {
124
				$cachedProperties[ $propertyId ] = true;
125
				continue;
126
			}
127
128 6
			$dataItem = $this->createDataItemById( $externalId, $propertyDI );
129
130 6
			if ( $dataItem instanceof DataItem ) {
131 4
				$cachedProperties[ $propertyId ] = true;
132 4
				$this->getSemanticData()->addPropertyObjectValue( $propertyDI, $dataItem );
133 4
			}
134
135 6
		}
136
137 6
		return true;
138
	}
139
140 6
	protected function hasRegisteredPropertyId( $propertyId, $cachedProperties ) {
141 6
		return ( DIProperty::getPredefinedPropertyTypeId( $propertyId ) === '' ) ||
0 ignored issues
show
Deprecated Code introduced by
The method SMW\DIProperty::getPredefinedPropertyTypeId() has been deprecated with message: since 2.1, use PropertyRegistry::getPredefinedPropertyTypeId

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
142 6
			array_key_exists( $propertyId, $cachedProperties );
143
	}
144
145 6
	protected function createDataItemById( $externalId, $property ) {
146
147 6
		$dataItem = null;
148
149
		// _REVID was incorrect in the original SESP because getId returns the
150
		// page id not the revision Id
151
152
		switch ( $externalId ) {
153 6
			case '_CUSER' :
154 1
				$dataItem = $this->makeFirstAuthorDataItem();
155 1
				break;
156 5
			case '_VIEWS' :
157
				$dataItem = $this->makeNumberOfPageViewsDataItem();
158
				break;
159 5
			case '_USERREG' :
160 2
				$dataItem = $this->makeUserRegistrationDataItem();
161 2
				break;
162 3
			case '_USEREDITCNT' :
163
				$dataItem = $this->makeUserEditCountDataItem();
164
				break;
165 3
			case '_PAGEID' :
166
				$dataItem = $this->makePageIdDataItem();
167
				break;
168 3
			case '_REVID' :
169 2
				$dataItem = $this->makeRevisionIdDataItem();
170 2
				break;
171 1
			case '_NREV' :
172 1
				$dataItem = $this->makeNumberOfRevisionsDataItem();
173 1
				break;
174
			case '_NTREV' :
175
				$dataItem = $this->makeNumberOfTalkPageRevisionsDataItem();
176
				break;
177
			case '_EUSER' :
178
				$this->addPropertyValuesForPageContributors( $property );
179
				break;
180
			case '_SUBP' :
181
				$this->addPropertyValuesForSubPages( $property );
182
				break;
183
			case '_MEDIATYPE' :
184
			case '_MIMETYPE' :
185
				$this->addPropertyValuesForMIMEAndMediaType();
186
				break;
187
			case '_EXIFDATA' :
188
				$this->addPropertyValuesForExifData();
189
				break;
190
			case '_SHORTURL' :
191
				$this->addPropertyValuesForShortUrl();
192
				break;
193
		}
194
195 6
		return $dataItem;
196
	}
197
198 2
	private function isUserPage() {
199 2
		return $this->getWikiPage()->getTitle()->inNamespace( NS_USER );
200
	}
201
202
	private function isFilePage() {
203
		return $this->getWikiPage()->getTitle()->inNamespace( NS_FILE );
204
	}
205
206 1
	private function makeFirstAuthorDataItem() {
207 1
		$creator = $this->getWikiPage()->getCreator();
208
209 1
		if ( $creator ) {
210 1
			return DIWikiPage::newFromTitle( $creator->getUserPage() );
211
		}
212
	}
213
214
	private function makeNumberOfPageViewsDataItem() {
215
		if ( $this->configuration['wgDisableCounters'] ) {
216
			return null;
217
		}
218
219
		$count = $this->getPageViewCount();
220
221
		if ( !is_numeric( $count ) ) {
222
			return null;
223
		}
224
225
		return new DINumber( $count );
226
	}
227
228
	private function getPageViewCount() {
229
		if ( class_exists( '\HitCounters\HitCounters' ) ) {
230
			return \HitCounters\HitCounters::getCount( $this->getWikiPage()->getTitle() );
231
		}
232
233
		if ( method_exists( $this->getWikiPage(), 'getCount' ) ) {
234
			return $this->getWikiPage()->getCount();
235
		}
236
237
		return null;
238
	}
239
240
	private function addPropertyValuesForPageContributors( DIProperty $property ) {
241
242
		$user = User::newFromId( $this->getWikiPage()->getUser() );
243
		$authors = $this->getWikiPage()->getContributors();
244
245
		while ( $user ) {
246
			if ( !( in_array( 'bot', $user->getRights() ) &&
247
				$this->configuration['wgSESPExcludeBots'] ) &&
248
				!$user->isAnon() ) { //no anonymous users (hidden users are not returned)
249
250
				$this->getSemanticData()->addPropertyObjectValue(
251
					$property,
252
					DIWikiPage::newFromTitle( $user->getUserPage() )
253
				);
254
			}
255
256
			$user = $authors->current();
257
			$authors->next();
258
		}
259
	}
260
261
	private function makePageIdDataItem() {
262
		$pageID = $this->getWikiPage()->getId();
263
264
		if ( is_integer( $pageID ) && $pageID > 0 ) {
265
			return new DINumber( $pageID );
266
		}
267
	}
268
269 2
	private function makeRevisionIdDataItem() {
270 2
		$revID = $this->getWikiPage()->getLatest();
271
272 2
		if ( is_integer( $revID ) && $revID > 0 ) {
273 1
			return new DINumber( $revID );
274
		}
275 1
	}
276
277 1
	private function getPageRevisionsForId( $pageId ) {
278
279 1
		if ( $this->dbConnection === null ) {
280 1
			$this->dbConnection = $this->appFactory->newDatabaseConnection(); //( 'DBConnection', 'DatabaseBase' );
281 1
		}
282
283 1
		return $this->dbConnection->estimateRowCount(
284 1
			"revision",
285 1
			"*",
286 1
			array( "rev_page" => $pageId )
287 1
		);
288
	}
289
290 1
	private function makeNumberOfRevisionsDataItem() {
291 1
		$numberOfPageRevisions = $this->getPageRevisionsForId(
292 1
			$this->getWikiPage()->getTitle()->getArticleID()
293 1
		);
294
295 1
		if ( $this->getWikiPage()->getTitle()->exists() && $numberOfPageRevisions > 0 ) {
296 1
			return new DINumber( $numberOfPageRevisions );
297
		}
298
	}
299
300
	private function makeNumberOfTalkPageRevisionsDataItem() {
301
		$numberOfTalkPageRevisions = $this->getPageRevisionsForId(
302
			$this->getWikiPage()->getTitle()->getTalkPage()->getArticleID()
303
		);
304
305
		if ( $this->getWikiPage()->getTitle()->getTalkPage()->exists() && $numberOfTalkPageRevisions > 0 ) {
306
			return new DINumber( $numberOfTalkPageRevisions );
307
		}
308
	}
309
310
	private function addPropertyValuesForMIMEAndMediaType(){
311
312
		if ( $this->isFilePage() ) {
313
314
			$file = $this->getWikiPage()->getFile();
315
			$mimetype = $file->getMimeType();
316
			$mediaType = \MimeMagic::singleton()->findMediaType( $mimetype );
317
			list( $mimetypemajor, $mimetypeminor ) = $file->splitMime( $mimetype );
0 ignored issues
show
Unused Code introduced by
The assignment to $mimetypemajor is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
318
319
			$this->getSemanticData()->addPropertyObjectValue(
320
				new DIProperty( PropertyRegistry::getInstance()->getPropertyId( '_MIMETYPE' ) ),
321
				new DIBlob( $mimetypeminor )
322
			);
323
324
			$this->getSemanticData()->addPropertyObjectValue(
325
				new DIProperty( PropertyRegistry::getInstance()->getPropertyId( '_MEDIATYPE' ) ),
326
				new DIBlob( $mediaType )
327
			);
328
		}
329
330
	}
331
332
	private function addPropertyValuesForSubPages( DIProperty $property ) {
333
334
		//-1 = no limit. Returns TitleArray object
335
		$subpages = $this->getWikiPage()->getTitle()->getSubpages ( -1 );
336
337
		foreach ( $subpages as $title ) {
338
			$this->getSemanticData()->addPropertyObjectValue(
339
				$property,
340
				DIWikiPage::newFromTitle( $title )
341
			);
342
		}
343
	}
344
345
	private function addPropertyValuesForExifData() {
346
		if ( $this->isFilePage() ) {
347
			$this->appFactory->newExifDataAnnotator( $this->getSemanticData(), $this->getWikiPage()->getFile() )->addAnnotation();
348
		}
349
	}
350
351
	private function addPropertyValuesForShortUrl() {
352
353
		$shortUrlAnnotator = $this->appFactory->newShortUrlAnnotator( $this->getSemanticData() );
354
355
		if ( $shortUrlAnnotator->canUseShortUrl() ) {
356
			$shortUrlAnnotator->addAnnotation();
357
		}
358
	}
359
360 2
	private function makeUserRegistrationDataItem() {
361
362 2
		if ( !$this->isUserPage() ) {
363
			return null;
364
		}
365
366 2
		$user = $this->appFactory->newUserFromTitle( $this->getWikiPage()->getTitle() );
367
368 2
		if ( $user instanceof User ) {
0 ignored issues
show
Bug introduced by
The class User does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
369
370 1
			$timestamp = wfTimestamp( TS_ISO_8601, $user->getRegistration() );
371 1
			$date = new \DateTime( $timestamp );
372
373 1
			return new DITime(
374 1
				DITime::CM_GREGORIAN,
375 1
				$date->format('Y'),
376 1
				$date->format('m'),
0 ignored issues
show
Documentation introduced by
$date->format('m') is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
377 1
				$date->format('d'),
0 ignored issues
show
Documentation introduced by
$date->format('d') is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
378 1
				$date->format('H'),
0 ignored issues
show
Documentation introduced by
$date->format('H') is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
379 1
				$date->format('i')
0 ignored issues
show
Documentation introduced by
$date->format('i') is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
380 1
			);
381
		}
382 1
	}
383
384
	private function makeUserEditCountDataItem() {
385
386
		if ( !$this->isUserPage() ) {
387
			return;
388
		}
389
390
		$user = $this->appFactory->newUserFromTitle( $this->getWikiPage()->getTitle() );
391
392
		$count = $user instanceof User ? $user->getEditCount() : false;
0 ignored issues
show
Bug introduced by
The class User does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
393
394
		if ( $count ) {
395
			return new DINumber( $count );
396
		}
397
	}
398
399
}