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