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