Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
63 / 63
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
MainSnakContext
100.00% covered (success)
100.00%
63 / 63
100.00% covered (success)
100.00%
10 / 10
27
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSnakRank
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSnakStatement
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSnakGroup
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
4
 getBestStatementsPerPropertyId
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 getStatementsWithSameQualifiersForProperties
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 haveSameQualifiers
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
7
 getSnaksWithPropertyId
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getCursor
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare( strict_types = 1 );
4
5namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Context;
6
7use LogicException;
8use Wikibase\DataModel\Entity\PropertyId;
9use Wikibase\DataModel\Entity\StatementListProvidingEntity;
10use Wikibase\DataModel\Snak\Snak;
11use Wikibase\DataModel\Snak\SnakList;
12use Wikibase\DataModel\Statement\Statement;
13use Wikibase\DataModel\Statement\StatementList;
14
15/**
16 * A constraint check context for the main snak of a statement.
17 *
18 * @license GPL-2.0-or-later
19 */
20class MainSnakContext extends AbstractContext {
21
22    private Statement $statement;
23
24    public function __construct( StatementListProvidingEntity $entity, Statement $statement ) {
25        parent::__construct( $entity, $statement->getMainSnak() );
26
27        $this->statement = $statement;
28    }
29
30    public function getType(): string {
31        return self::TYPE_STATEMENT;
32    }
33
34    public function getSnakRank(): ?int {
35        return $this->statement->getRank();
36    }
37
38    public function getSnakStatement(): Statement {
39        return $this->statement;
40    }
41
42    public function getSnakGroup( string $groupingMode, array $separators = [] ): array {
43        /** @var StatementList $statements */
44        $statements = $this->entity->getStatements();
45        switch ( $groupingMode ) {
46            case Context::GROUP_NON_DEPRECATED:
47                $statements = $statements->getByRank( [
48                    Statement::RANK_NORMAL,
49                    Statement::RANK_PREFERRED,
50                ] );
51                break;
52            case Context::GROUP_BEST_RANK:
53                $statements = $this->getBestStatementsPerPropertyId( $statements );
54                break;
55            default:
56                throw new LogicException( 'Unknown $groupingMode ' . $groupingMode );
57        }
58        return $this->getStatementsWithSameQualifiersForProperties(
59            $this->statement,
60            $statements,
61            $separators
62        )->getMainSnaks();
63    }
64
65    private function getBestStatementsPerPropertyId( StatementList $statements ): StatementList {
66        $allBestStatements = new StatementList();
67        foreach ( $statements->getPropertyIds() as $propertyId ) {
68            $bestStatements = $statements->getByPropertyId( $propertyId )
69                ->getBestStatements();
70            foreach ( $bestStatements as $bestStatement ) {
71                $allBestStatements->addStatement( $bestStatement );
72            }
73        }
74        return $allBestStatements;
75    }
76
77    /**
78     * Returns the statements of a statement list
79     * which for a set of propert IDs have the same qualifiers as a certain statement.
80     * “unknown value” qualifiers are considered different from each other.
81     *
82     * @param Statement $currentStatement
83     * @param StatementList $allStatements
84     * @param PropertyId[] $qualifierPropertyIds
85     * @return StatementList
86     */
87    private function getStatementsWithSameQualifiersForProperties(
88        Statement $currentStatement,
89        StatementList $allStatements,
90        array $qualifierPropertyIds
91    ): StatementList {
92        $similarStatements = new StatementList();
93        foreach ( $allStatements as $statement ) {
94            if ( $statement === $currentStatement ) {
95                // if the statement has an “unknown value” qualifier,
96                // it might be considered different from itself,
97                // so add it explicitly to ensure it’s always included
98                $similarStatements->addStatement( $statement );
99                continue;
100            }
101            foreach ( $qualifierPropertyIds as $qualifierPropertyId ) {
102                if ( !$this->haveSameQualifiers( $currentStatement, $statement, $qualifierPropertyId ) ) {
103                    continue 2;
104                }
105            }
106            $similarStatements->addStatement( $statement );
107        }
108        return $similarStatements;
109    }
110
111    /**
112     * Tests whether two statements have the same qualifiers with a certain property ID.
113     * “unknown value” qualifiers are considered different from each other.
114     */
115    private function haveSameQualifiers( Statement $s1, Statement $s2, PropertyId $propertyId ): bool {
116        $q1 = $this->getSnaksWithPropertyId( $s1->getQualifiers(), $propertyId );
117        $q2 = $this->getSnaksWithPropertyId( $s2->getQualifiers(), $propertyId );
118
119        if ( $q1->count() !== $q2->count() ) {
120            return false;
121        }
122
123        foreach ( $q1 as $qualifier ) {
124            switch ( $qualifier->getType() ) {
125                case 'value':
126                case 'novalue':
127                    if ( !$q2->hasSnak( $qualifier ) ) {
128                        return false;
129                    }
130                    break;
131                case 'somevalue':
132                    return false; // all “unknown value”s are considered different from each other
133            }
134        }
135
136        // a SnakList cannot contain the same snak more than once,
137        // so if every snak of q1 is also in q2 and their cardinality is identical,
138        // then they must be entirely identical
139        return true;
140    }
141
142    /**
143     * Returns the snaks of the given snak list with the specified property ID.
144     */
145    private function getSnaksWithPropertyId( SnakList $allSnaks, PropertyId $propertyId ): SnakList {
146        $snaks = new SnakList();
147        /** @var Snak $snak */
148        foreach ( $allSnaks as $snak ) {
149            if ( $snak->getPropertyId()->equals( $propertyId ) ) {
150                $snaks->addSnak( $snak );
151            }
152        }
153        return $snaks;
154    }
155
156    public function getCursor(): ContextCursor {
157        return new MainSnakContextCursor(
158            $this->entity->getId()->getSerialization(),
159            $this->statement->getPropertyId()->getSerialization(),
160            $this->getStatementGuid( $this->statement ),
161            $this->statement->getMainSnak()->getHash()
162        );
163    }
164
165}