MediaWiki  1.33.0
EditPage.php
Go to the documentation of this file.
1 <?php
27 use Wikimedia\ScopedCallback;
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 
480  // Make sure the local context is in sync with other member variables.
481  // Particularly make sure everything is using the same WikiPage instance.
482  // This should probably be the case in Article as well, but it's
483  // particularly important for EditPage, to make use of the in-place caching
484  // facility in WikiPage::prepareContentForEdit.
485  $this->context = new DerivativeContext( $article->getContext() );
486  $this->context->setWikiPage( $this->page );
487  $this->context->setTitle( $this->mTitle );
488 
489  $this->contentModel = $this->mTitle->getContentModel();
490 
491  $handler = ContentHandler::getForModelID( $this->contentModel );
492  $this->contentFormat = $handler->getDefaultFormat();
493  $this->editConflictHelperFactory = [ $this, 'newTextConflictHelper' ];
494  }
495 
499  public function getArticle() {
500  return $this->mArticle;
501  }
502 
507  public function getContext() {
508  return $this->context;
509  }
510 
515  public function getTitle() {
516  return $this->mTitle;
517  }
518 
524  public function setContextTitle( $title ) {
525  $this->mContextTitle = $title;
526  }
527 
536  public function getContextTitle() {
537  if ( is_null( $this->mContextTitle ) ) {
538  wfDeprecated( __METHOD__ . ' called with no title set', '1.32' );
539  global $wgTitle;
540  return $wgTitle;
541  } else {
542  return $this->mContextTitle;
543  }
544  }
545 
553  public function isSupportedContentModel( $modelId ) {
554  return $this->enableApiEditOverride === true ||
555  ContentHandler::getForModelID( $modelId )->supportsDirectEditing();
556  }
557 
564  public function setApiEditOverride( $enableOverride ) {
565  $this->enableApiEditOverride = $enableOverride;
566  }
567 
571  public function submit() {
572  wfDeprecated( __METHOD__, '1.29' );
573  $this->edit();
574  }
575 
587  public function edit() {
588  // Allow extensions to modify/prevent this form or submission
589  if ( !Hooks::run( 'AlternateEdit', [ $this ] ) ) {
590  return;
591  }
592 
593  wfDebug( __METHOD__ . ": enter\n" );
594 
595  $request = $this->context->getRequest();
596  // If they used redlink=1 and the page exists, redirect to the main article
597  if ( $request->getBool( 'redlink' ) && $this->mTitle->exists() ) {
598  $this->context->getOutput()->redirect( $this->mTitle->getFullURL() );
599  return;
600  }
601 
602  $this->importFormData( $request );
603  $this->firsttime = false;
604 
605  if ( wfReadOnly() && $this->save ) {
606  // Force preview
607  $this->save = false;
608  $this->preview = true;
609  }
610 
611  if ( $this->save ) {
612  $this->formtype = 'save';
613  } elseif ( $this->preview ) {
614  $this->formtype = 'preview';
615  } elseif ( $this->diff ) {
616  $this->formtype = 'diff';
617  } else { # First time through
618  $this->firsttime = true;
619  if ( $this->previewOnOpen() ) {
620  $this->formtype = 'preview';
621  } else {
622  $this->formtype = 'initial';
623  }
624  }
625 
626  $permErrors = $this->getEditPermissionErrors( $this->save ? 'secure' : 'full' );
627  if ( $permErrors ) {
628  wfDebug( __METHOD__ . ": User can't edit\n" );
629 
630  if ( $this->context->getUser()->getBlock() ) {
631  // track block with a cookie if it doesn't exists already
632  $this->context->getUser()->trackBlockWithCookie();
633 
634  // Auto-block user's IP if the account was "hard" blocked
635  if ( !wfReadOnly() ) {
637  $this->context->getUser()->spreadAnyEditBlock();
638  } );
639  }
640  }
641  $this->displayPermissionsError( $permErrors );
642 
643  return;
644  }
645 
646  $revision = $this->mArticle->getRevisionFetched();
647  // Disallow editing revisions with content models different from the current one
648  // Undo edits being an exception in order to allow reverting content model changes.
649  if ( $revision
650  && $revision->getContentModel() !== $this->contentModel
651  ) {
652  $prevRev = null;
653  if ( $this->undidRev ) {
654  $undidRevObj = Revision::newFromId( $this->undidRev );
655  $prevRev = $undidRevObj ? $undidRevObj->getPrevious() : null;
656  }
657  if ( !$this->undidRev
658  || !$prevRev
659  || $prevRev->getContentModel() !== $this->contentModel
660  ) {
661  $this->displayViewSourcePage(
662  $this->getContentObject(),
663  $this->context->msg(
664  'contentmodelediterror',
665  $revision->getContentModel(),
667  )->plain()
668  );
669  return;
670  }
671  }
672 
673  $this->isConflict = false;
674 
675  # Show applicable editing introductions
676  if ( $this->formtype == 'initial' || $this->firsttime ) {
677  $this->showIntro();
678  }
679 
680  # Attempt submission here. This will check for edit conflicts,
681  # and redundantly check for locked database, blocked IPs, etc.
682  # that edit() already checked just in case someone tries to sneak
683  # in the back door with a hand-edited submission URL.
684 
685  if ( $this->formtype == 'save' ) {
686  $resultDetails = null;
687  $status = $this->attemptSave( $resultDetails );
688  if ( !$this->handleStatus( $status, $resultDetails ) ) {
689  return;
690  }
691  }
692 
693  # First time through: get contents, set time for conflict
694  # checking, etc.
695  if ( $this->formtype == 'initial' || $this->firsttime ) {
696  if ( $this->initialiseForm() === false ) {
697  $out = $this->context->getOutput();
698  if ( $out->getRedirect() === '' ) { // mcrundo hack redirects, don't override it
699  $this->noSuchSectionPage();
700  }
701  return;
702  }
703 
704  if ( !$this->mTitle->getArticleID() ) {
705  Hooks::run( 'EditFormPreloadText', [ &$this->textbox1, &$this->mTitle ] );
706  } else {
707  Hooks::run( 'EditFormInitialText', [ $this ] );
708  }
709 
710  }
711 
712  $this->showEditForm();
713  }
714 
719  protected function getEditPermissionErrors( $rigor = 'secure' ) {
720  $user = $this->context->getUser();
721  $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $user, $rigor );
722  # Can this title be created?
723  if ( !$this->mTitle->exists() ) {
724  $permErrors = array_merge(
725  $permErrors,
726  wfArrayDiff2(
727  $this->mTitle->getUserPermissionsErrors( 'create', $user, $rigor ),
728  $permErrors
729  )
730  );
731  }
732  # Ignore some permissions errors when a user is just previewing/viewing diffs
733  $remove = [];
734  foreach ( $permErrors as $error ) {
735  if ( ( $this->preview || $this->diff )
736  && (
737  $error[0] == 'blockedtext' ||
738  $error[0] == 'autoblockedtext' ||
739  $error[0] == 'systemblockedtext'
740  )
741  ) {
742  $remove[] = $error;
743  }
744  }
745  $permErrors = wfArrayDiff2( $permErrors, $remove );
746 
747  return $permErrors;
748  }
749 
763  protected function displayPermissionsError( array $permErrors ) {
764  $out = $this->context->getOutput();
765  if ( $this->context->getRequest()->getBool( 'redlink' ) ) {
766  // The edit page was reached via a red link.
767  // Redirect to the article page and let them click the edit tab if
768  // they really want a permission error.
769  $out->redirect( $this->mTitle->getFullURL() );
770  return;
771  }
772 
773  $content = $this->getContentObject();
774 
775  # Use the normal message if there's nothing to display
776  if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
777  $action = $this->mTitle->exists() ? 'edit' :
778  ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
779  throw new PermissionsError( $action, $permErrors );
780  }
781 
782  $this->displayViewSourcePage(
783  $content,
784  $out->formatPermissionsErrorMessage( $permErrors, 'edit' )
785  );
786  }
787 
793  protected function displayViewSourcePage( Content $content, $errorMessage = '' ) {
794  $out = $this->context->getOutput();
795  Hooks::run( 'EditPage::showReadOnlyForm:initial', [ $this, &$out ] );
796 
797  $out->setRobotPolicy( 'noindex,nofollow' );
798  $out->setPageTitle( $this->context->msg(
799  'viewsource-title',
800  $this->getContextTitle()->getPrefixedText()
801  ) );
802  $out->addBacklinkSubtitle( $this->getContextTitle() );
803  $out->addHTML( $this->editFormPageTop );
804  $out->addHTML( $this->editFormTextTop );
805 
806  if ( $errorMessage !== '' ) {
807  $out->addWikiTextAsInterface( $errorMessage );
808  $out->addHTML( "<hr />\n" );
809  }
810 
811  # If the user made changes, preserve them when showing the markup
812  # (This happens when a user is blocked during edit, for instance)
813  if ( !$this->firsttime ) {
814  $text = $this->textbox1;
815  $out->addWikiMsg( 'viewyourtext' );
816  } else {
817  try {
818  $text = $this->toEditText( $content );
819  } catch ( MWException $e ) {
820  # Serialize using the default format if the content model is not supported
821  # (e.g. for an old revision with a different model)
822  $text = $content->serialize();
823  }
824  $out->addWikiMsg( 'viewsourcetext' );
825  }
826 
827  $out->addHTML( $this->editFormTextBeforeContent );
828  $this->showTextbox( $text, 'wpTextbox1', [ 'readonly' ] );
829  $out->addHTML( $this->editFormTextAfterContent );
830 
831  $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
832 
833  $out->addModules( 'mediawiki.action.edit.collapsibleFooter' );
834 
835  $out->addHTML( $this->editFormTextBottom );
836  if ( $this->mTitle->exists() ) {
837  $out->returnToMain( null, $this->mTitle );
838  }
839  }
840 
846  protected function previewOnOpen() {
847  $config = $this->context->getConfig();
848  $previewOnOpenNamespaces = $config->get( 'PreviewOnOpenNamespaces' );
849  $request = $this->context->getRequest();
850  if ( $config->get( 'RawHtml' ) ) {
851  // If raw HTML is enabled, disable preview on open
852  // since it has to be posted with a token for
853  // security reasons
854  return false;
855  }
856  if ( $request->getVal( 'preview' ) == 'yes' ) {
857  // Explicit override from request
858  return true;
859  } elseif ( $request->getVal( 'preview' ) == 'no' ) {
860  // Explicit override from request
861  return false;
862  } elseif ( $this->section == 'new' ) {
863  // Nothing *to* preview for new sections
864  return false;
865  } elseif ( ( $request->getCheck( 'preload' ) || $this->mTitle->exists() )
866  && $this->context->getUser()->getOption( 'previewonfirst' )
867  ) {
868  // Standard preference behavior
869  return true;
870  } elseif ( !$this->mTitle->exists()
871  && isset( $previewOnOpenNamespaces[$this->mTitle->getNamespace()] )
872  && $previewOnOpenNamespaces[$this->mTitle->getNamespace()]
873  ) {
874  // Categories are special
875  return true;
876  } else {
877  return false;
878  }
879  }
880 
887  protected function isWrongCaseUserConfigPage() {
888  if ( $this->mTitle->isUserConfigPage() ) {
889  $name = $this->mTitle->getSkinFromConfigSubpage();
890  $skins = array_merge(
891  array_keys( Skin::getSkinNames() ),
892  [ 'common' ]
893  );
894  return !in_array( $name, $skins )
895  && in_array( strtolower( $name ), $skins );
896  } else {
897  return false;
898  }
899  }
900 
908  protected function isSectionEditSupported() {
909  $contentHandler = ContentHandler::getForTitle( $this->mTitle );
910  return $contentHandler->supportsSections();
911  }
912 
918  public function importFormData( &$request ) {
919  # Section edit can come from either the form or a link
920  $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
921 
922  if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) {
923  throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
924  }
925 
926  $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
927 
928  if ( $request->wasPosted() ) {
929  # These fields need to be checked for encoding.
930  # Also remove trailing whitespace, but don't remove _initial_
931  # whitespace from the text boxes. This may be significant formatting.
932  $this->textbox1 = rtrim( $request->getText( 'wpTextbox1' ) );
933  if ( !$request->getCheck( 'wpTextbox2' ) ) {
934  // Skip this if wpTextbox2 has input, it indicates that we came
935  // from a conflict page with raw page text, not a custom form
936  // modified by subclasses
938  if ( $textbox1 !== null ) {
939  $this->textbox1 = $textbox1;
940  }
941  }
942 
943  $this->unicodeCheck = $request->getText( 'wpUnicodeCheck' );
944 
945  $this->summary = $request->getText( 'wpSummary' );
946 
947  # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
948  # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
949  # section titles.
950  $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
951 
952  # Treat sectiontitle the same way as summary.
953  # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
954  # currently doing double duty as both edit summary and section title. Right now this
955  # is just to allow API edits to work around this limitation, but this should be
956  # incorporated into the actual edit form when EditPage is rewritten (T20654, T28312).
957  $this->sectiontitle = $request->getText( 'wpSectionTitle' );
958  $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
959 
960  $this->edittime = $request->getVal( 'wpEdittime' );
961  $this->editRevId = $request->getIntOrNull( 'editRevId' );
962  $this->starttime = $request->getVal( 'wpStarttime' );
963 
964  $undidRev = $request->getInt( 'wpUndidRevision' );
965  if ( $undidRev ) {
966  $this->undidRev = $undidRev;
967  }
968 
969  $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
970 
971  if ( $this->textbox1 === '' && !$request->getCheck( 'wpTextbox1' ) ) {
972  // wpTextbox1 field is missing, possibly due to being "too big"
973  // according to some filter rules such as Suhosin's setting for
974  // suhosin.request.max_value_length (d'oh)
975  $this->incompleteForm = true;
976  } else {
977  // If we receive the last parameter of the request, we can fairly
978  // claim the POST request has not been truncated.
979  $this->incompleteForm = !$request->getVal( 'wpUltimateParam' );
980  }
981  if ( $this->incompleteForm ) {
982  # If the form is incomplete, force to preview.
983  wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
984  wfDebug( "POST DATA: " . var_export( $request->getPostValues(), true ) . "\n" );
985  $this->preview = true;
986  } else {
987  $this->preview = $request->getCheck( 'wpPreview' );
988  $this->diff = $request->getCheck( 'wpDiff' );
989 
990  // Remember whether a save was requested, so we can indicate
991  // if we forced preview due to session failure.
992  $this->mTriedSave = !$this->preview;
993 
994  if ( $this->tokenOk( $request ) ) {
995  # Some browsers will not report any submit button
996  # if the user hits enter in the comment box.
997  # The unmarked state will be assumed to be a save,
998  # if the form seems otherwise complete.
999  wfDebug( __METHOD__ . ": Passed token check.\n" );
1000  } elseif ( $this->diff ) {
1001  # Failed token check, but only requested "Show Changes".
1002  wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
1003  } else {
1004  # Page might be a hack attempt posted from
1005  # an external site. Preview instead of saving.
1006  wfDebug( __METHOD__ . ": Failed token check; forcing preview\n" );
1007  $this->preview = true;
1008  }
1009  }
1010  $this->save = !$this->preview && !$this->diff;
1011  if ( !preg_match( '/^\d{14}$/', $this->edittime ) ) {
1012  $this->edittime = null;
1013  }
1014 
1015  if ( !preg_match( '/^\d{14}$/', $this->starttime ) ) {
1016  $this->starttime = null;
1017  }
1018 
1019  $this->recreate = $request->getCheck( 'wpRecreate' );
1020 
1021  $this->minoredit = $request->getCheck( 'wpMinoredit' );
1022  $this->watchthis = $request->getCheck( 'wpWatchthis' );
1023 
1024  $user = $this->context->getUser();
1025  # Don't force edit summaries when a user is editing their own user or talk page
1026  if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK )
1027  && $this->mTitle->getText() == $user->getName()
1028  ) {
1029  $this->allowBlankSummary = true;
1030  } else {
1031  $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' )
1032  || !$user->getOption( 'forceeditsummary' );
1033  }
1034 
1035  $this->autoSumm = $request->getText( 'wpAutoSummary' );
1036 
1037  $this->allowBlankArticle = $request->getBool( 'wpIgnoreBlankArticle' );
1038  $this->allowSelfRedirect = $request->getBool( 'wpIgnoreSelfRedirect' );
1039 
1040  $changeTags = $request->getVal( 'wpChangeTags' );
1041  if ( is_null( $changeTags ) || $changeTags === '' ) {
1042  $this->changeTags = [];
1043  } else {
1044  $this->changeTags = array_filter( array_map( 'trim', explode( ',',
1045  $changeTags ) ) );
1046  }
1047  } else {
1048  # Not a posted form? Start with nothing.
1049  wfDebug( __METHOD__ . ": Not a posted form.\n" );
1050  $this->textbox1 = '';
1051  $this->summary = '';
1052  $this->sectiontitle = '';
1053  $this->edittime = '';
1054  $this->editRevId = null;
1055  $this->starttime = wfTimestampNow();
1056  $this->edit = false;
1057  $this->preview = false;
1058  $this->save = false;
1059  $this->diff = false;
1060  $this->minoredit = false;
1061  // Watch may be overridden by request parameters
1062  $this->watchthis = $request->getBool( 'watchthis', false );
1063  $this->recreate = false;
1064 
1065  // When creating a new section, we can preload a section title by passing it as the
1066  // preloadtitle parameter in the URL (T15100)
1067  if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
1068  $this->sectiontitle = $request->getVal( 'preloadtitle' );
1069  // Once wpSummary isn't being use for setting section titles, we should delete this.
1070  $this->summary = $request->getVal( 'preloadtitle' );
1071  } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) !== '' ) {
1072  $this->summary = $request->getText( 'summary' );
1073  if ( $this->summary !== '' ) {
1074  $this->hasPresetSummary = true;
1075  }
1076  }
1077 
1078  if ( $request->getVal( 'minor' ) ) {
1079  $this->minoredit = true;
1080  }
1081  }
1082 
1083  $this->oldid = $request->getInt( 'oldid' );
1084  $this->parentRevId = $request->getInt( 'parentRevId' );
1085 
1086  $this->bot = $request->getBool( 'bot', true );
1087  $this->nosummary = $request->getBool( 'nosummary' );
1088 
1089  // May be overridden by revision.
1090  $this->contentModel = $request->getText( 'model', $this->contentModel );
1091  // May be overridden by revision.
1092  $this->contentFormat = $request->getText( 'format', $this->contentFormat );
1093 
1094  try {
1095  $handler = ContentHandler::getForModelID( $this->contentModel );
1096  } catch ( MWUnknownContentModelException $e ) {
1097  throw new ErrorPageError(
1098  'editpage-invalidcontentmodel-title',
1099  'editpage-invalidcontentmodel-text',
1100  [ wfEscapeWikiText( $this->contentModel ) ]
1101  );
1102  }
1103 
1104  if ( !$handler->isSupportedFormat( $this->contentFormat ) ) {
1105  throw new ErrorPageError(
1106  'editpage-notsupportedcontentformat-title',
1107  'editpage-notsupportedcontentformat-text',
1108  [
1109  wfEscapeWikiText( $this->contentFormat ),
1110  wfEscapeWikiText( ContentHandler::getLocalizedName( $this->contentModel ) )
1111  ]
1112  );
1113  }
1114 
1121  $this->editintro = $request->getText( 'editintro',
1122  // Custom edit intro for new sections
1123  $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
1124 
1125  // Allow extensions to modify form data
1126  Hooks::run( 'EditPage::importFormData', [ $this, $request ] );
1127  }
1128 
1138  protected function importContentFormData( &$request ) {
1139  return; // Don't do anything, EditPage already extracted wpTextbox1
1140  }
1141 
1147  public function initialiseForm() {
1148  $this->edittime = $this->page->getTimestamp();
1149  $this->editRevId = $this->page->getLatest();
1150 
1151  $content = $this->getContentObject( false ); # TODO: track content object?!
1152  if ( $content === false ) {
1153  return false;
1154  }
1155  $this->textbox1 = $this->toEditText( $content );
1156 
1157  $user = $this->context->getUser();
1158  // activate checkboxes if user wants them to be always active
1159  # Sort out the "watch" checkbox
1160  if ( $user->getOption( 'watchdefault' ) ) {
1161  # Watch all edits
1162  $this->watchthis = true;
1163  } elseif ( $user->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
1164  # Watch creations
1165  $this->watchthis = true;
1166  } elseif ( $user->isWatched( $this->mTitle ) ) {
1167  # Already watched
1168  $this->watchthis = true;
1169  }
1170  if ( $user->getOption( 'minordefault' ) && !$this->isNew ) {
1171  $this->minoredit = true;
1172  }
1173  if ( $this->textbox1 === false ) {
1174  return false;
1175  }
1176  return true;
1177  }
1178 
1186  protected function getContentObject( $def_content = null ) {
1187  $content = false;
1188 
1189  $user = $this->context->getUser();
1190  $request = $this->context->getRequest();
1191  // For message page not locally set, use the i18n message.
1192  // For other non-existent articles, use preload text if any.
1193  if ( !$this->mTitle->exists() || $this->section == 'new' ) {
1194  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
1195  # If this is a system message, get the default text.
1196  $msg = $this->mTitle->getDefaultMessageText();
1197 
1198  $content = $this->toEditContent( $msg );
1199  }
1200  if ( $content === false ) {
1201  # If requested, preload some text.
1202  $preload = $request->getVal( 'preload',
1203  // Custom preload text for new sections
1204  $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
1205  $params = $request->getArray( 'preloadparams', [] );
1206 
1207  $content = $this->getPreloadedContent( $preload, $params );
1208  }
1209  // For existing pages, get text based on "undo" or section parameters.
1210  } elseif ( $this->section != '' ) {
1211  // Get section edit text (returns $def_text for invalid sections)
1212  $orig = $this->getOriginalContent( $user );
1213  $content = $orig ? $orig->getSection( $this->section ) : null;
1214 
1215  if ( !$content ) {
1216  $content = $def_content;
1217  }
1218  } else {
1219  $undoafter = $request->getInt( 'undoafter' );
1220  $undo = $request->getInt( 'undo' );
1221 
1222  if ( $undo > 0 && $undoafter > 0 ) {
1223  $undorev = Revision::newFromId( $undo );
1224  $oldrev = Revision::newFromId( $undoafter );
1225  $undoMsg = null;
1226 
1227  # Sanity check, make sure it's the right page,
1228  # the revisions exist and they were not deleted.
1229  # Otherwise, $content will be left as-is.
1230  if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
1231  !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
1232  !$oldrev->isDeleted( Revision::DELETED_TEXT )
1233  ) {
1234  if ( WikiPage::hasDifferencesOutsideMainSlot( $undorev, $oldrev )
1235  || !$this->isSupportedContentModel( $oldrev->getContentModel() )
1236  ) {
1237  // Hack for undo while EditPage can't handle multi-slot editing
1238  $this->context->getOutput()->redirect( $this->mTitle->getFullURL( [
1239  'action' => 'mcrundo',
1240  'undo' => $undo,
1241  'undoafter' => $undoafter,
1242  ] ) );
1243  return false;
1244  } else {
1245  $content = $this->page->getUndoContent( $undorev, $oldrev );
1246 
1247  if ( $content === false ) {
1248  # Warn the user that something went wrong
1249  $undoMsg = 'failure';
1250  }
1251  }
1252 
1253  if ( $undoMsg === null ) {
1254  $oldContent = $this->page->getContent( Revision::RAW );
1256  $user, MediaWikiServices::getInstance()->getContentLanguage() );
1257  $newContent = $content->preSaveTransform( $this->mTitle, $user, $popts );
1258  if ( $newContent->getModel() !== $oldContent->getModel() ) {
1259  // The undo may change content
1260  // model if its reverting the top
1261  // edit. This can result in
1262  // mismatched content model/format.
1263  $this->contentModel = $newContent->getModel();
1264  $this->contentFormat = $oldrev->getContentFormat();
1265  }
1266 
1267  if ( $newContent->equals( $oldContent ) ) {
1268  # Tell the user that the undo results in no change,
1269  # i.e. the revisions were already undone.
1270  $undoMsg = 'nochange';
1271  $content = false;
1272  } else {
1273  # Inform the user of our success and set an automatic edit summary
1274  $undoMsg = 'success';
1275 
1276  # If we just undid one rev, use an autosummary
1277  $firstrev = $oldrev->getNext();
1278  if ( $firstrev && $firstrev->getId() == $undo ) {
1279  $userText = $undorev->getUserText();
1280  if ( $userText === '' ) {
1281  $undoSummary = $this->context->msg(
1282  'undo-summary-username-hidden',
1283  $undo
1284  )->inContentLanguage()->text();
1285  } else {
1286  $undoSummary = $this->context->msg(
1287  'undo-summary',
1288  $undo,
1289  $userText
1290  )->inContentLanguage()->text();
1291  }
1292  if ( $this->summary === '' ) {
1293  $this->summary = $undoSummary;
1294  } else {
1295  $this->summary = $undoSummary . $this->context->msg( 'colon-separator' )
1296  ->inContentLanguage()->text() . $this->summary;
1297  }
1298  $this->undidRev = $undo;
1299  }
1300  $this->formtype = 'diff';
1301  }
1302  }
1303  } else {
1304  // Failed basic sanity checks.
1305  // Older revisions may have been removed since the link
1306  // was created, or we may simply have got bogus input.
1307  $undoMsg = 'norev';
1308  }
1309 
1310  $out = $this->context->getOutput();
1311  // Messages: undo-success, undo-failure, undo-main-slot-only, undo-norev,
1312  // undo-nochange.
1313  $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
1314  $this->editFormPageTop .= Html::rawElement(
1315  'div', [ 'class' => $class ],
1316  $out->parseAsInterface(
1317  $this->context->msg( 'undo-' . $undoMsg )->plain()
1318  )
1319  );
1320  }
1321 
1322  if ( $content === false ) {
1323  // Hack for restoring old revisions while EditPage
1324  // can't handle multi-slot editing.
1325 
1326  $curRevision = $this->page->getRevision();
1327  $oldRevision = $this->mArticle->getRevisionFetched();
1328 
1329  if ( $curRevision
1330  && $oldRevision
1331  && $curRevision->getId() !== $oldRevision->getId()
1332  && ( WikiPage::hasDifferencesOutsideMainSlot( $oldRevision, $curRevision )
1333  || !$this->isSupportedContentModel( $oldRevision->getContentModel() ) )
1334  ) {
1335  $this->context->getOutput()->redirect(
1336  $this->mTitle->getFullURL(
1337  [
1338  'action' => 'mcrrestore',
1339  'restore' => $oldRevision->getId(),
1340  ]
1341  )
1342  );
1343 
1344  return false;
1345  }
1346  }
1347 
1348  if ( $content === false ) {
1349  $content = $this->getOriginalContent( $user );
1350  }
1351  }
1352 
1353  return $content;
1354  }
1355 
1371  private function getOriginalContent( User $user ) {
1372  if ( $this->section == 'new' ) {
1373  return $this->getCurrentContent();
1374  }
1375  $revision = $this->mArticle->getRevisionFetched();
1376  if ( $revision === null ) {
1377  $handler = ContentHandler::getForModelID( $this->contentModel );
1378  return $handler->makeEmptyContent();
1379  }
1380  $content = $revision->getContent( Revision::FOR_THIS_USER, $user );
1381  return $content;
1382  }
1383 
1396  public function getParentRevId() {
1397  if ( $this->parentRevId ) {
1398  return $this->parentRevId;
1399  } else {
1400  return $this->mArticle->getRevIdFetched();
1401  }
1402  }
1403 
1412  protected function getCurrentContent() {
1413  $rev = $this->page->getRevision();
1414  $content = $rev ? $rev->getContent( Revision::RAW ) : null;
1415 
1416  if ( $content === false || $content === null ) {
1417  $handler = ContentHandler::getForModelID( $this->contentModel );
1418  return $handler->makeEmptyContent();
1419  } elseif ( !$this->undidRev ) {
1420  // Content models should always be the same since we error
1421  // out if they are different before this point (in ->edit()).
1422  // The exception being, during an undo, the current revision might
1423  // differ from the prior revision.
1424  $logger = LoggerFactory::getInstance( 'editpage' );
1425  if ( $this->contentModel !== $rev->getContentModel() ) {
1426  $logger->warning( "Overriding content model from current edit {prev} to {new}", [
1427  'prev' => $this->contentModel,
1428  'new' => $rev->getContentModel(),
1429  'title' => $this->getTitle()->getPrefixedDBkey(),
1430  'method' => __METHOD__
1431  ] );
1432  $this->contentModel = $rev->getContentModel();
1433  }
1434 
1435  // Given that the content models should match, the current selected
1436  // format should be supported.
1437  if ( !$content->isSupportedFormat( $this->contentFormat ) ) {
1438  $logger->warning( "Current revision content format unsupported. Overriding {prev} to {new}", [
1439 
1440  'prev' => $this->contentFormat,
1441  'new' => $rev->getContentFormat(),
1442  'title' => $this->getTitle()->getPrefixedDBkey(),
1443  'method' => __METHOD__
1444  ] );
1445  $this->contentFormat = $rev->getContentFormat();
1446  }
1447  }
1448  return $content;
1449  }
1450 
1458  public function setPreloadedContent( Content $content ) {
1459  $this->mPreloadContent = $content;
1460  }
1461 
1473  protected function getPreloadedContent( $preload, $params = [] ) {
1474  if ( !empty( $this->mPreloadContent ) ) {
1475  return $this->mPreloadContent;
1476  }
1477 
1478  $handler = ContentHandler::getForModelID( $this->contentModel );
1479 
1480  if ( $preload === '' ) {
1481  return $handler->makeEmptyContent();
1482  }
1483 
1484  $user = $this->context->getUser();
1485  $title = Title::newFromText( $preload );
1486  # Check for existence to avoid getting MediaWiki:Noarticletext
1487  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $user ) ) {
1488  // TODO: somehow show a warning to the user!
1489  return $handler->makeEmptyContent();
1490  }
1491 
1493  if ( $page->isRedirect() ) {
1495  # Same as before
1496  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $user ) ) {
1497  // TODO: somehow show a warning to the user!
1498  return $handler->makeEmptyContent();
1499  }
1501  }
1502 
1503  $parserOptions = ParserOptions::newFromUser( $user );
1505 
1506  if ( !$content ) {
1507  // TODO: somehow show a warning to the user!
1508  return $handler->makeEmptyContent();
1509  }
1510 
1511  if ( $content->getModel() !== $handler->getModelID() ) {
1512  $converted = $content->convert( $handler->getModelID() );
1513 
1514  if ( !$converted ) {
1515  // TODO: somehow show a warning to the user!
1516  wfDebug( "Attempt to preload incompatible content: " .
1517  "can't convert " . $content->getModel() .
1518  " to " . $handler->getModelID() );
1519 
1520  return $handler->makeEmptyContent();
1521  }
1522 
1523  $content = $converted;
1524  }
1525 
1526  return $content->preloadTransform( $title, $parserOptions, $params );
1527  }
1528 
1536  public function tokenOk( &$request ) {
1537  $token = $request->getVal( 'wpEditToken' );
1538  $user = $this->context->getUser();
1539  $this->mTokenOk = $user->matchEditToken( $token );
1540  $this->mTokenOkExceptSuffix = $user->matchEditTokenNoSuffix( $token );
1541  return $this->mTokenOk;
1542  }
1543 
1558  protected function setPostEditCookie( $statusValue ) {
1559  $revisionId = $this->page->getLatest();
1560  $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1561 
1562  $val = 'saved';
1563  if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) {
1564  $val = 'created';
1565  } elseif ( $this->oldid ) {
1566  $val = 'restored';
1567  }
1568 
1569  $response = $this->context->getRequest()->response();
1570  $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION );
1571  }
1572 
1579  public function attemptSave( &$resultDetails = false ) {
1580  // TODO: MCR: treat $this->minoredit like $this->bot and check isAllowed( 'minoredit' )!
1581  // Also, add $this->autopatrol like $this->bot and check isAllowed( 'autopatrol' )!
1582  // This is needed since PageUpdater no longer checks these rights!
1583 
1584  // Allow bots to exempt some edits from bot flagging
1585  $bot = $this->context->getUser()->isAllowed( 'bot' ) && $this->bot;
1586  $status = $this->internalAttemptSave( $resultDetails, $bot );
1587 
1588  Hooks::run( 'EditPage::attemptSave:after', [ $this, $status, $resultDetails ] );
1589 
1590  return $status;
1591  }
1592 
1596  private function incrementResolvedConflicts() {
1597  if ( $this->context->getRequest()->getText( 'mode' ) !== 'conflict' ) {
1598  return;
1599  }
1600 
1601  $this->getEditConflictHelper()->incrementResolvedStats();
1602  }
1603 
1613  private function handleStatus( Status $status, $resultDetails ) {
1618  if ( $status->value == self::AS_SUCCESS_UPDATE
1619  || $status->value == self::AS_SUCCESS_NEW_ARTICLE
1620  ) {
1621  $this->incrementResolvedConflicts();
1622 
1623  $this->didSave = true;
1624  if ( !$resultDetails['nullEdit'] ) {
1625  $this->setPostEditCookie( $status->value );
1626  }
1627  }
1628 
1629  $out = $this->context->getOutput();
1630 
1631  // "wpExtraQueryRedirect" is a hidden input to modify
1632  // after save URL and is not used by actual edit form
1633  $request = $this->context->getRequest();
1634  $extraQueryRedirect = $request->getVal( 'wpExtraQueryRedirect' );
1635 
1636  switch ( $status->value ) {
1644  case self::AS_END:
1647  return true;
1648 
1649  case self::AS_HOOK_ERROR:
1650  return false;
1651 
1653  case self::AS_PARSE_ERROR:
1655  $out->wrapWikiTextAsInterface( 'error', $status->getWikiText() );
1656  return true;
1657 
1659  $query = $resultDetails['redirect'] ? 'redirect=no' : '';
1660  if ( $extraQueryRedirect ) {
1661  if ( $query !== '' ) {
1662  $query .= '&';
1663  }
1664  $query .= $extraQueryRedirect;
1665  }
1666  $anchor = $resultDetails['sectionanchor'] ?? '';
1667  $out->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
1668  return false;
1669 
1671  $extraQuery = '';
1672  $sectionanchor = $resultDetails['sectionanchor'];
1673 
1674  // Give extensions a chance to modify URL query on update
1675  Hooks::run(
1676  'ArticleUpdateBeforeRedirect',
1677  [ $this->mArticle, &$sectionanchor, &$extraQuery ]
1678  );
1679 
1680  if ( $resultDetails['redirect'] ) {
1681  if ( $extraQuery !== '' ) {
1682  $extraQuery = '&' . $extraQuery;
1683  }
1684  $extraQuery = 'redirect=no' . $extraQuery;
1685  }
1686  if ( $extraQueryRedirect ) {
1687  if ( $extraQuery !== '' ) {
1688  $extraQuery .= '&';
1689  }
1690  $extraQuery .= $extraQueryRedirect;
1691  }
1692 
1693  $out->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1694  return false;
1695 
1696  case self::AS_SPAM_ERROR:
1697  $this->spamPageWithContent( $resultDetails['spam'] );
1698  return false;
1699 
1701  throw new UserBlockedError( $this->context->getUser()->getBlock() );
1702 
1705  throw new PermissionsError( 'upload' );
1706 
1709  throw new PermissionsError( 'edit' );
1710 
1712  throw new ReadOnlyError;
1713 
1714  case self::AS_RATE_LIMITED:
1715  throw new ThrottledError();
1716 
1718  $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
1719  throw new PermissionsError( $permission );
1720 
1722  throw new PermissionsError( 'editcontentmodel' );
1723 
1724  default:
1725  // We don't recognize $status->value. The only way that can happen
1726  // is if an extension hook aborted from inside ArticleSave.
1727  // Render the status object into $this->hookError
1728  // FIXME this sucks, we should just use the Status object throughout
1729  $this->hookError = '<div class="error">' . "\n" . $status->getWikiText() .
1730  '</div>';
1731  return true;
1732  }
1733  }
1734 
1745  // Run old style post-section-merge edit filter
1746  if ( $this->hookError != '' ) {
1747  # ...or the hook could be expecting us to produce an error
1748  $status->fatal( 'hookaborted' );
1750  return false;
1751  }
1752 
1753  // Run new style post-section-merge edit filter
1754  if ( !Hooks::run( 'EditFilterMergedContent',
1755  [ $this->context, $content, $status, $this->summary,
1756  $user, $this->minoredit ] )
1757  ) {
1758  # Error messages etc. could be handled within the hook...
1759  if ( $status->isGood() ) {
1760  $status->fatal( 'hookaborted' );
1761  // Not setting $this->hookError here is a hack to allow the hook
1762  // to cause a return to the edit page without $this->hookError
1763  // being set. This is used by ConfirmEdit to display a captcha
1764  // without any error message cruft.
1765  } else {
1766  $this->hookError = $this->formatStatusErrors( $status );
1767  }
1768  // Use the existing $status->value if the hook set it
1769  if ( !$status->value ) {
1770  $status->value = self::AS_HOOK_ERROR;
1771  }
1772  return false;
1773  } elseif ( !$status->isOK() ) {
1774  # ...or the hook could be expecting us to produce an error
1775  // FIXME this sucks, we should just use the Status object throughout
1776  $this->hookError = $this->formatStatusErrors( $status );
1777  $status->fatal( 'hookaborted' );
1779  return false;
1780  }
1781 
1782  return true;
1783  }
1784 
1791  private function formatStatusErrors( Status $status ) {
1792  $errmsg = $status->getWikiText(
1793  'edit-error-short',
1794  'edit-error-long',
1795  $this->context->getLanguage()
1796  );
1797  return <<<ERROR
1798 <div class="errorbox">
1799 {$errmsg}
1800 </div>
1801 <br clear="all" />
1802 ERROR;
1803  }
1804 
1811  private function newSectionSummary( &$sectionanchor = null ) {
1812  global $wgParser;
1813 
1814  if ( $this->sectiontitle !== '' ) {
1815  $sectionanchor = $this->guessSectionName( $this->sectiontitle );
1816  // If no edit summary was specified, create one automatically from the section
1817  // title and have it link to the new section. Otherwise, respect the summary as
1818  // passed.
1819  if ( $this->summary === '' ) {
1820  $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
1821  return $this->context->msg( 'newsectionsummary' )
1822  ->plaintextParams( $cleanSectionTitle )->inContentLanguage()->text();
1823  }
1824  } elseif ( $this->summary !== '' ) {
1825  $sectionanchor = $this->guessSectionName( $this->summary );
1826  # This is a new section, so create a link to the new section
1827  # in the revision summary.
1828  $cleanSummary = $wgParser->stripSectionName( $this->summary );
1829  return $this->context->msg( 'newsectionsummary' )
1830  ->plaintextParams( $cleanSummary )->inContentLanguage()->text();
1831  }
1832  return $this->summary;
1833  }
1834 
1859  public function internalAttemptSave( &$result, $bot = false ) {
1861  $user = $this->context->getUser();
1862 
1863  if ( !Hooks::run( 'EditPage::attemptSave', [ $this ] ) ) {
1864  wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
1865  $status->fatal( 'hookaborted' );
1866  $status->value = self::AS_HOOK_ERROR;
1867  return $status;
1868  }
1869 
1870  if ( $this->unicodeCheck !== self::UNICODE_CHECK ) {
1871  $status->fatal( 'unicode-support-fail' );
1873  return $status;
1874  }
1875 
1876  $request = $this->context->getRequest();
1877  $spam = $request->getText( 'wpAntispam' );
1878  if ( $spam !== '' ) {
1879  wfDebugLog(
1880  'SimpleAntiSpam',
1881  $user->getName() .
1882  ' editing "' .
1883  $this->mTitle->getPrefixedText() .
1884  '" submitted bogus field "' .
1885  $spam .
1886  '"'
1887  );
1888  $status->fatal( 'spamprotectionmatch', false );
1889  $status->value = self::AS_SPAM_ERROR;
1890  return $status;
1891  }
1892 
1893  try {
1894  # Construct Content object
1895  $textbox_content = $this->toEditContent( $this->textbox1 );
1896  } catch ( MWContentSerializationException $ex ) {
1897  $status->fatal(
1898  'content-failed-to-parse',
1899  $this->contentModel,
1900  $this->contentFormat,
1901  $ex->getMessage()
1902  );
1903  $status->value = self::AS_PARSE_ERROR;
1904  return $status;
1905  }
1906 
1907  # Check image redirect
1908  if ( $this->mTitle->getNamespace() == NS_FILE &&
1909  $textbox_content->isRedirect() &&
1910  !$user->isAllowed( 'upload' )
1911  ) {
1913  $status->setResult( false, $code );
1914 
1915  return $status;
1916  }
1917 
1918  # Check for spam
1919  $match = self::matchSummarySpamRegex( $this->summary );
1920  if ( $match === false && $this->section == 'new' ) {
1921  # $wgSpamRegex is enforced on this new heading/summary because, unlike
1922  # regular summaries, it is added to the actual wikitext.
1923  if ( $this->sectiontitle !== '' ) {
1924  # This branch is taken when the API is used with the 'sectiontitle' parameter.
1925  $match = self::matchSpamRegex( $this->sectiontitle );
1926  } else {
1927  # This branch is taken when the "Add Topic" user interface is used, or the API
1928  # is used with the 'summary' parameter.
1929  $match = self::matchSpamRegex( $this->summary );
1930  }
1931  }
1932  if ( $match === false ) {
1933  $match = self::matchSpamRegex( $this->textbox1 );
1934  }
1935  if ( $match !== false ) {
1936  $result['spam'] = $match;
1937  $ip = $request->getIP();
1938  $pdbk = $this->mTitle->getPrefixedDBkey();
1939  $match = str_replace( "\n", '', $match );
1940  wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
1941  $status->fatal( 'spamprotectionmatch', $match );
1942  $status->value = self::AS_SPAM_ERROR;
1943  return $status;
1944  }
1945  if ( !Hooks::run(
1946  'EditFilter',
1947  [ $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ] )
1948  ) {
1949  # Error messages etc. could be handled within the hook...
1950  $status->fatal( 'hookaborted' );
1951  $status->value = self::AS_HOOK_ERROR;
1952  return $status;
1953  } elseif ( $this->hookError != '' ) {
1954  # ...or the hook could be expecting us to produce an error
1955  $status->fatal( 'hookaborted' );
1957  return $status;
1958  }
1959 
1960  if ( $user->isBlockedFrom( $this->mTitle ) ) {
1961  // Auto-block user's IP if the account was "hard" blocked
1962  if ( !wfReadOnly() ) {
1963  $user->spreadAnyEditBlock();
1964  }
1965  # Check block state against master, thus 'false'.
1966  $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
1967  return $status;
1968  }
1969 
1970  $this->contentLength = strlen( $this->textbox1 );
1971  $config = $this->context->getConfig();
1972  $maxArticleSize = $config->get( 'MaxArticleSize' );
1973  if ( $this->contentLength > $maxArticleSize * 1024 ) {
1974  // Error will be displayed by showEditForm()
1975  $this->tooBig = true;
1976  $status->setResult( false, self::AS_CONTENT_TOO_BIG );
1977  return $status;
1978  }
1979 
1980  if ( !$user->isAllowed( 'edit' ) ) {
1981  if ( $user->isAnon() ) {
1982  $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
1983  return $status;
1984  } else {
1985  $status->fatal( 'readonlytext' );
1987  return $status;
1988  }
1989  }
1990 
1991  $changingContentModel = false;
1992  if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
1993  if ( !$config->get( 'ContentHandlerUseDB' ) ) {
1994  $status->fatal( 'editpage-cannot-use-custom-model' );
1996  return $status;
1997  } elseif ( !$user->isAllowed( 'editcontentmodel' ) ) {
1998  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
1999  return $status;
2000  }
2001  // Make sure the user can edit the page under the new content model too
2002  $titleWithNewContentModel = clone $this->mTitle;
2003  $titleWithNewContentModel->setContentModel( $this->contentModel );
2004  if ( !$titleWithNewContentModel->userCan( 'editcontentmodel', $user )
2005  || !$titleWithNewContentModel->userCan( 'edit', $user )
2006  ) {
2007  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
2008  return $status;
2009  }
2010 
2011  $changingContentModel = true;
2012  $oldContentModel = $this->mTitle->getContentModel();
2013  }
2014 
2015  if ( $this->changeTags ) {
2016  $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange(
2017  $this->changeTags, $user );
2018  if ( !$changeTagsStatus->isOK() ) {
2019  $changeTagsStatus->value = self::AS_CHANGE_TAG_ERROR;
2020  return $changeTagsStatus;
2021  }
2022  }
2023 
2024  if ( wfReadOnly() ) {
2025  $status->fatal( 'readonlytext' );
2027  return $status;
2028  }
2029  if ( $user->pingLimiter() || $user->pingLimiter( 'linkpurge', 0 )
2030  || ( $changingContentModel && $user->pingLimiter( 'editcontentmodel' ) )
2031  ) {
2032  $status->fatal( 'actionthrottledtext' );
2033  $status->value = self::AS_RATE_LIMITED;
2034  return $status;
2035  }
2036 
2037  # If the article has been deleted while editing, don't save it without
2038  # confirmation
2039  if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
2040  $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
2041  return $status;
2042  }
2043 
2044  # Load the page data from the master. If anything changes in the meantime,
2045  # we detect it by using page_latest like a token in a 1 try compare-and-swap.
2046  $this->page->loadPageData( 'fromdbmaster' );
2047  $new = !$this->page->exists();
2048 
2049  if ( $new ) {
2050  // Late check for create permission, just in case *PARANOIA*
2051  if ( !$this->mTitle->userCan( 'create', $user ) ) {
2052  $status->fatal( 'nocreatetext' );
2054  wfDebug( __METHOD__ . ": no create permission\n" );
2055  return $status;
2056  }
2057 
2058  // Don't save a new page if it's blank or if it's a MediaWiki:
2059  // message with content equivalent to default (allow empty pages
2060  // in this case to disable messages, see T52124)
2061  $defaultMessageText = $this->mTitle->getDefaultMessageText();
2062  if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false ) {
2063  $defaultText = $defaultMessageText;
2064  } else {
2065  $defaultText = '';
2066  }
2067 
2068  if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) {
2069  $this->blankArticle = true;
2070  $status->fatal( 'blankarticle' );
2071  $status->setResult( false, self::AS_BLANK_ARTICLE );
2072  return $status;
2073  }
2074 
2075  if ( !$this->runPostMergeFilters( $textbox_content, $status, $user ) ) {
2076  return $status;
2077  }
2078 
2079  $content = $textbox_content;
2080 
2081  $result['sectionanchor'] = '';
2082  if ( $this->section == 'new' ) {
2083  if ( $this->sectiontitle !== '' ) {
2084  // Insert the section title above the content.
2085  $content = $content->addSectionHeader( $this->sectiontitle );
2086  } elseif ( $this->summary !== '' ) {
2087  // Insert the section title above the content.
2088  $content = $content->addSectionHeader( $this->summary );
2089  }
2090  $this->summary = $this->newSectionSummary( $result['sectionanchor'] );
2091  }
2092 
2094 
2095  } else { # not $new
2096 
2097  # Article exists. Check for edit conflict.
2098 
2099  $this->page->clear(); # Force reload of dates, etc.
2100  $timestamp = $this->page->getTimestamp();
2101  $latest = $this->page->getLatest();
2102 
2103  wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
2104 
2105  // An edit conflict is detected if the current revision is different from the
2106  // revision that was current when editing was initiated on the client.
2107  // This is checked based on the timestamp and revision ID.
2108  // TODO: the timestamp based check can probably go away now.
2109  if ( $timestamp != $this->edittime
2110  || ( $this->editRevId !== null && $this->editRevId != $latest )
2111  ) {
2112  $this->isConflict = true;
2113  if ( $this->section == 'new' ) {
2114  if ( $this->page->getUserText() == $user->getName() &&
2115  $this->page->getComment() == $this->newSectionSummary()
2116  ) {
2117  // Probably a duplicate submission of a new comment.
2118  // This can happen when CDN resends a request after
2119  // a timeout but the first one actually went through.
2120  wfDebug( __METHOD__
2121  . ": duplicate new section submission; trigger edit conflict!\n" );
2122  } else {
2123  // New comment; suppress conflict.
2124  $this->isConflict = false;
2125  wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
2126  }
2127  } elseif ( $this->section == ''
2129  DB_MASTER, $this->mTitle->getArticleID(),
2130  $user->getId(), $this->edittime
2131  )
2132  ) {
2133  # Suppress edit conflict with self, except for section edits where merging is required.
2134  wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
2135  $this->isConflict = false;
2136  }
2137  }
2138 
2139  // If sectiontitle is set, use it, otherwise use the summary as the section title.
2140  if ( $this->sectiontitle !== '' ) {
2141  $sectionTitle = $this->sectiontitle;
2142  } else {
2143  $sectionTitle = $this->summary;
2144  }
2145 
2146  $content = null;
2147 
2148  if ( $this->isConflict ) {
2149  wfDebug( __METHOD__
2150  . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
2151  . " (id '{$this->editRevId}') (article time '{$timestamp}')\n" );
2152  // @TODO: replaceSectionAtRev() with base ID (not prior current) for ?oldid=X case
2153  // ...or disable section editing for non-current revisions (not exposed anyway).
2154  if ( $this->editRevId !== null ) {
2155  $content = $this->page->replaceSectionAtRev(
2156  $this->section,
2157  $textbox_content,
2158  $sectionTitle,
2159  $this->editRevId
2160  );
2161  } else {
2162  $content = $this->page->replaceSectionContent(
2163  $this->section,
2164  $textbox_content,
2165  $sectionTitle,
2166  $this->edittime
2167  );
2168  }
2169  } else {
2170  wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
2171  $content = $this->page->replaceSectionContent(
2172  $this->section,
2173  $textbox_content,
2174  $sectionTitle
2175  );
2176  }
2177 
2178  if ( is_null( $content ) ) {
2179  wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
2180  $this->isConflict = true;
2181  $content = $textbox_content; // do not try to merge here!
2182  } elseif ( $this->isConflict ) {
2183  # Attempt merge
2184  if ( $this->mergeChangesIntoContent( $content ) ) {
2185  // Successful merge! Maybe we should tell the user the good news?
2186  $this->isConflict = false;
2187  wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
2188  } else {
2189  $this->section = '';
2190  $this->textbox1 = ContentHandler::getContentText( $content );
2191  wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
2192  }
2193  }
2194 
2195  if ( $this->isConflict ) {
2196  $status->setResult( false, self::AS_CONFLICT_DETECTED );
2197  return $status;
2198  }
2199 
2200  if ( !$this->runPostMergeFilters( $content, $status, $user ) ) {
2201  return $status;
2202  }
2203 
2204  if ( $this->section == 'new' ) {
2205  // Handle the user preference to force summaries here
2206  if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) {
2207  $this->missingSummary = true;
2208  $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
2210  return $status;
2211  }
2212 
2213  // Do not allow the user to post an empty comment
2214  if ( $this->textbox1 == '' ) {
2215  $this->missingComment = true;
2216  $status->fatal( 'missingcommenttext' );
2218  return $status;
2219  }
2220  } elseif ( !$this->allowBlankSummary
2221  && !$content->equals( $this->getOriginalContent( $user ) )
2222  && !$content->isRedirect()
2223  && md5( $this->summary ) == $this->autoSumm
2224  ) {
2225  $this->missingSummary = true;
2226  $status->fatal( 'missingsummary' );
2228  return $status;
2229  }
2230 
2231  # All's well
2232  $sectionanchor = '';
2233  if ( $this->section == 'new' ) {
2234  $this->summary = $this->newSectionSummary( $sectionanchor );
2235  } elseif ( $this->section != '' ) {
2236  # Try to get a section anchor from the section source, redirect
2237  # to edited section if header found.
2238  # XXX: Might be better to integrate this into Article::replaceSectionAtRev
2239  # for duplicate heading checking and maybe parsing.
2240  $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
2241  # We can't deal with anchors, includes, html etc in the header for now,
2242  # headline would need to be parsed to improve this.
2243  if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
2244  $sectionanchor = $this->guessSectionName( $matches[2] );
2245  }
2246  }
2247  $result['sectionanchor'] = $sectionanchor;
2248 
2249  // Save errors may fall down to the edit form, but we've now
2250  // merged the section into full text. Clear the section field
2251  // so that later submission of conflict forms won't try to
2252  // replace that into a duplicated mess.
2253  $this->textbox1 = $this->toEditText( $content );
2254  $this->section = '';
2255 
2257  }
2258 
2259  if ( !$this->allowSelfRedirect
2260  && $content->isRedirect()
2261  && $content->getRedirectTarget()->equals( $this->getTitle() )
2262  ) {
2263  // If the page already redirects to itself, don't warn.
2264  $currentTarget = $this->getCurrentContent()->getRedirectTarget();
2265  if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
2266  $this->selfRedirect = true;
2267  $status->fatal( 'selfredirect' );
2269  return $status;
2270  }
2271  }
2272 
2273  // Check for length errors again now that the section is merged in
2274  $this->contentLength = strlen( $this->toEditText( $content ) );
2275  if ( $this->contentLength > $maxArticleSize * 1024 ) {
2276  $this->tooBig = true;
2277  $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
2278  return $status;
2279  }
2280 
2281  $flags = EDIT_AUTOSUMMARY |
2282  ( $new ? EDIT_NEW : EDIT_UPDATE ) |
2283  ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
2284  ( $bot ? EDIT_FORCE_BOT : 0 );
2285 
2286  $doEditStatus = $this->page->doEditContent(
2287  $content,
2288  $this->summary,
2289  $flags,
2290  false,
2291  $user,
2292  $content->getDefaultFormat(),
2295  );
2296 
2297  if ( !$doEditStatus->isOK() ) {
2298  // Failure from doEdit()
2299  // Show the edit conflict page for certain recognized errors from doEdit(),
2300  // but don't show it for errors from extension hooks
2301  $errors = $doEditStatus->getErrorsArray();
2302  if ( in_array( $errors[0][0],
2303  [ 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ] )
2304  ) {
2305  $this->isConflict = true;
2306  // Destroys data doEdit() put in $status->value but who cares
2307  $doEditStatus->value = self::AS_END;
2308  }
2309  return $doEditStatus;
2310  }
2311 
2312  $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
2313  if ( $result['nullEdit'] ) {
2314  // We don't know if it was a null edit until now, so increment here
2315  $user->pingLimiter( 'linkpurge' );
2316  }
2317  $result['redirect'] = $content->isRedirect();
2318 
2319  $this->updateWatchlist();
2320 
2321  // If the content model changed, add a log entry
2322  if ( $changingContentModel ) {
2324  $user,
2325  $new ? false : $oldContentModel,
2326  $this->contentModel,
2327  $this->summary
2328  );
2329  }
2330 
2331  return $status;
2332  }
2333 
2340  protected function addContentModelChangeLogEntry( User $user, $oldModel, $newModel, $reason ) {
2341  $new = $oldModel === false;
2342  $log = new ManualLogEntry( 'contentmodel', $new ? 'new' : 'change' );
2343  $log->setPerformer( $user );
2344  $log->setTarget( $this->mTitle );
2345  $log->setComment( $reason );
2346  $log->setParameters( [
2347  '4::oldmodel' => $oldModel,
2348  '5::newmodel' => $newModel
2349  ] );
2350  $logid = $log->insert();
2351  $log->publish( $logid );
2352  }
2353 
2357  protected function updateWatchlist() {
2358  $user = $this->context->getUser();
2359  if ( !$user->isLoggedIn() ) {
2360  return;
2361  }
2362 
2364  $watch = $this->watchthis;
2365  // Do this in its own transaction to reduce contention...
2366  DeferredUpdates::addCallableUpdate( function () use ( $user, $title, $watch ) {
2367  if ( $watch == $user->isWatched( $title, User::IGNORE_USER_RIGHTS ) ) {
2368  return; // nothing to change
2369  }
2371  } );
2372  }
2373 
2385  private function mergeChangesIntoContent( &$editContent ) {
2386  $db = wfGetDB( DB_MASTER );
2387 
2388  // This is the revision that was current at the time editing was initiated on the client,
2389  // even if the edit was based on an old revision.
2390  $baseRevision = $this->getBaseRevision();
2391  $baseContent = $baseRevision ? $baseRevision->getContent() : null;
2392 
2393  if ( is_null( $baseContent ) ) {
2394  return false;
2395  }
2396 
2397  // The current state, we want to merge updates into it
2398  $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
2399  $currentContent = $currentRevision ? $currentRevision->getContent() : null;
2400 
2401  if ( is_null( $currentContent ) ) {
2402  return false;
2403  }
2404 
2405  $handler = ContentHandler::getForModelID( $baseContent->getModel() );
2406 
2407  $result = $handler->merge3( $baseContent, $editContent, $currentContent );
2408 
2409  if ( $result ) {
2410  $editContent = $result;
2411  // Update parentRevId to what we just merged.
2412  $this->parentRevId = $currentRevision->getId();
2413  return true;
2414  }
2415 
2416  return false;
2417  }
2418 
2431  public function getBaseRevision() {
2432  if ( !$this->mBaseRevision ) {
2433  $db = wfGetDB( DB_MASTER );
2434  $this->mBaseRevision = $this->editRevId
2435  ? Revision::newFromId( $this->editRevId, Revision::READ_LATEST )
2436  : Revision::loadFromTimestamp( $db, $this->mTitle, $this->edittime );
2437  }
2438  return $this->mBaseRevision;
2439  }
2440 
2448  public static function matchSpamRegex( $text ) {
2449  global $wgSpamRegex;
2450  // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
2451  $regexes = (array)$wgSpamRegex;
2452  return self::matchSpamRegexInternal( $text, $regexes );
2453  }
2454 
2462  public static function matchSummarySpamRegex( $text ) {
2463  global $wgSummarySpamRegex;
2464  $regexes = (array)$wgSummarySpamRegex;
2465  return self::matchSpamRegexInternal( $text, $regexes );
2466  }
2467 
2473  protected static function matchSpamRegexInternal( $text, $regexes ) {
2474  foreach ( $regexes as $regex ) {
2475  $matches = [];
2476  if ( preg_match( $regex, $text, $matches ) ) {
2477  return $matches[0];
2478  }
2479  }
2480  return false;
2481  }
2482 
2483  public function setHeaders() {
2484  $out = $this->context->getOutput();
2485 
2486  $out->addModules( 'mediawiki.action.edit' );
2487  $out->addModuleStyles( 'mediawiki.action.edit.styles' );
2488  $out->addModuleStyles( 'mediawiki.editfont.styles' );
2489 
2490  $user = $this->context->getUser();
2491 
2492  if ( $user->getOption( 'uselivepreview' ) ) {
2493  $out->addModules( 'mediawiki.action.edit.preview' );
2494  }
2495 
2496  if ( $user->getOption( 'useeditwarning' ) ) {
2497  $out->addModules( 'mediawiki.action.edit.editWarning' );
2498  }
2499 
2500  # Enabled article-related sidebar, toplinks, etc.
2501  $out->setArticleRelated( true );
2502 
2503  $contextTitle = $this->getContextTitle();
2504  if ( $this->isConflict ) {
2505  $msg = 'editconflict';
2506  } elseif ( $contextTitle->exists() && $this->section != '' ) {
2507  $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
2508  } else {
2509  $msg = $contextTitle->exists()
2510  || ( $contextTitle->getNamespace() == NS_MEDIAWIKI
2511  && $contextTitle->getDefaultMessageText() !== false
2512  )
2513  ? 'editing'
2514  : 'creating';
2515  }
2516 
2517  # Use the title defined by DISPLAYTITLE magic word when present
2518  # NOTE: getDisplayTitle() returns HTML while getPrefixedText() returns plain text.
2519  # setPageTitle() treats the input as wikitext, which should be safe in either case.
2520  $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
2521  if ( $displayTitle === false ) {
2522  $displayTitle = $contextTitle->getPrefixedText();
2523  } else {
2524  $out->setDisplayTitle( $displayTitle );
2525  }
2526  $out->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
2527 
2528  $config = $this->context->getConfig();
2529 
2530  # Transmit the name of the message to JavaScript for live preview
2531  # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
2532  $out->addJsConfigVars( [
2533  'wgEditMessage' => $msg,
2534  'wgAjaxEditStash' => $config->get( 'AjaxEditStash' ),
2535  ] );
2536 
2537  // Add whether to use 'save' or 'publish' messages to JavaScript for post-edit, other
2538  // editors, etc.
2539  $out->addJsConfigVars(
2540  'wgEditSubmitButtonLabelPublish',
2541  $config->get( 'EditSubmitButtonLabelPublish' )
2542  );
2543  }
2544 
2548  protected function showIntro() {
2549  if ( $this->suppressIntro ) {
2550  return;
2551  }
2552 
2553  $out = $this->context->getOutput();
2554  $namespace = $this->mTitle->getNamespace();
2555 
2556  if ( $namespace == NS_MEDIAWIKI ) {
2557  # Show a warning if editing an interface message
2558  $out->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
2559  # If this is a default message (but not css, json, or js),
2560  # show a hint that it is translatable on translatewiki.net
2561  if (
2562  !$this->mTitle->hasContentModel( CONTENT_MODEL_CSS )
2563  && !$this->mTitle->hasContentModel( CONTENT_MODEL_JSON )
2564  && !$this->mTitle->hasContentModel( CONTENT_MODEL_JAVASCRIPT )
2565  ) {
2566  $defaultMessageText = $this->mTitle->getDefaultMessageText();
2567  if ( $defaultMessageText !== false ) {
2568  $out->wrapWikiMsg( "<div class='mw-translateinterface'>\n$1\n</div>",
2569  'translateinterface' );
2570  }
2571  }
2572  } elseif ( $namespace == NS_FILE ) {
2573  # Show a hint to shared repo
2574  $file = wfFindFile( $this->mTitle );
2575  if ( $file && !$file->isLocal() ) {
2576  $descUrl = $file->getDescriptionUrl();
2577  # there must be a description url to show a hint to shared repo
2578  if ( $descUrl ) {
2579  if ( !$this->mTitle->exists() ) {
2580  $out->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
2581  'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
2582  ] );
2583  } else {
2584  $out->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
2585  'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
2586  ] );
2587  }
2588  }
2589  }
2590  }
2591 
2592  # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2593  # Show log extract when the user is currently blocked
2594  if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
2595  $username = explode( '/', $this->mTitle->getText(), 2 )[0];
2596  $user = User::newFromName( $username, false /* allow IP users */ );
2597  $ip = User::isIP( $username );
2598  $block = Block::newFromTarget( $user, $user );
2599  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
2600  $out->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2601  [ 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ] );
2602  } elseif (
2603  !is_null( $block ) &&
2604  $block->getType() != Block::TYPE_AUTO &&
2605  ( $block->isSitewide() || $user->isBlockedFrom( $this->mTitle ) )
2606  ) {
2607  // Show log extract if the user is sitewide blocked or is partially
2608  // blocked and not allowed to edit their user page or user talk page
2610  $out,
2611  'block',
2612  MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
2613  '',
2614  [
2615  'lim' => 1,
2616  'showIfEmpty' => false,
2617  'msgKey' => [
2618  'blocked-notice-logextract',
2619  $user->getName() # Support GENDER in notice
2620  ]
2621  ]
2622  );
2623  }
2624  }
2625  # Try to add a custom edit intro, or use the standard one if this is not possible.
2626  if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
2628  $this->context->msg( 'helppage' )->inContentLanguage()->text()
2629  ) );
2630  if ( $this->context->getUser()->isLoggedIn() ) {
2631  $out->wrapWikiMsg(
2632  // Suppress the external link icon, consider the help url an internal one
2633  "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2634  [
2635  'newarticletext',
2636  $helpLink
2637  ]
2638  );
2639  } else {
2640  $out->wrapWikiMsg(
2641  // Suppress the external link icon, consider the help url an internal one
2642  "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2643  [
2644  'newarticletextanon',
2645  $helpLink
2646  ]
2647  );
2648  }
2649  }
2650  # Give a notice if the user is editing a deleted/moved page...
2651  if ( !$this->mTitle->exists() ) {
2652  $dbr = wfGetDB( DB_REPLICA );
2653 
2654  LogEventsList::showLogExtract( $out, [ 'delete', 'move' ], $this->mTitle,
2655  '',
2656  [
2657  'lim' => 10,
2658  'conds' => [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ],
2659  'showIfEmpty' => false,
2660  'msgKey' => [ 'recreate-moveddeleted-warn' ]
2661  ]
2662  );
2663  }
2664  }
2665 
2671  protected function showCustomIntro() {
2672  if ( $this->editintro ) {
2673  $title = Title::newFromText( $this->editintro );
2674  if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
2675  // Added using template syntax, to take <noinclude>'s into account.
2676  $this->context->getOutput()->addWikiTextAsContent(
2677  '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>',
2678  /*linestart*/true,
2680  );
2681  return true;
2682  }
2683  }
2684  return false;
2685  }
2686 
2705  protected function toEditText( $content ) {
2706  if ( $content === null || $content === false || is_string( $content ) ) {
2707  return $content;
2708  }
2709 
2710  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2711  throw new MWException( 'This content model is not supported: ' . $content->getModel() );
2712  }
2713 
2714  return $content->serialize( $this->contentFormat );
2715  }
2716 
2733  protected function toEditContent( $text ) {
2734  if ( $text === false || $text === null ) {
2735  return $text;
2736  }
2737 
2738  $content = ContentHandler::makeContent( $text, $this->getTitle(),
2739  $this->contentModel, $this->contentFormat );
2740 
2741  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2742  throw new MWException( 'This content model is not supported: ' . $content->getModel() );
2743  }
2744 
2745  return $content;
2746  }
2747 
2756  public function showEditForm( $formCallback = null ) {
2757  # need to parse the preview early so that we know which templates are used,
2758  # otherwise users with "show preview after edit box" will get a blank list
2759  # we parse this near the beginning so that setHeaders can do the title
2760  # setting work instead of leaving it in getPreviewText
2761  $previewOutput = '';
2762  if ( $this->formtype == 'preview' ) {
2763  $previewOutput = $this->getPreviewText();
2764  }
2765 
2766  $out = $this->context->getOutput();
2767 
2768  // Avoid PHP 7.1 warning of passing $this by reference
2769  $editPage = $this;
2770  Hooks::run( 'EditPage::showEditForm:initial', [ &$editPage, &$out ] );
2771 
2772  $this->setHeaders();
2773 
2774  $this->addTalkPageText();
2775  $this->addEditNotices();
2776 
2777  if ( !$this->isConflict &&
2778  $this->section != '' &&
2779  !$this->isSectionEditSupported() ) {
2780  // We use $this->section to much before this and getVal('wgSection') directly in other places
2781  // at this point we can't reset $this->section to '' to fallback to non-section editing.
2782  // Someone is welcome to try refactoring though
2783  $out->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
2784  return;
2785  }
2786 
2787  $this->showHeader();
2788 
2789  $out->addHTML( $this->editFormPageTop );
2790 
2791  $user = $this->context->getUser();
2792  if ( $user->getOption( 'previewontop' ) ) {
2793  $this->displayPreviewArea( $previewOutput, true );
2794  }
2795 
2796  $out->addHTML( $this->editFormTextTop );
2797 
2798  if ( $this->wasDeletedSinceLastEdit() && $this->formtype !== 'save' ) {
2799  $out->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2800  'deletedwhileediting' );
2801  }
2802 
2803  // @todo add EditForm plugin interface and use it here!
2804  // search for textarea1 and textarea2, and allow EditForm to override all uses.
2805  $out->addHTML( Html::openElement(
2806  'form',
2807  [
2808  'class' => 'mw-editform',
2809  'id' => self::EDITFORM_ID,
2810  'name' => self::EDITFORM_ID,
2811  'method' => 'post',
2812  'action' => $this->getActionURL( $this->getContextTitle() ),
2813  'enctype' => 'multipart/form-data'
2814  ]
2815  ) );
2816 
2817  if ( is_callable( $formCallback ) ) {
2818  wfWarn( 'The $formCallback parameter to ' . __METHOD__ . 'is deprecated' );
2819  call_user_func_array( $formCallback, [ &$out ] );
2820  }
2821 
2822  // Add a check for Unicode support
2823  $out->addHTML( Html::hidden( 'wpUnicodeCheck', self::UNICODE_CHECK ) );
2824 
2825  // Add an empty field to trip up spambots
2826  $out->addHTML(
2827  Xml::openElement( 'div', [ 'id' => 'antispam-container', 'style' => 'display: none;' ] )
2828  . Html::rawElement(
2829  'label',
2830  [ 'for' => 'wpAntispam' ],
2831  $this->context->msg( 'simpleantispam-label' )->parse()
2832  )
2833  . Xml::element(
2834  'input',
2835  [
2836  'type' => 'text',
2837  'name' => 'wpAntispam',
2838  'id' => 'wpAntispam',
2839  'value' => ''
2840  ]
2841  )
2842  . Xml::closeElement( 'div' )
2843  );
2844 
2845  // Avoid PHP 7.1 warning of passing $this by reference
2846  $editPage = $this;
2847  Hooks::run( 'EditPage::showEditForm:fields', [ &$editPage, &$out ] );
2848 
2849  // Put these up at the top to ensure they aren't lost on early form submission
2850  $this->showFormBeforeText();
2851 
2852  if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
2853  $username = $this->lastDelete->user_name;
2854  $comment = CommentStore::getStore()
2855  ->getComment( 'log_comment', $this->lastDelete )->text;
2856 
2857  // It is better to not parse the comment at all than to have templates expanded in the middle
2858  // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
2859  $key = $comment === ''
2860  ? 'confirmrecreate-noreason'
2861  : 'confirmrecreate';
2862  $out->addHTML(
2863  '<div class="mw-confirm-recreate">' .
2864  $this->context->msg( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
2865  Xml::checkLabel( $this->context->msg( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
2866  [ 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' ]
2867  ) .
2868  '</div>'
2869  );
2870  }
2871 
2872  # When the summary is hidden, also hide them on preview/show changes
2873  if ( $this->nosummary ) {
2874  $out->addHTML( Html::hidden( 'nosummary', true ) );
2875  }
2876 
2877  # If a blank edit summary was previously provided, and the appropriate
2878  # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2879  # user being bounced back more than once in the event that a summary
2880  # is not required.
2881  # ####
2882  # For a bit more sophisticated detection of blank summaries, hash the
2883  # automatic one and pass that in the hidden field wpAutoSummary.
2884  if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
2885  $out->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
2886  }
2887 
2888  if ( $this->undidRev ) {
2889  $out->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
2890  }
2891 
2892  if ( $this->selfRedirect ) {
2893  $out->addHTML( Html::hidden( 'wpIgnoreSelfRedirect', true ) );
2894  }
2895 
2896  if ( $this->hasPresetSummary ) {
2897  // If a summary has been preset using &summary= we don't want to prompt for
2898  // a different summary. Only prompt for a summary if the summary is blanked.
2899  // (T19416)
2900  $this->autoSumm = md5( '' );
2901  }
2902 
2903  $autosumm = $this->autoSumm !== '' ? $this->autoSumm : md5( $this->summary );
2904  $out->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
2905 
2906  $out->addHTML( Html::hidden( 'oldid', $this->oldid ) );
2907  $out->addHTML( Html::hidden( 'parentRevId', $this->getParentRevId() ) );
2908 
2909  $out->addHTML( Html::hidden( 'format', $this->contentFormat ) );
2910  $out->addHTML( Html::hidden( 'model', $this->contentModel ) );
2911 
2912  $out->enableOOUI();
2913 
2914  if ( $this->section == 'new' ) {
2915  $this->showSummaryInput( true, $this->summary );
2916  $out->addHTML( $this->getSummaryPreview( true, $this->summary ) );
2917  }
2918 
2919  $out->addHTML( $this->editFormTextBeforeContent );
2920  if ( $this->isConflict ) {
2921  // In an edit conflict, we turn textbox2 into the user's text,
2922  // and textbox1 into the stored version
2923  $this->textbox2 = $this->textbox1;
2924 
2925  $content = $this->getCurrentContent();
2926  $this->textbox1 = $this->toEditText( $content );
2927 
2929  $editConflictHelper->setTextboxes( $this->textbox2, $this->textbox1 );
2930  $editConflictHelper->setContentModel( $this->contentModel );
2931  $editConflictHelper->setContentFormat( $this->contentFormat );
2933  }
2934 
2935  if ( !$this->mTitle->isUserConfigPage() ) {
2936  $out->addHTML( self::getEditToolbar( $this->mTitle ) );
2937  }
2938 
2939  if ( $this->blankArticle ) {
2940  $out->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) );
2941  }
2942 
2943  if ( $this->isConflict ) {
2944  // In an edit conflict bypass the overridable content form method
2945  // and fallback to the raw wpTextbox1 since editconflicts can't be
2946  // resolved between page source edits and custom ui edits using the
2947  // custom edit ui.
2948  $conflictTextBoxAttribs = [];
2949  if ( $this->wasDeletedSinceLastEdit() ) {
2950  $conflictTextBoxAttribs['style'] = 'display:none;';
2951  } elseif ( $this->isOldRev ) {
2952  $conflictTextBoxAttribs['class'] = 'mw-textarea-oldrev';
2953  }
2954 
2955  $out->addHTML( $editConflictHelper->getEditConflictMainTextBox( $conflictTextBoxAttribs ) );
2957  } else {
2958  $this->showContentForm();
2959  }
2960 
2961  $out->addHTML( $this->editFormTextAfterContent );
2962 
2963  $this->showStandardInputs();
2964 
2965  $this->showFormAfterText();
2966 
2967  $this->showTosSummary();
2968 
2969  $this->showEditTools();
2970 
2971  $out->addHTML( $this->editFormTextAfterTools . "\n" );
2972 
2973  $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
2974 
2975  $out->addHTML( Html::rawElement( 'div', [ 'class' => 'hiddencats' ],
2976  Linker::formatHiddenCategories( $this->page->getHiddenCategories() ) ) );
2977 
2978  $out->addHTML( Html::rawElement( 'div', [ 'class' => 'limitreport' ],
2979  self::getPreviewLimitReport( $this->mParserOutput ) ) );
2980 
2981  $out->addModules( 'mediawiki.action.edit.collapsibleFooter' );
2982 
2983  if ( $this->isConflict ) {
2984  try {
2985  $this->showConflict();
2986  } catch ( MWContentSerializationException $ex ) {
2987  // this can't really happen, but be nice if it does.
2988  $msg = $this->context->msg(
2989  'content-failed-to-parse',
2990  $this->contentModel,
2991  $this->contentFormat,
2992  $ex->getMessage()
2993  );
2994  $out->wrapWikiTextAsInterface( 'error', $msg->plain() );
2995  }
2996  }
2997 
2998  // Set a hidden field so JS knows what edit form mode we are in
2999  if ( $this->isConflict ) {
3000  $mode = 'conflict';
3001  } elseif ( $this->preview ) {
3002  $mode = 'preview';
3003  } elseif ( $this->diff ) {
3004  $mode = 'diff';
3005  } else {
3006  $mode = 'text';
3007  }
3008  $out->addHTML( Html::hidden( 'mode', $mode, [ 'id' => 'mw-edit-mode' ] ) );
3009 
3010  // Marker for detecting truncated form data. This must be the last
3011  // parameter sent in order to be of use, so do not move me.
3012  $out->addHTML( Html::hidden( 'wpUltimateParam', true ) );
3013  $out->addHTML( $this->editFormTextBottom . "\n</form>\n" );
3014 
3015  if ( !$user->getOption( 'previewontop' ) ) {
3016  $this->displayPreviewArea( $previewOutput, false );
3017  }
3018  }
3019 
3027  public function makeTemplatesOnThisPageList( array $templates ) {
3028  $templateListFormatter = new TemplatesOnThisPageFormatter(
3029  $this->context, MediaWikiServices::getInstance()->getLinkRenderer()
3030  );
3031 
3032  // preview if preview, else section if section, else false
3033  $type = false;
3034  if ( $this->preview ) {
3035  $type = 'preview';
3036  } elseif ( $this->section != '' ) {
3037  $type = 'section';
3038  }
3039 
3040  return Html::rawElement( 'div', [ 'class' => 'templatesUsed' ],
3041  $templateListFormatter->format( $templates, $type )
3042  );
3043  }
3044 
3051  public static function extractSectionTitle( $text ) {
3052  preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
3053  if ( !empty( $matches[2] ) ) {
3054  global $wgParser;
3055  return $wgParser->stripSectionName( trim( $matches[2] ) );
3056  } else {
3057  return false;
3058  }
3059  }
3060 
3061  protected function showHeader() {
3062  $out = $this->context->getOutput();
3063  $user = $this->context->getUser();
3064  if ( $this->isConflict ) {
3065  $this->addExplainConflictHeader( $out );
3066  $this->editRevId = $this->page->getLatest();
3067  } else {
3068  if ( $this->section != '' && $this->section != 'new' && !$this->summary &&
3069  !$this->preview && !$this->diff
3070  ) {
3071  $sectionTitle = self::extractSectionTitle( $this->textbox1 ); // FIXME: use Content object
3072  if ( $sectionTitle !== false ) {
3073  $this->summary = "/* $sectionTitle */ ";
3074  }
3075  }
3076 
3077  $buttonLabel = $this->context->msg( $this->getSubmitButtonLabel() )->text();
3078 
3079  if ( $this->missingComment ) {
3080  $out->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
3081  }
3082 
3083  if ( $this->missingSummary && $this->section != 'new' ) {
3084  $out->wrapWikiMsg(
3085  "<div id='mw-missingsummary'>\n$1\n</div>",
3086  [ 'missingsummary', $buttonLabel ]
3087  );
3088  }
3089 
3090  if ( $this->missingSummary && $this->section == 'new' ) {
3091  $out->wrapWikiMsg(
3092  "<div id='mw-missingcommentheader'>\n$1\n</div>",
3093  [ 'missingcommentheader', $buttonLabel ]
3094  );
3095  }
3096 
3097  if ( $this->blankArticle ) {
3098  $out->wrapWikiMsg(
3099  "<div id='mw-blankarticle'>\n$1\n</div>",
3100  [ 'blankarticle', $buttonLabel ]
3101  );
3102  }
3103 
3104  if ( $this->selfRedirect ) {
3105  $out->wrapWikiMsg(
3106  "<div id='mw-selfredirect'>\n$1\n</div>",
3107  [ 'selfredirect', $buttonLabel ]
3108  );
3109  }
3110 
3111  if ( $this->hookError !== '' ) {
3112  $out->addWikiTextAsInterface( $this->hookError );
3113  }
3114 
3115  if ( $this->section != 'new' ) {
3116  $revision = $this->mArticle->getRevisionFetched();
3117  if ( $revision ) {
3118  // Let sysop know that this will make private content public if saved
3119 
3120  if ( !$revision->userCan( Revision::DELETED_TEXT, $user ) ) {
3121  $out->wrapWikiMsg(
3122  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
3123  'rev-deleted-text-permission'
3124  );
3125  } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
3126  $out->wrapWikiMsg(
3127  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
3128  'rev-deleted-text-view'
3129  );
3130  }
3131 
3132  if ( !$revision->isCurrent() ) {
3133  $this->mArticle->setOldSubtitle( $revision->getId() );
3134  $out->wrapWikiMsg(
3135  Html::warningBox( "\n$1\n" ),
3136  'editingold'
3137  );
3138  $this->isOldRev = true;
3139  }
3140  } elseif ( $this->mTitle->exists() ) {
3141  // Something went wrong
3142 
3143  $out->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
3144  [ 'missing-revision', $this->oldid ] );
3145  }
3146  }
3147  }
3148 
3149  if ( wfReadOnly() ) {
3150  $out->wrapWikiMsg(
3151  "<div id=\"mw-read-only-warning\">\n$1\n</div>",
3152  [ 'readonlywarning', wfReadOnlyReason() ]
3153  );
3154  } elseif ( $user->isAnon() ) {
3155  if ( $this->formtype != 'preview' ) {
3156  $returntoquery = array_diff_key(
3157  $this->context->getRequest()->getValues(),
3158  [ 'title' => true, 'returnto' => true, 'returntoquery' => true ]
3159  );
3160  $out->wrapWikiMsg(
3161  "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
3162  [ 'anoneditwarning',
3163  // Log-in link
3164  SpecialPage::getTitleFor( 'Userlogin' )->getFullURL( [
3165  'returnto' => $this->getTitle()->getPrefixedDBkey(),
3166  'returntoquery' => wfArrayToCgi( $returntoquery ),
3167  ] ),
3168  // Sign-up link
3169  SpecialPage::getTitleFor( 'CreateAccount' )->getFullURL( [
3170  'returnto' => $this->getTitle()->getPrefixedDBkey(),
3171  'returntoquery' => wfArrayToCgi( $returntoquery ),
3172  ] )
3173  ]
3174  );
3175  } else {
3176  $out->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
3177  'anonpreviewwarning'
3178  );
3179  }
3180  } elseif ( $this->mTitle->isUserConfigPage() ) {
3181  # Check the skin exists
3182  if ( $this->isWrongCaseUserConfigPage() ) {
3183  $out->wrapWikiMsg(
3184  "<div class='error' id='mw-userinvalidconfigtitle'>\n$1\n</div>",
3185  [ 'userinvalidconfigtitle', $this->mTitle->getSkinFromConfigSubpage() ]
3186  );
3187  }
3188  if ( $this->getTitle()->isSubpageOf( $user->getUserPage() ) ) {
3189  $isUserCssConfig = $this->mTitle->isUserCssConfigPage();
3190  $isUserJsonConfig = $this->mTitle->isUserJsonConfigPage();
3191  $isUserJsConfig = $this->mTitle->isUserJsConfigPage();
3192 
3193  $warning = $isUserCssConfig
3194  ? 'usercssispublic'
3195  : ( $isUserJsonConfig ? 'userjsonispublic' : 'userjsispublic' );
3196 
3197  $out->wrapWikiMsg( '<div class="mw-userconfigpublic">$1</div>', $warning );
3198 
3199  if ( $this->formtype !== 'preview' ) {
3200  $config = $this->context->getConfig();
3201  if ( $isUserCssConfig && $config->get( 'AllowUserCss' ) ) {
3202  $out->wrapWikiMsg(
3203  "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
3204  [ 'usercssyoucanpreview' ]
3205  );
3206  } elseif ( $isUserJsonConfig /* No comparable 'AllowUserJson' */ ) {
3207  $out->wrapWikiMsg(
3208  "<div id='mw-userjsonyoucanpreview'>\n$1\n</div>",
3209  [ 'userjsonyoucanpreview' ]
3210  );
3211  } elseif ( $isUserJsConfig && $config->get( 'AllowUserJs' ) ) {
3212  $out->wrapWikiMsg(
3213  "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
3214  [ 'userjsyoucanpreview' ]
3215  );
3216  }
3217  }
3218  }
3219  }
3220 
3222 
3223  $this->addLongPageWarningHeader();
3224 
3225  # Add header copyright warning
3226  $this->showHeaderCopyrightWarning();
3227  }
3228 
3236  private function getSummaryInputAttributes( array $inputAttrs = null ) {
3237  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
3238  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
3239  // Unicode codepoints.
3240  return ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
3241  'id' => 'wpSummary',
3242  'name' => 'wpSummary',
3244  'tabindex' => 1,
3245  'size' => 60,
3246  'spellcheck' => 'true',
3247  ];
3248  }
3249 
3259  function getSummaryInputWidget( $summary = "", $labelText = null, $inputAttrs = null ) {
3260  $inputAttrs = OOUI\Element::configFromHtmlAttributes(
3261  $this->getSummaryInputAttributes( $inputAttrs )
3262  );
3263  $inputAttrs += [
3264  'title' => Linker::titleAttrib( 'summary' ),
3265  'accessKey' => Linker::accesskey( 'summary' ),
3266  ];
3267 
3268  // For compatibility with old scripts and extensions, we want the legacy 'id' on the `<input>`
3269  $inputAttrs['inputId'] = $inputAttrs['id'];
3270  $inputAttrs['id'] = 'wpSummaryWidget';
3271 
3272  return new OOUI\FieldLayout(
3273  new OOUI\TextInputWidget( [
3274  'value' => $summary,
3275  'infusable' => true,
3276  ] + $inputAttrs ),
3277  [
3278  'label' => new OOUI\HtmlSnippet( $labelText ),
3279  'align' => 'top',
3280  'id' => 'wpSummaryLabel',
3281  'classes' => [ $this->missingSummary ? 'mw-summarymissed' : 'mw-summary' ],
3282  ]
3283  );
3284  }
3285 
3292  protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
3293  # Add a class if 'missingsummary' is triggered to allow styling of the summary line
3294  $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
3295  if ( $isSubjectPreview ) {
3296  if ( $this->nosummary ) {
3297  return;
3298  }
3299  } elseif ( !$this->mShowSummaryField ) {
3300  return;
3301  }
3302 
3303  $labelText = $this->context->msg( $isSubjectPreview ? 'subject' : 'summary' )->parse();
3304  $this->context->getOutput()->addHTML( $this->getSummaryInputWidget(
3305  $summary,
3306  $labelText,
3307  [ 'class' => $summaryClass ]
3308  ) );
3309  }
3310 
3318  protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
3319  // avoid spaces in preview, gets always trimmed on save
3320  $summary = trim( $summary );
3321  if ( !$summary || ( !$this->preview && !$this->diff ) ) {
3322  return "";
3323  }
3324 
3325  global $wgParser;
3326 
3327  if ( $isSubjectPreview ) {
3328  $summary = $this->context->msg( 'newsectionsummary' )
3329  ->rawParams( $wgParser->stripSectionName( $summary ) )
3330  ->inContentLanguage()->text();
3331  }
3332 
3333  $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
3334 
3335  $summary = $this->context->msg( $message )->parse()
3336  . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
3337  return Xml::tags( 'div', [ 'class' => 'mw-summary-preview' ], $summary );
3338  }
3339 
3340  protected function showFormBeforeText() {
3341  $out = $this->context->getOutput();
3342  $out->addHTML( Html::hidden( 'wpSection', $this->section ) );
3343  $out->addHTML( Html::hidden( 'wpStarttime', $this->starttime ) );
3344  $out->addHTML( Html::hidden( 'wpEdittime', $this->edittime ) );
3345  $out->addHTML( Html::hidden( 'editRevId', $this->editRevId ) );
3346  $out->addHTML( Html::hidden( 'wpScrolltop', $this->scrolltop, [ 'id' => 'wpScrolltop' ] ) );
3347  }
3348 
3349  protected function showFormAfterText() {
3362  $this->context->getOutput()->addHTML(
3363  "\n" .
3364  Html::hidden( "wpEditToken", $this->context->getUser()->getEditToken() ) .
3365  "\n"
3366  );
3367  }
3368 
3377  protected function showContentForm() {
3378  $this->showTextbox1();
3379  }
3380 
3389  protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
3390  if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
3391  $attribs = [ 'style' => 'display:none;' ];
3392  } else {
3393  $builder = new TextboxBuilder();
3394  $classes = $builder->getTextboxProtectionCSSClasses( $this->getTitle() );
3395 
3396  # Is an old revision being edited?
3397  if ( $this->isOldRev ) {
3398  $classes[] = 'mw-textarea-oldrev';
3399  }
3400 
3401  $attribs = [ 'tabindex' => 1 ];
3402 
3403  if ( is_array( $customAttribs ) ) {
3405  }
3406 
3407  $attribs = $builder->mergeClassesIntoAttributes( $classes, $attribs );
3408  }
3409 
3410  $this->showTextbox(
3411  $textoverride ?? $this->textbox1,
3412  'wpTextbox1',
3413  $attribs
3414  );
3415  }
3416 
3417  protected function showTextbox2() {
3418  $this->showTextbox( $this->textbox2, 'wpTextbox2', [ 'tabindex' => 6, 'readonly' ] );
3419  }
3420 
3421  protected function showTextbox( $text, $name, $customAttribs = [] ) {
3422  $builder = new TextboxBuilder();
3423  $attribs = $builder->buildTextboxAttribs(
3424  $name,
3426  $this->context->getUser(),
3428  );
3429 
3430  $this->context->getOutput()->addHTML(
3431  Html::textarea( $name, $builder->addNewLineAtEnd( $text ), $attribs )
3432  );
3433  }
3434 
3435  protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
3436  $classes = [];
3437  if ( $isOnTop ) {
3438  $classes[] = 'ontop';
3439  }
3440 
3441  $attribs = [ 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) ];
3442 
3443  if ( $this->formtype != 'preview' ) {
3444  $attribs['style'] = 'display: none;';
3445  }
3446 
3447  $out = $this->context->getOutput();
3448  $out->addHTML( Xml::openElement( 'div', $attribs ) );
3449 
3450  if ( $this->formtype == 'preview' ) {
3451  $this->showPreview( $previewOutput );
3452  } else {
3453  // Empty content container for LivePreview
3454  $pageViewLang = $this->mTitle->getPageViewLanguage();
3455  $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3456  'class' => 'mw-content-' . $pageViewLang->getDir() ];
3457  $out->addHTML( Html::rawElement( 'div', $attribs ) );
3458  }
3459 
3460  $out->addHTML( '</div>' );
3461 
3462  if ( $this->formtype == 'diff' ) {
3463  try {
3464  $this->showDiff();
3465  } catch ( MWContentSerializationException $ex ) {
3466  $msg = $this->context->msg(
3467  'content-failed-to-parse',
3468  $this->contentModel,
3469  $this->contentFormat,
3470  $ex->getMessage()
3471  );
3472  $out->wrapWikiTextAsInterface( 'error', $msg->plain() );
3473  }
3474  }
3475  }
3476 
3483  protected function showPreview( $text ) {
3484  if ( $this->mArticle instanceof CategoryPage ) {
3485  $this->mArticle->openShowCategory();
3486  }
3487  # This hook seems slightly odd here, but makes things more
3488  # consistent for extensions.
3489  $out = $this->context->getOutput();
3490  Hooks::run( 'OutputPageBeforeHTML', [ &$out, &$text ] );
3491  $out->addHTML( $text );
3492  if ( $this->mArticle instanceof CategoryPage ) {
3493  $this->mArticle->closeShowCategory();
3494  }
3495  }
3496 
3504  public function showDiff() {
3505  $oldtitlemsg = 'currentrev';
3506  # if message does not exist, show diff against the preloaded default
3507  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
3508  $oldtext = $this->mTitle->getDefaultMessageText();
3509  if ( $oldtext !== false ) {
3510  $oldtitlemsg = 'defaultmessagetext';
3511  $oldContent = $this->toEditContent( $oldtext );
3512  } else {
3513  $oldContent = null;
3514  }
3515  } else {
3516  $oldContent = $this->getCurrentContent();
3517  }
3518 
3519  $textboxContent = $this->toEditContent( $this->textbox1 );
3520  if ( $this->editRevId !== null ) {
3521  $newContent = $this->page->replaceSectionAtRev(
3522  $this->section, $textboxContent, $this->summary, $this->editRevId
3523  );
3524  } else {
3525  $newContent = $this->page->replaceSectionContent(
3526  $this->section, $textboxContent, $this->summary, $this->edittime
3527  );
3528  }
3529 
3530  if ( $newContent ) {
3531  Hooks::run( 'EditPageGetDiffContent', [ $this, &$newContent ] );
3532 
3533  $user = $this->context->getUser();
3535  MediaWikiServices::getInstance()->getContentLanguage() );
3536  $newContent = $newContent->preSaveTransform( $this->mTitle, $user, $popts );
3537  }
3538 
3539  if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
3540  $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
3541  $newtitle = $this->context->msg( 'yourtext' )->parse();
3542 
3543  if ( !$oldContent ) {
3544  $oldContent = $newContent->getContentHandler()->makeEmptyContent();
3545  }
3546 
3547  if ( !$newContent ) {
3548  $newContent = $oldContent->getContentHandler()->makeEmptyContent();
3549  }
3550 
3551  $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->context );
3552  $de->setContent( $oldContent, $newContent );
3553 
3554  $difftext = $de->getDiff( $oldtitle, $newtitle );
3555  $de->showDiffStyle();
3556  } else {
3557  $difftext = '';
3558  }
3559 
3560  $this->context->getOutput()->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
3561  }
3562 
3566  protected function showHeaderCopyrightWarning() {
3567  $msg = 'editpage-head-copy-warn';
3568  if ( !$this->context->msg( $msg )->isDisabled() ) {
3569  $this->context->getOutput()->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
3570  'editpage-head-copy-warn' );
3571  }
3572  }
3573 
3582  protected function showTosSummary() {
3583  $msg = 'editpage-tos-summary';
3584  Hooks::run( 'EditPageTosSummary', [ $this->mTitle, &$msg ] );
3585  if ( !$this->context->msg( $msg )->isDisabled() ) {
3586  $out = $this->context->getOutput();
3587  $out->addHTML( '<div class="mw-tos-summary">' );
3588  $out->addWikiMsg( $msg );
3589  $out->addHTML( '</div>' );
3590  }
3591  }
3592 
3597  protected function showEditTools() {
3598  $this->context->getOutput()->addHTML( '<div class="mw-editTools">' .
3599  $this->context->msg( 'edittools' )->inContentLanguage()->parse() .
3600  '</div>' );
3601  }
3602 
3609  protected function getCopywarn() {
3610  return self::getCopyrightWarning( $this->mTitle );
3611  }
3612 
3621  public static function getCopyrightWarning( $title, $format = 'plain', $langcode = null ) {
3622  global $wgRightsText;
3623  if ( $wgRightsText ) {
3624  $copywarnMsg = [ 'copyrightwarning',
3625  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]',
3626  $wgRightsText ];
3627  } else {
3628  $copywarnMsg = [ 'copyrightwarning2',
3629  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' ];
3630  }
3631  // Allow for site and per-namespace customization of contribution/copyright notice.
3632  Hooks::run( 'EditPageCopyrightWarning', [ $title, &$copywarnMsg ] );
3633 
3634  $msg = wfMessage( ...$copywarnMsg )->title( $title );
3635  if ( $langcode ) {
3636  $msg->inLanguage( $langcode );
3637  }
3638  return "<div id=\"editpage-copywarn\">\n" .
3639  $msg->$format() . "\n</div>";
3640  }
3641 
3649  public static function getPreviewLimitReport( ParserOutput $output = null ) {
3650  global $wgLang;
3651 
3652  if ( !$output || !$output->getLimitReportData() ) {
3653  return '';
3654  }
3655 
3656  $limitReport = Html::rawElement( 'div', [ 'class' => 'mw-limitReportExplanation' ],
3657  wfMessage( 'limitreport-title' )->parseAsBlock()
3658  );
3659 
3660  // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
3661  $limitReport .= Html::openElement( 'div', [ 'class' => 'preview-limit-report-wrapper' ] );
3662 
3663  $limitReport .= Html::openElement( 'table', [
3664  'class' => 'preview-limit-report wikitable'
3665  ] ) .
3666  Html::openElement( 'tbody' );
3667 
3668  foreach ( $output->getLimitReportData() as $key => $value ) {
3669  if ( Hooks::run( 'ParserLimitReportFormat',
3670  [ $key, &$value, &$limitReport, true, true ]
3671  ) ) {
3672  $keyMsg = wfMessage( $key );
3673  $valueMsg = wfMessage( [ "$key-value-html", "$key-value" ] );
3674  if ( !$valueMsg->exists() ) {
3675  $valueMsg = new RawMessage( '$1' );
3676  }
3677  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3678  $limitReport .= Html::openElement( 'tr' ) .
3679  Html::rawElement( 'th', null, $keyMsg->parse() ) .
3680  Html::rawElement( 'td', null,
3681  $wgLang->formatNum( $valueMsg->params( $value )->parse() )
3682  ) .
3683  Html::closeElement( 'tr' );
3684  }
3685  }
3686  }
3687 
3688  $limitReport .= Html::closeElement( 'tbody' ) .
3689  Html::closeElement( 'table' ) .
3690  Html::closeElement( 'div' );
3691 
3692  return $limitReport;
3693  }
3694 
3695  protected function showStandardInputs( &$tabindex = 2 ) {
3696  $out = $this->context->getOutput();
3697  $out->addHTML( "<div class='editOptions'>\n" );
3698 
3699  if ( $this->section != 'new' ) {
3700  $this->showSummaryInput( false, $this->summary );
3701  $out->addHTML( $this->getSummaryPreview( false, $this->summary ) );
3702  }
3703 
3704  $checkboxes = $this->getCheckboxesWidget(
3705  $tabindex,
3706  [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ]
3707  );
3708  $checkboxesHTML = new OOUI\HorizontalLayout( [ 'items' => $checkboxes ] );
3709 
3710  $out->addHTML( "<div class='editCheckboxes'>" . $checkboxesHTML . "</div>\n" );
3711 
3712  // Show copyright warning.
3713  $out->addWikiTextAsInterface( $this->getCopywarn() );
3714  $out->addHTML( $this->editFormTextAfterWarn );
3715 
3716  $out->addHTML( "<div class='editButtons'>\n" );
3717  $out->addHTML( implode( "\n", $this->getEditButtons( $tabindex ) ) . "\n" );
3718 
3719  $cancel = $this->getCancelLink();
3720 
3721  $message = $this->context->msg( 'edithelppage' )->inContentLanguage()->text();
3722  $edithelpurl = Skin::makeInternalOrExternalUrl( $message );
3723  $edithelp =
3724  Html::linkButton(
3725  $this->context->msg( 'edithelp' )->text(),
3726  [ 'target' => 'helpwindow', 'href' => $edithelpurl ],
3727  [ 'mw-ui-quiet' ]
3728  ) .
3729  $this->context->msg( 'word-separator' )->escaped() .
3730  $this->context->msg( 'newwindow' )->parse();
3731 
3732  $out->addHTML( " <span class='cancelLink'>{$cancel}</span>\n" );
3733  $out->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" );
3734  $out->addHTML( "</div><!-- editButtons -->\n" );
3735 
3736  Hooks::run( 'EditPage::showStandardInputs:options', [ $this, $out, &$tabindex ] );
3737 
3738  $out->addHTML( "</div><!-- editOptions -->\n" );
3739  }
3740 
3745  protected function showConflict() {
3746  $out = $this->context->getOutput();
3747  // Avoid PHP 7.1 warning of passing $this by reference
3748  $editPage = $this;
3749  if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$editPage, &$out ] ) ) {
3750  $this->incrementConflictStats();
3751 
3752  $this->getEditConflictHelper()->showEditFormTextAfterFooters();
3753  }
3754  }
3755 
3756  protected function incrementConflictStats() {
3757  $this->getEditConflictHelper()->incrementConflictStats();
3758  }
3759 
3763  public function getCancelLink() {
3764  $cancelParams = [];
3765  if ( !$this->isConflict && $this->oldid > 0 ) {
3766  $cancelParams['oldid'] = $this->oldid;
3767  } elseif ( $this->getContextTitle()->isRedirect() ) {
3768  $cancelParams['redirect'] = 'no';
3769  }
3770 
3771  return new OOUI\ButtonWidget( [
3772  'id' => 'mw-editform-cancel',
3773  'href' => $this->getContextTitle()->getLinkURL( $cancelParams ),
3774  'label' => new OOUI\HtmlSnippet( $this->context->msg( 'cancel' )->parse() ),
3775  'framed' => false,
3776  'infusable' => true,
3777  'flags' => 'destructive',
3778  ] );
3779  }
3780 
3790  protected function getActionURL( Title $title ) {
3791  return $title->getLocalURL( [ 'action' => $this->action ] );
3792  }
3793 
3801  protected function wasDeletedSinceLastEdit() {
3802  if ( $this->deletedSinceEdit !== null ) {
3803  return $this->deletedSinceEdit;
3804  }
3805 
3806  $this->deletedSinceEdit = false;
3807 
3808  if ( !$this->mTitle->exists() && $this->mTitle->isDeletedQuick() ) {
3809  $this->lastDelete = $this->getLastDelete();
3810  if ( $this->lastDelete ) {
3811  $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
3812  if ( $deleteTime > $this->starttime ) {
3813  $this->deletedSinceEdit = true;
3814  }
3815  }
3816  }
3817 
3818  return $this->deletedSinceEdit;
3819  }
3820 
3826  protected function getLastDelete() {
3827  $dbr = wfGetDB( DB_REPLICA );
3828  $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
3829  $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
3830  $data = $dbr->selectRow(
3831  array_merge( [ 'logging' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ] ),
3832  [
3833  'log_type',
3834  'log_action',
3835  'log_timestamp',
3836  'log_namespace',
3837  'log_title',
3838  'log_params',
3839  'log_deleted',
3840  'user_name'
3841  ] + $commentQuery['fields'] + $actorQuery['fields'],
3842  [
3843  'log_namespace' => $this->mTitle->getNamespace(),
3844  'log_title' => $this->mTitle->getDBkey(),
3845  'log_type' => 'delete',
3846  'log_action' => 'delete',
3847  ],
3848  __METHOD__,
3849  [ 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ],
3850  [
3851  'user' => [ 'JOIN', 'user_id=' . $actorQuery['fields']['log_user'] ],
3852  ] + $commentQuery['joins'] + $actorQuery['joins']
3853  );
3854  // Quick paranoid permission checks...
3855  if ( is_object( $data ) ) {
3856  if ( $data->log_deleted & LogPage::DELETED_USER ) {
3857  $data->user_name = $this->context->msg( 'rev-deleted-user' )->escaped();
3858  }
3859 
3860  if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
3861  $data->log_comment_text = $this->context->msg( 'rev-deleted-comment' )->escaped();
3862  $data->log_comment_data = null;
3863  }
3864  }
3865 
3866  return $data;
3867  }
3868 
3874  public function getPreviewText() {
3875  $out = $this->context->getOutput();
3876  $config = $this->context->getConfig();
3877 
3878  if ( $config->get( 'RawHtml' ) && !$this->mTokenOk ) {
3879  // Could be an offsite preview attempt. This is very unsafe if
3880  // HTML is enabled, as it could be an attack.
3881  $parsedNote = '';
3882  if ( $this->textbox1 !== '' ) {
3883  // Do not put big scary notice, if previewing the empty
3884  // string, which happens when you initially edit
3885  // a category page, due to automatic preview-on-open.
3886  $parsedNote = Html::rawElement( 'div', [ 'class' => 'previewnote' ],
3887  $out->parseAsInterface(
3888  $this->context->msg( 'session_fail_preview_html' )->plain()
3889  ) );
3890  }
3891  $this->incrementEditFailureStats( 'session_loss' );
3892  return $parsedNote;
3893  }
3894 
3895  $note = '';
3896 
3897  try {
3898  $content = $this->toEditContent( $this->textbox1 );
3899 
3900  $previewHTML = '';
3901  if ( !Hooks::run(
3902  'AlternateEditPreview',
3903  [ $this, &$content, &$previewHTML, &$this->mParserOutput ] )
3904  ) {
3905  return $previewHTML;
3906  }
3907 
3908  # provide a anchor link to the editform
3909  $continueEditing = '<span class="mw-continue-editing">' .
3910  '[[#' . self::EDITFORM_ID . '|' .
3911  $this->context->getLanguage()->getArrow() . ' ' .
3912  $this->context->msg( 'continue-editing' )->text() . ']]</span>';
3913  if ( $this->mTriedSave && !$this->mTokenOk ) {
3914  if ( $this->mTokenOkExceptSuffix ) {
3915  $note = $this->context->msg( 'token_suffix_mismatch' )->plain();
3916  $this->incrementEditFailureStats( 'bad_token' );
3917  } else {
3918  $note = $this->context->msg( 'session_fail_preview' )->plain();
3919  $this->incrementEditFailureStats( 'session_loss' );
3920  }
3921  } elseif ( $this->incompleteForm ) {
3922  $note = $this->context->msg( 'edit_form_incomplete' )->plain();
3923  if ( $this->mTriedSave ) {
3924  $this->incrementEditFailureStats( 'incomplete_form' );
3925  }
3926  } else {
3927  $note = $this->context->msg( 'previewnote' )->plain() . ' ' . $continueEditing;
3928  }
3929 
3930  # don't parse non-wikitext pages, show message about preview
3931  if ( $this->mTitle->isUserConfigPage() || $this->mTitle->isSiteConfigPage() ) {
3932  if ( $this->mTitle->isUserConfigPage() ) {
3933  $level = 'user';
3934  } elseif ( $this->mTitle->isSiteConfigPage() ) {
3935  $level = 'site';
3936  } else {
3937  $level = false;
3938  }
3939 
3940  if ( $content->getModel() == CONTENT_MODEL_CSS ) {
3941  $format = 'css';
3942  if ( $level === 'user' && !$config->get( 'AllowUserCss' ) ) {
3943  $format = false;
3944  }
3945  } elseif ( $content->getModel() == CONTENT_MODEL_JSON ) {
3946  $format = 'json';
3947  if ( $level === 'user' /* No comparable 'AllowUserJson' */ ) {
3948  $format = false;
3949  }
3950  } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
3951  $format = 'js';
3952  if ( $level === 'user' && !$config->get( 'AllowUserJs' ) ) {
3953  $format = false;
3954  }
3955  } else {
3956  $format = false;
3957  }
3958 
3959  # Used messages to make sure grep find them:
3960  # Messages: usercsspreview, userjsonpreview, userjspreview,
3961  # sitecsspreview, sitejsonpreview, sitejspreview
3962  if ( $level && $format ) {
3963  $note = "<div id='mw-{$level}{$format}preview'>" .
3964  $this->context->msg( "{$level}{$format}preview" )->plain() .
3965  ' ' . $continueEditing . "</div>";
3966  }
3967  }
3968 
3969  # If we're adding a comment, we need to show the
3970  # summary as the headline
3971  if ( $this->section === "new" && $this->summary !== "" ) {
3972  $content = $content->addSectionHeader( $this->summary );
3973  }
3974 
3975  $hook_args = [ $this, &$content ];
3976  Hooks::run( 'EditPageGetPreviewContent', $hook_args );
3977 
3978  $parserResult = $this->doPreviewParse( $content );
3979  $parserOutput = $parserResult['parserOutput'];
3980  $previewHTML = $parserResult['html'];
3981  $this->mParserOutput = $parserOutput;
3982  $out->addParserOutputMetadata( $parserOutput );
3983  if ( $out->userCanPreview() ) {
3984  $out->addContentOverride( $this->getTitle(), $content );
3985  }
3986 
3987  if ( count( $parserOutput->getWarnings() ) ) {
3988  $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
3989  }
3990 
3991  } catch ( MWContentSerializationException $ex ) {
3992  $m = $this->context->msg(
3993  'content-failed-to-parse',
3994  $this->contentModel,
3995  $this->contentFormat,
3996  $ex->getMessage()
3997  );
3998  $note .= "\n\n" . $m->plain(); # gets parsed down below
3999  $previewHTML = '';
4000  }
4001 
4002  if ( $this->isConflict ) {
4003  $conflict = Html::rawElement(
4004  'h2', [ 'id' => 'mw-previewconflict' ],
4005  $this->context->msg( 'previewconflict' )->escaped()
4006  );
4007  } else {
4008  $conflict = '<hr />';
4009  }
4010 
4011  $previewhead = Html::rawElement(
4012  'div', [ 'class' => 'previewnote' ],
4013  Html::rawElement(
4014  'h2', [ 'id' => 'mw-previewheader' ],
4015  $this->context->msg( 'preview' )->escaped()
4016  ) .
4017  $out->parseAsInterface( $note ) . $conflict
4018  );
4019 
4020  $pageViewLang = $this->mTitle->getPageViewLanguage();
4021  $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
4022  'class' => 'mw-content-' . $pageViewLang->getDir() ];
4023  $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
4024 
4025  return $previewhead . $previewHTML . $this->previewTextAfterContent;
4026  }
4027 
4028  private function incrementEditFailureStats( $failureType ) {
4029  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
4030  $stats->increment( 'edit.failures.' . $failureType );
4031  }
4032 
4037  protected function getPreviewParserOptions() {
4038  $parserOptions = $this->page->makeParserOptions( $this->context );
4039  $parserOptions->setIsPreview( true );
4040  $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
4041  $parserOptions->enableLimitReport();
4042 
4043  // XXX: we could call $parserOptions->setCurrentRevisionCallback here to force the
4044  // current revision to be null during PST, until setupFakeRevision is called on
4045  // the ParserOptions. Currently, we rely on Parser::getRevisionObject() to ignore
4046  // existing revisions in preview mode.
4047 
4048  return $parserOptions;
4049  }
4050 
4060  protected function doPreviewParse( Content $content ) {
4061  $user = $this->context->getUser();
4062  $parserOptions = $this->getPreviewParserOptions();
4063 
4064  // NOTE: preSaveTransform doesn't have a fake revision to operate on.
4065  // Parser::getRevisionObject() will return null in preview mode,
4066  // causing the context user to be used for {{subst:REVISIONUSER}}.
4067  // XXX: Alternatively, we could also call setupFakeRevision() a second time:
4068  // once before PST with $content, and then after PST with $pstContent.
4069  $pstContent = $content->preSaveTransform( $this->mTitle, $user, $parserOptions );
4070  $scopedCallback = $parserOptions->setupFakeRevision( $this->mTitle, $pstContent, $user );
4071  $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions );
4072  ScopedCallback::consume( $scopedCallback );
4073  return [
4074  'parserOutput' => $parserOutput,
4075  'html' => $parserOutput->getText( [
4076  'enableSectionEditLinks' => false
4077  ] )
4078  ];
4079  }
4080 
4084  public function getTemplates() {
4085  if ( $this->preview || $this->section != '' ) {
4086  $templates = [];
4087  if ( !isset( $this->mParserOutput ) ) {
4088  return $templates;
4089  }
4090  foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
4091  foreach ( array_keys( $template ) as $dbk ) {
4092  $templates[] = Title::makeTitle( $ns, $dbk );
4093  }
4094  }
4095  return $templates;
4096  } else {
4097  return $this->mTitle->getTemplateLinksFrom();
4098  }
4099  }
4100 
4107  public static function getEditToolbar( $title = null ) {
4108  $startingToolbar = '<div id="toolbar"></div>';
4109  $toolbar = $startingToolbar;
4110 
4111  if ( !Hooks::run( 'EditPageBeforeEditToolbar', [ &$toolbar ] ) ) {
4112  return null;
4113  };
4114  // Don't add a pointless `<div>` to the page unless a hook caller populated it
4115  return ( $toolbar === $startingToolbar ) ? null : $toolbar;
4116  }
4117 
4136  public function getCheckboxesDefinition( $checked ) {
4137  $checkboxes = [];
4138 
4139  $user = $this->context->getUser();
4140  // don't show the minor edit checkbox if it's a new page or section
4141  if ( !$this->isNew && $user->isAllowed( 'minoredit' ) ) {
4142  $checkboxes['wpMinoredit'] = [
4143  'id' => 'wpMinoredit',
4144  'label-message' => 'minoredit',
4145  // Uses messages: tooltip-minoredit, accesskey-minoredit
4146  'tooltip' => 'minoredit',
4147  'label-id' => 'mw-editpage-minoredit',
4148  'legacy-name' => 'minor',
4149  'default' => $checked['minor'],
4150  ];
4151  }
4152 
4153  if ( $user->isLoggedIn() ) {
4154  $checkboxes['wpWatchthis'] = [
4155  'id' => 'wpWatchthis',
4156  'label-message' => 'watchthis',
4157  // Uses messages: tooltip-watch, accesskey-watch
4158  'tooltip' => 'watch',
4159  'label-id' => 'mw-editpage-watch',
4160  'legacy-name' => 'watch',
4161  'default' => $checked['watch'],
4162  ];
4163  }
4164 
4165  $editPage = $this;
4166  Hooks::run( 'EditPageGetCheckboxesDefinition', [ $editPage, &$checkboxes ] );
4167 
4168  return $checkboxes;
4169  }
4170 
4181  public function getCheckboxesWidget( &$tabindex, $checked ) {
4182  $checkboxes = [];
4183  $checkboxesDef = $this->getCheckboxesDefinition( $checked );
4184 
4185  foreach ( $checkboxesDef as $name => $options ) {
4186  $legacyName = $options['legacy-name'] ?? $name;
4187 
4188  $title = null;
4189  $accesskey = null;
4190  if ( isset( $options['tooltip'] ) ) {
4191  $accesskey = $this->context->msg( "accesskey-{$options['tooltip']}" )->text();
4192  $title = Linker::titleAttrib( $options['tooltip'] );
4193  }
4194  if ( isset( $options['title-message'] ) ) {
4195  $title = $this->context->msg( $options['title-message'] )->text();
4196  }
4197 
4198  $checkboxes[ $legacyName ] = new OOUI\FieldLayout(
4199  new OOUI\CheckboxInputWidget( [
4200  'tabIndex' => ++$tabindex,
4201  'accessKey' => $accesskey,
4202  'id' => $options['id'] . 'Widget',
4203  'inputId' => $options['id'],
4204  'name' => $name,
4205  'selected' => $options['default'],
4206  'infusable' => true,
4207  ] ),
4208  [
4209  'align' => 'inline',
4210  'label' => new OOUI\HtmlSnippet( $this->context->msg( $options['label-message'] )->parse() ),
4211  'title' => $title,
4212  'id' => $options['label-id'] ?? null,
4213  ]
4214  );
4215  }
4216 
4217  return $checkboxes;
4218  }
4219 
4226  protected function getSubmitButtonLabel() {
4227  $labelAsPublish =
4228  $this->context->getConfig()->get( 'EditSubmitButtonLabelPublish' );
4229 
4230  // Can't use $this->isNew as that's also true if we're adding a new section to an extant page
4231  $newPage = !$this->mTitle->exists();
4232 
4233  if ( $labelAsPublish ) {
4234  $buttonLabelKey = $newPage ? 'publishpage' : 'publishchanges';
4235  } else {
4236  $buttonLabelKey = $newPage ? 'savearticle' : 'savechanges';
4237  }
4238 
4239  return $buttonLabelKey;
4240  }
4241 
4250  public function getEditButtons( &$tabindex ) {
4251  $buttons = [];
4252 
4253  $labelAsPublish =
4254  $this->context->getConfig()->get( 'EditSubmitButtonLabelPublish' );
4255 
4256  $buttonLabel = $this->context->msg( $this->getSubmitButtonLabel() )->text();
4257  $buttonTooltip = $labelAsPublish ? 'publish' : 'save';
4258 
4259  $buttons['save'] = new OOUI\ButtonInputWidget( [
4260  'name' => 'wpSave',
4261  'tabIndex' => ++$tabindex,
4262  'id' => 'wpSaveWidget',
4263  'inputId' => 'wpSave',
4264  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4265  'useInputTag' => true,
4266  'flags' => [ 'progressive', 'primary' ],
4267  'label' => $buttonLabel,
4268  'infusable' => true,
4269  'type' => 'submit',
4270  // Messages used: tooltip-save, tooltip-publish
4271  'title' => Linker::titleAttrib( $buttonTooltip ),
4272  // Messages used: accesskey-save, accesskey-publish
4273  'accessKey' => Linker::accesskey( $buttonTooltip ),
4274  ] );
4275 
4276  $buttons['preview'] = new OOUI\ButtonInputWidget( [
4277  'name' => 'wpPreview',
4278  'tabIndex' => ++$tabindex,
4279  'id' => 'wpPreviewWidget',
4280  'inputId' => 'wpPreview',
4281  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4282  'useInputTag' => true,
4283  'label' => $this->context->msg( 'showpreview' )->text(),
4284  'infusable' => true,
4285  'type' => 'submit',
4286  // Message used: tooltip-preview
4287  'title' => Linker::titleAttrib( 'preview' ),
4288  // Message used: accesskey-preview
4289  'accessKey' => Linker::accesskey( 'preview' ),
4290  ] );
4291 
4292  $buttons['diff'] = new OOUI\ButtonInputWidget( [
4293  'name' => 'wpDiff',
4294  'tabIndex' => ++$tabindex,
4295  'id' => 'wpDiffWidget',
4296  'inputId' => 'wpDiff',
4297  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4298  'useInputTag' => true,
4299  'label' => $this->context->msg( 'showdiff' )->text(),
4300  'infusable' => true,
4301  'type' => 'submit',
4302  // Message used: tooltip-diff
4303  'title' => Linker::titleAttrib( 'diff' ),
4304  // Message used: accesskey-diff
4305  'accessKey' => Linker::accesskey( 'diff' ),
4306  ] );
4307 
4308  // Avoid PHP 7.1 warning of passing $this by reference
4309  $editPage = $this;
4310  Hooks::run( 'EditPageBeforeEditButtons', [ &$editPage, &$buttons, &$tabindex ] );
4311 
4312  return $buttons;
4313  }
4314 
4319  public function noSuchSectionPage() {
4320  $out = $this->context->getOutput();
4321  $out->prepareErrorPage( $this->context->msg( 'nosuchsectiontitle' ) );
4322 
4323  $res = $this->context->msg( 'nosuchsectiontext', $this->section )->parseAsBlock();
4324 
4325  // Avoid PHP 7.1 warning of passing $this by reference
4326  $editPage = $this;
4327  Hooks::run( 'EditPageNoSuchSection', [ &$editPage, &$res ] );
4328  $out->addHTML( $res );
4329 
4330  $out->returnToMain( false, $this->mTitle );
4331  }
4332 
4338  public function spamPageWithContent( $match = false ) {
4339  $this->textbox2 = $this->textbox1;
4340 
4341  if ( is_array( $match ) ) {
4342  $match = $this->context->getLanguage()->listToText( $match );
4343  }
4344  $out = $this->context->getOutput();
4345  $out->prepareErrorPage( $this->context->msg( 'spamprotectiontitle' ) );
4346 
4347  $out->addHTML( '<div id="spamprotected">' );
4348  $out->addWikiMsg( 'spamprotectiontext' );
4349  if ( $match ) {
4350  $out->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
4351  }
4352  $out->addHTML( '</div>' );
4353 
4354  $out->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
4355  $this->showDiff();
4356 
4357  $out->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
4358  $this->showTextbox2();
4359 
4360  $out->addReturnTo( $this->getContextTitle(), [ 'action' => 'edit' ] );
4361  }
4362 
4373  protected function safeUnicodeInput( $request, $field ) {
4374  return rtrim( $request->getText( $field ) );
4375  }
4376 
4386  protected function safeUnicodeOutput( $text ) {
4387  return $text;
4388  }
4389 
4393  protected function addEditNotices() {
4394  $out = $this->context->getOutput();
4395  $editNotices = $this->mTitle->getEditNotices( $this->oldid );
4396  if ( count( $editNotices ) ) {
4397  $out->addHTML( implode( "\n", $editNotices ) );
4398  } else {
4399  $msg = $this->context->msg( 'editnotice-notext' );
4400  if ( !$msg->isDisabled() ) {
4401  $out->addHTML(
4402  '<div class="mw-editnotice-notext">'
4403  . $msg->parseAsBlock()
4404  . '</div>'
4405  );
4406  }
4407  }
4408  }
4409 
4413  protected function addTalkPageText() {
4414  if ( $this->mTitle->isTalkPage() ) {
4415  $this->context->getOutput()->addWikiMsg( 'talkpagetext' );
4416  }
4417  }
4418 
4422  protected function addLongPageWarningHeader() {
4423  if ( $this->contentLength === false ) {
4424  $this->contentLength = strlen( $this->textbox1 );
4425  }
4426 
4427  $out = $this->context->getOutput();
4428  $lang = $this->context->getLanguage();
4429  $maxArticleSize = $this->context->getConfig()->get( 'MaxArticleSize' );
4430  if ( $this->tooBig || $this->contentLength > $maxArticleSize * 1024 ) {
4431  $out->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
4432  [
4433  'longpageerror',
4434  $lang->formatNum( round( $this->contentLength / 1024, 3 ) ),
4435  $lang->formatNum( $maxArticleSize )
4436  ]
4437  );
4438  } elseif ( !$this->context->msg( 'longpage-hint' )->isDisabled() ) {
4439  $out->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
4440  [
4441  'longpage-hint',
4442  $lang->formatSize( strlen( $this->textbox1 ) ),
4443  strlen( $this->textbox1 )
4444  ]
4445  );
4446  }
4447  }
4448 
4452  protected function addPageProtectionWarningHeaders() {
4453  $out = $this->context->getOutput();
4454  if ( $this->mTitle->isProtected( 'edit' ) &&
4455  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [ '' ]
4456  ) {
4457  # Is the title semi-protected?
4458  if ( $this->mTitle->isSemiProtected() ) {
4459  $noticeMsg = 'semiprotectedpagewarning';
4460  } else {
4461  # Then it must be protected based on static groups (regular)
4462  $noticeMsg = 'protectedpagewarning';
4463  }
4464  LogEventsList::showLogExtract( $out, 'protect', $this->mTitle, '',
4465  [ 'lim' => 1, 'msgKey' => [ $noticeMsg ] ] );
4466  }
4467  if ( $this->mTitle->isCascadeProtected() ) {
4468  # Is this page under cascading protection from some source pages?
4469 
4470  list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
4471  $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
4472  $cascadeSourcesCount = count( $cascadeSources );
4473  if ( $cascadeSourcesCount > 0 ) {
4474  # Explain, and list the titles responsible
4475  foreach ( $cascadeSources as $page ) {
4476  $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
4477  }
4478  }
4479  $notice .= '</div>';
4480  $out->wrapWikiMsg( $notice, [ 'cascadeprotectedwarning', $cascadeSourcesCount ] );
4481  }
4482  if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
4483  LogEventsList::showLogExtract( $out, 'protect', $this->mTitle, '',
4484  [ 'lim' => 1,
4485  'showIfEmpty' => false,
4486  'msgKey' => [ 'titleprotectedwarning' ],
4487  'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ] );
4488  }
4489  }
4490 
4495  protected function addExplainConflictHeader( OutputPage $out ) {
4496  $out->addHTML(
4497  $this->getEditConflictHelper()->getExplainHeader()
4498  );
4499  }
4500 
4509  return ( new TextboxBuilder() )->buildTextboxAttribs(
4510  $name, $customAttribs, $user, $this->mTitle
4511  );
4512  }
4513 
4519  protected function addNewLineAtEnd( $wikitext ) {
4520  return ( new TextboxBuilder() )->addNewLineAtEnd( $wikitext );
4521  }
4522 
4533  private function guessSectionName( $text ) {
4534  global $wgParser;
4535 
4536  // Detect Microsoft browsers
4537  $userAgent = $this->context->getRequest()->getHeader( 'User-Agent' );
4538  if ( $userAgent && preg_match( '/MSIE|Edge/', $userAgent ) ) {
4539  // ...and redirect them to legacy encoding, if available
4540  return $wgParser->guessLegacySectionNameFromWikiText( $text );
4541  }
4542  // Meanwhile, real browsers get real anchors
4543  $name = $wgParser->guessSectionNameFromWikiText( $text );
4544  // With one little caveat: per T216029, fragments in HTTP redirects need to be urlencoded,
4545  // otherwise Chrome double-escapes the rest of the URL.
4546  return '#' . urlencode( mb_substr( $name, 1 ) );
4547  }
4548 
4555  public function setEditConflictHelperFactory( callable $factory ) {
4556  $this->editConflictHelperFactory = $factory;
4557  $this->editConflictHelper = null;
4558  }
4559 
4563  private function getEditConflictHelper() {
4564  if ( !$this->editConflictHelper ) {
4565  $this->editConflictHelper = call_user_func(
4566  $this->editConflictHelperFactory,
4567  $this->getSubmitButtonLabel()
4568  );
4569  }
4570 
4572  }
4573 
4578  private function newTextConflictHelper( $submitButtonLabel ) {
4579  return new TextConflictHelper(
4580  $this->getTitle(),
4581  $this->getContext()->getOutput(),
4582  MediaWikiServices::getInstance()->getStatsdDataFactory(),
4583  $submitButtonLabel
4584  );
4585  }
4586 }
ReadOnlyError
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
Definition: ReadOnlyError.php:28
EditPage\__construct
__construct(Article $article)
Definition: EditPage.php:475
EditPage\$editFormTextBeforeContent
$editFormTextBeforeContent
Definition: EditPage.php:420
EditPage\$mTriedSave
bool $mTriedSave
Definition: EditPage.php:269
$status
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:1266
CONTENT_MODEL_JSON
const CONTENT_MODEL_JSON
Definition: Defines.php:239
save
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
ContentHandler\getForModelID
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
Definition: ContentHandler.php:252
$user
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1476
$article
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:1476
EditPage\AS_SELF_REDIRECT
const AS_SELF_REDIRECT
Status: user tried to create self-redirect (redirect to the same article) and wpIgnoreSelfRedirect ==...
Definition: EditPage.php:168
EditPage\$contentModel
string $contentModel
Definition: EditPage.php:406
EditPage\showFormBeforeText
showFormBeforeText()
Definition: EditPage.php:3340
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:306
EditPage\internalAttemptSave
internalAttemptSave(&$result, $bot=false)
Attempt submission (no UI)
Definition: EditPage.php:1859
EditPage\$lastDelete
bool stdClass $lastDelete
Definition: EditPage.php:260
EditPage\tokenOk
tokenOk(&$request)
Make sure the form isn't faking a user's credentials.
Definition: EditPage.php:1536
EditPage\$editFormPageTop
string $editFormPageTop
Before even the preview.
Definition: EditPage.php:418
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:40
EditPage\AS_BLANK_ARTICLE
const AS_BLANK_ARTICLE
Status: user tried to create a blank page and wpIgnoreBlankArticle == false.
Definition: EditPage.php:115
EditPage\showContentForm
showContentForm()
Subpage overridable method for printing the form for page content editing By default this simply outp...
Definition: EditPage.php:3377
EditPage\$mTitle
Title $mTitle
Definition: EditPage.php:230
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
EditPage\spamPageWithContent
spamPageWithContent( $match=false)
Show "your edit contains spam" page with your diff and text.
Definition: EditPage.php:4338
EditPage\$section
string $section
Definition: EditPage.php:372
ParserOutput
Definition: ParserOutput.php:25
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
WikiPage\getRedirectTarget
getRedirectTarget()
If this page is a redirect, get its target.
Definition: WikiPage.php:995
UserBlockedError
Show an error when the user tries to do something whilst blocked.
Definition: UserBlockedError.php:27
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:118
EditPage\$scrolltop
null $scrolltop
Definition: EditPage.php:400
content
per default it will return the text for text based content
Definition: contenthandler.txt:104
EditPage\mergeChangesIntoContent
mergeChangesIntoContent(&$editContent)
Attempts to do 3-way merge of edit content with a base revision and current content,...
Definition: EditPage.php:2385
EditPage\displayPermissionsError
displayPermissionsError(array $permErrors)
Display a permissions error page, like OutputPage::showPermissionsErrorPage(), but with the following...
Definition: EditPage.php:763
$wgParser
$wgParser
Definition: Setup.php:886
EditPage\$editFormTextAfterContent
$editFormTextAfterContent
Definition: EditPage.php:424
EditPage\displayPreviewArea
displayPreviewArea( $previewOutput, $isOnTop=false)
Definition: EditPage.php:3435
EditPage\$blankArticle
bool $blankArticle
Definition: EditPage.php:287
EditPage\$allowBlankSummary
bool $allowBlankSummary
Definition: EditPage.php:284
$wgRightsText
$wgRightsText
If either $wgRightsUrl or $wgRightsPage is specified then this variable gives the text for the link.
Definition: DefaultSettings.php:7148
EditPage\$editFormTextBottom
$editFormTextBottom
Definition: EditPage.php:423
EditPage\getSummaryInputAttributes
getSummaryInputAttributes(array $inputAttrs=null)
Helper function for summary input functions, which returns the necessary attributes for the input.
Definition: EditPage.php:3236
EditPage\$editFormTextTop
$editFormTextTop
Definition: EditPage.php:419
EditPage\$editintro
string $editintro
Definition: EditPage.php:397
EditPage\showTextbox2
showTextbox2()
Definition: EditPage.php:3417
EDIT_FORCE_BOT
const EDIT_FORCE_BOT
Definition: Defines.php:156
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
EditPage\$summary
string $summary
Definition: EditPage.php:345
EditPage\AS_READ_ONLY_PAGE_ANON
const AS_READ_ONLY_PAGE_ANON
Status: this anonymous user is not allowed to edit this page.
Definition: EditPage.php:83
EditPage\buildTextboxAttribs
buildTextboxAttribs( $name, array $customAttribs, User $user)
Definition: EditPage.php:4508
captcha-old.count
count
Definition: captcha-old.py:249
EditPage\$textbox2
string $textbox2
Definition: EditPage.php:342
EditPage\getPreloadedContent
getPreloadedContent( $preload, $params=[])
Get the contents to be preloaded into the box, either set by an earlier setPreloadText() or by loadin...
Definition: EditPage.php:1473
EditPage\$mTokenOk
bool $mTokenOk
Definition: EditPage.php:263
CONTENT_MODEL_CSS
const CONTENT_MODEL_CSS
Definition: Defines.php:237
$result
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. '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 '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:1983
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1912
EditPage\$oldid
int $oldid
Revision ID the edit is based on, or 0 if it's the current revision.
Definition: EditPage.php:387
MediaWiki\EditPage\TextboxBuilder
Helps EditPage build textboxes.
Definition: TextboxBuilder.php:37
EditPage\getContextTitle
getContextTitle()
Get the context title object.
Definition: EditPage.php:536
$out
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password 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:780
EditPage\showTosSummary
showTosSummary()
Give a chance for site and per-namespace customizations of terms of service summary link that might e...
Definition: EditPage.php:3582
EditPage\AS_UNICODE_NOT_SUPPORTED
const AS_UNICODE_NOT_SUPPORTED
Status: edit rejected because browser doesn't support Unicode.
Definition: EditPage.php:190
CategoryPage
Special handling for category description pages, showing pages, subcategories and file that belong to...
Definition: CategoryPage.php:28
EditPage\$page
WikiPage $page
Definition: EditPage.php:224
EditPage\$save
bool $save
Definition: EditPage.php:319
DerivativeContext\setWikiPage
setWikiPage(WikiPage $wikiPage)
Definition: DerivativeContext.php:165
EditPage\addPageProtectionWarningHeaders
addPageProtectionWarningHeaders()
Definition: EditPage.php:4452
EditPage\setContextTitle
setContextTitle( $title)
Set the context Title object.
Definition: EditPage.php:524
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:45
EditPage\edit
edit()
This is the function that gets called for "action=edit".
Definition: EditPage.php:587
EditPage\$autoSumm
string $autoSumm
Definition: EditPage.php:299
NS_FILE
const NS_FILE
Definition: Defines.php:70
EditPage\safeUnicodeInput
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:4373
$params
$params
Definition: styleTest.css.php:44
Block\newFromTarget
static newFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target's type, get an existing Block object if possible.
Definition: Block.php:1403
EditPage\getLastDelete
getLastDelete()
Get the last log record of this page being deleted, if ever.
Definition: EditPage.php:3826
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1197
EditPage\incrementConflictStats
incrementConflictStats()
Definition: EditPage.php:3756
EditPage\addEditNotices
addEditNotices()
Definition: EditPage.php:4393
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:585
EditPage\$editFormTextAfterTools
$editFormTextAfterTools
Definition: EditPage.php:422
SpecialPage\getTitleFor
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Definition: SpecialPage.php:82
EditPage\AS_HOOK_ERROR
const AS_HOOK_ERROR
Status: Article update aborted by a hook function.
Definition: EditPage.php:63
page
target page
Definition: All_system_messages.txt:1267
EditPage\$editFormTextAfterWarn
$editFormTextAfterWarn
Definition: EditPage.php:421
ContentHandler\getForTitle
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
Definition: ContentHandler.php:199
diff
also included in $newHeader if any indicating whether we should show just the diff
Definition: hooks.txt:1269
$res
$res
Definition: database.txt:21
EditPage\setPreloadedContent
setPreloadedContent(Content $content)
Use this method before edit() to preload some content into the edit box.
Definition: EditPage.php:1458
EditPage\AS_CONTENT_TOO_BIG
const AS_CONTENT_TOO_BIG
Status: Content too big (> $wgMaxArticleSize)
Definition: EditPage.php:78
$wgTitle
if(! $wgRequest->checkUrlExtension()) if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') $wgTitle
Definition: api.php:57
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:28
MediaWiki\EditPage\TextConflictHelper\setContentFormat
setContentFormat( $contentFormat)
Definition: TextConflictHelper.php:118
EditPage\showHeaderCopyrightWarning
showHeaderCopyrightWarning()
Show the header copyright warning.
Definition: EditPage.php:3566
EditPage\getEditPermissionErrors
getEditPermissionErrors( $rigor='secure')
Definition: EditPage.php:719
EditPage\addLongPageWarningHeader
addLongPageWarningHeader()
Definition: EditPage.php:4422
EditPage\$context
IContextSource $context
Definition: EditPage.php:448
EditPage\$didSave
$didSave
Definition: EditPage.php:429
ContextSource\getTitle
getTitle()
Definition: ContextSource.php:79
EditPage\AS_BLOCKED_PAGE_FOR_USER
const AS_BLOCKED_PAGE_FOR_USER
Status: User is blocked from editing this page.
Definition: EditPage.php:73
EditPage\AS_SUMMARY_NEEDED
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
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:108
EditPage\AS_NO_CREATE_PERMISSION
const AS_NO_CREATE_PERMISSION
Status: user tried to create this page, but is not allowed to do that ( Title->userCan('create') == f...
Definition: EditPage.php:110
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1043
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:111
php
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
Linker\formatHiddenCategories
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition: Linker.php:1930
EditPage\$mArticle
Article $mArticle
Definition: EditPage.php:222
EditPage\$contentFormat
null string $contentFormat
Definition: EditPage.php:409
Skin\getSkinNames
static getSkinNames()
Fetch the set of available skins.
Definition: Skin.php:57
EditPage\POST_EDIT_COOKIE_DURATION
const POST_EDIT_COOKIE_DURATION
Duration of PostEdit cookie, in seconds.
Definition: EditPage.php:216
$dbr
$dbr
Definition: testCompression.php:50
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
EditPage\$editConflictHelper
TextConflictHelper null $editConflictHelper
Definition: EditPage.php:470
Revision\FOR_THIS_USER
const FOR_THIS_USER
Definition: Revision.php:55
Revision
Definition: Revision.php:40
EditPage\$watchthis
bool $watchthis
Definition: EditPage.php:331
EditPage\$previewTextAfterContent
$previewTextAfterContent
Definition: EditPage.php:425
EditPage\showDiff
showDiff()
Get a diff between the current contents of the edit box and the version of the page we're editing fro...
Definition: EditPage.php:3504
$customAttribs
null means default & $customAttribs
Definition: hooks.txt:1985
EditPage\$tooBig
bool $tooBig
Definition: EditPage.php:275
$query
null for the 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:1588
$data
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
Definition: generatePhpCharToUpperMappings.php:13
DerivativeContext
An IContextSource implementation which will inherit context from another source but allow individual ...
Definition: DerivativeContext.php:30
EditPage\AS_SUCCESS_NEW_ARTICLE
const AS_SUCCESS_NEW_ARTICLE
Status: Article successfully created.
Definition: EditPage.php:58
EditPage\UNICODE_CHECK
const UNICODE_CHECK
Used for Unicode support checks.
Definition: EditPage.php:48
MWException
MediaWiki exception.
Definition: MWException.php:26
EditPage\addContentModelChangeLogEntry
addContentModelChangeLogEntry(User $user, $oldModel, $newModel, $reason)
Definition: EditPage.php:2340
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:138
EditPage\toEditContent
toEditContent( $text)
Turns the given text into a Content object by unserializing it.
Definition: EditPage.php:2733
EditPage\getEditButtons
getEditButtons(&$tabindex)
Returns an array of html code of the following buttons: save, diff and preview.
Definition: EditPage.php:4250
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1078
EditPage\AS_END
const AS_END
Status: WikiPage::doEdit() was unsuccessful.
Definition: EditPage.php:141
LogPage\DELETED_COMMENT
const DELETED_COMMENT
Definition: LogPage.php:35
EditPage\$editRevId
int $editRevId
Revision ID of the latest revision of the page when editing was initiated on the client.
Definition: EditPage.php:369
EditPage\showSummaryInput
showSummaryInput( $isSubjectPreview, $summary="")
Definition: EditPage.php:3292
EditPage\getParentRevId
getParentRevId()
Get the edit's parent revision ID.
Definition: EditPage.php:1396
ParserOptions\newFromUserAndLang
static newFromUserAndLang(User $user, Language $lang)
Get a ParserOptions object from a given user and language.
Definition: ParserOptions.php:1031
wfArrayDiff2
wfArrayDiff2( $a, $b)
Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
Definition: GlobalFunctions.php:111
EditPage\isSectionEditSupported
isSectionEditSupported()
Returns whether section editing is supported for the current page.
Definition: EditPage.php:908
EditPage\importFormData
importFormData(&$request)
This function collects the form data and uses it to populate various member variables.
Definition: EditPage.php:918
$handler
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password 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:780
EditPage\getActionURL
getActionURL(Title $title)
Returns the URL to use in the form's action attribute.
Definition: EditPage.php:3790
EditPage\addExplainConflictHeader
addExplainConflictHeader(OutputPage $out)
Definition: EditPage.php:4495
LogPage\DELETED_USER
const DELETED_USER
Definition: LogPage.php:36
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2636
EditPage\showIntro
showIntro()
Show all applicable editing introductions.
Definition: EditPage.php:2548
EditPage\$firsttime
bool $firsttime
True the first time the edit form is rendered, false after re-rendering with diff,...
Definition: EditPage.php:257
$matches
$matches
Definition: NoLocalSettings.php:24
in
null for the wiki Added in
Definition: hooks.txt:1588
EditPage\$missingComment
bool $missingComment
Definition: EditPage.php:278
EditPage\getPreviewLimitReport
static getPreviewLimitReport(ParserOutput $output=null)
Get the Limit report for page previews.
Definition: EditPage.php:3649
EditPage\$editConflictHelperFactory
callable $editConflictHelperFactory
Factory function to create an edit conflict helper.
Definition: EditPage.php:465
$wgLang
$wgLang
Definition: Setup.php:875
below
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two distribute and or modify the software for each author s protection and we want to make certain that everyone understands that there is no warranty for this free software If the software is modified by someone else and passed we want its recipients to know that what they have is not the so that any problems introduced by others will not reflect on the original authors reputations any free program is threatened constantly by software patents We wish to avoid the danger that redistributors of a free program will individually obtain patent in effect making the program proprietary To prevent we have made it clear that any patent must be licensed for everyone s free use or not licensed at all The precise terms and conditions for distribution and modification follow GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR DISTRIBUTION AND MODIFICATION This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License The below
Definition: COPYING.txt:43
$attribs
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:1985
not
if not
Definition: COPYING.txt:307
MWContentSerializationException
Exception representing a failure to serialize or unserialize a content object.
Definition: MWContentSerializationException.php:7
EditPage\attemptSave
attemptSave(&$resultDetails=false)
Attempt submission.
Definition: EditPage.php:1579
WikiPage\getContent
getContent( $audience=Revision::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
Definition: WikiPage.php:816
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
$tabindex
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:1423
Article\getPage
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:230
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:967
EditPage\getArticle
getArticle()
Definition: EditPage.php:499
EditPage\AS_CANNOT_USE_CUSTOM_MODEL
const AS_CANNOT_USE_CUSTOM_MODEL
Status: when changing the content model is disallowed due to $wgContentHandlerUseDB being false.
Definition: EditPage.php:185
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
ThrottledError
Show an error when the user hits a rate limit.
Definition: ThrottledError.php:27
EditPage\AS_NO_CHANGE_CONTENT_MODEL
const AS_NO_CHANGE_CONTENT_MODEL
Status: user tried to modify the content model, but is not allowed to do that ( User::isAllowed('edit...
Definition: EditPage.php:162
$code
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password 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:780
div
div
Definition: parserTests.txt:6850
EditPage\getCheckboxesWidget
getCheckboxesWidget(&$tabindex, $checked)
Returns an array of checkboxes for the edit form, including 'minor' and 'watch' checkboxes and any ot...
Definition: EditPage.php:4181
EditPage\previewOnOpen
previewOnOpen()
Should we show a preview when the edit form is first shown?
Definition: EditPage.php:846
WatchAction\doWatchOrUnwatch
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:89
EditPage\incrementEditFailureStats
incrementEditFailureStats( $failureType)
Definition: EditPage.php:4028
EditPage\AS_READ_ONLY_PAGE
const AS_READ_ONLY_PAGE
Status: wiki is in readonly mode (wfReadOnly() == true)
Definition: EditPage.php:93
EditPage\AS_SPAM_ERROR
const AS_SPAM_ERROR
Status: summary contained spam according to one of the regexes in $wgSummarySpamRegex.
Definition: EditPage.php:146
EditPage\$allowSelfRedirect
bool $allowSelfRedirect
Definition: EditPage.php:296
EditPage\showEditForm
showEditForm( $formCallback=null)
Send the edit form and related headers to OutputPage.
Definition: EditPage.php:2756
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:576
$output
$output
Definition: SyntaxHighlight.php:334
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:627
EditPage\wasDeletedSinceLastEdit
wasDeletedSinceLastEdit()
Check if a page was deleted while the user was editing it, before submit.
Definition: EditPage.php:3801
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
EditPage\getTemplates
getTemplates()
Definition: EditPage.php:4084
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1941
EditPage\getPreviewParserOptions
getPreviewParserOptions()
Get parser options for a preview.
Definition: EditPage.php:4037
EditPage\runPostMergeFilters
runPostMergeFilters(Content $content, Status $status, User $user)
Run hooks that can filter edits just before they get saved.
Definition: EditPage.php:1744
EditPage\AS_MAX_ARTICLE_SIZE_EXCEEDED
const AS_MAX_ARTICLE_SIZE_EXCEEDED
Status: article is too big (> $wgMaxArticleSize), after merging in the new section.
Definition: EditPage.php:136
DB_MASTER
const DB_MASTER
Definition: defines.php:26
EditPage\$mContextTitle
null Title $mContextTitle
Definition: EditPage.php:233
etc
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing etc
Definition: hooks.txt:91
array
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))
MWNamespace\getRestrictionLevels
static getRestrictionLevels( $index, User $user=null)
Determine which restriction levels it makes sense to use in a namespace, optionally filtered by a use...
Definition: MWNamespace.php:489
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:949
EditPage\showFormAfterText
showFormAfterText()
Definition: EditPage.php:3349
EditPage\showPreview
showPreview( $text)
Append preview output to OutputPage.
Definition: EditPage.php:3483
list
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
EditPage\initialiseForm
initialiseForm()
Initialise form fields in the object Called on the first invocation, e.g.
Definition: EditPage.php:1147
EditPage\getEditToolbar
static getEditToolbar( $title=null)
Allow extensions to provide a toolbar.
Definition: EditPage.php:4107
ContentHandler\makeContent
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Definition: ContentHandler.php:133
MediaWiki\EditPage\TextConflictHelper\setTextboxes
setTextboxes( $yourtext, $storedversion)
Definition: TextConflictHelper.php:103
captcha-old.action
action
Definition: captcha-old.py:212
null
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
$request
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:2636
EditPage\safeUnicodeOutput
safeUnicodeOutput( $text)
Filter an output field through a Unicode armoring process if it is going to an old browser with known...
Definition: EditPage.php:4386
MediaWiki\EditPage\TextConflictHelper
Helper for displaying edit conflicts in text content models to users.
Definition: TextConflictHelper.php:40
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
EditPage\matchSpamRegex
static matchSpamRegex( $text)
Check given input text against $wgSpamRegex, and return the text of the first match.
Definition: EditPage.php:2448
EditPage\$recreate
bool $recreate
Definition: EditPage.php:334
Revision\userWasLastToEdit
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:1306
EditPage\$contentLength
bool int $contentLength
Definition: EditPage.php:438
EditPage\AS_SUCCESS_UPDATE
const AS_SUCCESS_UPDATE
Status: Article successfully updated.
Definition: EditPage.php:53
EditPage\showTextbox1
showTextbox1( $customAttribs=null, $textoverride=null)
Method to output wpTextbox1 The $textoverride method can be used by subclasses overriding showContent...
Definition: EditPage.php:3389
EditPage\addTalkPageText
addTalkPageText()
Definition: EditPage.php:4413
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:67
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2162
EditPage\getSummaryPreview
getSummaryPreview( $isSubjectPreview, $summary="")
Definition: EditPage.php:3318
EditPage\importContentFormData
importContentFormData(&$request)
Subpage overridable method for extracting the page content data from the posted form to be placed in ...
Definition: EditPage.php:1138
EditPage\$minoredit
bool $minoredit
Definition: EditPage.php:328
EditPage\$isOldRev
bool $isOldRev
Whether an old revision is edited.
Definition: EditPage.php:453
TemplatesOnThisPageFormatter
Handles formatting for the "templates used on this page" lists.
Definition: TemplatesOnThisPageFormatter.php:30
EditPage\$enableApiEditOverride
bool $enableApiEditOverride
Set in ApiEditPage, based on ContentHandler::allowsDirectApiEditing.
Definition: EditPage.php:443
$value
$value
Definition: styleTest.css.php:49
EDIT_UPDATE
const EDIT_UPDATE
Definition: Defines.php:153
EditPage\showHeader
showHeader()
Definition: EditPage.php:3061
MediaWiki\EditPage\TextConflictHelper\getEditFormHtmlAfterContent
getEditFormHtmlAfterContent()
Content to go in the edit form after textbox1.
Definition: TextConflictHelper.php:207
EditPage\getBaseRevision
getBaseRevision()
Returns the revision that was current at the time editing was initiated on the client,...
Definition: EditPage.php:2431
ContentHandler\getLocalizedName
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
Definition: ContentHandler.php:314
Linker\commentBlock
static commentBlock( $comment, $title=null, $local=false, $wikiId=null, $useParentheses=true)
Wrap a comment in standard punctuation and formatting if it's non-empty, otherwise return empty strin...
Definition: Linker.php:1480
EditPage\addNewLineAtEnd
addNewLineAtEnd( $wikitext)
Definition: EditPage.php:4519
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
EditPage\incrementResolvedConflicts
incrementResolvedConflicts()
Log when a page was successfully saved after the edit conflict view.
Definition: EditPage.php:1596
Xml\tags
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:130
EditPage\$edittime
string $edittime
Timestamp of the latest revision of the page when editing was initiated on the client.
Definition: EditPage.php:356
EditPage\matchSummarySpamRegex
static matchSummarySpamRegex( $text)
Check given input text against $wgSummarySpamRegex, and return the text of the first match.
Definition: EditPage.php:2462
EditPage\$mTokenOkExceptSuffix
bool $mTokenOkExceptSuffix
Definition: EditPage.php:266
EditPage\showEditTools
showEditTools()
Inserts optional text shown below edit and upload forms.
Definition: EditPage.php:3597
EditPage\$preview
bool $preview
Definition: EditPage.php:322
EditPage\$isNew
bool $isNew
New page or new section.
Definition: EditPage.php:245
EditPage\getCheckboxesDefinition
getCheckboxesDefinition( $checked)
Return an array of checkbox definitions.
Definition: EditPage.php:4136
$wgSummarySpamRegex
$wgSummarySpamRegex
Same as the above except for edit summaries.
Definition: DefaultSettings.php:5570
EditPage\$mBaseRevision
Revision bool null $mBaseRevision
A revision object corresponding to $this->editRevId.
Definition: EditPage.php:311
First
The First
Definition: primes.txt:1
EditPage\getCopywarn
getCopywarn()
Get the copyright warning.
Definition: EditPage.php:3609
EditPage\setApiEditOverride
setApiEditOverride( $enableOverride)
Allow editing of content that supports API direct editing, but not general direct editing.
Definition: EditPage.php:564
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1577
EditPage\AS_IMAGE_REDIRECT_LOGGED
const AS_IMAGE_REDIRECT_LOGGED
Status: logged in user is not allowed to upload (User::isAllowed('upload') == false)
Definition: EditPage.php:156
EditPage\newTextConflictHelper
newTextConflictHelper( $submitButtonLabel)
Definition: EditPage.php:4578
Revision\RAW
const RAW
Definition: Revision.php:56
EditPage\getCancelLink
getCancelLink()
Definition: EditPage.php:3763
EditPage\showCustomIntro
showCustomIntro()
Attempt to show a custom editing introduction, if supplied.
Definition: EditPage.php:2671
EditPage\getContext
getContext()
Definition: EditPage.php:507
EditPage\AS_PARSE_ERROR
const AS_PARSE_ERROR
Status: can't parse content.
Definition: EditPage.php:179
EditPage\EDITFORM_ID
const EDITFORM_ID
HTML id and name for the beginning of the edit form.
Definition: EditPage.php:195
EditPage\extractSectionTitle
static extractSectionTitle( $text)
Extract the section title from current section text, if any.
Definition: EditPage.php:3051
Block\TYPE_AUTO
const TYPE_AUTO
Definition: Block.php:99
EditPage\makeTemplatesOnThisPageList
makeTemplatesOnThisPageList(array $templates)
Wrapper around TemplatesOnThisPageFormatter to make a "templates on this page" list.
Definition: EditPage.php:3027
EditPage\$textbox1
string $textbox1
Page content input field.
Definition: EditPage.php:339
plain
either a plain
Definition: hooks.txt:2046
EditPage\$parentRevId
int $parentRevId
Revision ID the edit is based on, adjusted when an edit conflict is resolved.
Definition: EditPage.php:394
MediaWiki\EditPage\TextConflictHelper\setContentModel
setContentModel( $contentModel)
Definition: TextConflictHelper.php:111
EditPage\$undidRev
$undidRev
Definition: EditPage.php:430
Linker\titleAttrib
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
wfFindFile
wfFindFile( $title, $options=[])
Find a file.
Definition: GlobalFunctions.php:2677
EditPage\$changeTags
null array $changeTags
Definition: EditPage.php:412
EditPage
The edit page/HTML interface (split from Article) The actual database and text munging is still in Ar...
Definition: EditPage.php:44
$response
this hook is for auditing only $response
Definition: hooks.txt:780
EditPage\noSuchSectionPage
noSuchSectionPage()
Creates a basic error page which informs the user that they have attempted to edit a nonexistent sect...
Definition: EditPage.php:4319
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
EditPage\$formtype
string $formtype
Definition: EditPage.php:251
Content
Base interface for content objects.
Definition: Content.php:34
EditPage\getSummaryInputWidget
getSummaryInputWidget( $summary="", $labelText=null, $inputAttrs=null)
Builds a standard summary input with a label.
Definition: EditPage.php:3259
CommentStore\COMMENT_CHARACTER_LIMIT
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
Definition: CommentStore.php:37
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:152
EditPage\$hasPresetSummary
bool $hasPresetSummary
Has a summary been preset using GET parameter &summary= ?
Definition: EditPage.php:308
ChangeTags\canAddTagsAccompanyingChange
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:481
EditPage\$mParserOutput
ParserOutput $mParserOutput
Definition: EditPage.php:305
Title
Represents a title within MediaWiki.
Definition: Title.php:40
EDIT_AUTOSUMMARY
const EDIT_AUTOSUMMARY
Definition: Defines.php:158
EditPage\$mShowSummaryField
bool $mShowSummaryField
Definition: EditPage.php:314
EditPage\$sectiontitle
string $sectiontitle
Definition: EditPage.php:375
EditPage\$starttime
string $starttime
Timestamp from the first time the edit form was rendered.
Definition: EditPage.php:380
EditPage\$suppressIntro
$suppressIntro
Definition: EditPage.php:432
ContentHandler\getContentText
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
Definition: ContentHandler.php:83
MediaWiki\EditPage\TextConflictHelper\getEditFormHtmlBeforeContent
getEditFormHtmlBeforeContent()
Content to go in the edit form before textbox1.
Definition: TextConflictHelper.php:197
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:117
wfReadOnlyReason
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
Definition: GlobalFunctions.php:1210
$options
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:1985
EditPage\formatStatusErrors
formatStatusErrors(Status $status)
Wrap status errors in an errorbox for increased visibility.
Definition: EditPage.php:1791
EditPage\$deletedSinceEdit
bool $deletedSinceEdit
Definition: EditPage.php:248
EditPage\$selfRedirect
bool $selfRedirect
Definition: EditPage.php:293
EditPage\$edit
bool $edit
Definition: EditPage.php:435
EditPage\isSupportedContentModel
isSupportedContentModel( $modelId)
Returns if the given content model is editable.
Definition: EditPage.php:553
EditPage\$mPreloadContent
$mPreloadContent
Definition: EditPage.php:426
EditPage\showConflict
showConflict()
Show an edit conflict.
Definition: EditPage.php:3745
EditPage\getSubmitButtonLabel
getSubmitButtonLabel()
Get the message key of the label for the button to save the page.
Definition: EditPage.php:4226
EditPage\$unicodeCheck
string null $unicodeCheck
What the user submitted in the 'wpUnicodeCheck' field.
Definition: EditPage.php:458
$rev
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:1769
EditPage\$diff
bool $diff
Definition: EditPage.php:325
as
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
EditPage\doPreviewParse
doPreviewParse(Content $content)
Parse the page for a preview.
Definition: EditPage.php:4060
EditPage\newSectionSummary
newSectionSummary(&$sectionanchor=null)
Return the summary to be used for a new section.
Definition: EditPage.php:1811
EditPage\$action
string $action
Definition: EditPage.php:236
EditPage\setEditConflictHelperFactory
setEditConflictHelperFactory(callable $factory)
Set a factory function to create an EditConflictHelper.
Definition: EditPage.php:4555
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:295
EditPage\AS_READ_ONLY_PAGE_LOGGED
const AS_READ_ONLY_PAGE_LOGGED
Status: this logged in user is not allowed to edit this page.
Definition: EditPage.php:88
NS_USER
const NS_USER
Definition: Defines.php:66
EditPage\showTextbox
showTextbox( $text, $name, $customAttribs=[])
Definition: EditPage.php:3421
LoggerFactory
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
EditPage\getTitle
getTitle()
Definition: EditPage.php:515
true
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:1985
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: LogEntry.php:441
$content
$content
Definition: pageupdater.txt:72
EditPage\AS_CONFLICT_DETECTED
const AS_CONFLICT_DETECTED
Status: (non-resolvable) edit conflict.
Definition: EditPage.php:120
EditPage\AS_RATE_LIMITED
const AS_RATE_LIMITED
Status: rate limiter for action 'edit' was tripped.
Definition: EditPage.php:98
MWUnknownContentModelException
Exception thrown when an unregistered content model is requested.
Definition: MWUnknownContentModelException.php:10
of
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
Definition: globals.txt:10
EditPage\getCurrentContent
getCurrentContent()
Get the current content of the page.
Definition: EditPage.php:1412
Title\setContentModel
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition: Title.php:1044
wfWarn
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
Definition: GlobalFunctions.php:1092
EditPage\$isConflict
bool $isConflict
Whether an edit conflict needs to be resolved.
Definition: EditPage.php:242
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:72
CONTENT_MODEL_JAVASCRIPT
const CONTENT_MODEL_JAVASCRIPT
Definition: Defines.php:236
EditPage\displayViewSourcePage
displayViewSourcePage(Content $content, $errorMessage='')
Display a read-only View Source page.
Definition: EditPage.php:793
EDIT_MINOR
const EDIT_MINOR
Definition: Defines.php:154
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:37
EditPage\getContentObject
getContentObject( $def_content=null)
Definition: EditPage.php:1186
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:78
EditPage\AS_IMAGE_REDIRECT_ANON
const AS_IMAGE_REDIRECT_ANON
Status: anonymous user is not allowed to upload (User::isAllowed('upload') == false)
Definition: EditPage.php:151
EditPage\showStandardInputs
showStandardInputs(&$tabindex=2)
Definition: EditPage.php:3695
MediaWiki\EditPage\TextConflictHelper\getEditConflictMainTextBox
getEditConflictMainTextBox(array $customAttribs=[])
HTML to build the textbox1 on edit conflicts.
Definition: TextConflictHelper.php:170
MediaWikiServices
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
RawMessage
Variant of the Message class.
Definition: RawMessage.php:34
EditPage\matchSpamRegexInternal
static matchSpamRegexInternal( $text, $regexes)
Definition: EditPage.php:2473
ErrorPageError
An error page which can definitely be safely rendered using the OutputPage.
Definition: ErrorPageError.php:27
wfMessage
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
WikiPage\isRedirect
isRedirect()
Tests if the article content represents a redirect.
Definition: WikiPage.php:630
Revision\loadFromTitle
static loadFromTitle( $db, $title, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition: Revision.php:277
EditPage\POST_EDIT_COOKIE_KEY_PREFIX
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition: EditPage.php:201
EditPage\getOriginalContent
getOriginalContent(User $user)
Get the content of the wanted revision, without section extraction.
Definition: EditPage.php:1371
EditPage\AS_ARTICLE_WAS_DELETED
const AS_ARTICLE_WAS_DELETED
Status: article was deleted while editing and param wpRecreate == false or form was not posted.
Definition: EditPage.php:104
EditPage\setPostEditCookie
setPostEditCookie( $statusValue)
Sets post-edit cookie indicating the user just saved a particular revision.
Definition: EditPage.php:1558
Linker\accesskey
static accesskey( $name)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition: Linker.php:2013
CommentStore\getStore
static getStore()
Definition: CommentStore.php:130
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:48
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:118
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
EditPage\isWrongCaseUserConfigPage
isWrongCaseUserConfigPage()
Checks whether the user entered a skin name in uppercase, e.g.
Definition: EditPage.php:887
EditPage\$incompleteForm
bool $incompleteForm
Definition: EditPage.php:272
$username
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:780
EditPage\$missingSummary
bool $missingSummary
Definition: EditPage.php:281
EditPage\getEditConflictHelper
getEditConflictHelper()
Definition: EditPage.php:4563
WikiPage\hasDifferencesOutsideMainSlot
static hasDifferencesOutsideMainSlot(Revision $a, Revision $b)
Helper method for checking whether two revisions have differences that go beyond the main slot.
Definition: WikiPage.php:1526
$template
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password 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:780
EditPage\$bot
bool $bot
Definition: EditPage.php:403
EditPage\getCopyrightWarning
static getCopyrightWarning( $title, $format='plain', $langcode=null)
Get the copyright warning, by default returns wikitext.
Definition: EditPage.php:3621
MWNamespace\getCanonicalName
static getCanonicalName( $index)
Returns the canonical (English) name for a given index.
Definition: MWNamespace.php:256
Revision\DELETED_TEXT
const DELETED_TEXT
Definition: Revision.php:46
EditPage\AS_HOOK_ERROR_EXPECTED
const AS_HOOK_ERROR_EXPECTED
Status: A hook function returned an error.
Definition: EditPage.php:68
Skin\makeInternalOrExternalUrl
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition: Skin.php:1192
EditPage\updateWatchlist
updateWatchlist()
Register the change of watch status.
Definition: EditPage.php:2357
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:515
EditPage\handleStatus
handleStatus(Status $status, $resultDetails)
Handle status, such as after attempt save.
Definition: EditPage.php:1613
ParserOptions\newFromUser
static newFromUser( $user)
Get a ParserOptions object from a given user.
Definition: ParserOptions.php:1018
EditPage\$hookError
string $hookError
Definition: EditPage.php:302
EditPage\$allowBlankArticle
bool $allowBlankArticle
Definition: EditPage.php:290
EditPage\toEditText
toEditText( $content)
Gets an editable textual representation of $content.
Definition: EditPage.php:2705
Xml\checkLabel
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition: Xml.php:419
EditPage\setHeaders
setHeaders()
Definition: EditPage.php:2483
EditPage\AS_TEXTBOX_EMPTY
const AS_TEXTBOX_EMPTY
Status: user tried to create a new section without content.
Definition: EditPage.php:131
EditPage\AS_CHANGE_TAG_ERROR
const AS_CHANGE_TAG_ERROR
Status: an error relating to change tagging.
Definition: EditPage.php:174
EditPage\guessSectionName
guessSectionName( $text)
Turns section name wikitext into anchors for use in HTTP redirects.
Definition: EditPage.php:4533
wfArrayToCgi
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
Definition: GlobalFunctions.php:371
$wgSpamRegex
$wgSpamRegex
Edits matching these regular expressions in body text will be recognised as spam and rejected automat...
Definition: DefaultSettings.php:5565
EditPage\submit
submit()
Definition: EditPage.php:571
$type
$type
Definition: testCompression.php:48
EditPage\$nosummary
bool $nosummary
If true, hide the summary field.
Definition: EditPage.php:350
EditPage\getPreviewText
getPreviewText()
Get the rendered text for previewing.
Definition: EditPage.php:3874