42 const AS_SUCCESS_UPDATE = 200;
47 const AS_SUCCESS_NEW_ARTICLE = 201;
52 const AS_HOOK_ERROR = 210;
57 const AS_HOOK_ERROR_EXPECTED = 212;
62 const AS_BLOCKED_PAGE_FOR_USER = 215;
67 const AS_CONTENT_TOO_BIG = 216;
72 const AS_USER_CANNOT_EDIT = 217;
77 const AS_READ_ONLY_PAGE_ANON = 218;
82 const AS_READ_ONLY_PAGE_LOGGED = 219;
87 const AS_READ_ONLY_PAGE = 220;
92 const AS_RATE_LIMITED = 221;
98 const AS_ARTICLE_WAS_DELETED = 222;
104 const AS_NO_CREATE_PERMISSION = 223;
109 const AS_BLANK_ARTICLE = 224;
114 const AS_CONFLICT_DETECTED = 225;
120 const AS_SUMMARY_NEEDED = 226;
125 const AS_TEXTBOX_EMPTY = 228;
130 const AS_MAX_ARTICLE_SIZE_EXCEEDED = 229;
145 const AS_SPAM_ERROR = 232;
150 const AS_IMAGE_REDIRECT_ANON = 233;
155 const AS_IMAGE_REDIRECT_LOGGED = 234;
161 const AS_NO_CHANGE_CONTENT_MODEL = 235;
166 const AS_PARSE_ERROR = 240;
171 const EDITFORM_ID =
'editform';
177 const POST_EDIT_COOKIE_KEY_PREFIX =
'PostEditRevision';
191 const POST_EDIT_COOKIE_DURATION = 1200;
202 private $mContextTitle =
null;
203 var $action =
'submit';
204 var $isConflict =
false;
205 var $isCssJsSubpage =
false;
206 var $isCssSubpage =
false;
207 var $isJsSubpage =
false;
208 var $isWrongCaseCssJsPage =
false;
210 var $deletedSinceEdit;
214 var $mTokenOk =
false;
215 var $mTokenOkExceptSuffix =
false;
216 var $mTriedSave =
false;
217 var $incompleteForm =
false;
219 var $kblength =
false;
220 var $missingComment =
false;
221 var $missingSummary =
false;
222 var $allowBlankSummary =
false;
225 #var $mPreviewTemplates;
236 var $hasPresetSummary =
false;
238 var $mBaseRevision =
false;
239 var $mShowSummaryField =
true;
242 var $save =
false, $preview =
false, $diff =
false;
243 var $minoredit =
false, $watchthis =
false, $recreate =
false;
244 var $textbox1 =
'', $textbox2 =
'',
$summary =
'', $nosummary =
false;
246 var $oldid = 0, $editintro =
'', $scrolltop =
null, $bot =
true;
247 var $contentModel =
null, $contentFormat =
null;
249 # Placeholders for text injection by hooks (must be HTML)
250 # extensions should take care to _append_ to the present value
251 public $editFormPageTop =
'';
252 public $editFormTextTop =
'';
253 public $editFormTextBeforeContent =
'';
254 public $editFormTextAfterWarn =
'';
255 public $editFormTextAfterTools =
'';
256 public $editFormTextBottom =
'';
257 public $editFormTextAfterContent =
'';
258 public $previewTextAfterContent =
'';
259 public $mPreloadContent =
null;
262 public $didSave =
false;
263 public $undidRev = 0;
265 public $suppressIntro =
false;
272 public $allowNonTextContent =
false;
279 $this->mTitle =
$article->getTitle();
281 $this->contentModel = $this->mTitle->getContentModel();
284 $this->contentFormat = $handler->getDefaultFormat();
290 public function getArticle() {
291 return $this->mArticle;
298 public function getTitle() {
299 return $this->mTitle;
307 public function setContextTitle(
$title ) {
308 $this->mContextTitle =
$title;
318 public function getContextTitle() {
319 if ( is_null( $this->mContextTitle ) ) {
323 return $this->mContextTitle;
334 public function isSupportedContentModel( $modelId ) {
335 return $this->allowNonTextContent ||
362 wfDebug( __METHOD__ .
": enter\n" );
365 if ( $wgRequest->getBool(
'redlink' ) && $this->mTitle->exists() ) {
366 $wgOut->redirect( $this->mTitle->getFullURL() );
371 $this->importFormData( $wgRequest );
372 $this->firsttime =
false;
375 $this->livePreview();
383 $this->preview =
true;
387 $this->formtype =
'save';
388 } elseif ( $this->preview ) {
389 $this->formtype =
'preview';
390 } elseif ( $this->diff ) {
391 $this->formtype =
'diff';
392 }
else { # First time through
393 $this->firsttime =
true;
394 if ( $this->previewOnOpen() ) {
395 $this->formtype =
'preview';
397 $this->formtype =
'initial';
401 $permErrors = $this->getEditPermissionErrors();
403 wfDebug( __METHOD__ .
": User can't edit\n" );
407 $this->displayPermissionsError( $permErrors );
415 $this->isConflict =
false;
417 $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
418 $this->isCssSubpage = $this->mTitle->isCssSubpage();
419 $this->isJsSubpage = $this->mTitle->isJsSubpage();
420 $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
422 # Show applicable editing introductions
423 if ( $this->formtype ==
'initial' || $this->firsttime ) {
427 # Attempt submission here. This will check for edit conflicts,
428 # and redundantly check for locked database, blocked IPs, etc.
429 # that edit() already checked just in case someone tries to sneak
430 # in the back door with a hand-edited submission URL.
432 if (
'save' == $this->formtype ) {
433 if ( !$this->attemptSave() ) {
440 # First time through: get contents, set time for conflict
442 if (
'initial' == $this->formtype || $this->firsttime ) {
443 if ( $this->initialiseForm() ===
false ) {
444 $this->noSuchSectionPage();
450 if ( !$this->mTitle->getArticleID() ) {
451 wfRunHooks(
'EditFormPreloadText',
array( &$this->textbox1, &$this->mTitle ) );
458 $this->showEditForm();
466 protected function getEditPermissionErrors() {
468 $permErrors = $this->mTitle->getUserPermissionsErrors(
'edit',
$wgUser );
469 # Can this title be created?
470 if ( !$this->mTitle->exists() ) {
471 $permErrors = array_merge( $permErrors,
472 wfArrayDiff2( $this->mTitle->getUserPermissionsErrors(
'create',
$wgUser ), $permErrors ) );
474 # Ignore some permissions errors when a user is just previewing/viewing diffs
477 if ( ( $this->preview || $this->diff )
478 && (
$error[0] ==
'blockedtext' ||
$error[0] ==
'autoblockedtext' )
500 protected function displayPermissionsError(
array $permErrors ) {
503 if ( $wgRequest->getBool(
'redlink' ) ) {
507 $wgOut->redirect( $this->mTitle->getFullURL() );
511 $content = $this->getContentObject();
513 # Use the normal message if there's nothing to display
514 if ( $this->firsttime && ( !$content || $content->
isEmpty() ) ) {
515 $action = $this->mTitle->exists() ?
'edit' :
516 ( $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage' );
520 $wgOut->setRobotPolicy(
'noindex,nofollow' );
521 $wgOut->setPageTitle(
wfMessage(
'viewsource-title', $this->getContextTitle()->getPrefixedText() ) );
522 $wgOut->addBacklinkSubtitle( $this->getContextTitle() );
523 $wgOut->addWikiText(
$wgOut->formatPermissionsErrorMessage( $permErrors,
'edit' ) );
524 $wgOut->addHTML(
"<hr />\n" );
526 # If the user made changes, preserve them when showing the markup
527 # (This happens when a user is blocked during edit, for instance)
528 if ( !$this->firsttime ) {
529 $text = $this->textbox1;
530 $wgOut->addWikiMsg(
'viewyourtext' );
532 $text = $this->toEditText( $content );
533 $wgOut->addWikiMsg(
'viewsourcetext' );
536 $this->showTextbox( $text,
'wpTextbox1',
array(
'readonly' ) );
541 $wgOut->addModules(
'mediawiki.action.edit.collapsibleFooter' );
543 if ( $this->mTitle->exists() ) {
544 $wgOut->returnToMain(
null, $this->mTitle );
554 function readOnlyPage(
$source =
null, $protected =
false, $reasons =
array(), $action =
null ) {
558 if ( $wgRequest->getBool(
'redlink' ) ) {
562 $wgOut->redirect( $this->mTitle->getFullURL() );
564 $wgOut->readOnlyPage(
$source, $protected, $reasons, $action );
573 protected function previewOnOpen() {
575 if ( $wgRequest->getVal(
'preview' ) ==
'yes' ) {
578 } elseif ( $wgRequest->getVal(
'preview' ) ==
'no' ) {
581 } elseif ( $this->
section ==
'new' ) {
584 } elseif ( ( $wgRequest->getVal(
'preload' ) !==
null || $this->mTitle->exists() ) &&
$wgUser->getOption(
'previewonfirst' ) ) {
587 } elseif ( !$this->mTitle->exists()
588 && isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
589 && $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()]
604 protected function isWrongCaseCssJsPage() {
605 if ( $this->mTitle->isCssJsSubpage() ) {
606 $name = $this->mTitle->getSkinFromCssJsSubpage();
607 $skins = array_merge(
611 return !in_array(
$name, $skins )
612 && in_array( strtolower(
$name ), $skins );
625 protected function isSectionEditSupported() {
627 return $contentHandler->supportsSections();
635 function importFormData( &$request ) {
640 # Section edit can come from either the form or a link
641 $this->
section = $request->getVal(
'wpSection', $request->getVal(
'section' ) );
643 if ( $this->
section !==
null && $this->
section !==
'' && !$this->isSectionEditSupported() ) {
645 throw new ErrorPageError(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
648 $this->isNew = !$this->mTitle->exists() || $this->
section ==
'new';
650 if ( $request->wasPosted() ) {
651 # These fields need to be checked for encoding.
652 # Also remove trailing whitespace, but don't remove _initial_
653 # whitespace from the text boxes. This may be significant formatting.
654 $this->textbox1 = $this->safeUnicodeInput( $request,
'wpTextbox1' );
655 if ( !$request->getCheck(
'wpTextbox2' ) ) {
659 wfProfileIn( get_class( $this ) .
"::importContentFormData" );
660 $textbox1 = $this->importContentFormData( $request );
661 if ( $textbox1 !==
null ) {
662 $this->textbox1 = $textbox1;
665 wfProfileOut( get_class( $this ) .
"::importContentFormData" );
668 # Truncate for whole multibyte characters
669 $this->summary =
$wgContLang->truncate( $request->getText(
'wpSummary' ), 255 );
671 # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
672 # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
674 $this->summary = preg_replace(
'/^\s*=+\s*(.*?)\s*=+\s*$/',
'$1', $this->summary );
676 # Treat sectiontitle the same way as summary.
677 # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
678 # currently doing double duty as both edit summary and section title. Right now this
679 # is just to allow API edits to work around this limitation, but this should be
680 # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
681 $this->sectiontitle =
$wgContLang->truncate( $request->getText(
'wpSectionTitle' ), 255 );
682 $this->sectiontitle = preg_replace(
'/^\s*=+\s*(.*?)\s*=+\s*$/',
'$1', $this->sectiontitle );
684 $this->edittime = $request->getVal(
'wpEdittime' );
685 $this->starttime = $request->getVal(
'wpStarttime' );
687 $undidRev = $request->getInt(
'wpUndidRevision' );
689 $this->undidRev = $undidRev;
692 $this->scrolltop = $request->getIntOrNull(
'wpScrolltop' );
694 if ( $this->textbox1 ===
'' && $request->getVal(
'wpTextbox1' ) === null ) {
698 $this->incompleteForm =
true;
702 $this->incompleteForm = is_null( $this->edittime );
704 if ( $this->incompleteForm ) {
705 # If the form is incomplete, force to preview.
706 wfDebug( __METHOD__ .
": Form data appears to be incomplete\n" );
707 wfDebug(
"POST DATA: " . var_export( $_POST,
true ) .
"\n" );
708 $this->preview =
true;
711 $this->preview = $request->getCheck(
'wpPreview' ) || $request->getCheck(
'wpLivePreview' );
712 $this->diff = $request->getCheck(
'wpDiff' );
716 $this->mTriedSave = !$this->preview;
718 if ( $this->tokenOk( $request ) ) {
719 # Some browsers will not report any submit button
720 # if the user hits enter in the comment box.
721 # The unmarked state will be assumed to be a save,
722 # if the form seems otherwise complete.
723 wfDebug( __METHOD__ .
": Passed token check.\n" );
724 } elseif ( $this->diff ) {
725 # Failed token check, but only requested "Show Changes".
726 wfDebug( __METHOD__ .
": Failed token check; Show Changes requested.\n" );
728 # Page might be a hack attempt posted from
729 # an external site. Preview instead of saving.
730 wfDebug( __METHOD__ .
": Failed token check; forcing preview\n" );
731 $this->preview =
true;
734 $this->
save = !$this->preview && !$this->diff;
735 if ( !preg_match(
'/^\d{14}$/', $this->edittime ) ) {
736 $this->edittime =
null;
739 if ( !preg_match(
'/^\d{14}$/', $this->starttime ) ) {
740 $this->starttime =
null;
743 $this->recreate = $request->getCheck(
'wpRecreate' );
745 $this->minoredit = $request->getCheck(
'wpMinoredit' );
746 $this->watchthis = $request->getCheck(
'wpWatchthis' );
748 # Don't force edit summaries when a user is editing their own user or talk page
750 && $this->mTitle->getText() ==
$wgUser->getName()
752 $this->allowBlankSummary =
true;
754 $this->allowBlankSummary = $request->getBool(
'wpIgnoreBlankSummary' ) || !
$wgUser->getOption(
'forceeditsummary' );
757 $this->autoSumm = $request->getText(
'wpAutoSummary' );
759 # Not a posted form? Start with nothing.
760 wfDebug( __METHOD__ .
": Not a posted form.\n" );
761 $this->textbox1 =
'';
763 $this->sectiontitle =
'';
764 $this->edittime =
'';
767 $this->preview =
false;
770 $this->minoredit =
false;
771 $this->watchthis = $request->getBool(
'watchthis',
false );
772 $this->recreate =
false;
776 if ( $this->
section ==
'new' && $request->getVal(
'preloadtitle' ) ) {
777 $this->sectiontitle = $request->getVal(
'preloadtitle' );
779 $this->summary = $request->getVal(
'preloadtitle' );
780 } elseif ( $this->
section !=
'new' && $request->getVal(
'summary' ) ) {
781 $this->summary = $request->getText(
'summary' );
782 if ( $this->summary !==
'' ) {
783 $this->hasPresetSummary =
true;
787 if ( $request->getVal(
'minor' ) ) {
788 $this->minoredit =
true;
792 $this->oldid = $request->getInt(
'oldid' );
794 $this->bot = $request->getBool(
'bot',
true );
795 $this->nosummary = $request->getBool(
'nosummary' );
797 $this->contentModel = $request->getText(
'model', $this->contentModel ); #may be overridden by revision
798 $this->contentFormat = $request->getText(
'format', $this->contentFormat ); #may be overridden by revision
802 'editpage-notsupportedcontentformat-title',
803 'editpage-notsupportedcontentformat-text',
807 #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
809 $this->live = $request->getCheck(
'live' );
810 $this->editintro = $request->getText(
'editintro',
812 $this->
section ===
'new' ?
'MediaWiki:addsection-editintro' :
'' );
828 protected function importContentFormData( &$request ) {
837 function initialiseForm() {
839 $this->edittime = $this->mArticle->getTimestamp();
841 $content = $this->getContentObject(
false ); #TODO: track
content object?!
842 if ( $content ===
false ) {
845 $this->textbox1 = $this->toEditText( $content );
848 # Sort out the "watch" checkbox
849 if (
$wgUser->getOption(
'watchdefault' ) ) {
851 $this->watchthis =
true;
852 } elseif (
$wgUser->getOption(
'watchcreations' ) && !$this->mTitle->exists() ) {
854 $this->watchthis =
true;
855 } elseif (
$wgUser->isWatched( $this->mTitle ) ) {
857 $this->watchthis =
true;
859 if (
$wgUser->getOption(
'minordefault' ) && !$this->isNew ) {
860 $this->minoredit =
true;
862 if ( $this->textbox1 ===
false ) {
876 function getContent( $def_text =
false ) {
879 if ( $def_text !==
null && $def_text !==
false && $def_text !==
'' ) {
880 $def_content = $this->toEditContent( $def_text );
882 $def_content =
false;
885 $content = $this->getContentObject( $def_content );
888 return $this->toEditText( $content );
898 protected function getContentObject( $def_content =
null ) {
907 if ( !$this->mTitle->exists() || $this->
section ==
'new' ) {
909 # If this is a system message, get the default text.
910 $msg = $this->mTitle->getDefaultMessageText();
912 $content = $this->toEditContent( $msg );
914 if ( $content ===
false ) {
915 # If requested, preload some text.
916 $preload = $wgRequest->getVal(
'preload',
918 $this->
section ===
'new' ?
'MediaWiki:addsection-preload' :
'' );
919 $params = $wgRequest->getArray(
'preloadparams',
array() );
921 $content = $this->getPreloadedContent( $preload,
$params );
927 $orig = $this->getOriginalContent(
$wgUser );
931 $content = $def_content;
934 $undoafter = $wgRequest->getInt(
'undoafter' );
935 $undo = $wgRequest->getInt(
'undo' );
937 if ( $undo > 0 && $undoafter > 0 ) {
942 # Sanity check, make sure it's the right page,
943 # the revisions exist and they were not deleted.
944 # Otherwise, $content will be left as-is.
945 if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
949 $content = $this->mArticle->getUndoContent( $undorev, $oldrev );
951 if ( $content ===
false ) {
952 # Warn the user that something went wrong
953 $undoMsg =
'failure';
955 $oldContent = $this->mArticle->getPage()->getContent(
Revision::RAW );
959 if ( $newContent->equals( $oldContent ) ) {
960 # Tell the user that the undo results in no change,
961 # i.e. the revisions were already undone.
962 $undoMsg =
'nochange';
965 # Inform the user of our success and set an automatic edit summary
966 $undoMsg =
'success';
968 # If we just undid one rev, use an autosummary
969 $firstrev = $oldrev->getNext();
970 if ( $firstrev && $firstrev->getId() == $undo ) {
971 $userText = $undorev->getUserText();
972 if ( $userText ===
'' ) {
974 'undo-summary-username-hidden',
976 )->inContentLanguage()->text();
982 )->inContentLanguage()->text();
984 if ( $this->summary ===
'' ) {
985 $this->summary = $undoSummary;
987 $this->summary = $undoSummary .
wfMessage(
'colon-separator' )
990 $this->undidRev = $undo;
992 $this->formtype =
'diff';
1003 $class = ( $undoMsg ==
'success' ?
'' :
'error ' ) .
"mw-undo-{$undoMsg}";
1004 $this->editFormPageTop .=
$wgOut->parse(
"<div class=\"{$class}\">" .
1005 wfMessage(
'undo-' . $undoMsg )->plain() .
'</div>',
true,
true );
1008 if ( $content ===
false ) {
1009 $content = $this->getOriginalContent(
$wgUser );
1033 private function getOriginalContent(
User $user ) {
1034 if ( $this->
section ==
'new' ) {
1035 return $this->getCurrentContent();
1037 $revision = $this->mArticle->getRevisionFetched();
1038 if ( $revision ===
null ) {
1039 if ( !$this->contentModel ) {
1040 $this->contentModel = $this->getTitle()->getContentModel();
1044 return $handler->makeEmptyContent();
1058 protected function getCurrentContent() {
1059 $rev = $this->mArticle->getRevision();
1062 if ( $content ===
false || $content ===
null ) {
1063 if ( !$this->contentModel ) {
1064 $this->contentModel = $this->getTitle()->getContentModel();
1068 return $handler->makeEmptyContent();
1070 # nasty side-effect, but needed for consistency
1071 $this->contentModel =
$rev->getContentModel();
1072 $this->contentFormat =
$rev->getContentFormat();
1084 public function setPreloadedText( $text ) {
1087 $content = $this->toEditContent( $text );
1089 $this->setPreloadedContent( $content );
1099 public function setPreloadedContent(
Content $content ) {
1100 $this->mPreloadContent = $content;
1113 protected function getPreloadedText( $preload ) {
1116 $content = $this->getPreloadedContent( $preload );
1117 $text = $this->toEditText( $content );
1133 protected function getPreloadedContent( $preload,
$params =
array() ) {
1136 if ( !empty( $this->mPreloadContent ) ) {
1137 return $this->mPreloadContent;
1142 if ( $preload ===
'' ) {
1143 return $handler->makeEmptyContent();
1147 # Check for existence to avoid getting MediaWiki:Noarticletext
1150 return $handler->makeEmptyContent();
1154 if ( $page->isRedirect() ) {
1155 $title = $page->getRedirectTarget();
1159 return $handler->makeEmptyContent();
1169 return $handler->makeEmptyContent();
1172 if ( $content->
getModel() !== $handler->getModelID() ) {
1173 $converted = $content->
convert( $handler->getModelID() );
1175 if ( !$converted ) {
1177 wfDebug(
"Attempt to preload incompatible content: "
1178 .
"can't convert " . $content->
getModel()
1179 .
" to " . $handler->getModelID() );
1181 return $handler->makeEmptyContent();
1184 $content = $converted;
1197 function tokenOk( &$request ) {
1199 $token = $request->getVal(
'wpEditToken' );
1200 $this->mTokenOk =
$wgUser->matchEditToken( $token );
1201 $this->mTokenOkExceptSuffix =
$wgUser->matchEditTokenNoSuffix( $token );
1202 return $this->mTokenOk;
1221 protected function setPostEditCookie() {
1222 $revisionId = $this->mArticle->getLatest();
1223 $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1226 $response->setcookie( $postEditKey,
'1', time() + self::POST_EDIT_COOKIE_DURATION,
array(
1228 'httpOnly' =>
false,
1237 public function attemptSave() {
1240 $resultDetails =
false;
1241 # Allow bots to exempt some edits from bot flagging
1242 $bot =
$wgUser->isAllowed(
'bot' ) && $this->bot;
1243 $status = $this->internalAttemptSave( $resultDetails, $bot );
1245 return $this->handleStatus( $status, $resultDetails );
1257 private function handleStatus(
Status $status, $resultDetails ) {
1261 if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
1262 $this->didSave =
true;
1263 if ( !$resultDetails[
'nullEdit'] ) {
1264 $this->setPostEditCookie();
1268 switch ( $status->value ) {
1269 case self::AS_HOOK_ERROR_EXPECTED:
1270 case self::AS_CONTENT_TOO_BIG:
1271 case self::AS_ARTICLE_WAS_DELETED:
1272 case self::AS_CONFLICT_DETECTED:
1273 case self::AS_SUMMARY_NEEDED:
1274 case self::AS_TEXTBOX_EMPTY:
1275 case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
1279 case self::AS_HOOK_ERROR:
1282 case self::AS_PARSE_ERROR:
1283 $wgOut->addWikiText(
'<div class="error">' . $status->
getWikiText() .
'</div>' );
1286 case self::AS_SUCCESS_NEW_ARTICLE:
1287 $query = $resultDetails[
'redirect'] ?
'redirect=no' :
'';
1288 $anchor = isset( $resultDetails[
'sectionanchor'] ) ? $resultDetails[
'sectionanchor'] :
'';
1289 $wgOut->redirect( $this->mTitle->getFullURL(
$query ) . $anchor );
1292 case self::AS_SUCCESS_UPDATE:
1294 $sectionanchor = $resultDetails[
'sectionanchor'];
1297 wfRunHooks(
'ArticleUpdateBeforeRedirect',
array( $this->mArticle, &$sectionanchor, &$extraQuery ) );
1299 if ( $resultDetails[
'redirect'] ) {
1300 if ( $extraQuery ==
'' ) {
1301 $extraQuery =
'redirect=no';
1303 $extraQuery =
'redirect=no&' . $extraQuery;
1306 $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1309 case self::AS_BLANK_ARTICLE:
1310 $wgOut->redirect( $this->getContextTitle()->getFullURL() );
1313 case self::AS_SPAM_ERROR:
1314 $this->spamPageWithContent( $resultDetails[
'spam'] );
1317 case self::AS_BLOCKED_PAGE_FOR_USER:
1320 case self::AS_IMAGE_REDIRECT_ANON:
1321 case self::AS_IMAGE_REDIRECT_LOGGED:
1324 case self::AS_READ_ONLY_PAGE_ANON:
1325 case self::AS_READ_ONLY_PAGE_LOGGED:
1328 case self::AS_READ_ONLY_PAGE:
1331 case self::AS_RATE_LIMITED:
1334 case self::AS_NO_CREATE_PERMISSION:
1335 $permission = $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage';
1338 case self::AS_NO_CHANGE_CONTENT_MODEL:
1346 $this->hookError =
'<div class="error">' . $status->getWikitext() .
1364 array( $this, $content, &$this->hookError, $this->summary ) ) ) {
1366 # Error messages etc. could be handled within the hook...
1367 $status->
fatal(
'hookaborted' );
1368 $status->value = self::AS_HOOK_ERROR;
1370 } elseif ( $this->hookError !=
'' ) {
1371 # ...or the hook could be expecting us to produce an error
1372 $status->
fatal(
'hookaborted' );
1373 $status->value = self::AS_HOOK_ERROR_EXPECTED;
1380 $user, $this->minoredit ) ) ) {
1382 # Error messages etc. could be handled within the hook...
1385 $status->fatal(
'hookaborted' );
1386 $status->value = self::AS_HOOK_ERROR;
1388 } elseif ( !$status->isOK() ) {
1389 # ...or the hook could be expecting us to produce an error
1391 $this->hookError = $status->getWikiText();
1392 $status->fatal(
'hookaborted' );
1393 $status->value = self::AS_HOOK_ERROR_EXPECTED;
1416 function internalAttemptSave( &
$result, $bot =
false ) {
1425 wfDebug(
"Hook 'EditPage::attemptSave' aborted article saving\n" );
1426 $status->fatal(
'hookaborted' );
1427 $status->value = self::AS_HOOK_ERROR;
1433 $spam = $wgRequest->getText(
'wpAntispam' );
1434 if ( $spam !==
'' ) {
1439 $this->mTitle->getPrefixedText() .
1440 '" submitted bogus field "' .
1444 $status->fatal(
'spamprotectionmatch',
false );
1445 $status->value = self::AS_SPAM_ERROR;
1452 # Construct Content object
1453 $textbox_content = $this->toEditContent( $this->textbox1 );
1455 $status->fatal(
'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
1456 $status->value = self::AS_PARSE_ERROR;
1462 # Check image redirect
1463 if ( $this->mTitle->getNamespace() ==
NS_FILE &&
1464 $textbox_content->isRedirect() &&
1465 !
$wgUser->isAllowed(
'upload' ) ) {
1466 $code =
$wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
1467 $status->setResult(
false, $code );
1476 $match = self::matchSummarySpamRegex( $this->summary );
1477 if ( $match ===
false && $this->
section ==
'new' ) {
1478 # $wgSpamRegex is enforced on this new heading/summary because, unlike
1479 # regular summaries, it is added to the actual wikitext.
1480 if ( $this->sectiontitle !==
'' ) {
1481 # This branch is taken when the API is used with the 'sectiontitle' parameter.
1482 $match = self::matchSpamRegex( $this->sectiontitle );
1484 # This branch is taken when the "Add Topic" user interface is used, or the API
1485 # is used with the 'summary' parameter.
1486 $match = self::matchSpamRegex( $this->summary );
1489 if ( $match ===
false ) {
1490 $match = self::matchSpamRegex( $this->textbox1 );
1492 if ( $match !==
false ) {
1494 $ip = $wgRequest->getIP();
1495 $pdbk = $this->mTitle->getPrefixedDBkey();
1496 $match = str_replace(
"\n",
'', $match );
1497 wfDebugLog(
'SpamRegex',
"$ip spam regex hit [[$pdbk]]: \"$match\"" );
1498 $status->fatal(
'spamprotectionmatch', $match );
1499 $status->value = self::AS_SPAM_ERROR;
1504 if ( !
wfRunHooks(
'EditFilter',
array( $this, $this->textbox1, $this->
section, &$this->hookError, $this->summary ) ) ) {
1505 # Error messages etc. could be handled within the hook...
1506 $status->fatal(
'hookaborted' );
1507 $status->value = self::AS_HOOK_ERROR;
1511 } elseif ( $this->hookError !=
'' ) {
1512 # ...or the hook could be expecting us to produce an error
1513 $status->fatal(
'hookaborted' );
1514 $status->value = self::AS_HOOK_ERROR_EXPECTED;
1520 if (
$wgUser->isBlockedFrom( $this->mTitle,
false ) ) {
1522 $wgUser->spreadAnyEditBlock();
1523 # Check block state against master, thus 'false'.
1524 $status->setResult(
false, self::AS_BLOCKED_PAGE_FOR_USER );
1530 $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
1531 if ( $this->kblength > $wgMaxArticleSize ) {
1533 $this->tooBig =
true;
1534 $status->setResult(
false, self::AS_CONTENT_TOO_BIG );
1540 if ( !
$wgUser->isAllowed(
'edit' ) ) {
1542 $status->setResult(
false, self::AS_READ_ONLY_PAGE_ANON );
1547 $status->fatal(
'readonlytext' );
1548 $status->value = self::AS_READ_ONLY_PAGE_LOGGED;
1555 if ( $this->contentModel !== $this->mTitle->getContentModel()
1556 && !
$wgUser->isAllowed(
'editcontentmodel' )
1558 $status->setResult(
false, self::AS_NO_CHANGE_CONTENT_MODEL );
1565 $status->fatal(
'readonlytext' );
1566 $status->value = self::AS_READ_ONLY_PAGE;
1571 if (
$wgUser->pingLimiter() ||
$wgUser->pingLimiter(
'linkpurge', 0 ) ) {
1572 $status->fatal(
'actionthrottledtext' );
1573 $status->value = self::AS_RATE_LIMITED;
1579 # If the article has been deleted while editing, don't save it without
1581 if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
1582 $status->setResult(
false, self::AS_ARTICLE_WAS_DELETED );
1590 # Load the page data from the master. If anything changes in the meantime,
1591 # we detect it by using page_latest like a token in a 1 try compare-and-swap.
1592 $this->mArticle->loadPageData(
'fromdbmaster' );
1593 $new = !$this->mArticle->exists();
1597 if ( !$this->mTitle->userCan(
'create',
$wgUser ) ) {
1598 $status->fatal(
'nocreatetext' );
1599 $status->value = self::AS_NO_CREATE_PERMISSION;
1600 wfDebug( __METHOD__ .
": no create permission\n" );
1608 $defaultMessageText = $this->mTitle->getDefaultMessageText();
1609 if ( $this->mTitle->getNamespace() ===
NS_MEDIAWIKI && $defaultMessageText !==
false ) {
1610 $defaultText = $defaultMessageText;
1615 if ( $this->textbox1 === $defaultText ) {
1616 $status->setResult(
false, self::AS_BLANK_ARTICLE );
1621 if ( !$this->runPostMergeFilters( $textbox_content, $status,
$wgUser ) ) {
1626 $content = $textbox_content;
1628 $result[
'sectionanchor'] =
'';
1629 if ( $this->
section ==
'new' ) {
1630 if ( $this->sectiontitle !==
'' ) {
1635 $result[
'sectionanchor'] =
$wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
1640 if ( $this->summary ===
'' ) {
1641 $cleanSectionTitle =
$wgParser->stripSectionName( $this->sectiontitle );
1642 $this->summary =
wfMessage(
'newsectionsummary' )
1643 ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1645 } elseif ( $this->summary !==
'' ) {
1650 $result[
'sectionanchor'] =
$wgParser->guessLegacySectionNameFromWikiText( $this->summary );
1653 $cleanSummary =
$wgParser->stripSectionName( $this->summary );
1654 $this->summary =
wfMessage(
'newsectionsummary' )
1655 ->rawParams( $cleanSummary )->inContentLanguage()->text();
1659 $status->value = self::AS_SUCCESS_NEW_ARTICLE;
1663 # Article exists. Check for edit conflict.
1665 $this->mArticle->clear(); # Force reload
of dates,
etc.
1666 $timestamp = $this->mArticle->getTimestamp();
1668 wfDebug(
"timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
1671 $this->isConflict =
true;
1672 if ( $this->
section ==
'new' ) {
1673 if ( $this->mArticle->getUserText() ==
$wgUser->getName() &&
1678 wfDebug( __METHOD__ .
": duplicate new section submission; trigger edit conflict!\n" );
1681 $this->isConflict =
false;
1682 wfDebug( __METHOD__ .
": conflict suppressed; new section\n" );
1685 $wgUser->getId(), $this->edittime ) ) {
1686 # Suppress edit conflict with self, except for section edits where merging is required.
1687 wfDebug( __METHOD__ .
": Suppressing edit conflict, same user.\n" );
1688 $this->isConflict =
false;
1693 if ( $this->sectiontitle !==
'' ) {
1694 $sectionTitle = $this->sectiontitle;
1701 if ( $this->isConflict ) {
1702 wfDebug( __METHOD__ .
": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
1703 .
" (article time '{$timestamp}')\n" );
1705 $content = $this->mArticle->replaceSectionContent( $this->
section, $textbox_content, $sectionTitle, $this->edittime );
1707 wfDebug( __METHOD__ .
": getting section '{$this->section}'\n" );
1708 $content = $this->mArticle->replaceSectionContent( $this->
section, $textbox_content, $sectionTitle );
1711 if ( is_null( $content ) ) {
1712 wfDebug( __METHOD__ .
": activating conflict; section replace failed.\n" );
1713 $this->isConflict =
true;
1714 $content = $textbox_content;
1715 } elseif ( $this->isConflict ) {
1717 if ( $this->mergeChangesIntoContent( $content ) ) {
1719 $this->isConflict =
false;
1720 wfDebug( __METHOD__ .
": Suppressing edit conflict, successful merge.\n" );
1724 wfDebug( __METHOD__ .
": Keeping edit conflict, failed merge.\n" );
1728 if ( $this->isConflict ) {
1729 $status->setResult(
false, self::AS_CONFLICT_DETECTED );
1734 if ( !$this->runPostMergeFilters( $content, $status,
$wgUser ) ) {
1739 if ( $this->
section ==
'new' ) {
1741 if ( !$this->allowBlankSummary && trim( $this->summary ) ==
'' ) {
1742 $this->missingSummary =
true;
1743 $status->fatal(
'missingsummary' );
1744 $status->value = self::AS_SUMMARY_NEEDED;
1750 if ( $this->textbox1 ==
'' ) {
1751 $this->missingComment =
true;
1752 $status->fatal(
'missingcommenttext' );
1753 $status->value = self::AS_TEXTBOX_EMPTY;
1757 } elseif ( !$this->allowBlankSummary
1758 && !$content->
equals( $this->getOriginalContent(
$wgUser ) )
1760 && md5( $this->summary ) == $this->autoSumm
1762 $this->missingSummary =
true;
1763 $status->fatal(
'missingsummary' );
1764 $status->value = self::AS_SUMMARY_NEEDED;
1771 $sectionanchor =
'';
1772 if ( $this->
section ==
'new' ) {
1773 if ( $this->sectiontitle !==
'' ) {
1774 $sectionanchor =
$wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
1778 if ( $this->summary ===
'' ) {
1779 $cleanSectionTitle =
$wgParser->stripSectionName( $this->sectiontitle );
1780 $this->summary =
wfMessage(
'newsectionsummary' )
1781 ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1783 } elseif ( $this->summary !==
'' ) {
1784 $sectionanchor =
$wgParser->guessLegacySectionNameFromWikiText( $this->summary );
1785 # This is a new section, so create a link to the new section
1786 # in the revision summary.
1787 $cleanSummary =
$wgParser->stripSectionName( $this->summary );
1788 $this->summary =
wfMessage(
'newsectionsummary' )
1789 ->rawParams( $cleanSummary )->inContentLanguage()->text();
1791 } elseif ( $this->
section !=
'' ) {
1792 # Try to get a section anchor from the section source, redirect to edited section if header found
1793 # XXX: might be better to integrate this into Article::replaceSection
1794 # for duplicate heading checking and maybe parsing
1795 $hasmatch = preg_match(
"/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1,
$matches );
1796 # we can't deal with anchors, includes, html etc in the header for now,
1797 # headline would need to be parsed to improve this
1798 if ( $hasmatch && strlen(
$matches[2] ) > 0 ) {
1802 $result[
'sectionanchor'] = $sectionanchor;
1809 $this->textbox1 = $this->toEditText( $content );
1812 $status->value = self::AS_SUCCESS_UPDATE;
1816 $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
1817 if ( $this->kblength > $wgMaxArticleSize ) {
1818 $this->tooBig =
true;
1819 $status->setResult(
false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
1826 ( ( $this->minoredit && !$this->isNew ) ?
EDIT_MINOR : 0 ) |
1829 $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary,
$flags,
1830 false,
null, $this->contentFormat );
1832 if ( !$doEditStatus->isOK() ) {
1836 $errors = $doEditStatus->getErrorsArray();
1837 if ( in_array( $errors[0][0],
1838 array(
'edit-gone-missing',
'edit-conflict',
'edit-already-exists' ) )
1840 $this->isConflict =
true;
1842 $doEditStatus->value = self::AS_END;
1845 return $doEditStatus;
1848 $result[
'nullEdit'] = $doEditStatus->hasMessage(
'edit-no-change' );
1851 $wgUser->pingLimiter(
'linkpurge' );
1854 $this->updateWatchlist();
1862 protected function updateWatchlist() {
1870 $watch = $this->watchthis;
1890 function mergeChangesInto( &$editText ) {
1893 $editContent = $this->toEditContent( $editText );
1895 $ok = $this->mergeChangesIntoContent( $editContent );
1898 $editText = $this->toEditText( $editContent );
1915 private function mergeChangesIntoContent( &$editContent ) {
1921 $baseRevision = $this->getBaseRevision();
1922 $baseContent = $baseRevision ? $baseRevision->getContent() :
null;
1924 if ( is_null( $baseContent ) ) {
1931 $currentContent = $currentRevision ? $currentRevision->getContent() :
null;
1933 if ( is_null( $currentContent ) ) {
1940 $result = $handler->merge3( $baseContent, $editContent, $currentContent );
1955 function getBaseRevision() {
1956 if ( !$this->mBaseRevision ) {
1959 $db, $this->mTitle, $this->edittime );
1961 return $this->mBaseRevision;
1971 public static function matchSpamRegex( $text ) {
1974 $regexes = (
array)$wgSpamRegex;
1975 return self::matchSpamRegexInternal( $text, $regexes );
1985 public static function matchSummarySpamRegex( $text ) {
1986 global $wgSummarySpamRegex;
1987 $regexes = (
array)$wgSummarySpamRegex;
1988 return self::matchSpamRegexInternal( $text, $regexes );
1996 protected static function matchSpamRegexInternal( $text, $regexes ) {
1997 foreach ( $regexes
as $regex ) {
1999 if ( preg_match( $regex, $text,
$matches ) ) {
2006 function setHeaders() {
2009 $wgOut->addModules(
'mediawiki.action.edit' );
2010 $wgOut->addModuleStyles(
'mediawiki.action.edit.styles' );
2012 if (
$wgUser->getOption(
'uselivepreview',
false ) ) {
2013 $wgOut->addModules(
'mediawiki.action.edit.preview' );
2016 if (
$wgUser->getOption(
'useeditwarning',
false ) ) {
2017 $wgOut->addModules(
'mediawiki.action.edit.editWarning' );
2020 $wgOut->setRobotPolicy(
'noindex,nofollow' );
2022 # Enabled article-related sidebar, toplinks, etc.
2023 $wgOut->setArticleRelated(
true );
2025 $contextTitle = $this->getContextTitle();
2026 if ( $this->isConflict ) {
2027 $msg =
'editconflict';
2028 } elseif ( $contextTitle->exists() && $this->
section !=
'' ) {
2029 $msg = $this->
section ==
'new' ?
'editingcomment' :
'editingsection';
2031 $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() ==
NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !==
false ) ?
2032 'editing' :
'creating';
2034 # Use the title defined by DISPLAYTITLE magic word when present
2035 $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() :
false;
2036 if ( $displayTitle ===
false ) {
2037 $displayTitle = $contextTitle->getPrefixedText();
2045 protected function showIntro() {
2047 if ( $this->suppressIntro ) {
2051 $namespace = $this->mTitle->getNamespace();
2054 # Show a warning if editing an interface message
2055 $wgOut->wrapWikiMsg(
"<div class='mw-editinginterface'>\n$1\n</div>",
'editinginterface' );
2056 } elseif ( $namespace ==
NS_FILE ) {
2057 # Show a hint to shared repo
2060 $descUrl =
$file->getDescriptionUrl();
2061 # there must be a description url to show a hint to shared repo
2063 if ( !$this->mTitle->exists() ) {
2064 $wgOut->wrapWikiMsg(
"<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>",
array(
2065 'sharedupload-desc-create',
$file->getRepo()->getDisplayName(), $descUrl
2068 $wgOut->wrapWikiMsg(
"<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>",
array(
2069 'sharedupload-desc-edit',
$file->getRepo()->getDisplayName(), $descUrl
2076 # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2077 # Show log extract when the user is currently blocked
2079 $parts = explode(
'/', $this->mTitle->getText(), 2 );
2080 $username = $parts[0];
2083 if ( !(
$user &&
$user->isLoggedIn() ) && !$ip ) { #
User does not exist
2084 $wgOut->wrapWikiMsg(
"<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2086 } elseif (
$user->isBlocked() ) { # Show log extract
if the
user is currently blocked
2090 $user->getUserPage(),
2094 'showIfEmpty' =>
false,
2096 'blocked-notice-logextract',
2097 $user->getName() # Support GENDER
in notice
2103 # Try to add a custom edit intro, or use the standard one if this is not possible.
2104 if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
2108 if (
$wgUser->isLoggedIn() ) {
2111 "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2120 "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2122 'newarticletextanon',
2128 # Give a notice if the user is editing a deleted/moved page...
2129 if ( !$this->mTitle->exists() ) {
2134 'conds' =>
array(
"log_action != 'revision'" ),
2135 'showIfEmpty' =>
false,
2136 'msgKey' =>
array(
'recreate-moveddeleted-warn' )
2147 protected function showCustomIntro() {
2148 if ( $this->editintro ) {
2153 $wgOut->addWikiTextTitleTidy(
'{{:' .
$title->getFullText() .
'}}', $this->mTitle );
2176 protected function toEditText( $content ) {
2177 if ( $content ===
null || $content ===
false ) {
2181 if ( is_string( $content ) ) {
2185 if ( !$this->isSupportedContentModel( $content->
getModel() ) ) {
2186 throw new MWException(
'This content model is not supported: '
2190 return $content->
serialize( $this->contentFormat );
2207 protected function toEditContent( $text ) {
2208 if ( $text ===
false || $text ===
null ) {
2213 $this->contentModel, $this->contentFormat );
2215 if ( !$this->isSupportedContentModel( $content->
getModel() ) ) {
2216 throw new MWException(
'This content model is not supported: '
2228 function showEditForm( $formCallback =
null ) {
2233 # need to parse the preview early so that we know which templates are used,
2234 # otherwise users with "show preview after edit box" will get a blank list
2235 # we parse this near the beginning so that setHeaders can do the title
2236 # setting work instead of leaving it in getPreviewText
2237 $previewOutput =
'';
2238 if ( $this->formtype ==
'preview' ) {
2239 $previewOutput = $this->getPreviewText();
2244 $this->setHeaders();
2246 if ( $this->showHeader() ===
false ) {
2251 $wgOut->addHTML( $this->editFormPageTop );
2253 if (
$wgUser->getOption(
'previewontop' ) ) {
2254 $this->displayPreviewArea( $previewOutput,
true );
2257 $wgOut->addHTML( $this->editFormTextTop );
2259 $showToolbar =
true;
2260 if ( $this->wasDeletedSinceLastEdit() ) {
2261 if ( $this->formtype ==
'save' ) {
2264 $showToolbar =
false;
2266 $wgOut->wrapWikiMsg(
"<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2267 'deletedwhileediting' );
2274 'method' =>
'post',
'action' => $this->getActionURL( $this->getContextTitle() ),
2275 'enctype' =>
'multipart/form-data' ) ) );
2277 if ( is_callable( $formCallback ) ) {
2278 call_user_func_array( $formCallback,
array( &
$wgOut ) );
2285 .
Xml::element(
'input',
array(
'type' =>
'text',
'name' =>
'wpAntispam',
'id' =>
'wpAntispam',
'value' =>
'' ) )
2292 $this->showFormBeforeText();
2294 if ( $this->wasDeletedSinceLastEdit() &&
'save' == $this->formtype ) {
2295 $username = $this->lastDelete->user_name;
2296 $comment = $this->lastDelete->log_comment;
2301 ?
'confirmrecreate-noreason'
2302 :
'confirmrecreate';
2304 '<div class="mw-confirm-recreate">' .
2305 wfMessage( $key, $username,
"<nowiki>$comment</nowiki>" )->parse() .
2313 # When the summary is hidden, also hide them on preview/show changes
2314 if ( $this->nosummary ) {
2318 # If a blank edit summary was previously provided, and the appropriate
2319 # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2320 # user being bounced back more than once in the event that a summary
2323 # For a bit more sophisticated detection of blank summaries, hash the
2324 # automatic one and pass that in the hidden field wpAutoSummary.
2325 if ( $this->missingSummary || ( $this->
section ==
'new' && $this->nosummary ) ) {
2329 if ( $this->undidRev ) {
2333 if ( $this->hasPresetSummary ) {
2337 $this->autoSumm = md5(
'' );
2340 $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
2348 if ( $this->
section ==
'new' ) {
2349 $this->showSummaryInput(
true, $this->summary );
2350 $wgOut->addHTML( $this->getSummaryPreview(
true, $this->summary ) );
2353 $wgOut->addHTML( $this->editFormTextBeforeContent );
2355 if ( !$this->isCssJsSubpage && $showToolbar &&
$wgUser->getOption(
'showtoolbar' ) ) {
2356 $wgOut->addHTML( EditPage::getEditToolbar() );
2359 if ( $this->isConflict ) {
2364 $this->textbox2 = $this->textbox1;
2366 $content = $this->getCurrentContent();
2367 $this->textbox1 = $this->toEditText( $content );
2369 $this->showTextbox1();
2371 $this->showContentForm();
2374 $wgOut->addHTML( $this->editFormTextAfterContent );
2376 $this->showStandardInputs();
2378 $this->showFormAfterText();
2380 $this->showTosSummary();
2382 $this->showEditTools();
2384 $wgOut->addHTML( $this->editFormTextAfterTools .
"\n" );
2393 self::getPreviewLimitReport( $this->mParserOutput ) ) );
2395 $wgOut->addModules(
'mediawiki.action.edit.collapsibleFooter' );
2397 if ( $this->isConflict ) {
2399 $this->showConflict();
2402 $msg =
wfMessage(
'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
2403 $wgOut->addWikiText(
'<div class="error">' . $msg->text() .
'</div>' );
2407 $wgOut->addHTML( $this->editFormTextBottom .
"\n</form>\n" );
2409 if ( !
$wgUser->getOption(
'previewontop' ) ) {
2410 $this->displayPreviewArea( $previewOutput,
false );
2422 public static function extractSectionTitle( $text ) {
2423 preg_match(
"/^(=+)(.+)\\1\\s*(\n|$)/i", $text,
$matches );
2432 protected function showHeader() {
2435 if ( $this->mTitle->isTalkPage() ) {
2436 $wgOut->addWikiMsg(
'talkpagetext' );
2440 $wgOut->addHTML( implode(
"\n", $this->mTitle->getEditNotices( $this->oldid ) ) );
2442 if ( $this->isConflict ) {
2443 $wgOut->wrapWikiMsg(
"<div class='mw-explainconflict'>\n$1\n</div>",
'explainconflict' );
2444 $this->edittime = $this->mArticle->getTimestamp();
2446 if ( $this->
section !=
'' && !$this->isSectionEditSupported() ) {
2450 $wgOut->showErrorPage(
'sectioneditnotsupported-title',
'sectioneditnotsupported-text' );
2455 if ( !$this->summary && !$this->preview && !$this->diff ) {
2456 $sectionTitle = self::extractSectionTitle( $this->textbox1 );
2457 if ( $sectionTitle !==
false ) {
2458 $this->summary =
"/* $sectionTitle */ ";
2463 if ( $this->missingComment ) {
2464 $wgOut->wrapWikiMsg(
"<div id='mw-missingcommenttext'>\n$1\n</div>",
'missingcommenttext' );
2467 if ( $this->missingSummary && $this->
section !=
'new' ) {
2468 $wgOut->wrapWikiMsg(
"<div id='mw-missingsummary'>\n$1\n</div>",
'missingsummary' );
2471 if ( $this->missingSummary && $this->
section ==
'new' ) {
2472 $wgOut->wrapWikiMsg(
"<div id='mw-missingcommentheader'>\n$1\n</div>",
'missingcommentheader' );
2475 if ( $this->hookError !==
'' ) {
2476 $wgOut->addWikiText( $this->hookError );
2479 if ( !$this->checkUnicodeCompliantBrowser() ) {
2480 $wgOut->addWikiMsg(
'nonunicodebrowser' );
2483 if ( $this->
section !=
'new' ) {
2484 $revision = $this->mArticle->getRevisionFetched();
2489 $wgOut->wrapWikiMsg(
"<div class='mw-warning plainlinks'>\n$1\n</div>\n",
'rev-deleted-text-permission' );
2491 $wgOut->wrapWikiMsg(
"<div class='mw-warning plainlinks'>\n$1\n</div>\n",
'rev-deleted-text-view' );
2494 if ( !$revision->isCurrent() ) {
2495 $this->mArticle->setOldSubtitle( $revision->getId() );
2496 $wgOut->addWikiMsg(
'editingold' );
2498 } elseif ( $this->mTitle->exists() ) {
2501 $wgOut->wrapWikiMsg(
"<div class='errorbox'>\n$1\n</div>\n",
2502 array(
'missing-revision', $this->oldid ) );
2509 } elseif (
$wgUser->isAnon() ) {
2510 if ( $this->formtype !=
'preview' ) {
2511 $wgOut->wrapWikiMsg(
"<div id=\"mw-anon-edit-warning\">\n$1</div>",
'anoneditwarning' );
2513 $wgOut->wrapWikiMsg(
"<div id=\"mw-anon-preview-warning\">\n$1</div>",
'anonpreviewwarning' );
2516 if ( $this->isCssJsSubpage ) {
2517 # Check the skin exists
2518 if ( $this->isWrongCaseCssJsPage ) {
2519 $wgOut->wrapWikiMsg(
"<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>",
array(
'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) );
2521 if ( $this->getTitle()->isSubpageOf(
$wgUser->getUserPage() ) ) {
2522 if ( $this->formtype !==
'preview' ) {
2523 if ( $this->isCssSubpage ) {
2524 $wgOut->wrapWikiMsg(
"<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
array(
'usercssyoucanpreview' ) );
2527 if ( $this->isJsSubpage ) {
2528 $wgOut->wrapWikiMsg(
"<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
array(
'userjsyoucanpreview' ) );
2535 if ( $this->mTitle->isProtected(
'edit' ) &&
2538 # Is the title semi-protected?
2539 if ( $this->mTitle->isSemiProtected() ) {
2540 $noticeMsg =
'semiprotectedpagewarning';
2542 # Then it must be protected based on static groups (regular)
2543 $noticeMsg =
'protectedpagewarning';
2546 array(
'lim' => 1,
'msgKey' =>
array( $noticeMsg ) ) );
2548 if ( $this->mTitle->isCascadeProtected() ) {
2549 # Is this page under cascading protection from some source pages?
2550 list( $cascadeSources, ) = $this->mTitle->getCascadeProtectionSources();
2551 $notice =
"<div class='mw-cascadeprotectedwarning'>\n$1\n";
2552 $cascadeSourcesCount = count( $cascadeSources );
2553 if ( $cascadeSourcesCount > 0 ) {
2554 # Explain, and list the titles responsible
2555 foreach ( $cascadeSources
as $page ) {
2556 $notice .=
'* [[:' . $page->getPrefixedText() .
"]]\n";
2559 $notice .=
'</div>';
2560 $wgOut->wrapWikiMsg( $notice,
array(
'cascadeprotectedwarning', $cascadeSourcesCount ) );
2562 if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions(
'create' ) ) {
2565 'showIfEmpty' =>
false,
2566 'msgKey' =>
array(
'titleprotectedwarning' ),
2567 'wrap' =>
"<div class=\"mw-titleprotectedwarning\">\n$1</div>" ) );
2570 if ( $this->kblength ===
false ) {
2571 $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
2574 if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
2575 $wgOut->wrapWikiMsg(
"<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
2576 array(
'longpageerror',
$wgLang->formatNum( $this->kblength ),
$wgLang->formatNum( $wgMaxArticleSize ) ) );
2578 if ( !
wfMessage(
'longpage-hint' )->isDisabled() ) {
2579 $wgOut->wrapWikiMsg(
"<div id='mw-edit-longpage-hint'>\n$1\n</div>",
2580 array(
'longpage-hint',
$wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) )
2584 # Add header copyright warning
2585 $this->showHeaderCopyrightWarning();
2602 function getSummaryInput(
$summary =
"", $labelText =
null, $inputAttrs =
null, $spanLabelAttrs =
null ) {
2604 $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs :
array() ) +
array(
2605 'id' =>
'wpSummary',
2606 'maxlength' =>
'200',
2609 'spellcheck' =>
'true',
2612 $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs :
array() ) +
array(
2613 'class' => $this->missingSummary ?
'mw-summarymissed' :
'mw-summary',
2614 'id' =>
"wpSummaryLabel"
2619 $label =
Xml::tags(
'label', $inputAttrs[
'id'] ?
array(
'for' => $inputAttrs[
'id'] ) :
null, $labelText );
2620 $label =
Xml::tags(
'span', $spanLabelAttrs, $label );
2625 return array( $label, $input );
2635 protected function showSummaryInput( $isSubjectPreview,
$summary =
"" ) {
2637 # Add a class if 'missingsummary' is triggered to allow styling of the summary line
2638 $summaryClass = $this->missingSummary ?
'mw-summarymissed' :
'mw-summary';
2639 if ( $isSubjectPreview ) {
2640 if ( $this->nosummary ) {
2644 if ( !$this->mShowSummaryField ) {
2649 $labelText =
wfMessage( $isSubjectPreview ?
'subject' :
'summary' )->parse();
2650 list( $label, $input ) = $this->getSummaryInput(
$summary, $labelText,
array(
'class' => $summaryClass ),
array() );
2651 $wgOut->addHTML(
"{$label} {$input}" );
2661 protected function getSummaryPreview( $isSubjectPreview,
$summary =
"" ) {
2664 if ( !
$summary || ( !$this->preview && !$this->diff ) ) {
2670 if ( $isSubjectPreview ) {
2672 ->inContentLanguage()->text();
2675 $message = $isSubjectPreview ?
'subject-preview' :
'summary-preview';
2681 protected function showFormBeforeText() {
2684 $wgOut->addHTML( <<<HTML
2685 <input
type=
'hidden' value=
"{$section}" name=
"wpSection" />
2686 <input
type=
'hidden' value=
"{$this->starttime}" name=
"wpStarttime" />
2687 <input
type=
'hidden' value=
"{$this->edittime}" name=
"wpEdittime" />
2688 <input
type=
'hidden' value=
"{$this->scrolltop}" name=
"wpScrolltop" id=
"wpScrolltop" />
2692 if ( !$this->checkUnicodeCompliantBrowser() ) {
2697 protected function showFormAfterText() {
2722 protected function showContentForm() {
2723 $this->showTextbox1();
2734 protected function showTextbox1(
$customAttribs =
null, $textoverride =
null ) {
2735 if ( $this->wasDeletedSinceLastEdit() && $this->formtype ==
'save' ) {
2739 if ( $this->mTitle->isProtected(
'edit' ) &&
2742 # Is the title semi-protected?
2743 if ( $this->mTitle->isSemiProtected() ) {
2744 $classes[] =
'mw-textarea-sprotected';
2746 # Then it must be protected based on static groups (regular)
2747 $classes[] =
'mw-textarea-protected';
2749 # Is the title cascade-protected?
2750 if ( $this->mTitle->isCascadeProtected() ) {
2751 $classes[] =
'mw-textarea-cprotected';
2761 if ( count( $classes ) ) {
2762 if ( isset(
$attribs[
'class'] ) ) {
2765 $attribs[
'class'] = implode(
' ', $classes );
2769 $this->showTextbox( $textoverride !==
null ? $textoverride : $this->textbox1,
'wpTextbox1',
$attribs );
2772 protected function showTextbox2() {
2773 $this->showTextbox( $this->textbox2,
'wpTextbox2',
array(
'tabindex' => 6,
'readonly' ) );
2779 $wikitext = $this->safeUnicodeOutput( $text );
2780 if ( strval( $wikitext ) !==
'' ) {
2791 'cols' =>
$wgUser->getIntOption(
'cols' ),
2792 'rows' =>
$wgUser->getIntOption(
'rows' ),
2796 $pageLang = $this->mTitle->getPageLanguage();
2797 $attribs[
'lang'] = $pageLang->getCode();
2798 $attribs[
'dir'] = $pageLang->getDir();
2803 protected function displayPreviewArea( $previewOutput, $isOnTop =
false ) {
2807 $classes[] =
'ontop';
2810 $attribs =
array(
'id' =>
'wikiPreview',
'class' => implode(
' ', $classes ) );
2812 if ( $this->formtype !=
'preview' ) {
2813 $attribs[
'style'] =
'display: none;';
2818 if ( $this->formtype ==
'preview' ) {
2819 $this->showPreview( $previewOutput );
2822 $wgOut->addHTML(
'</div>' );
2824 if ( $this->formtype ==
'diff' ) {
2828 $msg =
wfMessage(
'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
2829 $wgOut->addWikiText(
'<div class="error">' . $msg->text() .
'</div>' );
2840 protected function showPreview( $text ) {
2842 if ( $this->mTitle->getNamespace() ==
NS_CATEGORY ) {
2843 $this->mArticle->openShowCategory();
2845 # This hook seems slightly odd here, but makes things more
2846 # consistent for extensions.
2848 $wgOut->addHTML( $text );
2849 if ( $this->mTitle->getNamespace() ==
NS_CATEGORY ) {
2850 $this->mArticle->closeShowCategory();
2861 function showDiff() {
2864 $oldtitlemsg =
'currentrev';
2865 # if message does not exist, show diff against the preloaded default
2866 if ( $this->mTitle->getNamespace() ==
NS_MEDIAWIKI && !$this->mTitle->exists() ) {
2867 $oldtext = $this->mTitle->getDefaultMessageText();
2868 if ( $oldtext !==
false ) {
2869 $oldtitlemsg =
'defaultmessagetext';
2870 $oldContent = $this->toEditContent( $oldtext );
2875 $oldContent = $this->getCurrentContent();
2878 $textboxContent = $this->toEditContent( $this->textbox1 );
2880 $newContent = $this->mArticle->replaceSectionContent(
2881 $this->
section, $textboxContent,
2882 $this->summary, $this->edittime );
2884 if ( $newContent ) {
2889 $newContent = $newContent->preSaveTransform( $this->mTitle,
$wgUser, $popts );
2892 if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
2893 $oldtitle =
wfMessage( $oldtitlemsg )->parse();
2894 $newtitle =
wfMessage(
'yourtext' )->parse();
2896 if ( !$oldContent ) {
2897 $oldContent = $newContent->getContentHandler()->makeEmptyContent();
2900 if ( !$newContent ) {
2901 $newContent = $oldContent->getContentHandler()->makeEmptyContent();
2904 $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
2905 $de->setContent( $oldContent, $newContent );
2907 $difftext = $de->getDiff( $oldtitle, $newtitle );
2908 $de->showDiffStyle();
2913 $wgOut->addHTML(
'<div id="wikiDiff">' . $difftext .
'</div>' );
2919 protected function showHeaderCopyrightWarning() {
2920 $msg =
'editpage-head-copy-warn';
2921 if ( !
wfMessage( $msg )->isDisabled() ) {
2923 $wgOut->wrapWikiMsg(
"<div class='editpage-head-copywarn'>\n$1\n</div>",
2924 'editpage-head-copy-warn' );
2936 protected function showTosSummary() {
2937 $msg =
'editpage-tos-summary';
2939 if ( !
wfMessage( $msg )->isDisabled() ) {
2941 $wgOut->addHTML(
'<div class="mw-tos-summary">' );
2942 $wgOut->addWikiMsg( $msg );
2943 $wgOut->addHTML(
'</div>' );
2947 protected function showEditTools() {
2949 $wgOut->addHTML(
'<div class="mw-editTools">' .
2950 wfMessage(
'edittools' )->inContentLanguage()->parse() .
2959 protected function getCopywarn() {
2960 return self::getCopyrightWarning( $this->mTitle );
2971 public static function getCopyrightWarning(
$title, $format =
'plain' ) {
2973 if ( $wgRightsText ) {
2974 $copywarnMsg =
array(
'copyrightwarning',
2975 '[[' .
wfMessage(
'copyrightpage' )->inContentLanguage()->
text() .
']]',
2978 $copywarnMsg =
array(
'copyrightwarning2',
2979 '[[' .
wfMessage(
'copyrightpage' )->inContentLanguage()->
text() .
']]' );
2984 return "<div id=\"editpage-copywarn\">\n" .
2985 call_user_func_array(
'wfMessage', $copywarnMsg )->$format() .
"\n</div>";
2995 public static function getPreviewLimitReport(
$output ) {
3003 wfMessage(
'limitreport-title' )->parseAsBlock()
3010 'class' =>
'preview-limit-report wikitable'
3019 $valueMsg =
wfMessage(
array(
"$key-value-html",
"$key-value" ) );
3020 if ( !$valueMsg->exists() ) {
3021 $valueMsg =
new RawMessage(
'$1' );
3023 if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3038 return $limitReport;
3041 protected function showStandardInputs( &
$tabindex = 2 ) {
3043 $wgOut->addHTML(
"<div class='editOptions'>\n" );
3045 if ( $this->
section !=
'new' ) {
3046 $this->showSummaryInput(
false, $this->summary );
3047 $wgOut->addHTML( $this->getSummaryPreview(
false, $this->summary ) );
3050 $checkboxes = $this->getCheckboxes(
$tabindex,
3051 array(
'minor' => $this->minoredit,
'watch' => $this->watchthis ) );
3052 $wgOut->addHTML(
"<div class='editCheckboxes'>" . implode( $checkboxes,
"\n" ) .
"</div>\n" );
3055 $wgOut->addWikiText( $this->getCopywarn() );
3056 $wgOut->addHTML( $this->editFormTextAfterWarn );
3058 $wgOut->addHTML(
"<div class='editButtons'>\n" );
3059 $wgOut->addHTML( implode( $this->getEditButtons(
$tabindex ),
"\n" ) .
"\n" );
3061 $cancel = $this->getCancelLink();
3062 if ( $cancel !==
'' ) {
3064 array(
'class' =>
'mw-editButtons-pipe-separator' ),
3068 $edithelp =
'<a target="helpwindow" href="' . $edithelpurl .
'">' .
3069 wfMessage(
'edithelp' )->escaped() .
'</a> ' .
3071 $wgOut->addHTML(
" <span class='cancelLink'>{$cancel}</span>\n" );
3072 $wgOut->addHTML(
" <span class='editHelp'>{$edithelp}</span>\n" );
3073 $wgOut->addHTML(
"</div><!-- editButtons -->\n" );
3075 $wgOut->addHTML(
"</div><!-- editOptions -->\n" );
3082 protected function showConflict() {
3086 $wgOut->wrapWikiMsg(
'<h2>$1</h2>',
"yourdiff" );
3088 $content1 = $this->toEditContent( $this->textbox1 );
3089 $content2 = $this->toEditContent( $this->textbox2 );
3092 $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
3093 $de->setContent( $content2, $content1 );
3099 $wgOut->wrapWikiMsg(
'<h2>$1</h2>',
"yourtext" );
3100 $this->showTextbox2();
3107 public function getCancelLink() {
3108 $cancelParams =
array();
3109 if ( !$this->isConflict && $this->oldid > 0 ) {
3110 $cancelParams[
'oldid'] = $this->oldid;
3114 $this->getContextTitle(),
3116 array(
'id' =>
'mw-editform-cancel' ),
3140 protected function wasDeletedSinceLastEdit() {
3141 if ( $this->deletedSinceEdit !==
null ) {
3142 return $this->deletedSinceEdit;
3145 $this->deletedSinceEdit =
false;
3147 if ( $this->mTitle->isDeletedQuick() ) {
3148 $this->lastDelete = $this->getLastDelete();
3149 if ( $this->lastDelete ) {
3151 if ( $deleteTime > $this->starttime ) {
3152 $this->deletedSinceEdit =
true;
3157 return $this->deletedSinceEdit;
3160 protected function getLastDelete() {
3162 $data =
$dbr->selectRow(
3163 array(
'logging',
'user' ),
3176 'log_namespace' => $this->mTitle->getNamespace(),
3177 'log_title' => $this->mTitle->getDBkey(),
3178 'log_type' =>
'delete',
3179 'log_action' =>
'delete',
3183 array(
'LIMIT' => 1,
'ORDER BY' =>
'log_timestamp DESC' )
3186 if ( is_object( $data ) ) {
3188 $data->user_name =
wfMessage(
'rev-deleted-user' )->escaped();
3192 $data->log_comment =
wfMessage(
'rev-deleted-comment' )->escaped();
3203 function getPreviewText() {
3208 if ( $wgRawHtml && !$this->mTokenOk ) {
3212 if ( $this->textbox1 !==
'' ) {
3216 $parsedNote =
$wgOut->parse(
"<div class='previewnote'>" .
3217 wfMessage(
'session_fail_preview_html' )->
text() .
"</div>",
true,
true );
3226 $content = $this->toEditContent( $this->textbox1 );
3229 if ( !
wfRunHooks(
'AlternateEditPreview',
array( $this, &$content, &$previewHTML, &$this->mParserOutput ) ) ) {
3231 return $previewHTML;
3234 # provide a anchor link to the editform
3235 $continueEditing =
'<span class="mw-continue-editing">' .
3236 '[[#' . self::EDITFORM_ID .
'|' .
$wgLang->getArrow() .
' ' .
3237 wfMessage(
'continue-editing' )->text() .
']]</span>';
3238 if ( $this->mTriedSave && !$this->mTokenOk ) {
3239 if ( $this->mTokenOkExceptSuffix ) {
3240 $note =
wfMessage(
'token_suffix_mismatch' )->plain();
3243 $note =
wfMessage(
'session_fail_preview' )->plain();
3245 } elseif ( $this->incompleteForm ) {
3246 $note =
wfMessage(
'edit_form_incomplete' )->plain();
3248 $note =
wfMessage(
'previewnote' )->plain() .
' ' . $continueEditing;
3251 $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
3252 $parserOptions->setEditSection(
false );
3253 $parserOptions->setIsPreview(
true );
3254 $parserOptions->setIsSectionPreview( !is_null( $this->
section ) && $this->
section !==
'' );
3256 # don't parse non-wikitext pages, show message about preview
3257 if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
3258 if ( $this->mTitle->isCssJsSubpage() ) {
3260 } elseif ( $this->mTitle->isCssOrJsPage() ) {
3274 # Used messages to make sure grep find them:
3275 # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
3276 if ( $level && $format ) {
3277 $note =
"<div id='mw-{$level}{$format}preview'>" .
3278 wfMessage(
"{$level}{$format}preview" )->text() .
3279 ' ' . $continueEditing .
"</div>";
3285 $previewHTML = $this->mArticle->viewRedirect( $rt,
false );
3288 # If we're adding a comment, we need to show the
3289 # summary as the headline
3290 if ( $this->
section ===
"new" && $this->summary !==
"" ) {
3294 $hook_args =
array( $this, &$content );
3296 wfRunHooks(
'EditPageGetPreviewContent', $hook_args );
3298 $parserOptions->enableLimitReport();
3300 # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
3301 # But it's now deprecated, so never mind
3304 $parserOutput = $content->
getParserOutput( $this->getArticle()->getTitle(),
null, $parserOptions );
3306 $previewHTML = $parserOutput->getText();
3307 $this->mParserOutput = $parserOutput;
3308 $wgOut->addParserOutputNoText( $parserOutput );
3310 if ( count( $parserOutput->getWarnings() ) ) {
3311 $note .=
"\n\n" . implode(
"\n\n", $parserOutput->getWarnings() );
3315 $m =
wfMessage(
'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
3316 $note .=
"\n\n" . $m->parse();
3320 if ( $this->isConflict ) {
3321 $conflict =
'<h2 id="mw-previewconflict">' .
wfMessage(
'previewconflict' )->escaped() .
"</h2>\n";
3323 $conflict =
'<hr />';
3326 $previewhead =
"<div class='previewnote'>\n" .
3327 '<h2 id="mw-previewheader">' .
wfMessage(
'preview' )->escaped() .
"</h2>" .
3328 $wgOut->parse( $note,
true,
true ) . $conflict .
"</div>\n";
3330 $pageViewLang = $this->mTitle->getPageViewLanguage();
3331 $attribs =
array(
'lang' => $pageViewLang->getHtmlCode(),
'dir' => $pageViewLang->getDir(),
3332 'class' =>
'mw-content-' . $pageViewLang->getDir() );
3336 return $previewhead . $previewHTML . $this->previewTextAfterContent;
3342 function getTemplates() {
3343 if ( $this->preview || $this->
section !=
'' ) {
3344 $templates =
array();
3345 if ( !isset( $this->mParserOutput ) ) {
3348 foreach ( $this->mParserOutput->getTemplates()
as $ns => $template ) {
3349 foreach ( array_keys( $template )
as $dbk ) {
3355 return $this->mTitle->getTemplateLinksFrom();
3366 static function getEditToolbar() {
3368 global $wgEnableUploads, $wgForeignFileRepos;
3370 $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
3387 'image' =>
$wgLang->getImageFile(
'button-bold' ),
3388 'id' =>
'mw-editbutton-bold',
3390 'close' =>
'\'\
'\'',
3391 'sample' =>
wfMessage(
'bold_sample' )->text(),
3392 'tip' =>
wfMessage(
'bold_tip' )->text(),
3396 'image' =>
$wgLang->getImageFile(
'button-italic' ),
3397 'id' =>
'mw-editbutton-italic',
3400 'sample' =>
wfMessage(
'italic_sample' )->text(),
3401 'tip' =>
wfMessage(
'italic_tip' )->text(),
3405 'image' =>
$wgLang->getImageFile(
'button-link' ),
3406 'id' =>
'mw-editbutton-link',
3409 'sample' =>
wfMessage(
'link_sample' )->text(),
3410 'tip' =>
wfMessage(
'link_tip' )->text(),
3414 'image' =>
$wgLang->getImageFile(
'button-extlink' ),
3415 'id' =>
'mw-editbutton-extlink',
3418 'sample' =>
wfMessage(
'extlink_sample' )->text(),
3419 'tip' =>
wfMessage(
'extlink_tip' )->text(),
3423 'image' =>
$wgLang->getImageFile(
'button-headline' ),
3424 'id' =>
'mw-editbutton-headline',
3427 'sample' =>
wfMessage(
'headline_sample' )->text(),
3428 'tip' =>
wfMessage(
'headline_tip' )->text(),
3431 $imagesAvailable ?
array(
3432 'image' =>
$wgLang->getImageFile(
'button-image' ),
3433 'id' =>
'mw-editbutton-image',
3436 'sample' =>
wfMessage(
'image_sample' )->text(),
3437 'tip' =>
wfMessage(
'image_tip' )->text(),
3440 $imagesAvailable ?
array(
3441 'image' =>
$wgLang->getImageFile(
'button-media' ),
3442 'id' =>
'mw-editbutton-media',
3445 'sample' =>
wfMessage(
'media_sample' )->text(),
3446 'tip' =>
wfMessage(
'media_tip' )->text(),
3450 'image' =>
$wgLang->getImageFile(
'button-nowiki' ),
3451 'id' =>
'mw-editbutton-nowiki',
3452 'open' =>
"<nowiki>",
3453 'close' =>
"</nowiki>",
3454 'sample' =>
wfMessage(
'nowiki_sample' )->text(),
3455 'tip' =>
wfMessage(
'nowiki_tip' )->text(),
3459 'image' =>
$wgLang->getImageFile(
'button-sig' ),
3460 'id' =>
'mw-editbutton-signature',
3464 'tip' =>
wfMessage(
'sig_tip' )->text(),
3468 'image' =>
$wgLang->getImageFile(
'button-hr' ),
3469 'id' =>
'mw-editbutton-hr',
3470 'open' =>
"\n----\n",
3478 $script =
'mw.loader.using("mediawiki.action.edit", function() {';
3479 foreach ( $toolarray
as $tool ) {
3485 $wgStylePath .
'/common/images/' . $tool[
'image'],
3503 "// Create button bar\n" .
3504 "$(function() { mw.toolbar.init(); } );\n";
3509 $toolbar =
'<div id="toolbar"></div>';
3526 public function getCheckboxes( &
$tabindex, $checked ) {
3529 $checkboxes =
array();
3532 if ( !$this->isNew ) {
3533 $checkboxes[
'minor'] =
'';
3534 $minorLabel =
wfMessage(
'minoredit' )->parse();
3535 if (
$wgUser->isAllowed(
'minoredit' ) ) {
3539 'id' =>
'wpMinoredit',
3541 $checkboxes[
'minor'] =
3543 " <label for='wpMinoredit' id='mw-editpage-minoredit'" .
3545 ">{$minorLabel}</label>";
3549 $watchLabel =
wfMessage(
'watchthis' )->parse();
3550 $checkboxes[
'watch'] =
'';
3551 if (
$wgUser->isLoggedIn() ) {
3555 'id' =>
'wpWatchthis',
3557 $checkboxes[
'watch'] =
3559 " <label for='wpWatchthis' id='mw-editpage-watch'" .
3561 ">{$watchLabel}</label>";
3575 public function getEditButtons( &
$tabindex ) {
3591 'id' =>
'wpPreview',
3592 'name' =>
'wpPreview',
3599 $buttons[
'preview'] =
Xml::element(
'input', $temp,
'' );
3600 $buttons[
'live'] =
'';
3629 function livePreview() {
3632 header(
'Content-type: text/xml; charset=utf-8' );
3633 header(
'Cache-control: no-cache' );
3635 $previewText = $this->getPreviewText();
3636 #$categories = $skin->getCategoryLinks();
3639 '<?xml version="1.0" encoding="UTF-8" ?>' .
"\n" .
3652 function blockedPage() {
3664 function userNotLoggedInPage() {
3675 function noCreatePermission() {
3677 $permission = $this->mTitle->isTalkPage() ?
'createtalk' :
'createpage';
3685 function noSuchSectionPage() {
3694 $wgOut->returnToMain(
false, $this->mTitle );
3702 public function spamPageWithContent( $match =
false ) {
3704 $this->textbox2 = $this->textbox1;
3706 if ( is_array( $match ) ) {
3707 $match =
$wgLang->listToText( $match );
3711 $wgOut->addHTML(
'<div id="spamprotected">' );
3712 $wgOut->addWikiMsg(
'spamprotectiontext' );
3716 $wgOut->addHTML(
'</div>' );
3718 $wgOut->wrapWikiMsg(
'<h2>$1</h2>',
"yourdiff" );
3721 $wgOut->wrapWikiMsg(
'<h2>$1</h2>',
"yourtext" );
3722 $this->showTextbox2();
3724 $wgOut->addReturnTo( $this->getContextTitle(),
array(
'action' =>
'edit' ) );
3733 private function checkUnicodeCompliantBrowser() {
3734 global $wgBrowserBlackList, $wgRequest;
3736 $currentbrowser = $wgRequest->getHeader(
'User-Agent' );
3737 if ( $currentbrowser ===
false ) {
3742 foreach ( $wgBrowserBlackList
as $browser ) {
3743 if ( preg_match( $browser, $currentbrowser ) ) {
3758 protected function safeUnicodeInput( $request, $field ) {
3759 $text = rtrim( $request->getText( $field ) );
3760 return $request->getBool(
'safemode' )
3761 ? $this->unmakeSafe( $text )
3772 protected function safeUnicodeOutput( $text ) {
3775 return $this->checkUnicodeCompliantBrowser()
3777 : $this->makeSafe( $codedText );
3792 private function makeSafe( $invalue ) {
3794 $invalue = strtr( $invalue,
array(
"&#x" =>
"�" ) );
3799 for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
3800 $bytevalue = ord( $invalue[$i] );
3801 if ( $bytevalue <= 0x7F ) {
3804 } elseif ( $bytevalue <= 0xBF ) {
3805 $working = $working << 6;
3806 $working += ( $bytevalue & 0x3F );
3808 if ( $bytesleft <= 0 ) {
3809 $result .=
"&#x" . strtoupper( dechex( $working ) ) .
";";
3811 } elseif ( $bytevalue <= 0xDF ) {
3812 $working = $bytevalue & 0x1F;
3814 } elseif ( $bytevalue <= 0xEF ) {
3815 $working = $bytevalue & 0x0F;
3818 $working = $bytevalue & 0x07;
3833 private function unmakeSafe( $invalue ) {
3835 $valueLength = strlen( $invalue );
3836 for ( $i = 0; $i < $valueLength; $i++ ) {
3837 if ( ( substr( $invalue, $i, 3 ) ==
"&#x" ) && ( $invalue[$i + 3] !=
'0' ) ) {
3841 $hexstring .= $invalue[$i];
3843 }
while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
3848 if ( ( substr( $invalue, $i, 1 ) ==
";" ) and ( strlen( $hexstring ) <= 6 ) ) {
3849 $codepoint = hexdec( $hexstring );
3852 $result .=
"&#x" . $hexstring . substr( $invalue, $i, 1 );
3855 $result .= substr( $invalue, $i, 1 );