Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
2.56% covered (danger)
2.56%
2 / 78
22.22% covered (danger)
22.22%
2 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Field
2.56% covered (danger)
2.56%
2 / 78
22.22% covered (danger)
22.22%
2 / 9
651.32
0.00% covered (danger)
0.00%
0 / 1
 getLog
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __construct
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 isKnown
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSnaks
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
110
 setIfDefined
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 propagateFieldInfo
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
42
 isExcludedFromWb
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getExternalIdType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\MathSearch\StackExchange;
4
5use InvalidArgumentException;
6use MediaWiki\Logger\LoggerFactory;
7use Wikibase\DataModel\Entity\NumericPropertyId;
8use Wikibase\DataModel\Snak\PropertyValueSnak;
9use Wikibase\Repo\WikibaseRepo;
10
11class 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}