Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
44 / 44
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
RefreshSecondaryDataUpdate
100.00% covered (success)
100.00%
44 / 44
100.00% covered (success)
100.00%
4 / 4
11
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 getTransactionRoundRequirement
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doUpdate
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
7
 getAsJobSpecification
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * Updater for secondary data after a page edit.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23use MediaWiki\Deferred\DataUpdate;
24use MediaWiki\Deferred\DeferredUpdates;
25use MediaWiki\Deferred\EnqueueableDataUpdate;
26use MediaWiki\Deferred\LinksUpdate\LinksUpdate;
27use MediaWiki\Deferred\TransactionRoundAwareUpdate;
28use MediaWiki\Revision\RevisionRecord;
29use MediaWiki\Storage\DerivedPageDataUpdater;
30use MediaWiki\User\UserIdentity;
31use Wikimedia\Rdbms\ILBFactory;
32
33/**
34 * Update object handling the cleanup of secondary data after a page was edited.
35 *
36 * This makes it possible for DeferredUpdates to have retry logic using a
37 * single refreshLinks job if any of the bundled updates fail.
38 *
39 * @since 1.34
40 */
41class RefreshSecondaryDataUpdate extends DataUpdate
42    implements TransactionRoundAwareUpdate, EnqueueableDataUpdate
43{
44    /** @var ILBFactory */
45    private $lbFactory;
46    /** @var WikiPage */
47    private $page;
48    /** @var DerivedPageDataUpdater */
49    private $updater;
50    /** @var bool */
51    private $recursive;
52    /** @var string|false TS_MW */
53    private $freshness;
54
55    /** @var RevisionRecord */
56    private $revisionRecord;
57    /** @var UserIdentity */
58    private $user;
59
60    /**
61     * @param ILBFactory $lbFactory
62     * @param UserIdentity $user
63     * @param WikiPage $page Page we are updating
64     * @param RevisionRecord $revisionRecord
65     * @param DerivedPageDataUpdater $updater
66     * @param array $options Options map; supports "recursive" (bool) and "freshness" (string|false, TS_MW)
67     */
68    public function __construct(
69        ILBFactory $lbFactory,
70        UserIdentity $user,
71        WikiPage $page,
72        RevisionRecord $revisionRecord,
73        DerivedPageDataUpdater $updater,
74        array $options
75    ) {
76        parent::__construct();
77
78        $this->lbFactory = $lbFactory;
79        $this->user = $user;
80        $this->page = $page;
81        $this->revisionRecord = $revisionRecord;
82        $this->updater = $updater;
83        $this->recursive = !empty( $options['recursive'] );
84        $this->freshness = $options['freshness'] ?? false;
85    }
86
87    public function getTransactionRoundRequirement() {
88        return self::TRX_ROUND_ABSENT;
89    }
90
91    public function doUpdate() {
92        $updates = $this->updater->getSecondaryDataUpdates( $this->recursive );
93        foreach ( $updates as $update ) {
94            if ( $update instanceof LinksUpdate ) {
95                $update->setRevisionRecord( $this->revisionRecord );
96                $update->setTriggeringUser( $this->user );
97            }
98            if ( $update instanceof DataUpdate ) {
99                $update->setCause( $this->causeAction, $this->causeAgent );
100            }
101        }
102
103        // T221577, T248003: flush any transaction; each update needs outer transaction scope
104        // and the code above may have implicitly started one.
105        $this->lbFactory->commitPrimaryChanges( __METHOD__ );
106
107        $e = null;
108        foreach ( $updates as $update ) {
109            try {
110                DeferredUpdates::attemptUpdate( $update );
111            } catch ( Exception $e ) {
112                // Try as many updates as possible on the first pass
113                MWExceptionHandler::rollbackPrimaryChangesAndLog( $e );
114            }
115        }
116
117        if ( $e instanceof Exception ) {
118            throw $e; // trigger RefreshLinksJob enqueue via getAsJobSpecification()
119        }
120    }
121
122    public function getAsJobSpecification() {
123        return [
124            'domain' => $this->lbFactory->getLocalDomainID(),
125            'job' => new JobSpecification(
126                'refreshLinksPrioritized',
127                [
128                    'namespace' => $this->page->getTitle()->getNamespace(),
129                    'title' => $this->page->getTitle()->getDBkey(),
130                    // Ensure fresh data are used, for normal data reuse the parser cache if it was saved
131                    'rootJobTimestamp' => $this->freshness ?: $this->revisionRecord->getTimestamp(),
132                    'useRecursiveLinksUpdate' => $this->recursive,
133                    'triggeringUser' => [
134                        'userId' => $this->user->getId(),
135                        'userName' => $this->user->getName()
136                    ],
137                    'triggeringRevisionId' => $this->revisionRecord->getId(),
138                    'causeAction' => $this->getCauseAction(),
139                    'causeAgent' => $this->getCauseAgent()
140                ],
141                [ 'removeDuplicates' => true ]
142            )
143        ];
144    }
145}