Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
OpenAlex
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 5
420
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 run
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 getDeQIdMap
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 processRow
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 1
110
 getNumericPropertyId
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace MediaWiki\Extension\MathSearch\Graph\Job;
4
5use DataValues\MonolingualTextValue;
6use DataValues\StringValue;
7use MediaWiki\Extension\MathSearch\Graph\Query;
8use Throwable;
9use Wikibase\DataModel\Entity\Item;
10use Wikibase\DataModel\Entity\ItemId;
11use Wikibase\DataModel\Entity\NumericPropertyId;
12use Wikibase\DataModel\Services\Lookup\EntityLookup;
13use Wikibase\DataModel\Services\Statement\GuidGenerator;
14use Wikibase\DataModel\Snak\PropertyValueSnak;
15use Wikibase\Lib\Store\EntityStore;
16use Wikibase\Repo\WikibaseRepo;
17
18class OpenAlex extends GraphJob {
19    private EntityStore $entityStore;
20    private EntityLookup $entityLookup;
21    private GuidGenerator $guidGenerator;
22
23    private array $propertyIds = [];
24
25    public function __construct( $params ) {
26        parent::__construct( 'OpenAlex', $params );
27        $this->entityStore = WikibaseRepo::getEntityStore();
28        $this->entityLookup = WikibaseRepo::getEntityLookup();
29        $this->guidGenerator = new GuidGenerator();
30    }
31
32    public function run(): bool {
33        $qIdMap = $this->getDeQIdMap();
34        foreach ( $this->params['rows'] as $de => $row ) {
35            try {
36                if ( !isset( $qIdMap[(string)$de] ) ) {
37                    self::getLog()->error( "No Qid found for Zbl $de." );
38                    continue;
39                }
40                $this->processRow( $de, $qIdMap[$de], $row );
41            } catch ( Throwable $ex ) {
42                self::getLog()->error( "Skip processing Zbl $de", [ 'error' => $ex ] );
43            }
44        }
45
46        return true;
47    }
48
49    private function getDeQIdMap(): array {
50        $des = '"' . implode( '" "', array_keys( $this->params['rows'] ) ) . '"';
51        $query = Query::getQidFromDe( $des );
52        $rs = Query::getResults( $query );
53        $qIdMap = [];
54        foreach ( $rs as $row ) {
55            $de = $row['de'];
56            if ( isset( $qIdMap[$de] ) ) {
57                self::getLog()->error( "Multiple Qid found for Zbl $de." );
58                unset( $this->params['rows'][$de] );
59                continue;
60            }
61            $qIdMap[$de] = $row['qid'];
62        }
63
64        return $qIdMap;
65    }
66
67    private function processRow( $de, $qid, $row ) {
68        global $wgMathOpenAlexQIdMap;
69        $pDe = $this->getNumericPropertyId( $wgMathOpenAlexQIdMap['document'] );
70        self::getLog()->info( "Add OpenAlex data for Zbl $de to $qid." );
71        $item = $this->entityLookup->getEntity( ItemId::newFromNumber( $qid ) );
72        if ( !$item instanceof Item ) {
73            self::getLog()->error( "Item Q$qid not found." );
74            return;
75        }
76        $statements = $item->getStatements();
77        $profileTypeStatements = $statements->getByPropertyId( $pDe )->getMainSnaks();
78        if ( count( $profileTypeStatements ) !== 1 ) {
79            self::getLog()->error( "No or multiple statements found for Zbl $de." );
80            return;
81        }
82        $mainSnak = $profileTypeStatements[0];
83        if ( !$mainSnak->getDataValue()->getValue() == $de ) {
84            self::getLog()->error( "Wrong statement found for Zbl $de." );
85            return;
86        }
87        $changed = false;
88        foreach ( $row as $key => $value ) {
89            $propertyId = $this->getNumericPropertyId( $key );
90            $profileTypeStatements = $statements->getByPropertyId( $propertyId );
91            if ( !$profileTypeStatements->isEmpty() ) {
92                self::getLog()->debug( "$key of Zbl $de already set." );
93                continue;
94            }
95            if ( $key === $wgMathOpenAlexQIdMap['openalex_title'] && $item->getLabels()->isEmpty() ) {
96                $item->setLabel( 'en', $value );
97            }
98
99            $changed = true;
100
101            $mainSnak = new PropertyValueSnak(
102                $propertyId,
103                $key === $wgMathOpenAlexQIdMap['openalex_title'] ?
104                    new MonolingualTextValue( 'en', $value )
105                    : new StringValue( $value
106                ) );
107            $statements->addNewStatement( $mainSnak, [], null,
108                $this->guidGenerator->newGuid( $item->getId() ) );
109        }
110        if ( $changed === false ) {
111            self::getLog()->info( "Skip Zbl $de  (no change)." );
112            return;
113        }
114        $item->setStatements( $statements );
115        $this->entityStore->saveEntity( $item, "Set OpenAlex properties.", $this->getUser(),
116            EDIT_FORCE_BOT );
117    }
118
119    public function getNumericPropertyId( string $key ): NumericPropertyId {
120        if ( !isset( $this->propertyIds[$key] ) ) {
121            $this->propertyIds[$key] = new NumericPropertyId( $key );
122        }
123        return $this->propertyIds[$key];
124    }
125}