Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.33% covered (success)
93.33%
42 / 45
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
RefreshSecondaryDataUpdate
95.45% covered (success)
95.45%
42 / 44
75.00% covered (warning)
75.00%
3 / 4
11
0.00% covered (danger)
0.00%
0 / 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
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
7.12
 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 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace MediaWiki\Deferred;
10
11use Exception;
12use MediaWiki\Deferred\LinksUpdate\LinksUpdate;
13use MediaWiki\Exception\MWExceptionHandler;
14use MediaWiki\JobQueue\JobSpecification;
15use MediaWiki\Page\PageIdentity;
16use MediaWiki\Revision\RevisionRecord;
17use MediaWiki\Storage\DerivedPageDataUpdater;
18use MediaWiki\User\UserIdentity;
19use Wikimedia\Rdbms\ILBFactory;
20
21/**
22 * Update object handling the cleanup of secondary data after a page was edited.
23 *
24 * This makes it possible for DeferredUpdates to have retry logic using a
25 * single refreshLinks job if any of the bundled updates fail.
26 *
27 * @since 1.34
28 */
29class RefreshSecondaryDataUpdate extends DataUpdate
30    implements TransactionRoundAwareUpdate, EnqueueableDataUpdate
31{
32    /** @var ILBFactory */
33    private $lbFactory;
34    /** @var PageIdentity */
35    private $page;
36    /** @var DerivedPageDataUpdater */
37    private $updater;
38    /** @var bool */
39    private $recursive;
40    /** @var string|false TS::MW */
41    private $freshness;
42
43    /** @var RevisionRecord */
44    private $revisionRecord;
45    /** @var UserIdentity */
46    private $user;
47
48    /**
49     * @param ILBFactory $lbFactory
50     * @param UserIdentity $user
51     * @param PageIdentity $page Page we are updating
52     * @param RevisionRecord $revisionRecord
53     * @param DerivedPageDataUpdater $updater
54     * @param array $options Options map; supports "recursive" (bool) and "freshness" (string|false, TS::MW)
55     */
56    public function __construct(
57        ILBFactory $lbFactory,
58        UserIdentity $user,
59        PageIdentity $page,
60        RevisionRecord $revisionRecord,
61        DerivedPageDataUpdater $updater,
62        array $options
63    ) {
64        parent::__construct();
65
66        $this->lbFactory = $lbFactory;
67        $this->user = $user;
68        $this->page = $page;
69        $this->revisionRecord = $revisionRecord;
70        $this->updater = $updater;
71        $this->recursive = !empty( $options['recursive'] );
72        $this->freshness = $options['freshness'] ?? false;
73    }
74
75    /** @inheritDoc */
76    public function getTransactionRoundRequirement() {
77        return self::TRX_ROUND_ABSENT;
78    }
79
80    public function doUpdate() {
81        $updates = $this->updater->getSecondaryDataUpdates( $this->recursive );
82        foreach ( $updates as $update ) {
83            if ( $update instanceof LinksUpdate ) {
84                $update->setRevisionRecord( $this->revisionRecord );
85                $update->setTriggeringUser( $this->user );
86            }
87            if ( $update instanceof DataUpdate ) {
88                $update->setCause( $this->causeAction, $this->causeAgent );
89            }
90        }
91
92        // T221577, T248003: flush any transaction; each update needs outer transaction scope
93        // and the code above may have implicitly started one.
94        $this->lbFactory->commitPrimaryChanges( __METHOD__ );
95
96        $e = null;
97        foreach ( $updates as $update ) {
98            try {
99                DeferredUpdates::attemptUpdate( $update );
100            } catch ( Exception $e ) {
101                // Try as many updates as possible on the first pass
102                MWExceptionHandler::rollbackPrimaryChangesAndLog( $e );
103            }
104        }
105
106        if ( $e instanceof Exception ) {
107            throw $e; // trigger RefreshLinksJob enqueue via getAsJobSpecification()
108        }
109    }
110
111    /** @inheritDoc */
112    public function getAsJobSpecification() {
113        return [
114            'domain' => $this->lbFactory->getLocalDomainID(),
115            'job' => new JobSpecification(
116                'refreshLinksPrioritized',
117                [
118                    'namespace' => $this->page->getNamespace(),
119                    'title' => $this->page->getDBkey(),
120                    // Ensure fresh data are used, for normal data reuse the parser cache if it was saved
121                    'rootJobTimestamp' => $this->freshness ?: $this->revisionRecord->getTimestamp(),
122                    'useRecursiveLinksUpdate' => $this->recursive,
123                    'triggeringUser' => [
124                        'userId' => $this->user->getId(),
125                        'userName' => $this->user->getName()
126                    ],
127                    'triggeringRevisionId' => $this->revisionRecord->getId(),
128                    'causeAction' => $this->getCauseAction(),
129                    'causeAgent' => $this->getCauseAgent()
130                ],
131                [ 'removeDuplicates' => true ]
132            )
133        ];
134    }
135}
136/** @deprecated class alias since 1.42 */
137class_alias( RefreshSecondaryDataUpdate::class, 'RefreshSecondaryDataUpdate' );