Completed
Push — master ( 6b7037...267da0 )
by
unknown
02:02
created

CommonsLinkChecker::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Checker;
4
5
use MalformedTitleException;
6
use MediaWiki\MediaWikiServices;
7
use TitleParser;
8
use TitleValue;
9
use Wikibase\DataModel\Entity\ItemId;
10
use Wikibase\DataModel\Snak\PropertyValueSnak;
11
use WikibaseQuality\ConstraintReport\ConstraintCheck\ConstraintChecker;
12
use WikibaseQuality\ConstraintReport\ConstraintCheck\Context\Context;
13
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterException;
14
use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser;
15
use WikibaseQuality\ConstraintReport\ConstraintCheck\Message\ViolationMessage;
16
use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
17
use WikibaseQuality\ConstraintReport\Constraint;
18
use WikibaseQuality\ConstraintReport\Role;
19
20
/**
21
 * @author BP2014N1
22
 * @license GPL-2.0-or-later
23
 */
24
class CommonsLinkChecker implements ConstraintChecker {
25
26
	/**
27
	 * @var ConstraintParameterParser
28
	 */
29
	private $constraintParameterParser;
30
31
	/**
32
	 * @var TitleParser
33
	 */
34
	private $titleParser;
35
36
	public function __construct(
37
		ConstraintParameterParser $constraintParameterParser,
38
		TitleParser $titleParser
39
	) {
40
		$this->constraintParameterParser = $constraintParameterParser;
41
		$this->titleParser = $titleParser;
42
	}
43
44
	/**
45
	 * @codeCoverageIgnore This method is purely declarative.
46
	 */
47
	public function getSupportedContextTypes() {
48
		return [
49
			Context::TYPE_STATEMENT => CheckResult::STATUS_COMPLIANCE,
50
			Context::TYPE_QUALIFIER => CheckResult::STATUS_COMPLIANCE,
51
			Context::TYPE_REFERENCE => CheckResult::STATUS_COMPLIANCE,
52
		];
53
	}
54
55
	/**
56
	 * @codeCoverageIgnore This method is purely declarative.
57
	 */
58
	public function getDefaultContextTypes() {
59
		return [
60
			Context::TYPE_STATEMENT,
61
			Context::TYPE_QUALIFIER,
62
			Context::TYPE_REFERENCE,
63
		];
64
	}
65
66
	/**
67
	 * Get the number of a namespace on Wikimedia Commons (commonswiki).
68
	 * All namespaces not known to this function will be looked up by the TitleParser.
69
	 *
70
	 * @param string $namespace
71
	 *
72
	 * @return array first element is the namespace number (default namespace for TitleParser),
73
	 * second element is a string to prepend to the title before giving it to the TitleParser
74
	 */
75
	private function getCommonsNamespace( $namespace ) {
76
		switch ( $namespace ) {
77
			case '':
78
				return [ NS_MAIN, '' ];
79
			// extra namespaces, see operations/mediawiki-config.git,
80
			// wmf-config/InitialiseSettings.php, 'wgExtraNamespaces' key, 'commonswiki' subkey
81
			case 'Creator':
82
				return [ 100, '' ];
83
			case 'TimedText':
84
				return [ 102, '' ];
85
			case 'Sequence':
86
				return [ 104, '' ];
87
			case 'Institution':
88
				return [ 106, '' ];
89
			// extension namespace, see mediawiki/extensions/JsonConfig.git,
90
			// extension.json, 'namespaces' key, third element
91
			case 'Data':
92
				return [ 486, '' ];
93
			default:
94
				return [ NS_MAIN, $namespace . ':' ];
95
		}
96
	}
97
98
	/**
99
	 * Checks 'Commons link' constraint.
100
	 *
101
	 * @param Context $context
102
	 * @param Constraint $constraint
103
	 *
104
	 * @throws ConstraintParameterException
105
	 * @return CheckResult
106
	 */
107
	public function checkConstraint( Context $context, Constraint $constraint ) {
108
		$parameters = [];
109
		$constraintParameters = $constraint->getConstraintParameters();
110
		$namespace = $this->constraintParameterParser->parseNamespaceParameter( $constraintParameters, $constraint->getConstraintTypeItemId() );
111
		$parameters['namespace'] = [ $namespace ];
112
113
		$snak = $context->getSnak();
114
115
		if ( !$snak instanceof PropertyValueSnak ) {
116
			// nothing to check
117
			return new CheckResult( $context, $constraint, $parameters, CheckResult::STATUS_COMPLIANCE );
118
		}
119
120
		$dataValue = $snak->getDataValue();
121
122
		/*
123
		 * error handling:
124
		 *   type of $dataValue for properties with 'Commons link' constraint has to be 'string'
125
		 *   parameter $namespace can be null, works for commons galleries
126
		 */
127 View Code Duplication
		if ( $dataValue->getType() !== 'string' ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
128
			$message = ( new ViolationMessage( 'wbqc-violation-message-value-needed-of-type' ) )
129
				->withEntityId( new ItemId( $constraint->getConstraintTypeItemId() ), Role::CONSTRAINT_TYPE_ITEM )
130
				->withDataValueType( 'string' );
131
			return new CheckResult( $context, $constraint, $parameters, CheckResult::STATUS_VIOLATION, $message );
132
		}
133
134
		$commonsLink = $dataValue->getValue();
135
136
		try {
137
			if ( !$this->commonsLinkIsWellFormed( $commonsLink ) ) {
138
				throw new MalformedTitleException( 'wbqc-violation-message-commons-link-not-well-formed', $commonsLink ); // caught below
139
			}
140
			list( $defaultNamespace, $prefix ) = $this->getCommonsNamespace( $namespace );
141
			$title = $this->titleParser->parseTitle( $prefix . $commonsLink, $defaultNamespace );
142
			if ( $this->pageExists( $title ) ) {
143
				$message = null;
144
				$status = CheckResult::STATUS_COMPLIANCE;
145
			} else {
146
				if ( $this->valueIncludesNamespace( $commonsLink, $namespace ) ) {
147
					throw new MalformedTitleException( 'wbqc-violation-message-commons-link-not-well-formed', $commonsLink ); // caught below
148
				} else {
149
					$message = new ViolationMessage( 'wbqc-violation-message-commons-link-no-existent' );
150
					$status = CheckResult::STATUS_VIOLATION;
151
				}
152
			}
153
		} catch ( MalformedTitleException $e ) {
0 ignored issues
show
Bug introduced by
The class MalformedTitleException 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...
154
			$message = new ViolationMessage( 'wbqc-violation-message-commons-link-not-well-formed' );
155
			$status = CheckResult::STATUS_VIOLATION;
156
		}
157
158
		return new CheckResult( $context, $constraint, $parameters, $status, $message );
159
	}
160
161
	public function checkConstraintParameters( Constraint $constraint ) {
162
		$constraintParameters = $constraint->getConstraintParameters();
163
		$exceptions = [];
164
		try {
165
			$this->constraintParameterParser->parseNamespaceParameter( $constraintParameters, $constraint->getConstraintTypeItemId() );
166
		} catch ( ConstraintParameterException $e ) {
167
			$exceptions[] = $e;
168
		}
169
		return $exceptions;
170
	}
171
172
	/**
173
	 * @param TitleValue $title
174
	 *
175
	 * @return bool
176
	 */
177
	private function pageExists( TitleValue $title ) {
178
		$commonsWikiId = 'commonswiki';
179
		if ( defined( 'MW_PHPUNIT_TEST' ) ) {
180
			$commonsWikiId = false;
181
		}
182
183
		$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
184
		$dbConnection = $lbFactory->getMainLB( $commonsWikiId )->getConnection(
185
			DB_REPLICA, false, $commonsWikiId
186
		);
187
		$row = $dbConnection->selectRow( 'page', '*', [
188
			'page_title' => $title->getDBkey(),
189
			'page_namespace' => $title->getNamespace()
190
		] );
191
192
		return $row !== false;
193
	}
194
195
	/**
196
	 * @param string $commonsLink
197
	 *
198
	 * @return bool
199
	 */
200
	private function commonsLinkIsWellFormed( $commonsLink ) {
201
		$toReplace = [ "_", "%20" ];
202
		$compareString = trim( str_replace( $toReplace, '', $commonsLink ) );
203
		return $commonsLink === $compareString;
204
	}
205
206
	/**
207
	 * Checks whether the value of the statement already includes the namespace.
208
	 * This special case should be reported as “malformed title” instead of “title does not exist”.
209
	 *
210
	 * @param string $value
211
	 * @param string $namespace
212
	 *
213
	 * @return bool
214
	 */
215
	private function valueIncludesNamespace( $value, $namespace ) {
216
		return $namespace !== '' &&
217
			strncasecmp( $value, $namespace . ':', strlen( $namespace ) + 1 ) === 0;
218
	}
219
220
}
221