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