* Added a script to prune old file cache entries.
[lhc/web/wiklou.git] / includes / EditPage.php
index e979c61..d1c8ec5 100644 (file)
@@ -15,6 +15,9 @@
  * redirects go to, etc. $this->mTitle (as well as $mArticle) is the
  * page in the database that is actually being edited. These are
  * usually the same, but they are now allowed to be different.
+ *
+ * Surgeon General's Warning: prolonged exposure to this class is known to cause
+ * headaches, which may be fatal.
  */
 class EditPage {
        const AS_SUCCESS_UPDATE            = 200;
@@ -225,11 +228,13 @@ class EditPage {
                                        $undotext = $this->mArticle->getUndoText( $undorev, $oldrev );
                                        if ( $undotext === false ) {
                                                # Warn the user that something went wrong
-                                               $this->editFormPageTop .= $wgOut->parse( '<div class="error mw-undo-failure">' . wfMsgNoTrans( 'undo-failure' ) . '</div>' );
+                                               $this->editFormPageTop .= $wgOut->parse( '<div class="error mw-undo-failure">' .
+                                                       wfMsgNoTrans( 'undo-failure' ) . '</div>', true, /* interface */true );
                                        } else {
                                                $text = $undotext;
                                                # Inform the user of our success and set an automatic edit summary
-                                               $this->editFormPageTop .= $wgOut->parse( '<div class="mw-undo-success">' . wfMsgNoTrans( 'undo-success' ) . '</div>' );
+                                               $this->editFormPageTop .= $wgOut->parse( '<div class="mw-undo-success">' .
+                                                       wfMsgNoTrans( 'undo-success' ) . '</div>', true, /* interface */true );
                                                $firstrev = $oldrev->getNext();
                                                # If we just undid one rev, use an autosummary
                                                if ( $firstrev->mId == $undo ) {
@@ -242,9 +247,10 @@ class EditPage {
                                        // Failed basic sanity checks.
                                        // Older revisions may have been removed since the link
                                        // was created, or we may simply have got bogus input.
-                                       $this->editFormPageTop .= $wgOut->parse( '<div class="error mw-undo-norev">' . wfMsgNoTrans( 'undo-norev' ) . '</div>' );
+                                       $this->editFormPageTop .= $wgOut->parse( '<div class="error mw-undo-norev">' .
+                                               wfMsgNoTrans( 'undo-norev' ) . '</div>', true, /* interface */true );
                                }
-                       } else if ( $section != '' ) {
+                       } elseif ( $section != '' ) {
                                if ( $section == 'new' ) {
                                        $text = $this->getPreloadedText( $preload );
                                } else {
@@ -374,9 +380,6 @@ class EditPage {
                wfProfileIn( __METHOD__ );
                wfDebug( __METHOD__.": enter\n" );
 
-               // This is not an article
-               $wgOut->setArticleFlag( false );
-
                $this->importFormData( $wgRequest );
                $this->firsttime = false;
 
@@ -411,9 +414,9 @@ class EditPage {
                } else {
                        if ( $this->save ) {
                                $this->formtype = 'save';
-                       } else if ( $this->preview ) {
+                       } elseif ( $this->preview ) {
                                $this->formtype = 'preview';
-                       } else if ( $this->diff ) {
+                       } elseif ( $this->diff ) {
                                $this->formtype = 'diff';
                        } else { # First time through
                                $this->firsttime = true;
@@ -464,6 +467,13 @@ class EditPage {
                                        $wgOut->addWikiText( $editnotice_base_msg->plain()  );
                                }
                        }
+               } else {
+                       # Even if there are no subpages in namespace, we still don't want / in MW ns.
+                       $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->mTitle->getDBkey() );
+                       $editnoticeMsg = wfMessage( $editnoticeText )->inContentLanguage();
+                       if ( $editnoticeMsg->exists() ) {
+                               $wgOut->addWikiText( $editnoticeMsg->plain() );
+                       }
                }
 
                # Attempt submission here.  This will check for edit conflicts,
@@ -553,7 +563,7 @@ class EditPage {
                } elseif ( $wgRequest->getVal( 'preview' ) == 'no' ) {
                        // Explicit override from request
                        return false;
-               } elseif ( $this->section == 'new' ) { 
+               } elseif ( $this->section == 'new' ) {
                        // Nothing *to* preview for new sections
                        return false;
                } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) {
@@ -662,7 +672,7 @@ class EditPage {
                                        # The unmarked state will be assumed to be a save,
                                        # if the form seems otherwise complete.
                                        wfDebug( __METHOD__ . ": Passed token check.\n" );
-                               } else if ( $this->diff ) {
+                               } elseif ( $this->diff ) {
                                        # Failed token check, but only requested "Show Changes".
                                        wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
                                } else {
@@ -792,12 +802,12 @@ class EditPage {
                        $ip = User::isIP( $username );
                        if ( !$user->isLoggedIn() && !$ip ) { # User does not exist
                                $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
-                                       array( 'userpage-userdoesnotexist', $username ) );
-                       } else if ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
+                                       array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
+                       } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
                                LogEventsList::showLogExtract(
                                        $wgOut,
                                        'block',
-                                       $user->getUserPage()->getPrefixedText(),
+                                       $user->getUserPage(),
                                        '',
                                        array(
                                                'lim' => 1,
@@ -820,7 +830,7 @@ class EditPage {
                }
                # Give a notice if the user is editing a deleted/moved page...
                if ( !$this->mTitle->exists() ) {
-                       LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle->getPrefixedText(),
+                       LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle,
                                '', array( 'lim' => 10,
                                           'conds' => array( "log_action != 'revision'" ),
                                           'showIfEmpty' => false,
@@ -856,32 +866,41 @@ class EditPage {
         * @param $result
         * @param $bot bool
         *
-        * @return int one of the constants describing the result
+        * @return Status object, possibly with a message, but always with one of the AS_* constants in $status->value,
+        *
+        * FIXME: This interface is TERRIBLE, but hard to get rid of due to various error display idiosyncrasies. There are
+        * also lots of cases where error metadata is set in the object and retrieved later instead of being returned, e.g.
+        * AS_CONTENT_TOO_BIG and AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some time.
         */
        function internalAttemptSave( &$result, $bot = false ) {
-               global $wgFilterCallback, $wgUser, $wgParser;
+               global $wgFilterCallback, $wgUser, $wgRequest, $wgParser;
                global $wgMaxArticleSize;
 
+               $status = Status::newGood();
+
                wfProfileIn( __METHOD__  );
                wfProfileIn( __METHOD__ . '-checks' );
 
                if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
                        wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
+                       $status->fatal( 'hookaborted' );
+                       $status->value = self::AS_HOOK_ERROR;
                        wfProfileOut( __METHOD__ . '-checks' );
                        wfProfileOut( __METHOD__  );
-                       return self::AS_HOOK_ERROR;
+                       return $status;
                }
 
                # Check image redirect
                if ( $this->mTitle->getNamespace() == NS_FILE &&
                        Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
                        !$wgUser->isAllowed( 'upload' ) ) {
-                               $isAnon = $wgUser->isAnon();
+                               $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
+                               $status->setResult( false, $code );
 
                                wfProfileOut( __METHOD__ . '-checks' );
                                wfProfileOut( __METHOD__  );
 
-                               return $isAnon ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
+                               return $status;
                }
 
                # Check for spam
@@ -891,75 +910,94 @@ class EditPage {
                }
                if ( $match !== false ) {
                        $result['spam'] = $match;
-                       $ip = wfGetIP();
+                       $ip = $wgRequest->getIP();
                        $pdbk = $this->mTitle->getPrefixedDBkey();
                        $match = str_replace( "\n", '', $match );
                        wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
+                       $status->fatal( 'spamprotectionmatch', $match );
+                       $status->value = self::AS_SPAM_ERROR;
                        wfProfileOut( __METHOD__ . '-checks' );
                        wfProfileOut( __METHOD__ );
-                       return self::AS_SPAM_ERROR;
+                       return $status;
                }
                if ( $wgFilterCallback && $wgFilterCallback( $this->mTitle, $this->textbox1, $this->section, $this->hookError, $this->summary ) ) {
                        # Error messages or other handling should be performed by the filter function
+                       $status->setResult( false, self::AS_FILTERING );
                        wfProfileOut( __METHOD__ . '-checks' );
                        wfProfileOut( __METHOD__ );
-                       return self::AS_FILTERING;
+                       return $status;
                }
                if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) {
                        # Error messages etc. could be handled within the hook...
+                       $status->fatal( 'hookaborted' );
+                       $status->value = self::AS_HOOK_ERROR;
                        wfProfileOut( __METHOD__ . '-checks' );
                        wfProfileOut( __METHOD__ );
-                       return self::AS_HOOK_ERROR;
+                       return $status;
                } elseif ( $this->hookError != '' ) {
                        # ...or the hook could be expecting us to produce an error
+                       $status->fatal( 'hookaborted' );
+                       $status->value = self::AS_HOOK_ERROR_EXPECTED;
                        wfProfileOut( __METHOD__ . '-checks' );
                        wfProfileOut( __METHOD__ );
-                       return self::AS_HOOK_ERROR_EXPECTED;
+                       return $status;
                }
+
                if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) {
                        # Check block state against master, thus 'false'.
+                       $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
                        wfProfileOut( __METHOD__ . '-checks' );
                        wfProfileOut( __METHOD__ );
-                       return self::AS_BLOCKED_PAGE_FOR_USER;
+                       return $status;
                }
+
                $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
                if ( $this->kblength > $wgMaxArticleSize ) {
                        // Error will be displayed by showEditForm()
                        $this->tooBig = true;
+                       $status->setResult( false, self::AS_CONTENT_TOO_BIG );
                        wfProfileOut( __METHOD__ . '-checks' );
                        wfProfileOut( __METHOD__ );
-                       return self::AS_CONTENT_TOO_BIG;
+                       return $status;
                }
 
                if ( !$wgUser->isAllowed( 'edit' ) ) {
                        if ( $wgUser->isAnon() ) {
+                               $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
                                wfProfileOut( __METHOD__ . '-checks' );
                                wfProfileOut( __METHOD__ );
-                               return self::AS_READ_ONLY_PAGE_ANON;
+                               return $status;
                        } else {
+                               $status->fatal( 'readonlytext' );
+                               $status->value = self::AS_READ_ONLY_PAGE_LOGGED;
                                wfProfileOut( __METHOD__ . '-checks' );
                                wfProfileOut( __METHOD__ );
-                               return self::AS_READ_ONLY_PAGE_LOGGED;
+                               return $status;
                        }
                }
 
                if ( wfReadOnly() ) {
+                       $status->fatal( 'readonlytext' );
+                       $status->value = self::AS_READ_ONLY_PAGE;
                        wfProfileOut( __METHOD__ . '-checks' );
                        wfProfileOut( __METHOD__ );
-                       return self::AS_READ_ONLY_PAGE;
+                       return $status;
                }
                if ( $wgUser->pingLimiter() ) {
+                       $status->fatal( 'actionthrottledtext' );
+                       $status->value = self::AS_RATE_LIMITED;
                        wfProfileOut( __METHOD__ . '-checks' );
                        wfProfileOut( __METHOD__ );
-                       return self::AS_RATE_LIMITED;
+                       return $status;
                }
 
                # If the article has been deleted while editing, don't save it without
                # confirmation
                if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
+                       $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
                        wfProfileOut( __METHOD__ . '-checks' );
                        wfProfileOut( __METHOD__ );
-                       return self::AS_ARTICLE_WAS_DELETED;
+                       return $status;
                }
 
                wfProfileOut( __METHOD__ . '-checks' );
@@ -971,43 +1009,52 @@ class EditPage {
                if ( $new ) {
                        // Late check for create permission, just in case *PARANOIA*
                        if ( !$this->mTitle->userCan( 'create' ) ) {
+                               $status->fatal( 'nocreatetext' );
+                               $status->value = self::AS_NO_CREATE_PERMISSION;
                                wfDebug( __METHOD__ . ": no create permission\n" );
                                wfProfileOut( __METHOD__ );
-                               return self::AS_NO_CREATE_PERMISSION;
+                               return $status;
                        }
 
                        # Don't save a new article if it's blank.
                        if ( $this->textbox1 == '' ) {
+                               $status->setResult( false, self::AS_BLANK_ARTICLE );
                                wfProfileOut( __METHOD__ );
-                               return self::AS_BLANK_ARTICLE;
+                               return $status;
                        }
 
                        // Run post-section-merge edit filter
                        if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) {
                                # Error messages etc. could be handled within the hook...
+                               $status->fatal( 'hookaborted' );
+                               $status->value = self::AS_HOOK_ERROR;
                                wfProfileOut( __METHOD__ );
-                               return self::AS_HOOK_ERROR;
+                               return $status;
                        } elseif ( $this->hookError != '' ) {
                                # ...or the hook could be expecting us to produce an error
+                               $status->fatal( 'hookaborted' );
+                               $status->value = self::AS_HOOK_ERROR_EXPECTED;
                                wfProfileOut( __METHOD__ );
-                               return self::AS_HOOK_ERROR_EXPECTED;
+                               return $status;
                        }
 
                        # Handle the user preference to force summaries here. Check if it's not a redirect.
                        if ( !$this->allowBlankSummary && !Title::newFromRedirect( $this->textbox1 ) ) {
                                if ( md5( $this->summary ) == $this->autoSumm ) {
                                        $this->missingSummary = true;
+                                       $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
+                                       $status->value = self::AS_SUMMARY_NEEDED;
                                        wfProfileOut( __METHOD__ );
-                                       return self::AS_SUMMARY_NEEDED;
+                                       return $status;
                                }
                        }
 
                        $text = $this->textbox1;
-                       if ( $this->section == 'new' && $this->summary != '' ) { 
+                       if ( $this->section == 'new' && $this->summary != '' ) {
                                $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $text;
                        }
 
-                       $retval = self::AS_SUCCESS_NEW_ARTICLE;
+                       $status->value = self::AS_SUCCESS_NEW_ARTICLE;
 
                } else {
 
@@ -1031,15 +1078,12 @@ class EditPage {
                                                $this->isConflict = false;
                                                wfDebug( __METHOD__ .": conflict suppressed; new section\n" );
                                        }
+                               } elseif ( $this->section == '' && $this->userWasLastToEdit( $wgUser->getId(), $this->edittime ) ) {
+                                       # Suppress edit conflict with self, except for section edits where merging is required.
+                                       wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
+                                       $this->isConflict = false;
                                }
                        }
-                       $userid = $wgUser->getId();
-
-                       # Suppress edit conflict with self, except for section edits where merging is required.
-                       if ( $this->isConflict && $this->section == '' && $this->userWasLastToEdit( $userid, $this->edittime ) ) {
-                               wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
-                               $this->isConflict = false;
-                       }
 
                        if ( $this->isConflict ) {
                                wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '" .
@@ -1053,7 +1097,7 @@ class EditPage {
                                wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
                                $this->isConflict = true;
                                $text = $this->textbox1; // do not try to merge here!
-                       } else if ( $this->isConflict ) {
+                       } elseif ( $this->isConflict ) {
                                # Attempt merge
                                if ( $this->mergeChangesInto( $text ) ) {
                                        // Successful merge! Maybe we should tell the user the good news?
@@ -1067,52 +1111,62 @@ class EditPage {
                        }
 
                        if ( $this->isConflict ) {
+                               $status->setResult( false, self::AS_CONFLICT_DETECTED );
                                wfProfileOut( __METHOD__ );
-                               return self::AS_CONFLICT_DETECTED;
+                               return $status;
                        }
 
-                       $oldtext = $this->mArticle->getContent();
-
                        // Run post-section-merge edit filter
                        if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
                                # Error messages etc. could be handled within the hook...
+                               $status->fatal( 'hookaborted' );
+                               $status->value = self::AS_HOOK_ERROR;
                                wfProfileOut( __METHOD__ );
-                               return self::AS_HOOK_ERROR;
+                               return $status;
                        } elseif ( $this->hookError != '' ) {
                                # ...or the hook could be expecting us to produce an error
+                               $status->fatal( 'hookaborted' );
+                               $status->value = self::AS_HOOK_ERROR_EXPECTED;
                                wfProfileOut( __METHOD__ );
-                               return self::AS_HOOK_ERROR_EXPECTED;
+                               return $status;
                        }
 
                        # Handle the user preference to force summaries here, but not for null edits
-                       if ( $this->section != 'new' && !$this->allowBlankSummary && 0 != strcmp( $oldtext, $text ) 
+                       if ( $this->section != 'new' && !$this->allowBlankSummary
+                               && 0 != strcmp( $this->mArticle->getContent(), $text )
                                && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
                        {
                                if ( md5( $this->summary ) == $this->autoSumm ) {
                                        $this->missingSummary = true;
+                                       $status->fatal( 'missingsummary' );
+                                       $status->value = self::AS_SUMMARY_NEEDED;
                                        wfProfileOut( __METHOD__ );
-                                       return self::AS_SUMMARY_NEEDED;
+                                       return $status;
                                }
                        }
 
                        # And a similar thing for new sections
-                       if ( $this->section == 'new' && !$this->allowBlankSummary ) { 
+                       if ( $this->section == 'new' && !$this->allowBlankSummary ) {
                                if ( trim( $this->summary ) == '' ) {
                                        $this->missingSummary = true;
+                                       $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
+                                       $status->value = self::AS_SUMMARY_NEEDED;
                                        wfProfileOut( __METHOD__ );
-                                       return self::AS_SUMMARY_NEEDED;
+                                       return $status;
                                }
                        }
 
                        # All's well
                        wfProfileIn( __METHOD__ . '-sectionanchor' );
                        $sectionanchor = '';
-                       if ( $this->section == 'new' ) { 
+                       if ( $this->section == 'new' ) {
                                if ( $this->textbox1 == '' ) {
                                        $this->missingComment = true;
+                                       $status->fatal( 'missingcommenttext' );
+                                       $status->value = self::AS_TEXTBOX_EMPTY;
                                        wfProfileOut( __METHOD__ . '-sectionanchor' );
                                        wfProfileOut( __METHOD__ );
-                                       return self::AS_TEXTBOX_EMPTY;
+                                       return $status;
                                }
                                if ( $this->summary != '' ) {
                                        $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
@@ -1142,15 +1196,16 @@ class EditPage {
                        $this->textbox1 = $text;
                        $this->section = '';
 
-                       $retval = self::AS_SUCCESS_UPDATE;
+                       $status->value = self::AS_SUCCESS_UPDATE;
                }
 
                // Check for length errors again now that the section is merged in
                $this->kblength = (int)( strlen( $text ) / 1024 );
                if ( $this->kblength > $wgMaxArticleSize ) {
                        $this->tooBig = true;
+                       $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
                        wfProfileOut( __METHOD__ );
-                       return self::AS_MAX_ARTICLE_SIZE_EXCEEDED;
+                       return $status;
                }
 
                $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
@@ -1158,17 +1213,18 @@ class EditPage {
                        ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
                        ( $bot ? EDIT_FORCE_BOT : 0 );
 
-               $status = $this->mArticle->doEdit( $text, $this->summary, $flags );
+               $doEditStatus = $this->mArticle->doEdit( $text, $this->summary, $flags );
 
-               if ( $status->isOK() ) {
+               if ( $doEditStatus->isOK() ) {
                        $result['redirect'] = Title::newFromRedirect( $text ) !== null;
                        $this->commitWatch();
                        wfProfileOut( __METHOD__ );
-                       return $retval;
+                       return $status;
                } else {
                        $this->isConflict = true;
+                       $doEditStatus->value = self::AS_END; // Destroys data doEdit() put in $status->value but who cares
                        wfProfileOut( __METHOD__ );
-                       return self::AS_END;
+                       return $doEditStatus;
                }
        }
 
@@ -1324,8 +1380,6 @@ class EditPage {
 
                wfProfileIn( __METHOD__ );
 
-               $sk = $wgUser->getSkin();
-
                #need to parse the preview early so that we know which templates are used,
                #otherwise users with "show preview after edit box" will get a blank list
                #we parse this near the beginning so that setHeaders can do the title
@@ -1366,10 +1420,10 @@ class EditPage {
                $wgOut->addHTML( $this->editFormTextTop );
 
                $templates = $this->getTemplates();
-               $formattedtemplates = $sk->formatTemplates( $templates, $this->preview, $this->section != '');
+               $formattedtemplates = Linker::formatTemplates( $templates, $this->preview, $this->section != '');
 
                $hiddencats = $this->mArticle->getHiddenCategories();
-               $formattedhiddencats = $sk->formatHiddenCategories( $hiddencats );
+               $formattedhiddencats = Linker::formatHiddenCategories( $hiddencats );
 
                if ( $this->wasDeletedSinceLastEdit() && 'save' != $this->formtype ) {
                        $wgOut->wrapWikiMsg(
@@ -1408,7 +1462,7 @@ HTML
                                '<div class="mw-confirm-recreate">' .
                                wfMsgExt( $key, 'parseinline', $username, "<nowiki>$comment</nowiki>" ) .
                                Xml::checkLabel( wfMsg( 'recreate' ), 'wpRecreate', 'wpRecreate', false,
-                                       array( 'title' => $sk->titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
+                                       array( 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
                                ) .
                                '</div>'
                        );
@@ -1435,7 +1489,7 @@ HTML
                }
 
                $wgOut->addHTML( $this->editFormTextBeforeContent );
-               
+
                $wgOut->addHTML( $toolbar );
 
                if ( $this->isConflict ) {
@@ -1536,7 +1590,7 @@ HTML
 
                                if ( !$this->mArticle->mRevision->userCan( Revision::DELETED_TEXT ) ) {
                                        $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
-                               } else if ( $this->mArticle->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
+                               } elseif ( $this->mArticle->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
                                        $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
                                }
 
@@ -1578,7 +1632,7 @@ HTML
                                # Then it must be protected based on static groups (regular)
                                $noticeMsg = 'protectedpagewarning';
                        }
-                       LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle->getPrefixedText(), '',
+                       LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
                                array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) );
                }
                if ( $this->mTitle->isCascadeProtected() ) {
@@ -1596,7 +1650,7 @@ HTML
                        $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', $cascadeSourcesCount ) );
                }
                if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
-                       LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle->getPrefixedText(), '',
+                       LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
                                array(  'lim' => 1,
                                        'showIfEmpty' => false,
                                        'msgKey' => array( 'titleprotectedwarning' ),
@@ -1634,7 +1688,6 @@ HTML
         * @return array An array in the format array( $label, $input )
         */
        function getSummaryInput($summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null) {
-               global $wgUser;
                //Note: the maxlength is overriden in JS to 250 and to make it use UTF-8 bytes, not characters.
                $inputAttrs = ( is_array($inputAttrs) ? $inputAttrs : array() ) + array(
                        'id' => 'wpSummary',
@@ -1642,7 +1695,7 @@ HTML
                        'tabindex' => '1',
                        'size' => 60,
                        'spellcheck' => 'true',
-               ) + $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'summary' );
+               ) + Linker::tooltipAndAccesskeyAttribs( 'summary' );
 
                $spanLabelAttrs = ( is_array($spanLabelAttrs) ? $spanLabelAttrs : array() ) + array(
                        'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
@@ -1697,15 +1750,14 @@ HTML
                if ( !$summary || ( !$this->preview && !$this->diff ) )
                        return "";
 
-               global $wgParser, $wgUser;
-               $sk = $wgUser->getSkin();
+               global $wgParser;
 
                if ( $isSubjectPreview )
                        $summary = wfMsgForContent( 'newsectionsummary', $wgParser->stripSectionName( $summary ) );
 
                $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
 
-               $summary = wfMsgExt( $message, 'parseinline' ) . $sk->commentBlock( $summary, $this->mTitle, $isSubjectPreview );
+               $summary = wfMsgExt( $message, 'parseinline' ) . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
                return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary );
        }
 
@@ -1815,6 +1867,10 @@ HTML
                        'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work
                );
 
+               $pageLang = $this->mTitle->getPageLanguage();
+               $attribs['lang'] = $pageLang->getCode();
+               $attribs['dir'] = $pageLang->getDir();
+
                $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
        }
 
@@ -1876,16 +1932,16 @@ HTML
                if( !wfMessage( $msg )->isDisabled() ) {
                        global $wgOut;
                        $wgOut->addHTML( '<div class="mw-tos-summary">' );
-                       $wgOut->addWikiMsgArray( $msg, array() );
+                       $wgOut->addWikiMsg( $msg );
                        $wgOut->addHTML( '</div>' );
                }
        }
 
        protected function showEditTools() {
                global $wgOut;
-               $wgOut->addHTML( '<div class="mw-editTools">' );
-               $wgOut->addWikiMsgArray( 'edittools', array(), array( 'content' ) );
-               $wgOut->addHTML( '</div>' );
+               $wgOut->addHTML( '<div class="mw-editTools">' .
+                       wfMessage( 'edittools' )->inContentLanguage()->parse() .
+                       '</div>' );
        }
 
        protected function getCopywarn() {
@@ -1906,7 +1962,7 @@ HTML
        }
 
        protected function showStandardInputs( &$tabindex = 2 ) {
-               global $wgOut, $wgUser;
+               global $wgOut;
                $wgOut->addHTML( "<div class='editOptions'>\n" );
 
                if ( $this->section != 'new' ) {
@@ -1914,19 +1970,21 @@ HTML
                        $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
                }
 
-               $checkboxes = $this->getCheckboxes( $tabindex, $wgUser->getSkin(),
+               $checkboxes = $this->getCheckboxes( $tabindex,
                        array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) );
                $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" );
                $wgOut->addHTML( "<div class='editButtons'>\n" );
                $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
 
                $cancel = $this->getCancelLink();
-               $separator = wfMsgExt( 'pipe-separator' , 'escapenoentities' );
+               if ( $cancel !== '' ) {
+                       $cancel .= wfMsgExt( 'pipe-separator' , 'escapenoentities' );
+               }
                $edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ) );
                $edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'.
                        htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '.
                        htmlspecialchars( wfMsg( 'newwindow' ) );
-               $wgOut->addHTML( "      <span class='editHelp'>{$cancel}{$separator}{$edithelp}</span>\n" );
+               $wgOut->addHTML( "      <span class='editHelp'>{$cancel}{$edithelp}</span>\n" );
                $wgOut->addHTML( "</div><!-- editButtons -->\n</div><!-- editOptions -->\n" );
        }
 
@@ -1997,7 +2055,7 @@ HTML
                        } else {
                                $note = wfMsg( 'session_fail_preview' );
                        }
-               } else if ( $this->incompleteForm ) {
+               } elseif ( $this->incompleteForm ) {
                        $note = wfMsg( 'edit_form_incomplete' );
                } else {
                        $note = wfMsg( 'previewnote' );
@@ -2012,31 +2070,42 @@ HTML
                if ( $wgRawHtml && !$this->mTokenOk ) {
                        // Could be an offsite preview attempt. This is very unsafe if
                        // HTML is enabled, as it could be an attack.
-                       $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
-                               wfMsg( 'session_fail_preview_html' ) . "</div>" );
+                       $parsedNote = '';
+                       if ( $this->textbox1 !== '' ) {
+                               // Do not put big scary notice, if previewing the empty
+                               // string, which happens when you initially edit
+                               // a category page, due to automatic preview-on-open.
+                               $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
+                                       wfMsg( 'session_fail_preview_html' ) . "</div>", true, /* interface */true );
+                       }
                        wfProfileOut( __METHOD__ );
                        return $parsedNote;
                }
 
-               # don't parse user css/js, show message about preview
+               # don't parse non-wikitext pages, show message about preview
                # XXX: stupid php bug won't let us use $this->getContextTitle()->isCssJsSubpage() here -- This note has been there since r3530. Sure the bug was fixed time ago?
 
-               if ( $this->isCssJsSubpage || $this->mTitle->isCssOrJsPage() ) {
-                       $level = 'user';
-                       if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+               if ( $this->isCssJsSubpage || !$this->mTitle->isWikitextPage() ) {
+                       if( $this->mTitle->isCssJsSubpage() ) {
+                               $level = 'user';
+                       } elseif( $this->mTitle->isCssOrJsPage() ) {
                                $level = 'site';
+                       } else {
+                               $level = false;
                        }
 
                        # Used messages to make sure grep find them:
                        # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
-                       if (preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
-                               $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMsg( "{$level}csspreview" ) . "\n</div>";
-                               $class = "mw-code mw-css";
-                       } elseif (preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
-                               $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMsg( "{$level}jspreview" ) . "\n</div>";
-                               $class = "mw-code mw-js";
-                       } else {
-                               throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
+                       if( $level ) {
+                               if (preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
+                                       $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMsg( "{$level}csspreview" ) . "\n</div>";
+                                       $class = "mw-code mw-css";
+                               } elseif (preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
+                                       $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMsg( "{$level}jspreview" ) . "\n</div>";
+                                       $class = "mw-code mw-js";
+                               } else {
+                                       throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
+                               }
                        }
 
                        $parserOptions->setTidy( true );
@@ -2058,13 +2127,6 @@ HTML
 
                                wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
 
-                               // Parse mediawiki messages with correct target language
-                               if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
-                                       list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $this->mTitle->getText() );
-                                       $obj = wfGetLangObj( $lang );
-                                       $parserOptions->setTargetLanguage( $obj );
-                               }
-
                                $parserOptions->setTidy( true );
                                $parserOptions->enableLimitReport();
                                $parserOutput = $wgParser->parse( $this->mArticle->preSaveTransform( $toparse ),
@@ -2088,7 +2150,12 @@ HTML
 
                $previewhead = "<div class='previewnote'>\n" .
                        '<h2 id="mw-previewheader">' . htmlspecialchars( wfMsg( 'preview' ) ) . "</h2>" .
-                       $wgOut->parse( $note ) . $conflict . "</div>\n";
+                       $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
+
+               $pageLang = $this->mTitle->getPageLanguage();
+               $attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
+                       'class' => 'mw-content-'.$pageLang->getDir() );
+               $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
 
                wfProfileOut( __METHOD__ );
                return $previewhead . $previewHTML . $this->previewTextAfterContent;
@@ -2142,23 +2209,21 @@ HTML
         * Produce the stock "please login to edit pages" page
         */
        function userNotLoggedInPage() {
-               global $wgUser, $wgOut;
-               $skin = $wgUser->getSkin();
+               global $wgOut;
 
                $loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
-               $loginLink = $skin->link(
+               $loginLink = Linker::linkKnown(
                        $loginTitle,
                        wfMsgHtml( 'loginreqlink' ),
                        array(),
-                       array( 'returnto' => $this->getContextTitle()->getPrefixedText() ),
-                       array( 'known', 'noclasses' )
+                       array( 'returnto' => $this->getContextTitle()->getPrefixedText() )
                );
 
                $wgOut->setPageTitle( wfMsg( 'whitelistedittitle' ) );
                $wgOut->setRobotPolicy( 'noindex,nofollow' );
                $wgOut->setArticleRelated( false );
 
-               $wgOut->addWikiMsgArray( 'whitelistedittext', array( $loginLink ), array( 'replaceafter' ) );
+               $wgOut->addHTML( wfMessage( 'whitelistedittext' )->rawParams( $loginLink )->parse() );
                $wgOut->returnToMain( false, $this->getContextTitle() );
        }
 
@@ -2326,10 +2391,8 @@ HTML
                 * filename of the button image (without path), the opening
                 * tag, the closing tag, optionally a sample text that is
                 * inserted between the two when no selection is highlighted
-                * and an option to select which switches the automatic
-                * selection of inserted text (default is true, see
-                * mw-editbutton-image).  The tip text is shown when the user
-                * moves the mouse over the button.
+                * and.  The tip text is shown when the user moves the mouse
+                * over the button.
                 *
                 * Also here: accesskeys (key), which are not used yet until
                 * someone can figure out a way to make them work in
@@ -2390,7 +2453,6 @@ HTML
                                'sample' => wfMsg( 'image_sample' ),
                                'tip'    => wfMsg( 'image_tip' ),
                                'key'    => 'D',
-                               'select' => true
                        ) : false,
                        $imagesAvailable ? array(
                                'image'  => $wgLang->getImageFile( 'button-media' ),
@@ -2438,7 +2500,6 @@ HTML
                                'key'    => 'R'
                        )
                );
-               $toolbar = "<div id='toolbar'>\n";
 
                $script = '';
                foreach ( $toolarray as $tool ) {
@@ -2446,10 +2507,6 @@ HTML
                                continue;
                        }
 
-                       if( !isset( $tool['select'] ) ) {
-                         $tool['select'] = true;
-                       }
-
                        $params = array(
                                $image = $wgStylePath . '/common/images/' . $tool['image'],
                                // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
@@ -2463,15 +2520,11 @@ HTML
                                $cssId = $tool['id'],
                        );
 
-                       $paramList = implode( ',',
-                               array_map( array( 'Xml', 'encodeJsVar' ), $params ) );
-                       $script .= "mw.toolbar.addButton($paramList);\n";
+                       $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
                }
-               $wgOut->addScript( Html::inlineScript(
-                       "if ( window.mediaWiki ) {{$script}}"
-               ) );
+               $wgOut->addScript( Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) ) );
 
-               $toolbar .= "\n</div>";
+               $toolbar = '<div id="toolbar"></div>';
 
                wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
 
@@ -2483,13 +2536,12 @@ HTML
         * minor and watch
         *
         * @param $tabindex Current tabindex
-        * @param $skin Skin object
         * @param $checked Array of checkbox => bool, where bool indicates the checked
         *                 status of the checkbox
         *
         * @return array
         */
-       public function getCheckboxes( &$tabindex, $skin, $checked ) {
+       public function getCheckboxes( &$tabindex, $checked ) {
                global $wgUser;
 
                $checkboxes = array();
@@ -2507,7 +2559,7 @@ HTML
                                $checkboxes['minor'] =
                                        Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
                                        "&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
-                                       Xml::expandAttributes( array( 'title' => $skin->titleAttrib( 'minoredit', 'withaccess' ) ) ) .
+                                       Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'minoredit', 'withaccess' ) ) ) .
                                        ">{$minorLabel}</label>";
                        }
                }
@@ -2523,7 +2575,7 @@ HTML
                        $checkboxes['watch'] =
                                Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
                                "&#160;<label for='wpWatchthis' id='mw-editpage-watch'" .
-                               Xml::expandAttributes( array( 'title' => $skin->titleAttrib( 'watch', 'withaccess' ) ) ) .
+                               Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ) ) .
                                ">{$watchLabel}</label>";
                }
                wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
@@ -2614,19 +2666,16 @@ HTML
         * @return string
         */
        public function getCancelLink() {
-               global $wgUser;
-
                $cancelParams = array();
                if ( !$this->isConflict && $this->mArticle->getOldID() > 0 ) {
                        $cancelParams['oldid'] = $this->mArticle->getOldID();
                }
 
-               return $wgUser->getSkin()->link(
+               return Linker::linkKnown(
                        $this->getContextTitle(),
                        wfMsgExt( 'cancel', array( 'parseinline' ) ),
                        array( 'id' => 'mw-editform-cancel' ),
-                       $cancelParams,
-                       array( 'known', 'noclasses' )
+                       $cancelParams
                );
        }
 
@@ -2803,13 +2852,14 @@ HTML
                $resultDetails = false;
                # Allow bots to exempt some edits from bot flagging
                $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
-               $value = $this->internalAttemptSave( $resultDetails, $bot );
+               $status = $this->internalAttemptSave( $resultDetails, $bot );
+               // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
 
-               if ( $value == self::AS_SUCCESS_UPDATE || $value == self::AS_SUCCESS_NEW_ARTICLE ) {
+               if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
                        $this->didSave = true;
                }
 
-               switch ( $value ) {
+               switch ( $status->value ) {
                        case self::AS_HOOK_ERROR_EXPECTED:
                        case self::AS_CONTENT_TOO_BIG:
                        case self::AS_ARTICLE_WAS_DELETED: