Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 94
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
Job
0.00% covered (danger)
0.00%
0 / 94
0.00% covered (danger)
0.00%
0 / 2
552
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 run
0.00% covered (danger)
0.00%
0 / 88
0.00% covered (danger)
0.00%
0 / 1
506
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * https://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 * @author Yaron Koren
20 * @author Ankit Garg
21 */
22namespace MediaWiki\Extension\ReplaceText;
23
24use Job as JobParent;
25use MediaWiki\CommentStore\CommentStoreComment;
26use MediaWiki\Content\ContentHandler;
27use MediaWiki\Content\TextContent;
28use MediaWiki\Context\RequestContext;
29use MediaWiki\Page\MovePageFactory;
30use MediaWiki\Page\WikiPageFactory;
31use MediaWiki\Permissions\PermissionManager;
32use MediaWiki\Title\Title;
33use MediaWiki\User\UserFactory;
34use MediaWiki\Watchlist\WatchlistManager;
35use RecentChange;
36use Wikimedia\ScopedCallback;
37
38/**
39 * Background job to replace text in a given page
40 * - based on /includes/RefreshLinksJob.php
41 */
42class Job extends JobParent {
43    private MovePageFactory $movePageFactory;
44    private PermissionManager $permissionManager;
45    private UserFactory $userFactory;
46    private WatchlistManager $watchlistManager;
47    private WikiPageFactory $wikiPageFactory;
48
49    /**
50     * Constructor.
51     * @param Title $title
52     * @param array|bool $params Cannot be === true
53     * @param MovePageFactory $movePageFactory
54     * @param PermissionManager $permissionManager
55     * @param UserFactory $userFactory
56     * @param WatchlistManager $watchlistManager
57     * @param WikiPageFactory $wikiPageFactory
58     */
59    function __construct( $title, $params,
60        MovePageFactory $movePageFactory,
61        PermissionManager $permissionManager,
62        UserFactory $userFactory,
63        WatchlistManager $watchlistManager,
64        WikiPageFactory $wikiPageFactory
65    ) {
66        parent::__construct( 'replaceText', $title, $params );
67        $this->movePageFactory = $movePageFactory;
68        $this->permissionManager = $permissionManager;
69        $this->userFactory = $userFactory;
70        $this->watchlistManager = $watchlistManager;
71        $this->wikiPageFactory = $wikiPageFactory;
72    }
73
74    /**
75     * Run a replaceText job
76     * @return bool success
77     */
78    function run() {
79        // T279090
80        $current_user = $this->userFactory->newFromId( $this->params['user_id'] );
81        if ( !$current_user->isRegistered() ) {
82            $this->error = 'replacetext: the user ID ' . $this->params['user_id'] .
83                ' does not belong to a registered user.';
84            return false;
85        }
86        if ( !$this->permissionManager->userCan(
87            'replacetext', $current_user, $this->title
88        ) ) {
89            $this->error = 'replacetext: permission no longer valid';
90            // T279090#6978214
91            return true;
92        }
93
94        if ( isset( $this->params['session'] ) ) {
95            $callback = RequestContext::importScopedSession( $this->params['session'] );
96            $this->addTeardownCallback( static function () use ( &$callback ) {
97                ScopedCallback::consume( $callback );
98            } );
99        }
100
101        if ( $this->title === null ) {
102            $this->error = 'replaceText: Invalid title';
103            return false;
104        }
105
106        if ( array_key_exists( 'move_page', $this->params ) ) {
107            $new_title = Search::getReplacedTitle(
108                $this->title,
109                $this->params['target_str'],
110                $this->params['replacement_str'],
111                $this->params['use_regex']
112            );
113
114            if ( $new_title === null ) {
115                $this->error = 'replaceText: Invalid new title - ' . $this->params['replacement_str'];
116                return false;
117            }
118
119            $reason = $this->params['edit_summary'];
120            $create_redirect = $this->params['create_redirect'];
121            $mvPage = $this->movePageFactory->newMovePage( $this->title, $new_title );
122            $mvStatus = $mvPage->move( $current_user, $reason, $create_redirect );
123            if ( !$mvStatus->isOK() ) {
124                $this->error = 'replaceText: error while moving: ' . $this->title->getPrefixedDBkey() .
125                    '. Errors: ' . $mvStatus->getWikiText();
126                return false;
127            }
128
129            if ( $this->params['watch_page'] ) {
130                $this->watchlistManager->addWatch( $current_user, $new_title );
131            }
132        } else {
133            $wikiPage = $this->wikiPageFactory->newFromTitle( $this->title );
134            $latestRevision = $wikiPage->getRevisionRecord();
135
136            if ( $latestRevision === null ) {
137                $this->error =
138                    'replaceText: No revision found for wiki page at "' . $this->title->getPrefixedDBkey() . '".';
139                return false;
140            }
141
142            if ( isset( $this->params['roles'] ) ) {
143                $slotRoles = $this->params['roles'];
144            } else {
145                $slotRoles = $latestRevision->getSlotRoles();
146            }
147
148            $revisionSlots = $latestRevision->getSlots();
149            $updater = $wikiPage->newPageUpdater( $current_user );
150            $hasMatches = false;
151
152            foreach ( $slotRoles as $role ) {
153                if ( !$revisionSlots->hasSlot( $role ) ) {
154                    $this->error =
155                        'replaceText: Slot "' . $role .
156                        '" does not exist for wiki page "' . $this->title->getPrefixedDBkey() . '".';
157                    return false;
158                }
159
160                $slotContent = $revisionSlots->getContent( $role );
161
162                if ( !( $slotContent instanceof TextContent ) ) {
163                    // Sanity check: Does the slot actually contain TextContent?
164                    $this->error =
165                        'replaceText: Slot "' . $role .
166                        '" does not hold regular wikitext for wiki page "' . $this->title->getPrefixedDBkey() . '".';
167                    return false;
168                }
169
170                $slot_text = $slotContent->getText();
171
172                $target_str = $this->params['target_str'];
173                $replacement_str = $this->params['replacement_str'];
174                $num_matches = 0;
175
176                if ( $this->params['use_regex'] ) {
177                    $new_text =
178                        preg_replace( '/' . $target_str . '/Uu', $replacement_str, $slot_text, -1, $num_matches );
179                } else {
180                    $new_text = str_replace( $target_str, $replacement_str, $slot_text, $num_matches );
181                }
182
183                // If there's at least one replacement, modify the slot.
184                if ( $num_matches > 0 ) {
185                    $hasMatches = true;
186                    $updater->setContent(
187                        $role,
188                        ContentHandler::makeContent( $new_text, $this->title, $slotContent->getModel() )
189                    );
190                }
191            }
192
193            // If at least one slot is edited, modify the page,
194            // using the passed-in edit summary.
195            if ( $hasMatches ) {
196                $edit_summary = CommentStoreComment::newUnsavedComment( $this->params['edit_summary'] );
197                $flags = EDIT_MINOR;
198                if ( $this->permissionManager->userHasRight( $current_user, 'bot' ) ) {
199                    $flags |= EDIT_FORCE_BOT;
200                }
201                if ( isset( $this->params['botEdit'] ) && $this->params['botEdit'] ) {
202                    $flags |= EDIT_FORCE_BOT;
203                }
204                if ( $this->permissionManager->userHasRight( $current_user, 'patrol' ) ||
205                    $this->permissionManager->userHasRight( $current_user, 'autopatrol' ) ) {
206                    $updater->setRcPatrolStatus( RecentChange::PRC_PATROLLED );
207                }
208                $updater->saveRevision( $edit_summary, $flags );
209            }
210        }
211        return true;
212    }
213}