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