Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
80.65% covered (warning)
80.65%
50 / 62
88.24% covered (warning)
88.24%
15 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
PagePropsTable
80.65% covered (warning)
80.65%
50 / 62
88.24% covered (warning)
88.24%
15 / 17
42.38
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 setParserOutput
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTableName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFromField
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExistingFields
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getNewLinkIDs
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getExistingProps
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getExistingLinkIDs
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 isExisting
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 isInNewSet
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 encodeValue
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
3.58
 insertLink
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getPropertySortKeyValue
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
4
 deleteLink
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 finishUpdate
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 invalidateProperties
28.57% covered (danger)
28.57%
4 / 14
0.00% covered (danger)
0.00%
0 / 1
19.12
 getAssocArray
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace MediaWiki\Deferred\LinksUpdate;
4
5use MediaWiki\Config\ServiceOptions;
6use MediaWiki\JobQueue\JobQueueGroup;
7use MediaWiki\JobQueue\Jobs\HTMLCacheUpdateJob;
8use MediaWiki\MainConfigNames;
9use MediaWiki\Parser\ParserOutput;
10
11/**
12 * page_props
13 *
14 * Link ID format: string[]
15 *   0: Property name (pp_propname)
16 *   1: Property value (pp_value)
17 *
18 * @since 1.38
19 */
20class PagePropsTable extends LinksTable {
21    /** @var JobQueueGroup */
22    private $jobQueueGroup;
23
24    /** @var array */
25    private $newProps = [];
26
27    /** @var array|null */
28    private $existingProps;
29
30    /**
31     * The configured PagePropLinkInvalidations. An associative array where the
32     * key is the property name and the value is a string or array of strings
33     * giving the link table names which will be used for backlink cache
34     * invalidation.
35     *
36     * @var array
37     */
38    private $linkInvalidations;
39
40    public const CONSTRUCTOR_OPTIONS = [ MainConfigNames::PagePropLinkInvalidations ];
41
42    public function __construct(
43        ServiceOptions $options,
44        JobQueueGroup $jobQueueGroup
45    ) {
46        $this->jobQueueGroup = $jobQueueGroup;
47        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
48        $this->linkInvalidations = $options->get( MainConfigNames::PagePropLinkInvalidations );
49    }
50
51    public function setParserOutput( ParserOutput $parserOutput ) {
52        $this->newProps = $parserOutput->getPageProperties();
53    }
54
55    /** @inheritDoc */
56    protected function getTableName() {
57        return 'page_props';
58    }
59
60    /** @inheritDoc */
61    protected function getFromField() {
62        return 'pp_page';
63    }
64
65    /** @inheritDoc */
66    protected function getExistingFields() {
67        return [ 'pp_propname', 'pp_value' ];
68    }
69
70    /** @inheritDoc */
71    protected function getNewLinkIDs() {
72        foreach ( $this->newProps as $name => $value ) {
73            yield [ (string)$name, $value ];
74        }
75    }
76
77    /**
78     * Get the existing page_props as an associative array
79     *
80     * @return array
81     */
82    private function getExistingProps() {
83        if ( $this->existingProps === null ) {
84            $this->existingProps = [];
85            foreach ( $this->fetchExistingRows() as $row ) {
86                $this->existingProps[$row->pp_propname] = $row->pp_value;
87            }
88        }
89        return $this->existingProps;
90    }
91
92    /** @inheritDoc */
93    protected function getExistingLinkIDs() {
94        foreach ( $this->getExistingProps() as $name => $value ) {
95            yield [ (string)$name, $value ];
96        }
97    }
98
99    /** @inheritDoc */
100    protected function isExisting( $linkId ) {
101        $existing = $this->getExistingProps();
102        [ $name, $value ] = $linkId;
103        return \array_key_exists( $name, $existing )
104            && $this->encodeValue( $existing[$name] ) === $this->encodeValue( $value );
105    }
106
107    /** @inheritDoc */
108    protected function isInNewSet( $linkId ) {
109        [ $name, $value ] = $linkId;
110        return \array_key_exists( $name, $this->newProps )
111            && $this->encodeValue( $this->newProps[$name] ) === $this->encodeValue( $value );
112    }
113
114    /**
115     * @param mixed $value
116     */
117    private function encodeValue( $value ): string {
118        if ( is_bool( $value ) ) {
119            return (string)(int)$value;
120        } elseif ( $value === null ) {
121            return '';
122        } else {
123            return (string)$value;
124        }
125    }
126
127    /** @inheritDoc */
128    protected function insertLink( $linkId ) {
129        [ $name, $value ] = $linkId;
130        $this->insertRow( [
131            'pp_propname' => $name,
132            'pp_value' => $this->encodeValue( $value ),
133            'pp_sortkey' => $this->getPropertySortKeyValue( $value )
134        ] );
135    }
136
137    /**
138     * Determines the sort key for the given property value.
139     * This will return $value if it is a float or int,
140     * 1 or resp. 0 if it is a bool, and null otherwise.
141     *
142     * @note In the future, we may allow the sortkey to be specified explicitly
143     *       in ParserOutput::setPageProperty (T357783).
144     *
145     * @param mixed $value
146     *
147     * @return float|null
148     */
149    private function getPropertySortKeyValue( $value ) {
150        if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) {
151            return floatval( $value );
152        }
153
154        return null;
155    }
156
157    /** @inheritDoc */
158    protected function deleteLink( $linkId ) {
159        $this->deleteRow( [
160            'pp_propname' => $linkId[0]
161        ] );
162    }
163
164    protected function finishUpdate() {
165        $changed = array_unique( array_merge(
166            array_column( $this->insertedLinks, 0 ),
167            array_column( $this->deletedLinks, 0 ) ) );
168        $this->invalidateProperties( $changed );
169    }
170
171    /**
172     * Invalidate the properties given the list of changed property names
173     *
174     * @param string[] $changed
175     */
176    private function invalidateProperties( array $changed ) {
177        $jobs = [];
178        foreach ( $changed as $name ) {
179            if ( isset( $this->linkInvalidations[$name] ) ) {
180                $inv = $this->linkInvalidations[$name];
181                if ( !is_array( $inv ) ) {
182                    $inv = [ $inv ];
183                }
184                foreach ( $inv as $table ) {
185                    $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
186                        $this->getSourcePage(),
187                        $table,
188                        [ 'causeAction' => 'page-props' ]
189                    );
190                }
191            }
192        }
193
194        if ( $jobs ) {
195            $this->jobQueueGroup->lazyPush( $jobs );
196        }
197    }
198
199    /**
200     * Get the properties for a given link set as an associative array
201     *
202     * @param int $setType The set type as in LinksTable::getLinkIDs()
203     * @return array
204     */
205    public function getAssocArray( $setType ) {
206        $props = [];
207        foreach ( $this->getLinkIDs( $setType ) as [ $name, $value ] ) {
208            $props[$name] = $value;
209        }
210        return $props;
211    }
212}