Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
82.02% |
73 / 89 |
|
81.82% |
9 / 11 |
CRAP | |
0.00% |
0 / 1 |
WikibaseClientSiteLinksForItemHandler | |
82.02% |
73 / 89 |
|
81.82% |
9 / 11 |
32.56 | |
0.00% |
0 / 1 |
newFromGlobalState | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
provideSiteLinks | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
doProvideSiteLinks | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
addSiteLink | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCommonsSiteLink | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
5 | |||
getLinkedItemSitelink | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
getCommonsCategoryName | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
getCommonsSitelinkFromMainSnaks | |
52.38% |
11 / 21 |
|
0.00% |
0 / 1 |
14.91 | |||
getItem | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getStringValueFromMainSnaks | |
45.45% |
5 / 11 |
|
0.00% |
0 / 1 |
6.60 |
1 | <?php |
2 | |
3 | declare( strict_types = 1 ); |
4 | |
5 | namespace WikimediaBadges; |
6 | |
7 | use DataValues\StringValue; |
8 | use MediaWiki\MediaWikiServices; |
9 | use OutOfBoundsException; |
10 | use Wikibase\Client\Usage\UsageAccumulator; |
11 | use Wikibase\Client\WikibaseClient; |
12 | use Wikibase\DataModel\Entity\EntityIdValue; |
13 | use Wikibase\DataModel\Entity\Item; |
14 | use Wikibase\DataModel\Entity\ItemId; |
15 | use Wikibase\DataModel\Entity\NumericPropertyId; |
16 | use Wikibase\DataModel\Services\Lookup\EntityLookup; |
17 | use Wikibase\DataModel\Services\Lookup\EntityLookupException; |
18 | use Wikibase\DataModel\SiteLink; |
19 | use Wikibase\DataModel\Snak\PropertyValueSnak; |
20 | use Wikibase\DataModel\Snak\Snak; |
21 | |
22 | /** |
23 | * Handler for the WikibaseClientSiteLinksForItem hook that changes the link |
24 | * to Wikimedia Commons with the one to the commons category. |
25 | * |
26 | * @since 0.1 |
27 | * |
28 | * @license GPL-2.0-or-later |
29 | * @author Marius Hoch < hoo@online.de > |
30 | */ |
31 | class WikibaseClientSiteLinksForItemHandler { |
32 | |
33 | /** @var EntityLookup */ |
34 | private $entityLookup; |
35 | |
36 | /** @var string|null */ |
37 | private $topicsMainCategoryProperty; |
38 | |
39 | /** @var string|null */ |
40 | private $categoryRelatedToListProperty; |
41 | |
42 | /** |
43 | * @var string|null |
44 | */ |
45 | private $commonsCategoryPropertySetting; |
46 | |
47 | private static function newFromGlobalState(): self { |
48 | $services = MediaWikiServices::getInstance(); |
49 | $config = $services->getMainConfig(); |
50 | |
51 | return new self( |
52 | WikibaseClient::getEntityLookup( $services ), |
53 | $config->get( 'WikimediaBadgesTopicsMainCategoryProperty' ), |
54 | $config->get( 'WikimediaBadgesCategoryRelatedToListProperty' ), |
55 | $config->get( 'WikimediaBadgesCommonsCategoryProperty' ) |
56 | ); |
57 | } |
58 | |
59 | public function __construct( |
60 | EntityLookup $entityLookup, |
61 | ?string $topicsMainCategoryProperty, |
62 | ?string $categoryRelatedToListProperty, |
63 | ?string $commonsCategoryPropertySetting |
64 | ) { |
65 | $this->entityLookup = $entityLookup; |
66 | $this->topicsMainCategoryProperty = $topicsMainCategoryProperty; |
67 | $this->categoryRelatedToListProperty = $categoryRelatedToListProperty; |
68 | $this->commonsCategoryPropertySetting = $commonsCategoryPropertySetting; |
69 | } |
70 | |
71 | /** |
72 | * @param Item $item |
73 | * @param SiteLink[] &$siteLinks |
74 | * @param UsageAccumulator $usageAccumulator |
75 | */ |
76 | public static function provideSiteLinks( |
77 | Item $item, array &$siteLinks, UsageAccumulator $usageAccumulator |
78 | ): void { |
79 | $self = self::newFromGlobalState(); |
80 | |
81 | $self->doProvideSiteLinks( $item, $siteLinks ); |
82 | } |
83 | |
84 | /** |
85 | * @param Item $item |
86 | * @param SiteLink[] &$siteLinks |
87 | */ |
88 | public function doProvideSiteLinks( Item $item, array &$siteLinks ): void { |
89 | $sitelink = $this->getCommonsSiteLink( $item ); |
90 | if ( $sitelink !== null ) { |
91 | $this->addSiteLink( $sitelink, $siteLinks ); |
92 | } |
93 | } |
94 | |
95 | /** |
96 | * @param string $siteLink |
97 | * @param SiteLink[] &$siteLinks |
98 | */ |
99 | private function addSiteLink( string $siteLink, array &$siteLinks ): void { |
100 | $siteLinks['commonswiki'] = new SiteLink( 'commonswiki', $siteLink ); |
101 | } |
102 | |
103 | private function getCommonsSiteLink( Item $item ): ?string { |
104 | try { |
105 | return $item->getSiteLink( 'commonswiki' )->getPageName(); |
106 | } catch ( OutOfBoundsException $e ) { |
107 | // pass |
108 | } |
109 | |
110 | $topicsMainCategorySitelink = $this->getLinkedItemSitelink( |
111 | $item, |
112 | $this->topicsMainCategoryProperty |
113 | ); |
114 | if ( $topicsMainCategorySitelink !== null ) { |
115 | return $topicsMainCategorySitelink; |
116 | } |
117 | |
118 | $categoryRelatedToListSitelink = $this->getLinkedItemSitelink( |
119 | $item, |
120 | $this->categoryRelatedToListProperty |
121 | ); |
122 | if ( $categoryRelatedToListSitelink !== null ) { |
123 | return $categoryRelatedToListSitelink; |
124 | } |
125 | |
126 | $categoryName = $this->getCommonsCategoryName( $item ); |
127 | if ( $categoryName !== null ) { |
128 | return 'Category:' . $categoryName; |
129 | } |
130 | |
131 | return null; |
132 | } |
133 | |
134 | private function getLinkedItemSitelink( Item $item, ?string $propertyIdString ): ?string { |
135 | if ( $propertyIdString === null ) { |
136 | return null; |
137 | } |
138 | |
139 | $propertyId = new NumericPropertyId( $propertyIdString ); |
140 | $statements = $item->getStatements()->getByPropertyId( $propertyId ); |
141 | |
142 | $mainSnaks = $statements->getBestStatements()->getMainSnaks(); |
143 | |
144 | return $this->getCommonsSitelinkFromMainSnaks( |
145 | $mainSnaks, |
146 | $item->getId(), |
147 | $propertyId |
148 | ); |
149 | } |
150 | |
151 | private function getCommonsCategoryName( Item $item ): ?string { |
152 | if ( $this->commonsCategoryPropertySetting === null ) { |
153 | return null; |
154 | } |
155 | |
156 | $propertyId = new NumericPropertyId( $this->commonsCategoryPropertySetting ); |
157 | $statements = $item->getStatements()->getByPropertyId( $propertyId ); |
158 | |
159 | $mainSnaks = $statements->getBestStatements()->getMainSnaks(); |
160 | |
161 | return $this->getStringValueFromMainSnaks( |
162 | $mainSnaks, |
163 | $item->getId(), |
164 | $propertyId |
165 | ); |
166 | } |
167 | |
168 | private function getCommonsSitelinkFromMainSnaks( |
169 | array $mainSnaks, |
170 | ItemId $itemId, |
171 | NumericPropertyId $propertyId |
172 | ): ?string { |
173 | foreach ( $mainSnaks as $snak ) { |
174 | if ( !( $snak instanceof PropertyValueSnak ) ) { |
175 | continue; |
176 | } |
177 | |
178 | $dataValue = $snak->getDataValue(); |
179 | if ( !( |
180 | $dataValue instanceof EntityIdValue && |
181 | $dataValue->getEntityId() instanceof ItemId |
182 | ) ) { |
183 | wfLogWarning( |
184 | $itemId->getSerialization() . ' has a PropertyValueSnak with ' . |
185 | $propertyId->getSerialization() . ' that has non-ItemId data.' |
186 | ); |
187 | |
188 | continue; |
189 | } |
190 | $itemId = $dataValue->getEntityId(); |
191 | '@phan-var ItemId $itemId'; |
192 | |
193 | try { |
194 | $item = $this->getItem( $itemId ); |
195 | } catch ( EntityLookupException $e ) { |
196 | continue; |
197 | } |
198 | if ( $item === null ) { |
199 | continue; |
200 | } |
201 | |
202 | try { |
203 | return $item->getSiteLink( 'commonswiki' )->getPageName(); |
204 | } catch ( OutOfBoundsException $e ) { |
205 | continue; |
206 | } |
207 | } |
208 | |
209 | return null; |
210 | } |
211 | |
212 | /** @throws EntityLookupException */ |
213 | private function getItem( ItemId $itemId ): ?Item { |
214 | return $this->entityLookup->getEntity( $itemId ); |
215 | } |
216 | |
217 | /** |
218 | * @param Snak[] $mainSnaks |
219 | * @param ItemId $itemId |
220 | * @param NumericPropertyId $propertyId |
221 | * |
222 | * @return string|null |
223 | */ |
224 | private function getStringValueFromMainSnaks( |
225 | array $mainSnaks, |
226 | ItemId $itemId, |
227 | NumericPropertyId $propertyId |
228 | ): ?string { |
229 | foreach ( $mainSnaks as $snak ) { |
230 | if ( !( $snak instanceof PropertyValueSnak ) ) { |
231 | continue; |
232 | } |
233 | |
234 | if ( !( $snak->getDataValue() instanceof StringValue ) ) { |
235 | wfLogWarning( |
236 | $itemId->getSerialization() . ' has a PropertyValueSnak with ' . |
237 | $propertyId->getSerialization() . ' that has non-StringValue data.' |
238 | ); |
239 | |
240 | continue; |
241 | } |
242 | |
243 | return $snak->getDataValue()->getValue(); |
244 | } |
245 | |
246 | return null; |
247 | } |
248 | } |