MediaWiki
REL1_24
|
00001 <?php 00038 class EditPage { 00042 const AS_SUCCESS_UPDATE = 200; 00043 00047 const AS_SUCCESS_NEW_ARTICLE = 201; 00048 00052 const AS_HOOK_ERROR = 210; 00053 00057 const AS_HOOK_ERROR_EXPECTED = 212; 00058 00062 const AS_BLOCKED_PAGE_FOR_USER = 215; 00063 00067 const AS_CONTENT_TOO_BIG = 216; 00068 00072 const AS_READ_ONLY_PAGE_ANON = 218; 00073 00077 const AS_READ_ONLY_PAGE_LOGGED = 219; 00078 00082 const AS_READ_ONLY_PAGE = 220; 00083 00087 const AS_RATE_LIMITED = 221; 00088 00093 const AS_ARTICLE_WAS_DELETED = 222; 00094 00099 const AS_NO_CREATE_PERMISSION = 223; 00100 00104 const AS_BLANK_ARTICLE = 224; 00105 00109 const AS_CONFLICT_DETECTED = 225; 00110 00115 const AS_SUMMARY_NEEDED = 226; 00116 00120 const AS_TEXTBOX_EMPTY = 228; 00121 00125 const AS_MAX_ARTICLE_SIZE_EXCEEDED = 229; 00126 00130 const AS_END = 231; 00131 00135 const AS_SPAM_ERROR = 232; 00136 00140 const AS_IMAGE_REDIRECT_ANON = 233; 00141 00145 const AS_IMAGE_REDIRECT_LOGGED = 234; 00146 00150 const AS_PARSE_ERROR = 240; 00151 00155 const EDITFORM_ID = 'editform'; 00156 00161 const POST_EDIT_COOKIE_KEY_PREFIX = 'PostEditRevision'; 00162 00176 const POST_EDIT_COOKIE_DURATION = 1200; 00177 00179 public $mArticle; 00180 00182 public $mTitle; 00183 00185 private $mContextTitle = null; 00186 00188 public $action = 'submit'; 00189 00191 public $isConflict = false; 00192 00194 public $isCssJsSubpage = false; 00195 00197 public $isCssSubpage = false; 00198 00200 public $isJsSubpage = false; 00201 00203 public $isWrongCaseCssJsPage = false; 00204 00206 public $isNew = false; 00207 00209 public $deletedSinceEdit; 00210 00212 public $formtype; 00213 00215 public $firsttime; 00216 00218 public $lastDelete; 00219 00221 public $mTokenOk = false; 00222 00224 public $mTokenOkExceptSuffix = false; 00225 00227 public $mTriedSave = false; 00228 00230 public $incompleteForm = false; 00231 00233 public $tooBig = false; 00234 00236 public $kblength = false; 00237 00239 public $missingComment = false; 00240 00242 public $missingSummary = false; 00243 00245 public $allowBlankSummary = false; 00246 00248 protected $blankArticle = false; 00249 00251 protected $allowBlankArticle = false; 00252 00254 public $autoSumm = ''; 00255 00257 public $hookError = ''; 00258 00260 public $mParserOutput; 00261 00263 public $hasPresetSummary = false; 00264 00266 public $mBaseRevision = false; 00267 00269 public $mShowSummaryField = true; 00270 00271 # Form values 00272 00274 public $save = false; 00275 00277 public $preview = false; 00278 00280 public $diff = false; 00281 00283 public $minoredit = false; 00284 00286 public $watchthis = false; 00287 00289 public $recreate = false; 00290 00292 public $textbox1 = ''; 00293 00295 public $textbox2 = ''; 00296 00298 public $summary = ''; 00299 00301 public $nosummary = false; 00302 00304 public $edittime = ''; 00305 00307 public $section = ''; 00308 00310 public $sectiontitle = ''; 00311 00313 public $starttime = ''; 00314 00316 public $oldid = 0; 00317 00319 public $editintro = ''; 00320 00322 public $scrolltop = null; 00323 00325 public $bot = true; 00326 00328 public $contentModel = null; 00329 00331 public $contentFormat = null; 00332 00333 # Placeholders for text injection by hooks (must be HTML) 00334 # extensions should take care to _append_ to the present value 00335 00337 public $editFormPageTop = ''; 00338 public $editFormTextTop = ''; 00339 public $editFormTextBeforeContent = ''; 00340 public $editFormTextAfterWarn = ''; 00341 public $editFormTextAfterTools = ''; 00342 public $editFormTextBottom = ''; 00343 public $editFormTextAfterContent = ''; 00344 public $previewTextAfterContent = ''; 00345 public $mPreloadContent = null; 00346 00347 /* $didSave should be set to true whenever an article was successfully altered. */ 00348 public $didSave = false; 00349 public $undidRev = 0; 00350 00351 public $suppressIntro = false; 00352 00354 public $allowNonTextContent = false; 00355 00357 protected $edit; 00358 00360 public $live; 00361 00365 public function __construct( Article $article ) { 00366 $this->mArticle = $article; 00367 $this->mTitle = $article->getTitle(); 00368 00369 $this->contentModel = $this->mTitle->getContentModel(); 00370 00371 $handler = ContentHandler::getForModelID( $this->contentModel ); 00372 $this->contentFormat = $handler->getDefaultFormat(); 00373 } 00374 00378 public function getArticle() { 00379 return $this->mArticle; 00380 } 00381 00386 public function getTitle() { 00387 return $this->mTitle; 00388 } 00389 00395 public function setContextTitle( $title ) { 00396 $this->mContextTitle = $title; 00397 } 00398 00406 public function getContextTitle() { 00407 if ( is_null( $this->mContextTitle ) ) { 00408 global $wgTitle; 00409 return $wgTitle; 00410 } else { 00411 return $this->mContextTitle; 00412 } 00413 } 00414 00422 public function isSupportedContentModel( $modelId ) { 00423 return $this->allowNonTextContent || 00424 ContentHandler::getForModelID( $modelId ) instanceof TextContentHandler; 00425 } 00426 00427 function submit() { 00428 $this->edit(); 00429 } 00430 00442 function edit() { 00443 global $wgOut, $wgRequest, $wgUser; 00444 // Allow extensions to modify/prevent this form or submission 00445 if ( !wfRunHooks( 'AlternateEdit', array( $this ) ) ) { 00446 return; 00447 } 00448 00449 wfProfileIn( __METHOD__ ); 00450 wfDebug( __METHOD__ . ": enter\n" ); 00451 00452 // If they used redlink=1 and the page exists, redirect to the main article 00453 if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) { 00454 $wgOut->redirect( $this->mTitle->getFullURL() ); 00455 wfProfileOut( __METHOD__ ); 00456 return; 00457 } 00458 00459 $this->importFormData( $wgRequest ); 00460 $this->firsttime = false; 00461 00462 if ( $this->live ) { 00463 $this->livePreview(); 00464 wfProfileOut( __METHOD__ ); 00465 return; 00466 } 00467 00468 if ( wfReadOnly() && $this->save ) { 00469 // Force preview 00470 $this->save = false; 00471 $this->preview = true; 00472 } 00473 00474 if ( $this->save ) { 00475 $this->formtype = 'save'; 00476 } elseif ( $this->preview ) { 00477 $this->formtype = 'preview'; 00478 } elseif ( $this->diff ) { 00479 $this->formtype = 'diff'; 00480 } else { # First time through 00481 $this->firsttime = true; 00482 if ( $this->previewOnOpen() ) { 00483 $this->formtype = 'preview'; 00484 } else { 00485 $this->formtype = 'initial'; 00486 } 00487 } 00488 00489 $permErrors = $this->getEditPermissionErrors(); 00490 if ( $permErrors ) { 00491 wfDebug( __METHOD__ . ": User can't edit\n" ); 00492 // Auto-block user's IP if the account was "hard" blocked 00493 $wgUser->spreadAnyEditBlock(); 00494 00495 $this->displayPermissionsError( $permErrors ); 00496 00497 wfProfileOut( __METHOD__ ); 00498 return; 00499 } 00500 00501 wfProfileIn( __METHOD__ . "-business-end" ); 00502 00503 $this->isConflict = false; 00504 // css / js subpages of user pages get a special treatment 00505 $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage(); 00506 $this->isCssSubpage = $this->mTitle->isCssSubpage(); 00507 $this->isJsSubpage = $this->mTitle->isJsSubpage(); 00508 // @todo FIXME: Silly assignment. 00509 $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage(); 00510 00511 # Show applicable editing introductions 00512 if ( $this->formtype == 'initial' || $this->firsttime ) { 00513 $this->showIntro(); 00514 } 00515 00516 # Attempt submission here. This will check for edit conflicts, 00517 # and redundantly check for locked database, blocked IPs, etc. 00518 # that edit() already checked just in case someone tries to sneak 00519 # in the back door with a hand-edited submission URL. 00520 00521 if ( 'save' == $this->formtype ) { 00522 if ( !$this->attemptSave() ) { 00523 wfProfileOut( __METHOD__ . "-business-end" ); 00524 wfProfileOut( __METHOD__ ); 00525 return; 00526 } 00527 } 00528 00529 # First time through: get contents, set time for conflict 00530 # checking, etc. 00531 if ( 'initial' == $this->formtype || $this->firsttime ) { 00532 if ( $this->initialiseForm() === false ) { 00533 $this->noSuchSectionPage(); 00534 wfProfileOut( __METHOD__ . "-business-end" ); 00535 wfProfileOut( __METHOD__ ); 00536 return; 00537 } 00538 00539 if ( !$this->mTitle->getArticleID() ) { 00540 wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) ); 00541 } else { 00542 wfRunHooks( 'EditFormInitialText', array( $this ) ); 00543 } 00544 00545 } 00546 00547 $this->showEditForm(); 00548 wfProfileOut( __METHOD__ . "-business-end" ); 00549 wfProfileOut( __METHOD__ ); 00550 } 00551 00555 protected function getEditPermissionErrors() { 00556 global $wgUser; 00557 $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser ); 00558 # Can this title be created? 00559 if ( !$this->mTitle->exists() ) { 00560 $permErrors = array_merge( $permErrors, 00561 wfArrayDiff2( $this->mTitle->getUserPermissionsErrors( 'create', $wgUser ), $permErrors ) ); 00562 } 00563 # Ignore some permissions errors when a user is just previewing/viewing diffs 00564 $remove = array(); 00565 foreach ( $permErrors as $error ) { 00566 if ( ( $this->preview || $this->diff ) 00567 && ( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' ) 00568 ) { 00569 $remove[] = $error; 00570 } 00571 } 00572 $permErrors = wfArrayDiff2( $permErrors, $remove ); 00573 return $permErrors; 00574 } 00575 00589 protected function displayPermissionsError( array $permErrors ) { 00590 global $wgRequest, $wgOut; 00591 00592 if ( $wgRequest->getBool( 'redlink' ) ) { 00593 // The edit page was reached via a red link. 00594 // Redirect to the article page and let them click the edit tab if 00595 // they really want a permission error. 00596 $wgOut->redirect( $this->mTitle->getFullURL() ); 00597 return; 00598 } 00599 00600 $content = $this->getContentObject(); 00601 00602 # Use the normal message if there's nothing to display 00603 if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) { 00604 $action = $this->mTitle->exists() ? 'edit' : 00605 ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' ); 00606 throw new PermissionsError( $action, $permErrors ); 00607 } 00608 00609 wfRunHooks( 'EditPage::showReadOnlyForm:initial', array( $this, &$wgOut ) ); 00610 00611 $wgOut->setRobotPolicy( 'noindex,nofollow' ); 00612 $wgOut->setPageTitle( wfMessage( 00613 'viewsource-title', 00614 $this->getContextTitle()->getPrefixedText() 00615 ) ); 00616 $wgOut->addBacklinkSubtitle( $this->getContextTitle() ); 00617 $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) ); 00618 $wgOut->addHTML( "<hr />\n" ); 00619 00620 # If the user made changes, preserve them when showing the markup 00621 # (This happens when a user is blocked during edit, for instance) 00622 if ( !$this->firsttime ) { 00623 $text = $this->textbox1; 00624 $wgOut->addWikiMsg( 'viewyourtext' ); 00625 } else { 00626 $text = $this->toEditText( $content ); 00627 $wgOut->addWikiMsg( 'viewsourcetext' ); 00628 } 00629 00630 $this->showTextbox( $text, 'wpTextbox1', array( 'readonly' ) ); 00631 00632 $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ), 00633 Linker::formatTemplates( $this->getTemplates() ) ) ); 00634 00635 $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' ); 00636 00637 if ( $this->mTitle->exists() ) { 00638 $wgOut->returnToMain( null, $this->mTitle ); 00639 } 00640 } 00641 00647 protected function previewOnOpen() { 00648 global $wgRequest, $wgUser, $wgPreviewOnOpenNamespaces; 00649 if ( $wgRequest->getVal( 'preview' ) == 'yes' ) { 00650 // Explicit override from request 00651 return true; 00652 } elseif ( $wgRequest->getVal( 'preview' ) == 'no' ) { 00653 // Explicit override from request 00654 return false; 00655 } elseif ( $this->section == 'new' ) { 00656 // Nothing *to* preview for new sections 00657 return false; 00658 } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) 00659 && $wgUser->getOption( 'previewonfirst' ) 00660 ) { 00661 // Standard preference behavior 00662 return true; 00663 } elseif ( !$this->mTitle->exists() 00664 && isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] ) 00665 && $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] 00666 ) { 00667 // Categories are special 00668 return true; 00669 } else { 00670 return false; 00671 } 00672 } 00673 00680 protected function isWrongCaseCssJsPage() { 00681 if ( $this->mTitle->isCssJsSubpage() ) { 00682 $name = $this->mTitle->getSkinFromCssJsSubpage(); 00683 $skins = array_merge( 00684 array_keys( Skin::getSkinNames() ), 00685 array( 'common' ) 00686 ); 00687 return !in_array( $name, $skins ) 00688 && in_array( strtolower( $name ), $skins ); 00689 } else { 00690 return false; 00691 } 00692 } 00693 00701 protected function isSectionEditSupported() { 00702 $contentHandler = ContentHandler::getForTitle( $this->mTitle ); 00703 return $contentHandler->supportsSections(); 00704 } 00705 00711 function importFormData( &$request ) { 00712 global $wgContLang, $wgUser; 00713 00714 wfProfileIn( __METHOD__ ); 00715 00716 # Section edit can come from either the form or a link 00717 $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) ); 00718 00719 if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) { 00720 wfProfileOut( __METHOD__ ); 00721 throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' ); 00722 } 00723 00724 $this->isNew = !$this->mTitle->exists() || $this->section == 'new'; 00725 00726 if ( $request->wasPosted() ) { 00727 # These fields need to be checked for encoding. 00728 # Also remove trailing whitespace, but don't remove _initial_ 00729 # whitespace from the text boxes. This may be significant formatting. 00730 $this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' ); 00731 if ( !$request->getCheck( 'wpTextbox2' ) ) { 00732 // Skip this if wpTextbox2 has input, it indicates that we came 00733 // from a conflict page with raw page text, not a custom form 00734 // modified by subclasses 00735 wfProfileIn( get_class( $this ) . "::importContentFormData" ); 00736 $textbox1 = $this->importContentFormData( $request ); 00737 if ( $textbox1 !== null ) { 00738 $this->textbox1 = $textbox1; 00739 } 00740 00741 wfProfileOut( get_class( $this ) . "::importContentFormData" ); 00742 } 00743 00744 # Truncate for whole multibyte characters 00745 $this->summary = $wgContLang->truncate( $request->getText( 'wpSummary' ), 255 ); 00746 00747 # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the 00748 # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for 00749 # section titles. 00750 $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary ); 00751 00752 # Treat sectiontitle the same way as summary. 00753 # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is 00754 # currently doing double duty as both edit summary and section title. Right now this 00755 # is just to allow API edits to work around this limitation, but this should be 00756 # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312). 00757 $this->sectiontitle = $wgContLang->truncate( $request->getText( 'wpSectionTitle' ), 255 ); 00758 $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle ); 00759 00760 $this->edittime = $request->getVal( 'wpEdittime' ); 00761 $this->starttime = $request->getVal( 'wpStarttime' ); 00762 00763 $undidRev = $request->getInt( 'wpUndidRevision' ); 00764 if ( $undidRev ) { 00765 $this->undidRev = $undidRev; 00766 } 00767 00768 $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' ); 00769 00770 if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) { 00771 // wpTextbox1 field is missing, possibly due to being "too big" 00772 // according to some filter rules such as Suhosin's setting for 00773 // suhosin.request.max_value_length (d'oh) 00774 $this->incompleteForm = true; 00775 } else { 00776 // If we receive the last parameter of the request, we can fairly 00777 // claim the POST request has not been truncated. 00778 00779 // TODO: softened the check for cutover. Once we determine 00780 // that it is safe, we should complete the transition by 00781 // removing the "edittime" clause. 00782 $this->incompleteForm = ( !$request->getVal( 'wpUltimateParam' ) && is_null( $this->edittime ) ); 00783 } 00784 if ( $this->incompleteForm ) { 00785 # If the form is incomplete, force to preview. 00786 wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" ); 00787 wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" ); 00788 $this->preview = true; 00789 } else { 00790 /* Fallback for live preview */ 00791 $this->preview = $request->getCheck( 'wpPreview' ) || $request->getCheck( 'wpLivePreview' ); 00792 $this->diff = $request->getCheck( 'wpDiff' ); 00793 00794 // Remember whether a save was requested, so we can indicate 00795 // if we forced preview due to session failure. 00796 $this->mTriedSave = !$this->preview; 00797 00798 if ( $this->tokenOk( $request ) ) { 00799 # Some browsers will not report any submit button 00800 # if the user hits enter in the comment box. 00801 # The unmarked state will be assumed to be a save, 00802 # if the form seems otherwise complete. 00803 wfDebug( __METHOD__ . ": Passed token check.\n" ); 00804 } elseif ( $this->diff ) { 00805 # Failed token check, but only requested "Show Changes". 00806 wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" ); 00807 } else { 00808 # Page might be a hack attempt posted from 00809 # an external site. Preview instead of saving. 00810 wfDebug( __METHOD__ . ": Failed token check; forcing preview\n" ); 00811 $this->preview = true; 00812 } 00813 } 00814 $this->save = !$this->preview && !$this->diff; 00815 if ( !preg_match( '/^\d{14}$/', $this->edittime ) ) { 00816 $this->edittime = null; 00817 } 00818 00819 if ( !preg_match( '/^\d{14}$/', $this->starttime ) ) { 00820 $this->starttime = null; 00821 } 00822 00823 $this->recreate = $request->getCheck( 'wpRecreate' ); 00824 00825 $this->minoredit = $request->getCheck( 'wpMinoredit' ); 00826 $this->watchthis = $request->getCheck( 'wpWatchthis' ); 00827 00828 # Don't force edit summaries when a user is editing their own user or talk page 00829 if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK ) 00830 && $this->mTitle->getText() == $wgUser->getName() 00831 ) { 00832 $this->allowBlankSummary = true; 00833 } else { 00834 $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ) 00835 || !$wgUser->getOption( 'forceeditsummary' ); 00836 } 00837 00838 $this->autoSumm = $request->getText( 'wpAutoSummary' ); 00839 00840 $this->allowBlankArticle = $request->getBool( 'wpIgnoreBlankArticle' ); 00841 } else { 00842 # Not a posted form? Start with nothing. 00843 wfDebug( __METHOD__ . ": Not a posted form.\n" ); 00844 $this->textbox1 = ''; 00845 $this->summary = ''; 00846 $this->sectiontitle = ''; 00847 $this->edittime = ''; 00848 $this->starttime = wfTimestampNow(); 00849 $this->edit = false; 00850 $this->preview = false; 00851 $this->save = false; 00852 $this->diff = false; 00853 $this->minoredit = false; 00854 // Watch may be overridden by request parameters 00855 $this->watchthis = $request->getBool( 'watchthis', false ); 00856 $this->recreate = false; 00857 00858 // When creating a new section, we can preload a section title by passing it as the 00859 // preloadtitle parameter in the URL (Bug 13100) 00860 if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) { 00861 $this->sectiontitle = $request->getVal( 'preloadtitle' ); 00862 // Once wpSummary isn't being use for setting section titles, we should delete this. 00863 $this->summary = $request->getVal( 'preloadtitle' ); 00864 } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) { 00865 $this->summary = $request->getText( 'summary' ); 00866 if ( $this->summary !== '' ) { 00867 $this->hasPresetSummary = true; 00868 } 00869 } 00870 00871 if ( $request->getVal( 'minor' ) ) { 00872 $this->minoredit = true; 00873 } 00874 } 00875 00876 $this->oldid = $request->getInt( 'oldid' ); 00877 00878 $this->bot = $request->getBool( 'bot', true ); 00879 $this->nosummary = $request->getBool( 'nosummary' ); 00880 00881 // May be overridden by revision. 00882 $this->contentModel = $request->getText( 'model', $this->contentModel ); 00883 // May be overridden by revision. 00884 $this->contentFormat = $request->getText( 'format', $this->contentFormat ); 00885 00886 if ( !ContentHandler::getForModelID( $this->contentModel ) 00887 ->isSupportedFormat( $this->contentFormat ) 00888 ) { 00889 throw new ErrorPageError( 00890 'editpage-notsupportedcontentformat-title', 00891 'editpage-notsupportedcontentformat-text', 00892 array( $this->contentFormat, ContentHandler::getLocalizedName( $this->contentModel ) ) 00893 ); 00894 } 00895 00902 $this->live = $request->getCheck( 'live' ); 00903 $this->editintro = $request->getText( 'editintro', 00904 // Custom edit intro for new sections 00905 $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' ); 00906 00907 // Allow extensions to modify form data 00908 wfRunHooks( 'EditPage::importFormData', array( $this, $request ) ); 00909 00910 wfProfileOut( __METHOD__ ); 00911 } 00912 00921 protected function importContentFormData( &$request ) { 00922 return; // Don't do anything, EditPage already extracted wpTextbox1 00923 } 00924 00930 function initialiseForm() { 00931 global $wgUser; 00932 $this->edittime = $this->mArticle->getTimestamp(); 00933 00934 $content = $this->getContentObject( false ); #TODO: track content object?! 00935 if ( $content === false ) { 00936 return false; 00937 } 00938 $this->textbox1 = $this->toEditText( $content ); 00939 00940 // activate checkboxes if user wants them to be always active 00941 # Sort out the "watch" checkbox 00942 if ( $wgUser->getOption( 'watchdefault' ) ) { 00943 # Watch all edits 00944 $this->watchthis = true; 00945 } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) { 00946 # Watch creations 00947 $this->watchthis = true; 00948 } elseif ( $wgUser->isWatched( $this->mTitle ) ) { 00949 # Already watched 00950 $this->watchthis = true; 00951 } 00952 if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) { 00953 $this->minoredit = true; 00954 } 00955 if ( $this->textbox1 === false ) { 00956 return false; 00957 } 00958 return true; 00959 } 00960 00968 protected function getContentObject( $def_content = null ) { 00969 global $wgOut, $wgRequest, $wgUser, $wgContLang; 00970 00971 wfProfileIn( __METHOD__ ); 00972 00973 $content = false; 00974 00975 // For message page not locally set, use the i18n message. 00976 // For other non-existent articles, use preload text if any. 00977 if ( !$this->mTitle->exists() || $this->section == 'new' ) { 00978 if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) { 00979 # If this is a system message, get the default text. 00980 $msg = $this->mTitle->getDefaultMessageText(); 00981 00982 $content = $this->toEditContent( $msg ); 00983 } 00984 if ( $content === false ) { 00985 # If requested, preload some text. 00986 $preload = $wgRequest->getVal( 'preload', 00987 // Custom preload text for new sections 00988 $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' ); 00989 $params = $wgRequest->getArray( 'preloadparams', array() ); 00990 00991 $content = $this->getPreloadedContent( $preload, $params ); 00992 } 00993 // For existing pages, get text based on "undo" or section parameters. 00994 } else { 00995 if ( $this->section != '' ) { 00996 // Get section edit text (returns $def_text for invalid sections) 00997 $orig = $this->getOriginalContent( $wgUser ); 00998 $content = $orig ? $orig->getSection( $this->section ) : null; 00999 01000 if ( !$content ) { 01001 $content = $def_content; 01002 } 01003 } else { 01004 $undoafter = $wgRequest->getInt( 'undoafter' ); 01005 $undo = $wgRequest->getInt( 'undo' ); 01006 01007 if ( $undo > 0 && $undoafter > 0 ) { 01008 01009 $undorev = Revision::newFromId( $undo ); 01010 $oldrev = Revision::newFromId( $undoafter ); 01011 01012 # Sanity check, make sure it's the right page, 01013 # the revisions exist and they were not deleted. 01014 # Otherwise, $content will be left as-is. 01015 if ( !is_null( $undorev ) && !is_null( $oldrev ) && 01016 !$undorev->isDeleted( Revision::DELETED_TEXT ) && 01017 !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) { 01018 01019 $content = $this->mArticle->getUndoContent( $undorev, $oldrev ); 01020 01021 if ( $content === false ) { 01022 # Warn the user that something went wrong 01023 $undoMsg = 'failure'; 01024 } else { 01025 $oldContent = $this->mArticle->getPage()->getContent( Revision::RAW ); 01026 $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang ); 01027 $newContent = $content->preSaveTransform( $this->mTitle, $wgUser, $popts ); 01028 01029 if ( $newContent->equals( $oldContent ) ) { 01030 # Tell the user that the undo results in no change, 01031 # i.e. the revisions were already undone. 01032 $undoMsg = 'nochange'; 01033 $content = false; 01034 } else { 01035 # Inform the user of our success and set an automatic edit summary 01036 $undoMsg = 'success'; 01037 01038 # If we just undid one rev, use an autosummary 01039 $firstrev = $oldrev->getNext(); 01040 if ( $firstrev && $firstrev->getId() == $undo ) { 01041 $userText = $undorev->getUserText(); 01042 if ( $userText === '' ) { 01043 $undoSummary = wfMessage( 01044 'undo-summary-username-hidden', 01045 $undo 01046 )->inContentLanguage()->text(); 01047 } else { 01048 $undoSummary = wfMessage( 01049 'undo-summary', 01050 $undo, 01051 $userText 01052 )->inContentLanguage()->text(); 01053 } 01054 if ( $this->summary === '' ) { 01055 $this->summary = $undoSummary; 01056 } else { 01057 $this->summary = $undoSummary . wfMessage( 'colon-separator' ) 01058 ->inContentLanguage()->text() . $this->summary; 01059 } 01060 $this->undidRev = $undo; 01061 } 01062 $this->formtype = 'diff'; 01063 } 01064 } 01065 } else { 01066 // Failed basic sanity checks. 01067 // Older revisions may have been removed since the link 01068 // was created, or we may simply have got bogus input. 01069 $undoMsg = 'norev'; 01070 } 01071 01072 // Messages: undo-success, undo-failure, undo-norev, undo-nochange 01073 $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}"; 01074 $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" . 01075 wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true ); 01076 } 01077 01078 if ( $content === false ) { 01079 $content = $this->getOriginalContent( $wgUser ); 01080 } 01081 } 01082 } 01083 01084 wfProfileOut( __METHOD__ ); 01085 return $content; 01086 } 01087 01103 private function getOriginalContent( User $user ) { 01104 if ( $this->section == 'new' ) { 01105 return $this->getCurrentContent(); 01106 } 01107 $revision = $this->mArticle->getRevisionFetched(); 01108 if ( $revision === null ) { 01109 if ( !$this->contentModel ) { 01110 $this->contentModel = $this->getTitle()->getContentModel(); 01111 } 01112 $handler = ContentHandler::getForModelID( $this->contentModel ); 01113 01114 return $handler->makeEmptyContent(); 01115 } 01116 $content = $revision->getContent( Revision::FOR_THIS_USER, $user ); 01117 return $content; 01118 } 01119 01128 protected function getCurrentContent() { 01129 $rev = $this->mArticle->getRevision(); 01130 $content = $rev ? $rev->getContent( Revision::RAW ) : null; 01131 01132 if ( $content === false || $content === null ) { 01133 if ( !$this->contentModel ) { 01134 $this->contentModel = $this->getTitle()->getContentModel(); 01135 } 01136 $handler = ContentHandler::getForModelID( $this->contentModel ); 01137 01138 return $handler->makeEmptyContent(); 01139 } else { 01140 # nasty side-effect, but needed for consistency 01141 $this->contentModel = $rev->getContentModel(); 01142 $this->contentFormat = $rev->getContentFormat(); 01143 01144 return $content; 01145 } 01146 } 01147 01155 public function setPreloadedContent( Content $content ) { 01156 $this->mPreloadContent = $content; 01157 } 01158 01170 protected function getPreloadedContent( $preload, $params = array() ) { 01171 global $wgUser; 01172 01173 if ( !empty( $this->mPreloadContent ) ) { 01174 return $this->mPreloadContent; 01175 } 01176 01177 $handler = ContentHandler::getForTitle( $this->getTitle() ); 01178 01179 if ( $preload === '' ) { 01180 return $handler->makeEmptyContent(); 01181 } 01182 01183 $title = Title::newFromText( $preload ); 01184 # Check for existence to avoid getting MediaWiki:Noarticletext 01185 if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) { 01186 //TODO: somehow show a warning to the user! 01187 return $handler->makeEmptyContent(); 01188 } 01189 01190 $page = WikiPage::factory( $title ); 01191 if ( $page->isRedirect() ) { 01192 $title = $page->getRedirectTarget(); 01193 # Same as before 01194 if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) { 01195 //TODO: somehow show a warning to the user! 01196 return $handler->makeEmptyContent(); 01197 } 01198 $page = WikiPage::factory( $title ); 01199 } 01200 01201 $parserOptions = ParserOptions::newFromUser( $wgUser ); 01202 $content = $page->getContent( Revision::RAW ); 01203 01204 if ( !$content ) { 01205 //TODO: somehow show a warning to the user! 01206 return $handler->makeEmptyContent(); 01207 } 01208 01209 if ( $content->getModel() !== $handler->getModelID() ) { 01210 $converted = $content->convert( $handler->getModelID() ); 01211 01212 if ( !$converted ) { 01213 //TODO: somehow show a warning to the user! 01214 wfDebug( "Attempt to preload incompatible content: " 01215 . "can't convert " . $content->getModel() 01216 . " to " . $handler->getModelID() ); 01217 01218 return $handler->makeEmptyContent(); 01219 } 01220 01221 $content = $converted; 01222 } 01223 01224 return $content->preloadTransform( $title, $parserOptions, $params ); 01225 } 01226 01234 function tokenOk( &$request ) { 01235 global $wgUser; 01236 $token = $request->getVal( 'wpEditToken' ); 01237 $this->mTokenOk = $wgUser->matchEditToken( $token ); 01238 $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token ); 01239 return $this->mTokenOk; 01240 } 01241 01258 protected function setPostEditCookie( $statusValue ) { 01259 $revisionId = $this->mArticle->getLatest(); 01260 $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId; 01261 01262 $val = 'saved'; 01263 if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) { 01264 $val = 'created'; 01265 } elseif ( $this->oldid ) { 01266 $val = 'restored'; 01267 } 01268 01269 $response = RequestContext::getMain()->getRequest()->response(); 01270 $response->setcookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION, array( 01271 'httpOnly' => false, 01272 ) ); 01273 } 01274 01280 public function attemptSave() { 01281 global $wgUser; 01282 01283 $resultDetails = false; 01284 # Allow bots to exempt some edits from bot flagging 01285 $bot = $wgUser->isAllowed( 'bot' ) && $this->bot; 01286 $status = $this->internalAttemptSave( $resultDetails, $bot ); 01287 01288 return $this->handleStatus( $status, $resultDetails ); 01289 } 01290 01300 private function handleStatus( Status $status, $resultDetails ) { 01301 global $wgUser, $wgOut; 01302 01307 if ( $status->value == self::AS_SUCCESS_UPDATE 01308 || $status->value == self::AS_SUCCESS_NEW_ARTICLE 01309 ) { 01310 $this->didSave = true; 01311 if ( !$resultDetails['nullEdit'] ) { 01312 $this->setPostEditCookie( $status->value ); 01313 } 01314 } 01315 01316 switch ( $status->value ) { 01317 case self::AS_HOOK_ERROR_EXPECTED: 01318 case self::AS_CONTENT_TOO_BIG: 01319 case self::AS_ARTICLE_WAS_DELETED: 01320 case self::AS_CONFLICT_DETECTED: 01321 case self::AS_SUMMARY_NEEDED: 01322 case self::AS_TEXTBOX_EMPTY: 01323 case self::AS_MAX_ARTICLE_SIZE_EXCEEDED: 01324 case self::AS_END: 01325 case self::AS_BLANK_ARTICLE: 01326 return true; 01327 01328 case self::AS_HOOK_ERROR: 01329 return false; 01330 01331 case self::AS_PARSE_ERROR: 01332 $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>' ); 01333 return true; 01334 01335 case self::AS_SUCCESS_NEW_ARTICLE: 01336 $query = $resultDetails['redirect'] ? 'redirect=no' : ''; 01337 $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : ''; 01338 $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor ); 01339 return false; 01340 01341 case self::AS_SUCCESS_UPDATE: 01342 $extraQuery = ''; 01343 $sectionanchor = $resultDetails['sectionanchor']; 01344 01345 // Give extensions a chance to modify URL query on update 01346 wfRunHooks( 01347 'ArticleUpdateBeforeRedirect', 01348 array( $this->mArticle, &$sectionanchor, &$extraQuery ) 01349 ); 01350 01351 if ( $resultDetails['redirect'] ) { 01352 if ( $extraQuery == '' ) { 01353 $extraQuery = 'redirect=no'; 01354 } else { 01355 $extraQuery = 'redirect=no&' . $extraQuery; 01356 } 01357 } 01358 $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor ); 01359 return false; 01360 01361 case self::AS_SPAM_ERROR: 01362 $this->spamPageWithContent( $resultDetails['spam'] ); 01363 return false; 01364 01365 case self::AS_BLOCKED_PAGE_FOR_USER: 01366 throw new UserBlockedError( $wgUser->getBlock() ); 01367 01368 case self::AS_IMAGE_REDIRECT_ANON: 01369 case self::AS_IMAGE_REDIRECT_LOGGED: 01370 throw new PermissionsError( 'upload' ); 01371 01372 case self::AS_READ_ONLY_PAGE_ANON: 01373 case self::AS_READ_ONLY_PAGE_LOGGED: 01374 throw new PermissionsError( 'edit' ); 01375 01376 case self::AS_READ_ONLY_PAGE: 01377 throw new ReadOnlyError; 01378 01379 case self::AS_RATE_LIMITED: 01380 throw new ThrottledError(); 01381 01382 case self::AS_NO_CREATE_PERMISSION: 01383 $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage'; 01384 throw new PermissionsError( $permission ); 01385 01386 default: 01387 // We don't recognize $status->value. The only way that can happen 01388 // is if an extension hook aborted from inside ArticleSave. 01389 // Render the status object into $this->hookError 01390 // FIXME this sucks, we should just use the Status object throughout 01391 $this->hookError = '<div class="error">' . $status->getWikitext() . 01392 '</div>'; 01393 return true; 01394 } 01395 } 01396 01406 protected function runPostMergeFilters( Content $content, Status $status, User $user ) { 01407 // Run old style post-section-merge edit filter 01408 if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged', 01409 array( $this, $content, &$this->hookError, $this->summary ) ) ) { 01410 01411 # Error messages etc. could be handled within the hook... 01412 $status->fatal( 'hookaborted' ); 01413 $status->value = self::AS_HOOK_ERROR; 01414 return false; 01415 } elseif ( $this->hookError != '' ) { 01416 # ...or the hook could be expecting us to produce an error 01417 $status->fatal( 'hookaborted' ); 01418 $status->value = self::AS_HOOK_ERROR_EXPECTED; 01419 return false; 01420 } 01421 01422 // Run new style post-section-merge edit filter 01423 if ( !wfRunHooks( 'EditFilterMergedContent', 01424 array( $this->mArticle->getContext(), $content, $status, $this->summary, 01425 $user, $this->minoredit ) ) ) { 01426 01427 # Error messages etc. could be handled within the hook... 01428 // XXX: $status->value may already be something informative... 01429 $this->hookError = $status->getWikiText(); 01430 $status->fatal( 'hookaborted' ); 01431 $status->value = self::AS_HOOK_ERROR; 01432 return false; 01433 } elseif ( !$status->isOK() ) { 01434 # ...or the hook could be expecting us to produce an error 01435 // FIXME this sucks, we should just use the Status object throughout 01436 $this->hookError = $status->getWikiText(); 01437 $status->fatal( 'hookaborted' ); 01438 $status->value = self::AS_HOOK_ERROR_EXPECTED; 01439 return false; 01440 } 01441 01442 return true; 01443 } 01444 01451 private function newSectionSummary( &$sectionanchor = null ) { 01452 global $wgParser; 01453 01454 if ( $this->sectiontitle !== '' ) { 01455 $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle ); 01456 // If no edit summary was specified, create one automatically from the section 01457 // title and have it link to the new section. Otherwise, respect the summary as 01458 // passed. 01459 if ( $this->summary === '' ) { 01460 $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle ); 01461 return wfMessage( 'newsectionsummary' ) 01462 ->rawParams( $cleanSectionTitle )->inContentLanguage()->text(); 01463 } 01464 } elseif ( $this->summary !== '' ) { 01465 $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary ); 01466 # This is a new section, so create a link to the new section 01467 # in the revision summary. 01468 $cleanSummary = $wgParser->stripSectionName( $this->summary ); 01469 return wfMessage( 'newsectionsummary' ) 01470 ->rawParams( $cleanSummary )->inContentLanguage()->text(); 01471 } 01472 return $this->summary; 01473 } 01474 01499 function internalAttemptSave( &$result, $bot = false ) { 01500 global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize; 01501 01502 $status = Status::newGood(); 01503 01504 wfProfileIn( __METHOD__ ); 01505 wfProfileIn( __METHOD__ . '-checks' ); 01506 01507 if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) { 01508 wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" ); 01509 $status->fatal( 'hookaborted' ); 01510 $status->value = self::AS_HOOK_ERROR; 01511 wfProfileOut( __METHOD__ . '-checks' ); 01512 wfProfileOut( __METHOD__ ); 01513 return $status; 01514 } 01515 01516 $spam = $wgRequest->getText( 'wpAntispam' ); 01517 if ( $spam !== '' ) { 01518 wfDebugLog( 01519 'SimpleAntiSpam', 01520 $wgUser->getName() . 01521 ' editing "' . 01522 $this->mTitle->getPrefixedText() . 01523 '" submitted bogus field "' . 01524 $spam . 01525 '"' 01526 ); 01527 $status->fatal( 'spamprotectionmatch', false ); 01528 $status->value = self::AS_SPAM_ERROR; 01529 wfProfileOut( __METHOD__ . '-checks' ); 01530 wfProfileOut( __METHOD__ ); 01531 return $status; 01532 } 01533 01534 try { 01535 # Construct Content object 01536 $textbox_content = $this->toEditContent( $this->textbox1 ); 01537 } catch ( MWContentSerializationException $ex ) { 01538 $status->fatal( 01539 'content-failed-to-parse', 01540 $this->contentModel, 01541 $this->contentFormat, 01542 $ex->getMessage() 01543 ); 01544 $status->value = self::AS_PARSE_ERROR; 01545 wfProfileOut( __METHOD__ . '-checks' ); 01546 wfProfileOut( __METHOD__ ); 01547 return $status; 01548 } 01549 01550 # Check image redirect 01551 if ( $this->mTitle->getNamespace() == NS_FILE && 01552 $textbox_content->isRedirect() && 01553 !$wgUser->isAllowed( 'upload' ) 01554 ) { 01555 $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED; 01556 $status->setResult( false, $code ); 01557 01558 wfProfileOut( __METHOD__ . '-checks' ); 01559 wfProfileOut( __METHOD__ ); 01560 01561 return $status; 01562 } 01563 01564 # Check for spam 01565 $match = self::matchSummarySpamRegex( $this->summary ); 01566 if ( $match === false && $this->section == 'new' ) { 01567 # $wgSpamRegex is enforced on this new heading/summary because, unlike 01568 # regular summaries, it is added to the actual wikitext. 01569 if ( $this->sectiontitle !== '' ) { 01570 # This branch is taken when the API is used with the 'sectiontitle' parameter. 01571 $match = self::matchSpamRegex( $this->sectiontitle ); 01572 } else { 01573 # This branch is taken when the "Add Topic" user interface is used, or the API 01574 # is used with the 'summary' parameter. 01575 $match = self::matchSpamRegex( $this->summary ); 01576 } 01577 } 01578 if ( $match === false ) { 01579 $match = self::matchSpamRegex( $this->textbox1 ); 01580 } 01581 if ( $match !== false ) { 01582 $result['spam'] = $match; 01583 $ip = $wgRequest->getIP(); 01584 $pdbk = $this->mTitle->getPrefixedDBkey(); 01585 $match = str_replace( "\n", '', $match ); 01586 wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" ); 01587 $status->fatal( 'spamprotectionmatch', $match ); 01588 $status->value = self::AS_SPAM_ERROR; 01589 wfProfileOut( __METHOD__ . '-checks' ); 01590 wfProfileOut( __METHOD__ ); 01591 return $status; 01592 } 01593 if ( !wfRunHooks( 01594 'EditFilter', 01595 array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) 01596 ) { 01597 # Error messages etc. could be handled within the hook... 01598 $status->fatal( 'hookaborted' ); 01599 $status->value = self::AS_HOOK_ERROR; 01600 wfProfileOut( __METHOD__ . '-checks' ); 01601 wfProfileOut( __METHOD__ ); 01602 return $status; 01603 } elseif ( $this->hookError != '' ) { 01604 # ...or the hook could be expecting us to produce an error 01605 $status->fatal( 'hookaborted' ); 01606 $status->value = self::AS_HOOK_ERROR_EXPECTED; 01607 wfProfileOut( __METHOD__ . '-checks' ); 01608 wfProfileOut( __METHOD__ ); 01609 return $status; 01610 } 01611 01612 if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) { 01613 // Auto-block user's IP if the account was "hard" blocked 01614 $wgUser->spreadAnyEditBlock(); 01615 # Check block state against master, thus 'false'. 01616 $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER ); 01617 wfProfileOut( __METHOD__ . '-checks' ); 01618 wfProfileOut( __METHOD__ ); 01619 return $status; 01620 } 01621 01622 $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 ); 01623 if ( $this->kblength > $wgMaxArticleSize ) { 01624 // Error will be displayed by showEditForm() 01625 $this->tooBig = true; 01626 $status->setResult( false, self::AS_CONTENT_TOO_BIG ); 01627 wfProfileOut( __METHOD__ . '-checks' ); 01628 wfProfileOut( __METHOD__ ); 01629 return $status; 01630 } 01631 01632 if ( !$wgUser->isAllowed( 'edit' ) ) { 01633 if ( $wgUser->isAnon() ) { 01634 $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON ); 01635 wfProfileOut( __METHOD__ . '-checks' ); 01636 wfProfileOut( __METHOD__ ); 01637 return $status; 01638 } else { 01639 $status->fatal( 'readonlytext' ); 01640 $status->value = self::AS_READ_ONLY_PAGE_LOGGED; 01641 wfProfileOut( __METHOD__ . '-checks' ); 01642 wfProfileOut( __METHOD__ ); 01643 return $status; 01644 } 01645 } 01646 01647 if ( wfReadOnly() ) { 01648 $status->fatal( 'readonlytext' ); 01649 $status->value = self::AS_READ_ONLY_PAGE; 01650 wfProfileOut( __METHOD__ . '-checks' ); 01651 wfProfileOut( __METHOD__ ); 01652 return $status; 01653 } 01654 if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 ) ) { 01655 $status->fatal( 'actionthrottledtext' ); 01656 $status->value = self::AS_RATE_LIMITED; 01657 wfProfileOut( __METHOD__ . '-checks' ); 01658 wfProfileOut( __METHOD__ ); 01659 return $status; 01660 } 01661 01662 # If the article has been deleted while editing, don't save it without 01663 # confirmation 01664 if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) { 01665 $status->setResult( false, self::AS_ARTICLE_WAS_DELETED ); 01666 wfProfileOut( __METHOD__ . '-checks' ); 01667 wfProfileOut( __METHOD__ ); 01668 return $status; 01669 } 01670 01671 wfProfileOut( __METHOD__ . '-checks' ); 01672 01673 # Load the page data from the master. If anything changes in the meantime, 01674 # we detect it by using page_latest like a token in a 1 try compare-and-swap. 01675 $this->mArticle->loadPageData( 'fromdbmaster' ); 01676 $new = !$this->mArticle->exists(); 01677 01678 if ( $new ) { 01679 // Late check for create permission, just in case *PARANOIA* 01680 if ( !$this->mTitle->userCan( 'create', $wgUser ) ) { 01681 $status->fatal( 'nocreatetext' ); 01682 $status->value = self::AS_NO_CREATE_PERMISSION; 01683 wfDebug( __METHOD__ . ": no create permission\n" ); 01684 wfProfileOut( __METHOD__ ); 01685 return $status; 01686 } 01687 01688 // Don't save a new page if it's blank or if it's a MediaWiki: 01689 // message with content equivalent to default (allow empty pages 01690 // in this case to disable messages, see bug 50124) 01691 $defaultMessageText = $this->mTitle->getDefaultMessageText(); 01692 if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false ) { 01693 $defaultText = $defaultMessageText; 01694 } else { 01695 $defaultText = ''; 01696 } 01697 01698 if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) { 01699 $this->blankArticle = true; 01700 $status->fatal( 'blankarticle' ); 01701 $status->setResult( false, self::AS_BLANK_ARTICLE ); 01702 wfProfileOut( __METHOD__ ); 01703 return $status; 01704 } 01705 01706 if ( !$this->runPostMergeFilters( $textbox_content, $status, $wgUser ) ) { 01707 wfProfileOut( __METHOD__ ); 01708 return $status; 01709 } 01710 01711 $content = $textbox_content; 01712 01713 $result['sectionanchor'] = ''; 01714 if ( $this->section == 'new' ) { 01715 if ( $this->sectiontitle !== '' ) { 01716 // Insert the section title above the content. 01717 $content = $content->addSectionHeader( $this->sectiontitle ); 01718 } elseif ( $this->summary !== '' ) { 01719 // Insert the section title above the content. 01720 $content = $content->addSectionHeader( $this->summary ); 01721 } 01722 $this->summary = $this->newSectionSummary( $result['sectionanchor'] ); 01723 } 01724 01725 $status->value = self::AS_SUCCESS_NEW_ARTICLE; 01726 01727 } else { # not $new 01728 01729 # Article exists. Check for edit conflict. 01730 01731 $this->mArticle->clear(); # Force reload of dates, etc. 01732 $timestamp = $this->mArticle->getTimestamp(); 01733 01734 wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" ); 01735 01736 if ( $timestamp != $this->edittime ) { 01737 $this->isConflict = true; 01738 if ( $this->section == 'new' ) { 01739 if ( $this->mArticle->getUserText() == $wgUser->getName() && 01740 $this->mArticle->getComment() == $this->newSectionSummary() 01741 ) { 01742 // Probably a duplicate submission of a new comment. 01743 // This can happen when squid resends a request after 01744 // a timeout but the first one actually went through. 01745 wfDebug( __METHOD__ 01746 . ": duplicate new section submission; trigger edit conflict!\n" ); 01747 } else { 01748 // New comment; suppress conflict. 01749 $this->isConflict = false; 01750 wfDebug( __METHOD__ . ": conflict suppressed; new section\n" ); 01751 } 01752 } elseif ( $this->section == '' 01753 && Revision::userWasLastToEdit( 01754 DB_MASTER, $this->mTitle->getArticleID(), 01755 $wgUser->getId(), $this->edittime 01756 ) 01757 ) { 01758 # Suppress edit conflict with self, except for section edits where merging is required. 01759 wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" ); 01760 $this->isConflict = false; 01761 } 01762 } 01763 01764 // If sectiontitle is set, use it, otherwise use the summary as the section title. 01765 if ( $this->sectiontitle !== '' ) { 01766 $sectionTitle = $this->sectiontitle; 01767 } else { 01768 $sectionTitle = $this->summary; 01769 } 01770 01771 $content = null; 01772 01773 if ( $this->isConflict ) { 01774 wfDebug( __METHOD__ 01775 . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'" 01776 . " (article time '{$timestamp}')\n" ); 01777 01778 $content = $this->mArticle->replaceSectionContent( 01779 $this->section, 01780 $textbox_content, 01781 $sectionTitle, 01782 $this->edittime 01783 ); 01784 } else { 01785 wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" ); 01786 $content = $this->mArticle->replaceSectionContent( 01787 $this->section, 01788 $textbox_content, 01789 $sectionTitle 01790 ); 01791 } 01792 01793 if ( is_null( $content ) ) { 01794 wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" ); 01795 $this->isConflict = true; 01796 $content = $textbox_content; // do not try to merge here! 01797 } elseif ( $this->isConflict ) { 01798 # Attempt merge 01799 if ( $this->mergeChangesIntoContent( $content ) ) { 01800 // Successful merge! Maybe we should tell the user the good news? 01801 $this->isConflict = false; 01802 wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" ); 01803 } else { 01804 $this->section = ''; 01805 $this->textbox1 = ContentHandler::getContentText( $content ); 01806 wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" ); 01807 } 01808 } 01809 01810 if ( $this->isConflict ) { 01811 $status->setResult( false, self::AS_CONFLICT_DETECTED ); 01812 wfProfileOut( __METHOD__ ); 01813 return $status; 01814 } 01815 01816 if ( !$this->runPostMergeFilters( $content, $status, $wgUser ) ) { 01817 wfProfileOut( __METHOD__ ); 01818 return $status; 01819 } 01820 01821 if ( $this->section == 'new' ) { 01822 // Handle the user preference to force summaries here 01823 if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) { 01824 $this->missingSummary = true; 01825 $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh 01826 $status->value = self::AS_SUMMARY_NEEDED; 01827 wfProfileOut( __METHOD__ ); 01828 return $status; 01829 } 01830 01831 // Do not allow the user to post an empty comment 01832 if ( $this->textbox1 == '' ) { 01833 $this->missingComment = true; 01834 $status->fatal( 'missingcommenttext' ); 01835 $status->value = self::AS_TEXTBOX_EMPTY; 01836 wfProfileOut( __METHOD__ ); 01837 return $status; 01838 } 01839 } elseif ( !$this->allowBlankSummary 01840 && !$content->equals( $this->getOriginalContent( $wgUser ) ) 01841 && !$content->isRedirect() 01842 && md5( $this->summary ) == $this->autoSumm 01843 ) { 01844 $this->missingSummary = true; 01845 $status->fatal( 'missingsummary' ); 01846 $status->value = self::AS_SUMMARY_NEEDED; 01847 wfProfileOut( __METHOD__ ); 01848 return $status; 01849 } 01850 01851 # All's well 01852 wfProfileIn( __METHOD__ . '-sectionanchor' ); 01853 $sectionanchor = ''; 01854 if ( $this->section == 'new' ) { 01855 $this->summary = $this->newSectionSummary( $sectionanchor ); 01856 } elseif ( $this->section != '' ) { 01857 # Try to get a section anchor from the section source, redirect 01858 # to edited section if header found. 01859 # XXX: Might be better to integrate this into Article::replaceSection 01860 # for duplicate heading checking and maybe parsing. 01861 $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches ); 01862 # We can't deal with anchors, includes, html etc in the header for now, 01863 # headline would need to be parsed to improve this. 01864 if ( $hasmatch && strlen( $matches[2] ) > 0 ) { 01865 $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] ); 01866 } 01867 } 01868 $result['sectionanchor'] = $sectionanchor; 01869 wfProfileOut( __METHOD__ . '-sectionanchor' ); 01870 01871 // Save errors may fall down to the edit form, but we've now 01872 // merged the section into full text. Clear the section field 01873 // so that later submission of conflict forms won't try to 01874 // replace that into a duplicated mess. 01875 $this->textbox1 = $this->toEditText( $content ); 01876 $this->section = ''; 01877 01878 $status->value = self::AS_SUCCESS_UPDATE; 01879 } 01880 01881 // Check for length errors again now that the section is merged in 01882 $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 ); 01883 if ( $this->kblength > $wgMaxArticleSize ) { 01884 $this->tooBig = true; 01885 $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED ); 01886 wfProfileOut( __METHOD__ ); 01887 return $status; 01888 } 01889 01890 $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY | 01891 ( $new ? EDIT_NEW : EDIT_UPDATE ) | 01892 ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) | 01893 ( $bot ? EDIT_FORCE_BOT : 0 ); 01894 01895 $doEditStatus = $this->mArticle->doEditContent( 01896 $content, 01897 $this->summary, 01898 $flags, 01899 false, 01900 null, 01901 $content->getDefaultFormat() 01902 ); 01903 01904 if ( !$doEditStatus->isOK() ) { 01905 // Failure from doEdit() 01906 // Show the edit conflict page for certain recognized errors from doEdit(), 01907 // but don't show it for errors from extension hooks 01908 $errors = $doEditStatus->getErrorsArray(); 01909 if ( in_array( $errors[0][0], 01910 array( 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ) ) 01911 ) { 01912 $this->isConflict = true; 01913 // Destroys data doEdit() put in $status->value but who cares 01914 $doEditStatus->value = self::AS_END; 01915 } 01916 wfProfileOut( __METHOD__ ); 01917 return $doEditStatus; 01918 } 01919 01920 $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' ); 01921 if ( $result['nullEdit'] ) { 01922 // We don't know if it was a null edit until now, so increment here 01923 $wgUser->pingLimiter( 'linkpurge' ); 01924 } 01925 $result['redirect'] = $content->isRedirect(); 01926 $this->updateWatchlist(); 01927 wfProfileOut( __METHOD__ ); 01928 return $status; 01929 } 01930 01934 protected function updateWatchlist() { 01935 global $wgUser; 01936 01937 if ( $wgUser->isLoggedIn() 01938 && $this->watchthis != $wgUser->isWatched( $this->mTitle, WatchedItem::IGNORE_USER_RIGHTS ) 01939 ) { 01940 $fname = __METHOD__; 01941 $title = $this->mTitle; 01942 $watch = $this->watchthis; 01943 01944 // Do this in its own transaction to reduce contention... 01945 $dbw = wfGetDB( DB_MASTER ); 01946 $dbw->onTransactionIdle( function () use ( $dbw, $title, $watch, $wgUser, $fname ) { 01947 WatchAction::doWatchOrUnwatch( $watch, $title, $wgUser ); 01948 } ); 01949 } 01950 } 01951 01963 private function mergeChangesIntoContent( &$editContent ) { 01964 wfProfileIn( __METHOD__ ); 01965 01966 $db = wfGetDB( DB_MASTER ); 01967 01968 // This is the revision the editor started from 01969 $baseRevision = $this->getBaseRevision(); 01970 $baseContent = $baseRevision ? $baseRevision->getContent() : null; 01971 01972 if ( is_null( $baseContent ) ) { 01973 wfProfileOut( __METHOD__ ); 01974 return false; 01975 } 01976 01977 // The current state, we want to merge updates into it 01978 $currentRevision = Revision::loadFromTitle( $db, $this->mTitle ); 01979 $currentContent = $currentRevision ? $currentRevision->getContent() : null; 01980 01981 if ( is_null( $currentContent ) ) { 01982 wfProfileOut( __METHOD__ ); 01983 return false; 01984 } 01985 01986 $handler = ContentHandler::getForModelID( $baseContent->getModel() ); 01987 01988 $result = $handler->merge3( $baseContent, $editContent, $currentContent ); 01989 01990 if ( $result ) { 01991 $editContent = $result; 01992 wfProfileOut( __METHOD__ ); 01993 return true; 01994 } 01995 01996 wfProfileOut( __METHOD__ ); 01997 return false; 01998 } 01999 02003 function getBaseRevision() { 02004 if ( !$this->mBaseRevision ) { 02005 $db = wfGetDB( DB_MASTER ); 02006 $this->mBaseRevision = Revision::loadFromTimestamp( 02007 $db, $this->mTitle, $this->edittime ); 02008 } 02009 return $this->mBaseRevision; 02010 } 02011 02019 public static function matchSpamRegex( $text ) { 02020 global $wgSpamRegex; 02021 // For back compatibility, $wgSpamRegex may be a single string or an array of regexes. 02022 $regexes = (array)$wgSpamRegex; 02023 return self::matchSpamRegexInternal( $text, $regexes ); 02024 } 02025 02033 public static function matchSummarySpamRegex( $text ) { 02034 global $wgSummarySpamRegex; 02035 $regexes = (array)$wgSummarySpamRegex; 02036 return self::matchSpamRegexInternal( $text, $regexes ); 02037 } 02038 02044 protected static function matchSpamRegexInternal( $text, $regexes ) { 02045 foreach ( $regexes as $regex ) { 02046 $matches = array(); 02047 if ( preg_match( $regex, $text, $matches ) ) { 02048 return $matches[0]; 02049 } 02050 } 02051 return false; 02052 } 02053 02054 function setHeaders() { 02055 global $wgOut, $wgUser; 02056 02057 $wgOut->addModules( 'mediawiki.action.edit' ); 02058 $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' ); 02059 02060 if ( $wgUser->getOption( 'uselivepreview', false ) ) { 02061 $wgOut->addModules( 'mediawiki.action.edit.preview' ); 02062 } 02063 02064 if ( $wgUser->getOption( 'useeditwarning', false ) ) { 02065 $wgOut->addModules( 'mediawiki.action.edit.editWarning' ); 02066 } 02067 02068 $wgOut->setRobotPolicy( 'noindex,nofollow' ); 02069 02070 # Enabled article-related sidebar, toplinks, etc. 02071 $wgOut->setArticleRelated( true ); 02072 02073 $contextTitle = $this->getContextTitle(); 02074 if ( $this->isConflict ) { 02075 $msg = 'editconflict'; 02076 } elseif ( $contextTitle->exists() && $this->section != '' ) { 02077 $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection'; 02078 } else { 02079 $msg = $contextTitle->exists() 02080 || ( $contextTitle->getNamespace() == NS_MEDIAWIKI 02081 && $contextTitle->getDefaultMessageText() !== false 02082 ) 02083 ? 'editing' 02084 : 'creating'; 02085 } 02086 02087 # Use the title defined by DISPLAYTITLE magic word when present 02088 $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false; 02089 if ( $displayTitle === false ) { 02090 $displayTitle = $contextTitle->getPrefixedText(); 02091 } 02092 $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) ); 02093 } 02094 02098 protected function showIntro() { 02099 global $wgOut, $wgUser; 02100 if ( $this->suppressIntro ) { 02101 return; 02102 } 02103 02104 $namespace = $this->mTitle->getNamespace(); 02105 02106 if ( $namespace == NS_MEDIAWIKI ) { 02107 # Show a warning if editing an interface message 02108 $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' ); 02109 } elseif ( $namespace == NS_FILE ) { 02110 # Show a hint to shared repo 02111 $file = wfFindFile( $this->mTitle ); 02112 if ( $file && !$file->isLocal() ) { 02113 $descUrl = $file->getDescriptionUrl(); 02114 # there must be a description url to show a hint to shared repo 02115 if ( $descUrl ) { 02116 if ( !$this->mTitle->exists() ) { 02117 $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", array( 02118 'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl 02119 ) ); 02120 } else { 02121 $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", array( 02122 'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl 02123 ) ); 02124 } 02125 } 02126 } 02127 } 02128 02129 # Show a warning message when someone creates/edits a user (talk) page but the user does not exist 02130 # Show log extract when the user is currently blocked 02131 if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) { 02132 $parts = explode( '/', $this->mTitle->getText(), 2 ); 02133 $username = $parts[0]; 02134 $user = User::newFromName( $username, false /* allow IP users*/ ); 02135 $ip = User::isIP( $username ); 02136 $block = Block::newFromTarget( $user, $user ); 02137 if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist 02138 $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>", 02139 array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) ); 02140 } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) { # Show log extract if the user is currently blocked 02141 LogEventsList::showLogExtract( 02142 $wgOut, 02143 'block', 02144 MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(), 02145 '', 02146 array( 02147 'lim' => 1, 02148 'showIfEmpty' => false, 02149 'msgKey' => array( 02150 'blocked-notice-logextract', 02151 $user->getName() # Support GENDER in notice 02152 ) 02153 ) 02154 ); 02155 } 02156 } 02157 # Try to add a custom edit intro, or use the standard one if this is not possible. 02158 if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) { 02159 $helpLink = wfExpandUrl( Skin::makeInternalOrExternalUrl( 02160 wfMessage( 'helppage' )->inContentLanguage()->text() 02161 ) ); 02162 if ( $wgUser->isLoggedIn() ) { 02163 $wgOut->wrapWikiMsg( 02164 // Suppress the external link icon, consider the help url an internal one 02165 "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>", 02166 array( 02167 'newarticletext', 02168 $helpLink 02169 ) 02170 ); 02171 } else { 02172 $wgOut->wrapWikiMsg( 02173 // Suppress the external link icon, consider the help url an internal one 02174 "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>", 02175 array( 02176 'newarticletextanon', 02177 $helpLink 02178 ) 02179 ); 02180 } 02181 } 02182 # Give a notice if the user is editing a deleted/moved page... 02183 if ( !$this->mTitle->exists() ) { 02184 LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle, 02185 '', 02186 array( 02187 'lim' => 10, 02188 'conds' => array( "log_action != 'revision'" ), 02189 'showIfEmpty' => false, 02190 'msgKey' => array( 'recreate-moveddeleted-warn' ) 02191 ) 02192 ); 02193 } 02194 } 02195 02201 protected function showCustomIntro() { 02202 if ( $this->editintro ) { 02203 $title = Title::newFromText( $this->editintro ); 02204 if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) { 02205 global $wgOut; 02206 // Added using template syntax, to take <noinclude>'s into account. 02207 $wgOut->addWikiTextTitleTidy( '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>', $this->mTitle ); 02208 return true; 02209 } 02210 } 02211 return false; 02212 } 02213 02232 protected function toEditText( $content ) { 02233 if ( $content === null || $content === false ) { 02234 return $content; 02235 } 02236 02237 if ( is_string( $content ) ) { 02238 return $content; 02239 } 02240 02241 if ( !$this->isSupportedContentModel( $content->getModel() ) ) { 02242 throw new MWException( 'This content model is not supported: ' 02243 . ContentHandler::getLocalizedName( $content->getModel() ) ); 02244 } 02245 02246 return $content->serialize( $this->contentFormat ); 02247 } 02248 02265 protected function toEditContent( $text ) { 02266 if ( $text === false || $text === null ) { 02267 return $text; 02268 } 02269 02270 $content = ContentHandler::makeContent( $text, $this->getTitle(), 02271 $this->contentModel, $this->contentFormat ); 02272 02273 if ( !$this->isSupportedContentModel( $content->getModel() ) ) { 02274 throw new MWException( 'This content model is not supported: ' 02275 . ContentHandler::getLocalizedName( $content->getModel() ) ); 02276 } 02277 02278 return $content; 02279 } 02280 02286 function showEditForm( $formCallback = null ) { 02287 global $wgOut, $wgUser; 02288 02289 wfProfileIn( __METHOD__ ); 02290 02291 # need to parse the preview early so that we know which templates are used, 02292 # otherwise users with "show preview after edit box" will get a blank list 02293 # we parse this near the beginning so that setHeaders can do the title 02294 # setting work instead of leaving it in getPreviewText 02295 $previewOutput = ''; 02296 if ( $this->formtype == 'preview' ) { 02297 $previewOutput = $this->getPreviewText(); 02298 } 02299 02300 wfRunHooks( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) ); 02301 02302 $this->setHeaders(); 02303 02304 if ( $this->showHeader() === false ) { 02305 wfProfileOut( __METHOD__ ); 02306 return; 02307 } 02308 02309 $wgOut->addHTML( $this->editFormPageTop ); 02310 02311 if ( $wgUser->getOption( 'previewontop' ) ) { 02312 $this->displayPreviewArea( $previewOutput, true ); 02313 } 02314 02315 $wgOut->addHTML( $this->editFormTextTop ); 02316 02317 $showToolbar = true; 02318 if ( $this->wasDeletedSinceLastEdit() ) { 02319 if ( $this->formtype == 'save' ) { 02320 // Hide the toolbar and edit area, user can click preview to get it back 02321 // Add an confirmation checkbox and explanation. 02322 $showToolbar = false; 02323 } else { 02324 $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>", 02325 'deletedwhileediting' ); 02326 } 02327 } 02328 02329 // @todo add EditForm plugin interface and use it here! 02330 // search for textarea1 and textares2, and allow EditForm to override all uses. 02331 $wgOut->addHTML( Html::openElement( 02332 'form', 02333 array( 02334 'id' => self::EDITFORM_ID, 02335 'name' => self::EDITFORM_ID, 02336 'method' => 'post', 02337 'action' => $this->getActionURL( $this->getContextTitle() ), 02338 'enctype' => 'multipart/form-data' 02339 ) 02340 ) ); 02341 02342 if ( is_callable( $formCallback ) ) { 02343 call_user_func_array( $formCallback, array( &$wgOut ) ); 02344 } 02345 02346 // Add an empty field to trip up spambots 02347 $wgOut->addHTML( 02348 Xml::openElement( 'div', array( 'id' => 'antispam-container', 'style' => 'display: none;' ) ) 02349 . Html::rawElement( 02350 'label', 02351 array( 'for' => 'wpAntiSpam' ), 02352 wfMessage( 'simpleantispam-label' )->parse() 02353 ) 02354 . Xml::element( 02355 'input', 02356 array( 02357 'type' => 'text', 02358 'name' => 'wpAntispam', 02359 'id' => 'wpAntispam', 02360 'value' => '' 02361 ) 02362 ) 02363 . Xml::closeElement( 'div' ) 02364 ); 02365 02366 wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) ); 02367 02368 // Put these up at the top to ensure they aren't lost on early form submission 02369 $this->showFormBeforeText(); 02370 02371 if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) { 02372 $username = $this->lastDelete->user_name; 02373 $comment = $this->lastDelete->log_comment; 02374 02375 // It is better to not parse the comment at all than to have templates expanded in the middle 02376 // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used? 02377 $key = $comment === '' 02378 ? 'confirmrecreate-noreason' 02379 : 'confirmrecreate'; 02380 $wgOut->addHTML( 02381 '<div class="mw-confirm-recreate">' . 02382 wfMessage( $key, $username, "<nowiki>$comment</nowiki>" )->parse() . 02383 Xml::checkLabel( wfMessage( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false, 02384 array( 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' ) 02385 ) . 02386 '</div>' 02387 ); 02388 } 02389 02390 # When the summary is hidden, also hide them on preview/show changes 02391 if ( $this->nosummary ) { 02392 $wgOut->addHTML( Html::hidden( 'nosummary', true ) ); 02393 } 02394 02395 # If a blank edit summary was previously provided, and the appropriate 02396 # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the 02397 # user being bounced back more than once in the event that a summary 02398 # is not required. 02399 ##### 02400 # For a bit more sophisticated detection of blank summaries, hash the 02401 # automatic one and pass that in the hidden field wpAutoSummary. 02402 if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) { 02403 $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) ); 02404 } 02405 02406 if ( $this->undidRev ) { 02407 $wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) ); 02408 } 02409 02410 if ( $this->hasPresetSummary ) { 02411 // If a summary has been preset using &summary= we don't want to prompt for 02412 // a different summary. Only prompt for a summary if the summary is blanked. 02413 // (Bug 17416) 02414 $this->autoSumm = md5( '' ); 02415 } 02416 02417 $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary ); 02418 $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) ); 02419 02420 $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) ); 02421 02422 $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) ); 02423 $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) ); 02424 02425 if ( $this->section == 'new' ) { 02426 $this->showSummaryInput( true, $this->summary ); 02427 $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) ); 02428 } 02429 02430 $wgOut->addHTML( $this->editFormTextBeforeContent ); 02431 02432 if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) { 02433 $wgOut->addHTML( EditPage::getEditToolbar() ); 02434 } 02435 02436 if ( $this->blankArticle ) { 02437 $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) ); 02438 } 02439 02440 if ( $this->isConflict ) { 02441 // In an edit conflict bypass the overridable content form method 02442 // and fallback to the raw wpTextbox1 since editconflicts can't be 02443 // resolved between page source edits and custom ui edits using the 02444 // custom edit ui. 02445 $this->textbox2 = $this->textbox1; 02446 02447 $content = $this->getCurrentContent(); 02448 $this->textbox1 = $this->toEditText( $content ); 02449 02450 $this->showTextbox1(); 02451 } else { 02452 $this->showContentForm(); 02453 } 02454 02455 $wgOut->addHTML( $this->editFormTextAfterContent ); 02456 02457 $this->showStandardInputs(); 02458 02459 $this->showFormAfterText(); 02460 02461 $this->showTosSummary(); 02462 02463 $this->showEditTools(); 02464 02465 $wgOut->addHTML( $this->editFormTextAfterTools . "\n" ); 02466 02467 $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ), 02468 Linker::formatTemplates( $this->getTemplates(), $this->preview, $this->section != '' ) ) ); 02469 02470 $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'hiddencats' ), 02471 Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) ); 02472 02473 $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'limitreport' ), 02474 self::getPreviewLimitReport( $this->mParserOutput ) ) ); 02475 02476 $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' ); 02477 02478 if ( $this->isConflict ) { 02479 try { 02480 $this->showConflict(); 02481 } catch ( MWContentSerializationException $ex ) { 02482 // this can't really happen, but be nice if it does. 02483 $msg = wfMessage( 02484 'content-failed-to-parse', 02485 $this->contentModel, 02486 $this->contentFormat, 02487 $ex->getMessage() 02488 ); 02489 $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' ); 02490 } 02491 } 02492 02493 // Marker for detecting truncated form data. This must be the last 02494 // parameter sent in order to be of use, so do not move me. 02495 $wgOut->addHTML( Html::hidden( 'wpUltimateParam', true ) ); 02496 $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" ); 02497 02498 if ( !$wgUser->getOption( 'previewontop' ) ) { 02499 $this->displayPreviewArea( $previewOutput, false ); 02500 } 02501 02502 wfProfileOut( __METHOD__ ); 02503 } 02504 02511 public static function extractSectionTitle( $text ) { 02512 preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches ); 02513 if ( !empty( $matches[2] ) ) { 02514 global $wgParser; 02515 return $wgParser->stripSectionName( trim( $matches[2] ) ); 02516 } else { 02517 return false; 02518 } 02519 } 02520 02524 protected function showHeader() { 02525 global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang; 02526 global $wgAllowUserCss, $wgAllowUserJs; 02527 02528 if ( $this->mTitle->isTalkPage() ) { 02529 $wgOut->addWikiMsg( 'talkpagetext' ); 02530 } 02531 02532 // Add edit notices 02533 $wgOut->addHTML( implode( "\n", $this->mTitle->getEditNotices( $this->oldid ) ) ); 02534 02535 if ( $this->isConflict ) { 02536 $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' ); 02537 $this->edittime = $this->mArticle->getTimestamp(); 02538 } else { 02539 if ( $this->section != '' && !$this->isSectionEditSupported() ) { 02540 // We use $this->section to much before this and getVal('wgSection') directly in other places 02541 // at this point we can't reset $this->section to '' to fallback to non-section editing. 02542 // Someone is welcome to try refactoring though 02543 $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' ); 02544 return false; 02545 } 02546 02547 if ( $this->section != '' && $this->section != 'new' ) { 02548 if ( !$this->summary && !$this->preview && !$this->diff ) { 02549 $sectionTitle = self::extractSectionTitle( $this->textbox1 ); //FIXME: use Content object 02550 if ( $sectionTitle !== false ) { 02551 $this->summary = "/* $sectionTitle */ "; 02552 } 02553 } 02554 } 02555 02556 if ( $this->missingComment ) { 02557 $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' ); 02558 } 02559 02560 if ( $this->missingSummary && $this->section != 'new' ) { 02561 $wgOut->wrapWikiMsg( "<div id='mw-missingsummary'>\n$1\n</div>", 'missingsummary' ); 02562 } 02563 02564 if ( $this->missingSummary && $this->section == 'new' ) { 02565 $wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1\n</div>", 'missingcommentheader' ); 02566 } 02567 02568 if ( $this->blankArticle ) { 02569 $wgOut->wrapWikiMsg( "<div id='mw-blankarticle'>\n$1\n</div>", 'blankarticle' ); 02570 } 02571 02572 if ( $this->hookError !== '' ) { 02573 $wgOut->addWikiText( $this->hookError ); 02574 } 02575 02576 if ( !$this->checkUnicodeCompliantBrowser() ) { 02577 $wgOut->addWikiMsg( 'nonunicodebrowser' ); 02578 } 02579 02580 if ( $this->section != 'new' ) { 02581 $revision = $this->mArticle->getRevisionFetched(); 02582 if ( $revision ) { 02583 // Let sysop know that this will make private content public if saved 02584 02585 if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) { 02586 $wgOut->wrapWikiMsg( 02587 "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 02588 'rev-deleted-text-permission' 02589 ); 02590 } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) { 02591 $wgOut->wrapWikiMsg( 02592 "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 02593 'rev-deleted-text-view' 02594 ); 02595 } 02596 02597 if ( !$revision->isCurrent() ) { 02598 $this->mArticle->setOldSubtitle( $revision->getId() ); 02599 $wgOut->addWikiMsg( 'editingold' ); 02600 } 02601 } elseif ( $this->mTitle->exists() ) { 02602 // Something went wrong 02603 02604 $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n", 02605 array( 'missing-revision', $this->oldid ) ); 02606 } 02607 } 02608 } 02609 02610 if ( wfReadOnly() ) { 02611 $wgOut->wrapWikiMsg( 02612 "<div id=\"mw-read-only-warning\">\n$1\n</div>", 02613 array( 'readonlywarning', wfReadOnlyReason() ) 02614 ); 02615 } elseif ( $wgUser->isAnon() ) { 02616 if ( $this->formtype != 'preview' ) { 02617 $wgOut->wrapWikiMsg( 02618 "<div id='mw-anon-edit-warning'>\n$1\n</div>", 02619 array( 'anoneditwarning', 02620 // Log-in link 02621 '{{fullurl:Special:UserLogin|returnto={{FULLPAGENAMEE}}}}', 02622 // Sign-up link 02623 '{{fullurl:Special:UserLogin/signup|returnto={{FULLPAGENAMEE}}}}' ) 02624 ); 02625 } else { 02626 $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>", 02627 'anonpreviewwarning' 02628 ); 02629 } 02630 } else { 02631 if ( $this->isCssJsSubpage ) { 02632 # Check the skin exists 02633 if ( $this->isWrongCaseCssJsPage ) { 02634 $wgOut->wrapWikiMsg( 02635 "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", 02636 array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) 02637 ); 02638 } 02639 if ( $this->formtype !== 'preview' ) { 02640 if ( $this->isCssSubpage && $wgAllowUserCss ) { 02641 $wgOut->wrapWikiMsg( 02642 "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", 02643 array( 'usercssyoucanpreview' ) 02644 ); 02645 } 02646 02647 if ( $this->isJsSubpage && $wgAllowUserJs ) { 02648 $wgOut->wrapWikiMsg( 02649 "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", 02650 array( 'userjsyoucanpreview' ) 02651 ); 02652 } 02653 } 02654 } 02655 } 02656 02657 if ( $this->mTitle->isProtected( 'edit' ) && 02658 MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' ) 02659 ) { 02660 # Is the title semi-protected? 02661 if ( $this->mTitle->isSemiProtected() ) { 02662 $noticeMsg = 'semiprotectedpagewarning'; 02663 } else { 02664 # Then it must be protected based on static groups (regular) 02665 $noticeMsg = 'protectedpagewarning'; 02666 } 02667 LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '', 02668 array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) ); 02669 } 02670 if ( $this->mTitle->isCascadeProtected() ) { 02671 # Is this page under cascading protection from some source pages? 02672 list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources(); 02673 $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n"; 02674 $cascadeSourcesCount = count( $cascadeSources ); 02675 if ( $cascadeSourcesCount > 0 ) { 02676 # Explain, and list the titles responsible 02677 foreach ( $cascadeSources as $page ) { 02678 $notice .= '* [[:' . $page->getPrefixedText() . "]]\n"; 02679 } 02680 } 02681 $notice .= '</div>'; 02682 $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', $cascadeSourcesCount ) ); 02683 } 02684 if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) { 02685 LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '', 02686 array( 'lim' => 1, 02687 'showIfEmpty' => false, 02688 'msgKey' => array( 'titleprotectedwarning' ), 02689 'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ) ); 02690 } 02691 02692 if ( $this->kblength === false ) { 02693 $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 ); 02694 } 02695 02696 if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) { 02697 $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>", 02698 array( 02699 'longpageerror', 02700 $wgLang->formatNum( $this->kblength ), 02701 $wgLang->formatNum( $wgMaxArticleSize ) 02702 ) 02703 ); 02704 } else { 02705 if ( !wfMessage( 'longpage-hint' )->isDisabled() ) { 02706 $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>", 02707 array( 02708 'longpage-hint', 02709 $wgLang->formatSize( strlen( $this->textbox1 ) ), 02710 strlen( $this->textbox1 ) 02711 ) 02712 ); 02713 } 02714 } 02715 # Add header copyright warning 02716 $this->showHeaderCopyrightWarning(); 02717 02718 return true; 02719 } 02720 02735 function getSummaryInput( $summary = "", $labelText = null, 02736 $inputAttrs = null, $spanLabelAttrs = null 02737 ) { 02738 // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters. 02739 $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : array() ) + array( 02740 'id' => 'wpSummary', 02741 'maxlength' => '200', 02742 'tabindex' => '1', 02743 'size' => 60, 02744 'spellcheck' => 'true', 02745 ) + Linker::tooltipAndAccesskeyAttribs( 'summary' ); 02746 02747 $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : array() ) + array( 02748 'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary', 02749 'id' => "wpSummaryLabel" 02750 ); 02751 02752 $label = null; 02753 if ( $labelText ) { 02754 $label = Xml::tags( 02755 'label', 02756 $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null, 02757 $labelText 02758 ); 02759 $label = Xml::tags( 'span', $spanLabelAttrs, $label ); 02760 } 02761 02762 $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs ); 02763 02764 return array( $label, $input ); 02765 } 02766 02773 protected function showSummaryInput( $isSubjectPreview, $summary = "" ) { 02774 global $wgOut, $wgContLang; 02775 # Add a class if 'missingsummary' is triggered to allow styling of the summary line 02776 $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary'; 02777 if ( $isSubjectPreview ) { 02778 if ( $this->nosummary ) { 02779 return; 02780 } 02781 } else { 02782 if ( !$this->mShowSummaryField ) { 02783 return; 02784 } 02785 } 02786 $summary = $wgContLang->recodeForEdit( $summary ); 02787 $labelText = wfMessage( $isSubjectPreview ? 'subject' : 'summary' )->parse(); 02788 list( $label, $input ) = $this->getSummaryInput( 02789 $summary, 02790 $labelText, 02791 array( 'class' => $summaryClass ), 02792 array() 02793 ); 02794 $wgOut->addHTML( "{$label} {$input}" ); 02795 } 02796 02804 protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) { 02805 // avoid spaces in preview, gets always trimmed on save 02806 $summary = trim( $summary ); 02807 if ( !$summary || ( !$this->preview && !$this->diff ) ) { 02808 return ""; 02809 } 02810 02811 global $wgParser; 02812 02813 if ( $isSubjectPreview ) { 02814 $summary = wfMessage( 'newsectionsummary' )->rawParams( $wgParser->stripSectionName( $summary ) ) 02815 ->inContentLanguage()->text(); 02816 } 02817 02818 $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview'; 02819 02820 $summary = wfMessage( $message )->parse() 02821 . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview ); 02822 return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary ); 02823 } 02824 02825 protected function showFormBeforeText() { 02826 global $wgOut; 02827 $section = htmlspecialchars( $this->section ); 02828 $wgOut->addHTML( <<<HTML 02829 <input type='hidden' value="{$section}" name="wpSection" /> 02830 <input type='hidden' value="{$this->starttime}" name="wpStarttime" /> 02831 <input type='hidden' value="{$this->edittime}" name="wpEdittime" /> 02832 <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" /> 02833 02834 HTML 02835 ); 02836 if ( !$this->checkUnicodeCompliantBrowser() ) { 02837 $wgOut->addHTML( Html::hidden( 'safemode', '1' ) ); 02838 } 02839 } 02840 02841 protected function showFormAfterText() { 02842 global $wgOut, $wgUser; 02855 $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" ); 02856 } 02857 02866 protected function showContentForm() { 02867 $this->showTextbox1(); 02868 } 02869 02878 protected function showTextbox1( $customAttribs = null, $textoverride = null ) { 02879 if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) { 02880 $attribs = array( 'style' => 'display:none;' ); 02881 } else { 02882 $classes = array(); // Textarea CSS 02883 if ( $this->mTitle->isProtected( 'edit' ) && 02884 MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' ) 02885 ) { 02886 # Is the title semi-protected? 02887 if ( $this->mTitle->isSemiProtected() ) { 02888 $classes[] = 'mw-textarea-sprotected'; 02889 } else { 02890 # Then it must be protected based on static groups (regular) 02891 $classes[] = 'mw-textarea-protected'; 02892 } 02893 # Is the title cascade-protected? 02894 if ( $this->mTitle->isCascadeProtected() ) { 02895 $classes[] = 'mw-textarea-cprotected'; 02896 } 02897 } 02898 02899 $attribs = array( 'tabindex' => 1 ); 02900 02901 if ( is_array( $customAttribs ) ) { 02902 $attribs += $customAttribs; 02903 } 02904 02905 if ( count( $classes ) ) { 02906 if ( isset( $attribs['class'] ) ) { 02907 $classes[] = $attribs['class']; 02908 } 02909 $attribs['class'] = implode( ' ', $classes ); 02910 } 02911 } 02912 02913 $this->showTextbox( 02914 $textoverride !== null ? $textoverride : $this->textbox1, 02915 'wpTextbox1', 02916 $attribs 02917 ); 02918 } 02919 02920 protected function showTextbox2() { 02921 $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) ); 02922 } 02923 02924 protected function showTextbox( $text, $name, $customAttribs = array() ) { 02925 global $wgOut, $wgUser; 02926 02927 $wikitext = $this->safeUnicodeOutput( $text ); 02928 if ( strval( $wikitext ) !== '' ) { 02929 // Ensure there's a newline at the end, otherwise adding lines 02930 // is awkward. 02931 // But don't add a newline if the ext is empty, or Firefox in XHTML 02932 // mode will show an extra newline. A bit annoying. 02933 $wikitext .= "\n"; 02934 } 02935 02936 $attribs = $customAttribs + array( 02937 'accesskey' => ',', 02938 'id' => $name, 02939 'cols' => $wgUser->getIntOption( 'cols' ), 02940 'rows' => $wgUser->getIntOption( 'rows' ), 02941 // Avoid PHP notices when appending preferences 02942 // (appending allows customAttribs['style'] to still work). 02943 'style' => '' 02944 ); 02945 02946 $pageLang = $this->mTitle->getPageLanguage(); 02947 $attribs['lang'] = $pageLang->getCode(); 02948 $attribs['dir'] = $pageLang->getDir(); 02949 02950 $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) ); 02951 } 02952 02953 protected function displayPreviewArea( $previewOutput, $isOnTop = false ) { 02954 global $wgOut; 02955 $classes = array(); 02956 if ( $isOnTop ) { 02957 $classes[] = 'ontop'; 02958 } 02959 02960 $attribs = array( 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) ); 02961 02962 if ( $this->formtype != 'preview' ) { 02963 $attribs['style'] = 'display: none;'; 02964 } 02965 02966 $wgOut->addHTML( Xml::openElement( 'div', $attribs ) ); 02967 02968 if ( $this->formtype == 'preview' ) { 02969 $this->showPreview( $previewOutput ); 02970 } 02971 02972 $wgOut->addHTML( '</div>' ); 02973 02974 if ( $this->formtype == 'diff' ) { 02975 try { 02976 $this->showDiff(); 02977 } catch ( MWContentSerializationException $ex ) { 02978 $msg = wfMessage( 02979 'content-failed-to-parse', 02980 $this->contentModel, 02981 $this->contentFormat, 02982 $ex->getMessage() 02983 ); 02984 $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' ); 02985 } 02986 } 02987 } 02988 02995 protected function showPreview( $text ) { 02996 global $wgOut; 02997 if ( $this->mTitle->getNamespace() == NS_CATEGORY ) { 02998 $this->mArticle->openShowCategory(); 02999 } 03000 # This hook seems slightly odd here, but makes things more 03001 # consistent for extensions. 03002 wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) ); 03003 $wgOut->addHTML( $text ); 03004 if ( $this->mTitle->getNamespace() == NS_CATEGORY ) { 03005 $this->mArticle->closeShowCategory(); 03006 } 03007 } 03008 03016 function showDiff() { 03017 global $wgUser, $wgContLang, $wgOut; 03018 03019 $oldtitlemsg = 'currentrev'; 03020 # if message does not exist, show diff against the preloaded default 03021 if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) { 03022 $oldtext = $this->mTitle->getDefaultMessageText(); 03023 if ( $oldtext !== false ) { 03024 $oldtitlemsg = 'defaultmessagetext'; 03025 $oldContent = $this->toEditContent( $oldtext ); 03026 } else { 03027 $oldContent = null; 03028 } 03029 } else { 03030 $oldContent = $this->getCurrentContent(); 03031 } 03032 03033 $textboxContent = $this->toEditContent( $this->textbox1 ); 03034 03035 $newContent = $this->mArticle->replaceSectionContent( 03036 $this->section, $textboxContent, 03037 $this->summary, $this->edittime ); 03038 03039 if ( $newContent ) { 03040 ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) ); 03041 wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) ); 03042 03043 $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang ); 03044 $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts ); 03045 } 03046 03047 if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) { 03048 $oldtitle = wfMessage( $oldtitlemsg )->parse(); 03049 $newtitle = wfMessage( 'yourtext' )->parse(); 03050 03051 if ( !$oldContent ) { 03052 $oldContent = $newContent->getContentHandler()->makeEmptyContent(); 03053 } 03054 03055 if ( !$newContent ) { 03056 $newContent = $oldContent->getContentHandler()->makeEmptyContent(); 03057 } 03058 03059 $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() ); 03060 $de->setContent( $oldContent, $newContent ); 03061 03062 $difftext = $de->getDiff( $oldtitle, $newtitle ); 03063 $de->showDiffStyle(); 03064 } else { 03065 $difftext = ''; 03066 } 03067 03068 $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' ); 03069 } 03070 03074 protected function showHeaderCopyrightWarning() { 03075 $msg = 'editpage-head-copy-warn'; 03076 if ( !wfMessage( $msg )->isDisabled() ) { 03077 global $wgOut; 03078 $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>", 03079 'editpage-head-copy-warn' ); 03080 } 03081 } 03082 03091 protected function showTosSummary() { 03092 $msg = 'editpage-tos-summary'; 03093 wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) ); 03094 if ( !wfMessage( $msg )->isDisabled() ) { 03095 global $wgOut; 03096 $wgOut->addHTML( '<div class="mw-tos-summary">' ); 03097 $wgOut->addWikiMsg( $msg ); 03098 $wgOut->addHTML( '</div>' ); 03099 } 03100 } 03101 03102 protected function showEditTools() { 03103 global $wgOut; 03104 $wgOut->addHTML( '<div class="mw-editTools">' . 03105 wfMessage( 'edittools' )->inContentLanguage()->parse() . 03106 '</div>' ); 03107 } 03108 03115 protected function getCopywarn() { 03116 return self::getCopyrightWarning( $this->mTitle ); 03117 } 03118 03126 public static function getCopyrightWarning( $title, $format = 'plain' ) { 03127 global $wgRightsText; 03128 if ( $wgRightsText ) { 03129 $copywarnMsg = array( 'copyrightwarning', 03130 '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]', 03131 $wgRightsText ); 03132 } else { 03133 $copywarnMsg = array( 'copyrightwarning2', 03134 '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' ); 03135 } 03136 // Allow for site and per-namespace customization of contribution/copyright notice. 03137 wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) ); 03138 03139 return "<div id=\"editpage-copywarn\">\n" . 03140 call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>"; 03141 } 03142 03150 public static function getPreviewLimitReport( $output ) { 03151 if ( !$output || !$output->getLimitReportData() ) { 03152 return ''; 03153 } 03154 03155 wfProfileIn( __METHOD__ ); 03156 03157 $limitReport = Html::rawElement( 'div', array( 'class' => 'mw-limitReportExplanation' ), 03158 wfMessage( 'limitreport-title' )->parseAsBlock() 03159 ); 03160 03161 // Show/hide animation doesn't work correctly on a table, so wrap it in a div. 03162 $limitReport .= Html::openElement( 'div', array( 'class' => 'preview-limit-report-wrapper' ) ); 03163 03164 $limitReport .= Html::openElement( 'table', array( 03165 'class' => 'preview-limit-report wikitable' 03166 ) ) . 03167 Html::openElement( 'tbody' ); 03168 03169 foreach ( $output->getLimitReportData() as $key => $value ) { 03170 if ( wfRunHooks( 'ParserLimitReportFormat', 03171 array( $key, &$value, &$limitReport, true, true ) 03172 ) ) { 03173 $keyMsg = wfMessage( $key ); 03174 $valueMsg = wfMessage( array( "$key-value-html", "$key-value" ) ); 03175 if ( !$valueMsg->exists() ) { 03176 $valueMsg = new RawMessage( '$1' ); 03177 } 03178 if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) { 03179 $limitReport .= Html::openElement( 'tr' ) . 03180 Html::rawElement( 'th', null, $keyMsg->parse() ) . 03181 Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) . 03182 Html::closeElement( 'tr' ); 03183 } 03184 } 03185 } 03186 03187 $limitReport .= Html::closeElement( 'tbody' ) . 03188 Html::closeElement( 'table' ) . 03189 Html::closeElement( 'div' ); 03190 03191 wfProfileOut( __METHOD__ ); 03192 03193 return $limitReport; 03194 } 03195 03196 protected function showStandardInputs( &$tabindex = 2 ) { 03197 global $wgOut, $wgUseMediaWikiUIEverywhere; 03198 $wgOut->addHTML( "<div class='editOptions'>\n" ); 03199 03200 if ( $this->section != 'new' ) { 03201 $this->showSummaryInput( false, $this->summary ); 03202 $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) ); 03203 } 03204 03205 $checkboxes = $this->getCheckboxes( $tabindex, 03206 array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) ); 03207 $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" ); 03208 03209 // Show copyright warning. 03210 $wgOut->addWikiText( $this->getCopywarn() ); 03211 $wgOut->addHTML( $this->editFormTextAfterWarn ); 03212 03213 $wgOut->addHTML( "<div class='editButtons'>\n" ); 03214 $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" ); 03215 03216 $cancel = $this->getCancelLink(); 03217 if ( $cancel !== '' ) { 03218 $cancel .= Html::element( 'span', 03219 array( 'class' => 'mw-editButtons-pipe-separator' ), 03220 wfMessage( 'pipe-separator' )->text() ); 03221 } 03222 03223 $message = wfMessage( 'edithelppage' )->inContentLanguage()->text(); 03224 $edithelpurl = Skin::makeInternalOrExternalUrl( $message ); 03225 $attrs = array( 03226 'target' => 'helpwindow', 03227 'href' => $edithelpurl, 03228 ); 03229 if ( $wgUseMediaWikiUIEverywhere ) { 03230 $attrs['class'] = 'mw-ui-button mw-ui-quiet'; 03231 } 03232 $edithelp = Html::element( 'a', $attrs, wfMessage( 'edithelp' )->text() ) . 03233 wfMessage( 'word-separator' )->escaped() . 03234 wfMessage( 'newwindow' )->parse(); 03235 03236 $wgOut->addHTML( " <span class='cancelLink'>{$cancel}</span>\n" ); 03237 $wgOut->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" ); 03238 $wgOut->addHTML( "</div><!-- editButtons -->\n" ); 03239 03240 wfRunHooks( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) ); 03241 03242 $wgOut->addHTML( "</div><!-- editOptions -->\n" ); 03243 } 03244 03249 protected function showConflict() { 03250 global $wgOut; 03251 03252 if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) { 03253 $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" ); 03254 03255 $content1 = $this->toEditContent( $this->textbox1 ); 03256 $content2 = $this->toEditContent( $this->textbox2 ); 03257 03258 $handler = ContentHandler::getForModelID( $this->contentModel ); 03259 $de = $handler->createDifferenceEngine( $this->mArticle->getContext() ); 03260 $de->setContent( $content2, $content1 ); 03261 $de->showDiff( 03262 wfMessage( 'yourtext' )->parse(), 03263 wfMessage( 'storedversion' )->text() 03264 ); 03265 03266 $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" ); 03267 $this->showTextbox2(); 03268 } 03269 } 03270 03274 public function getCancelLink() { 03275 global $wgUseMediaWikiUIEverywhere; 03276 $cancelParams = array(); 03277 if ( !$this->isConflict && $this->oldid > 0 ) { 03278 $cancelParams['oldid'] = $this->oldid; 03279 } 03280 $attrs = array( 'id' => 'mw-editform-cancel' ); 03281 if ( $wgUseMediaWikiUIEverywhere ) { 03282 $attrs['class'] = 'mw-ui-button mw-ui-quiet'; 03283 } 03284 03285 return Linker::linkKnown( 03286 $this->getContextTitle(), 03287 wfMessage( 'cancel' )->parse(), 03288 $attrs, 03289 $cancelParams 03290 ); 03291 } 03292 03302 protected function getActionURL( Title $title ) { 03303 return $title->getLocalURL( array( 'action' => $this->action ) ); 03304 } 03305 03313 protected function wasDeletedSinceLastEdit() { 03314 if ( $this->deletedSinceEdit !== null ) { 03315 return $this->deletedSinceEdit; 03316 } 03317 03318 $this->deletedSinceEdit = false; 03319 03320 if ( $this->mTitle->isDeletedQuick() ) { 03321 $this->lastDelete = $this->getLastDelete(); 03322 if ( $this->lastDelete ) { 03323 $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp ); 03324 if ( $deleteTime > $this->starttime ) { 03325 $this->deletedSinceEdit = true; 03326 } 03327 } 03328 } 03329 03330 return $this->deletedSinceEdit; 03331 } 03332 03336 protected function getLastDelete() { 03337 $dbr = wfGetDB( DB_SLAVE ); 03338 $data = $dbr->selectRow( 03339 array( 'logging', 'user' ), 03340 array( 03341 'log_type', 03342 'log_action', 03343 'log_timestamp', 03344 'log_user', 03345 'log_namespace', 03346 'log_title', 03347 'log_comment', 03348 'log_params', 03349 'log_deleted', 03350 'user_name' 03351 ), array( 03352 'log_namespace' => $this->mTitle->getNamespace(), 03353 'log_title' => $this->mTitle->getDBkey(), 03354 'log_type' => 'delete', 03355 'log_action' => 'delete', 03356 'user_id=log_user' 03357 ), 03358 __METHOD__, 03359 array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ) 03360 ); 03361 // Quick paranoid permission checks... 03362 if ( is_object( $data ) ) { 03363 if ( $data->log_deleted & LogPage::DELETED_USER ) { 03364 $data->user_name = wfMessage( 'rev-deleted-user' )->escaped(); 03365 } 03366 03367 if ( $data->log_deleted & LogPage::DELETED_COMMENT ) { 03368 $data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped(); 03369 } 03370 } 03371 03372 return $data; 03373 } 03374 03380 function getPreviewText() { 03381 global $wgOut, $wgUser, $wgRawHtml, $wgLang; 03382 global $wgAllowUserCss, $wgAllowUserJs; 03383 03384 wfProfileIn( __METHOD__ ); 03385 03386 if ( $wgRawHtml && !$this->mTokenOk ) { 03387 // Could be an offsite preview attempt. This is very unsafe if 03388 // HTML is enabled, as it could be an attack. 03389 $parsedNote = ''; 03390 if ( $this->textbox1 !== '' ) { 03391 // Do not put big scary notice, if previewing the empty 03392 // string, which happens when you initially edit 03393 // a category page, due to automatic preview-on-open. 03394 $parsedNote = $wgOut->parse( "<div class='previewnote'>" . 03395 wfMessage( 'session_fail_preview_html' )->text() . "</div>", true, /* interface */true ); 03396 } 03397 wfProfileOut( __METHOD__ ); 03398 return $parsedNote; 03399 } 03400 03401 $note = ''; 03402 03403 try { 03404 $content = $this->toEditContent( $this->textbox1 ); 03405 03406 $previewHTML = ''; 03407 if ( !wfRunHooks( 03408 'AlternateEditPreview', 03409 array( $this, &$content, &$previewHTML, &$this->mParserOutput ) ) 03410 ) { 03411 wfProfileOut( __METHOD__ ); 03412 return $previewHTML; 03413 } 03414 03415 # provide a anchor link to the editform 03416 $continueEditing = '<span class="mw-continue-editing">' . 03417 '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . 03418 wfMessage( 'continue-editing' )->text() . ']]</span>'; 03419 if ( $this->mTriedSave && !$this->mTokenOk ) { 03420 if ( $this->mTokenOkExceptSuffix ) { 03421 $note = wfMessage( 'token_suffix_mismatch' )->plain(); 03422 } else { 03423 $note = wfMessage( 'session_fail_preview' )->plain(); 03424 } 03425 } elseif ( $this->incompleteForm ) { 03426 $note = wfMessage( 'edit_form_incomplete' )->plain(); 03427 } else { 03428 $note = wfMessage( 'previewnote' )->plain() . ' ' . $continueEditing; 03429 } 03430 03431 $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() ); 03432 $parserOptions->setEditSection( false ); 03433 $parserOptions->setIsPreview( true ); 03434 $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' ); 03435 03436 # don't parse non-wikitext pages, show message about preview 03437 if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) { 03438 if ( $this->mTitle->isCssJsSubpage() ) { 03439 $level = 'user'; 03440 } elseif ( $this->mTitle->isCssOrJsPage() ) { 03441 $level = 'site'; 03442 } else { 03443 $level = false; 03444 } 03445 03446 if ( $content->getModel() == CONTENT_MODEL_CSS ) { 03447 $format = 'css'; 03448 if ( $level === 'user' && !$wgAllowUserCss ) { 03449 $format = false; 03450 } 03451 } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) { 03452 $format = 'js'; 03453 if ( $level === 'user' && !$wgAllowUserJs ) { 03454 $format = false; 03455 } 03456 } else { 03457 $format = false; 03458 } 03459 03460 # Used messages to make sure grep find them: 03461 # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview 03462 if ( $level && $format ) { 03463 $note = "<div id='mw-{$level}{$format}preview'>" . 03464 wfMessage( "{$level}{$format}preview" )->text() . 03465 ' ' . $continueEditing . "</div>"; 03466 } 03467 } 03468 03469 # If we're adding a comment, we need to show the 03470 # summary as the headline 03471 if ( $this->section === "new" && $this->summary !== "" ) { 03472 $content = $content->addSectionHeader( $this->summary ); 03473 } 03474 03475 $hook_args = array( $this, &$content ); 03476 ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args ); 03477 wfRunHooks( 'EditPageGetPreviewContent', $hook_args ); 03478 03479 $parserOptions->enableLimitReport(); 03480 03481 # For CSS/JS pages, we should have called the ShowRawCssJs hook here. 03482 # But it's now deprecated, so never mind 03483 03484 $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions ); 03485 $parserOutput = $content->getParserOutput( 03486 $this->getArticle()->getTitle(), 03487 null, 03488 $parserOptions 03489 ); 03490 03491 $previewHTML = $parserOutput->getText(); 03492 $this->mParserOutput = $parserOutput; 03493 $wgOut->addParserOutputMetadata( $parserOutput ); 03494 03495 if ( count( $parserOutput->getWarnings() ) ) { 03496 $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() ); 03497 } 03498 } catch ( MWContentSerializationException $ex ) { 03499 $m = wfMessage( 03500 'content-failed-to-parse', 03501 $this->contentModel, 03502 $this->contentFormat, 03503 $ex->getMessage() 03504 ); 03505 $note .= "\n\n" . $m->parse(); 03506 $previewHTML = ''; 03507 } 03508 03509 if ( $this->isConflict ) { 03510 $conflict = '<h2 id="mw-previewconflict">' 03511 . wfMessage( 'previewconflict' )->escaped() . "</h2>\n"; 03512 } else { 03513 $conflict = '<hr />'; 03514 } 03515 03516 $previewhead = "<div class='previewnote'>\n" . 03517 '<h2 id="mw-previewheader">' . wfMessage( 'preview' )->escaped() . "</h2>" . 03518 $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n"; 03519 03520 $pageViewLang = $this->mTitle->getPageViewLanguage(); 03521 $attribs = array( 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(), 03522 'class' => 'mw-content-' . $pageViewLang->getDir() ); 03523 $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML ); 03524 03525 wfProfileOut( __METHOD__ ); 03526 return $previewhead . $previewHTML . $this->previewTextAfterContent; 03527 } 03528 03532 function getTemplates() { 03533 if ( $this->preview || $this->section != '' ) { 03534 $templates = array(); 03535 if ( !isset( $this->mParserOutput ) ) { 03536 return $templates; 03537 } 03538 foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) { 03539 foreach ( array_keys( $template ) as $dbk ) { 03540 $templates[] = Title::makeTitle( $ns, $dbk ); 03541 } 03542 } 03543 return $templates; 03544 } else { 03545 return $this->mTitle->getTemplateLinksFrom(); 03546 } 03547 } 03548 03555 static function getEditToolbar() { 03556 global $wgContLang, $wgOut; 03557 global $wgEnableUploads, $wgForeignFileRepos; 03558 03559 $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos ); 03560 03570 $toolarray = array( 03571 array( 03572 'id' => 'mw-editbutton-bold', 03573 'open' => '\'\'\'', 03574 'close' => '\'\'\'', 03575 'sample' => wfMessage( 'bold_sample' )->text(), 03576 'tip' => wfMessage( 'bold_tip' )->text(), 03577 ), 03578 array( 03579 'id' => 'mw-editbutton-italic', 03580 'open' => '\'\'', 03581 'close' => '\'\'', 03582 'sample' => wfMessage( 'italic_sample' )->text(), 03583 'tip' => wfMessage( 'italic_tip' )->text(), 03584 ), 03585 array( 03586 'id' => 'mw-editbutton-link', 03587 'open' => '[[', 03588 'close' => ']]', 03589 'sample' => wfMessage( 'link_sample' )->text(), 03590 'tip' => wfMessage( 'link_tip' )->text(), 03591 ), 03592 array( 03593 'id' => 'mw-editbutton-extlink', 03594 'open' => '[', 03595 'close' => ']', 03596 'sample' => wfMessage( 'extlink_sample' )->text(), 03597 'tip' => wfMessage( 'extlink_tip' )->text(), 03598 ), 03599 array( 03600 'id' => 'mw-editbutton-headline', 03601 'open' => "\n== ", 03602 'close' => " ==\n", 03603 'sample' => wfMessage( 'headline_sample' )->text(), 03604 'tip' => wfMessage( 'headline_tip' )->text(), 03605 ), 03606 $imagesAvailable ? array( 03607 'id' => 'mw-editbutton-image', 03608 'open' => '[[' . $wgContLang->getNsText( NS_FILE ) . ':', 03609 'close' => ']]', 03610 'sample' => wfMessage( 'image_sample' )->text(), 03611 'tip' => wfMessage( 'image_tip' )->text(), 03612 ) : false, 03613 $imagesAvailable ? array( 03614 'id' => 'mw-editbutton-media', 03615 'open' => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':', 03616 'close' => ']]', 03617 'sample' => wfMessage( 'media_sample' )->text(), 03618 'tip' => wfMessage( 'media_tip' )->text(), 03619 ) : false, 03620 array( 03621 'id' => 'mw-editbutton-nowiki', 03622 'open' => "<nowiki>", 03623 'close' => "</nowiki>", 03624 'sample' => wfMessage( 'nowiki_sample' )->text(), 03625 'tip' => wfMessage( 'nowiki_tip' )->text(), 03626 ), 03627 array( 03628 'id' => 'mw-editbutton-signature', 03629 'open' => '--~~~~', 03630 'close' => '', 03631 'sample' => '', 03632 'tip' => wfMessage( 'sig_tip' )->text(), 03633 ), 03634 array( 03635 'id' => 'mw-editbutton-hr', 03636 'open' => "\n----\n", 03637 'close' => '', 03638 'sample' => '', 03639 'tip' => wfMessage( 'hr_tip' )->text(), 03640 ) 03641 ); 03642 03643 $script = 'mw.loader.using("mediawiki.action.edit", function() {'; 03644 foreach ( $toolarray as $tool ) { 03645 if ( !$tool ) { 03646 continue; 03647 } 03648 03649 $params = array( 03650 // Images are defined in ResourceLoaderEditToolbarModule 03651 false, 03652 // Note that we use the tip both for the ALT tag and the TITLE tag of the image. 03653 // Older browsers show a "speedtip" type message only for ALT. 03654 // Ideally these should be different, realistically they 03655 // probably don't need to be. 03656 $tool['tip'], 03657 $tool['open'], 03658 $tool['close'], 03659 $tool['sample'], 03660 $tool['id'], 03661 ); 03662 03663 $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params ); 03664 } 03665 03666 // This used to be called on DOMReady from mediawiki.action.edit, which 03667 // ended up causing race conditions with the setup code above. 03668 $script .= "\n" . 03669 "// Create button bar\n" . 03670 "$(function() { mw.toolbar.init(); } );\n"; 03671 03672 $script .= '});'; 03673 $wgOut->addScript( Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) ) ); 03674 03675 $toolbar = '<div id="toolbar"></div>'; 03676 03677 wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) ); 03678 03679 return $toolbar; 03680 } 03681 03692 public function getCheckboxes( &$tabindex, $checked ) { 03693 global $wgUser, $wgUseMediaWikiUIEverywhere; 03694 03695 $checkboxes = array(); 03696 03697 // don't show the minor edit checkbox if it's a new page or section 03698 if ( !$this->isNew ) { 03699 $checkboxes['minor'] = ''; 03700 $minorLabel = wfMessage( 'minoredit' )->parse(); 03701 if ( $wgUser->isAllowed( 'minoredit' ) ) { 03702 $attribs = array( 03703 'tabindex' => ++$tabindex, 03704 'accesskey' => wfMessage( 'accesskey-minoredit' )->text(), 03705 'id' => 'wpMinoredit', 03706 ); 03707 $minorEditHtml = 03708 Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) . 03709 " <label for='wpMinoredit' id='mw-editpage-minoredit'" . 03710 Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'minoredit', 'withaccess' ) ) ) . 03711 ">{$minorLabel}</label>"; 03712 03713 if ( $wgUseMediaWikiUIEverywhere ) { 03714 $checkboxes['minor'] = Html::openElement( 'div', array( 'class' => 'mw-ui-checkbox' ) ) . 03715 $minorEditHtml . 03716 Html::closeElement( 'div' ); 03717 } else { 03718 $checkboxes['minor'] = $minorEditHtml; 03719 } 03720 } 03721 } 03722 03723 $watchLabel = wfMessage( 'watchthis' )->parse(); 03724 $checkboxes['watch'] = ''; 03725 if ( $wgUser->isLoggedIn() ) { 03726 $attribs = array( 03727 'tabindex' => ++$tabindex, 03728 'accesskey' => wfMessage( 'accesskey-watch' )->text(), 03729 'id' => 'wpWatchthis', 03730 ); 03731 $watchThisHtml = 03732 Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) . 03733 " <label for='wpWatchthis' id='mw-editpage-watch'" . 03734 Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ) ) . 03735 ">{$watchLabel}</label>"; 03736 if ( $wgUseMediaWikiUIEverywhere ) { 03737 $checkboxes['watch'] = Html::openElement( 'div', array( 'class' => 'mw-ui-checkbox' ) ) . 03738 $watchThisHtml . 03739 Html::closeElement( 'div' ); 03740 } else { 03741 $checkboxes['watch'] = $watchThisHtml; 03742 } 03743 } 03744 wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) ); 03745 return $checkboxes; 03746 } 03747 03756 public function getEditButtons( &$tabindex ) { 03757 global $wgUseMediaWikiUIEverywhere; 03758 03759 $buttons = array(); 03760 03761 $attribs = array( 03762 'id' => 'wpSave', 03763 'name' => 'wpSave', 03764 'type' => 'submit', 03765 'tabindex' => ++$tabindex, 03766 'value' => wfMessage( 'savearticle' )->text(), 03767 ) + Linker::tooltipAndAccesskeyAttribs( 'save' ); 03768 if ( $wgUseMediaWikiUIEverywhere ) { 03769 $attribs['class'] = 'mw-ui-button mw-ui-constructive'; 03770 } 03771 $buttons['save'] = Xml::element( 'input', $attribs, '' ); 03772 03773 ++$tabindex; // use the same for preview and live preview 03774 $attribs = array( 03775 'id' => 'wpPreview', 03776 'name' => 'wpPreview', 03777 'type' => 'submit', 03778 'tabindex' => $tabindex, 03779 'value' => wfMessage( 'showpreview' )->text(), 03780 ) + Linker::tooltipAndAccesskeyAttribs( 'preview' ); 03781 if ( $wgUseMediaWikiUIEverywhere ) { 03782 $attribs['class'] = 'mw-ui-button mw-ui-progressive'; 03783 } 03784 $buttons['preview'] = Xml::element( 'input', $attribs, '' ); 03785 $buttons['live'] = ''; 03786 03787 $attribs = array( 03788 'id' => 'wpDiff', 03789 'name' => 'wpDiff', 03790 'type' => 'submit', 03791 'tabindex' => ++$tabindex, 03792 'value' => wfMessage( 'showdiff' )->text(), 03793 ) + Linker::tooltipAndAccesskeyAttribs( 'diff' ); 03794 if ( $wgUseMediaWikiUIEverywhere ) { 03795 $attribs['class'] = 'mw-ui-button mw-ui-progressive'; 03796 } 03797 $buttons['diff'] = Xml::element( 'input', $attribs, '' ); 03798 03799 wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) ); 03800 return $buttons; 03801 } 03802 03815 function livePreview() { 03816 global $wgOut; 03817 $wgOut->disable(); 03818 header( 'Content-type: text/xml; charset=utf-8' ); 03819 header( 'Cache-control: no-cache' ); 03820 03821 $previewText = $this->getPreviewText(); 03822 #$categories = $skin->getCategoryLinks(); 03823 03824 $s = 03825 '<?xml version="1.0" encoding="UTF-8" ?>' . "\n" . 03826 Xml::tags( 'livepreview', null, 03827 Xml::element( 'preview', null, $previewText ) 03828 #. Xml::element( 'category', null, $categories ) 03829 ); 03830 echo $s; 03831 } 03832 03837 function noSuchSectionPage() { 03838 global $wgOut; 03839 03840 $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) ); 03841 03842 $res = wfMessage( 'nosuchsectiontext', $this->section )->parseAsBlock(); 03843 wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) ); 03844 $wgOut->addHTML( $res ); 03845 03846 $wgOut->returnToMain( false, $this->mTitle ); 03847 } 03848 03854 public function spamPageWithContent( $match = false ) { 03855 global $wgOut, $wgLang; 03856 $this->textbox2 = $this->textbox1; 03857 03858 if ( is_array( $match ) ) { 03859 $match = $wgLang->listToText( $match ); 03860 } 03861 $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) ); 03862 03863 $wgOut->addHTML( '<div id="spamprotected">' ); 03864 $wgOut->addWikiMsg( 'spamprotectiontext' ); 03865 if ( $match ) { 03866 $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) ); 03867 } 03868 $wgOut->addHTML( '</div>' ); 03869 03870 $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" ); 03871 $this->showDiff(); 03872 03873 $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" ); 03874 $this->showTextbox2(); 03875 03876 $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) ); 03877 } 03878 03885 private function checkUnicodeCompliantBrowser() { 03886 global $wgBrowserBlackList, $wgRequest; 03887 03888 $currentbrowser = $wgRequest->getHeader( 'User-Agent' ); 03889 if ( $currentbrowser === false ) { 03890 // No User-Agent header sent? Trust it by default... 03891 return true; 03892 } 03893 03894 foreach ( $wgBrowserBlackList as $browser ) { 03895 if ( preg_match( $browser, $currentbrowser ) ) { 03896 return false; 03897 } 03898 } 03899 return true; 03900 } 03901 03910 protected function safeUnicodeInput( $request, $field ) { 03911 $text = rtrim( $request->getText( $field ) ); 03912 return $request->getBool( 'safemode' ) 03913 ? $this->unmakeSafe( $text ) 03914 : $text; 03915 } 03916 03924 protected function safeUnicodeOutput( $text ) { 03925 global $wgContLang; 03926 $codedText = $wgContLang->recodeForEdit( $text ); 03927 return $this->checkUnicodeCompliantBrowser() 03928 ? $codedText 03929 : $this->makeSafe( $codedText ); 03930 } 03931 03944 private function makeSafe( $invalue ) { 03945 // Armor existing references for reversibility. 03946 $invalue = strtr( $invalue, array( "&#x" => "�" ) ); 03947 03948 $bytesleft = 0; 03949 $result = ""; 03950 $working = 0; 03951 $valueLength = strlen( $invalue ); 03952 for ( $i = 0; $i < $valueLength; $i++ ) { 03953 $bytevalue = ord( $invalue[$i] ); 03954 if ( $bytevalue <= 0x7F ) { // 0xxx xxxx 03955 $result .= chr( $bytevalue ); 03956 $bytesleft = 0; 03957 } elseif ( $bytevalue <= 0xBF ) { // 10xx xxxx 03958 $working = $working << 6; 03959 $working += ( $bytevalue & 0x3F ); 03960 $bytesleft--; 03961 if ( $bytesleft <= 0 ) { 03962 $result .= "&#x" . strtoupper( dechex( $working ) ) . ";"; 03963 } 03964 } elseif ( $bytevalue <= 0xDF ) { // 110x xxxx 03965 $working = $bytevalue & 0x1F; 03966 $bytesleft = 1; 03967 } elseif ( $bytevalue <= 0xEF ) { // 1110 xxxx 03968 $working = $bytevalue & 0x0F; 03969 $bytesleft = 2; 03970 } else { // 1111 0xxx 03971 $working = $bytevalue & 0x07; 03972 $bytesleft = 3; 03973 } 03974 } 03975 return $result; 03976 } 03977 03986 private function unmakeSafe( $invalue ) { 03987 $result = ""; 03988 $valueLength = strlen( $invalue ); 03989 for ( $i = 0; $i < $valueLength; $i++ ) { 03990 if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) { 03991 $i += 3; 03992 $hexstring = ""; 03993 do { 03994 $hexstring .= $invalue[$i]; 03995 $i++; 03996 } while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) ); 03997 03998 // Do some sanity checks. These aren't needed for reversibility, 03999 // but should help keep the breakage down if the editor 04000 // breaks one of the entities whilst editing. 04001 if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) { 04002 $codepoint = hexdec( $hexstring ); 04003 $result .= codepointToUtf8( $codepoint ); 04004 } else { 04005 $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 ); 04006 } 04007 } else { 04008 $result .= substr( $invalue, $i, 1 ); 04009 } 04010 } 04011 // reverse the transform that we made for reversibility reasons. 04012 return strtr( $result, array( "�" => "&#x" ) ); 04013 } 04014 }