MediaWiki  REL1_19
EditPage.php
Go to the documentation of this file.
00001 <?php
00022 class EditPage {
00023 
00027         const AS_SUCCESS_UPDATE            = 200;
00028 
00032         const AS_SUCCESS_NEW_ARTICLE       = 201;
00033 
00037         const AS_HOOK_ERROR                = 210;
00038 
00042         const AS_FILTERING                 = 211;
00043 
00047         const AS_HOOK_ERROR_EXPECTED       = 212;
00048 
00052         const AS_BLOCKED_PAGE_FOR_USER     = 215;
00053 
00057         const AS_CONTENT_TOO_BIG           = 216;
00058 
00062         const AS_USER_CANNOT_EDIT          = 217;
00063 
00067         const AS_READ_ONLY_PAGE_ANON       = 218;
00068 
00072         const AS_READ_ONLY_PAGE_LOGGED     = 219;
00073 
00077         const AS_READ_ONLY_PAGE            = 220;
00078 
00082         const AS_RATE_LIMITED              = 221;
00083 
00088         const AS_ARTICLE_WAS_DELETED       = 222;
00089 
00094         const AS_NO_CREATE_PERMISSION      = 223;
00095 
00099         const AS_BLANK_ARTICLE             = 224;
00100 
00104         const AS_CONFLICT_DETECTED         = 225;
00105 
00110         const AS_SUMMARY_NEEDED            = 226;
00111 
00115         const AS_TEXTBOX_EMPTY             = 228;
00116 
00120         const AS_MAX_ARTICLE_SIZE_EXCEEDED = 229;
00121 
00125         const AS_OK                        = 230;
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         var $mArticle;
00151 
00155         var $mTitle;
00156         private $mContextTitle = null;
00157         var $action = 'submit';
00158         var $isConflict = false;
00159         var $isCssJsSubpage = false;
00160         var $isCssSubpage = false;
00161         var $isJsSubpage = false;
00162         var $isWrongCaseCssJsPage = false;
00163         var $isNew = false; // new page or new section
00164         var $deletedSinceEdit;
00165         var $formtype;
00166         var $firsttime;
00167         var $lastDelete;
00168         var $mTokenOk = false;
00169         var $mTokenOkExceptSuffix = false;
00170         var $mTriedSave = false;
00171         var $incompleteForm = false;
00172         var $tooBig = false;
00173         var $kblength = false;
00174         var $missingComment = false;
00175         var $missingSummary = false;
00176         var $allowBlankSummary = false;
00177         var $autoSumm = '';
00178         var $hookError = '';
00179         #var $mPreviewTemplates;
00180 
00184         var $mParserOutput;
00185 
00186         var $mBaseRevision = false;
00187         var $mShowSummaryField = true;
00188 
00189         # Form values
00190         var $save = false, $preview = false, $diff = false;
00191         var $minoredit = false, $watchthis = false, $recreate = false;
00192         var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
00193         var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
00194         var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
00195 
00196         # Placeholders for text injection by hooks (must be HTML)
00197         # extensions should take care to _append_ to the present value
00198         public $editFormPageTop = ''; // Before even the preview
00199         public $editFormTextTop = '';
00200         public $editFormTextBeforeContent = '';
00201         public $editFormTextAfterWarn = '';
00202         public $editFormTextAfterTools = '';
00203         public $editFormTextBottom = '';
00204         public $editFormTextAfterContent = '';
00205         public $previewTextAfterContent = '';
00206         public $mPreloadText = '';
00207 
00208         /* $didSave should be set to true whenever an article was succesfully altered. */
00209         public $didSave = false;
00210         public $undidRev = 0;
00211 
00212         public $suppressIntro = false;
00213 
00217         public function __construct( Article $article ) {
00218                 $this->mArticle = $article;
00219                 $this->mTitle = $article->getTitle();
00220         }
00221 
00225         public function getArticle() {
00226                 return $this->mArticle;
00227         }
00228 
00233         public function getTitle() {
00234                 return $this->mTitle;
00235         }
00236 
00242         public function setContextTitle( $title ) {
00243                 $this->mContextTitle = $title;
00244         }
00245 
00253         public function getContextTitle() {
00254                 if ( is_null( $this->mContextTitle ) ) {
00255                         global $wgTitle;
00256                         return $wgTitle;
00257                 } else {
00258                         return $this->mContextTitle;
00259                 }
00260         }
00261 
00262         function submit() {
00263                 $this->edit();
00264         }
00265 
00277         function edit() {
00278                 global $wgOut, $wgRequest, $wgUser;
00279                 // Allow extensions to modify/prevent this form or submission
00280                 if ( !wfRunHooks( 'AlternateEdit', array( $this ) ) ) {
00281                         return;
00282                 }
00283 
00284                 wfProfileIn( __METHOD__ );
00285                 wfDebug( __METHOD__.": enter\n" );
00286 
00287                 // If they used redlink=1 and the page exists, redirect to the main article
00288                 if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
00289                         $wgOut->redirect( $this->mTitle->getFullURL() );
00290                         wfProfileOut( __METHOD__ );
00291                         return;
00292                 }
00293 
00294                 $this->importFormData( $wgRequest );
00295                 $this->firsttime = false;
00296 
00297                 if ( $this->live ) {
00298                         $this->livePreview();
00299                         wfProfileOut( __METHOD__ );
00300                         return;
00301                 }
00302 
00303                 if ( wfReadOnly() && $this->save ) {
00304                         // Force preview
00305                         $this->save = false;
00306                         $this->preview = true;
00307                 }
00308 
00309                 if ( $this->save ) {
00310                         $this->formtype = 'save';
00311                 } elseif ( $this->preview ) {
00312                         $this->formtype = 'preview';
00313                 } elseif ( $this->diff ) {
00314                         $this->formtype = 'diff';
00315                 } else { # First time through
00316                         $this->firsttime = true;
00317                         if ( $this->previewOnOpen() ) {
00318                                 $this->formtype = 'preview';
00319                         } else {
00320                                 $this->formtype = 'initial';
00321                         }
00322                 }
00323 
00324                 $permErrors = $this->getEditPermissionErrors();
00325                 if ( $permErrors ) {
00326                         wfDebug( __METHOD__ . ": User can't edit\n" );
00327                         // Auto-block user's IP if the account was "hard" blocked
00328                         $wgUser->spreadAnyEditBlock();
00329 
00330                         $this->displayPermissionsError( $permErrors );
00331 
00332                         wfProfileOut( __METHOD__ );
00333                         return;
00334                 }
00335 
00336                 wfProfileIn( __METHOD__."-business-end" );
00337 
00338                 $this->isConflict = false;
00339                 // css / js subpages of user pages get a special treatment
00340                 $this->isCssJsSubpage       = $this->mTitle->isCssJsSubpage();
00341                 $this->isCssSubpage         = $this->mTitle->isCssSubpage();
00342                 $this->isJsSubpage          = $this->mTitle->isJsSubpage();
00343                 $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
00344                 $this->isNew                = !$this->mTitle->exists() || $this->section == 'new';
00345 
00346                 # Show applicable editing introductions
00347                 if ( $this->formtype == 'initial' || $this->firsttime ) {
00348                         $this->showIntro();
00349                 }
00350 
00351                 # Attempt submission here.  This will check for edit conflicts,
00352                 # and redundantly check for locked database, blocked IPs, etc.
00353                 # that edit() already checked just in case someone tries to sneak
00354                 # in the back door with a hand-edited submission URL.
00355 
00356                 if ( 'save' == $this->formtype ) {
00357                         if ( !$this->attemptSave() ) {
00358                                 wfProfileOut( __METHOD__."-business-end" );
00359                                 wfProfileOut( __METHOD__ );
00360                                 return;
00361                         }
00362                 }
00363 
00364                 # First time through: get contents, set time for conflict
00365                 # checking, etc.
00366                 if ( 'initial' == $this->formtype || $this->firsttime ) {
00367                         if ( $this->initialiseForm() === false ) {
00368                                 $this->noSuchSectionPage();
00369                                 wfProfileOut( __METHOD__."-business-end" );
00370                                 wfProfileOut( __METHOD__ );
00371                                 return;
00372                         }
00373                         if ( !$this->mTitle->getArticleId() )
00374                                 wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
00375                         else
00376                                 wfRunHooks( 'EditFormInitialText', array( $this ) );
00377                 }
00378 
00379                 $this->showEditForm();
00380                 wfProfileOut( __METHOD__."-business-end" );
00381                 wfProfileOut( __METHOD__ );
00382         }
00383 
00387         protected function getEditPermissionErrors() {
00388                 global $wgUser;
00389                 $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
00390                 # Can this title be created?
00391                 if ( !$this->mTitle->exists() ) {
00392                         $permErrors = array_merge( $permErrors,
00393                                 wfArrayDiff2( $this->mTitle->getUserPermissionsErrors( 'create', $wgUser ), $permErrors ) );
00394                 }
00395                 # Ignore some permissions errors when a user is just previewing/viewing diffs
00396                 $remove = array();
00397                 foreach( $permErrors as $error ) {
00398                         if ( ( $this->preview || $this->diff ) &&
00399                                 ( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' ) )
00400                         {
00401                                 $remove[] = $error;
00402                         }
00403                 }
00404                 $permErrors = wfArrayDiff2( $permErrors, $remove );
00405                 return $permErrors;
00406         }
00407 
00420         protected function displayPermissionsError( array $permErrors ) {
00421                 global $wgRequest, $wgOut;
00422 
00423                 if ( $wgRequest->getBool( 'redlink' ) ) {
00424                         // The edit page was reached via a red link.
00425                         // Redirect to the article page and let them click the edit tab if
00426                         // they really want a permission error.
00427                         $wgOut->redirect( $this->mTitle->getFullUrl() );
00428                         return;
00429                 }
00430 
00431                 $content = $this->getContent();
00432 
00433                 # Use the normal message if there's nothing to display
00434                 if ( $this->firsttime && $content === '' ) {
00435                         $action = $this->mTitle->exists() ? 'edit' :
00436                                 ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
00437                         throw new PermissionsError( $action, $permErrors );
00438                 }
00439 
00440                 $wgOut->setPageTitle( wfMessage( 'viewsource-title', $this->getContextTitle()->getPrefixedText() ) );
00441                 $wgOut->addBacklinkSubtitle( $this->getContextTitle() );
00442                 $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) );
00443                 $wgOut->addHTML( "<hr />\n" );
00444 
00445                 # If the user made changes, preserve them when showing the markup
00446                 # (This happens when a user is blocked during edit, for instance)
00447                 if ( !$this->firsttime ) {
00448                         $content = $this->textbox1;
00449                         $wgOut->addWikiMsg( 'viewyourtext' );
00450                 } else {
00451                         $wgOut->addWikiMsg( 'viewsourcetext' );
00452                 }
00453 
00454                 $this->showTextbox( $content, 'wpTextbox1', array( 'readonly' ) );
00455 
00456                 $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
00457                         Linker::formatTemplates( $this->getTemplates() ) ) );
00458 
00459                 if ( $this->mTitle->exists() ) {
00460                         $wgOut->returnToMain( null, $this->mTitle );
00461                 }
00462         }
00463 
00470         function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
00471                 wfDeprecated( __METHOD__, '1.19' );
00472                 
00473                 global $wgRequest, $wgOut;
00474                 if ( $wgRequest->getBool( 'redlink' ) ) {
00475                         // The edit page was reached via a red link.
00476                         // Redirect to the article page and let them click the edit tab if
00477                         // they really want a permission error.
00478                         $wgOut->redirect( $this->mTitle->getFullUrl() );
00479                 } else {
00480                         $wgOut->readOnlyPage( $source, $protected, $reasons, $action );
00481                 }
00482         }
00483 
00489         protected function previewOnOpen() {
00490                 global $wgRequest, $wgUser, $wgPreviewOnOpenNamespaces;
00491                 if ( $wgRequest->getVal( 'preview' ) == 'yes' ) {
00492                         // Explicit override from request
00493                         return true;
00494                 } elseif ( $wgRequest->getVal( 'preview' ) == 'no' ) {
00495                         // Explicit override from request
00496                         return false;
00497                 } elseif ( $this->section == 'new' ) {
00498                         // Nothing *to* preview for new sections
00499                         return false;
00500                 } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) {
00501                         // Standard preference behaviour
00502                         return true;
00503                 } elseif ( !$this->mTitle->exists() &&
00504                   isset($wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()]) &&
00505                   $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
00506                 {
00507                         // Categories are special
00508                         return true;
00509                 } else {
00510                         return false;
00511                 }
00512         }
00513 
00520         protected function isWrongCaseCssJsPage() {
00521                 if( $this->mTitle->isCssJsSubpage() ) {
00522                         $name = $this->mTitle->getSkinFromCssJsSubpage();
00523                         $skins = array_merge(
00524                                 array_keys( Skin::getSkinNames() ),
00525                                 array( 'common' )
00526                         );
00527                         return !in_array( $name, $skins )
00528                                 && in_array( strtolower( $name ), $skins );
00529                 } else {
00530                         return false;
00531                 }
00532         }
00533 
00540         protected function isSectionEditSupported() {
00541                 return true;
00542         }
00543 
00548         function importFormData( &$request ) {
00549                 global $wgLang, $wgUser;
00550 
00551                 wfProfileIn( __METHOD__ );
00552 
00553                 # Section edit can come from either the form or a link
00554                 $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
00555 
00556                 if ( $request->wasPosted() ) {
00557                         # These fields need to be checked for encoding.
00558                         # Also remove trailing whitespace, but don't remove _initial_
00559                         # whitespace from the text boxes. This may be significant formatting.
00560                         $this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' );
00561                         if ( !$request->getCheck('wpTextbox2') ) {
00562                                 // Skip this if wpTextbox2 has input, it indicates that we came
00563                                 // from a conflict page with raw page text, not a custom form
00564                                 // modified by subclasses
00565                                 wfProfileIn( get_class($this)."::importContentFormData" );
00566                                 $textbox1 = $this->importContentFormData( $request );
00567                                 if ( isset($textbox1) )
00568                                         $this->textbox1 = $textbox1;
00569                                 wfProfileOut( get_class($this)."::importContentFormData" );
00570                         }
00571 
00572                         # Truncate for whole multibyte characters. +5 bytes for ellipsis
00573                         $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 250 );
00574 
00575                         # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
00576                         # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
00577                         # section titles.
00578                         $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
00579                         
00580                         # Treat sectiontitle the same way as summary.
00581                         # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
00582                         # currently doing double duty as both edit summary and section title. Right now this
00583                         # is just to allow API edits to work around this limitation, but this should be
00584                         # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
00585                         $this->sectiontitle = $wgLang->truncate( $request->getText( 'wpSectionTitle' ), 250 );
00586                         $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
00587 
00588                         $this->edittime = $request->getVal( 'wpEdittime' );
00589                         $this->starttime = $request->getVal( 'wpStarttime' );
00590 
00591                         $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
00592 
00593                         if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
00594                                 // wpTextbox1 field is missing, possibly due to being "too big"
00595                                 // according to some filter rules such as Suhosin's setting for
00596                                 // suhosin.request.max_value_length (d'oh)
00597                                 $this->incompleteForm = true;
00598                         } else {
00599                                 // edittime should be one of our last fields; if it's missing,
00600                                 // the submission probably broke somewhere in the middle.
00601                                 $this->incompleteForm = is_null( $this->edittime );
00602                         }
00603                         if ( $this->incompleteForm ) {
00604                                 # If the form is incomplete, force to preview.
00605                                 wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
00606                                 wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
00607                                 $this->preview = true;
00608                         } else {
00609                                 /* Fallback for live preview */
00610                                 $this->preview = $request->getCheck( 'wpPreview' ) || $request->getCheck( 'wpLivePreview' );
00611                                 $this->diff = $request->getCheck( 'wpDiff' );
00612 
00613                                 // Remember whether a save was requested, so we can indicate
00614                                 // if we forced preview due to session failure.
00615                                 $this->mTriedSave = !$this->preview;
00616 
00617                                 if ( $this->tokenOk( $request ) ) {
00618                                         # Some browsers will not report any submit button
00619                                         # if the user hits enter in the comment box.
00620                                         # The unmarked state will be assumed to be a save,
00621                                         # if the form seems otherwise complete.
00622                                         wfDebug( __METHOD__ . ": Passed token check.\n" );
00623                                 } elseif ( $this->diff ) {
00624                                         # Failed token check, but only requested "Show Changes".
00625                                         wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
00626                                 } else {
00627                                         # Page might be a hack attempt posted from
00628                                         # an external site. Preview instead of saving.
00629                                         wfDebug( __METHOD__ . ": Failed token check; forcing preview\n" );
00630                                         $this->preview = true;
00631                                 }
00632                         }
00633                         $this->save = !$this->preview && !$this->diff;
00634                         if ( !preg_match( '/^\d{14}$/', $this->edittime ) ) {
00635                                 $this->edittime = null;
00636                         }
00637 
00638                         if ( !preg_match( '/^\d{14}$/', $this->starttime ) ) {
00639                                 $this->starttime = null;
00640                         }
00641 
00642                         $this->recreate  = $request->getCheck( 'wpRecreate' );
00643 
00644                         $this->minoredit = $request->getCheck( 'wpMinoredit' );
00645                         $this->watchthis = $request->getCheck( 'wpWatchthis' );
00646 
00647                         # Don't force edit summaries when a user is editing their own user or talk page
00648                         if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK ) &&
00649                                 $this->mTitle->getText() == $wgUser->getName() )
00650                         {
00651                                 $this->allowBlankSummary = true;
00652                         } else {
00653                                 $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ) || !$wgUser->getOption( 'forceeditsummary');
00654                         }
00655 
00656                         $this->autoSumm = $request->getText( 'wpAutoSummary' );
00657                 } else {
00658                         # Not a posted form? Start with nothing.
00659                         wfDebug( __METHOD__ . ": Not a posted form.\n" );
00660                         $this->textbox1     = '';
00661                         $this->summary      = '';
00662                         $this->sectiontitle = '';
00663                         $this->edittime     = '';
00664                         $this->starttime    = wfTimestampNow();
00665                         $this->edit         = false;
00666                         $this->preview      = false;
00667                         $this->save         = false;
00668                         $this->diff         = false;
00669                         $this->minoredit    = false;
00670                         $this->watchthis    = $request->getBool( 'watchthis', false ); // Watch may be overriden by request parameters
00671                         $this->recreate     = false;
00672                         
00673                         // When creating a new section, we can preload a section title by passing it as the
00674                         // preloadtitle parameter in the URL (Bug 13100)
00675                         if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
00676                                 $this->sectiontitle = $request->getVal( 'preloadtitle' );
00677                                 // Once wpSummary isn't being use for setting section titles, we should delete this.
00678                                 $this->summary = $request->getVal( 'preloadtitle' );
00679                         }
00680                         elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
00681                                 $this->summary = $request->getText( 'summary' );
00682                         }
00683 
00684                         if ( $request->getVal( 'minor' ) ) {
00685                                 $this->minoredit = true;
00686                         }
00687                 }
00688 
00689                 $this->bot = $request->getBool( 'bot', true );
00690                 $this->nosummary = $request->getBool( 'nosummary' );
00691 
00692                 $this->oldid = $request->getInt( 'oldid' );
00693 
00694                 $this->live = $request->getCheck( 'live' );
00695                 $this->editintro = $request->getText( 'editintro',
00696                         // Custom edit intro for new sections
00697                         $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
00698 
00699                 // Allow extensions to modify form data
00700                 wfRunHooks( 'EditPage::importFormData', array( $this, $request ) );
00701 
00702                 wfProfileOut( __METHOD__ );
00703         }
00704 
00713         protected function importContentFormData( &$request ) {
00714                 return; // Don't do anything, EditPage already extracted wpTextbox1
00715         }
00716 
00722         function initialiseForm() {
00723                 global $wgUser;
00724                 $this->edittime = $this->mArticle->getTimestamp();
00725                 $this->textbox1 = $this->getContent( false );
00726                 // activate checkboxes if user wants them to be always active
00727                 # Sort out the "watch" checkbox
00728                 if ( $wgUser->getOption( 'watchdefault' ) ) {
00729                         # Watch all edits
00730                         $this->watchthis = true;
00731                 } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
00732                         # Watch creations
00733                         $this->watchthis = true;
00734                 } elseif ( $this->mTitle->userIsWatching() ) {
00735                         # Already watched
00736                         $this->watchthis = true;
00737                 }
00738                 if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
00739                         $this->minoredit = true;
00740                 }
00741                 if ( $this->textbox1 === false ) {
00742                         return false;
00743                 }
00744                 wfProxyCheck();
00745                 return true;
00746         }
00747 
00755         function getContent( $def_text = '' ) {
00756                 global $wgOut, $wgRequest, $wgParser;
00757 
00758                 wfProfileIn( __METHOD__ );
00759 
00760                 $text = false;
00761 
00762                 // For message page not locally set, use the i18n message.
00763                 // For other non-existent articles, use preload text if any.
00764                 if ( !$this->mTitle->exists() || $this->section == 'new' ) {
00765                         if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
00766                                 # If this is a system message, get the default text.
00767                                 $text = $this->mTitle->getDefaultMessageText();
00768                         }
00769                         if ( $text === false ) {
00770                                 # If requested, preload some text.
00771                                 $preload = $wgRequest->getVal( 'preload',
00772                                         // Custom preload text for new sections
00773                                         $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
00774                                 $text = $this->getPreloadedText( $preload );
00775                         }
00776                 // For existing pages, get text based on "undo" or section parameters.
00777                 } else {
00778                         if ( $this->section != '' ) {
00779                                 // Get section edit text (returns $def_text for invalid sections)
00780                                 $text = $wgParser->getSection( $this->getOriginalContent(), $this->section, $def_text );
00781                         } else {
00782                                 $undoafter = $wgRequest->getInt( 'undoafter' );
00783                                 $undo = $wgRequest->getInt( 'undo' );
00784 
00785                                 if ( $undo > 0 && $undoafter > 0 ) {
00786                                         if ( $undo < $undoafter ) {
00787                                                 # If they got undoafter and undo round the wrong way, switch them
00788                                                 list( $undo, $undoafter ) = array( $undoafter, $undo );
00789                                         }
00790 
00791                                         $undorev = Revision::newFromId( $undo );
00792                                         $oldrev = Revision::newFromId( $undoafter );
00793 
00794                                         # Sanity check, make sure it's the right page,
00795                                         # the revisions exist and they were not deleted.
00796                                         # Otherwise, $text will be left as-is.
00797                                         if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
00798                                                 $undorev->getPage() == $oldrev->getPage() &&
00799                                                 $undorev->getPage() == $this->mTitle->getArticleId() &&
00800                                                 !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
00801                                                 !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
00802 
00803                                                 $text = $this->mArticle->getUndoText( $undorev, $oldrev );
00804                                                 if ( $text === false ) {
00805                                                         # Warn the user that something went wrong
00806                                                         $undoMsg = 'failure';
00807                                                 } else {
00808                                                         # Inform the user of our success and set an automatic edit summary
00809                                                         $undoMsg = 'success';
00810 
00811                                                         # If we just undid one rev, use an autosummary
00812                                                         $firstrev = $oldrev->getNext();
00813                                                         if ( $firstrev->getId() == $undo ) {
00814                                                                 $undoSummary = wfMsgForContent( 'undo-summary', $undo, $undorev->getUserText() );
00815                                                                 if ( $this->summary === '' ) {
00816                                                                         $this->summary = $undoSummary;
00817                                                                 } else {
00818                                                                         $this->summary = $undoSummary . wfMsgForContent( 'colon-separator' ) . $this->summary;
00819                                                                 }
00820                                                                 $this->undidRev = $undo;
00821                                                         }
00822                                                         $this->formtype = 'diff';
00823                                                 }
00824                                         } else {
00825                                                 // Failed basic sanity checks.
00826                                                 // Older revisions may have been removed since the link
00827                                                 // was created, or we may simply have got bogus input.
00828                                                 $undoMsg = 'norev';
00829                                         }
00830 
00831                                         $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
00832                                         $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
00833                                                 wfMsgNoTrans( 'undo-' . $undoMsg ) . '</div>', true, /* interface */true );
00834                                 }
00835 
00836                                 if ( $text === false ) {
00837                                         $text = $this->getOriginalContent();
00838                                 }
00839                         }
00840                 }
00841 
00842                 wfProfileOut( __METHOD__ );
00843                 return $text;
00844         }
00845 
00860         private function getOriginalContent() {
00861                 if ( $this->section == 'new' ) {
00862                         return $this->getCurrentText();
00863                 }
00864                 $revision = $this->mArticle->getRevisionFetched();
00865                 if ( $revision === null ) {
00866                         return '';
00867                 }
00868                 return $this->mArticle->getContent();
00869         }
00870 
00879         private function getCurrentText() {
00880                 $text = $this->mArticle->getRawText();
00881                 if ( $text === false ) {
00882                         return '';
00883                 } else {
00884                         return $text;
00885                 }
00886         }
00887 
00893         public function setPreloadedText( $text ) {
00894                 $this->mPreloadText = $text;
00895         }
00896 
00904         protected function getPreloadedText( $preload ) {
00905                 global $wgUser, $wgParser;
00906 
00907                 if ( !empty( $this->mPreloadText ) ) {
00908                         return $this->mPreloadText;
00909                 }
00910                 
00911                 if ( $preload === '' ) {
00912                         return '';
00913                 }
00914 
00915                 $title = Title::newFromText( $preload );
00916                 # Check for existence to avoid getting MediaWiki:Noarticletext
00917                 if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
00918                         return '';
00919                 }
00920 
00921                 $page = WikiPage::factory( $title );
00922                 if ( $page->isRedirect() ) {
00923                         $title = $page->getRedirectTarget();
00924                         # Same as before
00925                         if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
00926                                 return '';
00927                         }
00928                         $page = WikiPage::factory( $title );
00929                 }
00930 
00931                 $parserOptions = ParserOptions::newFromUser( $wgUser );
00932                 return $wgParser->getPreloadText( $page->getRawText(), $title, $parserOptions );
00933         }
00934 
00942         function tokenOk( &$request ) {
00943                 global $wgUser;
00944                 $token = $request->getVal( 'wpEditToken' );
00945                 $this->mTokenOk = $wgUser->matchEditToken( $token );
00946                 $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
00947                 return $this->mTokenOk;
00948         }
00949 
00954         function attemptSave() {
00955                 global $wgUser, $wgOut;
00956 
00957                 $resultDetails = false;
00958                 # Allow bots to exempt some edits from bot flagging
00959                 $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
00960                 $status = $this->internalAttemptSave( $resultDetails, $bot );
00961                 // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
00962 
00963                 if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
00964                         $this->didSave = true;
00965                 }
00966 
00967                 switch ( $status->value ) {
00968                         case self::AS_HOOK_ERROR_EXPECTED:
00969                         case self::AS_CONTENT_TOO_BIG:
00970                         case self::AS_ARTICLE_WAS_DELETED:
00971                         case self::AS_CONFLICT_DETECTED:
00972                         case self::AS_SUMMARY_NEEDED:
00973                         case self::AS_TEXTBOX_EMPTY:
00974                         case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
00975                         case self::AS_END:
00976                                 return true;
00977 
00978                         case self::AS_HOOK_ERROR:
00979                         case self::AS_FILTERING:
00980                                 return false;
00981 
00982                         case self::AS_SUCCESS_NEW_ARTICLE:
00983                                 $query = $resultDetails['redirect'] ? 'redirect=no' : '';
00984                                 $anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
00985                                 $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
00986                                 return false;
00987 
00988                         case self::AS_SUCCESS_UPDATE:
00989                                 $extraQuery = '';
00990                                 $sectionanchor = $resultDetails['sectionanchor'];
00991 
00992                                 // Give extensions a chance to modify URL query on update
00993                                 wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this->mArticle, &$sectionanchor, &$extraQuery ) );
00994 
00995                                 if ( $resultDetails['redirect'] ) {
00996                                         if ( $extraQuery == '' ) {
00997                                                 $extraQuery = 'redirect=no';
00998                                         } else {
00999                                                 $extraQuery = 'redirect=no&' . $extraQuery;
01000                                         }
01001                                 }
01002                                 $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
01003                                 return false;
01004 
01005                         case self::AS_BLANK_ARTICLE:
01006                                 $wgOut->redirect( $this->getContextTitle()->getFullURL() );
01007                                 return false;
01008 
01009                         case self::AS_SPAM_ERROR:
01010                                 $this->spamPageWithContent( $resultDetails['spam'] );
01011                                 return false;
01012 
01013                         case self::AS_BLOCKED_PAGE_FOR_USER:
01014                                 throw new UserBlockedError( $wgUser->mBlock );
01015 
01016                         case self::AS_IMAGE_REDIRECT_ANON:
01017                         case self::AS_IMAGE_REDIRECT_LOGGED:
01018                                 throw new PermissionsError( 'upload' );
01019 
01020                         case self::AS_READ_ONLY_PAGE_ANON:
01021                         case self::AS_READ_ONLY_PAGE_LOGGED:
01022                                 throw new PermissionsError( 'edit' );
01023 
01024                         case self::AS_READ_ONLY_PAGE:
01025                                 throw new ReadOnlyError;
01026 
01027                         case self::AS_RATE_LIMITED:
01028                                 throw new ThrottledError();
01029 
01030                         case self::AS_NO_CREATE_PERMISSION:
01031                                 $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
01032                                 throw new PermissionsError( $permission );
01033 
01034                 }
01035                 return false;
01036         }
01037 
01050         function internalAttemptSave( &$result, $bot = false ) {
01051                 global $wgFilterCallback, $wgUser, $wgRequest, $wgParser;
01052                 global $wgMaxArticleSize;
01053 
01054                 $status = Status::newGood();
01055 
01056                 wfProfileIn( __METHOD__  );
01057                 wfProfileIn( __METHOD__ . '-checks' );
01058 
01059                 if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
01060                         wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
01061                         $status->fatal( 'hookaborted' );
01062                         $status->value = self::AS_HOOK_ERROR;
01063                         wfProfileOut( __METHOD__ . '-checks' );
01064                         wfProfileOut( __METHOD__  );
01065                         return $status;
01066                 }
01067 
01068                 # Check image redirect
01069                 if ( $this->mTitle->getNamespace() == NS_FILE &&
01070                         Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
01071                         !$wgUser->isAllowed( 'upload' ) ) {
01072                                 $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
01073                                 $status->setResult( false, $code );
01074 
01075                                 wfProfileOut( __METHOD__ . '-checks' );
01076                                 wfProfileOut( __METHOD__  );
01077 
01078                                 return $status;
01079                 }
01080 
01081                 # Check for spam
01082                 $match = self::matchSummarySpamRegex( $this->summary );
01083                 if ( $match === false ) {
01084                         $match = self::matchSpamRegex( $this->textbox1 );
01085                 }
01086                 if ( $match !== false ) {
01087                         $result['spam'] = $match;
01088                         $ip = $wgRequest->getIP();
01089                         $pdbk = $this->mTitle->getPrefixedDBkey();
01090                         $match = str_replace( "\n", '', $match );
01091                         wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
01092                         $status->fatal( 'spamprotectionmatch', $match );
01093                         $status->value = self::AS_SPAM_ERROR;
01094                         wfProfileOut( __METHOD__ . '-checks' );
01095                         wfProfileOut( __METHOD__ );
01096                         return $status;
01097                 }
01098                 if ( $wgFilterCallback && is_callable( $wgFilterCallback ) && $wgFilterCallback( $this->mTitle, $this->textbox1, $this->section, $this->hookError, $this->summary ) ) {
01099                         # Error messages or other handling should be performed by the filter function
01100                         $status->setResult( false, self::AS_FILTERING );
01101                         wfProfileOut( __METHOD__ . '-checks' );
01102                         wfProfileOut( __METHOD__ );
01103                         return $status;
01104                 }
01105                 if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) {
01106                         # Error messages etc. could be handled within the hook...
01107                         $status->fatal( 'hookaborted' );
01108                         $status->value = self::AS_HOOK_ERROR;
01109                         wfProfileOut( __METHOD__ . '-checks' );
01110                         wfProfileOut( __METHOD__ );
01111                         return $status;
01112                 } elseif ( $this->hookError != '' ) {
01113                         # ...or the hook could be expecting us to produce an error
01114                         $status->fatal( 'hookaborted' );
01115                         $status->value = self::AS_HOOK_ERROR_EXPECTED;
01116                         wfProfileOut( __METHOD__ . '-checks' );
01117                         wfProfileOut( __METHOD__ );
01118                         return $status;
01119                 }
01120 
01121                 if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) {
01122                         // Auto-block user's IP if the account was "hard" blocked
01123                         $wgUser->spreadAnyEditBlock();
01124                         # Check block state against master, thus 'false'.
01125                         $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
01126                         wfProfileOut( __METHOD__ . '-checks' );
01127                         wfProfileOut( __METHOD__ );
01128                         return $status;
01129                 }
01130 
01131                 $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
01132                 if ( $this->kblength > $wgMaxArticleSize ) {
01133                         // Error will be displayed by showEditForm()
01134                         $this->tooBig = true;
01135                         $status->setResult( false, self::AS_CONTENT_TOO_BIG );
01136                         wfProfileOut( __METHOD__ . '-checks' );
01137                         wfProfileOut( __METHOD__ );
01138                         return $status;
01139                 }
01140 
01141                 if ( !$wgUser->isAllowed( 'edit' ) ) {
01142                         if ( $wgUser->isAnon() ) {
01143                                 $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
01144                                 wfProfileOut( __METHOD__ . '-checks' );
01145                                 wfProfileOut( __METHOD__ );
01146                                 return $status;
01147                         } else {
01148                                 $status->fatal( 'readonlytext' );
01149                                 $status->value = self::AS_READ_ONLY_PAGE_LOGGED;
01150                                 wfProfileOut( __METHOD__ . '-checks' );
01151                                 wfProfileOut( __METHOD__ );
01152                                 return $status;
01153                         }
01154                 }
01155 
01156                 if ( wfReadOnly() ) {
01157                         $status->fatal( 'readonlytext' );
01158                         $status->value = self::AS_READ_ONLY_PAGE;
01159                         wfProfileOut( __METHOD__ . '-checks' );
01160                         wfProfileOut( __METHOD__ );
01161                         return $status;
01162                 }
01163                 if ( $wgUser->pingLimiter() ) {
01164                         $status->fatal( 'actionthrottledtext' );
01165                         $status->value = self::AS_RATE_LIMITED;
01166                         wfProfileOut( __METHOD__ . '-checks' );
01167                         wfProfileOut( __METHOD__ );
01168                         return $status;
01169                 }
01170 
01171                 # If the article has been deleted while editing, don't save it without
01172                 # confirmation
01173                 if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
01174                         $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
01175                         wfProfileOut( __METHOD__ . '-checks' );
01176                         wfProfileOut( __METHOD__ );
01177                         return $status;
01178                 }
01179 
01180                 wfProfileOut( __METHOD__ . '-checks' );
01181 
01182                 # If article is new, insert it.
01183                 $aid = $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
01184                 $new = ( $aid == 0 );
01185 
01186                 if ( $new ) {
01187                         // Late check for create permission, just in case *PARANOIA*
01188                         if ( !$this->mTitle->userCan( 'create' ) ) {
01189                                 $status->fatal( 'nocreatetext' );
01190                                 $status->value = self::AS_NO_CREATE_PERMISSION;
01191                                 wfDebug( __METHOD__ . ": no create permission\n" );
01192                                 wfProfileOut( __METHOD__ );
01193                                 return $status;
01194                         }
01195 
01196                         # Don't save a new article if it's blank.
01197                         if ( $this->textbox1 == '' ) {
01198                                 $status->setResult( false, self::AS_BLANK_ARTICLE );
01199                                 wfProfileOut( __METHOD__ );
01200                                 return $status;
01201                         }
01202 
01203                         // Run post-section-merge edit filter
01204                         if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) {
01205                                 # Error messages etc. could be handled within the hook...
01206                                 $status->fatal( 'hookaborted' );
01207                                 $status->value = self::AS_HOOK_ERROR;
01208                                 wfProfileOut( __METHOD__ );
01209                                 return $status;
01210                         } elseif ( $this->hookError != '' ) {
01211                                 # ...or the hook could be expecting us to produce an error
01212                                 $status->fatal( 'hookaborted' );
01213                                 $status->value = self::AS_HOOK_ERROR_EXPECTED;
01214                                 wfProfileOut( __METHOD__ );
01215                                 return $status;
01216                         }
01217 
01218                         # Handle the user preference to force summaries here. Check if it's not a redirect.
01219                         if ( !$this->allowBlankSummary && !Title::newFromRedirect( $this->textbox1 ) ) {
01220                                 if ( md5( $this->summary ) == $this->autoSumm ) {
01221                                         $this->missingSummary = true;
01222                                         $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
01223                                         $status->value = self::AS_SUMMARY_NEEDED;
01224                                         wfProfileOut( __METHOD__ );
01225                                         return $status;
01226                                 }
01227                         }
01228 
01229                         $text = $this->textbox1;
01230                         $result['sectionanchor'] = '';
01231                         if ( $this->section == 'new' ) {
01232                                 if ( $this->sectiontitle !== '' ) {
01233                                         // Insert the section title above the content.
01234                                         $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->sectiontitle ) . "\n\n" . $text;
01235                                         
01236                                         // Jump to the new section
01237                                         $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
01238                                         
01239                                         // If no edit summary was specified, create one automatically from the section
01240                                         // title and have it link to the new section. Otherwise, respect the summary as
01241                                         // passed.
01242                                         if ( $this->summary === '' ) {
01243                                                 $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
01244                                                 $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
01245                                         }
01246                                 } elseif ( $this->summary !== '' ) {
01247                                         // Insert the section title above the content.
01248                                         $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $text;
01249                                         
01250                                         // Jump to the new section
01251                                         $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
01252 
01253                                         // Create a link to the new section from the edit summary.
01254                                         $cleanSummary = $wgParser->stripSectionName( $this->summary );
01255                                         $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
01256                                 }
01257                         }
01258 
01259                         $status->value = self::AS_SUCCESS_NEW_ARTICLE;
01260 
01261                 } else {
01262 
01263                         # Article exists. Check for edit conflict.
01264 
01265                         $this->mArticle->clear(); # Force reload of dates, etc.
01266                         $timestamp = $this->mArticle->getTimestamp();
01267 
01268                         wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
01269 
01270                         if ( $timestamp != $this->edittime ) {
01271                                 $this->isConflict = true;
01272                                 if ( $this->section == 'new' ) {
01273                                         if ( $this->mArticle->getUserText() == $wgUser->getName() &&
01274                                                 $this->mArticle->getComment() == $this->summary ) {
01275                                                 // Probably a duplicate submission of a new comment.
01276                                                 // This can happen when squid resends a request after
01277                                                 // a timeout but the first one actually went through.
01278                                                 wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
01279                                         } else {
01280                                                 // New comment; suppress conflict.
01281                                                 $this->isConflict = false;
01282                                                 wfDebug( __METHOD__ .": conflict suppressed; new section\n" );
01283                                         }
01284                                 } elseif ( $this->section == '' && $this->userWasLastToEdit( $wgUser->getId(), $this->edittime ) ) {
01285                                         # Suppress edit conflict with self, except for section edits where merging is required.
01286                                         wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
01287                                         $this->isConflict = false;
01288                                 }
01289                         }
01290                         
01291                         // If sectiontitle is set, use it, otherwise use the summary as the section title (for
01292                         // backwards compatibility with old forms/bots).
01293                         if ( $this->sectiontitle !== '' ) {
01294                                 $sectionTitle = $this->sectiontitle;
01295                         } else {
01296                                 $sectionTitle = $this->summary;
01297                         }
01298                         
01299                         if ( $this->isConflict ) {
01300                                 wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
01301                                 $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
01302                         } else {
01303                                 wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
01304                                 $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
01305                         }
01306                         if ( is_null( $text ) ) {
01307                                 wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
01308                                 $this->isConflict = true;
01309                                 $text = $this->textbox1; // do not try to merge here!
01310                         } elseif ( $this->isConflict ) {
01311                                 # Attempt merge
01312                                 if ( $this->mergeChangesInto( $text ) ) {
01313                                         // Successful merge! Maybe we should tell the user the good news?
01314                                         $this->isConflict = false;
01315                                         wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
01316                                 } else {
01317                                         $this->section = '';
01318                                         $this->textbox1 = $text;
01319                                         wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
01320                                 }
01321                         }
01322 
01323                         if ( $this->isConflict ) {
01324                                 $status->setResult( false, self::AS_CONFLICT_DETECTED );
01325                                 wfProfileOut( __METHOD__ );
01326                                 return $status;
01327                         }
01328 
01329                         // Run post-section-merge edit filter
01330                         if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
01331                                 # Error messages etc. could be handled within the hook...
01332                                 $status->fatal( 'hookaborted' );
01333                                 $status->value = self::AS_HOOK_ERROR;
01334                                 wfProfileOut( __METHOD__ );
01335                                 return $status;
01336                         } elseif ( $this->hookError != '' ) {
01337                                 # ...or the hook could be expecting us to produce an error
01338                                 $status->fatal( 'hookaborted' );
01339                                 $status->value = self::AS_HOOK_ERROR_EXPECTED;
01340                                 wfProfileOut( __METHOD__ );
01341                                 return $status;
01342                         }
01343 
01344                         # Handle the user preference to force summaries here, but not for null edits
01345                         if ( $this->section != 'new' && !$this->allowBlankSummary
01346                                 && $this->getOriginalContent() != $text
01347                                 && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
01348                         {
01349                                 if ( md5( $this->summary ) == $this->autoSumm ) {
01350                                         $this->missingSummary = true;
01351                                         $status->fatal( 'missingsummary' );
01352                                         $status->value = self::AS_SUMMARY_NEEDED;
01353                                         wfProfileOut( __METHOD__ );
01354                                         return $status;
01355                                 }
01356                         }
01357 
01358                         # And a similar thing for new sections
01359                         if ( $this->section == 'new' && !$this->allowBlankSummary ) {
01360                                 if ( trim( $this->summary ) == '' ) {
01361                                         $this->missingSummary = true;
01362                                         $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
01363                                         $status->value = self::AS_SUMMARY_NEEDED;
01364                                         wfProfileOut( __METHOD__ );
01365                                         return $status;
01366                                 }
01367                         }
01368 
01369                         # All's well
01370                         wfProfileIn( __METHOD__ . '-sectionanchor' );
01371                         $sectionanchor = '';
01372                         if ( $this->section == 'new' ) {
01373                                 if ( $this->textbox1 == '' ) {
01374                                         $this->missingComment = true;
01375                                         $status->fatal( 'missingcommenttext' );
01376                                         $status->value = self::AS_TEXTBOX_EMPTY;
01377                                         wfProfileOut( __METHOD__ . '-sectionanchor' );
01378                                         wfProfileOut( __METHOD__ );
01379                                         return $status;
01380                                 }
01381                                 if ( $this->sectiontitle !== '' ) {
01382                                         $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
01383                                         // If no edit summary was specified, create one automatically from the section
01384                                         // title and have it link to the new section. Otherwise, respect the summary as
01385                                         // passed.
01386                                         if ( $this->summary === '' ) {
01387                                                 $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
01388                                                 $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
01389                                         }
01390                                 } elseif ( $this->summary !== '' ) {
01391                                         $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
01392                                         # This is a new section, so create a link to the new section
01393                                         # in the revision summary.
01394                                         $cleanSummary = $wgParser->stripSectionName( $this->summary );
01395                                         $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
01396                                 }
01397                         } elseif ( $this->section != '' ) {
01398                                 # Try to get a section anchor from the section source, redirect to edited section if header found
01399                                 # XXX: might be better to integrate this into Article::replaceSection
01400                                 # for duplicate heading checking and maybe parsing
01401                                 $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
01402                                 # we can't deal with anchors, includes, html etc in the header for now,
01403                                 # headline would need to be parsed to improve this
01404                                 if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
01405                                         $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
01406                                 }
01407                         }
01408                         $result['sectionanchor'] = $sectionanchor;
01409                         wfProfileOut( __METHOD__ . '-sectionanchor' );
01410 
01411                         // Save errors may fall down to the edit form, but we've now
01412                         // merged the section into full text. Clear the section field
01413                         // so that later submission of conflict forms won't try to
01414                         // replace that into a duplicated mess.
01415                         $this->textbox1 = $text;
01416                         $this->section = '';
01417 
01418                         $status->value = self::AS_SUCCESS_UPDATE;
01419                 }
01420 
01421                 // Check for length errors again now that the section is merged in
01422                 $this->kblength = (int)( strlen( $text ) / 1024 );
01423                 if ( $this->kblength > $wgMaxArticleSize ) {
01424                         $this->tooBig = true;
01425                         $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
01426                         wfProfileOut( __METHOD__ );
01427                         return $status;
01428                 }
01429 
01430                 $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
01431                         ( $new ? EDIT_NEW : EDIT_UPDATE ) |
01432                         ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
01433                         ( $bot ? EDIT_FORCE_BOT : 0 );
01434 
01435                 $doEditStatus = $this->mArticle->doEdit( $text, $this->summary, $flags );
01436 
01437                 if ( $doEditStatus->isOK() ) {
01438                         $result['redirect'] = Title::newFromRedirect( $text ) !== null;
01439                         $this->commitWatch();
01440                         wfProfileOut( __METHOD__ );
01441                         return $status;
01442                 } else {
01443                         $this->isConflict = true;
01444                         $doEditStatus->value = self::AS_END; // Destroys data doEdit() put in $status->value but who cares
01445                         wfProfileOut( __METHOD__ );
01446                         return $doEditStatus;
01447                 }
01448         }
01449 
01453         protected function commitWatch() {
01454                 global $wgUser;
01455                 if ( $this->watchthis xor $this->mTitle->userIsWatching() ) {
01456                         $dbw = wfGetDB( DB_MASTER );
01457                         $dbw->begin();
01458                         if ( $this->watchthis ) {
01459                                 WatchAction::doWatch( $this->mTitle, $wgUser );
01460                         } else {
01461                                 WatchAction::doUnwatch( $this->mTitle, $wgUser );
01462                         }
01463                         $dbw->commit();
01464                 }
01465         }
01466 
01477         protected function userWasLastToEdit( $id, $edittime ) {
01478                 if( !$id ) return false;
01479                 $dbw = wfGetDB( DB_MASTER );
01480                 $res = $dbw->select( 'revision',
01481                         'rev_user',
01482                         array(
01483                                 'rev_page' => $this->mTitle->getArticleId(),
01484                                 'rev_timestamp > '.$dbw->addQuotes( $dbw->timestamp($edittime) )
01485                         ),
01486                         __METHOD__,
01487                         array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ) );
01488                 foreach ( $res as $row ) {
01489                         if( $row->rev_user != $id ) {
01490                                 return false;
01491                         }
01492                 }
01493                 return true;
01494         }
01495 
01504         function mergeChangesInto( &$editText ){
01505                 wfProfileIn( __METHOD__ );
01506 
01507                 $db = wfGetDB( DB_MASTER );
01508 
01509                 // This is the revision the editor started from
01510                 $baseRevision = $this->getBaseRevision();
01511                 if ( is_null( $baseRevision ) ) {
01512                         wfProfileOut( __METHOD__ );
01513                         return false;
01514                 }
01515                 $baseText = $baseRevision->getText();
01516 
01517                 // The current state, we want to merge updates into it
01518                 $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
01519                 if ( is_null( $currentRevision ) ) {
01520                         wfProfileOut( __METHOD__ );
01521                         return false;
01522                 }
01523                 $currentText = $currentRevision->getText();
01524 
01525                 $result = '';
01526                 if ( wfMerge( $baseText, $editText, $currentText, $result ) ) {
01527                         $editText = $result;
01528                         wfProfileOut( __METHOD__ );
01529                         return true;
01530                 } else {
01531                         wfProfileOut( __METHOD__ );
01532                         return false;
01533                 }
01534         }
01535 
01539         function getBaseRevision() {
01540                 if ( !$this->mBaseRevision ) {
01541                         $db = wfGetDB( DB_MASTER );
01542                         $baseRevision = Revision::loadFromTimestamp(
01543                                 $db, $this->mTitle, $this->edittime );
01544                         return $this->mBaseRevision = $baseRevision;
01545                 } else {
01546                         return $this->mBaseRevision;
01547                 }
01548         }
01549 
01557         public static function matchSpamRegex( $text ) {
01558                 global $wgSpamRegex;
01559                 // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
01560                 $regexes = (array)$wgSpamRegex;
01561                 return self::matchSpamRegexInternal( $text, $regexes );
01562         }
01563 
01571         public static function matchSummarySpamRegex( $text ) {
01572                 global $wgSummarySpamRegex;
01573                 $regexes = (array)$wgSummarySpamRegex;
01574                 return self::matchSpamRegexInternal( $text, $regexes );
01575         }
01576 
01582         protected static function matchSpamRegexInternal( $text, $regexes ) {
01583                 foreach( $regexes as $regex ) {
01584                         $matches = array();
01585                         if( preg_match( $regex, $text, $matches ) ) {
01586                                 return $matches[0];
01587                         }
01588                 }
01589                 return false;
01590         }
01591 
01592         function setHeaders() {
01593                 global $wgOut, $wgUser;
01594 
01595                 $wgOut->addModules( 'mediawiki.action.edit' );
01596 
01597                 if ( $wgUser->getOption( 'uselivepreview', false ) ) {
01598                         $wgOut->addModules( 'mediawiki.legacy.preview' );
01599                 }
01600                 // Bug #19334: textarea jumps when editing articles in IE8
01601                 $wgOut->addStyle( 'common/IE80Fixes.css', 'screen', 'IE 8' );
01602 
01603                 $wgOut->setRobotPolicy( 'noindex,nofollow' );
01604 
01605                 # Enabled article-related sidebar, toplinks, etc.
01606                 $wgOut->setArticleRelated( true );
01607 
01608                 if ( $this->isConflict ) {
01609                         $wgOut->setPageTitle( wfMessage( 'editconflict', $this->getContextTitle()->getPrefixedText() ) );
01610                 } elseif ( $this->section != '' ) {
01611                         $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
01612                         $wgOut->setPageTitle( wfMessage( $msg, $this->getContextTitle()->getPrefixedText() ) );
01613                 } else {
01614                         # Use the title defined by DISPLAYTITLE magic word when present
01615                         if ( isset( $this->mParserOutput )
01616                          && ( $dt = $this->mParserOutput->getDisplayTitle() ) !== false ) {
01617                                 $title = $dt;
01618                         } else {
01619                                 $title = $this->getContextTitle()->getPrefixedText();
01620                         }
01621                         $wgOut->setPageTitle( wfMessage( 'editing', $title ) );
01622                 }
01623         }
01624 
01628         protected function showIntro() {
01629                 global $wgOut, $wgUser;
01630                 if ( $this->suppressIntro ) {
01631                         return;
01632                 }
01633 
01634                 $namespace = $this->mTitle->getNamespace();
01635 
01636                 if ( $namespace == NS_MEDIAWIKI ) {
01637                         # Show a warning if editing an interface message
01638                         $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
01639                 }
01640 
01641                 # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
01642                 # Show log extract when the user is currently blocked
01643                 if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
01644                         $parts = explode( '/', $this->mTitle->getText(), 2 );
01645                         $username = $parts[0];
01646                         $user = User::newFromName( $username, false /* allow IP users*/ );
01647                         $ip = User::isIP( $username );
01648                         if ( !($user && $user->isLoggedIn()) && !$ip ) { # User does not exist
01649                                 $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
01650                                         array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
01651                         } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
01652                                 LogEventsList::showLogExtract(
01653                                         $wgOut,
01654                                         'block',
01655                                         $user->getUserPage(),
01656                                         '',
01657                                         array(
01658                                                 'lim' => 1,
01659                                                 'showIfEmpty' => false,
01660                                                 'msgKey' => array(
01661                                                         'blocked-notice-logextract',
01662                                                         $user->getName() # Support GENDER in notice
01663                                                 )
01664                                         )
01665                                 );
01666                         }
01667                 }
01668                 # Try to add a custom edit intro, or use the standard one if this is not possible.
01669                 if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
01670                         if ( $wgUser->isLoggedIn() ) {
01671                                 $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletext\">\n$1\n</div>", 'newarticletext' );
01672                         } else {
01673                                 $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletextanon\">\n$1\n</div>", 'newarticletextanon' );
01674                         }
01675                 }
01676                 # Give a notice if the user is editing a deleted/moved page...
01677                 if ( !$this->mTitle->exists() ) {
01678                         LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle,
01679                                 '', array( 'lim' => 10,
01680                                            'conds' => array( "log_action != 'revision'" ),
01681                                            'showIfEmpty' => false,
01682                                            'msgKey' => array( 'recreate-moveddeleted-warn') )
01683                         );
01684                 }
01685         }
01686 
01692         protected function showCustomIntro() {
01693                 if ( $this->editintro ) {
01694                         $title = Title::newFromText( $this->editintro );
01695                         if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
01696                                 global $wgOut;
01697                                 // Added using template syntax, to take <noinclude>'s into account.
01698                                 $wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
01699                                 return true;
01700                         } else {
01701                                 return false;
01702                         }
01703                 } else {
01704                         return false;
01705                 }
01706         }
01707 
01713         function showEditForm( $formCallback = null ) {
01714                 global $wgOut, $wgUser;
01715 
01716                 wfProfileIn( __METHOD__ );
01717 
01718                 #need to parse the preview early so that we know which templates are used,
01719                 #otherwise users with "show preview after edit box" will get a blank list
01720                 #we parse this near the beginning so that setHeaders can do the title
01721                 #setting work instead of leaving it in getPreviewText
01722                 $previewOutput = '';
01723                 if ( $this->formtype == 'preview' ) {
01724                         $previewOutput = $this->getPreviewText();
01725                 }
01726 
01727                 wfRunHooks( 'EditPage::showEditForm:initial', array( &$this ) );
01728 
01729                 $this->setHeaders();
01730 
01731                 if ( $this->showHeader() === false ) {
01732                         wfProfileOut( __METHOD__ );
01733                         return;
01734                 }
01735 
01736                 $wgOut->addHTML( $this->editFormPageTop );
01737 
01738                 if ( $wgUser->getOption( 'previewontop' ) ) {
01739                         $this->displayPreviewArea( $previewOutput, true );
01740                 }
01741 
01742                 $wgOut->addHTML( $this->editFormTextTop );
01743 
01744                 $showToolbar = true;
01745                 if ( $this->wasDeletedSinceLastEdit() ) {
01746                         if ( $this->formtype == 'save' ) {
01747                                 // Hide the toolbar and edit area, user can click preview to get it back
01748                                 // Add an confirmation checkbox and explanation.
01749                                 $showToolbar = false;
01750                         } else {
01751                                 $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
01752                                         'deletedwhileediting' );
01753                         }
01754                 }
01755 
01756                 $wgOut->addHTML( Html::openElement( 'form', array( 'id' => 'editform', 'name' => 'editform',
01757                         'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
01758                         'enctype' => 'multipart/form-data' ) ) );
01759 
01760                 if ( is_callable( $formCallback ) ) {
01761                         call_user_func_array( $formCallback, array( &$wgOut ) );
01762                 }
01763 
01764                 wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
01765 
01766                 // Put these up at the top to ensure they aren't lost on early form submission
01767                 $this->showFormBeforeText();
01768 
01769                 if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
01770                         $username = $this->lastDelete->user_name;
01771                         $comment = $this->lastDelete->log_comment;
01772 
01773                         // It is better to not parse the comment at all than to have templates expanded in the middle
01774                         // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
01775                         $key = $comment === ''
01776                                 ? 'confirmrecreate-noreason'
01777                                 : 'confirmrecreate';
01778                         $wgOut->addHTML(
01779                                 '<div class="mw-confirm-recreate">' .
01780                                 wfMsgExt( $key, 'parseinline', $username, "<nowiki>$comment</nowiki>" ) .
01781                                 Xml::checkLabel( wfMsg( 'recreate' ), 'wpRecreate', 'wpRecreate', false,
01782                                         array( 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
01783                                 ) .
01784                                 '</div>'
01785                         );
01786                 }
01787 
01788                 # If a blank edit summary was previously provided, and the appropriate
01789                 # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
01790                 # user being bounced back more than once in the event that a summary
01791                 # is not required.
01792                 #####
01793                 # For a bit more sophisticated detection of blank summaries, hash the
01794                 # automatic one and pass that in the hidden field wpAutoSummary.
01795                 if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
01796                         $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
01797                 }
01798 
01799                 $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
01800                 $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
01801 
01802                 $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
01803 
01804                 if ( $this->section == 'new' ) {
01805                         $this->showSummaryInput( true, $this->summary );
01806                         $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
01807                 }
01808 
01809                 $wgOut->addHTML( $this->editFormTextBeforeContent );
01810 
01811                 if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
01812                         $wgOut->addHTML( EditPage::getEditToolbar() );
01813                 }
01814 
01815                 if ( $this->isConflict ) {
01816                         // In an edit conflict bypass the overrideable content form method
01817                         // and fallback to the raw wpTextbox1 since editconflicts can't be
01818                         // resolved between page source edits and custom ui edits using the
01819                         // custom edit ui.
01820                         $this->textbox2 = $this->textbox1;
01821                         $this->textbox1 = $this->getCurrentText();
01822 
01823                         $this->showTextbox1();
01824                 } else {
01825                         $this->showContentForm();
01826                 }
01827 
01828                 $wgOut->addHTML( $this->editFormTextAfterContent );
01829 
01830                 $wgOut->addWikiText( $this->getCopywarn() );
01831 
01832                 $wgOut->addHTML( $this->editFormTextAfterWarn );
01833 
01834                 $this->showStandardInputs();
01835 
01836                 $this->showFormAfterText();
01837 
01838                 $this->showTosSummary();
01839 
01840                 $this->showEditTools();
01841 
01842                 $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
01843 
01844                 $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
01845                         Linker::formatTemplates( $this->getTemplates(), $this->preview, $this->section != '' ) ) );
01846 
01847                 $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'hiddencats' ),
01848                         Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
01849 
01850                 if ( $this->isConflict ) {
01851                         $this->showConflict();
01852                 }
01853 
01854                 $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
01855 
01856                 if ( !$wgUser->getOption( 'previewontop' ) ) {
01857                         $this->displayPreviewArea( $previewOutput, false );
01858                 }
01859 
01860                 wfProfileOut( __METHOD__ );
01861         }
01862 
01869         public static function extractSectionTitle( $text ) {
01870                 preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
01871                 if ( !empty( $matches[2] ) ) {
01872                         global $wgParser;
01873                         return $wgParser->stripSectionName(trim($matches[2]));
01874                 } else {
01875                         return false;
01876                 }
01877         }
01878 
01879         protected function showHeader() {
01880                 global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang;
01881 
01882                 if ( $this->mTitle->isTalkPage() ) {
01883                         $wgOut->addWikiMsg( 'talkpagetext' );
01884                 }
01885 
01886                 # Optional notices on a per-namespace and per-page basis
01887                 $editnotice_ns   = 'editnotice-'.$this->mTitle->getNamespace();
01888                 $editnotice_ns_message = wfMessage( $editnotice_ns )->inContentLanguage();
01889                 if ( $editnotice_ns_message->exists() ) {
01890                         $wgOut->addWikiText( $editnotice_ns_message->plain() );
01891                 }
01892                 if ( MWNamespace::hasSubpages( $this->mTitle->getNamespace() ) ) {
01893                         $parts = explode( '/', $this->mTitle->getDBkey() );
01894                         $editnotice_base = $editnotice_ns;
01895                         while ( count( $parts ) > 0 ) {
01896                                 $editnotice_base .= '-'.array_shift( $parts );
01897                                 $editnotice_base_msg = wfMessage( $editnotice_base )->inContentLanguage();
01898                                 if ( $editnotice_base_msg->exists() ) {
01899                                         $wgOut->addWikiText( $editnotice_base_msg->plain()  );
01900                                 }
01901                         }
01902                 } else {
01903                         # Even if there are no subpages in namespace, we still don't want / in MW ns.
01904                         $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->mTitle->getDBkey() );
01905                         $editnoticeMsg = wfMessage( $editnoticeText )->inContentLanguage();
01906                         if ( $editnoticeMsg->exists() ) {
01907                                 $wgOut->addWikiText( $editnoticeMsg->plain() );
01908                         }
01909                 }
01910 
01911                 if ( $this->isConflict ) {
01912                         $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
01913                         $this->edittime = $this->mArticle->getTimestamp();
01914                 } else {
01915                         if ( $this->section != '' && !$this->isSectionEditSupported() ) {
01916                                 // We use $this->section to much before this and getVal('wgSection') directly in other places
01917                                 // at this point we can't reset $this->section to '' to fallback to non-section editing.
01918                                 // Someone is welcome to try refactoring though
01919                                 $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
01920                                 return false;
01921                         }
01922 
01923                         if ( $this->section != '' && $this->section != 'new' ) {
01924                                 if ( !$this->summary && !$this->preview && !$this->diff ) {
01925                                         $sectionTitle = self::extractSectionTitle( $this->textbox1 );
01926                                         if ( $sectionTitle !== false ) {
01927                                                 $this->summary = "/* $sectionTitle */ ";
01928                                         }
01929                                 }
01930                         }
01931 
01932                         if ( $this->missingComment ) {
01933                                 $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
01934                         }
01935 
01936                         if ( $this->missingSummary && $this->section != 'new' ) {
01937                                 $wgOut->wrapWikiMsg( "<div id='mw-missingsummary'>\n$1\n</div>", 'missingsummary' );
01938                         }
01939 
01940                         if ( $this->missingSummary && $this->section == 'new' ) {
01941                                 $wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1\n</div>", 'missingcommentheader' );
01942                         }
01943 
01944                         if ( $this->hookError !== '' ) {
01945                                 $wgOut->addWikiText( $this->hookError );
01946                         }
01947 
01948                         if ( !$this->checkUnicodeCompliantBrowser() ) {
01949                                 $wgOut->addWikiMsg( 'nonunicodebrowser' );
01950                         }
01951 
01952                         if ( $this->section != 'new' ) {
01953                                 $revision = $this->mArticle->getRevisionFetched();
01954                                 if ( $revision ) {
01955                                         // Let sysop know that this will make private content public if saved
01956 
01957                                         if ( !$revision->userCan( Revision::DELETED_TEXT ) ) {
01958                                                 $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
01959                                         } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
01960                                                 $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
01961                                         }
01962 
01963                                         if ( !$revision->isCurrent() ) {
01964                                                 $this->mArticle->setOldSubtitle( $revision->getId() );
01965                                                 $wgOut->addWikiMsg( 'editingold' );
01966                                         }
01967                                 } elseif ( $this->mTitle->exists() ) {
01968                                         // Something went wrong
01969 
01970                                         $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
01971                                                 array( 'missing-article', $this->mTitle->getPrefixedText(),
01972                                                 wfMsgNoTrans( 'missingarticle-rev', $this->oldid ) ) );
01973                                 }
01974                         }
01975                 }
01976 
01977                 if ( wfReadOnly() ) {
01978                         $wgOut->wrapWikiMsg( "<div id=\"mw-read-only-warning\">\n$1\n</div>", array( 'readonlywarning', wfReadOnlyReason() ) );
01979                 } elseif ( $wgUser->isAnon() ) {
01980                         if ( $this->formtype != 'preview' ) {
01981                                 $wgOut->wrapWikiMsg( "<div id=\"mw-anon-edit-warning\">\n$1</div>", 'anoneditwarning' );
01982                         } else {
01983                                 $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>", 'anonpreviewwarning' );
01984                         }
01985                 } else {
01986                         if ( $this->isCssJsSubpage ) {
01987                                 # Check the skin exists
01988                                 if ( $this->isWrongCaseCssJsPage ) {
01989                                         $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) );
01990                                 }
01991                                 if ( $this->formtype !== 'preview' ) {
01992                                         if ( $this->isCssSubpage )
01993                                                 $wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) );
01994                                         if ( $this->isJsSubpage )
01995                                                 $wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) );
01996                                 }
01997                         }
01998                 }
01999 
02000                 if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
02001                         # Is the title semi-protected?
02002                         if ( $this->mTitle->isSemiProtected() ) {
02003                                 $noticeMsg = 'semiprotectedpagewarning';
02004                         } else {
02005                                 # Then it must be protected based on static groups (regular)
02006                                 $noticeMsg = 'protectedpagewarning';
02007                         }
02008                         LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
02009                                 array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) );
02010                 }
02011                 if ( $this->mTitle->isCascadeProtected() ) {
02012                         # Is this page under cascading protection from some source pages?
02013                         list($cascadeSources, /* $restrictions */) = $this->mTitle->getCascadeProtectionSources();
02014                         $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
02015                         $cascadeSourcesCount = count( $cascadeSources );
02016                         if ( $cascadeSourcesCount > 0 ) {
02017                                 # Explain, and list the titles responsible
02018                                 foreach( $cascadeSources as $page ) {
02019                                         $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
02020                                 }
02021                         }
02022                         $notice .= '</div>';
02023                         $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', $cascadeSourcesCount ) );
02024                 }
02025                 if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
02026                         LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
02027                                 array(  'lim' => 1,
02028                                         'showIfEmpty' => false,
02029                                         'msgKey' => array( 'titleprotectedwarning' ),
02030                                         'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ) );
02031                 }
02032 
02033                 if ( $this->kblength === false ) {
02034                         $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
02035                 }
02036 
02037                 if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
02038                         $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
02039                                 array( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) ) );
02040                 } else {
02041                         if( !wfMessage('longpage-hint')->isDisabled() ) {
02042                                 $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
02043                                         array( 'longpage-hint', $wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) )
02044                                 );
02045                         }
02046                 }
02047         }
02048 
02063         function getSummaryInput($summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null) {
02064                 //Note: the maxlength is overriden in JS to 250 and to make it use UTF-8 bytes, not characters.
02065                 $inputAttrs = ( is_array($inputAttrs) ? $inputAttrs : array() ) + array(
02066                         'id' => 'wpSummary',
02067                         'maxlength' => '200',
02068                         'tabindex' => '1',
02069                         'size' => 60,
02070                         'spellcheck' => 'true',
02071                 ) + Linker::tooltipAndAccesskeyAttribs( 'summary' );
02072 
02073                 $spanLabelAttrs = ( is_array($spanLabelAttrs) ? $spanLabelAttrs : array() ) + array(
02074                         'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
02075                         'id' => "wpSummaryLabel"
02076                 );
02077 
02078                 $label = null;
02079                 if ( $labelText ) {
02080                         $label = Xml::tags( 'label', $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null, $labelText );
02081                         $label = Xml::tags( 'span', $spanLabelAttrs, $label );
02082                 }
02083 
02084                 $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
02085 
02086                 return array( $label, $input );
02087         }
02088 
02096         protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
02097                 global $wgOut, $wgContLang;
02098                 # Add a class if 'missingsummary' is triggered to allow styling of the summary line
02099                 $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
02100                 if ( $isSubjectPreview ) {
02101                         if ( $this->nosummary ) {
02102                                 return;
02103                         }
02104                 } else {
02105                         if ( !$this->mShowSummaryField ) {
02106                                 return;
02107                         }
02108                 }
02109                 $summary = $wgContLang->recodeForEdit( $summary );
02110                 $labelText = wfMsgExt( $isSubjectPreview ? 'subject' : 'summary', 'parseinline' );
02111                 list($label, $input) = $this->getSummaryInput($summary, $labelText, array( 'class' => $summaryClass ), array());
02112                 $wgOut->addHTML("{$label} {$input}");
02113         }
02114 
02122         protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
02123                 if ( !$summary || ( !$this->preview && !$this->diff ) )
02124                         return "";
02125 
02126                 global $wgParser;
02127 
02128                 if ( $isSubjectPreview )
02129                         $summary = wfMsgForContent( 'newsectionsummary', $wgParser->stripSectionName( $summary ) );
02130 
02131                 $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
02132 
02133                 $summary = wfMsgExt( $message, 'parseinline' ) . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
02134                 return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary );
02135         }
02136 
02137         protected function showFormBeforeText() {
02138                 global $wgOut;
02139                 $section = htmlspecialchars( $this->section );
02140                 $wgOut->addHTML( <<<HTML
02141 <input type='hidden' value="{$section}" name="wpSection" />
02142 <input type='hidden' value="{$this->starttime}" name="wpStarttime" />
02143 <input type='hidden' value="{$this->edittime}" name="wpEdittime" />
02144 <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
02145 
02146 HTML
02147                 );
02148                 if ( !$this->checkUnicodeCompliantBrowser() )
02149                         $wgOut->addHTML(Html::hidden( 'safemode', '1' ));
02150         }
02151 
02152         protected function showFormAfterText() {
02153                 global $wgOut, $wgUser;
02166                 $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
02167         }
02168 
02177         protected function showContentForm() {
02178                 $this->showTextbox1();
02179         }
02180 
02189         protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
02190                 if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
02191                         $attribs = array( 'style' => 'display:none;' );
02192                 } else {
02193                         $classes = array(); // Textarea CSS
02194                         if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
02195                                 # Is the title semi-protected?
02196                                 if ( $this->mTitle->isSemiProtected() ) {
02197                                         $classes[] = 'mw-textarea-sprotected';
02198                                 } else {
02199                                         # Then it must be protected based on static groups (regular)
02200                                         $classes[] = 'mw-textarea-protected';
02201                                 }
02202                                 # Is the title cascade-protected?
02203                                 if ( $this->mTitle->isCascadeProtected() ) {
02204                                         $classes[] = 'mw-textarea-cprotected';
02205                                 }
02206                         }
02207 
02208                         $attribs = array( 'tabindex' => 1 );
02209 
02210                         if ( is_array( $customAttribs ) ) {
02211                                 $attribs += $customAttribs;
02212                         }
02213 
02214                         if ( count( $classes ) ) {
02215                                 if ( isset( $attribs['class'] ) ) {
02216                                         $classes[] = $attribs['class'];
02217                                 }
02218                                 $attribs['class'] = implode( ' ', $classes );
02219                         }
02220                 }
02221 
02222                 $this->showTextbox( $textoverride !== null ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
02223         }
02224 
02225         protected function showTextbox2() {
02226                 $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
02227         }
02228 
02229         protected function showTextbox( $content, $name, $customAttribs = array() ) {
02230                 global $wgOut, $wgUser;
02231 
02232                 $wikitext = $this->safeUnicodeOutput( $content );
02233                 if ( strval($wikitext) !== '' ) {
02234                         // Ensure there's a newline at the end, otherwise adding lines
02235                         // is awkward.
02236                         // But don't add a newline if the ext is empty, or Firefox in XHTML
02237                         // mode will show an extra newline. A bit annoying.
02238                         $wikitext .= "\n";
02239                 }
02240 
02241                 $attribs = $customAttribs + array(
02242                         'accesskey' => ',',
02243                         'id'   => $name,
02244                         'cols' => $wgUser->getIntOption( 'cols' ),
02245                         'rows' => $wgUser->getIntOption( 'rows' ),
02246                         'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work
02247                 );
02248 
02249                 $pageLang = $this->mTitle->getPageLanguage();
02250                 $attribs['lang'] = $pageLang->getCode();
02251                 $attribs['dir'] = $pageLang->getDir();
02252 
02253                 $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
02254         }
02255 
02256         protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
02257                 global $wgOut;
02258                 $classes = array();
02259                 if ( $isOnTop )
02260                         $classes[] = 'ontop';
02261 
02262                 $attribs = array( 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) );
02263 
02264                 if ( $this->formtype != 'preview' )
02265                         $attribs['style'] = 'display: none;';
02266 
02267                 $wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
02268 
02269                 if ( $this->formtype == 'preview' ) {
02270                         $this->showPreview( $previewOutput );
02271                 }
02272 
02273                 $wgOut->addHTML( '</div>' );
02274 
02275                 if ( $this->formtype == 'diff') {
02276                         $this->showDiff();
02277                 }
02278         }
02279 
02286         protected function showPreview( $text ) {
02287                 global $wgOut;
02288                 if ( $this->mTitle->getNamespace() == NS_CATEGORY) {
02289                         $this->mArticle->openShowCategory();
02290                 }
02291                 # This hook seems slightly odd here, but makes things more
02292                 # consistent for extensions.
02293                 wfRunHooks( 'OutputPageBeforeHTML',array( &$wgOut, &$text ) );
02294                 $wgOut->addHTML( $text );
02295                 if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
02296                         $this->mArticle->closeShowCategory();
02297                 }
02298         }
02299 
02307         function showDiff() {
02308                 global $wgUser, $wgContLang, $wgParser, $wgOut;
02309 
02310                 $oldtext = $this->mArticle->getRawText();
02311                 $newtext = $this->mArticle->replaceSection(
02312                         $this->section, $this->textbox1, $this->summary, $this->edittime );
02313 
02314                 wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
02315 
02316                 $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
02317                 $newtext = $wgParser->preSaveTransform( $newtext, $this->mTitle, $wgUser, $popts );
02318 
02319                 if ( $oldtext !== false  || $newtext != '' ) {
02320                         $oldtitle = wfMsgExt( 'currentrev', array( 'parseinline' ) );
02321                         $newtitle = wfMsgExt( 'yourtext', array( 'parseinline' ) );
02322 
02323                         $de = new DifferenceEngine( $this->mArticle->getContext() );
02324                         $de->setText( $oldtext, $newtext );
02325                         $difftext = $de->getDiff( $oldtitle, $newtitle );
02326                         $de->showDiffStyle();
02327                 } else {
02328                         $difftext = '';
02329                 }
02330 
02331                 $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
02332         }
02333 
02342         protected function showTosSummary() {
02343                 $msg = 'editpage-tos-summary';
02344                 wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
02345                 if( !wfMessage( $msg )->isDisabled() ) {
02346                         global $wgOut;
02347                         $wgOut->addHTML( '<div class="mw-tos-summary">' );
02348                         $wgOut->addWikiMsg( $msg );
02349                         $wgOut->addHTML( '</div>' );
02350                 }
02351         }
02352 
02353         protected function showEditTools() {
02354                 global $wgOut;
02355                 $wgOut->addHTML( '<div class="mw-editTools">' .
02356                         wfMessage( 'edittools' )->inContentLanguage()->parse() .
02357                         '</div>' );
02358         }
02359 
02360         protected function getCopywarn() {
02361                 global $wgRightsText;
02362                 if ( $wgRightsText ) {
02363                         $copywarnMsg = array( 'copyrightwarning',
02364                                 '[[' . wfMsgForContent( 'copyrightpage' ) . ']]',
02365                                 $wgRightsText );
02366                 } else {
02367                         $copywarnMsg = array( 'copyrightwarning2',
02368                                 '[[' . wfMsgForContent( 'copyrightpage' ) . ']]' );
02369                 }
02370                 // Allow for site and per-namespace customization of contribution/copyright notice.
02371                 wfRunHooks( 'EditPageCopyrightWarning', array( $this->mTitle, &$copywarnMsg ) );
02372 
02373                 return "<div id=\"editpage-copywarn\">\n" .
02374                         call_user_func_array("wfMsgNoTrans", $copywarnMsg) . "\n</div>";
02375         }
02376 
02377         protected function showStandardInputs( &$tabindex = 2 ) {
02378                 global $wgOut;
02379                 $wgOut->addHTML( "<div class='editOptions'>\n" );
02380 
02381                 if ( $this->section != 'new' ) {
02382                         $this->showSummaryInput( false, $this->summary );
02383                         $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
02384                 }
02385 
02386                 $checkboxes = $this->getCheckboxes( $tabindex,
02387                         array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) );
02388                 $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" );
02389                 $wgOut->addHTML( "<div class='editButtons'>\n" );
02390                 $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
02391 
02392                 $cancel = $this->getCancelLink();
02393                 if ( $cancel !== '' ) {
02394                         $cancel .= wfMsgExt( 'pipe-separator' , 'escapenoentities' );
02395                 }
02396                 $edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ) );
02397                 $edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'.
02398                         htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '.
02399                         htmlspecialchars( wfMsg( 'newwindow' ) );
02400                 $wgOut->addHTML( "      <span class='editHelp'>{$cancel}{$edithelp}</span>\n" );
02401                 $wgOut->addHTML( "</div><!-- editButtons -->\n</div><!-- editOptions -->\n" );
02402         }
02403 
02408         protected function showConflict() {
02409                 global $wgOut;
02410 
02411                 if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
02412                         $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
02413 
02414                         $de = new DifferenceEngine( $this->mArticle->getContext() );
02415                         $de->setText( $this->textbox2, $this->textbox1 );
02416                         $de->showDiff( wfMsgExt( 'yourtext', 'parseinline' ), wfMsg( 'storedversion' ) );
02417 
02418                         $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
02419                         $this->showTextbox2();
02420                 }
02421         }
02422 
02426         public function getCancelLink() {
02427                 $cancelParams = array();
02428                 if ( !$this->isConflict && $this->oldid > 0 ) {
02429                         $cancelParams['oldid'] = $this->oldid;
02430                 }
02431 
02432                 return Linker::linkKnown(
02433                         $this->getContextTitle(),
02434                         wfMsgExt( 'cancel', array( 'parseinline' ) ),
02435                         array( 'id' => 'mw-editform-cancel' ),
02436                         $cancelParams
02437                 );
02438         }
02439 
02449         protected function getActionURL( Title $title ) {
02450                 return $title->getLocalURL( array( 'action' => $this->action ) );
02451         }
02452 
02459         protected function wasDeletedSinceLastEdit() {
02460                 if ( $this->deletedSinceEdit !== null ) {
02461                         return $this->deletedSinceEdit;
02462                 }
02463 
02464                 $this->deletedSinceEdit = false;
02465 
02466                 if ( $this->mTitle->isDeletedQuick() ) {
02467                         $this->lastDelete = $this->getLastDelete();
02468                         if ( $this->lastDelete ) {
02469                                 $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
02470                                 if ( $deleteTime > $this->starttime ) {
02471                                         $this->deletedSinceEdit = true;
02472                                 }
02473                         }
02474                 }
02475 
02476                 return $this->deletedSinceEdit;
02477         }
02478 
02479         protected function getLastDelete() {
02480                 $dbr = wfGetDB( DB_SLAVE );
02481                 $data = $dbr->selectRow(
02482                         array( 'logging', 'user' ),
02483                         array( 'log_type',
02484                                    'log_action',
02485                                    'log_timestamp',
02486                                    'log_user',
02487                                    'log_namespace',
02488                                    'log_title',
02489                                    'log_comment',
02490                                    'log_params',
02491                                    'log_deleted',
02492                                    'user_name' ),
02493                         array( 'log_namespace' => $this->mTitle->getNamespace(),
02494                                    'log_title' => $this->mTitle->getDBkey(),
02495                                    'log_type' => 'delete',
02496                                    'log_action' => 'delete',
02497                                    'user_id=log_user' ),
02498                         __METHOD__,
02499                         array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' )
02500                 );
02501                 // Quick paranoid permission checks...
02502                 if( is_object( $data ) ) {
02503                         if( $data->log_deleted & LogPage::DELETED_USER )
02504                                 $data->user_name = wfMsgHtml( 'rev-deleted-user' );
02505                         if( $data->log_deleted & LogPage::DELETED_COMMENT )
02506                                 $data->log_comment = wfMsgHtml( 'rev-deleted-comment' );
02507                 }
02508                 return $data;
02509         }
02510 
02515         function getPreviewText() {
02516                 global $wgOut, $wgUser, $wgParser, $wgRawHtml;
02517 
02518                 wfProfileIn( __METHOD__ );
02519 
02520                 if ( $wgRawHtml && !$this->mTokenOk ) {
02521                         // Could be an offsite preview attempt. This is very unsafe if
02522                         // HTML is enabled, as it could be an attack.
02523                         $parsedNote = '';
02524                         if ( $this->textbox1 !== '' ) {
02525                                 // Do not put big scary notice, if previewing the empty
02526                                 // string, which happens when you initially edit
02527                                 // a category page, due to automatic preview-on-open.
02528                                 $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
02529                                         wfMsg( 'session_fail_preview_html' ) . "</div>", true, /* interface */true );
02530                         }
02531                         wfProfileOut( __METHOD__ );
02532                         return $parsedNote;
02533                 }
02534 
02535                 if ( $this->mTriedSave && !$this->mTokenOk ) {
02536                         if ( $this->mTokenOkExceptSuffix ) {
02537                                 $note = wfMsg( 'token_suffix_mismatch' );
02538                         } else {
02539                                 $note = wfMsg( 'session_fail_preview' );
02540                         }
02541                 } elseif ( $this->incompleteForm ) {
02542                         $note = wfMsg( 'edit_form_incomplete' );
02543                 } else {
02544                         $note = wfMsg( 'previewnote' );
02545                 }
02546 
02547                 $parserOptions = ParserOptions::newFromUser( $wgUser );
02548                 $parserOptions->setEditSection( false );
02549                 $parserOptions->setTidy( true );
02550                 $parserOptions->setIsPreview( true );
02551                 $parserOptions->setIsSectionPreview( !is_null($this->section) && $this->section !== '' );
02552 
02553                 # don't parse non-wikitext pages, show message about preview
02554                 # XXX: stupid php bug won't let us use $this->getContextTitle()->isCssJsSubpage() here -- This note has been there since r3530. Sure the bug was fixed time ago?
02555 
02556                 if ( $this->isCssJsSubpage || !$this->mTitle->isWikitextPage() ) {
02557                         if( $this->mTitle->isCssJsSubpage() ) {
02558                                 $level = 'user';
02559                         } elseif( $this->mTitle->isCssOrJsPage() ) {
02560                                 $level = 'site';
02561                         } else {
02562                                 $level = false;
02563                         }
02564 
02565                         # Used messages to make sure grep find them:
02566                         # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
02567                         if( $level ) {
02568                                 if (preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
02569                                         $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMsg( "{$level}csspreview" ) . "\n</div>";
02570                                         $class = "mw-code mw-css";
02571                                 } elseif (preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
02572                                         $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMsg( "{$level}jspreview" ) . "\n</div>";
02573                                         $class = "mw-code mw-js";
02574                                 } else {
02575                                         throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
02576                                 }
02577                         }
02578 
02579                         $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
02580                         $previewHTML = $parserOutput->mText;
02581                         $previewHTML .= "<pre class=\"$class\" dir=\"ltr\">\n" . htmlspecialchars( $this->textbox1 ) . "\n</pre>\n";
02582                 } else {
02583                         $rt = Title::newFromRedirectArray( $this->textbox1 );
02584                         if ( $rt ) {
02585                                 $previewHTML = $this->mArticle->viewRedirect( $rt, false );
02586                         } else {
02587                                 $toparse = $this->textbox1;
02588 
02589                                 # If we're adding a comment, we need to show the
02590                                 # summary as the headline
02591                                 if ( $this->section == "new" && $this->summary != "" ) {
02592                                         $toparse = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $toparse;
02593                                 }
02594 
02595                                 wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
02596 
02597                                 $parserOptions->enableLimitReport();
02598 
02599                                 $toparse = $wgParser->preSaveTransform( $toparse, $this->mTitle, $wgUser, $parserOptions );
02600                                 $parserOutput = $wgParser->parse( $toparse, $this->mTitle, $parserOptions );
02601 
02602                                 $previewHTML = $parserOutput->getText();
02603                                 $this->mParserOutput = $parserOutput;
02604                                 $wgOut->addParserOutputNoText( $parserOutput );
02605 
02606                                 if ( count( $parserOutput->getWarnings() ) ) {
02607                                         $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
02608                                 }
02609                         }
02610                 }
02611 
02612                 if( $this->isConflict ) {
02613                         $conflict = '<h2 id="mw-previewconflict">' . htmlspecialchars( wfMsg( 'previewconflict' ) ) . "</h2>\n";
02614                 } else {
02615                         $conflict = '<hr />';
02616                 }
02617 
02618                 $previewhead = "<div class='previewnote'>\n" .
02619                         '<h2 id="mw-previewheader">' . htmlspecialchars( wfMsg( 'preview' ) ) . "</h2>" .
02620                         $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
02621 
02622                 $pageLang = $this->mTitle->getPageLanguage();
02623                 $attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
02624                         'class' => 'mw-content-'.$pageLang->getDir() );
02625                 $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
02626 
02627                 wfProfileOut( __METHOD__ );
02628                 return $previewhead . $previewHTML . $this->previewTextAfterContent;
02629         }
02630 
02634         function getTemplates() {
02635                 if ( $this->preview || $this->section != '' ) {
02636                         $templates = array();
02637                         if ( !isset( $this->mParserOutput ) ) {
02638                                 return $templates;
02639                         }
02640                         foreach( $this->mParserOutput->getTemplates() as $ns => $template) {
02641                                 foreach( array_keys( $template ) as $dbk ) {
02642                                         $templates[] = Title::makeTitle($ns, $dbk);
02643                                 }
02644                         }
02645                         return $templates;
02646                 } else {
02647                         return $this->mTitle->getTemplateLinksFrom();
02648                 }
02649         }
02650 
02658         static function getEditToolbar() {
02659                 global $wgStylePath, $wgContLang, $wgLang, $wgOut;
02660                 global $wgUseTeX, $wgEnableUploads, $wgForeignFileRepos;
02661 
02662                 $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
02663 
02677                 $toolarray = array(
02678                         array(
02679                                 'image'  => $wgLang->getImageFile( 'button-bold' ),
02680                                 'id'     => 'mw-editbutton-bold',
02681                                 'open'   => '\'\'\'',
02682                                 'close'  => '\'\'\'',
02683                                 'sample' => wfMsg( 'bold_sample' ),
02684                                 'tip'    => wfMsg( 'bold_tip' ),
02685                                 'key'    => 'B'
02686                         ),
02687                         array(
02688                                 'image'  => $wgLang->getImageFile( 'button-italic' ),
02689                                 'id'     => 'mw-editbutton-italic',
02690                                 'open'   => '\'\'',
02691                                 'close'  => '\'\'',
02692                                 'sample' => wfMsg( 'italic_sample' ),
02693                                 'tip'    => wfMsg( 'italic_tip' ),
02694                                 'key'    => 'I'
02695                         ),
02696                         array(
02697                                 'image'  => $wgLang->getImageFile( 'button-link' ),
02698                                 'id'     => 'mw-editbutton-link',
02699                                 'open'   => '[[',
02700                                 'close'  => ']]',
02701                                 'sample' => wfMsg( 'link_sample' ),
02702                                 'tip'    => wfMsg( 'link_tip' ),
02703                                 'key'    => 'L'
02704                         ),
02705                         array(
02706                                 'image'  => $wgLang->getImageFile( 'button-extlink' ),
02707                                 'id'     => 'mw-editbutton-extlink',
02708                                 'open'   => '[',
02709                                 'close'  => ']',
02710                                 'sample' => wfMsg( 'extlink_sample' ),
02711                                 'tip'    => wfMsg( 'extlink_tip' ),
02712                                 'key'    => 'X'
02713                         ),
02714                         array(
02715                                 'image'  => $wgLang->getImageFile( 'button-headline' ),
02716                                 'id'     => 'mw-editbutton-headline',
02717                                 'open'   => "\n== ",
02718                                 'close'  => " ==\n",
02719                                 'sample' => wfMsg( 'headline_sample' ),
02720                                 'tip'    => wfMsg( 'headline_tip' ),
02721                                 'key'    => 'H'
02722                         ),
02723                         $imagesAvailable ? array(
02724                                 'image'  => $wgLang->getImageFile( 'button-image' ),
02725                                 'id'     => 'mw-editbutton-image',
02726                                 'open'   => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
02727                                 'close'  => ']]',
02728                                 'sample' => wfMsg( 'image_sample' ),
02729                                 'tip'    => wfMsg( 'image_tip' ),
02730                                 'key'    => 'D',
02731                         ) : false,
02732                         $imagesAvailable ? array(
02733                                 'image'  => $wgLang->getImageFile( 'button-media' ),
02734                                 'id'     => 'mw-editbutton-media',
02735                                 'open'   => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
02736                                 'close'  => ']]',
02737                                 'sample' => wfMsg( 'media_sample' ),
02738                                 'tip'    => wfMsg( 'media_tip' ),
02739                                 'key'    => 'M'
02740                         ) : false,
02741                         $wgUseTeX ?     array(
02742                                 'image'  => $wgLang->getImageFile( 'button-math' ),
02743                                 'id'     => 'mw-editbutton-math',
02744                                 'open'   => "<math>",
02745                                 'close'  => "</math>",
02746                                 'sample' => wfMsg( 'math_sample' ),
02747                                 'tip'    => wfMsg( 'math_tip' ),
02748                                 'key'    => 'C'
02749                         ) : false,
02750                         array(
02751                                 'image'  => $wgLang->getImageFile( 'button-nowiki' ),
02752                                 'id'     => 'mw-editbutton-nowiki',
02753                                 'open'   => "<nowiki>",
02754                                 'close'  => "</nowiki>",
02755                                 'sample' => wfMsg( 'nowiki_sample' ),
02756                                 'tip'    => wfMsg( 'nowiki_tip' ),
02757                                 'key'    => 'N'
02758                         ),
02759                         array(
02760                                 'image'  => $wgLang->getImageFile( 'button-sig' ),
02761                                 'id'     => 'mw-editbutton-signature',
02762                                 'open'   => '--~~~~',
02763                                 'close'  => '',
02764                                 'sample' => '',
02765                                 'tip'    => wfMsg( 'sig_tip' ),
02766                                 'key'    => 'Y'
02767                         ),
02768                         array(
02769                                 'image'  => $wgLang->getImageFile( 'button-hr' ),
02770                                 'id'     => 'mw-editbutton-hr',
02771                                 'open'   => "\n----\n",
02772                                 'close'  => '',
02773                                 'sample' => '',
02774                                 'tip'    => wfMsg( 'hr_tip' ),
02775                                 'key'    => 'R'
02776                         )
02777                 );
02778 
02779                 $script = 'mw.loader.using("mediawiki.action.edit", function() {';
02780                 foreach ( $toolarray as $tool ) {
02781                         if ( !$tool ) {
02782                                 continue;
02783                         }
02784 
02785                         $params = array(
02786                                 $image = $wgStylePath . '/common/images/' . $tool['image'],
02787                                 // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
02788                                 // Older browsers show a "speedtip" type message only for ALT.
02789                                 // Ideally these should be different, realistically they
02790                                 // probably don't need to be.
02791                                 $tip = $tool['tip'],
02792                                 $open = $tool['open'],
02793                                 $close = $tool['close'],
02794                                 $sample = $tool['sample'],
02795                                 $cssId = $tool['id'],
02796                         );
02797 
02798                         $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
02799                 }
02800                 
02801                 // This used to be called on DOMReady from mediawiki.action.edit, which
02802                 // ended up causing race conditions with the setup code above.
02803                 $script .= "\n" .
02804                         "// Create button bar\n" .
02805                         "$(function() { mw.toolbar.init(); } );\n";
02806 
02807                 $script .= '});';
02808                 $wgOut->addScript( Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) ) );
02809 
02810                 $toolbar = '<div id="toolbar"></div>';
02811 
02812                 wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
02813 
02814                 return $toolbar;
02815         }
02816 
02827         public function getCheckboxes( &$tabindex, $checked ) {
02828                 global $wgUser;
02829 
02830                 $checkboxes = array();
02831 
02832                 // don't show the minor edit checkbox if it's a new page or section
02833                 if ( !$this->isNew ) {
02834                         $checkboxes['minor'] = '';
02835                         $minorLabel = wfMsgExt( 'minoredit', array( 'parseinline' ) );
02836                         if ( $wgUser->isAllowed( 'minoredit' ) ) {
02837                                 $attribs = array(
02838                                         'tabindex'  => ++$tabindex,
02839                                         'accesskey' => wfMsg( 'accesskey-minoredit' ),
02840                                         'id'        => 'wpMinoredit',
02841                                 );
02842                                 $checkboxes['minor'] =
02843                                         Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
02844                                         "&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
02845                                         Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'minoredit', 'withaccess' ) ) ) .
02846                                         ">{$minorLabel}</label>";
02847                         }
02848                 }
02849 
02850                 $watchLabel = wfMsgExt( 'watchthis', array( 'parseinline' ) );
02851                 $checkboxes['watch'] = '';
02852                 if ( $wgUser->isLoggedIn() ) {
02853                         $attribs = array(
02854                                 'tabindex'  => ++$tabindex,
02855                                 'accesskey' => wfMsg( 'accesskey-watch' ),
02856                                 'id'        => 'wpWatchthis',
02857                         );
02858                         $checkboxes['watch'] =
02859                                 Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
02860                                 "&#160;<label for='wpWatchthis' id='mw-editpage-watch'" .
02861                                 Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ) ) .
02862                                 ">{$watchLabel}</label>";
02863                 }
02864                 wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
02865                 return $checkboxes;
02866         }
02867 
02876         public function getEditButtons( &$tabindex ) {
02877                 $buttons = array();
02878 
02879                 $temp = array(
02880                         'id'        => 'wpSave',
02881                         'name'      => 'wpSave',
02882                         'type'      => 'submit',
02883                         'tabindex'  => ++$tabindex,
02884                         'value'     => wfMsg( 'savearticle' ),
02885                         'accesskey' => wfMsg( 'accesskey-save' ),
02886                         'title'     => wfMsg( 'tooltip-save' ).' ['.wfMsg( 'accesskey-save' ).']',
02887                 );
02888                 $buttons['save'] = Xml::element('input', $temp, '');
02889 
02890                 ++$tabindex; // use the same for preview and live preview
02891                 $temp = array(
02892                         'id'        => 'wpPreview',
02893                         'name'      => 'wpPreview',
02894                         'type'      => 'submit',
02895                         'tabindex'  => $tabindex,
02896                         'value'     => wfMsg( 'showpreview' ),
02897                         'accesskey' => wfMsg( 'accesskey-preview' ),
02898                         'title'     => wfMsg( 'tooltip-preview' ) . ' [' . wfMsg( 'accesskey-preview' ) . ']',
02899                 );
02900                 $buttons['preview'] = Xml::element( 'input', $temp, '' );
02901                 $buttons['live'] = '';
02902 
02903                 $temp = array(
02904                         'id'        => 'wpDiff',
02905                         'name'      => 'wpDiff',
02906                         'type'      => 'submit',
02907                         'tabindex'  => ++$tabindex,
02908                         'value'     => wfMsg( 'showdiff' ),
02909                         'accesskey' => wfMsg( 'accesskey-diff' ),
02910                         'title'     => wfMsg( 'tooltip-diff' ) . ' [' . wfMsg( 'accesskey-diff' ) . ']',
02911                 );
02912                 $buttons['diff'] = Xml::element( 'input', $temp, '' );
02913 
02914                 wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) );
02915                 return $buttons;
02916         }
02917 
02930         function livePreview() {
02931                 global $wgOut;
02932                 $wgOut->disable();
02933                 header( 'Content-type: text/xml; charset=utf-8' );
02934                 header( 'Cache-control: no-cache' );
02935 
02936                 $previewText = $this->getPreviewText();
02937                 #$categories = $skin->getCategoryLinks();
02938 
02939                 $s =
02940                 '<?xml version="1.0" encoding="UTF-8" ?>' . "\n" .
02941                 Xml::tags( 'livepreview', null,
02942                         Xml::element( 'preview', null, $previewText )
02943                         #.      Xml::element( 'category', null, $categories )
02944                 );
02945                 echo $s;
02946         }
02947 
02953         function blockedPage() {
02954                 wfDeprecated( __METHOD__, '1.19' );
02955                 global $wgUser;
02956 
02957                 throw new UserBlockedError( $wgUser->mBlock );
02958         }
02959 
02965         function userNotLoggedInPage() {
02966                 wfDeprecated( __METHOD__, '1.19' );
02967                 throw new PermissionsError( 'edit' );
02968         }
02969 
02976         function noCreatePermission() {
02977                 wfDeprecated( __METHOD__, '1.19' );
02978                 $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
02979                 throw new PermissionsError( $permission );
02980         }
02981 
02986         function noSuchSectionPage() {
02987                 global $wgOut;
02988 
02989                 $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
02990 
02991                 $res = wfMsgExt( 'nosuchsectiontext', 'parse', $this->section );
02992                 wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
02993                 $wgOut->addHTML( $res );
02994 
02995                 $wgOut->returnToMain( false, $this->mTitle );
02996         }
02997 
03004         static function spamPage( $match = false ) {
03005                 wfDeprecated( __METHOD__, '1.17' );
03006                 
03007                 global $wgOut, $wgTitle;
03008 
03009                 $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
03010 
03011                 $wgOut->addHTML( '<div id="spamprotected">' );
03012                 $wgOut->addWikiMsg( 'spamprotectiontext' );
03013                 if ( $match ) {
03014                         $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
03015                 }
03016                 $wgOut->addHTML( '</div>' );
03017 
03018                 $wgOut->returnToMain( false, $wgTitle );
03019         }
03020 
03026         public function spamPageWithContent( $match = false ) {
03027                 global $wgOut;
03028                 $this->textbox2 = $this->textbox1;
03029 
03030                 $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
03031 
03032                 $wgOut->addHTML( '<div id="spamprotected">' );
03033                 $wgOut->addWikiMsg( 'spamprotectiontext' );
03034                 if ( $match ) {
03035                         $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
03036                 }
03037                 $wgOut->addHTML( '</div>' );
03038 
03039                 $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
03040                 $de = new DifferenceEngine( $this->mArticle->getContext() );
03041                 $de->setText( $this->getCurrentText(), $this->textbox2 );
03042                 $de->showDiff( wfMsg( "storedversion" ), wfMsgExt( 'yourtext', 'parseinline' ) );
03043 
03044                 $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
03045                 $this->showTextbox2();
03046 
03047                 $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) );
03048         }
03049 
03056         function sectionAnchor( $text ) {
03057                 global $wgParser;
03058                 return $wgParser->guessSectionNameFromWikiText( $text );
03059         }
03060 
03068         function checkUnicodeCompliantBrowser() {
03069                 global $wgBrowserBlackList;
03070                 if ( empty( $_SERVER["HTTP_USER_AGENT"] ) ) {
03071                         // No User-Agent header sent? Trust it by default...
03072                         return true;
03073                 }
03074                 $currentbrowser = $_SERVER["HTTP_USER_AGENT"];
03075                 foreach ( $wgBrowserBlackList as $browser ) {
03076                         if ( preg_match($browser, $currentbrowser) ) {
03077                                 return false;
03078                         }
03079                 }
03080                 return true;
03081         }
03082 
03092         function safeUnicodeInput( $request, $field ) {
03093                 $text = rtrim( $request->getText( $field ) );
03094                 return $request->getBool( 'safemode' )
03095                         ? $this->unmakesafe( $text )
03096                         : $text;
03097         }
03098 
03104         function safeUnicodeText( $request, $text ) {
03105                 $text = rtrim( $text );
03106                 return $request->getBool( 'safemode' )
03107                         ? $this->unmakesafe( $text )
03108                         : $text;
03109         }
03110 
03119         function safeUnicodeOutput( $text ) {
03120                 global $wgContLang;
03121                 $codedText = $wgContLang->recodeForEdit( $text );
03122                 return $this->checkUnicodeCompliantBrowser()
03123                         ? $codedText
03124                         : $this->makesafe( $codedText );
03125         }
03126 
03140         function makesafe( $invalue ) {
03141                 // Armor existing references for reversability.
03142                 $invalue = strtr( $invalue, array( "&#x" => "&#x0" ) );
03143 
03144                 $bytesleft = 0;
03145                 $result = "";
03146                 $working = 0;
03147                 for( $i = 0; $i < strlen( $invalue ); $i++ ) {
03148                         $bytevalue = ord( $invalue[$i] );
03149                         if ( $bytevalue <= 0x7F ) { //0xxx xxxx
03150                                 $result .= chr( $bytevalue );
03151                                 $bytesleft = 0;
03152                         } elseif ( $bytevalue <= 0xBF ) { //10xx xxxx
03153                                 $working = $working << 6;
03154                                 $working += ($bytevalue & 0x3F);
03155                                 $bytesleft--;
03156                                 if ( $bytesleft <= 0 ) {
03157                                         $result .= "&#x" . strtoupper( dechex( $working ) ) . ";";
03158                                 }
03159                         } elseif ( $bytevalue <= 0xDF ) { //110x xxxx
03160                                 $working = $bytevalue & 0x1F;
03161                                 $bytesleft = 1;
03162                         } elseif ( $bytevalue <= 0xEF ) { //1110 xxxx
03163                                 $working = $bytevalue & 0x0F;
03164                                 $bytesleft = 2;
03165                         } else { //1111 0xxx
03166                                 $working = $bytevalue & 0x07;
03167                                 $bytesleft = 3;
03168                         }
03169                 }
03170                 return $result;
03171         }
03172 
03182         function unmakesafe( $invalue ) {
03183                 $result = "";
03184                 for( $i = 0; $i < strlen( $invalue ); $i++ ) {
03185                         if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i+3] != '0' ) ) {
03186                                 $i += 3;
03187                                 $hexstring = "";
03188                                 do {
03189                                         $hexstring .= $invalue[$i];
03190                                         $i++;
03191                                 } while( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
03192 
03193                                 // Do some sanity checks. These aren't needed for reversability,
03194                                 // but should help keep the breakage down if the editor
03195                                 // breaks one of the entities whilst editing.
03196                                 if ( (substr($invalue,$i,1)==";") and (strlen($hexstring) <= 6) ) {
03197                                         $codepoint = hexdec($hexstring);
03198                                         $result .= codepointToUtf8( $codepoint );
03199                                 } else {
03200                                         $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
03201                                 }
03202                         } else {
03203                                 $result .= substr( $invalue, $i, 1 );
03204                         }
03205                 }
03206                 // reverse the transform that we made for reversability reasons.
03207                 return strtr( $result, array( "&#x0" => "&#x" ) );
03208         }
03209 }