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