MediaWiki  REL1_23
EditPage.php
Go to the documentation of this file.
00001 <?php
00038 class EditPage {
00042     const AS_SUCCESS_UPDATE = 200;
00043 
00047     const AS_SUCCESS_NEW_ARTICLE = 201;
00048 
00052     const AS_HOOK_ERROR = 210;
00053 
00057     const AS_HOOK_ERROR_EXPECTED = 212;
00058 
00062     const AS_BLOCKED_PAGE_FOR_USER = 215;
00063 
00067     const AS_CONTENT_TOO_BIG = 216;
00068 
00072     const AS_USER_CANNOT_EDIT = 217;
00073 
00077     const AS_READ_ONLY_PAGE_ANON = 218;
00078 
00082     const AS_READ_ONLY_PAGE_LOGGED = 219;
00083 
00087     const AS_READ_ONLY_PAGE = 220;
00088 
00092     const AS_RATE_LIMITED = 221;
00093 
00098     const AS_ARTICLE_WAS_DELETED = 222;
00099 
00104     const AS_NO_CREATE_PERMISSION = 223;
00105 
00109     const AS_BLANK_ARTICLE = 224;
00110 
00114     const AS_CONFLICT_DETECTED = 225;
00115 
00120     const AS_SUMMARY_NEEDED = 226;
00121 
00125     const AS_TEXTBOX_EMPTY = 228;
00126 
00130     const AS_MAX_ARTICLE_SIZE_EXCEEDED = 229;
00131 
00135     const AS_OK = 230;
00136 
00140     const AS_END = 231;
00141 
00145     const AS_SPAM_ERROR = 232;
00146 
00150     const AS_IMAGE_REDIRECT_ANON = 233;
00151 
00155     const AS_IMAGE_REDIRECT_LOGGED = 234;
00156 
00160     const AS_PARSE_ERROR = 240;
00161 
00165     const EDITFORM_ID = 'editform';
00166 
00171     const POST_EDIT_COOKIE_KEY_PREFIX = 'PostEditRevision';
00172 
00185     const POST_EDIT_COOKIE_DURATION = 1200;
00186 
00190     var $mArticle;
00191 
00195     var $mTitle;
00196     private $mContextTitle = null;
00197     var $action = 'submit';
00198     var $isConflict = false;
00199     var $isCssJsSubpage = false;
00200     var $isCssSubpage = false;
00201     var $isJsSubpage = false;
00202     var $isWrongCaseCssJsPage = false;
00203     var $isNew = false; // new page or new section
00204     var $deletedSinceEdit;
00205     var $formtype;
00206     var $firsttime;
00207     var $lastDelete;
00208     var $mTokenOk = false;
00209     var $mTokenOkExceptSuffix = false;
00210     var $mTriedSave = false;
00211     var $incompleteForm = false;
00212     var $tooBig = false;
00213     var $kblength = false;
00214     var $missingComment = false;
00215     var $missingSummary = false;
00216     var $allowBlankSummary = false;
00217     var $autoSumm = '';
00218     var $hookError = '';
00219     #var $mPreviewTemplates;
00220 
00224     var $mParserOutput;
00225 
00230     var $hasPresetSummary = false;
00231 
00232     var $mBaseRevision = false;
00233     var $mShowSummaryField = true;
00234 
00235     # Form values
00236     var $save = false, $preview = false, $diff = false;
00237     var $minoredit = false, $watchthis = false, $recreate = false;
00238     var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
00239     var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
00240     var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
00241     var $contentModel = null, $contentFormat = null;
00242 
00243     # Placeholders for text injection by hooks (must be HTML)
00244     # extensions should take care to _append_ to the present value
00245     public $editFormPageTop = ''; // Before even the preview
00246     public $editFormTextTop = '';
00247     public $editFormTextBeforeContent = '';
00248     public $editFormTextAfterWarn = '';
00249     public $editFormTextAfterTools = '';
00250     public $editFormTextBottom = '';
00251     public $editFormTextAfterContent = '';
00252     public $previewTextAfterContent = '';
00253     public $mPreloadContent = null;
00254 
00255     /* $didSave should be set to true whenever an article was successfully altered. */
00256     public $didSave = false;
00257     public $undidRev = 0;
00258 
00259     public $suppressIntro = false;
00260 
00266     public $allowNonTextContent = false;
00267 
00271     public function __construct( Article $article ) {
00272         $this->mArticle = $article;
00273         $this->mTitle = $article->getTitle();
00274 
00275         $this->contentModel = $this->mTitle->getContentModel();
00276 
00277         $handler = ContentHandler::getForModelID( $this->contentModel );
00278         $this->contentFormat = $handler->getDefaultFormat();
00279     }
00280 
00284     public function getArticle() {
00285         return $this->mArticle;
00286     }
00287 
00292     public function getTitle() {
00293         return $this->mTitle;
00294     }
00295 
00301     public function setContextTitle( $title ) {
00302         $this->mContextTitle = $title;
00303     }
00304 
00312     public function getContextTitle() {
00313         if ( is_null( $this->mContextTitle ) ) {
00314             global $wgTitle;
00315             return $wgTitle;
00316         } else {
00317             return $this->mContextTitle;
00318         }
00319     }
00320 
00328     public function isSupportedContentModel( $modelId ) {
00329         return $this->allowNonTextContent ||
00330             ContentHandler::getForModelID( $modelId ) instanceof TextContentHandler;
00331     }
00332 
00333     function submit() {
00334         $this->edit();
00335     }
00336 
00348     function edit() {
00349         global $wgOut, $wgRequest, $wgUser;
00350         // Allow extensions to modify/prevent this form or submission
00351         if ( !wfRunHooks( 'AlternateEdit', array( $this ) ) ) {
00352             return;
00353         }
00354 
00355         wfProfileIn( __METHOD__ );
00356         wfDebug( __METHOD__ . ": enter\n" );
00357 
00358         // If they used redlink=1 and the page exists, redirect to the main article
00359         if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
00360             $wgOut->redirect( $this->mTitle->getFullURL() );
00361             wfProfileOut( __METHOD__ );
00362             return;
00363         }
00364 
00365         $this->importFormData( $wgRequest );
00366         $this->firsttime = false;
00367 
00368         if ( $this->live ) {
00369             $this->livePreview();
00370             wfProfileOut( __METHOD__ );
00371             return;
00372         }
00373 
00374         if ( wfReadOnly() && $this->save ) {
00375             // Force preview
00376             $this->save = false;
00377             $this->preview = true;
00378         }
00379 
00380         if ( $this->save ) {
00381             $this->formtype = 'save';
00382         } elseif ( $this->preview ) {
00383             $this->formtype = 'preview';
00384         } elseif ( $this->diff ) {
00385             $this->formtype = 'diff';
00386         } else { # First time through
00387             $this->firsttime = true;
00388             if ( $this->previewOnOpen() ) {
00389                 $this->formtype = 'preview';
00390             } else {
00391                 $this->formtype = 'initial';
00392             }
00393         }
00394 
00395         $permErrors = $this->getEditPermissionErrors();
00396         if ( $permErrors ) {
00397             wfDebug( __METHOD__ . ": User can't edit\n" );
00398             // Auto-block user's IP if the account was "hard" blocked
00399             $wgUser->spreadAnyEditBlock();
00400 
00401             $this->displayPermissionsError( $permErrors );
00402 
00403             wfProfileOut( __METHOD__ );
00404             return;
00405         }
00406 
00407         wfProfileIn( __METHOD__ . "-business-end" );
00408 
00409         $this->isConflict = false;
00410         // css / js subpages of user pages get a special treatment
00411         $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
00412         $this->isCssSubpage = $this->mTitle->isCssSubpage();
00413         $this->isJsSubpage = $this->mTitle->isJsSubpage();
00414         $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
00415 
00416         # Show applicable editing introductions
00417         if ( $this->formtype == 'initial' || $this->firsttime ) {
00418             $this->showIntro();
00419         }
00420 
00421         # Attempt submission here.  This will check for edit conflicts,
00422         # and redundantly check for locked database, blocked IPs, etc.
00423         # that edit() already checked just in case someone tries to sneak
00424         # in the back door with a hand-edited submission URL.
00425 
00426         if ( 'save' == $this->formtype ) {
00427             if ( !$this->attemptSave() ) {
00428                 wfProfileOut( __METHOD__ . "-business-end" );
00429                 wfProfileOut( __METHOD__ );
00430                 return;
00431             }
00432         }
00433 
00434         # First time through: get contents, set time for conflict
00435         # checking, etc.
00436         if ( 'initial' == $this->formtype || $this->firsttime ) {
00437             if ( $this->initialiseForm() === false ) {
00438                 $this->noSuchSectionPage();
00439                 wfProfileOut( __METHOD__ . "-business-end" );
00440                 wfProfileOut( __METHOD__ );
00441                 return;
00442             }
00443 
00444             if ( !$this->mTitle->getArticleID() ) {
00445                 wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
00446             } else {
00447                 wfRunHooks( 'EditFormInitialText', array( $this ) );
00448             }
00449 
00450         }
00451 
00452         $this->showEditForm();
00453         wfProfileOut( __METHOD__ . "-business-end" );
00454         wfProfileOut( __METHOD__ );
00455     }
00456 
00460     protected function getEditPermissionErrors() {
00461         global $wgUser;
00462         $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
00463         # Can this title be created?
00464         if ( !$this->mTitle->exists() ) {
00465             $permErrors = array_merge( $permErrors,
00466                 wfArrayDiff2( $this->mTitle->getUserPermissionsErrors( 'create', $wgUser ), $permErrors ) );
00467         }
00468         # Ignore some permissions errors when a user is just previewing/viewing diffs
00469         $remove = array();
00470         foreach ( $permErrors as $error ) {
00471             if ( ( $this->preview || $this->diff )
00472                 && ( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' )
00473             ) {
00474                 $remove[] = $error;
00475             }
00476         }
00477         $permErrors = wfArrayDiff2( $permErrors, $remove );
00478         return $permErrors;
00479     }
00480 
00494     protected function displayPermissionsError( array $permErrors ) {
00495         global $wgRequest, $wgOut;
00496 
00497         if ( $wgRequest->getBool( 'redlink' ) ) {
00498             // The edit page was reached via a red link.
00499             // Redirect to the article page and let them click the edit tab if
00500             // they really want a permission error.
00501             $wgOut->redirect( $this->mTitle->getFullURL() );
00502             return;
00503         }
00504 
00505         $content = $this->getContentObject();
00506 
00507         # Use the normal message if there's nothing to display
00508         if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
00509             $action = $this->mTitle->exists() ? 'edit' :
00510                 ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
00511             throw new PermissionsError( $action, $permErrors );
00512         }
00513 
00514         $wgOut->setRobotPolicy( 'noindex,nofollow' );
00515         $wgOut->setPageTitle( wfMessage( 'viewsource-title', $this->getContextTitle()->getPrefixedText() ) );
00516         $wgOut->addBacklinkSubtitle( $this->getContextTitle() );
00517         $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) );
00518         $wgOut->addHTML( "<hr />\n" );
00519 
00520         # If the user made changes, preserve them when showing the markup
00521         # (This happens when a user is blocked during edit, for instance)
00522         if ( !$this->firsttime ) {
00523             $text = $this->textbox1;
00524             $wgOut->addWikiMsg( 'viewyourtext' );
00525         } else {
00526             $text = $this->toEditText( $content );
00527             $wgOut->addWikiMsg( 'viewsourcetext' );
00528         }
00529 
00530         $this->showTextbox( $text, 'wpTextbox1', array( 'readonly' ) );
00531 
00532         $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
00533             Linker::formatTemplates( $this->getTemplates() ) ) );
00534 
00535         $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
00536 
00537         if ( $this->mTitle->exists() ) {
00538             $wgOut->returnToMain( null, $this->mTitle );
00539         }
00540     }
00541 
00548     function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
00549         wfDeprecated( __METHOD__, '1.19' );
00550 
00551         global $wgRequest, $wgOut;
00552         if ( $wgRequest->getBool( 'redlink' ) ) {
00553             // The edit page was reached via a red link.
00554             // Redirect to the article page and let them click the edit tab if
00555             // they really want a permission error.
00556             $wgOut->redirect( $this->mTitle->getFullURL() );
00557         } else {
00558             $wgOut->readOnlyPage( $source, $protected, $reasons, $action );
00559         }
00560     }
00561 
00567     protected function previewOnOpen() {
00568         global $wgRequest, $wgUser, $wgPreviewOnOpenNamespaces;
00569         if ( $wgRequest->getVal( 'preview' ) == 'yes' ) {
00570             // Explicit override from request
00571             return true;
00572         } elseif ( $wgRequest->getVal( 'preview' ) == 'no' ) {
00573             // Explicit override from request
00574             return false;
00575         } elseif ( $this->section == 'new' ) {
00576             // Nothing *to* preview for new sections
00577             return false;
00578         } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) {
00579             // Standard preference behavior
00580             return true;
00581         } elseif ( !$this->mTitle->exists()
00582             && isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
00583             && $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()]
00584         ) {
00585             // Categories are special
00586             return true;
00587         } else {
00588             return false;
00589         }
00590     }
00591 
00598     protected function isWrongCaseCssJsPage() {
00599         if ( $this->mTitle->isCssJsSubpage() ) {
00600             $name = $this->mTitle->getSkinFromCssJsSubpage();
00601             $skins = array_merge(
00602                 array_keys( Skin::getSkinNames() ),
00603                 array( 'common' )
00604             );
00605             return !in_array( $name, $skins )
00606                 && in_array( strtolower( $name ), $skins );
00607         } else {
00608             return false;
00609         }
00610     }
00611 
00619     protected function isSectionEditSupported() {
00620         $contentHandler = ContentHandler::getForTitle( $this->mTitle );
00621         return $contentHandler->supportsSections();
00622     }
00623 
00629     function importFormData( &$request ) {
00630         global $wgContLang, $wgUser;
00631 
00632         wfProfileIn( __METHOD__ );
00633 
00634         # Section edit can come from either the form or a link
00635         $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
00636 
00637         if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) {
00638             wfProfileOut( __METHOD__ );
00639             throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
00640         }
00641 
00642         $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
00643 
00644         if ( $request->wasPosted() ) {
00645             # These fields need to be checked for encoding.
00646             # Also remove trailing whitespace, but don't remove _initial_
00647             # whitespace from the text boxes. This may be significant formatting.
00648             $this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' );
00649             if ( !$request->getCheck( 'wpTextbox2' ) ) {
00650                 // Skip this if wpTextbox2 has input, it indicates that we came
00651                 // from a conflict page with raw page text, not a custom form
00652                 // modified by subclasses
00653                 wfProfileIn( get_class( $this ) . "::importContentFormData" );
00654                 $textbox1 = $this->importContentFormData( $request );
00655                 if ( $textbox1 !== null ) {
00656                     $this->textbox1 = $textbox1;
00657                 }
00658 
00659                 wfProfileOut( get_class( $this ) . "::importContentFormData" );
00660             }
00661 
00662             # Truncate for whole multibyte characters
00663             $this->summary = $wgContLang->truncate( $request->getText( 'wpSummary' ), 255 );
00664 
00665             # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
00666             # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
00667             # section titles.
00668             $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
00669 
00670             # Treat sectiontitle the same way as summary.
00671             # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
00672             # currently doing double duty as both edit summary and section title. Right now this
00673             # is just to allow API edits to work around this limitation, but this should be
00674             # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
00675             $this->sectiontitle = $wgContLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
00676             $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
00677 
00678             $this->edittime = $request->getVal( 'wpEdittime' );
00679             $this->starttime = $request->getVal( 'wpStarttime' );
00680 
00681             $undidRev = $request->getInt( 'wpUndidRevision' );
00682             if ( $undidRev ) {
00683                 $this->undidRev = $undidRev;
00684             }
00685 
00686             $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
00687 
00688             if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
00689                 // wpTextbox1 field is missing, possibly due to being "too big"
00690                 // according to some filter rules such as Suhosin's setting for
00691                 // suhosin.request.max_value_length (d'oh)
00692                 $this->incompleteForm = true;
00693             } else {
00694                 // edittime should be one of our last fields; if it's missing,
00695                 // the submission probably broke somewhere in the middle.
00696                 $this->incompleteForm = is_null( $this->edittime );
00697             }
00698             if ( $this->incompleteForm ) {
00699                 # If the form is incomplete, force to preview.
00700                 wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
00701                 wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
00702                 $this->preview = true;
00703             } else {
00704                 /* Fallback for live preview */
00705                 $this->preview = $request->getCheck( 'wpPreview' ) || $request->getCheck( 'wpLivePreview' );
00706                 $this->diff = $request->getCheck( 'wpDiff' );
00707 
00708                 // Remember whether a save was requested, so we can indicate
00709                 // if we forced preview due to session failure.
00710                 $this->mTriedSave = !$this->preview;
00711 
00712                 if ( $this->tokenOk( $request ) ) {
00713                     # Some browsers will not report any submit button
00714                     # if the user hits enter in the comment box.
00715                     # The unmarked state will be assumed to be a save,
00716                     # if the form seems otherwise complete.
00717                     wfDebug( __METHOD__ . ": Passed token check.\n" );
00718                 } elseif ( $this->diff ) {
00719                     # Failed token check, but only requested "Show Changes".
00720                     wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
00721                 } else {
00722                     # Page might be a hack attempt posted from
00723                     # an external site. Preview instead of saving.
00724                     wfDebug( __METHOD__ . ": Failed token check; forcing preview\n" );
00725                     $this->preview = true;
00726                 }
00727             }
00728             $this->save = !$this->preview && !$this->diff;
00729             if ( !preg_match( '/^\d{14}$/', $this->edittime ) ) {
00730                 $this->edittime = null;
00731             }
00732 
00733             if ( !preg_match( '/^\d{14}$/', $this->starttime ) ) {
00734                 $this->starttime = null;
00735             }
00736 
00737             $this->recreate = $request->getCheck( 'wpRecreate' );
00738 
00739             $this->minoredit = $request->getCheck( 'wpMinoredit' );
00740             $this->watchthis = $request->getCheck( 'wpWatchthis' );
00741 
00742             # Don't force edit summaries when a user is editing their own user or talk page
00743             if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK )
00744                 && $this->mTitle->getText() == $wgUser->getName()
00745             ) {
00746                 $this->allowBlankSummary = true;
00747             } else {
00748                 $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ) || !$wgUser->getOption( 'forceeditsummary' );
00749             }
00750 
00751             $this->autoSumm = $request->getText( 'wpAutoSummary' );
00752         } else {
00753             # Not a posted form? Start with nothing.
00754             wfDebug( __METHOD__ . ": Not a posted form.\n" );
00755             $this->textbox1 = '';
00756             $this->summary = '';
00757             $this->sectiontitle = '';
00758             $this->edittime = '';
00759             $this->starttime = wfTimestampNow();
00760             $this->edit = false;
00761             $this->preview = false;
00762             $this->save = false;
00763             $this->diff = false;
00764             $this->minoredit = false;
00765             $this->watchthis = $request->getBool( 'watchthis', false ); // Watch may be overridden by request parameters
00766             $this->recreate = false;
00767 
00768             // When creating a new section, we can preload a section title by passing it as the
00769             // preloadtitle parameter in the URL (Bug 13100)
00770             if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
00771                 $this->sectiontitle = $request->getVal( 'preloadtitle' );
00772                 // Once wpSummary isn't being use for setting section titles, we should delete this.
00773                 $this->summary = $request->getVal( 'preloadtitle' );
00774             } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
00775                 $this->summary = $request->getText( 'summary' );
00776                 if ( $this->summary !== '' ) {
00777                     $this->hasPresetSummary = true;
00778                 }
00779             }
00780 
00781             if ( $request->getVal( 'minor' ) ) {
00782                 $this->minoredit = true;
00783             }
00784         }
00785 
00786         $this->oldid = $request->getInt( 'oldid' );
00787 
00788         $this->bot = $request->getBool( 'bot', true );
00789         $this->nosummary = $request->getBool( 'nosummary' );
00790 
00791         $this->contentModel = $request->getText( 'model', $this->contentModel ); #may be overridden by revision
00792         $this->contentFormat = $request->getText( 'format', $this->contentFormat ); #may be overridden by revision
00793 
00794         if ( !ContentHandler::getForModelID( $this->contentModel )->isSupportedFormat( $this->contentFormat ) ) {
00795             throw new ErrorPageError(
00796                 'editpage-notsupportedcontentformat-title',
00797                 'editpage-notsupportedcontentformat-text',
00798                 array( $this->contentFormat, ContentHandler::getLocalizedName( $this->contentModel ) )
00799             );
00800         }
00801         #TODO: check if the desired model is allowed in this namespace, and if a transition from the page's current model to the new model is allowed
00802 
00803         $this->live = $request->getCheck( 'live' );
00804         $this->editintro = $request->getText( 'editintro',
00805             // Custom edit intro for new sections
00806             $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
00807 
00808         // Allow extensions to modify form data
00809         wfRunHooks( 'EditPage::importFormData', array( $this, $request ) );
00810 
00811         wfProfileOut( __METHOD__ );
00812     }
00813 
00822     protected function importContentFormData( &$request ) {
00823         return; // Don't do anything, EditPage already extracted wpTextbox1
00824     }
00825 
00831     function initialiseForm() {
00832         global $wgUser;
00833         $this->edittime = $this->mArticle->getTimestamp();
00834 
00835         $content = $this->getContentObject( false ); #TODO: track content object?!
00836         if ( $content === false ) {
00837             return false;
00838         }
00839         $this->textbox1 = $this->toEditText( $content );
00840 
00841         // activate checkboxes if user wants them to be always active
00842         # Sort out the "watch" checkbox
00843         if ( $wgUser->getOption( 'watchdefault' ) ) {
00844             # Watch all edits
00845             $this->watchthis = true;
00846         } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
00847             # Watch creations
00848             $this->watchthis = true;
00849         } elseif ( $wgUser->isWatched( $this->mTitle ) ) {
00850             # Already watched
00851             $this->watchthis = true;
00852         }
00853         if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
00854             $this->minoredit = true;
00855         }
00856         if ( $this->textbox1 === false ) {
00857             return false;
00858         }
00859         return true;
00860     }
00861 
00870     function getContent( $def_text = false ) {
00871         ContentHandler::deprecated( __METHOD__, '1.21' );
00872 
00873         if ( $def_text !== null && $def_text !== false && $def_text !== '' ) {
00874             $def_content = $this->toEditContent( $def_text );
00875         } else {
00876             $def_content = false;
00877         }
00878 
00879         $content = $this->getContentObject( $def_content );
00880 
00881         // Note: EditPage should only be used with text based content anyway.
00882         return $this->toEditText( $content );
00883     }
00884 
00892     protected function getContentObject( $def_content = null ) {
00893         global $wgOut, $wgRequest, $wgUser, $wgContLang;
00894 
00895         wfProfileIn( __METHOD__ );
00896 
00897         $content = false;
00898 
00899         // For message page not locally set, use the i18n message.
00900         // For other non-existent articles, use preload text if any.
00901         if ( !$this->mTitle->exists() || $this->section == 'new' ) {
00902             if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
00903                 # If this is a system message, get the default text.
00904                 $msg = $this->mTitle->getDefaultMessageText();
00905 
00906                 $content = $this->toEditContent( $msg );
00907             }
00908             if ( $content === false ) {
00909                 # If requested, preload some text.
00910                 $preload = $wgRequest->getVal( 'preload',
00911                     // Custom preload text for new sections
00912                     $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
00913                 $params = $wgRequest->getArray( 'preloadparams', array() );
00914 
00915                 $content = $this->getPreloadedContent( $preload, $params );
00916             }
00917         // For existing pages, get text based on "undo" or section parameters.
00918         } else {
00919             if ( $this->section != '' ) {
00920                 // Get section edit text (returns $def_text for invalid sections)
00921                 $orig = $this->getOriginalContent( $wgUser );
00922                 $content = $orig ? $orig->getSection( $this->section ) : null;
00923 
00924                 if ( !$content ) {
00925                     $content = $def_content;
00926                 }
00927             } else {
00928                 $undoafter = $wgRequest->getInt( 'undoafter' );
00929                 $undo = $wgRequest->getInt( 'undo' );
00930 
00931                 if ( $undo > 0 && $undoafter > 0 ) {
00932 
00933                     $undorev = Revision::newFromId( $undo );
00934                     $oldrev = Revision::newFromId( $undoafter );
00935 
00936                     # Sanity check, make sure it's the right page,
00937                     # the revisions exist and they were not deleted.
00938                     # Otherwise, $content will be left as-is.
00939                     if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
00940                         !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
00941                         !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
00942 
00943                         $content = $this->mArticle->getUndoContent( $undorev, $oldrev );
00944 
00945                         if ( $content === false ) {
00946                             # Warn the user that something went wrong
00947                             $undoMsg = 'failure';
00948                         } else {
00949                             $oldContent = $this->mArticle->getPage()->getContent( Revision::RAW );
00950                             $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
00951                             $newContent = $content->preSaveTransform( $this->mTitle, $wgUser, $popts );
00952 
00953                             if ( $newContent->equals( $oldContent ) ) {
00954                                 # Tell the user that the undo results in no change,
00955                                 # i.e. the revisions were already undone.
00956                                 $undoMsg = 'nochange';
00957                                 $content = false;
00958                             } else {
00959                                 # Inform the user of our success and set an automatic edit summary
00960                                 $undoMsg = 'success';
00961 
00962                                 # If we just undid one rev, use an autosummary
00963                                 $firstrev = $oldrev->getNext();
00964                                 if ( $firstrev && $firstrev->getId() == $undo ) {
00965                                     $userText = $undorev->getUserText();
00966                                     if ( $userText === '' ) {
00967                                         $undoSummary = wfMessage(
00968                                             'undo-summary-username-hidden',
00969                                             $undo
00970                                         )->inContentLanguage()->text();
00971                                     } else {
00972                                         $undoSummary = wfMessage(
00973                                             'undo-summary',
00974                                             $undo,
00975                                             $userText
00976                                         )->inContentLanguage()->text();
00977                                     }
00978                                     if ( $this->summary === '' ) {
00979                                         $this->summary = $undoSummary;
00980                                     } else {
00981                                         $this->summary = $undoSummary . wfMessage( 'colon-separator' )
00982                                             ->inContentLanguage()->text() . $this->summary;
00983                                     }
00984                                     $this->undidRev = $undo;
00985                                 }
00986                                 $this->formtype = 'diff';
00987                             }
00988                         }
00989                     } else {
00990                         // Failed basic sanity checks.
00991                         // Older revisions may have been removed since the link
00992                         // was created, or we may simply have got bogus input.
00993                         $undoMsg = 'norev';
00994                     }
00995 
00996                     // Messages: undo-success, undo-failure, undo-norev, undo-nochange
00997                     $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
00998                     $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
00999                         wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
01000                 }
01001 
01002                 if ( $content === false ) {
01003                     $content = $this->getOriginalContent( $wgUser );
01004                 }
01005             }
01006         }
01007 
01008         wfProfileOut( __METHOD__ );
01009         return $content;
01010     }
01011 
01027     private function getOriginalContent( User $user ) {
01028         if ( $this->section == 'new' ) {
01029             return $this->getCurrentContent();
01030         }
01031         $revision = $this->mArticle->getRevisionFetched();
01032         if ( $revision === null ) {
01033             if ( !$this->contentModel ) {
01034                 $this->contentModel = $this->getTitle()->getContentModel();
01035             }
01036             $handler = ContentHandler::getForModelID( $this->contentModel );
01037 
01038             return $handler->makeEmptyContent();
01039         }
01040         $content = $revision->getContent( Revision::FOR_THIS_USER, $user );
01041         return $content;
01042     }
01043 
01052     protected function getCurrentContent() {
01053         $rev = $this->mArticle->getRevision();
01054         $content = $rev ? $rev->getContent( Revision::RAW ) : null;
01055 
01056         if ( $content === false || $content === null ) {
01057             if ( !$this->contentModel ) {
01058                 $this->contentModel = $this->getTitle()->getContentModel();
01059             }
01060             $handler = ContentHandler::getForModelID( $this->contentModel );
01061 
01062             return $handler->makeEmptyContent();
01063         } else {
01064             # nasty side-effect, but needed for consistency
01065             $this->contentModel = $rev->getContentModel();
01066             $this->contentFormat = $rev->getContentFormat();
01067 
01068             return $content;
01069         }
01070     }
01071 
01078     public function setPreloadedText( $text ) {
01079         ContentHandler::deprecated( __METHOD__, "1.21" );
01080 
01081         $content = $this->toEditContent( $text );
01082 
01083         $this->setPreloadedContent( $content );
01084     }
01085 
01093     public function setPreloadedContent( Content $content ) {
01094         $this->mPreloadContent = $content;
01095     }
01096 
01107     protected function getPreloadedText( $preload ) {
01108         ContentHandler::deprecated( __METHOD__, "1.21" );
01109 
01110         $content = $this->getPreloadedContent( $preload );
01111         $text = $this->toEditText( $content );
01112 
01113         return $text;
01114     }
01115 
01127     protected function getPreloadedContent( $preload, $params = array() ) {
01128         global $wgUser;
01129 
01130         if ( !empty( $this->mPreloadContent ) ) {
01131             return $this->mPreloadContent;
01132         }
01133 
01134         $handler = ContentHandler::getForTitle( $this->getTitle() );
01135 
01136         if ( $preload === '' ) {
01137             return $handler->makeEmptyContent();
01138         }
01139 
01140         $title = Title::newFromText( $preload );
01141         # Check for existence to avoid getting MediaWiki:Noarticletext
01142         if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
01143             //TODO: somehow show a warning to the user!
01144             return $handler->makeEmptyContent();
01145         }
01146 
01147         $page = WikiPage::factory( $title );
01148         if ( $page->isRedirect() ) {
01149             $title = $page->getRedirectTarget();
01150             # Same as before
01151             if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
01152                 //TODO: somehow show a warning to the user!
01153                 return $handler->makeEmptyContent();
01154             }
01155             $page = WikiPage::factory( $title );
01156         }
01157 
01158         $parserOptions = ParserOptions::newFromUser( $wgUser );
01159         $content = $page->getContent( Revision::RAW );
01160 
01161         if ( !$content ) {
01162             //TODO: somehow show a warning to the user!
01163             return $handler->makeEmptyContent();
01164         }
01165 
01166         if ( $content->getModel() !== $handler->getModelID() ) {
01167             $converted = $content->convert( $handler->getModelID() );
01168 
01169             if ( !$converted ) {
01170                 //TODO: somehow show a warning to the user!
01171                 wfDebug( "Attempt to preload incompatible content: "
01172                         . "can't convert " . $content->getModel()
01173                         . " to " . $handler->getModelID() );
01174 
01175                 return $handler->makeEmptyContent();
01176             }
01177 
01178             $content = $converted;
01179         }
01180 
01181         return $content->preloadTransform( $title, $parserOptions, $params );
01182     }
01183 
01191     function tokenOk( &$request ) {
01192         global $wgUser;
01193         $token = $request->getVal( 'wpEditToken' );
01194         $this->mTokenOk = $wgUser->matchEditToken( $token );
01195         $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
01196         return $this->mTokenOk;
01197     }
01198 
01215     protected function setPostEditCookie() {
01216         $revisionId = $this->mArticle->getLatest();
01217         $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
01218 
01219         $response = RequestContext::getMain()->getRequest()->response();
01220         $response->setcookie( $postEditKey, '1', time() + self::POST_EDIT_COOKIE_DURATION, array(
01221             'path' => '/',
01222             'httpOnly' => false,
01223         ) );
01224     }
01225 
01231     public function attemptSave() {
01232         global $wgUser;
01233 
01234         $resultDetails = false;
01235         # Allow bots to exempt some edits from bot flagging
01236         $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
01237         $status = $this->internalAttemptSave( $resultDetails, $bot );
01238 
01239         return $this->handleStatus( $status, $resultDetails );
01240     }
01241 
01251     private function handleStatus( Status $status, $resultDetails ) {
01252         global $wgUser, $wgOut;
01253 
01254         // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
01255         if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
01256             $this->didSave = true;
01257             if ( !$resultDetails['nullEdit'] ) {
01258                 $this->setPostEditCookie();
01259             }
01260         }
01261 
01262         switch ( $status->value ) {
01263             case self::AS_HOOK_ERROR_EXPECTED:
01264             case self::AS_CONTENT_TOO_BIG:
01265             case self::AS_ARTICLE_WAS_DELETED:
01266             case self::AS_CONFLICT_DETECTED:
01267             case self::AS_SUMMARY_NEEDED:
01268             case self::AS_TEXTBOX_EMPTY:
01269             case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
01270             case self::AS_END:
01271                 return true;
01272 
01273             case self::AS_HOOK_ERROR:
01274                 return false;
01275 
01276             case self::AS_PARSE_ERROR:
01277                 $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>' );
01278                 return true;
01279 
01280             case self::AS_SUCCESS_NEW_ARTICLE:
01281                 $query = $resultDetails['redirect'] ? 'redirect=no' : '';
01282                 $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
01283                 $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
01284                 return false;
01285 
01286             case self::AS_SUCCESS_UPDATE:
01287                 $extraQuery = '';
01288                 $sectionanchor = $resultDetails['sectionanchor'];
01289 
01290                 // Give extensions a chance to modify URL query on update
01291                 wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this->mArticle, &$sectionanchor, &$extraQuery ) );
01292 
01293                 if ( $resultDetails['redirect'] ) {
01294                     if ( $extraQuery == '' ) {
01295                         $extraQuery = 'redirect=no';
01296                     } else {
01297                         $extraQuery = 'redirect=no&' . $extraQuery;
01298                     }
01299                 }
01300                 $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
01301                 return false;
01302 
01303             case self::AS_BLANK_ARTICLE:
01304                 $wgOut->redirect( $this->getContextTitle()->getFullURL() );
01305                 return false;
01306 
01307             case self::AS_SPAM_ERROR:
01308                 $this->spamPageWithContent( $resultDetails['spam'] );
01309                 return false;
01310 
01311             case self::AS_BLOCKED_PAGE_FOR_USER:
01312                 throw new UserBlockedError( $wgUser->getBlock() );
01313 
01314             case self::AS_IMAGE_REDIRECT_ANON:
01315             case self::AS_IMAGE_REDIRECT_LOGGED:
01316                 throw new PermissionsError( 'upload' );
01317 
01318             case self::AS_READ_ONLY_PAGE_ANON:
01319             case self::AS_READ_ONLY_PAGE_LOGGED:
01320                 throw new PermissionsError( 'edit' );
01321 
01322             case self::AS_READ_ONLY_PAGE:
01323                 throw new ReadOnlyError;
01324 
01325             case self::AS_RATE_LIMITED:
01326                 throw new ThrottledError();
01327 
01328             case self::AS_NO_CREATE_PERMISSION:
01329                 $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
01330                 throw new PermissionsError( $permission );
01331 
01332             default:
01333                 // We don't recognize $status->value. The only way that can happen
01334                 // is if an extension hook aborted from inside ArticleSave.
01335                 // Render the status object into $this->hookError
01336                 // FIXME this sucks, we should just use the Status object throughout
01337                 $this->hookError = '<div class="error">' . $status->getWikitext() .
01338                     '</div>';
01339                 return true;
01340         }
01341     }
01342 
01352     protected function runPostMergeFilters( Content $content, Status $status, User $user ) {
01353         // Run old style post-section-merge edit filter
01354         if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged',
01355             array( $this, $content, &$this->hookError, $this->summary ) ) ) {
01356 
01357             # Error messages etc. could be handled within the hook...
01358             $status->fatal( 'hookaborted' );
01359             $status->value = self::AS_HOOK_ERROR;
01360             return false;
01361         } elseif ( $this->hookError != '' ) {
01362             # ...or the hook could be expecting us to produce an error
01363             $status->fatal( 'hookaborted' );
01364             $status->value = self::AS_HOOK_ERROR_EXPECTED;
01365             return false;
01366         }
01367 
01368         // Run new style post-section-merge edit filter
01369         if ( !wfRunHooks( 'EditFilterMergedContent',
01370             array( $this->mArticle->getContext(), $content, $status, $this->summary,
01371                 $user, $this->minoredit ) ) ) {
01372 
01373             # Error messages etc. could be handled within the hook...
01374             // XXX: $status->value may already be something informative...
01375             $this->hookError = $status->getWikiText();
01376             $status->fatal( 'hookaborted' );
01377             $status->value = self::AS_HOOK_ERROR;
01378             return false;
01379         } elseif ( !$status->isOK() ) {
01380             # ...or the hook could be expecting us to produce an error
01381             // FIXME this sucks, we should just use the Status object throughout
01382             $this->hookError = $status->getWikiText();
01383             $status->fatal( 'hookaborted' );
01384             $status->value = self::AS_HOOK_ERROR_EXPECTED;
01385             return false;
01386         }
01387 
01388         return true;
01389     }
01390 
01407     function internalAttemptSave( &$result, $bot = false ) {
01408         global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize;
01409 
01410         $status = Status::newGood();
01411 
01412         wfProfileIn( __METHOD__ );
01413         wfProfileIn( __METHOD__ . '-checks' );
01414 
01415         if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
01416             wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
01417             $status->fatal( 'hookaborted' );
01418             $status->value = self::AS_HOOK_ERROR;
01419             wfProfileOut( __METHOD__ . '-checks' );
01420             wfProfileOut( __METHOD__ );
01421             return $status;
01422         }
01423 
01424         $spam = $wgRequest->getText( 'wpAntispam' );
01425         if ( $spam !== '' ) {
01426             wfDebugLog(
01427                 'SimpleAntiSpam',
01428                 $wgUser->getName() .
01429                 ' editing "' .
01430                 $this->mTitle->getPrefixedText() .
01431                 '" submitted bogus field "' .
01432                 $spam .
01433                 '"'
01434             );
01435             $status->fatal( 'spamprotectionmatch', false );
01436             $status->value = self::AS_SPAM_ERROR;
01437             wfProfileOut( __METHOD__ . '-checks' );
01438             wfProfileOut( __METHOD__ );
01439             return $status;
01440         }
01441 
01442         try {
01443             # Construct Content object
01444             $textbox_content = $this->toEditContent( $this->textbox1 );
01445         } catch ( MWContentSerializationException $ex ) {
01446             $status->fatal( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
01447             $status->value = self::AS_PARSE_ERROR;
01448             wfProfileOut( __METHOD__ . '-checks' );
01449             wfProfileOut( __METHOD__ );
01450             return $status;
01451         }
01452 
01453         # Check image redirect
01454         if ( $this->mTitle->getNamespace() == NS_FILE &&
01455             $textbox_content->isRedirect() &&
01456             !$wgUser->isAllowed( 'upload' ) ) {
01457                 $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
01458                 $status->setResult( false, $code );
01459 
01460                 wfProfileOut( __METHOD__ . '-checks' );
01461                 wfProfileOut( __METHOD__ );
01462 
01463                 return $status;
01464         }
01465 
01466         # Check for spam
01467         $match = self::matchSummarySpamRegex( $this->summary );
01468         if ( $match === false && $this->section == 'new' ) {
01469             # $wgSpamRegex is enforced on this new heading/summary because, unlike
01470             # regular summaries, it is added to the actual wikitext.
01471             if ( $this->sectiontitle !== '' ) {
01472                 # This branch is taken when the API is used with the 'sectiontitle' parameter.
01473                 $match = self::matchSpamRegex( $this->sectiontitle );
01474             } else {
01475                 # This branch is taken when the "Add Topic" user interface is used, or the API
01476                 # is used with the 'summary' parameter.
01477                 $match = self::matchSpamRegex( $this->summary );
01478             }
01479         }
01480         if ( $match === false ) {
01481             $match = self::matchSpamRegex( $this->textbox1 );
01482         }
01483         if ( $match !== false ) {
01484             $result['spam'] = $match;
01485             $ip = $wgRequest->getIP();
01486             $pdbk = $this->mTitle->getPrefixedDBkey();
01487             $match = str_replace( "\n", '', $match );
01488             wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
01489             $status->fatal( 'spamprotectionmatch', $match );
01490             $status->value = self::AS_SPAM_ERROR;
01491             wfProfileOut( __METHOD__ . '-checks' );
01492             wfProfileOut( __METHOD__ );
01493             return $status;
01494         }
01495         if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) {
01496             # Error messages etc. could be handled within the hook...
01497             $status->fatal( 'hookaborted' );
01498             $status->value = self::AS_HOOK_ERROR;
01499             wfProfileOut( __METHOD__ . '-checks' );
01500             wfProfileOut( __METHOD__ );
01501             return $status;
01502         } elseif ( $this->hookError != '' ) {
01503             # ...or the hook could be expecting us to produce an error
01504             $status->fatal( 'hookaborted' );
01505             $status->value = self::AS_HOOK_ERROR_EXPECTED;
01506             wfProfileOut( __METHOD__ . '-checks' );
01507             wfProfileOut( __METHOD__ );
01508             return $status;
01509         }
01510 
01511         if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) {
01512             // Auto-block user's IP if the account was "hard" blocked
01513             $wgUser->spreadAnyEditBlock();
01514             # Check block state against master, thus 'false'.
01515             $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
01516             wfProfileOut( __METHOD__ . '-checks' );
01517             wfProfileOut( __METHOD__ );
01518             return $status;
01519         }
01520 
01521         $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
01522         if ( $this->kblength > $wgMaxArticleSize ) {
01523             // Error will be displayed by showEditForm()
01524             $this->tooBig = true;
01525             $status->setResult( false, self::AS_CONTENT_TOO_BIG );
01526             wfProfileOut( __METHOD__ . '-checks' );
01527             wfProfileOut( __METHOD__ );
01528             return $status;
01529         }
01530 
01531         if ( !$wgUser->isAllowed( 'edit' ) ) {
01532             if ( $wgUser->isAnon() ) {
01533                 $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
01534                 wfProfileOut( __METHOD__ . '-checks' );
01535                 wfProfileOut( __METHOD__ );
01536                 return $status;
01537             } else {
01538                 $status->fatal( 'readonlytext' );
01539                 $status->value = self::AS_READ_ONLY_PAGE_LOGGED;
01540                 wfProfileOut( __METHOD__ . '-checks' );
01541                 wfProfileOut( __METHOD__ );
01542                 return $status;
01543             }
01544         }
01545 
01546         if ( wfReadOnly() ) {
01547             $status->fatal( 'readonlytext' );
01548             $status->value = self::AS_READ_ONLY_PAGE;
01549             wfProfileOut( __METHOD__ . '-checks' );
01550             wfProfileOut( __METHOD__ );
01551             return $status;
01552         }
01553         if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 ) ) {
01554             $status->fatal( 'actionthrottledtext' );
01555             $status->value = self::AS_RATE_LIMITED;
01556             wfProfileOut( __METHOD__ . '-checks' );
01557             wfProfileOut( __METHOD__ );
01558             return $status;
01559         }
01560 
01561         # If the article has been deleted while editing, don't save it without
01562         # confirmation
01563         if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
01564             $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
01565             wfProfileOut( __METHOD__ . '-checks' );
01566             wfProfileOut( __METHOD__ );
01567             return $status;
01568         }
01569 
01570         wfProfileOut( __METHOD__ . '-checks' );
01571 
01572         # Load the page data from the master. If anything changes in the meantime,
01573         # we detect it by using page_latest like a token in a 1 try compare-and-swap.
01574         $this->mArticle->loadPageData( 'fromdbmaster' );
01575         $new = !$this->mArticle->exists();
01576 
01577         if ( $new ) {
01578             // Late check for create permission, just in case *PARANOIA*
01579             if ( !$this->mTitle->userCan( 'create', $wgUser ) ) {
01580                 $status->fatal( 'nocreatetext' );
01581                 $status->value = self::AS_NO_CREATE_PERMISSION;
01582                 wfDebug( __METHOD__ . ": no create permission\n" );
01583                 wfProfileOut( __METHOD__ );
01584                 return $status;
01585             }
01586 
01587             // Don't save a new page if it's blank or if it's a MediaWiki:
01588             // message with content equivalent to default (allow empty pages
01589             // in this case to disable messages, see bug 50124)
01590             $defaultMessageText = $this->mTitle->getDefaultMessageText();
01591             if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false ) {
01592                 $defaultText = $defaultMessageText;
01593             } else {
01594                 $defaultText = '';
01595             }
01596 
01597             if ( $this->textbox1 === $defaultText ) {
01598                 $status->setResult( false, self::AS_BLANK_ARTICLE );
01599                 wfProfileOut( __METHOD__ );
01600                 return $status;
01601             }
01602 
01603             if ( !$this->runPostMergeFilters( $textbox_content, $status, $wgUser ) ) {
01604                 wfProfileOut( __METHOD__ );
01605                 return $status;
01606             }
01607 
01608             $content = $textbox_content;
01609 
01610             $result['sectionanchor'] = '';
01611             if ( $this->section == 'new' ) {
01612                 if ( $this->sectiontitle !== '' ) {
01613                     // Insert the section title above the content.
01614                     $content = $content->addSectionHeader( $this->sectiontitle );
01615 
01616                     // Jump to the new section
01617                     $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
01618 
01619                     // If no edit summary was specified, create one automatically from the section
01620                     // title and have it link to the new section. Otherwise, respect the summary as
01621                     // passed.
01622                     if ( $this->summary === '' ) {
01623                         $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
01624                         $this->summary = wfMessage( 'newsectionsummary' )
01625                             ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
01626                     }
01627                 } elseif ( $this->summary !== '' ) {
01628                     // Insert the section title above the content.
01629                     $content = $content->addSectionHeader( $this->summary );
01630 
01631                     // Jump to the new section
01632                     $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
01633 
01634                     // Create a link to the new section from the edit summary.
01635                     $cleanSummary = $wgParser->stripSectionName( $this->summary );
01636                     $this->summary = wfMessage( 'newsectionsummary' )
01637                         ->rawParams( $cleanSummary )->inContentLanguage()->text();
01638                 }
01639             }
01640 
01641             $status->value = self::AS_SUCCESS_NEW_ARTICLE;
01642 
01643         } else { # not $new
01644 
01645             # Article exists. Check for edit conflict.
01646 
01647             $this->mArticle->clear(); # Force reload of dates, etc.
01648             $timestamp = $this->mArticle->getTimestamp();
01649 
01650             wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
01651 
01652             if ( $timestamp != $this->edittime ) {
01653                 $this->isConflict = true;
01654                 if ( $this->section == 'new' ) {
01655                     if ( $this->mArticle->getUserText() == $wgUser->getName() &&
01656                         $this->mArticle->getComment() == $this->summary ) {
01657                         // Probably a duplicate submission of a new comment.
01658                         // This can happen when squid resends a request after
01659                         // a timeout but the first one actually went through.
01660                         wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
01661                     } else {
01662                         // New comment; suppress conflict.
01663                         $this->isConflict = false;
01664                         wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
01665                     }
01666                 } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER, $this->mTitle->getArticleID(),
01667                             $wgUser->getId(), $this->edittime ) ) {
01668                     # Suppress edit conflict with self, except for section edits where merging is required.
01669                     wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
01670                     $this->isConflict = false;
01671                 }
01672             }
01673 
01674             // If sectiontitle is set, use it, otherwise use the summary as the section title.
01675             if ( $this->sectiontitle !== '' ) {
01676                 $sectionTitle = $this->sectiontitle;
01677             } else {
01678                 $sectionTitle = $this->summary;
01679             }
01680 
01681             $content = null;
01682 
01683             if ( $this->isConflict ) {
01684                 wfDebug( __METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
01685                         . " (article time '{$timestamp}')\n" );
01686 
01687                 $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime );
01688             } else {
01689                 wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
01690                 $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
01691             }
01692 
01693             if ( is_null( $content ) ) {
01694                 wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
01695                 $this->isConflict = true;
01696                 $content = $textbox_content; // do not try to merge here!
01697             } elseif ( $this->isConflict ) {
01698                 # Attempt merge
01699                 if ( $this->mergeChangesIntoContent( $content ) ) {
01700                     // Successful merge! Maybe we should tell the user the good news?
01701                     $this->isConflict = false;
01702                     wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
01703                 } else {
01704                     $this->section = '';
01705                     $this->textbox1 = ContentHandler::getContentText( $content );
01706                     wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
01707                 }
01708             }
01709 
01710             if ( $this->isConflict ) {
01711                 $status->setResult( false, self::AS_CONFLICT_DETECTED );
01712                 wfProfileOut( __METHOD__ );
01713                 return $status;
01714             }
01715 
01716             if ( !$this->runPostMergeFilters( $content, $status, $wgUser ) ) {
01717                 wfProfileOut( __METHOD__ );
01718                 return $status;
01719             }
01720 
01721             if ( $this->section == 'new' ) {
01722                 // Handle the user preference to force summaries here
01723                 if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) {
01724                     $this->missingSummary = true;
01725                     $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
01726                     $status->value = self::AS_SUMMARY_NEEDED;
01727                     wfProfileOut( __METHOD__ );
01728                     return $status;
01729                 }
01730 
01731                 // Do not allow the user to post an empty comment
01732                 if ( $this->textbox1 == '' ) {
01733                     $this->missingComment = true;
01734                     $status->fatal( 'missingcommenttext' );
01735                     $status->value = self::AS_TEXTBOX_EMPTY;
01736                     wfProfileOut( __METHOD__ );
01737                     return $status;
01738                 }
01739             } elseif ( !$this->allowBlankSummary
01740                 && !$content->equals( $this->getOriginalContent( $wgUser ) )
01741                 && !$content->isRedirect()
01742                 && md5( $this->summary ) == $this->autoSumm
01743             ) {
01744                 $this->missingSummary = true;
01745                 $status->fatal( 'missingsummary' );
01746                 $status->value = self::AS_SUMMARY_NEEDED;
01747                 wfProfileOut( __METHOD__ );
01748                 return $status;
01749             }
01750 
01751             # All's well
01752             wfProfileIn( __METHOD__ . '-sectionanchor' );
01753             $sectionanchor = '';
01754             if ( $this->section == 'new' ) {
01755                 if ( $this->sectiontitle !== '' ) {
01756                     $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
01757                     // If no edit summary was specified, create one automatically from the section
01758                     // title and have it link to the new section. Otherwise, respect the summary as
01759                     // passed.
01760                     if ( $this->summary === '' ) {
01761                         $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
01762                         $this->summary = wfMessage( 'newsectionsummary' )
01763                             ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
01764                     }
01765                 } elseif ( $this->summary !== '' ) {
01766                     $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
01767                     # This is a new section, so create a link to the new section
01768                     # in the revision summary.
01769                     $cleanSummary = $wgParser->stripSectionName( $this->summary );
01770                     $this->summary = wfMessage( 'newsectionsummary' )
01771                         ->rawParams( $cleanSummary )->inContentLanguage()->text();
01772                 }
01773             } elseif ( $this->section != '' ) {
01774                 # Try to get a section anchor from the section source, redirect to edited section if header found
01775                 # XXX: might be better to integrate this into Article::replaceSection
01776                 # for duplicate heading checking and maybe parsing
01777                 $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
01778                 # we can't deal with anchors, includes, html etc in the header for now,
01779                 # headline would need to be parsed to improve this
01780                 if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
01781                     $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
01782                 }
01783             }
01784             $result['sectionanchor'] = $sectionanchor;
01785             wfProfileOut( __METHOD__ . '-sectionanchor' );
01786 
01787             // Save errors may fall down to the edit form, but we've now
01788             // merged the section into full text. Clear the section field
01789             // so that later submission of conflict forms won't try to
01790             // replace that into a duplicated mess.
01791             $this->textbox1 = $this->toEditText( $content );
01792             $this->section = '';
01793 
01794             $status->value = self::AS_SUCCESS_UPDATE;
01795         }
01796 
01797         // Check for length errors again now that the section is merged in
01798             $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
01799         if ( $this->kblength > $wgMaxArticleSize ) {
01800             $this->tooBig = true;
01801             $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
01802             wfProfileOut( __METHOD__ );
01803             return $status;
01804         }
01805 
01806         $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
01807             ( $new ? EDIT_NEW : EDIT_UPDATE ) |
01808             ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
01809             ( $bot ? EDIT_FORCE_BOT : 0 );
01810 
01811             $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary, $flags,
01812                                                             false, null, $this->contentFormat );
01813 
01814         if ( !$doEditStatus->isOK() ) {
01815             // Failure from doEdit()
01816             // Show the edit conflict page for certain recognized errors from doEdit(),
01817             // but don't show it for errors from extension hooks
01818             $errors = $doEditStatus->getErrorsArray();
01819             if ( in_array( $errors[0][0],
01820                     array( 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ) )
01821             ) {
01822                 $this->isConflict = true;
01823                 // Destroys data doEdit() put in $status->value but who cares
01824                 $doEditStatus->value = self::AS_END;
01825             }
01826             wfProfileOut( __METHOD__ );
01827             return $doEditStatus;
01828         }
01829 
01830         $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
01831         if ( $result['nullEdit'] ) {
01832             // We don't know if it was a null edit until now, so increment here
01833             $wgUser->pingLimiter( 'linkpurge' );
01834         }
01835         $result['redirect'] = $content->isRedirect();
01836         $this->updateWatchlist();
01837         wfProfileOut( __METHOD__ );
01838         return $status;
01839     }
01840 
01844     protected function updateWatchlist() {
01845         global $wgUser;
01846 
01847         if ( $wgUser->isLoggedIn()
01848             && $this->watchthis != $wgUser->isWatched( $this->mTitle, WatchedItem::IGNORE_USER_RIGHTS )
01849         ) {
01850             $fname = __METHOD__;
01851             $title = $this->mTitle;
01852             $watch = $this->watchthis;
01853 
01854             // Do this in its own transaction to reduce contention...
01855             $dbw = wfGetDB( DB_MASTER );
01856             $dbw->onTransactionIdle( function() use ( $dbw, $title, $watch, $wgUser, $fname ) {
01857                 $dbw->begin( $fname );
01858                 WatchAction::doWatchOrUnwatch( $watch, $title, $wgUser );
01859                 $dbw->commit( $fname );
01860             } );
01861         }
01862     }
01863 
01872     function mergeChangesInto( &$editText ) {
01873         ContentHandler::deprecated( __METHOD__, "1.21" );
01874 
01875         $editContent = $this->toEditContent( $editText );
01876 
01877         $ok = $this->mergeChangesIntoContent( $editContent );
01878 
01879         if ( $ok ) {
01880             $editText = $this->toEditText( $editContent );
01881             return true;
01882         }
01883         return false;
01884     }
01885 
01897     private function mergeChangesIntoContent( &$editContent ) {
01898         wfProfileIn( __METHOD__ );
01899 
01900         $db = wfGetDB( DB_MASTER );
01901 
01902         // This is the revision the editor started from
01903         $baseRevision = $this->getBaseRevision();
01904         $baseContent = $baseRevision ? $baseRevision->getContent() : null;
01905 
01906         if ( is_null( $baseContent ) ) {
01907             wfProfileOut( __METHOD__ );
01908             return false;
01909         }
01910 
01911         // The current state, we want to merge updates into it
01912         $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
01913         $currentContent = $currentRevision ? $currentRevision->getContent() : null;
01914 
01915         if ( is_null( $currentContent ) ) {
01916             wfProfileOut( __METHOD__ );
01917             return false;
01918         }
01919 
01920         $handler = ContentHandler::getForModelID( $baseContent->getModel() );
01921 
01922         $result = $handler->merge3( $baseContent, $editContent, $currentContent );
01923 
01924         if ( $result ) {
01925             $editContent = $result;
01926             wfProfileOut( __METHOD__ );
01927             return true;
01928         }
01929 
01930         wfProfileOut( __METHOD__ );
01931         return false;
01932     }
01933 
01937     function getBaseRevision() {
01938         if ( !$this->mBaseRevision ) {
01939             $db = wfGetDB( DB_MASTER );
01940             $this->mBaseRevision = Revision::loadFromTimestamp(
01941                 $db, $this->mTitle, $this->edittime );
01942         }
01943         return $this->mBaseRevision;
01944     }
01945 
01953     public static function matchSpamRegex( $text ) {
01954         global $wgSpamRegex;
01955         // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
01956         $regexes = (array)$wgSpamRegex;
01957         return self::matchSpamRegexInternal( $text, $regexes );
01958     }
01959 
01967     public static function matchSummarySpamRegex( $text ) {
01968         global $wgSummarySpamRegex;
01969         $regexes = (array)$wgSummarySpamRegex;
01970         return self::matchSpamRegexInternal( $text, $regexes );
01971     }
01972 
01978     protected static function matchSpamRegexInternal( $text, $regexes ) {
01979         foreach ( $regexes as $regex ) {
01980             $matches = array();
01981             if ( preg_match( $regex, $text, $matches ) ) {
01982                 return $matches[0];
01983             }
01984         }
01985         return false;
01986     }
01987 
01988     function setHeaders() {
01989         global $wgOut, $wgUser;
01990 
01991         $wgOut->addModules( 'mediawiki.action.edit' );
01992         $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
01993 
01994         if ( $wgUser->getOption( 'uselivepreview', false ) ) {
01995             $wgOut->addModules( 'mediawiki.action.edit.preview' );
01996         }
01997 
01998         if ( $wgUser->getOption( 'useeditwarning', false ) ) {
01999             $wgOut->addModules( 'mediawiki.action.edit.editWarning' );
02000         }
02001 
02002         $wgOut->setRobotPolicy( 'noindex,nofollow' );
02003 
02004         # Enabled article-related sidebar, toplinks, etc.
02005         $wgOut->setArticleRelated( true );
02006 
02007         $contextTitle = $this->getContextTitle();
02008         if ( $this->isConflict ) {
02009             $msg = 'editconflict';
02010         } elseif ( $contextTitle->exists() && $this->section != '' ) {
02011             $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
02012         } else {
02013             $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() == NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !== false ) ?
02014                 'editing' : 'creating';
02015         }
02016         # Use the title defined by DISPLAYTITLE magic word when present
02017         $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
02018         if ( $displayTitle === false ) {
02019             $displayTitle = $contextTitle->getPrefixedText();
02020         }
02021         $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) );
02022     }
02023 
02027     protected function showIntro() {
02028         global $wgOut, $wgUser;
02029         if ( $this->suppressIntro ) {
02030             return;
02031         }
02032 
02033         $namespace = $this->mTitle->getNamespace();
02034 
02035         if ( $namespace == NS_MEDIAWIKI ) {
02036             # Show a warning if editing an interface message
02037             $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
02038         } elseif ( $namespace == NS_FILE ) {
02039             # Show a hint to shared repo
02040             $file = wfFindFile( $this->mTitle );
02041             if ( $file && !$file->isLocal() ) {
02042                 $descUrl = $file->getDescriptionUrl();
02043                 # there must be a description url to show a hint to shared repo
02044                 if ( $descUrl ) {
02045                     if ( !$this->mTitle->exists() ) {
02046                         $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", array(
02047                                     'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
02048                         ) );
02049                     } else {
02050                         $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", array(
02051                                     'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
02052                         ) );
02053                     }
02054                 }
02055             }
02056         }
02057 
02058         # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
02059         # Show log extract when the user is currently blocked
02060         if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
02061             $parts = explode( '/', $this->mTitle->getText(), 2 );
02062             $username = $parts[0];
02063             $user = User::newFromName( $username, false /* allow IP users*/ );
02064             $ip = User::isIP( $username );
02065             if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
02066                 $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
02067                     array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
02068             } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
02069                 LogEventsList::showLogExtract(
02070                     $wgOut,
02071                     'block',
02072                     $user->getUserPage(),
02073                     '',
02074                     array(
02075                         'lim' => 1,
02076                         'showIfEmpty' => false,
02077                         'msgKey' => array(
02078                             'blocked-notice-logextract',
02079                             $user->getName() # Support GENDER in notice
02080                         )
02081                     )
02082                 );
02083             }
02084         }
02085         # Try to add a custom edit intro, or use the standard one if this is not possible.
02086         if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
02087             $helpLink = wfExpandUrl( Skin::makeInternalOrExternalUrl(
02088                 wfMessage( 'helppage' )->inContentLanguage()->text()
02089             ) );
02090             if ( $wgUser->isLoggedIn() ) {
02091                 $wgOut->wrapWikiMsg(
02092                     // Suppress the external link icon, consider the help url an internal one
02093                     "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
02094                     array(
02095                         'newarticletext',
02096                         $helpLink
02097                     )
02098                 );
02099             } else {
02100                 $wgOut->wrapWikiMsg(
02101                     // Suppress the external link icon, consider the help url an internal one
02102                     "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
02103                     array(
02104                         'newarticletextanon',
02105                         $helpLink
02106                     )
02107                 );
02108             }
02109         }
02110         # Give a notice if the user is editing a deleted/moved page...
02111         if ( !$this->mTitle->exists() ) {
02112             LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle,
02113                 '',
02114                 array(
02115                     'lim' => 10,
02116                     'conds' => array( "log_action != 'revision'" ),
02117                     'showIfEmpty' => false,
02118                     'msgKey' => array( 'recreate-moveddeleted-warn' )
02119                 )
02120             );
02121         }
02122     }
02123 
02129     protected function showCustomIntro() {
02130         if ( $this->editintro ) {
02131             $title = Title::newFromText( $this->editintro );
02132             if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
02133                 global $wgOut;
02134                 // Added using template syntax, to take <noinclude>'s into account.
02135                 $wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
02136                 return true;
02137             }
02138         }
02139         return false;
02140     }
02141 
02158     protected function toEditText( $content ) {
02159         if ( $content === null || $content === false ) {
02160             return $content;
02161         }
02162 
02163         if ( is_string( $content ) ) {
02164             return $content;
02165         }
02166 
02167         if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
02168             throw new MWException( 'This content model is not supported: '
02169                 . ContentHandler::getLocalizedName( $content->getModel() ) );
02170         }
02171 
02172         return $content->serialize( $this->contentFormat );
02173     }
02174 
02189     protected function toEditContent( $text ) {
02190         if ( $text === false || $text === null ) {
02191             return $text;
02192         }
02193 
02194         $content = ContentHandler::makeContent( $text, $this->getTitle(),
02195             $this->contentModel, $this->contentFormat );
02196 
02197         if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
02198             throw new MWException( 'This content model is not supported: '
02199                 . ContentHandler::getLocalizedName( $content->getModel() ) );
02200         }
02201 
02202         return $content;
02203     }
02204 
02210     function showEditForm( $formCallback = null ) {
02211         global $wgOut, $wgUser;
02212 
02213         wfProfileIn( __METHOD__ );
02214 
02215         # need to parse the preview early so that we know which templates are used,
02216         # otherwise users with "show preview after edit box" will get a blank list
02217         # we parse this near the beginning so that setHeaders can do the title
02218         # setting work instead of leaving it in getPreviewText
02219         $previewOutput = '';
02220         if ( $this->formtype == 'preview' ) {
02221             $previewOutput = $this->getPreviewText();
02222         }
02223 
02224         wfRunHooks( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) );
02225 
02226         $this->setHeaders();
02227 
02228         if ( $this->showHeader() === false ) {
02229             wfProfileOut( __METHOD__ );
02230             return;
02231         }
02232 
02233         $wgOut->addHTML( $this->editFormPageTop );
02234 
02235         if ( $wgUser->getOption( 'previewontop' ) ) {
02236             $this->displayPreviewArea( $previewOutput, true );
02237         }
02238 
02239         $wgOut->addHTML( $this->editFormTextTop );
02240 
02241         $showToolbar = true;
02242         if ( $this->wasDeletedSinceLastEdit() ) {
02243             if ( $this->formtype == 'save' ) {
02244                 // Hide the toolbar and edit area, user can click preview to get it back
02245                 // Add an confirmation checkbox and explanation.
02246                 $showToolbar = false;
02247             } else {
02248                 $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
02249                     'deletedwhileediting' );
02250             }
02251         }
02252 
02253         // @todo add EditForm plugin interface and use it here!
02254         //       search for textarea1 and textares2, and allow EditForm to override all uses.
02255         $wgOut->addHTML( Html::openElement( 'form', array( 'id' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID,
02256             'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
02257             'enctype' => 'multipart/form-data' ) ) );
02258 
02259         if ( is_callable( $formCallback ) ) {
02260             call_user_func_array( $formCallback, array( &$wgOut ) );
02261         }
02262 
02263         // Add an empty field to trip up spambots
02264         $wgOut->addHTML(
02265             Xml::openElement( 'div', array( 'id' => 'antispam-container', 'style' => 'display: none;' ) )
02266             . Html::rawElement( 'label', array( 'for' => 'wpAntiSpam' ), wfMessage( 'simpleantispam-label' )->parse() )
02267             . Xml::element( 'input', array( 'type' => 'text', 'name' => 'wpAntispam', 'id' => 'wpAntispam', 'value' => '' ) )
02268             . Xml::closeElement( 'div' )
02269         );
02270 
02271         wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
02272 
02273         // Put these up at the top to ensure they aren't lost on early form submission
02274         $this->showFormBeforeText();
02275 
02276         if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
02277             $username = $this->lastDelete->user_name;
02278             $comment = $this->lastDelete->log_comment;
02279 
02280             // It is better to not parse the comment at all than to have templates expanded in the middle
02281             // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
02282             $key = $comment === ''
02283                 ? 'confirmrecreate-noreason'
02284                 : 'confirmrecreate';
02285             $wgOut->addHTML(
02286                 '<div class="mw-confirm-recreate">' .
02287                     wfMessage( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
02288                 Xml::checkLabel( wfMessage( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
02289                     array( 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
02290                 ) .
02291                 '</div>'
02292             );
02293         }
02294 
02295         # When the summary is hidden, also hide them on preview/show changes
02296         if ( $this->nosummary ) {
02297             $wgOut->addHTML( Html::hidden( 'nosummary', true ) );
02298         }
02299 
02300         # If a blank edit summary was previously provided, and the appropriate
02301         # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
02302         # user being bounced back more than once in the event that a summary
02303         # is not required.
02304         #####
02305         # For a bit more sophisticated detection of blank summaries, hash the
02306         # automatic one and pass that in the hidden field wpAutoSummary.
02307         if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
02308             $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
02309         }
02310 
02311         if ( $this->undidRev ) {
02312             $wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
02313         }
02314 
02315         if ( $this->hasPresetSummary ) {
02316             // If a summary has been preset using &summary= we don't want to prompt for
02317             // a different summary. Only prompt for a summary if the summary is blanked.
02318             // (Bug 17416)
02319             $this->autoSumm = md5( '' );
02320         }
02321 
02322         $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
02323         $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
02324 
02325         $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
02326 
02327         $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
02328         $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
02329 
02330         if ( $this->section == 'new' ) {
02331             $this->showSummaryInput( true, $this->summary );
02332             $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
02333         }
02334 
02335         $wgOut->addHTML( $this->editFormTextBeforeContent );
02336 
02337         if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
02338             $wgOut->addHTML( EditPage::getEditToolbar() );
02339         }
02340 
02341         if ( $this->isConflict ) {
02342             // In an edit conflict bypass the overridable content form method
02343             // and fallback to the raw wpTextbox1 since editconflicts can't be
02344             // resolved between page source edits and custom ui edits using the
02345             // custom edit ui.
02346             $this->textbox2 = $this->textbox1;
02347 
02348             $content = $this->getCurrentContent();
02349             $this->textbox1 = $this->toEditText( $content );
02350 
02351             $this->showTextbox1();
02352         } else {
02353             $this->showContentForm();
02354         }
02355 
02356         $wgOut->addHTML( $this->editFormTextAfterContent );
02357 
02358         $this->showStandardInputs();
02359 
02360         $this->showFormAfterText();
02361 
02362         $this->showTosSummary();
02363 
02364         $this->showEditTools();
02365 
02366         $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
02367 
02368         $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
02369             Linker::formatTemplates( $this->getTemplates(), $this->preview, $this->section != '' ) ) );
02370 
02371         $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'hiddencats' ),
02372             Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
02373 
02374         $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'limitreport' ),
02375             self::getPreviewLimitReport( $this->mParserOutput ) ) );
02376 
02377         $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
02378 
02379         if ( $this->isConflict ) {
02380             try {
02381                 $this->showConflict();
02382             } catch ( MWContentSerializationException $ex ) {
02383                 // this can't really happen, but be nice if it does.
02384                 $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
02385                 $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
02386             }
02387         }
02388 
02389         $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
02390 
02391         if ( !$wgUser->getOption( 'previewontop' ) ) {
02392             $this->displayPreviewArea( $previewOutput, false );
02393         }
02394 
02395         wfProfileOut( __METHOD__ );
02396     }
02397 
02404     public static function extractSectionTitle( $text ) {
02405         preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
02406         if ( !empty( $matches[2] ) ) {
02407             global $wgParser;
02408             return $wgParser->stripSectionName( trim( $matches[2] ) );
02409         } else {
02410             return false;
02411         }
02412     }
02413 
02414     protected function showHeader() {
02415         global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang;
02416 
02417         if ( $this->mTitle->isTalkPage() ) {
02418             $wgOut->addWikiMsg( 'talkpagetext' );
02419         }
02420 
02421         // Add edit notices
02422         $wgOut->addHTML( implode( "\n", $this->mTitle->getEditNotices( $this->oldid ) ) );
02423 
02424         if ( $this->isConflict ) {
02425             $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
02426             $this->edittime = $this->mArticle->getTimestamp();
02427         } else {
02428             if ( $this->section != '' && !$this->isSectionEditSupported() ) {
02429                 // We use $this->section to much before this and getVal('wgSection') directly in other places
02430                 // at this point we can't reset $this->section to '' to fallback to non-section editing.
02431                 // Someone is welcome to try refactoring though
02432                 $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
02433                 return false;
02434             }
02435 
02436             if ( $this->section != '' && $this->section != 'new' ) {
02437                 if ( !$this->summary && !$this->preview && !$this->diff ) {
02438                     $sectionTitle = self::extractSectionTitle( $this->textbox1 ); //FIXME: use Content object
02439                     if ( $sectionTitle !== false ) {
02440                         $this->summary = "/* $sectionTitle */ ";
02441                     }
02442                 }
02443             }
02444 
02445             if ( $this->missingComment ) {
02446                 $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
02447             }
02448 
02449             if ( $this->missingSummary && $this->section != 'new' ) {
02450                 $wgOut->wrapWikiMsg( "<div id='mw-missingsummary'>\n$1\n</div>", 'missingsummary' );
02451             }
02452 
02453             if ( $this->missingSummary && $this->section == 'new' ) {
02454                 $wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1\n</div>", 'missingcommentheader' );
02455             }
02456 
02457             if ( $this->hookError !== '' ) {
02458                 $wgOut->addWikiText( $this->hookError );
02459             }
02460 
02461             if ( !$this->checkUnicodeCompliantBrowser() ) {
02462                 $wgOut->addWikiMsg( 'nonunicodebrowser' );
02463             }
02464 
02465             if ( $this->section != 'new' ) {
02466                 $revision = $this->mArticle->getRevisionFetched();
02467                 if ( $revision ) {
02468                     // Let sysop know that this will make private content public if saved
02469 
02470                     if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) {
02471                         $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
02472                     } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
02473                         $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
02474                     }
02475 
02476                     if ( !$revision->isCurrent() ) {
02477                         $this->mArticle->setOldSubtitle( $revision->getId() );
02478                         $wgOut->addWikiMsg( 'editingold' );
02479                     }
02480                 } elseif ( $this->mTitle->exists() ) {
02481                     // Something went wrong
02482 
02483                     $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
02484                         array( 'missing-revision', $this->oldid ) );
02485                 }
02486             }
02487         }
02488 
02489         if ( wfReadOnly() ) {
02490             $wgOut->wrapWikiMsg( "<div id=\"mw-read-only-warning\">\n$1\n</div>", array( 'readonlywarning', wfReadOnlyReason() ) );
02491         } elseif ( $wgUser->isAnon() ) {
02492             if ( $this->formtype != 'preview' ) {
02493                 $wgOut->wrapWikiMsg( "<div id=\"mw-anon-edit-warning\">\n$1</div>", 'anoneditwarning' );
02494             } else {
02495                 $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>", 'anonpreviewwarning' );
02496             }
02497         } else {
02498             if ( $this->isCssJsSubpage ) {
02499                 # Check the skin exists
02500                 if ( $this->isWrongCaseCssJsPage ) {
02501                     $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) );
02502                 }
02503                 if ( $this->formtype !== 'preview' ) {
02504                     if ( $this->isCssSubpage ) {
02505                         $wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) );
02506                     }
02507 
02508                     if ( $this->isJsSubpage ) {
02509                         $wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) );
02510                     }
02511                 }
02512             }
02513         }
02514 
02515         if ( $this->mTitle->isProtected( 'edit' ) &&
02516             MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
02517         ) {
02518             # Is the title semi-protected?
02519             if ( $this->mTitle->isSemiProtected() ) {
02520                 $noticeMsg = 'semiprotectedpagewarning';
02521             } else {
02522                 # Then it must be protected based on static groups (regular)
02523                 $noticeMsg = 'protectedpagewarning';
02524             }
02525             LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
02526                 array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) );
02527         }
02528         if ( $this->mTitle->isCascadeProtected() ) {
02529             # Is this page under cascading protection from some source pages?
02530             list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
02531             $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
02532             $cascadeSourcesCount = count( $cascadeSources );
02533             if ( $cascadeSourcesCount > 0 ) {
02534                 # Explain, and list the titles responsible
02535                 foreach ( $cascadeSources as $page ) {
02536                     $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
02537                 }
02538             }
02539             $notice .= '</div>';
02540             $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', $cascadeSourcesCount ) );
02541         }
02542         if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
02543             LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
02544                 array( 'lim' => 1,
02545                     'showIfEmpty' => false,
02546                     'msgKey' => array( 'titleprotectedwarning' ),
02547                     'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ) );
02548         }
02549 
02550         if ( $this->kblength === false ) {
02551             $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
02552         }
02553 
02554         if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
02555             $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
02556                 array( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) ) );
02557         } else {
02558             if ( !wfMessage( 'longpage-hint' )->isDisabled() ) {
02559                 $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
02560                     array( 'longpage-hint', $wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) )
02561                 );
02562             }
02563         }
02564         # Add header copyright warning
02565         $this->showHeaderCopyrightWarning();
02566     }
02567 
02582     function getSummaryInput( $summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null ) {
02583         // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
02584         $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : array() ) + array(
02585             'id' => 'wpSummary',
02586             'maxlength' => '200',
02587             'tabindex' => '1',
02588             'size' => 60,
02589             'spellcheck' => 'true',
02590         ) + Linker::tooltipAndAccesskeyAttribs( 'summary' );
02591 
02592         $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : array() ) + array(
02593             'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
02594             'id' => "wpSummaryLabel"
02595         );
02596 
02597         $label = null;
02598         if ( $labelText ) {
02599             $label = Xml::tags( 'label', $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null, $labelText );
02600             $label = Xml::tags( 'span', $spanLabelAttrs, $label );
02601         }
02602 
02603         $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
02604 
02605         return array( $label, $input );
02606     }
02607 
02615     protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
02616         global $wgOut, $wgContLang;
02617         # Add a class if 'missingsummary' is triggered to allow styling of the summary line
02618         $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
02619         if ( $isSubjectPreview ) {
02620             if ( $this->nosummary ) {
02621                 return;
02622             }
02623         } else {
02624             if ( !$this->mShowSummaryField ) {
02625                 return;
02626             }
02627         }
02628         $summary = $wgContLang->recodeForEdit( $summary );
02629         $labelText = wfMessage( $isSubjectPreview ? 'subject' : 'summary' )->parse();
02630         list( $label, $input ) = $this->getSummaryInput( $summary, $labelText, array( 'class' => $summaryClass ), array() );
02631         $wgOut->addHTML( "{$label} {$input}" );
02632     }
02633 
02641     protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
02642         // avoid spaces in preview, gets always trimmed on save
02643         $summary = trim( $summary );
02644         if ( !$summary || ( !$this->preview && !$this->diff ) ) {
02645             return "";
02646         }
02647 
02648         global $wgParser;
02649 
02650         if ( $isSubjectPreview ) {
02651             $summary = wfMessage( 'newsectionsummary' )->rawParams( $wgParser->stripSectionName( $summary ) )
02652                 ->inContentLanguage()->text();
02653         }
02654 
02655         $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
02656 
02657         $summary = wfMessage( $message )->parse() . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
02658         return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary );
02659     }
02660 
02661     protected function showFormBeforeText() {
02662         global $wgOut;
02663         $section = htmlspecialchars( $this->section );
02664         $wgOut->addHTML( <<<HTML
02665 <input type='hidden' value="{$section}" name="wpSection" />
02666 <input type='hidden' value="{$this->starttime}" name="wpStarttime" />
02667 <input type='hidden' value="{$this->edittime}" name="wpEdittime" />
02668 <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
02669 
02670 HTML
02671         );
02672         if ( !$this->checkUnicodeCompliantBrowser() ) {
02673             $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
02674         }
02675     }
02676 
02677     protected function showFormAfterText() {
02678         global $wgOut, $wgUser;
02691         $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
02692     }
02693 
02702     protected function showContentForm() {
02703         $this->showTextbox1();
02704     }
02705 
02714     protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
02715         if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
02716             $attribs = array( 'style' => 'display:none;' );
02717         } else {
02718             $classes = array(); // Textarea CSS
02719             if ( $this->mTitle->isProtected( 'edit' ) &&
02720                 MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
02721             ) {
02722                 # Is the title semi-protected?
02723                 if ( $this->mTitle->isSemiProtected() ) {
02724                     $classes[] = 'mw-textarea-sprotected';
02725                 } else {
02726                     # Then it must be protected based on static groups (regular)
02727                     $classes[] = 'mw-textarea-protected';
02728                 }
02729                 # Is the title cascade-protected?
02730                 if ( $this->mTitle->isCascadeProtected() ) {
02731                     $classes[] = 'mw-textarea-cprotected';
02732                 }
02733             }
02734 
02735             $attribs = array( 'tabindex' => 1 );
02736 
02737             if ( is_array( $customAttribs ) ) {
02738                 $attribs += $customAttribs;
02739             }
02740 
02741             if ( count( $classes ) ) {
02742                 if ( isset( $attribs['class'] ) ) {
02743                     $classes[] = $attribs['class'];
02744                 }
02745                 $attribs['class'] = implode( ' ', $classes );
02746             }
02747         }
02748 
02749         $this->showTextbox( $textoverride !== null ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
02750     }
02751 
02752     protected function showTextbox2() {
02753         $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
02754     }
02755 
02756     protected function showTextbox( $text, $name, $customAttribs = array() ) {
02757         global $wgOut, $wgUser;
02758 
02759         $wikitext = $this->safeUnicodeOutput( $text );
02760         if ( strval( $wikitext ) !== '' ) {
02761             // Ensure there's a newline at the end, otherwise adding lines
02762             // is awkward.
02763             // But don't add a newline if the ext is empty, or Firefox in XHTML
02764             // mode will show an extra newline. A bit annoying.
02765             $wikitext .= "\n";
02766         }
02767 
02768         $attribs = $customAttribs + array(
02769             'accesskey' => ',',
02770             'id' => $name,
02771             'cols' => $wgUser->getIntOption( 'cols' ),
02772             'rows' => $wgUser->getIntOption( 'rows' ),
02773             'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work
02774         );
02775 
02776         $pageLang = $this->mTitle->getPageLanguage();
02777         $attribs['lang'] = $pageLang->getCode();
02778         $attribs['dir'] = $pageLang->getDir();
02779 
02780         $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
02781     }
02782 
02783     protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
02784         global $wgOut;
02785         $classes = array();
02786         if ( $isOnTop ) {
02787             $classes[] = 'ontop';
02788         }
02789 
02790         $attribs = array( 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) );
02791 
02792         if ( $this->formtype != 'preview' ) {
02793             $attribs['style'] = 'display: none;';
02794         }
02795 
02796         $wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
02797 
02798         if ( $this->formtype == 'preview' ) {
02799             $this->showPreview( $previewOutput );
02800         }
02801 
02802         $wgOut->addHTML( '</div>' );
02803 
02804         if ( $this->formtype == 'diff' ) {
02805             try {
02806                 $this->showDiff();
02807             } catch ( MWContentSerializationException $ex ) {
02808                 $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
02809                 $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
02810             }
02811         }
02812     }
02813 
02820     protected function showPreview( $text ) {
02821         global $wgOut;
02822         if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
02823             $this->mArticle->openShowCategory();
02824         }
02825         # This hook seems slightly odd here, but makes things more
02826         # consistent for extensions.
02827         wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) );
02828         $wgOut->addHTML( $text );
02829         if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
02830             $this->mArticle->closeShowCategory();
02831         }
02832     }
02833 
02841     function showDiff() {
02842         global $wgUser, $wgContLang, $wgOut;
02843 
02844         $oldtitlemsg = 'currentrev';
02845         # if message does not exist, show diff against the preloaded default
02846         if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
02847             $oldtext = $this->mTitle->getDefaultMessageText();
02848             if ( $oldtext !== false ) {
02849                 $oldtitlemsg = 'defaultmessagetext';
02850                 $oldContent = $this->toEditContent( $oldtext );
02851             } else {
02852                 $oldContent = null;
02853             }
02854         } else {
02855             $oldContent = $this->getCurrentContent();
02856         }
02857 
02858         $textboxContent = $this->toEditContent( $this->textbox1 );
02859 
02860         $newContent = $this->mArticle->replaceSectionContent(
02861                             $this->section, $textboxContent,
02862                             $this->summary, $this->edittime );
02863 
02864         if ( $newContent ) {
02865             ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
02866             wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
02867 
02868             $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
02869             $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
02870         }
02871 
02872         if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
02873             $oldtitle = wfMessage( $oldtitlemsg )->parse();
02874             $newtitle = wfMessage( 'yourtext' )->parse();
02875 
02876             if ( !$oldContent ) {
02877                 $oldContent = $newContent->getContentHandler()->makeEmptyContent();
02878             }
02879 
02880             if ( !$newContent ) {
02881                 $newContent = $oldContent->getContentHandler()->makeEmptyContent();
02882             }
02883 
02884             $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
02885             $de->setContent( $oldContent, $newContent );
02886 
02887             $difftext = $de->getDiff( $oldtitle, $newtitle );
02888             $de->showDiffStyle();
02889         } else {
02890             $difftext = '';
02891         }
02892 
02893         $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
02894     }
02895 
02899     protected function showHeaderCopyrightWarning() {
02900         $msg = 'editpage-head-copy-warn';
02901         if ( !wfMessage( $msg )->isDisabled() ) {
02902             global $wgOut;
02903             $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
02904                 'editpage-head-copy-warn' );
02905         }
02906     }
02907 
02916     protected function showTosSummary() {
02917         $msg = 'editpage-tos-summary';
02918         wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
02919         if ( !wfMessage( $msg )->isDisabled() ) {
02920             global $wgOut;
02921             $wgOut->addHTML( '<div class="mw-tos-summary">' );
02922             $wgOut->addWikiMsg( $msg );
02923             $wgOut->addHTML( '</div>' );
02924         }
02925     }
02926 
02927     protected function showEditTools() {
02928         global $wgOut;
02929         $wgOut->addHTML( '<div class="mw-editTools">' .
02930             wfMessage( 'edittools' )->inContentLanguage()->parse() .
02931             '</div>' );
02932     }
02933 
02939     protected function getCopywarn() {
02940         return self::getCopyrightWarning( $this->mTitle );
02941     }
02942 
02951     public static function getCopyrightWarning( $title, $format = 'plain' ) {
02952         global $wgRightsText;
02953         if ( $wgRightsText ) {
02954             $copywarnMsg = array( 'copyrightwarning',
02955                 '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]',
02956                 $wgRightsText );
02957         } else {
02958             $copywarnMsg = array( 'copyrightwarning2',
02959                 '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' );
02960         }
02961         // Allow for site and per-namespace customization of contribution/copyright notice.
02962         wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
02963 
02964         return "<div id=\"editpage-copywarn\">\n" .
02965             call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>";
02966     }
02967 
02975     public static function getPreviewLimitReport( $output ) {
02976         if ( !$output || !$output->getLimitReportData() ) {
02977             return '';
02978         }
02979 
02980         wfProfileIn( __METHOD__ );
02981 
02982         $limitReport = Html::rawElement( 'div', array( 'class' => 'mw-limitReportExplanation' ),
02983             wfMessage( 'limitreport-title' )->parseAsBlock()
02984         );
02985 
02986         // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
02987         $limitReport .= Html::openElement( 'div', array( 'class' => 'preview-limit-report-wrapper' ) );
02988 
02989         $limitReport .= Html::openElement( 'table', array(
02990             'class' => 'preview-limit-report wikitable'
02991         ) ) .
02992             Html::openElement( 'tbody' );
02993 
02994         foreach ( $output->getLimitReportData() as $key => $value ) {
02995             if ( wfRunHooks( 'ParserLimitReportFormat',
02996                 array( $key, &$value, &$limitReport, true, true )
02997             ) ) {
02998                 $keyMsg = wfMessage( $key );
02999                 $valueMsg = wfMessage( array( "$key-value-html", "$key-value" ) );
03000                 if ( !$valueMsg->exists() ) {
03001                     $valueMsg = new RawMessage( '$1' );
03002                 }
03003                 if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
03004                     $limitReport .= Html::openElement( 'tr' ) .
03005                         Html::rawElement( 'th', null, $keyMsg->parse() ) .
03006                         Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) .
03007                         Html::closeElement( 'tr' );
03008                 }
03009             }
03010         }
03011 
03012         $limitReport .= Html::closeElement( 'tbody' ) .
03013             Html::closeElement( 'table' ) .
03014             Html::closeElement( 'div' );
03015 
03016         wfProfileOut( __METHOD__ );
03017 
03018         return $limitReport;
03019     }
03020 
03021     protected function showStandardInputs( &$tabindex = 2 ) {
03022         global $wgOut;
03023         $wgOut->addHTML( "<div class='editOptions'>\n" );
03024 
03025         if ( $this->section != 'new' ) {
03026             $this->showSummaryInput( false, $this->summary );
03027             $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
03028         }
03029 
03030         $checkboxes = $this->getCheckboxes( $tabindex,
03031             array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) );
03032         $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" );
03033 
03034         // Show copyright warning.
03035         $wgOut->addWikiText( $this->getCopywarn() );
03036         $wgOut->addHTML( $this->editFormTextAfterWarn );
03037 
03038         $wgOut->addHTML( "<div class='editButtons'>\n" );
03039         $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
03040 
03041         $cancel = $this->getCancelLink();
03042         if ( $cancel !== '' ) {
03043             $cancel .= Html::element( 'span',
03044                 array( 'class' => 'mw-editButtons-pipe-separator' ),
03045                 wfMessage( 'pipe-separator' )->text() );
03046         }
03047         $edithelpurl = Skin::makeInternalOrExternalUrl( wfMessage( 'edithelppage' )->inContentLanguage()->text() );
03048         $edithelp = '<a target="helpwindow" href="' . $edithelpurl . '">' .
03049             wfMessage( 'edithelp' )->escaped() . '</a> ' .
03050             wfMessage( 'newwindow' )->parse();
03051         $wgOut->addHTML( "  <span class='cancelLink'>{$cancel}</span>\n" );
03052         $wgOut->addHTML( "  <span class='editHelp'>{$edithelp}</span>\n" );
03053         $wgOut->addHTML( "</div><!-- editButtons -->\n" );
03054         wfRunHooks( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) );
03055         $wgOut->addHTML( "</div><!-- editOptions -->\n" );
03056     }
03057 
03062     protected function showConflict() {
03063         global $wgOut;
03064 
03065         if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
03066             $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
03067 
03068             $content1 = $this->toEditContent( $this->textbox1 );
03069             $content2 = $this->toEditContent( $this->textbox2 );
03070 
03071             $handler = ContentHandler::getForModelID( $this->contentModel );
03072             $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
03073             $de->setContent( $content2, $content1 );
03074             $de->showDiff(
03075                 wfMessage( 'yourtext' )->parse(),
03076                 wfMessage( 'storedversion' )->text()
03077             );
03078 
03079             $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
03080             $this->showTextbox2();
03081         }
03082     }
03083 
03087     public function getCancelLink() {
03088         $cancelParams = array();
03089         if ( !$this->isConflict && $this->oldid > 0 ) {
03090             $cancelParams['oldid'] = $this->oldid;
03091         }
03092 
03093         return Linker::linkKnown(
03094             $this->getContextTitle(),
03095             wfMessage( 'cancel' )->parse(),
03096             array( 'id' => 'mw-editform-cancel' ),
03097             $cancelParams
03098         );
03099     }
03100 
03110     protected function getActionURL( Title $title ) {
03111         return $title->getLocalURL( array( 'action' => $this->action ) );
03112     }
03113 
03120     protected function wasDeletedSinceLastEdit() {
03121         if ( $this->deletedSinceEdit !== null ) {
03122             return $this->deletedSinceEdit;
03123         }
03124 
03125         $this->deletedSinceEdit = false;
03126 
03127         if ( $this->mTitle->isDeletedQuick() ) {
03128             $this->lastDelete = $this->getLastDelete();
03129             if ( $this->lastDelete ) {
03130                 $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
03131                 if ( $deleteTime > $this->starttime ) {
03132                     $this->deletedSinceEdit = true;
03133                 }
03134             }
03135         }
03136 
03137         return $this->deletedSinceEdit;
03138     }
03139 
03140     protected function getLastDelete() {
03141         $dbr = wfGetDB( DB_SLAVE );
03142         $data = $dbr->selectRow(
03143             array( 'logging', 'user' ),
03144             array(
03145                 'log_type',
03146                 'log_action',
03147                 'log_timestamp',
03148                 'log_user',
03149                 'log_namespace',
03150                 'log_title',
03151                 'log_comment',
03152                 'log_params',
03153                 'log_deleted',
03154                 'user_name'
03155             ), array(
03156                 'log_namespace' => $this->mTitle->getNamespace(),
03157                 'log_title' => $this->mTitle->getDBkey(),
03158                 'log_type' => 'delete',
03159                 'log_action' => 'delete',
03160                 'user_id=log_user'
03161             ),
03162             __METHOD__,
03163             array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' )
03164         );
03165         // Quick paranoid permission checks...
03166         if ( is_object( $data ) ) {
03167             if ( $data->log_deleted & LogPage::DELETED_USER ) {
03168                 $data->user_name = wfMessage( 'rev-deleted-user' )->escaped();
03169             }
03170 
03171             if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
03172                 $data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped();
03173             }
03174         }
03175         return $data;
03176     }
03177 
03183     function getPreviewText() {
03184         global $wgOut, $wgUser, $wgRawHtml, $wgLang;
03185 
03186         wfProfileIn( __METHOD__ );
03187 
03188         if ( $wgRawHtml && !$this->mTokenOk ) {
03189             // Could be an offsite preview attempt. This is very unsafe if
03190             // HTML is enabled, as it could be an attack.
03191             $parsedNote = '';
03192             if ( $this->textbox1 !== '' ) {
03193                 // Do not put big scary notice, if previewing the empty
03194                 // string, which happens when you initially edit
03195                 // a category page, due to automatic preview-on-open.
03196                 $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
03197                     wfMessage( 'session_fail_preview_html' )->text() . "</div>", true, /* interface */true );
03198             }
03199             wfProfileOut( __METHOD__ );
03200             return $parsedNote;
03201         }
03202 
03203         $note = '';
03204 
03205         try {
03206             $content = $this->toEditContent( $this->textbox1 );
03207 
03208             $previewHTML = '';
03209             if ( !wfRunHooks( 'AlternateEditPreview', array( $this, &$content, &$previewHTML, &$this->mParserOutput ) ) ) {
03210                 wfProfileOut( __METHOD__ );
03211                 return $previewHTML;
03212             }
03213 
03214             # provide a anchor link to the editform
03215             $continueEditing = '<span class="mw-continue-editing">' .
03216                 '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' .
03217                 wfMessage( 'continue-editing' )->text() . ']]</span>';
03218             if ( $this->mTriedSave && !$this->mTokenOk ) {
03219                 if ( $this->mTokenOkExceptSuffix ) {
03220                     $note = wfMessage( 'token_suffix_mismatch' )->plain();
03221 
03222                 } else {
03223                     $note = wfMessage( 'session_fail_preview' )->plain();
03224                 }
03225             } elseif ( $this->incompleteForm ) {
03226                 $note = wfMessage( 'edit_form_incomplete' )->plain();
03227             } else {
03228                 $note = wfMessage( 'previewnote' )->plain() . ' ' . $continueEditing;
03229             }
03230 
03231             $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
03232             $parserOptions->setEditSection( false );
03233             $parserOptions->setIsPreview( true );
03234             $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
03235 
03236             # don't parse non-wikitext pages, show message about preview
03237             if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
03238                 if ( $this->mTitle->isCssJsSubpage() ) {
03239                     $level = 'user';
03240                 } elseif ( $this->mTitle->isCssOrJsPage() ) {
03241                     $level = 'site';
03242                 } else {
03243                     $level = false;
03244                 }
03245 
03246                 if ( $content->getModel() == CONTENT_MODEL_CSS ) {
03247                     $format = 'css';
03248                 } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
03249                     $format = 'js';
03250                 } else {
03251                     $format = false;
03252                 }
03253 
03254                 # Used messages to make sure grep find them:
03255                 # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
03256                 if ( $level && $format ) {
03257                     $note = "<div id='mw-{$level}{$format}preview'>" .
03258                         wfMessage( "{$level}{$format}preview" )->text() .
03259                         ' ' . $continueEditing . "</div>";
03260                 }
03261             }
03262 
03263             $rt = $content->getRedirectChain();
03264             if ( $rt ) {
03265                 $previewHTML = $this->mArticle->viewRedirect( $rt, false );
03266             } else {
03267 
03268                 # If we're adding a comment, we need to show the
03269                 # summary as the headline
03270                 if ( $this->section === "new" && $this->summary !== "" ) {
03271                     $content = $content->addSectionHeader( $this->summary );
03272                 }
03273 
03274                 $hook_args = array( $this, &$content );
03275                 ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
03276                 wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
03277 
03278                 $parserOptions->enableLimitReport();
03279 
03280                 # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
03281                 # But it's now deprecated, so never mind
03282 
03283                 $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
03284                 $parserOutput = $content->getParserOutput( $this->getArticle()->getTitle(), null, $parserOptions );
03285 
03286                 $previewHTML = $parserOutput->getText();
03287                 $this->mParserOutput = $parserOutput;
03288                 $wgOut->addParserOutputNoText( $parserOutput );
03289 
03290                 if ( count( $parserOutput->getWarnings() ) ) {
03291                     $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
03292                 }
03293             }
03294         } catch ( MWContentSerializationException $ex ) {
03295             $m = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
03296             $note .= "\n\n" . $m->parse();
03297             $previewHTML = '';
03298         }
03299 
03300         if ( $this->isConflict ) {
03301             $conflict = '<h2 id="mw-previewconflict">' . wfMessage( 'previewconflict' )->escaped() . "</h2>\n";
03302         } else {
03303             $conflict = '<hr />';
03304         }
03305 
03306         $previewhead = "<div class='previewnote'>\n" .
03307             '<h2 id="mw-previewheader">' . wfMessage( 'preview' )->escaped() . "</h2>" .
03308             $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
03309 
03310         $pageViewLang = $this->mTitle->getPageViewLanguage();
03311         $attribs = array( 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
03312             'class' => 'mw-content-' . $pageViewLang->getDir() );
03313         $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
03314 
03315         wfProfileOut( __METHOD__ );
03316         return $previewhead . $previewHTML . $this->previewTextAfterContent;
03317     }
03318 
03322     function getTemplates() {
03323         if ( $this->preview || $this->section != '' ) {
03324             $templates = array();
03325             if ( !isset( $this->mParserOutput ) ) {
03326                 return $templates;
03327             }
03328             foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
03329                 foreach ( array_keys( $template ) as $dbk ) {
03330                     $templates[] = Title::makeTitle( $ns, $dbk );
03331                 }
03332             }
03333             return $templates;
03334         } else {
03335             return $this->mTitle->getTemplateLinksFrom();
03336         }
03337     }
03338 
03346     static function getEditToolbar() {
03347         global $wgStylePath, $wgContLang, $wgLang, $wgOut;
03348         global $wgEnableUploads, $wgForeignFileRepos;
03349 
03350         $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
03351 
03365         $toolarray = array(
03366             array(
03367                 'image'  => $wgLang->getImageFile( 'button-bold' ),
03368                 'id'     => 'mw-editbutton-bold',
03369                 'open'   => '\'\'\'',
03370                 'close'  => '\'\'\'',
03371                 'sample' => wfMessage( 'bold_sample' )->text(),
03372                 'tip'    => wfMessage( 'bold_tip' )->text(),
03373                 'key'    => 'B'
03374             ),
03375             array(
03376                 'image'  => $wgLang->getImageFile( 'button-italic' ),
03377                 'id'     => 'mw-editbutton-italic',
03378                 'open'   => '\'\'',
03379                 'close'  => '\'\'',
03380                 'sample' => wfMessage( 'italic_sample' )->text(),
03381                 'tip'    => wfMessage( 'italic_tip' )->text(),
03382                 'key'    => 'I'
03383             ),
03384             array(
03385                 'image'  => $wgLang->getImageFile( 'button-link' ),
03386                 'id'     => 'mw-editbutton-link',
03387                 'open'   => '[[',
03388                 'close'  => ']]',
03389                 'sample' => wfMessage( 'link_sample' )->text(),
03390                 'tip'    => wfMessage( 'link_tip' )->text(),
03391                 'key'    => 'L'
03392             ),
03393             array(
03394                 'image'  => $wgLang->getImageFile( 'button-extlink' ),
03395                 'id'     => 'mw-editbutton-extlink',
03396                 'open'   => '[',
03397                 'close'  => ']',
03398                 'sample' => wfMessage( 'extlink_sample' )->text(),
03399                 'tip'    => wfMessage( 'extlink_tip' )->text(),
03400                 'key'    => 'X'
03401             ),
03402             array(
03403                 'image'  => $wgLang->getImageFile( 'button-headline' ),
03404                 'id'     => 'mw-editbutton-headline',
03405                 'open'   => "\n== ",
03406                 'close'  => " ==\n",
03407                 'sample' => wfMessage( 'headline_sample' )->text(),
03408                 'tip'    => wfMessage( 'headline_tip' )->text(),
03409                 'key'    => 'H'
03410             ),
03411             $imagesAvailable ? array(
03412                 'image'  => $wgLang->getImageFile( 'button-image' ),
03413                 'id'     => 'mw-editbutton-image',
03414                 'open'   => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
03415                 'close'  => ']]',
03416                 'sample' => wfMessage( 'image_sample' )->text(),
03417                 'tip'    => wfMessage( 'image_tip' )->text(),
03418                 'key'    => 'D',
03419             ) : false,
03420             $imagesAvailable ? array(
03421                 'image'  => $wgLang->getImageFile( 'button-media' ),
03422                 'id'     => 'mw-editbutton-media',
03423                 'open'   => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
03424                 'close'  => ']]',
03425                 'sample' => wfMessage( 'media_sample' )->text(),
03426                 'tip'    => wfMessage( 'media_tip' )->text(),
03427                 'key'    => 'M'
03428             ) : false,
03429             array(
03430                 'image'  => $wgLang->getImageFile( 'button-nowiki' ),
03431                 'id'     => 'mw-editbutton-nowiki',
03432                 'open'   => "<nowiki>",
03433                 'close'  => "</nowiki>",
03434                 'sample' => wfMessage( 'nowiki_sample' )->text(),
03435                 'tip'    => wfMessage( 'nowiki_tip' )->text(),
03436                 'key'    => 'N'
03437             ),
03438             array(
03439                 'image'  => $wgLang->getImageFile( 'button-sig' ),
03440                 'id'     => 'mw-editbutton-signature',
03441                 'open'   => '--~~~~',
03442                 'close'  => '',
03443                 'sample' => '',
03444                 'tip'    => wfMessage( 'sig_tip' )->text(),
03445                 'key'    => 'Y'
03446             ),
03447             array(
03448                 'image'  => $wgLang->getImageFile( 'button-hr' ),
03449                 'id'     => 'mw-editbutton-hr',
03450                 'open'   => "\n----\n",
03451                 'close'  => '',
03452                 'sample' => '',
03453                 'tip'    => wfMessage( 'hr_tip' )->text(),
03454                 'key'    => 'R'
03455             )
03456         );
03457 
03458         $script = 'mw.loader.using("mediawiki.action.edit", function() {';
03459         foreach ( $toolarray as $tool ) {
03460             if ( !$tool ) {
03461                 continue;
03462             }
03463 
03464             $params = array(
03465                 $wgStylePath . '/common/images/' . $tool['image'],
03466                 // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
03467                 // Older browsers show a "speedtip" type message only for ALT.
03468                 // Ideally these should be different, realistically they
03469                 // probably don't need to be.
03470                 $tool['tip'],
03471                 $tool['open'],
03472                 $tool['close'],
03473                 $tool['sample'],
03474                 $tool['id'],
03475             );
03476 
03477             $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
03478         }
03479 
03480         // This used to be called on DOMReady from mediawiki.action.edit, which
03481         // ended up causing race conditions with the setup code above.
03482         $script .= "\n" .
03483             "// Create button bar\n" .
03484             "$(function() { mw.toolbar.init(); } );\n";
03485 
03486         $script .= '});';
03487         $wgOut->addScript( Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) ) );
03488 
03489         $toolbar = '<div id="toolbar"></div>';
03490 
03491         wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
03492 
03493         return $toolbar;
03494     }
03495 
03506     public function getCheckboxes( &$tabindex, $checked ) {
03507         global $wgUser;
03508 
03509         $checkboxes = array();
03510 
03511         // don't show the minor edit checkbox if it's a new page or section
03512         if ( !$this->isNew ) {
03513             $checkboxes['minor'] = '';
03514             $minorLabel = wfMessage( 'minoredit' )->parse();
03515             if ( $wgUser->isAllowed( 'minoredit' ) ) {
03516                 $attribs = array(
03517                     'tabindex' => ++$tabindex,
03518                     'accesskey' => wfMessage( 'accesskey-minoredit' )->text(),
03519                     'id' => 'wpMinoredit',
03520                 );
03521                 $checkboxes['minor'] =
03522                     Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
03523                     "&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
03524                     Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'minoredit', 'withaccess' ) ) ) .
03525                     ">{$minorLabel}</label>";
03526             }
03527         }
03528 
03529         $watchLabel = wfMessage( 'watchthis' )->parse();
03530         $checkboxes['watch'] = '';
03531         if ( $wgUser->isLoggedIn() ) {
03532             $attribs = array(
03533                 'tabindex' => ++$tabindex,
03534                 'accesskey' => wfMessage( 'accesskey-watch' )->text(),
03535                 'id' => 'wpWatchthis',
03536             );
03537             $checkboxes['watch'] =
03538                 Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
03539                 "&#160;<label for='wpWatchthis' id='mw-editpage-watch'" .
03540                 Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ) ) .
03541                 ">{$watchLabel}</label>";
03542         }
03543         wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
03544         return $checkboxes;
03545     }
03546 
03555     public function getEditButtons( &$tabindex ) {
03556         $buttons = array();
03557 
03558         $temp = array(
03559             'id' => 'wpSave',
03560             'name' => 'wpSave',
03561             'type' => 'submit',
03562             'tabindex' => ++$tabindex,
03563             'value' => wfMessage( 'savearticle' )->text(),
03564             'accesskey' => wfMessage( 'accesskey-save' )->text(),
03565             'title' => wfMessage( 'tooltip-save' )->text() . ' [' . wfMessage( 'accesskey-save' )->text() . ']',
03566         );
03567         $buttons['save'] = Xml::element( 'input', $temp, '' );
03568 
03569         ++$tabindex; // use the same for preview and live preview
03570         $temp = array(
03571             'id' => 'wpPreview',
03572             'name' => 'wpPreview',
03573             'type' => 'submit',
03574             'tabindex' => $tabindex,
03575             'value' => wfMessage( 'showpreview' )->text(),
03576             'accesskey' => wfMessage( 'accesskey-preview' )->text(),
03577             'title' => wfMessage( 'tooltip-preview' )->text() . ' [' . wfMessage( 'accesskey-preview' )->text() . ']',
03578         );
03579         $buttons['preview'] = Xml::element( 'input', $temp, '' );
03580         $buttons['live'] = '';
03581 
03582         $temp = array(
03583             'id' => 'wpDiff',
03584             'name' => 'wpDiff',
03585             'type' => 'submit',
03586             'tabindex' => ++$tabindex,
03587             'value' => wfMessage( 'showdiff' )->text(),
03588             'accesskey' => wfMessage( 'accesskey-diff' )->text(),
03589             'title' => wfMessage( 'tooltip-diff' )->text() . ' [' . wfMessage( 'accesskey-diff' )->text() . ']',
03590         );
03591         $buttons['diff'] = Xml::element( 'input', $temp, '' );
03592 
03593         wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) );
03594         return $buttons;
03595     }
03596 
03609     function livePreview() {
03610         global $wgOut;
03611         $wgOut->disable();
03612         header( 'Content-type: text/xml; charset=utf-8' );
03613         header( 'Cache-control: no-cache' );
03614 
03615         $previewText = $this->getPreviewText();
03616         #$categories = $skin->getCategoryLinks();
03617 
03618         $s =
03619         '<?xml version="1.0" encoding="UTF-8" ?>' . "\n" .
03620         Xml::tags( 'livepreview', null,
03621             Xml::element( 'preview', null, $previewText )
03622             #.  Xml::element( 'category', null, $categories )
03623         );
03624         echo $s;
03625     }
03626 
03632     function blockedPage() {
03633         wfDeprecated( __METHOD__, '1.19' );
03634         global $wgUser;
03635 
03636         throw new UserBlockedError( $wgUser->getBlock() );
03637     }
03638 
03644     function userNotLoggedInPage() {
03645         wfDeprecated( __METHOD__, '1.19' );
03646         throw new PermissionsError( 'edit' );
03647     }
03648 
03655     function noCreatePermission() {
03656         wfDeprecated( __METHOD__, '1.19' );
03657         $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
03658         throw new PermissionsError( $permission );
03659     }
03660 
03665     function noSuchSectionPage() {
03666         global $wgOut;
03667 
03668         $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
03669 
03670         $res = wfMessage( 'nosuchsectiontext', $this->section )->parseAsBlock();
03671         wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
03672         $wgOut->addHTML( $res );
03673 
03674         $wgOut->returnToMain( false, $this->mTitle );
03675     }
03676 
03682     public function spamPageWithContent( $match = false ) {
03683         global $wgOut, $wgLang;
03684         $this->textbox2 = $this->textbox1;
03685 
03686         if ( is_array( $match ) ) {
03687             $match = $wgLang->listToText( $match );
03688         }
03689         $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
03690 
03691         $wgOut->addHTML( '<div id="spamprotected">' );
03692         $wgOut->addWikiMsg( 'spamprotectiontext' );
03693         if ( $match ) {
03694             $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
03695         }
03696         $wgOut->addHTML( '</div>' );
03697 
03698         $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
03699         $this->showDiff();
03700 
03701         $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
03702         $this->showTextbox2();
03703 
03704         $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) );
03705     }
03706 
03713     private function checkUnicodeCompliantBrowser() {
03714         global $wgBrowserBlackList, $wgRequest;
03715 
03716         $currentbrowser = $wgRequest->getHeader( 'User-Agent' );
03717         if ( $currentbrowser === false ) {
03718             // No User-Agent header sent? Trust it by default...
03719             return true;
03720         }
03721 
03722         foreach ( $wgBrowserBlackList as $browser ) {
03723             if ( preg_match( $browser, $currentbrowser ) ) {
03724                 return false;
03725             }
03726         }
03727         return true;
03728     }
03729 
03738     protected function safeUnicodeInput( $request, $field ) {
03739         $text = rtrim( $request->getText( $field ) );
03740         return $request->getBool( 'safemode' )
03741             ? $this->unmakeSafe( $text )
03742             : $text;
03743     }
03744 
03752     protected function safeUnicodeOutput( $text ) {
03753         global $wgContLang;
03754         $codedText = $wgContLang->recodeForEdit( $text );
03755         return $this->checkUnicodeCompliantBrowser()
03756             ? $codedText
03757             : $this->makeSafe( $codedText );
03758     }
03759 
03772     private function makeSafe( $invalue ) {
03773         // Armor existing references for reversibility.
03774         $invalue = strtr( $invalue, array( "&#x" => "&#x0" ) );
03775 
03776         $bytesleft = 0;
03777         $result = "";
03778         $working = 0;
03779         for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
03780             $bytevalue = ord( $invalue[$i] );
03781             if ( $bytevalue <= 0x7F ) { // 0xxx xxxx
03782                 $result .= chr( $bytevalue );
03783                 $bytesleft = 0;
03784             } elseif ( $bytevalue <= 0xBF ) { // 10xx xxxx
03785                 $working = $working << 6;
03786                 $working += ( $bytevalue & 0x3F );
03787                 $bytesleft--;
03788                 if ( $bytesleft <= 0 ) {
03789                     $result .= "&#x" . strtoupper( dechex( $working ) ) . ";";
03790                 }
03791             } elseif ( $bytevalue <= 0xDF ) { // 110x xxxx
03792                 $working = $bytevalue & 0x1F;
03793                 $bytesleft = 1;
03794             } elseif ( $bytevalue <= 0xEF ) { // 1110 xxxx
03795                 $working = $bytevalue & 0x0F;
03796                 $bytesleft = 2;
03797             } else { // 1111 0xxx
03798                 $working = $bytevalue & 0x07;
03799                 $bytesleft = 3;
03800             }
03801         }
03802         return $result;
03803     }
03804 
03813     private function unmakeSafe( $invalue ) {
03814         $result = "";
03815         $valueLength = strlen( $invalue );
03816         for ( $i = 0; $i < $valueLength; $i++ ) {
03817             if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) {
03818                 $i += 3;
03819                 $hexstring = "";
03820                 do {
03821                     $hexstring .= $invalue[$i];
03822                     $i++;
03823                 } while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
03824 
03825                 // Do some sanity checks. These aren't needed for reversibility,
03826                 // but should help keep the breakage down if the editor
03827                 // breaks one of the entities whilst editing.
03828                 if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) {
03829                     $codepoint = hexdec( $hexstring );
03830                     $result .= codepointToUtf8( $codepoint );
03831                 } else {
03832                     $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
03833                 }
03834             } else {
03835                 $result .= substr( $invalue, $i, 1 );
03836             }
03837         }
03838         // reverse the transform that we made for reversibility reasons.
03839         return strtr( $result, array( "&#x0" => "&#x" ) );
03840     }
03841 }