fix some spacing
[lhc/web/wiklou.git] / includes / EditPage.php
index 23a3e27..1bb165f 100644 (file)
@@ -40,90 +40,90 @@ class EditPage {
        /**
         * Status: Article successfully updated
         */
-       const AS_SUCCESS_UPDATE            = 200;
+       const AS_SUCCESS_UPDATE = 200;
 
        /**
         * Status: Article successfully created
         */
-       const AS_SUCCESS_NEW_ARTICLE       = 201;
+       const AS_SUCCESS_NEW_ARTICLE = 201;
 
        /**
         * Status: Article update aborted by a hook function
         */
-       const AS_HOOK_ERROR                = 210;
+       const AS_HOOK_ERROR = 210;
 
        /**
         * Status: A hook function returned an error
         */
-       const AS_HOOK_ERROR_EXPECTED       = 212;
+       const AS_HOOK_ERROR_EXPECTED = 212;
 
        /**
         * Status: User is blocked from editting this page
         */
-       const AS_BLOCKED_PAGE_FOR_USER     = 215;
+       const AS_BLOCKED_PAGE_FOR_USER = 215;
 
        /**
         * Status: Content too big (> $wgMaxArticleSize)
         */
-       const AS_CONTENT_TOO_BIG           = 216;
+       const AS_CONTENT_TOO_BIG = 216;
 
        /**
         * Status: User cannot edit? (not used)
         */
-       const AS_USER_CANNOT_EDIT          = 217;
+       const AS_USER_CANNOT_EDIT = 217;
 
        /**
         * Status: this anonymous user is not allowed to edit this page
         */
-       const AS_READ_ONLY_PAGE_ANON       = 218;
+       const AS_READ_ONLY_PAGE_ANON = 218;
 
        /**
         * Status: this logged in user is not allowed to edit this page
         */
-       const AS_READ_ONLY_PAGE_LOGGED     = 219;
+       const AS_READ_ONLY_PAGE_LOGGED = 219;
 
        /**
         * Status: wiki is in readonly mode (wfReadOnly() == true)
         */
-       const AS_READ_ONLY_PAGE            = 220;
+       const AS_READ_ONLY_PAGE = 220;
 
        /**
         * Status: rate limiter for action 'edit' was tripped
         */
-       const AS_RATE_LIMITED              = 221;
+       const AS_RATE_LIMITED = 221;
 
        /**
         * Status: article was deleted while editting and param wpRecreate == false or form
         * was not posted
         */
-       const AS_ARTICLE_WAS_DELETED       = 222;
+       const AS_ARTICLE_WAS_DELETED = 222;
 
        /**
         * Status: user tried to create this page, but is not allowed to do that
         * ( Title->usercan('create') == false )
         */
-       const AS_NO_CREATE_PERMISSION      = 223;
+       const AS_NO_CREATE_PERMISSION = 223;
 
        /**
         * Status: user tried to create a blank page
         */
-       const AS_BLANK_ARTICLE             = 224;
+       const AS_BLANK_ARTICLE = 224;
 
        /**
         * Status: (non-resolvable) edit conflict
         */
-       const AS_CONFLICT_DETECTED         = 225;
+       const AS_CONFLICT_DETECTED = 225;
 
        /**
         * Status: no edit summary given and the user has forceeditsummary set and the user is not
         * editting in his own userspace or talkspace and wpIgnoreBlankSummary == false
         */
-       const AS_SUMMARY_NEEDED            = 226;
+       const AS_SUMMARY_NEEDED = 226;
 
        /**
         * Status: user tried to create a new section without content
         */
-       const AS_TEXTBOX_EMPTY             = 228;
+       const AS_TEXTBOX_EMPTY = 228;
 
        /**
         * Status: article is too big (> $wgMaxArticleSize), after merging in the new section
@@ -133,37 +133,57 @@ class EditPage {
        /**
         * not used
         */
-       const AS_OK                        = 230;
+       const AS_OK = 230;
 
        /**
         * Status: WikiPage::doEdit() was unsuccessfull
         */
-       const AS_END                       = 231;
+       const AS_END = 231;
 
        /**
         * Status: summary contained spam according to one of the regexes in $wgSummarySpamRegex
         */
-       const AS_SPAM_ERROR                = 232;
+       const AS_SPAM_ERROR = 232;
 
        /**
         * Status: anonymous user is not allowed to upload (User::isAllowed('upload') == false)
         */
-       const AS_IMAGE_REDIRECT_ANON       = 233;
+       const AS_IMAGE_REDIRECT_ANON = 233;
 
        /**
         * Status: logged in user is not allowed to upload (User::isAllowed('upload') == false)
         */
-       const AS_IMAGE_REDIRECT_LOGGED     = 234;
+       const AS_IMAGE_REDIRECT_LOGGED = 234;
 
        /**
         * Status: can't parse content
         */
-       const AS_PARSE_ERROR                = 240;
+       const AS_PARSE_ERROR = 240;
 
        /**
         * HTML id and name for the beginning of the edit form.
         */
-       const EDITFORM_ID                  = 'editform';
+       const EDITFORM_ID = 'editform';
+
+       /**
+        * Prefix of key for cookie used to pass post-edit state.
+        * The revision id edited is added after this
+        */
+       const POST_EDIT_COOKIE_KEY_PREFIX = 'PostEditRevision';
+
+       /**
+        * Duration of PostEdit cookie, in seconds.
+        * The cookie will be removed instantly if the JavaScript runs.
+        *
+        * Otherwise, though, we don't want the cookies to accumulate.
+        * RFC 2109 ( https://www.ietf.org/rfc/rfc2109.txt ) specifies a possible limit of only 20 cookies per domain.
+        * This still applies at least to some versions of IE without full updates:
+        * https://blogs.msdn.com/b/ieinternals/archive/2009/08/20/wininet-ie-cookie-internals-faq.aspx
+        *
+        * A value of 20 minutes should be enough to take into account slow loads and minor
+        * clock skew while still avoiding cookie accumulation when JavaScript is turned off.
+        */
+       const POST_EDIT_COOKIE_DURATION = 1200;
 
        /**
         * @var Article
@@ -377,9 +397,9 @@ class EditPage {
 
                $this->isConflict = false;
                // css / js subpages of user pages get a special treatment
-               $this->isCssJsSubpage       = $this->mTitle->isCssJsSubpage();
-               $this->isCssSubpage         = $this->mTitle->isCssSubpage();
-               $this->isJsSubpage          = $this->mTitle->isJsSubpage();
+               $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
+               $this->isCssSubpage = $this->mTitle->isCssSubpage();
+               $this->isJsSubpage = $this->mTitle->isJsSubpage();
                $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
 
                # Show applicable editing introductions
@@ -543,7 +563,7 @@ class EditPage {
                        // Nothing *to* preview for new sections
                        return false;
                } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) {
-                       // Standard preference behaviour
+                       // Standard preference behavior
                        return true;
                } elseif ( !$this->mTitle->exists() &&
                        isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] ) &&
@@ -624,11 +644,8 @@ class EditPage {
                                wfProfileOut( get_class( $this ) . "::importContentFormData" );
                        }
 
-                       # Trim spaces on user supplied text
-                       $summary = trim( $request->getText( 'wpSummary' ) );
-
                        # Truncate for whole multibyte characters
-                       $this->summary = $wgContLang->truncate( $summary, 255 );
+                       $this->summary = $wgContLang->truncate( $request->getText( 'wpSummary' ), 255 );
 
                        # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
                        # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
@@ -697,7 +714,7 @@ class EditPage {
                                $this->starttime = null;
                        }
 
-                       $this->recreate  = $request->getCheck( 'wpRecreate' );
+                       $this->recreate = $request->getCheck( 'wpRecreate' );
 
                        $this->minoredit = $request->getCheck( 'wpMinoredit' );
                        $this->watchthis = $request->getCheck( 'wpWatchthis' );
@@ -715,18 +732,18 @@ class EditPage {
                } else {
                        # Not a posted form? Start with nothing.
                        wfDebug( __METHOD__ . ": Not a posted form.\n" );
-                       $this->textbox1     = '';
-                       $this->summary      = '';
+                       $this->textbox1 = '';
+                       $this->summary = '';
                        $this->sectiontitle = '';
-                       $this->edittime     = '';
-                       $this->starttime    = wfTimestampNow();
-                       $this->edit         = false;
-                       $this->preview      = false;
-                       $this->save         = false;
-                       $this->diff         = false;
-                       $this->minoredit    = false;
-                       $this->watchthis    = $request->getBool( 'watchthis', false ); // Watch may be overriden by request parameters
-                       $this->recreate     = false;
+                       $this->edittime = '';
+                       $this->starttime = wfTimestampNow();
+                       $this->edit = false;
+                       $this->preview = false;
+                       $this->save = false;
+                       $this->diff = false;
+                       $this->minoredit = false;
+                       $this->watchthis = $request->getBool( 'watchthis', false ); // Watch may be overriden by request parameters
+                       $this->recreate = false;
 
                        // When creating a new section, we can preload a section title by passing it as the
                        // preloadtitle parameter in the URL (Bug 13100)
@@ -989,7 +1006,7 @@ class EditPage {
                $rev = $this->mArticle->getRevision();
                $content = $rev ? $rev->getContent( Revision::RAW ) : null;
 
-               if ( $content  === false || $content === null ) {
+               if ( $content === false || $content === null ) {
                        if ( !$this->contentModel ) $this->contentModel = $this->getTitle()->getContentModel();
                        $handler = ContentHandler::getForModelID( $this->contentModel );
 
@@ -1003,7 +1020,6 @@ class EditPage {
                }
        }
 
-
        /**
         * Use this method before edit() to preload some text into the edit box
         *
@@ -1130,6 +1146,33 @@ class EditPage {
                return $this->mTokenOk;
        }
 
+       /**
+        * Sets post-edit cookie indicating the user just saved a particular revision.
+        *
+        * This uses a temporary cookie for each revision ID so separate saves will never
+        * interfere with each other.
+        *
+        * The cookie is deleted in the mediawiki.action.view.postEdit JS module after
+        * the redirect.  It must be clearable by JavaScript code, so it must not be
+        * marked HttpOnly. The JavaScript code converts the cookie to a wgPostEdit config
+        * variable.
+        *
+        * Since WebResponse::setcookie does not allow forcing HttpOnly for a single
+        * cookie, we have to use PHP's setcookie() directly.
+        *
+        * We use a path of '/' since wgCookiePath is not exposed to JS
+        *
+        * If the variable were set on the server, it would be cached, which is unwanted
+        * since the post-edit state should only apply to the load right after the save.
+        */
+       protected function setPostEditCookie() {
+               global $wgCookiePrefix, $wgCookieDomain;
+               $revisionId = $this->mArticle->getLatest();
+               $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
+
+               setcookie( $wgCookiePrefix . $postEditKey, '1', time() + self::POST_EDIT_COOKIE_DURATION, '/', $wgCookieDomain );
+       }
+
        /**
         * Attempt submission
         * @throws UserBlockedError|ReadOnlyError|ThrottledError|PermissionsError
@@ -1145,6 +1188,9 @@ class EditPage {
                // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
                if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
                        $this->didSave = true;
+                       if ( !$resultDetails['nullEdit'] ) {
+                               $this->setPostEditCookie();
+                       }
                }
 
                switch ( $status->value ) {
@@ -1279,8 +1325,12 @@ class EditPage {
        /**
         * Attempt submission (no UI)
         *
-        * @param $result
-        * @param $bot bool
+        * @param array $result array to add statuses to, currently with the possible keys:
+        *  spam - string - Spam string from content if any spam is detected by matchSpamRegex
+        *  sectionanchor - string - Section anchor for a section save
+        *  nullEdit - boolean - Set if doEditContent is OK.  True if null edit, false otherwise.
+        *  redirect - boolean -  Set if doEditContent is OK.  True if resulting revision is a redirect
+        * @param bool $bot True if edit is being made under the bot right.
         *
         * @return Status object, possibly with a message, but always with one of the AS_* constants in $status->value,
         *
@@ -1564,43 +1614,40 @@ class EditPage {
                                return $status;
                        }
 
-                       # Handle the user preference to force summaries here, but not for null edits
-                       if ( $this->section != 'new' && !$this->allowBlankSummary
-                               && !$content->equals( $this->getOriginalContent() )
-                               && !$content->isRedirect() ) # check if it's not a redirect
-                       {
-                               if ( md5( $this->summary ) == $this->autoSumm ) {
+                       if ( $this->section == 'new' ) {
+                               // Handle the user preference to force summaries here
+                               if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) {
                                        $this->missingSummary = true;
-                                       $status->fatal( 'missingsummary' );
+                                       $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
                                        $status->value = self::AS_SUMMARY_NEEDED;
                                        wfProfileOut( __METHOD__ );
                                        return $status;
                                }
-                       }
 
-                       # And a similar thing for new sections
-                       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;
+                               // Do not allow the user to post an empty comment
+                               if ( $this->textbox1 == '' ) {
+                                       $this->missingComment = true;
+                                       $status->fatal( 'missingcommenttext' );
+                                       $status->value = self::AS_TEXTBOX_EMPTY;
                                        wfProfileOut( __METHOD__ );
                                        return $status;
                                }
+                       } elseif ( !$this->allowBlankSummary
+                               && !$content->equals( $this->getOriginalContent() )
+                               && !$content->isRedirect()
+                               && md5( $this->summary ) == $this->autoSumm
+                       ) {
+                               $this->missingSummary = true;
+                               $status->fatal( 'missingsummary' );
+                               $status->value = self::AS_SUMMARY_NEEDED;
+                               wfProfileOut( __METHOD__ );
+                               return $status;
                        }
 
                        # All's well
                        wfProfileIn( __METHOD__ . '-sectionanchor' );
                        $sectionanchor = '';
                        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 $status;
-                               }
                                if ( $this->sectiontitle !== '' ) {
                                        $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
                                        // If no edit summary was specified, create one automatically from the section
@@ -1676,6 +1723,7 @@ class EditPage {
                        return $doEditStatus;
                }
 
+               $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
                $result['redirect'] = $content->isRedirect();
                $this->updateWatchlist();
                wfProfileOut( __METHOD__ );
@@ -2378,7 +2426,6 @@ class EditPage {
                $this->showHeaderCopyrightWarning();
        }
 
-
        /**
         * Standard summary input and label (wgSummary), abstracted so EditPage
         * subclasses may reorganize the form.
@@ -2453,6 +2500,8 @@ class EditPage {
         * @return String
         */
        protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
+               // avoid spaces in preview, gets always trimmed on save
+               $summary = trim( $summary );
                if ( !$summary || ( !$this->preview && !$this->diff ) ) {
                        return "";
                }
@@ -2960,13 +3009,13 @@ HTML
 
                        if ( $this->mTriedSave && !$this->mTokenOk ) {
                                if ( $this->mTokenOkExceptSuffix ) {
-                                       $note = wfMessage( 'token_suffix_mismatch' )->plain() ;
+                                       $note = wfMessage( 'token_suffix_mismatch' )->plain();
 
                                } else {
-                                       $note = wfMessage( 'session_fail_preview' )->plain() ;
+                                       $note = wfMessage( 'session_fail_preview' )->plain();
                                }
                        } elseif ( $this->incompleteForm ) {
-                               $note = wfMessage( 'edit_form_incomplete' )->plain() ;
+                               $note = wfMessage( 'edit_form_incomplete' )->plain();
                        } else {
                                $note = wfMessage( 'previewnote' )->plain() .
                                        ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';