Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
81.82% covered (warning)
81.82%
9 / 11
CRAP
83.33% covered (warning)
83.33%
70 / 84
WikibaseClientSiteLinksForItemHandler
0.00% covered (danger)
0.00%
0 / 1
81.82% covered (warning)
81.82%
9 / 11
44.69
83.33% covered (warning)
83.33%
70 / 84
 newFromGlobalState
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 __construct
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
5 / 5
 provideSiteLinks
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 doProvideSiteLinks
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 addSiteLink
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getCommonsSiteLink
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
16 / 16
 getLinkedItemSitelink
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
8 / 8
 getCommonsCategoryName
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
8 / 8
 getCommonsSitelinkFromMainSnaks
0.00% covered (danger)
0.00%
0 / 1
16.38
55.00% covered (warning)
55.00%
11 / 20
 getItem
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 getStringValueFromMainSnaks
0.00% covered (danger)
0.00%
0 / 1
8.12
50.00% covered (danger)
50.00%
5 / 10
<?php
declare( strict_types = 1 );
namespace WikimediaBadges;
use DataValues\StringValue;
use MediaWiki\MediaWikiServices;
use OutOfBoundsException;
use Wikibase\Client\Usage\UsageAccumulator;
use Wikibase\Client\WikibaseClient;
use Wikibase\DataModel\Entity\EntityIdValue;
use Wikibase\DataModel\Entity\Item;
use Wikibase\DataModel\Entity\ItemId;
use Wikibase\DataModel\Entity\NumericPropertyId;
use Wikibase\DataModel\Services\Lookup\EntityLookup;
use Wikibase\DataModel\Services\Lookup\EntityLookupException;
use Wikibase\DataModel\SiteLink;
use Wikibase\DataModel\Snak\PropertyValueSnak;
use Wikibase\DataModel\Snak\Snak;
/**
 * Handler for the WikibaseClientSiteLinksForItem hook that changes the link
 * to Wikimedia Commons with the one to the commons category.
 *
 * @since 0.1
 *
 * @license GPL-2.0-or-later
 * @author Marius Hoch < hoo@online.de >
 */
class WikibaseClientSiteLinksForItemHandler {
    /** @var EntityLookup */
    private $entityLookup;
    /** @var string|null */
    private $topicsMainCategoryProperty;
    /** @var string|null */
    private $categoryRelatedToListProperty;
    /**
     * @var string|null
     */
    private $commonsCategoryPropertySetting;
    private static function newFromGlobalState(): self {
        $services = MediaWikiServices::getInstance();
        $config = $services->getMainConfig();
        return new self(
            WikibaseClient::getEntityLookup( $services ),
            $config->get( 'WikimediaBadgesTopicsMainCategoryProperty' ),
            $config->get( 'WikimediaBadgesCategoryRelatedToListProperty' ),
            $config->get( 'WikimediaBadgesCommonsCategoryProperty' )
        );
    }
    public function __construct(
        EntityLookup $entityLookup,
        ?string $topicsMainCategoryProperty,
        ?string $categoryRelatedToListProperty,
        ?string $commonsCategoryPropertySetting
    ) {
        $this->entityLookup = $entityLookup;
        $this->topicsMainCategoryProperty = $topicsMainCategoryProperty;
        $this->categoryRelatedToListProperty = $categoryRelatedToListProperty;
        $this->commonsCategoryPropertySetting = $commonsCategoryPropertySetting;
    }
    /**
     * @param Item $item
     * @param SiteLink[] &$siteLinks
     * @param UsageAccumulator $usageAccumulator
     */
    public static function provideSiteLinks(
        Item $item, array &$siteLinks, UsageAccumulator $usageAccumulator
    ): void {
        $self = self::newFromGlobalState();
        $self->doProvideSiteLinks( $item, $siteLinks );
    }
    /**
     * @param Item $item
     * @param SiteLink[] &$siteLinks
     */
    public function doProvideSiteLinks( Item $item, array &$siteLinks ): void {
        $sitelink = $this->getCommonsSiteLink( $item );
        if ( $sitelink !== null ) {
            $this->addSiteLink( $sitelink, $siteLinks );
        }
    }
    /**
     * @param string $siteLink
     * @param SiteLink[] &$siteLinks
     */
    private function addSiteLink( string $siteLink, array &$siteLinks ): void {
        $siteLinks['commonswiki'] = new SiteLink( 'commonswiki', $siteLink );
    }
    private function getCommonsSiteLink( Item $item ): ?string {
        try {
            return $item->getSiteLink( 'commonswiki' )->getPageName();
        } catch ( OutOfBoundsException $e ) {
            // pass
        }
        $topicsMainCategorySitelink = $this->getLinkedItemSitelink(
            $item,
            $this->topicsMainCategoryProperty
        );
        if ( $topicsMainCategorySitelink !== null ) {
            return $topicsMainCategorySitelink;
        }
        $categoryRelatedToListSitelink = $this->getLinkedItemSitelink(
            $item,
            $this->categoryRelatedToListProperty
        );
        if ( $categoryRelatedToListSitelink !== null ) {
            return $categoryRelatedToListSitelink;
        }
        $categoryName = $this->getCommonsCategoryName( $item );
        if ( $categoryName !== null ) {
            return 'Category:' . $categoryName;
        }
        return null;
    }
    private function getLinkedItemSitelink( Item $item, ?string $propertyIdString ): ?string {
        if ( $propertyIdString === null ) {
            return null;
        }
        $propertyId = new NumericPropertyId( $propertyIdString );
        $statements = $item->getStatements()->getByPropertyId( $propertyId );
        $mainSnaks = $statements->getBestStatements()->getMainSnaks();
        return $this->getCommonsSitelinkFromMainSnaks(
            $mainSnaks,
            $item->getId(),
            $propertyId
        );
    }
    private function getCommonsCategoryName( Item $item ): ?string {
        if ( $this->commonsCategoryPropertySetting === null ) {
            return null;
        }
        $propertyId = new NumericPropertyId( $this->commonsCategoryPropertySetting );
        $statements = $item->getStatements()->getByPropertyId( $propertyId );
        $mainSnaks = $statements->getBestStatements()->getMainSnaks();
        return $this->getStringValueFromMainSnaks(
            $mainSnaks,
            $item->getId(),
            $propertyId
        );
    }
    private function getCommonsSitelinkFromMainSnaks(
        array $mainSnaks,
        ItemId $itemId,
        NumericPropertyId $propertyId
    ): ?string {
        foreach ( $mainSnaks as $snak ) {
            if ( !( $snak instanceof PropertyValueSnak ) ) {
                continue;
            }
            $dataValue = $snak->getDataValue();
            if ( !(
                $dataValue instanceof EntityIdValue &&
                $dataValue->getEntityId() instanceof ItemId
            ) ) {
                wfLogWarning(
                    $itemId->getSerialization() . ' has a PropertyValueSnak with ' .
                    $propertyId->getSerialization() . ' that has non-ItemId data.'
                );
                continue;
            }
            $itemId = $dataValue->getEntityId();
            '@phan-var ItemId $itemId';
            try {
                $item = $this->getItem( $itemId );
            } catch ( EntityLookupException $e ) {
                continue;
            }
            if ( $item === null ) {
                continue;
            }
            try {
                return $item->getSiteLink( 'commonswiki' )->getPageName();
            } catch ( OutOfBoundsException $e ) {
                continue;
            }
        }
        return null;
    }
    /** @throws EntityLookupException */
    private function getItem( ItemId $itemId ): ?Item {
        return $this->entityLookup->getEntity( $itemId );
    }
    /**
     * @param Snak[] $mainSnaks
     * @param ItemId $itemId
     * @param NumericPropertyId $propertyId
     *
     * @return string|null
     */
    private function getStringValueFromMainSnaks(
        array $mainSnaks,
        ItemId $itemId,
        NumericPropertyId $propertyId
    ): ?string {
        foreach ( $mainSnaks as $snak ) {
            if ( !( $snak instanceof PropertyValueSnak ) ) {
                continue;
            }
            if ( !( $snak->getDataValue() instanceof StringValue ) ) {
                wfLogWarning(
                    $itemId->getSerialization() . ' has a PropertyValueSnak with ' .
                        $propertyId->getSerialization() . ' that has non-StringValue data.'
                );
                continue;
            }
            return $snak->getDataValue()->getValue();
        }
        return null;
    }
}