doxygen: Fix trailing star in class member descriptions
[lhc/web/wiklou.git] / includes / MovePage.php
1 <?php
2
3 /**
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * http://www.gnu.org/copyleft/gpl.html
18 *
19 * @file
20 */
21
22 /**
23 * Handles the backend logic of moving a page from one title
24 * to another.
25 *
26 * @since 1.24
27 */
28 class MovePage {
29
30 /**
31 * @var Title
32 */
33 protected $oldTitle;
34
35 /**
36 * @var Title
37 */
38 protected $newTitle;
39
40 public function __construct( Title $oldTitle, Title $newTitle ) {
41 $this->oldTitle = $oldTitle;
42 $this->newTitle = $newTitle;
43 }
44
45 /**
46 * @param User $user
47 * @param string $reason
48 * @param bool $createRedirect
49 * @return Status
50 */
51 public function move( User $user, $reason, $createRedirect ) {
52 global $wgCategoryCollation;
53
54 // If it is a file, move it first.
55 // It is done before all other moving stuff is done because it's hard to revert.
56 $dbw = wfGetDB( DB_MASTER );
57 if ( $this->oldTitle->getNamespace() == NS_FILE ) {
58 $file = wfLocalFile( $this->oldTitle );
59 if ( $file->exists() ) {
60 $status = $file->move( $this->newTitle );
61 if ( !$status->isOk() ) {
62 return $status;
63 }
64 }
65 // Clear RepoGroup process cache
66 RepoGroup::singleton()->clearCache( $this->oldTitle );
67 RepoGroup::singleton()->clearCache( $this->newTitle ); # clear false negative cache
68 }
69
70 $dbw->begin( __METHOD__ ); # If $file was a LocalFile, its transaction would have closed our own.
71 $pageid = $this->oldTitle->getArticleID( Title::GAID_FOR_UPDATE );
72 $protected = $this->oldTitle->isProtected();
73
74 // Do the actual move
75 $this->moveToInternal( $user, $this->newTitle, $reason, $createRedirect );
76
77 // Refresh the sortkey for this row. Be careful to avoid resetting
78 // cl_timestamp, which may disturb time-based lists on some sites.
79 // @todo This block should be killed, it's duplicating code
80 // from LinksUpdate::getCategoryInsertions() and friends.
81 $prefixes = $dbw->select(
82 'categorylinks',
83 array( 'cl_sortkey_prefix', 'cl_to' ),
84 array( 'cl_from' => $pageid ),
85 __METHOD__
86 );
87 if ( $this->newTitle->getNamespace() == NS_CATEGORY ) {
88 $type = 'subcat';
89 } elseif ( $this->newTitle->getNamespace() == NS_FILE ) {
90 $type = 'file';
91 } else {
92 $type = 'page';
93 }
94 foreach ( $prefixes as $prefixRow ) {
95 $prefix = $prefixRow->cl_sortkey_prefix;
96 $catTo = $prefixRow->cl_to;
97 $dbw->update( 'categorylinks',
98 array(
99 'cl_sortkey' => Collation::singleton()->getSortKey(
100 $this->newTitle->getCategorySortkey( $prefix ) ),
101 'cl_collation' => $wgCategoryCollation,
102 'cl_type' => $type,
103 'cl_timestamp=cl_timestamp' ),
104 array(
105 'cl_from' => $pageid,
106 'cl_to' => $catTo ),
107 __METHOD__
108 );
109 }
110
111 $redirid = $this->oldTitle->getArticleID();
112
113 if ( $protected ) {
114 # Protect the redirect title as the title used to be...
115 $dbw->insertSelect( 'page_restrictions', 'page_restrictions',
116 array(
117 'pr_page' => $redirid,
118 'pr_type' => 'pr_type',
119 'pr_level' => 'pr_level',
120 'pr_cascade' => 'pr_cascade',
121 'pr_user' => 'pr_user',
122 'pr_expiry' => 'pr_expiry'
123 ),
124 array( 'pr_page' => $pageid ),
125 __METHOD__,
126 array( 'IGNORE' )
127 );
128 # Update the protection log
129 $log = new LogPage( 'protect' );
130 $comment = wfMessage(
131 'prot_1movedto2',
132 $this->oldTitle->getPrefixedText(),
133 $this->newTitle->getPrefixedText()
134 )->inContentLanguage()->text();
135 if ( $reason ) {
136 $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
137 }
138 // @todo FIXME: $params?
139 $logId = $log->addEntry(
140 'move_prot',
141 $this->newTitle,
142 $comment,
143 array( $this->oldTitle->getPrefixedText() ),
144 $user
145 );
146
147 // reread inserted pr_ids for log relation
148 $insertedPrIds = $dbw->select(
149 'page_restrictions',
150 'pr_id',
151 array( 'pr_page' => $redirid ),
152 __METHOD__
153 );
154 $logRelationsValues = array();
155 foreach ( $insertedPrIds as $prid ) {
156 $logRelationsValues[] = $prid->pr_id;
157 }
158 $log->addRelations( 'pr_id', $logRelationsValues, $logId );
159 }
160
161 // Update *_from_namespace fields as needed
162 if ( $this->oldTitle->getNamespace() != $this->newTitle->getNamespace() ) {
163 $dbw->update( 'pagelinks',
164 array( 'pl_from_namespace' => $this->newTitle->getNamespace() ),
165 array( 'pl_from' => $pageid ),
166 __METHOD__
167 );
168 $dbw->update( 'templatelinks',
169 array( 'tl_from_namespace' => $this->newTitle->getNamespace() ),
170 array( 'tl_from' => $pageid ),
171 __METHOD__
172 );
173 $dbw->update( 'imagelinks',
174 array( 'il_from_namespace' => $this->newTitle->getNamespace() ),
175 array( 'il_from' => $pageid ),
176 __METHOD__
177 );
178 }
179
180 # Update watchlists
181 $oldtitle = $this->oldTitle->getDBkey();
182 $newtitle = $this->newTitle->getDBkey();
183 $oldsnamespace = MWNamespace::getSubject( $this->oldTitle->getNamespace() );
184 $newsnamespace = MWNamespace::getSubject( $this->newTitle->getNamespace() );
185 if ( $oldsnamespace != $newsnamespace || $oldtitle != $newtitle ) {
186 WatchedItem::duplicateEntries( $this->oldTitle, $this->newTitle );
187 }
188
189 $dbw->commit( __METHOD__ );
190
191 wfRunHooks( 'TitleMoveComplete', array( &$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason ) );
192 return Status::newGood();
193
194 }
195
196 /**
197 * Move page to a title which is either a redirect to the
198 * source page or nonexistent
199 *
200 * @fixme This was basically directly moved from Title, it should be split into smaller functions
201 * @param User $user the User doing the move
202 * @param Title $nt The page to move to, which should be a redirect or nonexistent
203 * @param string $reason The reason for the move
204 * @param bool $createRedirect Whether to leave a redirect at the old title. Does not check
205 * if the user has the suppressredirect right
206 * @throws MWException
207 */
208 private function moveToInternal( User $user, &$nt, $reason = '', $createRedirect = true ) {
209 global $wgContLang;
210
211 if ( $nt->exists() ) {
212 $moveOverRedirect = true;
213 $logType = 'move_redir';
214 } else {
215 $moveOverRedirect = false;
216 $logType = 'move';
217 }
218
219 if ( $createRedirect ) {
220 if ( $this->oldTitle->getNamespace() == NS_CATEGORY
221 && !wfMessage( 'category-move-redirect-override' )->inContentLanguage()->isDisabled()
222 ) {
223 $redirectContent = new WikitextContent(
224 wfMessage( 'category-move-redirect-override' )
225 ->params( $nt->getPrefixedText() )->inContentLanguage()->plain() );
226 } else {
227 $contentHandler = ContentHandler::getForTitle( $this->oldTitle );
228 $redirectContent = $contentHandler->makeRedirectContent( $nt,
229 wfMessage( 'move-redirect-text' )->inContentLanguage()->plain() );
230 }
231
232 // NOTE: If this page's content model does not support redirects, $redirectContent will be null.
233 } else {
234 $redirectContent = null;
235 }
236
237 // bug 57084: log_page should be the ID of the *moved* page
238 $oldid = $this->oldTitle->getArticleID();
239 $logTitle = clone $this->oldTitle;
240
241 $logEntry = new ManualLogEntry( 'move', $logType );
242 $logEntry->setPerformer( $user );
243 $logEntry->setTarget( $logTitle );
244 $logEntry->setComment( $reason );
245 $logEntry->setParameters( array(
246 '4::target' => $nt->getPrefixedText(),
247 '5::noredir' => $redirectContent ? '0': '1',
248 ) );
249
250 $formatter = LogFormatter::newFromEntry( $logEntry );
251 $formatter->setContext( RequestContext::newExtraneousContext( $this->oldTitle ) );
252 $comment = $formatter->getPlainActionText();
253 if ( $reason ) {
254 $comment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
255 }
256 # Truncate for whole multibyte characters.
257 $comment = $wgContLang->truncate( $comment, 255 );
258
259 $dbw = wfGetDB( DB_MASTER );
260
261 $newpage = WikiPage::factory( $nt );
262
263 if ( $moveOverRedirect ) {
264 $newid = $nt->getArticleID();
265 $newcontent = $newpage->getContent();
266
267 # Delete the old redirect. We don't save it to history since
268 # by definition if we've got here it's rather uninteresting.
269 # We have to remove it so that the next step doesn't trigger
270 # a conflict on the unique namespace+title index...
271 $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ );
272
273 $newpage->doDeleteUpdates( $newid, $newcontent );
274 }
275
276 # Save a null revision in the page's history notifying of the move
277 $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true, $user );
278 if ( !is_object( $nullRevision ) ) {
279 throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
280 }
281
282 $nullRevision->insertOn( $dbw );
283
284 # Change the name of the target page:
285 $dbw->update( 'page',
286 /* SET */ array(
287 'page_namespace' => $nt->getNamespace(),
288 'page_title' => $nt->getDBkey(),
289 ),
290 /* WHERE */ array( 'page_id' => $oldid ),
291 __METHOD__
292 );
293
294 // clean up the old title before reset article id - bug 45348
295 if ( !$redirectContent ) {
296 WikiPage::onArticleDelete( $this->oldTitle );
297 }
298
299 $this->oldTitle->resetArticleID( 0 ); // 0 == non existing
300 $nt->resetArticleID( $oldid );
301 $newpage->loadPageData( WikiPage::READ_LOCKING ); // bug 46397
302
303 $newpage->updateRevisionOn( $dbw, $nullRevision );
304
305 wfRunHooks( 'NewRevisionFromEditComplete',
306 array( $newpage, $nullRevision, $nullRevision->getParentId(), $user ) );
307
308 $newpage->doEditUpdates( $nullRevision, $user, array( 'changed' => false ) );
309
310 if ( !$moveOverRedirect ) {
311 WikiPage::onArticleCreate( $nt );
312 }
313
314 # Recreate the redirect, this time in the other direction.
315 if ( $redirectContent ) {
316 $redirectArticle = WikiPage::factory( $this->oldTitle );
317 $redirectArticle->loadFromRow( false, WikiPage::READ_LOCKING ); // bug 46397
318 $newid = $redirectArticle->insertOn( $dbw );
319 if ( $newid ) { // sanity
320 $this->oldTitle->resetArticleID( $newid );
321 $redirectRevision = new Revision( array(
322 'title' => $this->oldTitle, // for determining the default content model
323 'page' => $newid,
324 'user_text' => $user->getName(),
325 'user' => $user->getId(),
326 'comment' => $comment,
327 'content' => $redirectContent ) );
328 $redirectRevision->insertOn( $dbw );
329 $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
330
331 wfRunHooks( 'NewRevisionFromEditComplete',
332 array( $redirectArticle, $redirectRevision, false, $user ) );
333
334 $redirectArticle->doEditUpdates( $redirectRevision, $user, array( 'created' => true ) );
335 }
336 }
337
338 # Log the move
339 $logid = $logEntry->insert();
340 $logEntry->publish( $logid );
341 }
342
343 }