MediaWiki  REL1_20
EditPage.php
Go to the documentation of this file.
00001 <?php
00038 class EditPage {
00039 
00043         const AS_SUCCESS_UPDATE            = 200;
00044 
00048         const AS_SUCCESS_NEW_ARTICLE       = 201;
00049 
00053         const AS_HOOK_ERROR                = 210;
00054 
00058         const AS_HOOK_ERROR_EXPECTED       = 212;
00059 
00063         const AS_BLOCKED_PAGE_FOR_USER     = 215;
00064 
00068         const AS_CONTENT_TOO_BIG           = 216;
00069 
00073         const AS_USER_CANNOT_EDIT          = 217;
00074 
00078         const AS_READ_ONLY_PAGE_ANON       = 218;
00079 
00083         const AS_READ_ONLY_PAGE_LOGGED     = 219;
00084 
00088         const AS_READ_ONLY_PAGE            = 220;
00089 
00093         const AS_RATE_LIMITED              = 221;
00094 
00099         const AS_ARTICLE_WAS_DELETED       = 222;
00100 
00105         const AS_NO_CREATE_PERMISSION      = 223;
00106 
00110         const AS_BLANK_ARTICLE             = 224;
00111 
00115         const AS_CONFLICT_DETECTED         = 225;
00116 
00121         const AS_SUMMARY_NEEDED            = 226;
00122 
00126         const AS_TEXTBOX_EMPTY             = 228;
00127 
00131         const AS_MAX_ARTICLE_SIZE_EXCEEDED = 229;
00132 
00136         const AS_OK                        = 230;
00137 
00141         const AS_END                       = 231;
00142 
00146         const AS_SPAM_ERROR                = 232;
00147 
00151         const AS_IMAGE_REDIRECT_ANON       = 233;
00152 
00156         const AS_IMAGE_REDIRECT_LOGGED     = 234;
00157 
00161         const EDITFORM_ID                  = 'editform';
00162 
00166         var $mArticle;
00167 
00171         var $mTitle;
00172         private $mContextTitle = null;
00173         var $action = 'submit';
00174         var $isConflict = false;
00175         var $isCssJsSubpage = false;
00176         var $isCssSubpage = false;
00177         var $isJsSubpage = false;
00178         var $isWrongCaseCssJsPage = false;
00179         var $isNew = false; // new page or new section
00180         var $deletedSinceEdit;
00181         var $formtype;
00182         var $firsttime;
00183         var $lastDelete;
00184         var $mTokenOk = false;
00185         var $mTokenOkExceptSuffix = false;
00186         var $mTriedSave = false;
00187         var $incompleteForm = false;
00188         var $tooBig = false;
00189         var $kblength = false;
00190         var $missingComment = false;
00191         var $missingSummary = false;
00192         var $allowBlankSummary = false;
00193         var $autoSumm = '';
00194         var $hookError = '';
00195         #var $mPreviewTemplates;
00196 
00200         var $mParserOutput;
00201 
00206         var $hasPresetSummary = false;
00207 
00208         var $mBaseRevision = false;
00209         var $mShowSummaryField = true;
00210 
00211         # Form values
00212         var $save = false, $preview = false, $diff = false;
00213         var $minoredit = false, $watchthis = false, $recreate = false;
00214         var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
00215         var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
00216         var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
00217 
00218         # Placeholders for text injection by hooks (must be HTML)
00219         # extensions should take care to _append_ to the present value
00220         public $editFormPageTop = ''; // Before even the preview
00221         public $editFormTextTop = '';
00222         public $editFormTextBeforeContent = '';
00223         public $editFormTextAfterWarn = '';
00224         public $editFormTextAfterTools = '';
00225         public $editFormTextBottom = '';
00226         public $editFormTextAfterContent = '';
00227         public $previewTextAfterContent = '';
00228         public $mPreloadText = '';
00229 
00230         /* $didSave should be set to true whenever an article was succesfully altered. */
00231         public $didSave = false;
00232         public $undidRev = 0;
00233 
00234         public $suppressIntro = false;
00235 
00239         public function __construct( Article $article ) {
00240                 $this->mArticle = $article;
00241                 $this->mTitle = $article->getTitle();
00242         }
00243 
00247         public function getArticle() {
00248                 return $this->mArticle;
00249         }
00250 
00255         public function getTitle() {
00256                 return $this->mTitle;
00257         }
00258 
00264         public function setContextTitle( $title ) {
00265                 $this->mContextTitle = $title;
00266         }
00267 
00275         public function getContextTitle() {
00276                 if ( is_null( $this->mContextTitle ) ) {
00277                         global $wgTitle;
00278                         return $wgTitle;
00279                 } else {
00280                         return $this->mContextTitle;
00281                 }
00282         }
00283 
00284         function submit() {
00285                 $this->edit();
00286         }
00287 
00299         function edit() {
00300                 global $wgOut, $wgRequest, $wgUser;
00301                 // Allow extensions to modify/prevent this form or submission
00302                 if ( !wfRunHooks( 'AlternateEdit', array( $this ) ) ) {
00303                         return;
00304                 }
00305 
00306                 wfProfileIn( __METHOD__ );
00307                 wfDebug( __METHOD__ . ": enter\n" );
00308 
00309                 // If they used redlink=1 and the page exists, redirect to the main article
00310                 if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
00311                         $wgOut->redirect( $this->mTitle->getFullURL() );
00312                         wfProfileOut( __METHOD__ );
00313                         return;
00314                 }
00315 
00316                 $this->importFormData( $wgRequest );
00317                 $this->firsttime = false;
00318 
00319                 if ( $this->live ) {
00320                         $this->livePreview();
00321                         wfProfileOut( __METHOD__ );
00322                         return;
00323                 }
00324 
00325                 if ( wfReadOnly() && $this->save ) {
00326                         // Force preview
00327                         $this->save = false;
00328                         $this->preview = true;
00329                 }
00330 
00331                 if ( $this->save ) {
00332                         $this->formtype = 'save';
00333                 } elseif ( $this->preview ) {
00334                         $this->formtype = 'preview';
00335                 } elseif ( $this->diff ) {
00336                         $this->formtype = 'diff';
00337                 } else { # First time through
00338                         $this->firsttime = true;
00339                         if ( $this->previewOnOpen() ) {
00340                                 $this->formtype = 'preview';
00341                         } else {
00342                                 $this->formtype = 'initial';
00343                         }
00344                 }
00345 
00346                 $permErrors = $this->getEditPermissionErrors();
00347                 if ( $permErrors ) {
00348                         wfDebug( __METHOD__ . ": User can't edit\n" );
00349                         // Auto-block user's IP if the account was "hard" blocked
00350                         $wgUser->spreadAnyEditBlock();
00351 
00352                         $this->displayPermissionsError( $permErrors );
00353 
00354                         wfProfileOut( __METHOD__ );
00355                         return;
00356                 }
00357 
00358                 wfProfileIn( __METHOD__ . "-business-end" );
00359 
00360                 $this->isConflict = false;
00361                 // css / js subpages of user pages get a special treatment
00362                 $this->isCssJsSubpage       = $this->mTitle->isCssJsSubpage();
00363                 $this->isCssSubpage         = $this->mTitle->isCssSubpage();
00364                 $this->isJsSubpage          = $this->mTitle->isJsSubpage();
00365                 $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
00366                 $this->isNew                = !$this->mTitle->exists() || $this->section == 'new';
00367 
00368                 # Show applicable editing introductions
00369                 if ( $this->formtype == 'initial' || $this->firsttime ) {
00370                         $this->showIntro();
00371                 }
00372 
00373                 # Attempt submission here.  This will check for edit conflicts,
00374                 # and redundantly check for locked database, blocked IPs, etc.
00375                 # that edit() already checked just in case someone tries to sneak
00376                 # in the back door with a hand-edited submission URL.
00377 
00378                 if ( 'save' == $this->formtype ) {
00379                         if ( !$this->attemptSave() ) {
00380                                 wfProfileOut( __METHOD__ . "-business-end" );
00381                                 wfProfileOut( __METHOD__ );
00382                                 return;
00383                         }
00384                 }
00385 
00386                 # First time through: get contents, set time for conflict
00387                 # checking, etc.
00388                 if ( 'initial' == $this->formtype || $this->firsttime ) {
00389                         if ( $this->initialiseForm() === false ) {
00390                                 $this->noSuchSectionPage();
00391                                 wfProfileOut( __METHOD__ . "-business-end" );
00392                                 wfProfileOut( __METHOD__ );
00393                                 return;
00394                         }
00395                         if ( !$this->mTitle->getArticleID() )
00396                                 wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
00397                         else
00398                                 wfRunHooks( 'EditFormInitialText', array( $this ) );
00399                 }
00400 
00401                 $this->showEditForm();
00402                 wfProfileOut( __METHOD__ . "-business-end" );
00403                 wfProfileOut( __METHOD__ );
00404         }
00405 
00409         protected function getEditPermissionErrors() {
00410                 global $wgUser;
00411                 $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
00412                 # Can this title be created?
00413                 if ( !$this->mTitle->exists() ) {
00414                         $permErrors = array_merge( $permErrors,
00415                                 wfArrayDiff2( $this->mTitle->getUserPermissionsErrors( 'create', $wgUser ), $permErrors ) );
00416                 }
00417                 # Ignore some permissions errors when a user is just previewing/viewing diffs
00418                 $remove = array();
00419                 foreach ( $permErrors as $error ) {
00420                         if ( ( $this->preview || $this->diff ) &&
00421                                 ( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' ) )
00422                         {
00423                                 $remove[] = $error;
00424                         }
00425                 }
00426                 $permErrors = wfArrayDiff2( $permErrors, $remove );
00427                 return $permErrors;
00428         }
00429 
00442         protected function displayPermissionsError( array $permErrors ) {
00443                 global $wgRequest, $wgOut;
00444 
00445                 if ( $wgRequest->getBool( 'redlink' ) ) {
00446                         // The edit page was reached via a red link.
00447                         // Redirect to the article page and let them click the edit tab if
00448                         // they really want a permission error.
00449                         $wgOut->redirect( $this->mTitle->getFullUrl() );
00450                         return;
00451                 }
00452 
00453                 $content = $this->getContent();
00454 
00455                 # Use the normal message if there's nothing to display
00456                 if ( $this->firsttime && $content === '' ) {
00457                         $action = $this->mTitle->exists() ? 'edit' :
00458                                 ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
00459                         throw new PermissionsError( $action, $permErrors );
00460                 }
00461 
00462                 $wgOut->setPageTitle( wfMessage( 'viewsource-title', $this->getContextTitle()->getPrefixedText() ) );
00463                 $wgOut->addBacklinkSubtitle( $this->getContextTitle() );
00464                 $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) );
00465                 $wgOut->addHTML( "<hr />\n" );
00466 
00467                 # If the user made changes, preserve them when showing the markup
00468                 # (This happens when a user is blocked during edit, for instance)
00469                 if ( !$this->firsttime ) {
00470                         $content = $this->textbox1;
00471                         $wgOut->addWikiMsg( 'viewyourtext' );
00472                 } else {
00473                         $wgOut->addWikiMsg( 'viewsourcetext' );
00474                 }
00475 
00476                 $this->showTextbox( $content, 'wpTextbox1', array( 'readonly' ) );
00477 
00478                 $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
00479                         Linker::formatTemplates( $this->getTemplates() ) ) );
00480 
00481                 if ( $this->mTitle->exists() ) {
00482                         $wgOut->returnToMain( null, $this->mTitle );
00483                 }
00484         }
00485 
00492         function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
00493                 wfDeprecated( __METHOD__, '1.19' );
00494 
00495                 global $wgRequest, $wgOut;
00496                 if ( $wgRequest->getBool( 'redlink' ) ) {
00497                         // The edit page was reached via a red link.
00498                         // Redirect to the article page and let them click the edit tab if
00499                         // they really want a permission error.
00500                         $wgOut->redirect( $this->mTitle->getFullUrl() );
00501                 } else {
00502                         $wgOut->readOnlyPage( $source, $protected, $reasons, $action );
00503                 }
00504         }
00505 
00511         protected function previewOnOpen() {
00512                 global $wgRequest, $wgUser, $wgPreviewOnOpenNamespaces;
00513                 if ( $wgRequest->getVal( 'preview' ) == 'yes' ) {
00514                         // Explicit override from request
00515                         return true;
00516                 } elseif ( $wgRequest->getVal( 'preview' ) == 'no' ) {
00517                         // Explicit override from request
00518                         return false;
00519                 } elseif ( $this->section == 'new' ) {
00520                         // Nothing *to* preview for new sections
00521                         return false;
00522                 } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) {
00523                         // Standard preference behaviour
00524                         return true;
00525                 } elseif ( !$this->mTitle->exists() &&
00526                   isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] ) &&
00527                   $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
00528                 {
00529                         // Categories are special
00530                         return true;
00531                 } else {
00532                         return false;
00533                 }
00534         }
00535 
00542         protected function isWrongCaseCssJsPage() {
00543                 if ( $this->mTitle->isCssJsSubpage() ) {
00544                         $name = $this->mTitle->getSkinFromCssJsSubpage();
00545                         $skins = array_merge(
00546                                 array_keys( Skin::getSkinNames() ),
00547                                 array( 'common' )
00548                         );
00549                         return !in_array( $name, $skins )
00550                                 && in_array( strtolower( $name ), $skins );
00551                 } else {
00552                         return false;
00553                 }
00554         }
00555 
00562         protected function isSectionEditSupported() {
00563                 return true;
00564         }
00565 
00570         function importFormData( &$request ) {
00571                 global $wgLang, $wgUser;
00572 
00573                 wfProfileIn( __METHOD__ );
00574 
00575                 # Section edit can come from either the form or a link
00576                 $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
00577 
00578                 if ( $request->wasPosted() ) {
00579                         # These fields need to be checked for encoding.
00580                         # Also remove trailing whitespace, but don't remove _initial_
00581                         # whitespace from the text boxes. This may be significant formatting.
00582                         $this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' );
00583                         if ( !$request->getCheck( 'wpTextbox2' ) ) {
00584                                 // Skip this if wpTextbox2 has input, it indicates that we came
00585                                 // from a conflict page with raw page text, not a custom form
00586                                 // modified by subclasses
00587                                 wfProfileIn( get_class( $this ) . "::importContentFormData" );
00588                                 $textbox1 = $this->importContentFormData( $request );
00589                                 if ( isset( $textbox1 ) )
00590                                         $this->textbox1 = $textbox1;
00591                                 wfProfileOut( get_class( $this ) . "::importContentFormData" );
00592                         }
00593 
00594                         # Truncate for whole multibyte characters
00595                         $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 255 );
00596 
00597                         # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
00598                         # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
00599                         # section titles.
00600                         $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
00601 
00602                         # Treat sectiontitle the same way as summary.
00603                         # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
00604                         # currently doing double duty as both edit summary and section title. Right now this
00605                         # is just to allow API edits to work around this limitation, but this should be
00606                         # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
00607                         $this->sectiontitle = $wgLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
00608                         $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
00609 
00610                         $this->edittime = $request->getVal( 'wpEdittime' );
00611                         $this->starttime = $request->getVal( 'wpStarttime' );
00612 
00613                         $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
00614 
00615                         if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
00616                                 // wpTextbox1 field is missing, possibly due to being "too big"
00617                                 // according to some filter rules such as Suhosin's setting for
00618                                 // suhosin.request.max_value_length (d'oh)
00619                                 $this->incompleteForm = true;
00620                         } else {
00621                                 // edittime should be one of our last fields; if it's missing,
00622                                 // the submission probably broke somewhere in the middle.
00623                                 $this->incompleteForm = is_null( $this->edittime );
00624                         }
00625                         if ( $this->incompleteForm ) {
00626                                 # If the form is incomplete, force to preview.
00627                                 wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
00628                                 wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
00629                                 $this->preview = true;
00630                         } else {
00631                                 /* Fallback for live preview */
00632                                 $this->preview = $request->getCheck( 'wpPreview' ) || $request->getCheck( 'wpLivePreview' );
00633                                 $this->diff = $request->getCheck( 'wpDiff' );
00634 
00635                                 // Remember whether a save was requested, so we can indicate
00636                                 // if we forced preview due to session failure.
00637                                 $this->mTriedSave = !$this->preview;
00638 
00639                                 if ( $this->tokenOk( $request ) ) {
00640                                         # Some browsers will not report any submit button
00641                                         # if the user hits enter in the comment box.
00642                                         # The unmarked state will be assumed to be a save,
00643                                         # if the form seems otherwise complete.
00644                                         wfDebug( __METHOD__ . ": Passed token check.\n" );
00645                                 } elseif ( $this->diff ) {
00646                                         # Failed token check, but only requested "Show Changes".
00647                                         wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
00648                                 } else {
00649                                         # Page might be a hack attempt posted from
00650                                         # an external site. Preview instead of saving.
00651                                         wfDebug( __METHOD__ . ": Failed token check; forcing preview\n" );
00652                                         $this->preview = true;
00653                                 }
00654                         }
00655                         $this->save = !$this->preview && !$this->diff;
00656                         if ( !preg_match( '/^\d{14}$/', $this->edittime ) ) {
00657                                 $this->edittime = null;
00658                         }
00659 
00660                         if ( !preg_match( '/^\d{14}$/', $this->starttime ) ) {
00661                                 $this->starttime = null;
00662                         }
00663 
00664                         $this->recreate  = $request->getCheck( 'wpRecreate' );
00665 
00666                         $this->minoredit = $request->getCheck( 'wpMinoredit' );
00667                         $this->watchthis = $request->getCheck( 'wpWatchthis' );
00668 
00669                         # Don't force edit summaries when a user is editing their own user or talk page
00670                         if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK ) &&
00671                                 $this->mTitle->getText() == $wgUser->getName() )
00672                         {
00673                                 $this->allowBlankSummary = true;
00674                         } else {
00675                                 $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ) || !$wgUser->getOption( 'forceeditsummary' );
00676                         }
00677 
00678                         $this->autoSumm = $request->getText( 'wpAutoSummary' );
00679                 } else {
00680                         # Not a posted form? Start with nothing.
00681                         wfDebug( __METHOD__ . ": Not a posted form.\n" );
00682                         $this->textbox1     = '';
00683                         $this->summary      = '';
00684                         $this->sectiontitle = '';
00685                         $this->edittime     = '';
00686                         $this->starttime    = wfTimestampNow();
00687                         $this->edit         = false;
00688                         $this->preview      = false;
00689                         $this->save         = false;
00690                         $this->diff         = false;
00691                         $this->minoredit    = false;
00692                         $this->watchthis    = $request->getBool( 'watchthis', false ); // Watch may be overriden by request parameters
00693                         $this->recreate     = false;
00694 
00695                         // When creating a new section, we can preload a section title by passing it as the
00696                         // preloadtitle parameter in the URL (Bug 13100)
00697                         if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
00698                                 $this->sectiontitle = $request->getVal( 'preloadtitle' );
00699                                 // Once wpSummary isn't being use for setting section titles, we should delete this.
00700                                 $this->summary = $request->getVal( 'preloadtitle' );
00701                         }
00702                         elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
00703                                 $this->summary = $request->getText( 'summary' );
00704                                 if ( $this->summary !== '' ) {
00705                                         $this->hasPresetSummary = true;
00706                                 }
00707                         }
00708 
00709                         if ( $request->getVal( 'minor' ) ) {
00710                                 $this->minoredit = true;
00711                         }
00712                 }
00713 
00714                 $this->bot = $request->getBool( 'bot', true );
00715                 $this->nosummary = $request->getBool( 'nosummary' );
00716 
00717                 $this->oldid = $request->getInt( 'oldid' );
00718 
00719                 $this->live = $request->getCheck( 'live' );
00720                 $this->editintro = $request->getText( 'editintro',
00721                         // Custom edit intro for new sections
00722                         $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
00723 
00724                 // Allow extensions to modify form data
00725                 wfRunHooks( 'EditPage::importFormData', array( $this, $request ) );
00726 
00727                 wfProfileOut( __METHOD__ );
00728         }
00729 
00738         protected function importContentFormData( &$request ) {
00739                 return; // Don't do anything, EditPage already extracted wpTextbox1
00740         }
00741 
00747         function initialiseForm() {
00748                 global $wgUser;
00749                 $this->edittime = $this->mArticle->getTimestamp();
00750                 $this->textbox1 = $this->getContent( false );
00751                 // activate checkboxes if user wants them to be always active
00752                 # Sort out the "watch" checkbox
00753                 if ( $wgUser->getOption( 'watchdefault' ) ) {
00754                         # Watch all edits
00755                         $this->watchthis = true;
00756                 } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
00757                         # Watch creations
00758                         $this->watchthis = true;
00759                 } elseif ( $wgUser->isWatched( $this->mTitle ) ) {
00760                         # Already watched
00761                         $this->watchthis = true;
00762                 }
00763                 if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
00764                         $this->minoredit = true;
00765                 }
00766                 if ( $this->textbox1 === false ) {
00767                         return false;
00768                 }
00769                 wfProxyCheck();
00770                 return true;
00771         }
00772 
00780         function getContent( $def_text = '' ) {
00781                 global $wgOut, $wgRequest, $wgParser;
00782 
00783                 wfProfileIn( __METHOD__ );
00784 
00785                 $text = false;
00786 
00787                 // For message page not locally set, use the i18n message.
00788                 // For other non-existent articles, use preload text if any.
00789                 if ( !$this->mTitle->exists() || $this->section == 'new' ) {
00790                         if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
00791                                 # If this is a system message, get the default text.
00792                                 $text = $this->mTitle->getDefaultMessageText();
00793                         }
00794                         if ( $text === false ) {
00795                                 # If requested, preload some text.
00796                                 $preload = $wgRequest->getVal( 'preload',
00797                                         // Custom preload text for new sections
00798                                         $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
00799                                 $text = $this->getPreloadedText( $preload );
00800                         }
00801                 // For existing pages, get text based on "undo" or section parameters.
00802                 } else {
00803                         if ( $this->section != '' ) {
00804                                 // Get section edit text (returns $def_text for invalid sections)
00805                                 $text = $wgParser->getSection( $this->getOriginalContent(), $this->section, $def_text );
00806                         } else {
00807                                 $undoafter = $wgRequest->getInt( 'undoafter' );
00808                                 $undo = $wgRequest->getInt( 'undo' );
00809 
00810                                 if ( $undo > 0 && $undoafter > 0 ) {
00811                                         if ( $undo < $undoafter ) {
00812                                                 # If they got undoafter and undo round the wrong way, switch them
00813                                                 list( $undo, $undoafter ) = array( $undoafter, $undo );
00814                                         }
00815 
00816                                         $undorev = Revision::newFromId( $undo );
00817                                         $oldrev = Revision::newFromId( $undoafter );
00818 
00819                                         # Sanity check, make sure it's the right page,
00820                                         # the revisions exist and they were not deleted.
00821                                         # Otherwise, $text will be left as-is.
00822                                         if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
00823                                                 $undorev->getPage() == $oldrev->getPage() &&
00824                                                 $undorev->getPage() == $this->mTitle->getArticleID() &&
00825                                                 !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
00826                                                 !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
00827 
00828                                                 $text = $this->mArticle->getUndoText( $undorev, $oldrev );
00829                                                 if ( $text === false ) {
00830                                                         # Warn the user that something went wrong
00831                                                         $undoMsg = 'failure';
00832                                                 } else {
00833                                                         # Inform the user of our success and set an automatic edit summary
00834                                                         $undoMsg = 'success';
00835 
00836                                                         # If we just undid one rev, use an autosummary
00837                                                         $firstrev = $oldrev->getNext();
00838                                                         if ( $firstrev && $firstrev->getId() == $undo ) {
00839                                                                 $undoSummary = wfMessage( 'undo-summary', $undo, $undorev->getUserText() )->inContentLanguage()->text();
00840                                                                 if ( $this->summary === '' ) {
00841                                                                         $this->summary = $undoSummary;
00842                                                                 } else {
00843                                                                         $this->summary = $undoSummary . wfMessage( 'colon-separator' )
00844                                                                                 ->inContentLanguage()->text() . $this->summary;
00845                                                                 }
00846                                                                 $this->undidRev = $undo;
00847                                                         }
00848                                                         $this->formtype = 'diff';
00849                                                 }
00850                                         } else {
00851                                                 // Failed basic sanity checks.
00852                                                 // Older revisions may have been removed since the link
00853                                                 // was created, or we may simply have got bogus input.
00854                                                 $undoMsg = 'norev';
00855                                         }
00856 
00857                                         $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
00858                                         $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
00859                                                 wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
00860                                 }
00861 
00862                                 if ( $text === false ) {
00863                                         $text = $this->getOriginalContent();
00864                                 }
00865                         }
00866                 }
00867 
00868                 wfProfileOut( __METHOD__ );
00869                 return $text;
00870         }
00871 
00886         private function getOriginalContent() {
00887                 if ( $this->section == 'new' ) {
00888                         return $this->getCurrentText();
00889                 }
00890                 $revision = $this->mArticle->getRevisionFetched();
00891                 if ( $revision === null ) {
00892                         return '';
00893                 }
00894                 return $this->mArticle->getContent();
00895         }
00896 
00905         private function getCurrentText() {
00906                 $text = $this->mArticle->getRawText();
00907                 if ( $text === false ) {
00908                         return '';
00909                 } else {
00910                         return $text;
00911                 }
00912         }
00913 
00919         public function setPreloadedText( $text ) {
00920                 $this->mPreloadText = $text;
00921         }
00922 
00930         protected function getPreloadedText( $preload ) {
00931                 global $wgUser, $wgParser;
00932 
00933                 if ( !empty( $this->mPreloadText ) ) {
00934                         return $this->mPreloadText;
00935                 }
00936 
00937                 if ( $preload === '' ) {
00938                         return '';
00939                 }
00940 
00941                 $title = Title::newFromText( $preload );
00942                 # Check for existence to avoid getting MediaWiki:Noarticletext
00943                 if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
00944                         return '';
00945                 }
00946 
00947                 $page = WikiPage::factory( $title );
00948                 if ( $page->isRedirect() ) {
00949                         $title = $page->getRedirectTarget();
00950                         # Same as before
00951                         if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
00952                                 return '';
00953                         }
00954                         $page = WikiPage::factory( $title );
00955                 }
00956 
00957                 $parserOptions = ParserOptions::newFromUser( $wgUser );
00958                 return $wgParser->getPreloadText( $page->getRawText(), $title, $parserOptions );
00959         }
00960 
00968         function tokenOk( &$request ) {
00969                 global $wgUser;
00970                 $token = $request->getVal( 'wpEditToken' );
00971                 $this->mTokenOk = $wgUser->matchEditToken( $token );
00972                 $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
00973                 return $this->mTokenOk;
00974         }
00975 
00980         function attemptSave() {
00981                 global $wgUser, $wgOut;
00982 
00983                 $resultDetails = false;
00984                 # Allow bots to exempt some edits from bot flagging
00985                 $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
00986                 $status = $this->internalAttemptSave( $resultDetails, $bot );
00987                 // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
00988                 if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
00989                         $this->didSave = true;
00990                 }
00991 
00992                 switch ( $status->value ) {
00993                         case self::AS_HOOK_ERROR_EXPECTED:
00994                         case self::AS_CONTENT_TOO_BIG:
00995                         case self::AS_ARTICLE_WAS_DELETED:
00996                         case self::AS_CONFLICT_DETECTED:
00997                         case self::AS_SUMMARY_NEEDED:
00998                         case self::AS_TEXTBOX_EMPTY:
00999                         case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
01000                         case self::AS_END:
01001                                 return true;
01002 
01003                         case self::AS_HOOK_ERROR:
01004                                 return false;
01005 
01006                         case self::AS_SUCCESS_NEW_ARTICLE:
01007                                 $query = $resultDetails['redirect'] ? 'redirect=no' : '';
01008                                 $anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
01009                                 $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
01010                                 return false;
01011 
01012                         case self::AS_SUCCESS_UPDATE:
01013                                 $extraQuery = '';
01014                                 $sectionanchor = $resultDetails['sectionanchor'];
01015 
01016                                 // Give extensions a chance to modify URL query on update
01017                                 wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this->mArticle, &$sectionanchor, &$extraQuery ) );
01018 
01019                                 if ( $resultDetails['redirect'] ) {
01020                                         if ( $extraQuery == '' ) {
01021                                                 $extraQuery = 'redirect=no';
01022                                         } else {
01023                                                 $extraQuery = 'redirect=no&' . $extraQuery;
01024                                         }
01025                                 }
01026                                 $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
01027                                 return false;
01028 
01029                         case self::AS_BLANK_ARTICLE:
01030                                 $wgOut->redirect( $this->getContextTitle()->getFullURL() );
01031                                 return false;
01032 
01033                         case self::AS_SPAM_ERROR:
01034                                 $this->spamPageWithContent( $resultDetails['spam'] );
01035                                 return false;
01036 
01037                         case self::AS_BLOCKED_PAGE_FOR_USER:
01038                                 throw new UserBlockedError( $wgUser->getBlock() );
01039 
01040                         case self::AS_IMAGE_REDIRECT_ANON:
01041                         case self::AS_IMAGE_REDIRECT_LOGGED:
01042                                 throw new PermissionsError( 'upload' );
01043 
01044                         case self::AS_READ_ONLY_PAGE_ANON:
01045                         case self::AS_READ_ONLY_PAGE_LOGGED:
01046                                 throw new PermissionsError( 'edit' );
01047 
01048                         case self::AS_READ_ONLY_PAGE:
01049                                 throw new ReadOnlyError;
01050 
01051                         case self::AS_RATE_LIMITED:
01052                                 throw new ThrottledError();
01053 
01054                         case self::AS_NO_CREATE_PERMISSION:
01055                                 $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
01056                                 throw new PermissionsError( $permission );
01057 
01058                         default:
01059                                 // We don't recognize $status->value. The only way that can happen
01060                                 // is if an extension hook aborted from inside ArticleSave.
01061                                 // Render the status object into $this->hookError
01062                                 // FIXME this sucks, we should just use the Status object throughout
01063                                 $this->hookError = '<div class="error">' . $status->getWikitext() .
01064                                         '</div>';
01065                                 return true;
01066                 }
01067         }
01068 
01081         function internalAttemptSave( &$result, $bot = false ) {
01082                 global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize;
01083 
01084                 $status = Status::newGood();
01085 
01086                 wfProfileIn( __METHOD__  );
01087                 wfProfileIn( __METHOD__ . '-checks' );
01088 
01089                 if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
01090                         wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
01091                         $status->fatal( 'hookaborted' );
01092                         $status->value = self::AS_HOOK_ERROR;
01093                         wfProfileOut( __METHOD__ . '-checks' );
01094                         wfProfileOut( __METHOD__  );
01095                         return $status;
01096                 }
01097 
01098                 # Check image redirect
01099                 if ( $this->mTitle->getNamespace() == NS_FILE &&
01100                         Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
01101                         !$wgUser->isAllowed( 'upload' ) ) {
01102                                 $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
01103                                 $status->setResult( false, $code );
01104 
01105                                 wfProfileOut( __METHOD__ . '-checks' );
01106                                 wfProfileOut( __METHOD__  );
01107 
01108                                 return $status;
01109                 }
01110 
01111                 # Check for spam
01112                 $match = self::matchSummarySpamRegex( $this->summary );
01113                 if ( $match === false ) {
01114                         $match = self::matchSpamRegex( $this->textbox1 );
01115                 }
01116                 if ( $match !== false ) {
01117                         $result['spam'] = $match;
01118                         $ip = $wgRequest->getIP();
01119                         $pdbk = $this->mTitle->getPrefixedDBkey();
01120                         $match = str_replace( "\n", '', $match );
01121                         wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
01122                         $status->fatal( 'spamprotectionmatch', $match );
01123                         $status->value = self::AS_SPAM_ERROR;
01124                         wfProfileOut( __METHOD__ . '-checks' );
01125                         wfProfileOut( __METHOD__ );
01126                         return $status;
01127                 }
01128                 if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) {
01129                         # Error messages etc. could be handled within the hook...
01130                         $status->fatal( 'hookaborted' );
01131                         $status->value = self::AS_HOOK_ERROR;
01132                         wfProfileOut( __METHOD__ . '-checks' );
01133                         wfProfileOut( __METHOD__ );
01134                         return $status;
01135                 } elseif ( $this->hookError != '' ) {
01136                         # ...or the hook could be expecting us to produce an error
01137                         $status->fatal( 'hookaborted' );
01138                         $status->value = self::AS_HOOK_ERROR_EXPECTED;
01139                         wfProfileOut( __METHOD__ . '-checks' );
01140                         wfProfileOut( __METHOD__ );
01141                         return $status;
01142                 }
01143 
01144                 if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) {
01145                         // Auto-block user's IP if the account was "hard" blocked
01146                         $wgUser->spreadAnyEditBlock();
01147                         # Check block state against master, thus 'false'.
01148                         $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
01149                         wfProfileOut( __METHOD__ . '-checks' );
01150                         wfProfileOut( __METHOD__ );
01151                         return $status;
01152                 }
01153 
01154                 $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
01155                 if ( $this->kblength > $wgMaxArticleSize ) {
01156                         // Error will be displayed by showEditForm()
01157                         $this->tooBig = true;
01158                         $status->setResult( false, self::AS_CONTENT_TOO_BIG );
01159                         wfProfileOut( __METHOD__ . '-checks' );
01160                         wfProfileOut( __METHOD__ );
01161                         return $status;
01162                 }
01163 
01164                 if ( !$wgUser->isAllowed( 'edit' ) ) {
01165                         if ( $wgUser->isAnon() ) {
01166                                 $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
01167                                 wfProfileOut( __METHOD__ . '-checks' );
01168                                 wfProfileOut( __METHOD__ );
01169                                 return $status;
01170                         } else {
01171                                 $status->fatal( 'readonlytext' );
01172                                 $status->value = self::AS_READ_ONLY_PAGE_LOGGED;
01173                                 wfProfileOut( __METHOD__ . '-checks' );
01174                                 wfProfileOut( __METHOD__ );
01175                                 return $status;
01176                         }
01177                 }
01178 
01179                 if ( wfReadOnly() ) {
01180                         $status->fatal( 'readonlytext' );
01181                         $status->value = self::AS_READ_ONLY_PAGE;
01182                         wfProfileOut( __METHOD__ . '-checks' );
01183                         wfProfileOut( __METHOD__ );
01184                         return $status;
01185                 }
01186                 if ( $wgUser->pingLimiter() ) {
01187                         $status->fatal( 'actionthrottledtext' );
01188                         $status->value = self::AS_RATE_LIMITED;
01189                         wfProfileOut( __METHOD__ . '-checks' );
01190                         wfProfileOut( __METHOD__ );
01191                         return $status;
01192                 }
01193 
01194                 # If the article has been deleted while editing, don't save it without
01195                 # confirmation
01196                 if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
01197                         $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
01198                         wfProfileOut( __METHOD__ . '-checks' );
01199                         wfProfileOut( __METHOD__ );
01200                         return $status;
01201                 }
01202 
01203                 wfProfileOut( __METHOD__ . '-checks' );
01204 
01205                 # Load the page data from the master. If anything changes in the meantime,
01206                 # we detect it by using page_latest like a token in a 1 try compare-and-swap.
01207                 $this->mArticle->loadPageData( 'fromdbmaster' );
01208                 $new = !$this->mArticle->exists();
01209 
01210                 if ( $new ) {
01211                         // Late check for create permission, just in case *PARANOIA*
01212                         if ( !$this->mTitle->userCan( 'create' ) ) {
01213                                 $status->fatal( 'nocreatetext' );
01214                                 $status->value = self::AS_NO_CREATE_PERMISSION;
01215                                 wfDebug( __METHOD__ . ": no create permission\n" );
01216                                 wfProfileOut( __METHOD__ );
01217                                 return $status;
01218                         }
01219 
01220                         # Don't save a new article if it's blank.
01221                         if ( $this->textbox1 == '' ) {
01222                                 $status->setResult( false, self::AS_BLANK_ARTICLE );
01223                                 wfProfileOut( __METHOD__ );
01224                                 return $status;
01225                         }
01226 
01227                         // Run post-section-merge edit filter
01228                         if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) {
01229                                 # Error messages etc. could be handled within the hook...
01230                                 $status->fatal( 'hookaborted' );
01231                                 $status->value = self::AS_HOOK_ERROR;
01232                                 wfProfileOut( __METHOD__ );
01233                                 return $status;
01234                         } elseif ( $this->hookError != '' ) {
01235                                 # ...or the hook could be expecting us to produce an error
01236                                 $status->fatal( 'hookaborted' );
01237                                 $status->value = self::AS_HOOK_ERROR_EXPECTED;
01238                                 wfProfileOut( __METHOD__ );
01239                                 return $status;
01240                         }
01241 
01242                         $text = $this->textbox1;
01243                         $result['sectionanchor'] = '';
01244                         if ( $this->section == 'new' ) {
01245                                 if ( $this->sectiontitle !== '' ) {
01246                                         // Insert the section title above the content.
01247                                         $text = wfMessage( 'newsectionheaderdefaultlevel', $this->sectiontitle )
01248                                                 ->inContentLanguage()->text() . "\n\n" . $text;
01249 
01250                                         // Jump to the new section
01251                                         $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
01252 
01253                                         // If no edit summary was specified, create one automatically from the section
01254                                         // title and have it link to the new section. Otherwise, respect the summary as
01255                                         // passed.
01256                                         if ( $this->summary === '' ) {
01257                                                 $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
01258                                                 $this->summary = wfMessage( 'newsectionsummary' )
01259                                                         ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
01260                                         }
01261                                 } elseif ( $this->summary !== '' ) {
01262                                         // Insert the section title above the content.
01263                                         $text = wfMessage( 'newsectionheaderdefaultlevel', $this->summary )
01264                                                 ->inContentLanguage()->text() . "\n\n" . $text;
01265 
01266                                         // Jump to the new section
01267                                         $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
01268 
01269                                         // Create a link to the new section from the edit summary.
01270                                         $cleanSummary = $wgParser->stripSectionName( $this->summary );
01271                                         $this->summary = wfMessage( 'newsectionsummary' )
01272                                                 ->rawParams( $cleanSummary )->inContentLanguage()->text();
01273                                 }
01274                         }
01275 
01276                         $status->value = self::AS_SUCCESS_NEW_ARTICLE;
01277 
01278                 } else {
01279 
01280                         # Article exists. Check for edit conflict.
01281                         $timestamp = $this->mArticle->getTimestamp();
01282                         wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
01283 
01284                         if ( $timestamp != $this->edittime ) {
01285                                 $this->isConflict = true;
01286                                 if ( $this->section == 'new' ) {
01287                                         if ( $this->mArticle->getUserText() == $wgUser->getName() &&
01288                                                 $this->mArticle->getComment() == $this->summary ) {
01289                                                 // Probably a duplicate submission of a new comment.
01290                                                 // This can happen when squid resends a request after
01291                                                 // a timeout but the first one actually went through.
01292                                                 wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
01293                                         } else {
01294                                                 // New comment; suppress conflict.
01295                                                 $this->isConflict = false;
01296                                                 wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
01297                                         }
01298                                 } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER,  $this->mTitle->getArticleID(), $wgUser->getId(), $this->edittime ) ) {
01299                                         # Suppress edit conflict with self, except for section edits where merging is required.
01300                                         wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
01301                                         $this->isConflict = false;
01302                                 }
01303                         }
01304 
01305                         // If sectiontitle is set, use it, otherwise use the summary as the section title (for
01306                         // backwards compatibility with old forms/bots).
01307                         if ( $this->sectiontitle !== '' ) {
01308                                 $sectionTitle = $this->sectiontitle;
01309                         } else {
01310                                 $sectionTitle = $this->summary;
01311                         }
01312 
01313                         if ( $this->isConflict ) {
01314                                 wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
01315                                 $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
01316                         } else {
01317                                 wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
01318                                 $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
01319                         }
01320                         if ( is_null( $text ) ) {
01321                                 wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
01322                                 $this->isConflict = true;
01323                                 $text = $this->textbox1; // do not try to merge here!
01324                         } elseif ( $this->isConflict ) {
01325                                 # Attempt merge
01326                                 if ( $this->mergeChangesInto( $text ) ) {
01327                                         // Successful merge! Maybe we should tell the user the good news?
01328                                         $this->isConflict = false;
01329                                         wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
01330                                 } else {
01331                                         $this->section = '';
01332                                         $this->textbox1 = $text;
01333                                         wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
01334                                 }
01335                         }
01336 
01337                         if ( $this->isConflict ) {
01338                                 $status->setResult( false, self::AS_CONFLICT_DETECTED );
01339                                 wfProfileOut( __METHOD__ );
01340                                 return $status;
01341                         }
01342 
01343                         // Run post-section-merge edit filter
01344                         if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
01345                                 # Error messages etc. could be handled within the hook...
01346                                 $status->fatal( 'hookaborted' );
01347                                 $status->value = self::AS_HOOK_ERROR;
01348                                 wfProfileOut( __METHOD__ );
01349                                 return $status;
01350                         } elseif ( $this->hookError != '' ) {
01351                                 # ...or the hook could be expecting us to produce an error
01352                                 $status->fatal( 'hookaborted' );
01353                                 $status->value = self::AS_HOOK_ERROR_EXPECTED;
01354                                 wfProfileOut( __METHOD__ );
01355                                 return $status;
01356                         }
01357 
01358                         # Handle the user preference to force summaries here, but not for null edits
01359                         if ( $this->section != 'new' && !$this->allowBlankSummary
01360                                 && $this->getOriginalContent() != $text
01361                                 && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
01362                         {
01363                                 if ( md5( $this->summary ) == $this->autoSumm ) {
01364                                         $this->missingSummary = true;
01365                                         $status->fatal( 'missingsummary' );
01366                                         $status->value = self::AS_SUMMARY_NEEDED;
01367                                         wfProfileOut( __METHOD__ );
01368                                         return $status;
01369                                 }
01370                         }
01371 
01372                         # And a similar thing for new sections
01373                         if ( $this->section == 'new' && !$this->allowBlankSummary ) {
01374                                 if ( trim( $this->summary ) == '' ) {
01375                                         $this->missingSummary = true;
01376                                         $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
01377                                         $status->value = self::AS_SUMMARY_NEEDED;
01378                                         wfProfileOut( __METHOD__ );
01379                                         return $status;
01380                                 }
01381                         }
01382 
01383                         # All's well
01384                         wfProfileIn( __METHOD__ . '-sectionanchor' );
01385                         $sectionanchor = '';
01386                         if ( $this->section == 'new' ) {
01387                                 if ( $this->textbox1 == '' ) {
01388                                         $this->missingComment = true;
01389                                         $status->fatal( 'missingcommenttext' );
01390                                         $status->value = self::AS_TEXTBOX_EMPTY;
01391                                         wfProfileOut( __METHOD__ . '-sectionanchor' );
01392                                         wfProfileOut( __METHOD__ );
01393                                         return $status;
01394                                 }
01395                                 if ( $this->sectiontitle !== '' ) {
01396                                         $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
01397                                         // If no edit summary was specified, create one automatically from the section
01398                                         // title and have it link to the new section. Otherwise, respect the summary as
01399                                         // passed.
01400                                         if ( $this->summary === '' ) {
01401                                                 $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
01402                                                 $this->summary = wfMessage( 'newsectionsummary' )
01403                                                         ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
01404                                         }
01405                                 } elseif ( $this->summary !== '' ) {
01406                                         $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
01407                                         # This is a new section, so create a link to the new section
01408                                         # in the revision summary.
01409                                         $cleanSummary = $wgParser->stripSectionName( $this->summary );
01410                                         $this->summary = wfMessage( 'newsectionsummary' )
01411                                                 ->rawParams( $cleanSummary )->inContentLanguage()->text();
01412                                 }
01413                         } elseif ( $this->section != '' ) {
01414                                 # Try to get a section anchor from the section source, redirect to edited section if header found
01415                                 # XXX: might be better to integrate this into Article::replaceSection
01416                                 # for duplicate heading checking and maybe parsing
01417                                 $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
01418                                 # we can't deal with anchors, includes, html etc in the header for now,
01419                                 # headline would need to be parsed to improve this
01420                                 if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
01421                                         $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
01422                                 }
01423                         }
01424                         $result['sectionanchor'] = $sectionanchor;
01425                         wfProfileOut( __METHOD__ . '-sectionanchor' );
01426 
01427                         // Save errors may fall down to the edit form, but we've now
01428                         // merged the section into full text. Clear the section field
01429                         // so that later submission of conflict forms won't try to
01430                         // replace that into a duplicated mess.
01431                         $this->textbox1 = $text;
01432                         $this->section = '';
01433 
01434                         $status->value = self::AS_SUCCESS_UPDATE;
01435                 }
01436 
01437                 // Check for length errors again now that the section is merged in
01438                 $this->kblength = (int)( strlen( $text ) / 1024 );
01439                 if ( $this->kblength > $wgMaxArticleSize ) {
01440                         $this->tooBig = true;
01441                         $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
01442                         wfProfileOut( __METHOD__ );
01443                         return $status;
01444                 }
01445 
01446                 $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
01447                         ( $new ? EDIT_NEW : EDIT_UPDATE ) |
01448                         ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
01449                         ( $bot ? EDIT_FORCE_BOT : 0 );
01450 
01451                 $doEditStatus = $this->mArticle->doEdit( $text, $this->summary, $flags );
01452 
01453                 if ( $doEditStatus->isOK() ) {
01454                         $result['redirect'] = Title::newFromRedirect( $text ) !== null;
01455                         $this->commitWatch();
01456                         wfProfileOut( __METHOD__ );
01457                         return $status;
01458                 } else {
01459                         // Failure from doEdit()
01460                         // Show the edit conflict page for certain recognized errors from doEdit(),
01461                         // but don't show it for errors from extension hooks
01462                         $errors = $doEditStatus->getErrorsArray();
01463                         if ( in_array( $errors[0][0], array( 'edit-gone-missing', 'edit-conflict',
01464                                 'edit-already-exists' ) ) )
01465                         {
01466                                 $this->isConflict = true;
01467                                 // Destroys data doEdit() put in $status->value but who cares
01468                                 $doEditStatus->value = self::AS_END;
01469                         }
01470                         wfProfileOut( __METHOD__ );
01471                         return $doEditStatus;
01472                 }
01473         }
01474 
01478         protected function commitWatch() {
01479                 global $wgUser;
01480                 if ( $wgUser->isLoggedIn() && $this->watchthis != $wgUser->isWatched( $this->mTitle ) ) {
01481                         $dbw = wfGetDB( DB_MASTER );
01482                         $dbw->begin( __METHOD__ );
01483                         if ( $this->watchthis ) {
01484                                 WatchAction::doWatch( $this->mTitle, $wgUser );
01485                         } else {
01486                                 WatchAction::doUnwatch( $this->mTitle, $wgUser );
01487                         }
01488                         $dbw->commit( __METHOD__ );
01489                 }
01490         }
01491 
01500         function mergeChangesInto( &$editText ) {
01501                 wfProfileIn( __METHOD__ );
01502 
01503                 $db = wfGetDB( DB_MASTER );
01504 
01505                 // This is the revision the editor started from
01506                 $baseRevision = $this->getBaseRevision();
01507                 if ( is_null( $baseRevision ) ) {
01508                         wfProfileOut( __METHOD__ );
01509                         return false;
01510                 }
01511                 $baseText = $baseRevision->getText();
01512 
01513                 // The current state, we want to merge updates into it
01514                 $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
01515                 if ( is_null( $currentRevision ) ) {
01516                         wfProfileOut( __METHOD__ );
01517                         return false;
01518                 }
01519                 $currentText = $currentRevision->getText();
01520 
01521                 $result = '';
01522                 if ( wfMerge( $baseText, $editText, $currentText, $result ) ) {
01523                         $editText = $result;
01524                         wfProfileOut( __METHOD__ );
01525                         return true;
01526                 } else {
01527                         wfProfileOut( __METHOD__ );
01528                         return false;
01529                 }
01530         }
01531 
01535         function getBaseRevision() {
01536                 if ( !$this->mBaseRevision ) {
01537                         $db = wfGetDB( DB_MASTER );
01538                         $baseRevision = Revision::loadFromTimestamp(
01539                                 $db, $this->mTitle, $this->edittime );
01540                         return $this->mBaseRevision = $baseRevision;
01541                 } else {
01542                         return $this->mBaseRevision;
01543                 }
01544         }
01545 
01553         public static function matchSpamRegex( $text ) {
01554                 global $wgSpamRegex;
01555                 // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
01556                 $regexes = (array)$wgSpamRegex;
01557                 return self::matchSpamRegexInternal( $text, $regexes );
01558         }
01559 
01567         public static function matchSummarySpamRegex( $text ) {
01568                 global $wgSummarySpamRegex;
01569                 $regexes = (array)$wgSummarySpamRegex;
01570                 return self::matchSpamRegexInternal( $text, $regexes );
01571         }
01572 
01578         protected static function matchSpamRegexInternal( $text, $regexes ) {
01579                 foreach ( $regexes as $regex ) {
01580                         $matches = array();
01581                         if ( preg_match( $regex, $text, $matches ) ) {
01582                                 return $matches[0];
01583                         }
01584                 }
01585                 return false;
01586         }
01587 
01588         function setHeaders() {
01589                 global $wgOut, $wgUser;
01590 
01591                 $wgOut->addModules( 'mediawiki.action.edit' );
01592 
01593                 if ( $wgUser->getOption( 'uselivepreview', false ) ) {
01594                         $wgOut->addModules( 'mediawiki.action.edit.preview' );
01595                 }
01596                 // Bug #19334: textarea jumps when editing articles in IE8
01597                 $wgOut->addStyle( 'common/IE80Fixes.css', 'screen', 'IE 8' );
01598 
01599                 $wgOut->setRobotPolicy( 'noindex,nofollow' );
01600 
01601                 # Enabled article-related sidebar, toplinks, etc.
01602                 $wgOut->setArticleRelated( true );
01603 
01604                 $contextTitle = $this->getContextTitle();
01605                 if ( $this->isConflict ) {
01606                         $msg = 'editconflict';
01607                 } elseif ( $contextTitle->exists() && $this->section != '' ) {
01608                         $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
01609                 } else {
01610                         $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() == NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !== false ) ?
01611                                 'editing' : 'creating';
01612                 }
01613                 # Use the title defined by DISPLAYTITLE magic word when present
01614                 $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
01615                 if ( $displayTitle === false ) {
01616                         $displayTitle = $contextTitle->getPrefixedText();
01617                 }
01618                 $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) );
01619         }
01620 
01624         protected function showIntro() {
01625                 global $wgOut, $wgUser;
01626                 if ( $this->suppressIntro ) {
01627                         return;
01628                 }
01629 
01630                 $namespace = $this->mTitle->getNamespace();
01631 
01632                 if ( $namespace == NS_MEDIAWIKI ) {
01633                         # Show a warning if editing an interface message
01634                         $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
01635                 } else if( $namespace == NS_FILE ) {
01636                         # Show a hint to shared repo
01637                         $file = wfFindFile( $this->mTitle );
01638                         if( $file && !$file->isLocal() ) {
01639                                 $descUrl = $file->getDescriptionUrl();
01640                                 # there must be a description url to show a hint to shared repo
01641                                 if( $descUrl ) {
01642                                         if( !$this->mTitle->exists() ) {
01643                                                 $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", array (
01644                                                                         'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
01645                                                 ) );
01646                                         } else {
01647                                                 $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", array(
01648                                                                         'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
01649                                                 ) );
01650                                         }
01651                                 }
01652                         }
01653                 }
01654 
01655                 # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
01656                 # Show log extract when the user is currently blocked
01657                 if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
01658                         $parts = explode( '/', $this->mTitle->getText(), 2 );
01659                         $username = $parts[0];
01660                         $user = User::newFromName( $username, false /* allow IP users*/ );
01661                         $ip = User::isIP( $username );
01662                         if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
01663                                 $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
01664                                         array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
01665                         } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
01666                                 LogEventsList::showLogExtract(
01667                                         $wgOut,
01668                                         'block',
01669                                         $user->getUserPage(),
01670                                         '',
01671                                         array(
01672                                                 'lim' => 1,
01673                                                 'showIfEmpty' => false,
01674                                                 'msgKey' => array(
01675                                                         'blocked-notice-logextract',
01676                                                         $user->getName() # Support GENDER in notice
01677                                                 )
01678                                         )
01679                                 );
01680                         }
01681                 }
01682                 # Try to add a custom edit intro, or use the standard one if this is not possible.
01683                 if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
01684                         if ( $wgUser->isLoggedIn() ) {
01685                                 $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletext\">\n$1\n</div>", 'newarticletext' );
01686                         } else {
01687                                 $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletextanon\">\n$1\n</div>", 'newarticletextanon' );
01688                         }
01689                 }
01690                 # Give a notice if the user is editing a deleted/moved page...
01691                 if ( !$this->mTitle->exists() ) {
01692                         LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle,
01693                                 '', array( 'lim' => 10,
01694                                            'conds' => array( "log_action != 'revision'" ),
01695                                            'showIfEmpty' => false,
01696                                            'msgKey' => array( 'recreate-moveddeleted-warn' ) )
01697                         );
01698                 }
01699         }
01700 
01706         protected function showCustomIntro() {
01707                 if ( $this->editintro ) {
01708                         $title = Title::newFromText( $this->editintro );
01709                         if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
01710                                 global $wgOut;
01711                                 // Added using template syntax, to take <noinclude>'s into account.
01712                                 $wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
01713                                 return true;
01714                         } else {
01715                                 return false;
01716                         }
01717                 } else {
01718                         return false;
01719                 }
01720         }
01721 
01727         function showEditForm( $formCallback = null ) {
01728                 global $wgOut, $wgUser;
01729 
01730                 wfProfileIn( __METHOD__ );
01731 
01732                 # need to parse the preview early so that we know which templates are used,
01733                 # otherwise users with "show preview after edit box" will get a blank list
01734                 # we parse this near the beginning so that setHeaders can do the title
01735                 # setting work instead of leaving it in getPreviewText
01736                 $previewOutput = '';
01737                 if ( $this->formtype == 'preview' ) {
01738                         $previewOutput = $this->getPreviewText();
01739                 }
01740 
01741                 wfRunHooks( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) );
01742 
01743                 $this->setHeaders();
01744 
01745                 if ( $this->showHeader() === false ) {
01746                         wfProfileOut( __METHOD__ );
01747                         return;
01748                 }
01749 
01750                 $wgOut->addHTML( $this->editFormPageTop );
01751 
01752                 if ( $wgUser->getOption( 'previewontop' ) ) {
01753                         $this->displayPreviewArea( $previewOutput, true );
01754                 }
01755 
01756                 $wgOut->addHTML( $this->editFormTextTop );
01757 
01758                 $showToolbar = true;
01759                 if ( $this->wasDeletedSinceLastEdit() ) {
01760                         if ( $this->formtype == 'save' ) {
01761                                 // Hide the toolbar and edit area, user can click preview to get it back
01762                                 // Add an confirmation checkbox and explanation.
01763                                 $showToolbar = false;
01764                         } else {
01765                                 $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
01766                                         'deletedwhileediting' );
01767                         }
01768                 }
01769 
01770                 $wgOut->addHTML( Html::openElement( 'form', array( 'id' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID,
01771                         'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
01772                         'enctype' => 'multipart/form-data' ) ) );
01773 
01774                 if ( is_callable( $formCallback ) ) {
01775                         call_user_func_array( $formCallback, array( &$wgOut ) );
01776                 }
01777 
01778                 wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
01779 
01780                 // Put these up at the top to ensure they aren't lost on early form submission
01781                 $this->showFormBeforeText();
01782 
01783                 if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
01784                         $username = $this->lastDelete->user_name;
01785                         $comment = $this->lastDelete->log_comment;
01786 
01787                         // It is better to not parse the comment at all than to have templates expanded in the middle
01788                         // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
01789                         $key = $comment === ''
01790                                 ? 'confirmrecreate-noreason'
01791                                 : 'confirmrecreate';
01792                         $wgOut->addHTML(
01793                                 '<div class="mw-confirm-recreate">' .
01794                                         wfMessage( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
01795                                 Xml::checkLabel( wfMessage( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
01796                                         array( 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
01797                                 ) .
01798                                 '</div>'
01799                         );
01800                 }
01801 
01802                 # When the summary is hidden, also hide them on preview/show changes
01803                 if( $this->nosummary ) {
01804                         $wgOut->addHTML( Html::hidden( 'nosummary', true ) );
01805                 }
01806 
01807                 # If a blank edit summary was previously provided, and the appropriate
01808                 # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
01809                 # user being bounced back more than once in the event that a summary
01810                 # is not required.
01811                 #####
01812                 # For a bit more sophisticated detection of blank summaries, hash the
01813                 # automatic one and pass that in the hidden field wpAutoSummary.
01814                 if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
01815                         $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
01816                 }
01817 
01818                 if ( $this->undidRev ) {
01819                         $wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
01820                 }
01821 
01822                 if ( $this->hasPresetSummary ) {
01823                         // If a summary has been preset using &summary= we dont want to prompt for
01824                         // a different summary. Only prompt for a summary if the summary is blanked.
01825                         // (Bug 17416)
01826                         $this->autoSumm = md5( '' );
01827                 }
01828 
01829                 $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
01830                 $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
01831 
01832                 $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
01833 
01834                 if ( $this->section == 'new' ) {
01835                         $this->showSummaryInput( true, $this->summary );
01836                         $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
01837                 }
01838 
01839                 $wgOut->addHTML( $this->editFormTextBeforeContent );
01840 
01841                 if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
01842                         $wgOut->addHTML( EditPage::getEditToolbar() );
01843                 }
01844 
01845                 if ( $this->isConflict ) {
01846                         // In an edit conflict bypass the overrideable content form method
01847                         // and fallback to the raw wpTextbox1 since editconflicts can't be
01848                         // resolved between page source edits and custom ui edits using the
01849                         // custom edit ui.
01850                         $this->textbox2 = $this->textbox1;
01851                         $this->textbox1 = $this->getCurrentText();
01852 
01853                         $this->showTextbox1();
01854                 } else {
01855                         $this->showContentForm();
01856                 }
01857 
01858                 $wgOut->addHTML( $this->editFormTextAfterContent );
01859 
01860                 $this->showStandardInputs();
01861 
01862                 $this->showFormAfterText();
01863 
01864                 $this->showTosSummary();
01865 
01866                 $this->showEditTools();
01867 
01868                 $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
01869 
01870                 $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
01871                         Linker::formatTemplates( $this->getTemplates(), $this->preview, $this->section != '' ) ) );
01872 
01873                 $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'hiddencats' ),
01874                         Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
01875 
01876                 if ( $this->isConflict ) {
01877                         $this->showConflict();
01878                 }
01879 
01880                 $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
01881 
01882                 if ( !$wgUser->getOption( 'previewontop' ) ) {
01883                         $this->displayPreviewArea( $previewOutput, false );
01884                 }
01885 
01886                 wfProfileOut( __METHOD__ );
01887         }
01888 
01895         public static function extractSectionTitle( $text ) {
01896                 preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
01897                 if ( !empty( $matches[2] ) ) {
01898                         global $wgParser;
01899                         return $wgParser->stripSectionName( trim( $matches[2] ) );
01900                 } else {
01901                         return false;
01902                 }
01903         }
01904 
01905         protected function showHeader() {
01906                 global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang;
01907 
01908                 if ( $this->mTitle->isTalkPage() ) {
01909                         $wgOut->addWikiMsg( 'talkpagetext' );
01910                 }
01911 
01912                 # Optional notices on a per-namespace and per-page basis
01913                 $editnotice_ns = 'editnotice-' . $this->mTitle->getNamespace();
01914                 $editnotice_ns_message = wfMessage( $editnotice_ns );
01915                 if ( $editnotice_ns_message->exists() ) {
01916                         $wgOut->addWikiText( $editnotice_ns_message->plain() );
01917                 }
01918                 if ( MWNamespace::hasSubpages( $this->mTitle->getNamespace() ) ) {
01919                         $parts = explode( '/', $this->mTitle->getDBkey() );
01920                         $editnotice_base = $editnotice_ns;
01921                         while ( count( $parts ) > 0 ) {
01922                                 $editnotice_base .= '-' . array_shift( $parts );
01923                                 $editnotice_base_msg = wfMessage( $editnotice_base );
01924                                 if ( $editnotice_base_msg->exists() ) {
01925                                         $wgOut->addWikiText( $editnotice_base_msg->plain() );
01926                                 }
01927                         }
01928                 } else {
01929                         # Even if there are no subpages in namespace, we still don't want / in MW ns.
01930                         $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->mTitle->getDBkey() );
01931                         $editnoticeMsg = wfMessage( $editnoticeText );
01932                         if ( $editnoticeMsg->exists() ) {
01933                                 $wgOut->addWikiText( $editnoticeMsg->plain() );
01934                         }
01935                 }
01936 
01937                 if ( $this->isConflict ) {
01938                         $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
01939                         $this->edittime = $this->mArticle->getTimestamp();
01940                 } else {
01941                         if ( $this->section != '' && !$this->isSectionEditSupported() ) {
01942                                 // We use $this->section to much before this and getVal('wgSection') directly in other places
01943                                 // at this point we can't reset $this->section to '' to fallback to non-section editing.
01944                                 // Someone is welcome to try refactoring though
01945                                 $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
01946                                 return false;
01947                         }
01948 
01949                         if ( $this->section != '' && $this->section != 'new' ) {
01950                                 if ( !$this->summary && !$this->preview && !$this->diff ) {
01951                                         $sectionTitle = self::extractSectionTitle( $this->textbox1 );
01952                                         if ( $sectionTitle !== false ) {
01953                                                 $this->summary = "/* $sectionTitle */ ";
01954                                         }
01955                                 }
01956                         }
01957 
01958                         if ( $this->missingComment ) {
01959                                 $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
01960                         }
01961 
01962                         if ( $this->missingSummary && $this->section != 'new' ) {
01963                                 $wgOut->wrapWikiMsg( "<div id='mw-missingsummary'>\n$1\n</div>", 'missingsummary' );
01964                         }
01965 
01966                         if ( $this->missingSummary && $this->section == 'new' ) {
01967                                 $wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1\n</div>", 'missingcommentheader' );
01968                         }
01969 
01970                         if ( $this->hookError !== '' ) {
01971                                 $wgOut->addWikiText( $this->hookError );
01972                         }
01973 
01974                         if ( !$this->checkUnicodeCompliantBrowser() ) {
01975                                 $wgOut->addWikiMsg( 'nonunicodebrowser' );
01976                         }
01977 
01978                         if ( $this->section != 'new' ) {
01979                                 $revision = $this->mArticle->getRevisionFetched();
01980                                 if ( $revision ) {
01981                                         // Let sysop know that this will make private content public if saved
01982 
01983                                         if ( !$revision->userCan( Revision::DELETED_TEXT ) ) {
01984                                                 $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
01985                                         } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
01986                                                 $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
01987                                         }
01988 
01989                                         if ( !$revision->isCurrent() ) {
01990                                                 $this->mArticle->setOldSubtitle( $revision->getId() );
01991                                                 $wgOut->addWikiMsg( 'editingold' );
01992                                         }
01993                                 } elseif ( $this->mTitle->exists() ) {
01994                                         // Something went wrong
01995 
01996                                         $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
01997                                                 array( 'missing-revision', $this->oldid ) );
01998                                 }
01999                         }
02000                 }
02001 
02002                 if ( wfReadOnly() ) {
02003                         $wgOut->wrapWikiMsg( "<div id=\"mw-read-only-warning\">\n$1\n</div>", array( 'readonlywarning', wfReadOnlyReason() ) );
02004                 } elseif ( $wgUser->isAnon() ) {
02005                         if ( $this->formtype != 'preview' ) {
02006                                 $wgOut->wrapWikiMsg( "<div id=\"mw-anon-edit-warning\">\n$1</div>", 'anoneditwarning' );
02007                         } else {
02008                                 $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>", 'anonpreviewwarning' );
02009                         }
02010                 } else {
02011                         if ( $this->isCssJsSubpage ) {
02012                                 # Check the skin exists
02013                                 if ( $this->isWrongCaseCssJsPage ) {
02014                                         $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) );
02015                                 }
02016                                 if ( $this->formtype !== 'preview' ) {
02017                                         if ( $this->isCssSubpage )
02018                                                 $wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) );
02019                                         if ( $this->isJsSubpage )
02020                                                 $wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) );
02021                                 }
02022                         }
02023                 }
02024 
02025                 if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
02026                         # Is the title semi-protected?
02027                         if ( $this->mTitle->isSemiProtected() ) {
02028                                 $noticeMsg = 'semiprotectedpagewarning';
02029                         } else {
02030                                 # Then it must be protected based on static groups (regular)
02031                                 $noticeMsg = 'protectedpagewarning';
02032                         }
02033                         LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
02034                                 array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) );
02035                 }
02036                 if ( $this->mTitle->isCascadeProtected() ) {
02037                         # Is this page under cascading protection from some source pages?
02038                         list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
02039                         $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
02040                         $cascadeSourcesCount = count( $cascadeSources );
02041                         if ( $cascadeSourcesCount > 0 ) {
02042                                 # Explain, and list the titles responsible
02043                                 foreach ( $cascadeSources as $page ) {
02044                                         $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
02045                                 }
02046                         }
02047                         $notice .= '</div>';
02048                         $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', $cascadeSourcesCount ) );
02049                 }
02050                 if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
02051                         LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
02052                                 array( 'lim' => 1,
02053                                         'showIfEmpty' => false,
02054                                         'msgKey' => array( 'titleprotectedwarning' ),
02055                                         'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ) );
02056                 }
02057 
02058                 if ( $this->kblength === false ) {
02059                         $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
02060                 }
02061 
02062                 if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
02063                         $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
02064                                 array( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) ) );
02065                 } else {
02066                         if ( !wfMessage( 'longpage-hint' )->isDisabled() ) {
02067                                 $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
02068                                         array( 'longpage-hint', $wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) )
02069                                 );
02070                         }
02071                 }
02072                 # Add header copyright warning
02073                 $this->showHeaderCopyrightWarning();
02074         }
02075 
02076 
02091         function getSummaryInput( $summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null ) {
02092                 // Note: the maxlength is overriden in JS to 255 and to make it use UTF-8 bytes, not characters.
02093                 $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : array() ) + array(
02094                         'id' => 'wpSummary',
02095                         'maxlength' => '200',
02096                         'tabindex' => '1',
02097                         'size' => 60,
02098                         'spellcheck' => 'true',
02099                 ) + Linker::tooltipAndAccesskeyAttribs( 'summary' );
02100 
02101                 $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : array() ) + array(
02102                         'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
02103                         'id' => "wpSummaryLabel"
02104                 );
02105 
02106                 $label = null;
02107                 if ( $labelText ) {
02108                         $label = Xml::tags( 'label', $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null, $labelText );
02109                         $label = Xml::tags( 'span', $spanLabelAttrs, $label );
02110                 }
02111 
02112                 $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
02113 
02114                 return array( $label, $input );
02115         }
02116 
02124         protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
02125                 global $wgOut, $wgContLang;
02126                 # Add a class if 'missingsummary' is triggered to allow styling of the summary line
02127                 $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
02128                 if ( $isSubjectPreview ) {
02129                         if ( $this->nosummary ) {
02130                                 return;
02131                         }
02132                 } else {
02133                         if ( !$this->mShowSummaryField ) {
02134                                 return;
02135                         }
02136                 }
02137                 $summary = $wgContLang->recodeForEdit( $summary );
02138                 $labelText = wfMessage( $isSubjectPreview ? 'subject' : 'summary' )->parse();
02139                 list( $label, $input ) = $this->getSummaryInput( $summary, $labelText, array( 'class' => $summaryClass ), array() );
02140                 $wgOut->addHTML( "{$label} {$input}" );
02141         }
02142 
02150         protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
02151                 if ( !$summary || ( !$this->preview && !$this->diff ) )
02152                         return "";
02153 
02154                 global $wgParser;
02155 
02156                 if ( $isSubjectPreview )
02157                         $summary = wfMessage( 'newsectionsummary', $wgParser->stripSectionName( $summary ) )
02158                                 ->inContentLanguage()->text();
02159 
02160                 $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
02161 
02162                 $summary = wfMessage( $message )->parse() . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
02163                 return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary );
02164         }
02165 
02166         protected function showFormBeforeText() {
02167                 global $wgOut;
02168                 $section = htmlspecialchars( $this->section );
02169                 $wgOut->addHTML( <<<HTML
02170 <input type='hidden' value="{$section}" name="wpSection" />
02171 <input type='hidden' value="{$this->starttime}" name="wpStarttime" />
02172 <input type='hidden' value="{$this->edittime}" name="wpEdittime" />
02173 <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
02174 
02175 HTML
02176                 );
02177                 if ( !$this->checkUnicodeCompliantBrowser() )
02178                         $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
02179         }
02180 
02181         protected function showFormAfterText() {
02182                 global $wgOut, $wgUser;
02195                 $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
02196         }
02197 
02206         protected function showContentForm() {
02207                 $this->showTextbox1();
02208         }
02209 
02218         protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
02219                 if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
02220                         $attribs = array( 'style' => 'display:none;' );
02221                 } else {
02222                         $classes = array(); // Textarea CSS
02223                         if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
02224                                 # Is the title semi-protected?
02225                                 if ( $this->mTitle->isSemiProtected() ) {
02226                                         $classes[] = 'mw-textarea-sprotected';
02227                                 } else {
02228                                         # Then it must be protected based on static groups (regular)
02229                                         $classes[] = 'mw-textarea-protected';
02230                                 }
02231                                 # Is the title cascade-protected?
02232                                 if ( $this->mTitle->isCascadeProtected() ) {
02233                                         $classes[] = 'mw-textarea-cprotected';
02234                                 }
02235                         }
02236 
02237                         $attribs = array( 'tabindex' => 1 );
02238 
02239                         if ( is_array( $customAttribs ) ) {
02240                                 $attribs += $customAttribs;
02241                         }
02242 
02243                         if ( count( $classes ) ) {
02244                                 if ( isset( $attribs['class'] ) ) {
02245                                         $classes[] = $attribs['class'];
02246                                 }
02247                                 $attribs['class'] = implode( ' ', $classes );
02248                         }
02249                 }
02250 
02251                 $this->showTextbox( $textoverride !== null ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
02252         }
02253 
02254         protected function showTextbox2() {
02255                 $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
02256         }
02257 
02258         protected function showTextbox( $content, $name, $customAttribs = array() ) {
02259                 global $wgOut, $wgUser;
02260 
02261                 $wikitext = $this->safeUnicodeOutput( $content );
02262                 if ( strval( $wikitext ) !== '' ) {
02263                         // Ensure there's a newline at the end, otherwise adding lines
02264                         // is awkward.
02265                         // But don't add a newline if the ext is empty, or Firefox in XHTML
02266                         // mode will show an extra newline. A bit annoying.
02267                         $wikitext .= "\n";
02268                 }
02269 
02270                 $attribs = $customAttribs + array(
02271                         'accesskey' => ',',
02272                         'id'   => $name,
02273                         'cols' => $wgUser->getIntOption( 'cols' ),
02274                         'rows' => $wgUser->getIntOption( 'rows' ),
02275                         'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work
02276                 );
02277 
02278                 $pageLang = $this->mTitle->getPageLanguage();
02279                 $attribs['lang'] = $pageLang->getCode();
02280                 $attribs['dir'] = $pageLang->getDir();
02281 
02282                 $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
02283         }
02284 
02285         protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
02286                 global $wgOut;
02287                 $classes = array();
02288                 if ( $isOnTop )
02289                         $classes[] = 'ontop';
02290 
02291                 $attribs = array( 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) );
02292 
02293                 if ( $this->formtype != 'preview' )
02294                         $attribs['style'] = 'display: none;';
02295 
02296                 $wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
02297 
02298                 if ( $this->formtype == 'preview' ) {
02299                         $this->showPreview( $previewOutput );
02300                 }
02301 
02302                 $wgOut->addHTML( '</div>' );
02303 
02304                 if ( $this->formtype == 'diff' ) {
02305                         $this->showDiff();
02306                 }
02307         }
02308 
02315         protected function showPreview( $text ) {
02316                 global $wgOut;
02317                 if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
02318                         $this->mArticle->openShowCategory();
02319                 }
02320                 # This hook seems slightly odd here, but makes things more
02321                 # consistent for extensions.
02322                 wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) );
02323                 $wgOut->addHTML( $text );
02324                 if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
02325                         $this->mArticle->closeShowCategory();
02326                 }
02327         }
02328 
02336         function showDiff() {
02337                 global $wgUser, $wgContLang, $wgParser, $wgOut;
02338 
02339                 $oldtitlemsg = 'currentrev';
02340                 # if message does not exist, show diff against the preloaded default
02341                 if( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
02342                         $oldtext = $this->mTitle->getDefaultMessageText();
02343                         if( $oldtext !== false ) {
02344                                 $oldtitlemsg = 'defaultmessagetext';
02345                         }
02346                 } else {
02347                         $oldtext = $this->mArticle->getRawText();
02348                 }
02349                 $newtext = $this->mArticle->replaceSection(
02350                         $this->section, $this->textbox1, $this->summary, $this->edittime );
02351 
02352                 wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
02353 
02354                 $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
02355                 $newtext = $wgParser->preSaveTransform( $newtext, $this->mTitle, $wgUser, $popts );
02356 
02357                 if ( $oldtext !== false  || $newtext != '' ) {
02358                         $oldtitle = wfMessage( $oldtitlemsg )->parse();
02359                         $newtitle = wfMessage( 'yourtext' )->parse();
02360 
02361                         $de = new DifferenceEngine( $this->mArticle->getContext() );
02362                         $de->setText( $oldtext, $newtext );
02363                         $difftext = $de->getDiff( $oldtitle, $newtitle );
02364                         $de->showDiffStyle();
02365                 } else {
02366                         $difftext = '';
02367                 }
02368 
02369                 $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
02370         }
02371 
02375         protected function showHeaderCopyrightWarning() {
02376                 $msg = 'editpage-head-copy-warn';
02377                 if ( !wfMessage( $msg )->isDisabled() ) {
02378                         global $wgOut;
02379                         $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
02380                                 'editpage-head-copy-warn' );
02381                 }
02382         }
02383 
02392         protected function showTosSummary() {
02393                 $msg = 'editpage-tos-summary';
02394                 wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
02395                 if ( !wfMessage( $msg )->isDisabled() ) {
02396                         global $wgOut;
02397                         $wgOut->addHTML( '<div class="mw-tos-summary">' );
02398                         $wgOut->addWikiMsg( $msg );
02399                         $wgOut->addHTML( '</div>' );
02400                 }
02401         }
02402 
02403         protected function showEditTools() {
02404                 global $wgOut;
02405                 $wgOut->addHTML( '<div class="mw-editTools">' .
02406                         wfMessage( 'edittools' )->inContentLanguage()->parse() .
02407                         '</div>' );
02408         }
02409 
02415         protected function getCopywarn() {
02416                 return self::getCopyrightWarning( $this->mTitle );
02417         }
02418 
02419         public static function getCopyrightWarning( $title ) {
02420                 global $wgRightsText;
02421                 if ( $wgRightsText ) {
02422                         $copywarnMsg = array( 'copyrightwarning',
02423                                 '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]',
02424                                 $wgRightsText );
02425                 } else {
02426                         $copywarnMsg = array( 'copyrightwarning2',
02427                                 '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' );
02428                 }
02429                 // Allow for site and per-namespace customization of contribution/copyright notice.
02430                 wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
02431 
02432                 return "<div id=\"editpage-copywarn\">\n" .
02433                         call_user_func_array( 'wfMessage', $copywarnMsg )->plain() . "\n</div>";
02434         }
02435 
02436         protected function showStandardInputs( &$tabindex = 2 ) {
02437                 global $wgOut;
02438                 $wgOut->addHTML( "<div class='editOptions'>\n" );
02439 
02440                 if ( $this->section != 'new' ) {
02441                         $this->showSummaryInput( false, $this->summary );
02442                         $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
02443                 }
02444 
02445                 $checkboxes = $this->getCheckboxes( $tabindex,
02446                         array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) );
02447                 $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" );
02448 
02449                 // Show copyright warning.
02450                 $wgOut->addWikiText( $this->getCopywarn() );
02451                 $wgOut->addHTML( $this->editFormTextAfterWarn );
02452 
02453                 $wgOut->addHTML( "<div class='editButtons'>\n" );
02454                 $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
02455 
02456                 $cancel = $this->getCancelLink();
02457                 if ( $cancel !== '' ) {
02458                         $cancel .= wfMessage( 'pipe-separator' )->text();
02459                 }
02460                 $edithelpurl = Skin::makeInternalOrExternalUrl( wfMessage( 'edithelppage' )->inContentLanguage()->text() );
02461                 $edithelp = '<a target="helpwindow" href="' . $edithelpurl . '">' .
02462                         wfMessage( 'edithelp' )->escaped() . '</a> ' .
02463                         wfMessage( 'newwindow' )->parse();
02464                 $wgOut->addHTML( "      <span class='cancelLink'>{$cancel}</span>\n" );
02465                 $wgOut->addHTML( "      <span class='editHelp'>{$edithelp}</span>\n" );
02466                 $wgOut->addHTML( "</div><!-- editButtons -->\n</div><!-- editOptions -->\n" );
02467         }
02468 
02473         protected function showConflict() {
02474                 global $wgOut;
02475 
02476                 if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
02477                         $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
02478 
02479                         $de = new DifferenceEngine( $this->mArticle->getContext() );
02480                         $de->setText( $this->textbox2, $this->textbox1 );
02481                         $de->showDiff(
02482                                 wfMessage( 'yourtext' )->parse(),
02483                                 wfMessage( 'storedversion' )->text()
02484                         );
02485 
02486                         $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
02487                         $this->showTextbox2();
02488                 }
02489         }
02490 
02494         public function getCancelLink() {
02495                 $cancelParams = array();
02496                 if ( !$this->isConflict && $this->oldid > 0 ) {
02497                         $cancelParams['oldid'] = $this->oldid;
02498                 }
02499 
02500                 return Linker::linkKnown(
02501                         $this->getContextTitle(),
02502                         wfMessage( 'cancel' )->parse(),
02503                         array( 'id' => 'mw-editform-cancel' ),
02504                         $cancelParams
02505                 );
02506         }
02507 
02517         protected function getActionURL( Title $title ) {
02518                 return $title->getLocalURL( array( 'action' => $this->action ) );
02519         }
02520 
02527         protected function wasDeletedSinceLastEdit() {
02528                 if ( $this->deletedSinceEdit !== null ) {
02529                         return $this->deletedSinceEdit;
02530                 }
02531 
02532                 $this->deletedSinceEdit = false;
02533 
02534                 if ( $this->mTitle->isDeletedQuick() ) {
02535                         $this->lastDelete = $this->getLastDelete();
02536                         if ( $this->lastDelete ) {
02537                                 $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
02538                                 if ( $deleteTime > $this->starttime ) {
02539                                         $this->deletedSinceEdit = true;
02540                                 }
02541                         }
02542                 }
02543 
02544                 return $this->deletedSinceEdit;
02545         }
02546 
02547         protected function getLastDelete() {
02548                 $dbr = wfGetDB( DB_SLAVE );
02549                 $data = $dbr->selectRow(
02550                         array( 'logging', 'user' ),
02551                         array( 'log_type',
02552                                    'log_action',
02553                                    'log_timestamp',
02554                                    'log_user',
02555                                    'log_namespace',
02556                                    'log_title',
02557                                    'log_comment',
02558                                    'log_params',
02559                                    'log_deleted',
02560                                    'user_name' ),
02561                         array( 'log_namespace' => $this->mTitle->getNamespace(),
02562                                    'log_title' => $this->mTitle->getDBkey(),
02563                                    'log_type' => 'delete',
02564                                    'log_action' => 'delete',
02565                                    'user_id=log_user' ),
02566                         __METHOD__,
02567                         array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' )
02568                 );
02569                 // Quick paranoid permission checks...
02570                 if ( is_object( $data ) ) {
02571                         if ( $data->log_deleted & LogPage::DELETED_USER )
02572                                 $data->user_name = wfMessage( 'rev-deleted-user' )->escaped();
02573                         if ( $data->log_deleted & LogPage::DELETED_COMMENT )
02574                                 $data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped();
02575                 }
02576                 return $data;
02577         }
02578 
02583         function getPreviewText() {
02584                 global $wgOut, $wgUser, $wgParser, $wgRawHtml, $wgLang;
02585 
02586                 wfProfileIn( __METHOD__ );
02587 
02588                 if ( $wgRawHtml && !$this->mTokenOk ) {
02589                         // Could be an offsite preview attempt. This is very unsafe if
02590                         // HTML is enabled, as it could be an attack.
02591                         $parsedNote = '';
02592                         if ( $this->textbox1 !== '' ) {
02593                                 // Do not put big scary notice, if previewing the empty
02594                                 // string, which happens when you initially edit
02595                                 // a category page, due to automatic preview-on-open.
02596                                 $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
02597                                         wfMessage( 'session_fail_preview_html' )->text() . "</div>", true, /* interface */true );
02598                         }
02599                         wfProfileOut( __METHOD__ );
02600                         return $parsedNote;
02601                 }
02602 
02603                 if ( $this->mTriedSave && !$this->mTokenOk ) {
02604                         if ( $this->mTokenOkExceptSuffix ) {
02605                                 $note = wfMessage( 'token_suffix_mismatch' )->plain();
02606                         } else {
02607                                 $note = wfMessage( 'session_fail_preview' )->plain();
02608                         }
02609                 } elseif ( $this->incompleteForm ) {
02610                         $note = wfMessage( 'edit_form_incomplete' )->plain();
02611                 } else {
02612                         $note = wfMessage( 'previewnote' )->plain() .
02613                                 ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
02614                 }
02615 
02616                 $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
02617 
02618                 $parserOptions->setEditSection( false );
02619                 $parserOptions->setIsPreview( true );
02620                 $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
02621 
02622                 # don't parse non-wikitext pages, show message about preview
02623                 if ( $this->mTitle->isCssJsSubpage() || !$this->mTitle->isWikitextPage() ) {
02624                         if ( $this->mTitle->isCssJsSubpage() ) {
02625                                 $level = 'user';
02626                         } elseif ( $this->mTitle->isCssOrJsPage() ) {
02627                                 $level = 'site';
02628                         } else {
02629                                 $level = false;
02630                         }
02631 
02632                         # Used messages to make sure grep find them:
02633                         # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
02634                         $class = 'mw-code';
02635                         if ( $level ) {
02636                                 if ( preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
02637                                         $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMessage( "{$level}csspreview" )->text() . "\n</div>";
02638                                         $class .= " mw-css";
02639                                 } elseif ( preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
02640                                         $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMessage( "{$level}jspreview" )->text() . "\n</div>";
02641                                         $class .= " mw-js";
02642                                 } else {
02643                                         throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
02644                                 }
02645                                 $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
02646                                 $previewHTML = $parserOutput->getText();
02647                         } else {
02648                                 $previewHTML = '';
02649                         }
02650 
02651                         $previewHTML .= "<pre class=\"$class\" dir=\"ltr\">\n" . htmlspecialchars( $this->textbox1 ) . "\n</pre>\n";
02652                 } else {
02653                         $toparse = $this->textbox1;
02654 
02655                         # If we're adding a comment, we need to show the
02656                         # summary as the headline
02657                         if ( $this->section == "new" && $this->summary != "" ) {
02658                                 $toparse = wfMessage( 'newsectionheaderdefaultlevel', $this->summary )->inContentLanguage()->text() . "\n\n" . $toparse;
02659                         }
02660 
02661                         wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
02662 
02663                         $toparse = $wgParser->preSaveTransform( $toparse, $this->mTitle, $wgUser, $parserOptions );
02664                         $parserOutput = $wgParser->parse( $toparse, $this->mTitle, $parserOptions );
02665 
02666                         $rt = Title::newFromRedirectArray( $this->textbox1 );
02667                         if ( $rt ) {
02668                                 $previewHTML = $this->mArticle->viewRedirect( $rt, false );
02669                         } else {
02670                                 $previewHTML = $parserOutput->getText();
02671                         }
02672 
02673                         $this->mParserOutput = $parserOutput;
02674                         $wgOut->addParserOutputNoText( $parserOutput );
02675 
02676                         if ( count( $parserOutput->getWarnings() ) ) {
02677                                 $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
02678                         }
02679                 }
02680 
02681                 if ( $this->isConflict ) {
02682                         $conflict = '<h2 id="mw-previewconflict">' . wfMessage( 'previewconflict' )->escaped() . "</h2>\n";
02683                 } else {
02684                         $conflict = '<hr />';
02685                 }
02686 
02687                 $previewhead = "<div class='previewnote'>\n" .
02688                         '<h2 id="mw-previewheader">' . wfMessage( 'preview' )->escaped() . "</h2>" .
02689                         $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
02690 
02691                 $pageLang = $this->mTitle->getPageLanguage();
02692                 $attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
02693                         'class' => 'mw-content-' . $pageLang->getDir() );
02694                 $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
02695 
02696                 wfProfileOut( __METHOD__ );
02697                 return $previewhead . $previewHTML . $this->previewTextAfterContent;
02698         }
02699 
02703         function getTemplates() {
02704                 if ( $this->preview || $this->section != '' ) {
02705                         $templates = array();
02706                         if ( !isset( $this->mParserOutput ) ) {
02707                                 return $templates;
02708                         }
02709                         foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
02710                                 foreach ( array_keys( $template ) as $dbk ) {
02711                                         $templates[] = Title::makeTitle( $ns, $dbk );
02712                                 }
02713                         }
02714                         return $templates;
02715                 } else {
02716                         return $this->mTitle->getTemplateLinksFrom();
02717                 }
02718         }
02719 
02727         static function getEditToolbar() {
02728                 global $wgStylePath, $wgContLang, $wgLang, $wgOut;
02729                 global $wgUseTeX, $wgEnableUploads, $wgForeignFileRepos;
02730 
02731                 $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
02732 
02746                 $toolarray = array(
02747                         array(
02748                                 'image'  => $wgLang->getImageFile( 'button-bold' ),
02749                                 'id'     => 'mw-editbutton-bold',
02750                                 'open'   => '\'\'\'',
02751                                 'close'  => '\'\'\'',
02752                                 'sample' => wfMessage( 'bold_sample' )->text(),
02753                                 'tip'    => wfMessage( 'bold_tip' )->text(),
02754                                 'key'    => 'B'
02755                         ),
02756                         array(
02757                                 'image'  => $wgLang->getImageFile( 'button-italic' ),
02758                                 'id'     => 'mw-editbutton-italic',
02759                                 'open'   => '\'\'',
02760                                 'close'  => '\'\'',
02761                                 'sample' => wfMessage( 'italic_sample' )->text(),
02762                                 'tip'    => wfMessage( 'italic_tip' )->text(),
02763                                 'key'    => 'I'
02764                         ),
02765                         array(
02766                                 'image'  => $wgLang->getImageFile( 'button-link' ),
02767                                 'id'     => 'mw-editbutton-link',
02768                                 'open'   => '[[',
02769                                 'close'  => ']]',
02770                                 'sample' => wfMessage( 'link_sample' )->text(),
02771                                 'tip'    => wfMessage( 'link_tip' )->text(),
02772                                 'key'    => 'L'
02773                         ),
02774                         array(
02775                                 'image'  => $wgLang->getImageFile( 'button-extlink' ),
02776                                 'id'     => 'mw-editbutton-extlink',
02777                                 'open'   => '[',
02778                                 'close'  => ']',
02779                                 'sample' => wfMessage( 'extlink_sample' )->text(),
02780                                 'tip'    => wfMessage( 'extlink_tip' )->text(),
02781                                 'key'    => 'X'
02782                         ),
02783                         array(
02784                                 'image'  => $wgLang->getImageFile( 'button-headline' ),
02785                                 'id'     => 'mw-editbutton-headline',
02786                                 'open'   => "\n== ",
02787                                 'close'  => " ==\n",
02788                                 'sample' => wfMessage( 'headline_sample' )->text(),
02789                                 'tip'    => wfMessage( 'headline_tip' )->text(),
02790                                 'key'    => 'H'
02791                         ),
02792                         $imagesAvailable ? array(
02793                                 'image'  => $wgLang->getImageFile( 'button-image' ),
02794                                 'id'     => 'mw-editbutton-image',
02795                                 'open'   => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
02796                                 'close'  => ']]',
02797                                 'sample' => wfMessage( 'image_sample' )->text(),
02798                                 'tip'    => wfMessage( 'image_tip' )->text(),
02799                                 'key'    => 'D',
02800                         ) : false,
02801                         $imagesAvailable ? array(
02802                                 'image'  => $wgLang->getImageFile( 'button-media' ),
02803                                 'id'     => 'mw-editbutton-media',
02804                                 'open'   => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
02805                                 'close'  => ']]',
02806                                 'sample' => wfMessage( 'media_sample' )->text(),
02807                                 'tip'    => wfMessage( 'media_tip' )->text(),
02808                                 'key'    => 'M'
02809                         ) : false,
02810                         $wgUseTeX ? array(
02811                                 'image'  => $wgLang->getImageFile( 'button-math' ),
02812                                 'id'     => 'mw-editbutton-math',
02813                                 'open'   => "<math>",
02814                                 'close'  => "</math>",
02815                                 'sample' => wfMessage( 'math_sample' )->text(),
02816                                 'tip'    => wfMessage( 'math_tip' )->text(),
02817                                 'key'    => 'C'
02818                         ) : false,
02819                         array(
02820                                 'image'  => $wgLang->getImageFile( 'button-nowiki' ),
02821                                 'id'     => 'mw-editbutton-nowiki',
02822                                 'open'   => "<nowiki>",
02823                                 'close'  => "</nowiki>",
02824                                 'sample' => wfMessage( 'nowiki_sample' )->text(),
02825                                 'tip'    => wfMessage( 'nowiki_tip' )->text(),
02826                                 'key'    => 'N'
02827                         ),
02828                         array(
02829                                 'image'  => $wgLang->getImageFile( 'button-sig' ),
02830                                 'id'     => 'mw-editbutton-signature',
02831                                 'open'   => '--~~~~',
02832                                 'close'  => '',
02833                                 'sample' => '',
02834                                 'tip'    => wfMessage( 'sig_tip' )->text(),
02835                                 'key'    => 'Y'
02836                         ),
02837                         array(
02838                                 'image'  => $wgLang->getImageFile( 'button-hr' ),
02839                                 'id'     => 'mw-editbutton-hr',
02840                                 'open'   => "\n----\n",
02841                                 'close'  => '',
02842                                 'sample' => '',
02843                                 'tip'    => wfMessage( 'hr_tip' )->text(),
02844                                 'key'    => 'R'
02845                         )
02846                 );
02847 
02848                 $script = 'mw.loader.using("mediawiki.action.edit", function() {';
02849                 foreach ( $toolarray as $tool ) {
02850                         if ( !$tool ) {
02851                                 continue;
02852                         }
02853 
02854                         $params = array(
02855                                 $image = $wgStylePath . '/common/images/' . $tool['image'],
02856                                 // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
02857                                 // Older browsers show a "speedtip" type message only for ALT.
02858                                 // Ideally these should be different, realistically they
02859                                 // probably don't need to be.
02860                                 $tip = $tool['tip'],
02861                                 $open = $tool['open'],
02862                                 $close = $tool['close'],
02863                                 $sample = $tool['sample'],
02864                                 $cssId = $tool['id'],
02865                         );
02866 
02867                         $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
02868                 }
02869 
02870                 // This used to be called on DOMReady from mediawiki.action.edit, which
02871                 // ended up causing race conditions with the setup code above.
02872                 $script .= "\n" .
02873                         "// Create button bar\n" .
02874                         "$(function() { mw.toolbar.init(); } );\n";
02875 
02876                 $script .= '});';
02877                 $wgOut->addScript( Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) ) );
02878 
02879                 $toolbar = '<div id="toolbar"></div>';
02880 
02881                 wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
02882 
02883                 return $toolbar;
02884         }
02885 
02896         public function getCheckboxes( &$tabindex, $checked ) {
02897                 global $wgUser;
02898 
02899                 $checkboxes = array();
02900 
02901                 // don't show the minor edit checkbox if it's a new page or section
02902                 if ( !$this->isNew ) {
02903                         $checkboxes['minor'] = '';
02904                         $minorLabel = wfMessage( 'minoredit' )->parse();
02905                         if ( $wgUser->isAllowed( 'minoredit' ) ) {
02906                                 $attribs = array(
02907                                         'tabindex'  => ++$tabindex,
02908                                         'accesskey' => wfMessage( 'accesskey-minoredit' )->text(),
02909                                         'id'        => 'wpMinoredit',
02910                                 );
02911                                 $checkboxes['minor'] =
02912                                         Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
02913                                         "&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
02914                                         Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'minoredit', 'withaccess' ) ) ) .
02915                                         ">{$minorLabel}</label>";
02916                         }
02917                 }
02918 
02919                 $watchLabel = wfMessage( 'watchthis' )->parse();
02920                 $checkboxes['watch'] = '';
02921                 if ( $wgUser->isLoggedIn() ) {
02922                         $attribs = array(
02923                                 'tabindex'  => ++$tabindex,
02924                                 'accesskey' => wfMessage( 'accesskey-watch' )->text(),
02925                                 'id'        => 'wpWatchthis',
02926                         );
02927                         $checkboxes['watch'] =
02928                                 Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
02929                                 "&#160;<label for='wpWatchthis' id='mw-editpage-watch'" .
02930                                 Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ) ) .
02931                                 ">{$watchLabel}</label>";
02932                 }
02933                 wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
02934                 return $checkboxes;
02935         }
02936 
02945         public function getEditButtons( &$tabindex ) {
02946                 $buttons = array();
02947 
02948                 $temp = array(
02949                         'id'        => 'wpSave',
02950                         'name'      => 'wpSave',
02951                         'type'      => 'submit',
02952                         'tabindex'  => ++$tabindex,
02953                         'value'     => wfMessage( 'savearticle' )->text(),
02954                         'accesskey' => wfMessage( 'accesskey-save' )->text(),
02955                         'title'     => wfMessage( 'tooltip-save' )->text() . ' [' . wfMessage( 'accesskey-save' )->text() . ']',
02956                 );
02957                 $buttons['save'] = Xml::element( 'input', $temp, '' );
02958 
02959                 ++$tabindex; // use the same for preview and live preview
02960                 $temp = array(
02961                         'id'        => 'wpPreview',
02962                         'name'      => 'wpPreview',
02963                         'type'      => 'submit',
02964                         'tabindex'  => $tabindex,
02965                         'value'     => wfMessage( 'showpreview' )->text(),
02966                         'accesskey' => wfMessage( 'accesskey-preview' )->text(),
02967                         'title'     => wfMessage( 'tooltip-preview' )->text() . ' [' . wfMessage( 'accesskey-preview' )->text() . ']',
02968                 );
02969                 $buttons['preview'] = Xml::element( 'input', $temp, '' );
02970                 $buttons['live'] = '';
02971 
02972                 $temp = array(
02973                         'id'        => 'wpDiff',
02974                         'name'      => 'wpDiff',
02975                         'type'      => 'submit',
02976                         'tabindex'  => ++$tabindex,
02977                         'value'     => wfMessage( 'showdiff' )->text(),
02978                         'accesskey' => wfMessage( 'accesskey-diff' )->text(),
02979                         'title'     => wfMessage( 'tooltip-diff' )->text() . ' [' . wfMessage( 'accesskey-diff' )->text() . ']',
02980                 );
02981                 $buttons['diff'] = Xml::element( 'input', $temp, '' );
02982 
02983                 wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) );
02984                 return $buttons;
02985         }
02986 
02999         function livePreview() {
03000                 global $wgOut;
03001                 $wgOut->disable();
03002                 header( 'Content-type: text/xml; charset=utf-8' );
03003                 header( 'Cache-control: no-cache' );
03004 
03005                 $previewText = $this->getPreviewText();
03006                 #$categories = $skin->getCategoryLinks();
03007 
03008                 $s =
03009                 '<?xml version="1.0" encoding="UTF-8" ?>' . "\n" .
03010                 Xml::tags( 'livepreview', null,
03011                         Xml::element( 'preview', null, $previewText )
03012                         #.      Xml::element( 'category', null, $categories )
03013                 );
03014                 echo $s;
03015         }
03016 
03022         function blockedPage() {
03023                 wfDeprecated( __METHOD__, '1.19' );
03024                 global $wgUser;
03025 
03026                 throw new UserBlockedError( $wgUser->getBlock() );
03027         }
03028 
03034         function userNotLoggedInPage() {
03035                 wfDeprecated( __METHOD__, '1.19' );
03036                 throw new PermissionsError( 'edit' );
03037         }
03038 
03045         function noCreatePermission() {
03046                 wfDeprecated( __METHOD__, '1.19' );
03047                 $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
03048                 throw new PermissionsError( $permission );
03049         }
03050 
03055         function noSuchSectionPage() {
03056                 global $wgOut;
03057 
03058                 $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
03059 
03060                 $res = wfMessage( 'nosuchsectiontext', $this->section )->parseAsBlock();
03061                 wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
03062                 $wgOut->addHTML( $res );
03063 
03064                 $wgOut->returnToMain( false, $this->mTitle );
03065         }
03066 
03073         static function spamPage( $match = false ) {
03074                 wfDeprecated( __METHOD__, '1.17' );
03075 
03076                 global $wgOut, $wgTitle;
03077 
03078                 $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
03079 
03080                 $wgOut->addHTML( '<div id="spamprotected">' );
03081                 $wgOut->addWikiMsg( 'spamprotectiontext' );
03082                 if ( $match ) {
03083                         $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
03084                 }
03085                 $wgOut->addHTML( '</div>' );
03086 
03087                 $wgOut->returnToMain( false, $wgTitle );
03088         }
03089 
03095         public function spamPageWithContent( $match = false ) {
03096                 global $wgOut, $wgLang;
03097                 $this->textbox2 = $this->textbox1;
03098 
03099                 if( is_array( $match ) ){
03100                         $match = $wgLang->listToText( $match );
03101                 }
03102                 $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
03103 
03104                 $wgOut->addHTML( '<div id="spamprotected">' );
03105                 $wgOut->addWikiMsg( 'spamprotectiontext' );
03106                 if ( $match ) {
03107                         $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
03108                 }
03109                 $wgOut->addHTML( '</div>' );
03110 
03111                 $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
03112                 $this->showDiff();
03113 
03114                 $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
03115                 $this->showTextbox2();
03116 
03117                 $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) );
03118         }
03119 
03126         function sectionAnchor( $text ) {
03127                 global $wgParser;
03128                 return $wgParser->guessSectionNameFromWikiText( $text );
03129         }
03130 
03138         function checkUnicodeCompliantBrowser() {
03139                 global $wgBrowserBlackList, $wgRequest;
03140 
03141                 $currentbrowser = $wgRequest->getHeader( 'User-Agent' );
03142                 if ( $currentbrowser === false ) {
03143                         // No User-Agent header sent? Trust it by default...
03144                         return true;
03145                 }
03146 
03147                 foreach ( $wgBrowserBlackList as $browser ) {
03148                         if ( preg_match( $browser, $currentbrowser ) ) {
03149                                 return false;
03150                         }
03151                 }
03152                 return true;
03153         }
03154 
03164         function safeUnicodeInput( $request, $field ) {
03165                 $text = rtrim( $request->getText( $field ) );
03166                 return $request->getBool( 'safemode' )
03167                         ? $this->unmakesafe( $text )
03168                         : $text;
03169         }
03170 
03176         function safeUnicodeText( $request, $text ) {
03177                 $text = rtrim( $text );
03178                 return $request->getBool( 'safemode' )
03179                         ? $this->unmakesafe( $text )
03180                         : $text;
03181         }
03182 
03191         function safeUnicodeOutput( $text ) {
03192                 global $wgContLang;
03193                 $codedText = $wgContLang->recodeForEdit( $text );
03194                 return $this->checkUnicodeCompliantBrowser()
03195                         ? $codedText
03196                         : $this->makesafe( $codedText );
03197         }
03198 
03212         function makesafe( $invalue ) {
03213                 // Armor existing references for reversability.
03214                 $invalue = strtr( $invalue, array( "&#x" => "&#x0" ) );
03215 
03216                 $bytesleft = 0;
03217                 $result = "";
03218                 $working = 0;
03219                 for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
03220                         $bytevalue = ord( $invalue[$i] );
03221                         if ( $bytevalue <= 0x7F ) { // 0xxx xxxx
03222                                 $result .= chr( $bytevalue );
03223                                 $bytesleft = 0;
03224                         } elseif ( $bytevalue <= 0xBF ) { // 10xx xxxx
03225                                 $working = $working << 6;
03226                                 $working += ( $bytevalue & 0x3F );
03227                                 $bytesleft--;
03228                                 if ( $bytesleft <= 0 ) {
03229                                         $result .= "&#x" . strtoupper( dechex( $working ) ) . ";";
03230                                 }
03231                         } elseif ( $bytevalue <= 0xDF ) { // 110x xxxx
03232                                 $working = $bytevalue & 0x1F;
03233                                 $bytesleft = 1;
03234                         } elseif ( $bytevalue <= 0xEF ) { // 1110 xxxx
03235                                 $working = $bytevalue & 0x0F;
03236                                 $bytesleft = 2;
03237                         } else { // 1111 0xxx
03238                                 $working = $bytevalue & 0x07;
03239                                 $bytesleft = 3;
03240                         }
03241                 }
03242                 return $result;
03243         }
03244 
03254         function unmakesafe( $invalue ) {
03255                 $result = "";
03256                 for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
03257                         if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) {
03258                                 $i += 3;
03259                                 $hexstring = "";
03260                                 do {
03261                                         $hexstring .= $invalue[$i];
03262                                         $i++;
03263                                 } while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
03264 
03265                                 // Do some sanity checks. These aren't needed for reversability,
03266                                 // but should help keep the breakage down if the editor
03267                                 // breaks one of the entities whilst editing.
03268                                 if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) {
03269                                         $codepoint = hexdec( $hexstring );
03270                                         $result .= codepointToUtf8( $codepoint );
03271                                 } else {
03272                                         $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
03273                                 }
03274                         } else {
03275                                 $result .= substr( $invalue, $i, 1 );
03276                         }
03277                 }
03278                 // reverse the transform that we made for reversability reasons.
03279                 return strtr( $result, array( "&#x0" => "&#x" ) );
03280         }
03281 }