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