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