Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
2.56% |
2 / 78 |
|
22.22% |
2 / 9 |
CRAP | |
0.00% |
0 / 1 |
Field | |
2.56% |
2 / 78 |
|
22.22% |
2 / 9 |
651.32 | |
0.00% |
0 / 1 |
getLog | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
__construct | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
isKnown | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getContent | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSnaks | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
110 | |||
setIfDefined | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
propagateFieldInfo | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
42 | |||
isExcludedFromWb | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getExternalIdType | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\MathSearch\StackExchange; |
4 | |
5 | use InvalidArgumentException; |
6 | use MediaWiki\Logger\LoggerFactory; |
7 | use Wikibase\DataModel\Entity\NumericPropertyId; |
8 | use Wikibase\DataModel\Snak\PropertyValueSnak; |
9 | use Wikibase\Repo\WikibaseRepo; |
10 | |
11 | class Field { |
12 | |
13 | private $seName; |
14 | private $propertyId = null; |
15 | private $references = false; |
16 | private $known = false; |
17 | private $excludeFromWb = null; |
18 | private $content; |
19 | private $normFileName; |
20 | private $externalIdType = null; |
21 | |
22 | // TODO: read this from a config file |
23 | private const FIELD_MAP = [ |
24 | 'posts' => [ |
25 | 'Id' => [ |
26 | 'propertyId' => 'P5', |
27 | 'externalIdType' => 1, |
28 | ], |
29 | 'PostTypeId' => [ |
30 | 'propertyId' => 'P9', |
31 | 'map' => [ |
32 | '1' => '887', |
33 | '2' => '888', |
34 | ], |
35 | ], |
36 | 'ParentId' => [ |
37 | 'propertyId' => 'P16', |
38 | 'references' => 'posts.Id', |
39 | ], |
40 | 'Score' => [ |
41 | 'propertyId' => 'P6', |
42 | ], |
43 | 'ViewCount' => [ |
44 | 'propertyId' => 'P17', |
45 | ], |
46 | 'AnswerCount' => [ |
47 | 'propertyId' => 'P18', |
48 | ], |
49 | 'CommentCount' => [ |
50 | 'propertyId' => 'P19', |
51 | ], |
52 | 'OwnerUserId' => [ |
53 | 'propertyId' => 'P20', |
54 | 'references' => 'users.Id', |
55 | ], |
56 | 'AcceptedAnswerId' => [ |
57 | 'propertyId' => 'P14', |
58 | 'references' => 'posts.Id', |
59 | ], |
60 | 'CreationDate' => [ |
61 | 'propertyId' => 'P21', |
62 | ], |
63 | 'LastActivityDate' => [ |
64 | 'propertyId' => 'P22', |
65 | |
66 | ], |
67 | 'OwnerDisplayName' => [ |
68 | 'propertyId' => 'P23', |
69 | ], |
70 | 'Body' => [ |
71 | 'excludeFromWb' => true, |
72 | ], |
73 | 'Title' => [ |
74 | 'excludeFromWb' => true, |
75 | ], |
76 | 'Tags' => [ |
77 | 'separator' => '/(&[lg]t;|<|>|,)/', |
78 | 'propertyId' => 'P10', |
79 | ], |
80 | ], |
81 | 'users' => [ |
82 | 'Id' => [ |
83 | 'propertyId' => 'P7', |
84 | 'externalIdType' => 2, |
85 | ], |
86 | 'Reputation' => [ |
87 | 'propertyId' => 'P24', |
88 | ], |
89 | 'CreationDate' => [ |
90 | 'propertyId' => 'P20', |
91 | ], |
92 | 'LastAccessDate' => [ |
93 | 'propertyId' => 'P21', |
94 | ], |
95 | 'DisplayName' => [ |
96 | 'propertyId' => 'P23', |
97 | ], |
98 | 'Views' => [ |
99 | 'propertyId' => 'P17', |
100 | ], |
101 | 'UpVotes' => [ |
102 | 'propertyId' => 'P25', |
103 | ], |
104 | 'DownVotes' => [ |
105 | 'propertyId' => 'P26', |
106 | ], |
107 | 'AccountId' => [ |
108 | 'propertyId' => 'P27', |
109 | ], |
110 | 'AboutMe' => [ |
111 | 'excludeFromWb' => true, |
112 | ], |
113 | 'WebsiteUrl' => [ |
114 | 'propertyId' => 'P28', |
115 | ], |
116 | 'Location' => [ |
117 | 'excludeFromWb' => true, |
118 | ], |
119 | ], |
120 | ]; |
121 | |
122 | private static function getLog() { |
123 | return LoggerFactory::getInstance( 'MathSearch' ); |
124 | } |
125 | |
126 | /** |
127 | * @param string $seName |
128 | * @param string $content |
129 | * @param string $normFileName |
130 | */ |
131 | public function __construct( $seName, $content, $normFileName ) { |
132 | $this->seName = $seName; |
133 | $this->content = $content; |
134 | $this->normFileName = $normFileName; |
135 | // some posts file from arq20 math task were modified with additional version |
136 | // information by appending either .V1.0 or _V1_0 |
137 | $fileparts = preg_split( "/[\._]/", $normFileName ); |
138 | $normalized_fn = strtolower( $fileparts[0] ); |
139 | self::getLog()->debug( "'$normFileName' is normalized to '$normalized_fn'." ); |
140 | if ( array_key_exists( $normalized_fn, self::FIELD_MAP ) ) { |
141 | if ( array_key_exists( $seName, self::FIELD_MAP[$normalized_fn] ) ) { |
142 | $this->propagateFieldInfo( self::FIELD_MAP[$normalized_fn][$seName] ); |
143 | } else { |
144 | self::getLog()->warning( "Field {$seName} unknown in {$normalized_fn}-file." ); |
145 | } |
146 | } else { |
147 | self::getLog()->warning( "Unsupported file name, {$normFileName}." ); |
148 | } |
149 | } |
150 | |
151 | /** |
152 | * @return bool |
153 | */ |
154 | public function isKnown(): bool { |
155 | return $this->known; |
156 | } |
157 | |
158 | /** |
159 | * @return int|string |
160 | */ |
161 | public function getContent() { |
162 | return $this->content; |
163 | } |
164 | |
165 | /** |
166 | * @return PropertyValueSnak |
167 | */ |
168 | public function getSnaks() { |
169 | $sf = WikibaseRepo::getSnakFactory(); |
170 | $propertyId = new NumericPropertyId( $this->propertyId ); |
171 | $type = WikibaseRepo::getPropertyDataTypeLookup()->getDataTypeIdForProperty( $propertyId ); |
172 | $content = $this->content; |
173 | if ( !is_array( $content ) ) { |
174 | $content = [ $content ]; |
175 | } |
176 | $result = []; |
177 | foreach ( $content as $c ) { |
178 | switch ( $type ) { |
179 | case 'wikibase-item': |
180 | $c = [ 'entity-type' => 'item', 'numeric-id' => (int)$c ]; |
181 | break; |
182 | case 'quantity': |
183 | $c = [ 'unit' => '1', 'amount' => $c ]; |
184 | break; |
185 | case 'time': |
186 | $c = [ |
187 | 'time' => '+' . substr( $c, 0, -4 ) . 'Z', |
188 | 'timezone' => 0, |
189 | 'before' => 0, |
190 | 'after' => 0, |
191 | 'precision' => 11, |
192 | 'calendarmodel' => 'http://www.wikidata.org/entity/Q1985727', |
193 | ]; |
194 | break; |
195 | case 'string': |
196 | case 'external-id': |
197 | break; |
198 | default: |
199 | self::getLog()->warning( "Unexpected type" . $type ); |
200 | } |
201 | try { |
202 | $result[] = $sf->newSnak( $propertyId, 'value', $c ); |
203 | } catch ( InvalidArgumentException $e ) { |
204 | $c_ser = var_export( $c, false ); |
205 | self::getLog() |
206 | ->error( "Cannot create value '$c_ser' for property {$this->propertyId}", |
207 | [ $e ] ); |
208 | } |
209 | } |
210 | |
211 | return $result; |
212 | } |
213 | |
214 | private function setIfDefined( $key, $field ) { |
215 | if ( array_key_exists( $key, $field ) ) { |
216 | $this->$key = $field[$key]; |
217 | |
218 | return true; |
219 | } else { |
220 | return false; |
221 | } |
222 | } |
223 | |
224 | private function propagateFieldInfo( $fieldInfo ) { |
225 | $hasKey = static function ( $key ) use ( $fieldInfo ) { |
226 | return array_key_exists( $key, $fieldInfo ); |
227 | }; |
228 | $this->known = true; |
229 | $isExcluded = $this->setIfDefined( 'excludeFromWb', $fieldInfo ); |
230 | if ( $isExcluded ) { |
231 | // assert( count( $fieldInfo ) === 1, |
232 | // 'key-map cannot contain excludeFromWb and other fields.' ); |
233 | |
234 | return; |
235 | } |
236 | $hasProperty = $this->setIfDefined( 'propertyId', $fieldInfo ); |
237 | // assert( $hasProperty === true, "key-map for {$this->seName} must contain property" ); |
238 | $isReference = $this->setIfDefined( 'references', $fieldInfo ); |
239 | if ( $isReference ) { |
240 | // assert( count( $fieldInfo ) === 2, |
241 | // 'references can only specify propertyID and referenced field.' ); |
242 | $ref_path = explode( '.', $fieldInfo['references'] ); |
243 | $externalIdType = self::FIELD_MAP[$ref_path[0]][$ref_path[1]]['externalIdType']; |
244 | $this->content = IdMap::getInstance()->addQid( $this->content, $externalIdType ); |
245 | |
246 | return; |
247 | } |
248 | if ( $hasKey( 'map' ) ) { |
249 | // assert( array_key_exists( $this->content, $fieldInfo['map'] ), |
250 | // "key-map {$this->seName} does not contain value {$this->content}" ); |
251 | $this->content = $fieldInfo['map'][$this->content]; |
252 | |
253 | return; |
254 | } |
255 | if ( $hasKey( 'separator' ) ) { |
256 | $this->content = |
257 | preg_split( $fieldInfo['separator'], $this->content, -1, PREG_SPLIT_NO_EMPTY ); |
258 | } |
259 | $this->setIfDefined( 'externalIdType', $fieldInfo ); |
260 | |
261 | if ( $hasKey( 'external_id_type' ) ) { |
262 | $this->externalIdType = $fieldInfo['external_id_type']; |
263 | } |
264 | } |
265 | |
266 | /** |
267 | * @return null |
268 | */ |
269 | public function isExcludedFromWb() { |
270 | return $this->excludeFromWb; |
271 | } |
272 | |
273 | /** |
274 | * @return null |
275 | */ |
276 | public function getExternalIdType() { |
277 | return $this->externalIdType; |
278 | } |
279 | |
280 | } |