]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/changes/CategoryMembershipChange.php
MediaWiki 1.30.2-scripts2
[autoinstallsdev/mediawiki.git] / includes / changes / CategoryMembershipChange.php
1 <?php
2 /**
3  * Helper class for category membership changes
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  * @author Kai Nissen
22  * @author Addshore
23  * @since 1.27
24  */
25
26 use Wikimedia\Assert\Assert;
27
28 class CategoryMembershipChange {
29
30         const CATEGORY_ADDITION = 1;
31         const CATEGORY_REMOVAL = -1;
32
33         /**
34          * @var string Current timestamp, set during CategoryMembershipChange::__construct()
35          */
36         private $timestamp;
37
38         /**
39          * @var Title Title instance of the categorized page
40          */
41         private $pageTitle;
42
43         /**
44          * @var Revision|null Latest Revision instance of the categorized page
45          */
46         private $revision;
47
48         /**
49          * @var int
50          * Number of pages this WikiPage is embedded by
51          * Set by CategoryMembershipChange::checkTemplateLinks()
52          */
53         private $numTemplateLinks = 0;
54
55         /**
56          * @var callable|null
57          */
58         private $newForCategorizationCallback = null;
59
60         /**
61          * @param Title $pageTitle Title instance of the categorized page
62          * @param Revision $revision Latest Revision instance of the categorized page
63          *
64          * @throws MWException
65          */
66         public function __construct( Title $pageTitle, Revision $revision = null ) {
67                 $this->pageTitle = $pageTitle;
68                 if ( $revision === null ) {
69                         $this->timestamp = wfTimestampNow();
70                 } else {
71                         $this->timestamp = $revision->getTimestamp();
72                 }
73                 $this->revision = $revision;
74                 $this->newForCategorizationCallback = [ 'RecentChange', 'newForCategorization' ];
75         }
76
77         /**
78          * Overrides the default new for categorization callback
79          * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
80          *
81          * @param callable $callback
82          * @see RecentChange::newForCategorization for callback signiture
83          *
84          * @throws MWException
85          */
86         public function overrideNewForCategorizationCallback( $callback ) {
87                 if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
88                         throw new MWException( 'Cannot override newForCategorization callback in operation.' );
89                 }
90                 Assert::parameterType( 'callable', $callback, '$callback' );
91                 $this->newForCategorizationCallback = $callback;
92         }
93
94         /**
95          * Determines the number of template links for recursive link updates
96          */
97         public function checkTemplateLinks() {
98                 $this->numTemplateLinks = $this->pageTitle->getBacklinkCache()->getNumLinks( 'templatelinks' );
99         }
100
101         /**
102          * Create a recentchanges entry for category additions
103          *
104          * @param Title $categoryTitle
105          */
106         public function triggerCategoryAddedNotification( Title $categoryTitle ) {
107                 $this->createRecentChangesEntry( $categoryTitle, self::CATEGORY_ADDITION );
108         }
109
110         /**
111          * Create a recentchanges entry for category removals
112          *
113          * @param Title $categoryTitle
114          */
115         public function triggerCategoryRemovedNotification( Title $categoryTitle ) {
116                 $this->createRecentChangesEntry( $categoryTitle, self::CATEGORY_REMOVAL );
117         }
118
119         /**
120          * Create a recentchanges entry using RecentChange::notifyCategorization()
121          *
122          * @param Title $categoryTitle
123          * @param int $type
124          */
125         private function createRecentChangesEntry( Title $categoryTitle, $type ) {
126                 $this->notifyCategorization(
127                         $this->timestamp,
128                         $categoryTitle,
129                         $this->getUser(),
130                         $this->getChangeMessageText(
131                                 $type,
132                                 $this->pageTitle->getPrefixedText(),
133                                 $this->numTemplateLinks
134                         ),
135                         $this->pageTitle,
136                         $this->getPreviousRevisionTimestamp(),
137                         $this->revision,
138                         $type === self::CATEGORY_ADDITION
139                 );
140         }
141
142         /**
143          * @param string $timestamp Timestamp of the recent change to occur in TS_MW format
144          * @param Title $categoryTitle Title of the category a page is being added to or removed from
145          * @param User $user User object of the user that made the change
146          * @param string $comment Change summary
147          * @param Title $pageTitle Title of the page that is being added or removed
148          * @param string $lastTimestamp Parent revision timestamp of this change in TS_MW format
149          * @param Revision|null $revision
150          * @param bool $added true, if the category was added, false for removed
151          *
152          * @throws MWException
153          */
154         private function notifyCategorization(
155                 $timestamp,
156                 Title $categoryTitle,
157                 User $user = null,
158                 $comment,
159                 Title $pageTitle,
160                 $lastTimestamp,
161                 $revision,
162                 $added
163         ) {
164                 $deleted = $revision ? $revision->getVisibility() & Revision::SUPPRESSED_USER : 0;
165                 $newRevId = $revision ? $revision->getId() : 0;
166
167                 /**
168                  * T109700 - Default bot flag to true when there is no corresponding RC entry
169                  * This means all changes caused by parser functions & Lua on reparse are marked as bot
170                  * Also in the case no RC entry could be found due to replica DB lag
171                  */
172                 $bot = 1;
173                 $lastRevId = 0;
174                 $ip = '';
175
176                 # If no revision is given, the change was probably triggered by parser functions
177                 if ( $revision !== null ) {
178                         $correspondingRc = $this->revision->getRecentChange();
179                         if ( $correspondingRc === null ) {
180                                 $correspondingRc = $this->revision->getRecentChange( Revision::READ_LATEST );
181                         }
182                         if ( $correspondingRc !== null ) {
183                                 $bot = $correspondingRc->getAttribute( 'rc_bot' ) ?: 0;
184                                 $ip = $correspondingRc->getAttribute( 'rc_ip' ) ?: '';
185                                 $lastRevId = $correspondingRc->getAttribute( 'rc_last_oldid' ) ?: 0;
186                         }
187                 }
188
189                 /** @var RecentChange $rc */
190                 $rc = call_user_func_array(
191                         $this->newForCategorizationCallback,
192                         [
193                                 $timestamp,
194                                 $categoryTitle,
195                                 $user,
196                                 $comment,
197                                 $pageTitle,
198                                 $lastRevId,
199                                 $newRevId,
200                                 $lastTimestamp,
201                                 $bot,
202                                 $ip,
203                                 $deleted,
204                                 $added
205                         ]
206                 );
207                 $rc->save();
208         }
209
210         /**
211          * Get the user associated with this change.
212          *
213          * If there is no revision associated with the change and thus no editing user
214          * fallback to a default.
215          *
216          * False will be returned if the user name specified in the
217          * 'autochange-username' message is invalid.
218          *
219          * @return User|bool
220          */
221         private function getUser() {
222                 if ( $this->revision ) {
223                         $userId = $this->revision->getUser( Revision::RAW );
224                         if ( $userId === 0 ) {
225                                 return User::newFromName( $this->revision->getUserText( Revision::RAW ), false );
226                         } else {
227                                 return User::newFromId( $userId );
228                         }
229                 }
230
231                 $username = wfMessage( 'autochange-username' )->inContentLanguage()->text();
232                 $user = User::newFromName( $username );
233                 # User::newFromName() can return false on a badly configured wiki.
234                 if ( $user && !$user->isLoggedIn() ) {
235                         $user->addToDatabase();
236                 }
237
238                 return $user;
239         }
240
241         /**
242          * Returns the change message according to the type of category membership change
243          *
244          * The message keys created in this method may be one of:
245          * - recentchanges-page-added-to-category
246          * - recentchanges-page-added-to-category-bundled
247          * - recentchanges-page-removed-from-category
248          * - recentchanges-page-removed-from-category-bundled
249          *
250          * @param int $type may be CategoryMembershipChange::CATEGORY_ADDITION
251          * or CategoryMembershipChange::CATEGORY_REMOVAL
252          * @param string $prefixedText result of Title::->getPrefixedText()
253          * @param int $numTemplateLinks
254          *
255          * @return string
256          */
257         private function getChangeMessageText( $type, $prefixedText, $numTemplateLinks ) {
258                 $array = [
259                         self::CATEGORY_ADDITION => 'recentchanges-page-added-to-category',
260                         self::CATEGORY_REMOVAL => 'recentchanges-page-removed-from-category',
261                 ];
262
263                 $msgKey = $array[$type];
264
265                 if ( intval( $numTemplateLinks ) > 0 ) {
266                         $msgKey .= '-bundled';
267                 }
268
269                 return wfMessage( $msgKey, $prefixedText )->inContentLanguage()->text();
270         }
271
272         /**
273          * Returns the timestamp of the page's previous revision or null if the latest revision
274          * does not refer to a parent revision
275          *
276          * @return null|string
277          */
278         private function getPreviousRevisionTimestamp() {
279                 $previousRev = Revision::newFromId(
280                                 $this->pageTitle->getPreviousRevisionID( $this->pageTitle->getLatestRevID() )
281                         );
282
283                 return $previousRev ? $previousRev->getTimestamp() : null;
284         }
285
286 }