X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FEditPage.php;h=6bbeaac9b94bfc8d8ed3c24c0048c3265a3695cd;hb=a52726551b2e0e6106520288d1adb12cc2b864ab;hp=6b3c033f7dad4b8caccf0459762894a3f5d6f86d;hpb=9c9f7fd57fa32f032e31adb4030a37b01060e73e;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/EditPage.php b/includes/EditPage.php index 6b3c033f7d..6bbeaac9b9 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -23,7 +23,15 @@ class EditPage { var $formtype; var $firsttime; var $lastDelete; - var $mTokenOk = true; + var $mTokenOk = false; + var $mTriedSave = false; + var $tooBig = false; + var $kblength = false; + var $missingComment = false; + var $missingSummary = false; + var $allowBlankSummary = false; + var $autoSumm = ''; + var $hookError = ''; # Form values var $save = false, $preview = false, $diff = false; @@ -41,6 +49,74 @@ class EditPage { global $wgTitle; $this->mTitle =& $wgTitle; } + + /** + * Fetch initial editing page content. + */ + private function getContent() { + global $wgRequest, $wgParser; + + # Get variables from query string :P + $section = $wgRequest->getVal( 'section' ); + $preload = $wgRequest->getVal( 'preload' ); + + wfProfileIn( __METHOD__ ); + + $text = ''; + if( !$this->mTitle->exists() ) { + + # If requested, preload some text. + $text = $this->getPreloadedText( $preload ); + + # We used to put MediaWiki:Newarticletext here if + # $text was empty at this point. + # This is now shown above the edit box instead. + } else { + // FIXME: may be better to use Revision class directly + // But don't mess with it just yet. Article knows how to + // fetch the page record from the high-priority server, + // which is needed to guarantee we don't pick up lagged + // information. + + $text = $this->mArticle->getContent(); + + if( $section != '' ) { + if( $section == 'new' ) { + $text = $this->getPreloadedText( $preload ); + } else { + $text = $wgParser->getSection( $text, $section ); + } + } + } + + wfProfileOut( __METHOD__ ); + return $text; + } + + /** + * Get the contents of a page from its title and remove includeonly tags + * + * @param $preload String: the title of the page. + * @return string The contents of the page. + */ + private function getPreloadedText($preload) { + if ( $preload === '' ) + return ''; + else { + $preloadTitle = Title::newFromText( $preload ); + if ( isset( $preloadTitle ) && $preloadTitle->userCanRead() ) { + $rev=Revision::newFromTitle($preloadTitle); + if ( is_object( $rev ) ) { + $text = $rev->getText(); + // TODO FIXME: AAAAAAAAAAA, this shouldn't be implementing + // its own mini-parser! -ævar + $text = preg_replace( '~~', '', $text ); + return $text; + } else + return ''; + } + } + } /** * This is the function that extracts metadata from the article body on the first view. @@ -53,7 +129,7 @@ class EditPage { if ( !$wgUseMetadataEdit ) return ; if ( $wgMetadataWhitelist == '' ) return ; $s = '' ; - $t = $this->mArticle->getContent ( true ) ; + $t = $this->getContent(); # MISSING : filtering @@ -90,7 +166,7 @@ class EditPage { $sat = array () ; # stand-alone-templates; must be lowercase $wl_title = Title::newFromText ( $wgMetadataWhitelist ) ; $wl_article = new Article ( $wl_title ) ; - $wl = explode ( "\n" , $wl_article->getContent(true) ) ; + $wl = explode ( "\n" , $wl_article->getContent() ) ; foreach ( $wl AS $x ) { $isentry = false ; @@ -142,10 +218,10 @@ class EditPage { } /** - * This is the function that gets called for "action=edit". It - * sets up various member variables, then passes execution to + * This is the function that gets called for "action=edit". It + * sets up various member variables, then passes execution to * another function, usually showEditForm() - * + * * The edit form is self-submitting, so that when things like * preview and edit conflicts occur, we get the same form back * with the extra stuff added. Only when the final submission @@ -154,10 +230,11 @@ class EditPage { */ function edit() { global $wgOut, $wgUser, $wgRequest, $wgTitle; - + global $wgEmailConfirmToEdit; + if ( ! wfRunHooks( 'AlternateEdit', array( &$this ) ) ) return; - + $fname = 'EditPage::edit'; wfProfileIn( $fname ); wfDebug( "$fname: enter\n" ); @@ -176,7 +253,7 @@ class EditPage { if ( ! $this->mTitle->userCanEdit() ) { wfDebug( "$fname: user can't edit\n" ); - $wgOut->readOnlyPage( $this->mArticle->getContent( true ), true ); + $wgOut->readOnlyPage( $this->getContent(), true ); wfProfileOut( $fname ); return; } @@ -185,7 +262,7 @@ class EditPage { # When previewing, don't check blocked state - will get caught at save time. # Also, check when starting edition is done against slave to improve performance. wfDebug( "$fname: user is blocked\n" ); - $this->blockedIPpage(); + $this->blockedPage(); wfProfileOut( $fname ); return; } @@ -197,11 +274,23 @@ class EditPage { return; } else { wfDebug( "$fname: read-only page\n" ); - $wgOut->readOnlyPage( $this->mArticle->getContent( true ), true ); + $wgOut->readOnlyPage( $this->getContent(), true ); wfProfileOut( $fname ); return; } } + if ($wgEmailConfirmToEdit && !$wgUser->isEmailConfirmed()) { + wfDebug("$fname: user must confirm e-mail address\n"); + $this->userNotConfirmedPage(); + wfProfileOut($fname); + return; + } + if ( !$this->mTitle->userCanCreate() && !$this->mTitle->exists() ) { + wfDebug( "$fname: no create permission\n" ); + $this->noCreatePermission(); + wfProfileOut( $fname ); + return; + } if ( wfReadOnly() ) { wfDebug( "$fname: read-only mode is engaged\n" ); if( $this->save || $this->preview ) { @@ -209,7 +298,7 @@ class EditPage { } else if ( $this->diff ) { $this->formtype = 'diff'; } else { - $wgOut->readOnlyPage( $this->mArticle->getContent( true ) ); + $wgOut->readOnlyPage( $this->getContent() ); wfProfileOut( $fname ); return; } @@ -232,11 +321,12 @@ class EditPage { } wfProfileIn( "$fname-business-end" ); - + $this->isConflict = false; // css / js subpages of user pages get a special treatment - $this->isCssJsSubpage = $wgTitle->isCssJsSubpage(); - + $this->isCssJsSubpage = $wgTitle->isCssJsSubpage(); + $this->isValidCssJsSubpage = $wgTitle->isValidCssJsSubpage(); + /* Notice that we can't use isDeleted, because it returns true if article is ever deleted * no matter it's current state */ @@ -257,7 +347,7 @@ class EditPage { } } } - + if(!$this->mTitle->getArticleID() && ('initial' == $this->formtype || $this->firsttime )) { # new article $this->showIntro(); } @@ -277,11 +367,13 @@ class EditPage { return; } } - + # First time through: get contents, set time for conflict # checking, etc. if ( 'initial' == $this->formtype || $this->firsttime ) { $this->initialiseForm(); + if( !$this->mTitle->getArticleId() ) + wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) ); } $this->showEditForm(); @@ -293,20 +385,22 @@ class EditPage { * Return true if this page should be previewed when the edit form * is initially opened. * @return bool - * @access private + * @private */ function previewOnOpen() { global $wgUser; - return $wgUser->getOption( 'previewonfirst' ) || - ( $this->mTitle->getNamespace() == NS_CATEGORY && - !$this->mTitle->exists() ); + return $this->section != 'new' && + ( ( $wgUser->getOption( 'previewonfirst' ) && $this->mTitle->exists() ) || + ( $this->mTitle->getNamespace() == NS_CATEGORY && + !$this->mTitle->exists() ) ); } /** * @todo document + * @param $request */ function importFormData( &$request ) { - global $wgLang ; + global $wgLang, $wgUser; $fname = 'EditPage::importFormData'; wfProfileIn( $fname ); @@ -331,22 +425,28 @@ class EditPage { wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" ); $this->preview = true; } else { - $this->preview = $request->getCheck( 'wpPreview' ); + /* Fallback for live preview */ + $this->preview = $request->getCheck( 'wpPreview' ) || $request->getCheck( 'wpLivePreview' ); $this->diff = $request->getCheck( 'wpDiff' ); + + // Remember whether a save was requested, so we can indicate + // if we forced preview due to session failure. + $this->mTriedSave = !$this->preview; - if( !$this->preview ) { - if ( $this->tokenOk( $request ) ) { - # Some browsers will not report any submit button - # if the user hits enter in the comment box. - # The unmarked state will be assumed to be a save, - # if the form seems otherwise complete. - wfDebug( "$fname: Passed token check.\n" ); - } else { - # Page might be a hack attempt posted from - # an external site. Preview instead of saving. - wfDebug( "$fname: Failed token check; forcing preview\n" ); - $this->preview = true; - } + if ( $this->tokenOk( $request ) ) { + # Some browsers will not report any submit button + # if the user hits enter in the comment box. + # The unmarked state will be assumed to be a save, + # if the form seems otherwise complete. + wfDebug( "$fname: Passed token check.\n" ); + } else if ( $this->diff ) { + # Failed token check, but only requested "Show Changes". + wfDebug( "$fname: Failed token check; Show Changes requested.\n" ); + } else { + # Page might be a hack attempt posted from + # an external site. Preview instead of saving. + wfDebug( "$fname: Failed token check; forcing preview\n" ); + $this->preview = true; } } $this->save = ! ( $this->preview OR $this->diff ); @@ -357,11 +457,20 @@ class EditPage { if( !preg_match( '/^\d{14}$/', $this->starttime )) { $this->starttime = null; } - + $this->recreate = $request->getCheck( 'wpRecreate' ); $this->minoredit = $request->getCheck( 'wpMinoredit' ); $this->watchthis = $request->getCheck( 'wpWatchthis' ); + + # Don't force edit summaries when a user is editing their own user or talk page + if( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK ) && $this->mTitle->getText() == $wgUser->getName() ) { + $this->allowBlankSummary = true; + } else { + $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ); + } + + $this->autoSumm = $request->getText( 'wpAutoSummary' ); } else { # Not a posted form? Start with nothing. wfDebug( "$fname: Not a posted form.\n" ); @@ -386,16 +495,16 @@ class EditPage { $this->live = $request->getCheck( 'live' ); $this->editintro = $request->getText( 'editintro' ); - + wfProfileOut( $fname ); } /** * Make sure the form isn't faking a user's credentials. * - * @param WebRequest $request + * @param $request WebRequest * @return bool - * @access private + * @private */ function tokenOk( &$request ) { global $wgUser; @@ -409,6 +518,7 @@ class EditPage { return $this->mTokenOk; } + /** */ function showIntro() { global $wgOut, $wgUser; $addstandardintro=true; @@ -417,13 +527,13 @@ class EditPage { if(isset($introtitle) && $introtitle->userCanRead()) { $rev=Revision::newFromTitle($introtitle); if($rev) { - $wgOut->addWikiText($rev->getText()); + $wgOut->addSecondaryWikiText($rev->getText()); $addstandardintro=false; } } } if($addstandardintro) { - if ( $wgUser->isLoggedIn() ) + if ( $wgUser->isLoggedIn() ) $wgOut->addWikiText( wfMsg( 'newarticletext' ) ); else $wgOut->addWikiText( wfMsg( 'newarticletextanon' ) ); @@ -436,7 +546,8 @@ class EditPage { */ function attemptSave() { global $wgSpamRegex, $wgFilterCallback, $wgUser, $wgOut; - + global $wgMaxArticleSize; + $fname = 'EditPage::attemptSave'; wfProfileIn( $fname ); wfProfileIn( "$fname-checks" ); @@ -458,20 +569,33 @@ class EditPage { wfProfileOut( "$fname-checks" ); return false; } - if ( !wfRunHooks( 'EditFilter', array( &$this, $this->textbox1, $this->section ) ) ) { - # Error messages or other handling should be performed by the filter function + if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError ) ) ) { + # Error messages etc. could be handled within the hook... wfProfileOut( $fname ); wfProfileOut( "$fname-checks" ); return false; + } elseif( $this->hookError != '' ) { + # ...or the hook could be expecting us to produce an error + wfProfileOut( "$fname-checks " ); + wfProfileOut( $fname ); + return true; } if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) { # Check block state against master, thus 'false'. - $this->blockedIPpage(); + $this->blockedPage(); wfProfileOut( "$fname-checks" ); wfProfileOut( $fname ); return false; } - + $this->kblength = (int)(strlen( $this->textbox1 ) / 1024); + if ( $this->kblength > $wgMaxArticleSize ) { + // Error will be displayed by showEditForm() + $this->tooBig = true; + wfProfileOut( "$fname-checks" ); + wfProfileOut( $fname ); + return true; + } + if ( !$wgUser->isAllowed('edit') ) { if ( $wgUser->isAnon() ) { $this->userNotLoggedInPage(); @@ -507,12 +631,20 @@ class EditPage { wfProfileOut( $fname ); return true; } - + wfProfileOut( "$fname-checks" ); - + # If article is new, insert it. $aid = $this->mTitle->getArticleID( GAID_FOR_UPDATE ); if ( 0 == $aid ) { + // Late check for create permission, just in case *PARANOIA* + if ( !$this->mTitle->userCanCreate() ) { + wfDebug( "$fname: no create permission\n" ); + $this->noCreatePermission(); + wfProfileOut( $fname ); + return; + } + # Don't save a new article if it's blank. if ( ( '' == $this->textbox1 ) ) { $wgOut->redirect( $this->mTitle->getFullURL() ); @@ -520,10 +652,18 @@ class EditPage { return false; } + # If no edit comment was given when creating a new page, and what's being + # created is a redirect, be smart and fill in a neat auto-comment + if( $this->summary == '' ) { + $rt = Title::newFromRedirect( $this->textbox1 ); + if( is_object( $rt ) ) + $this->summary = wfMsgForContent( 'autoredircomment', $rt->getPrefixedText() ); + } + $isComment=($this->section=='new'); $this->mArticle->insertNewArticle( $this->textbox1, $this->summary, $this->minoredit, $this->watchthis, false, $isComment); - + wfProfileOut( $fname ); return false; } @@ -533,10 +673,21 @@ class EditPage { $this->mArticle->clear(); # Force reload of dates, etc. $this->mArticle->forUpdate( true ); # Lock the article - if( ( $this->section != 'new' ) && - ($this->mArticle->getTimestamp() != $this->edittime ) ) - { + if( $this->mArticle->getTimestamp() != $this->edittime ) { $this->isConflict = true; + if( $this->section == 'new' ) { + if( $this->mArticle->getUserText() == $wgUser->getName() && + $this->mArticle->getComment() == $this->summary ) { + // Probably a duplicate submission of a new comment. + // This can happen when squid resends a request after + // a timeout but the first one actually went through. + wfDebug( "EditPage::editForm duplicate new section submission; trigger edit conflict!\n" ); + } else { + // New comment; suppress conflict. + $this->isConflict = false; + wfDebug( "EditPage::editForm conflict suppressed; new section\n" ); + } + } } $userid = $wgUser->getID(); @@ -574,16 +725,37 @@ class EditPage { } } } - + if ( $this->isConflict ) { wfProfileOut( $fname ); return true; } - + + # If no edit comment was given when turning a page into a redirect, be smart + # and fill in a neat auto-comment + if( $this->summary == '' ) { + $rt = Title::newFromRedirect( $this->textbox1 ); + if( is_object( $rt ) ) + $this->summary = wfMsgForContent( 'autoredircomment', $rt->getPrefixedText() ); + } + + # Handle the user preference to force summaries here + if( $this->section != 'new' && !$this->allowBlankSummary && $wgUser->getOption( 'forceeditsummary' ) ) { + if( md5( $this->summary ) == $this->autoSumm ) { + $this->missingSummary = true; + wfProfileOut( $fname ); + return( true ); + } + } + # All's well wfProfileIn( "$fname-sectionanchor" ); $sectionanchor = ''; if( $this->section == 'new' ) { + if ( $this->textbox1 == '' ) { + $this->missingComment = true; + return true; + } if( $this->summary != '' ) { $sectionanchor = $this->sectionAnchor( $this->summary ); } @@ -607,6 +779,14 @@ class EditPage { $this->textbox1 = $text; $this->section = ''; + // 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; + wfProfileOut( $fname ); + return true; + } + # update the article here if( $this->mArticle->updateArticle( $text, $this->summary, $this->minoredit, $this->watchthis, '', $sectionanchor ) ) { @@ -625,8 +805,10 @@ class EditPage { */ function initialiseForm() { $this->edittime = $this->mArticle->getTimestamp(); - $this->textbox1 = $this->mArticle->getContent( true ); + $this->textbox1 = $this->getContent(); $this->summary = ''; + if ( !$this->mArticle->exists() && $this->mArticle->mTitle->getNamespace() == NS_MEDIAWIKI ) + $this->textbox1 = wfMsgWeirdKey( $this->mArticle->mTitle->getText() ) ; wfProxyCheck(); } @@ -637,13 +819,15 @@ class EditPage { * near the top, for captchas and the like. */ function showEditForm( $formCallback=null ) { - global $wgOut, $wgUser, $wgAllowAnonymousMinor, $wgLang, $wgContLang; + global $wgOut, $wgUser, $wgLang, $wgContLang, $wgMaxArticleSize; $fname = 'EditPage::showEditForm'; wfProfileIn( $fname ); $sk =& $wgUser->getSkin(); - + + wfRunHooks( 'EditPage::showEditForm:initial', array( &$this ) ) ; + $wgOut->setRobotpolicy( 'noindex,nofollow' ); # Enabled article-related sidebar, toplinks, etc. @@ -655,7 +839,7 @@ class EditPage { $wgOut->addWikiText( wfMsg( 'explainconflict' ) ); $this->textbox2 = $this->textbox1; - $this->textbox1 = $this->mArticle->getContent( true ); + $this->textbox1 = $this->getContent(); $this->edittime = $this->mArticle->getTimestamp(); } else { @@ -664,47 +848,87 @@ class EditPage { $s = wfMsg('editingcomment', $this->mTitle->getPrefixedText() ); } else { $s = wfMsg('editingsection', $this->mTitle->getPrefixedText() ); - if( !$this->preview && !$this->diff ) { + if( !$this->summary && !$this->preview && !$this->diff ) { preg_match( "/^(=+)(.+)\\1/mi", $this->textbox1, $matches ); if( !empty( $matches[2] ) ) { $this->summary = "/* ". trim($matches[2])." */ "; } - } + } } } else { $s = wfMsg( 'editing', $this->mTitle->getPrefixedText() ); } $wgOut->setPageTitle( $s ); + + if ( $this->missingComment ) { + $wgOut->addWikiText( wfMsg( 'missingcommenttext' ) ); + } + + if( $this->missingSummary ) { + $wgOut->addWikiText( wfMsg( 'missingsummary' ) ); + } + + if( !$this->hookError == '' ) { + $wgOut->addWikiText( $this->hookError ); + } + if ( !$this->checkUnicodeCompliantBrowser() ) { - $this->mArticle->setOldSubtitle(); $wgOut->addWikiText( wfMsg( 'nonunicodebrowser') ); } if ( isset( $this->mArticle ) && isset( $this->mArticle->mRevision ) && !$this->mArticle->mRevision->isCurrent() ) { - $this->mArticle->setOldSubtitle(); + $this->mArticle->setOldSubtitle( $this->mArticle->mRevision->getId() ); $wgOut->addWikiText( wfMsg( 'editingold' ) ); } } if( wfReadOnly() ) { $wgOut->addWikiText( wfMsg( 'readonlywarning' ) ); - } else if ( $this->isCssJsSubpage and 'preview' != $this->formtype) { - $wgOut->addWikiText( wfMsg( 'usercssjsyoucanpreview' )); + } elseif( $wgUser->isAnon() && $this->formtype != 'preview' ) { + $wgOut->addWikiText( wfMsg( 'anoneditwarning' ) ); + } else { + if( $this->isCssJsSubpage && $this->formtype != 'preview' ) { + # Check the skin exists + if( $this->isValidCssJsSubpage ) { + $wgOut->addWikiText( wfMsg( 'usercssjsyoucanpreview' ) ); + } else { + $wgOut->addWikiText( wfMsg( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) ); + } + } } - if( $this->mTitle->isProtected('edit') ) { - $wgOut->addWikiText( wfMsg( 'protectedpagewarning' ) ); + + if( $this->mTitle->isProtected( 'edit' ) ) { + # Is the protection due to the namespace, e.g. interface text? + if( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { + # Yes; remind the user + $notice = wfMsg( 'editinginterface' ); + } elseif( $this->mTitle->isSemiProtected() ) { + # No; semi protected + $notice = wfMsg( 'semiprotectedpagewarning' ); + if( wfEmptyMsg( 'semiprotectedpagewarning', $notice ) || $notice == '-' ) { + $notice = ''; + } + } else { + # No; regular protection + $notice = wfMsg( 'protectedpagewarning' ); + } + $wgOut->addWikiText( $notice ); } - $kblength = (int)(strlen( $this->textbox1 ) / 1024); - if( $kblength > 29 ) { - $wgOut->addWikiText( wfMsg( 'longpagewarning', $wgLang->formatNum( $kblength ) ) ); + if ( $this->kblength === false ) { + $this->kblength = (int)(strlen( $this->textbox1 ) / 1024); + } + if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) { + $wgOut->addWikiText( wfMsg( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgMaxArticleSize ) ); + } elseif( $this->kblength > 29 ) { + $wgOut->addWikiText( wfMsg( 'longpagewarning', $wgLang->formatNum( $this->kblength ) ) ); } - $rows = $wgUser->getOption( 'rows' ); - $cols = $wgUser->getOption( 'cols' ); + $rows = $wgUser->getIntOption( 'rows' ); + $cols = $wgUser->getIntOption( 'cols' ); $ew = $wgUser->getOption( 'editwidth' ); if ( $ew ) $ew = " style=\"width:100%\""; @@ -716,15 +940,12 @@ class EditPage { $summary = wfMsg('summary'); $subject = wfMsg('subject'); - $minor = wfMsg('minoredit'); - $watchthis = wfMsg ('watchthis'); - $save = wfMsg('savearticle'); - $prev = wfMsg('showpreview'); - $diff = wfMsg('showdiff'); + $minor = wfMsgExt('minoredit', array('parseinline')); + $watchthis = wfMsgExt('watchthis', array('parseinline')); $cancel = $sk->makeKnownLink( $this->mTitle->getPrefixedText(), - wfMsg('cancel') ); - $edithelpurl = $sk->makeInternalOrExternalUrl( wfMsg( 'edithelppage' )); + wfMsgExt('cancel', array('parseinline')) ); + $edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' )); $edithelp = ''. htmlspecialchars( wfMsg( 'edithelp' ) ).' '. htmlspecialchars( wfMsg( 'newwindow' ) ); @@ -744,22 +965,28 @@ class EditPage { // activate checkboxes if user wants them to be always active if( !$this->preview && !$this->diff ) { - if( $wgUser->getOption( 'watchdefault' ) ) $this->watchthis = true; + # Sort out the "watch" checkbox + if( $wgUser->getOption( 'watchdefault' ) ) { + # Watch all edits + $this->watchthis = true; + } elseif( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) { + # Watch creations + $this->watchthis = true; + } elseif( $this->mTitle->userIsWatching() ) { + # Already watched + $this->watchthis = true; + } + if( $wgUser->getOption( 'minordefault' ) ) $this->minoredit = true; - - // activate checkbox also if user is already watching the page, - // require wpWatchthis to be unset so that second condition is not - // checked unnecessarily - if( !$this->watchthis && $this->mTitle->userIsWatching() ) $this->watchthis = true; } $minoredithtml = ''; - if ( $wgUser->isLoggedIn() || $wgAllowAnonymousMinor ) { + if ( $wgUser->isAllowed('minoredit') ) { $minoredithtml = "minoredit?" checked='checked'":""). - " accesskey='".wfMsg('accesskey-minoredit')."' id='wpMinoredit' />". - ""; + " accesskey='".wfMsg('accesskey-minoredit')."' id='wpMinoredit' />\n". + "\n"; } $watchhtml = ''; @@ -767,18 +994,22 @@ class EditPage { if ( $wgUser->isLoggedIn() ) { $watchhtml = "watchthis?" checked='checked'":""). - " accesskey=\"".htmlspecialchars(wfMsg('accesskey-watch'))."\" id='wpWatchthis' />". + " accesskey=\"".htmlspecialchars(wfMsg('accesskey-watch'))."\" id='wpWatchthis' />\n". ""; + htmlspecialchars(wfMsg('tooltip-watch'))."\">{$watchthis}\n"; } $checkboxhtml = $minoredithtml . $watchhtml; - if ( 'preview' == $this->formtype && $wgUser->getOption( 'previewontop' ) ) { - $this->showPreview(); - } - if ( 'diff' == $this->formtype ) { - if ( $wgUser->getOption('previewontop' ) ) { + if ( $wgUser->getOption( 'previewontop' ) ) { + + if ( 'preview' == $this->formtype ) { + $this->showPreview(); + } else { + $wgOut->addHTML( '
' ); + } + + if ( 'diff' == $this->formtype ) { $wgOut->addHTML( $this->getDiff() ); } } @@ -788,32 +1019,24 @@ class EditPage { # Otherwise, show a summary field at the bottom $summarytext = htmlspecialchars( $wgContLang->recodeForEdit( $this->summary ) ); # FIXME if( $this->section == 'new' ) { - $commentsubject="

"; + $commentsubject="\n
\n
"; $editsummary = ''; } else { $commentsubject = ''; - $editsummary="

"; + $editsummary="\n
\n
"; } # Set focus to the edit box on load, except on preview or diff, where it would interfere with the display if( !$this->preview && !$this->diff ) { $wgOut->setOnloadHandler( 'document.editform.wpTextbox1.focus()' ); } - $templates = $this->getTemplatesUsed(); - - global $wgLivePreview; - if ( $wgLivePreview ) { - $liveOnclick = $this->doLivePreviewScript(); - } else { - $liveOnclick = ''; - } + $templates = $this->formatTemplates(); global $wgUseMetadataEdit ; if ( $wgUseMetadataEdit ) { $metadata = $this->mMetaData ; $metadata = htmlspecialchars( $wgContLang->recodeForEdit( $metadata ) ) ; - $helppage = Title::newFromText( wfMsg( "metadata_page" ) ) ; - $top = wfMsg( 'metadata', $helppage->getInternalURL() ); + $top = wfMsgWikiHtml( 'metadata_help' ); $metadata = $top . "" ; } else $metadata = "" ; @@ -835,19 +1058,86 @@ class EditPage { } } + $temp = array( + 'id' => 'wpSave', + 'name' => 'wpSave', + 'type' => 'submit', + 'tabindex' => '5', + 'value' => wfMsg('savearticle'), + 'accesskey' => wfMsg('accesskey-save'), + 'title' => wfMsg('tooltip-save'), + ); + $buttons['save'] = wfElement('input', $temp, ''); + $temp = array( + 'id' => 'wpDiff', + 'name' => 'wpDiff', + 'type' => 'submit', + 'tabindex' => '7', + 'value' => wfMsg('showdiff'), + 'accesskey' => wfMsg('accesskey-diff'), + 'title' => wfMsg('tooltip-diff'), + ); + $buttons['diff'] = wfElement('input', $temp, ''); + + global $wgLivePreview; + if ( $wgLivePreview && $wgUser->getOption( 'uselivepreview' ) ) { + $temp = array( + 'id' => 'wpPreview', + 'name' => 'wpPreview', + 'type' => 'submit', + 'tabindex' => '6', + 'value' => wfMsg('showpreview'), + 'accesskey' => '', + 'title' => wfMsg('tooltip-preview'), + 'style' => 'display: none;', + ); + $buttons['preview'] = wfElement('input', $temp, ''); + $temp = array( + 'id' => 'wpLivePreview', + 'name' => 'wpLivePreview', + 'type' => 'submit', + 'tabindex' => '6', + 'value' => wfMsg('showlivepreview'), + 'accesskey' => wfMsg('accesskey-preview'), + 'title' => '', + 'onclick' => $this->doLivePreviewScript(), + ); + $buttons['live'] = wfElement('input', $temp, ''); + } else { + $temp = array( + 'id' => 'wpPreview', + 'name' => 'wpPreview', + 'type' => 'submit', + 'tabindex' => '6', + 'value' => wfMsg('showpreview'), + 'accesskey' => wfMsg('accesskey-preview'), + 'title' => wfMsg('tooltip-preview'), + ); + $buttons['preview'] = wfElement('input', $temp, ''); + $buttons['live'] = ''; + } + $safemodehtml = $this->checkUnicodeCompliantBrowser() ? "" : "\n"; $wgOut->addHTML( << +
END ); + if( is_callable( $formCallback ) ) { call_user_func_array( $formCallback, array( &$wgOut ) ); } + + // Put these up at the top to ensure they aren't lost on early form submission + $wgOut->addHTML( " +section ) . "\" name=\"wpSection\" /> +starttime}\" name=\"wpStarttime\" />\n +edittime}\" name=\"wpEdittime\" />\n +scrolltop}\" name=\"wpScrolltop\" id=\"wpScrolltop\" />\n" ); + $wgOut->addHTML( << END . htmlspecialchars( $this->safeUnicodeOutput( $this->textbox1 ) ) . " -
+ + " ); + + $wgOut->addWikiText( $copywarn ); + $wgOut->addHTML( " {$metadata} {$editsummary} {$checkboxhtml} {$safemodehtml} -
- - - {$cancel} | {$edithelp}
-
+"); + + $wgOut->addHTML( +"
+ {$buttons['save']} + {$buttons['preview']} + {$buttons['live']} + {$buttons['diff']} + {$cancel} | {$edithelp} +
+
"); + + $wgOut->addWikiText( wfMsgForContent( 'edittools' ) ); + + $wgOut->addHTML( "
{$templates}
" ); - $wgOut->addWikiText( $copywarn ); - $wgOut->addHTML( " -section ) . "\" name=\"wpSection\" /> -starttime}\" name=\"wpStarttime\" />\n -edittime}\" name=\"wpEdittime\" />\n -scrolltop}\" name=\"wpScrolltop\" id=\"wpScrolltop\" />\n" ); if ( $wgUser->isLoggedIn() ) { /** @@ -893,9 +1188,20 @@ END $wgOut->addHTML( "\n\n" ); } + # If a blank edit summary was previously provided, and the appropriate + # user preference is active, pass a hidden tag here. This will stop the + # user being bounced back more than once in the event that a summary + # is not required. + if( $this->missingSummary ) { + $wgOut->addHTML( "\n" ); + } + + # For a bit more sophisticated detection of blank summaries, hash the + # automatic one and pass that in a hidden field. + $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary ); + $wgOut->addHtml( wfHidden( 'wpAutoSummary', $autosumm ) ); if ( $this->isConflict ) { - require_once( "DifferenceEngine.php" ); $wgOut->addWikiText( '==' . wfMsg( "yourdiff" ) . '==' ); $de = new DifferenceEngine( $this->mTitle ); @@ -907,21 +1213,27 @@ END . htmlspecialchars( $this->safeUnicodeOutput( $this->textbox2 ) ) . "\n" ); } $wgOut->addHTML( "\n" ); - if ( $this->formtype == 'preview' && !$wgUser->getOption( 'previewontop' ) ) { - $this->showPreview(); - } - if ( $this->formtype == 'diff' && !$wgUser->getOption( 'previewontop' ) ) { - #$wgOut->addHTML( '
' . $difftext . '
' ); - $wgOut->addHTML( $this->getDiff() ); + if ( !$wgUser->getOption( 'previewontop' ) ) { + + if ( $this->formtype == 'preview') { + $this->showPreview(); + } else { + $wgOut->addHTML( '
' ); + } + + if ( $this->formtype == 'diff') { + $wgOut->addHTML( $this->getDiff() ); + } + } wfProfileOut( $fname ); } - + /** * Append preview output to $wgOut. * Includes category rendering if this is a category page. - * @access private + * @private */ function showPreview() { global $wgOut; @@ -941,51 +1253,59 @@ END /** * Prepare a list of templates used by this page. Returns HTML. */ - function getTemplatesUsed() { + function formatTemplates() { global $wgUser; - $fname = 'EditPage::getTemplatesUsed'; + $fname = 'EditPage::formatTemplates'; wfProfileIn( $fname ); $sk =& $wgUser->getSkin(); - $templates = ''; - $articleTemplates = $this->mArticle->getUsedTemplates(); - if ( count( $articleTemplates ) > 0 ) { - $templates = '
'. wfMsg( 'templatesused' ) . '
    '; - foreach ( $articleTemplates as $tpl ) { - if ( $titleObj = Title::makeTitle( NS_TEMPLATE, $tpl ) ) { - $templates .= '
  • ' . $sk->makeLinkObj( $titleObj ) . '
  • '; - } + $outText = ''; + $templates = $this->mArticle->getUsedTemplates(); + if ( count( $templates ) > 0 ) { + # Do a batch existence check + $batch = new LinkBatch; + foreach( $templates as $title ) { + $batch->addObj( $title ); + } + $batch->execute(); + + # Construct the HTML + $outText = '
    ' . + wfMsgExt( 'templatesused', array( 'parse' ) ) . + '
      '; + foreach ( $templates as $titleObj ) { + $outText .= '
    • ' . $sk->makeLinkObj( $titleObj ) . '
    • '; } - $templates .= '
    '; + $outText .= '
'; } wfProfileOut( $fname ); - return $templates; + return $outText; } /** * Live Preview lets us fetch rendered preview page content and * add it to the page without refreshing the whole page. - * If not supported by the browser it will fall through to the normal form + * If not supported by the browser it will fall through to the normal form * submission method. - * - * This function outputs a script tag to support live preview, and - * returns an onclick handler which should be added to the attributes + * + * This function outputs a script tag to support live preview, and + * returns an onclick handler which should be added to the attributes * of the preview button */ function doLivePreviewScript() { - global $wgStylePath, $wgJsMimeType, $wgOut; + global $wgStylePath, $wgJsMimeType, $wgStyleVersion, $wgOut, $wgTitle; $wgOut->addHTML( '' . "\n" ); $liveAction = $wgTitle->getLocalUrl( 'action=submit&wpPreview=true&live=true' ); - return 'onclick="return !livePreview('. - 'getElementById(\'wikiPreview\'),' . - 'editform.wpTextbox1.value,' . - htmlspecialchars( '"' . $liveAction . '"' ) . ')"'; + return "return !livePreview(" . + "getElementById('wikiPreview')," . + "editform.wpTextbox1.value," . + '"' . $liveAction . '"' . ")"; } - + function getLastDelete() { $dbr =& wfGetDB( DB_SLAVE ); $fname = 'EditPage::getLastDelete'; @@ -1022,15 +1342,15 @@ END * @todo document */ function getPreviewText() { - global $wgOut, $wgUser, $wgTitle, $wgParser, $wgAllowDiffPreview, $wgEnableDiffPreviewPreference; + global $wgOut, $wgUser, $wgTitle, $wgParser; $fname = 'EditPage::getPreviewText'; wfProfileIn( $fname ); - if ( $this->mTokenOk ) { - $msg = 'previewnote'; - } else { + if ( $this->mTriedSave && !$this->mTokenOk ) { $msg = 'session_fail_preview'; + } else { + $msg = 'previewnote'; } $previewhead = '

' . htmlspecialchars( wfMsg( 'preview' ) ) . "

\n" . "
" . $wgOut->parse( wfMsg( $msg ) ) . "
\n"; @@ -1041,42 +1361,44 @@ END $parserOptions = ParserOptions::newFromUser( $wgUser ); $parserOptions->setEditSection( false ); + global $wgRawHtml; + if( $wgRawHtml && !$this->mTokenOk ) { + // Could be an offsite preview attempt. This is very unsafe if + // HTML is enabled, as it could be an attack. + return $wgOut->parse( "
" . + wfMsg( 'session_fail_preview_html' ) . "
" ); + } + # don't parse user css/js, show message about preview # XXX: stupid php bug won't let us use $wgTitle->isCssJsSubpage() here - + if ( $this->isCssJsSubpage ) { if(preg_match("/\\.css$/", $wgTitle->getText() ) ) { $previewtext = wfMsg('usercsspreview'); } else if(preg_match("/\\.js$/", $wgTitle->getText() ) ) { $previewtext = wfMsg('userjspreview'); } + $parserOptions->setTidy(true); $parserOutput = $wgParser->parse( $previewtext , $wgTitle, $parserOptions ); $wgOut->addHTML( $parserOutput->mText ); wfProfileOut( $fname ); return $previewhead; } else { - # if user want to see preview when he edit an article - if( $wgUser->getOption('previewonfirst') and ($this->textbox1 == '')) { - $this->textbox1 = $this->mArticle->getContent(true); - } - $toparse = $this->textbox1; - + # If we're adding a comment, we need to show the # summary as the headline if($this->section=="new" && $this->summary!="") { $toparse="== {$this->summary} ==\n\n".$toparse; } - - if ( $this->mMetaData != "" ) $toparse .= "\n" . $this->mMetaData ; + if ( $this->mMetaData != "" ) $toparse .= "\n" . $this->mMetaData ; + $parserOptions->setTidy(true); $parserOutput = $wgParser->parse( $this->mArticle->preSaveTransform( $toparse ) ."\n\n", $wgTitle, $parserOptions ); - $previewHTML = $parserOutput->mText; - - $wgOut->addCategoryLinks($parserOutput->getCategoryLinks()); - $wgOut->addLanguageLinks($parserOutput->getLanguageLinks()); + $previewHTML = $parserOutput->getText(); + $wgOut->addParserOutputNoText( $parserOutput ); wfProfileOut( $fname ); return $previewhead . $previewHTML; @@ -1084,64 +1406,87 @@ END } /** - * @todo document + * Call the stock "user is blocked" page */ - function blockedIPpage() { - global $wgOut, $wgUser, $wgContLang; - - $wgOut->setPageTitle( wfMsg( 'blockedtitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); - - $id = $wgUser->blockedBy(); - $reason = $wgUser->blockedFor(); - $ip = wfGetIP(); - - if ( is_numeric( $id ) ) { - $name = User::whoIs( $id ); + function blockedPage() { + global $wgOut, $wgUser; + $wgOut->blockedPage( false ); # Standard block notice on the top, don't 'return' + + # If the user made changes, preserve them when showing the markup + # (This happens when a user is blocked during edit, for instance) + $first = $this->firsttime || ( !$this->save && $this->textbox1 == '' ); + if( $first ) { + $source = $this->mTitle->exists() ? $this->getContent() : false; } else { - $name = $id; + $source = $this->textbox1; + } + + # Spit out the source or the user's modified version + if( $source !== false ) { + $rows = $wgUser->getOption( 'rows' ); + $cols = $wgUser->getOption( 'cols' ); + $attribs = array( 'id' => 'wpTextbox1', 'name' => 'wpTextbox1', 'cols' => $cols, 'rows' => $rows, 'readonly' => 'readonly' ); + $wgOut->addHtml( '
' ); + $wgOut->addWikiText( wfMsg( $first ? 'blockedoriginalsource' : 'blockededitsource', $this->mTitle->getPrefixedText() ) ); + $wgOut->addHtml( wfOpenElement( 'textarea', $attribs ) . htmlspecialchars( $source ) . wfCloseElement( 'textarea' ) ); } - $link = '[[' . $wgContLang->getNsText( NS_USER ) . - ":{$name}|{$name}]]"; - - $wgOut->addWikiText( wfMsg( 'blockedtext', $link, $reason, $ip, $name ) ); - $wgOut->returnToMain( false ); } /** - * @todo document + * Produce the stock "please login to edit pages" page */ function userNotLoggedInPage() { - global $wgOut; - + global $wgUser, $wgOut; + $skin = $wgUser->getSkin(); + + $loginTitle = SpecialPage::getTitleFor( 'Userlogin' ); + $loginLink = $skin->makeKnownLinkObj( $loginTitle, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $this->mTitle->getPrefixedUrl() ); + $wgOut->setPageTitle( wfMsg( 'whitelistedittitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); + + $wgOut->addHtml( wfMsgWikiHtml( 'whitelistedittext', $loginLink ) ); + $wgOut->returnToMain( false, $this->mTitle->getPrefixedUrl() ); + } + + /** + * Creates a basic error page which informs the user that + * they have to validate their email address before being + * allowed to edit. + */ + function userNotConfirmedPage() { + global $wgOut; - $wgOut->addWikiText( wfMsg( 'whitelistedittext' ) ); + $wgOut->setPageTitle( wfMsg( 'confirmedittitle' ) ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); + $wgOut->setArticleRelated( false ); + + $wgOut->addWikiText( wfMsg( 'confirmedittext' ) ); $wgOut->returnToMain( false ); } /** - * @todo document + * Produce the stock "your edit contains spam" page + * + * @param $match Text which triggered one or more filters */ - function spamPage ( $match = false ) - { + function spamPage( $match = false ) { global $wgOut; + $wgOut->setPageTitle( wfMsg( 'spamprotectiontitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); $wgOut->addWikiText( wfMsg( 'spamprotectiontext' ) ); - if ( $match ) { + if ( $match ) $wgOut->addWikiText( wfMsg( 'spamprotectionmatch', "{$match}" ) ); - } + $wgOut->returnToMain( false ); } /** - * @access private + * @private * @todo document */ function mergeChangesInto( &$editText ){ @@ -1183,7 +1528,7 @@ END * mangle UTF-8 data on form submission. Returns true if Unicode * should make it through, false if it's known to be a problem. * @return bool - * @access private + * @private */ function checkUnicodeCompliantBrowser() { global $wgBrowserBlackList; @@ -1204,7 +1549,7 @@ END * Format an anchor fragment as it would appear for a given section name * @param string $text * @return string - * @access private + * @private */ function sectionAnchor( $text ) { $headline = Sanitizer::decodeCharReferences( $text ); @@ -1228,7 +1573,7 @@ END * The necessary JavaScript code can be found in style/wikibits.js. */ function getEditToolbar() { - global $wgStylePath, $wgLang, $wgMimeType, $wgJsMimeType; + global $wgStylePath, $wgContLang, $wgJsMimeType; /** * toolarray an array of arrays which each include the filename of @@ -1278,29 +1623,29 @@ END 'key' => 'H' ), array( 'image'=>'button_image.png', - 'open' => '[['.$wgLang->getNsText(NS_IMAGE).":", + 'open' => '[['.$wgContLang->getNsText(NS_IMAGE).":", 'close' => ']]', 'sample'=> wfMsg('image_sample'), 'tip' => wfMsg('image_tip'), 'key' => 'D' ), array( 'image' =>'button_media.png', - 'open' => '[['.$wgLang->getNsText(NS_MEDIA).':', + 'open' => '[['.$wgContLang->getNsText(NS_MEDIA).':', 'close' => ']]', 'sample'=> wfMsg('media_sample'), 'tip' => wfMsg('media_tip'), 'key' => 'M' ), array( 'image' =>'button_math.png', - 'open' => "\\", - 'close' => "\\", + 'open' => "", + 'close' => "<\\/math>", 'sample'=> wfMsg('math_sample'), 'tip' => wfMsg('math_tip'), 'key' => 'C' ), array( 'image' =>'button_nowiki.png', - 'open' => "\\", - 'close' => "\\", + 'open' => "", + 'close' => "<\\/nowiki>", 'sample'=> wfMsg('nowiki_sample'), 'tip' => wfMsg('nowiki_tip'), 'key' => 'N' @@ -1320,9 +1665,9 @@ END 'key' => 'R' ) ); - $toolbar =""; + $toolbar.="\n
"; return $toolbar; } @@ -1364,7 +1709,9 @@ END header( 'Content-type: text/xml' ); header( 'Cache-control: no-cache' ); # FIXME - echo $this->getPreviewText( false, false ); + echo $this->getPreviewText( ); + /* To not shake screen up and down between preview and live-preview */ + echo "
\n"; } @@ -1378,14 +1725,12 @@ END * @return string HTML */ function getDiff() { - global $wgUser; - - require_once( 'DifferenceEngine.php' ); $oldtext = $this->mArticle->fetchContent(); $newtext = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary, $this->edittime ); - $oldtitle = wfMsg( 'currentrev' ); - $newtitle = wfMsg( 'yourtext' ); + $newtext = $this->mArticle->preSaveTransform( $newtext ); + $oldtitle = wfMsgExt( 'currentrev', array('parseinline') ); + $newtitle = wfMsgExt( 'yourtext', array('parseinline') ); if ( $oldtext !== false || $newtext != '' ) { $de = new DifferenceEngine( $this->mTitle ); $de->setText( $oldtext, $newtext ); @@ -1404,7 +1749,7 @@ END * @param WebRequest $request * @param string $field * @return string - * @access private + * @private */ function safeUnicodeInput( $request, $field ) { $text = rtrim( $request->getText( $field ) ); @@ -1412,14 +1757,14 @@ END ? $this->unmakesafe( $text ) : $text; } - + /** * Filter an output field through a Unicode armoring process if it is * going to an old browser with known broken Unicode editing issues. * * @param string $text * @return string - * @access private + * @private */ function safeUnicodeOutput( $text ) { global $wgContLang; @@ -1428,7 +1773,7 @@ END ? $codedText : $this->makesafe( $codedText ); } - + /** * A number of web browsers are known to corrupt non-ASCII characters * in a UTF-8 text editing environment. To protect against this, @@ -1440,12 +1785,12 @@ END * * @param string $invalue * @return string - * @access private + * @private */ function makesafe( $invalue ) { // Armor existing references for reversability. $invalue = strtr( $invalue, array( "&#x" => "�" ) ); - + $bytesleft = 0; $result = ""; $working = 0; @@ -1474,7 +1819,7 @@ END } return $result; } - + /** * Reverse the previously applied transliteration of non-ASCII characters * back to UTF-8. Used to protect data from corruption by broken web browsers @@ -1482,7 +1827,7 @@ END * * @param string $invalue * @return string - * @access private + * @private */ function unmakesafe( $invalue ) { $result = ""; @@ -1494,11 +1839,11 @@ END $hexstring .= $invalue{$i}; $i++; } while( ctype_xdigit( $invalue{$i} ) && ( $i < strlen( $invalue ) ) ); - + // Do some sanity checks. These aren't needed for reversability, - // but should help keep the breakage down if the editor + // but should help keep the breakage down if the editor // breaks one of the entities whilst editing. - if ((substr($invalue,$i,1)==";") and (strlen($hexstring) <= 6)) { + if ((substr($invalue,$i,1)==";") and (strlen($hexstring) <= 6)) { $codepoint = hexdec($hexstring); $result .= codepointToUtf8( $codepoint ); } else { @@ -1511,7 +1856,12 @@ END // reverse the transform that we made for reversability reasons. return strtr( $result, array( "�" => "&#x" ) ); } - + + function noCreatePermission() { + global $wgOut; + $wgOut->setPageTitle( wfMsg( 'nocreatetitle' ) ); + $wgOut->addWikiText( wfMsg( 'nocreatetext' ) ); + } }