MediaWiki  master
EditPage.php
Go to the documentation of this file.
1 <?php
28 
44 class EditPage {
48  const UNICODE_CHECK = 'ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ';
49 
53  const AS_SUCCESS_UPDATE = 200;
54 
59 
63  const AS_HOOK_ERROR = 210;
64 
69 
74 
78  const AS_CONTENT_TOO_BIG = 216;
79 
84 
89 
93  const AS_READ_ONLY_PAGE = 220;
94 
98  const AS_RATE_LIMITED = 221;
99 
105 
111 
115  const AS_BLANK_ARTICLE = 224;
116 
120  const AS_CONFLICT_DETECTED = 225;
121 
126  const AS_SUMMARY_NEEDED = 226;
127 
131  const AS_TEXTBOX_EMPTY = 228;
132 
137 
141  const AS_END = 231;
142 
146  const AS_SPAM_ERROR = 232;
147 
152 
157 
163 
168  const AS_SELF_REDIRECT = 236;
169 
174  const AS_CHANGE_TAG_ERROR = 237;
175 
179  const AS_PARSE_ERROR = 240;
180 
186 
191 
195  const EDITFORM_ID = 'editform';
196 
201  const POST_EDIT_COOKIE_KEY_PREFIX = 'PostEditRevision';
202 
217 
222  public $mArticle;
224  private $page;
225 
230  public $mTitle;
231 
233  private $mContextTitle = null;
234 
236  public $action = 'submit';
237 
242  public $isConflict = false;
243 
245  public $isNew = false;
246 
249 
251  public $formtype;
252 
257  public $firsttime;
258 
260  public $lastDelete;
261 
263  public $mTokenOk = false;
264 
266  public $mTokenOkExceptSuffix = false;
267 
269  public $mTriedSave = false;
270 
272  public $incompleteForm = false;
273 
275  public $tooBig = false;
276 
278  public $missingComment = false;
279 
281  public $missingSummary = false;
282 
284  public $allowBlankSummary = false;
285 
287  protected $blankArticle = false;
288 
290  protected $allowBlankArticle = false;
291 
293  protected $selfRedirect = false;
294 
296  protected $allowSelfRedirect = false;
297 
299  public $autoSumm = '';
300 
302  public $hookError = '';
303 
306 
308  public $hasPresetSummary = false;
309 
311  public $mBaseRevision = false;
312 
314  public $mShowSummaryField = true;
315 
316  # Form values
317 
319  public $save = false;
320 
322  public $preview = false;
323 
325  public $diff = false;
326 
328  public $minoredit = false;
329 
331  public $watchthis = false;
332 
334  public $recreate = false;
335 
339  public $textbox1 = '';
340 
342  public $textbox2 = '';
343 
345  public $summary = '';
346 
350  public $nosummary = false;
351 
356  public $edittime = '';
357 
369  private $editRevId = null;
370 
372  public $section = '';
373 
375  public $sectiontitle = '';
376 
380  public $starttime = '';
381 
387  public $oldid = 0;
388 
394  public $parentRevId = 0;
395 
397  public $editintro = '';
398 
400  public $scrolltop = null;
401 
403  public $bot = true;
404 
407 
409  public $contentFormat = null;
410 
412  private $changeTags = null;
413 
414  # Placeholders for text injection by hooks (must be HTML)
415  # extensions should take care to _append_ to the present value
416 
418  public $editFormPageTop = '';
419  public $editFormTextTop = '';
423  public $editFormTextBottom = '';
426  public $mPreloadContent = null;
427 
428  /* $didSave should be set to true whenever an article was successfully altered. */
429  public $didSave = false;
430  public $undidRev = 0;
431 
432  public $suppressIntro = false;
433 
435  protected $edit;
436 
438  protected $contentLength = false;
439 
443  private $enableApiEditOverride = false;
444 
448  protected $context;
449 
453  private $isOldRev = false;
454 
458  private $unicodeCheck;
459 
466 
471 
475  public function __construct( Article $article ) {
476  $this->mArticle = $article;
477  $this->page = $article->getPage(); // model object
478  $this->mTitle = $article->getTitle();
479  $this->context = $article->getContext();
480 
481  $this->contentModel = $this->mTitle->getContentModel();
482 
483  $handler = ContentHandler::getForModelID( $this->contentModel );
484  $this->contentFormat = $handler->getDefaultFormat();
485  $this->editConflictHelperFactory = [ $this, 'newTextConflictHelper' ];
486  }
487 
491  public function getArticle() {
492  return $this->mArticle;
493  }
494 
499  public function getContext() {
500  return $this->context;
501  }
502 
507  public function getTitle() {
508  return $this->mTitle;
509  }
510 
516  public function setContextTitle( $title ) {
517  $this->mContextTitle = $title;
518  }
519 
528  public function getContextTitle() {
529  if ( is_null( $this->mContextTitle ) ) {
530  wfDeprecated( __METHOD__ . ' called with no title set', '1.32' );
532  return $wgTitle;
533  } else {
534  return $this->mContextTitle;
535  }
536  }
537 
545  public function isSupportedContentModel( $modelId ) {
546  return $this->enableApiEditOverride === true ||
547  ContentHandler::getForModelID( $modelId )->supportsDirectEditing();
548  }
549 
556  public function setApiEditOverride( $enableOverride ) {
557  $this->enableApiEditOverride = $enableOverride;
558  }
559 
563  public function submit() {
564  wfDeprecated( __METHOD__, '1.29' );
565  $this->edit();
566  }
567 
579  public function edit() {
580  // Allow extensions to modify/prevent this form or submission
581  if ( !Hooks::run( 'AlternateEdit', [ $this ] ) ) {
582  return;
583  }
584 
585  wfDebug( __METHOD__ . ": enter\n" );
586 
587  $request = $this->context->getRequest();
588  // If they used redlink=1 and the page exists, redirect to the main article
589  if ( $request->getBool( 'redlink' ) && $this->mTitle->exists() ) {
590  $this->context->getOutput()->redirect( $this->mTitle->getFullURL() );
591  return;
592  }
593 
594  $this->importFormData( $request );
595  $this->firsttime = false;
596 
597  if ( wfReadOnly() && $this->save ) {
598  // Force preview
599  $this->save = false;
600  $this->preview = true;
601  }
602 
603  if ( $this->save ) {
604  $this->formtype = 'save';
605  } elseif ( $this->preview ) {
606  $this->formtype = 'preview';
607  } elseif ( $this->diff ) {
608  $this->formtype = 'diff';
609  } else { # First time through
610  $this->firsttime = true;
611  if ( $this->previewOnOpen() ) {
612  $this->formtype = 'preview';
613  } else {
614  $this->formtype = 'initial';
615  }
616  }
617 
618  $permErrors = $this->getEditPermissionErrors( $this->save ? 'secure' : 'full' );
619  if ( $permErrors ) {
620  wfDebug( __METHOD__ . ": User can't edit\n" );
621 
622  // track block with a cookie if it doesn't exists already
623  $this->context->getUser()->trackBlockWithCookie();
624 
625  // Auto-block user's IP if the account was "hard" blocked
626  if ( !wfReadOnly() ) {
628  $this->context->getUser()->spreadAnyEditBlock();
629  } );
630  }
631  $this->displayPermissionsError( $permErrors );
632 
633  return;
634  }
635 
636  $revision = $this->mArticle->getRevisionFetched();
637  // Disallow editing revisions with content models different from the current one
638  // Undo edits being an exception in order to allow reverting content model changes.
639  if ( $revision
640  && $revision->getContentModel() !== $this->contentModel
641  ) {
642  $prevRev = null;
643  if ( $this->undidRev ) {
644  $undidRevObj = Revision::newFromId( $this->undidRev );
645  $prevRev = $undidRevObj ? $undidRevObj->getPrevious() : null;
646  }
647  if ( !$this->undidRev
648  || !$prevRev
649  || $prevRev->getContentModel() !== $this->contentModel
650  ) {
651  $this->displayViewSourcePage(
652  $this->getContentObject(),
653  $this->context->msg(
654  'contentmodelediterror',
655  $revision->getContentModel(),
657  )->plain()
658  );
659  return;
660  }
661  }
662 
663  $this->isConflict = false;
664 
665  # Show applicable editing introductions
666  if ( $this->formtype == 'initial' || $this->firsttime ) {
667  $this->showIntro();
668  }
669 
670  # Attempt submission here. This will check for edit conflicts,
671  # and redundantly check for locked database, blocked IPs, etc.
672  # that edit() already checked just in case someone tries to sneak
673  # in the back door with a hand-edited submission URL.
674 
675  if ( 'save' == $this->formtype ) {
676  $resultDetails = null;
677  $status = $this->attemptSave( $resultDetails );
678  if ( !$this->handleStatus( $status, $resultDetails ) ) {
679  return;
680  }
681  }
682 
683  # First time through: get contents, set time for conflict
684  # checking, etc.
685  if ( 'initial' == $this->formtype || $this->firsttime ) {
686  if ( $this->initialiseForm() === false ) {
687  $this->noSuchSectionPage();
688  return;
689  }
690 
691  if ( !$this->mTitle->getArticleID() ) {
692  Hooks::run( 'EditFormPreloadText', [ &$this->textbox1, &$this->mTitle ] );
693  } else {
694  Hooks::run( 'EditFormInitialText', [ $this ] );
695  }
696 
697  }
698 
699  $this->showEditForm();
700  }
701 
706  protected function getEditPermissionErrors( $rigor = 'secure' ) {
707  $user = $this->context->getUser();
708  $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $user, $rigor );
709  # Can this title be created?
710  if ( !$this->mTitle->exists() ) {
711  $permErrors = array_merge(
712  $permErrors,
713  wfArrayDiff2(
714  $this->mTitle->getUserPermissionsErrors( 'create', $user, $rigor ),
715  $permErrors
716  )
717  );
718  }
719  # Ignore some permissions errors when a user is just previewing/viewing diffs
720  $remove = [];
721  foreach ( $permErrors as $error ) {
722  if ( ( $this->preview || $this->diff )
723  && (
724  $error[0] == 'blockedtext' ||
725  $error[0] == 'autoblockedtext' ||
726  $error[0] == 'systemblockedtext'
727  )
728  ) {
729  $remove[] = $error;
730  }
731  }
732  $permErrors = wfArrayDiff2( $permErrors, $remove );
733 
734  return $permErrors;
735  }
736 
750  protected function displayPermissionsError( array $permErrors ) {
751  $out = $this->context->getOutput();
752  if ( $this->context->getRequest()->getBool( 'redlink' ) ) {
753  // The edit page was reached via a red link.
754  // Redirect to the article page and let them click the edit tab if
755  // they really want a permission error.
756  $out->redirect( $this->mTitle->getFullURL() );
757  return;
758  }
759 
760  $content = $this->getContentObject();
761 
762  # Use the normal message if there's nothing to display
763  if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
764  $action = $this->mTitle->exists() ? 'edit' :
765  ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
766  throw new PermissionsError( $action, $permErrors );
767  }
768 
769  $this->displayViewSourcePage(
770  $content,
771  $out->formatPermissionsErrorMessage( $permErrors, 'edit' )
772  );
773  }
774 
780  protected function displayViewSourcePage( Content $content, $errorMessage = '' ) {
781  $out = $this->context->getOutput();
782  Hooks::run( 'EditPage::showReadOnlyForm:initial', [ $this, &$out ] );
783 
784  $out->setRobotPolicy( 'noindex,nofollow' );
785  $out->setPageTitle( $this->context->msg(
786  'viewsource-title',
787  $this->getContextTitle()->getPrefixedText()
788  ) );
789  $out->addBacklinkSubtitle( $this->getContextTitle() );
790  $out->addHTML( $this->editFormPageTop );
791  $out->addHTML( $this->editFormTextTop );
792 
793  if ( $errorMessage !== '' ) {
794  $out->addWikiText( $errorMessage );
795  $out->addHTML( "<hr />\n" );
796  }
797 
798  # If the user made changes, preserve them when showing the markup
799  # (This happens when a user is blocked during edit, for instance)
800  if ( !$this->firsttime ) {
801  $text = $this->textbox1;
802  $out->addWikiMsg( 'viewyourtext' );
803  } else {
804  try {
805  $text = $this->toEditText( $content );
806  } catch ( MWException $e ) {
807  # Serialize using the default format if the content model is not supported
808  # (e.g. for an old revision with a different model)
809  $text = $content->serialize();
810  }
811  $out->addWikiMsg( 'viewsourcetext' );
812  }
813 
814  $out->addHTML( $this->editFormTextBeforeContent );
815  $this->showTextbox( $text, 'wpTextbox1', [ 'readonly' ] );
816  $out->addHTML( $this->editFormTextAfterContent );
817 
818  $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
819 
820  $out->addModules( 'mediawiki.action.edit.collapsibleFooter' );
821 
822  $out->addHTML( $this->editFormTextBottom );
823  if ( $this->mTitle->exists() ) {
824  $out->returnToMain( null, $this->mTitle );
825  }
826  }
827 
833  protected function previewOnOpen() {
834  $config = $this->context->getConfig();
835  $previewOnOpenNamespaces = $config->get( 'PreviewOnOpenNamespaces' );
836  $request = $this->context->getRequest();
837  if ( $config->get( 'RawHtml' ) ) {
838  // If raw HTML is enabled, disable preview on open
839  // since it has to be posted with a token for
840  // security reasons
841  return false;
842  }
843  if ( $request->getVal( 'preview' ) == 'yes' ) {
844  // Explicit override from request
845  return true;
846  } elseif ( $request->getVal( 'preview' ) == 'no' ) {
847  // Explicit override from request
848  return false;
849  } elseif ( $this->section == 'new' ) {
850  // Nothing *to* preview for new sections
851  return false;
852  } elseif ( ( $request->getVal( 'preload' ) !== null || $this->mTitle->exists() )
853  && $this->context->getUser()->getOption( 'previewonfirst' )
854  ) {
855  // Standard preference behavior
856  return true;
857  } elseif ( !$this->mTitle->exists()
858  && isset( $previewOnOpenNamespaces[$this->mTitle->getNamespace()] )
859  && $previewOnOpenNamespaces[$this->mTitle->getNamespace()]
860  ) {
861  // Categories are special
862  return true;
863  } else {
864  return false;
865  }
866  }
867 
874  protected function isWrongCaseUserConfigPage() {
875  if ( $this->mTitle->isUserConfigPage() ) {
876  $name = $this->mTitle->getSkinFromConfigSubpage();
877  $skins = array_merge(
878  array_keys( Skin::getSkinNames() ),
879  [ 'common' ]
880  );
881  return !in_array( $name, $skins )
882  && in_array( strtolower( $name ), $skins );
883  } else {
884  return false;
885  }
886  }
887 
895  protected function isSectionEditSupported() {
896  $contentHandler = ContentHandler::getForTitle( $this->mTitle );
897  return $contentHandler->supportsSections();
898  }
899 
905  public function importFormData( &$request ) {
906  # Section edit can come from either the form or a link
907  $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
908 
909  if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) {
910  throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
911  }
912 
913  $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
914 
915  if ( $request->wasPosted() ) {
916  # These fields need to be checked for encoding.
917  # Also remove trailing whitespace, but don't remove _initial_
918  # whitespace from the text boxes. This may be significant formatting.
919  $this->textbox1 = rtrim( $request->getText( 'wpTextbox1' ) );
920  if ( !$request->getCheck( 'wpTextbox2' ) ) {
921  // Skip this if wpTextbox2 has input, it indicates that we came
922  // from a conflict page with raw page text, not a custom form
923  // modified by subclasses
925  if ( $textbox1 !== null ) {
926  $this->textbox1 = $textbox1;
927  }
928  }
929 
930  $this->unicodeCheck = $request->getText( 'wpUnicodeCheck' );
931 
932  $this->summary = $request->getText( 'wpSummary' );
933 
934  # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
935  # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
936  # section titles.
937  $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
938 
939  # Treat sectiontitle the same way as summary.
940  # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
941  # currently doing double duty as both edit summary and section title. Right now this
942  # is just to allow API edits to work around this limitation, but this should be
943  # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
944  $this->sectiontitle = $request->getText( 'wpSectionTitle' );
945  $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
946 
947  $this->edittime = $request->getVal( 'wpEdittime' );
948  $this->editRevId = $request->getIntOrNull( 'editRevId' );
949  $this->starttime = $request->getVal( 'wpStarttime' );
950 
951  $undidRev = $request->getInt( 'wpUndidRevision' );
952  if ( $undidRev ) {
953  $this->undidRev = $undidRev;
954  }
955 
956  $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
957 
958  if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
959  // wpTextbox1 field is missing, possibly due to being "too big"
960  // according to some filter rules such as Suhosin's setting for
961  // suhosin.request.max_value_length (d'oh)
962  $this->incompleteForm = true;
963  } else {
964  // If we receive the last parameter of the request, we can fairly
965  // claim the POST request has not been truncated.
966  $this->incompleteForm = !$request->getVal( 'wpUltimateParam' );
967  }
968  if ( $this->incompleteForm ) {
969  # If the form is incomplete, force to preview.
970  wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
971  wfDebug( "POST DATA: " . var_export( $request->getPostValues(), true ) . "\n" );
972  $this->preview = true;
973  } else {
974  $this->preview = $request->getCheck( 'wpPreview' );
975  $this->diff = $request->getCheck( 'wpDiff' );
976 
977  // Remember whether a save was requested, so we can indicate
978  // if we forced preview due to session failure.
979  $this->mTriedSave = !$this->preview;
980 
981  if ( $this->tokenOk( $request ) ) {
982  # Some browsers will not report any submit button
983  # if the user hits enter in the comment box.
984  # The unmarked state will be assumed to be a save,
985  # if the form seems otherwise complete.
986  wfDebug( __METHOD__ . ": Passed token check.\n" );
987  } elseif ( $this->diff ) {
988  # Failed token check, but only requested "Show Changes".
989  wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
990  } else {
991  # Page might be a hack attempt posted from
992  # an external site. Preview instead of saving.
993  wfDebug( __METHOD__ . ": Failed token check; forcing preview\n" );
994  $this->preview = true;
995  }
996  }
997  $this->save = !$this->preview && !$this->diff;
998  if ( !preg_match( '/^\d{14}$/', $this->edittime ) ) {
999  $this->edittime = null;
1000  }
1001 
1002  if ( !preg_match( '/^\d{14}$/', $this->starttime ) ) {
1003  $this->starttime = null;
1004  }
1005 
1006  $this->recreate = $request->getCheck( 'wpRecreate' );
1007 
1008  $this->minoredit = $request->getCheck( 'wpMinoredit' );
1009  $this->watchthis = $request->getCheck( 'wpWatchthis' );
1010 
1011  $user = $this->context->getUser();
1012  # Don't force edit summaries when a user is editing their own user or talk page
1013  if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK )
1014  && $this->mTitle->getText() == $user->getName()
1015  ) {
1016  $this->allowBlankSummary = true;
1017  } else {
1018  $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' )
1019  || !$user->getOption( 'forceeditsummary' );
1020  }
1021 
1022  $this->autoSumm = $request->getText( 'wpAutoSummary' );
1023 
1024  $this->allowBlankArticle = $request->getBool( 'wpIgnoreBlankArticle' );
1025  $this->allowSelfRedirect = $request->getBool( 'wpIgnoreSelfRedirect' );
1026 
1027  $changeTags = $request->getVal( 'wpChangeTags' );
1028  if ( is_null( $changeTags ) || $changeTags === '' ) {
1029  $this->changeTags = [];
1030  } else {
1031  $this->changeTags = array_filter( array_map( 'trim', explode( ',',
1032  $changeTags ) ) );
1033  }
1034  } else {
1035  # Not a posted form? Start with nothing.
1036  wfDebug( __METHOD__ . ": Not a posted form.\n" );
1037  $this->textbox1 = '';
1038  $this->summary = '';
1039  $this->sectiontitle = '';
1040  $this->edittime = '';
1041  $this->editRevId = null;
1042  $this->starttime = wfTimestampNow();
1043  $this->edit = false;
1044  $this->preview = false;
1045  $this->save = false;
1046  $this->diff = false;
1047  $this->minoredit = false;
1048  // Watch may be overridden by request parameters
1049  $this->watchthis = $request->getBool( 'watchthis', false );
1050  $this->recreate = false;
1051 
1052  // When creating a new section, we can preload a section title by passing it as the
1053  // preloadtitle parameter in the URL (T15100)
1054  if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
1055  $this->sectiontitle = $request->getVal( 'preloadtitle' );
1056  // Once wpSummary isn't being use for setting section titles, we should delete this.
1057  $this->summary = $request->getVal( 'preloadtitle' );
1058  } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
1059  $this->summary = $request->getText( 'summary' );
1060  if ( $this->summary !== '' ) {
1061  $this->hasPresetSummary = true;
1062  }
1063  }
1064 
1065  if ( $request->getVal( 'minor' ) ) {
1066  $this->minoredit = true;
1067  }
1068  }
1069 
1070  $this->oldid = $request->getInt( 'oldid' );
1071  $this->parentRevId = $request->getInt( 'parentRevId' );
1072 
1073  $this->bot = $request->getBool( 'bot', true );
1074  $this->nosummary = $request->getBool( 'nosummary' );
1075 
1076  // May be overridden by revision.
1077  $this->contentModel = $request->getText( 'model', $this->contentModel );
1078  // May be overridden by revision.
1079  $this->contentFormat = $request->getText( 'format', $this->contentFormat );
1080 
1081  try {
1082  $handler = ContentHandler::getForModelID( $this->contentModel );
1083  } catch ( MWUnknownContentModelException $e ) {
1084  throw new ErrorPageError(
1085  'editpage-invalidcontentmodel-title',
1086  'editpage-invalidcontentmodel-text',
1087  [ wfEscapeWikiText( $this->contentModel ) ]
1088  );
1089  }
1090 
1091  if ( !$handler->isSupportedFormat( $this->contentFormat ) ) {
1092  throw new ErrorPageError(
1093  'editpage-notsupportedcontentformat-title',
1094  'editpage-notsupportedcontentformat-text',
1095  [
1096  wfEscapeWikiText( $this->contentFormat ),
1097  wfEscapeWikiText( ContentHandler::getLocalizedName( $this->contentModel ) )
1098  ]
1099  );
1100  }
1101 
1108  $this->editintro = $request->getText( 'editintro',
1109  // Custom edit intro for new sections
1110  $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
1111 
1112  // Allow extensions to modify form data
1113  Hooks::run( 'EditPage::importFormData', [ $this, $request ] );
1114  }
1115 
1125  protected function importContentFormData( &$request ) {
1126  return; // Don't do anything, EditPage already extracted wpTextbox1
1127  }
1128 
1134  public function initialiseForm() {
1135  $this->edittime = $this->page->getTimestamp();
1136  $this->editRevId = $this->page->getLatest();
1137 
1138  $content = $this->getContentObject( false ); # TODO: track content object?!
1139  if ( $content === false ) {
1140  return false;
1141  }
1142  $this->textbox1 = $this->toEditText( $content );
1143 
1144  $user = $this->context->getUser();
1145  // activate checkboxes if user wants them to be always active
1146  # Sort out the "watch" checkbox
1147  if ( $user->getOption( 'watchdefault' ) ) {
1148  # Watch all edits
1149  $this->watchthis = true;
1150  } elseif ( $user->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
1151  # Watch creations
1152  $this->watchthis = true;
1153  } elseif ( $user->isWatched( $this->mTitle ) ) {
1154  # Already watched
1155  $this->watchthis = true;
1156  }
1157  if ( $user->getOption( 'minordefault' ) && !$this->isNew ) {
1158  $this->minoredit = true;
1159  }
1160  if ( $this->textbox1 === false ) {
1161  return false;
1162  }
1163  return true;
1164  }
1165 
1173  protected function getContentObject( $def_content = null ) {
1174  $content = false;
1175 
1176  $user = $this->context->getUser();
1177  $request = $this->context->getRequest();
1178  // For message page not locally set, use the i18n message.
1179  // For other non-existent articles, use preload text if any.
1180  if ( !$this->mTitle->exists() || $this->section == 'new' ) {
1181  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
1182  # If this is a system message, get the default text.
1183  $msg = $this->mTitle->getDefaultMessageText();
1184 
1185  $content = $this->toEditContent( $msg );
1186  }
1187  if ( $content === false ) {
1188  # If requested, preload some text.
1189  $preload = $request->getVal( 'preload',
1190  // Custom preload text for new sections
1191  $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
1192  $params = $request->getArray( 'preloadparams', [] );
1193 
1194  $content = $this->getPreloadedContent( $preload, $params );
1195  }
1196  // For existing pages, get text based on "undo" or section parameters.
1197  } else {
1198  if ( $this->section != '' ) {
1199  // Get section edit text (returns $def_text for invalid sections)
1200  $orig = $this->getOriginalContent( $user );
1201  $content = $orig ? $orig->getSection( $this->section ) : null;
1202 
1203  if ( !$content ) {
1204  $content = $def_content;
1205  }
1206  } else {
1207  $undoafter = $request->getInt( 'undoafter' );
1208  $undo = $request->getInt( 'undo' );
1209 
1210  if ( $undo > 0 && $undoafter > 0 ) {
1211  $undorev = Revision::newFromId( $undo );
1212  $oldrev = Revision::newFromId( $undoafter );
1213  $undoMsg = null;
1214 
1215  # Sanity check, make sure it's the right page,
1216  # the revisions exist and they were not deleted.
1217  # Otherwise, $content will be left as-is.
1218  if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
1219  !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
1220  !$oldrev->isDeleted( Revision::DELETED_TEXT )
1221  ) {
1222  if ( WikiPage::hasDifferencesOutsideMainSlot( $undorev, $oldrev ) ) {
1223  // Cannot yet undo edits that involve anything other the main slot.
1224  $undoMsg = 'main-slot-only';
1225  } else {
1226  $content = $this->page->getUndoContent( $undorev, $oldrev );
1227 
1228  if ( $content === false ) {
1229  # Warn the user that something went wrong
1230  $undoMsg = 'failure';
1231  }
1232  }
1233 
1234  if ( $undoMsg === null ) {
1235  $oldContent = $this->page->getContent( Revision::RAW );
1237  $user, MediaWikiServices::getInstance()->getContentLanguage() );
1238  $newContent = $content->preSaveTransform( $this->mTitle, $user, $popts );
1239  if ( $newContent->getModel() !== $oldContent->getModel() ) {
1240  // The undo may change content
1241  // model if its reverting the top
1242  // edit. This can result in
1243  // mismatched content model/format.
1244  $this->contentModel = $newContent->getModel();
1245  $this->contentFormat = $oldrev->getContentFormat();
1246  }
1247 
1248  if ( $newContent->equals( $oldContent ) ) {
1249  # Tell the user that the undo results in no change,
1250  # i.e. the revisions were already undone.
1251  $undoMsg = 'nochange';
1252  $content = false;
1253  } else {
1254  # Inform the user of our success and set an automatic edit summary
1255  $undoMsg = 'success';
1256 
1257  # If we just undid one rev, use an autosummary
1258  $firstrev = $oldrev->getNext();
1259  if ( $firstrev && $firstrev->getId() == $undo ) {
1260  $userText = $undorev->getUserText();
1261  if ( $userText === '' ) {
1262  $undoSummary = $this->context->msg(
1263  'undo-summary-username-hidden',
1264  $undo
1265  )->inContentLanguage()->text();
1266  } else {
1267  $undoSummary = $this->context->msg(
1268  'undo-summary',
1269  $undo,
1270  $userText
1271  )->inContentLanguage()->text();
1272  }
1273  if ( $this->summary === '' ) {
1274  $this->summary = $undoSummary;
1275  } else {
1276  $this->summary = $undoSummary . $this->context->msg( 'colon-separator' )
1277  ->inContentLanguage()->text() . $this->summary;
1278  }
1279  $this->undidRev = $undo;
1280  }
1281  $this->formtype = 'diff';
1282  }
1283  }
1284  } else {
1285  // Failed basic sanity checks.
1286  // Older revisions may have been removed since the link
1287  // was created, or we may simply have got bogus input.
1288  $undoMsg = 'norev';
1289  }
1290 
1291  $out = $this->context->getOutput();
1292  // Messages: undo-success, undo-failure, undo-main-slot-only, undo-norev,
1293  // undo-nochange.
1294  $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
1295  $this->editFormPageTop .= $out->parse( "<div class=\"{$class}\">" .
1296  $this->context->msg( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
1297  }
1298 
1299  if ( $content === false ) {
1300  $content = $this->getOriginalContent( $user );
1301  }
1302  }
1303  }
1304 
1305  return $content;
1306  }
1307 
1323  private function getOriginalContent( User $user ) {
1324  if ( $this->section == 'new' ) {
1325  return $this->getCurrentContent();
1326  }
1327  $revision = $this->mArticle->getRevisionFetched();
1328  if ( $revision === null ) {
1329  $handler = ContentHandler::getForModelID( $this->contentModel );
1330  return $handler->makeEmptyContent();
1331  }
1332  $content = $revision->getContent( Revision::FOR_THIS_USER, $user );
1333  return $content;
1334  }
1335 
1348  public function getParentRevId() {
1349  if ( $this->parentRevId ) {
1350  return $this->parentRevId;
1351  } else {
1352  return $this->mArticle->getRevIdFetched();
1353  }
1354  }
1355 
1364  protected function getCurrentContent() {
1365  $rev = $this->page->getRevision();
1366  $content = $rev ? $rev->getContent( Revision::RAW ) : null;
1367 
1368  if ( $content === false || $content === null ) {
1369  $handler = ContentHandler::getForModelID( $this->contentModel );
1370  return $handler->makeEmptyContent();
1371  } elseif ( !$this->undidRev ) {
1372  // Content models should always be the same since we error
1373  // out if they are different before this point (in ->edit()).
1374  // The exception being, during an undo, the current revision might
1375  // differ from the prior revision.
1376  $logger = LoggerFactory::getInstance( 'editpage' );
1377  if ( $this->contentModel !== $rev->getContentModel() ) {
1378  $logger->warning( "Overriding content model from current edit {prev} to {new}", [
1379  'prev' => $this->contentModel,
1380  'new' => $rev->getContentModel(),
1381  'title' => $this->getTitle()->getPrefixedDBkey(),
1382  'method' => __METHOD__
1383  ] );
1384  $this->contentModel = $rev->getContentModel();
1385  }
1386 
1387  // Given that the content models should match, the current selected
1388  // format should be supported.
1389  if ( !$content->isSupportedFormat( $this->contentFormat ) ) {
1390  $logger->warning( "Current revision content format unsupported. Overriding {prev} to {new}", [
1391 
1392  'prev' => $this->contentFormat,
1393  'new' => $rev->getContentFormat(),
1394  'title' => $this->getTitle()->getPrefixedDBkey(),
1395  'method' => __METHOD__
1396  ] );
1397  $this->contentFormat = $rev->getContentFormat();
1398  }
1399  }
1400  return $content;
1401  }
1402 
1410  public function setPreloadedContent( Content $content ) {
1411  $this->mPreloadContent = $content;
1412  }
1413 
1425  protected function getPreloadedContent( $preload, $params = [] ) {
1426  if ( !empty( $this->mPreloadContent ) ) {
1427  return $this->mPreloadContent;
1428  }
1429 
1430  $handler = ContentHandler::getForModelID( $this->contentModel );
1431 
1432  if ( $preload === '' ) {
1433  return $handler->makeEmptyContent();
1434  }
1435 
1436  $user = $this->context->getUser();
1437  $title = Title::newFromText( $preload );
1438  # Check for existence to avoid getting MediaWiki:Noarticletext
1439  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $user ) ) {
1440  // TODO: somehow show a warning to the user!
1441  return $handler->makeEmptyContent();
1442  }
1443 
1445  if ( $page->isRedirect() ) {
1447  # Same as before
1448  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $user ) ) {
1449  // TODO: somehow show a warning to the user!
1450  return $handler->makeEmptyContent();
1451  }
1453  }
1454 
1455  $parserOptions = ParserOptions::newFromUser( $user );
1457 
1458  if ( !$content ) {
1459  // TODO: somehow show a warning to the user!
1460  return $handler->makeEmptyContent();
1461  }
1462 
1463  if ( $content->getModel() !== $handler->getModelID() ) {
1464  $converted = $content->convert( $handler->getModelID() );
1465 
1466  if ( !$converted ) {
1467  // TODO: somehow show a warning to the user!
1468  wfDebug( "Attempt to preload incompatible content: " .
1469  "can't convert " . $content->getModel() .
1470  " to " . $handler->getModelID() );
1471 
1472  return $handler->makeEmptyContent();
1473  }
1474 
1475  $content = $converted;
1476  }
1477 
1478  return $content->preloadTransform( $title, $parserOptions, $params );
1479  }
1480 
1488  public function tokenOk( &$request ) {
1489  $token = $request->getVal( 'wpEditToken' );
1490  $user = $this->context->getUser();
1491  $this->mTokenOk = $user->matchEditToken( $token );
1492  $this->mTokenOkExceptSuffix = $user->matchEditTokenNoSuffix( $token );
1493  return $this->mTokenOk;
1494  }
1495 
1510  protected function setPostEditCookie( $statusValue ) {
1511  $revisionId = $this->page->getLatest();
1512  $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1513 
1514  $val = 'saved';
1515  if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) {
1516  $val = 'created';
1517  } elseif ( $this->oldid ) {
1518  $val = 'restored';
1519  }
1520 
1521  $response = $this->context->getRequest()->response();
1522  $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION );
1523  }
1524 
1531  public function attemptSave( &$resultDetails = false ) {
1532  // TODO: MCR: treat $this->minoredit like $this->bot and check isAllowed( 'minoredit' )!
1533  // Also, add $this->autopatrol like $this->bot and check isAllowed( 'autopatrol' )!
1534  // This is needed since PageUpdater no longer checks these rights!
1535 
1536  // Allow bots to exempt some edits from bot flagging
1537  $bot = $this->context->getUser()->isAllowed( 'bot' ) && $this->bot;
1538  $status = $this->internalAttemptSave( $resultDetails, $bot );
1539 
1540  Hooks::run( 'EditPage::attemptSave:after', [ $this, $status, $resultDetails ] );
1541 
1542  return $status;
1543  }
1544 
1548  private function incrementResolvedConflicts() {
1549  if ( $this->context->getRequest()->getText( 'mode' ) !== 'conflict' ) {
1550  return;
1551  }
1552 
1553  $this->getEditConflictHelper()->incrementResolvedStats();
1554  }
1555 
1565  private function handleStatus( Status $status, $resultDetails ) {
1570  if ( $status->value == self::AS_SUCCESS_UPDATE
1571  || $status->value == self::AS_SUCCESS_NEW_ARTICLE
1572  ) {
1573  $this->incrementResolvedConflicts();
1574 
1575  $this->didSave = true;
1576  if ( !$resultDetails['nullEdit'] ) {
1577  $this->setPostEditCookie( $status->value );
1578  }
1579  }
1580 
1581  $out = $this->context->getOutput();
1582 
1583  // "wpExtraQueryRedirect" is a hidden input to modify
1584  // after save URL and is not used by actual edit form
1585  $request = $this->context->getRequest();
1586  $extraQueryRedirect = $request->getVal( 'wpExtraQueryRedirect' );
1587 
1588  switch ( $status->value ) {
1589  case self::AS_HOOK_ERROR_EXPECTED:
1590  case self::AS_CONTENT_TOO_BIG:
1591  case self::AS_ARTICLE_WAS_DELETED:
1592  case self::AS_CONFLICT_DETECTED:
1593  case self::AS_SUMMARY_NEEDED:
1594  case self::AS_TEXTBOX_EMPTY:
1595  case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
1596  case self::AS_END:
1597  case self::AS_BLANK_ARTICLE:
1598  case self::AS_SELF_REDIRECT:
1599  return true;
1600 
1601  case self::AS_HOOK_ERROR:
1602  return false;
1603 
1604  case self::AS_CANNOT_USE_CUSTOM_MODEL:
1605  case self::AS_PARSE_ERROR:
1606  case self::AS_UNICODE_NOT_SUPPORTED:
1607  $out->addWikiText( '<div class="error">' . "\n" . $status->getWikiText() . '</div>' );
1608  return true;
1609 
1610  case self::AS_SUCCESS_NEW_ARTICLE:
1611  $query = $resultDetails['redirect'] ? 'redirect=no' : '';
1612  if ( $extraQueryRedirect ) {
1613  if ( $query === '' ) {
1614  $query = $extraQueryRedirect;
1615  } else {
1616  $query = $query . '&' . $extraQueryRedirect;
1617  }
1618  }
1619  $anchor = $resultDetails['sectionanchor'] ?? '';
1620  $out->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
1621  return false;
1622 
1623  case self::AS_SUCCESS_UPDATE:
1624  $extraQuery = '';
1625  $sectionanchor = $resultDetails['sectionanchor'];
1626 
1627  // Give extensions a chance to modify URL query on update
1628  Hooks::run(
1629  'ArticleUpdateBeforeRedirect',
1630  [ $this->mArticle, &$sectionanchor, &$extraQuery ]
1631  );
1632 
1633  if ( $resultDetails['redirect'] ) {
1634  if ( $extraQuery == '' ) {
1635  $extraQuery = 'redirect=no';
1636  } else {
1637  $extraQuery = 'redirect=no&' . $extraQuery;
1638  }
1639  }
1640  if ( $extraQueryRedirect ) {
1641  if ( $extraQuery === '' ) {
1642  $extraQuery = $extraQueryRedirect;
1643  } else {
1644  $extraQuery = $extraQuery . '&' . $extraQueryRedirect;
1645  }
1646  }
1647 
1648  $out->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1649  return false;
1650 
1651  case self::AS_SPAM_ERROR:
1652  $this->spamPageWithContent( $resultDetails['spam'] );
1653  return false;
1654 
1655  case self::AS_BLOCKED_PAGE_FOR_USER:
1656  throw new UserBlockedError( $this->context->getUser()->getBlock() );
1657 
1658  case self::AS_IMAGE_REDIRECT_ANON:
1659  case self::AS_IMAGE_REDIRECT_LOGGED:
1660  throw new PermissionsError( 'upload' );
1661 
1662  case self::AS_READ_ONLY_PAGE_ANON:
1663  case self::AS_READ_ONLY_PAGE_LOGGED:
1664  throw new PermissionsError( 'edit' );
1665 
1666  case self::AS_READ_ONLY_PAGE:
1667  throw new ReadOnlyError;
1668 
1669  case self::AS_RATE_LIMITED:
1670  throw new ThrottledError();
1671 
1672  case self::AS_NO_CREATE_PERMISSION:
1673  $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
1674  throw new PermissionsError( $permission );
1675 
1676  case self::AS_NO_CHANGE_CONTENT_MODEL:
1677  throw new PermissionsError( 'editcontentmodel' );
1678 
1679  default:
1680  // We don't recognize $status->value. The only way that can happen
1681  // is if an extension hook aborted from inside ArticleSave.
1682  // Render the status object into $this->hookError
1683  // FIXME this sucks, we should just use the Status object throughout
1684  $this->hookError = '<div class="error">' ."\n" . $status->getWikiText() .
1685  '</div>';
1686  return true;
1687  }
1688  }
1689 
1700  // Run old style post-section-merge edit filter
1701  if ( $this->hookError != '' ) {
1702  # ...or the hook could be expecting us to produce an error
1703  $status->fatal( 'hookaborted' );
1704  $status->value = self::AS_HOOK_ERROR_EXPECTED;
1705  return false;
1706  }
1707 
1708  // Run new style post-section-merge edit filter
1709  if ( !Hooks::run( 'EditFilterMergedContent',
1710  [ $this->context, $content, $status, $this->summary,
1711  $user, $this->minoredit ] )
1712  ) {
1713  # Error messages etc. could be handled within the hook...
1714  if ( $status->isGood() ) {
1715  $status->fatal( 'hookaborted' );
1716  // Not setting $this->hookError here is a hack to allow the hook
1717  // to cause a return to the edit page without $this->hookError
1718  // being set. This is used by ConfirmEdit to display a captcha
1719  // without any error message cruft.
1720  } else {
1721  $this->hookError = $this->formatStatusErrors( $status );
1722  }
1723  // Use the existing $status->value if the hook set it
1724  if ( !$status->value ) {
1725  $status->value = self::AS_HOOK_ERROR;
1726  }
1727  return false;
1728  } elseif ( !$status->isOK() ) {
1729  # ...or the hook could be expecting us to produce an error
1730  // FIXME this sucks, we should just use the Status object throughout
1731  $this->hookError = $this->formatStatusErrors( $status );
1732  $status->fatal( 'hookaborted' );
1733  $status->value = self::AS_HOOK_ERROR_EXPECTED;
1734  return false;
1735  }
1736 
1737  return true;
1738  }
1739 
1746  private function formatStatusErrors( Status $status ) {
1747  $errmsg = $status->getWikiText(
1748  'edit-error-short',
1749  'edit-error-long',
1750  $this->context->getLanguage()
1751  );
1752  return <<<ERROR
1753 <div class="errorbox">
1754 {$errmsg}
1755 </div>
1756 <br clear="all" />
1757 ERROR;
1758  }
1759 
1766  private function newSectionSummary( &$sectionanchor = null ) {
1767  global $wgParser;
1768 
1769  if ( $this->sectiontitle !== '' ) {
1770  $sectionanchor = $this->guessSectionName( $this->sectiontitle );
1771  // If no edit summary was specified, create one automatically from the section
1772  // title and have it link to the new section. Otherwise, respect the summary as
1773  // passed.
1774  if ( $this->summary === '' ) {
1775  $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
1776  return $this->context->msg( 'newsectionsummary' )
1777  ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1778  }
1779  } elseif ( $this->summary !== '' ) {
1780  $sectionanchor = $this->guessSectionName( $this->summary );
1781  # This is a new section, so create a link to the new section
1782  # in the revision summary.
1783  $cleanSummary = $wgParser->stripSectionName( $this->summary );
1784  return $this->context->msg( 'newsectionsummary' )
1785  ->rawParams( $cleanSummary )->inContentLanguage()->text();
1786  }
1787  return $this->summary;
1788  }
1789 
1814  public function internalAttemptSave( &$result, $bot = false ) {
1816  $user = $this->context->getUser();
1817 
1818  if ( !Hooks::run( 'EditPage::attemptSave', [ $this ] ) ) {
1819  wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
1820  $status->fatal( 'hookaborted' );
1821  $status->value = self::AS_HOOK_ERROR;
1822  return $status;
1823  }
1824 
1825  if ( $this->unicodeCheck !== self::UNICODE_CHECK ) {
1826  $status->fatal( 'unicode-support-fail' );
1827  $status->value = self::AS_UNICODE_NOT_SUPPORTED;
1828  return $status;
1829  }
1830 
1831  $request = $this->context->getRequest();
1832  $spam = $request->getText( 'wpAntispam' );
1833  if ( $spam !== '' ) {
1834  wfDebugLog(
1835  'SimpleAntiSpam',
1836  $user->getName() .
1837  ' editing "' .
1838  $this->mTitle->getPrefixedText() .
1839  '" submitted bogus field "' .
1840  $spam .
1841  '"'
1842  );
1843  $status->fatal( 'spamprotectionmatch', false );
1844  $status->value = self::AS_SPAM_ERROR;
1845  return $status;
1846  }
1847 
1848  try {
1849  # Construct Content object
1850  $textbox_content = $this->toEditContent( $this->textbox1 );
1851  } catch ( MWContentSerializationException $ex ) {
1852  $status->fatal(
1853  'content-failed-to-parse',
1854  $this->contentModel,
1855  $this->contentFormat,
1856  $ex->getMessage()
1857  );
1858  $status->value = self::AS_PARSE_ERROR;
1859  return $status;
1860  }
1861 
1862  # Check image redirect
1863  if ( $this->mTitle->getNamespace() == NS_FILE &&
1864  $textbox_content->isRedirect() &&
1865  !$user->isAllowed( 'upload' )
1866  ) {
1867  $code = $user->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
1868  $status->setResult( false, $code );
1869 
1870  return $status;
1871  }
1872 
1873  # Check for spam
1874  $match = self::matchSummarySpamRegex( $this->summary );
1875  if ( $match === false && $this->section == 'new' ) {
1876  # $wgSpamRegex is enforced on this new heading/summary because, unlike
1877  # regular summaries, it is added to the actual wikitext.
1878  if ( $this->sectiontitle !== '' ) {
1879  # This branch is taken when the API is used with the 'sectiontitle' parameter.
1880  $match = self::matchSpamRegex( $this->sectiontitle );
1881  } else {
1882  # This branch is taken when the "Add Topic" user interface is used, or the API
1883  # is used with the 'summary' parameter.
1884  $match = self::matchSpamRegex( $this->summary );
1885  }
1886  }
1887  if ( $match === false ) {
1888  $match = self::matchSpamRegex( $this->textbox1 );
1889  }
1890  if ( $match !== false ) {
1891  $result['spam'] = $match;
1892  $ip = $request->getIP();
1893  $pdbk = $this->mTitle->getPrefixedDBkey();
1894  $match = str_replace( "\n", '', $match );
1895  wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
1896  $status->fatal( 'spamprotectionmatch', $match );
1897  $status->value = self::AS_SPAM_ERROR;
1898  return $status;
1899  }
1900  if ( !Hooks::run(
1901  'EditFilter',
1902  [ $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ] )
1903  ) {
1904  # Error messages etc. could be handled within the hook...
1905  $status->fatal( 'hookaborted' );
1906  $status->value = self::AS_HOOK_ERROR;
1907  return $status;
1908  } elseif ( $this->hookError != '' ) {
1909  # ...or the hook could be expecting us to produce an error
1910  $status->fatal( 'hookaborted' );
1911  $status->value = self::AS_HOOK_ERROR_EXPECTED;
1912  return $status;
1913  }
1914 
1915  if ( $user->isBlockedFrom( $this->mTitle, false ) ) {
1916  // Auto-block user's IP if the account was "hard" blocked
1917  if ( !wfReadOnly() ) {
1918  $user->spreadAnyEditBlock();
1919  }
1920  # Check block state against master, thus 'false'.
1921  $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
1922  return $status;
1923  }
1924 
1925  $this->contentLength = strlen( $this->textbox1 );
1926  $config = $this->context->getConfig();
1927  $maxArticleSize = $config->get( 'MaxArticleSize' );
1928  if ( $this->contentLength > $maxArticleSize * 1024 ) {
1929  // Error will be displayed by showEditForm()
1930  $this->tooBig = true;
1931  $status->setResult( false, self::AS_CONTENT_TOO_BIG );
1932  return $status;
1933  }
1934 
1935  if ( !$user->isAllowed( 'edit' ) ) {
1936  if ( $user->isAnon() ) {
1937  $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
1938  return $status;
1939  } else {
1940  $status->fatal( 'readonlytext' );
1941  $status->value = self::AS_READ_ONLY_PAGE_LOGGED;
1942  return $status;
1943  }
1944  }
1945 
1946  $changingContentModel = false;
1947  if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
1948  if ( !$config->get( 'ContentHandlerUseDB' ) ) {
1949  $status->fatal( 'editpage-cannot-use-custom-model' );
1950  $status->value = self::AS_CANNOT_USE_CUSTOM_MODEL;
1951  return $status;
1952  } elseif ( !$user->isAllowed( 'editcontentmodel' ) ) {
1953  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
1954  return $status;
1955  }
1956  // Make sure the user can edit the page under the new content model too
1957  $titleWithNewContentModel = clone $this->mTitle;
1958  $titleWithNewContentModel->setContentModel( $this->contentModel );
1959  if ( !$titleWithNewContentModel->userCan( 'editcontentmodel', $user )
1960  || !$titleWithNewContentModel->userCan( 'edit', $user )
1961  ) {
1962  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
1963  return $status;
1964  }
1965 
1966  $changingContentModel = true;
1967  $oldContentModel = $this->mTitle->getContentModel();
1968  }
1969 
1970  if ( $this->changeTags ) {
1971  $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange(
1972  $this->changeTags, $user );
1973  if ( !$changeTagsStatus->isOK() ) {
1974  $changeTagsStatus->value = self::AS_CHANGE_TAG_ERROR;
1975  return $changeTagsStatus;
1976  }
1977  }
1978 
1979  if ( wfReadOnly() ) {
1980  $status->fatal( 'readonlytext' );
1981  $status->value = self::AS_READ_ONLY_PAGE;
1982  return $status;
1983  }
1984  if ( $user->pingLimiter() || $user->pingLimiter( 'linkpurge', 0 )
1985  || ( $changingContentModel && $user->pingLimiter( 'editcontentmodel' ) )
1986  ) {
1987  $status->fatal( 'actionthrottledtext' );
1988  $status->value = self::AS_RATE_LIMITED;
1989  return $status;
1990  }
1991 
1992  # If the article has been deleted while editing, don't save it without
1993  # confirmation
1994  if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
1995  $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
1996  return $status;
1997  }
1998 
1999  # Load the page data from the master. If anything changes in the meantime,
2000  # we detect it by using page_latest like a token in a 1 try compare-and-swap.
2001  $this->page->loadPageData( 'fromdbmaster' );
2002  $new = !$this->page->exists();
2003 
2004  if ( $new ) {
2005  // Late check for create permission, just in case *PARANOIA*
2006  if ( !$this->mTitle->userCan( 'create', $user ) ) {
2007  $status->fatal( 'nocreatetext' );
2008  $status->value = self::AS_NO_CREATE_PERMISSION;
2009  wfDebug( __METHOD__ . ": no create permission\n" );
2010  return $status;
2011  }
2012 
2013  // Don't save a new page if it's blank or if it's a MediaWiki:
2014  // message with content equivalent to default (allow empty pages
2015  // in this case to disable messages, see T52124)
2016  $defaultMessageText = $this->mTitle->getDefaultMessageText();
2017  if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false ) {
2018  $defaultText = $defaultMessageText;
2019  } else {
2020  $defaultText = '';
2021  }
2022 
2023  if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) {
2024  $this->blankArticle = true;
2025  $status->fatal( 'blankarticle' );
2026  $status->setResult( false, self::AS_BLANK_ARTICLE );
2027  return $status;
2028  }
2029 
2030  if ( !$this->runPostMergeFilters( $textbox_content, $status, $user ) ) {
2031  return $status;
2032  }
2033 
2034  $content = $textbox_content;
2035 
2036  $result['sectionanchor'] = '';
2037  if ( $this->section == 'new' ) {
2038  if ( $this->sectiontitle !== '' ) {
2039  // Insert the section title above the content.
2040  $content = $content->addSectionHeader( $this->sectiontitle );
2041  } elseif ( $this->summary !== '' ) {
2042  // Insert the section title above the content.
2043  $content = $content->addSectionHeader( $this->summary );
2044  }
2045  $this->summary = $this->newSectionSummary( $result['sectionanchor'] );
2046  }
2047 
2048  $status->value = self::AS_SUCCESS_NEW_ARTICLE;
2049 
2050  } else { # not $new
2051 
2052  # Article exists. Check for edit conflict.
2053 
2054  $this->page->clear(); # Force reload of dates, etc.
2055  $timestamp = $this->page->getTimestamp();
2056  $latest = $this->page->getLatest();
2057 
2058  wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
2059 
2060  // An edit conflict is detected if the current revision is different from the
2061  // revision that was current when editing was initiated on the client.
2062  // This is checked based on the timestamp and revision ID.
2063  // TODO: the timestamp based check can probably go away now.
2064  if ( $timestamp != $this->edittime
2065  || ( $this->editRevId !== null && $this->editRevId != $latest )
2066  ) {
2067  $this->isConflict = true;
2068  if ( $this->section == 'new' ) {
2069  if ( $this->page->getUserText() == $user->getName() &&
2070  $this->page->getComment() == $this->newSectionSummary()
2071  ) {
2072  // Probably a duplicate submission of a new comment.
2073  // This can happen when CDN resends a request after
2074  // a timeout but the first one actually went through.
2075  wfDebug( __METHOD__
2076  . ": duplicate new section submission; trigger edit conflict!\n" );
2077  } else {
2078  // New comment; suppress conflict.
2079  $this->isConflict = false;
2080  wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
2081  }
2082  } elseif ( $this->section == ''
2084  DB_MASTER, $this->mTitle->getArticleID(),
2085  $user->getId(), $this->edittime
2086  )
2087  ) {
2088  # Suppress edit conflict with self, except for section edits where merging is required.
2089  wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
2090  $this->isConflict = false;
2091  }
2092  }
2093 
2094  // If sectiontitle is set, use it, otherwise use the summary as the section title.
2095  if ( $this->sectiontitle !== '' ) {
2096  $sectionTitle = $this->sectiontitle;
2097  } else {
2098  $sectionTitle = $this->summary;
2099  }
2100 
2101  $content = null;
2102 
2103  if ( $this->isConflict ) {
2104  wfDebug( __METHOD__
2105  . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
2106  . " (id '{$this->editRevId}') (article time '{$timestamp}')\n" );
2107  // @TODO: replaceSectionAtRev() with base ID (not prior current) for ?oldid=X case
2108  // ...or disable section editing for non-current revisions (not exposed anyway).
2109  if ( $this->editRevId !== null ) {
2110  $content = $this->page->replaceSectionAtRev(
2111  $this->section,
2112  $textbox_content,
2113  $sectionTitle,
2114  $this->editRevId
2115  );
2116  } else {
2117  $content = $this->page->replaceSectionContent(
2118  $this->section,
2119  $textbox_content,
2120  $sectionTitle,
2121  $this->edittime
2122  );
2123  }
2124  } else {
2125  wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
2126  $content = $this->page->replaceSectionContent(
2127  $this->section,
2128  $textbox_content,
2129  $sectionTitle
2130  );
2131  }
2132 
2133  if ( is_null( $content ) ) {
2134  wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
2135  $this->isConflict = true;
2136  $content = $textbox_content; // do not try to merge here!
2137  } elseif ( $this->isConflict ) {
2138  # Attempt merge
2139  if ( $this->mergeChangesIntoContent( $content ) ) {
2140  // Successful merge! Maybe we should tell the user the good news?
2141  $this->isConflict = false;
2142  wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
2143  } else {
2144  $this->section = '';
2145  $this->textbox1 = ContentHandler::getContentText( $content );
2146  wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
2147  }
2148  }
2149 
2150  if ( $this->isConflict ) {
2151  $status->setResult( false, self::AS_CONFLICT_DETECTED );
2152  return $status;
2153  }
2154 
2155  if ( !$this->runPostMergeFilters( $content, $status, $user ) ) {
2156  return $status;
2157  }
2158 
2159  if ( $this->section == 'new' ) {
2160  // Handle the user preference to force summaries here
2161  if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) {
2162  $this->missingSummary = true;
2163  $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
2164  $status->value = self::AS_SUMMARY_NEEDED;
2165  return $status;
2166  }
2167 
2168  // Do not allow the user to post an empty comment
2169  if ( $this->textbox1 == '' ) {
2170  $this->missingComment = true;
2171  $status->fatal( 'missingcommenttext' );
2172  $status->value = self::AS_TEXTBOX_EMPTY;
2173  return $status;
2174  }
2175  } elseif ( !$this->allowBlankSummary
2176  && !$content->equals( $this->getOriginalContent( $user ) )
2177  && !$content->isRedirect()
2178  && md5( $this->summary ) == $this->autoSumm
2179  ) {
2180  $this->missingSummary = true;
2181  $status->fatal( 'missingsummary' );
2182  $status->value = self::AS_SUMMARY_NEEDED;
2183  return $status;
2184  }
2185 
2186  # All's well
2187  $sectionanchor = '';
2188  if ( $this->section == 'new' ) {
2189  $this->summary = $this->newSectionSummary( $sectionanchor );
2190  } elseif ( $this->section != '' ) {
2191  # Try to get a section anchor from the section source, redirect
2192  # to edited section if header found.
2193  # XXX: Might be better to integrate this into Article::replaceSectionAtRev
2194  # for duplicate heading checking and maybe parsing.
2195  $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
2196  # We can't deal with anchors, includes, html etc in the header for now,
2197  # headline would need to be parsed to improve this.
2198  if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
2199  $sectionanchor = $this->guessSectionName( $matches[2] );
2200  }
2201  }
2202  $result['sectionanchor'] = $sectionanchor;
2203 
2204  // Save errors may fall down to the edit form, but we've now
2205  // merged the section into full text. Clear the section field
2206  // so that later submission of conflict forms won't try to
2207  // replace that into a duplicated mess.
2208  $this->textbox1 = $this->toEditText( $content );
2209  $this->section = '';
2210 
2211  $status->value = self::AS_SUCCESS_UPDATE;
2212  }
2213 
2214  if ( !$this->allowSelfRedirect
2215  && $content->isRedirect()
2216  && $content->getRedirectTarget()->equals( $this->getTitle() )
2217  ) {
2218  // If the page already redirects to itself, don't warn.
2219  $currentTarget = $this->getCurrentContent()->getRedirectTarget();
2220  if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
2221  $this->selfRedirect = true;
2222  $status->fatal( 'selfredirect' );
2223  $status->value = self::AS_SELF_REDIRECT;
2224  return $status;
2225  }
2226  }
2227 
2228  // Check for length errors again now that the section is merged in
2229  $this->contentLength = strlen( $this->toEditText( $content ) );
2230  if ( $this->contentLength > $maxArticleSize * 1024 ) {
2231  $this->tooBig = true;
2232  $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
2233  return $status;
2234  }
2235 
2236  $flags = EDIT_AUTOSUMMARY |
2237  ( $new ? EDIT_NEW : EDIT_UPDATE ) |
2238  ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
2239  ( $bot ? EDIT_FORCE_BOT : 0 );
2240 
2241  $doEditStatus = $this->page->doEditContent(
2242  $content,
2243  $this->summary,
2244  $flags,
2245  false,
2246  $user,
2247  $content->getDefaultFormat(),
2250  );
2251 
2252  if ( !$doEditStatus->isOK() ) {
2253  // Failure from doEdit()
2254  // Show the edit conflict page for certain recognized errors from doEdit(),
2255  // but don't show it for errors from extension hooks
2256  $errors = $doEditStatus->getErrorsArray();
2257  if ( in_array( $errors[0][0],
2258  [ 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ] )
2259  ) {
2260  $this->isConflict = true;
2261  // Destroys data doEdit() put in $status->value but who cares
2262  $doEditStatus->value = self::AS_END;
2263  }
2264  return $doEditStatus;
2265  }
2266 
2267  $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
2268  if ( $result['nullEdit'] ) {
2269  // We don't know if it was a null edit until now, so increment here
2270  $user->pingLimiter( 'linkpurge' );
2271  }
2272  $result['redirect'] = $content->isRedirect();
2273 
2274  $this->updateWatchlist();
2275 
2276  // If the content model changed, add a log entry
2277  if ( $changingContentModel ) {
2279  $user,
2280  $new ? false : $oldContentModel,
2281  $this->contentModel,
2282  $this->summary
2283  );
2284  }
2285 
2286  return $status;
2287  }
2288 
2295  protected function addContentModelChangeLogEntry( User $user, $oldModel, $newModel, $reason ) {
2296  $new = $oldModel === false;
2297  $log = new ManualLogEntry( 'contentmodel', $new ? 'new' : 'change' );
2298  $log->setPerformer( $user );
2299  $log->setTarget( $this->mTitle );
2300  $log->setComment( $reason );
2301  $log->setParameters( [
2302  '4::oldmodel' => $oldModel,
2303  '5::newmodel' => $newModel
2304  ] );
2305  $logid = $log->insert();
2306  $log->publish( $logid );
2307  }
2308 
2312  protected function updateWatchlist() {
2313  $user = $this->context->getUser();
2314  if ( !$user->isLoggedIn() ) {
2315  return;
2316  }
2317 
2319  $watch = $this->watchthis;
2320  // Do this in its own transaction to reduce contention...
2321  DeferredUpdates::addCallableUpdate( function () use ( $user, $title, $watch ) {
2322  if ( $watch == $user->isWatched( $title, User::IGNORE_USER_RIGHTS ) ) {
2323  return; // nothing to change
2324  }
2326  } );
2327  }
2328 
2340  private function mergeChangesIntoContent( &$editContent ) {
2341  $db = wfGetDB( DB_MASTER );
2342 
2343  // This is the revision that was current at the time editing was initiated on the client,
2344  // even if the edit was based on an old revision.
2345  $baseRevision = $this->getBaseRevision();
2346  $baseContent = $baseRevision ? $baseRevision->getContent() : null;
2347 
2348  if ( is_null( $baseContent ) ) {
2349  return false;
2350  }
2351 
2352  // The current state, we want to merge updates into it
2353  $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
2354  $currentContent = $currentRevision ? $currentRevision->getContent() : null;
2355 
2356  if ( is_null( $currentContent ) ) {
2357  return false;
2358  }
2359 
2360  $handler = ContentHandler::getForModelID( $baseContent->getModel() );
2361 
2362  $result = $handler->merge3( $baseContent, $editContent, $currentContent );
2363 
2364  if ( $result ) {
2365  $editContent = $result;
2366  // Update parentRevId to what we just merged.
2367  $this->parentRevId = $currentRevision->getId();
2368  return true;
2369  }
2370 
2371  return false;
2372  }
2373 
2386  public function getBaseRevision() {
2387  if ( !$this->mBaseRevision ) {
2388  $db = wfGetDB( DB_MASTER );
2389  $this->mBaseRevision = $this->editRevId
2390  ? Revision::newFromId( $this->editRevId, Revision::READ_LATEST )
2391  : Revision::loadFromTimestamp( $db, $this->mTitle, $this->edittime );
2392  }
2393  return $this->mBaseRevision;
2394  }
2395 
2403  public static function matchSpamRegex( $text ) {
2405  // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
2406  $regexes = (array)$wgSpamRegex;
2407  return self::matchSpamRegexInternal( $text, $regexes );
2408  }
2409 
2417  public static function matchSummarySpamRegex( $text ) {
2419  $regexes = (array)$wgSummarySpamRegex;
2420  return self::matchSpamRegexInternal( $text, $regexes );
2421  }
2422 
2428  protected static function matchSpamRegexInternal( $text, $regexes ) {
2429  foreach ( $regexes as $regex ) {
2430  $matches = [];
2431  if ( preg_match( $regex, $text, $matches ) ) {
2432  return $matches[0];
2433  }
2434  }
2435  return false;
2436  }
2437 
2438  public function setHeaders() {
2439  $out = $this->context->getOutput();
2440 
2441  $out->addModules( 'mediawiki.action.edit' );
2442  $out->addModuleStyles( 'mediawiki.action.edit.styles' );
2443  $out->addModuleStyles( 'mediawiki.editfont.styles' );
2444 
2445  $user = $this->context->getUser();
2446  if ( $user->getOption( 'showtoolbar' ) ) {
2447  // The addition of default buttons is handled by getEditToolbar() which
2448  // has its own dependency on this module. The call here ensures the module
2449  // is loaded in time (it has position "top") for other modules to register
2450  // buttons (e.g. extensions, gadgets, user scripts).
2451  $out->addModules( 'mediawiki.toolbar' );
2452  }
2453 
2454  if ( $user->getOption( 'uselivepreview' ) ) {
2455  $out->addModules( 'mediawiki.action.edit.preview' );
2456  }
2457 
2458  if ( $user->getOption( 'useeditwarning' ) ) {
2459  $out->addModules( 'mediawiki.action.edit.editWarning' );
2460  }
2461 
2462  # Enabled article-related sidebar, toplinks, etc.
2463  $out->setArticleRelated( true );
2464 
2465  $contextTitle = $this->getContextTitle();
2466  if ( $this->isConflict ) {
2467  $msg = 'editconflict';
2468  } elseif ( $contextTitle->exists() && $this->section != '' ) {
2469  $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
2470  } else {
2471  $msg = $contextTitle->exists()
2472  || ( $contextTitle->getNamespace() == NS_MEDIAWIKI
2473  && $contextTitle->getDefaultMessageText() !== false
2474  )
2475  ? 'editing'
2476  : 'creating';
2477  }
2478 
2479  # Use the title defined by DISPLAYTITLE magic word when present
2480  # NOTE: getDisplayTitle() returns HTML while getPrefixedText() returns plain text.
2481  # setPageTitle() treats the input as wikitext, which should be safe in either case.
2482  $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
2483  if ( $displayTitle === false ) {
2484  $displayTitle = $contextTitle->getPrefixedText();
2485  }
2486  $out->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
2487 
2488  $config = $this->context->getConfig();
2489 
2490  # Transmit the name of the message to JavaScript for live preview
2491  # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
2492  $out->addJsConfigVars( [
2493  'wgEditMessage' => $msg,
2494  'wgAjaxEditStash' => $config->get( 'AjaxEditStash' ),
2495  ] );
2496 
2497  // Add whether to use 'save' or 'publish' messages to JavaScript for post-edit, other
2498  // editors, etc.
2499  $out->addJsConfigVars(
2500  'wgEditSubmitButtonLabelPublish',
2501  $config->get( 'EditSubmitButtonLabelPublish' )
2502  );
2503  }
2504 
2508  protected function showIntro() {
2509  if ( $this->suppressIntro ) {
2510  return;
2511  }
2512 
2513  $out = $this->context->getOutput();
2514  $namespace = $this->mTitle->getNamespace();
2515 
2516  if ( $namespace == NS_MEDIAWIKI ) {
2517  # Show a warning if editing an interface message
2518  $out->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
2519  # If this is a default message (but not css, json, or js),
2520  # show a hint that it is translatable on translatewiki.net
2521  if (
2522  !$this->mTitle->hasContentModel( CONTENT_MODEL_CSS )
2523  && !$this->mTitle->hasContentModel( CONTENT_MODEL_JSON )
2524  && !$this->mTitle->hasContentModel( CONTENT_MODEL_JAVASCRIPT )
2525  ) {
2526  $defaultMessageText = $this->mTitle->getDefaultMessageText();
2527  if ( $defaultMessageText !== false ) {
2528  $out->wrapWikiMsg( "<div class='mw-translateinterface'>\n$1\n</div>",
2529  'translateinterface' );
2530  }
2531  }
2532  } elseif ( $namespace == NS_FILE ) {
2533  # Show a hint to shared repo
2534  $file = wfFindFile( $this->mTitle );
2535  if ( $file && !$file->isLocal() ) {
2536  $descUrl = $file->getDescriptionUrl();
2537  # there must be a description url to show a hint to shared repo
2538  if ( $descUrl ) {
2539  if ( !$this->mTitle->exists() ) {
2540  $out->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
2541  'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
2542  ] );
2543  } else {
2544  $out->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
2545  'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
2546  ] );
2547  }
2548  }
2549  }
2550  }
2551 
2552  # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2553  # Show log extract when the user is currently blocked
2554  if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
2555  $username = explode( '/', $this->mTitle->getText(), 2 )[0];
2556  $user = User::newFromName( $username, false /* allow IP users */ );
2557  $ip = User::isIP( $username );
2558  $block = Block::newFromTarget( $user, $user );
2559  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
2560  $out->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2561  [ 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ] );
2562  } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
2563  # Show log extract if the user is currently blocked
2565  $out,
2566  'block',
2567  MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
2568  '',
2569  [
2570  'lim' => 1,
2571  'showIfEmpty' => false,
2572  'msgKey' => [
2573  'blocked-notice-logextract',
2574  $user->getName() # Support GENDER in notice
2575  ]
2576  ]
2577  );
2578  }
2579  }
2580  # Try to add a custom edit intro, or use the standard one if this is not possible.
2581  if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
2583  $this->context->msg( 'helppage' )->inContentLanguage()->text()
2584  ) );
2585  if ( $this->context->getUser()->isLoggedIn() ) {
2586  $out->wrapWikiMsg(
2587  // Suppress the external link icon, consider the help url an internal one
2588  "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2589  [
2590  'newarticletext',
2591  $helpLink
2592  ]
2593  );
2594  } else {
2595  $out->wrapWikiMsg(
2596  // Suppress the external link icon, consider the help url an internal one
2597  "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2598  [
2599  'newarticletextanon',
2600  $helpLink
2601  ]
2602  );
2603  }
2604  }
2605  # Give a notice if the user is editing a deleted/moved page...
2606  if ( !$this->mTitle->exists() ) {
2607  $dbr = wfGetDB( DB_REPLICA );
2608 
2609  LogEventsList::showLogExtract( $out, [ 'delete', 'move' ], $this->mTitle,
2610  '',
2611  [
2612  'lim' => 10,
2613  'conds' => [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ],
2614  'showIfEmpty' => false,
2615  'msgKey' => [ 'recreate-moveddeleted-warn' ]
2616  ]
2617  );
2618  }
2619  }
2620 
2626  protected function showCustomIntro() {
2627  if ( $this->editintro ) {
2628  $title = Title::newFromText( $this->editintro );
2629  if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
2630  // Added using template syntax, to take <noinclude>'s into account.
2631  $this->context->getOutput()->addWikiTextTitleTidy(
2632  '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>',
2634  );
2635  return true;
2636  }
2637  }
2638  return false;
2639  }
2640 
2659  protected function toEditText( $content ) {
2660  if ( $content === null || $content === false || is_string( $content ) ) {
2661  return $content;
2662  }
2663 
2664  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2665  throw new MWException( 'This content model is not supported: ' . $content->getModel() );
2666  }
2667 
2668  return $content->serialize( $this->contentFormat );
2669  }
2670 
2687  protected function toEditContent( $text ) {
2688  if ( $text === false || $text === null ) {
2689  return $text;
2690  }
2691 
2692  $content = ContentHandler::makeContent( $text, $this->getTitle(),
2693  $this->contentModel, $this->contentFormat );
2694 
2695  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2696  throw new MWException( 'This content model is not supported: ' . $content->getModel() );
2697  }
2698 
2699  return $content;
2700  }
2701 
2710  public function showEditForm( $formCallback = null ) {
2711  # need to parse the preview early so that we know which templates are used,
2712  # otherwise users with "show preview after edit box" will get a blank list
2713  # we parse this near the beginning so that setHeaders can do the title
2714  # setting work instead of leaving it in getPreviewText
2715  $previewOutput = '';
2716  if ( $this->formtype == 'preview' ) {
2717  $previewOutput = $this->getPreviewText();
2718  }
2719 
2720  $out = $this->context->getOutput();
2721 
2722  // Avoid PHP 7.1 warning of passing $this by reference
2723  $editPage = $this;
2724  Hooks::run( 'EditPage::showEditForm:initial', [ &$editPage, &$out ] );
2725 
2726  $this->setHeaders();
2727 
2728  $this->addTalkPageText();
2729  $this->addEditNotices();
2730 
2731  if ( !$this->isConflict &&
2732  $this->section != '' &&
2733  !$this->isSectionEditSupported() ) {
2734  // We use $this->section to much before this and getVal('wgSection') directly in other places
2735  // at this point we can't reset $this->section to '' to fallback to non-section editing.
2736  // Someone is welcome to try refactoring though
2737  $out->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
2738  return;
2739  }
2740 
2741  $this->showHeader();
2742 
2743  $out->addHTML( $this->editFormPageTop );
2744 
2745  $user = $this->context->getUser();
2746  if ( $user->getOption( 'previewontop' ) ) {
2747  $this->displayPreviewArea( $previewOutput, true );
2748  }
2749 
2750  $out->addHTML( $this->editFormTextTop );
2751 
2752  $showToolbar = true;
2753  if ( $this->wasDeletedSinceLastEdit() ) {
2754  if ( $this->formtype == 'save' ) {
2755  // Hide the toolbar and edit area, user can click preview to get it back
2756  // Add an confirmation checkbox and explanation.
2757  $showToolbar = false;
2758  } else {
2759  $out->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2760  'deletedwhileediting' );
2761  }
2762  }
2763 
2764  // @todo add EditForm plugin interface and use it here!
2765  // search for textarea1 and textarea2, and allow EditForm to override all uses.
2766  $out->addHTML( Html::openElement(
2767  'form',
2768  [
2769  'class' => 'mw-editform',
2770  'id' => self::EDITFORM_ID,
2771  'name' => self::EDITFORM_ID,
2772  'method' => 'post',
2773  'action' => $this->getActionURL( $this->getContextTitle() ),
2774  'enctype' => 'multipart/form-data'
2775  ]
2776  ) );
2777 
2778  if ( is_callable( $formCallback ) ) {
2779  wfWarn( 'The $formCallback parameter to ' . __METHOD__ . 'is deprecated' );
2780  call_user_func_array( $formCallback, [ &$out ] );
2781  }
2782 
2783  // Add a check for Unicode support
2784  $out->addHTML( Html::hidden( 'wpUnicodeCheck', self::UNICODE_CHECK ) );
2785 
2786  // Add an empty field to trip up spambots
2787  $out->addHTML(
2788  Xml::openElement( 'div', [ 'id' => 'antispam-container', 'style' => 'display: none;' ] )
2789  . Html::rawElement(
2790  'label',
2791  [ 'for' => 'wpAntispam' ],
2792  $this->context->msg( 'simpleantispam-label' )->parse()
2793  )
2794  . Xml::element(
2795  'input',
2796  [
2797  'type' => 'text',
2798  'name' => 'wpAntispam',
2799  'id' => 'wpAntispam',
2800  'value' => ''
2801  ]
2802  )
2803  . Xml::closeElement( 'div' )
2804  );
2805 
2806  // Avoid PHP 7.1 warning of passing $this by reference
2807  $editPage = $this;
2808  Hooks::run( 'EditPage::showEditForm:fields', [ &$editPage, &$out ] );
2809 
2810  // Put these up at the top to ensure they aren't lost on early form submission
2811  $this->showFormBeforeText();
2812 
2813  if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
2814  $username = $this->lastDelete->user_name;
2815  $comment = CommentStore::getStore()
2816  ->getComment( 'log_comment', $this->lastDelete )->text;
2817 
2818  // It is better to not parse the comment at all than to have templates expanded in the middle
2819  // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
2820  $key = $comment === ''
2821  ? 'confirmrecreate-noreason'
2822  : 'confirmrecreate';
2823  $out->addHTML(
2824  '<div class="mw-confirm-recreate">' .
2825  $this->context->msg( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
2826  Xml::checkLabel( $this->context->msg( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
2827  [ 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' ]
2828  ) .
2829  '</div>'
2830  );
2831  }
2832 
2833  # When the summary is hidden, also hide them on preview/show changes
2834  if ( $this->nosummary ) {
2835  $out->addHTML( Html::hidden( 'nosummary', true ) );
2836  }
2837 
2838  # If a blank edit summary was previously provided, and the appropriate
2839  # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2840  # user being bounced back more than once in the event that a summary
2841  # is not required.
2842  # ####
2843  # For a bit more sophisticated detection of blank summaries, hash the
2844  # automatic one and pass that in the hidden field wpAutoSummary.
2845  if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
2846  $out->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
2847  }
2848 
2849  if ( $this->undidRev ) {
2850  $out->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
2851  }
2852 
2853  if ( $this->selfRedirect ) {
2854  $out->addHTML( Html::hidden( 'wpIgnoreSelfRedirect', true ) );
2855  }
2856 
2857  if ( $this->hasPresetSummary ) {
2858  // If a summary has been preset using &summary= we don't want to prompt for
2859  // a different summary. Only prompt for a summary if the summary is blanked.
2860  // (T19416)
2861  $this->autoSumm = md5( '' );
2862  }
2863 
2864  $autosumm = $this->autoSumm ?: md5( $this->summary );
2865  $out->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
2866 
2867  $out->addHTML( Html::hidden( 'oldid', $this->oldid ) );
2868  $out->addHTML( Html::hidden( 'parentRevId', $this->getParentRevId() ) );
2869 
2870  $out->addHTML( Html::hidden( 'format', $this->contentFormat ) );
2871  $out->addHTML( Html::hidden( 'model', $this->contentModel ) );
2872 
2873  $out->enableOOUI();
2874 
2875  if ( $this->section == 'new' ) {
2876  $this->showSummaryInput( true, $this->summary );
2877  $out->addHTML( $this->getSummaryPreview( true, $this->summary ) );
2878  }
2879 
2880  $out->addHTML( $this->editFormTextBeforeContent );
2881  if ( $this->isConflict ) {
2882  // In an edit conflict, we turn textbox2 into the user's text,
2883  // and textbox1 into the stored version
2884  $this->textbox2 = $this->textbox1;
2885 
2886  $content = $this->getCurrentContent();
2887  $this->textbox1 = $this->toEditText( $content );
2888 
2890  $editConflictHelper->setTextboxes( $this->textbox2, $this->textbox1 );
2891  $editConflictHelper->setContentModel( $this->contentModel );
2892  $editConflictHelper->setContentFormat( $this->contentFormat );
2894  }
2895 
2896  if ( !$this->mTitle->isUserConfigPage() && $showToolbar && $user->getOption( 'showtoolbar' ) ) {
2897  $out->addHTML( self::getEditToolbar( $this->mTitle ) );
2898  }
2899 
2900  if ( $this->blankArticle ) {
2901  $out->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) );
2902  }
2903 
2904  if ( $this->isConflict ) {
2905  // In an edit conflict bypass the overridable content form method
2906  // and fallback to the raw wpTextbox1 since editconflicts can't be
2907  // resolved between page source edits and custom ui edits using the
2908  // custom edit ui.
2909  $conflictTextBoxAttribs = [];
2910  if ( $this->wasDeletedSinceLastEdit() ) {
2911  $conflictTextBoxAttribs['style'] = 'display:none;';
2912  } elseif ( $this->isOldRev ) {
2913  $conflictTextBoxAttribs['class'] = 'mw-textarea-oldrev';
2914  }
2915 
2916  $out->addHTML( $editConflictHelper->getEditConflictMainTextBox( $conflictTextBoxAttribs ) );
2918  } else {
2919  $this->showContentForm();
2920  }
2921 
2922  $out->addHTML( $this->editFormTextAfterContent );
2923 
2924  $this->showStandardInputs();
2925 
2926  $this->showFormAfterText();
2927 
2928  $this->showTosSummary();
2929 
2930  $this->showEditTools();
2931 
2932  $out->addHTML( $this->editFormTextAfterTools . "\n" );
2933 
2934  $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
2935 
2936  $out->addHTML( Html::rawElement( 'div', [ 'class' => 'hiddencats' ],
2937  Linker::formatHiddenCategories( $this->page->getHiddenCategories() ) ) );
2938 
2939  $out->addHTML( Html::rawElement( 'div', [ 'class' => 'limitreport' ],
2940  self::getPreviewLimitReport( $this->mParserOutput ) ) );
2941 
2942  $out->addModules( 'mediawiki.action.edit.collapsibleFooter' );
2943 
2944  if ( $this->isConflict ) {
2945  try {
2946  $this->showConflict();
2947  } catch ( MWContentSerializationException $ex ) {
2948  // this can't really happen, but be nice if it does.
2949  $msg = $this->context->msg(
2950  'content-failed-to-parse',
2951  $this->contentModel,
2952  $this->contentFormat,
2953  $ex->getMessage()
2954  );
2955  $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
2956  }
2957  }
2958 
2959  // Set a hidden field so JS knows what edit form mode we are in
2960  if ( $this->isConflict ) {
2961  $mode = 'conflict';
2962  } elseif ( $this->preview ) {
2963  $mode = 'preview';
2964  } elseif ( $this->diff ) {
2965  $mode = 'diff';
2966  } else {
2967  $mode = 'text';
2968  }
2969  $out->addHTML( Html::hidden( 'mode', $mode, [ 'id' => 'mw-edit-mode' ] ) );
2970 
2971  // Marker for detecting truncated form data. This must be the last
2972  // parameter sent in order to be of use, so do not move me.
2973  $out->addHTML( Html::hidden( 'wpUltimateParam', true ) );
2974  $out->addHTML( $this->editFormTextBottom . "\n</form>\n" );
2975 
2976  if ( !$user->getOption( 'previewontop' ) ) {
2977  $this->displayPreviewArea( $previewOutput, false );
2978  }
2979  }
2980 
2988  public function makeTemplatesOnThisPageList( array $templates ) {
2989  $templateListFormatter = new TemplatesOnThisPageFormatter(
2990  $this->context, MediaWikiServices::getInstance()->getLinkRenderer()
2991  );
2992 
2993  // preview if preview, else section if section, else false
2994  $type = false;
2995  if ( $this->preview ) {
2996  $type = 'preview';
2997  } elseif ( $this->section != '' ) {
2998  $type = 'section';
2999  }
3000 
3001  return Html::rawElement( 'div', [ 'class' => 'templatesUsed' ],
3002  $templateListFormatter->format( $templates, $type )
3003  );
3004  }
3005 
3012  public static function extractSectionTitle( $text ) {
3013  preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
3014  if ( !empty( $matches[2] ) ) {
3015  global $wgParser;
3016  return $wgParser->stripSectionName( trim( $matches[2] ) );
3017  } else {
3018  return false;
3019  }
3020  }
3021 
3022  protected function showHeader() {
3023  $out = $this->context->getOutput();
3024  $user = $this->context->getUser();
3025  if ( $this->isConflict ) {
3026  $this->addExplainConflictHeader( $out );
3027  $this->editRevId = $this->page->getLatest();
3028  } else {
3029  if ( $this->section != '' && $this->section != 'new' ) {
3030  if ( !$this->summary && !$this->preview && !$this->diff ) {
3031  $sectionTitle = self::extractSectionTitle( $this->textbox1 ); // FIXME: use Content object
3032  if ( $sectionTitle !== false ) {
3033  $this->summary = "/* $sectionTitle */ ";
3034  }
3035  }
3036  }
3037 
3038  $buttonLabel = $this->context->msg( $this->getSubmitButtonLabel() )->text();
3039 
3040  if ( $this->missingComment ) {
3041  $out->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
3042  }
3043 
3044  if ( $this->missingSummary && $this->section != 'new' ) {
3045  $out->wrapWikiMsg(
3046  "<div id='mw-missingsummary'>\n$1\n</div>",
3047  [ 'missingsummary', $buttonLabel ]
3048  );
3049  }
3050 
3051  if ( $this->missingSummary && $this->section == 'new' ) {
3052  $out->wrapWikiMsg(
3053  "<div id='mw-missingcommentheader'>\n$1\n</div>",
3054  [ 'missingcommentheader', $buttonLabel ]
3055  );
3056  }
3057 
3058  if ( $this->blankArticle ) {
3059  $out->wrapWikiMsg(
3060  "<div id='mw-blankarticle'>\n$1\n</div>",
3061  [ 'blankarticle', $buttonLabel ]
3062  );
3063  }
3064 
3065  if ( $this->selfRedirect ) {
3066  $out->wrapWikiMsg(
3067  "<div id='mw-selfredirect'>\n$1\n</div>",
3068  [ 'selfredirect', $buttonLabel ]
3069  );
3070  }
3071 
3072  if ( $this->hookError !== '' ) {
3073  $out->addWikiText( $this->hookError );
3074  }
3075 
3076  if ( $this->section != 'new' ) {
3077  $revision = $this->mArticle->getRevisionFetched();
3078  if ( $revision ) {
3079  // Let sysop know that this will make private content public if saved
3080 
3081  if ( !$revision->userCan( Revision::DELETED_TEXT, $user ) ) {
3082  $out->wrapWikiMsg(
3083  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
3084  'rev-deleted-text-permission'
3085  );
3086  } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
3087  $out->wrapWikiMsg(
3088  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
3089  'rev-deleted-text-view'
3090  );
3091  }
3092 
3093  if ( !$revision->isCurrent() ) {
3094  $this->mArticle->setOldSubtitle( $revision->getId() );
3095  $out->addWikiMsg( 'editingold' );
3096  $this->isOldRev = true;
3097  }
3098  } elseif ( $this->mTitle->exists() ) {
3099  // Something went wrong
3100 
3101  $out->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
3102  [ 'missing-revision', $this->oldid ] );
3103  }
3104  }
3105  }
3106 
3107  if ( wfReadOnly() ) {
3108  $out->wrapWikiMsg(
3109  "<div id=\"mw-read-only-warning\">\n$1\n</div>",
3110  [ 'readonlywarning', wfReadOnlyReason() ]
3111  );
3112  } elseif ( $user->isAnon() ) {
3113  if ( $this->formtype != 'preview' ) {
3114  $out->wrapWikiMsg(
3115  "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
3116  [ 'anoneditwarning',
3117  // Log-in link
3118  SpecialPage::getTitleFor( 'Userlogin' )->getFullURL( [
3119  'returnto' => $this->getTitle()->getPrefixedDBkey()
3120  ] ),
3121  // Sign-up link
3122  SpecialPage::getTitleFor( 'CreateAccount' )->getFullURL( [
3123  'returnto' => $this->getTitle()->getPrefixedDBkey()
3124  ] )
3125  ]
3126  );
3127  } else {
3128  $out->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
3129  'anonpreviewwarning'
3130  );
3131  }
3132  } else {
3133  if ( $this->mTitle->isUserConfigPage() ) {
3134  # Check the skin exists
3135  if ( $this->isWrongCaseUserConfigPage() ) {
3136  $out->wrapWikiMsg(
3137  "<div class='error' id='mw-userinvalidconfigtitle'>\n$1\n</div>",
3138  [ 'userinvalidconfigtitle', $this->mTitle->getSkinFromConfigSubpage() ]
3139  );
3140  }
3141  if ( $this->getTitle()->isSubpageOf( $user->getUserPage() ) ) {
3142  $isUserCssConfig = $this->mTitle->isUserCssConfigPage();
3143  $isUserJsonConfig = $this->mTitle->isUserJsonConfigPage();
3144  $isUserJsConfig = $this->mTitle->isUserJsConfigPage();
3145 
3146  $warning = $isUserCssConfig
3147  ? 'usercssispublic'
3148  : ( $isUserJsonConfig ? 'userjsonispublic' : 'userjsispublic' );
3149 
3150  $out->wrapWikiMsg( '<div class="mw-userconfigpublic">$1</div>', $warning );
3151 
3152  if ( $this->formtype !== 'preview' ) {
3153  $config = $this->context->getConfig();
3154  if ( $isUserCssConfig && $config->get( 'AllowUserCss' ) ) {
3155  $out->wrapWikiMsg(
3156  "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
3157  [ 'usercssyoucanpreview' ]
3158  );
3159  } elseif ( $isUserJsonConfig /* No comparable 'AllowUserJson' */ ) {
3160  $out->wrapWikiMsg(
3161  "<div id='mw-userjsonyoucanpreview'>\n$1\n</div>",
3162  [ 'userjsonyoucanpreview' ]
3163  );
3164  } elseif ( $isUserJsConfig && $config->get( 'AllowUserJs' ) ) {
3165  $out->wrapWikiMsg(
3166  "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
3167  [ 'userjsyoucanpreview' ]
3168  );
3169  }
3170  }
3171  }
3172  }
3173  }
3174 
3176 
3177  $this->addLongPageWarningHeader();
3178 
3179  # Add header copyright warning
3180  $this->showHeaderCopyrightWarning();
3181  }
3182 
3190  private function getSummaryInputAttributes( array $inputAttrs = null ) {
3191  $conf = $this->context->getConfig();
3192  $oldCommentSchema = $conf->get( 'CommentTableSchemaMigrationStage' ) === MIGRATION_OLD;
3193  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
3194  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
3195  // Unicode codepoints (or 255 UTF-8 bytes for old schema).
3196  return ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
3197  'id' => 'wpSummary',
3198  'name' => 'wpSummary',
3199  'maxlength' => $oldCommentSchema ? 200 : CommentStore::COMMENT_CHARACTER_LIMIT,
3200  'tabindex' => 1,
3201  'size' => 60,
3202  'spellcheck' => 'true',
3203  ];
3204  }
3205 
3215  function getSummaryInputWidget( $summary = "", $labelText = null, $inputAttrs = null ) {
3216  $inputAttrs = OOUI\Element::configFromHtmlAttributes(
3217  $this->getSummaryInputAttributes( $inputAttrs )
3218  );
3219  $inputAttrs += [
3220  'title' => Linker::titleAttrib( 'summary' ),
3221  'accessKey' => Linker::accesskey( 'summary' ),
3222  ];
3223 
3224  // For compatibility with old scripts and extensions, we want the legacy 'id' on the `<input>`
3225  $inputAttrs['inputId'] = $inputAttrs['id'];
3226  $inputAttrs['id'] = 'wpSummaryWidget';
3227 
3228  return new OOUI\FieldLayout(
3229  new OOUI\TextInputWidget( [
3230  'value' => $summary,
3231  'infusable' => true,
3232  ] + $inputAttrs ),
3233  [
3234  'label' => new OOUI\HtmlSnippet( $labelText ),
3235  'align' => 'top',
3236  'id' => 'wpSummaryLabel',
3237  'classes' => [ $this->missingSummary ? 'mw-summarymissed' : 'mw-summary' ],
3238  ]
3239  );
3240  }
3241 
3248  protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
3249  # Add a class if 'missingsummary' is triggered to allow styling of the summary line
3250  $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
3251  if ( $isSubjectPreview ) {
3252  if ( $this->nosummary ) {
3253  return;
3254  }
3255  } else {
3256  if ( !$this->mShowSummaryField ) {
3257  return;
3258  }
3259  }
3260 
3261  $labelText = $this->context->msg( $isSubjectPreview ? 'subject' : 'summary' )->parse();
3262  $this->context->getOutput()->addHTML( $this->getSummaryInputWidget(
3263  $summary,
3264  $labelText,
3265  [ 'class' => $summaryClass ]
3266  ) );
3267  }
3268 
3276  protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
3277  // avoid spaces in preview, gets always trimmed on save
3278  $summary = trim( $summary );
3279  if ( !$summary || ( !$this->preview && !$this->diff ) ) {
3280  return "";
3281  }
3282 
3283  global $wgParser;
3284 
3285  if ( $isSubjectPreview ) {
3286  $summary = $this->context->msg( 'newsectionsummary' )
3287  ->rawParams( $wgParser->stripSectionName( $summary ) )
3288  ->inContentLanguage()->text();
3289  }
3290 
3291  $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
3292 
3293  $summary = $this->context->msg( $message )->parse()
3294  . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
3295  return Xml::tags( 'div', [ 'class' => 'mw-summary-preview' ], $summary );
3296  }
3297 
3298  protected function showFormBeforeText() {
3299  $out = $this->context->getOutput();
3300  $out->addHTML( Html::hidden( 'wpSection', $this->section ) );
3301  $out->addHTML( Html::hidden( 'wpStarttime', $this->starttime ) );
3302  $out->addHTML( Html::hidden( 'wpEdittime', $this->edittime ) );
3303  $out->addHTML( Html::hidden( 'editRevId', $this->editRevId ) );
3304  $out->addHTML( Html::hidden( 'wpScrolltop', $this->scrolltop, [ 'id' => 'wpScrolltop' ] ) );
3305  }
3306 
3307  protected function showFormAfterText() {
3320  $this->context->getOutput()->addHTML(
3321  "\n" .
3322  Html::hidden( "wpEditToken", $this->context->getUser()->getEditToken() ) .
3323  "\n"
3324  );
3325  }
3326 
3335  protected function showContentForm() {
3336  $this->showTextbox1();
3337  }
3338 
3347  protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
3348  if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
3349  $attribs = [ 'style' => 'display:none;' ];
3350  } else {
3351  $builder = new TextboxBuilder();
3352  $classes = $builder->getTextboxProtectionCSSClasses( $this->getTitle() );
3353 
3354  # Is an old revision being edited?
3355  if ( $this->isOldRev ) {
3356  $classes[] = 'mw-textarea-oldrev';
3357  }
3358 
3359  $attribs = [ 'tabindex' => 1 ];
3360 
3361  if ( is_array( $customAttribs ) ) {
3363  }
3364 
3365  $attribs = $builder->mergeClassesIntoAttributes( $classes, $attribs );
3366  }
3367 
3368  $this->showTextbox(
3369  $textoverride ?? $this->textbox1,
3370  'wpTextbox1',
3371  $attribs
3372  );
3373  }
3374 
3375  protected function showTextbox2() {
3376  $this->showTextbox( $this->textbox2, 'wpTextbox2', [ 'tabindex' => 6, 'readonly' ] );
3377  }
3378 
3379  protected function showTextbox( $text, $name, $customAttribs = [] ) {
3380  $builder = new TextboxBuilder();
3381  $attribs = $builder->buildTextboxAttribs(
3382  $name,
3384  $this->context->getUser(),
3386  );
3387 
3388  $this->context->getOutput()->addHTML(
3389  Html::textarea( $name, $builder->addNewLineAtEnd( $text ), $attribs )
3390  );
3391  }
3392 
3393  protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
3394  $classes = [];
3395  if ( $isOnTop ) {
3396  $classes[] = 'ontop';
3397  }
3398 
3399  $attribs = [ 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) ];
3400 
3401  if ( $this->formtype != 'preview' ) {
3402  $attribs['style'] = 'display: none;';
3403  }
3404 
3405  $out = $this->context->getOutput();
3406  $out->addHTML( Xml::openElement( 'div', $attribs ) );
3407 
3408  if ( $this->formtype == 'preview' ) {
3409  $this->showPreview( $previewOutput );
3410  } else {
3411  // Empty content container for LivePreview
3412  $pageViewLang = $this->mTitle->getPageViewLanguage();
3413  $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3414  'class' => 'mw-content-' . $pageViewLang->getDir() ];
3415  $out->addHTML( Html::rawElement( 'div', $attribs ) );
3416  }
3417 
3418  $out->addHTML( '</div>' );
3419 
3420  if ( $this->formtype == 'diff' ) {
3421  try {
3422  $this->showDiff();
3423  } catch ( MWContentSerializationException $ex ) {
3424  $msg = $this->context->msg(
3425  'content-failed-to-parse',
3426  $this->contentModel,
3427  $this->contentFormat,
3428  $ex->getMessage()
3429  );
3430  $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
3431  }
3432  }
3433  }
3434 
3441  protected function showPreview( $text ) {
3442  if ( $this->mArticle instanceof CategoryPage ) {
3443  $this->mArticle->openShowCategory();
3444  }
3445  # This hook seems slightly odd here, but makes things more
3446  # consistent for extensions.
3447  $out = $this->context->getOutput();
3448  Hooks::run( 'OutputPageBeforeHTML', [ &$out, &$text ] );
3449  $out->addHTML( $text );
3450  if ( $this->mArticle instanceof CategoryPage ) {
3451  $this->mArticle->closeShowCategory();
3452  }
3453  }
3454 
3462  public function showDiff() {
3463  $oldtitlemsg = 'currentrev';
3464  # if message does not exist, show diff against the preloaded default
3465  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
3466  $oldtext = $this->mTitle->getDefaultMessageText();
3467  if ( $oldtext !== false ) {
3468  $oldtitlemsg = 'defaultmessagetext';
3469  $oldContent = $this->toEditContent( $oldtext );
3470  } else {
3471  $oldContent = null;
3472  }
3473  } else {
3474  $oldContent = $this->getCurrentContent();
3475  }
3476 
3477  $textboxContent = $this->toEditContent( $this->textbox1 );
3478  if ( $this->editRevId !== null ) {
3479  $newContent = $this->page->replaceSectionAtRev(
3480  $this->section, $textboxContent, $this->summary, $this->editRevId
3481  );
3482  } else {
3483  $newContent = $this->page->replaceSectionContent(
3484  $this->section, $textboxContent, $this->summary, $this->edittime
3485  );
3486  }
3487 
3488  if ( $newContent ) {
3489  Hooks::run( 'EditPageGetDiffContent', [ $this, &$newContent ] );
3490 
3491  $user = $this->context->getUser();
3493  MediaWikiServices::getInstance()->getContentLanguage() );
3494  $newContent = $newContent->preSaveTransform( $this->mTitle, $user, $popts );
3495  }
3496 
3497  if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
3498  $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
3499  $newtitle = $this->context->msg( 'yourtext' )->parse();
3500 
3501  if ( !$oldContent ) {
3502  $oldContent = $newContent->getContentHandler()->makeEmptyContent();
3503  }
3504 
3505  if ( !$newContent ) {
3506  $newContent = $oldContent->getContentHandler()->makeEmptyContent();
3507  }
3508 
3509  $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->context );
3510  $de->setContent( $oldContent, $newContent );
3511 
3512  $difftext = $de->getDiff( $oldtitle, $newtitle );
3513  $de->showDiffStyle();
3514  } else {
3515  $difftext = '';
3516  }
3517 
3518  $this->context->getOutput()->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
3519  }
3520 
3524  protected function showHeaderCopyrightWarning() {
3525  $msg = 'editpage-head-copy-warn';
3526  if ( !$this->context->msg( $msg )->isDisabled() ) {
3527  $this->context->getOutput()->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
3528  'editpage-head-copy-warn' );
3529  }
3530  }
3531 
3540  protected function showTosSummary() {
3541  $msg = 'editpage-tos-summary';
3542  Hooks::run( 'EditPageTosSummary', [ $this->mTitle, &$msg ] );
3543  if ( !$this->context->msg( $msg )->isDisabled() ) {
3544  $out = $this->context->getOutput();
3545  $out->addHTML( '<div class="mw-tos-summary">' );
3546  $out->addWikiMsg( $msg );
3547  $out->addHTML( '</div>' );
3548  }
3549  }
3550 
3555  protected function showEditTools() {
3556  $this->context->getOutput()->addHTML( '<div class="mw-editTools">' .
3557  $this->context->msg( 'edittools' )->inContentLanguage()->parse() .
3558  '</div>' );
3559  }
3560 
3567  protected function getCopywarn() {
3568  return self::getCopyrightWarning( $this->mTitle );
3569  }
3570 
3579  public static function getCopyrightWarning( $title, $format = 'plain', $langcode = null ) {
3581  if ( $wgRightsText ) {
3582  $copywarnMsg = [ 'copyrightwarning',
3583  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]',
3584  $wgRightsText ];
3585  } else {
3586  $copywarnMsg = [ 'copyrightwarning2',
3587  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' ];
3588  }
3589  // Allow for site and per-namespace customization of contribution/copyright notice.
3590  Hooks::run( 'EditPageCopyrightWarning', [ $title, &$copywarnMsg ] );
3591 
3592  $msg = wfMessage( ...$copywarnMsg )->title( $title );
3593  if ( $langcode ) {
3594  $msg->inLanguage( $langcode );
3595  }
3596  return "<div id=\"editpage-copywarn\">\n" .
3597  $msg->$format() . "\n</div>";
3598  }
3599 
3607  public static function getPreviewLimitReport( ParserOutput $output = null ) {
3608  global $wgLang;
3609 
3610  if ( !$output || !$output->getLimitReportData() ) {
3611  return '';
3612  }
3613 
3614  $limitReport = Html::rawElement( 'div', [ 'class' => 'mw-limitReportExplanation' ],
3615  wfMessage( 'limitreport-title' )->parseAsBlock()
3616  );
3617 
3618  // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
3619  $limitReport .= Html::openElement( 'div', [ 'class' => 'preview-limit-report-wrapper' ] );
3620 
3621  $limitReport .= Html::openElement( 'table', [
3622  'class' => 'preview-limit-report wikitable'
3623  ] ) .
3624  Html::openElement( 'tbody' );
3625 
3626  foreach ( $output->getLimitReportData() as $key => $value ) {
3627  if ( Hooks::run( 'ParserLimitReportFormat',
3628  [ $key, &$value, &$limitReport, true, true ]
3629  ) ) {
3630  $keyMsg = wfMessage( $key );
3631  $valueMsg = wfMessage( [ "$key-value-html", "$key-value" ] );
3632  if ( !$valueMsg->exists() ) {
3633  $valueMsg = new RawMessage( '$1' );
3634  }
3635  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3636  $limitReport .= Html::openElement( 'tr' ) .
3637  Html::rawElement( 'th', null, $keyMsg->parse() ) .
3638  Html::rawElement( 'td', null,
3639  $wgLang->formatNum( $valueMsg->params( $value )->parse() )
3640  ) .
3641  Html::closeElement( 'tr' );
3642  }
3643  }
3644  }
3645 
3646  $limitReport .= Html::closeElement( 'tbody' ) .
3647  Html::closeElement( 'table' ) .
3648  Html::closeElement( 'div' );
3649 
3650  return $limitReport;
3651  }
3652 
3653  protected function showStandardInputs( &$tabindex = 2 ) {
3654  $out = $this->context->getOutput();
3655  $out->addHTML( "<div class='editOptions'>\n" );
3656 
3657  if ( $this->section != 'new' ) {
3658  $this->showSummaryInput( false, $this->summary );
3659  $out->addHTML( $this->getSummaryPreview( false, $this->summary ) );
3660  }
3661 
3662  $checkboxes = $this->getCheckboxesWidget(
3663  $tabindex,
3664  [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ]
3665  );
3666  $checkboxesHTML = new OOUI\HorizontalLayout( [ 'items' => $checkboxes ] );
3667 
3668  $out->addHTML( "<div class='editCheckboxes'>" . $checkboxesHTML . "</div>\n" );
3669 
3670  // Show copyright warning.
3671  $out->addWikiText( $this->getCopywarn() );
3672  $out->addHTML( $this->editFormTextAfterWarn );
3673 
3674  $out->addHTML( "<div class='editButtons'>\n" );
3675  $out->addHTML( implode( "\n", $this->getEditButtons( $tabindex ) ) . "\n" );
3676 
3677  $cancel = $this->getCancelLink();
3678 
3679  $message = $this->context->msg( 'edithelppage' )->inContentLanguage()->text();
3680  $edithelpurl = Skin::makeInternalOrExternalUrl( $message );
3681  $edithelp =
3683  $this->context->msg( 'edithelp' )->text(),
3684  [ 'target' => 'helpwindow', 'href' => $edithelpurl ],
3685  [ 'mw-ui-quiet' ]
3686  ) .
3687  $this->context->msg( 'word-separator' )->escaped() .
3688  $this->context->msg( 'newwindow' )->parse();
3689 
3690  $out->addHTML( " <span class='cancelLink'>{$cancel}</span>\n" );
3691  $out->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" );
3692  $out->addHTML( "</div><!-- editButtons -->\n" );
3693 
3694  Hooks::run( 'EditPage::showStandardInputs:options', [ $this, $out, &$tabindex ] );
3695 
3696  $out->addHTML( "</div><!-- editOptions -->\n" );
3697  }
3698 
3703  protected function showConflict() {
3704  $out = $this->context->getOutput();
3705  // Avoid PHP 7.1 warning of passing $this by reference
3706  $editPage = $this;
3707  if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$editPage, &$out ] ) ) {
3708  $this->incrementConflictStats();
3709 
3710  $this->getEditConflictHelper()->showEditFormTextAfterFooters();
3711  }
3712  }
3713 
3714  protected function incrementConflictStats() {
3715  $this->getEditConflictHelper()->incrementConflictStats();
3716  }
3717 
3721  public function getCancelLink() {
3722  $cancelParams = [];
3723  if ( !$this->isConflict && $this->oldid > 0 ) {
3724  $cancelParams['oldid'] = $this->oldid;
3725  } elseif ( $this->getContextTitle()->isRedirect() ) {
3726  $cancelParams['redirect'] = 'no';
3727  }
3728 
3729  return new OOUI\ButtonWidget( [
3730  'id' => 'mw-editform-cancel',
3731  'href' => $this->getContextTitle()->getLinkURL( $cancelParams ),
3732  'label' => new OOUI\HtmlSnippet( $this->context->msg( 'cancel' )->parse() ),
3733  'framed' => false,
3734  'infusable' => true,
3735  'flags' => 'destructive',
3736  ] );
3737  }
3738 
3748  protected function getActionURL( Title $title ) {
3749  return $title->getLocalURL( [ 'action' => $this->action ] );
3750  }
3751 
3759  protected function wasDeletedSinceLastEdit() {
3760  if ( $this->deletedSinceEdit !== null ) {
3761  return $this->deletedSinceEdit;
3762  }
3763 
3764  $this->deletedSinceEdit = false;
3765 
3766  if ( !$this->mTitle->exists() && $this->mTitle->isDeletedQuick() ) {
3767  $this->lastDelete = $this->getLastDelete();
3768  if ( $this->lastDelete ) {
3769  $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
3770  if ( $deleteTime > $this->starttime ) {
3771  $this->deletedSinceEdit = true;
3772  }
3773  }
3774  }
3775 
3776  return $this->deletedSinceEdit;
3777  }
3778 
3784  protected function getLastDelete() {
3785  $dbr = wfGetDB( DB_REPLICA );
3786  $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
3787  $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
3788  $data = $dbr->selectRow(
3789  array_merge( [ 'logging' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ] ),
3790  [
3791  'log_type',
3792  'log_action',
3793  'log_timestamp',
3794  'log_namespace',
3795  'log_title',
3796  'log_params',
3797  'log_deleted',
3798  'user_name'
3799  ] + $commentQuery['fields'] + $actorQuery['fields'],
3800  [
3801  'log_namespace' => $this->mTitle->getNamespace(),
3802  'log_title' => $this->mTitle->getDBkey(),
3803  'log_type' => 'delete',
3804  'log_action' => 'delete',
3805  ],
3806  __METHOD__,
3807  [ 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ],
3808  [
3809  'user' => [ 'JOIN', 'user_id=' . $actorQuery['fields']['log_user'] ],
3810  ] + $commentQuery['joins'] + $actorQuery['joins']
3811  );
3812  // Quick paranoid permission checks...
3813  if ( is_object( $data ) ) {
3814  if ( $data->log_deleted & LogPage::DELETED_USER ) {
3815  $data->user_name = $this->context->msg( 'rev-deleted-user' )->escaped();
3816  }
3817 
3818  if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
3819  $data->log_comment_text = $this->context->msg( 'rev-deleted-comment' )->escaped();
3820  $data->log_comment_data = null;
3821  }
3822  }
3823 
3824  return $data;
3825  }
3826 
3832  public function getPreviewText() {
3833  $out = $this->context->getOutput();
3834  $config = $this->context->getConfig();
3835 
3836  if ( $config->get( 'RawHtml' ) && !$this->mTokenOk ) {
3837  // Could be an offsite preview attempt. This is very unsafe if
3838  // HTML is enabled, as it could be an attack.
3839  $parsedNote = '';
3840  if ( $this->textbox1 !== '' ) {
3841  // Do not put big scary notice, if previewing the empty
3842  // string, which happens when you initially edit
3843  // a category page, due to automatic preview-on-open.
3844  $parsedNote = $out->parse( "<div class='previewnote'>" .
3845  $this->context->msg( 'session_fail_preview_html' )->text() . "</div>",
3846  true, /* interface */true );
3847  }
3848  $this->incrementEditFailureStats( 'session_loss' );
3849  return $parsedNote;
3850  }
3851 
3852  $note = '';
3853 
3854  try {
3855  $content = $this->toEditContent( $this->textbox1 );
3856 
3857  $previewHTML = '';
3858  if ( !Hooks::run(
3859  'AlternateEditPreview',
3860  [ $this, &$content, &$previewHTML, &$this->mParserOutput ] )
3861  ) {
3862  return $previewHTML;
3863  }
3864 
3865  # provide a anchor link to the editform
3866  $continueEditing = '<span class="mw-continue-editing">' .
3867  '[[#' . self::EDITFORM_ID . '|' .
3868  $this->context->getLanguage()->getArrow() . ' ' .
3869  $this->context->msg( 'continue-editing' )->text() . ']]</span>';
3870  if ( $this->mTriedSave && !$this->mTokenOk ) {
3871  if ( $this->mTokenOkExceptSuffix ) {
3872  $note = $this->context->msg( 'token_suffix_mismatch' )->plain();
3873  $this->incrementEditFailureStats( 'bad_token' );
3874  } else {
3875  $note = $this->context->msg( 'session_fail_preview' )->plain();
3876  $this->incrementEditFailureStats( 'session_loss' );
3877  }
3878  } elseif ( $this->incompleteForm ) {
3879  $note = $this->context->msg( 'edit_form_incomplete' )->plain();
3880  if ( $this->mTriedSave ) {
3881  $this->incrementEditFailureStats( 'incomplete_form' );
3882  }
3883  } else {
3884  $note = $this->context->msg( 'previewnote' )->plain() . ' ' . $continueEditing;
3885  }
3886 
3887  # don't parse non-wikitext pages, show message about preview
3888  if ( $this->mTitle->isUserConfigPage() || $this->mTitle->isSiteConfigPage() ) {
3889  if ( $this->mTitle->isUserConfigPage() ) {
3890  $level = 'user';
3891  } elseif ( $this->mTitle->isSiteConfigPage() ) {
3892  $level = 'site';
3893  } else {
3894  $level = false;
3895  }
3896 
3897  if ( $content->getModel() == CONTENT_MODEL_CSS ) {
3898  $format = 'css';
3899  if ( $level === 'user' && !$config->get( 'AllowUserCss' ) ) {
3900  $format = false;
3901  }
3902  } elseif ( $content->getModel() == CONTENT_MODEL_JSON ) {
3903  $format = 'json';
3904  if ( $level === 'user' /* No comparable 'AllowUserJson' */ ) {
3905  $format = false;
3906  }
3907  } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
3908  $format = 'js';
3909  if ( $level === 'user' && !$config->get( 'AllowUserJs' ) ) {
3910  $format = false;
3911  }
3912  } else {
3913  $format = false;
3914  }
3915 
3916  # Used messages to make sure grep find them:
3917  # Messages: usercsspreview, userjsonpreview, userjspreview,
3918  # sitecsspreview, sitejsonpreview, sitejspreview
3919  if ( $level && $format ) {
3920  $note = "<div id='mw-{$level}{$format}preview'>" .
3921  $this->context->msg( "{$level}{$format}preview" )->text() .
3922  ' ' . $continueEditing . "</div>";
3923  }
3924  }
3925 
3926  # If we're adding a comment, we need to show the
3927  # summary as the headline
3928  if ( $this->section === "new" && $this->summary !== "" ) {
3929  $content = $content->addSectionHeader( $this->summary );
3930  }
3931 
3932  $hook_args = [ $this, &$content ];
3933  Hooks::run( 'EditPageGetPreviewContent', $hook_args );
3934 
3935  $parserResult = $this->doPreviewParse( $content );
3936  $parserOutput = $parserResult['parserOutput'];
3937  $previewHTML = $parserResult['html'];
3938  $this->mParserOutput = $parserOutput;
3939  $out->addParserOutputMetadata( $parserOutput );
3940  if ( $out->userCanPreview() ) {
3941  $out->addContentOverride( $this->getTitle(), $content );
3942  }
3943 
3944  if ( count( $parserOutput->getWarnings() ) ) {
3945  $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
3946  }
3947 
3948  } catch ( MWContentSerializationException $ex ) {
3949  $m = $this->context->msg(
3950  'content-failed-to-parse',
3951  $this->contentModel,
3952  $this->contentFormat,
3953  $ex->getMessage()
3954  );
3955  $note .= "\n\n" . $m->parse();
3956  $previewHTML = '';
3957  }
3958 
3959  if ( $this->isConflict ) {
3960  $conflict = '<h2 id="mw-previewconflict">'
3961  . $this->context->msg( 'previewconflict' )->escaped() . "</h2>\n";
3962  } else {
3963  $conflict = '<hr />';
3964  }
3965 
3966  $previewhead = "<div class='previewnote'>\n" .
3967  '<h2 id="mw-previewheader">' . $this->context->msg( 'preview' )->escaped() . "</h2>" .
3968  $out->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
3969 
3970  $pageViewLang = $this->mTitle->getPageViewLanguage();
3971  $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3972  'class' => 'mw-content-' . $pageViewLang->getDir() ];
3973  $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
3974 
3975  return $previewhead . $previewHTML . $this->previewTextAfterContent;
3976  }
3977 
3978  private function incrementEditFailureStats( $failureType ) {
3979  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
3980  $stats->increment( 'edit.failures.' . $failureType );
3981  }
3982 
3987  protected function getPreviewParserOptions() {
3988  $parserOptions = $this->page->makeParserOptions( $this->context );
3989  $parserOptions->setIsPreview( true );
3990  $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
3991  $parserOptions->enableLimitReport();
3992  return $parserOptions;
3993  }
3994 
4004  protected function doPreviewParse( Content $content ) {
4005  $user = $this->context->getUser();
4006  $parserOptions = $this->getPreviewParserOptions();
4007  $pstContent = $content->preSaveTransform( $this->mTitle, $user, $parserOptions );
4008  $scopedCallback = $parserOptions->setupFakeRevision(
4009  $this->mTitle, $pstContent, $user );
4010  $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions );
4011  ScopedCallback::consume( $scopedCallback );
4012  return [
4013  'parserOutput' => $parserOutput,
4014  'html' => $parserOutput->getText( [
4015  'enableSectionEditLinks' => false
4016  ] )
4017  ];
4018  }
4019 
4023  public function getTemplates() {
4024  if ( $this->preview || $this->section != '' ) {
4025  $templates = [];
4026  if ( !isset( $this->mParserOutput ) ) {
4027  return $templates;
4028  }
4029  foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
4030  foreach ( array_keys( $template ) as $dbk ) {
4031  $templates[] = Title::makeTitle( $ns, $dbk );
4032  }
4033  }
4034  return $templates;
4035  } else {
4036  return $this->mTitle->getTemplateLinksFrom();
4037  }
4038  }
4039 
4047  public static function getEditToolbar( $title = null ) {
4049 
4050  $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
4051  $showSignature = true;
4052  if ( $title ) {
4053  $showSignature = MWNamespace::wantSignatures( $title->getNamespace() );
4054  }
4055 
4056  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
4057 
4067  $toolarray = [
4068  [
4069  'id' => 'mw-editbutton-bold',
4070  'open' => '\'\'\'',
4071  'close' => '\'\'\'',
4072  'sample' => wfMessage( 'bold_sample' )->text(),
4073  'tip' => wfMessage( 'bold_tip' )->text(),
4074  ],
4075  [
4076  'id' => 'mw-editbutton-italic',
4077  'open' => '\'\'',
4078  'close' => '\'\'',
4079  'sample' => wfMessage( 'italic_sample' )->text(),
4080  'tip' => wfMessage( 'italic_tip' )->text(),
4081  ],
4082  [
4083  'id' => 'mw-editbutton-link',
4084  'open' => '[[',
4085  'close' => ']]',
4086  'sample' => wfMessage( 'link_sample' )->text(),
4087  'tip' => wfMessage( 'link_tip' )->text(),
4088  ],
4089  [
4090  'id' => 'mw-editbutton-extlink',
4091  'open' => '[',
4092  'close' => ']',
4093  'sample' => wfMessage( 'extlink_sample' )->text(),
4094  'tip' => wfMessage( 'extlink_tip' )->text(),
4095  ],
4096  [
4097  'id' => 'mw-editbutton-headline',
4098  'open' => "\n== ",
4099  'close' => " ==\n",
4100  'sample' => wfMessage( 'headline_sample' )->text(),
4101  'tip' => wfMessage( 'headline_tip' )->text(),
4102  ],
4103  $imagesAvailable ? [
4104  'id' => 'mw-editbutton-image',
4105  'open' => '[[' . $contLang->getNsText( NS_FILE ) . ':',
4106  'close' => ']]',
4107  'sample' => wfMessage( 'image_sample' )->text(),
4108  'tip' => wfMessage( 'image_tip' )->text(),
4109  ] : false,
4110  $imagesAvailable ? [
4111  'id' => 'mw-editbutton-media',
4112  'open' => '[[' . $contLang->getNsText( NS_MEDIA ) . ':',
4113  'close' => ']]',
4114  'sample' => wfMessage( 'media_sample' )->text(),
4115  'tip' => wfMessage( 'media_tip' )->text(),
4116  ] : false,
4117  [
4118  'id' => 'mw-editbutton-nowiki',
4119  'open' => "<nowiki>",
4120  'close' => "</nowiki>",
4121  'sample' => wfMessage( 'nowiki_sample' )->text(),
4122  'tip' => wfMessage( 'nowiki_tip' )->text(),
4123  ],
4124  $showSignature ? [
4125  'id' => 'mw-editbutton-signature',
4126  'open' => wfMessage( 'sig-text', '~~~~' )->inContentLanguage()->text(),
4127  'close' => '',
4128  'sample' => '',
4129  'tip' => wfMessage( 'sig_tip' )->text(),
4130  ] : false,
4131  [
4132  'id' => 'mw-editbutton-hr',
4133  'open' => "\n----\n",
4134  'close' => '',
4135  'sample' => '',
4136  'tip' => wfMessage( 'hr_tip' )->text(),
4137  ]
4138  ];
4139 
4140  $script = '';
4141  foreach ( $toolarray as $tool ) {
4142  if ( !$tool ) {
4143  continue;
4144  }
4145 
4146  $params = [
4147  // Images are defined in ResourceLoaderEditToolbarModule
4148  false,
4149  // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
4150  // Older browsers show a "speedtip" type message only for ALT.
4151  // Ideally these should be different, realistically they
4152  // probably don't need to be.
4153  $tool['tip'],
4154  $tool['open'],
4155  $tool['close'],
4156  $tool['sample'],
4157  $tool['id'],
4158  ];
4159 
4160  $script .= Xml::encodeJsCall(
4161  'mw.toolbar.addButton',
4162  $params,
4164  );
4165  }
4166 
4167  $toolbar = '<div id="toolbar"></div>';
4168 
4169  if ( Hooks::run( 'EditPageBeforeEditToolbar', [ &$toolbar ] ) ) {
4170  // Only add the old toolbar cruft to the page payload if the toolbar has not
4171  // been over-written by a hook caller
4172  $nonce = $wgOut->getCSPNonce();
4173  $wgOut->addScript( Html::inlineScript(
4174  ResourceLoader::makeInlineCodeWithModule( 'mediawiki.toolbar', $script ),
4175  $nonce
4176  ) );
4177  };
4178 
4179  return $toolbar;
4180  }
4181 
4200  public function getCheckboxesDefinition( $checked ) {
4201  $checkboxes = [];
4202 
4203  $user = $this->context->getUser();
4204  // don't show the minor edit checkbox if it's a new page or section
4205  if ( !$this->isNew && $user->isAllowed( 'minoredit' ) ) {
4206  $checkboxes['wpMinoredit'] = [
4207  'id' => 'wpMinoredit',
4208  'label-message' => 'minoredit',
4209  // Uses messages: tooltip-minoredit, accesskey-minoredit
4210  'tooltip' => 'minoredit',
4211  'label-id' => 'mw-editpage-minoredit',
4212  'legacy-name' => 'minor',
4213  'default' => $checked['minor'],
4214  ];
4215  }
4216 
4217  if ( $user->isLoggedIn() ) {
4218  $checkboxes['wpWatchthis'] = [
4219  'id' => 'wpWatchthis',
4220  'label-message' => 'watchthis',
4221  // Uses messages: tooltip-watch, accesskey-watch
4222  'tooltip' => 'watch',
4223  'label-id' => 'mw-editpage-watch',
4224  'legacy-name' => 'watch',
4225  'default' => $checked['watch'],
4226  ];
4227  }
4228 
4229  $editPage = $this;
4230  Hooks::run( 'EditPageGetCheckboxesDefinition', [ $editPage, &$checkboxes ] );
4231 
4232  return $checkboxes;
4233  }
4234 
4245  public function getCheckboxesWidget( &$tabindex, $checked ) {
4246  $checkboxes = [];
4247  $checkboxesDef = $this->getCheckboxesDefinition( $checked );
4248 
4249  foreach ( $checkboxesDef as $name => $options ) {
4250  $legacyName = $options['legacy-name'] ?? $name;
4251 
4252  $title = null;
4253  $accesskey = null;
4254  if ( isset( $options['tooltip'] ) ) {
4255  $accesskey = $this->context->msg( "accesskey-{$options['tooltip']}" )->text();
4256  $title = Linker::titleAttrib( $options['tooltip'] );
4257  }
4258  if ( isset( $options['title-message'] ) ) {
4259  $title = $this->context->msg( $options['title-message'] )->text();
4260  }
4261 
4262  $checkboxes[ $legacyName ] = new OOUI\FieldLayout(
4263  new OOUI\CheckboxInputWidget( [
4264  'tabIndex' => ++$tabindex,
4265  'accessKey' => $accesskey,
4266  'id' => $options['id'] . 'Widget',
4267  'inputId' => $options['id'],
4268  'name' => $name,
4269  'selected' => $options['default'],
4270  'infusable' => true,
4271  ] ),
4272  [
4273  'align' => 'inline',
4274  'label' => new OOUI\HtmlSnippet( $this->context->msg( $options['label-message'] )->parse() ),
4275  'title' => $title,
4276  'id' => $options['label-id'] ?? null,
4277  ]
4278  );
4279  }
4280 
4281  return $checkboxes;
4282  }
4283 
4290  protected function getSubmitButtonLabel() {
4291  $labelAsPublish =
4292  $this->context->getConfig()->get( 'EditSubmitButtonLabelPublish' );
4293 
4294  // Can't use $this->isNew as that's also true if we're adding a new section to an extant page
4295  $newPage = !$this->mTitle->exists();
4296 
4297  if ( $labelAsPublish ) {
4298  $buttonLabelKey = $newPage ? 'publishpage' : 'publishchanges';
4299  } else {
4300  $buttonLabelKey = $newPage ? 'savearticle' : 'savechanges';
4301  }
4302 
4303  return $buttonLabelKey;
4304  }
4305 
4314  public function getEditButtons( &$tabindex ) {
4315  $buttons = [];
4316 
4317  $labelAsPublish =
4318  $this->context->getConfig()->get( 'EditSubmitButtonLabelPublish' );
4319 
4320  $buttonLabel = $this->context->msg( $this->getSubmitButtonLabel() )->text();
4321  $buttonTooltip = $labelAsPublish ? 'publish' : 'save';
4322 
4323  $buttons['save'] = new OOUI\ButtonInputWidget( [
4324  'name' => 'wpSave',
4325  'tabIndex' => ++$tabindex,
4326  'id' => 'wpSaveWidget',
4327  'inputId' => 'wpSave',
4328  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4329  'useInputTag' => true,
4330  'flags' => [ 'progressive', 'primary' ],
4331  'label' => $buttonLabel,
4332  'infusable' => true,
4333  'type' => 'submit',
4334  // Messages used: tooltip-save, tooltip-publish
4335  'title' => Linker::titleAttrib( $buttonTooltip ),
4336  // Messages used: accesskey-save, accesskey-publish
4337  'accessKey' => Linker::accesskey( $buttonTooltip ),
4338  ] );
4339 
4340  $buttons['preview'] = new OOUI\ButtonInputWidget( [
4341  'name' => 'wpPreview',
4342  'tabIndex' => ++$tabindex,
4343  'id' => 'wpPreviewWidget',
4344  'inputId' => 'wpPreview',
4345  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4346  'useInputTag' => true,
4347  'label' => $this->context->msg( 'showpreview' )->text(),
4348  'infusable' => true,
4349  'type' => 'submit',
4350  // Message used: tooltip-preview
4351  'title' => Linker::titleAttrib( 'preview' ),
4352  // Message used: accesskey-preview
4353  'accessKey' => Linker::accesskey( 'preview' ),
4354  ] );
4355 
4356  $buttons['diff'] = new OOUI\ButtonInputWidget( [
4357  'name' => 'wpDiff',
4358  'tabIndex' => ++$tabindex,
4359  'id' => 'wpDiffWidget',
4360  'inputId' => 'wpDiff',
4361  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4362  'useInputTag' => true,
4363  'label' => $this->context->msg( 'showdiff' )->text(),
4364  'infusable' => true,
4365  'type' => 'submit',
4366  // Message used: tooltip-diff
4367  'title' => Linker::titleAttrib( 'diff' ),
4368  // Message used: accesskey-diff
4369  'accessKey' => Linker::accesskey( 'diff' ),
4370  ] );
4371 
4372  // Avoid PHP 7.1 warning of passing $this by reference
4373  $editPage = $this;
4374  Hooks::run( 'EditPageBeforeEditButtons', [ &$editPage, &$buttons, &$tabindex ] );
4375 
4376  return $buttons;
4377  }
4378 
4383  public function noSuchSectionPage() {
4384  $out = $this->context->getOutput();
4385  $out->prepareErrorPage( $this->context->msg( 'nosuchsectiontitle' ) );
4386 
4387  $res = $this->context->msg( 'nosuchsectiontext', $this->section )->parseAsBlock();
4388 
4389  // Avoid PHP 7.1 warning of passing $this by reference
4390  $editPage = $this;
4391  Hooks::run( 'EditPageNoSuchSection', [ &$editPage, &$res ] );
4392  $out->addHTML( $res );
4393 
4394  $out->returnToMain( false, $this->mTitle );
4395  }
4396 
4402  public function spamPageWithContent( $match = false ) {
4403  $this->textbox2 = $this->textbox1;
4404 
4405  if ( is_array( $match ) ) {
4406  $match = $this->context->getLanguage()->listToText( $match );
4407  }
4408  $out = $this->context->getOutput();
4409  $out->prepareErrorPage( $this->context->msg( 'spamprotectiontitle' ) );
4410 
4411  $out->addHTML( '<div id="spamprotected">' );
4412  $out->addWikiMsg( 'spamprotectiontext' );
4413  if ( $match ) {
4414  $out->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
4415  }
4416  $out->addHTML( '</div>' );
4417 
4418  $out->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
4419  $this->showDiff();
4420 
4421  $out->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
4422  $this->showTextbox2();
4423 
4424  $out->addReturnTo( $this->getContextTitle(), [ 'action' => 'edit' ] );
4425  }
4426 
4437  protected function safeUnicodeInput( $request, $field ) {
4438  return rtrim( $request->getText( $field ) );
4439  }
4440 
4450  protected function safeUnicodeOutput( $text ) {
4451  return $text;
4452  }
4453 
4457  protected function addEditNotices() {
4458  $out = $this->context->getOutput();
4459  $editNotices = $this->mTitle->getEditNotices( $this->oldid );
4460  if ( count( $editNotices ) ) {
4461  $out->addHTML( implode( "\n", $editNotices ) );
4462  } else {
4463  $msg = $this->context->msg( 'editnotice-notext' );
4464  if ( !$msg->isDisabled() ) {
4465  $out->addHTML(
4466  '<div class="mw-editnotice-notext">'
4467  . $msg->parseAsBlock()
4468  . '</div>'
4469  );
4470  }
4471  }
4472  }
4473 
4477  protected function addTalkPageText() {
4478  if ( $this->mTitle->isTalkPage() ) {
4479  $this->context->getOutput()->addWikiMsg( 'talkpagetext' );
4480  }
4481  }
4482 
4486  protected function addLongPageWarningHeader() {
4487  if ( $this->contentLength === false ) {
4488  $this->contentLength = strlen( $this->textbox1 );
4489  }
4490 
4491  $out = $this->context->getOutput();
4492  $lang = $this->context->getLanguage();
4493  $maxArticleSize = $this->context->getConfig()->get( 'MaxArticleSize' );
4494  if ( $this->tooBig || $this->contentLength > $maxArticleSize * 1024 ) {
4495  $out->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
4496  [
4497  'longpageerror',
4498  $lang->formatNum( round( $this->contentLength / 1024, 3 ) ),
4499  $lang->formatNum( $maxArticleSize )
4500  ]
4501  );
4502  } else {
4503  if ( !$this->context->msg( 'longpage-hint' )->isDisabled() ) {
4504  $out->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
4505  [
4506  'longpage-hint',
4507  $lang->formatSize( strlen( $this->textbox1 ) ),
4508  strlen( $this->textbox1 )
4509  ]
4510  );
4511  }
4512  }
4513  }
4514 
4518  protected function addPageProtectionWarningHeaders() {
4519  $out = $this->context->getOutput();
4520  if ( $this->mTitle->isProtected( 'edit' ) &&
4521  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [ '' ]
4522  ) {
4523  # Is the title semi-protected?
4524  if ( $this->mTitle->isSemiProtected() ) {
4525  $noticeMsg = 'semiprotectedpagewarning';
4526  } else {
4527  # Then it must be protected based on static groups (regular)
4528  $noticeMsg = 'protectedpagewarning';
4529  }
4530  LogEventsList::showLogExtract( $out, 'protect', $this->mTitle, '',
4531  [ 'lim' => 1, 'msgKey' => [ $noticeMsg ] ] );
4532  }
4533  if ( $this->mTitle->isCascadeProtected() ) {
4534  # Is this page under cascading protection from some source pages?
4535 
4536  list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
4537  $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
4538  $cascadeSourcesCount = count( $cascadeSources );
4539  if ( $cascadeSourcesCount > 0 ) {
4540  # Explain, and list the titles responsible
4541  foreach ( $cascadeSources as $page ) {
4542  $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
4543  }
4544  }
4545  $notice .= '</div>';
4546  $out->wrapWikiMsg( $notice, [ 'cascadeprotectedwarning', $cascadeSourcesCount ] );
4547  }
4548  if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
4549  LogEventsList::showLogExtract( $out, 'protect', $this->mTitle, '',
4550  [ 'lim' => 1,
4551  'showIfEmpty' => false,
4552  'msgKey' => [ 'titleprotectedwarning' ],
4553  'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ] );
4554  }
4555  }
4556 
4561  protected function addExplainConflictHeader( OutputPage $out ) {
4562  $out->addHTML(
4563  $this->getEditConflictHelper()->getExplainHeader()
4564  );
4565  }
4566 
4575  return ( new TextboxBuilder() )->buildTextboxAttribs(
4576  $name, $customAttribs, $user, $this->mTitle
4577  );
4578  }
4579 
4585  protected function addNewLineAtEnd( $wikitext ) {
4586  return ( new TextboxBuilder() )->addNewLineAtEnd( $wikitext );
4587  }
4588 
4599  private function guessSectionName( $text ) {
4600  global $wgParser;
4601 
4602  // Detect Microsoft browsers
4603  $userAgent = $this->context->getRequest()->getHeader( 'User-Agent' );
4604  if ( $userAgent && preg_match( '/MSIE|Edge/', $userAgent ) ) {
4605  // ...and redirect them to legacy encoding, if available
4606  return $wgParser->guessLegacySectionNameFromWikiText( $text );
4607  }
4608  // Meanwhile, real browsers get real anchors
4609  return $wgParser->guessSectionNameFromWikiText( $text );
4610  }
4611 
4618  public function setEditConflictHelperFactory( callable $factory ) {
4619  $this->editConflictHelperFactory = $factory;
4620  $this->editConflictHelper = null;
4621  }
4622 
4626  private function getEditConflictHelper() {
4627  if ( !$this->editConflictHelper ) {
4628  $this->editConflictHelper = call_user_func(
4629  $this->editConflictHelperFactory,
4630  $this->getSubmitButtonLabel()
4631  );
4632  }
4633 
4635  }
4636 
4641  private function newTextConflictHelper( $submitButtonLabel ) {
4642  return new TextConflictHelper(
4643  $this->getTitle(),
4644  $this->getContext()->getOutput(),
4645  MediaWikiServices::getInstance()->getStatsdDataFactory(),
4646  $submitButtonLabel
4647  );
4648  }
4649 }
string $autoSumm
Definition: EditPage.php:299
getContent( $audience=Revision::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
Definition: WikiPage.php:777
Helps EditPage build textboxes.
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:125
displayPermissionsError(array $permErrors)
Display a permissions error page, like OutputPage::showPermissionsErrorPage(), but with the following...
Definition: EditPage.php:750
safeUnicodeInput( $request, $field)
Filter an input field through a Unicode de-armoring process if it came from an old browser with known...
Definition: EditPage.php:4437
$wgForeignFileRepos
Enable the use of files from one or more other wikis.
incrementConflictStats()
Definition: EditPage.php:3714
const FOR_THIS_USER
Definition: Revision.php:56
bool $nosummary
If true, hide the summary field.
Definition: EditPage.php:350
getPreloadedContent( $preload, $params=[])
Get the contents to be preloaded into the box, either set by an earlier setPreloadText() or by loadin...
Definition: EditPage.php:1425
$editFormTextBottom
Definition: EditPage.php:423
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking, formatting, etc.
spamPageWithContent( $match=false)
Show "your edit contains spam" page with your diff and text.
Definition: EditPage.php:4402
const AS_READ_ONLY_PAGE_ANON
Status: this anonymous user is not allowed to edit this page.
Definition: EditPage.php:83
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
bool $missingSummary
Definition: EditPage.php:281
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:798
bool $bot
Definition: EditPage.php:403
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1636
string $textbox2
Definition: EditPage.php:342
either a plain
Definition: hooks.txt:2102
bool $mTokenOk
Definition: EditPage.php:263
$editFormTextAfterContent
Definition: EditPage.php:424
showContentForm()
Subpage overridable method for printing the form for page content editing By default this simply outp...
Definition: EditPage.php:3335
bool $allowBlankSummary
Definition: EditPage.php:284
getPreviewText()
Get the rendered text for previewing.
Definition: EditPage.php:3832
bool $isConflict
Whether an edit conflict needs to be resolved.
Definition: EditPage.php:242
int $oldid
Revision ID the edit is based on, or 0 if it&#39;s the current revision.
Definition: EditPage.php:387
getContentObject( $def_content=null)
Definition: EditPage.php:1173
handleStatus(Status $status, $resultDetails)
Handle status, such as after attempt save.
Definition: EditPage.php:1565
string $summary
Definition: EditPage.php:345
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
setHeaders()
Definition: EditPage.php:2438
WikiPage $page
Definition: EditPage.php:224
per default it will return the text for text based content
static matchSpamRegexInternal( $text, $regexes)
Definition: EditPage.php:2428
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:962
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
const AS_ARTICLE_WAS_DELETED
Status: article was deleted while editing and param wpRecreate == false or form was not posted...
Definition: EditPage.php:104
Handles formatting for the "templates used on this page" lists.
const AS_HOOK_ERROR
Status: Article update aborted by a hook function.
Definition: EditPage.php:63
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2218
showTextbox2()
Definition: EditPage.php:3375
bool $tooBig
Definition: EditPage.php:275
$wgParser
Definition: Setup.php:903
showHeaderCopyrightWarning()
Show the header copyright warning.
Definition: EditPage.php:3524
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:191
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
database rows
Definition: globals.txt:10
if(!isset( $args[0])) $lang
static getCopyrightWarning( $title, $format='plain', $langcode=null)
Get the copyright warning, by default returns wikitext.
Definition: EditPage.php:3579
static newFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target&#39;s type, get an existing Block object if possible.
Definition: Block.php:1173
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1300
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing &#39;/&#39;...
Definition: Html.php:252
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:210
showTosSummary()
Give a chance for site and per-namespace customizations of terms of service summary link that might e...
Definition: EditPage.php:3540
Special handling for category description pages, showing pages, subcategories and file that belong to...
The edit page/HTML interface (split from Article) The actual database and text munging is still in Ar...
Definition: EditPage.php:44
Title $mTitle
Definition: EditPage.php:230
const AS_SUMMARY_NEEDED
Status: no edit summary given and the user has forceeditsummary set and the user is not editing in hi...
Definition: EditPage.php:126
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
const AS_HOOK_ERROR_EXPECTED
Status: A hook function returned an error.
Definition: EditPage.php:68
getLocalURL( $query='', $query2=false)
Get a URL with no fragment or server name (relative URL) from a Title object.
Definition: Title.php:1957
static accesskey( $name)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition: Linker.php:2013
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:291
getSummaryInputAttributes(array $inputAttrs=null)
Helper function for summary input functions, which returns the necessary attributes for the input...
Definition: EditPage.php:3190
getEditButtons(&$tabindex)
Returns an array of html code of the following buttons: save, diff and preview.
Definition: EditPage.php:4314
static newFromUserAndLang(User $user, Language $lang)
Get a ParserOptions object from a given user and language.
also included in $newHeader if any indicating whether we should show just the diff
Definition: hooks.txt:1303
string $editintro
Definition: EditPage.php:397
Class for viewing MediaWiki article and history.
Definition: Article.php:35
null for the local wiki Added in
Definition: hooks.txt:1636
static getSkinNames()
Fetch the set of available skins.
Definition: Skin.php:57
bool $allowBlankArticle
Definition: EditPage.php:290
toEditText( $content)
Gets an editable textual representation of $content.
Definition: EditPage.php:2659
IContextSource $context
Definition: EditPage.php:448
$value
Article $mArticle
Definition: EditPage.php:222
null string $contentFormat
Definition: EditPage.php:409
const AS_BLOCKED_PAGE_FOR_USER
Status: User is blocked from editing this page.
Definition: EditPage.php:73
getWikiText( $shortContext=false, $longContext=false, $lang=null)
Get the error list as a wikitext formatted list.
Definition: Status.php:177
static linkButton( $contents, array $attrs, array $modifiers=[])
Returns an HTML link element in a string styled as a button (when $wgUseMediaWikiUIEverywhere is enab...
Definition: Html.php:166
bool $blankArticle
Definition: EditPage.php:287
buildTextboxAttribs( $name, array $customAttribs, User $user)
Definition: EditPage.php:4574
Helper for displaying edit conflicts in text content models to users.
isGood()
Returns whether the operation completed and didn&#39;t have any error or warnings.
const AS_NO_CREATE_PERMISSION
Status: user tried to create this page, but is not allowed to do that ( Title->userCan(&#39;create&#39;) == f...
Definition: EditPage.php:110
The First
Definition: primes.txt:1
getEditConflictMainTextBox(array $customAttribs=[])
HTML to build the textbox1 on edit conflicts.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
bool $missingComment
Definition: EditPage.php:278
const EDIT_MINOR
Definition: Defines.php:154
getEditPermissionErrors( $rigor='secure')
Definition: EditPage.php:706
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
const POST_EDIT_COOKIE_DURATION
Duration of PostEdit cookie, in seconds.
Definition: EditPage.php:216
const EDIT_UPDATE
Definition: Defines.php:153
static userWasLastToEdit( $db, $pageId, $userId, $since)
Check if no edits were made by other users since the time a user started editing the page...
Definition: Revision.php:1249
this hook is for auditing only $response
Definition: hooks.txt:798
showFormBeforeText()
Definition: EditPage.php:3298
null means default & $customAttribs
Definition: hooks.txt:2041
internalAttemptSave(&$result, $bot=false)
Attempt submission (no UI)
Definition: EditPage.php:1814
bool stdClass $lastDelete
Definition: EditPage.php:260
const AS_UNICODE_NOT_SUPPORTED
Status: edit rejected because browser doesn&#39;t support Unicode.
Definition: EditPage.php:190
target page
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
static inlineScript( $contents, $nonce=null)
Output a "<script>" tag with the given contents.
Definition: Html.php:564
addPageProtectionWarningHeaders()
Definition: EditPage.php:4518
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
getCheckboxesWidget(&$tabindex, $checked)
Returns an array of checkboxes for the edit form, including &#39;minor&#39; and &#39;watch&#39; checkboxes and any ot...
Definition: EditPage.php:4245
const CONTENT_MODEL_JSON
Definition: Defines.php:239
edit()
This is the function that gets called for "action=edit".
Definition: EditPage.php:579
getContextTitle()
Get the context title object.
Definition: EditPage.php:528
mergeChangesIntoContent(&$editContent)
Attempts to do 3-way merge of edit content with a base revision and current content, in case of edit conflict, in whichever way appropriate for the content type.
Definition: EditPage.php:2340
const DB_MASTER
Definition: defines.php:26
displayPreviewArea( $previewOutput, $isOnTop=false)
Definition: EditPage.php:3393
addEditNotices()
Definition: EditPage.php:4457
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition: Linker.php:1913
isRedirect()
Tests if the article content represents a redirect.
Definition: WikiPage.php:603
null Title $mContextTitle
Definition: EditPage.php:233
static textarea( $name, $value='', array $attribs=[])
Convenience function to produce a <textarea> element.
Definition: Html.php:807
getEditFormHtmlBeforeContent()
Content to go in the edit form before textbox1.
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place $output
Definition: hooks.txt:2275
int $editRevId
Revision ID of the latest revision of the page when editing was initiated on the client.
Definition: EditPage.php:369
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as $wgLang
Definition: design.txt:56
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED since 1.16! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:2039
const AS_CONTENT_TOO_BIG
Status: Content too big (> $wgMaxArticleSize)
Definition: EditPage.php:78
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping $template
Definition: hooks.txt:798
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:46
$wgEnableUploads
Allow users to upload files.
getContext()
Gets the context this Article is executed in.
Definition: Article.php:2028
addExplainConflictHeader(OutputPage $out)
Definition: EditPage.php:4561
attemptSave(&$resultDetails=false)
Attempt submission.
Definition: EditPage.php:1531
showIntro()
Show all applicable editing introductions.
Definition: EditPage.php:2508
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition: hooks.txt:2041
getArticle()
Definition: EditPage.php:491
$wgSpamRegex
Edits matching these regular expressions in body text will be recognised as spam and rejected automat...
bool $watchthis
Definition: EditPage.php:331
$previewTextAfterContent
Definition: EditPage.php:425
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
const DELETED_COMMENT
Definition: LogPage.php:35
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
getParentRevId()
Get the edit&#39;s parent revision ID.
Definition: EditPage.php:1348
addLongPageWarningHeader()
Definition: EditPage.php:4486
getTemplates()
Definition: EditPage.php:4023
bool $save
Definition: EditPage.php:319
wfReadOnly()
Check whether the wiki is in read-only mode.
static makeInlineCodeWithModule( $modules, $script)
Wraps JavaScript code to run after a required module.
static newMigration()
Static constructor.
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
setContextTitle( $title)
Set the context Title object.
Definition: EditPage.php:516
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:110
TextConflictHelper null $editConflictHelper
Definition: EditPage.php:470
toEditContent( $text)
Turns the given text into a Content object by unserializing it.
Definition: EditPage.php:2687
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
const AS_SPAM_ERROR
Status: summary contained spam according to one of the regexes in $wgSummarySpamRegex.
Definition: EditPage.php:146
const EDIT_FORCE_BOT
Definition: Defines.php:156
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:679
An error page which can definitely be safely rendered using the OutputPage.
static titleAttrib( $name, $options=null, array $msgParams=[])
Given the id of an interface element, constructs the appropriate title attribute from the system mess...
Definition: Linker.php:1965
static getPreviewLimitReport(ParserOutput $output=null)
Get the Limit report for page previews.
Definition: EditPage.php:3607
callable $editConflictHelperFactory
Factory function to create an edit conflict helper.
Definition: EditPage.php:465
getLastDelete()
Get the last log record of this page being deleted, if ever.
Definition: EditPage.php:3784
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add etc
Definition: design.txt:12
in this case you re responsible for computing and outputting the entire conflict i the difference between revisions and your text headers and sections and Diff & $tabindex
Definition: hooks.txt:1470
getActionURL(Title $title)
Returns the URL to use in the form&#39;s action attribute.
Definition: EditPage.php:3748
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:89
$editFormTextAfterTools
Definition: EditPage.php:422
const AS_CANNOT_USE_CUSTOM_MODEL
Status: when changing the content model is disallowed due to $wgContentHandlerUseDB being false...
Definition: EditPage.php:185
wfFindFile( $title, $options=[])
Find a file.
const MIGRATION_OLD
Definition: Defines.php:315
$editFormTextAfterWarn
Definition: EditPage.php:421
const NS_MEDIA
Definition: Defines.php:52
$res
Definition: database.txt:21
bool $recreate
Definition: EditPage.php:334
setPreloadedContent(Content $content)
Use this method before edit() to preload some content into the edit box.
Definition: EditPage.php:1410
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
const AS_SUCCESS_UPDATE
Status: Article successfully updated.
Definition: EditPage.php:53
const AS_READ_ONLY_PAGE
Status: wiki is in readonly mode (wfReadOnly() == true)
Definition: EditPage.php:93
static extractSectionTitle( $text)
Extract the section title from current section text, if any.
Definition: EditPage.php:3012
bool $isOldRev
Whether an old revision is edited.
Definition: EditPage.php:453
showHeader()
Definition: EditPage.php:3022
const EDIT_AUTOSUMMARY
Definition: Defines.php:158
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
const UNICODE_CHECK
Used for Unicode support checks.
Definition: EditPage.php:48
const AS_NO_CHANGE_CONTENT_MODEL
Status: user tried to modify the content model, but is not allowed to do that ( User::isAllowed(&#39;edit...
Definition: EditPage.php:162
addContentModelChangeLogEntry(User $user, $oldModel, $newModel, $reason)
Definition: EditPage.php:2295
getTitle()
Get the title object of the article.
Definition: Article.php:181
const IGNORE_USER_RIGHTS
Definition: User.php:83
$params
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:2041
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:132
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:310
string $edittime
Timestamp of the latest revision of the page when editing was initiated on the client.
Definition: EditPage.php:356
showSummaryInput( $isSubjectPreview, $summary="")
Definition: EditPage.php:3248
showEditForm( $formCallback=null)
Send the edit form and related headers to OutputPage.
Definition: EditPage.php:2710
static loadFromTitle( $db, $title, $id=0)
Load either the current, or a specified, revision that&#39;s attached to a given page.
Definition: Revision.php:273
initialiseForm()
Initialise form fields in the object Called on the first invocation, e.g.
Definition: EditPage.php:1134
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition: hooks.txt:2041
wasDeletedSinceLastEdit()
Check if a page was deleted while the user was editing it, before submit.
Definition: EditPage.php:3759
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a save
Definition: deferred.txt:4
isSectionEditSupported()
Returns whether section editing is supported for the current page.
Definition: EditPage.php:895
getEditFormHtmlAfterContent()
Content to go in the edit form after textbox1.
bool $firsttime
True the first time the edit form is rendered, false after re-rendering with diff, save prompts, etc.
Definition: EditPage.php:257
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:949
showFormAfterText()
Definition: EditPage.php:3307
showDiff()
Get a diff between the current contents of the edit box and the version of the page we&#39;re editing fro...
Definition: EditPage.php:3462
bool $isNew
New page or new section.
Definition: EditPage.php:245
$wgRightsText
If either $wgRightsUrl or $wgRightsPage is specified then this variable gives the text for the link...
previewOnOpen()
Should we show a preview when the edit form is first shown?
Definition: EditPage.php:833
const EDITFORM_ID
HTML id and name for the beginning of the edit form.
Definition: EditPage.php:195
const NS_FILE
Definition: Defines.php:70
getCopywarn()
Get the copyright warning.
Definition: EditPage.php:3567
bool $allowSelfRedirect
Definition: EditPage.php:296
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don&#39;t need a full Title object...
Definition: SpecialPage.php:82
const TYPE_AUTO
Definition: Block.php:86
const AS_SUCCESS_NEW_ARTICLE
Status: Article successfully created.
Definition: EditPage.php:58
Show an error when the user tries to do something whilst blocked.
safeUnicodeOutput( $text)
Filter an output field through a Unicode armoring process if it is going to an old browser with known...
Definition: EditPage.php:4450
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1813
isOK()
Returns whether the operation completed.
const AS_IMAGE_REDIRECT_LOGGED
Status: logged in user is not allowed to upload (User::isAllowed(&#39;upload&#39;) == false) ...
Definition: EditPage.php:156
const RAW
Definition: Revision.php:57
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
getPreviewParserOptions()
Get parser options for a preview.
Definition: EditPage.php:3987
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:798
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
getCancelLink()
Definition: EditPage.php:3721
bool int $contentLength
Definition: EditPage.php:438
const NS_MEDIAWIKI
Definition: Defines.php:72
showTextbox1( $customAttribs=null, $textoverride=null)
Method to output wpTextbox1 The $textoverride method can be used by subclasses overriding showContent...
Definition: EditPage.php:3347
incrementEditFailureStats( $failureType)
Definition: EditPage.php:3978
const AS_END
Status: WikiPage::doEdit() was unsuccessful.
Definition: EditPage.php:141
getSummaryPreview( $isSubjectPreview, $summary="")
Definition: EditPage.php:3276
makeTemplatesOnThisPageList(array $templates)
Wrapper around TemplatesOnThisPageFormatter to make a "templates on this page" list.
Definition: EditPage.php:2988
showPreview( $text)
Append preview output to OutputPage.
Definition: EditPage.php:3441
string $textbox1
Page content input field.
Definition: EditPage.php:339
CONTENT_MODEL_JAVASCRIPT
Allow users to upload files.
const DELETED_USER
Definition: LogPage.php:36
const DELETED_TEXT
Definition: Revision.php:47
importFormData(&$request)
This function collects the form data and uses it to populate various member variables.
Definition: EditPage.php:905
noSuchSectionPage()
Creates a basic error page which informs the user that they have attempted to edit a nonexistent sect...
Definition: EditPage.php:4383
$wgSummarySpamRegex
Same as the above except for edit summaries.
displayViewSourcePage(Content $content, $errorMessage='')
Display a read-only View Source page.
Definition: EditPage.php:780
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:538
static getEditToolbar( $title=null)
Shows a bulletin board style toolbar for common editing functions.
Definition: EditPage.php:4047
ParserOutput $mParserOutput
Definition: EditPage.php:305
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
bool $mShowSummaryField
Definition: EditPage.php:314
string $sectiontitle
Definition: EditPage.php:375
string null $unicodeCheck
What the user submitted in the &#39;wpUnicodeCheck&#39; field.
Definition: EditPage.php:458
bool $minoredit
Definition: EditPage.php:328
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:798
static inDebugMode()
Determine whether debug mode was requested Order of priority is 1) request param, 2) cookie...
bool $enableApiEditOverride
Set in ApiEditPage, based on ContentHandler::allowsDirectApiEditing.
Definition: EditPage.php:443
static getCanonicalName( $index)
Returns the canonical (English) name for a given index.
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:119
int $parentRevId
Revision ID the edit is based on, adjusted when an edit conflict is resolved.
Definition: EditPage.php:394
doPreviewParse(Content $content)
Parse the page for a preview.
Definition: EditPage.php:4004
static matchSpamRegex( $text)
Check given input text against $wgSpamRegex, and return the text of the first match.
Definition: EditPage.php:2403
string $action
Definition: EditPage.php:236
newTextConflictHelper( $submitButtonLabel)
Definition: EditPage.php:4641
bool $deletedSinceEdit
Definition: EditPage.php:248
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
static getStore()
showCustomIntro()
Attempt to show a custom editing introduction, if supplied.
Definition: EditPage.php:2626
static matchSummarySpamRegex( $text)
Check given input text against $wgSummarySpamRegex, and return the text of the first match...
Definition: EditPage.php:2417
importContentFormData(&$request)
Subpage overridable method for extracting the page content data from the posted form to be placed in ...
Definition: EditPage.php:1125
const EDIT_NEW
Definition: Defines.php:152
Revision bool null $mBaseRevision
A revision object corresponding to $this->editRevId.
Definition: EditPage.php:311
newSectionSummary(&$sectionanchor=null)
Return the summary to be used for a new section.
Definition: EditPage.php:1766
const AS_RATE_LIMITED
Status: rate limiter for action &#39;edit&#39; was tripped.
Definition: EditPage.php:98
getBaseRevision()
Returns the revision that was current at the time editing was initiated on the client, even if the edit was based on an old revision.
Definition: EditPage.php:2386
static hasDifferencesOutsideMainSlot(Revision $a, Revision $b)
Helper method for checking whether two revisions have differences that go beyond the main slot...
Definition: WikiPage.php:1468
addNewLineAtEnd( $wikitext)
Definition: EditPage.php:4585
Variant of the Message class.
Definition: RawMessage.php:34
runPostMergeFilters(Content $content, Status $status, User $user)
Run hooks that can filter edits just before they get saved.
Definition: EditPage.php:1699
const AS_MAX_ARTICLE_SIZE_EXCEEDED
Status: article is too big (> $wgMaxArticleSize), after merging in the new section.
Definition: EditPage.php:136
static newFromUser( $user)
Get a ParserOptions object from a given user.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
incrementResolvedConflicts()
Log when a page was successfully saved after the edit conflict view.
Definition: EditPage.php:1548
getCurrentContent()
Get the current content of the page.
Definition: EditPage.php:1364
updateWatchlist()
Register the change of watch status.
Definition: EditPage.php:2312
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:791
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
string $hookError
Definition: EditPage.php:302
showEditTools()
Inserts optional text shown below edit and upload forms.
Definition: EditPage.php:3555
bool $preview
Definition: EditPage.php:322
const AS_IMAGE_REDIRECT_ANON
Status: anonymous user is not allowed to upload (User::isAllowed(&#39;upload&#39;) == false) ...
Definition: EditPage.php:151
getCheckboxesDefinition( $checked)
Return an array of checkbox definitions.
Definition: EditPage.php:4200
showStandardInputs(&$tabindex=2)
Definition: EditPage.php:3653
preSaveTransform(Title $title, User $user, ParserOptions $parserOptions)
Returns a Content object with pre-save transformations applied (or this object if no transformations ...
const AS_TEXTBOX_EMPTY
Status: user tried to create a new section without content.
Definition: EditPage.php:131
Show an error when a user tries to do something they do not have the necessary permissions for...
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
setPostEditCookie( $statusValue)
Sets post-edit cookie indicating the user just saved a particular revision.
Definition: EditPage.php:1510
static getRestrictionLevels( $index, User $user=null)
Determine which restriction levels it makes sense to use in a namespace, optionally filtered by a use...
getRedirectTarget()
If this page is a redirect, get its target.
Definition: WikiPage.php:949
bool $mTriedSave
Definition: EditPage.php:269
const CONTENT_MODEL_CSS
Definition: Defines.php:237
static commentBlock( $comment, $title=null, $local=false, $wikiId=null)
Wrap a comment in standard punctuation and formatting if it&#39;s non-empty, otherwise return empty strin...
Definition: Linker.php:1439
$mPreloadContent
Definition: EditPage.php:426
getContext()
Definition: EditPage.php:499
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
showConflict()
Show an edit conflict.
Definition: EditPage.php:3703
addTalkPageText()
Definition: EditPage.php:4477
bool $hasPresetSummary
Has a summary been preset using GET parameter &summary= ?
Definition: EditPage.php:308
getEditConflictHelper()
Definition: EditPage.php:4626
MediaWiki Logger LoggerFactory implements a PSR [0] compatible message logging system Named Psr Log LoggerInterface instances can be obtained from the MediaWiki Logger LoggerFactory::getInstance() static method. MediaWiki\Logger\LoggerFactory expects a class implementing the MediaWiki\Logger\Spi interface to act as a factory for new Psr\Log\LoggerInterface instances. The "Spi" in MediaWiki\Logger\Spi stands for "service provider interface". An SPI is an API intended to be implemented or extended by a third party. This software design pattern is intended to enable framework extension and replaceable components. It is specifically used in the MediaWiki\Logger\LoggerFactory service to allow alternate PSR-3 logging implementations to be easily integrated with MediaWiki. The service provider interface allows the backend logging library to be implemented in multiple ways. The $wgMWLoggerDefaultSpi global provides the classname of the default MediaWiki\Logger\Spi implementation to be loaded at runtime. This can either be the name of a class implementing the MediaWiki\Logger\Spi with a zero argument const ructor or a callable that will return an MediaWiki\Logger\Spi instance. Alternately the MediaWiki\Logger\LoggerFactory MediaWiki Logger LoggerFactory
Definition: logger.txt:5
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition: EditPage.php:201
bool $diff
Definition: EditPage.php:325
const AS_BLANK_ARTICLE
Status: user tried to create a blank page and wpIgnoreBlankArticle == false.
Definition: EditPage.php:115
addHTML( $text)
Append $text to the body HTML.
setEditConflictHelperFactory(callable $factory)
Set a factory function to create an EditConflictHelper.
Definition: EditPage.php:4618
string $starttime
Timestamp from the first time the edit form was rendered.
Definition: EditPage.php:380
string $formtype
Definition: EditPage.php:251
string $section
Definition: EditPage.php:372
getSummaryInputWidget( $summary="", $labelText=null, $inputAttrs=null)
Builds a standard summary input with a label.
Definition: EditPage.php:3215
$wgOut
Definition: Setup.php:897
const DB_REPLICA
Definition: defines.php:25
return true to allow those checks to and false if checking is done remove or add to the links of a group of changes in EnhancedChangesList Hook subscribers can return false to omit this line from recentchanges use this to change the tables headers change it to an object instance and return false override the list derivative used the name of the old file & $article
Definition: hooks.txt:1524
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition: Skin.php:1195
formatStatusErrors(Status $status)
Wrap status errors in an errorbox for increased visibility.
Definition: EditPage.php:1746
static canAddTagsAccompanyingChange(array $tags, User $user=null)
Is it OK to allow the user to apply all the specified tags at the same time as they edit/make the cha...
Definition: ChangeTags.php:523
getTitle()
Definition: EditPage.php:507
bool $mTokenOkExceptSuffix
Definition: EditPage.php:266
if(! $wgRequest->checkUrlExtension()) if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') $wgTitle
Definition: api.php:57
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition: Xml.php:421
wfArrayDiff2( $a, $b)
Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
const AS_CONFLICT_DETECTED
Status: (non-resolvable) edit conflict.
Definition: EditPage.php:120
isSupportedContentModel( $modelId)
Returns if the given content model is editable.
Definition: EditPage.php:545
setApiEditOverride( $enableOverride)
Allow editing of content that supports API direct editing, but not general direct editing...
Definition: EditPage.php:556
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:798
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:588
$suppressIntro
Definition: EditPage.php:432
static wantSignatures( $index)
Might pages in this namespace require the use of the Signature button on the edit toolbar...
getOriginalContent(User $user)
Get the content of the wanted revision, without section extraction.
Definition: EditPage.php:1323
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:114
bool $selfRedirect
Definition: EditPage.php:293
bool $incompleteForm
Definition: EditPage.php:272
bool $edit
Definition: EditPage.php:435
const AS_READ_ONLY_PAGE_LOGGED
Status: this logged in user is not allowed to edit this page.
Definition: EditPage.php:88
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
fatal( $message)
Add an error and set OK to false, indicating that the operation as a whole was fatal.
$content
Definition: pageupdater.txt:72
const AS_PARSE_ERROR
Status: can&#39;t parse content.
Definition: EditPage.php:179
const NS_USER_TALK
Definition: Defines.php:67
getSubmitButtonLabel()
Get the message key of the label for the button to save the page.
Definition: EditPage.php:4290
showTextbox( $text, $name, $customAttribs=[])
Definition: EditPage.php:3379
const AS_SELF_REDIRECT
Status: user tried to create self-redirect (redirect to the same article) and wpIgnoreSelfRedirect ==...
Definition: EditPage.php:168
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition: Title.php:1015
$editFormTextBeforeContent
Definition: EditPage.php:420
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2681
null array $changeTags
Definition: EditPage.php:412
setTextboxes( $yourtext, $storedversion)
isWrongCaseUserConfigPage()
Checks whether the user entered a skin name in uppercase, e.g.
Definition: EditPage.php:874
Show an error when the user hits a rate limit.
string $contentModel
Definition: EditPage.php:406
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:244
Exception representing a failure to serialize or unserialize a content object.
tokenOk(&$request)
Make sure the form isn&#39;t faking a user&#39;s credentials.
Definition: EditPage.php:1488
string $editFormPageTop
Before even the preview.
Definition: EditPage.php:418
const AS_CHANGE_TAG_ERROR
Status: an error relating to change tagging.
Definition: EditPage.php:174
guessSectionName( $text)
Turns section name wikitext into anchors for use in HTTP redirects.
Definition: EditPage.php:4599
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
serialize( $format=null)
Convenience method for serializing this Content object.
__construct(Article $article)
Definition: EditPage.php:475
$editFormTextTop
Definition: EditPage.php:419
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
$matches
null $scrolltop
Definition: EditPage.php:400
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:273