MediaWiki  1.30.1
EditPage.php
Go to the documentation of this file.
1 <?php
25 use Wikimedia\ScopedCallback;
26 
42 class EditPage {
46  const UNICODE_CHECK = 'ℳ𝒲β™₯π“Šπ“ƒπ’Ύπ’Έβ„΄π’Ήβ„―';
47 
51  const AS_SUCCESS_UPDATE = 200;
52 
57 
61  const AS_HOOK_ERROR = 210;
62 
67 
72 
76  const AS_CONTENT_TOO_BIG = 216;
77 
82 
87 
91  const AS_READ_ONLY_PAGE = 220;
92 
96  const AS_RATE_LIMITED = 221;
97 
103 
109 
113  const AS_BLANK_ARTICLE = 224;
114 
118  const AS_CONFLICT_DETECTED = 225;
119 
124  const AS_SUMMARY_NEEDED = 226;
125 
129  const AS_TEXTBOX_EMPTY = 228;
130 
135 
139  const AS_END = 231;
140 
144  const AS_SPAM_ERROR = 232;
145 
150 
155 
161 
166  const AS_SELF_REDIRECT = 236;
167 
172  const AS_CHANGE_TAG_ERROR = 237;
173 
177  const AS_PARSE_ERROR = 240;
178 
184 
189 
193  const EDITFORM_ID = 'editform';
194 
199  const POST_EDIT_COOKIE_KEY_PREFIX = 'PostEditRevision';
200 
215 
220  public $mArticle;
222  private $page;
223 
228  public $mTitle;
229 
231  private $mContextTitle = null;
232 
234  public $action = 'submit';
235 
237  public $isConflict = false;
238 
243  public $isCssJsSubpage = false;
244 
249  public $isCssSubpage = false;
250 
255  public $isJsSubpage = false;
256 
261  public $isWrongCaseCssJsPage = false;
262 
264  public $isNew = false;
265 
268 
270  public $formtype;
271 
273  public $firsttime;
274 
276  public $lastDelete;
277 
279  public $mTokenOk = false;
280 
282  public $mTokenOkExceptSuffix = false;
283 
285  public $mTriedSave = false;
286 
288  public $incompleteForm = false;
289 
291  public $tooBig = false;
292 
294  public $missingComment = false;
295 
297  public $missingSummary = false;
298 
300  public $allowBlankSummary = false;
301 
303  protected $blankArticle = false;
304 
306  protected $allowBlankArticle = false;
307 
309  protected $selfRedirect = false;
310 
312  protected $allowSelfRedirect = false;
313 
315  public $autoSumm = '';
316 
318  public $hookError = '';
319 
322 
324  public $hasPresetSummary = false;
325 
327  public $mBaseRevision = false;
328 
330  public $mShowSummaryField = true;
331 
332  # Form values
333 
335  public $save = false;
336 
338  public $preview = false;
339 
341  public $diff = false;
342 
344  public $minoredit = false;
345 
347  public $watchthis = false;
348 
350  public $recreate = false;
351 
353  public $textbox1 = '';
354 
356  public $textbox2 = '';
357 
359  public $summary = '';
360 
362  public $nosummary = false;
363 
365  public $edittime = '';
366 
368  private $editRevId = null;
369 
371  public $section = '';
372 
374  public $sectiontitle = '';
375 
377  public $starttime = '';
378 
380  public $oldid = 0;
381 
383  public $parentRevId = 0;
384 
386  public $editintro = '';
387 
389  public $scrolltop = null;
390 
392  public $bot = true;
393 
396 
398  public $contentFormat = null;
399 
401  private $changeTags = null;
402 
403  # Placeholders for text injection by hooks (must be HTML)
404  # extensions should take care to _append_ to the present value
405 
407  public $editFormPageTop = '';
408  public $editFormTextTop = '';
412  public $editFormTextBottom = '';
415  public $mPreloadContent = null;
416 
417  /* $didSave should be set to true whenever an article was successfully altered. */
418  public $didSave = false;
419  public $undidRev = 0;
420 
421  public $suppressIntro = false;
422 
424  protected $edit;
425 
427  protected $contentLength = false;
428 
432  private $enableApiEditOverride = false;
433 
437  protected $context;
438 
442  private $isOldRev = false;
443 
447  private $unicodeCheck;
448 
452  public function __construct( Article $article ) {
453  $this->mArticle = $article;
454  $this->page = $article->getPage(); // model object
455  $this->mTitle = $article->getTitle();
456  $this->context = $article->getContext();
457 
458  $this->contentModel = $this->mTitle->getContentModel();
459 
460  $handler = ContentHandler::getForModelID( $this->contentModel );
461  $this->contentFormat = $handler->getDefaultFormat();
462  }
463 
467  public function getArticle() {
468  return $this->mArticle;
469  }
470 
475  public function getContext() {
476  return $this->context;
477  }
478 
483  public function getTitle() {
484  return $this->mTitle;
485  }
486 
492  public function setContextTitle( $title ) {
493  $this->mContextTitle = $title;
494  }
495 
503  public function getContextTitle() {
504  if ( is_null( $this->mContextTitle ) ) {
505  wfDebugLog(
506  'GlobalTitleFail',
507  __METHOD__ . ' called by ' . wfGetAllCallers( 5 ) . ' with no title set.'
508  );
510  return $wgTitle;
511  } else {
512  return $this->mContextTitle;
513  }
514  }
515 
521  public function isOouiEnabled() {
522  return true;
523  }
524 
532  public function isSupportedContentModel( $modelId ) {
533  return $this->enableApiEditOverride === true ||
534  ContentHandler::getForModelID( $modelId )->supportsDirectEditing();
535  }
536 
543  public function setApiEditOverride( $enableOverride ) {
544  $this->enableApiEditOverride = $enableOverride;
545  }
546 
550  public function submit() {
551  wfDeprecated( __METHOD__, '1.29' );
552  $this->edit();
553  }
554 
566  public function edit() {
567  // Allow extensions to modify/prevent this form or submission
568  if ( !Hooks::run( 'AlternateEdit', [ $this ] ) ) {
569  return;
570  }
571 
572  wfDebug( __METHOD__ . ": enter\n" );
573 
574  $request = $this->context->getRequest();
575  // If they used redlink=1 and the page exists, redirect to the main article
576  if ( $request->getBool( 'redlink' ) && $this->mTitle->exists() ) {
577  $this->context->getOutput()->redirect( $this->mTitle->getFullURL() );
578  return;
579  }
580 
581  $this->importFormData( $request );
582  $this->firsttime = false;
583 
584  if ( wfReadOnly() && $this->save ) {
585  // Force preview
586  $this->save = false;
587  $this->preview = true;
588  }
589 
590  if ( $this->save ) {
591  $this->formtype = 'save';
592  } elseif ( $this->preview ) {
593  $this->formtype = 'preview';
594  } elseif ( $this->diff ) {
595  $this->formtype = 'diff';
596  } else { # First time through
597  $this->firsttime = true;
598  if ( $this->previewOnOpen() ) {
599  $this->formtype = 'preview';
600  } else {
601  $this->formtype = 'initial';
602  }
603  }
604 
605  $permErrors = $this->getEditPermissionErrors( $this->save ? 'secure' : 'full' );
606  if ( $permErrors ) {
607  wfDebug( __METHOD__ . ": User can't edit\n" );
608  // Auto-block user's IP if the account was "hard" blocked
609  if ( !wfReadOnly() ) {
611  $this->context->getUser()->spreadAnyEditBlock();
612  } );
613  }
614  $this->displayPermissionsError( $permErrors );
615 
616  return;
617  }
618 
619  $revision = $this->mArticle->getRevisionFetched();
620  // Disallow editing revisions with content models different from the current one
621  // Undo edits being an exception in order to allow reverting content model changes.
622  if ( $revision
623  && $revision->getContentModel() !== $this->contentModel
624  ) {
625  $prevRev = null;
626  if ( $this->undidRev ) {
627  $undidRevObj = Revision::newFromId( $this->undidRev );
628  $prevRev = $undidRevObj ? $undidRevObj->getPrevious() : null;
629  }
630  if ( !$this->undidRev
631  || !$prevRev
632  || $prevRev->getContentModel() !== $this->contentModel
633  ) {
634  $this->displayViewSourcePage(
635  $this->getContentObject(),
636  $this->context->msg(
637  'contentmodelediterror',
638  $revision->getContentModel(),
640  )->plain()
641  );
642  return;
643  }
644  }
645 
646  $this->isConflict = false;
647  // css / js subpages of user pages get a special treatment
648  // The following member variables are deprecated since 1.30,
649  // the functions should be used instead.
650  $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
651  $this->isCssSubpage = $this->mTitle->isCssSubpage();
652  $this->isJsSubpage = $this->mTitle->isJsSubpage();
653  $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
654 
655  # Show applicable editing introductions
656  if ( $this->formtype == 'initial' || $this->firsttime ) {
657  $this->showIntro();
658  }
659 
660  # Attempt submission here. This will check for edit conflicts,
661  # and redundantly check for locked database, blocked IPs, etc.
662  # that edit() already checked just in case someone tries to sneak
663  # in the back door with a hand-edited submission URL.
664 
665  if ( 'save' == $this->formtype ) {
666  $resultDetails = null;
667  $status = $this->attemptSave( $resultDetails );
668  if ( !$this->handleStatus( $status, $resultDetails ) ) {
669  return;
670  }
671  }
672 
673  # First time through: get contents, set time for conflict
674  # checking, etc.
675  if ( 'initial' == $this->formtype || $this->firsttime ) {
676  if ( $this->initialiseForm() === false ) {
677  $this->noSuchSectionPage();
678  return;
679  }
680 
681  if ( !$this->mTitle->getArticleID() ) {
682  Hooks::run( 'EditFormPreloadText', [ &$this->textbox1, &$this->mTitle ] );
683  } else {
684  Hooks::run( 'EditFormInitialText', [ $this ] );
685  }
686 
687  }
688 
689  $this->showEditForm();
690  }
691 
696  protected function getEditPermissionErrors( $rigor = 'secure' ) {
697  $user = $this->context->getUser();
698  $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $user, $rigor );
699  # Can this title be created?
700  if ( !$this->mTitle->exists() ) {
701  $permErrors = array_merge(
702  $permErrors,
703  wfArrayDiff2(
704  $this->mTitle->getUserPermissionsErrors( 'create', $user, $rigor ),
705  $permErrors
706  )
707  );
708  }
709  # Ignore some permissions errors when a user is just previewing/viewing diffs
710  $remove = [];
711  foreach ( $permErrors as $error ) {
712  if ( ( $this->preview || $this->diff )
713  && (
714  $error[0] == 'blockedtext' ||
715  $error[0] == 'autoblockedtext' ||
716  $error[0] == 'systemblockedtext'
717  )
718  ) {
719  $remove[] = $error;
720  }
721  }
722  $permErrors = wfArrayDiff2( $permErrors, $remove );
723 
724  return $permErrors;
725  }
726 
740  protected function displayPermissionsError( array $permErrors ) {
741  $out = $this->context->getOutput();
742  if ( $this->context->getRequest()->getBool( 'redlink' ) ) {
743  // The edit page was reached via a red link.
744  // Redirect to the article page and let them click the edit tab if
745  // they really want a permission error.
746  $out->redirect( $this->mTitle->getFullURL() );
747  return;
748  }
749 
750  $content = $this->getContentObject();
751 
752  # Use the normal message if there's nothing to display
753  if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
754  $action = $this->mTitle->exists() ? 'edit' :
755  ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
756  throw new PermissionsError( $action, $permErrors );
757  }
758 
759  $this->displayViewSourcePage(
760  $content,
761  $out->formatPermissionsErrorMessage( $permErrors, 'edit' )
762  );
763  }
764 
770  protected function displayViewSourcePage( Content $content, $errorMessage = '' ) {
771  $out = $this->context->getOutput();
772  Hooks::run( 'EditPage::showReadOnlyForm:initial', [ $this, &$out ] );
773 
774  $out->setRobotPolicy( 'noindex,nofollow' );
775  $out->setPageTitle( $this->context->msg(
776  'viewsource-title',
777  $this->getContextTitle()->getPrefixedText()
778  ) );
779  $out->addBacklinkSubtitle( $this->getContextTitle() );
780  $out->addHTML( $this->editFormPageTop );
781  $out->addHTML( $this->editFormTextTop );
782 
783  if ( $errorMessage !== '' ) {
784  $out->addWikiText( $errorMessage );
785  $out->addHTML( "<hr />\n" );
786  }
787 
788  # If the user made changes, preserve them when showing the markup
789  # (This happens when a user is blocked during edit, for instance)
790  if ( !$this->firsttime ) {
791  $text = $this->textbox1;
792  $out->addWikiMsg( 'viewyourtext' );
793  } else {
794  try {
795  $text = $this->toEditText( $content );
796  } catch ( MWException $e ) {
797  # Serialize using the default format if the content model is not supported
798  # (e.g. for an old revision with a different model)
799  $text = $content->serialize();
800  }
801  $out->addWikiMsg( 'viewsourcetext' );
802  }
803 
804  $out->addHTML( $this->editFormTextBeforeContent );
805  $this->showTextbox( $text, 'wpTextbox1', [ 'readonly' ] );
806  $out->addHTML( $this->editFormTextAfterContent );
807 
808  $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
809 
810  $out->addModules( 'mediawiki.action.edit.collapsibleFooter' );
811 
812  $out->addHTML( $this->editFormTextBottom );
813  if ( $this->mTitle->exists() ) {
814  $out->returnToMain( null, $this->mTitle );
815  }
816  }
817 
823  protected function previewOnOpen() {
824  $previewOnOpenNamespaces = $this->context->getConfig()->get( 'PreviewOnOpenNamespaces' );
825  $request = $this->context->getRequest();
826  if ( $request->getVal( 'preview' ) == 'yes' ) {
827  // Explicit override from request
828  return true;
829  } elseif ( $request->getVal( 'preview' ) == 'no' ) {
830  // Explicit override from request
831  return false;
832  } elseif ( $this->section == 'new' ) {
833  // Nothing *to* preview for new sections
834  return false;
835  } elseif ( ( $request->getVal( 'preload' ) !== null || $this->mTitle->exists() )
836  && $this->context->getUser()->getOption( 'previewonfirst' )
837  ) {
838  // Standard preference behavior
839  return true;
840  } elseif ( !$this->mTitle->exists()
841  && isset( $previewOnOpenNamespaces[$this->mTitle->getNamespace()] )
842  && $previewOnOpenNamespaces[$this->mTitle->getNamespace()]
843  ) {
844  // Categories are special
845  return true;
846  } else {
847  return false;
848  }
849  }
850 
857  protected function isWrongCaseCssJsPage() {
858  if ( $this->mTitle->isCssJsSubpage() ) {
859  $name = $this->mTitle->getSkinFromCssJsSubpage();
860  $skins = array_merge(
861  array_keys( Skin::getSkinNames() ),
862  [ 'common' ]
863  );
864  return !in_array( $name, $skins )
865  && in_array( strtolower( $name ), $skins );
866  } else {
867  return false;
868  }
869  }
870 
878  protected function isSectionEditSupported() {
879  $contentHandler = ContentHandler::getForTitle( $this->mTitle );
880  return $contentHandler->supportsSections();
881  }
882 
888  public function importFormData( &$request ) {
889  # Section edit can come from either the form or a link
890  $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
891 
892  if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) {
893  throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
894  }
895 
896  $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
897 
898  if ( $request->wasPosted() ) {
899  # These fields need to be checked for encoding.
900  # Also remove trailing whitespace, but don't remove _initial_
901  # whitespace from the text boxes. This may be significant formatting.
902  $this->textbox1 = rtrim( $request->getText( 'wpTextbox1' ) );
903  if ( !$request->getCheck( 'wpTextbox2' ) ) {
904  // Skip this if wpTextbox2 has input, it indicates that we came
905  // from a conflict page with raw page text, not a custom form
906  // modified by subclasses
908  if ( $textbox1 !== null ) {
909  $this->textbox1 = $textbox1;
910  }
911  }
912 
913  $this->unicodeCheck = $request->getText( 'wpUnicodeCheck' );
914 
915  $this->summary = $request->getText( 'wpSummary' );
916 
917  # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
918  # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
919  # section titles.
920  $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
921 
922  # Treat sectiontitle the same way as summary.
923  # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
924  # currently doing double duty as both edit summary and section title. Right now this
925  # is just to allow API edits to work around this limitation, but this should be
926  # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
927  $this->sectiontitle = $request->getText( 'wpSectionTitle' );
928  $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
929 
930  $this->edittime = $request->getVal( 'wpEdittime' );
931  $this->editRevId = $request->getIntOrNull( 'editRevId' );
932  $this->starttime = $request->getVal( 'wpStarttime' );
933 
934  $undidRev = $request->getInt( 'wpUndidRevision' );
935  if ( $undidRev ) {
936  $this->undidRev = $undidRev;
937  }
938 
939  $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
940 
941  if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
942  // wpTextbox1 field is missing, possibly due to being "too big"
943  // according to some filter rules such as Suhosin's setting for
944  // suhosin.request.max_value_length (d'oh)
945  $this->incompleteForm = true;
946  } else {
947  // If we receive the last parameter of the request, we can fairly
948  // claim the POST request has not been truncated.
949 
950  // TODO: softened the check for cutover. Once we determine
951  // that it is safe, we should complete the transition by
952  // removing the "edittime" clause.
953  $this->incompleteForm = ( !$request->getVal( 'wpUltimateParam' )
954  && is_null( $this->edittime ) );
955  }
956  if ( $this->incompleteForm ) {
957  # If the form is incomplete, force to preview.
958  wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
959  wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
960  $this->preview = true;
961  } else {
962  $this->preview = $request->getCheck( 'wpPreview' );
963  $this->diff = $request->getCheck( 'wpDiff' );
964 
965  // Remember whether a save was requested, so we can indicate
966  // if we forced preview due to session failure.
967  $this->mTriedSave = !$this->preview;
968 
969  if ( $this->tokenOk( $request ) ) {
970  # Some browsers will not report any submit button
971  # if the user hits enter in the comment box.
972  # The unmarked state will be assumed to be a save,
973  # if the form seems otherwise complete.
974  wfDebug( __METHOD__ . ": Passed token check.\n" );
975  } elseif ( $this->diff ) {
976  # Failed token check, but only requested "Show Changes".
977  wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
978  } else {
979  # Page might be a hack attempt posted from
980  # an external site. Preview instead of saving.
981  wfDebug( __METHOD__ . ": Failed token check; forcing preview\n" );
982  $this->preview = true;
983  }
984  }
985  $this->save = !$this->preview && !$this->diff;
986  if ( !preg_match( '/^\d{14}$/', $this->edittime ) ) {
987  $this->edittime = null;
988  }
989 
990  if ( !preg_match( '/^\d{14}$/', $this->starttime ) ) {
991  $this->starttime = null;
992  }
993 
994  $this->recreate = $request->getCheck( 'wpRecreate' );
995 
996  $this->minoredit = $request->getCheck( 'wpMinoredit' );
997  $this->watchthis = $request->getCheck( 'wpWatchthis' );
998 
999  $user = $this->context->getUser();
1000  # Don't force edit summaries when a user is editing their own user or talk page
1001  if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK )
1002  && $this->mTitle->getText() == $user->getName()
1003  ) {
1004  $this->allowBlankSummary = true;
1005  } else {
1006  $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' )
1007  || !$user->getOption( 'forceeditsummary' );
1008  }
1009 
1010  $this->autoSumm = $request->getText( 'wpAutoSummary' );
1011 
1012  $this->allowBlankArticle = $request->getBool( 'wpIgnoreBlankArticle' );
1013  $this->allowSelfRedirect = $request->getBool( 'wpIgnoreSelfRedirect' );
1014 
1015  $changeTags = $request->getVal( 'wpChangeTags' );
1016  if ( is_null( $changeTags ) || $changeTags === '' ) {
1017  $this->changeTags = [];
1018  } else {
1019  $this->changeTags = array_filter( array_map( 'trim', explode( ',',
1020  $changeTags ) ) );
1021  }
1022  } else {
1023  # Not a posted form? Start with nothing.
1024  wfDebug( __METHOD__ . ": Not a posted form.\n" );
1025  $this->textbox1 = '';
1026  $this->summary = '';
1027  $this->sectiontitle = '';
1028  $this->edittime = '';
1029  $this->editRevId = null;
1030  $this->starttime = wfTimestampNow();
1031  $this->edit = false;
1032  $this->preview = false;
1033  $this->save = false;
1034  $this->diff = false;
1035  $this->minoredit = false;
1036  // Watch may be overridden by request parameters
1037  $this->watchthis = $request->getBool( 'watchthis', false );
1038  $this->recreate = false;
1039 
1040  // When creating a new section, we can preload a section title by passing it as the
1041  // preloadtitle parameter in the URL (T15100)
1042  if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
1043  $this->sectiontitle = $request->getVal( 'preloadtitle' );
1044  // Once wpSummary isn't being use for setting section titles, we should delete this.
1045  $this->summary = $request->getVal( 'preloadtitle' );
1046  } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
1047  $this->summary = $request->getText( 'summary' );
1048  if ( $this->summary !== '' ) {
1049  $this->hasPresetSummary = true;
1050  }
1051  }
1052 
1053  if ( $request->getVal( 'minor' ) ) {
1054  $this->minoredit = true;
1055  }
1056  }
1057 
1058  $this->oldid = $request->getInt( 'oldid' );
1059  $this->parentRevId = $request->getInt( 'parentRevId' );
1060 
1061  $this->bot = $request->getBool( 'bot', true );
1062  $this->nosummary = $request->getBool( 'nosummary' );
1063 
1064  // May be overridden by revision.
1065  $this->contentModel = $request->getText( 'model', $this->contentModel );
1066  // May be overridden by revision.
1067  $this->contentFormat = $request->getText( 'format', $this->contentFormat );
1068 
1069  try {
1070  $handler = ContentHandler::getForModelID( $this->contentModel );
1071  } catch ( MWUnknownContentModelException $e ) {
1072  throw new ErrorPageError(
1073  'editpage-invalidcontentmodel-title',
1074  'editpage-invalidcontentmodel-text',
1075  [ wfEscapeWikiText( $this->contentModel ) ]
1076  );
1077  }
1078 
1079  if ( !$handler->isSupportedFormat( $this->contentFormat ) ) {
1080  throw new ErrorPageError(
1081  'editpage-notsupportedcontentformat-title',
1082  'editpage-notsupportedcontentformat-text',
1083  [
1084  wfEscapeWikiText( $this->contentFormat ),
1085  wfEscapeWikiText( ContentHandler::getLocalizedName( $this->contentModel ) )
1086  ]
1087  );
1088  }
1089 
1096  $this->editintro = $request->getText( 'editintro',
1097  // Custom edit intro for new sections
1098  $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
1099 
1100  // Allow extensions to modify form data
1101  Hooks::run( 'EditPage::importFormData', [ $this, $request ] );
1102  }
1103 
1113  protected function importContentFormData( &$request ) {
1114  return; // Don't do anything, EditPage already extracted wpTextbox1
1115  }
1116 
1122  public function initialiseForm() {
1123  $this->edittime = $this->page->getTimestamp();
1124  $this->editRevId = $this->page->getLatest();
1125 
1126  $content = $this->getContentObject( false ); # TODO: track content object?!
1127  if ( $content === false ) {
1128  return false;
1129  }
1130  $this->textbox1 = $this->toEditText( $content );
1131 
1132  $user = $this->context->getUser();
1133  // activate checkboxes if user wants them to be always active
1134  # Sort out the "watch" checkbox
1135  if ( $user->getOption( 'watchdefault' ) ) {
1136  # Watch all edits
1137  $this->watchthis = true;
1138  } elseif ( $user->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
1139  # Watch creations
1140  $this->watchthis = true;
1141  } elseif ( $user->isWatched( $this->mTitle ) ) {
1142  # Already watched
1143  $this->watchthis = true;
1144  }
1145  if ( $user->getOption( 'minordefault' ) && !$this->isNew ) {
1146  $this->minoredit = true;
1147  }
1148  if ( $this->textbox1 === false ) {
1149  return false;
1150  }
1151  return true;
1152  }
1153 
1161  protected function getContentObject( $def_content = null ) {
1163 
1164  $content = false;
1165 
1166  $user = $this->context->getUser();
1167  $request = $this->context->getRequest();
1168  // For message page not locally set, use the i18n message.
1169  // For other non-existent articles, use preload text if any.
1170  if ( !$this->mTitle->exists() || $this->section == 'new' ) {
1171  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
1172  # If this is a system message, get the default text.
1173  $msg = $this->mTitle->getDefaultMessageText();
1174 
1175  $content = $this->toEditContent( $msg );
1176  }
1177  if ( $content === false ) {
1178  # If requested, preload some text.
1179  $preload = $request->getVal( 'preload',
1180  // Custom preload text for new sections
1181  $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
1182  $params = $request->getArray( 'preloadparams', [] );
1183 
1184  $content = $this->getPreloadedContent( $preload, $params );
1185  }
1186  // For existing pages, get text based on "undo" or section parameters.
1187  } else {
1188  if ( $this->section != '' ) {
1189  // Get section edit text (returns $def_text for invalid sections)
1190  $orig = $this->getOriginalContent( $user );
1191  $content = $orig ? $orig->getSection( $this->section ) : null;
1192 
1193  if ( !$content ) {
1194  $content = $def_content;
1195  }
1196  } else {
1197  $undoafter = $request->getInt( 'undoafter' );
1198  $undo = $request->getInt( 'undo' );
1199 
1200  if ( $undo > 0 && $undoafter > 0 ) {
1201  $undorev = Revision::newFromId( $undo );
1202  $oldrev = Revision::newFromId( $undoafter );
1203 
1204  # Sanity check, make sure it's the right page,
1205  # the revisions exist and they were not deleted.
1206  # Otherwise, $content will be left as-is.
1207  if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
1208  !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
1209  !$oldrev->isDeleted( Revision::DELETED_TEXT )
1210  ) {
1211  $content = $this->page->getUndoContent( $undorev, $oldrev );
1212 
1213  if ( $content === false ) {
1214  # Warn the user that something went wrong
1215  $undoMsg = 'failure';
1216  } else {
1217  $oldContent = $this->page->getContent( Revision::RAW );
1219  $newContent = $content->preSaveTransform( $this->mTitle, $user, $popts );
1220  if ( $newContent->getModel() !== $oldContent->getModel() ) {
1221  // The undo may change content
1222  // model if its reverting the top
1223  // edit. This can result in
1224  // mismatched content model/format.
1225  $this->contentModel = $newContent->getModel();
1226  $this->contentFormat = $oldrev->getContentFormat();
1227  }
1228 
1229  if ( $newContent->equals( $oldContent ) ) {
1230  # Tell the user that the undo results in no change,
1231  # i.e. the revisions were already undone.
1232  $undoMsg = 'nochange';
1233  $content = false;
1234  } else {
1235  # Inform the user of our success and set an automatic edit summary
1236  $undoMsg = 'success';
1237 
1238  # If we just undid one rev, use an autosummary
1239  $firstrev = $oldrev->getNext();
1240  if ( $firstrev && $firstrev->getId() == $undo ) {
1241  $userText = $undorev->getUserText();
1242  if ( $userText === '' ) {
1243  $undoSummary = $this->context->msg(
1244  'undo-summary-username-hidden',
1245  $undo
1246  )->inContentLanguage()->text();
1247  } else {
1248  $undoSummary = $this->context->msg(
1249  'undo-summary',
1250  $undo,
1251  $userText
1252  )->inContentLanguage()->text();
1253  }
1254  if ( $this->summary === '' ) {
1255  $this->summary = $undoSummary;
1256  } else {
1257  $this->summary = $undoSummary . $this->context->msg( 'colon-separator' )
1258  ->inContentLanguage()->text() . $this->summary;
1259  }
1260  $this->undidRev = $undo;
1261  }
1262  $this->formtype = 'diff';
1263  }
1264  }
1265  } else {
1266  // Failed basic sanity checks.
1267  // Older revisions may have been removed since the link
1268  // was created, or we may simply have got bogus input.
1269  $undoMsg = 'norev';
1270  }
1271 
1272  $out = $this->context->getOutput();
1273  // Messages: undo-success, undo-failure, undo-norev, undo-nochange
1274  $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
1275  $this->editFormPageTop .= $out->parse( "<div class=\"{$class}\">" .
1276  $this->context->msg( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
1277  }
1278 
1279  if ( $content === false ) {
1280  $content = $this->getOriginalContent( $user );
1281  }
1282  }
1283  }
1284 
1285  return $content;
1286  }
1287 
1303  private function getOriginalContent( User $user ) {
1304  if ( $this->section == 'new' ) {
1305  return $this->getCurrentContent();
1306  }
1307  $revision = $this->mArticle->getRevisionFetched();
1308  if ( $revision === null ) {
1309  $handler = ContentHandler::getForModelID( $this->contentModel );
1310  return $handler->makeEmptyContent();
1311  }
1312  $content = $revision->getContent( Revision::FOR_THIS_USER, $user );
1313  return $content;
1314  }
1315 
1328  public function getParentRevId() {
1329  if ( $this->parentRevId ) {
1330  return $this->parentRevId;
1331  } else {
1332  return $this->mArticle->getRevIdFetched();
1333  }
1334  }
1335 
1344  protected function getCurrentContent() {
1345  $rev = $this->page->getRevision();
1346  $content = $rev ? $rev->getContent( Revision::RAW ) : null;
1347 
1348  if ( $content === false || $content === null ) {
1349  $handler = ContentHandler::getForModelID( $this->contentModel );
1350  return $handler->makeEmptyContent();
1351  } elseif ( !$this->undidRev ) {
1352  // Content models should always be the same since we error
1353  // out if they are different before this point (in ->edit()).
1354  // The exception being, during an undo, the current revision might
1355  // differ from the prior revision.
1356  $logger = LoggerFactory::getInstance( 'editpage' );
1357  if ( $this->contentModel !== $rev->getContentModel() ) {
1358  $logger->warning( "Overriding content model from current edit {prev} to {new}", [
1359  'prev' => $this->contentModel,
1360  'new' => $rev->getContentModel(),
1361  'title' => $this->getTitle()->getPrefixedDBkey(),
1362  'method' => __METHOD__
1363  ] );
1364  $this->contentModel = $rev->getContentModel();
1365  }
1366 
1367  // Given that the content models should match, the current selected
1368  // format should be supported.
1369  if ( !$content->isSupportedFormat( $this->contentFormat ) ) {
1370  $logger->warning( "Current revision content format unsupported. Overriding {prev} to {new}", [
1371 
1372  'prev' => $this->contentFormat,
1373  'new' => $rev->getContentFormat(),
1374  'title' => $this->getTitle()->getPrefixedDBkey(),
1375  'method' => __METHOD__
1376  ] );
1377  $this->contentFormat = $rev->getContentFormat();
1378  }
1379  }
1380  return $content;
1381  }
1382 
1390  public function setPreloadedContent( Content $content ) {
1391  $this->mPreloadContent = $content;
1392  }
1393 
1405  protected function getPreloadedContent( $preload, $params = [] ) {
1406  if ( !empty( $this->mPreloadContent ) ) {
1407  return $this->mPreloadContent;
1408  }
1409 
1410  $handler = ContentHandler::getForModelID( $this->contentModel );
1411 
1412  if ( $preload === '' ) {
1413  return $handler->makeEmptyContent();
1414  }
1415 
1416  $user = $this->context->getUser();
1417  $title = Title::newFromText( $preload );
1418  # Check for existence to avoid getting MediaWiki:Noarticletext
1419  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $user ) ) {
1420  // TODO: somehow show a warning to the user!
1421  return $handler->makeEmptyContent();
1422  }
1423 
1425  if ( $page->isRedirect() ) {
1427  # Same as before
1428  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $user ) ) {
1429  // TODO: somehow show a warning to the user!
1430  return $handler->makeEmptyContent();
1431  }
1433  }
1434 
1435  $parserOptions = ParserOptions::newFromUser( $user );
1436  $content = $page->getContent( Revision::RAW );
1437 
1438  if ( !$content ) {
1439  // TODO: somehow show a warning to the user!
1440  return $handler->makeEmptyContent();
1441  }
1442 
1443  if ( $content->getModel() !== $handler->getModelID() ) {
1444  $converted = $content->convert( $handler->getModelID() );
1445 
1446  if ( !$converted ) {
1447  // TODO: somehow show a warning to the user!
1448  wfDebug( "Attempt to preload incompatible content: " .
1449  "can't convert " . $content->getModel() .
1450  " to " . $handler->getModelID() );
1451 
1452  return $handler->makeEmptyContent();
1453  }
1454 
1455  $content = $converted;
1456  }
1457 
1458  return $content->preloadTransform( $title, $parserOptions, $params );
1459  }
1460 
1468  public function tokenOk( &$request ) {
1469  $token = $request->getVal( 'wpEditToken' );
1470  $user = $this->context->getUser();
1471  $this->mTokenOk = $user->matchEditToken( $token );
1472  $this->mTokenOkExceptSuffix = $user->matchEditTokenNoSuffix( $token );
1473  return $this->mTokenOk;
1474  }
1475 
1490  protected function setPostEditCookie( $statusValue ) {
1491  $revisionId = $this->page->getLatest();
1492  $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1493 
1494  $val = 'saved';
1495  if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) {
1496  $val = 'created';
1497  } elseif ( $this->oldid ) {
1498  $val = 'restored';
1499  }
1500 
1501  $response = $this->context->getRequest()->response();
1502  $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION );
1503  }
1504 
1511  public function attemptSave( &$resultDetails = false ) {
1512  # Allow bots to exempt some edits from bot flagging
1513  $bot = $this->context->getUser()->isAllowed( 'bot' ) && $this->bot;
1514  $status = $this->internalAttemptSave( $resultDetails, $bot );
1515 
1516  Hooks::run( 'EditPage::attemptSave:after', [ $this, $status, $resultDetails ] );
1517 
1518  return $status;
1519  }
1520 
1524  private function incrementResolvedConflicts() {
1525  if ( $this->context->getRequest()->getText( 'mode' ) !== 'conflict' ) {
1526  return;
1527  }
1528 
1529  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1530  $stats->increment( 'edit.failures.conflict.resolved' );
1531  }
1532 
1542  private function handleStatus( Status $status, $resultDetails ) {
1547  if ( $status->value == self::AS_SUCCESS_UPDATE
1548  || $status->value == self::AS_SUCCESS_NEW_ARTICLE
1549  ) {
1550  $this->incrementResolvedConflicts();
1551 
1552  $this->didSave = true;
1553  if ( !$resultDetails['nullEdit'] ) {
1554  $this->setPostEditCookie( $status->value );
1555  }
1556  }
1557 
1558  $out = $this->context->getOutput();
1559 
1560  // "wpExtraQueryRedirect" is a hidden input to modify
1561  // after save URL and is not used by actual edit form
1562  $request = $this->context->getRequest();
1563  $extraQueryRedirect = $request->getVal( 'wpExtraQueryRedirect' );
1564 
1565  switch ( $status->value ) {
1573  case self::AS_END:
1576  return true;
1577 
1578  case self::AS_HOOK_ERROR:
1579  return false;
1580 
1582  case self::AS_PARSE_ERROR:
1584  $out->addWikiText( '<div class="error">' . "\n" . $status->getWikiText() . '</div>' );
1585  return true;
1586 
1588  $query = $resultDetails['redirect'] ? 'redirect=no' : '';
1589  if ( $extraQueryRedirect ) {
1590  if ( $query === '' ) {
1591  $query = $extraQueryRedirect;
1592  } else {
1593  $query = $query . '&' . $extraQueryRedirect;
1594  }
1595  }
1596  $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
1597  $out->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
1598  return false;
1599 
1601  $extraQuery = '';
1602  $sectionanchor = $resultDetails['sectionanchor'];
1603 
1604  // Give extensions a chance to modify URL query on update
1605  Hooks::run(
1606  'ArticleUpdateBeforeRedirect',
1607  [ $this->mArticle, &$sectionanchor, &$extraQuery ]
1608  );
1609 
1610  if ( $resultDetails['redirect'] ) {
1611  if ( $extraQuery == '' ) {
1612  $extraQuery = 'redirect=no';
1613  } else {
1614  $extraQuery = 'redirect=no&' . $extraQuery;
1615  }
1616  }
1617  if ( $extraQueryRedirect ) {
1618  if ( $extraQuery === '' ) {
1619  $extraQuery = $extraQueryRedirect;
1620  } else {
1621  $extraQuery = $extraQuery . '&' . $extraQueryRedirect;
1622  }
1623  }
1624 
1625  $out->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1626  return false;
1627 
1628  case self::AS_SPAM_ERROR:
1629  $this->spamPageWithContent( $resultDetails['spam'] );
1630  return false;
1631 
1633  throw new UserBlockedError( $this->context->getUser()->getBlock() );
1634 
1637  throw new PermissionsError( 'upload' );
1638 
1641  throw new PermissionsError( 'edit' );
1642 
1644  throw new ReadOnlyError;
1645 
1646  case self::AS_RATE_LIMITED:
1647  throw new ThrottledError();
1648 
1650  $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
1651  throw new PermissionsError( $permission );
1652 
1654  throw new PermissionsError( 'editcontentmodel' );
1655 
1656  default:
1657  // We don't recognize $status->value. The only way that can happen
1658  // is if an extension hook aborted from inside ArticleSave.
1659  // Render the status object into $this->hookError
1660  // FIXME this sucks, we should just use the Status object throughout
1661  $this->hookError = '<div class="error">' ."\n" . $status->getWikiText() .
1662  '</div>';
1663  return true;
1664  }
1665  }
1666 
1676  protected function runPostMergeFilters( Content $content, Status $status, User $user ) {
1677  // Run old style post-section-merge edit filter
1678  if ( $this->hookError != '' ) {
1679  # ...or the hook could be expecting us to produce an error
1680  $status->fatal( 'hookaborted' );
1682  return false;
1683  }
1684 
1685  // Run new style post-section-merge edit filter
1686  if ( !Hooks::run( 'EditFilterMergedContent',
1687  [ $this->context, $content, $status, $this->summary,
1688  $user, $this->minoredit ] )
1689  ) {
1690  # Error messages etc. could be handled within the hook...
1691  if ( $status->isGood() ) {
1692  $status->fatal( 'hookaborted' );
1693  // Not setting $this->hookError here is a hack to allow the hook
1694  // to cause a return to the edit page without $this->hookError
1695  // being set. This is used by ConfirmEdit to display a captcha
1696  // without any error message cruft.
1697  } else {
1698  $this->hookError = $status->getWikiText();
1699  }
1700  // Use the existing $status->value if the hook set it
1701  if ( !$status->value ) {
1702  $status->value = self::AS_HOOK_ERROR;
1703  }
1704  return false;
1705  } elseif ( !$status->isOK() ) {
1706  # ...or the hook could be expecting us to produce an error
1707  // FIXME this sucks, we should just use the Status object throughout
1708  $this->hookError = $status->getWikiText();
1709  $status->fatal( 'hookaborted' );
1711  return false;
1712  }
1713 
1714  return true;
1715  }
1716 
1723  private function newSectionSummary( &$sectionanchor = null ) {
1724  global $wgParser;
1725 
1726  if ( $this->sectiontitle !== '' ) {
1727  $sectionanchor = $this->guessSectionName( $this->sectiontitle );
1728  // If no edit summary was specified, create one automatically from the section
1729  // title and have it link to the new section. Otherwise, respect the summary as
1730  // passed.
1731  if ( $this->summary === '' ) {
1732  $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
1733  return $this->context->msg( 'newsectionsummary' )
1734  ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1735  }
1736  } elseif ( $this->summary !== '' ) {
1737  $sectionanchor = $this->guessSectionName( $this->summary );
1738  # This is a new section, so create a link to the new section
1739  # in the revision summary.
1740  $cleanSummary = $wgParser->stripSectionName( $this->summary );
1741  return $this->context->msg( 'newsectionsummary' )
1742  ->rawParams( $cleanSummary )->inContentLanguage()->text();
1743  }
1744  return $this->summary;
1745  }
1746 
1771  public function internalAttemptSave( &$result, $bot = false ) {
1773  $user = $this->context->getUser();
1774 
1775  if ( !Hooks::run( 'EditPage::attemptSave', [ $this ] ) ) {
1776  wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
1777  $status->fatal( 'hookaborted' );
1778  $status->value = self::AS_HOOK_ERROR;
1779  return $status;
1780  }
1781 
1782  if ( $this->unicodeCheck !== self::UNICODE_CHECK ) {
1783  $status->fatal( 'unicode-support-fail' );
1785  return $status;
1786  }
1787 
1788  $request = $this->context->getRequest();
1789  $spam = $request->getText( 'wpAntispam' );
1790  if ( $spam !== '' ) {
1791  wfDebugLog(
1792  'SimpleAntiSpam',
1793  $user->getName() .
1794  ' editing "' .
1795  $this->mTitle->getPrefixedText() .
1796  '" submitted bogus field "' .
1797  $spam .
1798  '"'
1799  );
1800  $status->fatal( 'spamprotectionmatch', false );
1801  $status->value = self::AS_SPAM_ERROR;
1802  return $status;
1803  }
1804 
1805  try {
1806  # Construct Content object
1807  $textbox_content = $this->toEditContent( $this->textbox1 );
1808  } catch ( MWContentSerializationException $ex ) {
1809  $status->fatal(
1810  'content-failed-to-parse',
1811  $this->contentModel,
1812  $this->contentFormat,
1813  $ex->getMessage()
1814  );
1815  $status->value = self::AS_PARSE_ERROR;
1816  return $status;
1817  }
1818 
1819  # Check image redirect
1820  if ( $this->mTitle->getNamespace() == NS_FILE &&
1821  $textbox_content->isRedirect() &&
1822  !$user->isAllowed( 'upload' )
1823  ) {
1825  $status->setResult( false, $code );
1826 
1827  return $status;
1828  }
1829 
1830  # Check for spam
1831  $match = self::matchSummarySpamRegex( $this->summary );
1832  if ( $match === false && $this->section == 'new' ) {
1833  # $wgSpamRegex is enforced on this new heading/summary because, unlike
1834  # regular summaries, it is added to the actual wikitext.
1835  if ( $this->sectiontitle !== '' ) {
1836  # This branch is taken when the API is used with the 'sectiontitle' parameter.
1837  $match = self::matchSpamRegex( $this->sectiontitle );
1838  } else {
1839  # This branch is taken when the "Add Topic" user interface is used, or the API
1840  # is used with the 'summary' parameter.
1841  $match = self::matchSpamRegex( $this->summary );
1842  }
1843  }
1844  if ( $match === false ) {
1845  $match = self::matchSpamRegex( $this->textbox1 );
1846  }
1847  if ( $match !== false ) {
1848  $result['spam'] = $match;
1849  $ip = $request->getIP();
1850  $pdbk = $this->mTitle->getPrefixedDBkey();
1851  $match = str_replace( "\n", '', $match );
1852  wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
1853  $status->fatal( 'spamprotectionmatch', $match );
1854  $status->value = self::AS_SPAM_ERROR;
1855  return $status;
1856  }
1857  if ( !Hooks::run(
1858  'EditFilter',
1859  [ $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ] )
1860  ) {
1861  # Error messages etc. could be handled within the hook...
1862  $status->fatal( 'hookaborted' );
1863  $status->value = self::AS_HOOK_ERROR;
1864  return $status;
1865  } elseif ( $this->hookError != '' ) {
1866  # ...or the hook could be expecting us to produce an error
1867  $status->fatal( 'hookaborted' );
1869  return $status;
1870  }
1871 
1872  if ( $user->isBlockedFrom( $this->mTitle, false ) ) {
1873  // Auto-block user's IP if the account was "hard" blocked
1874  if ( !wfReadOnly() ) {
1875  $user->spreadAnyEditBlock();
1876  }
1877  # Check block state against master, thus 'false'.
1878  $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
1879  return $status;
1880  }
1881 
1882  $this->contentLength = strlen( $this->textbox1 );
1883  $config = $this->context->getConfig();
1884  $maxArticleSize = $config->get( 'MaxArticleSize' );
1885  if ( $this->contentLength > $maxArticleSize * 1024 ) {
1886  // Error will be displayed by showEditForm()
1887  $this->tooBig = true;
1888  $status->setResult( false, self::AS_CONTENT_TOO_BIG );
1889  return $status;
1890  }
1891 
1892  if ( !$user->isAllowed( 'edit' ) ) {
1893  if ( $user->isAnon() ) {
1894  $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
1895  return $status;
1896  } else {
1897  $status->fatal( 'readonlytext' );
1899  return $status;
1900  }
1901  }
1902 
1903  $changingContentModel = false;
1904  if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
1905  if ( !$config->get( 'ContentHandlerUseDB' ) ) {
1906  $status->fatal( 'editpage-cannot-use-custom-model' );
1908  return $status;
1909  } elseif ( !$user->isAllowed( 'editcontentmodel' ) ) {
1910  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
1911  return $status;
1912  }
1913  // Make sure the user can edit the page under the new content model too
1914  $titleWithNewContentModel = clone $this->mTitle;
1915  $titleWithNewContentModel->setContentModel( $this->contentModel );
1916  if ( !$titleWithNewContentModel->userCan( 'editcontentmodel', $user )
1917  || !$titleWithNewContentModel->userCan( 'edit', $user )
1918  ) {
1919  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
1920  return $status;
1921  }
1922 
1923  $changingContentModel = true;
1924  $oldContentModel = $this->mTitle->getContentModel();
1925  }
1926 
1927  if ( $this->changeTags ) {
1928  $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange(
1929  $this->changeTags, $user );
1930  if ( !$changeTagsStatus->isOK() ) {
1931  $changeTagsStatus->value = self::AS_CHANGE_TAG_ERROR;
1932  return $changeTagsStatus;
1933  }
1934  }
1935 
1936  if ( wfReadOnly() ) {
1937  $status->fatal( 'readonlytext' );
1939  return $status;
1940  }
1941  if ( $user->pingLimiter() || $user->pingLimiter( 'linkpurge', 0 )
1942  || ( $changingContentModel && $user->pingLimiter( 'editcontentmodel' ) )
1943  ) {
1944  $status->fatal( 'actionthrottledtext' );
1945  $status->value = self::AS_RATE_LIMITED;
1946  return $status;
1947  }
1948 
1949  # If the article has been deleted while editing, don't save it without
1950  # confirmation
1951  if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
1952  $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
1953  return $status;
1954  }
1955 
1956  # Load the page data from the master. If anything changes in the meantime,
1957  # we detect it by using page_latest like a token in a 1 try compare-and-swap.
1958  $this->page->loadPageData( 'fromdbmaster' );
1959  $new = !$this->page->exists();
1960 
1961  if ( $new ) {
1962  // Late check for create permission, just in case *PARANOIA*
1963  if ( !$this->mTitle->userCan( 'create', $user ) ) {
1964  $status->fatal( 'nocreatetext' );
1966  wfDebug( __METHOD__ . ": no create permission\n" );
1967  return $status;
1968  }
1969 
1970  // Don't save a new page if it's blank or if it's a MediaWiki:
1971  // message with content equivalent to default (allow empty pages
1972  // in this case to disable messages, see T52124)
1973  $defaultMessageText = $this->mTitle->getDefaultMessageText();
1974  if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false ) {
1975  $defaultText = $defaultMessageText;
1976  } else {
1977  $defaultText = '';
1978  }
1979 
1980  if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) {
1981  $this->blankArticle = true;
1982  $status->fatal( 'blankarticle' );
1983  $status->setResult( false, self::AS_BLANK_ARTICLE );
1984  return $status;
1985  }
1986 
1987  if ( !$this->runPostMergeFilters( $textbox_content, $status, $user ) ) {
1988  return $status;
1989  }
1990 
1991  $content = $textbox_content;
1992 
1993  $result['sectionanchor'] = '';
1994  if ( $this->section == 'new' ) {
1995  if ( $this->sectiontitle !== '' ) {
1996  // Insert the section title above the content.
1997  $content = $content->addSectionHeader( $this->sectiontitle );
1998  } elseif ( $this->summary !== '' ) {
1999  // Insert the section title above the content.
2000  $content = $content->addSectionHeader( $this->summary );
2001  }
2002  $this->summary = $this->newSectionSummary( $result['sectionanchor'] );
2003  }
2004 
2006 
2007  } else { # not $new
2008 
2009  # Article exists. Check for edit conflict.
2010 
2011  $this->page->clear(); # Force reload of dates, etc.
2012  $timestamp = $this->page->getTimestamp();
2013  $latest = $this->page->getLatest();
2014 
2015  wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
2016 
2017  // Check editRevId if set, which handles same-second timestamp collisions
2018  if ( $timestamp != $this->edittime
2019  || ( $this->editRevId !== null && $this->editRevId != $latest )
2020  ) {
2021  $this->isConflict = true;
2022  if ( $this->section == 'new' ) {
2023  if ( $this->page->getUserText() == $user->getName() &&
2024  $this->page->getComment() == $this->newSectionSummary()
2025  ) {
2026  // Probably a duplicate submission of a new comment.
2027  // This can happen when CDN resends a request after
2028  // a timeout but the first one actually went through.
2029  wfDebug( __METHOD__
2030  . ": duplicate new section submission; trigger edit conflict!\n" );
2031  } else {
2032  // New comment; suppress conflict.
2033  $this->isConflict = false;
2034  wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
2035  }
2036  } elseif ( $this->section == ''
2038  DB_MASTER, $this->mTitle->getArticleID(),
2039  $user->getId(), $this->edittime
2040  )
2041  ) {
2042  # Suppress edit conflict with self, except for section edits where merging is required.
2043  wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
2044  $this->isConflict = false;
2045  }
2046  }
2047 
2048  // If sectiontitle is set, use it, otherwise use the summary as the section title.
2049  if ( $this->sectiontitle !== '' ) {
2050  $sectionTitle = $this->sectiontitle;
2051  } else {
2052  $sectionTitle = $this->summary;
2053  }
2054 
2055  $content = null;
2056 
2057  if ( $this->isConflict ) {
2058  wfDebug( __METHOD__
2059  . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
2060  . " (id '{$this->editRevId}') (article time '{$timestamp}')\n" );
2061  // @TODO: replaceSectionAtRev() with base ID (not prior current) for ?oldid=X case
2062  // ...or disable section editing for non-current revisions (not exposed anyway).
2063  if ( $this->editRevId !== null ) {
2064  $content = $this->page->replaceSectionAtRev(
2065  $this->section,
2066  $textbox_content,
2067  $sectionTitle,
2068  $this->editRevId
2069  );
2070  } else {
2071  $content = $this->page->replaceSectionContent(
2072  $this->section,
2073  $textbox_content,
2074  $sectionTitle,
2075  $this->edittime
2076  );
2077  }
2078  } else {
2079  wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
2080  $content = $this->page->replaceSectionContent(
2081  $this->section,
2082  $textbox_content,
2083  $sectionTitle
2084  );
2085  }
2086 
2087  if ( is_null( $content ) ) {
2088  wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
2089  $this->isConflict = true;
2090  $content = $textbox_content; // do not try to merge here!
2091  } elseif ( $this->isConflict ) {
2092  # Attempt merge
2093  if ( $this->mergeChangesIntoContent( $content ) ) {
2094  // Successful merge! Maybe we should tell the user the good news?
2095  $this->isConflict = false;
2096  wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
2097  } else {
2098  $this->section = '';
2099  $this->textbox1 = ContentHandler::getContentText( $content );
2100  wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
2101  }
2102  }
2103 
2104  if ( $this->isConflict ) {
2105  $status->setResult( false, self::AS_CONFLICT_DETECTED );
2106  return $status;
2107  }
2108 
2109  if ( !$this->runPostMergeFilters( $content, $status, $user ) ) {
2110  return $status;
2111  }
2112 
2113  if ( $this->section == 'new' ) {
2114  // Handle the user preference to force summaries here
2115  if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) {
2116  $this->missingSummary = true;
2117  $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
2119  return $status;
2120  }
2121 
2122  // Do not allow the user to post an empty comment
2123  if ( $this->textbox1 == '' ) {
2124  $this->missingComment = true;
2125  $status->fatal( 'missingcommenttext' );
2127  return $status;
2128  }
2129  } elseif ( !$this->allowBlankSummary
2130  && !$content->equals( $this->getOriginalContent( $user ) )
2131  && !$content->isRedirect()
2132  && md5( $this->summary ) == $this->autoSumm
2133  ) {
2134  $this->missingSummary = true;
2135  $status->fatal( 'missingsummary' );
2137  return $status;
2138  }
2139 
2140  # All's well
2141  $sectionanchor = '';
2142  if ( $this->section == 'new' ) {
2143  $this->summary = $this->newSectionSummary( $sectionanchor );
2144  } elseif ( $this->section != '' ) {
2145  # Try to get a section anchor from the section source, redirect
2146  # to edited section if header found.
2147  # XXX: Might be better to integrate this into Article::replaceSectionAtRev
2148  # for duplicate heading checking and maybe parsing.
2149  $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
2150  # We can't deal with anchors, includes, html etc in the header for now,
2151  # headline would need to be parsed to improve this.
2152  if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
2153  $sectionanchor = $this->guessSectionName( $matches[2] );
2154  }
2155  }
2156  $result['sectionanchor'] = $sectionanchor;
2157 
2158  // Save errors may fall down to the edit form, but we've now
2159  // merged the section into full text. Clear the section field
2160  // so that later submission of conflict forms won't try to
2161  // replace that into a duplicated mess.
2162  $this->textbox1 = $this->toEditText( $content );
2163  $this->section = '';
2164 
2166  }
2167 
2168  if ( !$this->allowSelfRedirect
2169  && $content->isRedirect()
2170  && $content->getRedirectTarget()->equals( $this->getTitle() )
2171  ) {
2172  // If the page already redirects to itself, don't warn.
2173  $currentTarget = $this->getCurrentContent()->getRedirectTarget();
2174  if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
2175  $this->selfRedirect = true;
2176  $status->fatal( 'selfredirect' );
2178  return $status;
2179  }
2180  }
2181 
2182  // Check for length errors again now that the section is merged in
2183  $this->contentLength = strlen( $this->toEditText( $content ) );
2184  if ( $this->contentLength > $maxArticleSize * 1024 ) {
2185  $this->tooBig = true;
2186  $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
2187  return $status;
2188  }
2189 
2191  ( $new ? EDIT_NEW : EDIT_UPDATE ) |
2192  ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
2193  ( $bot ? EDIT_FORCE_BOT : 0 );
2194 
2195  $doEditStatus = $this->page->doEditContent(
2196  $content,
2197  $this->summary,
2198  $flags,
2199  false,
2200  $user,
2201  $content->getDefaultFormat(),
2204  );
2205 
2206  if ( !$doEditStatus->isOK() ) {
2207  // Failure from doEdit()
2208  // Show the edit conflict page for certain recognized errors from doEdit(),
2209  // but don't show it for errors from extension hooks
2210  $errors = $doEditStatus->getErrorsArray();
2211  if ( in_array( $errors[0][0],
2212  [ 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ] )
2213  ) {
2214  $this->isConflict = true;
2215  // Destroys data doEdit() put in $status->value but who cares
2216  $doEditStatus->value = self::AS_END;
2217  }
2218  return $doEditStatus;
2219  }
2220 
2221  $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
2222  if ( $result['nullEdit'] ) {
2223  // We don't know if it was a null edit until now, so increment here
2224  $user->pingLimiter( 'linkpurge' );
2225  }
2226  $result['redirect'] = $content->isRedirect();
2227 
2228  $this->updateWatchlist();
2229 
2230  // If the content model changed, add a log entry
2231  if ( $changingContentModel ) {
2233  $user,
2234  $new ? false : $oldContentModel,
2235  $this->contentModel,
2236  $this->summary
2237  );
2238  }
2239 
2240  return $status;
2241  }
2242 
2249  protected function addContentModelChangeLogEntry( User $user, $oldModel, $newModel, $reason ) {
2250  $new = $oldModel === false;
2251  $log = new ManualLogEntry( 'contentmodel', $new ? 'new' : 'change' );
2252  $log->setPerformer( $user );
2253  $log->setTarget( $this->mTitle );
2254  $log->setComment( $reason );
2255  $log->setParameters( [
2256  '4::oldmodel' => $oldModel,
2257  '5::newmodel' => $newModel
2258  ] );
2259  $logid = $log->insert();
2260  $log->publish( $logid );
2261  }
2262 
2266  protected function updateWatchlist() {
2267  $user = $this->context->getUser();
2268  if ( !$user->isLoggedIn() ) {
2269  return;
2270  }
2271 
2273  $watch = $this->watchthis;
2274  // Do this in its own transaction to reduce contention...
2275  DeferredUpdates::addCallableUpdate( function () use ( $user, $title, $watch ) {
2276  if ( $watch == $user->isWatched( $title, User::IGNORE_USER_RIGHTS ) ) {
2277  return; // nothing to change
2278  }
2280  } );
2281  }
2282 
2294  private function mergeChangesIntoContent( &$editContent ) {
2295  $db = wfGetDB( DB_MASTER );
2296 
2297  // This is the revision the editor started from
2298  $baseRevision = $this->getBaseRevision();
2299  $baseContent = $baseRevision ? $baseRevision->getContent() : null;
2300 
2301  if ( is_null( $baseContent ) ) {
2302  return false;
2303  }
2304 
2305  // The current state, we want to merge updates into it
2306  $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
2307  $currentContent = $currentRevision ? $currentRevision->getContent() : null;
2308 
2309  if ( is_null( $currentContent ) ) {
2310  return false;
2311  }
2312 
2313  $handler = ContentHandler::getForModelID( $baseContent->getModel() );
2314 
2315  $result = $handler->merge3( $baseContent, $editContent, $currentContent );
2316 
2317  if ( $result ) {
2318  $editContent = $result;
2319  // Update parentRevId to what we just merged.
2320  $this->parentRevId = $currentRevision->getId();
2321  return true;
2322  }
2323 
2324  return false;
2325  }
2326 
2332  public function getBaseRevision() {
2333  if ( !$this->mBaseRevision ) {
2334  $db = wfGetDB( DB_MASTER );
2335  $this->mBaseRevision = $this->editRevId
2336  ? Revision::newFromId( $this->editRevId, Revision::READ_LATEST )
2337  : Revision::loadFromTimestamp( $db, $this->mTitle, $this->edittime );
2338  }
2339  return $this->mBaseRevision;
2340  }
2341 
2349  public static function matchSpamRegex( $text ) {
2351  // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
2353  return self::matchSpamRegexInternal( $text, $regexes );
2354  }
2355 
2363  public static function matchSummarySpamRegex( $text ) {
2366  return self::matchSpamRegexInternal( $text, $regexes );
2367  }
2368 
2374  protected static function matchSpamRegexInternal( $text, $regexes ) {
2375  foreach ( $regexes as $regex ) {
2376  $matches = [];
2377  if ( preg_match( $regex, $text, $matches ) ) {
2378  return $matches[0];
2379  }
2380  }
2381  return false;
2382  }
2383 
2384  public function setHeaders() {
2385  $out = $this->context->getOutput();
2386 
2387  $out->addModules( 'mediawiki.action.edit' );
2388  $out->addModuleStyles( 'mediawiki.action.edit.styles' );
2389 
2390  $user = $this->context->getUser();
2391  if ( $user->getOption( 'showtoolbar' ) ) {
2392  // The addition of default buttons is handled by getEditToolbar() which
2393  // has its own dependency on this module. The call here ensures the module
2394  // is loaded in time (it has position "top") for other modules to register
2395  // buttons (e.g. extensions, gadgets, user scripts).
2396  $out->addModules( 'mediawiki.toolbar' );
2397  }
2398 
2399  if ( $user->getOption( 'uselivepreview' ) ) {
2400  $out->addModules( 'mediawiki.action.edit.preview' );
2401  }
2402 
2403  if ( $user->getOption( 'useeditwarning' ) ) {
2404  $out->addModules( 'mediawiki.action.edit.editWarning' );
2405  }
2406 
2407  # Enabled article-related sidebar, toplinks, etc.
2408  $out->setArticleRelated( true );
2409 
2410  $contextTitle = $this->getContextTitle();
2411  if ( $this->isConflict ) {
2412  $msg = 'editconflict';
2413  } elseif ( $contextTitle->exists() && $this->section != '' ) {
2414  $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
2415  } else {
2416  $msg = $contextTitle->exists()
2417  || ( $contextTitle->getNamespace() == NS_MEDIAWIKI
2418  && $contextTitle->getDefaultMessageText() !== false
2419  )
2420  ? 'editing'
2421  : 'creating';
2422  }
2423 
2424  # Use the title defined by DISPLAYTITLE magic word when present
2425  # NOTE: getDisplayTitle() returns HTML while getPrefixedText() returns plain text.
2426  # setPageTitle() treats the input as wikitext, which should be safe in either case.
2427  $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
2428  if ( $displayTitle === false ) {
2429  $displayTitle = $contextTitle->getPrefixedText();
2430  }
2431  $out->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
2432  # Transmit the name of the message to JavaScript for live preview
2433  # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
2434  $out->addJsConfigVars( [
2435  'wgEditMessage' => $msg,
2436  'wgAjaxEditStash' => $this->context->getConfig()->get( 'AjaxEditStash' ),
2437  ] );
2438  }
2439 
2443  protected function showIntro() {
2444  if ( $this->suppressIntro ) {
2445  return;
2446  }
2447 
2448  $out = $this->context->getOutput();
2449  $namespace = $this->mTitle->getNamespace();
2450 
2451  if ( $namespace == NS_MEDIAWIKI ) {
2452  # Show a warning if editing an interface message
2453  $out->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
2454  # If this is a default message (but not css or js),
2455  # show a hint that it is translatable on translatewiki.net
2456  if ( !$this->mTitle->hasContentModel( CONTENT_MODEL_CSS )
2457  && !$this->mTitle->hasContentModel( CONTENT_MODEL_JAVASCRIPT )
2458  ) {
2459  $defaultMessageText = $this->mTitle->getDefaultMessageText();
2460  if ( $defaultMessageText !== false ) {
2461  $out->wrapWikiMsg( "<div class='mw-translateinterface'>\n$1\n</div>",
2462  'translateinterface' );
2463  }
2464  }
2465  } elseif ( $namespace == NS_FILE ) {
2466  # Show a hint to shared repo
2467  $file = wfFindFile( $this->mTitle );
2468  if ( $file && !$file->isLocal() ) {
2469  $descUrl = $file->getDescriptionUrl();
2470  # there must be a description url to show a hint to shared repo
2471  if ( $descUrl ) {
2472  if ( !$this->mTitle->exists() ) {
2473  $out->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
2474  'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
2475  ] );
2476  } else {
2477  $out->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
2478  'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
2479  ] );
2480  }
2481  }
2482  }
2483  }
2484 
2485  # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2486  # Show log extract when the user is currently blocked
2487  if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
2488  $username = explode( '/', $this->mTitle->getText(), 2 )[0];
2489  $user = User::newFromName( $username, false /* allow IP users */ );
2490  $ip = User::isIP( $username );
2491  $block = Block::newFromTarget( $user, $user );
2492  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
2493  $out->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2494  [ 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ] );
2495  } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
2496  # Show log extract if the user is currently blocked
2498  $out,
2499  'block',
2500  MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
2501  '',
2502  [
2503  'lim' => 1,
2504  'showIfEmpty' => false,
2505  'msgKey' => [
2506  'blocked-notice-logextract',
2507  $user->getName() # Support GENDER in notice
2508  ]
2509  ]
2510  );
2511  }
2512  }
2513  # Try to add a custom edit intro, or use the standard one if this is not possible.
2514  if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
2516  $this->context->msg( 'helppage' )->inContentLanguage()->text()
2517  ) );
2518  if ( $this->context->getUser()->isLoggedIn() ) {
2519  $out->wrapWikiMsg(
2520  // Suppress the external link icon, consider the help url an internal one
2521  "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2522  [
2523  'newarticletext',
2524  $helpLink
2525  ]
2526  );
2527  } else {
2528  $out->wrapWikiMsg(
2529  // Suppress the external link icon, consider the help url an internal one
2530  "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2531  [
2532  'newarticletextanon',
2533  $helpLink
2534  ]
2535  );
2536  }
2537  }
2538  # Give a notice if the user is editing a deleted/moved page...
2539  if ( !$this->mTitle->exists() ) {
2540  $dbr = wfGetDB( DB_REPLICA );
2541 
2542  LogEventsList::showLogExtract( $out, [ 'delete', 'move' ], $this->mTitle,
2543  '',
2544  [
2545  'lim' => 10,
2546  'conds' => [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ],
2547  'showIfEmpty' => false,
2548  'msgKey' => [ 'recreate-moveddeleted-warn' ]
2549  ]
2550  );
2551  }
2552  }
2553 
2559  protected function showCustomIntro() {
2560  if ( $this->editintro ) {
2561  $title = Title::newFromText( $this->editintro );
2562  if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
2563  // Added using template syntax, to take <noinclude>'s into account.
2564  $this->context->getOutput()->addWikiTextTitleTidy(
2565  '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>',
2567  );
2568  return true;
2569  }
2570  }
2571  return false;
2572  }
2573 
2592  protected function toEditText( $content ) {
2593  if ( $content === null || $content === false || is_string( $content ) ) {
2594  return $content;
2595  }
2596 
2597  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2598  throw new MWException( 'This content model is not supported: ' . $content->getModel() );
2599  }
2600 
2601  return $content->serialize( $this->contentFormat );
2602  }
2603 
2620  protected function toEditContent( $text ) {
2621  if ( $text === false || $text === null ) {
2622  return $text;
2623  }
2624 
2625  $content = ContentHandler::makeContent( $text, $this->getTitle(),
2626  $this->contentModel, $this->contentFormat );
2627 
2628  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2629  throw new MWException( 'This content model is not supported: ' . $content->getModel() );
2630  }
2631 
2632  return $content;
2633  }
2634 
2643  public function showEditForm( $formCallback = null ) {
2644  # need to parse the preview early so that we know which templates are used,
2645  # otherwise users with "show preview after edit box" will get a blank list
2646  # we parse this near the beginning so that setHeaders can do the title
2647  # setting work instead of leaving it in getPreviewText
2648  $previewOutput = '';
2649  if ( $this->formtype == 'preview' ) {
2650  $previewOutput = $this->getPreviewText();
2651  }
2652 
2653  $out = $this->context->getOutput();
2654 
2655  // Avoid PHP 7.1 warning of passing $this by reference
2656  $editPage = $this;
2657  Hooks::run( 'EditPage::showEditForm:initial', [ &$editPage, &$out ] );
2658 
2659  $this->setHeaders();
2660 
2661  $this->addTalkPageText();
2662  $this->addEditNotices();
2663 
2664  if ( !$this->isConflict &&
2665  $this->section != '' &&
2666  !$this->isSectionEditSupported() ) {
2667  // We use $this->section to much before this and getVal('wgSection') directly in other places
2668  // at this point we can't reset $this->section to '' to fallback to non-section editing.
2669  // Someone is welcome to try refactoring though
2670  $out->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
2671  return;
2672  }
2673 
2674  $this->showHeader();
2675 
2676  $out->addHTML( $this->editFormPageTop );
2677 
2678  $user = $this->context->getUser();
2679  if ( $user->getOption( 'previewontop' ) ) {
2680  $this->displayPreviewArea( $previewOutput, true );
2681  }
2682 
2683  $out->addHTML( $this->editFormTextTop );
2684 
2685  $showToolbar = true;
2686  if ( $this->wasDeletedSinceLastEdit() ) {
2687  if ( $this->formtype == 'save' ) {
2688  // Hide the toolbar and edit area, user can click preview to get it back
2689  // Add an confirmation checkbox and explanation.
2690  $showToolbar = false;
2691  } else {
2692  $out->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2693  'deletedwhileediting' );
2694  }
2695  }
2696 
2697  // @todo add EditForm plugin interface and use it here!
2698  // search for textarea1 and textarea2, and allow EditForm to override all uses.
2699  $out->addHTML( Html::openElement(
2700  'form',
2701  [
2702  'class' => 'mw-editform',
2703  'id' => self::EDITFORM_ID,
2704  'name' => self::EDITFORM_ID,
2705  'method' => 'post',
2706  'action' => $this->getActionURL( $this->getContextTitle() ),
2707  'enctype' => 'multipart/form-data'
2708  ]
2709  ) );
2710 
2711  if ( is_callable( $formCallback ) ) {
2712  wfWarn( 'The $formCallback parameter to ' . __METHOD__ . 'is deprecated' );
2713  call_user_func_array( $formCallback, [ &$out ] );
2714  }
2715 
2716  // Add a check for Unicode support
2717  $out->addHTML( Html::hidden( 'wpUnicodeCheck', self::UNICODE_CHECK ) );
2718 
2719  // Add an empty field to trip up spambots
2720  $out->addHTML(
2721  Xml::openElement( 'div', [ 'id' => 'antispam-container', 'style' => 'display: none;' ] )
2722  . Html::rawElement(
2723  'label',
2724  [ 'for' => 'wpAntispam' ],
2725  $this->context->msg( 'simpleantispam-label' )->parse()
2726  )
2727  . Xml::element(
2728  'input',
2729  [
2730  'type' => 'text',
2731  'name' => 'wpAntispam',
2732  'id' => 'wpAntispam',
2733  'value' => ''
2734  ]
2735  )
2736  . Xml::closeElement( 'div' )
2737  );
2738 
2739  // Avoid PHP 7.1 warning of passing $this by reference
2740  $editPage = $this;
2741  Hooks::run( 'EditPage::showEditForm:fields', [ &$editPage, &$out ] );
2742 
2743  // Put these up at the top to ensure they aren't lost on early form submission
2744  $this->showFormBeforeText();
2745 
2746  if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
2747  $username = $this->lastDelete->user_name;
2748  $comment = CommentStore::newKey( 'log_comment' )->getComment( $this->lastDelete )->text;
2749 
2750  // It is better to not parse the comment at all than to have templates expanded in the middle
2751  // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
2752  $key = $comment === ''
2753  ? 'confirmrecreate-noreason'
2754  : 'confirmrecreate';
2755  $out->addHTML(
2756  '<div class="mw-confirm-recreate">' .
2757  $this->context->msg( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
2758  Xml::checkLabel( $this->context->msg( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
2759  [ 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' ]
2760  ) .
2761  '</div>'
2762  );
2763  }
2764 
2765  # When the summary is hidden, also hide them on preview/show changes
2766  if ( $this->nosummary ) {
2767  $out->addHTML( Html::hidden( 'nosummary', true ) );
2768  }
2769 
2770  # If a blank edit summary was previously provided, and the appropriate
2771  # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2772  # user being bounced back more than once in the event that a summary
2773  # is not required.
2774  # ####
2775  # For a bit more sophisticated detection of blank summaries, hash the
2776  # automatic one and pass that in the hidden field wpAutoSummary.
2777  if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
2778  $out->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
2779  }
2780 
2781  if ( $this->undidRev ) {
2782  $out->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
2783  }
2784 
2785  if ( $this->selfRedirect ) {
2786  $out->addHTML( Html::hidden( 'wpIgnoreSelfRedirect', true ) );
2787  }
2788 
2789  if ( $this->hasPresetSummary ) {
2790  // If a summary has been preset using &summary= we don't want to prompt for
2791  // a different summary. Only prompt for a summary if the summary is blanked.
2792  // (T19416)
2793  $this->autoSumm = md5( '' );
2794  }
2795 
2796  $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
2797  $out->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
2798 
2799  $out->addHTML( Html::hidden( 'oldid', $this->oldid ) );
2800  $out->addHTML( Html::hidden( 'parentRevId', $this->getParentRevId() ) );
2801 
2802  $out->addHTML( Html::hidden( 'format', $this->contentFormat ) );
2803  $out->addHTML( Html::hidden( 'model', $this->contentModel ) );
2804 
2805  $out->enableOOUI();
2806 
2807  if ( $this->section == 'new' ) {
2808  $this->showSummaryInput( true, $this->summary );
2809  $out->addHTML( $this->getSummaryPreview( true, $this->summary ) );
2810  }
2811 
2812  $out->addHTML( $this->editFormTextBeforeContent );
2813 
2814  if ( !$this->mTitle->isCssJsSubpage() && $showToolbar && $user->getOption( 'showtoolbar' ) ) {
2815  $out->addHTML( self::getEditToolbar( $this->mTitle ) );
2816  }
2817 
2818  if ( $this->blankArticle ) {
2819  $out->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) );
2820  }
2821 
2822  if ( $this->isConflict ) {
2823  // In an edit conflict bypass the overridable content form method
2824  // and fallback to the raw wpTextbox1 since editconflicts can't be
2825  // resolved between page source edits and custom ui edits using the
2826  // custom edit ui.
2827  $this->textbox2 = $this->textbox1;
2828 
2829  $content = $this->getCurrentContent();
2830  $this->textbox1 = $this->toEditText( $content );
2831 
2832  $this->showTextbox1();
2833  } else {
2834  $this->showContentForm();
2835  }
2836 
2837  $out->addHTML( $this->editFormTextAfterContent );
2838 
2839  $this->showStandardInputs();
2840 
2841  $this->showFormAfterText();
2842 
2843  $this->showTosSummary();
2844 
2845  $this->showEditTools();
2846 
2847  $out->addHTML( $this->editFormTextAfterTools . "\n" );
2848 
2849  $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
2850 
2851  $out->addHTML( Html::rawElement( 'div', [ 'class' => 'hiddencats' ],
2852  Linker::formatHiddenCategories( $this->page->getHiddenCategories() ) ) );
2853 
2854  $out->addHTML( Html::rawElement( 'div', [ 'class' => 'limitreport' ],
2855  self::getPreviewLimitReport( $this->mParserOutput ) ) );
2856 
2857  $out->addModules( 'mediawiki.action.edit.collapsibleFooter' );
2858 
2859  if ( $this->isConflict ) {
2860  try {
2861  $this->showConflict();
2862  } catch ( MWContentSerializationException $ex ) {
2863  // this can't really happen, but be nice if it does.
2864  $msg = $this->context->msg(
2865  'content-failed-to-parse',
2866  $this->contentModel,
2867  $this->contentFormat,
2868  $ex->getMessage()
2869  );
2870  $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
2871  }
2872  }
2873 
2874  // Set a hidden field so JS knows what edit form mode we are in
2875  if ( $this->isConflict ) {
2876  $mode = 'conflict';
2877  } elseif ( $this->preview ) {
2878  $mode = 'preview';
2879  } elseif ( $this->diff ) {
2880  $mode = 'diff';
2881  } else {
2882  $mode = 'text';
2883  }
2884  $out->addHTML( Html::hidden( 'mode', $mode, [ 'id' => 'mw-edit-mode' ] ) );
2885 
2886  // Marker for detecting truncated form data. This must be the last
2887  // parameter sent in order to be of use, so do not move me.
2888  $out->addHTML( Html::hidden( 'wpUltimateParam', true ) );
2889  $out->addHTML( $this->editFormTextBottom . "\n</form>\n" );
2890 
2891  if ( !$user->getOption( 'previewontop' ) ) {
2892  $this->displayPreviewArea( $previewOutput, false );
2893  }
2894  }
2895 
2903  public function makeTemplatesOnThisPageList( array $templates ) {
2904  $templateListFormatter = new TemplatesOnThisPageFormatter(
2905  $this->context, MediaWikiServices::getInstance()->getLinkRenderer()
2906  );
2907 
2908  // preview if preview, else section if section, else false
2909  $type = false;
2910  if ( $this->preview ) {
2911  $type = 'preview';
2912  } elseif ( $this->section != '' ) {
2913  $type = 'section';
2914  }
2915 
2916  return Html::rawElement( 'div', [ 'class' => 'templatesUsed' ],
2917  $templateListFormatter->format( $templates, $type )
2918  );
2919  }
2920 
2927  public static function extractSectionTitle( $text ) {
2928  preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
2929  if ( !empty( $matches[2] ) ) {
2930  global $wgParser;
2931  return $wgParser->stripSectionName( trim( $matches[2] ) );
2932  } else {
2933  return false;
2934  }
2935  }
2936 
2937  protected function showHeader() {
2938  $out = $this->context->getOutput();
2939  $user = $this->context->getUser();
2940  if ( $this->isConflict ) {
2941  $this->addExplainConflictHeader( $out );
2942  $this->editRevId = $this->page->getLatest();
2943  } else {
2944  if ( $this->section != '' && $this->section != 'new' ) {
2945  if ( !$this->summary && !$this->preview && !$this->diff ) {
2946  $sectionTitle = self::extractSectionTitle( $this->textbox1 ); // FIXME: use Content object
2947  if ( $sectionTitle !== false ) {
2948  $this->summary = "/* $sectionTitle */ ";
2949  }
2950  }
2951  }
2952 
2953  $buttonLabel = $this->context->msg( $this->getSubmitButtonLabel() )->text();
2954 
2955  if ( $this->missingComment ) {
2956  $out->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
2957  }
2958 
2959  if ( $this->missingSummary && $this->section != 'new' ) {
2960  $out->wrapWikiMsg(
2961  "<div id='mw-missingsummary'>\n$1\n</div>",
2962  [ 'missingsummary', $buttonLabel ]
2963  );
2964  }
2965 
2966  if ( $this->missingSummary && $this->section == 'new' ) {
2967  $out->wrapWikiMsg(
2968  "<div id='mw-missingcommentheader'>\n$1\n</div>",
2969  [ 'missingcommentheader', $buttonLabel ]
2970  );
2971  }
2972 
2973  if ( $this->blankArticle ) {
2974  $out->wrapWikiMsg(
2975  "<div id='mw-blankarticle'>\n$1\n</div>",
2976  [ 'blankarticle', $buttonLabel ]
2977  );
2978  }
2979 
2980  if ( $this->selfRedirect ) {
2981  $out->wrapWikiMsg(
2982  "<div id='mw-selfredirect'>\n$1\n</div>",
2983  [ 'selfredirect', $buttonLabel ]
2984  );
2985  }
2986 
2987  if ( $this->hookError !== '' ) {
2988  $out->addWikiText( $this->hookError );
2989  }
2990 
2991  if ( $this->section != 'new' ) {
2992  $revision = $this->mArticle->getRevisionFetched();
2993  if ( $revision ) {
2994  // Let sysop know that this will make private content public if saved
2995 
2996  if ( !$revision->userCan( Revision::DELETED_TEXT, $user ) ) {
2997  $out->wrapWikiMsg(
2998  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
2999  'rev-deleted-text-permission'
3000  );
3001  } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
3002  $out->wrapWikiMsg(
3003  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
3004  'rev-deleted-text-view'
3005  );
3006  }
3007 
3008  if ( !$revision->isCurrent() ) {
3009  $this->mArticle->setOldSubtitle( $revision->getId() );
3010  $out->addWikiMsg( 'editingold' );
3011  $this->isOldRev = true;
3012  }
3013  } elseif ( $this->mTitle->exists() ) {
3014  // Something went wrong
3015 
3016  $out->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
3017  [ 'missing-revision', $this->oldid ] );
3018  }
3019  }
3020  }
3021 
3022  if ( wfReadOnly() ) {
3023  $out->wrapWikiMsg(
3024  "<div id=\"mw-read-only-warning\">\n$1\n</div>",
3025  [ 'readonlywarning', wfReadOnlyReason() ]
3026  );
3027  } elseif ( $user->isAnon() ) {
3028  if ( $this->formtype != 'preview' ) {
3029  $out->wrapWikiMsg(
3030  "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
3031  [ 'anoneditwarning',
3032  // Log-in link
3033  SpecialPage::getTitleFor( 'Userlogin' )->getFullURL( [
3034  'returnto' => $this->getTitle()->getPrefixedDBkey()
3035  ] ),
3036  // Sign-up link
3037  SpecialPage::getTitleFor( 'CreateAccount' )->getFullURL( [
3038  'returnto' => $this->getTitle()->getPrefixedDBkey()
3039  ] )
3040  ]
3041  );
3042  } else {
3043  $out->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
3044  'anonpreviewwarning'
3045  );
3046  }
3047  } else {
3048  if ( $this->mTitle->isCssJsSubpage() ) {
3049  # Check the skin exists
3050  if ( $this->isWrongCaseCssJsPage() ) {
3051  $out->wrapWikiMsg(
3052  "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>",
3053  [ 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ]
3054  );
3055  }
3056  if ( $this->getTitle()->isSubpageOf( $user->getUserPage() ) ) {
3057  $isCssSubpage = $this->mTitle->isCssSubpage();
3058  $out->wrapWikiMsg( '<div class="mw-usercssjspublic">$1</div>',
3059  $isCssSubpage ? 'usercssispublic' : 'userjsispublic'
3060  );
3061  if ( $this->formtype !== 'preview' ) {
3062  $config = $this->context->getConfig();
3063  if ( $isCssSubpage && $config->get( 'AllowUserCss' ) ) {
3064  $out->wrapWikiMsg(
3065  "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
3066  [ 'usercssyoucanpreview' ]
3067  );
3068  }
3069 
3070  if ( $this->mTitle->isJsSubpage() && $config->get( 'AllowUserJs' ) ) {
3071  $out->wrapWikiMsg(
3072  "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
3073  [ 'userjsyoucanpreview' ]
3074  );
3075  }
3076  }
3077  }
3078  }
3079  }
3080 
3082 
3083  $this->addLongPageWarningHeader();
3084 
3085  # Add header copyright warning
3086  $this->showHeaderCopyrightWarning();
3087  }
3088 
3096  private function getSummaryInputAttributes( array $inputAttrs = null ) {
3097  // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
3098  return ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
3099  'id' => 'wpSummary',
3100  'name' => 'wpSummary',
3101  'maxlength' => '200',
3102  'tabindex' => 1,
3103  'size' => 60,
3104  'spellcheck' => 'true',
3105  ];
3106  }
3107 
3122  public function getSummaryInput( $summary = "", $labelText = null,
3123  $inputAttrs = null, $spanLabelAttrs = null
3124  ) {
3125  wfDeprecated( __METHOD__, '1.30' );
3126  $inputAttrs = $this->getSummaryInputAttributes( $inputAttrs );
3127  $inputAttrs += Linker::tooltipAndAccesskeyAttribs( 'summary' );
3128 
3129  $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : [] ) + [
3130  'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
3131  'id' => "wpSummaryLabel"
3132  ];
3133 
3134  $label = null;
3135  if ( $labelText ) {
3136  $label = Xml::tags(
3137  'label',
3138  $inputAttrs['id'] ? [ 'for' => $inputAttrs['id'] ] : null,
3139  $labelText
3140  );
3141  $label = Xml::tags( 'span', $spanLabelAttrs, $label );
3142  }
3143 
3144  $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
3145 
3146  return [ $label, $input ];
3147  }
3148 
3159  function getSummaryInputOOUI( $summary = "", $labelText = null, $inputAttrs = null ) {
3160  wfDeprecated( __METHOD__, '1.30' );
3161  $this->getSummaryInputWidget( $summary, $labelText, $inputAttrs );
3162  }
3163 
3173  function getSummaryInputWidget( $summary = "", $labelText = null, $inputAttrs = null ) {
3174  $inputAttrs = OOUI\Element::configFromHtmlAttributes(
3175  $this->getSummaryInputAttributes( $inputAttrs )
3176  );
3177  $inputAttrs += [
3178  'title' => Linker::titleAttrib( 'summary' ),
3179  'accessKey' => Linker::accesskey( 'summary' ),
3180  ];
3181 
3182  // For compatibility with old scripts and extensions, we want the legacy 'id' on the `<input>`
3183  $inputAttrs['inputId'] = $inputAttrs['id'];
3184  $inputAttrs['id'] = 'wpSummaryWidget';
3185 
3186  return new OOUI\FieldLayout(
3187  new OOUI\TextInputWidget( [
3188  'value' => $summary,
3189  'infusable' => true,
3190  ] + $inputAttrs ),
3191  [
3192  'label' => new OOUI\HtmlSnippet( $labelText ),
3193  'align' => 'top',
3194  'id' => 'wpSummaryLabel',
3195  'classes' => [ $this->missingSummary ? 'mw-summarymissed' : 'mw-summary' ],
3196  ]
3197  );
3198  }
3199 
3206  protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
3207  # Add a class if 'missingsummary' is triggered to allow styling of the summary line
3208  $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
3209  if ( $isSubjectPreview ) {
3210  if ( $this->nosummary ) {
3211  return;
3212  }
3213  } else {
3214  if ( !$this->mShowSummaryField ) {
3215  return;
3216  }
3217  }
3218 
3219  $labelText = $this->context->msg( $isSubjectPreview ? 'subject' : 'summary' )->parse();
3220  $this->context->getOutput()->addHTML( $this->getSummaryInputWidget(
3221  $summary,
3222  $labelText,
3223  [ 'class' => $summaryClass ]
3224  ) );
3225  }
3226 
3234  protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
3235  // avoid spaces in preview, gets always trimmed on save
3236  $summary = trim( $summary );
3237  if ( !$summary || ( !$this->preview && !$this->diff ) ) {
3238  return "";
3239  }
3240 
3241  global $wgParser;
3242 
3243  if ( $isSubjectPreview ) {
3244  $summary = $this->context->msg( 'newsectionsummary' )
3245  ->rawParams( $wgParser->stripSectionName( $summary ) )
3246  ->inContentLanguage()->text();
3247  }
3248 
3249  $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
3250 
3251  $summary = $this->context->msg( $message )->parse()
3252  . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
3253  return Xml::tags( 'div', [ 'class' => 'mw-summary-preview' ], $summary );
3254  }
3255 
3256  protected function showFormBeforeText() {
3257  $out = $this->context->getOutput();
3258  $out->addHTML( Html::hidden( 'wpSection', $this->section ) );
3259  $out->addHTML( Html::hidden( 'wpStarttime', $this->starttime ) );
3260  $out->addHTML( Html::hidden( 'wpEdittime', $this->edittime ) );
3261  $out->addHTML( Html::hidden( 'editRevId', $this->editRevId ) );
3262  $out->addHTML( Html::hidden( 'wpScrolltop', $this->scrolltop, [ 'id' => 'wpScrolltop' ] ) );
3263  }
3264 
3265  protected function showFormAfterText() {
3278  $this->context->getOutput()->addHTML(
3279  "\n" .
3280  Html::hidden( "wpEditToken", $this->context->getUser()->getEditToken() ) .
3281  "\n"
3282  );
3283  }
3284 
3293  protected function showContentForm() {
3294  $this->showTextbox1();
3295  }
3296 
3305  protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
3306  if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
3307  $attribs = [ 'style' => 'display:none;' ];
3308  } else {
3309  $classes = []; // Textarea CSS
3310  if ( $this->mTitle->isProtected( 'edit' ) &&
3311  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [ '' ]
3312  ) {
3313  # Is the title semi-protected?
3314  if ( $this->mTitle->isSemiProtected() ) {
3315  $classes[] = 'mw-textarea-sprotected';
3316  } else {
3317  # Then it must be protected based on static groups (regular)
3318  $classes[] = 'mw-textarea-protected';
3319  }
3320  # Is the title cascade-protected?
3321  if ( $this->mTitle->isCascadeProtected() ) {
3322  $classes[] = 'mw-textarea-cprotected';
3323  }
3324  }
3325  # Is an old revision being edited?
3326  if ( $this->isOldRev ) {
3327  $classes[] = 'mw-textarea-oldrev';
3328  }
3329 
3330  $attribs = [ 'tabindex' => 1 ];
3331 
3332  if ( is_array( $customAttribs ) ) {
3334  }
3335 
3336  if ( count( $classes ) ) {
3337  if ( isset( $attribs['class'] ) ) {
3338  $classes[] = $attribs['class'];
3339  }
3340  $attribs['class'] = implode( ' ', $classes );
3341  }
3342  }
3343 
3344  $this->showTextbox(
3345  $textoverride !== null ? $textoverride : $this->textbox1,
3346  'wpTextbox1',
3347  $attribs
3348  );
3349  }
3350 
3351  protected function showTextbox2() {
3352  $this->showTextbox( $this->textbox2, 'wpTextbox2', [ 'tabindex' => 6, 'readonly' ] );
3353  }
3354 
3355  protected function showTextbox( $text, $name, $customAttribs = [] ) {
3356  $wikitext = $this->addNewLineAtEnd( $text );
3357 
3358  $attribs = $this->buildTextboxAttribs( $name, $customAttribs, $this->context->getUser() );
3359 
3360  $this->context->getOutput()->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
3361  }
3362 
3363  protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
3364  $classes = [];
3365  if ( $isOnTop ) {
3366  $classes[] = 'ontop';
3367  }
3368 
3369  $attribs = [ 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) ];
3370 
3371  if ( $this->formtype != 'preview' ) {
3372  $attribs['style'] = 'display: none;';
3373  }
3374 
3375  $out = $this->context->getOutput();
3376  $out->addHTML( Xml::openElement( 'div', $attribs ) );
3377 
3378  if ( $this->formtype == 'preview' ) {
3379  $this->showPreview( $previewOutput );
3380  } else {
3381  // Empty content container for LivePreview
3382  $pageViewLang = $this->mTitle->getPageViewLanguage();
3383  $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3384  'class' => 'mw-content-' . $pageViewLang->getDir() ];
3385  $out->addHTML( Html::rawElement( 'div', $attribs ) );
3386  }
3387 
3388  $out->addHTML( '</div>' );
3389 
3390  if ( $this->formtype == 'diff' ) {
3391  try {
3392  $this->showDiff();
3393  } catch ( MWContentSerializationException $ex ) {
3394  $msg = $this->context->msg(
3395  'content-failed-to-parse',
3396  $this->contentModel,
3397  $this->contentFormat,
3398  $ex->getMessage()
3399  );
3400  $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
3401  }
3402  }
3403  }
3404 
3411  protected function showPreview( $text ) {
3412  if ( $this->mArticle instanceof CategoryPage ) {
3413  $this->mArticle->openShowCategory();
3414  }
3415  # This hook seems slightly odd here, but makes things more
3416  # consistent for extensions.
3417  $out = $this->context->getOutput();
3418  Hooks::run( 'OutputPageBeforeHTML', [ &$out, &$text ] );
3419  $out->addHTML( $text );
3420  if ( $this->mArticle instanceof CategoryPage ) {
3421  $this->mArticle->closeShowCategory();
3422  }
3423  }
3424 
3432  public function showDiff() {
3434 
3435  $oldtitlemsg = 'currentrev';
3436  # if message does not exist, show diff against the preloaded default
3437  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
3438  $oldtext = $this->mTitle->getDefaultMessageText();
3439  if ( $oldtext !== false ) {
3440  $oldtitlemsg = 'defaultmessagetext';
3441  $oldContent = $this->toEditContent( $oldtext );
3442  } else {
3443  $oldContent = null;
3444  }
3445  } else {
3446  $oldContent = $this->getCurrentContent();
3447  }
3448 
3449  $textboxContent = $this->toEditContent( $this->textbox1 );
3450  if ( $this->editRevId !== null ) {
3451  $newContent = $this->page->replaceSectionAtRev(
3452  $this->section, $textboxContent, $this->summary, $this->editRevId
3453  );
3454  } else {
3455  $newContent = $this->page->replaceSectionContent(
3456  $this->section, $textboxContent, $this->summary, $this->edittime
3457  );
3458  }
3459 
3460  if ( $newContent ) {
3461  Hooks::run( 'EditPageGetDiffContent', [ $this, &$newContent ] );
3462 
3463  $user = $this->context->getUser();
3465  $newContent = $newContent->preSaveTransform( $this->mTitle, $user, $popts );
3466  }
3467 
3468  if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
3469  $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
3470  $newtitle = $this->context->msg( 'yourtext' )->parse();
3471 
3472  if ( !$oldContent ) {
3473  $oldContent = $newContent->getContentHandler()->makeEmptyContent();
3474  }
3475 
3476  if ( !$newContent ) {
3477  $newContent = $oldContent->getContentHandler()->makeEmptyContent();
3478  }
3479 
3480  $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->context );
3481  $de->setContent( $oldContent, $newContent );
3482 
3483  $difftext = $de->getDiff( $oldtitle, $newtitle );
3484  $de->showDiffStyle();
3485  } else {
3486  $difftext = '';
3487  }
3488 
3489  $this->context->getOutput()->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
3490  }
3491 
3495  protected function showHeaderCopyrightWarning() {
3496  $msg = 'editpage-head-copy-warn';
3497  if ( !$this->context->msg( $msg )->isDisabled() ) {
3498  $this->context->getOutput()->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
3499  'editpage-head-copy-warn' );
3500  }
3501  }
3502 
3511  protected function showTosSummary() {
3512  $msg = 'editpage-tos-summary';
3513  Hooks::run( 'EditPageTosSummary', [ $this->mTitle, &$msg ] );
3514  if ( !$this->context->msg( $msg )->isDisabled() ) {
3515  $out = $this->context->getOutput();
3516  $out->addHTML( '<div class="mw-tos-summary">' );
3517  $out->addWikiMsg( $msg );
3518  $out->addHTML( '</div>' );
3519  }
3520  }
3521 
3526  protected function showEditTools() {
3527  $this->context->getOutput()->addHTML( '<div class="mw-editTools">' .
3528  $this->context->msg( 'edittools' )->inContentLanguage()->parse() .
3529  '</div>' );
3530  }
3531 
3538  protected function getCopywarn() {
3539  return self::getCopyrightWarning( $this->mTitle );
3540  }
3541 
3550  public static function getCopyrightWarning( $title, $format = 'plain', $langcode = null ) {
3552  if ( $wgRightsText ) {
3553  $copywarnMsg = [ 'copyrightwarning',
3554  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]',
3555  $wgRightsText ];
3556  } else {
3557  $copywarnMsg = [ 'copyrightwarning2',
3558  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' ];
3559  }
3560  // Allow for site and per-namespace customization of contribution/copyright notice.
3561  Hooks::run( 'EditPageCopyrightWarning', [ $title, &$copywarnMsg ] );
3562 
3563  $msg = call_user_func_array( 'wfMessage', $copywarnMsg )->title( $title );
3564  if ( $langcode ) {
3565  $msg->inLanguage( $langcode );
3566  }
3567  return "<div id=\"editpage-copywarn\">\n" .
3568  $msg->$format() . "\n</div>";
3569  }
3570 
3578  public static function getPreviewLimitReport( $output ) {
3579  if ( !$output || !$output->getLimitReportData() ) {
3580  return '';
3581  }
3582 
3583  $limitReport = Html::rawElement( 'div', [ 'class' => 'mw-limitReportExplanation' ],
3584  wfMessage( 'limitreport-title' )->parseAsBlock()
3585  );
3586 
3587  // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
3588  $limitReport .= Html::openElement( 'div', [ 'class' => 'preview-limit-report-wrapper' ] );
3589 
3590  $limitReport .= Html::openElement( 'table', [
3591  'class' => 'preview-limit-report wikitable'
3592  ] ) .
3593  Html::openElement( 'tbody' );
3594 
3595  foreach ( $output->getLimitReportData() as $key => $value ) {
3596  if ( Hooks::run( 'ParserLimitReportFormat',
3597  [ $key, &$value, &$limitReport, true, true ]
3598  ) ) {
3599  $keyMsg = wfMessage( $key );
3600  $valueMsg = wfMessage( [ "$key-value-html", "$key-value" ] );
3601  if ( !$valueMsg->exists() ) {
3602  $valueMsg = new RawMessage( '$1' );
3603  }
3604  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3605  $limitReport .= Html::openElement( 'tr' ) .
3606  Html::rawElement( 'th', null, $keyMsg->parse() ) .
3607  Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) .
3608  Html::closeElement( 'tr' );
3609  }
3610  }
3611  }
3612 
3613  $limitReport .= Html::closeElement( 'tbody' ) .
3614  Html::closeElement( 'table' ) .
3615  Html::closeElement( 'div' );
3616 
3617  return $limitReport;
3618  }
3619 
3620  protected function showStandardInputs( &$tabindex = 2 ) {
3621  $out = $this->context->getOutput();
3622  $out->addHTML( "<div class='editOptions'>\n" );
3623 
3624  if ( $this->section != 'new' ) {
3625  $this->showSummaryInput( false, $this->summary );
3626  $out->addHTML( $this->getSummaryPreview( false, $this->summary ) );
3627  }
3628 
3629  $checkboxes = $this->getCheckboxesWidget(
3630  $tabindex,
3631  [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ]
3632  );
3633  $checkboxesHTML = new OOUI\HorizontalLayout( [ 'items' => $checkboxes ] );
3634 
3635  $out->addHTML( "<div class='editCheckboxes'>" . $checkboxesHTML . "</div>\n" );
3636 
3637  // Show copyright warning.
3638  $out->addWikiText( $this->getCopywarn() );
3639  $out->addHTML( $this->editFormTextAfterWarn );
3640 
3641  $out->addHTML( "<div class='editButtons'>\n" );
3642  $out->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
3643 
3644  $cancel = $this->getCancelLink();
3645  if ( $cancel !== '' ) {
3646  $cancel .= Html::element( 'span',
3647  [ 'class' => 'mw-editButtons-pipe-separator' ],
3648  $this->context->msg( 'pipe-separator' )->text() );
3649  }
3650 
3651  $message = $this->context->msg( 'edithelppage' )->inContentLanguage()->text();
3652  $edithelpurl = Skin::makeInternalOrExternalUrl( $message );
3653  $edithelp =
3655  $this->context->msg( 'edithelp' )->text(),
3656  [ 'target' => 'helpwindow', 'href' => $edithelpurl ],
3657  [ 'mw-ui-quiet' ]
3658  ) .
3659  $this->context->msg( 'word-separator' )->escaped() .
3660  $this->context->msg( 'newwindow' )->parse();
3661 
3662  $out->addHTML( " <span class='cancelLink'>{$cancel}</span>\n" );
3663  $out->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" );
3664  $out->addHTML( "</div><!-- editButtons -->\n" );
3665 
3666  Hooks::run( 'EditPage::showStandardInputs:options', [ $this, $out, &$tabindex ] );
3667 
3668  $out->addHTML( "</div><!-- editOptions -->\n" );
3669  }
3670 
3675  protected function showConflict() {
3676  $out = $this->context->getOutput();
3677  // Avoid PHP 7.1 warning of passing $this by reference
3678  $editPage = $this;
3679  if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$editPage, &$out ] ) ) {
3680  $this->incrementConflictStats();
3681 
3682  $out->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
3683 
3684  $content1 = $this->toEditContent( $this->textbox1 );
3685  $content2 = $this->toEditContent( $this->textbox2 );
3686 
3687  $handler = ContentHandler::getForModelID( $this->contentModel );
3688  $de = $handler->createDifferenceEngine( $this->context );
3689  $de->setContent( $content2, $content1 );
3690  $de->showDiff(
3691  $this->context->msg( 'yourtext' )->parse(),
3692  $this->context->msg( 'storedversion' )->text()
3693  );
3694 
3695  $out->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
3696  $this->showTextbox2();
3697  }
3698  }
3699 
3700  protected function incrementConflictStats() {
3701  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
3702  $stats->increment( 'edit.failures.conflict' );
3703  // Only include 'standard' namespaces to avoid creating unknown numbers of statsd metrics
3704  if (
3705  $this->mTitle->getNamespace() >= NS_MAIN &&
3706  $this->mTitle->getNamespace() <= NS_CATEGORY_TALK
3707  ) {
3708  $stats->increment( 'edit.failures.conflict.byNamespaceId.' . $this->mTitle->getNamespace() );
3709  }
3710  }
3711 
3715  public function getCancelLink() {
3716  $cancelParams = [];
3717  if ( !$this->isConflict && $this->oldid > 0 ) {
3718  $cancelParams['oldid'] = $this->oldid;
3719  } elseif ( $this->getContextTitle()->isRedirect() ) {
3720  $cancelParams['redirect'] = 'no';
3721  }
3722 
3723  return new OOUI\ButtonWidget( [
3724  'id' => 'mw-editform-cancel',
3725  'href' => $this->getContextTitle()->getLinkUrl( $cancelParams ),
3726  'label' => new OOUI\HtmlSnippet( $this->context->msg( 'cancel' )->parse() ),
3727  'framed' => false,
3728  'infusable' => true,
3729  'flags' => 'destructive',
3730  ] );
3731  }
3732 
3742  protected function getActionURL( Title $title ) {
3743  return $title->getLocalURL( [ 'action' => $this->action ] );
3744  }
3745 
3753  protected function wasDeletedSinceLastEdit() {
3754  if ( $this->deletedSinceEdit !== null ) {
3755  return $this->deletedSinceEdit;
3756  }
3757 
3758  $this->deletedSinceEdit = false;
3759 
3760  if ( !$this->mTitle->exists() && $this->mTitle->isDeletedQuick() ) {
3761  $this->lastDelete = $this->getLastDelete();
3762  if ( $this->lastDelete ) {
3763  $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
3764  if ( $deleteTime > $this->starttime ) {
3765  $this->deletedSinceEdit = true;
3766  }
3767  }
3768  }
3769 
3770  return $this->deletedSinceEdit;
3771  }
3772 
3776  protected function getLastDelete() {
3777  $dbr = wfGetDB( DB_REPLICA );
3778  $commentQuery = CommentStore::newKey( 'log_comment' )->getJoin();
3779  $data = $dbr->selectRow(
3780  [ 'logging', 'user' ] + $commentQuery['tables'],
3781  [
3782  'log_type',
3783  'log_action',
3784  'log_timestamp',
3785  'log_user',
3786  'log_namespace',
3787  'log_title',
3788  'log_params',
3789  'log_deleted',
3790  'user_name'
3791  ] + $commentQuery['fields'], [
3792  'log_namespace' => $this->mTitle->getNamespace(),
3793  'log_title' => $this->mTitle->getDBkey(),
3794  'log_type' => 'delete',
3795  'log_action' => 'delete',
3796  'user_id=log_user'
3797  ],
3798  __METHOD__,
3799  [ 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ],
3800  [
3801  'user' => [ 'JOIN', 'user_id=log_user' ],
3802  ] + $commentQuery['joins']
3803  );
3804  // Quick paranoid permission checks...
3805  if ( is_object( $data ) ) {
3806  if ( $data->log_deleted & LogPage::DELETED_USER ) {
3807  $data->user_name = $this->context->msg( 'rev-deleted-user' )->escaped();
3808  }
3809 
3810  if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
3811  $data->log_comment_text = $this->context->msg( 'rev-deleted-comment' )->escaped();
3812  $data->log_comment_data = null;
3813  }
3814  }
3815 
3816  return $data;
3817  }
3818 
3824  public function getPreviewText() {
3825  $out = $this->context->getOutput();
3826  $config = $this->context->getConfig();
3827 
3828  if ( $config->get( 'RawHtml' ) && !$this->mTokenOk ) {
3829  // Could be an offsite preview attempt. This is very unsafe if
3830  // HTML is enabled, as it could be an attack.
3831  $parsedNote = '';
3832  if ( $this->textbox1 !== '' ) {
3833  // Do not put big scary notice, if previewing the empty
3834  // string, which happens when you initially edit
3835  // a category page, due to automatic preview-on-open.
3836  $parsedNote = $out->parse( "<div class='previewnote'>" .
3837  $this->context->msg( 'session_fail_preview_html' )->text() . "</div>",
3838  true, /* interface */true );
3839  }
3840  $this->incrementEditFailureStats( 'session_loss' );
3841  return $parsedNote;
3842  }
3843 
3844  $note = '';
3845 
3846  try {
3847  $content = $this->toEditContent( $this->textbox1 );
3848 
3849  $previewHTML = '';
3850  if ( !Hooks::run(
3851  'AlternateEditPreview',
3852  [ $this, &$content, &$previewHTML, &$this->mParserOutput ] )
3853  ) {
3854  return $previewHTML;
3855  }
3856 
3857  # provide a anchor link to the editform
3858  $continueEditing = '<span class="mw-continue-editing">' .
3859  '[[#' . self::EDITFORM_ID . '|' .
3860  $this->context->getLanguage()->getArrow() . ' ' .
3861  $this->context->msg( 'continue-editing' )->text() . ']]</span>';
3862  if ( $this->mTriedSave && !$this->mTokenOk ) {
3863  if ( $this->mTokenOkExceptSuffix ) {
3864  $note = $this->context->msg( 'token_suffix_mismatch' )->plain();
3865  $this->incrementEditFailureStats( 'bad_token' );
3866  } else {
3867  $note = $this->context->msg( 'session_fail_preview' )->plain();
3868  $this->incrementEditFailureStats( 'session_loss' );
3869  }
3870  } elseif ( $this->incompleteForm ) {
3871  $note = $this->context->msg( 'edit_form_incomplete' )->plain();
3872  if ( $this->mTriedSave ) {
3873  $this->incrementEditFailureStats( 'incomplete_form' );
3874  }
3875  } else {
3876  $note = $this->context->msg( 'previewnote' )->plain() . ' ' . $continueEditing;
3877  }
3878 
3879  # don't parse non-wikitext pages, show message about preview
3880  if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
3881  if ( $this->mTitle->isCssJsSubpage() ) {
3882  $level = 'user';
3883  } elseif ( $this->mTitle->isCssOrJsPage() ) {
3884  $level = 'site';
3885  } else {
3886  $level = false;
3887  }
3888 
3889  if ( $content->getModel() == CONTENT_MODEL_CSS ) {
3890  $format = 'css';
3891  if ( $level === 'user' && !$config->get( 'AllowUserCss' ) ) {
3892  $format = false;
3893  }
3894  } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
3895  $format = 'js';
3896  if ( $level === 'user' && !$config->get( 'AllowUserJs' ) ) {
3897  $format = false;
3898  }
3899  } else {
3900  $format = false;
3901  }
3902 
3903  # Used messages to make sure grep find them:
3904  # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
3905  if ( $level && $format ) {
3906  $note = "<div id='mw-{$level}{$format}preview'>" .
3907  $this->context->msg( "{$level}{$format}preview" )->text() .
3908  ' ' . $continueEditing . "</div>";
3909  }
3910  }
3911 
3912  # If we're adding a comment, we need to show the
3913  # summary as the headline
3914  if ( $this->section === "new" && $this->summary !== "" ) {
3915  $content = $content->addSectionHeader( $this->summary );
3916  }
3917 
3918  $hook_args = [ $this, &$content ];
3919  Hooks::run( 'EditPageGetPreviewContent', $hook_args );
3920 
3921  $parserResult = $this->doPreviewParse( $content );
3922  $parserOutput = $parserResult['parserOutput'];
3923  $previewHTML = $parserResult['html'];
3924  $this->mParserOutput = $parserOutput;
3925  $out->addParserOutputMetadata( $parserOutput );
3926 
3927  if ( count( $parserOutput->getWarnings() ) ) {
3928  $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
3929  }
3930 
3931  } catch ( MWContentSerializationException $ex ) {
3932  $m = $this->context->msg(
3933  'content-failed-to-parse',
3934  $this->contentModel,
3935  $this->contentFormat,
3936  $ex->getMessage()
3937  );
3938  $note .= "\n\n" . $m->parse();
3939  $previewHTML = '';
3940  }
3941 
3942  if ( $this->isConflict ) {
3943  $conflict = '<h2 id="mw-previewconflict">'
3944  . $this->context->msg( 'previewconflict' )->escaped() . "</h2>\n";
3945  } else {
3946  $conflict = '<hr />';
3947  }
3948 
3949  $previewhead = "<div class='previewnote'>\n" .
3950  '<h2 id="mw-previewheader">' . $this->context->msg( 'preview' )->escaped() . "</h2>" .
3951  $out->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
3952 
3953  $pageViewLang = $this->mTitle->getPageViewLanguage();
3954  $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3955  'class' => 'mw-content-' . $pageViewLang->getDir() ];
3956  $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
3957 
3958  return $previewhead . $previewHTML . $this->previewTextAfterContent;
3959  }
3960 
3961  private function incrementEditFailureStats( $failureType ) {
3962  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
3963  $stats->increment( 'edit.failures.' . $failureType );
3964  }
3965 
3970  protected function getPreviewParserOptions() {
3971  $parserOptions = $this->page->makeParserOptions( $this->context );
3972  $parserOptions->setIsPreview( true );
3973  $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
3974  $parserOptions->enableLimitReport();
3975  return $parserOptions;
3976  }
3977 
3987  protected function doPreviewParse( Content $content ) {
3988  $user = $this->context->getUser();
3989  $parserOptions = $this->getPreviewParserOptions();
3990  $pstContent = $content->preSaveTransform( $this->mTitle, $user, $parserOptions );
3991  $scopedCallback = $parserOptions->setupFakeRevision(
3992  $this->mTitle, $pstContent, $user );
3993  $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions );
3994  ScopedCallback::consume( $scopedCallback );
3995  $parserOutput->setEditSectionTokens( false ); // no section edit links
3996  return [
3997  'parserOutput' => $parserOutput,
3998  'html' => $parserOutput->getText() ];
3999  }
4000 
4004  public function getTemplates() {
4005  if ( $this->preview || $this->section != '' ) {
4006  $templates = [];
4007  if ( !isset( $this->mParserOutput ) ) {
4008  return $templates;
4009  }
4010  foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
4011  foreach ( array_keys( $template ) as $dbk ) {
4012  $templates[] = Title::makeTitle( $ns, $dbk );
4013  }
4014  }
4015  return $templates;
4016  } else {
4017  return $this->mTitle->getTemplateLinksFrom();
4018  }
4019  }
4020 
4028  public static function getEditToolbar( $title = null ) {
4031 
4032  $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
4033  $showSignature = true;
4034  if ( $title ) {
4035  $showSignature = MWNamespace::wantSignatures( $title->getNamespace() );
4036  }
4037 
4047  $toolarray = [
4048  [
4049  'id' => 'mw-editbutton-bold',
4050  'open' => '\'\'\'',
4051  'close' => '\'\'\'',
4052  'sample' => wfMessage( 'bold_sample' )->text(),
4053  'tip' => wfMessage( 'bold_tip' )->text(),
4054  ],
4055  [
4056  'id' => 'mw-editbutton-italic',
4057  'open' => '\'\'',
4058  'close' => '\'\'',
4059  'sample' => wfMessage( 'italic_sample' )->text(),
4060  'tip' => wfMessage( 'italic_tip' )->text(),
4061  ],
4062  [
4063  'id' => 'mw-editbutton-link',
4064  'open' => '[[',
4065  'close' => ']]',
4066  'sample' => wfMessage( 'link_sample' )->text(),
4067  'tip' => wfMessage( 'link_tip' )->text(),
4068  ],
4069  [
4070  'id' => 'mw-editbutton-extlink',
4071  'open' => '[',
4072  'close' => ']',
4073  'sample' => wfMessage( 'extlink_sample' )->text(),
4074  'tip' => wfMessage( 'extlink_tip' )->text(),
4075  ],
4076  [
4077  'id' => 'mw-editbutton-headline',
4078  'open' => "\n== ",
4079  'close' => " ==\n",
4080  'sample' => wfMessage( 'headline_sample' )->text(),
4081  'tip' => wfMessage( 'headline_tip' )->text(),
4082  ],
4083  $imagesAvailable ? [
4084  'id' => 'mw-editbutton-image',
4085  'open' => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
4086  'close' => ']]',
4087  'sample' => wfMessage( 'image_sample' )->text(),
4088  'tip' => wfMessage( 'image_tip' )->text(),
4089  ] : false,
4090  $imagesAvailable ? [
4091  'id' => 'mw-editbutton-media',
4092  'open' => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
4093  'close' => ']]',
4094  'sample' => wfMessage( 'media_sample' )->text(),
4095  'tip' => wfMessage( 'media_tip' )->text(),
4096  ] : false,
4097  [
4098  'id' => 'mw-editbutton-nowiki',
4099  'open' => "<nowiki>",
4100  'close' => "</nowiki>",
4101  'sample' => wfMessage( 'nowiki_sample' )->text(),
4102  'tip' => wfMessage( 'nowiki_tip' )->text(),
4103  ],
4104  $showSignature ? [
4105  'id' => 'mw-editbutton-signature',
4106  'open' => wfMessage( 'sig-text', '~~~~' )->inContentLanguage()->text(),
4107  'close' => '',
4108  'sample' => '',
4109  'tip' => wfMessage( 'sig_tip' )->text(),
4110  ] : false,
4111  [
4112  'id' => 'mw-editbutton-hr',
4113  'open' => "\n----\n",
4114  'close' => '',
4115  'sample' => '',
4116  'tip' => wfMessage( 'hr_tip' )->text(),
4117  ]
4118  ];
4119 
4120  $script = 'mw.loader.using("mediawiki.toolbar", function () {';
4121  foreach ( $toolarray as $tool ) {
4122  if ( !$tool ) {
4123  continue;
4124  }
4125 
4126  $params = [
4127  // Images are defined in ResourceLoaderEditToolbarModule
4128  false,
4129  // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
4130  // Older browsers show a "speedtip" type message only for ALT.
4131  // Ideally these should be different, realistically they
4132  // probably don't need to be.
4133  $tool['tip'],
4134  $tool['open'],
4135  $tool['close'],
4136  $tool['sample'],
4137  $tool['id'],
4138  ];
4139 
4140  $script .= Xml::encodeJsCall(
4141  'mw.toolbar.addButton',
4142  $params,
4143  ResourceLoader::inDebugMode()
4144  );
4145  }
4146 
4147  $script .= '});';
4148 
4149  $toolbar = '<div id="toolbar"></div>';
4150 
4151  if ( Hooks::run( 'EditPageBeforeEditToolbar', [ &$toolbar ] ) ) {
4152  // Only add the old toolbar cruft to the page payload if the toolbar has not
4153  // been over-written by a hook caller
4154  $wgOut->addScript( ResourceLoader::makeInlineScript( $script ) );
4155  };
4156 
4157  return $toolbar;
4158  }
4159 
4178  public function getCheckboxesDefinition( $checked ) {
4179  $checkboxes = [];
4180 
4181  $user = $this->context->getUser();
4182  // don't show the minor edit checkbox if it's a new page or section
4183  if ( !$this->isNew && $user->isAllowed( 'minoredit' ) ) {
4184  $checkboxes['wpMinoredit'] = [
4185  'id' => 'wpMinoredit',
4186  'label-message' => 'minoredit',
4187  // Uses messages: tooltip-minoredit, accesskey-minoredit
4188  'tooltip' => 'minoredit',
4189  'label-id' => 'mw-editpage-minoredit',
4190  'legacy-name' => 'minor',
4191  'default' => $checked['minor'],
4192  ];
4193  }
4194 
4195  if ( $user->isLoggedIn() ) {
4196  $checkboxes['wpWatchthis'] = [
4197  'id' => 'wpWatchthis',
4198  'label-message' => 'watchthis',
4199  // Uses messages: tooltip-watch, accesskey-watch
4200  'tooltip' => 'watch',
4201  'label-id' => 'mw-editpage-watch',
4202  'legacy-name' => 'watch',
4203  'default' => $checked['watch'],
4204  ];
4205  }
4206 
4207  $editPage = $this;
4208  Hooks::run( 'EditPageGetCheckboxesDefinition', [ $editPage, &$checkboxes ] );
4209 
4210  return $checkboxes;
4211  }
4212 
4222  public function getCheckboxes( &$tabindex, $checked ) {
4224 
4225  $checkboxes = [];
4226  $checkboxesDef = $this->getCheckboxesDefinition( $checked );
4227 
4228  // Backwards-compatibility for the EditPageBeforeEditChecks hook
4229  if ( !$this->isNew ) {
4230  $checkboxes['minor'] = '';
4231  }
4232  $checkboxes['watch'] = '';
4233 
4234  foreach ( $checkboxesDef as $name => $options ) {
4235  $legacyName = isset( $options['legacy-name'] ) ? $options['legacy-name'] : $name;
4236  $label = $this->context->msg( $options['label-message'] )->parse();
4237  $attribs = [
4238  'tabindex' => ++$tabindex,
4239  'id' => $options['id'],
4240  ];
4241  $labelAttribs = [
4242  'for' => $options['id'],
4243  ];
4244  if ( isset( $options['tooltip'] ) ) {
4245  $attribs['accesskey'] = $this->context->msg( "accesskey-{$options['tooltip']}" )->text();
4246  $labelAttribs['title'] = Linker::titleAttrib( $options['tooltip'], 'withaccess' );
4247  }
4248  if ( isset( $options['title-message'] ) ) {
4249  $labelAttribs['title'] = $this->context->msg( $options['title-message'] )->text();
4250  }
4251  if ( isset( $options['label-id'] ) ) {
4252  $labelAttribs['id'] = $options['label-id'];
4253  }
4254  $checkboxHtml =
4255  Xml::check( $name, $options['default'], $attribs ) .
4256  '&#160;' .
4257  Xml::tags( 'label', $labelAttribs, $label );
4258 
4260  $checkboxHtml = Html::rawElement( 'div', [ 'class' => 'mw-ui-checkbox' ], $checkboxHtml );
4261  }
4262 
4263  $checkboxes[ $legacyName ] = $checkboxHtml;
4264  }
4265 
4266  // Avoid PHP 7.1 warning of passing $this by reference
4267  $editPage = $this;
4268  Hooks::run( 'EditPageBeforeEditChecks', [ &$editPage, &$checkboxes, &$tabindex ], '1.29' );
4269  return $checkboxes;
4270  }
4271 
4283  public function getCheckboxesOOUI( &$tabindex, $checked ) {
4284  return $this->getCheckboxesWidget( $tabindex, $checked );
4285  }
4286 
4297  public function getCheckboxesWidget( &$tabindex, $checked ) {
4298  $checkboxes = [];
4299  $checkboxesDef = $this->getCheckboxesDefinition( $checked );
4300 
4301  foreach ( $checkboxesDef as $name => $options ) {
4302  $legacyName = isset( $options['legacy-name'] ) ? $options['legacy-name'] : $name;
4303 
4304  $title = null;
4305  $accesskey = null;
4306  if ( isset( $options['tooltip'] ) ) {
4307  $accesskey = $this->context->msg( "accesskey-{$options['tooltip']}" )->text();
4308  $title = Linker::titleAttrib( $options['tooltip'] );
4309  }
4310  if ( isset( $options['title-message'] ) ) {
4311  $title = $this->context->msg( $options['title-message'] )->text();
4312  }
4313 
4314  $checkboxes[ $legacyName ] = new OOUI\FieldLayout(
4315  new OOUI\CheckboxInputWidget( [
4316  'tabIndex' => ++$tabindex,
4317  'accessKey' => $accesskey,
4318  'id' => $options['id'] . 'Widget',
4319  'inputId' => $options['id'],
4320  'name' => $name,
4321  'selected' => $options['default'],
4322  'infusable' => true,
4323  ] ),
4324  [
4325  'align' => 'inline',
4326  'label' => new OOUI\HtmlSnippet( $this->context->msg( $options['label-message'] )->parse() ),
4327  'title' => $title,
4328  'id' => isset( $options['label-id'] ) ? $options['label-id'] : null,
4329  ]
4330  );
4331  }
4332 
4333  // Backwards-compatibility hack to run the EditPageBeforeEditChecks hook. It's important,
4334  // people have used it for the weirdest things completely unrelated to checkboxes...
4335  // And if we're gonna run it, might as well allow its legacy checkboxes to be shown.
4336  $legacyCheckboxes = [];
4337  if ( !$this->isNew ) {
4338  $legacyCheckboxes['minor'] = '';
4339  }
4340  $legacyCheckboxes['watch'] = '';
4341  // Copy new-style checkboxes into an old-style structure
4342  foreach ( $checkboxes as $name => $oouiLayout ) {
4343  $legacyCheckboxes[$name] = (string)$oouiLayout;
4344  }
4345  // Avoid PHP 7.1 warning of passing $this by reference
4346  $ep = $this;
4347  Hooks::run( 'EditPageBeforeEditChecks', [ &$ep, &$legacyCheckboxes, &$tabindex ], '1.29' );
4348  // Copy back any additional old-style checkboxes into the new-style structure
4349  foreach ( $legacyCheckboxes as $name => $html ) {
4350  if ( $html && !isset( $checkboxes[$name] ) ) {
4351  $checkboxes[$name] = new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $html ) ] );
4352  }
4353  }
4354 
4355  return $checkboxes;
4356  }
4357 
4364  protected function getSubmitButtonLabel() {
4365  $labelAsPublish =
4366  $this->context->getConfig()->get( 'EditSubmitButtonLabelPublish' );
4367 
4368  // Can't use $this->isNew as that's also true if we're adding a new section to an extant page
4369  $newPage = !$this->mTitle->exists();
4370 
4371  if ( $labelAsPublish ) {
4372  $buttonLabelKey = $newPage ? 'publishpage' : 'publishchanges';
4373  } else {
4374  $buttonLabelKey = $newPage ? 'savearticle' : 'savechanges';
4375  }
4376 
4377  return $buttonLabelKey;
4378  }
4379 
4388  public function getEditButtons( &$tabindex ) {
4389  $buttons = [];
4390 
4391  $buttonLabel = $this->context->msg( $this->getSubmitButtonLabel() )->text();
4392 
4393  $attribs = [
4394  'name' => 'wpSave',
4395  'tabindex' => ++$tabindex,
4396  ];
4397 
4398  $saveConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
4399  $buttons['save'] = new OOUI\ButtonInputWidget( [
4400  'id' => 'wpSaveWidget',
4401  'inputId' => 'wpSave',
4402  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4403  'useInputTag' => true,
4404  'flags' => [ 'constructive', 'primary' ],
4405  'label' => $buttonLabel,
4406  'infusable' => true,
4407  'type' => 'submit',
4408  'title' => Linker::titleAttrib( 'save' ),
4409  'accessKey' => Linker::accesskey( 'save' ),
4410  ] + $saveConfig );
4411 
4412  $attribs = [
4413  'name' => 'wpPreview',
4414  'tabindex' => ++$tabindex,
4415  ];
4416 
4417  $previewConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
4418  $buttons['preview'] = new OOUI\ButtonInputWidget( [
4419  'id' => 'wpPreviewWidget',
4420  'inputId' => 'wpPreview',
4421  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4422  'useInputTag' => true,
4423  'label' => $this->context->msg( 'showpreview' )->text(),
4424  'infusable' => true,
4425  'type' => 'submit',
4426  'title' => Linker::titleAttrib( 'preview' ),
4427  'accessKey' => Linker::accesskey( 'preview' ),
4428  ] + $previewConfig );
4429 
4430  $attribs = [
4431  'name' => 'wpDiff',
4432  'tabindex' => ++$tabindex,
4433  ];
4434 
4435  $diffConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
4436  $buttons['diff'] = new OOUI\ButtonInputWidget( [
4437  'id' => 'wpDiffWidget',
4438  'inputId' => 'wpDiff',
4439  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4440  'useInputTag' => true,
4441  'label' => $this->context->msg( 'showdiff' )->text(),
4442  'infusable' => true,
4443  'type' => 'submit',
4444  'title' => Linker::titleAttrib( 'diff' ),
4445  'accessKey' => Linker::accesskey( 'diff' ),
4446  ] + $diffConfig );
4447 
4448  // Avoid PHP 7.1 warning of passing $this by reference
4449  $editPage = $this;
4450  Hooks::run( 'EditPageBeforeEditButtons', [ &$editPage, &$buttons, &$tabindex ] );
4451 
4452  return $buttons;
4453  }
4454 
4459  public function noSuchSectionPage() {
4460  $out = $this->context->getOutput();
4461  $out->prepareErrorPage( $this->context->msg( 'nosuchsectiontitle' ) );
4462 
4463  $res = $this->context->msg( 'nosuchsectiontext', $this->section )->parseAsBlock();
4464 
4465  // Avoid PHP 7.1 warning of passing $this by reference
4466  $editPage = $this;
4467  Hooks::run( 'EditPageNoSuchSection', [ &$editPage, &$res ] );
4468  $out->addHTML( $res );
4469 
4470  $out->returnToMain( false, $this->mTitle );
4471  }
4472 
4478  public function spamPageWithContent( $match = false ) {
4479  $this->textbox2 = $this->textbox1;
4480 
4481  if ( is_array( $match ) ) {
4482  $match = $this->context->getLanguage()->listToText( $match );
4483  }
4484  $out = $this->context->getOutput();
4485  $out->prepareErrorPage( $this->context->msg( 'spamprotectiontitle' ) );
4486 
4487  $out->addHTML( '<div id="spamprotected">' );
4488  $out->addWikiMsg( 'spamprotectiontext' );
4489  if ( $match ) {
4490  $out->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
4491  }
4492  $out->addHTML( '</div>' );
4493 
4494  $out->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
4495  $this->showDiff();
4496 
4497  $out->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
4498  $this->showTextbox2();
4499 
4500  $out->addReturnTo( $this->getContextTitle(), [ 'action' => 'edit' ] );
4501  }
4502 
4513  protected function safeUnicodeInput( $request, $field ) {
4514  return rtrim( $request->getText( $field ) );
4515  }
4516 
4526  protected function safeUnicodeOutput( $text ) {
4527  return $text;
4528  }
4529 
4533  protected function addEditNotices() {
4534  $out = $this->context->getOutput();
4535  $editNotices = $this->mTitle->getEditNotices( $this->oldid );
4536  if ( count( $editNotices ) ) {
4537  $out->addHTML( implode( "\n", $editNotices ) );
4538  } else {
4539  $msg = $this->context->msg( 'editnotice-notext' );
4540  if ( !$msg->isDisabled() ) {
4541  $out->addHTML(
4542  '<div class="mw-editnotice-notext">'
4543  . $msg->parseAsBlock()
4544  . '</div>'
4545  );
4546  }
4547  }
4548  }
4549 
4553  protected function addTalkPageText() {
4554  if ( $this->mTitle->isTalkPage() ) {
4555  $this->context->getOutput()->addWikiMsg( 'talkpagetext' );
4556  }
4557  }
4558 
4562  protected function addLongPageWarningHeader() {
4563  if ( $this->contentLength === false ) {
4564  $this->contentLength = strlen( $this->textbox1 );
4565  }
4566 
4567  $out = $this->context->getOutput();
4568  $lang = $this->context->getLanguage();
4569  $maxArticleSize = $this->context->getConfig()->get( 'MaxArticleSize' );
4570  if ( $this->tooBig || $this->contentLength > $maxArticleSize * 1024 ) {
4571  $out->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
4572  [
4573  'longpageerror',
4574  $lang->formatNum( round( $this->contentLength / 1024, 3 ) ),
4575  $lang->formatNum( $maxArticleSize )
4576  ]
4577  );
4578  } else {
4579  if ( !$this->context->msg( 'longpage-hint' )->isDisabled() ) {
4580  $out->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
4581  [
4582  'longpage-hint',
4583  $lang->formatSize( strlen( $this->textbox1 ) ),
4584  strlen( $this->textbox1 )
4585  ]
4586  );
4587  }
4588  }
4589  }
4590 
4594  protected function addPageProtectionWarningHeaders() {
4595  $out = $this->context->getOutput();
4596  if ( $this->mTitle->isProtected( 'edit' ) &&
4597  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [ '' ]
4598  ) {
4599  # Is the title semi-protected?
4600  if ( $this->mTitle->isSemiProtected() ) {
4601  $noticeMsg = 'semiprotectedpagewarning';
4602  } else {
4603  # Then it must be protected based on static groups (regular)
4604  $noticeMsg = 'protectedpagewarning';
4605  }
4606  LogEventsList::showLogExtract( $out, 'protect', $this->mTitle, '',
4607  [ 'lim' => 1, 'msgKey' => [ $noticeMsg ] ] );
4608  }
4609  if ( $this->mTitle->isCascadeProtected() ) {
4610  # Is this page under cascading protection from some source pages?
4611 
4612  list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
4613  $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
4614  $cascadeSourcesCount = count( $cascadeSources );
4615  if ( $cascadeSourcesCount > 0 ) {
4616  # Explain, and list the titles responsible
4617  foreach ( $cascadeSources as $page ) {
4618  $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
4619  }
4620  }
4621  $notice .= '</div>';
4622  $out->wrapWikiMsg( $notice, [ 'cascadeprotectedwarning', $cascadeSourcesCount ] );
4623  }
4624  if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
4625  LogEventsList::showLogExtract( $out, 'protect', $this->mTitle, '',
4626  [ 'lim' => 1,
4627  'showIfEmpty' => false,
4628  'msgKey' => [ 'titleprotectedwarning' ],
4629  'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ] );
4630  }
4631  }
4632 
4637  protected function addExplainConflictHeader( OutputPage $out ) {
4638  $out->wrapWikiMsg(
4639  "<div class='mw-explainconflict'>\n$1\n</div>",
4640  [ 'explainconflict', $this->context->msg( $this->getSubmitButtonLabel() )->text() ]
4641  );
4642  }
4643 
4652  $attribs = $customAttribs + [
4653  'accesskey' => ',',
4654  'id' => $name,
4655  'cols' => 80,
4656  'rows' => 25,
4657  // Avoid PHP notices when appending preferences
4658  // (appending allows customAttribs['style'] to still work).
4659  'style' => ''
4660  ];
4661 
4662  // The following classes can be used here:
4663  // * mw-editfont-default
4664  // * mw-editfont-monospace
4665  // * mw-editfont-sans-serif
4666  // * mw-editfont-serif
4667  $class = 'mw-editfont-' . $user->getOption( 'editfont' );
4668 
4669  if ( isset( $attribs['class'] ) ) {
4670  if ( is_string( $attribs['class'] ) ) {
4671  $attribs['class'] .= ' ' . $class;
4672  } elseif ( is_array( $attribs['class'] ) ) {
4673  $attribs['class'][] = $class;
4674  }
4675  } else {
4676  $attribs['class'] = $class;
4677  }
4678 
4679  $pageLang = $this->mTitle->getPageLanguage();
4680  $attribs['lang'] = $pageLang->getHtmlCode();
4681  $attribs['dir'] = $pageLang->getDir();
4682 
4683  return $attribs;
4684  }
4685 
4691  protected function addNewLineAtEnd( $wikitext ) {
4692  if ( strval( $wikitext ) !== '' ) {
4693  // Ensure there's a newline at the end, otherwise adding lines
4694  // is awkward.
4695  // But don't add a newline if the text is empty, or Firefox in XHTML
4696  // mode will show an extra newline. A bit annoying.
4697  $wikitext .= "\n";
4698  return $wikitext;
4699  }
4700  return $wikitext;
4701  }
4702 
4713  private function guessSectionName( $text ) {
4714  global $wgParser;
4715 
4716  // Detect Microsoft browsers
4717  $userAgent = $this->context->getRequest()->getHeader( 'User-Agent' );
4718  if ( $userAgent && preg_match( '/MSIE|Edge/', $userAgent ) ) {
4719  // ...and redirect them to legacy encoding, if available
4720  return $wgParser->guessLegacySectionNameFromWikiText( $text );
4721  }
4722  // Meanwhile, real browsers get real anchors
4723  return $wgParser->guessSectionNameFromWikiText( $text );
4724  }
4725 }
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:452
EditPage\$editFormTextBeforeContent
$editFormTextBeforeContent
Definition: EditPage.php:409
EditPage\$mTriedSave
bool $mTriedSave
Definition: EditPage.php:285
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:293
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:244
Html\linkButton
static linkButton( $contents, array $attrs, array $modifiers=[])
Returns an HTML link element in a string styled as a button (when $wgUseMediaWikiUIEverywhere is enab...
Definition: Html.php:165
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:166
EditPage\$contentModel
string $contentModel
Definition: EditPage.php:395
EditPage\showFormBeforeText
showFormBeforeText()
Definition: EditPage.php:3256
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:268
EditPage\internalAttemptSave
internalAttemptSave(&$result, $bot=false)
Attempt submission (no UI)
Definition: EditPage.php:1771
EditPage\$lastDelete
bool stdClass $lastDelete
Definition: EditPage.php:276
$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 probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping $template
Definition: hooks.txt:781
EditPage\tokenOk
tokenOk(&$request)
Make sure the form isn't faking a user's credentials.
Definition: EditPage.php:1468
EditPage\$editFormPageTop
string $editFormPageTop
Before even the preview.
Definition: EditPage.php:407
EditPage\AS_BLANK_ARTICLE
const AS_BLANK_ARTICLE
Status: user tried to create a blank page and wpIgnoreBlankArticle == false.
Definition: EditPage.php:113
EditPage\showContentForm
showContentForm()
Subpage overridable method for printing the form for page content editing By default this simply outp...
Definition: EditPage.php:3293
EditPage\$mTitle
Title $mTitle
Definition: EditPage.php:228
EditPage\getSummaryInputOOUI
getSummaryInputOOUI( $summary="", $labelText=null, $inputAttrs=null)
Builds a standard summary input with a label.
Definition: EditPage.php:3159
Html\textarea
static textarea( $name, $value='', array $attribs=[])
Convenience function to produce a <textarea> element.
Definition: Html.php:741
Content\serialize
serialize( $format=null)
Convenience method for serializing this Content object.
EditPage\spamPageWithContent
spamPageWithContent( $match=false)
Show "your edit contains spam" page with your diff and text.
Definition: EditPage.php:4478
EditPage\$section
string $section
Definition: EditPage.php:371
ParserOutput
Definition: ParserOutput.php:24
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:835
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:116
EditPage\$scrolltop
null $scrolltop
Definition: EditPage.php:389
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:2294
EditPage\displayPermissionsError
displayPermissionsError(array $permErrors)
Display a permissions error page, like OutputPage::showPermissionsErrorPage(), but with the following...
Definition: EditPage.php:740
$wgParser
$wgParser
Definition: Setup.php:824
EditPage\$editFormTextAfterContent
$editFormTextAfterContent
Definition: EditPage.php:413
EditPage\displayPreviewArea
displayPreviewArea( $previewOutput, $isOnTop=false)
Definition: EditPage.php:3363
EditPage\$blankArticle
bool $blankArticle
Definition: EditPage.php:303
EditPage\$allowBlankSummary
bool $allowBlankSummary
Definition: EditPage.php:300
Xml\tags
static tags( $element, $attribs=null, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:131
$wgRightsText
$wgRightsText
If either $wgRightsUrl or $wgRightsPage is specified then this variable gives the text for the link.
Definition: DefaultSettings.php:7036
EditPage\$editFormTextBottom
$editFormTextBottom
Definition: EditPage.php:412
EditPage\getSummaryInputAttributes
getSummaryInputAttributes(array $inputAttrs=null)
Helper function for summary input functions, which returns the neccessary attributes for the input.
Definition: EditPage.php:3096
EditPage\$editFormTextTop
$editFormTextTop
Definition: EditPage.php:408
EditPage\$editintro
string $editintro
Definition: EditPage.php:386
EditPage\showTextbox2
showTextbox2()
Definition: EditPage.php:3351
EDIT_FORCE_BOT
const EDIT_FORCE_BOT
Definition: Defines.php:157
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
EditPage\$summary
string $summary
Definition: EditPage.php:359
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:81
EditPage\buildTextboxAttribs
buildTextboxAttribs( $name, array $customAttribs, User $user)
Definition: EditPage.php:4651
captcha-old.count
count
Definition: captcha-old.py:249
EditPage\$textbox2
string $textbox2
Definition: EditPage.php:356
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:1405
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
EditPage\$mTokenOk
bool $mTokenOk
Definition: EditPage.php:279
$regexes
if( $wgSpamBlacklistFiles) $regexes
Definition: cleanup.php:88
CONTENT_MODEL_CSS
const CONTENT_MODEL_CSS
Definition: Defines.php:238
$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 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! 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:1963
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:2040
EditPage\$oldid
int $oldid
Definition: EditPage.php:380
EditPage\getContextTitle
getContextTitle()
Get the context title object.
Definition: EditPage.php:503
$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. '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). '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:1245
EditPage\showTosSummary
showTosSummary()
Give a chance for site and per-namespace customizations of terms of service summary link that might e...
Definition: EditPage.php:3511
EditPage\isWrongCaseCssJsPage
isWrongCaseCssJsPage()
Checks whether the user entered a skin name in uppercase, e.g.
Definition: EditPage.php:857
EditPage\AS_UNICODE_NOT_SUPPORTED
const AS_UNICODE_NOT_SUPPORTED
Status: edit rejected because browser doesn't support Unicode.
Definition: EditPage.php:188
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:222
EditPage\$save
bool $save
Definition: EditPage.php:335
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
EditPage\addPageProtectionWarningHeaders
addPageProtectionWarningHeaders()
Definition: EditPage.php:4594
$wgUseMediaWikiUIEverywhere
$wgUseMediaWikiUIEverywhere
Temporary variable that applies MediaWiki UI wherever it can be supported.
Definition: DefaultSettings.php:3208
EditPage\setContextTitle
setContextTitle( $title)
Set the context Title object.
Definition: EditPage.php:492
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:37
EditPage\edit
edit()
This is the function that gets called for "action=edit".
Definition: EditPage.php:566
EditPage\$autoSumm
string $autoSumm
Definition: EditPage.php:315
NS_FILE
const NS_FILE
Definition: Defines.php:71
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:4513
$params
$params
Definition: styleTest.css.php:40
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:1112
EditPage\getLastDelete
getLastDelete()
Definition: EditPage.php:3776
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1324
EditPage\incrementConflictStats
incrementConflictStats()
Definition: EditPage.php:3700
EditPage\addEditNotices
addEditNotices()
Definition: EditPage.php:4533
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:550
EditPage\$editFormTextAfterTools
$editFormTextAfterTools
Definition: EditPage.php:411
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:61
page
target page
Definition: All_system_messages.txt:1267
EditPage\$editFormTextAfterWarn
$editFormTextAfterWarn
Definition: EditPage.php:410
ContentHandler\getForTitle
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
Definition: ContentHandler.php:240
diff
also included in $newHeader if any indicating whether we should show just the diff
Definition: hooks.txt:1246
$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:1390
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
EditPage\AS_CONTENT_TOO_BIG
const AS_CONTENT_TOO_BIG
Status: Content too big (> $wgMaxArticleSize)
Definition: EditPage.php:76
$wgTitle
if(! $wgRequest->checkUrlExtension()) if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') if(! $wgEnableAPI) $wgTitle
Definition: api.php:68
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:28
EditPage\showHeaderCopyrightWarning
showHeaderCopyrightWarning()
Show the header copyright warning.
Definition: EditPage.php:3495
EditPage\getEditPermissionErrors
getEditPermissionErrors( $rigor='secure')
Definition: EditPage.php:696
EditPage\addLongPageWarningHeader
addLongPageWarningHeader()
Definition: EditPage.php:4562
EditPage\$context
IContextSource $context
Definition: EditPage.php:437
EditPage\$didSave
$didSave
Definition: EditPage.php:418
CommentStore\newKey
static newKey( $key)
Static constructor for easier chaining.
Definition: CommentStore.php:114
EditPage\AS_BLOCKED_PAGE_FOR_USER
const AS_BLOCKED_PAGE_FOR_USER
Status: User is blocked from editing this page.
Definition: EditPage.php:71
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:124
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:109
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:108
$output
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title after the basic globals have been set but before ordinary actions take place $output
Definition: hooks.txt:2198
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:1140
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:1900
EditPage\$mArticle
Article $mArticle
Definition: EditPage.php:220
EditPage\$contentFormat
null string $contentFormat
Definition: EditPage.php:398
Html\input
static input( $name, $value='', $type='text', array $attribs=[])
Convenience function to produce an "<input>" element.
Definition: Html.php:642
Skin\getSkinNames
static getSkinNames()
Fetch the set of available skins.
Definition: Skin.php:51
EditPage\$isWrongCaseCssJsPage
bool $isWrongCaseCssJsPage
Definition: EditPage.php:261
EditPage\POST_EDIT_COOKIE_DURATION
const POST_EDIT_COOKIE_DURATION
Duration of PostEdit cookie, in seconds.
Definition: EditPage.php:214
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
Revision\FOR_THIS_USER
const FOR_THIS_USER
Definition: Revision.php:99
Revision
Definition: Revision.php:33
NS_MAIN
const NS_MAIN
Definition: Defines.php:65
EditPage\getPreviewLimitReport
static getPreviewLimitReport( $output)
Get the Limit report for page previews.
Definition: EditPage.php:3578
EditPage\$watchthis
bool $watchthis
Definition: EditPage.php:347
EditPage\$previewTextAfterContent
$previewTextAfterContent
Definition: EditPage.php:414
Html\closeElement
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:309
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:3432
$customAttribs
null means default & $customAttribs
Definition: hooks.txt:1965
Xml\encodeJsCall
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:677
EditPage\$tooBig
bool $tooBig
Definition: EditPage.php:291
$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:1581
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, IDatabase $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:111
EditPage\AS_SUCCESS_NEW_ARTICLE
const AS_SUCCESS_NEW_ARTICLE
Status: Article successfully created.
Definition: EditPage.php:56
EditPage\UNICODE_CHECK
const UNICODE_CHECK
Used for Unicode support checks.
Definition: EditPage.php:46
$html
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 & $html
Definition: hooks.txt:1965
MWException
MediaWiki exception.
Definition: MWException.php:26
EditPage\addContentModelChangeLogEntry
addContentModelChangeLogEntry(User $user, $oldModel, $newModel, $reason)
Definition: EditPage.php:2249
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:932
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:121
EditPage\toEditContent
toEditContent( $text)
Turns the given text into a Content object by unserializing it.
Definition: EditPage.php:2620
EditPage\getEditButtons
getEditButtons(&$tabindex)
Returns an array of html code of the following buttons: save, diff and preview.
Definition: EditPage.php:4388
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1176
EditPage\AS_END
const AS_END
Status: WikiPage::doEdit() was unsuccessful.
Definition: EditPage.php:139
LogPage\DELETED_COMMENT
const DELETED_COMMENT
Definition: LogPage.php:33
EditPage\$editRevId
int $editRevId
Definition: EditPage.php:368
EditPage\showSummaryInput
showSummaryInput( $isSubjectPreview, $summary="")
Definition: EditPage.php:3206
EditPage\getParentRevId
getParentRevId()
Get the edit's parent revision ID.
Definition: EditPage.php:1328
ParserOptions\newFromUserAndLang
static newFromUserAndLang(User $user, Language $lang)
Get a ParserOptions object from a given user and language.
Definition: ParserOptions.php:992
wfArrayDiff2
wfArrayDiff2( $a, $b)
Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
Definition: GlobalFunctions.php:180
EditPage\isSectionEditSupported
isSectionEditSupported()
Returns whether section editing is supported for the current page.
Definition: EditPage.php:878
EditPage\importFormData
importFormData(&$request)
This function collects the form data and uses it to populate various member variables.
Definition: EditPage.php:888
EditPage\getActionURL
getActionURL(Title $title)
Returns the URL to use in the form's action attribute.
Definition: EditPage.php:3742
EditPage\addExplainConflictHeader
addExplainConflictHeader(OutputPage $out)
Definition: EditPage.php:4637
LogPage\DELETED_USER
const DELETED_USER
Definition: LogPage.php:34
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2856
EditPage\showIntro
showIntro()
Show all applicable editing introductions.
Definition: EditPage.php:2443
$input
if(is_array( $mode)) switch( $mode) $input
Definition: postprocess-phan.php:141
EditPage\$firsttime
bool $firsttime
Definition: EditPage.php:273
$matches
$matches
Definition: NoLocalSettings.php:24
in
null for the wiki Added in
Definition: hooks.txt:1581
EditPage\$isCssSubpage
bool $isCssSubpage
Definition: EditPage.php:249
$article
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function array $article
Definition: hooks.txt:77
Xml\check
static check( $name, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox.
Definition: Xml.php:323
EditPage\$missingComment
bool $missingComment
Definition: EditPage.php:294
$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:1965
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:1511
WikiPage\getContent
getContent( $audience=Revision::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
Definition: WikiPage.php:666
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:39
$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:1411
Article\getPage
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:184
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:825
EditPage\getArticle
getArticle()
Definition: EditPage.php:467
EditPage\getCheckboxes
getCheckboxes(&$tabindex, $checked)
Returns an array of html code of the following checkboxes old style: minor and watch.
Definition: EditPage.php:4222
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:183
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:160
EditPage\$mBaseRevision
Revision bool $mBaseRevision
Definition: EditPage.php:327
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:4297
EditPage\previewOnOpen
previewOnOpen()
Should we show a preview when the edit form is first shown?
Definition: EditPage.php:823
WatchAction\doWatchOrUnwatch
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:93
EditPage\incrementEditFailureStats
incrementEditFailureStats( $failureType)
Definition: EditPage.php:3961
EditPage\AS_READ_ONLY_PAGE
const AS_READ_ONLY_PAGE
Status: wiki is in readonly mode (wfReadOnly() == true)
Definition: EditPage.php:91
EditPage\AS_SPAM_ERROR
const AS_SPAM_ERROR
Status: summary contained spam according to one of the regexes in $wgSummarySpamRegex.
Definition: EditPage.php:144
EditPage\$allowSelfRedirect
bool $allowSelfRedirect
Definition: EditPage.php:312
EditPage\showEditForm
showEditForm( $formCallback=null)
Send the edit form and related headers to OutputPage.
Definition: EditPage.php:2643
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:529
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:594
EditPage\wasDeletedSinceLastEdit
wasDeletedSinceLastEdit()
Check if a page was deleted while the user was editing it, before submit.
Definition: EditPage.php:3753
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
EditPage\getTemplates
getTemplates()
Definition: EditPage.php:4004
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2069
EditPage\getPreviewParserOptions
getPreviewParserOptions()
Get parser options for a preview.
Definition: EditPage.php:3970
EditPage\runPostMergeFilters
runPostMergeFilters(Content $content, Status $status, User $user)
Run hooks that can filter edits just before they get saved.
Definition: EditPage.php:1676
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:134
DB_MASTER
const DB_MASTER
Definition: defines.php:26
EditPage\$mContextTitle
null Title $mContextTitle
Definition: EditPage.php:231
EditPage\$isCssJsSubpage
bool $isCssJsSubpage
Definition: EditPage.php:243
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:465
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:1047
EditPage\showFormAfterText
showFormAfterText()
Definition: EditPage.php:3265
string
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
OutputPage
This class should be covered by a general architecture document which does not exist as of January 20...
Definition: OutputPage.php:44
EditPage\showPreview
showPreview( $text)
Append preview output to OutputPage.
Definition: EditPage.php:3411
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:1122
EditPage\getEditToolbar
static getEditToolbar( $title=null)
Shows a bulletin board style toolbar for common editing functions.
Definition: EditPage.php:4028
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:129
captcha-old.action
action
Definition: captcha-old.py:212
$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:2581
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:4526
EditPage\matchSpamRegex
static matchSpamRegex( $text)
Check given input text against $wgSpamRegex, and return the text of the first match.
Definition: EditPage.php:2349
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:725
EditPage\$recreate
bool $recreate
Definition: EditPage.php:350
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:1902
EditPage\$contentLength
bool int $contentLength
Definition: EditPage.php:427
EditPage\AS_SUCCESS_UPDATE
const AS_SUCCESS_UPDATE
Status: Article successfully updated.
Definition: EditPage.php:51
EditPage\showTextbox1
showTextbox1( $customAttribs=null, $textoverride=null)
Method to output wpTextbox1 The $textoverride method can be used by subclasses overriding showContent...
Definition: EditPage.php:3305
EditPage\addTalkPageText
addTalkPageText()
Definition: EditPage.php:4553
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:68
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2141
EditPage\getSummaryPreview
getSummaryPreview( $isSubjectPreview, $summary="")
Definition: EditPage.php:3234
EditPage\importContentFormData
importContentFormData(&$request)
Subpage overridable method for extracting the page content data from the posted form to be placed in ...
Definition: EditPage.php:1113
EditPage\$minoredit
bool $minoredit
Definition: EditPage.php:344
EditPage\$isOldRev
bool $isOldRev
Whether an old revision is edited.
Definition: EditPage.php:442
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:432
$value
$value
Definition: styleTest.css.php:45
EDIT_UPDATE
const EDIT_UPDATE
Definition: Defines.php:154
Linker\tooltipAndAccesskeyAttribs
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[])
Returns the attributes for the tooltip and access key.
Definition: Linker.php:2111
EditPage\showHeader
showHeader()
Definition: EditPage.php:2937
EditPage\getBaseRevision
getBaseRevision()
Definition: EditPage.php:2332
ContentHandler\getLocalizedName
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
Definition: ContentHandler.php:348
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:53
EditPage\getSummaryInput
getSummaryInput( $summary="", $labelText=null, $inputAttrs=null, $spanLabelAttrs=null)
Standard summary input and label (wgSummary), abstracted so EditPage subclasses may reorganize the fo...
Definition: EditPage.php:3122
EditPage\addNewLineAtEnd
addNewLineAtEnd( $wikitext)
Definition: EditPage.php:4691
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:1524
MWNamespace\wantSignatures
static wantSignatures( $index)
Might pages in this namespace require the use of the Signature button on the edit toolbar?
Definition: MWNamespace.php:323
EditPage\$edittime
string $edittime
Definition: EditPage.php:365
EditPage\matchSummarySpamRegex
static matchSummarySpamRegex( $text)
Check given input text against $wgSummarySpamRegex, and return the text of the first match.
Definition: EditPage.php:2363
EditPage\$mTokenOkExceptSuffix
bool $mTokenOkExceptSuffix
Definition: EditPage.php:282
EditPage\showEditTools
showEditTools()
Inserts optional text shown below edit and upload forms.
Definition: EditPage.php:3526
EditPage\$preview
bool $preview
Definition: EditPage.php:338
EditPage\$isNew
bool $isNew
New page or new section.
Definition: EditPage.php:264
EditPage\getCheckboxesDefinition
getCheckboxesDefinition( $checked)
Return an array of checkbox definitions.
Definition: EditPage.php:4178
etc
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add etc
Definition: design.txt:12
$wgSummarySpamRegex
$wgSummarySpamRegex
Same as the above except for edit summaries.
Definition: DefaultSettings.php:5587
First
The First
Definition: primes.txt:1
EditPage\getCopywarn
getCopywarn()
Get the copyright warning.
Definition: EditPage.php:3538
EditPage\setApiEditOverride
setApiEditOverride( $enableOverride)
Allow editing of content that supports API direct editing, but not general direct editing.
Definition: EditPage.php:543
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1703
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:154
Revision\RAW
const RAW
Definition: Revision.php:100
EditPage\getCancelLink
getCancelLink()
Definition: EditPage.php:3715
EditPage\showCustomIntro
showCustomIntro()
Attempt to show a custom editing introduction, if supplied.
Definition: EditPage.php:2559
EditPage\getContext
getContext()
Definition: EditPage.php:475
EditPage\AS_PARSE_ERROR
const AS_PARSE_ERROR
Status: can't parse content.
Definition: EditPage.php:177
EditPage\EDITFORM_ID
const EDITFORM_ID
HTML id and name for the beginning of the edit form.
Definition: EditPage.php:193
EditPage\extractSectionTitle
static extractSectionTitle( $text)
Extract the section title from current section text, if any.
Definition: EditPage.php:2927
Block\TYPE_AUTO
const TYPE_AUTO
Definition: Block.php:86
$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 probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:781
EditPage\makeTemplatesOnThisPageList
makeTemplatesOnThisPageList(array $templates)
Wrapper around TemplatesOnThisPageFormatter to make a "templates on this page" list.
Definition: EditPage.php:2903
EditPage\$textbox1
string $textbox1
Definition: EditPage.php:353
plain
either a plain
Definition: hooks.txt:2026
EditPage\$parentRevId
int $parentRevId
Definition: EditPage.php:383
EditPage\$undidRev
$undidRev
Definition: EditPage.php:419
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:1951
wfFindFile
wfFindFile( $title, $options=[])
Find a file.
Definition: GlobalFunctions.php:2897
EditPage\$changeTags
null array $changeTags
Definition: EditPage.php:401
EditPage
The edit page/HTML interface (split from Article) The actual database and text munging is still in Ar...
Definition: EditPage.php:42
$response
this hook is for auditing only $response
Definition: hooks.txt:781
EditPage\noSuchSectionPage
noSuchSectionPage()
Creates a basic error page which informs the user that they have attempted to edit a nonexistent sect...
Definition: EditPage.php:4459
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:55
wfGetAllCallers
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
Definition: GlobalFunctions.php:1623
EditPage\$formtype
string $formtype
Definition: EditPage.php:270
Content\preSaveTransform
preSaveTransform(Title $title, User $user, ParserOptions $parserOptions)
Returns a Content object with pre-save transformations applied (or this object if no transformations ...
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:3173
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:153
EditPage\$hasPresetSummary
bool $hasPresetSummary
Has a summary been preset using GET parameter &summary= ?
Definition: EditPage.php:324
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:421
EditPage\$mParserOutput
ParserOutput $mParserOutput
Definition: EditPage.php:321
Title
Represents a title within MediaWiki.
Definition: Title.php:39
EDIT_AUTOSUMMARY
const EDIT_AUTOSUMMARY
Definition: Defines.php:159
EditPage\$mShowSummaryField
bool $mShowSummaryField
Definition: EditPage.php:330
EditPage\$sectiontitle
string $sectiontitle
Definition: EditPage.php:374
EditPage\getCheckboxesOOUI
getCheckboxesOOUI(&$tabindex, $checked)
Returns an array of checkboxes for the edit form, including 'minor' and 'watch' checkboxes and any ot...
Definition: EditPage.php:4283
EditPage\$starttime
string $starttime
Definition: EditPage.php:377
EditPage\$suppressIntro
$suppressIntro
Definition: EditPage.php:421
ContentHandler\getContentText
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
Definition: ContentHandler.php:79
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:118
wfReadOnlyReason
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
Definition: GlobalFunctions.php:1337
$dbr
if(! $regexes) $dbr
Definition: cleanup.php:94
$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:1965
EditPage\$deletedSinceEdit
bool $deletedSinceEdit
Definition: EditPage.php:267
EditPage\$selfRedirect
bool $selfRedirect
Definition: EditPage.php:309
EditPage\$edit
bool $edit
Definition: EditPage.php:424
$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 probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:781
EditPage\isSupportedContentModel
isSupportedContentModel( $modelId)
Returns if the given content model is editable.
Definition: EditPage.php:532
EditPage\$mPreloadContent
$mPreloadContent
Definition: EditPage.php:415
EditPage\showConflict
showConflict()
Show an edit conflict.
Definition: EditPage.php:3675
EditPage\getSubmitButtonLabel
getSubmitButtonLabel()
Get the message key of the label for the button to save the page.
Definition: EditPage.php:4364
EditPage\$unicodeCheck
string null $unicodeCheck
What the user submitted in the 'wpUnicodeCheck' field.
Definition: EditPage.php:447
$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:1750
EditPage\$diff
bool $diff
Definition: EditPage.php:341
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:3987
EditPage\newSectionSummary
newSectionSummary(&$sectionanchor=null)
Return the summary to be used for a new section.
Definition: EditPage.php:1723
EditPage\$action
string $action
Definition: EditPage.php:234
Html\openElement
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:251
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:309
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:86
$wgEnableUploads
$wgEnableUploads
Uploads have to be specially set up to be secure.
Definition: DefaultSettings.php:378
Linker\commentBlock
static commentBlock( $comment, $title=null, $local=false, $wikiId=null)
Wrap a comment in standard punctuation and formatting if it's non-empty, otherwise return empty strin...
Definition: Linker.php:1445
NS_USER
const NS_USER
Definition: Defines.php:67
EditPage\showTextbox
showTextbox( $text, $name, $customAttribs=[])
Definition: EditPage.php:3355
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:483
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:1965
ManualLogEntry
Class for creating log entries manually, to inject them into the database.
Definition: LogEntry.php:400
EditPage\AS_CONFLICT_DETECTED
const AS_CONFLICT_DETECTED
Status: (non-resolvable) edit conflict.
Definition: EditPage.php:118
EditPage\AS_RATE_LIMITED
const AS_RATE_LIMITED
Status: rate limiter for action 'edit' was tripped.
Definition: EditPage.php:96
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
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 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
EditPage\getCurrentContent
getCurrentContent()
Get the current content of the page.
Definition: EditPage.php:1344
Title\setContentModel
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition: Title.php:1026
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:1190
EditPage\$isConflict
bool $isConflict
Definition: EditPage.php:237
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:73
CONTENT_MODEL_JAVASCRIPT
const CONTENT_MODEL_JAVASCRIPT
Definition: Defines.php:237
EditPage\displayViewSourcePage
displayViewSourcePage(Content $content, $errorMessage='')
Display a read-only View Source page.
Definition: EditPage.php:770
EDIT_MINOR
const EDIT_MINOR
Definition: Defines.php:155
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:35
EditPage\getContentObject
getContentObject( $def_content=null)
Definition: EditPage.php:1161
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:88
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:149
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
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
EditPage\showStandardInputs
showStandardInputs(&$tabindex=2)
Definition: EditPage.php:3620
RawMessage
Variant of the Message class.
Definition: RawMessage.php:34
EditPage\isOouiEnabled
isOouiEnabled()
Check if the edit page is using OOUI controls.
Definition: EditPage.php:521
$wgOut
$wgOut
Definition: Setup.php:819
NS_CATEGORY_TALK
const NS_CATEGORY_TALK
Definition: Defines.php:80
EditPage\matchSpamRegexInternal
static matchSpamRegexInternal( $text, $regexes)
Definition: EditPage.php:2374
ErrorPageError
An error page which can definitely be safely rendered using the OutputPage.
Definition: ErrorPageError.php:27
WikiPage\isRedirect
isRedirect()
Tests if the article content represents a redirect.
Definition: WikiPage.php:492
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:284
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:199
EditPage\getOriginalContent
getOriginalContent(User $user)
Get the content of the wanted revision, without section extraction.
Definition: EditPage.php:1303
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:102
EditPage\setPostEditCookie
setPostEditCookie( $statusValue)
Sets post-edit cookie indicating the user just saved a particular revision.
Definition: EditPage.php:1490
Linker\accesskey
static accesskey( $name)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition: Linker.php:1994
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:51
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
EditPage\$incompleteForm
bool $incompleteForm
Definition: EditPage.php:288
$username
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:781
$wgForeignFileRepos
$wgForeignFileRepos
Definition: DefaultSettings.php:522
EditPage\$missingSummary
bool $missingSummary
Definition: EditPage.php:297
EditPage\$isJsSubpage
bool $isJsSubpage
Definition: EditPage.php:255
EditPage\$bot
bool $bot
Definition: EditPage.php:392
EditPage\getCopyrightWarning
static getCopyrightWarning( $title, $format='plain', $langcode=null)
Get the copyright warning, by default returns wikitext.
Definition: EditPage.php:3550
MWNamespace\getCanonicalName
static getCanonicalName( $index)
Returns the canonical (English) name for a given index.
Definition: MWNamespace.php:228
Revision\DELETED_TEXT
const DELETED_TEXT
Definition: Revision.php:90
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2801
EditPage\AS_HOOK_ERROR_EXPECTED
const AS_HOOK_ERROR_EXPECTED
Status: A hook function returned an error.
Definition: EditPage.php:66
Skin\makeInternalOrExternalUrl
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition: Skin.php:1161
EditPage\updateWatchlist
updateWatchlist()
Register the change of watch status.
Definition: EditPage.php:2266
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:586
array
the array() calling protocol came about after MediaWiki 1.4rc1.
EditPage\handleStatus
handleStatus(Status $status, $resultDetails)
Handle status, such as after attempt save.
Definition: EditPage.php:1542
ParserOptions\newFromUser
static newFromUser( $user)
Get a ParserOptions object from a given user.
Definition: ParserOptions.php:978
EditPage\$hookError
string $hookError
Definition: EditPage.php:318
EditPage\$allowBlankArticle
bool $allowBlankArticle
Definition: EditPage.php:306
EditPage\toEditText
toEditText( $content)
Gets an editable textual representation of $content.
Definition: EditPage.php:2592
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:2384
EditPage\AS_TEXTBOX_EMPTY
const AS_TEXTBOX_EMPTY
Status: user tried to create a new section without content.
Definition: EditPage.php:129
EditPage\AS_CHANGE_TAG_ERROR
const AS_CHANGE_TAG_ERROR
Status: an error relating to change tagging.
Definition: EditPage.php:172
$wgContLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the content language as $wgContLang
Definition: design.txt:56
EditPage\guessSectionName
guessSectionName( $text)
Turns section name wikitext into anchors for use in HTTP redirects.
Definition: EditPage.php:4713
$wgSpamRegex
$wgSpamRegex
Edits matching these regular expressions in body text will be recognised as spam and rejected automat...
Definition: DefaultSettings.php:5582
$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 probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:781
EditPage\submit
submit()
Definition: EditPage.php:550
$type
$type
Definition: testCompression.php:48
EditPage\$nosummary
bool $nosummary
Definition: EditPage.php:362
EditPage\getPreviewText
getPreviewText()
Get the rendered text for previewing.
Definition: EditPage.php:3824