Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.87% covered (warning)
83.87%
52 / 62
94.12% covered (success)
94.12%
16 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
PagePropsTable
83.87% covered (warning)
83.87%
52 / 62
94.12% covered (success)
94.12%
16 / 17
38.85
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
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 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 HTMLCacheUpdateJob;
6use JobQueueGroup;
7use MediaWiki\Config\ServiceOptions;
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    protected function getTableName() {
56        return 'page_props';
57    }
58
59    protected function getFromField() {
60        return 'pp_page';
61    }
62
63    protected function getExistingFields() {
64        return [ 'pp_propname', 'pp_value' ];
65    }
66
67    protected function getNewLinkIDs() {
68        foreach ( $this->newProps as $name => $value ) {
69            yield [ (string)$name, $value ];
70        }
71    }
72
73    /**
74     * Get the existing page_props as an associative array
75     *
76     * @return array
77     */
78    private function getExistingProps() {
79        if ( $this->existingProps === null ) {
80            $this->existingProps = [];
81            foreach ( $this->fetchExistingRows() as $row ) {
82                $this->existingProps[$row->pp_propname] = $row->pp_value;
83            }
84        }
85        return $this->existingProps;
86    }
87
88    protected function getExistingLinkIDs() {
89        foreach ( $this->getExistingProps() as $name => $value ) {
90            yield [ (string)$name, $value ];
91        }
92    }
93
94    protected function isExisting( $linkId ) {
95        $existing = $this->getExistingProps();
96        [ $name, $value ] = $linkId;
97        return \array_key_exists( $name, $existing )
98            && $this->encodeValue( $existing[$name] ) === $this->encodeValue( $value );
99    }
100
101    protected function isInNewSet( $linkId ) {
102        [ $name, $value ] = $linkId;
103        return \array_key_exists( $name, $this->newProps )
104            && $this->encodeValue( $this->newProps[$name] ) === $this->encodeValue( $value );
105    }
106
107    private function encodeValue( $value ) {
108        if ( is_bool( $value ) ) {
109            return (string)(int)$value;
110        } elseif ( $value === null ) {
111            return '';
112        } else {
113            return (string)$value;
114        }
115    }
116
117    protected function insertLink( $linkId ) {
118        [ $name, $value ] = $linkId;
119        $this->insertRow( [
120            'pp_propname' => $name,
121            'pp_value' => $this->encodeValue( $value ),
122            'pp_sortkey' => $this->getPropertySortKeyValue( $value )
123        ] );
124    }
125
126    /**
127     * Determines the sort key for the given property value.
128     * This will return $value if it is a float or int,
129     * 1 or resp. 0 if it is a bool, and null otherwise.
130     *
131     * @note In the future, we may allow the sortkey to be specified explicitly
132     *       in ParserOutput::setPageProperty (T357783).
133     *
134     * @param mixed $value
135     *
136     * @return float|null
137     */
138    private function getPropertySortKeyValue( $value ) {
139        if ( is_int( $value ) || is_float( $value ) || is_bool( $value ) ) {
140            return floatval( $value );
141        }
142
143        return null;
144    }
145
146    protected function deleteLink( $linkId ) {
147        $this->deleteRow( [
148            'pp_propname' => $linkId[0]
149        ] );
150    }
151
152    protected function finishUpdate() {
153        $changed = array_unique( array_merge(
154            array_column( $this->insertedLinks, 0 ),
155            array_column( $this->deletedLinks, 0 ) ) );
156        $this->invalidateProperties( $changed );
157    }
158
159    /**
160     * Invalidate the properties given the list of changed property names
161     *
162     * @param string[] $changed
163     */
164    private function invalidateProperties( array $changed ) {
165        $jobs = [];
166        foreach ( $changed as $name ) {
167            if ( isset( $this->linkInvalidations[$name] ) ) {
168                $inv = $this->linkInvalidations[$name];
169                if ( !is_array( $inv ) ) {
170                    $inv = [ $inv ];
171                }
172                foreach ( $inv as $table ) {
173                    $jobs[] = HTMLCacheUpdateJob::newForBacklinks(
174                        $this->getSourcePage(),
175                        $table,
176                        [ 'causeAction' => 'page-props' ]
177                    );
178                }
179            }
180        }
181
182        if ( $jobs ) {
183            $this->jobQueueGroup->lazyPush( $jobs );
184        }
185    }
186
187    /**
188     * Get the properties for a given link set as an associative array
189     *
190     * @param int $setType The set type as in LinksTable::getLinkIDs()
191     * @return array
192     */
193    public function getAssocArray( $setType ) {
194        $props = [];
195        foreach ( $this->getLinkIDs( $setType ) as [ $name, $value ] ) {
196            $props[$name] = $value;
197        }
198        return $props;
199    }
200}