Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
82.61% covered (warning)
82.61%
19 / 23
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
WikidataTemplateLookup
82.61% covered (warning)
82.61%
19 / 23
80.00% covered (warning)
80.00%
4 / 5
11.64
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 fetchNowCommonsLocalTitle
20.00% covered (danger)
20.00%
1 / 5
0.00% covered (danger)
0.00%
0 / 1
4.05
 fetchLocalTemplateForSource
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 fetchSiteLinkPageName
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 removeNamespace
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace FileImporter\Services;
4
5use FileImporter\Data\SourceUrl;
6use FileImporter\Remote\MediaWiki\SiteTableSiteLookup;
7use FileImporter\Services\Http\HttpRequestExecutor;
8use MediaWiki\Config\Config;
9use Psr\Log\LoggerInterface;
10use RuntimeException;
11
12/**
13 * FIXME: Rename the class to something like WikibaseSiteLinkLookup, and just remove all occurences
14 * of "NowCommons" as well as "Template" from this code. Reasoning: Even if this code was written
15 * for a very specific purpose (the NowCommons template), this class does not contain any knowledge
16 * about templates, and no knowledge about a specific Wikibase installation.
17 *
18 * This service fetches an Item from a Wikibase instance (both specified via configuration), and
19 * returns the sitelink that matches a given source URL, if such a sitelink exists. In other words:
20 * It checks if the source site contains a localized version of a page, and returns it.
21 *
22 * @license GPL-2.0-or-later
23 */
24class WikidataTemplateLookup {
25
26    /** @var string */
27    private $entityEndpoint;
28    /** @var string|null */
29    private $nowCommonsEntityId;
30
31    /** @var string[][] Array mapping site id and entity id to a template title name */
32    private array $templateCache = [];
33
34    public function __construct(
35        Config $config,
36        private readonly SiteTableSiteLookup $siteLookup,
37        private readonly HttpRequestExecutor $requestExecutor,
38        private readonly LoggerInterface $logger,
39    ) {
40        $this->entityEndpoint = $config->get( 'FileImporterWikidataEntityEndpoint' );
41        $this->nowCommonsEntityId = $config->get( 'FileImporterWikidataNowCommonsEntity' ) ?: null;
42    }
43
44    /**
45     * Fetch the source wiki template title corresponding to `NowCommons`
46     *
47     * @param SourceUrl $sourceUrl Source URL, used to look up source site ID.
48     *
49     * @return string|null Local template title, without namespace prefix.
50     */
51    public function fetchNowCommonsLocalTitle( SourceUrl $sourceUrl ): ?string {
52        try {
53            return $this->fetchLocalTemplateForSource( $this->nowCommonsEntityId, $sourceUrl );
54        } catch ( RuntimeException $ex ) {
55            $this->logger->error( 'Failed to fetch template mapping from Wikidata: ' .
56                $ex->getMessage() );
57            return null;
58        }
59    }
60
61    private function fetchLocalTemplateForSource( ?string $entityId, SourceUrl $sourceUrl ): ?string {
62        $sourceSite = $this->siteLookup->getSite( $sourceUrl );
63        if ( !$sourceSite || !$entityId ) {
64            return null;
65        }
66
67        $localPageName = $this->fetchSiteLinkPageName( $entityId, $sourceSite->getGlobalId() );
68        if ( $localPageName === null ) {
69            return null;
70        }
71
72        return $this->removeNamespace( $localPageName );
73    }
74
75    private function fetchSiteLinkPageName( string $entityId, string $siteId ): ?string {
76        if ( isset( $this->templateCache[$siteId][$entityId] ) ) {
77            return $this->templateCache[$siteId][$entityId];
78        }
79
80        $url = $this->entityEndpoint . $entityId;
81        $response = $this->requestExecutor->execute( $url );
82        $entityData = json_decode( $response->getContent(), true );
83        $this->templateCache[$siteId][$entityId] =
84            $entityData['entities'][$entityId]['sitelinks'][$siteId]['title'] ?? null;
85
86        return $this->templateCache[$siteId][$entityId];
87    }
88
89    /**
90     * FIXME: copied from WikitextConversions, should use Title methods instead.
91     */
92    private function removeNamespace( string $title ): string {
93        return array_last( explode( ':', $title, 2 ) );
94    }
95
96}