MediaWiki  REL1_24
EditPage.php
Go to the documentation of this file.
00001 <?php
00038 class EditPage {
00042     const AS_SUCCESS_UPDATE = 200;
00043 
00047     const AS_SUCCESS_NEW_ARTICLE = 201;
00048 
00052     const AS_HOOK_ERROR = 210;
00053 
00057     const AS_HOOK_ERROR_EXPECTED = 212;
00058 
00062     const AS_BLOCKED_PAGE_FOR_USER = 215;
00063 
00067     const AS_CONTENT_TOO_BIG = 216;
00068 
00072     const AS_READ_ONLY_PAGE_ANON = 218;
00073 
00077     const AS_READ_ONLY_PAGE_LOGGED = 219;
00078 
00082     const AS_READ_ONLY_PAGE = 220;
00083 
00087     const AS_RATE_LIMITED = 221;
00088 
00093     const AS_ARTICLE_WAS_DELETED = 222;
00094 
00099     const AS_NO_CREATE_PERMISSION = 223;
00100 
00104     const AS_BLANK_ARTICLE = 224;
00105 
00109     const AS_CONFLICT_DETECTED = 225;
00110 
00115     const AS_SUMMARY_NEEDED = 226;
00116 
00120     const AS_TEXTBOX_EMPTY = 228;
00121 
00125     const AS_MAX_ARTICLE_SIZE_EXCEEDED = 229;
00126 
00130     const AS_END = 231;
00131 
00135     const AS_SPAM_ERROR = 232;
00136 
00140     const AS_IMAGE_REDIRECT_ANON = 233;
00141 
00145     const AS_IMAGE_REDIRECT_LOGGED = 234;
00146 
00150     const AS_PARSE_ERROR = 240;
00151 
00155     const EDITFORM_ID = 'editform';
00156 
00161     const POST_EDIT_COOKIE_KEY_PREFIX = 'PostEditRevision';
00162 
00176     const POST_EDIT_COOKIE_DURATION = 1200;
00177 
00179     public $mArticle;
00180 
00182     public $mTitle;
00183 
00185     private $mContextTitle = null;
00186 
00188     public $action = 'submit';
00189 
00191     public $isConflict = false;
00192 
00194     public $isCssJsSubpage = false;
00195 
00197     public $isCssSubpage = false;
00198 
00200     public $isJsSubpage = false;
00201 
00203     public $isWrongCaseCssJsPage = false;
00204 
00206     public $isNew = false;
00207 
00209     public $deletedSinceEdit;
00210 
00212     public $formtype;
00213 
00215     public $firsttime;
00216 
00218     public $lastDelete;
00219 
00221     public $mTokenOk = false;
00222 
00224     public $mTokenOkExceptSuffix = false;
00225 
00227     public $mTriedSave = false;
00228 
00230     public $incompleteForm = false;
00231 
00233     public $tooBig = false;
00234 
00236     public $kblength = false;
00237 
00239     public $missingComment = false;
00240 
00242     public $missingSummary = false;
00243 
00245     public $allowBlankSummary = false;
00246 
00248     protected $blankArticle = false;
00249 
00251     protected $allowBlankArticle = false;
00252 
00254     public $autoSumm = '';
00255 
00257     public $hookError = '';
00258 
00260     public $mParserOutput;
00261 
00263     public $hasPresetSummary = false;
00264 
00266     public $mBaseRevision = false;
00267 
00269     public $mShowSummaryField = true;
00270 
00271     # Form values
00272 
00274     public $save = false;
00275 
00277     public $preview = false;
00278 
00280     public $diff = false;
00281 
00283     public $minoredit = false;
00284 
00286     public $watchthis = false;
00287 
00289     public $recreate = false;
00290 
00292     public $textbox1 = '';
00293 
00295     public $textbox2 = '';
00296 
00298     public $summary = '';
00299 
00301     public $nosummary = false;
00302 
00304     public $edittime = '';
00305 
00307     public $section = '';
00308 
00310     public $sectiontitle = '';
00311 
00313     public $starttime = '';
00314 
00316     public $oldid = 0;
00317 
00319     public $editintro = '';
00320 
00322     public $scrolltop = null;
00323 
00325     public $bot = true;
00326 
00328     public $contentModel = null;
00329 
00331     public $contentFormat = null;
00332 
00333     # Placeholders for text injection by hooks (must be HTML)
00334     # extensions should take care to _append_ to the present value
00335 
00337     public $editFormPageTop = '';
00338     public $editFormTextTop = '';
00339     public $editFormTextBeforeContent = '';
00340     public $editFormTextAfterWarn = '';
00341     public $editFormTextAfterTools = '';
00342     public $editFormTextBottom = '';
00343     public $editFormTextAfterContent = '';
00344     public $previewTextAfterContent = '';
00345     public $mPreloadContent = null;
00346 
00347     /* $didSave should be set to true whenever an article was successfully altered. */
00348     public $didSave = false;
00349     public $undidRev = 0;
00350 
00351     public $suppressIntro = false;
00352 
00354     public $allowNonTextContent = false;
00355 
00357     protected $edit;
00358 
00360     public $live;
00361 
00365     public function __construct( Article $article ) {
00366         $this->mArticle = $article;
00367         $this->mTitle = $article->getTitle();
00368 
00369         $this->contentModel = $this->mTitle->getContentModel();
00370 
00371         $handler = ContentHandler::getForModelID( $this->contentModel );
00372         $this->contentFormat = $handler->getDefaultFormat();
00373     }
00374 
00378     public function getArticle() {
00379         return $this->mArticle;
00380     }
00381 
00386     public function getTitle() {
00387         return $this->mTitle;
00388     }
00389 
00395     public function setContextTitle( $title ) {
00396         $this->mContextTitle = $title;
00397     }
00398 
00406     public function getContextTitle() {
00407         if ( is_null( $this->mContextTitle ) ) {
00408             global $wgTitle;
00409             return $wgTitle;
00410         } else {
00411             return $this->mContextTitle;
00412         }
00413     }
00414 
00422     public function isSupportedContentModel( $modelId ) {
00423         return $this->allowNonTextContent ||
00424             ContentHandler::getForModelID( $modelId ) instanceof TextContentHandler;
00425     }
00426 
00427     function submit() {
00428         $this->edit();
00429     }
00430 
00442     function edit() {
00443         global $wgOut, $wgRequest, $wgUser;
00444         // Allow extensions to modify/prevent this form or submission
00445         if ( !wfRunHooks( 'AlternateEdit', array( $this ) ) ) {
00446             return;
00447         }
00448 
00449         wfProfileIn( __METHOD__ );
00450         wfDebug( __METHOD__ . ": enter\n" );
00451 
00452         // If they used redlink=1 and the page exists, redirect to the main article
00453         if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
00454             $wgOut->redirect( $this->mTitle->getFullURL() );
00455             wfProfileOut( __METHOD__ );
00456             return;
00457         }
00458 
00459         $this->importFormData( $wgRequest );
00460         $this->firsttime = false;
00461 
00462         if ( $this->live ) {
00463             $this->livePreview();
00464             wfProfileOut( __METHOD__ );
00465             return;
00466         }
00467 
00468         if ( wfReadOnly() && $this->save ) {
00469             // Force preview
00470             $this->save = false;
00471             $this->preview = true;
00472         }
00473 
00474         if ( $this->save ) {
00475             $this->formtype = 'save';
00476         } elseif ( $this->preview ) {
00477             $this->formtype = 'preview';
00478         } elseif ( $this->diff ) {
00479             $this->formtype = 'diff';
00480         } else { # First time through
00481             $this->firsttime = true;
00482             if ( $this->previewOnOpen() ) {
00483                 $this->formtype = 'preview';
00484             } else {
00485                 $this->formtype = 'initial';
00486             }
00487         }
00488 
00489         $permErrors = $this->getEditPermissionErrors();
00490         if ( $permErrors ) {
00491             wfDebug( __METHOD__ . ": User can't edit\n" );
00492             // Auto-block user's IP if the account was "hard" blocked
00493             $wgUser->spreadAnyEditBlock();
00494 
00495             $this->displayPermissionsError( $permErrors );
00496 
00497             wfProfileOut( __METHOD__ );
00498             return;
00499         }
00500 
00501         wfProfileIn( __METHOD__ . "-business-end" );
00502 
00503         $this->isConflict = false;
00504         // css / js subpages of user pages get a special treatment
00505         $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
00506         $this->isCssSubpage = $this->mTitle->isCssSubpage();
00507         $this->isJsSubpage = $this->mTitle->isJsSubpage();
00508         // @todo FIXME: Silly assignment.
00509         $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
00510 
00511         # Show applicable editing introductions
00512         if ( $this->formtype == 'initial' || $this->firsttime ) {
00513             $this->showIntro();
00514         }
00515 
00516         # Attempt submission here.  This will check for edit conflicts,
00517         # and redundantly check for locked database, blocked IPs, etc.
00518         # that edit() already checked just in case someone tries to sneak
00519         # in the back door with a hand-edited submission URL.
00520 
00521         if ( 'save' == $this->formtype ) {
00522             if ( !$this->attemptSave() ) {
00523                 wfProfileOut( __METHOD__ . "-business-end" );
00524                 wfProfileOut( __METHOD__ );
00525                 return;
00526             }
00527         }
00528 
00529         # First time through: get contents, set time for conflict
00530         # checking, etc.
00531         if ( 'initial' == $this->formtype || $this->firsttime ) {
00532             if ( $this->initialiseForm() === false ) {
00533                 $this->noSuchSectionPage();
00534                 wfProfileOut( __METHOD__ . "-business-end" );
00535                 wfProfileOut( __METHOD__ );
00536                 return;
00537             }
00538 
00539             if ( !$this->mTitle->getArticleID() ) {
00540                 wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
00541             } else {
00542                 wfRunHooks( 'EditFormInitialText', array( $this ) );
00543             }
00544 
00545         }
00546 
00547         $this->showEditForm();
00548         wfProfileOut( __METHOD__ . "-business-end" );
00549         wfProfileOut( __METHOD__ );
00550     }
00551 
00555     protected function getEditPermissionErrors() {
00556         global $wgUser;
00557         $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
00558         # Can this title be created?
00559         if ( !$this->mTitle->exists() ) {
00560             $permErrors = array_merge( $permErrors,
00561                 wfArrayDiff2( $this->mTitle->getUserPermissionsErrors( 'create', $wgUser ), $permErrors ) );
00562         }
00563         # Ignore some permissions errors when a user is just previewing/viewing diffs
00564         $remove = array();
00565         foreach ( $permErrors as $error ) {
00566             if ( ( $this->preview || $this->diff )
00567                 && ( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' )
00568             ) {
00569                 $remove[] = $error;
00570             }
00571         }
00572         $permErrors = wfArrayDiff2( $permErrors, $remove );
00573         return $permErrors;
00574     }
00575 
00589     protected function displayPermissionsError( array $permErrors ) {
00590         global $wgRequest, $wgOut;
00591 
00592         if ( $wgRequest->getBool( 'redlink' ) ) {
00593             // The edit page was reached via a red link.
00594             // Redirect to the article page and let them click the edit tab if
00595             // they really want a permission error.
00596             $wgOut->redirect( $this->mTitle->getFullURL() );
00597             return;
00598         }
00599 
00600         $content = $this->getContentObject();
00601 
00602         # Use the normal message if there's nothing to display
00603         if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
00604             $action = $this->mTitle->exists() ? 'edit' :
00605                 ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
00606             throw new PermissionsError( $action, $permErrors );
00607         }
00608 
00609         wfRunHooks( 'EditPage::showReadOnlyForm:initial', array( $this, &$wgOut ) );
00610 
00611         $wgOut->setRobotPolicy( 'noindex,nofollow' );
00612         $wgOut->setPageTitle( wfMessage(
00613             'viewsource-title',
00614             $this->getContextTitle()->getPrefixedText()
00615         ) );
00616         $wgOut->addBacklinkSubtitle( $this->getContextTitle() );
00617         $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) );
00618         $wgOut->addHTML( "<hr />\n" );
00619 
00620         # If the user made changes, preserve them when showing the markup
00621         # (This happens when a user is blocked during edit, for instance)
00622         if ( !$this->firsttime ) {
00623             $text = $this->textbox1;
00624             $wgOut->addWikiMsg( 'viewyourtext' );
00625         } else {
00626             $text = $this->toEditText( $content );
00627             $wgOut->addWikiMsg( 'viewsourcetext' );
00628         }
00629 
00630         $this->showTextbox( $text, 'wpTextbox1', array( 'readonly' ) );
00631 
00632         $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
00633             Linker::formatTemplates( $this->getTemplates() ) ) );
00634 
00635         $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
00636 
00637         if ( $this->mTitle->exists() ) {
00638             $wgOut->returnToMain( null, $this->mTitle );
00639         }
00640     }
00641 
00647     protected function previewOnOpen() {
00648         global $wgRequest, $wgUser, $wgPreviewOnOpenNamespaces;
00649         if ( $wgRequest->getVal( 'preview' ) == 'yes' ) {
00650             // Explicit override from request
00651             return true;
00652         } elseif ( $wgRequest->getVal( 'preview' ) == 'no' ) {
00653             // Explicit override from request
00654             return false;
00655         } elseif ( $this->section == 'new' ) {
00656             // Nothing *to* preview for new sections
00657             return false;
00658         } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() )
00659             && $wgUser->getOption( 'previewonfirst' )
00660         ) {
00661             // Standard preference behavior
00662             return true;
00663         } elseif ( !$this->mTitle->exists()
00664             && isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
00665             && $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()]
00666         ) {
00667             // Categories are special
00668             return true;
00669         } else {
00670             return false;
00671         }
00672     }
00673 
00680     protected function isWrongCaseCssJsPage() {
00681         if ( $this->mTitle->isCssJsSubpage() ) {
00682             $name = $this->mTitle->getSkinFromCssJsSubpage();
00683             $skins = array_merge(
00684                 array_keys( Skin::getSkinNames() ),
00685                 array( 'common' )
00686             );
00687             return !in_array( $name, $skins )
00688                 && in_array( strtolower( $name ), $skins );
00689         } else {
00690             return false;
00691         }
00692     }
00693 
00701     protected function isSectionEditSupported() {
00702         $contentHandler = ContentHandler::getForTitle( $this->mTitle );
00703         return $contentHandler->supportsSections();
00704     }
00705 
00711     function importFormData( &$request ) {
00712         global $wgContLang, $wgUser;
00713 
00714         wfProfileIn( __METHOD__ );
00715 
00716         # Section edit can come from either the form or a link
00717         $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
00718 
00719         if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) {
00720             wfProfileOut( __METHOD__ );
00721             throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
00722         }
00723 
00724         $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
00725 
00726         if ( $request->wasPosted() ) {
00727             # These fields need to be checked for encoding.
00728             # Also remove trailing whitespace, but don't remove _initial_
00729             # whitespace from the text boxes. This may be significant formatting.
00730             $this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' );
00731             if ( !$request->getCheck( 'wpTextbox2' ) ) {
00732                 // Skip this if wpTextbox2 has input, it indicates that we came
00733                 // from a conflict page with raw page text, not a custom form
00734                 // modified by subclasses
00735                 wfProfileIn( get_class( $this ) . "::importContentFormData" );
00736                 $textbox1 = $this->importContentFormData( $request );
00737                 if ( $textbox1 !== null ) {
00738                     $this->textbox1 = $textbox1;
00739                 }
00740 
00741                 wfProfileOut( get_class( $this ) . "::importContentFormData" );
00742             }
00743 
00744             # Truncate for whole multibyte characters
00745             $this->summary = $wgContLang->truncate( $request->getText( 'wpSummary' ), 255 );
00746 
00747             # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
00748             # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
00749             # section titles.
00750             $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
00751 
00752             # Treat sectiontitle the same way as summary.
00753             # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
00754             # currently doing double duty as both edit summary and section title. Right now this
00755             # is just to allow API edits to work around this limitation, but this should be
00756             # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
00757             $this->sectiontitle = $wgContLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
00758             $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
00759 
00760             $this->edittime = $request->getVal( 'wpEdittime' );
00761             $this->starttime = $request->getVal( 'wpStarttime' );
00762 
00763             $undidRev = $request->getInt( 'wpUndidRevision' );
00764             if ( $undidRev ) {
00765                 $this->undidRev = $undidRev;
00766             }
00767 
00768             $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
00769 
00770             if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
00771                 // wpTextbox1 field is missing, possibly due to being "too big"
00772                 // according to some filter rules such as Suhosin's setting for
00773                 // suhosin.request.max_value_length (d'oh)
00774                 $this->incompleteForm = true;
00775             } else {
00776                 // If we receive the last parameter of the request, we can fairly
00777                 // claim the POST request has not been truncated.
00778 
00779                 // TODO: softened the check for cutover.  Once we determine
00780                 // that it is safe, we should complete the transition by
00781                 // removing the "edittime" clause.
00782                 $this->incompleteForm = ( !$request->getVal( 'wpUltimateParam' ) && is_null( $this->edittime ) );
00783             }
00784             if ( $this->incompleteForm ) {
00785                 # If the form is incomplete, force to preview.
00786                 wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
00787                 wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
00788                 $this->preview = true;
00789             } else {
00790                 /* Fallback for live preview */
00791                 $this->preview = $request->getCheck( 'wpPreview' ) || $request->getCheck( 'wpLivePreview' );
00792                 $this->diff = $request->getCheck( 'wpDiff' );
00793 
00794                 // Remember whether a save was requested, so we can indicate
00795                 // if we forced preview due to session failure.
00796                 $this->mTriedSave = !$this->preview;
00797 
00798                 if ( $this->tokenOk( $request ) ) {
00799                     # Some browsers will not report any submit button
00800                     # if the user hits enter in the comment box.
00801                     # The unmarked state will be assumed to be a save,
00802                     # if the form seems otherwise complete.
00803                     wfDebug( __METHOD__ . ": Passed token check.\n" );
00804                 } elseif ( $this->diff ) {
00805                     # Failed token check, but only requested "Show Changes".
00806                     wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
00807                 } else {
00808                     # Page might be a hack attempt posted from
00809                     # an external site. Preview instead of saving.
00810                     wfDebug( __METHOD__ . ": Failed token check; forcing preview\n" );
00811                     $this->preview = true;
00812                 }
00813             }
00814             $this->save = !$this->preview && !$this->diff;
00815             if ( !preg_match( '/^\d{14}$/', $this->edittime ) ) {
00816                 $this->edittime = null;
00817             }
00818 
00819             if ( !preg_match( '/^\d{14}$/', $this->starttime ) ) {
00820                 $this->starttime = null;
00821             }
00822 
00823             $this->recreate = $request->getCheck( 'wpRecreate' );
00824 
00825             $this->minoredit = $request->getCheck( 'wpMinoredit' );
00826             $this->watchthis = $request->getCheck( 'wpWatchthis' );
00827 
00828             # Don't force edit summaries when a user is editing their own user or talk page
00829             if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK )
00830                 && $this->mTitle->getText() == $wgUser->getName()
00831             ) {
00832                 $this->allowBlankSummary = true;
00833             } else {
00834                 $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' )
00835                     || !$wgUser->getOption( 'forceeditsummary' );
00836             }
00837 
00838             $this->autoSumm = $request->getText( 'wpAutoSummary' );
00839 
00840             $this->allowBlankArticle = $request->getBool( 'wpIgnoreBlankArticle' );
00841         } else {
00842             # Not a posted form? Start with nothing.
00843             wfDebug( __METHOD__ . ": Not a posted form.\n" );
00844             $this->textbox1 = '';
00845             $this->summary = '';
00846             $this->sectiontitle = '';
00847             $this->edittime = '';
00848             $this->starttime = wfTimestampNow();
00849             $this->edit = false;
00850             $this->preview = false;
00851             $this->save = false;
00852             $this->diff = false;
00853             $this->minoredit = false;
00854             // Watch may be overridden by request parameters
00855             $this->watchthis = $request->getBool( 'watchthis', false );
00856             $this->recreate = false;
00857 
00858             // When creating a new section, we can preload a section title by passing it as the
00859             // preloadtitle parameter in the URL (Bug 13100)
00860             if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
00861                 $this->sectiontitle = $request->getVal( 'preloadtitle' );
00862                 // Once wpSummary isn't being use for setting section titles, we should delete this.
00863                 $this->summary = $request->getVal( 'preloadtitle' );
00864             } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
00865                 $this->summary = $request->getText( 'summary' );
00866                 if ( $this->summary !== '' ) {
00867                     $this->hasPresetSummary = true;
00868                 }
00869             }
00870 
00871             if ( $request->getVal( 'minor' ) ) {
00872                 $this->minoredit = true;
00873             }
00874         }
00875 
00876         $this->oldid = $request->getInt( 'oldid' );
00877 
00878         $this->bot = $request->getBool( 'bot', true );
00879         $this->nosummary = $request->getBool( 'nosummary' );
00880 
00881         // May be overridden by revision.
00882         $this->contentModel = $request->getText( 'model', $this->contentModel );
00883         // May be overridden by revision.
00884         $this->contentFormat = $request->getText( 'format', $this->contentFormat );
00885 
00886         if ( !ContentHandler::getForModelID( $this->contentModel )
00887             ->isSupportedFormat( $this->contentFormat )
00888         ) {
00889             throw new ErrorPageError(
00890                 'editpage-notsupportedcontentformat-title',
00891                 'editpage-notsupportedcontentformat-text',
00892                 array( $this->contentFormat, ContentHandler::getLocalizedName( $this->contentModel ) )
00893             );
00894         }
00895 
00902         $this->live = $request->getCheck( 'live' );
00903         $this->editintro = $request->getText( 'editintro',
00904             // Custom edit intro for new sections
00905             $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
00906 
00907         // Allow extensions to modify form data
00908         wfRunHooks( 'EditPage::importFormData', array( $this, $request ) );
00909 
00910         wfProfileOut( __METHOD__ );
00911     }
00912 
00921     protected function importContentFormData( &$request ) {
00922         return; // Don't do anything, EditPage already extracted wpTextbox1
00923     }
00924 
00930     function initialiseForm() {
00931         global $wgUser;
00932         $this->edittime = $this->mArticle->getTimestamp();
00933 
00934         $content = $this->getContentObject( false ); #TODO: track content object?!
00935         if ( $content === false ) {
00936             return false;
00937         }
00938         $this->textbox1 = $this->toEditText( $content );
00939 
00940         // activate checkboxes if user wants them to be always active
00941         # Sort out the "watch" checkbox
00942         if ( $wgUser->getOption( 'watchdefault' ) ) {
00943             # Watch all edits
00944             $this->watchthis = true;
00945         } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
00946             # Watch creations
00947             $this->watchthis = true;
00948         } elseif ( $wgUser->isWatched( $this->mTitle ) ) {
00949             # Already watched
00950             $this->watchthis = true;
00951         }
00952         if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
00953             $this->minoredit = true;
00954         }
00955         if ( $this->textbox1 === false ) {
00956             return false;
00957         }
00958         return true;
00959     }
00960 
00968     protected function getContentObject( $def_content = null ) {
00969         global $wgOut, $wgRequest, $wgUser, $wgContLang;
00970 
00971         wfProfileIn( __METHOD__ );
00972 
00973         $content = false;
00974 
00975         // For message page not locally set, use the i18n message.
00976         // For other non-existent articles, use preload text if any.
00977         if ( !$this->mTitle->exists() || $this->section == 'new' ) {
00978             if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
00979                 # If this is a system message, get the default text.
00980                 $msg = $this->mTitle->getDefaultMessageText();
00981 
00982                 $content = $this->toEditContent( $msg );
00983             }
00984             if ( $content === false ) {
00985                 # If requested, preload some text.
00986                 $preload = $wgRequest->getVal( 'preload',
00987                     // Custom preload text for new sections
00988                     $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
00989                 $params = $wgRequest->getArray( 'preloadparams', array() );
00990 
00991                 $content = $this->getPreloadedContent( $preload, $params );
00992             }
00993         // For existing pages, get text based on "undo" or section parameters.
00994         } else {
00995             if ( $this->section != '' ) {
00996                 // Get section edit text (returns $def_text for invalid sections)
00997                 $orig = $this->getOriginalContent( $wgUser );
00998                 $content = $orig ? $orig->getSection( $this->section ) : null;
00999 
01000                 if ( !$content ) {
01001                     $content = $def_content;
01002                 }
01003             } else {
01004                 $undoafter = $wgRequest->getInt( 'undoafter' );
01005                 $undo = $wgRequest->getInt( 'undo' );
01006 
01007                 if ( $undo > 0 && $undoafter > 0 ) {
01008 
01009                     $undorev = Revision::newFromId( $undo );
01010                     $oldrev = Revision::newFromId( $undoafter );
01011 
01012                     # Sanity check, make sure it's the right page,
01013                     # the revisions exist and they were not deleted.
01014                     # Otherwise, $content will be left as-is.
01015                     if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
01016                         !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
01017                         !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
01018 
01019                         $content = $this->mArticle->getUndoContent( $undorev, $oldrev );
01020 
01021                         if ( $content === false ) {
01022                             # Warn the user that something went wrong
01023                             $undoMsg = 'failure';
01024                         } else {
01025                             $oldContent = $this->mArticle->getPage()->getContent( Revision::RAW );
01026                             $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
01027                             $newContent = $content->preSaveTransform( $this->mTitle, $wgUser, $popts );
01028 
01029                             if ( $newContent->equals( $oldContent ) ) {
01030                                 # Tell the user that the undo results in no change,
01031                                 # i.e. the revisions were already undone.
01032                                 $undoMsg = 'nochange';
01033                                 $content = false;
01034                             } else {
01035                                 # Inform the user of our success and set an automatic edit summary
01036                                 $undoMsg = 'success';
01037 
01038                                 # If we just undid one rev, use an autosummary
01039                                 $firstrev = $oldrev->getNext();
01040                                 if ( $firstrev && $firstrev->getId() == $undo ) {
01041                                     $userText = $undorev->getUserText();
01042                                     if ( $userText === '' ) {
01043                                         $undoSummary = wfMessage(
01044                                             'undo-summary-username-hidden',
01045                                             $undo
01046                                         )->inContentLanguage()->text();
01047                                     } else {
01048                                         $undoSummary = wfMessage(
01049                                             'undo-summary',
01050                                             $undo,
01051                                             $userText
01052                                         )->inContentLanguage()->text();
01053                                     }
01054                                     if ( $this->summary === '' ) {
01055                                         $this->summary = $undoSummary;
01056                                     } else {
01057                                         $this->summary = $undoSummary . wfMessage( 'colon-separator' )
01058                                             ->inContentLanguage()->text() . $this->summary;
01059                                     }
01060                                     $this->undidRev = $undo;
01061                                 }
01062                                 $this->formtype = 'diff';
01063                             }
01064                         }
01065                     } else {
01066                         // Failed basic sanity checks.
01067                         // Older revisions may have been removed since the link
01068                         // was created, or we may simply have got bogus input.
01069                         $undoMsg = 'norev';
01070                     }
01071 
01072                     // Messages: undo-success, undo-failure, undo-norev, undo-nochange
01073                     $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
01074                     $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
01075                         wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
01076                 }
01077 
01078                 if ( $content === false ) {
01079                     $content = $this->getOriginalContent( $wgUser );
01080                 }
01081             }
01082         }
01083 
01084         wfProfileOut( __METHOD__ );
01085         return $content;
01086     }
01087 
01103     private function getOriginalContent( User $user ) {
01104         if ( $this->section == 'new' ) {
01105             return $this->getCurrentContent();
01106         }
01107         $revision = $this->mArticle->getRevisionFetched();
01108         if ( $revision === null ) {
01109             if ( !$this->contentModel ) {
01110                 $this->contentModel = $this->getTitle()->getContentModel();
01111             }
01112             $handler = ContentHandler::getForModelID( $this->contentModel );
01113 
01114             return $handler->makeEmptyContent();
01115         }
01116         $content = $revision->getContent( Revision::FOR_THIS_USER, $user );
01117         return $content;
01118     }
01119 
01128     protected function getCurrentContent() {
01129         $rev = $this->mArticle->getRevision();
01130         $content = $rev ? $rev->getContent( Revision::RAW ) : null;
01131 
01132         if ( $content === false || $content === null ) {
01133             if ( !$this->contentModel ) {
01134                 $this->contentModel = $this->getTitle()->getContentModel();
01135             }
01136             $handler = ContentHandler::getForModelID( $this->contentModel );
01137 
01138             return $handler->makeEmptyContent();
01139         } else {
01140             # nasty side-effect, but needed for consistency
01141             $this->contentModel = $rev->getContentModel();
01142             $this->contentFormat = $rev->getContentFormat();
01143 
01144             return $content;
01145         }
01146     }
01147 
01155     public function setPreloadedContent( Content $content ) {
01156         $this->mPreloadContent = $content;
01157     }
01158 
01170     protected function getPreloadedContent( $preload, $params = array() ) {
01171         global $wgUser;
01172 
01173         if ( !empty( $this->mPreloadContent ) ) {
01174             return $this->mPreloadContent;
01175         }
01176 
01177         $handler = ContentHandler::getForTitle( $this->getTitle() );
01178 
01179         if ( $preload === '' ) {
01180             return $handler->makeEmptyContent();
01181         }
01182 
01183         $title = Title::newFromText( $preload );
01184         # Check for existence to avoid getting MediaWiki:Noarticletext
01185         if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
01186             //TODO: somehow show a warning to the user!
01187             return $handler->makeEmptyContent();
01188         }
01189 
01190         $page = WikiPage::factory( $title );
01191         if ( $page->isRedirect() ) {
01192             $title = $page->getRedirectTarget();
01193             # Same as before
01194             if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
01195                 //TODO: somehow show a warning to the user!
01196                 return $handler->makeEmptyContent();
01197             }
01198             $page = WikiPage::factory( $title );
01199         }
01200 
01201         $parserOptions = ParserOptions::newFromUser( $wgUser );
01202         $content = $page->getContent( Revision::RAW );
01203 
01204         if ( !$content ) {
01205             //TODO: somehow show a warning to the user!
01206             return $handler->makeEmptyContent();
01207         }
01208 
01209         if ( $content->getModel() !== $handler->getModelID() ) {
01210             $converted = $content->convert( $handler->getModelID() );
01211 
01212             if ( !$converted ) {
01213                 //TODO: somehow show a warning to the user!
01214                 wfDebug( "Attempt to preload incompatible content: "
01215                         . "can't convert " . $content->getModel()
01216                         . " to " . $handler->getModelID() );
01217 
01218                 return $handler->makeEmptyContent();
01219             }
01220 
01221             $content = $converted;
01222         }
01223 
01224         return $content->preloadTransform( $title, $parserOptions, $params );
01225     }
01226 
01234     function tokenOk( &$request ) {
01235         global $wgUser;
01236         $token = $request->getVal( 'wpEditToken' );
01237         $this->mTokenOk = $wgUser->matchEditToken( $token );
01238         $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
01239         return $this->mTokenOk;
01240     }
01241 
01258     protected function setPostEditCookie( $statusValue ) {
01259         $revisionId = $this->mArticle->getLatest();
01260         $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
01261 
01262         $val = 'saved';
01263         if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) {
01264             $val = 'created';
01265         } elseif ( $this->oldid ) {
01266             $val = 'restored';
01267         }
01268 
01269         $response = RequestContext::getMain()->getRequest()->response();
01270         $response->setcookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION, array(
01271             'httpOnly' => false,
01272         ) );
01273     }
01274 
01280     public function attemptSave() {
01281         global $wgUser;
01282 
01283         $resultDetails = false;
01284         # Allow bots to exempt some edits from bot flagging
01285         $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
01286         $status = $this->internalAttemptSave( $resultDetails, $bot );
01287 
01288         return $this->handleStatus( $status, $resultDetails );
01289     }
01290 
01300     private function handleStatus( Status $status, $resultDetails ) {
01301         global $wgUser, $wgOut;
01302 
01307         if ( $status->value == self::AS_SUCCESS_UPDATE
01308             || $status->value == self::AS_SUCCESS_NEW_ARTICLE
01309         ) {
01310             $this->didSave = true;
01311             if ( !$resultDetails['nullEdit'] ) {
01312                 $this->setPostEditCookie( $status->value );
01313             }
01314         }
01315 
01316         switch ( $status->value ) {
01317             case self::AS_HOOK_ERROR_EXPECTED:
01318             case self::AS_CONTENT_TOO_BIG:
01319             case self::AS_ARTICLE_WAS_DELETED:
01320             case self::AS_CONFLICT_DETECTED:
01321             case self::AS_SUMMARY_NEEDED:
01322             case self::AS_TEXTBOX_EMPTY:
01323             case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
01324             case self::AS_END:
01325             case self::AS_BLANK_ARTICLE:
01326                 return true;
01327 
01328             case self::AS_HOOK_ERROR:
01329                 return false;
01330 
01331             case self::AS_PARSE_ERROR:
01332                 $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>' );
01333                 return true;
01334 
01335             case self::AS_SUCCESS_NEW_ARTICLE:
01336                 $query = $resultDetails['redirect'] ? 'redirect=no' : '';
01337                 $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
01338                 $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
01339                 return false;
01340 
01341             case self::AS_SUCCESS_UPDATE:
01342                 $extraQuery = '';
01343                 $sectionanchor = $resultDetails['sectionanchor'];
01344 
01345                 // Give extensions a chance to modify URL query on update
01346                 wfRunHooks(
01347                     'ArticleUpdateBeforeRedirect',
01348                     array( $this->mArticle, &$sectionanchor, &$extraQuery )
01349                 );
01350 
01351                 if ( $resultDetails['redirect'] ) {
01352                     if ( $extraQuery == '' ) {
01353                         $extraQuery = 'redirect=no';
01354                     } else {
01355                         $extraQuery = 'redirect=no&' . $extraQuery;
01356                     }
01357                 }
01358                 $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
01359                 return false;
01360 
01361             case self::AS_SPAM_ERROR:
01362                 $this->spamPageWithContent( $resultDetails['spam'] );
01363                 return false;
01364 
01365             case self::AS_BLOCKED_PAGE_FOR_USER:
01366                 throw new UserBlockedError( $wgUser->getBlock() );
01367 
01368             case self::AS_IMAGE_REDIRECT_ANON:
01369             case self::AS_IMAGE_REDIRECT_LOGGED:
01370                 throw new PermissionsError( 'upload' );
01371 
01372             case self::AS_READ_ONLY_PAGE_ANON:
01373             case self::AS_READ_ONLY_PAGE_LOGGED:
01374                 throw new PermissionsError( 'edit' );
01375 
01376             case self::AS_READ_ONLY_PAGE:
01377                 throw new ReadOnlyError;
01378 
01379             case self::AS_RATE_LIMITED:
01380                 throw new ThrottledError();
01381 
01382             case self::AS_NO_CREATE_PERMISSION:
01383                 $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
01384                 throw new PermissionsError( $permission );
01385 
01386             default:
01387                 // We don't recognize $status->value. The only way that can happen
01388                 // is if an extension hook aborted from inside ArticleSave.
01389                 // Render the status object into $this->hookError
01390                 // FIXME this sucks, we should just use the Status object throughout
01391                 $this->hookError = '<div class="error">' . $status->getWikitext() .
01392                     '</div>';
01393                 return true;
01394         }
01395     }
01396 
01406     protected function runPostMergeFilters( Content $content, Status $status, User $user ) {
01407         // Run old style post-section-merge edit filter
01408         if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged',
01409             array( $this, $content, &$this->hookError, $this->summary ) ) ) {
01410 
01411             # Error messages etc. could be handled within the hook...
01412             $status->fatal( 'hookaborted' );
01413             $status->value = self::AS_HOOK_ERROR;
01414             return false;
01415         } elseif ( $this->hookError != '' ) {
01416             # ...or the hook could be expecting us to produce an error
01417             $status->fatal( 'hookaborted' );
01418             $status->value = self::AS_HOOK_ERROR_EXPECTED;
01419             return false;
01420         }
01421 
01422         // Run new style post-section-merge edit filter
01423         if ( !wfRunHooks( 'EditFilterMergedContent',
01424             array( $this->mArticle->getContext(), $content, $status, $this->summary,
01425                 $user, $this->minoredit ) ) ) {
01426 
01427             # Error messages etc. could be handled within the hook...
01428             // XXX: $status->value may already be something informative...
01429             $this->hookError = $status->getWikiText();
01430             $status->fatal( 'hookaborted' );
01431             $status->value = self::AS_HOOK_ERROR;
01432             return false;
01433         } elseif ( !$status->isOK() ) {
01434             # ...or the hook could be expecting us to produce an error
01435             // FIXME this sucks, we should just use the Status object throughout
01436             $this->hookError = $status->getWikiText();
01437             $status->fatal( 'hookaborted' );
01438             $status->value = self::AS_HOOK_ERROR_EXPECTED;
01439             return false;
01440         }
01441 
01442         return true;
01443     }
01444 
01451     private function newSectionSummary( &$sectionanchor = null ) {
01452         global $wgParser;
01453 
01454         if ( $this->sectiontitle !== '' ) {
01455             $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
01456             // If no edit summary was specified, create one automatically from the section
01457             // title and have it link to the new section. Otherwise, respect the summary as
01458             // passed.
01459             if ( $this->summary === '' ) {
01460                 $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
01461                 return wfMessage( 'newsectionsummary' )
01462                     ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
01463             }
01464         } elseif ( $this->summary !== '' ) {
01465             $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
01466             # This is a new section, so create a link to the new section
01467             # in the revision summary.
01468             $cleanSummary = $wgParser->stripSectionName( $this->summary );
01469             return wfMessage( 'newsectionsummary' )
01470                 ->rawParams( $cleanSummary )->inContentLanguage()->text();
01471         }
01472         return $this->summary;
01473     }
01474 
01499     function internalAttemptSave( &$result, $bot = false ) {
01500         global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize;
01501 
01502         $status = Status::newGood();
01503 
01504         wfProfileIn( __METHOD__ );
01505         wfProfileIn( __METHOD__ . '-checks' );
01506 
01507         if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
01508             wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
01509             $status->fatal( 'hookaborted' );
01510             $status->value = self::AS_HOOK_ERROR;
01511             wfProfileOut( __METHOD__ . '-checks' );
01512             wfProfileOut( __METHOD__ );
01513             return $status;
01514         }
01515 
01516         $spam = $wgRequest->getText( 'wpAntispam' );
01517         if ( $spam !== '' ) {
01518             wfDebugLog(
01519                 'SimpleAntiSpam',
01520                 $wgUser->getName() .
01521                 ' editing "' .
01522                 $this->mTitle->getPrefixedText() .
01523                 '" submitted bogus field "' .
01524                 $spam .
01525                 '"'
01526             );
01527             $status->fatal( 'spamprotectionmatch', false );
01528             $status->value = self::AS_SPAM_ERROR;
01529             wfProfileOut( __METHOD__ . '-checks' );
01530             wfProfileOut( __METHOD__ );
01531             return $status;
01532         }
01533 
01534         try {
01535             # Construct Content object
01536             $textbox_content = $this->toEditContent( $this->textbox1 );
01537         } catch ( MWContentSerializationException $ex ) {
01538             $status->fatal(
01539                 'content-failed-to-parse',
01540                 $this->contentModel,
01541                 $this->contentFormat,
01542                 $ex->getMessage()
01543             );
01544             $status->value = self::AS_PARSE_ERROR;
01545             wfProfileOut( __METHOD__ . '-checks' );
01546             wfProfileOut( __METHOD__ );
01547             return $status;
01548         }
01549 
01550         # Check image redirect
01551         if ( $this->mTitle->getNamespace() == NS_FILE &&
01552             $textbox_content->isRedirect() &&
01553             !$wgUser->isAllowed( 'upload' )
01554         ) {
01555                 $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
01556                 $status->setResult( false, $code );
01557 
01558                 wfProfileOut( __METHOD__ . '-checks' );
01559                 wfProfileOut( __METHOD__ );
01560 
01561                 return $status;
01562         }
01563 
01564         # Check for spam
01565         $match = self::matchSummarySpamRegex( $this->summary );
01566         if ( $match === false && $this->section == 'new' ) {
01567             # $wgSpamRegex is enforced on this new heading/summary because, unlike
01568             # regular summaries, it is added to the actual wikitext.
01569             if ( $this->sectiontitle !== '' ) {
01570                 # This branch is taken when the API is used with the 'sectiontitle' parameter.
01571                 $match = self::matchSpamRegex( $this->sectiontitle );
01572             } else {
01573                 # This branch is taken when the "Add Topic" user interface is used, or the API
01574                 # is used with the 'summary' parameter.
01575                 $match = self::matchSpamRegex( $this->summary );
01576             }
01577         }
01578         if ( $match === false ) {
01579             $match = self::matchSpamRegex( $this->textbox1 );
01580         }
01581         if ( $match !== false ) {
01582             $result['spam'] = $match;
01583             $ip = $wgRequest->getIP();
01584             $pdbk = $this->mTitle->getPrefixedDBkey();
01585             $match = str_replace( "\n", '', $match );
01586             wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
01587             $status->fatal( 'spamprotectionmatch', $match );
01588             $status->value = self::AS_SPAM_ERROR;
01589             wfProfileOut( __METHOD__ . '-checks' );
01590             wfProfileOut( __METHOD__ );
01591             return $status;
01592         }
01593         if ( !wfRunHooks(
01594             'EditFilter',
01595             array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) )
01596         ) {
01597             # Error messages etc. could be handled within the hook...
01598             $status->fatal( 'hookaborted' );
01599             $status->value = self::AS_HOOK_ERROR;
01600             wfProfileOut( __METHOD__ . '-checks' );
01601             wfProfileOut( __METHOD__ );
01602             return $status;
01603         } elseif ( $this->hookError != '' ) {
01604             # ...or the hook could be expecting us to produce an error
01605             $status->fatal( 'hookaborted' );
01606             $status->value = self::AS_HOOK_ERROR_EXPECTED;
01607             wfProfileOut( __METHOD__ . '-checks' );
01608             wfProfileOut( __METHOD__ );
01609             return $status;
01610         }
01611 
01612         if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) {
01613             // Auto-block user's IP if the account was "hard" blocked
01614             $wgUser->spreadAnyEditBlock();
01615             # Check block state against master, thus 'false'.
01616             $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
01617             wfProfileOut( __METHOD__ . '-checks' );
01618             wfProfileOut( __METHOD__ );
01619             return $status;
01620         }
01621 
01622         $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
01623         if ( $this->kblength > $wgMaxArticleSize ) {
01624             // Error will be displayed by showEditForm()
01625             $this->tooBig = true;
01626             $status->setResult( false, self::AS_CONTENT_TOO_BIG );
01627             wfProfileOut( __METHOD__ . '-checks' );
01628             wfProfileOut( __METHOD__ );
01629             return $status;
01630         }
01631 
01632         if ( !$wgUser->isAllowed( 'edit' ) ) {
01633             if ( $wgUser->isAnon() ) {
01634                 $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
01635                 wfProfileOut( __METHOD__ . '-checks' );
01636                 wfProfileOut( __METHOD__ );
01637                 return $status;
01638             } else {
01639                 $status->fatal( 'readonlytext' );
01640                 $status->value = self::AS_READ_ONLY_PAGE_LOGGED;
01641                 wfProfileOut( __METHOD__ . '-checks' );
01642                 wfProfileOut( __METHOD__ );
01643                 return $status;
01644             }
01645         }
01646 
01647         if ( wfReadOnly() ) {
01648             $status->fatal( 'readonlytext' );
01649             $status->value = self::AS_READ_ONLY_PAGE;
01650             wfProfileOut( __METHOD__ . '-checks' );
01651             wfProfileOut( __METHOD__ );
01652             return $status;
01653         }
01654         if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 ) ) {
01655             $status->fatal( 'actionthrottledtext' );
01656             $status->value = self::AS_RATE_LIMITED;
01657             wfProfileOut( __METHOD__ . '-checks' );
01658             wfProfileOut( __METHOD__ );
01659             return $status;
01660         }
01661 
01662         # If the article has been deleted while editing, don't save it without
01663         # confirmation
01664         if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
01665             $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
01666             wfProfileOut( __METHOD__ . '-checks' );
01667             wfProfileOut( __METHOD__ );
01668             return $status;
01669         }
01670 
01671         wfProfileOut( __METHOD__ . '-checks' );
01672 
01673         # Load the page data from the master. If anything changes in the meantime,
01674         # we detect it by using page_latest like a token in a 1 try compare-and-swap.
01675         $this->mArticle->loadPageData( 'fromdbmaster' );
01676         $new = !$this->mArticle->exists();
01677 
01678         if ( $new ) {
01679             // Late check for create permission, just in case *PARANOIA*
01680             if ( !$this->mTitle->userCan( 'create', $wgUser ) ) {
01681                 $status->fatal( 'nocreatetext' );
01682                 $status->value = self::AS_NO_CREATE_PERMISSION;
01683                 wfDebug( __METHOD__ . ": no create permission\n" );
01684                 wfProfileOut( __METHOD__ );
01685                 return $status;
01686             }
01687 
01688             // Don't save a new page if it's blank or if it's a MediaWiki:
01689             // message with content equivalent to default (allow empty pages
01690             // in this case to disable messages, see bug 50124)
01691             $defaultMessageText = $this->mTitle->getDefaultMessageText();
01692             if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false ) {
01693                 $defaultText = $defaultMessageText;
01694             } else {
01695                 $defaultText = '';
01696             }
01697 
01698             if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) {
01699                 $this->blankArticle = true;
01700                 $status->fatal( 'blankarticle' );
01701                 $status->setResult( false, self::AS_BLANK_ARTICLE );
01702                 wfProfileOut( __METHOD__ );
01703                 return $status;
01704             }
01705 
01706             if ( !$this->runPostMergeFilters( $textbox_content, $status, $wgUser ) ) {
01707                 wfProfileOut( __METHOD__ );
01708                 return $status;
01709             }
01710 
01711             $content = $textbox_content;
01712 
01713             $result['sectionanchor'] = '';
01714             if ( $this->section == 'new' ) {
01715                 if ( $this->sectiontitle !== '' ) {
01716                     // Insert the section title above the content.
01717                     $content = $content->addSectionHeader( $this->sectiontitle );
01718                 } elseif ( $this->summary !== '' ) {
01719                     // Insert the section title above the content.
01720                     $content = $content->addSectionHeader( $this->summary );
01721                 }
01722                 $this->summary = $this->newSectionSummary( $result['sectionanchor'] );
01723             }
01724 
01725             $status->value = self::AS_SUCCESS_NEW_ARTICLE;
01726 
01727         } else { # not $new
01728 
01729             # Article exists. Check for edit conflict.
01730 
01731             $this->mArticle->clear(); # Force reload of dates, etc.
01732             $timestamp = $this->mArticle->getTimestamp();
01733 
01734             wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
01735 
01736             if ( $timestamp != $this->edittime ) {
01737                 $this->isConflict = true;
01738                 if ( $this->section == 'new' ) {
01739                     if ( $this->mArticle->getUserText() == $wgUser->getName() &&
01740                         $this->mArticle->getComment() == $this->newSectionSummary()
01741                     ) {
01742                         // Probably a duplicate submission of a new comment.
01743                         // This can happen when squid resends a request after
01744                         // a timeout but the first one actually went through.
01745                         wfDebug( __METHOD__
01746                             . ": duplicate new section submission; trigger edit conflict!\n" );
01747                     } else {
01748                         // New comment; suppress conflict.
01749                         $this->isConflict = false;
01750                         wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
01751                     }
01752                 } elseif ( $this->section == ''
01753                     && Revision::userWasLastToEdit(
01754                         DB_MASTER, $this->mTitle->getArticleID(),
01755                         $wgUser->getId(), $this->edittime
01756                     )
01757                 ) {
01758                     # Suppress edit conflict with self, except for section edits where merging is required.
01759                     wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
01760                     $this->isConflict = false;
01761                 }
01762             }
01763 
01764             // If sectiontitle is set, use it, otherwise use the summary as the section title.
01765             if ( $this->sectiontitle !== '' ) {
01766                 $sectionTitle = $this->sectiontitle;
01767             } else {
01768                 $sectionTitle = $this->summary;
01769             }
01770 
01771             $content = null;
01772 
01773             if ( $this->isConflict ) {
01774                 wfDebug( __METHOD__
01775                     . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
01776                     . " (article time '{$timestamp}')\n" );
01777 
01778                 $content = $this->mArticle->replaceSectionContent(
01779                     $this->section,
01780                     $textbox_content,
01781                     $sectionTitle,
01782                     $this->edittime
01783                 );
01784             } else {
01785                 wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
01786                 $content = $this->mArticle->replaceSectionContent(
01787                     $this->section,
01788                     $textbox_content,
01789                     $sectionTitle
01790                 );
01791             }
01792 
01793             if ( is_null( $content ) ) {
01794                 wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
01795                 $this->isConflict = true;
01796                 $content = $textbox_content; // do not try to merge here!
01797             } elseif ( $this->isConflict ) {
01798                 # Attempt merge
01799                 if ( $this->mergeChangesIntoContent( $content ) ) {
01800                     // Successful merge! Maybe we should tell the user the good news?
01801                     $this->isConflict = false;
01802                     wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
01803                 } else {
01804                     $this->section = '';
01805                     $this->textbox1 = ContentHandler::getContentText( $content );
01806                     wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
01807                 }
01808             }
01809 
01810             if ( $this->isConflict ) {
01811                 $status->setResult( false, self::AS_CONFLICT_DETECTED );
01812                 wfProfileOut( __METHOD__ );
01813                 return $status;
01814             }
01815 
01816             if ( !$this->runPostMergeFilters( $content, $status, $wgUser ) ) {
01817                 wfProfileOut( __METHOD__ );
01818                 return $status;
01819             }
01820 
01821             if ( $this->section == 'new' ) {
01822                 // Handle the user preference to force summaries here
01823                 if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) {
01824                     $this->missingSummary = true;
01825                     $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
01826                     $status->value = self::AS_SUMMARY_NEEDED;
01827                     wfProfileOut( __METHOD__ );
01828                     return $status;
01829                 }
01830 
01831                 // Do not allow the user to post an empty comment
01832                 if ( $this->textbox1 == '' ) {
01833                     $this->missingComment = true;
01834                     $status->fatal( 'missingcommenttext' );
01835                     $status->value = self::AS_TEXTBOX_EMPTY;
01836                     wfProfileOut( __METHOD__ );
01837                     return $status;
01838                 }
01839             } elseif ( !$this->allowBlankSummary
01840                 && !$content->equals( $this->getOriginalContent( $wgUser ) )
01841                 && !$content->isRedirect()
01842                 && md5( $this->summary ) == $this->autoSumm
01843             ) {
01844                 $this->missingSummary = true;
01845                 $status->fatal( 'missingsummary' );
01846                 $status->value = self::AS_SUMMARY_NEEDED;
01847                 wfProfileOut( __METHOD__ );
01848                 return $status;
01849             }
01850 
01851             # All's well
01852             wfProfileIn( __METHOD__ . '-sectionanchor' );
01853             $sectionanchor = '';
01854             if ( $this->section == 'new' ) {
01855                 $this->summary = $this->newSectionSummary( $sectionanchor );
01856             } elseif ( $this->section != '' ) {
01857                 # Try to get a section anchor from the section source, redirect
01858                 # to edited section if header found.
01859                 # XXX: Might be better to integrate this into Article::replaceSection
01860                 # for duplicate heading checking and maybe parsing.
01861                 $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
01862                 # We can't deal with anchors, includes, html etc in the header for now,
01863                 # headline would need to be parsed to improve this.
01864                 if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
01865                     $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
01866                 }
01867             }
01868             $result['sectionanchor'] = $sectionanchor;
01869             wfProfileOut( __METHOD__ . '-sectionanchor' );
01870 
01871             // Save errors may fall down to the edit form, but we've now
01872             // merged the section into full text. Clear the section field
01873             // so that later submission of conflict forms won't try to
01874             // replace that into a duplicated mess.
01875             $this->textbox1 = $this->toEditText( $content );
01876             $this->section = '';
01877 
01878             $status->value = self::AS_SUCCESS_UPDATE;
01879         }
01880 
01881         // Check for length errors again now that the section is merged in
01882         $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
01883         if ( $this->kblength > $wgMaxArticleSize ) {
01884             $this->tooBig = true;
01885             $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
01886             wfProfileOut( __METHOD__ );
01887             return $status;
01888         }
01889 
01890         $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
01891             ( $new ? EDIT_NEW : EDIT_UPDATE ) |
01892             ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
01893             ( $bot ? EDIT_FORCE_BOT : 0 );
01894 
01895         $doEditStatus = $this->mArticle->doEditContent(
01896             $content,
01897             $this->summary,
01898             $flags,
01899             false,
01900             null,
01901             $content->getDefaultFormat()
01902         );
01903 
01904         if ( !$doEditStatus->isOK() ) {
01905             // Failure from doEdit()
01906             // Show the edit conflict page for certain recognized errors from doEdit(),
01907             // but don't show it for errors from extension hooks
01908             $errors = $doEditStatus->getErrorsArray();
01909             if ( in_array( $errors[0][0],
01910                     array( 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ) )
01911             ) {
01912                 $this->isConflict = true;
01913                 // Destroys data doEdit() put in $status->value but who cares
01914                 $doEditStatus->value = self::AS_END;
01915             }
01916             wfProfileOut( __METHOD__ );
01917             return $doEditStatus;
01918         }
01919 
01920         $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
01921         if ( $result['nullEdit'] ) {
01922             // We don't know if it was a null edit until now, so increment here
01923             $wgUser->pingLimiter( 'linkpurge' );
01924         }
01925         $result['redirect'] = $content->isRedirect();
01926         $this->updateWatchlist();
01927         wfProfileOut( __METHOD__ );
01928         return $status;
01929     }
01930 
01934     protected function updateWatchlist() {
01935         global $wgUser;
01936 
01937         if ( $wgUser->isLoggedIn()
01938             && $this->watchthis != $wgUser->isWatched( $this->mTitle, WatchedItem::IGNORE_USER_RIGHTS )
01939         ) {
01940             $fname = __METHOD__;
01941             $title = $this->mTitle;
01942             $watch = $this->watchthis;
01943 
01944             // Do this in its own transaction to reduce contention...
01945             $dbw = wfGetDB( DB_MASTER );
01946             $dbw->onTransactionIdle( function () use ( $dbw, $title, $watch, $wgUser, $fname ) {
01947                 WatchAction::doWatchOrUnwatch( $watch, $title, $wgUser );
01948             } );
01949         }
01950     }
01951 
01963     private function mergeChangesIntoContent( &$editContent ) {
01964         wfProfileIn( __METHOD__ );
01965 
01966         $db = wfGetDB( DB_MASTER );
01967 
01968         // This is the revision the editor started from
01969         $baseRevision = $this->getBaseRevision();
01970         $baseContent = $baseRevision ? $baseRevision->getContent() : null;
01971 
01972         if ( is_null( $baseContent ) ) {
01973             wfProfileOut( __METHOD__ );
01974             return false;
01975         }
01976 
01977         // The current state, we want to merge updates into it
01978         $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
01979         $currentContent = $currentRevision ? $currentRevision->getContent() : null;
01980 
01981         if ( is_null( $currentContent ) ) {
01982             wfProfileOut( __METHOD__ );
01983             return false;
01984         }
01985 
01986         $handler = ContentHandler::getForModelID( $baseContent->getModel() );
01987 
01988         $result = $handler->merge3( $baseContent, $editContent, $currentContent );
01989 
01990         if ( $result ) {
01991             $editContent = $result;
01992             wfProfileOut( __METHOD__ );
01993             return true;
01994         }
01995 
01996         wfProfileOut( __METHOD__ );
01997         return false;
01998     }
01999 
02003     function getBaseRevision() {
02004         if ( !$this->mBaseRevision ) {
02005             $db = wfGetDB( DB_MASTER );
02006             $this->mBaseRevision = Revision::loadFromTimestamp(
02007                 $db, $this->mTitle, $this->edittime );
02008         }
02009         return $this->mBaseRevision;
02010     }
02011 
02019     public static function matchSpamRegex( $text ) {
02020         global $wgSpamRegex;
02021         // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
02022         $regexes = (array)$wgSpamRegex;
02023         return self::matchSpamRegexInternal( $text, $regexes );
02024     }
02025 
02033     public static function matchSummarySpamRegex( $text ) {
02034         global $wgSummarySpamRegex;
02035         $regexes = (array)$wgSummarySpamRegex;
02036         return self::matchSpamRegexInternal( $text, $regexes );
02037     }
02038 
02044     protected static function matchSpamRegexInternal( $text, $regexes ) {
02045         foreach ( $regexes as $regex ) {
02046             $matches = array();
02047             if ( preg_match( $regex, $text, $matches ) ) {
02048                 return $matches[0];
02049             }
02050         }
02051         return false;
02052     }
02053 
02054     function setHeaders() {
02055         global $wgOut, $wgUser;
02056 
02057         $wgOut->addModules( 'mediawiki.action.edit' );
02058         $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
02059 
02060         if ( $wgUser->getOption( 'uselivepreview', false ) ) {
02061             $wgOut->addModules( 'mediawiki.action.edit.preview' );
02062         }
02063 
02064         if ( $wgUser->getOption( 'useeditwarning', false ) ) {
02065             $wgOut->addModules( 'mediawiki.action.edit.editWarning' );
02066         }
02067 
02068         $wgOut->setRobotPolicy( 'noindex,nofollow' );
02069 
02070         # Enabled article-related sidebar, toplinks, etc.
02071         $wgOut->setArticleRelated( true );
02072 
02073         $contextTitle = $this->getContextTitle();
02074         if ( $this->isConflict ) {
02075             $msg = 'editconflict';
02076         } elseif ( $contextTitle->exists() && $this->section != '' ) {
02077             $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
02078         } else {
02079             $msg = $contextTitle->exists()
02080                 || ( $contextTitle->getNamespace() == NS_MEDIAWIKI
02081                     && $contextTitle->getDefaultMessageText() !== false
02082                 )
02083                 ? 'editing'
02084                 : 'creating';
02085         }
02086 
02087         # Use the title defined by DISPLAYTITLE magic word when present
02088         $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
02089         if ( $displayTitle === false ) {
02090             $displayTitle = $contextTitle->getPrefixedText();
02091         }
02092         $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) );
02093     }
02094 
02098     protected function showIntro() {
02099         global $wgOut, $wgUser;
02100         if ( $this->suppressIntro ) {
02101             return;
02102         }
02103 
02104         $namespace = $this->mTitle->getNamespace();
02105 
02106         if ( $namespace == NS_MEDIAWIKI ) {
02107             # Show a warning if editing an interface message
02108             $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
02109         } elseif ( $namespace == NS_FILE ) {
02110             # Show a hint to shared repo
02111             $file = wfFindFile( $this->mTitle );
02112             if ( $file && !$file->isLocal() ) {
02113                 $descUrl = $file->getDescriptionUrl();
02114                 # there must be a description url to show a hint to shared repo
02115                 if ( $descUrl ) {
02116                     if ( !$this->mTitle->exists() ) {
02117                         $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", array(
02118                                     'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
02119                         ) );
02120                     } else {
02121                         $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", array(
02122                                     'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
02123                         ) );
02124                     }
02125                 }
02126             }
02127         }
02128 
02129         # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
02130         # Show log extract when the user is currently blocked
02131         if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
02132             $parts = explode( '/', $this->mTitle->getText(), 2 );
02133             $username = $parts[0];
02134             $user = User::newFromName( $username, false /* allow IP users*/ );
02135             $ip = User::isIP( $username );
02136             $block = Block::newFromTarget( $user, $user );
02137             if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
02138                 $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
02139                     array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
02140             } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { # Show log extract if the user is currently blocked
02141                 LogEventsList::showLogExtract(
02142                     $wgOut,
02143                     'block',
02144                     MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
02145                     '',
02146                     array(
02147                         'lim' => 1,
02148                         'showIfEmpty' => false,
02149                         'msgKey' => array(
02150                             'blocked-notice-logextract',
02151                             $user->getName() # Support GENDER in notice
02152                         )
02153                     )
02154                 );
02155             }
02156         }
02157         # Try to add a custom edit intro, or use the standard one if this is not possible.
02158         if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
02159             $helpLink = wfExpandUrl( Skin::makeInternalOrExternalUrl(
02160                 wfMessage( 'helppage' )->inContentLanguage()->text()
02161             ) );
02162             if ( $wgUser->isLoggedIn() ) {
02163                 $wgOut->wrapWikiMsg(
02164                     // Suppress the external link icon, consider the help url an internal one
02165                     "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
02166                     array(
02167                         'newarticletext',
02168                         $helpLink
02169                     )
02170                 );
02171             } else {
02172                 $wgOut->wrapWikiMsg(
02173                     // Suppress the external link icon, consider the help url an internal one
02174                     "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
02175                     array(
02176                         'newarticletextanon',
02177                         $helpLink
02178                     )
02179                 );
02180             }
02181         }
02182         # Give a notice if the user is editing a deleted/moved page...
02183         if ( !$this->mTitle->exists() ) {
02184             LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle,
02185                 '',
02186                 array(
02187                     'lim' => 10,
02188                     'conds' => array( "log_action != 'revision'" ),
02189                     'showIfEmpty' => false,
02190                     'msgKey' => array( 'recreate-moveddeleted-warn' )
02191                 )
02192             );
02193         }
02194     }
02195 
02201     protected function showCustomIntro() {
02202         if ( $this->editintro ) {
02203             $title = Title::newFromText( $this->editintro );
02204             if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
02205                 global $wgOut;
02206                 // Added using template syntax, to take <noinclude>'s into account.
02207                 $wgOut->addWikiTextTitleTidy( '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>', $this->mTitle );
02208                 return true;
02209             }
02210         }
02211         return false;
02212     }
02213 
02232     protected function toEditText( $content ) {
02233         if ( $content === null || $content === false ) {
02234             return $content;
02235         }
02236 
02237         if ( is_string( $content ) ) {
02238             return $content;
02239         }
02240 
02241         if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
02242             throw new MWException( 'This content model is not supported: '
02243                 . ContentHandler::getLocalizedName( $content->getModel() ) );
02244         }
02245 
02246         return $content->serialize( $this->contentFormat );
02247     }
02248 
02265     protected function toEditContent( $text ) {
02266         if ( $text === false || $text === null ) {
02267             return $text;
02268         }
02269 
02270         $content = ContentHandler::makeContent( $text, $this->getTitle(),
02271             $this->contentModel, $this->contentFormat );
02272 
02273         if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
02274             throw new MWException( 'This content model is not supported: '
02275                 . ContentHandler::getLocalizedName( $content->getModel() ) );
02276         }
02277 
02278         return $content;
02279     }
02280 
02286     function showEditForm( $formCallback = null ) {
02287         global $wgOut, $wgUser;
02288 
02289         wfProfileIn( __METHOD__ );
02290 
02291         # need to parse the preview early so that we know which templates are used,
02292         # otherwise users with "show preview after edit box" will get a blank list
02293         # we parse this near the beginning so that setHeaders can do the title
02294         # setting work instead of leaving it in getPreviewText
02295         $previewOutput = '';
02296         if ( $this->formtype == 'preview' ) {
02297             $previewOutput = $this->getPreviewText();
02298         }
02299 
02300         wfRunHooks( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) );
02301 
02302         $this->setHeaders();
02303 
02304         if ( $this->showHeader() === false ) {
02305             wfProfileOut( __METHOD__ );
02306             return;
02307         }
02308 
02309         $wgOut->addHTML( $this->editFormPageTop );
02310 
02311         if ( $wgUser->getOption( 'previewontop' ) ) {
02312             $this->displayPreviewArea( $previewOutput, true );
02313         }
02314 
02315         $wgOut->addHTML( $this->editFormTextTop );
02316 
02317         $showToolbar = true;
02318         if ( $this->wasDeletedSinceLastEdit() ) {
02319             if ( $this->formtype == 'save' ) {
02320                 // Hide the toolbar and edit area, user can click preview to get it back
02321                 // Add an confirmation checkbox and explanation.
02322                 $showToolbar = false;
02323             } else {
02324                 $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
02325                     'deletedwhileediting' );
02326             }
02327         }
02328 
02329         // @todo add EditForm plugin interface and use it here!
02330         //       search for textarea1 and textares2, and allow EditForm to override all uses.
02331         $wgOut->addHTML( Html::openElement(
02332             'form',
02333             array(
02334                 'id' => self::EDITFORM_ID,
02335                 'name' => self::EDITFORM_ID,
02336                 'method' => 'post',
02337                 'action' => $this->getActionURL( $this->getContextTitle() ),
02338                 'enctype' => 'multipart/form-data'
02339             )
02340         ) );
02341 
02342         if ( is_callable( $formCallback ) ) {
02343             call_user_func_array( $formCallback, array( &$wgOut ) );
02344         }
02345 
02346         // Add an empty field to trip up spambots
02347         $wgOut->addHTML(
02348             Xml::openElement( 'div', array( 'id' => 'antispam-container', 'style' => 'display: none;' ) )
02349             . Html::rawElement(
02350                 'label',
02351                 array( 'for' => 'wpAntiSpam' ),
02352                 wfMessage( 'simpleantispam-label' )->parse()
02353             )
02354             . Xml::element(
02355                 'input',
02356                 array(
02357                     'type' => 'text',
02358                     'name' => 'wpAntispam',
02359                     'id' => 'wpAntispam',
02360                     'value' => ''
02361                 )
02362             )
02363             . Xml::closeElement( 'div' )
02364         );
02365 
02366         wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
02367 
02368         // Put these up at the top to ensure they aren't lost on early form submission
02369         $this->showFormBeforeText();
02370 
02371         if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
02372             $username = $this->lastDelete->user_name;
02373             $comment = $this->lastDelete->log_comment;
02374 
02375             // It is better to not parse the comment at all than to have templates expanded in the middle
02376             // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
02377             $key = $comment === ''
02378                 ? 'confirmrecreate-noreason'
02379                 : 'confirmrecreate';
02380             $wgOut->addHTML(
02381                 '<div class="mw-confirm-recreate">' .
02382                     wfMessage( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
02383                 Xml::checkLabel( wfMessage( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
02384                     array( 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
02385                 ) .
02386                 '</div>'
02387             );
02388         }
02389 
02390         # When the summary is hidden, also hide them on preview/show changes
02391         if ( $this->nosummary ) {
02392             $wgOut->addHTML( Html::hidden( 'nosummary', true ) );
02393         }
02394 
02395         # If a blank edit summary was previously provided, and the appropriate
02396         # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
02397         # user being bounced back more than once in the event that a summary
02398         # is not required.
02399         #####
02400         # For a bit more sophisticated detection of blank summaries, hash the
02401         # automatic one and pass that in the hidden field wpAutoSummary.
02402         if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
02403             $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
02404         }
02405 
02406         if ( $this->undidRev ) {
02407             $wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
02408         }
02409 
02410         if ( $this->hasPresetSummary ) {
02411             // If a summary has been preset using &summary= we don't want to prompt for
02412             // a different summary. Only prompt for a summary if the summary is blanked.
02413             // (Bug 17416)
02414             $this->autoSumm = md5( '' );
02415         }
02416 
02417         $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
02418         $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
02419 
02420         $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
02421 
02422         $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
02423         $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
02424 
02425         if ( $this->section == 'new' ) {
02426             $this->showSummaryInput( true, $this->summary );
02427             $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
02428         }
02429 
02430         $wgOut->addHTML( $this->editFormTextBeforeContent );
02431 
02432         if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
02433             $wgOut->addHTML( EditPage::getEditToolbar() );
02434         }
02435 
02436         if ( $this->blankArticle ) {
02437             $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) );
02438         }
02439 
02440         if ( $this->isConflict ) {
02441             // In an edit conflict bypass the overridable content form method
02442             // and fallback to the raw wpTextbox1 since editconflicts can't be
02443             // resolved between page source edits and custom ui edits using the
02444             // custom edit ui.
02445             $this->textbox2 = $this->textbox1;
02446 
02447             $content = $this->getCurrentContent();
02448             $this->textbox1 = $this->toEditText( $content );
02449 
02450             $this->showTextbox1();
02451         } else {
02452             $this->showContentForm();
02453         }
02454 
02455         $wgOut->addHTML( $this->editFormTextAfterContent );
02456 
02457         $this->showStandardInputs();
02458 
02459         $this->showFormAfterText();
02460 
02461         $this->showTosSummary();
02462 
02463         $this->showEditTools();
02464 
02465         $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
02466 
02467         $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
02468             Linker::formatTemplates( $this->getTemplates(), $this->preview, $this->section != '' ) ) );
02469 
02470         $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'hiddencats' ),
02471             Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
02472 
02473         $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'limitreport' ),
02474             self::getPreviewLimitReport( $this->mParserOutput ) ) );
02475 
02476         $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
02477 
02478         if ( $this->isConflict ) {
02479             try {
02480                 $this->showConflict();
02481             } catch ( MWContentSerializationException $ex ) {
02482                 // this can't really happen, but be nice if it does.
02483                 $msg = wfMessage(
02484                     'content-failed-to-parse',
02485                     $this->contentModel,
02486                     $this->contentFormat,
02487                     $ex->getMessage()
02488                 );
02489                 $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
02490             }
02491         }
02492 
02493         // Marker for detecting truncated form data.  This must be the last
02494         // parameter sent in order to be of use, so do not move me.
02495         $wgOut->addHTML( Html::hidden( 'wpUltimateParam', true ) );
02496         $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
02497 
02498         if ( !$wgUser->getOption( 'previewontop' ) ) {
02499             $this->displayPreviewArea( $previewOutput, false );
02500         }
02501 
02502         wfProfileOut( __METHOD__ );
02503     }
02504 
02511     public static function extractSectionTitle( $text ) {
02512         preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
02513         if ( !empty( $matches[2] ) ) {
02514             global $wgParser;
02515             return $wgParser->stripSectionName( trim( $matches[2] ) );
02516         } else {
02517             return false;
02518         }
02519     }
02520 
02524     protected function showHeader() {
02525         global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang;
02526         global $wgAllowUserCss, $wgAllowUserJs;
02527 
02528         if ( $this->mTitle->isTalkPage() ) {
02529             $wgOut->addWikiMsg( 'talkpagetext' );
02530         }
02531 
02532         // Add edit notices
02533         $wgOut->addHTML( implode( "\n", $this->mTitle->getEditNotices( $this->oldid ) ) );
02534 
02535         if ( $this->isConflict ) {
02536             $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
02537             $this->edittime = $this->mArticle->getTimestamp();
02538         } else {
02539             if ( $this->section != '' && !$this->isSectionEditSupported() ) {
02540                 // We use $this->section to much before this and getVal('wgSection') directly in other places
02541                 // at this point we can't reset $this->section to '' to fallback to non-section editing.
02542                 // Someone is welcome to try refactoring though
02543                 $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
02544                 return false;
02545             }
02546 
02547             if ( $this->section != '' && $this->section != 'new' ) {
02548                 if ( !$this->summary && !$this->preview && !$this->diff ) {
02549                     $sectionTitle = self::extractSectionTitle( $this->textbox1 ); //FIXME: use Content object
02550                     if ( $sectionTitle !== false ) {
02551                         $this->summary = "/* $sectionTitle */ ";
02552                     }
02553                 }
02554             }
02555 
02556             if ( $this->missingComment ) {
02557                 $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
02558             }
02559 
02560             if ( $this->missingSummary && $this->section != 'new' ) {
02561                 $wgOut->wrapWikiMsg( "<div id='mw-missingsummary'>\n$1\n</div>", 'missingsummary' );
02562             }
02563 
02564             if ( $this->missingSummary && $this->section == 'new' ) {
02565                 $wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1\n</div>", 'missingcommentheader' );
02566             }
02567 
02568             if ( $this->blankArticle ) {
02569                 $wgOut->wrapWikiMsg( "<div id='mw-blankarticle'>\n$1\n</div>", 'blankarticle' );
02570             }
02571 
02572             if ( $this->hookError !== '' ) {
02573                 $wgOut->addWikiText( $this->hookError );
02574             }
02575 
02576             if ( !$this->checkUnicodeCompliantBrowser() ) {
02577                 $wgOut->addWikiMsg( 'nonunicodebrowser' );
02578             }
02579 
02580             if ( $this->section != 'new' ) {
02581                 $revision = $this->mArticle->getRevisionFetched();
02582                 if ( $revision ) {
02583                     // Let sysop know that this will make private content public if saved
02584 
02585                     if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) {
02586                         $wgOut->wrapWikiMsg(
02587                             "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
02588                             'rev-deleted-text-permission'
02589                         );
02590                     } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
02591                         $wgOut->wrapWikiMsg(
02592                             "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
02593                             'rev-deleted-text-view'
02594                         );
02595                     }
02596 
02597                     if ( !$revision->isCurrent() ) {
02598                         $this->mArticle->setOldSubtitle( $revision->getId() );
02599                         $wgOut->addWikiMsg( 'editingold' );
02600                     }
02601                 } elseif ( $this->mTitle->exists() ) {
02602                     // Something went wrong
02603 
02604                     $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
02605                         array( 'missing-revision', $this->oldid ) );
02606                 }
02607             }
02608         }
02609 
02610         if ( wfReadOnly() ) {
02611             $wgOut->wrapWikiMsg(
02612                 "<div id=\"mw-read-only-warning\">\n$1\n</div>",
02613                 array( 'readonlywarning', wfReadOnlyReason() )
02614             );
02615         } elseif ( $wgUser->isAnon() ) {
02616             if ( $this->formtype != 'preview' ) {
02617                 $wgOut->wrapWikiMsg(
02618                     "<div id='mw-anon-edit-warning'>\n$1\n</div>",
02619                     array( 'anoneditwarning',
02620                         // Log-in link
02621                         '{{fullurl:Special:UserLogin|returnto={{FULLPAGENAMEE}}}}',
02622                         // Sign-up link
02623                         '{{fullurl:Special:UserLogin/signup|returnto={{FULLPAGENAMEE}}}}' )
02624                 );
02625             } else {
02626                 $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>",
02627                     'anonpreviewwarning'
02628                 );
02629             }
02630         } else {
02631             if ( $this->isCssJsSubpage ) {
02632                 # Check the skin exists
02633                 if ( $this->isWrongCaseCssJsPage ) {
02634                     $wgOut->wrapWikiMsg(
02635                         "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>",
02636                         array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() )
02637                     );
02638                 }
02639                 if ( $this->formtype !== 'preview' ) {
02640                     if ( $this->isCssSubpage && $wgAllowUserCss ) {
02641                         $wgOut->wrapWikiMsg(
02642                             "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
02643                             array( 'usercssyoucanpreview' )
02644                         );
02645                     }
02646 
02647                     if ( $this->isJsSubpage && $wgAllowUserJs ) {
02648                         $wgOut->wrapWikiMsg(
02649                             "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
02650                             array( 'userjsyoucanpreview' )
02651                         );
02652                     }
02653                 }
02654             }
02655         }
02656 
02657         if ( $this->mTitle->isProtected( 'edit' ) &&
02658             MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
02659         ) {
02660             # Is the title semi-protected?
02661             if ( $this->mTitle->isSemiProtected() ) {
02662                 $noticeMsg = 'semiprotectedpagewarning';
02663             } else {
02664                 # Then it must be protected based on static groups (regular)
02665                 $noticeMsg = 'protectedpagewarning';
02666             }
02667             LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
02668                 array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) );
02669         }
02670         if ( $this->mTitle->isCascadeProtected() ) {
02671             # Is this page under cascading protection from some source pages?
02672             list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
02673             $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
02674             $cascadeSourcesCount = count( $cascadeSources );
02675             if ( $cascadeSourcesCount > 0 ) {
02676                 # Explain, and list the titles responsible
02677                 foreach ( $cascadeSources as $page ) {
02678                     $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
02679                 }
02680             }
02681             $notice .= '</div>';
02682             $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', $cascadeSourcesCount ) );
02683         }
02684         if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
02685             LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
02686                 array( 'lim' => 1,
02687                     'showIfEmpty' => false,
02688                     'msgKey' => array( 'titleprotectedwarning' ),
02689                     'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ) );
02690         }
02691 
02692         if ( $this->kblength === false ) {
02693             $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
02694         }
02695 
02696         if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
02697             $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
02698                 array(
02699                     'longpageerror',
02700                     $wgLang->formatNum( $this->kblength ),
02701                     $wgLang->formatNum( $wgMaxArticleSize )
02702                 )
02703             );
02704         } else {
02705             if ( !wfMessage( 'longpage-hint' )->isDisabled() ) {
02706                 $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
02707                     array(
02708                         'longpage-hint',
02709                         $wgLang->formatSize( strlen( $this->textbox1 ) ),
02710                         strlen( $this->textbox1 )
02711                     )
02712                 );
02713             }
02714         }
02715         # Add header copyright warning
02716         $this->showHeaderCopyrightWarning();
02717 
02718         return true;
02719     }
02720 
02735     function getSummaryInput( $summary = "", $labelText = null,
02736         $inputAttrs = null, $spanLabelAttrs = null
02737     ) {
02738         // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
02739         $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : array() ) + array(
02740             'id' => 'wpSummary',
02741             'maxlength' => '200',
02742             'tabindex' => '1',
02743             'size' => 60,
02744             'spellcheck' => 'true',
02745         ) + Linker::tooltipAndAccesskeyAttribs( 'summary' );
02746 
02747         $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : array() ) + array(
02748             'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
02749             'id' => "wpSummaryLabel"
02750         );
02751 
02752         $label = null;
02753         if ( $labelText ) {
02754             $label = Xml::tags(
02755                 'label',
02756                 $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null,
02757                 $labelText
02758             );
02759             $label = Xml::tags( 'span', $spanLabelAttrs, $label );
02760         }
02761 
02762         $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
02763 
02764         return array( $label, $input );
02765     }
02766 
02773     protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
02774         global $wgOut, $wgContLang;
02775         # Add a class if 'missingsummary' is triggered to allow styling of the summary line
02776         $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
02777         if ( $isSubjectPreview ) {
02778             if ( $this->nosummary ) {
02779                 return;
02780             }
02781         } else {
02782             if ( !$this->mShowSummaryField ) {
02783                 return;
02784             }
02785         }
02786         $summary = $wgContLang->recodeForEdit( $summary );
02787         $labelText = wfMessage( $isSubjectPreview ? 'subject' : 'summary' )->parse();
02788         list( $label, $input ) = $this->getSummaryInput(
02789             $summary,
02790             $labelText,
02791             array( 'class' => $summaryClass ),
02792             array()
02793         );
02794         $wgOut->addHTML( "{$label} {$input}" );
02795     }
02796 
02804     protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
02805         // avoid spaces in preview, gets always trimmed on save
02806         $summary = trim( $summary );
02807         if ( !$summary || ( !$this->preview && !$this->diff ) ) {
02808             return "";
02809         }
02810 
02811         global $wgParser;
02812 
02813         if ( $isSubjectPreview ) {
02814             $summary = wfMessage( 'newsectionsummary' )->rawParams( $wgParser->stripSectionName( $summary ) )
02815                 ->inContentLanguage()->text();
02816         }
02817 
02818         $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
02819 
02820         $summary = wfMessage( $message )->parse()
02821             . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
02822         return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary );
02823     }
02824 
02825     protected function showFormBeforeText() {
02826         global $wgOut;
02827         $section = htmlspecialchars( $this->section );
02828         $wgOut->addHTML( <<<HTML
02829 <input type='hidden' value="{$section}" name="wpSection" />
02830 <input type='hidden' value="{$this->starttime}" name="wpStarttime" />
02831 <input type='hidden' value="{$this->edittime}" name="wpEdittime" />
02832 <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
02833 
02834 HTML
02835         );
02836         if ( !$this->checkUnicodeCompliantBrowser() ) {
02837             $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
02838         }
02839     }
02840 
02841     protected function showFormAfterText() {
02842         global $wgOut, $wgUser;
02855         $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
02856     }
02857 
02866     protected function showContentForm() {
02867         $this->showTextbox1();
02868     }
02869 
02878     protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
02879         if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
02880             $attribs = array( 'style' => 'display:none;' );
02881         } else {
02882             $classes = array(); // Textarea CSS
02883             if ( $this->mTitle->isProtected( 'edit' ) &&
02884                 MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
02885             ) {
02886                 # Is the title semi-protected?
02887                 if ( $this->mTitle->isSemiProtected() ) {
02888                     $classes[] = 'mw-textarea-sprotected';
02889                 } else {
02890                     # Then it must be protected based on static groups (regular)
02891                     $classes[] = 'mw-textarea-protected';
02892                 }
02893                 # Is the title cascade-protected?
02894                 if ( $this->mTitle->isCascadeProtected() ) {
02895                     $classes[] = 'mw-textarea-cprotected';
02896                 }
02897             }
02898 
02899             $attribs = array( 'tabindex' => 1 );
02900 
02901             if ( is_array( $customAttribs ) ) {
02902                 $attribs += $customAttribs;
02903             }
02904 
02905             if ( count( $classes ) ) {
02906                 if ( isset( $attribs['class'] ) ) {
02907                     $classes[] = $attribs['class'];
02908                 }
02909                 $attribs['class'] = implode( ' ', $classes );
02910             }
02911         }
02912 
02913         $this->showTextbox(
02914             $textoverride !== null ? $textoverride : $this->textbox1,
02915             'wpTextbox1',
02916             $attribs
02917         );
02918     }
02919 
02920     protected function showTextbox2() {
02921         $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
02922     }
02923 
02924     protected function showTextbox( $text, $name, $customAttribs = array() ) {
02925         global $wgOut, $wgUser;
02926 
02927         $wikitext = $this->safeUnicodeOutput( $text );
02928         if ( strval( $wikitext ) !== '' ) {
02929             // Ensure there's a newline at the end, otherwise adding lines
02930             // is awkward.
02931             // But don't add a newline if the ext is empty, or Firefox in XHTML
02932             // mode will show an extra newline. A bit annoying.
02933             $wikitext .= "\n";
02934         }
02935 
02936         $attribs = $customAttribs + array(
02937             'accesskey' => ',',
02938             'id' => $name,
02939             'cols' => $wgUser->getIntOption( 'cols' ),
02940             'rows' => $wgUser->getIntOption( 'rows' ),
02941             // Avoid PHP notices when appending preferences
02942             // (appending allows customAttribs['style'] to still work).
02943             'style' => ''
02944         );
02945 
02946         $pageLang = $this->mTitle->getPageLanguage();
02947         $attribs['lang'] = $pageLang->getCode();
02948         $attribs['dir'] = $pageLang->getDir();
02949 
02950         $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
02951     }
02952 
02953     protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
02954         global $wgOut;
02955         $classes = array();
02956         if ( $isOnTop ) {
02957             $classes[] = 'ontop';
02958         }
02959 
02960         $attribs = array( 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) );
02961 
02962         if ( $this->formtype != 'preview' ) {
02963             $attribs['style'] = 'display: none;';
02964         }
02965 
02966         $wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
02967 
02968         if ( $this->formtype == 'preview' ) {
02969             $this->showPreview( $previewOutput );
02970         }
02971 
02972         $wgOut->addHTML( '</div>' );
02973 
02974         if ( $this->formtype == 'diff' ) {
02975             try {
02976                 $this->showDiff();
02977             } catch ( MWContentSerializationException $ex ) {
02978                 $msg = wfMessage(
02979                     'content-failed-to-parse',
02980                     $this->contentModel,
02981                     $this->contentFormat,
02982                     $ex->getMessage()
02983                 );
02984                 $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
02985             }
02986         }
02987     }
02988 
02995     protected function showPreview( $text ) {
02996         global $wgOut;
02997         if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
02998             $this->mArticle->openShowCategory();
02999         }
03000         # This hook seems slightly odd here, but makes things more
03001         # consistent for extensions.
03002         wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) );
03003         $wgOut->addHTML( $text );
03004         if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
03005             $this->mArticle->closeShowCategory();
03006         }
03007     }
03008 
03016     function showDiff() {
03017         global $wgUser, $wgContLang, $wgOut;
03018 
03019         $oldtitlemsg = 'currentrev';
03020         # if message does not exist, show diff against the preloaded default
03021         if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
03022             $oldtext = $this->mTitle->getDefaultMessageText();
03023             if ( $oldtext !== false ) {
03024                 $oldtitlemsg = 'defaultmessagetext';
03025                 $oldContent = $this->toEditContent( $oldtext );
03026             } else {
03027                 $oldContent = null;
03028             }
03029         } else {
03030             $oldContent = $this->getCurrentContent();
03031         }
03032 
03033         $textboxContent = $this->toEditContent( $this->textbox1 );
03034 
03035         $newContent = $this->mArticle->replaceSectionContent(
03036                             $this->section, $textboxContent,
03037                             $this->summary, $this->edittime );
03038 
03039         if ( $newContent ) {
03040             ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
03041             wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
03042 
03043             $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
03044             $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
03045         }
03046 
03047         if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
03048             $oldtitle = wfMessage( $oldtitlemsg )->parse();
03049             $newtitle = wfMessage( 'yourtext' )->parse();
03050 
03051             if ( !$oldContent ) {
03052                 $oldContent = $newContent->getContentHandler()->makeEmptyContent();
03053             }
03054 
03055             if ( !$newContent ) {
03056                 $newContent = $oldContent->getContentHandler()->makeEmptyContent();
03057             }
03058 
03059             $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
03060             $de->setContent( $oldContent, $newContent );
03061 
03062             $difftext = $de->getDiff( $oldtitle, $newtitle );
03063             $de->showDiffStyle();
03064         } else {
03065             $difftext = '';
03066         }
03067 
03068         $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
03069     }
03070 
03074     protected function showHeaderCopyrightWarning() {
03075         $msg = 'editpage-head-copy-warn';
03076         if ( !wfMessage( $msg )->isDisabled() ) {
03077             global $wgOut;
03078             $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
03079                 'editpage-head-copy-warn' );
03080         }
03081     }
03082 
03091     protected function showTosSummary() {
03092         $msg = 'editpage-tos-summary';
03093         wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
03094         if ( !wfMessage( $msg )->isDisabled() ) {
03095             global $wgOut;
03096             $wgOut->addHTML( '<div class="mw-tos-summary">' );
03097             $wgOut->addWikiMsg( $msg );
03098             $wgOut->addHTML( '</div>' );
03099         }
03100     }
03101 
03102     protected function showEditTools() {
03103         global $wgOut;
03104         $wgOut->addHTML( '<div class="mw-editTools">' .
03105             wfMessage( 'edittools' )->inContentLanguage()->parse() .
03106             '</div>' );
03107     }
03108 
03115     protected function getCopywarn() {
03116         return self::getCopyrightWarning( $this->mTitle );
03117     }
03118 
03126     public static function getCopyrightWarning( $title, $format = 'plain' ) {
03127         global $wgRightsText;
03128         if ( $wgRightsText ) {
03129             $copywarnMsg = array( 'copyrightwarning',
03130                 '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]',
03131                 $wgRightsText );
03132         } else {
03133             $copywarnMsg = array( 'copyrightwarning2',
03134                 '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' );
03135         }
03136         // Allow for site and per-namespace customization of contribution/copyright notice.
03137         wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
03138 
03139         return "<div id=\"editpage-copywarn\">\n" .
03140             call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>";
03141     }
03142 
03150     public static function getPreviewLimitReport( $output ) {
03151         if ( !$output || !$output->getLimitReportData() ) {
03152             return '';
03153         }
03154 
03155         wfProfileIn( __METHOD__ );
03156 
03157         $limitReport = Html::rawElement( 'div', array( 'class' => 'mw-limitReportExplanation' ),
03158             wfMessage( 'limitreport-title' )->parseAsBlock()
03159         );
03160 
03161         // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
03162         $limitReport .= Html::openElement( 'div', array( 'class' => 'preview-limit-report-wrapper' ) );
03163 
03164         $limitReport .= Html::openElement( 'table', array(
03165             'class' => 'preview-limit-report wikitable'
03166         ) ) .
03167             Html::openElement( 'tbody' );
03168 
03169         foreach ( $output->getLimitReportData() as $key => $value ) {
03170             if ( wfRunHooks( 'ParserLimitReportFormat',
03171                 array( $key, &$value, &$limitReport, true, true )
03172             ) ) {
03173                 $keyMsg = wfMessage( $key );
03174                 $valueMsg = wfMessage( array( "$key-value-html", "$key-value" ) );
03175                 if ( !$valueMsg->exists() ) {
03176                     $valueMsg = new RawMessage( '$1' );
03177                 }
03178                 if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
03179                     $limitReport .= Html::openElement( 'tr' ) .
03180                         Html::rawElement( 'th', null, $keyMsg->parse() ) .
03181                         Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) .
03182                         Html::closeElement( 'tr' );
03183                 }
03184             }
03185         }
03186 
03187         $limitReport .= Html::closeElement( 'tbody' ) .
03188             Html::closeElement( 'table' ) .
03189             Html::closeElement( 'div' );
03190 
03191         wfProfileOut( __METHOD__ );
03192 
03193         return $limitReport;
03194     }
03195 
03196     protected function showStandardInputs( &$tabindex = 2 ) {
03197         global $wgOut, $wgUseMediaWikiUIEverywhere;
03198         $wgOut->addHTML( "<div class='editOptions'>\n" );
03199 
03200         if ( $this->section != 'new' ) {
03201             $this->showSummaryInput( false, $this->summary );
03202             $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
03203         }
03204 
03205         $checkboxes = $this->getCheckboxes( $tabindex,
03206             array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) );
03207         $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" );
03208 
03209         // Show copyright warning.
03210         $wgOut->addWikiText( $this->getCopywarn() );
03211         $wgOut->addHTML( $this->editFormTextAfterWarn );
03212 
03213         $wgOut->addHTML( "<div class='editButtons'>\n" );
03214         $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
03215 
03216         $cancel = $this->getCancelLink();
03217         if ( $cancel !== '' ) {
03218             $cancel .= Html::element( 'span',
03219                 array( 'class' => 'mw-editButtons-pipe-separator' ),
03220                 wfMessage( 'pipe-separator' )->text() );
03221         }
03222 
03223         $message = wfMessage( 'edithelppage' )->inContentLanguage()->text();
03224         $edithelpurl = Skin::makeInternalOrExternalUrl( $message );
03225         $attrs = array(
03226             'target' => 'helpwindow',
03227             'href' => $edithelpurl,
03228         );
03229         if ( $wgUseMediaWikiUIEverywhere ) {
03230             $attrs['class'] = 'mw-ui-button mw-ui-quiet';
03231         }
03232         $edithelp = Html::element( 'a', $attrs, wfMessage( 'edithelp' )->text() ) .
03233             wfMessage( 'word-separator' )->escaped() .
03234             wfMessage( 'newwindow' )->parse();
03235 
03236         $wgOut->addHTML( "  <span class='cancelLink'>{$cancel}</span>\n" );
03237         $wgOut->addHTML( "  <span class='editHelp'>{$edithelp}</span>\n" );
03238         $wgOut->addHTML( "</div><!-- editButtons -->\n" );
03239 
03240         wfRunHooks( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) );
03241 
03242         $wgOut->addHTML( "</div><!-- editOptions -->\n" );
03243     }
03244 
03249     protected function showConflict() {
03250         global $wgOut;
03251 
03252         if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
03253             $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
03254 
03255             $content1 = $this->toEditContent( $this->textbox1 );
03256             $content2 = $this->toEditContent( $this->textbox2 );
03257 
03258             $handler = ContentHandler::getForModelID( $this->contentModel );
03259             $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
03260             $de->setContent( $content2, $content1 );
03261             $de->showDiff(
03262                 wfMessage( 'yourtext' )->parse(),
03263                 wfMessage( 'storedversion' )->text()
03264             );
03265 
03266             $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
03267             $this->showTextbox2();
03268         }
03269     }
03270 
03274     public function getCancelLink() {
03275         global $wgUseMediaWikiUIEverywhere;
03276         $cancelParams = array();
03277         if ( !$this->isConflict && $this->oldid > 0 ) {
03278             $cancelParams['oldid'] = $this->oldid;
03279         }
03280         $attrs = array( 'id' => 'mw-editform-cancel' );
03281         if ( $wgUseMediaWikiUIEverywhere ) {
03282             $attrs['class'] = 'mw-ui-button mw-ui-quiet';
03283         }
03284 
03285         return Linker::linkKnown(
03286             $this->getContextTitle(),
03287             wfMessage( 'cancel' )->parse(),
03288             $attrs,
03289             $cancelParams
03290         );
03291     }
03292 
03302     protected function getActionURL( Title $title ) {
03303         return $title->getLocalURL( array( 'action' => $this->action ) );
03304     }
03305 
03313     protected function wasDeletedSinceLastEdit() {
03314         if ( $this->deletedSinceEdit !== null ) {
03315             return $this->deletedSinceEdit;
03316         }
03317 
03318         $this->deletedSinceEdit = false;
03319 
03320         if ( $this->mTitle->isDeletedQuick() ) {
03321             $this->lastDelete = $this->getLastDelete();
03322             if ( $this->lastDelete ) {
03323                 $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
03324                 if ( $deleteTime > $this->starttime ) {
03325                     $this->deletedSinceEdit = true;
03326                 }
03327             }
03328         }
03329 
03330         return $this->deletedSinceEdit;
03331     }
03332 
03336     protected function getLastDelete() {
03337         $dbr = wfGetDB( DB_SLAVE );
03338         $data = $dbr->selectRow(
03339             array( 'logging', 'user' ),
03340             array(
03341                 'log_type',
03342                 'log_action',
03343                 'log_timestamp',
03344                 'log_user',
03345                 'log_namespace',
03346                 'log_title',
03347                 'log_comment',
03348                 'log_params',
03349                 'log_deleted',
03350                 'user_name'
03351             ), array(
03352                 'log_namespace' => $this->mTitle->getNamespace(),
03353                 'log_title' => $this->mTitle->getDBkey(),
03354                 'log_type' => 'delete',
03355                 'log_action' => 'delete',
03356                 'user_id=log_user'
03357             ),
03358             __METHOD__,
03359             array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' )
03360         );
03361         // Quick paranoid permission checks...
03362         if ( is_object( $data ) ) {
03363             if ( $data->log_deleted & LogPage::DELETED_USER ) {
03364                 $data->user_name = wfMessage( 'rev-deleted-user' )->escaped();
03365             }
03366 
03367             if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
03368                 $data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped();
03369             }
03370         }
03371 
03372         return $data;
03373     }
03374 
03380     function getPreviewText() {
03381         global $wgOut, $wgUser, $wgRawHtml, $wgLang;
03382         global $wgAllowUserCss, $wgAllowUserJs;
03383 
03384         wfProfileIn( __METHOD__ );
03385 
03386         if ( $wgRawHtml && !$this->mTokenOk ) {
03387             // Could be an offsite preview attempt. This is very unsafe if
03388             // HTML is enabled, as it could be an attack.
03389             $parsedNote = '';
03390             if ( $this->textbox1 !== '' ) {
03391                 // Do not put big scary notice, if previewing the empty
03392                 // string, which happens when you initially edit
03393                 // a category page, due to automatic preview-on-open.
03394                 $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
03395                     wfMessage( 'session_fail_preview_html' )->text() . "</div>", true, /* interface */true );
03396             }
03397             wfProfileOut( __METHOD__ );
03398             return $parsedNote;
03399         }
03400 
03401         $note = '';
03402 
03403         try {
03404             $content = $this->toEditContent( $this->textbox1 );
03405 
03406             $previewHTML = '';
03407             if ( !wfRunHooks(
03408                 'AlternateEditPreview',
03409                 array( $this, &$content, &$previewHTML, &$this->mParserOutput ) )
03410             ) {
03411                 wfProfileOut( __METHOD__ );
03412                 return $previewHTML;
03413             }
03414 
03415             # provide a anchor link to the editform
03416             $continueEditing = '<span class="mw-continue-editing">' .
03417                 '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' .
03418                 wfMessage( 'continue-editing' )->text() . ']]</span>';
03419             if ( $this->mTriedSave && !$this->mTokenOk ) {
03420                 if ( $this->mTokenOkExceptSuffix ) {
03421                     $note = wfMessage( 'token_suffix_mismatch' )->plain();
03422                 } else {
03423                     $note = wfMessage( 'session_fail_preview' )->plain();
03424                 }
03425             } elseif ( $this->incompleteForm ) {
03426                 $note = wfMessage( 'edit_form_incomplete' )->plain();
03427             } else {
03428                 $note = wfMessage( 'previewnote' )->plain() . ' ' . $continueEditing;
03429             }
03430 
03431             $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
03432             $parserOptions->setEditSection( false );
03433             $parserOptions->setIsPreview( true );
03434             $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
03435 
03436             # don't parse non-wikitext pages, show message about preview
03437             if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
03438                 if ( $this->mTitle->isCssJsSubpage() ) {
03439                     $level = 'user';
03440                 } elseif ( $this->mTitle->isCssOrJsPage() ) {
03441                     $level = 'site';
03442                 } else {
03443                     $level = false;
03444                 }
03445 
03446                 if ( $content->getModel() == CONTENT_MODEL_CSS ) {
03447                     $format = 'css';
03448                     if ( $level === 'user' && !$wgAllowUserCss ) {
03449                         $format = false;
03450                     }
03451                 } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
03452                     $format = 'js';
03453                     if ( $level === 'user' && !$wgAllowUserJs ) {
03454                         $format = false;
03455                     }
03456                 } else {
03457                     $format = false;
03458                 }
03459 
03460                 # Used messages to make sure grep find them:
03461                 # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
03462                 if ( $level && $format ) {
03463                     $note = "<div id='mw-{$level}{$format}preview'>" .
03464                         wfMessage( "{$level}{$format}preview" )->text() .
03465                         ' ' . $continueEditing . "</div>";
03466                 }
03467             }
03468 
03469             # If we're adding a comment, we need to show the
03470             # summary as the headline
03471             if ( $this->section === "new" && $this->summary !== "" ) {
03472                 $content = $content->addSectionHeader( $this->summary );
03473             }
03474 
03475             $hook_args = array( $this, &$content );
03476             ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
03477             wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
03478 
03479             $parserOptions->enableLimitReport();
03480 
03481             # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
03482             # But it's now deprecated, so never mind
03483 
03484             $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
03485             $parserOutput = $content->getParserOutput(
03486                 $this->getArticle()->getTitle(),
03487                 null,
03488                 $parserOptions
03489             );
03490 
03491             $previewHTML = $parserOutput->getText();
03492             $this->mParserOutput = $parserOutput;
03493             $wgOut->addParserOutputMetadata( $parserOutput );
03494 
03495             if ( count( $parserOutput->getWarnings() ) ) {
03496                 $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
03497             }
03498         } catch ( MWContentSerializationException $ex ) {
03499             $m = wfMessage(
03500                 'content-failed-to-parse',
03501                 $this->contentModel,
03502                 $this->contentFormat,
03503                 $ex->getMessage()
03504             );
03505             $note .= "\n\n" . $m->parse();
03506             $previewHTML = '';
03507         }
03508 
03509         if ( $this->isConflict ) {
03510             $conflict = '<h2 id="mw-previewconflict">'
03511                 . wfMessage( 'previewconflict' )->escaped() . "</h2>\n";
03512         } else {
03513             $conflict = '<hr />';
03514         }
03515 
03516         $previewhead = "<div class='previewnote'>\n" .
03517             '<h2 id="mw-previewheader">' . wfMessage( 'preview' )->escaped() . "</h2>" .
03518             $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
03519 
03520         $pageViewLang = $this->mTitle->getPageViewLanguage();
03521         $attribs = array( 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
03522             'class' => 'mw-content-' . $pageViewLang->getDir() );
03523         $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
03524 
03525         wfProfileOut( __METHOD__ );
03526         return $previewhead . $previewHTML . $this->previewTextAfterContent;
03527     }
03528 
03532     function getTemplates() {
03533         if ( $this->preview || $this->section != '' ) {
03534             $templates = array();
03535             if ( !isset( $this->mParserOutput ) ) {
03536                 return $templates;
03537             }
03538             foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
03539                 foreach ( array_keys( $template ) as $dbk ) {
03540                     $templates[] = Title::makeTitle( $ns, $dbk );
03541                 }
03542             }
03543             return $templates;
03544         } else {
03545             return $this->mTitle->getTemplateLinksFrom();
03546         }
03547     }
03548 
03555     static function getEditToolbar() {
03556         global $wgContLang, $wgOut;
03557         global $wgEnableUploads, $wgForeignFileRepos;
03558 
03559         $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
03560 
03570         $toolarray = array(
03571             array(
03572                 'id'     => 'mw-editbutton-bold',
03573                 'open'   => '\'\'\'',
03574                 'close'  => '\'\'\'',
03575                 'sample' => wfMessage( 'bold_sample' )->text(),
03576                 'tip'    => wfMessage( 'bold_tip' )->text(),
03577             ),
03578             array(
03579                 'id'     => 'mw-editbutton-italic',
03580                 'open'   => '\'\'',
03581                 'close'  => '\'\'',
03582                 'sample' => wfMessage( 'italic_sample' )->text(),
03583                 'tip'    => wfMessage( 'italic_tip' )->text(),
03584             ),
03585             array(
03586                 'id'     => 'mw-editbutton-link',
03587                 'open'   => '[[',
03588                 'close'  => ']]',
03589                 'sample' => wfMessage( 'link_sample' )->text(),
03590                 'tip'    => wfMessage( 'link_tip' )->text(),
03591             ),
03592             array(
03593                 'id'     => 'mw-editbutton-extlink',
03594                 'open'   => '[',
03595                 'close'  => ']',
03596                 'sample' => wfMessage( 'extlink_sample' )->text(),
03597                 'tip'    => wfMessage( 'extlink_tip' )->text(),
03598             ),
03599             array(
03600                 'id'     => 'mw-editbutton-headline',
03601                 'open'   => "\n== ",
03602                 'close'  => " ==\n",
03603                 'sample' => wfMessage( 'headline_sample' )->text(),
03604                 'tip'    => wfMessage( 'headline_tip' )->text(),
03605             ),
03606             $imagesAvailable ? array(
03607                 'id'     => 'mw-editbutton-image',
03608                 'open'   => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
03609                 'close'  => ']]',
03610                 'sample' => wfMessage( 'image_sample' )->text(),
03611                 'tip'    => wfMessage( 'image_tip' )->text(),
03612             ) : false,
03613             $imagesAvailable ? array(
03614                 'id'     => 'mw-editbutton-media',
03615                 'open'   => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
03616                 'close'  => ']]',
03617                 'sample' => wfMessage( 'media_sample' )->text(),
03618                 'tip'    => wfMessage( 'media_tip' )->text(),
03619             ) : false,
03620             array(
03621                 'id'     => 'mw-editbutton-nowiki',
03622                 'open'   => "<nowiki>",
03623                 'close'  => "</nowiki>",
03624                 'sample' => wfMessage( 'nowiki_sample' )->text(),
03625                 'tip'    => wfMessage( 'nowiki_tip' )->text(),
03626             ),
03627             array(
03628                 'id'     => 'mw-editbutton-signature',
03629                 'open'   => '--~~~~',
03630                 'close'  => '',
03631                 'sample' => '',
03632                 'tip'    => wfMessage( 'sig_tip' )->text(),
03633             ),
03634             array(
03635                 'id'     => 'mw-editbutton-hr',
03636                 'open'   => "\n----\n",
03637                 'close'  => '',
03638                 'sample' => '',
03639                 'tip'    => wfMessage( 'hr_tip' )->text(),
03640             )
03641         );
03642 
03643         $script = 'mw.loader.using("mediawiki.action.edit", function() {';
03644         foreach ( $toolarray as $tool ) {
03645             if ( !$tool ) {
03646                 continue;
03647             }
03648 
03649             $params = array(
03650                 // Images are defined in ResourceLoaderEditToolbarModule
03651                 false,
03652                 // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
03653                 // Older browsers show a "speedtip" type message only for ALT.
03654                 // Ideally these should be different, realistically they
03655                 // probably don't need to be.
03656                 $tool['tip'],
03657                 $tool['open'],
03658                 $tool['close'],
03659                 $tool['sample'],
03660                 $tool['id'],
03661             );
03662 
03663             $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
03664         }
03665 
03666         // This used to be called on DOMReady from mediawiki.action.edit, which
03667         // ended up causing race conditions with the setup code above.
03668         $script .= "\n" .
03669             "// Create button bar\n" .
03670             "$(function() { mw.toolbar.init(); } );\n";
03671 
03672         $script .= '});';
03673         $wgOut->addScript( Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) ) );
03674 
03675         $toolbar = '<div id="toolbar"></div>';
03676 
03677         wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
03678 
03679         return $toolbar;
03680     }
03681 
03692     public function getCheckboxes( &$tabindex, $checked ) {
03693         global $wgUser, $wgUseMediaWikiUIEverywhere;
03694 
03695         $checkboxes = array();
03696 
03697         // don't show the minor edit checkbox if it's a new page or section
03698         if ( !$this->isNew ) {
03699             $checkboxes['minor'] = '';
03700             $minorLabel = wfMessage( 'minoredit' )->parse();
03701             if ( $wgUser->isAllowed( 'minoredit' ) ) {
03702                 $attribs = array(
03703                     'tabindex' => ++$tabindex,
03704                     'accesskey' => wfMessage( 'accesskey-minoredit' )->text(),
03705                     'id' => 'wpMinoredit',
03706                 );
03707                 $minorEditHtml =
03708                     Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
03709                     "&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
03710                     Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'minoredit', 'withaccess' ) ) ) .
03711                     ">{$minorLabel}</label>";
03712 
03713                 if ( $wgUseMediaWikiUIEverywhere ) {
03714                     $checkboxes['minor'] = Html::openElement( 'div', array( 'class' => 'mw-ui-checkbox' ) ) .
03715                         $minorEditHtml .
03716                     Html::closeElement( 'div' );
03717                 } else {
03718                     $checkboxes['minor'] = $minorEditHtml;
03719                 }
03720             }
03721         }
03722 
03723         $watchLabel = wfMessage( 'watchthis' )->parse();
03724         $checkboxes['watch'] = '';
03725         if ( $wgUser->isLoggedIn() ) {
03726             $attribs = array(
03727                 'tabindex' => ++$tabindex,
03728                 'accesskey' => wfMessage( 'accesskey-watch' )->text(),
03729                 'id' => 'wpWatchthis',
03730             );
03731             $watchThisHtml =
03732                 Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
03733                 "&#160;<label for='wpWatchthis' id='mw-editpage-watch'" .
03734                 Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ) ) .
03735                 ">{$watchLabel}</label>";
03736             if ( $wgUseMediaWikiUIEverywhere ) {
03737                 $checkboxes['watch'] = Html::openElement( 'div', array( 'class' => 'mw-ui-checkbox' ) ) .
03738                     $watchThisHtml .
03739                     Html::closeElement( 'div' );
03740             } else {
03741                 $checkboxes['watch'] = $watchThisHtml;
03742             }
03743         }
03744         wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
03745         return $checkboxes;
03746     }
03747 
03756     public function getEditButtons( &$tabindex ) {
03757         global $wgUseMediaWikiUIEverywhere;
03758 
03759         $buttons = array();
03760 
03761         $attribs = array(
03762             'id' => 'wpSave',
03763             'name' => 'wpSave',
03764             'type' => 'submit',
03765             'tabindex' => ++$tabindex,
03766             'value' => wfMessage( 'savearticle' )->text(),
03767         ) + Linker::tooltipAndAccesskeyAttribs( 'save' );
03768         if ( $wgUseMediaWikiUIEverywhere ) {
03769             $attribs['class'] = 'mw-ui-button mw-ui-constructive';
03770         }
03771         $buttons['save'] = Xml::element( 'input', $attribs, '' );
03772 
03773         ++$tabindex; // use the same for preview and live preview
03774         $attribs = array(
03775             'id' => 'wpPreview',
03776             'name' => 'wpPreview',
03777             'type' => 'submit',
03778             'tabindex' => $tabindex,
03779             'value' => wfMessage( 'showpreview' )->text(),
03780         ) + Linker::tooltipAndAccesskeyAttribs( 'preview' );
03781         if ( $wgUseMediaWikiUIEverywhere ) {
03782             $attribs['class'] = 'mw-ui-button mw-ui-progressive';
03783         }
03784         $buttons['preview'] = Xml::element( 'input', $attribs, '' );
03785         $buttons['live'] = '';
03786 
03787         $attribs = array(
03788             'id' => 'wpDiff',
03789             'name' => 'wpDiff',
03790             'type' => 'submit',
03791             'tabindex' => ++$tabindex,
03792             'value' => wfMessage( 'showdiff' )->text(),
03793         ) + Linker::tooltipAndAccesskeyAttribs( 'diff' );
03794         if ( $wgUseMediaWikiUIEverywhere ) {
03795             $attribs['class'] = 'mw-ui-button mw-ui-progressive';
03796         }
03797         $buttons['diff'] = Xml::element( 'input', $attribs, '' );
03798 
03799         wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) );
03800         return $buttons;
03801     }
03802 
03815     function livePreview() {
03816         global $wgOut;
03817         $wgOut->disable();
03818         header( 'Content-type: text/xml; charset=utf-8' );
03819         header( 'Cache-control: no-cache' );
03820 
03821         $previewText = $this->getPreviewText();
03822         #$categories = $skin->getCategoryLinks();
03823 
03824         $s =
03825             '<?xml version="1.0" encoding="UTF-8" ?>' . "\n" .
03826             Xml::tags( 'livepreview', null,
03827                 Xml::element( 'preview', null, $previewText )
03828                 #.  Xml::element( 'category', null, $categories )
03829             );
03830         echo $s;
03831     }
03832 
03837     function noSuchSectionPage() {
03838         global $wgOut;
03839 
03840         $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
03841 
03842         $res = wfMessage( 'nosuchsectiontext', $this->section )->parseAsBlock();
03843         wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
03844         $wgOut->addHTML( $res );
03845 
03846         $wgOut->returnToMain( false, $this->mTitle );
03847     }
03848 
03854     public function spamPageWithContent( $match = false ) {
03855         global $wgOut, $wgLang;
03856         $this->textbox2 = $this->textbox1;
03857 
03858         if ( is_array( $match ) ) {
03859             $match = $wgLang->listToText( $match );
03860         }
03861         $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
03862 
03863         $wgOut->addHTML( '<div id="spamprotected">' );
03864         $wgOut->addWikiMsg( 'spamprotectiontext' );
03865         if ( $match ) {
03866             $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
03867         }
03868         $wgOut->addHTML( '</div>' );
03869 
03870         $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
03871         $this->showDiff();
03872 
03873         $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
03874         $this->showTextbox2();
03875 
03876         $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) );
03877     }
03878 
03885     private function checkUnicodeCompliantBrowser() {
03886         global $wgBrowserBlackList, $wgRequest;
03887 
03888         $currentbrowser = $wgRequest->getHeader( 'User-Agent' );
03889         if ( $currentbrowser === false ) {
03890             // No User-Agent header sent? Trust it by default...
03891             return true;
03892         }
03893 
03894         foreach ( $wgBrowserBlackList as $browser ) {
03895             if ( preg_match( $browser, $currentbrowser ) ) {
03896                 return false;
03897             }
03898         }
03899         return true;
03900     }
03901 
03910     protected function safeUnicodeInput( $request, $field ) {
03911         $text = rtrim( $request->getText( $field ) );
03912         return $request->getBool( 'safemode' )
03913             ? $this->unmakeSafe( $text )
03914             : $text;
03915     }
03916 
03924     protected function safeUnicodeOutput( $text ) {
03925         global $wgContLang;
03926         $codedText = $wgContLang->recodeForEdit( $text );
03927         return $this->checkUnicodeCompliantBrowser()
03928             ? $codedText
03929             : $this->makeSafe( $codedText );
03930     }
03931 
03944     private function makeSafe( $invalue ) {
03945         // Armor existing references for reversibility.
03946         $invalue = strtr( $invalue, array( "&#x" => "&#x0" ) );
03947 
03948         $bytesleft = 0;
03949         $result = "";
03950         $working = 0;
03951         $valueLength = strlen( $invalue );
03952         for ( $i = 0; $i < $valueLength; $i++ ) {
03953             $bytevalue = ord( $invalue[$i] );
03954             if ( $bytevalue <= 0x7F ) { // 0xxx xxxx
03955                 $result .= chr( $bytevalue );
03956                 $bytesleft = 0;
03957             } elseif ( $bytevalue <= 0xBF ) { // 10xx xxxx
03958                 $working = $working << 6;
03959                 $working += ( $bytevalue & 0x3F );
03960                 $bytesleft--;
03961                 if ( $bytesleft <= 0 ) {
03962                     $result .= "&#x" . strtoupper( dechex( $working ) ) . ";";
03963                 }
03964             } elseif ( $bytevalue <= 0xDF ) { // 110x xxxx
03965                 $working = $bytevalue & 0x1F;
03966                 $bytesleft = 1;
03967             } elseif ( $bytevalue <= 0xEF ) { // 1110 xxxx
03968                 $working = $bytevalue & 0x0F;
03969                 $bytesleft = 2;
03970             } else { // 1111 0xxx
03971                 $working = $bytevalue & 0x07;
03972                 $bytesleft = 3;
03973             }
03974         }
03975         return $result;
03976     }
03977 
03986     private function unmakeSafe( $invalue ) {
03987         $result = "";
03988         $valueLength = strlen( $invalue );
03989         for ( $i = 0; $i < $valueLength; $i++ ) {
03990             if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) {
03991                 $i += 3;
03992                 $hexstring = "";
03993                 do {
03994                     $hexstring .= $invalue[$i];
03995                     $i++;
03996                 } while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
03997 
03998                 // Do some sanity checks. These aren't needed for reversibility,
03999                 // but should help keep the breakage down if the editor
04000                 // breaks one of the entities whilst editing.
04001                 if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) {
04002                     $codepoint = hexdec( $hexstring );
04003                     $result .= codepointToUtf8( $codepoint );
04004                 } else {
04005                     $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
04006                 }
04007             } else {
04008                 $result .= substr( $invalue, $i, 1 );
04009             }
04010         }
04011         // reverse the transform that we made for reversibility reasons.
04012         return strtr( $result, array( "&#x0" => "&#x" ) );
04013     }
04014 }