MediaWiki  REL1_31
EditPage.php
Go to the documentation of this file.
1 <?php
27 use Wikimedia\ScopedCallback;
28 
44 class EditPage {
48  const UNICODE_CHECK = 'ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ';
49 
53  const AS_SUCCESS_UPDATE = 200;
54 
59 
63  const AS_HOOK_ERROR = 210;
64 
69 
74 
78  const AS_CONTENT_TOO_BIG = 216;
79 
84 
89 
93  const AS_READ_ONLY_PAGE = 220;
94 
98  const AS_RATE_LIMITED = 221;
99 
105 
111 
115  const AS_BLANK_ARTICLE = 224;
116 
120  const AS_CONFLICT_DETECTED = 225;
121 
126  const AS_SUMMARY_NEEDED = 226;
127 
131  const AS_TEXTBOX_EMPTY = 228;
132 
137 
141  const AS_END = 231;
142 
146  const AS_SPAM_ERROR = 232;
147 
152 
157 
163 
168  const AS_SELF_REDIRECT = 236;
169 
174  const AS_CHANGE_TAG_ERROR = 237;
175 
179  const AS_PARSE_ERROR = 240;
180 
186 
191 
195  const EDITFORM_ID = 'editform';
196 
201  const POST_EDIT_COOKIE_KEY_PREFIX = 'PostEditRevision';
202 
217 
222  public $mArticle;
224  private $page;
225 
230  public $mTitle;
231 
233  private $mContextTitle = null;
234 
236  public $action = 'submit';
237 
239  public $isConflict = false;
240 
242  public $isNew = false;
243 
246 
248  public $formtype;
249 
251  public $firsttime;
252 
254  public $lastDelete;
255 
257  public $mTokenOk = false;
258 
260  public $mTokenOkExceptSuffix = false;
261 
263  public $mTriedSave = false;
264 
266  public $incompleteForm = false;
267 
269  public $tooBig = false;
270 
272  public $missingComment = false;
273 
275  public $missingSummary = false;
276 
278  public $allowBlankSummary = false;
279 
281  protected $blankArticle = false;
282 
284  protected $allowBlankArticle = false;
285 
287  protected $selfRedirect = false;
288 
290  protected $allowSelfRedirect = false;
291 
293  public $autoSumm = '';
294 
296  public $hookError = '';
297 
300 
302  public $hasPresetSummary = false;
303 
305  public $mBaseRevision = false;
306 
308  public $mShowSummaryField = true;
309 
310  # Form values
311 
313  public $save = false;
314 
316  public $preview = false;
317 
319  public $diff = false;
320 
322  public $minoredit = false;
323 
325  public $watchthis = false;
326 
328  public $recreate = false;
329 
331  public $textbox1 = '';
332 
334  public $textbox2 = '';
335 
337  public $summary = '';
338 
340  public $nosummary = false;
341 
343  public $edittime = '';
344 
346  private $editRevId = null;
347 
349  public $section = '';
350 
352  public $sectiontitle = '';
353 
355  public $starttime = '';
356 
358  public $oldid = 0;
359 
361  public $parentRevId = 0;
362 
364  public $editintro = '';
365 
367  public $scrolltop = null;
368 
370  public $bot = true;
371 
374 
376  public $contentFormat = null;
377 
379  private $changeTags = null;
380 
381  # Placeholders for text injection by hooks (must be HTML)
382  # extensions should take care to _append_ to the present value
383 
385  public $editFormPageTop = '';
386  public $editFormTextTop = '';
390  public $editFormTextBottom = '';
393  public $mPreloadContent = null;
394 
395  /* $didSave should be set to true whenever an article was successfully altered. */
396  public $didSave = false;
397  public $undidRev = 0;
398 
399  public $suppressIntro = false;
400 
402  protected $edit;
403 
405  protected $contentLength = false;
406 
410  private $enableApiEditOverride = false;
411 
415  protected $context;
416 
420  private $isOldRev = false;
421 
425  private $unicodeCheck;
426 
433 
438 
442  public function __construct( Article $article ) {
443  $this->mArticle = $article;
444  $this->page = $article->getPage(); // model object
445  $this->mTitle = $article->getTitle();
446  $this->context = $article->getContext();
447 
448  $this->contentModel = $this->mTitle->getContentModel();
449 
450  $handler = ContentHandler::getForModelID( $this->contentModel );
451  $this->contentFormat = $handler->getDefaultFormat();
452  $this->editConflictHelperFactory = [ $this, 'newTextConflictHelper' ];
453  }
454 
458  public function getArticle() {
459  return $this->mArticle;
460  }
461 
466  public function getContext() {
467  return $this->context;
468  }
469 
474  public function getTitle() {
475  return $this->mTitle;
476  }
477 
483  public function setContextTitle( $title ) {
484  $this->mContextTitle = $title;
485  }
486 
494  public function getContextTitle() {
495  if ( is_null( $this->mContextTitle ) ) {
496  wfDebugLog(
497  'GlobalTitleFail',
498  __METHOD__ . ' called by ' . wfGetAllCallers( 5 ) . ' with no title set.'
499  );
501  return $wgTitle;
502  } else {
503  return $this->mContextTitle;
504  }
505  }
506 
512  public function isOouiEnabled() {
513  wfDeprecated( __METHOD__, '1.30' );
514  return true;
515  }
516 
524  public function isSupportedContentModel( $modelId ) {
525  return $this->enableApiEditOverride === true ||
526  ContentHandler::getForModelID( $modelId )->supportsDirectEditing();
527  }
528 
535  public function setApiEditOverride( $enableOverride ) {
536  $this->enableApiEditOverride = $enableOverride;
537  }
538 
542  public function submit() {
543  wfDeprecated( __METHOD__, '1.29' );
544  $this->edit();
545  }
546 
558  public function edit() {
559  // Allow extensions to modify/prevent this form or submission
560  if ( !Hooks::run( 'AlternateEdit', [ $this ] ) ) {
561  return;
562  }
563 
564  wfDebug( __METHOD__ . ": enter\n" );
565 
566  $request = $this->context->getRequest();
567  // If they used redlink=1 and the page exists, redirect to the main article
568  if ( $request->getBool( 'redlink' ) && $this->mTitle->exists() ) {
569  $this->context->getOutput()->redirect( $this->mTitle->getFullURL() );
570  return;
571  }
572 
573  $this->importFormData( $request );
574  $this->firsttime = false;
575 
576  if ( wfReadOnly() && $this->save ) {
577  // Force preview
578  $this->save = false;
579  $this->preview = true;
580  }
581 
582  if ( $this->save ) {
583  $this->formtype = 'save';
584  } elseif ( $this->preview ) {
585  $this->formtype = 'preview';
586  } elseif ( $this->diff ) {
587  $this->formtype = 'diff';
588  } else { # First time through
589  $this->firsttime = true;
590  if ( $this->previewOnOpen() ) {
591  $this->formtype = 'preview';
592  } else {
593  $this->formtype = 'initial';
594  }
595  }
596 
597  $permErrors = $this->getEditPermissionErrors( $this->save ? 'secure' : 'full' );
598  if ( $permErrors ) {
599  wfDebug( __METHOD__ . ": User can't edit\n" );
600  // Auto-block user's IP if the account was "hard" blocked
601  if ( !wfReadOnly() ) {
603  $this->context->getUser()->spreadAnyEditBlock();
604  } );
605  }
606  $this->displayPermissionsError( $permErrors );
607 
608  return;
609  }
610 
611  $revision = $this->mArticle->getRevisionFetched();
612  // Disallow editing revisions with content models different from the current one
613  // Undo edits being an exception in order to allow reverting content model changes.
614  if ( $revision
615  && $revision->getContentModel() !== $this->contentModel
616  ) {
617  $prevRev = null;
618  if ( $this->undidRev ) {
619  $undidRevObj = Revision::newFromId( $this->undidRev );
620  $prevRev = $undidRevObj ? $undidRevObj->getPrevious() : null;
621  }
622  if ( !$this->undidRev
623  || !$prevRev
624  || $prevRev->getContentModel() !== $this->contentModel
625  ) {
626  $this->displayViewSourcePage(
627  $this->getContentObject(),
628  $this->context->msg(
629  'contentmodelediterror',
630  $revision->getContentModel(),
631  $this->contentModel
632  )->plain()
633  );
634  return;
635  }
636  }
637 
638  $this->isConflict = false;
639 
640  # Show applicable editing introductions
641  if ( $this->formtype == 'initial' || $this->firsttime ) {
642  $this->showIntro();
643  }
644 
645  # Attempt submission here. This will check for edit conflicts,
646  # and redundantly check for locked database, blocked IPs, etc.
647  # that edit() already checked just in case someone tries to sneak
648  # in the back door with a hand-edited submission URL.
649 
650  if ( 'save' == $this->formtype ) {
651  $resultDetails = null;
652  $status = $this->attemptSave( $resultDetails );
653  if ( !$this->handleStatus( $status, $resultDetails ) ) {
654  return;
655  }
656  }
657 
658  # First time through: get contents, set time for conflict
659  # checking, etc.
660  if ( 'initial' == $this->formtype || $this->firsttime ) {
661  if ( $this->initialiseForm() === false ) {
662  $this->noSuchSectionPage();
663  return;
664  }
665 
666  if ( !$this->mTitle->getArticleID() ) {
667  Hooks::run( 'EditFormPreloadText', [ &$this->textbox1, &$this->mTitle ] );
668  } else {
669  Hooks::run( 'EditFormInitialText', [ $this ] );
670  }
671 
672  }
673 
674  $this->showEditForm();
675  }
676 
681  protected function getEditPermissionErrors( $rigor = 'secure' ) {
682  $user = $this->context->getUser();
683  $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $user, $rigor );
684  # Can this title be created?
685  if ( !$this->mTitle->exists() ) {
686  $permErrors = array_merge(
687  $permErrors,
688  wfArrayDiff2(
689  $this->mTitle->getUserPermissionsErrors( 'create', $user, $rigor ),
690  $permErrors
691  )
692  );
693  }
694  # Ignore some permissions errors when a user is just previewing/viewing diffs
695  $remove = [];
696  foreach ( $permErrors as $error ) {
697  if ( ( $this->preview || $this->diff )
698  && (
699  $error[0] == 'blockedtext' ||
700  $error[0] == 'autoblockedtext' ||
701  $error[0] == 'systemblockedtext'
702  )
703  ) {
704  $remove[] = $error;
705  }
706  }
707  $permErrors = wfArrayDiff2( $permErrors, $remove );
708 
709  return $permErrors;
710  }
711 
725  protected function displayPermissionsError( array $permErrors ) {
726  $out = $this->context->getOutput();
727  if ( $this->context->getRequest()->getBool( 'redlink' ) ) {
728  // The edit page was reached via a red link.
729  // Redirect to the article page and let them click the edit tab if
730  // they really want a permission error.
731  $out->redirect( $this->mTitle->getFullURL() );
732  return;
733  }
734 
735  $content = $this->getContentObject();
736 
737  # Use the normal message if there's nothing to display
738  if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
739  $action = $this->mTitle->exists() ? 'edit' :
740  ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
741  throw new PermissionsError( $action, $permErrors );
742  }
743 
744  $this->displayViewSourcePage(
745  $content,
746  $out->formatPermissionsErrorMessage( $permErrors, 'edit' )
747  );
748  }
749 
755  protected function displayViewSourcePage( Content $content, $errorMessage = '' ) {
756  $out = $this->context->getOutput();
757  Hooks::run( 'EditPage::showReadOnlyForm:initial', [ $this, &$out ] );
758 
759  $out->setRobotPolicy( 'noindex,nofollow' );
760  $out->setPageTitle( $this->context->msg(
761  'viewsource-title',
762  $this->getContextTitle()->getPrefixedText()
763  ) );
764  $out->addBacklinkSubtitle( $this->getContextTitle() );
765  $out->addHTML( $this->editFormPageTop );
766  $out->addHTML( $this->editFormTextTop );
767 
768  if ( $errorMessage !== '' ) {
769  $out->addWikiText( $errorMessage );
770  $out->addHTML( "<hr />\n" );
771  }
772 
773  # If the user made changes, preserve them when showing the markup
774  # (This happens when a user is blocked during edit, for instance)
775  if ( !$this->firsttime ) {
776  $text = $this->textbox1;
777  $out->addWikiMsg( 'viewyourtext' );
778  } else {
779  try {
780  $text = $this->toEditText( $content );
781  } catch ( MWException $e ) {
782  # Serialize using the default format if the content model is not supported
783  # (e.g. for an old revision with a different model)
784  $text = $content->serialize();
785  }
786  $out->addWikiMsg( 'viewsourcetext' );
787  }
788 
789  $out->addHTML( $this->editFormTextBeforeContent );
790  $this->showTextbox( $text, 'wpTextbox1', [ 'readonly' ] );
791  $out->addHTML( $this->editFormTextAfterContent );
792 
793  $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
794 
795  $out->addModules( 'mediawiki.action.edit.collapsibleFooter' );
796 
797  $out->addHTML( $this->editFormTextBottom );
798  if ( $this->mTitle->exists() ) {
799  $out->returnToMain( null, $this->mTitle );
800  }
801  }
802 
808  protected function previewOnOpen() {
809  $config = $this->context->getConfig();
810  $previewOnOpenNamespaces = $config->get( 'PreviewOnOpenNamespaces' );
811  $request = $this->context->getRequest();
812  if ( $config->get( 'RawHtml' ) ) {
813  // If raw HTML is enabled, disable preview on open
814  // since it has to be posted with a token for
815  // security reasons
816  return false;
817  }
818  if ( $request->getVal( 'preview' ) == 'yes' ) {
819  // Explicit override from request
820  return true;
821  } elseif ( $request->getVal( 'preview' ) == 'no' ) {
822  // Explicit override from request
823  return false;
824  } elseif ( $this->section == 'new' ) {
825  // Nothing *to* preview for new sections
826  return false;
827  } elseif ( ( $request->getVal( 'preload' ) !== null || $this->mTitle->exists() )
828  && $this->context->getUser()->getOption( 'previewonfirst' )
829  ) {
830  // Standard preference behavior
831  return true;
832  } elseif ( !$this->mTitle->exists()
833  && isset( $previewOnOpenNamespaces[$this->mTitle->getNamespace()] )
834  && $previewOnOpenNamespaces[$this->mTitle->getNamespace()]
835  ) {
836  // Categories are special
837  return true;
838  } else {
839  return false;
840  }
841  }
842 
849  protected function isWrongCaseUserConfigPage() {
850  if ( $this->mTitle->isUserConfigPage() ) {
851  $name = $this->mTitle->getSkinFromConfigSubpage();
852  $skins = array_merge(
853  array_keys( Skin::getSkinNames() ),
854  [ 'common' ]
855  );
856  return !in_array( $name, $skins )
857  && in_array( strtolower( $name ), $skins );
858  } else {
859  return false;
860  }
861  }
862 
870  protected function isSectionEditSupported() {
871  $contentHandler = ContentHandler::getForTitle( $this->mTitle );
872  return $contentHandler->supportsSections();
873  }
874 
880  public function importFormData( &$request ) {
881  # Section edit can come from either the form or a link
882  $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
883 
884  if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) {
885  throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
886  }
887 
888  $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
889 
890  if ( $request->wasPosted() ) {
891  # These fields need to be checked for encoding.
892  # Also remove trailing whitespace, but don't remove _initial_
893  # whitespace from the text boxes. This may be significant formatting.
894  $this->textbox1 = rtrim( $request->getText( 'wpTextbox1' ) );
895  if ( !$request->getCheck( 'wpTextbox2' ) ) {
896  // Skip this if wpTextbox2 has input, it indicates that we came
897  // from a conflict page with raw page text, not a custom form
898  // modified by subclasses
900  if ( $textbox1 !== null ) {
901  $this->textbox1 = $textbox1;
902  }
903  }
904 
905  $this->unicodeCheck = $request->getText( 'wpUnicodeCheck' );
906 
907  $this->summary = $request->getText( 'wpSummary' );
908 
909  # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
910  # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
911  # section titles.
912  $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
913 
914  # Treat sectiontitle the same way as summary.
915  # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
916  # currently doing double duty as both edit summary and section title. Right now this
917  # is just to allow API edits to work around this limitation, but this should be
918  # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
919  $this->sectiontitle = $request->getText( 'wpSectionTitle' );
920  $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
921 
922  $this->edittime = $request->getVal( 'wpEdittime' );
923  $this->editRevId = $request->getIntOrNull( 'editRevId' );
924  $this->starttime = $request->getVal( 'wpStarttime' );
925 
926  $undidRev = $request->getInt( 'wpUndidRevision' );
927  if ( $undidRev ) {
928  $this->undidRev = $undidRev;
929  }
930 
931  $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
932 
933  if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
934  // wpTextbox1 field is missing, possibly due to being "too big"
935  // according to some filter rules such as Suhosin's setting for
936  // suhosin.request.max_value_length (d'oh)
937  $this->incompleteForm = true;
938  } else {
939  // If we receive the last parameter of the request, we can fairly
940  // claim the POST request has not been truncated.
941 
942  // TODO: softened the check for cutover. Once we determine
943  // that it is safe, we should complete the transition by
944  // removing the "edittime" clause.
945  $this->incompleteForm = ( !$request->getVal( 'wpUltimateParam' )
946  && is_null( $this->edittime ) );
947  }
948  if ( $this->incompleteForm ) {
949  # If the form is incomplete, force to preview.
950  wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
951  wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
952  $this->preview = true;
953  } else {
954  $this->preview = $request->getCheck( 'wpPreview' );
955  $this->diff = $request->getCheck( 'wpDiff' );
956 
957  // Remember whether a save was requested, so we can indicate
958  // if we forced preview due to session failure.
959  $this->mTriedSave = !$this->preview;
960 
961  if ( $this->tokenOk( $request ) ) {
962  # Some browsers will not report any submit button
963  # if the user hits enter in the comment box.
964  # The unmarked state will be assumed to be a save,
965  # if the form seems otherwise complete.
966  wfDebug( __METHOD__ . ": Passed token check.\n" );
967  } elseif ( $this->diff ) {
968  # Failed token check, but only requested "Show Changes".
969  wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
970  } else {
971  # Page might be a hack attempt posted from
972  # an external site. Preview instead of saving.
973  wfDebug( __METHOD__ . ": Failed token check; forcing preview\n" );
974  $this->preview = true;
975  }
976  }
977  $this->save = !$this->preview && !$this->diff;
978  if ( !preg_match( '/^\d{14}$/', $this->edittime ) ) {
979  $this->edittime = null;
980  }
981 
982  if ( !preg_match( '/^\d{14}$/', $this->starttime ) ) {
983  $this->starttime = null;
984  }
985 
986  $this->recreate = $request->getCheck( 'wpRecreate' );
987 
988  $this->minoredit = $request->getCheck( 'wpMinoredit' );
989  $this->watchthis = $request->getCheck( 'wpWatchthis' );
990 
991  $user = $this->context->getUser();
992  # Don't force edit summaries when a user is editing their own user or talk page
993  if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK )
994  && $this->mTitle->getText() == $user->getName()
995  ) {
996  $this->allowBlankSummary = true;
997  } else {
998  $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' )
999  || !$user->getOption( 'forceeditsummary' );
1000  }
1001 
1002  $this->autoSumm = $request->getText( 'wpAutoSummary' );
1003 
1004  $this->allowBlankArticle = $request->getBool( 'wpIgnoreBlankArticle' );
1005  $this->allowSelfRedirect = $request->getBool( 'wpIgnoreSelfRedirect' );
1006 
1007  $changeTags = $request->getVal( 'wpChangeTags' );
1008  if ( is_null( $changeTags ) || $changeTags === '' ) {
1009  $this->changeTags = [];
1010  } else {
1011  $this->changeTags = array_filter( array_map( 'trim', explode( ',',
1012  $changeTags ) ) );
1013  }
1014  } else {
1015  # Not a posted form? Start with nothing.
1016  wfDebug( __METHOD__ . ": Not a posted form.\n" );
1017  $this->textbox1 = '';
1018  $this->summary = '';
1019  $this->sectiontitle = '';
1020  $this->edittime = '';
1021  $this->editRevId = null;
1022  $this->starttime = wfTimestampNow();
1023  $this->edit = false;
1024  $this->preview = false;
1025  $this->save = false;
1026  $this->diff = false;
1027  $this->minoredit = false;
1028  // Watch may be overridden by request parameters
1029  $this->watchthis = $request->getBool( 'watchthis', false );
1030  $this->recreate = false;
1031 
1032  // When creating a new section, we can preload a section title by passing it as the
1033  // preloadtitle parameter in the URL (T15100)
1034  if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
1035  $this->sectiontitle = $request->getVal( 'preloadtitle' );
1036  // Once wpSummary isn't being use for setting section titles, we should delete this.
1037  $this->summary = $request->getVal( 'preloadtitle' );
1038  } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
1039  $this->summary = $request->getText( 'summary' );
1040  if ( $this->summary !== '' ) {
1041  $this->hasPresetSummary = true;
1042  }
1043  }
1044 
1045  if ( $request->getVal( 'minor' ) ) {
1046  $this->minoredit = true;
1047  }
1048  }
1049 
1050  $this->oldid = $request->getInt( 'oldid' );
1051  $this->parentRevId = $request->getInt( 'parentRevId' );
1052 
1053  $this->bot = $request->getBool( 'bot', true );
1054  $this->nosummary = $request->getBool( 'nosummary' );
1055 
1056  // May be overridden by revision.
1057  $this->contentModel = $request->getText( 'model', $this->contentModel );
1058  // May be overridden by revision.
1059  $this->contentFormat = $request->getText( 'format', $this->contentFormat );
1060 
1061  try {
1062  $handler = ContentHandler::getForModelID( $this->contentModel );
1063  } catch ( MWUnknownContentModelException $e ) {
1064  throw new ErrorPageError(
1065  'editpage-invalidcontentmodel-title',
1066  'editpage-invalidcontentmodel-text',
1067  [ wfEscapeWikiText( $this->contentModel ) ]
1068  );
1069  }
1070 
1071  if ( !$handler->isSupportedFormat( $this->contentFormat ) ) {
1072  throw new ErrorPageError(
1073  'editpage-notsupportedcontentformat-title',
1074  'editpage-notsupportedcontentformat-text',
1075  [
1076  wfEscapeWikiText( $this->contentFormat ),
1077  wfEscapeWikiText( ContentHandler::getLocalizedName( $this->contentModel ) )
1078  ]
1079  );
1080  }
1081 
1088  $this->editintro = $request->getText( 'editintro',
1089  // Custom edit intro for new sections
1090  $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
1091 
1092  // Allow extensions to modify form data
1093  Hooks::run( 'EditPage::importFormData', [ $this, $request ] );
1094  }
1095 
1105  protected function importContentFormData( &$request ) {
1106  return; // Don't do anything, EditPage already extracted wpTextbox1
1107  }
1108 
1114  public function initialiseForm() {
1115  $this->edittime = $this->page->getTimestamp();
1116  $this->editRevId = $this->page->getLatest();
1117 
1118  $content = $this->getContentObject( false ); # TODO: track content object?!
1119  if ( $content === false ) {
1120  return false;
1121  }
1122  $this->textbox1 = $this->toEditText( $content );
1123 
1124  $user = $this->context->getUser();
1125  // activate checkboxes if user wants them to be always active
1126  # Sort out the "watch" checkbox
1127  if ( $user->getOption( 'watchdefault' ) ) {
1128  # Watch all edits
1129  $this->watchthis = true;
1130  } elseif ( $user->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
1131  # Watch creations
1132  $this->watchthis = true;
1133  } elseif ( $user->isWatched( $this->mTitle ) ) {
1134  # Already watched
1135  $this->watchthis = true;
1136  }
1137  if ( $user->getOption( 'minordefault' ) && !$this->isNew ) {
1138  $this->minoredit = true;
1139  }
1140  if ( $this->textbox1 === false ) {
1141  return false;
1142  }
1143  return true;
1144  }
1145 
1153  protected function getContentObject( $def_content = null ) {
1155 
1156  $content = false;
1157 
1158  $user = $this->context->getUser();
1159  $request = $this->context->getRequest();
1160  // For message page not locally set, use the i18n message.
1161  // For other non-existent articles, use preload text if any.
1162  if ( !$this->mTitle->exists() || $this->section == 'new' ) {
1163  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
1164  # If this is a system message, get the default text.
1165  $msg = $this->mTitle->getDefaultMessageText();
1166 
1167  $content = $this->toEditContent( $msg );
1168  }
1169  if ( $content === false ) {
1170  # If requested, preload some text.
1171  $preload = $request->getVal( 'preload',
1172  // Custom preload text for new sections
1173  $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
1174  $params = $request->getArray( 'preloadparams', [] );
1175 
1176  $content = $this->getPreloadedContent( $preload, $params );
1177  }
1178  // For existing pages, get text based on "undo" or section parameters.
1179  } else {
1180  if ( $this->section != '' ) {
1181  // Get section edit text (returns $def_text for invalid sections)
1182  $orig = $this->getOriginalContent( $user );
1183  $content = $orig ? $orig->getSection( $this->section ) : null;
1184 
1185  if ( !$content ) {
1186  $content = $def_content;
1187  }
1188  } else {
1189  $undoafter = $request->getInt( 'undoafter' );
1190  $undo = $request->getInt( 'undo' );
1191 
1192  if ( $undo > 0 && $undoafter > 0 ) {
1193  $undorev = Revision::newFromId( $undo );
1194  $oldrev = Revision::newFromId( $undoafter );
1195 
1196  # Sanity check, make sure it's the right page,
1197  # the revisions exist and they were not deleted.
1198  # Otherwise, $content will be left as-is.
1199  if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
1200  !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
1201  !$oldrev->isDeleted( Revision::DELETED_TEXT )
1202  ) {
1203  $content = $this->page->getUndoContent( $undorev, $oldrev );
1204 
1205  if ( $content === false ) {
1206  # Warn the user that something went wrong
1207  $undoMsg = 'failure';
1208  } else {
1209  $oldContent = $this->page->getContent( Revision::RAW );
1211  $newContent = $content->preSaveTransform( $this->mTitle, $user, $popts );
1212  if ( $newContent->getModel() !== $oldContent->getModel() ) {
1213  // The undo may change content
1214  // model if its reverting the top
1215  // edit. This can result in
1216  // mismatched content model/format.
1217  $this->contentModel = $newContent->getModel();
1218  $this->contentFormat = $oldrev->getContentFormat();
1219  }
1220 
1221  if ( $newContent->equals( $oldContent ) ) {
1222  # Tell the user that the undo results in no change,
1223  # i.e. the revisions were already undone.
1224  $undoMsg = 'nochange';
1225  $content = false;
1226  } else {
1227  # Inform the user of our success and set an automatic edit summary
1228  $undoMsg = 'success';
1229 
1230  # If we just undid one rev, use an autosummary
1231  $firstrev = $oldrev->getNext();
1232  if ( $firstrev && $firstrev->getId() == $undo ) {
1233  $userText = $undorev->getUserText();
1234  if ( $userText === '' ) {
1235  $undoSummary = $this->context->msg(
1236  'undo-summary-username-hidden',
1237  $undo
1238  )->inContentLanguage()->text();
1239  } else {
1240  $undoSummary = $this->context->msg(
1241  'undo-summary',
1242  $undo,
1243  $userText
1244  )->inContentLanguage()->text();
1245  }
1246  if ( $this->summary === '' ) {
1247  $this->summary = $undoSummary;
1248  } else {
1249  $this->summary = $undoSummary . $this->context->msg( 'colon-separator' )
1250  ->inContentLanguage()->text() . $this->summary;
1251  }
1252  $this->undidRev = $undo;
1253  }
1254  $this->formtype = 'diff';
1255  }
1256  }
1257  } else {
1258  // Failed basic sanity checks.
1259  // Older revisions may have been removed since the link
1260  // was created, or we may simply have got bogus input.
1261  $undoMsg = 'norev';
1262  }
1263 
1264  $out = $this->context->getOutput();
1265  // Messages: undo-success, undo-failure, undo-norev, undo-nochange
1266  $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
1267  $this->editFormPageTop .= $out->parse( "<div class=\"{$class}\">" .
1268  $this->context->msg( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
1269  }
1270 
1271  if ( $content === false ) {
1272  $content = $this->getOriginalContent( $user );
1273  }
1274  }
1275  }
1276 
1277  return $content;
1278  }
1279 
1295  private function getOriginalContent( User $user ) {
1296  if ( $this->section == 'new' ) {
1297  return $this->getCurrentContent();
1298  }
1299  $revision = $this->mArticle->getRevisionFetched();
1300  if ( $revision === null ) {
1301  $handler = ContentHandler::getForModelID( $this->contentModel );
1302  return $handler->makeEmptyContent();
1303  }
1304  $content = $revision->getContent( Revision::FOR_THIS_USER, $user );
1305  return $content;
1306  }
1307 
1320  public function getParentRevId() {
1321  if ( $this->parentRevId ) {
1322  return $this->parentRevId;
1323  } else {
1324  return $this->mArticle->getRevIdFetched();
1325  }
1326  }
1327 
1336  protected function getCurrentContent() {
1337  $rev = $this->page->getRevision();
1338  $content = $rev ? $rev->getContent( Revision::RAW ) : null;
1339 
1340  if ( $content === false || $content === null ) {
1341  $handler = ContentHandler::getForModelID( $this->contentModel );
1342  return $handler->makeEmptyContent();
1343  } elseif ( !$this->undidRev ) {
1344  // Content models should always be the same since we error
1345  // out if they are different before this point (in ->edit()).
1346  // The exception being, during an undo, the current revision might
1347  // differ from the prior revision.
1348  $logger = LoggerFactory::getInstance( 'editpage' );
1349  if ( $this->contentModel !== $rev->getContentModel() ) {
1350  $logger->warning( "Overriding content model from current edit {prev} to {new}", [
1351  'prev' => $this->contentModel,
1352  'new' => $rev->getContentModel(),
1353  'title' => $this->getTitle()->getPrefixedDBkey(),
1354  'method' => __METHOD__
1355  ] );
1356  $this->contentModel = $rev->getContentModel();
1357  }
1358 
1359  // Given that the content models should match, the current selected
1360  // format should be supported.
1361  if ( !$content->isSupportedFormat( $this->contentFormat ) ) {
1362  $logger->warning( "Current revision content format unsupported. Overriding {prev} to {new}", [
1363 
1364  'prev' => $this->contentFormat,
1365  'new' => $rev->getContentFormat(),
1366  'title' => $this->getTitle()->getPrefixedDBkey(),
1367  'method' => __METHOD__
1368  ] );
1369  $this->contentFormat = $rev->getContentFormat();
1370  }
1371  }
1372  return $content;
1373  }
1374 
1382  public function setPreloadedContent( Content $content ) {
1383  $this->mPreloadContent = $content;
1384  }
1385 
1397  protected function getPreloadedContent( $preload, $params = [] ) {
1398  if ( !empty( $this->mPreloadContent ) ) {
1399  return $this->mPreloadContent;
1400  }
1401 
1402  $handler = ContentHandler::getForModelID( $this->contentModel );
1403 
1404  if ( $preload === '' ) {
1405  return $handler->makeEmptyContent();
1406  }
1407 
1408  $user = $this->context->getUser();
1409  $title = Title::newFromText( $preload );
1410  # Check for existence to avoid getting MediaWiki:Noarticletext
1411  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $user ) ) {
1412  // TODO: somehow show a warning to the user!
1413  return $handler->makeEmptyContent();
1414  }
1415 
1417  if ( $page->isRedirect() ) {
1419  # Same as before
1420  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $user ) ) {
1421  // TODO: somehow show a warning to the user!
1422  return $handler->makeEmptyContent();
1423  }
1425  }
1426 
1427  $parserOptions = ParserOptions::newFromUser( $user );
1428  $content = $page->getContent( Revision::RAW );
1429 
1430  if ( !$content ) {
1431  // TODO: somehow show a warning to the user!
1432  return $handler->makeEmptyContent();
1433  }
1434 
1435  if ( $content->getModel() !== $handler->getModelID() ) {
1436  $converted = $content->convert( $handler->getModelID() );
1437 
1438  if ( !$converted ) {
1439  // TODO: somehow show a warning to the user!
1440  wfDebug( "Attempt to preload incompatible content: " .
1441  "can't convert " . $content->getModel() .
1442  " to " . $handler->getModelID() );
1443 
1444  return $handler->makeEmptyContent();
1445  }
1446 
1447  $content = $converted;
1448  }
1449 
1450  return $content->preloadTransform( $title, $parserOptions, $params );
1451  }
1452 
1460  public function tokenOk( &$request ) {
1461  $token = $request->getVal( 'wpEditToken' );
1462  $user = $this->context->getUser();
1463  $this->mTokenOk = $user->matchEditToken( $token );
1464  $this->mTokenOkExceptSuffix = $user->matchEditTokenNoSuffix( $token );
1465  return $this->mTokenOk;
1466  }
1467 
1482  protected function setPostEditCookie( $statusValue ) {
1483  $revisionId = $this->page->getLatest();
1484  $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1485 
1486  $val = 'saved';
1487  if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) {
1488  $val = 'created';
1489  } elseif ( $this->oldid ) {
1490  $val = 'restored';
1491  }
1492 
1493  $response = $this->context->getRequest()->response();
1494  $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION );
1495  }
1496 
1503  public function attemptSave( &$resultDetails = false ) {
1504  # Allow bots to exempt some edits from bot flagging
1505  $bot = $this->context->getUser()->isAllowed( 'bot' ) && $this->bot;
1506  $status = $this->internalAttemptSave( $resultDetails, $bot );
1507 
1508  Hooks::run( 'EditPage::attemptSave:after', [ $this, $status, $resultDetails ] );
1509 
1510  return $status;
1511  }
1512 
1516  private function incrementResolvedConflicts() {
1517  if ( $this->context->getRequest()->getText( 'mode' ) !== 'conflict' ) {
1518  return;
1519  }
1520 
1521  $this->getEditConflictHelper()->incrementResolvedStats();
1522  }
1523 
1533  private function handleStatus( Status $status, $resultDetails ) {
1538  if ( $status->value == self::AS_SUCCESS_UPDATE
1539  || $status->value == self::AS_SUCCESS_NEW_ARTICLE
1540  ) {
1541  $this->incrementResolvedConflicts();
1542 
1543  $this->didSave = true;
1544  if ( !$resultDetails['nullEdit'] ) {
1545  $this->setPostEditCookie( $status->value );
1546  }
1547  }
1548 
1549  $out = $this->context->getOutput();
1550 
1551  // "wpExtraQueryRedirect" is a hidden input to modify
1552  // after save URL and is not used by actual edit form
1553  $request = $this->context->getRequest();
1554  $extraQueryRedirect = $request->getVal( 'wpExtraQueryRedirect' );
1555 
1556  switch ( $status->value ) {
1564  case self::AS_END:
1567  return true;
1568 
1569  case self::AS_HOOK_ERROR:
1570  return false;
1571 
1573  case self::AS_PARSE_ERROR:
1575  $out->addWikiText( '<div class="error">' . "\n" . $status->getWikiText() . '</div>' );
1576  return true;
1577 
1579  $query = $resultDetails['redirect'] ? 'redirect=no' : '';
1580  if ( $extraQueryRedirect ) {
1581  if ( $query === '' ) {
1582  $query = $extraQueryRedirect;
1583  } else {
1584  $query = $query . '&' . $extraQueryRedirect;
1585  }
1586  }
1587  $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
1588  $out->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
1589  return false;
1590 
1592  $extraQuery = '';
1593  $sectionanchor = $resultDetails['sectionanchor'];
1594 
1595  // Give extensions a chance to modify URL query on update
1596  Hooks::run(
1597  'ArticleUpdateBeforeRedirect',
1598  [ $this->mArticle, &$sectionanchor, &$extraQuery ]
1599  );
1600 
1601  if ( $resultDetails['redirect'] ) {
1602  if ( $extraQuery == '' ) {
1603  $extraQuery = 'redirect=no';
1604  } else {
1605  $extraQuery = 'redirect=no&' . $extraQuery;
1606  }
1607  }
1608  if ( $extraQueryRedirect ) {
1609  if ( $extraQuery === '' ) {
1610  $extraQuery = $extraQueryRedirect;
1611  } else {
1612  $extraQuery = $extraQuery . '&' . $extraQueryRedirect;
1613  }
1614  }
1615 
1616  $out->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1617  return false;
1618 
1619  case self::AS_SPAM_ERROR:
1620  $this->spamPageWithContent( $resultDetails['spam'] );
1621  return false;
1622 
1624  throw new UserBlockedError( $this->context->getUser()->getBlock() );
1625 
1628  throw new PermissionsError( 'upload' );
1629 
1632  throw new PermissionsError( 'edit' );
1633 
1635  throw new ReadOnlyError;
1636 
1637  case self::AS_RATE_LIMITED:
1638  throw new ThrottledError();
1639 
1641  $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
1642  throw new PermissionsError( $permission );
1643 
1645  throw new PermissionsError( 'editcontentmodel' );
1646 
1647  default:
1648  // We don't recognize $status->value. The only way that can happen
1649  // is if an extension hook aborted from inside ArticleSave.
1650  // Render the status object into $this->hookError
1651  // FIXME this sucks, we should just use the Status object throughout
1652  $this->hookError = '<div class="error">' ."\n" . $status->getWikiText() .
1653  '</div>';
1654  return true;
1655  }
1656  }
1657 
1667  protected function runPostMergeFilters( Content $content, Status $status, User $user ) {
1668  // Run old style post-section-merge edit filter
1669  if ( $this->hookError != '' ) {
1670  # ...or the hook could be expecting us to produce an error
1671  $status->fatal( 'hookaborted' );
1673  return false;
1674  }
1675 
1676  // Run new style post-section-merge edit filter
1677  if ( !Hooks::run( 'EditFilterMergedContent',
1678  [ $this->context, $content, $status, $this->summary,
1679  $user, $this->minoredit ] )
1680  ) {
1681  # Error messages etc. could be handled within the hook...
1682  if ( $status->isGood() ) {
1683  $status->fatal( 'hookaborted' );
1684  // Not setting $this->hookError here is a hack to allow the hook
1685  // to cause a return to the edit page without $this->hookError
1686  // being set. This is used by ConfirmEdit to display a captcha
1687  // without any error message cruft.
1688  } else {
1689  $this->hookError = $this->formatStatusErrors( $status );
1690  }
1691  // Use the existing $status->value if the hook set it
1692  if ( !$status->value ) {
1693  $status->value = self::AS_HOOK_ERROR;
1694  }
1695  return false;
1696  } elseif ( !$status->isOK() ) {
1697  # ...or the hook could be expecting us to produce an error
1698  // FIXME this sucks, we should just use the Status object throughout
1699  $this->hookError = $this->formatStatusErrors( $status );
1700  $status->fatal( 'hookaborted' );
1702  return false;
1703  }
1704 
1705  return true;
1706  }
1707 
1714  private function formatStatusErrors( Status $status ) {
1715  $errmsg = $status->getWikiText(
1716  'edit-error-short',
1717  'edit-error-long',
1718  $this->context->getLanguage()
1719  );
1720  return <<<ERROR
1721 <div class="errorbox">
1722 {$errmsg}
1723 </div>
1724 <br clear="all" />
1725 ERROR;
1726  }
1727 
1734  private function newSectionSummary( &$sectionanchor = null ) {
1735  global $wgParser;
1736 
1737  if ( $this->sectiontitle !== '' ) {
1738  $sectionanchor = $this->guessSectionName( $this->sectiontitle );
1739  // If no edit summary was specified, create one automatically from the section
1740  // title and have it link to the new section. Otherwise, respect the summary as
1741  // passed.
1742  if ( $this->summary === '' ) {
1743  $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
1744  return $this->context->msg( 'newsectionsummary' )
1745  ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1746  }
1747  } elseif ( $this->summary !== '' ) {
1748  $sectionanchor = $this->guessSectionName( $this->summary );
1749  # This is a new section, so create a link to the new section
1750  # in the revision summary.
1751  $cleanSummary = $wgParser->stripSectionName( $this->summary );
1752  return $this->context->msg( 'newsectionsummary' )
1753  ->rawParams( $cleanSummary )->inContentLanguage()->text();
1754  }
1755  return $this->summary;
1756  }
1757 
1782  public function internalAttemptSave( &$result, $bot = false ) {
1784  $user = $this->context->getUser();
1785 
1786  if ( !Hooks::run( 'EditPage::attemptSave', [ $this ] ) ) {
1787  wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
1788  $status->fatal( 'hookaborted' );
1789  $status->value = self::AS_HOOK_ERROR;
1790  return $status;
1791  }
1792 
1793  if ( $this->unicodeCheck !== self::UNICODE_CHECK ) {
1794  $status->fatal( 'unicode-support-fail' );
1796  return $status;
1797  }
1798 
1799  $request = $this->context->getRequest();
1800  $spam = $request->getText( 'wpAntispam' );
1801  if ( $spam !== '' ) {
1802  wfDebugLog(
1803  'SimpleAntiSpam',
1804  $user->getName() .
1805  ' editing "' .
1806  $this->mTitle->getPrefixedText() .
1807  '" submitted bogus field "' .
1808  $spam .
1809  '"'
1810  );
1811  $status->fatal( 'spamprotectionmatch', false );
1812  $status->value = self::AS_SPAM_ERROR;
1813  return $status;
1814  }
1815 
1816  try {
1817  # Construct Content object
1818  $textbox_content = $this->toEditContent( $this->textbox1 );
1819  } catch ( MWContentSerializationException $ex ) {
1820  $status->fatal(
1821  'content-failed-to-parse',
1822  $this->contentModel,
1823  $this->contentFormat,
1824  $ex->getMessage()
1825  );
1826  $status->value = self::AS_PARSE_ERROR;
1827  return $status;
1828  }
1829 
1830  # Check image redirect
1831  if ( $this->mTitle->getNamespace() == NS_FILE &&
1832  $textbox_content->isRedirect() &&
1833  !$user->isAllowed( 'upload' )
1834  ) {
1836  $status->setResult( false, $code );
1837 
1838  return $status;
1839  }
1840 
1841  # Check for spam
1842  $match = self::matchSummarySpamRegex( $this->summary );
1843  if ( $match === false && $this->section == 'new' ) {
1844  # $wgSpamRegex is enforced on this new heading/summary because, unlike
1845  # regular summaries, it is added to the actual wikitext.
1846  if ( $this->sectiontitle !== '' ) {
1847  # This branch is taken when the API is used with the 'sectiontitle' parameter.
1848  $match = self::matchSpamRegex( $this->sectiontitle );
1849  } else {
1850  # This branch is taken when the "Add Topic" user interface is used, or the API
1851  # is used with the 'summary' parameter.
1852  $match = self::matchSpamRegex( $this->summary );
1853  }
1854  }
1855  if ( $match === false ) {
1856  $match = self::matchSpamRegex( $this->textbox1 );
1857  }
1858  if ( $match !== false ) {
1859  $result['spam'] = $match;
1860  $ip = $request->getIP();
1861  $pdbk = $this->mTitle->getPrefixedDBkey();
1862  $match = str_replace( "\n", '', $match );
1863  wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
1864  $status->fatal( 'spamprotectionmatch', $match );
1865  $status->value = self::AS_SPAM_ERROR;
1866  return $status;
1867  }
1868  if ( !Hooks::run(
1869  'EditFilter',
1870  [ $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ] )
1871  ) {
1872  # Error messages etc. could be handled within the hook...
1873  $status->fatal( 'hookaborted' );
1874  $status->value = self::AS_HOOK_ERROR;
1875  return $status;
1876  } elseif ( $this->hookError != '' ) {
1877  # ...or the hook could be expecting us to produce an error
1878  $status->fatal( 'hookaborted' );
1880  return $status;
1881  }
1882 
1883  if ( $user->isBlockedFrom( $this->mTitle, false ) ) {
1884  // Auto-block user's IP if the account was "hard" blocked
1885  if ( !wfReadOnly() ) {
1886  $user->spreadAnyEditBlock();
1887  }
1888  # Check block state against master, thus 'false'.
1889  $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
1890  return $status;
1891  }
1892 
1893  $this->contentLength = strlen( $this->textbox1 );
1894  $config = $this->context->getConfig();
1895  $maxArticleSize = $config->get( 'MaxArticleSize' );
1896  if ( $this->contentLength > $maxArticleSize * 1024 ) {
1897  // Error will be displayed by showEditForm()
1898  $this->tooBig = true;
1899  $status->setResult( false, self::AS_CONTENT_TOO_BIG );
1900  return $status;
1901  }
1902 
1903  if ( !$user->isAllowed( 'edit' ) ) {
1904  if ( $user->isAnon() ) {
1905  $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
1906  return $status;
1907  } else {
1908  $status->fatal( 'readonlytext' );
1910  return $status;
1911  }
1912  }
1913 
1914  $changingContentModel = false;
1915  if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
1916  if ( !$config->get( 'ContentHandlerUseDB' ) ) {
1917  $status->fatal( 'editpage-cannot-use-custom-model' );
1919  return $status;
1920  } elseif ( !$user->isAllowed( 'editcontentmodel' ) ) {
1921  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
1922  return $status;
1923  }
1924  // Make sure the user can edit the page under the new content model too
1925  $titleWithNewContentModel = clone $this->mTitle;
1926  $titleWithNewContentModel->setContentModel( $this->contentModel );
1927  if ( !$titleWithNewContentModel->userCan( 'editcontentmodel', $user )
1928  || !$titleWithNewContentModel->userCan( 'edit', $user )
1929  ) {
1930  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
1931  return $status;
1932  }
1933 
1934  $changingContentModel = true;
1935  $oldContentModel = $this->mTitle->getContentModel();
1936  }
1937 
1938  if ( $this->changeTags ) {
1939  $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange(
1940  $this->changeTags, $user );
1941  if ( !$changeTagsStatus->isOK() ) {
1942  $changeTagsStatus->value = self::AS_CHANGE_TAG_ERROR;
1943  return $changeTagsStatus;
1944  }
1945  }
1946 
1947  if ( wfReadOnly() ) {
1948  $status->fatal( 'readonlytext' );
1950  return $status;
1951  }
1952  if ( $user->pingLimiter() || $user->pingLimiter( 'linkpurge', 0 )
1953  || ( $changingContentModel && $user->pingLimiter( 'editcontentmodel' ) )
1954  ) {
1955  $status->fatal( 'actionthrottledtext' );
1956  $status->value = self::AS_RATE_LIMITED;
1957  return $status;
1958  }
1959 
1960  # If the article has been deleted while editing, don't save it without
1961  # confirmation
1962  if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
1963  $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
1964  return $status;
1965  }
1966 
1967  # Load the page data from the master. If anything changes in the meantime,
1968  # we detect it by using page_latest like a token in a 1 try compare-and-swap.
1969  $this->page->loadPageData( 'fromdbmaster' );
1970  $new = !$this->page->exists();
1971 
1972  if ( $new ) {
1973  // Late check for create permission, just in case *PARANOIA*
1974  if ( !$this->mTitle->userCan( 'create', $user ) ) {
1975  $status->fatal( 'nocreatetext' );
1977  wfDebug( __METHOD__ . ": no create permission\n" );
1978  return $status;
1979  }
1980 
1981  // Don't save a new page if it's blank or if it's a MediaWiki:
1982  // message with content equivalent to default (allow empty pages
1983  // in this case to disable messages, see T52124)
1984  $defaultMessageText = $this->mTitle->getDefaultMessageText();
1985  if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false ) {
1986  $defaultText = $defaultMessageText;
1987  } else {
1988  $defaultText = '';
1989  }
1990 
1991  if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) {
1992  $this->blankArticle = true;
1993  $status->fatal( 'blankarticle' );
1994  $status->setResult( false, self::AS_BLANK_ARTICLE );
1995  return $status;
1996  }
1997 
1998  if ( !$this->runPostMergeFilters( $textbox_content, $status, $user ) ) {
1999  return $status;
2000  }
2001 
2002  $content = $textbox_content;
2003 
2004  $result['sectionanchor'] = '';
2005  if ( $this->section == 'new' ) {
2006  if ( $this->sectiontitle !== '' ) {
2007  // Insert the section title above the content.
2008  $content = $content->addSectionHeader( $this->sectiontitle );
2009  } elseif ( $this->summary !== '' ) {
2010  // Insert the section title above the content.
2011  $content = $content->addSectionHeader( $this->summary );
2012  }
2013  $this->summary = $this->newSectionSummary( $result['sectionanchor'] );
2014  }
2015 
2017 
2018  } else { # not $new
2019 
2020  # Article exists. Check for edit conflict.
2021 
2022  $this->page->clear(); # Force reload of dates, etc.
2023  $timestamp = $this->page->getTimestamp();
2024  $latest = $this->page->getLatest();
2025 
2026  wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
2027 
2028  // Check editRevId if set, which handles same-second timestamp collisions
2029  if ( $timestamp != $this->edittime
2030  || ( $this->editRevId !== null && $this->editRevId != $latest )
2031  ) {
2032  $this->isConflict = true;
2033  if ( $this->section == 'new' ) {
2034  if ( $this->page->getUserText() == $user->getName() &&
2035  $this->page->getComment() == $this->newSectionSummary()
2036  ) {
2037  // Probably a duplicate submission of a new comment.
2038  // This can happen when CDN resends a request after
2039  // a timeout but the first one actually went through.
2040  wfDebug( __METHOD__
2041  . ": duplicate new section submission; trigger edit conflict!\n" );
2042  } else {
2043  // New comment; suppress conflict.
2044  $this->isConflict = false;
2045  wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
2046  }
2047  } elseif ( $this->section == ''
2049  DB_MASTER, $this->mTitle->getArticleID(),
2050  $user->getId(), $this->edittime
2051  )
2052  ) {
2053  # Suppress edit conflict with self, except for section edits where merging is required.
2054  wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
2055  $this->isConflict = false;
2056  }
2057  }
2058 
2059  // If sectiontitle is set, use it, otherwise use the summary as the section title.
2060  if ( $this->sectiontitle !== '' ) {
2061  $sectionTitle = $this->sectiontitle;
2062  } else {
2063  $sectionTitle = $this->summary;
2064  }
2065 
2066  $content = null;
2067 
2068  if ( $this->isConflict ) {
2069  wfDebug( __METHOD__
2070  . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
2071  . " (id '{$this->editRevId}') (article time '{$timestamp}')\n" );
2072  // @TODO: replaceSectionAtRev() with base ID (not prior current) for ?oldid=X case
2073  // ...or disable section editing for non-current revisions (not exposed anyway).
2074  if ( $this->editRevId !== null ) {
2075  $content = $this->page->replaceSectionAtRev(
2076  $this->section,
2077  $textbox_content,
2078  $sectionTitle,
2079  $this->editRevId
2080  );
2081  } else {
2082  $content = $this->page->replaceSectionContent(
2083  $this->section,
2084  $textbox_content,
2085  $sectionTitle,
2086  $this->edittime
2087  );
2088  }
2089  } else {
2090  wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
2091  $content = $this->page->replaceSectionContent(
2092  $this->section,
2093  $textbox_content,
2094  $sectionTitle
2095  );
2096  }
2097 
2098  if ( is_null( $content ) ) {
2099  wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
2100  $this->isConflict = true;
2101  $content = $textbox_content; // do not try to merge here!
2102  } elseif ( $this->isConflict ) {
2103  # Attempt merge
2104  if ( $this->mergeChangesIntoContent( $content ) ) {
2105  // Successful merge! Maybe we should tell the user the good news?
2106  $this->isConflict = false;
2107  wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
2108  } else {
2109  $this->section = '';
2110  $this->textbox1 = ContentHandler::getContentText( $content );
2111  wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
2112  }
2113  }
2114 
2115  if ( $this->isConflict ) {
2116  $status->setResult( false, self::AS_CONFLICT_DETECTED );
2117  return $status;
2118  }
2119 
2120  if ( !$this->runPostMergeFilters( $content, $status, $user ) ) {
2121  return $status;
2122  }
2123 
2124  if ( $this->section == 'new' ) {
2125  // Handle the user preference to force summaries here
2126  if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) {
2127  $this->missingSummary = true;
2128  $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
2130  return $status;
2131  }
2132 
2133  // Do not allow the user to post an empty comment
2134  if ( $this->textbox1 == '' ) {
2135  $this->missingComment = true;
2136  $status->fatal( 'missingcommenttext' );
2138  return $status;
2139  }
2140  } elseif ( !$this->allowBlankSummary
2141  && !$content->equals( $this->getOriginalContent( $user ) )
2142  && !$content->isRedirect()
2143  && md5( $this->summary ) == $this->autoSumm
2144  ) {
2145  $this->missingSummary = true;
2146  $status->fatal( 'missingsummary' );
2148  return $status;
2149  }
2150 
2151  # All's well
2152  $sectionanchor = '';
2153  if ( $this->section == 'new' ) {
2154  $this->summary = $this->newSectionSummary( $sectionanchor );
2155  } elseif ( $this->section != '' ) {
2156  # Try to get a section anchor from the section source, redirect
2157  # to edited section if header found.
2158  # XXX: Might be better to integrate this into Article::replaceSectionAtRev
2159  # for duplicate heading checking and maybe parsing.
2160  $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
2161  # We can't deal with anchors, includes, html etc in the header for now,
2162  # headline would need to be parsed to improve this.
2163  if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
2164  $sectionanchor = $this->guessSectionName( $matches[2] );
2165  }
2166  }
2167  $result['sectionanchor'] = $sectionanchor;
2168 
2169  // Save errors may fall down to the edit form, but we've now
2170  // merged the section into full text. Clear the section field
2171  // so that later submission of conflict forms won't try to
2172  // replace that into a duplicated mess.
2173  $this->textbox1 = $this->toEditText( $content );
2174  $this->section = '';
2175 
2177  }
2178 
2179  if ( !$this->allowSelfRedirect
2180  && $content->isRedirect()
2181  && $content->getRedirectTarget()->equals( $this->getTitle() )
2182  ) {
2183  // If the page already redirects to itself, don't warn.
2184  $currentTarget = $this->getCurrentContent()->getRedirectTarget();
2185  if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
2186  $this->selfRedirect = true;
2187  $status->fatal( 'selfredirect' );
2189  return $status;
2190  }
2191  }
2192 
2193  // Check for length errors again now that the section is merged in
2194  $this->contentLength = strlen( $this->toEditText( $content ) );
2195  if ( $this->contentLength > $maxArticleSize * 1024 ) {
2196  $this->tooBig = true;
2197  $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
2198  return $status;
2199  }
2200 
2201  $flags = EDIT_AUTOSUMMARY |
2202  ( $new ? EDIT_NEW : EDIT_UPDATE ) |
2203  ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
2204  ( $bot ? EDIT_FORCE_BOT : 0 );
2205 
2206  $doEditStatus = $this->page->doEditContent(
2207  $content,
2208  $this->summary,
2209  $flags,
2210  false,
2211  $user,
2212  $content->getDefaultFormat(),
2215  );
2216 
2217  if ( !$doEditStatus->isOK() ) {
2218  // Failure from doEdit()
2219  // Show the edit conflict page for certain recognized errors from doEdit(),
2220  // but don't show it for errors from extension hooks
2221  $errors = $doEditStatus->getErrorsArray();
2222  if ( in_array( $errors[0][0],
2223  [ 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ] )
2224  ) {
2225  $this->isConflict = true;
2226  // Destroys data doEdit() put in $status->value but who cares
2227  $doEditStatus->value = self::AS_END;
2228  }
2229  return $doEditStatus;
2230  }
2231 
2232  $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
2233  if ( $result['nullEdit'] ) {
2234  // We don't know if it was a null edit until now, so increment here
2235  $user->pingLimiter( 'linkpurge' );
2236  }
2237  $result['redirect'] = $content->isRedirect();
2238 
2239  $this->updateWatchlist();
2240 
2241  // If the content model changed, add a log entry
2242  if ( $changingContentModel ) {
2244  $user,
2245  $new ? false : $oldContentModel,
2246  $this->contentModel,
2247  $this->summary
2248  );
2249  }
2250 
2251  return $status;
2252  }
2253 
2260  protected function addContentModelChangeLogEntry( User $user, $oldModel, $newModel, $reason ) {
2261  $new = $oldModel === false;
2262  $log = new ManualLogEntry( 'contentmodel', $new ? 'new' : 'change' );
2263  $log->setPerformer( $user );
2264  $log->setTarget( $this->mTitle );
2265  $log->setComment( $reason );
2266  $log->setParameters( [
2267  '4::oldmodel' => $oldModel,
2268  '5::newmodel' => $newModel
2269  ] );
2270  $logid = $log->insert();
2271  $log->publish( $logid );
2272  }
2273 
2277  protected function updateWatchlist() {
2278  $user = $this->context->getUser();
2279  if ( !$user->isLoggedIn() ) {
2280  return;
2281  }
2282 
2284  $watch = $this->watchthis;
2285  // Do this in its own transaction to reduce contention...
2286  DeferredUpdates::addCallableUpdate( function () use ( $user, $title, $watch ) {
2287  if ( $watch == $user->isWatched( $title, User::IGNORE_USER_RIGHTS ) ) {
2288  return; // nothing to change
2289  }
2291  } );
2292  }
2293 
2305  private function mergeChangesIntoContent( &$editContent ) {
2306  $db = wfGetDB( DB_MASTER );
2307 
2308  // This is the revision the editor started from
2309  $baseRevision = $this->getBaseRevision();
2310  $baseContent = $baseRevision ? $baseRevision->getContent() : null;
2311 
2312  if ( is_null( $baseContent ) ) {
2313  return false;
2314  }
2315 
2316  // The current state, we want to merge updates into it
2317  $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
2318  $currentContent = $currentRevision ? $currentRevision->getContent() : null;
2319 
2320  if ( is_null( $currentContent ) ) {
2321  return false;
2322  }
2323 
2324  $handler = ContentHandler::getForModelID( $baseContent->getModel() );
2325 
2326  $result = $handler->merge3( $baseContent, $editContent, $currentContent );
2327 
2328  if ( $result ) {
2329  $editContent = $result;
2330  // Update parentRevId to what we just merged.
2331  $this->parentRevId = $currentRevision->getId();
2332  return true;
2333  }
2334 
2335  return false;
2336  }
2337 
2343  public function getBaseRevision() {
2344  if ( !$this->mBaseRevision ) {
2345  $db = wfGetDB( DB_MASTER );
2346  $this->mBaseRevision = $this->editRevId
2347  ? Revision::newFromId( $this->editRevId, Revision::READ_LATEST )
2348  : Revision::loadFromTimestamp( $db, $this->mTitle, $this->edittime );
2349  }
2350  return $this->mBaseRevision;
2351  }
2352 
2360  public static function matchSpamRegex( $text ) {
2362  // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
2363  $regexes = (array)$wgSpamRegex;
2364  return self::matchSpamRegexInternal( $text, $regexes );
2365  }
2366 
2374  public static function matchSummarySpamRegex( $text ) {
2376  $regexes = (array)$wgSummarySpamRegex;
2377  return self::matchSpamRegexInternal( $text, $regexes );
2378  }
2379 
2385  protected static function matchSpamRegexInternal( $text, $regexes ) {
2386  foreach ( $regexes as $regex ) {
2387  $matches = [];
2388  if ( preg_match( $regex, $text, $matches ) ) {
2389  return $matches[0];
2390  }
2391  }
2392  return false;
2393  }
2394 
2395  public function setHeaders() {
2396  $out = $this->context->getOutput();
2397 
2398  $out->addModules( 'mediawiki.action.edit' );
2399  $out->addModuleStyles( 'mediawiki.action.edit.styles' );
2400  $out->addModuleStyles( 'mediawiki.editfont.styles' );
2401 
2402  $user = $this->context->getUser();
2403  if ( $user->getOption( 'showtoolbar' ) ) {
2404  // The addition of default buttons is handled by getEditToolbar() which
2405  // has its own dependency on this module. The call here ensures the module
2406  // is loaded in time (it has position "top") for other modules to register
2407  // buttons (e.g. extensions, gadgets, user scripts).
2408  $out->addModules( 'mediawiki.toolbar' );
2409  }
2410 
2411  if ( $user->getOption( 'uselivepreview' ) ) {
2412  $out->addModules( 'mediawiki.action.edit.preview' );
2413  }
2414 
2415  if ( $user->getOption( 'useeditwarning' ) ) {
2416  $out->addModules( 'mediawiki.action.edit.editWarning' );
2417  }
2418 
2419  # Enabled article-related sidebar, toplinks, etc.
2420  $out->setArticleRelated( true );
2421 
2422  $contextTitle = $this->getContextTitle();
2423  if ( $this->isConflict ) {
2424  $msg = 'editconflict';
2425  } elseif ( $contextTitle->exists() && $this->section != '' ) {
2426  $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
2427  } else {
2428  $msg = $contextTitle->exists()
2429  || ( $contextTitle->getNamespace() == NS_MEDIAWIKI
2430  && $contextTitle->getDefaultMessageText() !== false
2431  )
2432  ? 'editing'
2433  : 'creating';
2434  }
2435 
2436  # Use the title defined by DISPLAYTITLE magic word when present
2437  # NOTE: getDisplayTitle() returns HTML while getPrefixedText() returns plain text.
2438  # setPageTitle() treats the input as wikitext, which should be safe in either case.
2439  $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
2440  if ( $displayTitle === false ) {
2441  $displayTitle = $contextTitle->getPrefixedText();
2442  }
2443  $out->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
2444 
2445  $config = $this->context->getConfig();
2446 
2447  # Transmit the name of the message to JavaScript for live preview
2448  # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
2449  $out->addJsConfigVars( [
2450  'wgEditMessage' => $msg,
2451  'wgAjaxEditStash' => $config->get( 'AjaxEditStash' ),
2452  ] );
2453 
2454  // Add whether to use 'save' or 'publish' messages to JavaScript for post-edit, other
2455  // editors, etc.
2456  $out->addJsConfigVars(
2457  'wgEditSubmitButtonLabelPublish',
2458  $config->get( 'EditSubmitButtonLabelPublish' )
2459  );
2460  }
2461 
2465  protected function showIntro() {
2466  if ( $this->suppressIntro ) {
2467  return;
2468  }
2469 
2470  $out = $this->context->getOutput();
2471  $namespace = $this->mTitle->getNamespace();
2472 
2473  if ( $namespace == NS_MEDIAWIKI ) {
2474  # Show a warning if editing an interface message
2475  $out->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
2476  # If this is a default message (but not css, json, or js),
2477  # show a hint that it is translatable on translatewiki.net
2478  if (
2479  !$this->mTitle->hasContentModel( CONTENT_MODEL_CSS )
2480  && !$this->mTitle->hasContentModel( CONTENT_MODEL_JSON )
2481  && !$this->mTitle->hasContentModel( CONTENT_MODEL_JAVASCRIPT )
2482  ) {
2483  $defaultMessageText = $this->mTitle->getDefaultMessageText();
2484  if ( $defaultMessageText !== false ) {
2485  $out->wrapWikiMsg( "<div class='mw-translateinterface'>\n$1\n</div>",
2486  'translateinterface' );
2487  }
2488  }
2489  } elseif ( $namespace == NS_FILE ) {
2490  # Show a hint to shared repo
2491  $file = wfFindFile( $this->mTitle );
2492  if ( $file && !$file->isLocal() ) {
2493  $descUrl = $file->getDescriptionUrl();
2494  # there must be a description url to show a hint to shared repo
2495  if ( $descUrl ) {
2496  if ( !$this->mTitle->exists() ) {
2497  $out->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
2498  'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
2499  ] );
2500  } else {
2501  $out->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
2502  'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
2503  ] );
2504  }
2505  }
2506  }
2507  }
2508 
2509  # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2510  # Show log extract when the user is currently blocked
2511  if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
2512  $username = explode( '/', $this->mTitle->getText(), 2 )[0];
2513  $user = User::newFromName( $username, false /* allow IP users */ );
2514  $ip = User::isIP( $username );
2515  $block = Block::newFromTarget( $user, $user );
2516 
2517  $userExists = ( $user && $user->isLoggedIn() );
2518  if ( $userExists && $user->isHidden() && !$this->context->getUser()->isAllowed( 'hideuser' ) ) {
2519  // If the user exists, but is hidden, and the viewer cannot see hidden
2520  // users, pretend like they don't exist at all. See T120883
2521  $userExists = false;
2522  }
2523 
2524  if ( !$userExists && !$ip ) { # User does not exist
2525  $out->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2526  [ 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ] );
2527  } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
2528  # Show log extract if the user is currently blocked
2530  $out,
2531  'block',
2532  MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
2533  '',
2534  [
2535  'lim' => 1,
2536  'showIfEmpty' => false,
2537  'msgKey' => [
2538  'blocked-notice-logextract',
2539  $user->getName() # Support GENDER in notice
2540  ]
2541  ]
2542  );
2543  }
2544  }
2545  # Try to add a custom edit intro, or use the standard one if this is not possible.
2546  if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
2548  $this->context->msg( 'helppage' )->inContentLanguage()->text()
2549  ) );
2550  if ( $this->context->getUser()->isLoggedIn() ) {
2551  $out->wrapWikiMsg(
2552  // Suppress the external link icon, consider the help url an internal one
2553  "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2554  [
2555  'newarticletext',
2556  $helpLink
2557  ]
2558  );
2559  } else {
2560  $out->wrapWikiMsg(
2561  // Suppress the external link icon, consider the help url an internal one
2562  "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2563  [
2564  'newarticletextanon',
2565  $helpLink
2566  ]
2567  );
2568  }
2569  }
2570  # Give a notice if the user is editing a deleted/moved page...
2571  if ( !$this->mTitle->exists() ) {
2572  $dbr = wfGetDB( DB_REPLICA );
2573 
2574  LogEventsList::showLogExtract( $out, [ 'delete', 'move' ], $this->mTitle,
2575  '',
2576  [
2577  'lim' => 10,
2578  'conds' => [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ],
2579  'showIfEmpty' => false,
2580  'msgKey' => [ 'recreate-moveddeleted-warn' ]
2581  ]
2582  );
2583  }
2584  }
2585 
2591  protected function showCustomIntro() {
2592  if ( $this->editintro ) {
2593  $title = Title::newFromText( $this->editintro );
2594  if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
2595  // Added using template syntax, to take <noinclude>'s into account.
2596  $this->context->getOutput()->addWikiTextTitleTidy(
2597  '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>',
2598  $this->mTitle
2599  );
2600  return true;
2601  }
2602  }
2603  return false;
2604  }
2605 
2624  protected function toEditText( $content ) {
2625  if ( $content === null || $content === false || is_string( $content ) ) {
2626  return $content;
2627  }
2628 
2629  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2630  throw new MWException( 'This content model is not supported: ' . $content->getModel() );
2631  }
2632 
2633  return $content->serialize( $this->contentFormat );
2634  }
2635 
2652  protected function toEditContent( $text ) {
2653  if ( $text === false || $text === null ) {
2654  return $text;
2655  }
2656 
2657  $content = ContentHandler::makeContent( $text, $this->getTitle(),
2658  $this->contentModel, $this->contentFormat );
2659 
2660  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2661  throw new MWException( 'This content model is not supported: ' . $content->getModel() );
2662  }
2663 
2664  return $content;
2665  }
2666 
2675  public function showEditForm( $formCallback = null ) {
2676  # need to parse the preview early so that we know which templates are used,
2677  # otherwise users with "show preview after edit box" will get a blank list
2678  # we parse this near the beginning so that setHeaders can do the title
2679  # setting work instead of leaving it in getPreviewText
2680  $previewOutput = '';
2681  if ( $this->formtype == 'preview' ) {
2682  $previewOutput = $this->getPreviewText();
2683  }
2684 
2685  $out = $this->context->getOutput();
2686 
2687  // Avoid PHP 7.1 warning of passing $this by reference
2688  $editPage = $this;
2689  Hooks::run( 'EditPage::showEditForm:initial', [ &$editPage, &$out ] );
2690 
2691  $this->setHeaders();
2692 
2693  $this->addTalkPageText();
2694  $this->addEditNotices();
2695 
2696  if ( !$this->isConflict &&
2697  $this->section != '' &&
2698  !$this->isSectionEditSupported() ) {
2699  // We use $this->section to much before this and getVal('wgSection') directly in other places
2700  // at this point we can't reset $this->section to '' to fallback to non-section editing.
2701  // Someone is welcome to try refactoring though
2702  $out->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
2703  return;
2704  }
2705 
2706  $this->showHeader();
2707 
2708  $out->addHTML( $this->editFormPageTop );
2709 
2710  $user = $this->context->getUser();
2711  if ( $user->getOption( 'previewontop' ) ) {
2712  $this->displayPreviewArea( $previewOutput, true );
2713  }
2714 
2715  $out->addHTML( $this->editFormTextTop );
2716 
2717  $showToolbar = true;
2718  if ( $this->wasDeletedSinceLastEdit() ) {
2719  if ( $this->formtype == 'save' ) {
2720  // Hide the toolbar and edit area, user can click preview to get it back
2721  // Add an confirmation checkbox and explanation.
2722  $showToolbar = false;
2723  } else {
2724  $out->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2725  'deletedwhileediting' );
2726  }
2727  }
2728 
2729  // @todo add EditForm plugin interface and use it here!
2730  // search for textarea1 and textarea2, and allow EditForm to override all uses.
2731  $out->addHTML( Html::openElement(
2732  'form',
2733  [
2734  'class' => 'mw-editform',
2735  'id' => self::EDITFORM_ID,
2736  'name' => self::EDITFORM_ID,
2737  'method' => 'post',
2738  'action' => $this->getActionURL( $this->getContextTitle() ),
2739  'enctype' => 'multipart/form-data'
2740  ]
2741  ) );
2742 
2743  if ( is_callable( $formCallback ) ) {
2744  wfWarn( 'The $formCallback parameter to ' . __METHOD__ . 'is deprecated' );
2745  call_user_func_array( $formCallback, [ &$out ] );
2746  }
2747 
2748  // Add a check for Unicode support
2749  $out->addHTML( Html::hidden( 'wpUnicodeCheck', self::UNICODE_CHECK ) );
2750 
2751  // Add an empty field to trip up spambots
2752  $out->addHTML(
2753  Xml::openElement( 'div', [ 'id' => 'antispam-container', 'style' => 'display: none;' ] )
2754  . Html::rawElement(
2755  'label',
2756  [ 'for' => 'wpAntispam' ],
2757  $this->context->msg( 'simpleantispam-label' )->parse()
2758  )
2759  . Xml::element(
2760  'input',
2761  [
2762  'type' => 'text',
2763  'name' => 'wpAntispam',
2764  'id' => 'wpAntispam',
2765  'value' => ''
2766  ]
2767  )
2768  . Xml::closeElement( 'div' )
2769  );
2770 
2771  // Avoid PHP 7.1 warning of passing $this by reference
2772  $editPage = $this;
2773  Hooks::run( 'EditPage::showEditForm:fields', [ &$editPage, &$out ] );
2774 
2775  // Put these up at the top to ensure they aren't lost on early form submission
2776  $this->showFormBeforeText();
2777 
2778  if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
2779  $username = $this->lastDelete->user_name;
2780  $comment = CommentStore::getStore()
2781  ->getComment( 'log_comment', $this->lastDelete )->text;
2782 
2783  // It is better to not parse the comment at all than to have templates expanded in the middle
2784  // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
2785  $key = $comment === ''
2786  ? 'confirmrecreate-noreason'
2787  : 'confirmrecreate';
2788  $out->addHTML(
2789  '<div class="mw-confirm-recreate">' .
2790  $this->context->msg( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
2791  Xml::checkLabel( $this->context->msg( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
2792  [ 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' ]
2793  ) .
2794  '</div>'
2795  );
2796  }
2797 
2798  # When the summary is hidden, also hide them on preview/show changes
2799  if ( $this->nosummary ) {
2800  $out->addHTML( Html::hidden( 'nosummary', true ) );
2801  }
2802 
2803  # If a blank edit summary was previously provided, and the appropriate
2804  # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2805  # user being bounced back more than once in the event that a summary
2806  # is not required.
2807  # ####
2808  # For a bit more sophisticated detection of blank summaries, hash the
2809  # automatic one and pass that in the hidden field wpAutoSummary.
2810  if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
2811  $out->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
2812  }
2813 
2814  if ( $this->undidRev ) {
2815  $out->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
2816  }
2817 
2818  if ( $this->selfRedirect ) {
2819  $out->addHTML( Html::hidden( 'wpIgnoreSelfRedirect', true ) );
2820  }
2821 
2822  if ( $this->hasPresetSummary ) {
2823  // If a summary has been preset using &summary= we don't want to prompt for
2824  // a different summary. Only prompt for a summary if the summary is blanked.
2825  // (T19416)
2826  $this->autoSumm = md5( '' );
2827  }
2828 
2829  $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
2830  $out->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
2831 
2832  $out->addHTML( Html::hidden( 'oldid', $this->oldid ) );
2833  $out->addHTML( Html::hidden( 'parentRevId', $this->getParentRevId() ) );
2834 
2835  $out->addHTML( Html::hidden( 'format', $this->contentFormat ) );
2836  $out->addHTML( Html::hidden( 'model', $this->contentModel ) );
2837 
2838  $out->enableOOUI();
2839 
2840  if ( $this->section == 'new' ) {
2841  $this->showSummaryInput( true, $this->summary );
2842  $out->addHTML( $this->getSummaryPreview( true, $this->summary ) );
2843  }
2844 
2845  $out->addHTML( $this->editFormTextBeforeContent );
2846  if ( $this->isConflict ) {
2847  // In an edit conflict, we turn textbox2 into the user's text,
2848  // and textbox1 into the stored version
2849  $this->textbox2 = $this->textbox1;
2850 
2851  $content = $this->getCurrentContent();
2852  $this->textbox1 = $this->toEditText( $content );
2853 
2855  $editConflictHelper->setTextboxes( $this->textbox2, $this->textbox1 );
2856  $editConflictHelper->setContentModel( $this->contentModel );
2857  $editConflictHelper->setContentFormat( $this->contentFormat );
2859  }
2860 
2861  if ( !$this->mTitle->isUserConfigPage() && $showToolbar && $user->getOption( 'showtoolbar' ) ) {
2862  $out->addHTML( self::getEditToolbar( $this->mTitle ) );
2863  }
2864 
2865  if ( $this->blankArticle ) {
2866  $out->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) );
2867  }
2868 
2869  if ( $this->isConflict ) {
2870  // In an edit conflict bypass the overridable content form method
2871  // and fallback to the raw wpTextbox1 since editconflicts can't be
2872  // resolved between page source edits and custom ui edits using the
2873  // custom edit ui.
2874  $conflictTextBoxAttribs = [];
2875  if ( $this->wasDeletedSinceLastEdit() ) {
2876  $conflictTextBoxAttribs['style'] = 'display:none;';
2877  } elseif ( $this->isOldRev ) {
2878  $conflictTextBoxAttribs['class'] = 'mw-textarea-oldrev';
2879  }
2880 
2881  $out->addHTML( $editConflictHelper->getEditConflictMainTextBox( $conflictTextBoxAttribs ) );
2883  } else {
2884  $this->showContentForm();
2885  }
2886 
2887  $out->addHTML( $this->editFormTextAfterContent );
2888 
2889  $this->showStandardInputs();
2890 
2891  $this->showFormAfterText();
2892 
2893  $this->showTosSummary();
2894 
2895  $this->showEditTools();
2896 
2897  $out->addHTML( $this->editFormTextAfterTools . "\n" );
2898 
2899  $out->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
2900 
2901  $out->addHTML( Html::rawElement( 'div', [ 'class' => 'hiddencats' ],
2902  Linker::formatHiddenCategories( $this->page->getHiddenCategories() ) ) );
2903 
2904  $out->addHTML( Html::rawElement( 'div', [ 'class' => 'limitreport' ],
2905  self::getPreviewLimitReport( $this->mParserOutput ) ) );
2906 
2907  $out->addModules( 'mediawiki.action.edit.collapsibleFooter' );
2908 
2909  if ( $this->isConflict ) {
2910  try {
2911  $this->showConflict();
2912  } catch ( MWContentSerializationException $ex ) {
2913  // this can't really happen, but be nice if it does.
2914  $msg = $this->context->msg(
2915  'content-failed-to-parse',
2916  $this->contentModel,
2917  $this->contentFormat,
2918  $ex->getMessage()
2919  );
2920  $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
2921  }
2922  }
2923 
2924  // Set a hidden field so JS knows what edit form mode we are in
2925  if ( $this->isConflict ) {
2926  $mode = 'conflict';
2927  } elseif ( $this->preview ) {
2928  $mode = 'preview';
2929  } elseif ( $this->diff ) {
2930  $mode = 'diff';
2931  } else {
2932  $mode = 'text';
2933  }
2934  $out->addHTML( Html::hidden( 'mode', $mode, [ 'id' => 'mw-edit-mode' ] ) );
2935 
2936  // Marker for detecting truncated form data. This must be the last
2937  // parameter sent in order to be of use, so do not move me.
2938  $out->addHTML( Html::hidden( 'wpUltimateParam', true ) );
2939  $out->addHTML( $this->editFormTextBottom . "\n</form>\n" );
2940 
2941  if ( !$user->getOption( 'previewontop' ) ) {
2942  $this->displayPreviewArea( $previewOutput, false );
2943  }
2944  }
2945 
2953  public function makeTemplatesOnThisPageList( array $templates ) {
2954  $templateListFormatter = new TemplatesOnThisPageFormatter(
2955  $this->context, MediaWikiServices::getInstance()->getLinkRenderer()
2956  );
2957 
2958  // preview if preview, else section if section, else false
2959  $type = false;
2960  if ( $this->preview ) {
2961  $type = 'preview';
2962  } elseif ( $this->section != '' ) {
2963  $type = 'section';
2964  }
2965 
2966  return Html::rawElement( 'div', [ 'class' => 'templatesUsed' ],
2967  $templateListFormatter->format( $templates, $type )
2968  );
2969  }
2970 
2977  public static function extractSectionTitle( $text ) {
2978  preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
2979  if ( !empty( $matches[2] ) ) {
2980  global $wgParser;
2981  return $wgParser->stripSectionName( trim( $matches[2] ) );
2982  } else {
2983  return false;
2984  }
2985  }
2986 
2987  protected function showHeader() {
2988  $out = $this->context->getOutput();
2989  $user = $this->context->getUser();
2990  if ( $this->isConflict ) {
2991  $this->addExplainConflictHeader( $out );
2992  $this->editRevId = $this->page->getLatest();
2993  } else {
2994  if ( $this->section != '' && $this->section != 'new' ) {
2995  if ( !$this->summary && !$this->preview && !$this->diff ) {
2996  $sectionTitle = self::extractSectionTitle( $this->textbox1 ); // FIXME: use Content object
2997  if ( $sectionTitle !== false ) {
2998  $this->summary = "/* $sectionTitle */ ";
2999  }
3000  }
3001  }
3002 
3003  $buttonLabel = $this->context->msg( $this->getSubmitButtonLabel() )->text();
3004 
3005  if ( $this->missingComment ) {
3006  $out->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
3007  }
3008 
3009  if ( $this->missingSummary && $this->section != 'new' ) {
3010  $out->wrapWikiMsg(
3011  "<div id='mw-missingsummary'>\n$1\n</div>",
3012  [ 'missingsummary', $buttonLabel ]
3013  );
3014  }
3015 
3016  if ( $this->missingSummary && $this->section == 'new' ) {
3017  $out->wrapWikiMsg(
3018  "<div id='mw-missingcommentheader'>\n$1\n</div>",
3019  [ 'missingcommentheader', $buttonLabel ]
3020  );
3021  }
3022 
3023  if ( $this->blankArticle ) {
3024  $out->wrapWikiMsg(
3025  "<div id='mw-blankarticle'>\n$1\n</div>",
3026  [ 'blankarticle', $buttonLabel ]
3027  );
3028  }
3029 
3030  if ( $this->selfRedirect ) {
3031  $out->wrapWikiMsg(
3032  "<div id='mw-selfredirect'>\n$1\n</div>",
3033  [ 'selfredirect', $buttonLabel ]
3034  );
3035  }
3036 
3037  if ( $this->hookError !== '' ) {
3038  $out->addWikiText( $this->hookError );
3039  }
3040 
3041  if ( $this->section != 'new' ) {
3042  $revision = $this->mArticle->getRevisionFetched();
3043  if ( $revision ) {
3044  // Let sysop know that this will make private content public if saved
3045 
3046  if ( !$revision->userCan( Revision::DELETED_TEXT, $user ) ) {
3047  $out->wrapWikiMsg(
3048  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
3049  'rev-deleted-text-permission'
3050  );
3051  } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
3052  $out->wrapWikiMsg(
3053  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
3054  'rev-deleted-text-view'
3055  );
3056  }
3057 
3058  if ( !$revision->isCurrent() ) {
3059  $this->mArticle->setOldSubtitle( $revision->getId() );
3060  $out->addWikiMsg( 'editingold' );
3061  $this->isOldRev = true;
3062  }
3063  } elseif ( $this->mTitle->exists() ) {
3064  // Something went wrong
3065 
3066  $out->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
3067  [ 'missing-revision', $this->oldid ] );
3068  }
3069  }
3070  }
3071 
3072  if ( wfReadOnly() ) {
3073  $out->wrapWikiMsg(
3074  "<div id=\"mw-read-only-warning\">\n$1\n</div>",
3075  [ 'readonlywarning', wfReadOnlyReason() ]
3076  );
3077  } elseif ( $user->isAnon() ) {
3078  if ( $this->formtype != 'preview' ) {
3079  $out->wrapWikiMsg(
3080  "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
3081  [ 'anoneditwarning',
3082  // Log-in link
3083  SpecialPage::getTitleFor( 'Userlogin' )->getFullURL( [
3084  'returnto' => $this->getTitle()->getPrefixedDBkey()
3085  ] ),
3086  // Sign-up link
3087  SpecialPage::getTitleFor( 'CreateAccount' )->getFullURL( [
3088  'returnto' => $this->getTitle()->getPrefixedDBkey()
3089  ] )
3090  ]
3091  );
3092  } else {
3093  $out->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
3094  'anonpreviewwarning'
3095  );
3096  }
3097  } else {
3098  if ( $this->mTitle->isUserConfigPage() ) {
3099  # Check the skin exists
3100  if ( $this->isWrongCaseUserConfigPage() ) {
3101  $out->wrapWikiMsg(
3102  "<div class='error' id='mw-userinvalidconfigtitle'>\n$1\n</div>",
3103  [ 'userinvalidconfigtitle', $this->mTitle->getSkinFromConfigSubpage() ]
3104  );
3105  }
3106  if ( $this->getTitle()->isSubpageOf( $user->getUserPage() ) ) {
3107  $isUserCssConfig = $this->mTitle->isUserCssConfigPage();
3108  $isUserJsonConfig = $this->mTitle->isUserJsonConfigPage();
3109  $isUserJsConfig = $this->mTitle->isUserJsConfigPage();
3110 
3111  $warning = $isUserCssConfig
3112  ? 'usercssispublic'
3113  : ( $isUserJsonConfig ? 'userjsonispublic' : 'userjsispublic' );
3114 
3115  $out->wrapWikiMsg( '<div class="mw-userconfigpublic">$1</div>', $warning );
3116 
3117  if ( $this->formtype !== 'preview' ) {
3118  $config = $this->context->getConfig();
3119  if ( $isUserCssConfig && $config->get( 'AllowUserCss' ) ) {
3120  $out->wrapWikiMsg(
3121  "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
3122  [ 'usercssyoucanpreview' ]
3123  );
3124  } elseif ( $isUserJsonConfig /* No comparable 'AllowUserJson' */ ) {
3125  $out->wrapWikiMsg(
3126  "<div id='mw-userjsonyoucanpreview'>\n$1\n</div>",
3127  [ 'userjsonyoucanpreview' ]
3128  );
3129  } elseif ( $isUserJsConfig && $config->get( 'AllowUserJs' ) ) {
3130  $out->wrapWikiMsg(
3131  "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
3132  [ 'userjsyoucanpreview' ]
3133  );
3134  }
3135  }
3136  }
3137  }
3138  }
3139 
3141 
3142  $this->addLongPageWarningHeader();
3143 
3144  # Add header copyright warning
3145  $this->showHeaderCopyrightWarning();
3146  }
3147 
3155  private function getSummaryInputAttributes( array $inputAttrs = null ) {
3156  $conf = $this->context->getConfig();
3157  $oldCommentSchema = $conf->get( 'CommentTableSchemaMigrationStage' ) === MIGRATION_OLD;
3158  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
3159  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
3160  // Unicode codepoints (or 255 UTF-8 bytes for old schema).
3161  return ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
3162  'id' => 'wpSummary',
3163  'name' => 'wpSummary',
3164  'maxlength' => $oldCommentSchema ? 200 : CommentStore::COMMENT_CHARACTER_LIMIT,
3165  'tabindex' => 1,
3166  'size' => 60,
3167  'spellcheck' => 'true',
3168  ];
3169  }
3170 
3180  function getSummaryInputWidget( $summary = "", $labelText = null, $inputAttrs = null ) {
3181  $inputAttrs = OOUI\Element::configFromHtmlAttributes(
3182  $this->getSummaryInputAttributes( $inputAttrs )
3183  );
3184  $inputAttrs += [
3185  'title' => Linker::titleAttrib( 'summary' ),
3186  'accessKey' => Linker::accesskey( 'summary' ),
3187  ];
3188 
3189  // For compatibility with old scripts and extensions, we want the legacy 'id' on the `<input>`
3190  $inputAttrs['inputId'] = $inputAttrs['id'];
3191  $inputAttrs['id'] = 'wpSummaryWidget';
3192 
3193  return new OOUI\FieldLayout(
3194  new OOUI\TextInputWidget( [
3195  'value' => $summary,
3196  'infusable' => true,
3197  ] + $inputAttrs ),
3198  [
3199  'label' => new OOUI\HtmlSnippet( $labelText ),
3200  'align' => 'top',
3201  'id' => 'wpSummaryLabel',
3202  'classes' => [ $this->missingSummary ? 'mw-summarymissed' : 'mw-summary' ],
3203  ]
3204  );
3205  }
3206 
3213  protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
3214  # Add a class if 'missingsummary' is triggered to allow styling of the summary line
3215  $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
3216  if ( $isSubjectPreview ) {
3217  if ( $this->nosummary ) {
3218  return;
3219  }
3220  } else {
3221  if ( !$this->mShowSummaryField ) {
3222  return;
3223  }
3224  }
3225 
3226  $labelText = $this->context->msg( $isSubjectPreview ? 'subject' : 'summary' )->parse();
3227  $this->context->getOutput()->addHTML( $this->getSummaryInputWidget(
3228  $summary,
3229  $labelText,
3230  [ 'class' => $summaryClass ]
3231  ) );
3232  }
3233 
3241  protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
3242  // avoid spaces in preview, gets always trimmed on save
3243  $summary = trim( $summary );
3244  if ( !$summary || ( !$this->preview && !$this->diff ) ) {
3245  return "";
3246  }
3247 
3248  global $wgParser;
3249 
3250  if ( $isSubjectPreview ) {
3251  $summary = $this->context->msg( 'newsectionsummary' )
3252  ->rawParams( $wgParser->stripSectionName( $summary ) )
3253  ->inContentLanguage()->text();
3254  }
3255 
3256  $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
3257 
3258  $summary = $this->context->msg( $message )->parse()
3259  . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
3260  return Xml::tags( 'div', [ 'class' => 'mw-summary-preview' ], $summary );
3261  }
3262 
3263  protected function showFormBeforeText() {
3264  $out = $this->context->getOutput();
3265  $out->addHTML( Html::hidden( 'wpSection', $this->section ) );
3266  $out->addHTML( Html::hidden( 'wpStarttime', $this->starttime ) );
3267  $out->addHTML( Html::hidden( 'wpEdittime', $this->edittime ) );
3268  $out->addHTML( Html::hidden( 'editRevId', $this->editRevId ) );
3269  $out->addHTML( Html::hidden( 'wpScrolltop', $this->scrolltop, [ 'id' => 'wpScrolltop' ] ) );
3270  }
3271 
3272  protected function showFormAfterText() {
3285  $this->context->getOutput()->addHTML(
3286  "\n" .
3287  Html::hidden( "wpEditToken", $this->context->getUser()->getEditToken() ) .
3288  "\n"
3289  );
3290  }
3291 
3300  protected function showContentForm() {
3301  $this->showTextbox1();
3302  }
3303 
3312  protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
3313  if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
3314  $attribs = [ 'style' => 'display:none;' ];
3315  } else {
3316  $builder = new TextboxBuilder();
3317  $classes = $builder->getTextboxProtectionCSSClasses( $this->getTitle() );
3318 
3319  # Is an old revision being edited?
3320  if ( $this->isOldRev ) {
3321  $classes[] = 'mw-textarea-oldrev';
3322  }
3323 
3324  $attribs = [ 'tabindex' => 1 ];
3325 
3326  if ( is_array( $customAttribs ) ) {
3328  }
3329 
3330  $attribs = $builder->mergeClassesIntoAttributes( $classes, $attribs );
3331  }
3332 
3333  $this->showTextbox(
3334  $textoverride !== null ? $textoverride : $this->textbox1,
3335  'wpTextbox1',
3336  $attribs
3337  );
3338  }
3339 
3340  protected function showTextbox2() {
3341  $this->showTextbox( $this->textbox2, 'wpTextbox2', [ 'tabindex' => 6, 'readonly' ] );
3342  }
3343 
3344  protected function showTextbox( $text, $name, $customAttribs = [] ) {
3345  $builder = new TextboxBuilder();
3346  $attribs = $builder->buildTextboxAttribs(
3347  $name,
3349  $this->context->getUser(),
3350  $this->mTitle
3351  );
3352 
3353  $this->context->getOutput()->addHTML(
3354  Html::textarea( $name, $builder->addNewLineAtEnd( $text ), $attribs )
3355  );
3356  }
3357 
3358  protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
3359  $classes = [];
3360  if ( $isOnTop ) {
3361  $classes[] = 'ontop';
3362  }
3363 
3364  $attribs = [ 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) ];
3365 
3366  if ( $this->formtype != 'preview' ) {
3367  $attribs['style'] = 'display: none;';
3368  }
3369 
3370  $out = $this->context->getOutput();
3371  $out->addHTML( Xml::openElement( 'div', $attribs ) );
3372 
3373  if ( $this->formtype == 'preview' ) {
3374  $this->showPreview( $previewOutput );
3375  } else {
3376  // Empty content container for LivePreview
3377  $pageViewLang = $this->mTitle->getPageViewLanguage();
3378  $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3379  'class' => 'mw-content-' . $pageViewLang->getDir() ];
3380  $out->addHTML( Html::rawElement( 'div', $attribs ) );
3381  }
3382 
3383  $out->addHTML( '</div>' );
3384 
3385  if ( $this->formtype == 'diff' ) {
3386  try {
3387  $this->showDiff();
3388  } catch ( MWContentSerializationException $ex ) {
3389  $msg = $this->context->msg(
3390  'content-failed-to-parse',
3391  $this->contentModel,
3392  $this->contentFormat,
3393  $ex->getMessage()
3394  );
3395  $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
3396  }
3397  }
3398  }
3399 
3406  protected function showPreview( $text ) {
3407  if ( $this->mArticle instanceof CategoryPage ) {
3408  $this->mArticle->openShowCategory();
3409  }
3410  # This hook seems slightly odd here, but makes things more
3411  # consistent for extensions.
3412  $out = $this->context->getOutput();
3413  Hooks::run( 'OutputPageBeforeHTML', [ &$out, &$text ] );
3414  $out->addHTML( $text );
3415  if ( $this->mArticle instanceof CategoryPage ) {
3416  $this->mArticle->closeShowCategory();
3417  }
3418  }
3419 
3427  public function showDiff() {
3429 
3430  $oldtitlemsg = 'currentrev';
3431  # if message does not exist, show diff against the preloaded default
3432  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
3433  $oldtext = $this->mTitle->getDefaultMessageText();
3434  if ( $oldtext !== false ) {
3435  $oldtitlemsg = 'defaultmessagetext';
3436  $oldContent = $this->toEditContent( $oldtext );
3437  } else {
3438  $oldContent = null;
3439  }
3440  } else {
3441  $oldContent = $this->getCurrentContent();
3442  }
3443 
3444  $textboxContent = $this->toEditContent( $this->textbox1 );
3445  if ( $this->editRevId !== null ) {
3446  $newContent = $this->page->replaceSectionAtRev(
3447  $this->section, $textboxContent, $this->summary, $this->editRevId
3448  );
3449  } else {
3450  $newContent = $this->page->replaceSectionContent(
3451  $this->section, $textboxContent, $this->summary, $this->edittime
3452  );
3453  }
3454 
3455  if ( $newContent ) {
3456  Hooks::run( 'EditPageGetDiffContent', [ $this, &$newContent ] );
3457 
3458  $user = $this->context->getUser();
3460  $newContent = $newContent->preSaveTransform( $this->mTitle, $user, $popts );
3461  }
3462 
3463  if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
3464  $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
3465  $newtitle = $this->context->msg( 'yourtext' )->parse();
3466 
3467  if ( !$oldContent ) {
3468  $oldContent = $newContent->getContentHandler()->makeEmptyContent();
3469  }
3470 
3471  if ( !$newContent ) {
3472  $newContent = $oldContent->getContentHandler()->makeEmptyContent();
3473  }
3474 
3475  $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->context );
3476  $de->setContent( $oldContent, $newContent );
3477 
3478  $difftext = $de->getDiff( $oldtitle, $newtitle );
3479  $de->showDiffStyle();
3480  } else {
3481  $difftext = '';
3482  }
3483 
3484  $this->context->getOutput()->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
3485  }
3486 
3490  protected function showHeaderCopyrightWarning() {
3491  $msg = 'editpage-head-copy-warn';
3492  if ( !$this->context->msg( $msg )->isDisabled() ) {
3493  $this->context->getOutput()->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
3494  'editpage-head-copy-warn' );
3495  }
3496  }
3497 
3506  protected function showTosSummary() {
3507  $msg = 'editpage-tos-summary';
3508  Hooks::run( 'EditPageTosSummary', [ $this->mTitle, &$msg ] );
3509  if ( !$this->context->msg( $msg )->isDisabled() ) {
3510  $out = $this->context->getOutput();
3511  $out->addHTML( '<div class="mw-tos-summary">' );
3512  $out->addWikiMsg( $msg );
3513  $out->addHTML( '</div>' );
3514  }
3515  }
3516 
3521  protected function showEditTools() {
3522  $this->context->getOutput()->addHTML( '<div class="mw-editTools">' .
3523  $this->context->msg( 'edittools' )->inContentLanguage()->parse() .
3524  '</div>' );
3525  }
3526 
3533  protected function getCopywarn() {
3534  return self::getCopyrightWarning( $this->mTitle );
3535  }
3536 
3545  public static function getCopyrightWarning( $title, $format = 'plain', $langcode = null ) {
3547  if ( $wgRightsText ) {
3548  $copywarnMsg = [ 'copyrightwarning',
3549  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]',
3550  $wgRightsText ];
3551  } else {
3552  $copywarnMsg = [ 'copyrightwarning2',
3553  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' ];
3554  }
3555  // Allow for site and per-namespace customization of contribution/copyright notice.
3556  Hooks::run( 'EditPageCopyrightWarning', [ $title, &$copywarnMsg ] );
3557 
3558  $msg = call_user_func_array( 'wfMessage', $copywarnMsg )->title( $title );
3559  if ( $langcode ) {
3560  $msg->inLanguage( $langcode );
3561  }
3562  return "<div id=\"editpage-copywarn\">\n" .
3563  $msg->$format() . "\n</div>";
3564  }
3565 
3573  public static function getPreviewLimitReport( $output ) {
3574  global $wgLang;
3575 
3576  if ( !$output || !$output->getLimitReportData() ) {
3577  return '';
3578  }
3579 
3580  $limitReport = Html::rawElement( 'div', [ 'class' => 'mw-limitReportExplanation' ],
3581  wfMessage( 'limitreport-title' )->parseAsBlock()
3582  );
3583 
3584  // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
3585  $limitReport .= Html::openElement( 'div', [ 'class' => 'preview-limit-report-wrapper' ] );
3586 
3587  $limitReport .= Html::openElement( 'table', [
3588  'class' => 'preview-limit-report wikitable'
3589  ] ) .
3590  Html::openElement( 'tbody' );
3591 
3592  foreach ( $output->getLimitReportData() as $key => $value ) {
3593  if ( Hooks::run( 'ParserLimitReportFormat',
3594  [ $key, &$value, &$limitReport, true, true ]
3595  ) ) {
3596  $keyMsg = wfMessage( $key );
3597  $valueMsg = wfMessage( [ "$key-value-html", "$key-value" ] );
3598  if ( !$valueMsg->exists() ) {
3599  $valueMsg = new RawMessage( '$1' );
3600  }
3601  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3602  $limitReport .= Html::openElement( 'tr' ) .
3603  Html::rawElement( 'th', null, $keyMsg->parse() ) .
3604  Html::rawElement( 'td', null,
3605  $wgLang->formatNum( $valueMsg->params( $value )->parse() )
3606  ) .
3607  Html::closeElement( 'tr' );
3608  }
3609  }
3610  }
3611 
3612  $limitReport .= Html::closeElement( 'tbody' ) .
3613  Html::closeElement( 'table' ) .
3614  Html::closeElement( 'div' );
3615 
3616  return $limitReport;
3617  }
3618 
3619  protected function showStandardInputs( &$tabindex = 2 ) {
3620  $out = $this->context->getOutput();
3621  $out->addHTML( "<div class='editOptions'>\n" );
3622 
3623  if ( $this->section != 'new' ) {
3624  $this->showSummaryInput( false, $this->summary );
3625  $out->addHTML( $this->getSummaryPreview( false, $this->summary ) );
3626  }
3627 
3628  $checkboxes = $this->getCheckboxesWidget(
3629  $tabindex,
3630  [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ]
3631  );
3632  $checkboxesHTML = new OOUI\HorizontalLayout( [ 'items' => $checkboxes ] );
3633 
3634  $out->addHTML( "<div class='editCheckboxes'>" . $checkboxesHTML . "</div>\n" );
3635 
3636  // Show copyright warning.
3637  $out->addWikiText( $this->getCopywarn() );
3638  $out->addHTML( $this->editFormTextAfterWarn );
3639 
3640  $out->addHTML( "<div class='editButtons'>\n" );
3641  $out->addHTML( implode( "\n", $this->getEditButtons( $tabindex ) ) . "\n" );
3642 
3643  $cancel = $this->getCancelLink();
3644 
3645  $message = $this->context->msg( 'edithelppage' )->inContentLanguage()->text();
3646  $edithelpurl = Skin::makeInternalOrExternalUrl( $message );
3647  $edithelp =
3649  $this->context->msg( 'edithelp' )->text(),
3650  [ 'target' => 'helpwindow', 'href' => $edithelpurl ],
3651  [ 'mw-ui-quiet' ]
3652  ) .
3653  $this->context->msg( 'word-separator' )->escaped() .
3654  $this->context->msg( 'newwindow' )->parse();
3655 
3656  $out->addHTML( " <span class='cancelLink'>{$cancel}</span>\n" );
3657  $out->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" );
3658  $out->addHTML( "</div><!-- editButtons -->\n" );
3659 
3660  Hooks::run( 'EditPage::showStandardInputs:options', [ $this, $out, &$tabindex ] );
3661 
3662  $out->addHTML( "</div><!-- editOptions -->\n" );
3663  }
3664 
3669  protected function showConflict() {
3670  $out = $this->context->getOutput();
3671  // Avoid PHP 7.1 warning of passing $this by reference
3672  $editPage = $this;
3673  if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$editPage, &$out ] ) ) {
3674  $this->incrementConflictStats();
3675 
3676  $this->getEditConflictHelper()->showEditFormTextAfterFooters();
3677  }
3678  }
3679 
3680  protected function incrementConflictStats() {
3681  $this->getEditConflictHelper()->incrementConflictStats();
3682  }
3683 
3687  public function getCancelLink() {
3688  $cancelParams = [];
3689  if ( !$this->isConflict && $this->oldid > 0 ) {
3690  $cancelParams['oldid'] = $this->oldid;
3691  } elseif ( $this->getContextTitle()->isRedirect() ) {
3692  $cancelParams['redirect'] = 'no';
3693  }
3694 
3695  return new OOUI\ButtonWidget( [
3696  'id' => 'mw-editform-cancel',
3697  'href' => $this->getContextTitle()->getLinkURL( $cancelParams ),
3698  'label' => new OOUI\HtmlSnippet( $this->context->msg( 'cancel' )->parse() ),
3699  'framed' => false,
3700  'infusable' => true,
3701  'flags' => 'destructive',
3702  ] );
3703  }
3704 
3714  protected function getActionURL( Title $title ) {
3715  return $title->getLocalURL( [ 'action' => $this->action ] );
3716  }
3717 
3725  protected function wasDeletedSinceLastEdit() {
3726  if ( $this->deletedSinceEdit !== null ) {
3727  return $this->deletedSinceEdit;
3728  }
3729 
3730  $this->deletedSinceEdit = false;
3731 
3732  if ( !$this->mTitle->exists() && $this->mTitle->isDeletedQuick() ) {
3733  $this->lastDelete = $this->getLastDelete();
3734  if ( $this->lastDelete ) {
3735  $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
3736  if ( $deleteTime > $this->starttime ) {
3737  $this->deletedSinceEdit = true;
3738  }
3739  }
3740  }
3741 
3742  return $this->deletedSinceEdit;
3743  }
3744 
3748  protected function getLastDelete() {
3749  $dbr = wfGetDB( DB_REPLICA );
3750  $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
3751  $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
3752  $data = $dbr->selectRow(
3753  array_merge( [ 'logging' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ] ),
3754  [
3755  'log_type',
3756  'log_action',
3757  'log_timestamp',
3758  'log_namespace',
3759  'log_title',
3760  'log_params',
3761  'log_deleted',
3762  'user_name'
3763  ] + $commentQuery['fields'] + $actorQuery['fields'],
3764  [
3765  'log_namespace' => $this->mTitle->getNamespace(),
3766  'log_title' => $this->mTitle->getDBkey(),
3767  'log_type' => 'delete',
3768  'log_action' => 'delete',
3769  ],
3770  __METHOD__,
3771  [ 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ],
3772  [
3773  'user' => [ 'JOIN', 'user_id=' . $actorQuery['fields']['log_user'] ],
3774  ] + $commentQuery['joins'] + $actorQuery['joins']
3775  );
3776  // Quick paranoid permission checks...
3777  if ( is_object( $data ) ) {
3778  if ( $data->log_deleted & LogPage::DELETED_USER ) {
3779  $data->user_name = $this->context->msg( 'rev-deleted-user' )->escaped();
3780  }
3781 
3782  if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
3783  $data->log_comment_text = $this->context->msg( 'rev-deleted-comment' )->escaped();
3784  $data->log_comment_data = null;
3785  }
3786  }
3787 
3788  return $data;
3789  }
3790 
3796  public function getPreviewText() {
3797  $out = $this->context->getOutput();
3798  $config = $this->context->getConfig();
3799 
3800  if ( $config->get( 'RawHtml' ) && !$this->mTokenOk ) {
3801  // Could be an offsite preview attempt. This is very unsafe if
3802  // HTML is enabled, as it could be an attack.
3803  $parsedNote = '';
3804  if ( $this->textbox1 !== '' ) {
3805  // Do not put big scary notice, if previewing the empty
3806  // string, which happens when you initially edit
3807  // a category page, due to automatic preview-on-open.
3808  $parsedNote = $out->parse( "<div class='previewnote'>" .
3809  $this->context->msg( 'session_fail_preview_html' )->text() . "</div>",
3810  true, /* interface */true );
3811  }
3812  $this->incrementEditFailureStats( 'session_loss' );
3813  return $parsedNote;
3814  }
3815 
3816  $note = '';
3817 
3818  try {
3819  $content = $this->toEditContent( $this->textbox1 );
3820 
3821  $previewHTML = '';
3822  if ( !Hooks::run(
3823  'AlternateEditPreview',
3824  [ $this, &$content, &$previewHTML, &$this->mParserOutput ] )
3825  ) {
3826  return $previewHTML;
3827  }
3828 
3829  # provide a anchor link to the editform
3830  $continueEditing = '<span class="mw-continue-editing">' .
3831  '[[#' . self::EDITFORM_ID . '|' .
3832  $this->context->getLanguage()->getArrow() . ' ' .
3833  $this->context->msg( 'continue-editing' )->text() . ']]</span>';
3834  if ( $this->mTriedSave && !$this->mTokenOk ) {
3835  if ( $this->mTokenOkExceptSuffix ) {
3836  $note = $this->context->msg( 'token_suffix_mismatch' )->plain();
3837  $this->incrementEditFailureStats( 'bad_token' );
3838  } else {
3839  $note = $this->context->msg( 'session_fail_preview' )->plain();
3840  $this->incrementEditFailureStats( 'session_loss' );
3841  }
3842  } elseif ( $this->incompleteForm ) {
3843  $note = $this->context->msg( 'edit_form_incomplete' )->plain();
3844  if ( $this->mTriedSave ) {
3845  $this->incrementEditFailureStats( 'incomplete_form' );
3846  }
3847  } else {
3848  $note = $this->context->msg( 'previewnote' )->plain() . ' ' . $continueEditing;
3849  }
3850 
3851  # don't parse non-wikitext pages, show message about preview
3852  if ( $this->mTitle->isUserConfigPage() || $this->mTitle->isSiteConfigPage() ) {
3853  if ( $this->mTitle->isUserConfigPage() ) {
3854  $level = 'user';
3855  } elseif ( $this->mTitle->isSiteConfigPage() ) {
3856  $level = 'site';
3857  } else {
3858  $level = false;
3859  }
3860 
3861  if ( $content->getModel() == CONTENT_MODEL_CSS ) {
3862  $format = 'css';
3863  if ( $level === 'user' && !$config->get( 'AllowUserCss' ) ) {
3864  $format = false;
3865  }
3866  } elseif ( $content->getModel() == CONTENT_MODEL_JSON ) {
3867  $format = 'json';
3868  if ( $level === 'user' /* No comparable 'AllowUserJson' */ ) {
3869  $format = false;
3870  }
3871  } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
3872  $format = 'js';
3873  if ( $level === 'user' && !$config->get( 'AllowUserJs' ) ) {
3874  $format = false;
3875  }
3876  } else {
3877  $format = false;
3878  }
3879 
3880  # Used messages to make sure grep find them:
3881  # Messages: usercsspreview, userjsonpreview, userjspreview,
3882  # sitecsspreview, sitejsonpreview, sitejspreview
3883  if ( $level && $format ) {
3884  $note = "<div id='mw-{$level}{$format}preview'>" .
3885  $this->context->msg( "{$level}{$format}preview" )->text() .
3886  ' ' . $continueEditing . "</div>";
3887  }
3888  }
3889 
3890  # If we're adding a comment, we need to show the
3891  # summary as the headline
3892  if ( $this->section === "new" && $this->summary !== "" ) {
3893  $content = $content->addSectionHeader( $this->summary );
3894  }
3895 
3896  $hook_args = [ $this, &$content ];
3897  Hooks::run( 'EditPageGetPreviewContent', $hook_args );
3898 
3899  $parserResult = $this->doPreviewParse( $content );
3900  $parserOutput = $parserResult['parserOutput'];
3901  $previewHTML = $parserResult['html'];
3902  $this->mParserOutput = $parserOutput;
3903  $out->addParserOutputMetadata( $parserOutput );
3904 
3905  if ( count( $parserOutput->getWarnings() ) ) {
3906  $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
3907  }
3908 
3909  } catch ( MWContentSerializationException $ex ) {
3910  $m = $this->context->msg(
3911  'content-failed-to-parse',
3912  $this->contentModel,
3913  $this->contentFormat,
3914  $ex->getMessage()
3915  );
3916  $note .= "\n\n" . $m->parse();
3917  $previewHTML = '';
3918  }
3919 
3920  if ( $this->isConflict ) {
3921  $conflict = '<h2 id="mw-previewconflict">'
3922  . $this->context->msg( 'previewconflict' )->escaped() . "</h2>\n";
3923  } else {
3924  $conflict = '<hr />';
3925  }
3926 
3927  $previewhead = "<div class='previewnote'>\n" .
3928  '<h2 id="mw-previewheader">' . $this->context->msg( 'preview' )->escaped() . "</h2>" .
3929  $out->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
3930 
3931  $pageViewLang = $this->mTitle->getPageViewLanguage();
3932  $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3933  'class' => 'mw-content-' . $pageViewLang->getDir() ];
3934  $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
3935 
3936  return $previewhead . $previewHTML . $this->previewTextAfterContent;
3937  }
3938 
3939  private function incrementEditFailureStats( $failureType ) {
3940  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
3941  $stats->increment( 'edit.failures.' . $failureType );
3942  }
3943 
3948  protected function getPreviewParserOptions() {
3949  $parserOptions = $this->page->makeParserOptions( $this->context );
3950  $parserOptions->setIsPreview( true );
3951  $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
3952  $parserOptions->enableLimitReport();
3953  return $parserOptions;
3954  }
3955 
3965  protected function doPreviewParse( Content $content ) {
3966  $user = $this->context->getUser();
3967  $parserOptions = $this->getPreviewParserOptions();
3968  $pstContent = $content->preSaveTransform( $this->mTitle, $user, $parserOptions );
3969  $scopedCallback = $parserOptions->setupFakeRevision(
3970  $this->mTitle, $pstContent, $user );
3971  $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions );
3972  ScopedCallback::consume( $scopedCallback );
3973  return [
3974  'parserOutput' => $parserOutput,
3975  'html' => $parserOutput->getText( [
3976  'enableSectionEditLinks' => false
3977  ] )
3978  ];
3979  }
3980 
3984  public function getTemplates() {
3985  if ( $this->preview || $this->section != '' ) {
3986  $templates = [];
3987  if ( !isset( $this->mParserOutput ) ) {
3988  return $templates;
3989  }
3990  foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
3991  foreach ( array_keys( $template ) as $dbk ) {
3992  $templates[] = Title::makeTitle( $ns, $dbk );
3993  }
3994  }
3995  return $templates;
3996  } else {
3997  return $this->mTitle->getTemplateLinksFrom();
3998  }
3999  }
4000 
4008  public static function getEditToolbar( $title = null ) {
4011 
4012  $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
4013  $showSignature = true;
4014  if ( $title ) {
4015  $showSignature = MWNamespace::wantSignatures( $title->getNamespace() );
4016  }
4017 
4027  $toolarray = [
4028  [
4029  'id' => 'mw-editbutton-bold',
4030  'open' => '\'\'\'',
4031  'close' => '\'\'\'',
4032  'sample' => wfMessage( 'bold_sample' )->text(),
4033  'tip' => wfMessage( 'bold_tip' )->text(),
4034  ],
4035  [
4036  'id' => 'mw-editbutton-italic',
4037  'open' => '\'\'',
4038  'close' => '\'\'',
4039  'sample' => wfMessage( 'italic_sample' )->text(),
4040  'tip' => wfMessage( 'italic_tip' )->text(),
4041  ],
4042  [
4043  'id' => 'mw-editbutton-link',
4044  'open' => '[[',
4045  'close' => ']]',
4046  'sample' => wfMessage( 'link_sample' )->text(),
4047  'tip' => wfMessage( 'link_tip' )->text(),
4048  ],
4049  [
4050  'id' => 'mw-editbutton-extlink',
4051  'open' => '[',
4052  'close' => ']',
4053  'sample' => wfMessage( 'extlink_sample' )->text(),
4054  'tip' => wfMessage( 'extlink_tip' )->text(),
4055  ],
4056  [
4057  'id' => 'mw-editbutton-headline',
4058  'open' => "\n== ",
4059  'close' => " ==\n",
4060  'sample' => wfMessage( 'headline_sample' )->text(),
4061  'tip' => wfMessage( 'headline_tip' )->text(),
4062  ],
4063  $imagesAvailable ? [
4064  'id' => 'mw-editbutton-image',
4065  'open' => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
4066  'close' => ']]',
4067  'sample' => wfMessage( 'image_sample' )->text(),
4068  'tip' => wfMessage( 'image_tip' )->text(),
4069  ] : false,
4070  $imagesAvailable ? [
4071  'id' => 'mw-editbutton-media',
4072  'open' => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
4073  'close' => ']]',
4074  'sample' => wfMessage( 'media_sample' )->text(),
4075  'tip' => wfMessage( 'media_tip' )->text(),
4076  ] : false,
4077  [
4078  'id' => 'mw-editbutton-nowiki',
4079  'open' => "<nowiki>",
4080  'close' => "</nowiki>",
4081  'sample' => wfMessage( 'nowiki_sample' )->text(),
4082  'tip' => wfMessage( 'nowiki_tip' )->text(),
4083  ],
4084  $showSignature ? [
4085  'id' => 'mw-editbutton-signature',
4086  'open' => wfMessage( 'sig-text', '~~~~' )->inContentLanguage()->text(),
4087  'close' => '',
4088  'sample' => '',
4089  'tip' => wfMessage( 'sig_tip' )->text(),
4090  ] : false,
4091  [
4092  'id' => 'mw-editbutton-hr',
4093  'open' => "\n----\n",
4094  'close' => '',
4095  'sample' => '',
4096  'tip' => wfMessage( 'hr_tip' )->text(),
4097  ]
4098  ];
4099 
4100  $script = 'mw.loader.using("mediawiki.toolbar", function () {';
4101  foreach ( $toolarray as $tool ) {
4102  if ( !$tool ) {
4103  continue;
4104  }
4105 
4106  $params = [
4107  // Images are defined in ResourceLoaderEditToolbarModule
4108  false,
4109  // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
4110  // Older browsers show a "speedtip" type message only for ALT.
4111  // Ideally these should be different, realistically they
4112  // probably don't need to be.
4113  $tool['tip'],
4114  $tool['open'],
4115  $tool['close'],
4116  $tool['sample'],
4117  $tool['id'],
4118  ];
4119 
4120  $script .= Xml::encodeJsCall(
4121  'mw.toolbar.addButton',
4122  $params,
4124  );
4125  }
4126 
4127  $script .= '});';
4128 
4129  $toolbar = '<div id="toolbar"></div>';
4130 
4131  if ( Hooks::run( 'EditPageBeforeEditToolbar', [ &$toolbar ] ) ) {
4132  // Only add the old toolbar cruft to the page payload if the toolbar has not
4133  // been over-written by a hook caller
4134  $wgOut->addScript( ResourceLoader::makeInlineScript( $script ) );
4135  };
4136 
4137  return $toolbar;
4138  }
4139 
4158  public function getCheckboxesDefinition( $checked ) {
4159  $checkboxes = [];
4160 
4161  $user = $this->context->getUser();
4162  // don't show the minor edit checkbox if it's a new page or section
4163  if ( !$this->isNew && $user->isAllowed( 'minoredit' ) ) {
4164  $checkboxes['wpMinoredit'] = [
4165  'id' => 'wpMinoredit',
4166  'label-message' => 'minoredit',
4167  // Uses messages: tooltip-minoredit, accesskey-minoredit
4168  'tooltip' => 'minoredit',
4169  'label-id' => 'mw-editpage-minoredit',
4170  'legacy-name' => 'minor',
4171  'default' => $checked['minor'],
4172  ];
4173  }
4174 
4175  if ( $user->isLoggedIn() ) {
4176  $checkboxes['wpWatchthis'] = [
4177  'id' => 'wpWatchthis',
4178  'label-message' => 'watchthis',
4179  // Uses messages: tooltip-watch, accesskey-watch
4180  'tooltip' => 'watch',
4181  'label-id' => 'mw-editpage-watch',
4182  'legacy-name' => 'watch',
4183  'default' => $checked['watch'],
4184  ];
4185  }
4186 
4187  $editPage = $this;
4188  Hooks::run( 'EditPageGetCheckboxesDefinition', [ $editPage, &$checkboxes ] );
4189 
4190  return $checkboxes;
4191  }
4192 
4203  public function getCheckboxesWidget( &$tabindex, $checked ) {
4204  $checkboxes = [];
4205  $checkboxesDef = $this->getCheckboxesDefinition( $checked );
4206 
4207  foreach ( $checkboxesDef as $name => $options ) {
4208  $legacyName = isset( $options['legacy-name'] ) ? $options['legacy-name'] : $name;
4209 
4210  $title = null;
4211  $accesskey = null;
4212  if ( isset( $options['tooltip'] ) ) {
4213  $accesskey = $this->context->msg( "accesskey-{$options['tooltip']}" )->text();
4214  $title = Linker::titleAttrib( $options['tooltip'] );
4215  }
4216  if ( isset( $options['title-message'] ) ) {
4217  $title = $this->context->msg( $options['title-message'] )->text();
4218  }
4219 
4220  $checkboxes[ $legacyName ] = new OOUI\FieldLayout(
4221  new OOUI\CheckboxInputWidget( [
4222  'tabIndex' => ++$tabindex,
4223  'accessKey' => $accesskey,
4224  'id' => $options['id'] . 'Widget',
4225  'inputId' => $options['id'],
4226  'name' => $name,
4227  'selected' => $options['default'],
4228  'infusable' => true,
4229  ] ),
4230  [
4231  'align' => 'inline',
4232  'label' => new OOUI\HtmlSnippet( $this->context->msg( $options['label-message'] )->parse() ),
4233  'title' => $title,
4234  'id' => isset( $options['label-id'] ) ? $options['label-id'] : null,
4235  ]
4236  );
4237  }
4238 
4239  // Backwards-compatibility hack to run the EditPageBeforeEditChecks hook. It's important,
4240  // people have used it for the weirdest things completely unrelated to checkboxes...
4241  // And if we're gonna run it, might as well allow its legacy checkboxes to be shown.
4242  $legacyCheckboxes = [];
4243  if ( !$this->isNew ) {
4244  $legacyCheckboxes['minor'] = '';
4245  }
4246  $legacyCheckboxes['watch'] = '';
4247  // Copy new-style checkboxes into an old-style structure
4248  foreach ( $checkboxes as $name => $oouiLayout ) {
4249  $legacyCheckboxes[$name] = (string)$oouiLayout;
4250  }
4251  // Avoid PHP 7.1 warning of passing $this by reference
4252  $ep = $this;
4253  Hooks::run( 'EditPageBeforeEditChecks', [ &$ep, &$legacyCheckboxes, &$tabindex ], '1.29' );
4254  // Copy back any additional old-style checkboxes into the new-style structure
4255  foreach ( $legacyCheckboxes as $name => $html ) {
4256  if ( $html && !isset( $checkboxes[$name] ) ) {
4257  $checkboxes[$name] = new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $html ) ] );
4258  }
4259  }
4260 
4261  return $checkboxes;
4262  }
4263 
4270  protected function getSubmitButtonLabel() {
4271  $labelAsPublish =
4272  $this->context->getConfig()->get( 'EditSubmitButtonLabelPublish' );
4273 
4274  // Can't use $this->isNew as that's also true if we're adding a new section to an extant page
4275  $newPage = !$this->mTitle->exists();
4276 
4277  if ( $labelAsPublish ) {
4278  $buttonLabelKey = $newPage ? 'publishpage' : 'publishchanges';
4279  } else {
4280  $buttonLabelKey = $newPage ? 'savearticle' : 'savechanges';
4281  }
4282 
4283  return $buttonLabelKey;
4284  }
4285 
4294  public function getEditButtons( &$tabindex ) {
4295  $buttons = [];
4296 
4297  $labelAsPublish =
4298  $this->context->getConfig()->get( 'EditSubmitButtonLabelPublish' );
4299 
4300  $buttonLabel = $this->context->msg( $this->getSubmitButtonLabel() )->text();
4301  $buttonTooltip = $labelAsPublish ? 'publish' : 'save';
4302 
4303  $buttons['save'] = new OOUI\ButtonInputWidget( [
4304  'name' => 'wpSave',
4305  'tabIndex' => ++$tabindex,
4306  'id' => 'wpSaveWidget',
4307  'inputId' => 'wpSave',
4308  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4309  'useInputTag' => true,
4310  'flags' => [ 'progressive', 'primary' ],
4311  'label' => $buttonLabel,
4312  'infusable' => true,
4313  'type' => 'submit',
4314  // Messages used: tooltip-save, tooltip-publish
4315  'title' => Linker::titleAttrib( $buttonTooltip ),
4316  // Messages used: accesskey-save, accesskey-publish
4317  'accessKey' => Linker::accesskey( $buttonTooltip ),
4318  ] );
4319 
4320  $buttons['preview'] = new OOUI\ButtonInputWidget( [
4321  'name' => 'wpPreview',
4322  'tabIndex' => ++$tabindex,
4323  'id' => 'wpPreviewWidget',
4324  'inputId' => 'wpPreview',
4325  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4326  'useInputTag' => true,
4327  'label' => $this->context->msg( 'showpreview' )->text(),
4328  'infusable' => true,
4329  'type' => 'submit',
4330  // Message used: tooltip-preview
4331  'title' => Linker::titleAttrib( 'preview' ),
4332  // Message used: accesskey-preview
4333  'accessKey' => Linker::accesskey( 'preview' ),
4334  ] );
4335 
4336  $buttons['diff'] = new OOUI\ButtonInputWidget( [
4337  'name' => 'wpDiff',
4338  'tabIndex' => ++$tabindex,
4339  'id' => 'wpDiffWidget',
4340  'inputId' => 'wpDiff',
4341  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4342  'useInputTag' => true,
4343  'label' => $this->context->msg( 'showdiff' )->text(),
4344  'infusable' => true,
4345  'type' => 'submit',
4346  // Message used: tooltip-diff
4347  'title' => Linker::titleAttrib( 'diff' ),
4348  // Message used: accesskey-diff
4349  'accessKey' => Linker::accesskey( 'diff' ),
4350  ] );
4351 
4352  // Avoid PHP 7.1 warning of passing $this by reference
4353  $editPage = $this;
4354  Hooks::run( 'EditPageBeforeEditButtons', [ &$editPage, &$buttons, &$tabindex ] );
4355 
4356  return $buttons;
4357  }
4358 
4363  public function noSuchSectionPage() {
4364  $out = $this->context->getOutput();
4365  $out->prepareErrorPage( $this->context->msg( 'nosuchsectiontitle' ) );
4366 
4367  $res = $this->context->msg( 'nosuchsectiontext', $this->section )->parseAsBlock();
4368 
4369  // Avoid PHP 7.1 warning of passing $this by reference
4370  $editPage = $this;
4371  Hooks::run( 'EditPageNoSuchSection', [ &$editPage, &$res ] );
4372  $out->addHTML( $res );
4373 
4374  $out->returnToMain( false, $this->mTitle );
4375  }
4376 
4382  public function spamPageWithContent( $match = false ) {
4383  $this->textbox2 = $this->textbox1;
4384 
4385  if ( is_array( $match ) ) {
4386  $match = $this->context->getLanguage()->listToText( $match );
4387  }
4388  $out = $this->context->getOutput();
4389  $out->prepareErrorPage( $this->context->msg( 'spamprotectiontitle' ) );
4390 
4391  $out->addHTML( '<div id="spamprotected">' );
4392  $out->addWikiMsg( 'spamprotectiontext' );
4393  if ( $match ) {
4394  $out->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
4395  }
4396  $out->addHTML( '</div>' );
4397 
4398  $out->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
4399  $this->showDiff();
4400 
4401  $out->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
4402  $this->showTextbox2();
4403 
4404  $out->addReturnTo( $this->getContextTitle(), [ 'action' => 'edit' ] );
4405  }
4406 
4417  protected function safeUnicodeInput( $request, $field ) {
4418  return rtrim( $request->getText( $field ) );
4419  }
4420 
4430  protected function safeUnicodeOutput( $text ) {
4431  return $text;
4432  }
4433 
4437  protected function addEditNotices() {
4438  $out = $this->context->getOutput();
4439  $editNotices = $this->mTitle->getEditNotices( $this->oldid );
4440  if ( count( $editNotices ) ) {
4441  $out->addHTML( implode( "\n", $editNotices ) );
4442  } else {
4443  $msg = $this->context->msg( 'editnotice-notext' );
4444  if ( !$msg->isDisabled() ) {
4445  $out->addHTML(
4446  '<div class="mw-editnotice-notext">'
4447  . $msg->parseAsBlock()
4448  . '</div>'
4449  );
4450  }
4451  }
4452  }
4453 
4457  protected function addTalkPageText() {
4458  if ( $this->mTitle->isTalkPage() ) {
4459  $this->context->getOutput()->addWikiMsg( 'talkpagetext' );
4460  }
4461  }
4462 
4466  protected function addLongPageWarningHeader() {
4467  if ( $this->contentLength === false ) {
4468  $this->contentLength = strlen( $this->textbox1 );
4469  }
4470 
4471  $out = $this->context->getOutput();
4472  $lang = $this->context->getLanguage();
4473  $maxArticleSize = $this->context->getConfig()->get( 'MaxArticleSize' );
4474  if ( $this->tooBig || $this->contentLength > $maxArticleSize * 1024 ) {
4475  $out->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
4476  [
4477  'longpageerror',
4478  $lang->formatNum( round( $this->contentLength / 1024, 3 ) ),
4479  $lang->formatNum( $maxArticleSize )
4480  ]
4481  );
4482  } else {
4483  if ( !$this->context->msg( 'longpage-hint' )->isDisabled() ) {
4484  $out->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
4485  [
4486  'longpage-hint',
4487  $lang->formatSize( strlen( $this->textbox1 ) ),
4488  strlen( $this->textbox1 )
4489  ]
4490  );
4491  }
4492  }
4493  }
4494 
4498  protected function addPageProtectionWarningHeaders() {
4499  $out = $this->context->getOutput();
4500  if ( $this->mTitle->isProtected( 'edit' ) &&
4501  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [ '' ]
4502  ) {
4503  # Is the title semi-protected?
4504  if ( $this->mTitle->isSemiProtected() ) {
4505  $noticeMsg = 'semiprotectedpagewarning';
4506  } else {
4507  # Then it must be protected based on static groups (regular)
4508  $noticeMsg = 'protectedpagewarning';
4509  }
4510  LogEventsList::showLogExtract( $out, 'protect', $this->mTitle, '',
4511  [ 'lim' => 1, 'msgKey' => [ $noticeMsg ] ] );
4512  }
4513  if ( $this->mTitle->isCascadeProtected() ) {
4514  # Is this page under cascading protection from some source pages?
4515 
4516  list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
4517  $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
4518  $cascadeSourcesCount = count( $cascadeSources );
4519  if ( $cascadeSourcesCount > 0 ) {
4520  # Explain, and list the titles responsible
4521  foreach ( $cascadeSources as $page ) {
4522  $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
4523  }
4524  }
4525  $notice .= '</div>';
4526  $out->wrapWikiMsg( $notice, [ 'cascadeprotectedwarning', $cascadeSourcesCount ] );
4527  }
4528  if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
4529  LogEventsList::showLogExtract( $out, 'protect', $this->mTitle, '',
4530  [ 'lim' => 1,
4531  'showIfEmpty' => false,
4532  'msgKey' => [ 'titleprotectedwarning' ],
4533  'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ] );
4534  }
4535  }
4536 
4541  protected function addExplainConflictHeader( OutputPage $out ) {
4542  $out->addHTML(
4543  $this->getEditConflictHelper()->getExplainHeader()
4544  );
4545  }
4546 
4555  return ( new TextboxBuilder() )->buildTextboxAttribs(
4556  $name, $customAttribs, $user, $this->mTitle
4557  );
4558  }
4559 
4565  protected function addNewLineAtEnd( $wikitext ) {
4566  return ( new TextboxBuilder() )->addNewLineAtEnd( $wikitext );
4567  }
4568 
4579  private function guessSectionName( $text ) {
4580  global $wgParser;
4581 
4582  // Detect Microsoft browsers
4583  $userAgent = $this->context->getRequest()->getHeader( 'User-Agent' );
4584  if ( $userAgent && preg_match( '/MSIE|Edge/', $userAgent ) ) {
4585  // ...and redirect them to legacy encoding, if available
4586  return $wgParser->guessLegacySectionNameFromWikiText( $text );
4587  }
4588  // Meanwhile, real browsers get real anchors
4589  $name = $wgParser->guessSectionNameFromWikiText( $text );
4590  // With one little caveat: per T216029, fragments in HTTP redirects need to be urlencoded,
4591  // otherwise Chrome double-escapes the rest of the URL.
4592  return '#' . urlencode( mb_substr( $name, 1 ) );
4593  }
4594 
4601  public function setEditConflictHelperFactory( callable $factory ) {
4602  $this->editConflictHelperFactory = $factory;
4603  $this->editConflictHelper = null;
4604  }
4605 
4609  private function getEditConflictHelper() {
4610  if ( !$this->editConflictHelper ) {
4611  $this->editConflictHelper = call_user_func(
4612  $this->editConflictHelperFactory,
4613  $this->getSubmitButtonLabel()
4614  );
4615  }
4616 
4618  }
4619 
4624  private function newTextConflictHelper( $submitButtonLabel ) {
4625  return new TextConflictHelper(
4626  $this->getTitle(),
4627  $this->getContext()->getOutput(),
4628  MediaWikiServices::getInstance()->getStatsdDataFactory(),
4629  $submitButtonLabel
4630  );
4631  }
4632 }
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:442
EditPage\$editFormTextBeforeContent
$editFormTextBeforeContent
Definition: EditPage.php:387
$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:247
EditPage\$mTriedSave
bool $mTriedSave
Definition: EditPage.php:263
CONTENT_MODEL_JSON
const CONTENT_MODEL_JSON
Definition: Defines.php:249
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:5
ContentHandler\getForModelID
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
Definition: ContentHandler.php:293
$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:903
diff
also included in $newHeader if any indicating whether we should show just the diff
Definition: hooks.txt:1259
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:168
EditPage\$contentModel
string $contentModel
Definition: EditPage.php:373
EditPage\showFormBeforeText
showFormBeforeText()
Definition: EditPage.php:3263
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:273
EditPage\internalAttemptSave
internalAttemptSave(&$result, $bot=false)
Attempt submission (no UI)
Definition: EditPage.php:1782
EditPage\$lastDelete
bool stdClass $lastDelete
Definition: EditPage.php:254
EditPage\tokenOk
tokenOk(&$request)
Make sure the form isn't faking a user's credentials.
Definition: EditPage.php:1460
EditPage\$editFormPageTop
string $editFormPageTop
Before even the preview.
Definition: EditPage.php:385
EditPage\AS_BLANK_ARTICLE
const AS_BLANK_ARTICLE
Status: user tried to create a blank page and wpIgnoreBlankArticle == false.
Definition: EditPage.php:115
EditPage\showContentForm
showContentForm()
Subpage overridable method for printing the form for page content editing By default this simply outp...
Definition: EditPage.php:3300
EditPage\$mTitle
Title $mTitle
Definition: EditPage.php:230
Html\textarea
static textarea( $name, $value='', array $attribs=[])
Convenience function to produce a <textarea> element.
Definition: Html.php:790
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:4382
EditPage\$section
string $section
Definition: EditPage.php:349
ParserOutput
Definition: ParserOutput.php:25
WikiPage\getRedirectTarget
getRedirectTarget()
If this page is a redirect, get its target.
Definition: WikiPage.php:887
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
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:114
EditPage\$scrolltop
null $scrolltop
Definition: EditPage.php:367
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:2305
EditPage\displayPermissionsError
displayPermissionsError(array $permErrors)
Display a permissions error page, like OutputPage::showPermissionsErrorPage(), but with the following...
Definition: EditPage.php:725
$wgParser
$wgParser
Definition: Setup.php:917
$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:2013
EditPage\$editFormTextAfterContent
$editFormTextAfterContent
Definition: EditPage.php:391
EditPage\displayPreviewArea
displayPreviewArea( $previewOutput, $isOnTop=false)
Definition: EditPage.php:3358
EditPage\$blankArticle
bool $blankArticle
Definition: EditPage.php:281
EditPage\$allowBlankSummary
bool $allowBlankSummary
Definition: EditPage.php:278
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:7115
EditPage\$editFormTextBottom
$editFormTextBottom
Definition: EditPage.php:390
EditPage\getSummaryInputAttributes
getSummaryInputAttributes(array $inputAttrs=null)
Helper function for summary input functions, which returns the neccessary attributes for the input.
Definition: EditPage.php:3155
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\$editFormTextTop
$editFormTextTop
Definition: EditPage.php:386
$response
this hook is for auditing only $response
Definition: hooks.txt:783
array
the array() calling protocol came about after MediaWiki 1.4rc1.
EditPage\$editintro
string $editintro
Definition: EditPage.php:364
MediaWiki\EditPage\TextConflictHelper\getEditConflictMainTextBox
getEditConflictMainTextBox( $customAttribs=[])
HTML to build the textbox1 on edit conflicts.
Definition: TextConflictHelper.php:171
$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:2255
EditPage\showTextbox2
showTextbox2()
Definition: EditPage.php:3340
EDIT_FORCE_BOT
const EDIT_FORCE_BOT
Definition: Defines.php:166
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
EditPage\$summary
string $summary
Definition: EditPage.php:337
EditPage\AS_READ_ONLY_PAGE_ANON
const AS_READ_ONLY_PAGE_ANON
Status: this anonymous user is not allowed to edit this page.
Definition: EditPage.php:83
EditPage\buildTextboxAttribs
buildTextboxAttribs( $name, array $customAttribs, User $user)
Definition: EditPage.php:4554
EditPage\$textbox2
string $textbox2
Definition: EditPage.php:334
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:1397
EditPage\$mTokenOk
bool $mTokenOk
Definition: EditPage.php:257
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:181
CONTENT_MODEL_CSS
const CONTENT_MODEL_CSS
Definition: Defines.php:247
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1980
EditPage\$oldid
int $oldid
Definition: EditPage.php:358
MediaWiki\EditPage\TextboxBuilder
Helps EditPage build textboxes.
Definition: TextboxBuilder.php:37
EditPage\getContextTitle
getContextTitle()
Get the context title object.
Definition: EditPage.php:494
$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:831
EditPage\showTosSummary
showTosSummary()
Give a chance for site and per-namespace customizations of terms of service summary link that might e...
Definition: EditPage.php:3506
EditPage\AS_UNICODE_NOT_SUPPORTED
const AS_UNICODE_NOT_SUPPORTED
Status: edit rejected because browser doesn't support Unicode.
Definition: EditPage.php:190
CategoryPage
Special handling for category description pages, showing pages, subcategories and file that belong to...
Definition: CategoryPage.php:28
EditPage\$page
WikiPage $page
Definition: EditPage.php:224
EditPage\$save
bool $save
Definition: EditPage.php:313
EditPage\addPageProtectionWarningHeaders
addPageProtectionWarningHeaders()
Definition: EditPage.php:4498
EditPage\setContextTitle
setContextTitle( $title)
Set the context Title object.
Definition: EditPage.php:483
$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:864
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:37
$customAttribs
null means default & $customAttribs
Definition: hooks.txt:1995
EditPage\edit
edit()
This is the function that gets called for "action=edit".
Definition: EditPage.php:558
EditPage\$autoSumm
string $autoSumm
Definition: EditPage.php:293
NS_FILE
const NS_FILE
Definition: Defines.php:80
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:4417
$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:1173
EditPage\getLastDelete
getLastDelete()
Definition: EditPage.php:3748
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1262
EditPage\incrementConflictStats
incrementConflictStats()
Definition: EditPage.php:3680
EditPage\addEditNotices
addEditNotices()
Definition: EditPage.php:4437
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:591
EditPage\$editFormTextAfterTools
$editFormTextAfterTools
Definition: EditPage.php:389
SpecialPage\getTitleFor
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Definition: SpecialPage.php:82
EditPage\AS_HOOK_ERROR
const AS_HOOK_ERROR
Status: Article update aborted by a hook function.
Definition: EditPage.php:63
page
target page
Definition: All_system_messages.txt:1267
EditPage\$editFormTextAfterWarn
$editFormTextAfterWarn
Definition: EditPage.php:388
ContentHandler\getForTitle
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
Definition: ContentHandler.php:240
$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:1382
EditPage\AS_CONTENT_TOO_BIG
const AS_CONTENT_TOO_BIG
Status: Content too big (> $wgMaxArticleSize)
Definition: EditPage.php:78
$wgTitle
if(! $wgRequest->checkUrlExtension()) if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') 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
MediaWiki\EditPage\TextConflictHelper\setContentFormat
setContentFormat( $contentFormat)
Definition: TextConflictHelper.php:118
EditPage\showHeaderCopyrightWarning
showHeaderCopyrightWarning()
Show the header copyright warning.
Definition: EditPage.php:3490
EditPage\getEditPermissionErrors
getEditPermissionErrors( $rigor='secure')
Definition: EditPage.php:681
$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. 'ImgAuthModifyHeaders':Executed just before a file is streamed to a user via img_auth.php, allowing headers to be modified beforehand. $title:LinkTarget object & $headers:HTTP headers(name=> value, names are case insensitive). Two headers get special handling:If-Modified-Since(value must be a valid HTTP date) and Range(must be of the form "bytes=(\d*-\d*)") will be honored when streaming the file. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! 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:1993
EditPage\addLongPageWarningHeader
addLongPageWarningHeader()
Definition: EditPage.php:4466
EditPage\$context
IContextSource $context
Definition: EditPage.php:415
EditPage\$didSave
$didSave
Definition: EditPage.php:396
EditPage\AS_BLOCKED_PAGE_FOR_USER
const AS_BLOCKED_PAGE_FOR_USER
Status: User is blocked from editing this page.
Definition: EditPage.php:73
EditPage\AS_SUMMARY_NEEDED
const AS_SUMMARY_NEEDED
Status: no edit summary given and the user has forceeditsummary set and the user is not editing in hi...
Definition: EditPage.php:126
$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:1432
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:110
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1087
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:89
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:2006
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:37
Linker\formatHiddenCategories
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition: Linker.php:1917
EditPage\$mArticle
Article $mArticle
Definition: EditPage.php:222
EditPage\$contentFormat
null string $contentFormat
Definition: EditPage.php:376
Skin\getSkinNames
static getSkinNames()
Fetch the set of available skins.
Definition: Skin.php:55
EditPage\POST_EDIT_COOKIE_DURATION
const POST_EDIT_COOKIE_DURATION
Duration of PostEdit cookie, in seconds.
Definition: EditPage.php:216
$dbr
$dbr
Definition: testCompression.php:50
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
EditPage\$editConflictHelper
TextConflictHelper null $editConflictHelper
Definition: EditPage.php:437
Revision\FOR_THIS_USER
const FOR_THIS_USER
Definition: Revision.php:56
Revision
Definition: Revision.php:41
EditPage\getPreviewLimitReport
static getPreviewLimitReport( $output)
Get the Limit report for page previews.
Definition: EditPage.php:3573
EditPage\$watchthis
bool $watchthis
Definition: EditPage.php:325
EditPage\$previewTextAfterContent
$previewTextAfterContent
Definition: EditPage.php:392
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:3427
Xml\encodeJsCall
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:678
EditPage\$tooBig
bool $tooBig
Definition: EditPage.php:269
EditPage\AS_SUCCESS_NEW_ARTICLE
const AS_SUCCESS_NEW_ARTICLE
Status: Article successfully created.
Definition: EditPage.php:58
EditPage\UNICODE_CHECK
const UNICODE_CHECK
Used for Unicode support checks.
Definition: EditPage.php:48
MWException
MediaWiki exception.
Definition: MWException.php:26
EditPage\addContentModelChangeLogEntry
addContentModelChangeLogEntry(User $user, $oldModel, $newModel, $reason)
Definition: EditPage.php:2260
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:115
EditPage\toEditContent
toEditContent( $text)
Turns the given text into a Content object by unserializing it.
Definition: EditPage.php:2652
EditPage\getEditButtons
getEditButtons(&$tabindex)
Returns an array of html code of the following buttons: save, diff and preview.
Definition: EditPage.php:4294
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1123
EditPage\AS_END
const AS_END
Status: WikiPage::doEdit() was unsuccessful.
Definition: EditPage.php:141
LogPage\DELETED_COMMENT
const DELETED_COMMENT
Definition: LogPage.php:33
EditPage\$editRevId
int $editRevId
Definition: EditPage.php:346
EditPage\showSummaryInput
showSummaryInput( $isSubjectPreview, $summary="")
Definition: EditPage.php:3213
EditPage\getParentRevId
getParentRevId()
Get the edit's parent revision ID.
Definition: EditPage.php:1320
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:111
EditPage\isSectionEditSupported
isSectionEditSupported()
Returns whether section editing is supported for the current page.
Definition: EditPage.php:870
EditPage\importFormData
importFormData(&$request)
This function collects the form data and uses it to populate various member variables.
Definition: EditPage.php:880
EditPage\getActionURL
getActionURL(Title $title)
Returns the URL to use in the form's action attribute.
Definition: EditPage.php:3714
EditPage\addExplainConflictHeader
addExplainConflictHeader(OutputPage $out)
Definition: EditPage.php:4541
LogPage\DELETED_USER
const DELETED_USER
Definition: LogPage.php:34
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2812
EditPage\showIntro
showIntro()
Show all applicable editing introductions.
Definition: EditPage.php:2465
EditPage\$firsttime
bool $firsttime
Definition: EditPage.php:251
$matches
$matches
Definition: NoLocalSettings.php:24
EditPage\$missingComment
bool $missingComment
Definition: EditPage.php:272
EditPage\$editConflictHelperFactory
callable $editConflictHelperFactory
Factory function to create an edit conflict helper.
Definition: EditPage.php:432
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:1503
WikiPage\getContent
getContent( $audience=Revision::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
Definition: WikiPage.php:718
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
Article\getPage
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:191
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:943
EditPage\getArticle
getArticle()
Definition: EditPage.php:458
EditPage\AS_CANNOT_USE_CUSTOM_MODEL
const AS_CANNOT_USE_CUSTOM_MODEL
Status: when changing the content model is disallowed due to $wgContentHandlerUseDB being false.
Definition: EditPage.php:185
ThrottledError
Show an error when the user hits a rate limit.
Definition: ThrottledError.php:27
EditPage\AS_NO_CHANGE_CONTENT_MODEL
const AS_NO_CHANGE_CONTENT_MODEL
Status: user tried to modify the content model, but is not allowed to do that ( User::isAllowed('edit...
Definition: EditPage.php:162
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:4203
EditPage\previewOnOpen
previewOnOpen()
Should we show a preview when the edit form is first shown?
Definition: EditPage.php:808
WatchAction\doWatchOrUnwatch
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:91
EditPage\incrementEditFailureStats
incrementEditFailureStats( $failureType)
Definition: EditPage.php:3939
$wgLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as $wgLang
Definition: design.txt:56
EditPage\AS_READ_ONLY_PAGE
const AS_READ_ONLY_PAGE
Status: wiki is in readonly mode (wfReadOnly() == true)
Definition: EditPage.php:93
EditPage\AS_SPAM_ERROR
const AS_SPAM_ERROR
Status: summary contained spam according to one of the regexes in $wgSummarySpamRegex.
Definition: EditPage.php:146
EditPage\$allowSelfRedirect
bool $allowSelfRedirect
Definition: EditPage.php:290
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:964
EditPage\showEditForm
showEditForm( $formCallback=null)
Send the edit form and related headers to OutputPage.
Definition: EditPage.php:2675
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:534
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:95
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:616
EditPage\wasDeletedSinceLastEdit
wasDeletedSinceLastEdit()
Check if a page was deleted while the user was editing it, before submit.
Definition: EditPage.php:3725
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
EditPage\getTemplates
getTemplates()
Definition: EditPage.php:3984
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2009
EditPage\getPreviewParserOptions
getPreviewParserOptions()
Get parser options for a preview.
Definition: EditPage.php:3948
EditPage\runPostMergeFilters
runPostMergeFilters(Content $content, Status $status, User $user)
Run hooks that can filter edits just before they get saved.
Definition: EditPage.php:1667
EditPage\AS_MAX_ARTICLE_SIZE_EXCEEDED
const AS_MAX_ARTICLE_SIZE_EXCEEDED
Status: article is too big (> $wgMaxArticleSize), after merging in the new section.
Definition: EditPage.php:136
DB_MASTER
const DB_MASTER
Definition: defines.php:29
EditPage\$mContextTitle
null Title $mContextTitle
Definition: EditPage.php:233
div
div
Definition: parserTests.txt:6593
MWNamespace\getRestrictionLevels
static getRestrictionLevels( $index, User $user=null)
Determine which restriction levels it makes sense to use in a namespace, optionally filtered by a use...
Definition: MWNamespace.php:489
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:994
EditPage\showFormAfterText
showFormAfterText()
Definition: EditPage.php:3272
OutputPage
This class should be covered by a general architecture document which does not exist as of January 20...
Definition: OutputPage.php:45
EditPage\showPreview
showPreview( $text)
Append preview output to OutputPage.
Definition: EditPage.php:3406
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:1114
EditPage\getEditToolbar
static getEditToolbar( $title=null)
Shows a bulletin board style toolbar for common editing functions.
Definition: EditPage.php:4008
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
MediaWiki\EditPage\TextConflictHelper\setTextboxes
setTextboxes( $yourtext, $storedversion)
Definition: TextConflictHelper.php:103
MIGRATION_OLD
const MIGRATION_OLD
Definition: Defines.php:302
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:4430
$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:2001
MediaWiki\EditPage\TextConflictHelper
Helper for displaying edit conflicts in text content models to users.
Definition: TextConflictHelper.php:40
EditPage\matchSpamRegex
static matchSpamRegex( $text)
Check given input text against $wgSpamRegex, and return the text of the first match.
Definition: EditPage.php:2360
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:774
EditPage\$recreate
bool $recreate
Definition: EditPage.php:328
$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:2014
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:1267
EditPage\$contentLength
bool int $contentLength
Definition: EditPage.php:405
EditPage\AS_SUCCESS_UPDATE
const AS_SUCCESS_UPDATE
Status: Article successfully updated.
Definition: EditPage.php:53
EditPage\showTextbox1
showTextbox1( $customAttribs=null, $textoverride=null)
Method to output wpTextbox1 The $textoverride method can be used by subclasses overriding showContent...
Definition: EditPage.php:3312
EditPage\addTalkPageText
addTalkPageText()
Definition: EditPage.php:4457
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:77
EditPage\getSummaryPreview
getSummaryPreview( $isSubjectPreview, $summary="")
Definition: EditPage.php:3241
EditPage\importContentFormData
importContentFormData(&$request)
Subpage overridable method for extracting the page content data from the posted form to be placed in ...
Definition: EditPage.php:1105
EditPage\$minoredit
bool $minoredit
Definition: EditPage.php:322
EditPage\$isOldRev
bool $isOldRev
Whether an old revision is edited.
Definition: EditPage.php:420
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:410
$value
$value
Definition: styleTest.css.php:45
EDIT_UPDATE
const EDIT_UPDATE
Definition: Defines.php:163
EditPage\showHeader
showHeader()
Definition: EditPage.php:2987
MediaWiki\EditPage\TextConflictHelper\getEditFormHtmlAfterContent
getEditFormHtmlAfterContent()
Content to go in the edit form after textbox1.
Definition: TextConflictHelper.php:208
EditPage\getBaseRevision
getBaseRevision()
Definition: EditPage.php:2343
ContentHandler\getLocalizedName
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
Definition: ContentHandler.php:355
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:62
EditPage\addNewLineAtEnd
addNewLineAtEnd( $wikitext)
Definition: EditPage.php:4565
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:1516
MWNamespace\wantSignatures
static wantSignatures( $index)
Might pages in this namespace require the use of the Signature button on the edit toolbar?
Definition: MWNamespace.php:347
EditPage\$edittime
string $edittime
Definition: EditPage.php:343
EditPage\matchSummarySpamRegex
static matchSummarySpamRegex( $text)
Check given input text against $wgSummarySpamRegex, and return the text of the first match.
Definition: EditPage.php:2374
EditPage\$mTokenOkExceptSuffix
bool $mTokenOkExceptSuffix
Definition: EditPage.php:260
EditPage\showEditTools
showEditTools()
Inserts optional text shown below edit and upload forms.
Definition: EditPage.php:3521
EditPage\$preview
bool $preview
Definition: EditPage.php:316
EditPage\$isNew
bool $isNew
New page or new section.
Definition: EditPage.php:242
EditPage\getCheckboxesDefinition
getCheckboxesDefinition( $checked)
Return an array of checkbox definitions.
Definition: EditPage.php:4158
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:19
$wgSummarySpamRegex
$wgSummarySpamRegex
Same as the above except for edit summaries.
Definition: DefaultSettings.php:5583
EditPage\$mBaseRevision
Revision bool null $mBaseRevision
Definition: EditPage.php:305
First
The First
Definition: primes.txt:1
EditPage\getCopywarn
getCopywarn()
Get the copyright warning.
Definition: EditPage.php:3533
EditPage\setApiEditOverride
setApiEditOverride( $enableOverride)
Allow editing of content that supports API direct editing, but not general direct editing.
Definition: EditPage.php:535
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1643
EditPage\AS_IMAGE_REDIRECT_LOGGED
const AS_IMAGE_REDIRECT_LOGGED
Status: logged in user is not allowed to upload (User::isAllowed('upload') == false)
Definition: EditPage.php:156
EditPage\newTextConflictHelper
newTextConflictHelper( $submitButtonLabel)
Definition: EditPage.php:4624
Revision\RAW
const RAW
Definition: Revision.php:57
EditPage\getCancelLink
getCancelLink()
Definition: EditPage.php:3687
EditPage\showCustomIntro
showCustomIntro()
Attempt to show a custom editing introduction, if supplied.
Definition: EditPage.php:2591
EditPage\getContext
getContext()
Definition: EditPage.php:466
$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). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1255
EditPage\AS_PARSE_ERROR
const AS_PARSE_ERROR
Status: can't parse content.
Definition: EditPage.php:179
EditPage\EDITFORM_ID
const EDITFORM_ID
HTML id and name for the beginning of the edit form.
Definition: EditPage.php:195
EditPage\extractSectionTitle
static extractSectionTitle( $text)
Extract the section title from current section text, if any.
Definition: EditPage.php:2977
Block\TYPE_AUTO
const TYPE_AUTO
Definition: Block.php:86
EditPage\makeTemplatesOnThisPageList
makeTemplatesOnThisPageList(array $templates)
Wrapper around TemplatesOnThisPageFormatter to make a "templates on this page" list.
Definition: EditPage.php:2953
EditPage\$textbox1
string $textbox1
Definition: EditPage.php:331
EditPage\$parentRevId
int $parentRevId
Definition: EditPage.php:361
MediaWiki\EditPage\TextConflictHelper\setContentModel
setContentModel( $contentModel)
Definition: TextConflictHelper.php:111
EditPage\$undidRev
$undidRev
Definition: EditPage.php:397
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:1969
wfFindFile
wfFindFile( $title, $options=[])
Find a file.
Definition: GlobalFunctions.php:2853
EditPage\$changeTags
null array $changeTags
Definition: EditPage.php:379
EditPage
The edit page/HTML interface (split from Article) The actual database and text munging is still in Ar...
Definition: EditPage.php:44
EditPage\noSuchSectionPage
noSuchSectionPage()
Creates a basic error page which informs the user that they have attempted to edit a nonexistent sect...
Definition: EditPage.php:4363
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
wfGetAllCallers
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
Definition: GlobalFunctions.php:1563
EditPage\$formtype
string $formtype
Definition: EditPage.php:248
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:3180
CommentStore\COMMENT_CHARACTER_LIMIT
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
Definition: CommentStore.php:37
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:162
EditPage\$hasPresetSummary
bool $hasPresetSummary
Has a summary been preset using GET parameter &summary= ?
Definition: EditPage.php:302
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:483
$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:1777
EditPage\$mParserOutput
ParserOutput $mParserOutput
Definition: EditPage.php:299
Title
Represents a title within MediaWiki.
Definition: Title.php:39
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
EDIT_AUTOSUMMARY
const EDIT_AUTOSUMMARY
Definition: Defines.php:168
EditPage\$mShowSummaryField
bool $mShowSummaryField
Definition: EditPage.php:308
EditPage\$sectiontitle
string $sectiontitle
Definition: EditPage.php:352
EditPage\$starttime
string $starttime
Definition: EditPage.php:355
EditPage\$suppressIntro
$suppressIntro
Definition: EditPage.php:399
ContentHandler\getContentText
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
Definition: ContentHandler.php:79
MediaWiki\EditPage\TextConflictHelper\getEditFormHtmlBeforeContent
getEditFormHtmlBeforeContent()
Content to go in the edit form before textbox1.
Definition: TextConflictHelper.php:198
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:1275
$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:865
EditPage\formatStatusErrors
formatStatusErrors(Status $status)
Wrap status errors in an errorbox for increased visibility.
Definition: EditPage.php:1714
EditPage\$deletedSinceEdit
bool $deletedSinceEdit
Definition: EditPage.php:245
ResourceLoader\inDebugMode
static inDebugMode()
Determine whether debug mode was requested Order of priority is 1) request param, 2) cookie,...
Definition: ResourceLoader.php:1565
EditPage\$selfRedirect
bool $selfRedirect
Definition: EditPage.php:287
EditPage\$edit
bool $edit
Definition: EditPage.php:402
EditPage\isSupportedContentModel
isSupportedContentModel( $modelId)
Returns if the given content model is editable.
Definition: EditPage.php:524
EditPage\$mPreloadContent
$mPreloadContent
Definition: EditPage.php:393
EditPage\showConflict
showConflict()
Show an edit conflict.
Definition: EditPage.php:3669
EditPage\getSubmitButtonLabel
getSubmitButtonLabel()
Get the message key of the label for the button to save the page.
Definition: EditPage.php:4270
EditPage\$unicodeCheck
string null $unicodeCheck
What the user submitted in the 'wpUnicodeCheck' field.
Definition: EditPage.php:425
EditPage\$diff
bool $diff
Definition: EditPage.php:319
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:22
EditPage\doPreviewParse
doPreviewParse(Content $content)
Parse the page for a preview.
Definition: EditPage.php:3965
EditPage\newSectionSummary
newSectionSummary(&$sectionanchor=null)
Return the summary to be used for a new section.
Definition: EditPage.php:1734
EditPage\$action
string $action
Definition: EditPage.php:236
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
EditPage\setEditConflictHelperFactory
setEditConflictHelperFactory(callable $factory)
Set a factory function to create an EditConflictHelper.
Definition: EditPage.php:4601
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:291
EditPage\AS_READ_ONLY_PAGE_LOGGED
const AS_READ_ONLY_PAGE_LOGGED
Status: this logged in user is not allowed to edit this page.
Definition: EditPage.php:88
$wgEnableUploads
$wgEnableUploads
Uploads have to be specially set up to be secure.
Definition: DefaultSettings.php:403
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:1455
NS_USER
const NS_USER
Definition: Defines.php:76
EditPage\showTextbox
showTextbox( $text, $name, $customAttribs=[])
Definition: EditPage.php:3344
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
$username
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:785
EditPage\getTitle
getTitle()
Definition: EditPage.php:474
ManualLogEntry
Class for creating log entries manually, to inject them into the database.
Definition: LogEntry.php:432
EditPage\AS_CONFLICT_DETECTED
const AS_CONFLICT_DETECTED
Status: (non-resolvable) edit conflict.
Definition: EditPage.php:120
in
null for the wiki Added in
Definition: hooks.txt:1591
EditPage\AS_RATE_LIMITED
const AS_RATE_LIMITED
Status: rate limiter for action 'edit' was tripped.
Definition: EditPage.php:98
MWUnknownContentModelException
Exception thrown when an unregistered content model is requested.
Definition: MWUnknownContentModelException.php:10
of
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
database rows
Definition: globals.txt:10
EditPage\getCurrentContent
getCurrentContent()
Get the current content of the page.
Definition: EditPage.php:1336
Title\setContentModel
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition: Title.php:1018
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:1137
EditPage\$isConflict
bool $isConflict
Definition: EditPage.php:239
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:82
CONTENT_MODEL_JAVASCRIPT
const CONTENT_MODEL_JAVASCRIPT
Definition: Defines.php:246
EditPage\displayViewSourcePage
displayViewSourcePage(Content $content, $errorMessage='')
Display a read-only View Source page.
Definition: EditPage.php:755
EDIT_MINOR
const EDIT_MINOR
Definition: Defines.php:164
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:35
EditPage\getContentObject
getContentObject( $def_content=null)
Definition: EditPage.php:1153
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:90
$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:2806
EditPage\AS_IMAGE_REDIRECT_ANON
const AS_IMAGE_REDIRECT_ANON
Status: anonymous user is not allowed to upload (User::isAllowed('upload') == false)
Definition: EditPage.php:151
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:25
EditPage\showStandardInputs
showStandardInputs(&$tabindex=2)
Definition: EditPage.php:3619
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:512
$wgOut
$wgOut
Definition: Setup.php:912
EditPage\matchSpamRegexInternal
static matchSpamRegexInternal( $text, $regexes)
Definition: EditPage.php:2385
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:544
$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:1620
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:273
EditPage\POST_EDIT_COOKIE_KEY_PREFIX
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition: EditPage.php:201
EditPage\getOriginalContent
getOriginalContent(User $user)
Get the content of the wanted revision, without section extraction.
Definition: EditPage.php:1295
EditPage\AS_ARTICLE_WAS_DELETED
const AS_ARTICLE_WAS_DELETED
Status: article was deleted while editing and param wpRecreate == false or form was not posted.
Definition: EditPage.php:104
EditPage\setPostEditCookie
setPostEditCookie( $statusValue)
Sets post-edit cookie indicating the user just saved a particular revision.
Definition: EditPage.php:1482
Linker\accesskey
static accesskey( $name)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition: Linker.php:2017
CommentStore\getStore
static getStore()
Definition: CommentStore.php:130
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:53
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:111
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:203
EditPage\isWrongCaseUserConfigPage
isWrongCaseUserConfigPage()
Checks whether the user entered a skin name in uppercase, e.g.
Definition: EditPage.php:849
EditPage\$incompleteForm
bool $incompleteForm
Definition: EditPage.php:266
$wgForeignFileRepos
$wgForeignFileRepos
Definition: DefaultSettings.php:544
EditPage\$missingSummary
bool $missingSummary
Definition: EditPage.php:275
EditPage\getEditConflictHelper
getEditConflictHelper()
Definition: EditPage.php:4609
EditPage\$bot
bool $bot
Definition: EditPage.php:370
EditPage\getCopyrightWarning
static getCopyrightWarning( $title, $format='plain', $langcode=null)
Get the copyright warning, by default returns wikitext.
Definition: EditPage.php:3545
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
MWNamespace\getCanonicalName
static getCanonicalName( $index)
Returns the canonical (English) name for a given index.
Definition: MWNamespace.php:255
Revision\DELETED_TEXT
const DELETED_TEXT
Definition: Revision.php:47
$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
ResourceLoader\makeInlineScript
static makeInlineScript( $script)
Returns an HTML script tag that runs given JS code after startup and base modules.
Definition: ResourceLoader.php:1506
EditPage\AS_HOOK_ERROR_EXPECTED
const AS_HOOK_ERROR_EXPECTED
Status: A hook function returned an error.
Definition: EditPage.php:68
Skin\makeInternalOrExternalUrl
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition: Skin.php:1166
EditPage\updateWatchlist
updateWatchlist()
Register the change of watch status.
Definition: EditPage.php:2277
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:521
EditPage\handleStatus
handleStatus(Status $status, $resultDetails)
Handle status, such as after attempt save.
Definition: EditPage.php:1533
ParserOptions\newFromUser
static newFromUser( $user)
Get a ParserOptions object from a given user.
Definition: ParserOptions.php:978
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2171
EditPage\$hookError
string $hookError
Definition: EditPage.php:296
EditPage\$allowBlankArticle
bool $allowBlankArticle
Definition: EditPage.php:284
EditPage\toEditText
toEditText( $content)
Gets an editable textual representation of $content.
Definition: EditPage.php:2624
Xml\checkLabel
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition: Xml.php:420
EditPage\setHeaders
setHeaders()
Definition: EditPage.php:2395
EditPage\AS_TEXTBOX_EMPTY
const AS_TEXTBOX_EMPTY
Status: user tried to create a new section without content.
Definition: EditPage.php:131
EditPage\AS_CHANGE_TAG_ERROR
const AS_CHANGE_TAG_ERROR
Status: an error relating to change tagging.
Definition: EditPage.php:174
$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:57
EditPage\guessSectionName
guessSectionName( $text)
Turns section name wikitext into anchors for use in HTTP redirects.
Definition: EditPage.php:4579
$wgSpamRegex
$wgSpamRegex
Edits matching these regular expressions in body text will be recognised as spam and rejected automat...
Definition: DefaultSettings.php:5578
EditPage\submit
submit()
Definition: EditPage.php:542
$type
$type
Definition: testCompression.php:48
EditPage\$nosummary
bool $nosummary
Definition: EditPage.php:340
EditPage\getPreviewText
getPreviewText()
Get the rendered text for previewing.
Definition: EditPage.php:3796