MediaWiki  REL1_28
EditPage.php
Go to the documentation of this file.
1 <?php
25 use Wikimedia\ScopedCallback;
26 
42 class EditPage {
46  const AS_SUCCESS_UPDATE = 200;
47 
52 
56  const AS_HOOK_ERROR = 210;
57 
62 
67 
71  const AS_CONTENT_TOO_BIG = 216;
72 
77 
82 
86  const AS_READ_ONLY_PAGE = 220;
87 
91  const AS_RATE_LIMITED = 221;
92 
98 
104 
108  const AS_BLANK_ARTICLE = 224;
109 
113  const AS_CONFLICT_DETECTED = 225;
114 
119  const AS_SUMMARY_NEEDED = 226;
120 
124  const AS_TEXTBOX_EMPTY = 228;
125 
130 
134  const AS_END = 231;
135 
139  const AS_SPAM_ERROR = 232;
140 
145 
150 
156 
161  const AS_SELF_REDIRECT = 236;
162 
167  const AS_CHANGE_TAG_ERROR = 237;
168 
172  const AS_PARSE_ERROR = 240;
173 
179 
183  const EDITFORM_ID = 'editform';
184 
189  const POST_EDIT_COOKIE_KEY_PREFIX = 'PostEditRevision';
190 
205 
207  public $mArticle;
209  private $page;
210 
212  public $mTitle;
213 
215  private $mContextTitle = null;
216 
218  public $action = 'submit';
219 
221  public $isConflict = false;
222 
224  public $isCssJsSubpage = false;
225 
227  public $isCssSubpage = false;
228 
230  public $isJsSubpage = false;
231 
233  public $isWrongCaseCssJsPage = false;
234 
236  public $isNew = false;
237 
240 
242  public $formtype;
243 
245  public $firsttime;
246 
248  public $lastDelete;
249 
251  public $mTokenOk = false;
252 
254  public $mTokenOkExceptSuffix = false;
255 
257  public $mTriedSave = false;
258 
260  public $incompleteForm = false;
261 
263  public $tooBig = false;
264 
266  public $missingComment = false;
267 
269  public $missingSummary = false;
270 
272  public $allowBlankSummary = false;
273 
275  protected $blankArticle = false;
276 
278  protected $allowBlankArticle = false;
279 
281  protected $selfRedirect = false;
282 
284  protected $allowSelfRedirect = false;
285 
287  public $autoSumm = '';
288 
290  public $hookError = '';
291 
294 
296  public $hasPresetSummary = false;
297 
299  public $mBaseRevision = false;
300 
302  public $mShowSummaryField = true;
303 
304  # Form values
305 
307  public $save = false;
308 
310  public $preview = false;
311 
313  public $diff = false;
314 
316  public $minoredit = false;
317 
319  public $watchthis = false;
320 
322  public $recreate = false;
323 
325  public $textbox1 = '';
326 
328  public $textbox2 = '';
329 
331  public $summary = '';
332 
334  public $nosummary = false;
335 
337  public $edittime = '';
338 
340  private $editRevId = null;
341 
343  public $section = '';
344 
346  public $sectiontitle = '';
347 
349  public $starttime = '';
350 
352  public $oldid = 0;
353 
355  public $parentRevId = 0;
356 
358  public $editintro = '';
359 
361  public $scrolltop = null;
362 
364  public $bot = true;
365 
367  public $contentModel = null;
368 
370  public $contentFormat = null;
371 
373  private $changeTags = null;
374 
375  # Placeholders for text injection by hooks (must be HTML)
376  # extensions should take care to _append_ to the present value
377 
379  public $editFormPageTop = '';
380  public $editFormTextTop = '';
384  public $editFormTextBottom = '';
387  public $mPreloadContent = null;
388 
389  /* $didSave should be set to true whenever an article was successfully altered. */
390  public $didSave = false;
391  public $undidRev = 0;
392 
393  public $suppressIntro = false;
394 
396  protected $edit;
397 
399  protected $contentLength = false;
400 
404  private $enableApiEditOverride = false;
405 
409  protected $context;
410 
414  public function __construct( Article $article ) {
415  $this->mArticle = $article;
416  $this->page = $article->getPage(); // model object
417  $this->mTitle = $article->getTitle();
418  $this->context = $article->getContext();
419 
420  $this->contentModel = $this->mTitle->getContentModel();
421 
422  $handler = ContentHandler::getForModelID( $this->contentModel );
423  $this->contentFormat = $handler->getDefaultFormat();
424  }
425 
429  public function getArticle() {
430  return $this->mArticle;
431  }
432 
437  public function getContext() {
438  return $this->context;
439  }
440 
445  public function getTitle() {
446  return $this->mTitle;
447  }
448 
454  public function setContextTitle( $title ) {
455  $this->mContextTitle = $title;
456  }
457 
465  public function getContextTitle() {
466  if ( is_null( $this->mContextTitle ) ) {
468  return $wgTitle;
469  } else {
470  return $this->mContextTitle;
471  }
472  }
473 
481  public function isSupportedContentModel( $modelId ) {
482  return $this->enableApiEditOverride === true ||
483  ContentHandler::getForModelID( $modelId )->supportsDirectEditing();
484  }
485 
492  public function setApiEditOverride( $enableOverride ) {
493  $this->enableApiEditOverride = $enableOverride;
494  }
495 
496  function submit() {
497  $this->edit();
498  }
499 
511  function edit() {
513  // Allow extensions to modify/prevent this form or submission
514  if ( !Hooks::run( 'AlternateEdit', [ $this ] ) ) {
515  return;
516  }
517 
518  wfDebug( __METHOD__ . ": enter\n" );
519 
520  // If they used redlink=1 and the page exists, redirect to the main article
521  if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
522  $wgOut->redirect( $this->mTitle->getFullURL() );
523  return;
524  }
525 
526  $this->importFormData( $wgRequest );
527  $this->firsttime = false;
528 
529  if ( wfReadOnly() && $this->save ) {
530  // Force preview
531  $this->save = false;
532  $this->preview = true;
533  }
534 
535  if ( $this->save ) {
536  $this->formtype = 'save';
537  } elseif ( $this->preview ) {
538  $this->formtype = 'preview';
539  } elseif ( $this->diff ) {
540  $this->formtype = 'diff';
541  } else { # First time through
542  $this->firsttime = true;
543  if ( $this->previewOnOpen() ) {
544  $this->formtype = 'preview';
545  } else {
546  $this->formtype = 'initial';
547  }
548  }
549 
550  $permErrors = $this->getEditPermissionErrors( $this->save ? 'secure' : 'full' );
551  if ( $permErrors ) {
552  wfDebug( __METHOD__ . ": User can't edit\n" );
553  // Auto-block user's IP if the account was "hard" blocked
554  if ( !wfReadOnly() ) {
555  $user = $wgUser;
556  DeferredUpdates::addCallableUpdate( function () use ( $user ) {
557  $user->spreadAnyEditBlock();
558  } );
559  }
560  $this->displayPermissionsError( $permErrors );
561 
562  return;
563  }
564 
565  $revision = $this->mArticle->getRevisionFetched();
566  // Disallow editing revisions with content models different from the current one
567  // Undo edits being an exception in order to allow reverting content model changes.
568  if ( $revision
569  && $revision->getContentModel() !== $this->contentModel
570  ) {
571  $prevRev = null;
572  if ( $this->undidRev ) {
573  $undidRevObj = Revision::newFromId( $this->undidRev );
574  $prevRev = $undidRevObj ? $undidRevObj->getPrevious() : null;
575  }
576  if ( !$this->undidRev
577  || !$prevRev
578  || $prevRev->getContentModel() !== $this->contentModel
579  ) {
580  $this->displayViewSourcePage(
581  $this->getContentObject(),
582  $this->context->msg(
583  'contentmodelediterror',
584  $revision->getContentModel(),
586  )->plain()
587  );
588  return;
589  }
590  }
591 
592  $this->isConflict = false;
593  // css / js subpages of user pages get a special treatment
594  $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
595  $this->isCssSubpage = $this->mTitle->isCssSubpage();
596  $this->isJsSubpage = $this->mTitle->isJsSubpage();
597  // @todo FIXME: Silly assignment.
598  $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
599 
600  # Show applicable editing introductions
601  if ( $this->formtype == 'initial' || $this->firsttime ) {
602  $this->showIntro();
603  }
604 
605  # Attempt submission here. This will check for edit conflicts,
606  # and redundantly check for locked database, blocked IPs, etc.
607  # that edit() already checked just in case someone tries to sneak
608  # in the back door with a hand-edited submission URL.
609 
610  if ( 'save' == $this->formtype ) {
611  $resultDetails = null;
612  $status = $this->attemptSave( $resultDetails );
613  if ( !$this->handleStatus( $status, $resultDetails ) ) {
614  return;
615  }
616  }
617 
618  # First time through: get contents, set time for conflict
619  # checking, etc.
620  if ( 'initial' == $this->formtype || $this->firsttime ) {
621  if ( $this->initialiseForm() === false ) {
622  $this->noSuchSectionPage();
623  return;
624  }
625 
626  if ( !$this->mTitle->getArticleID() ) {
627  Hooks::run( 'EditFormPreloadText', [ &$this->textbox1, &$this->mTitle ] );
628  } else {
629  Hooks::run( 'EditFormInitialText', [ $this ] );
630  }
631 
632  }
633 
634  $this->showEditForm();
635  }
636 
641  protected function getEditPermissionErrors( $rigor = 'secure' ) {
642  global $wgUser;
643 
644  $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser, $rigor );
645  # Can this title be created?
646  if ( !$this->mTitle->exists() ) {
647  $permErrors = array_merge(
648  $permErrors,
649  wfArrayDiff2(
650  $this->mTitle->getUserPermissionsErrors( 'create', $wgUser, $rigor ),
651  $permErrors
652  )
653  );
654  }
655  # Ignore some permissions errors when a user is just previewing/viewing diffs
656  $remove = [];
657  foreach ( $permErrors as $error ) {
658  if ( ( $this->preview || $this->diff )
659  && ( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' )
660  ) {
661  $remove[] = $error;
662  }
663  }
664  $permErrors = wfArrayDiff2( $permErrors, $remove );
665 
666  return $permErrors;
667  }
668 
682  protected function displayPermissionsError( array $permErrors ) {
684 
685  if ( $wgRequest->getBool( 'redlink' ) ) {
686  // The edit page was reached via a red link.
687  // Redirect to the article page and let them click the edit tab if
688  // they really want a permission error.
689  $wgOut->redirect( $this->mTitle->getFullURL() );
690  return;
691  }
692 
693  $content = $this->getContentObject();
694 
695  # Use the normal message if there's nothing to display
696  if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
697  $action = $this->mTitle->exists() ? 'edit' :
698  ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
699  throw new PermissionsError( $action, $permErrors );
700  }
701 
702  $this->displayViewSourcePage(
703  $content,
704  $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' )
705  );
706  }
707 
713  protected function displayViewSourcePage( Content $content, $errorMessage = '' ) {
714  global $wgOut;
715 
716  Hooks::run( 'EditPage::showReadOnlyForm:initial', [ $this, &$wgOut ] );
717 
718  $wgOut->setRobotPolicy( 'noindex,nofollow' );
719  $wgOut->setPageTitle( $this->context->msg(
720  'viewsource-title',
721  $this->getContextTitle()->getPrefixedText()
722  ) );
723  $wgOut->addBacklinkSubtitle( $this->getContextTitle() );
724  $wgOut->addHTML( $this->editFormPageTop );
725  $wgOut->addHTML( $this->editFormTextTop );
726 
727  if ( $errorMessage !== '' ) {
728  $wgOut->addWikiText( $errorMessage );
729  $wgOut->addHTML( "<hr />\n" );
730  }
731 
732  # If the user made changes, preserve them when showing the markup
733  # (This happens when a user is blocked during edit, for instance)
734  if ( !$this->firsttime ) {
735  $text = $this->textbox1;
736  $wgOut->addWikiMsg( 'viewyourtext' );
737  } else {
738  try {
739  $text = $this->toEditText( $content );
740  } catch ( MWException $e ) {
741  # Serialize using the default format if the content model is not supported
742  # (e.g. for an old revision with a different model)
743  $text = $content->serialize();
744  }
745  $wgOut->addWikiMsg( 'viewsourcetext' );
746  }
747 
748  $wgOut->addHTML( $this->editFormTextBeforeContent );
749  $this->showTextbox( $text, 'wpTextbox1', [ 'readonly' ] );
750  $wgOut->addHTML( $this->editFormTextAfterContent );
751 
752  $wgOut->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
753 
754  $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
755 
756  $wgOut->addHTML( $this->editFormTextBottom );
757  if ( $this->mTitle->exists() ) {
758  $wgOut->returnToMain( null, $this->mTitle );
759  }
760  }
761 
767  protected function previewOnOpen() {
768  global $wgRequest, $wgUser, $wgPreviewOnOpenNamespaces;
769  if ( $wgRequest->getVal( 'preview' ) == 'yes' ) {
770  // Explicit override from request
771  return true;
772  } elseif ( $wgRequest->getVal( 'preview' ) == 'no' ) {
773  // Explicit override from request
774  return false;
775  } elseif ( $this->section == 'new' ) {
776  // Nothing *to* preview for new sections
777  return false;
778  } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() )
779  && $wgUser->getOption( 'previewonfirst' )
780  ) {
781  // Standard preference behavior
782  return true;
783  } elseif ( !$this->mTitle->exists()
784  && isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
785  && $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()]
786  ) {
787  // Categories are special
788  return true;
789  } else {
790  return false;
791  }
792  }
793 
800  protected function isWrongCaseCssJsPage() {
801  if ( $this->mTitle->isCssJsSubpage() ) {
802  $name = $this->mTitle->getSkinFromCssJsSubpage();
803  $skins = array_merge(
804  array_keys( Skin::getSkinNames() ),
805  [ 'common' ]
806  );
807  return !in_array( $name, $skins )
808  && in_array( strtolower( $name ), $skins );
809  } else {
810  return false;
811  }
812  }
813 
821  protected function isSectionEditSupported() {
822  $contentHandler = ContentHandler::getForTitle( $this->mTitle );
823  return $contentHandler->supportsSections();
824  }
825 
831  function importFormData( &$request ) {
833 
834  # Section edit can come from either the form or a link
835  $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
836 
837  if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) {
838  throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
839  }
840 
841  $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
842 
843  if ( $request->wasPosted() ) {
844  # These fields need to be checked for encoding.
845  # Also remove trailing whitespace, but don't remove _initial_
846  # whitespace from the text boxes. This may be significant formatting.
847  $this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' );
848  if ( !$request->getCheck( 'wpTextbox2' ) ) {
849  // Skip this if wpTextbox2 has input, it indicates that we came
850  // from a conflict page with raw page text, not a custom form
851  // modified by subclasses
853  if ( $textbox1 !== null ) {
854  $this->textbox1 = $textbox1;
855  }
856  }
857 
858  # Truncate for whole multibyte characters
859  $this->summary = $wgContLang->truncate( $request->getText( 'wpSummary' ), 255 );
860 
861  # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
862  # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
863  # section titles.
864  $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
865 
866  # Treat sectiontitle the same way as summary.
867  # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
868  # currently doing double duty as both edit summary and section title. Right now this
869  # is just to allow API edits to work around this limitation, but this should be
870  # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
871  $this->sectiontitle = $wgContLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
872  $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
873 
874  $this->edittime = $request->getVal( 'wpEdittime' );
875  $this->editRevId = $request->getIntOrNull( 'editRevId' );
876  $this->starttime = $request->getVal( 'wpStarttime' );
877 
878  $undidRev = $request->getInt( 'wpUndidRevision' );
879  if ( $undidRev ) {
880  $this->undidRev = $undidRev;
881  }
882 
883  $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
884 
885  if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
886  // wpTextbox1 field is missing, possibly due to being "too big"
887  // according to some filter rules such as Suhosin's setting for
888  // suhosin.request.max_value_length (d'oh)
889  $this->incompleteForm = true;
890  } else {
891  // If we receive the last parameter of the request, we can fairly
892  // claim the POST request has not been truncated.
893 
894  // TODO: softened the check for cutover. Once we determine
895  // that it is safe, we should complete the transition by
896  // removing the "edittime" clause.
897  $this->incompleteForm = ( !$request->getVal( 'wpUltimateParam' )
898  && is_null( $this->edittime ) );
899  }
900  if ( $this->incompleteForm ) {
901  # If the form is incomplete, force to preview.
902  wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
903  wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
904  $this->preview = true;
905  } else {
906  $this->preview = $request->getCheck( 'wpPreview' );
907  $this->diff = $request->getCheck( 'wpDiff' );
908 
909  // Remember whether a save was requested, so we can indicate
910  // if we forced preview due to session failure.
911  $this->mTriedSave = !$this->preview;
912 
913  if ( $this->tokenOk( $request ) ) {
914  # Some browsers will not report any submit button
915  # if the user hits enter in the comment box.
916  # The unmarked state will be assumed to be a save,
917  # if the form seems otherwise complete.
918  wfDebug( __METHOD__ . ": Passed token check.\n" );
919  } elseif ( $this->diff ) {
920  # Failed token check, but only requested "Show Changes".
921  wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
922  } else {
923  # Page might be a hack attempt posted from
924  # an external site. Preview instead of saving.
925  wfDebug( __METHOD__ . ": Failed token check; forcing preview\n" );
926  $this->preview = true;
927  }
928  }
929  $this->save = !$this->preview && !$this->diff;
930  if ( !preg_match( '/^\d{14}$/', $this->edittime ) ) {
931  $this->edittime = null;
932  }
933 
934  if ( !preg_match( '/^\d{14}$/', $this->starttime ) ) {
935  $this->starttime = null;
936  }
937 
938  $this->recreate = $request->getCheck( 'wpRecreate' );
939 
940  $this->minoredit = $request->getCheck( 'wpMinoredit' );
941  $this->watchthis = $request->getCheck( 'wpWatchthis' );
942 
943  # Don't force edit summaries when a user is editing their own user or talk page
944  if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK )
945  && $this->mTitle->getText() == $wgUser->getName()
946  ) {
947  $this->allowBlankSummary = true;
948  } else {
949  $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' )
950  || !$wgUser->getOption( 'forceeditsummary' );
951  }
952 
953  $this->autoSumm = $request->getText( 'wpAutoSummary' );
954 
955  $this->allowBlankArticle = $request->getBool( 'wpIgnoreBlankArticle' );
956  $this->allowSelfRedirect = $request->getBool( 'wpIgnoreSelfRedirect' );
957 
958  $changeTags = $request->getVal( 'wpChangeTags' );
959  if ( is_null( $changeTags ) || $changeTags === '' ) {
960  $this->changeTags = [];
961  } else {
962  $this->changeTags = array_filter( array_map( 'trim', explode( ',',
963  $changeTags ) ) );
964  }
965  } else {
966  # Not a posted form? Start with nothing.
967  wfDebug( __METHOD__ . ": Not a posted form.\n" );
968  $this->textbox1 = '';
969  $this->summary = '';
970  $this->sectiontitle = '';
971  $this->edittime = '';
972  $this->editRevId = null;
973  $this->starttime = wfTimestampNow();
974  $this->edit = false;
975  $this->preview = false;
976  $this->save = false;
977  $this->diff = false;
978  $this->minoredit = false;
979  // Watch may be overridden by request parameters
980  $this->watchthis = $request->getBool( 'watchthis', false );
981  $this->recreate = false;
982 
983  // When creating a new section, we can preload a section title by passing it as the
984  // preloadtitle parameter in the URL (Bug 13100)
985  if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
986  $this->sectiontitle = $request->getVal( 'preloadtitle' );
987  // Once wpSummary isn't being use for setting section titles, we should delete this.
988  $this->summary = $request->getVal( 'preloadtitle' );
989  } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
990  $this->summary = $request->getText( 'summary' );
991  if ( $this->summary !== '' ) {
992  $this->hasPresetSummary = true;
993  }
994  }
995 
996  if ( $request->getVal( 'minor' ) ) {
997  $this->minoredit = true;
998  }
999  }
1000 
1001  $this->oldid = $request->getInt( 'oldid' );
1002  $this->parentRevId = $request->getInt( 'parentRevId' );
1003 
1004  $this->bot = $request->getBool( 'bot', true );
1005  $this->nosummary = $request->getBool( 'nosummary' );
1006 
1007  // May be overridden by revision.
1008  $this->contentModel = $request->getText( 'model', $this->contentModel );
1009  // May be overridden by revision.
1010  $this->contentFormat = $request->getText( 'format', $this->contentFormat );
1011 
1012  try {
1013  $handler = ContentHandler::getForModelID( $this->contentModel );
1014  } catch ( MWUnknownContentModelException $e ) {
1015  throw new ErrorPageError(
1016  'editpage-invalidcontentmodel-title',
1017  'editpage-invalidcontentmodel-text',
1018  [ wfEscapeWikiText( $this->contentModel ) ]
1019  );
1020  }
1021 
1022  if ( !$handler->isSupportedFormat( $this->contentFormat ) ) {
1023  throw new ErrorPageError(
1024  'editpage-notsupportedcontentformat-title',
1025  'editpage-notsupportedcontentformat-text',
1026  [
1027  wfEscapeWikiText( $this->contentFormat ),
1028  wfEscapeWikiText( ContentHandler::getLocalizedName( $this->contentModel ) )
1029  ]
1030  );
1031  }
1032 
1039  $this->editintro = $request->getText( 'editintro',
1040  // Custom edit intro for new sections
1041  $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
1042 
1043  // Allow extensions to modify form data
1044  Hooks::run( 'EditPage::importFormData', [ $this, $request ] );
1045 
1046  }
1047 
1057  protected function importContentFormData( &$request ) {
1058  return; // Don't do anything, EditPage already extracted wpTextbox1
1059  }
1060 
1066  function initialiseForm() {
1067  global $wgUser;
1068  $this->edittime = $this->page->getTimestamp();
1069  $this->editRevId = $this->page->getLatest();
1070 
1071  $content = $this->getContentObject( false ); # TODO: track content object?!
1072  if ( $content === false ) {
1073  return false;
1074  }
1075  $this->textbox1 = $this->toEditText( $content );
1076 
1077  // activate checkboxes if user wants them to be always active
1078  # Sort out the "watch" checkbox
1079  if ( $wgUser->getOption( 'watchdefault' ) ) {
1080  # Watch all edits
1081  $this->watchthis = true;
1082  } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
1083  # Watch creations
1084  $this->watchthis = true;
1085  } elseif ( $wgUser->isWatched( $this->mTitle ) ) {
1086  # Already watched
1087  $this->watchthis = true;
1088  }
1089  if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
1090  $this->minoredit = true;
1091  }
1092  if ( $this->textbox1 === false ) {
1093  return false;
1094  }
1095  return true;
1096  }
1097 
1105  protected function getContentObject( $def_content = null ) {
1107 
1108  $content = false;
1109 
1110  // For message page not locally set, use the i18n message.
1111  // For other non-existent articles, use preload text if any.
1112  if ( !$this->mTitle->exists() || $this->section == 'new' ) {
1113  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
1114  # If this is a system message, get the default text.
1115  $msg = $this->mTitle->getDefaultMessageText();
1116 
1117  $content = $this->toEditContent( $msg );
1118  }
1119  if ( $content === false ) {
1120  # If requested, preload some text.
1121  $preload = $wgRequest->getVal( 'preload',
1122  // Custom preload text for new sections
1123  $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
1124  $params = $wgRequest->getArray( 'preloadparams', [] );
1125 
1126  $content = $this->getPreloadedContent( $preload, $params );
1127  }
1128  // For existing pages, get text based on "undo" or section parameters.
1129  } else {
1130  if ( $this->section != '' ) {
1131  // Get section edit text (returns $def_text for invalid sections)
1132  $orig = $this->getOriginalContent( $wgUser );
1133  $content = $orig ? $orig->getSection( $this->section ) : null;
1134 
1135  if ( !$content ) {
1136  $content = $def_content;
1137  }
1138  } else {
1139  $undoafter = $wgRequest->getInt( 'undoafter' );
1140  $undo = $wgRequest->getInt( 'undo' );
1141 
1142  if ( $undo > 0 && $undoafter > 0 ) {
1143  $undorev = Revision::newFromId( $undo );
1144  $oldrev = Revision::newFromId( $undoafter );
1145 
1146  # Sanity check, make sure it's the right page,
1147  # the revisions exist and they were not deleted.
1148  # Otherwise, $content will be left as-is.
1149  if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
1150  !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
1151  !$oldrev->isDeleted( Revision::DELETED_TEXT )
1152  ) {
1153  $content = $this->page->getUndoContent( $undorev, $oldrev );
1154 
1155  if ( $content === false ) {
1156  # Warn the user that something went wrong
1157  $undoMsg = 'failure';
1158  } else {
1159  $oldContent = $this->page->getContent( Revision::RAW );
1161  $newContent = $content->preSaveTransform( $this->mTitle, $wgUser, $popts );
1162  if ( $newContent->getModel() !== $oldContent->getModel() ) {
1163  // The undo may change content
1164  // model if its reverting the top
1165  // edit. This can result in
1166  // mismatched content model/format.
1167  $this->contentModel = $newContent->getModel();
1168  $this->contentFormat = $oldrev->getContentFormat();
1169  }
1170 
1171  if ( $newContent->equals( $oldContent ) ) {
1172  # Tell the user that the undo results in no change,
1173  # i.e. the revisions were already undone.
1174  $undoMsg = 'nochange';
1175  $content = false;
1176  } else {
1177  # Inform the user of our success and set an automatic edit summary
1178  $undoMsg = 'success';
1179 
1180  # If we just undid one rev, use an autosummary
1181  $firstrev = $oldrev->getNext();
1182  if ( $firstrev && $firstrev->getId() == $undo ) {
1183  $userText = $undorev->getUserText();
1184  if ( $userText === '' ) {
1185  $undoSummary = $this->context->msg(
1186  'undo-summary-username-hidden',
1187  $undo
1188  )->inContentLanguage()->text();
1189  } else {
1190  $undoSummary = $this->context->msg(
1191  'undo-summary',
1192  $undo,
1193  $userText
1194  )->inContentLanguage()->text();
1195  }
1196  if ( $this->summary === '' ) {
1197  $this->summary = $undoSummary;
1198  } else {
1199  $this->summary = $undoSummary . $this->context->msg( 'colon-separator' )
1200  ->inContentLanguage()->text() . $this->summary;
1201  }
1202  $this->undidRev = $undo;
1203  }
1204  $this->formtype = 'diff';
1205  }
1206  }
1207  } else {
1208  // Failed basic sanity checks.
1209  // Older revisions may have been removed since the link
1210  // was created, or we may simply have got bogus input.
1211  $undoMsg = 'norev';
1212  }
1213 
1214  // Messages: undo-success, undo-failure, undo-norev, undo-nochange
1215  $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
1216  $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
1217  $this->context->msg( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
1218  }
1219 
1220  if ( $content === false ) {
1221  $content = $this->getOriginalContent( $wgUser );
1222  }
1223  }
1224  }
1225 
1226  return $content;
1227  }
1228 
1244  private function getOriginalContent( User $user ) {
1245  if ( $this->section == 'new' ) {
1246  return $this->getCurrentContent();
1247  }
1248  $revision = $this->mArticle->getRevisionFetched();
1249  if ( $revision === null ) {
1250  if ( !$this->contentModel ) {
1251  $this->contentModel = $this->getTitle()->getContentModel();
1252  }
1253  $handler = ContentHandler::getForModelID( $this->contentModel );
1254 
1255  return $handler->makeEmptyContent();
1256  }
1257  $content = $revision->getContent( Revision::FOR_THIS_USER, $user );
1258  return $content;
1259  }
1260 
1273  public function getParentRevId() {
1274  if ( $this->parentRevId ) {
1275  return $this->parentRevId;
1276  } else {
1277  return $this->mArticle->getRevIdFetched();
1278  }
1279  }
1280 
1289  protected function getCurrentContent() {
1290  $rev = $this->page->getRevision();
1291  $content = $rev ? $rev->getContent( Revision::RAW ) : null;
1292 
1293  if ( $content === false || $content === null ) {
1294  if ( !$this->contentModel ) {
1295  $this->contentModel = $this->getTitle()->getContentModel();
1296  }
1297  $handler = ContentHandler::getForModelID( $this->contentModel );
1298 
1299  return $handler->makeEmptyContent();
1300  } elseif ( !$this->undidRev ) {
1301  // Content models should always be the same since we error
1302  // out if they are different before this point (in ->edit()).
1303  // The exception being, during an undo, the current revision might
1304  // differ from the prior revision.
1305  $logger = LoggerFactory::getInstance( 'editpage' );
1306  if ( $this->contentModel !== $rev->getContentModel() ) {
1307  $logger->warning( "Overriding content model from current edit {prev} to {new}", [
1308  'prev' => $this->contentModel,
1309  'new' => $rev->getContentModel(),
1310  'title' => $this->getTitle()->getPrefixedDBkey(),
1311  'method' => __METHOD__
1312  ] );
1313  $this->contentModel = $rev->getContentModel();
1314  }
1315 
1316  // Given that the content models should match, the current selected
1317  // format should be supported.
1318  if ( !$content->isSupportedFormat( $this->contentFormat ) ) {
1319  $logger->warning( "Current revision content format unsupported. Overriding {prev} to {new}", [
1320 
1321  'prev' => $this->contentFormat,
1322  'new' => $rev->getContentFormat(),
1323  'title' => $this->getTitle()->getPrefixedDBkey(),
1324  'method' => __METHOD__
1325  ] );
1326  $this->contentFormat = $rev->getContentFormat();
1327  }
1328  }
1329  return $content;
1330  }
1331 
1339  public function setPreloadedContent( Content $content ) {
1340  $this->mPreloadContent = $content;
1341  }
1342 
1354  protected function getPreloadedContent( $preload, $params = [] ) {
1355  global $wgUser;
1356 
1357  if ( !empty( $this->mPreloadContent ) ) {
1358  return $this->mPreloadContent;
1359  }
1360 
1361  $handler = ContentHandler::getForModelID( $this->contentModel );
1362 
1363  if ( $preload === '' ) {
1364  return $handler->makeEmptyContent();
1365  }
1366 
1367  $title = Title::newFromText( $preload );
1368  # Check for existence to avoid getting MediaWiki:Noarticletext
1369  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
1370  // TODO: somehow show a warning to the user!
1371  return $handler->makeEmptyContent();
1372  }
1373 
1375  if ( $page->isRedirect() ) {
1377  # Same as before
1378  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
1379  // TODO: somehow show a warning to the user!
1380  return $handler->makeEmptyContent();
1381  }
1383  }
1384 
1385  $parserOptions = ParserOptions::newFromUser( $wgUser );
1387 
1388  if ( !$content ) {
1389  // TODO: somehow show a warning to the user!
1390  return $handler->makeEmptyContent();
1391  }
1392 
1393  if ( $content->getModel() !== $handler->getModelID() ) {
1394  $converted = $content->convert( $handler->getModelID() );
1395 
1396  if ( !$converted ) {
1397  // TODO: somehow show a warning to the user!
1398  wfDebug( "Attempt to preload incompatible content: " .
1399  "can't convert " . $content->getModel() .
1400  " to " . $handler->getModelID() );
1401 
1402  return $handler->makeEmptyContent();
1403  }
1404 
1405  $content = $converted;
1406  }
1407 
1408  return $content->preloadTransform( $title, $parserOptions, $params );
1409  }
1410 
1418  function tokenOk( &$request ) {
1419  global $wgUser;
1420  $token = $request->getVal( 'wpEditToken' );
1421  $this->mTokenOk = $wgUser->matchEditToken( $token );
1422  $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
1423  return $this->mTokenOk;
1424  }
1425 
1442  protected function setPostEditCookie( $statusValue ) {
1443  $revisionId = $this->page->getLatest();
1444  $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1445 
1446  $val = 'saved';
1447  if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) {
1448  $val = 'created';
1449  } elseif ( $this->oldid ) {
1450  $val = 'restored';
1451  }
1452 
1453  $response = RequestContext::getMain()->getRequest()->response();
1454  $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION, [
1455  'httpOnly' => false,
1456  ] );
1457  }
1458 
1465  public function attemptSave( &$resultDetails = false ) {
1466  global $wgUser;
1467 
1468  # Allow bots to exempt some edits from bot flagging
1469  $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
1470  $status = $this->internalAttemptSave( $resultDetails, $bot );
1471 
1472  Hooks::run( 'EditPage::attemptSave:after', [ $this, $status, $resultDetails ] );
1473 
1474  return $status;
1475  }
1476 
1486  private function handleStatus( Status $status, $resultDetails ) {
1488 
1493  if ( $status->value == self::AS_SUCCESS_UPDATE
1494  || $status->value == self::AS_SUCCESS_NEW_ARTICLE
1495  ) {
1496  $this->didSave = true;
1497  if ( !$resultDetails['nullEdit'] ) {
1498  $this->setPostEditCookie( $status->value );
1499  }
1500  }
1501 
1502  // "wpExtraQueryRedirect" is a hidden input to modify
1503  // after save URL and is not used by actual edit form
1504  $request = RequestContext::getMain()->getRequest();
1505  $extraQueryRedirect = $request->getVal( 'wpExtraQueryRedirect' );
1506 
1507  switch ( $status->value ) {
1515  case self::AS_END:
1518  return true;
1519 
1520  case self::AS_HOOK_ERROR:
1521  return false;
1522 
1524  case self::AS_PARSE_ERROR:
1525  $wgOut->addWikiText( '<div class="error">' . "\n" . $status->getWikiText() . '</div>' );
1526  return true;
1527 
1529  $query = $resultDetails['redirect'] ? 'redirect=no' : '';
1530  if ( $extraQueryRedirect ) {
1531  if ( $query === '' ) {
1532  $query = $extraQueryRedirect;
1533  } else {
1534  $query = $query . '&' . $extraQueryRedirect;
1535  }
1536  }
1537  $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
1538  $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
1539  return false;
1540 
1542  $extraQuery = '';
1543  $sectionanchor = $resultDetails['sectionanchor'];
1544 
1545  // Give extensions a chance to modify URL query on update
1546  Hooks::run(
1547  'ArticleUpdateBeforeRedirect',
1548  [ $this->mArticle, &$sectionanchor, &$extraQuery ]
1549  );
1550 
1551  if ( $resultDetails['redirect'] ) {
1552  if ( $extraQuery == '' ) {
1553  $extraQuery = 'redirect=no';
1554  } else {
1555  $extraQuery = 'redirect=no&' . $extraQuery;
1556  }
1557  }
1558  if ( $extraQueryRedirect ) {
1559  if ( $extraQuery === '' ) {
1560  $extraQuery = $extraQueryRedirect;
1561  } else {
1562  $extraQuery = $extraQuery . '&' . $extraQueryRedirect;
1563  }
1564  }
1565 
1566  $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1567  return false;
1568 
1569  case self::AS_SPAM_ERROR:
1570  $this->spamPageWithContent( $resultDetails['spam'] );
1571  return false;
1572 
1574  throw new UserBlockedError( $wgUser->getBlock() );
1575 
1578  throw new PermissionsError( 'upload' );
1579 
1582  throw new PermissionsError( 'edit' );
1583 
1585  throw new ReadOnlyError;
1586 
1587  case self::AS_RATE_LIMITED:
1588  throw new ThrottledError();
1589 
1591  $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
1592  throw new PermissionsError( $permission );
1593 
1595  throw new PermissionsError( 'editcontentmodel' );
1596 
1597  default:
1598  // We don't recognize $status->value. The only way that can happen
1599  // is if an extension hook aborted from inside ArticleSave.
1600  // Render the status object into $this->hookError
1601  // FIXME this sucks, we should just use the Status object throughout
1602  $this->hookError = '<div class="error">' ."\n" . $status->getWikiText() .
1603  '</div>';
1604  return true;
1605  }
1606  }
1607 
1618  // Run old style post-section-merge edit filter
1619  if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged',
1620  [ $this, $content, &$this->hookError, $this->summary ],
1621  '1.21'
1622  ) ) {
1623  # Error messages etc. could be handled within the hook...
1624  $status->fatal( 'hookaborted' );
1625  $status->value = self::AS_HOOK_ERROR;
1626  return false;
1627  } elseif ( $this->hookError != '' ) {
1628  # ...or the hook could be expecting us to produce an error
1629  $status->fatal( 'hookaborted' );
1631  return false;
1632  }
1633 
1634  // Run new style post-section-merge edit filter
1635  if ( !Hooks::run( 'EditFilterMergedContent',
1636  [ $this->mArticle->getContext(), $content, $status, $this->summary,
1637  $user, $this->minoredit ] )
1638  ) {
1639  # Error messages etc. could be handled within the hook...
1640  if ( $status->isGood() ) {
1641  $status->fatal( 'hookaborted' );
1642  // Not setting $this->hookError here is a hack to allow the hook
1643  // to cause a return to the edit page without $this->hookError
1644  // being set. This is used by ConfirmEdit to display a captcha
1645  // without any error message cruft.
1646  } else {
1647  $this->hookError = $status->getWikiText();
1648  }
1649  // Use the existing $status->value if the hook set it
1650  if ( !$status->value ) {
1651  $status->value = self::AS_HOOK_ERROR;
1652  }
1653  return false;
1654  } elseif ( !$status->isOK() ) {
1655  # ...or the hook could be expecting us to produce an error
1656  // FIXME this sucks, we should just use the Status object throughout
1657  $this->hookError = $status->getWikiText();
1658  $status->fatal( 'hookaborted' );
1660  return false;
1661  }
1662 
1663  return true;
1664  }
1665 
1672  private function newSectionSummary( &$sectionanchor = null ) {
1673  global $wgParser;
1674 
1675  if ( $this->sectiontitle !== '' ) {
1676  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
1677  // If no edit summary was specified, create one automatically from the section
1678  // title and have it link to the new section. Otherwise, respect the summary as
1679  // passed.
1680  if ( $this->summary === '' ) {
1681  $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
1682  return $this->context->msg( 'newsectionsummary' )
1683  ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1684  }
1685  } elseif ( $this->summary !== '' ) {
1686  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
1687  # This is a new section, so create a link to the new section
1688  # in the revision summary.
1689  $cleanSummary = $wgParser->stripSectionName( $this->summary );
1690  return $this->context->msg( 'newsectionsummary' )
1691  ->rawParams( $cleanSummary )->inContentLanguage()->text();
1692  }
1693  return $this->summary;
1694  }
1695 
1720  function internalAttemptSave( &$result, $bot = false ) {
1722  global $wgContentHandlerUseDB;
1723 
1725 
1726  if ( !Hooks::run( 'EditPage::attemptSave', [ $this ] ) ) {
1727  wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
1728  $status->fatal( 'hookaborted' );
1729  $status->value = self::AS_HOOK_ERROR;
1730  return $status;
1731  }
1732 
1733  $spam = $wgRequest->getText( 'wpAntispam' );
1734  if ( $spam !== '' ) {
1735  wfDebugLog(
1736  'SimpleAntiSpam',
1737  $wgUser->getName() .
1738  ' editing "' .
1739  $this->mTitle->getPrefixedText() .
1740  '" submitted bogus field "' .
1741  $spam .
1742  '"'
1743  );
1744  $status->fatal( 'spamprotectionmatch', false );
1745  $status->value = self::AS_SPAM_ERROR;
1746  return $status;
1747  }
1748 
1749  try {
1750  # Construct Content object
1751  $textbox_content = $this->toEditContent( $this->textbox1 );
1752  } catch ( MWContentSerializationException $ex ) {
1753  $status->fatal(
1754  'content-failed-to-parse',
1755  $this->contentModel,
1756  $this->contentFormat,
1757  $ex->getMessage()
1758  );
1759  $status->value = self::AS_PARSE_ERROR;
1760  return $status;
1761  }
1762 
1763  # Check image redirect
1764  if ( $this->mTitle->getNamespace() == NS_FILE &&
1765  $textbox_content->isRedirect() &&
1766  !$wgUser->isAllowed( 'upload' )
1767  ) {
1769  $status->setResult( false, $code );
1770 
1771  return $status;
1772  }
1773 
1774  # Check for spam
1775  $match = self::matchSummarySpamRegex( $this->summary );
1776  if ( $match === false && $this->section == 'new' ) {
1777  # $wgSpamRegex is enforced on this new heading/summary because, unlike
1778  # regular summaries, it is added to the actual wikitext.
1779  if ( $this->sectiontitle !== '' ) {
1780  # This branch is taken when the API is used with the 'sectiontitle' parameter.
1781  $match = self::matchSpamRegex( $this->sectiontitle );
1782  } else {
1783  # This branch is taken when the "Add Topic" user interface is used, or the API
1784  # is used with the 'summary' parameter.
1785  $match = self::matchSpamRegex( $this->summary );
1786  }
1787  }
1788  if ( $match === false ) {
1789  $match = self::matchSpamRegex( $this->textbox1 );
1790  }
1791  if ( $match !== false ) {
1792  $result['spam'] = $match;
1793  $ip = $wgRequest->getIP();
1794  $pdbk = $this->mTitle->getPrefixedDBkey();
1795  $match = str_replace( "\n", '', $match );
1796  wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
1797  $status->fatal( 'spamprotectionmatch', $match );
1798  $status->value = self::AS_SPAM_ERROR;
1799  return $status;
1800  }
1801  if ( !Hooks::run(
1802  'EditFilter',
1803  [ $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ] )
1804  ) {
1805  # Error messages etc. could be handled within the hook...
1806  $status->fatal( 'hookaborted' );
1807  $status->value = self::AS_HOOK_ERROR;
1808  return $status;
1809  } elseif ( $this->hookError != '' ) {
1810  # ...or the hook could be expecting us to produce an error
1811  $status->fatal( 'hookaborted' );
1813  return $status;
1814  }
1815 
1816  if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) {
1817  // Auto-block user's IP if the account was "hard" blocked
1818  if ( !wfReadOnly() ) {
1819  $wgUser->spreadAnyEditBlock();
1820  }
1821  # Check block state against master, thus 'false'.
1822  $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
1823  return $status;
1824  }
1825 
1826  $this->contentLength = strlen( $this->textbox1 );
1827  if ( $this->contentLength > $wgMaxArticleSize * 1024 ) {
1828  // Error will be displayed by showEditForm()
1829  $this->tooBig = true;
1830  $status->setResult( false, self::AS_CONTENT_TOO_BIG );
1831  return $status;
1832  }
1833 
1834  if ( !$wgUser->isAllowed( 'edit' ) ) {
1835  if ( $wgUser->isAnon() ) {
1836  $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
1837  return $status;
1838  } else {
1839  $status->fatal( 'readonlytext' );
1841  return $status;
1842  }
1843  }
1844 
1845  $changingContentModel = false;
1846  if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
1847  if ( !$wgContentHandlerUseDB ) {
1848  $status->fatal( 'editpage-cannot-use-custom-model' );
1850  return $status;
1851  } elseif ( !$wgUser->isAllowed( 'editcontentmodel' ) ) {
1852  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
1853  return $status;
1854  }
1855  // Make sure the user can edit the page under the new content model too
1856  $titleWithNewContentModel = clone $this->mTitle;
1857  $titleWithNewContentModel->setContentModel( $this->contentModel );
1858  if ( !$titleWithNewContentModel->userCan( 'editcontentmodel', $wgUser )
1859  || !$titleWithNewContentModel->userCan( 'edit', $wgUser )
1860  ) {
1861  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
1862  return $status;
1863  }
1864 
1865  $changingContentModel = true;
1866  $oldContentModel = $this->mTitle->getContentModel();
1867  }
1868 
1869  if ( $this->changeTags ) {
1870  $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange(
1871  $this->changeTags, $wgUser );
1872  if ( !$changeTagsStatus->isOK() ) {
1873  $changeTagsStatus->value = self::AS_CHANGE_TAG_ERROR;
1874  return $changeTagsStatus;
1875  }
1876  }
1877 
1878  if ( wfReadOnly() ) {
1879  $status->fatal( 'readonlytext' );
1881  return $status;
1882  }
1883  if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 )
1884  || ( $changingContentModel && $wgUser->pingLimiter( 'editcontentmodel' ) )
1885  ) {
1886  $status->fatal( 'actionthrottledtext' );
1887  $status->value = self::AS_RATE_LIMITED;
1888  return $status;
1889  }
1890 
1891  # If the article has been deleted while editing, don't save it without
1892  # confirmation
1893  if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
1894  $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
1895  return $status;
1896  }
1897 
1898  # Load the page data from the master. If anything changes in the meantime,
1899  # we detect it by using page_latest like a token in a 1 try compare-and-swap.
1900  $this->page->loadPageData( 'fromdbmaster' );
1901  $new = !$this->page->exists();
1902 
1903  if ( $new ) {
1904  // Late check for create permission, just in case *PARANOIA*
1905  if ( !$this->mTitle->userCan( 'create', $wgUser ) ) {
1906  $status->fatal( 'nocreatetext' );
1908  wfDebug( __METHOD__ . ": no create permission\n" );
1909  return $status;
1910  }
1911 
1912  // Don't save a new page if it's blank or if it's a MediaWiki:
1913  // message with content equivalent to default (allow empty pages
1914  // in this case to disable messages, see bug 50124)
1915  $defaultMessageText = $this->mTitle->getDefaultMessageText();
1916  if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false ) {
1917  $defaultText = $defaultMessageText;
1918  } else {
1919  $defaultText = '';
1920  }
1921 
1922  if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) {
1923  $this->blankArticle = true;
1924  $status->fatal( 'blankarticle' );
1925  $status->setResult( false, self::AS_BLANK_ARTICLE );
1926  return $status;
1927  }
1928 
1929  if ( !$this->runPostMergeFilters( $textbox_content, $status, $wgUser ) ) {
1930  return $status;
1931  }
1932 
1933  $content = $textbox_content;
1934 
1935  $result['sectionanchor'] = '';
1936  if ( $this->section == 'new' ) {
1937  if ( $this->sectiontitle !== '' ) {
1938  // Insert the section title above the content.
1939  $content = $content->addSectionHeader( $this->sectiontitle );
1940  } elseif ( $this->summary !== '' ) {
1941  // Insert the section title above the content.
1942  $content = $content->addSectionHeader( $this->summary );
1943  }
1944  $this->summary = $this->newSectionSummary( $result['sectionanchor'] );
1945  }
1946 
1948 
1949  } else { # not $new
1950 
1951  # Article exists. Check for edit conflict.
1952 
1953  $this->page->clear(); # Force reload of dates, etc.
1954  $timestamp = $this->page->getTimestamp();
1955  $latest = $this->page->getLatest();
1956 
1957  wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
1958 
1959  // Check editRevId if set, which handles same-second timestamp collisions
1960  if ( $timestamp != $this->edittime
1961  || ( $this->editRevId !== null && $this->editRevId != $latest )
1962  ) {
1963  $this->isConflict = true;
1964  if ( $this->section == 'new' ) {
1965  if ( $this->page->getUserText() == $wgUser->getName() &&
1966  $this->page->getComment() == $this->newSectionSummary()
1967  ) {
1968  // Probably a duplicate submission of a new comment.
1969  // This can happen when CDN resends a request after
1970  // a timeout but the first one actually went through.
1971  wfDebug( __METHOD__
1972  . ": duplicate new section submission; trigger edit conflict!\n" );
1973  } else {
1974  // New comment; suppress conflict.
1975  $this->isConflict = false;
1976  wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
1977  }
1978  } elseif ( $this->section == ''
1980  DB_MASTER, $this->mTitle->getArticleID(),
1981  $wgUser->getId(), $this->edittime
1982  )
1983  ) {
1984  # Suppress edit conflict with self, except for section edits where merging is required.
1985  wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
1986  $this->isConflict = false;
1987  }
1988  }
1989 
1990  // If sectiontitle is set, use it, otherwise use the summary as the section title.
1991  if ( $this->sectiontitle !== '' ) {
1992  $sectionTitle = $this->sectiontitle;
1993  } else {
1994  $sectionTitle = $this->summary;
1995  }
1996 
1997  $content = null;
1998 
1999  if ( $this->isConflict ) {
2000  wfDebug( __METHOD__
2001  . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
2002  . " (id '{$this->editRevId}') (article time '{$timestamp}')\n" );
2003  // @TODO: replaceSectionAtRev() with base ID (not prior current) for ?oldid=X case
2004  // ...or disable section editing for non-current revisions (not exposed anyway).
2005  if ( $this->editRevId !== null ) {
2006  $content = $this->page->replaceSectionAtRev(
2007  $this->section,
2008  $textbox_content,
2009  $sectionTitle,
2010  $this->editRevId
2011  );
2012  } else {
2013  $content = $this->page->replaceSectionContent(
2014  $this->section,
2015  $textbox_content,
2016  $sectionTitle,
2017  $this->edittime
2018  );
2019  }
2020  } else {
2021  wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
2022  $content = $this->page->replaceSectionContent(
2023  $this->section,
2024  $textbox_content,
2025  $sectionTitle
2026  );
2027  }
2028 
2029  if ( is_null( $content ) ) {
2030  wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
2031  $this->isConflict = true;
2032  $content = $textbox_content; // do not try to merge here!
2033  } elseif ( $this->isConflict ) {
2034  # Attempt merge
2035  if ( $this->mergeChangesIntoContent( $content ) ) {
2036  // Successful merge! Maybe we should tell the user the good news?
2037  $this->isConflict = false;
2038  wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
2039  } else {
2040  $this->section = '';
2041  $this->textbox1 = ContentHandler::getContentText( $content );
2042  wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
2043  }
2044  }
2045 
2046  if ( $this->isConflict ) {
2047  $status->setResult( false, self::AS_CONFLICT_DETECTED );
2048  return $status;
2049  }
2050 
2051  if ( !$this->runPostMergeFilters( $content, $status, $wgUser ) ) {
2052  return $status;
2053  }
2054 
2055  if ( $this->section == 'new' ) {
2056  // Handle the user preference to force summaries here
2057  if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) {
2058  $this->missingSummary = true;
2059  $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
2061  return $status;
2062  }
2063 
2064  // Do not allow the user to post an empty comment
2065  if ( $this->textbox1 == '' ) {
2066  $this->missingComment = true;
2067  $status->fatal( 'missingcommenttext' );
2069  return $status;
2070  }
2071  } elseif ( !$this->allowBlankSummary
2072  && !$content->equals( $this->getOriginalContent( $wgUser ) )
2073  && !$content->isRedirect()
2074  && md5( $this->summary ) == $this->autoSumm
2075  ) {
2076  $this->missingSummary = true;
2077  $status->fatal( 'missingsummary' );
2079  return $status;
2080  }
2081 
2082  # All's well
2083  $sectionanchor = '';
2084  if ( $this->section == 'new' ) {
2085  $this->summary = $this->newSectionSummary( $sectionanchor );
2086  } elseif ( $this->section != '' ) {
2087  # Try to get a section anchor from the section source, redirect
2088  # to edited section if header found.
2089  # XXX: Might be better to integrate this into Article::replaceSectionAtRev
2090  # for duplicate heading checking and maybe parsing.
2091  $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
2092  # We can't deal with anchors, includes, html etc in the header for now,
2093  # headline would need to be parsed to improve this.
2094  if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
2095  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
2096  }
2097  }
2098  $result['sectionanchor'] = $sectionanchor;
2099 
2100  // Save errors may fall down to the edit form, but we've now
2101  // merged the section into full text. Clear the section field
2102  // so that later submission of conflict forms won't try to
2103  // replace that into a duplicated mess.
2104  $this->textbox1 = $this->toEditText( $content );
2105  $this->section = '';
2106 
2108  }
2109 
2110  if ( !$this->allowSelfRedirect
2111  && $content->isRedirect()
2112  && $content->getRedirectTarget()->equals( $this->getTitle() )
2113  ) {
2114  // If the page already redirects to itself, don't warn.
2115  $currentTarget = $this->getCurrentContent()->getRedirectTarget();
2116  if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
2117  $this->selfRedirect = true;
2118  $status->fatal( 'selfredirect' );
2120  return $status;
2121  }
2122  }
2123 
2124  // Check for length errors again now that the section is merged in
2125  $this->contentLength = strlen( $this->toEditText( $content ) );
2126  if ( $this->contentLength > $wgMaxArticleSize * 1024 ) {
2127  $this->tooBig = true;
2128  $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
2129  return $status;
2130  }
2131 
2133  ( $new ? EDIT_NEW : EDIT_UPDATE ) |
2134  ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
2135  ( $bot ? EDIT_FORCE_BOT : 0 );
2136 
2137  $doEditStatus = $this->page->doEditContent(
2138  $content,
2139  $this->summary,
2140  $flags,
2141  false,
2142  $wgUser,
2143  $content->getDefaultFormat(),
2145  );
2146 
2147  if ( !$doEditStatus->isOK() ) {
2148  // Failure from doEdit()
2149  // Show the edit conflict page for certain recognized errors from doEdit(),
2150  // but don't show it for errors from extension hooks
2151  $errors = $doEditStatus->getErrorsArray();
2152  if ( in_array( $errors[0][0],
2153  [ 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ] )
2154  ) {
2155  $this->isConflict = true;
2156  // Destroys data doEdit() put in $status->value but who cares
2157  $doEditStatus->value = self::AS_END;
2158  }
2159  return $doEditStatus;
2160  }
2161 
2162  $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
2163  if ( $result['nullEdit'] ) {
2164  // We don't know if it was a null edit until now, so increment here
2165  $wgUser->pingLimiter( 'linkpurge' );
2166  }
2167  $result['redirect'] = $content->isRedirect();
2168 
2169  $this->updateWatchlist();
2170 
2171  // If the content model changed, add a log entry
2172  if ( $changingContentModel ) {
2174  $wgUser,
2175  $new ? false : $oldContentModel,
2176  $this->contentModel,
2177  $this->summary
2178  );
2179  }
2180 
2181  return $status;
2182  }
2183 
2190  protected function addContentModelChangeLogEntry( User $user, $oldModel, $newModel, $reason ) {
2191  $new = $oldModel === false;
2192  $log = new ManualLogEntry( 'contentmodel', $new ? 'new' : 'change' );
2193  $log->setPerformer( $user );
2194  $log->setTarget( $this->mTitle );
2195  $log->setComment( $reason );
2196  $log->setParameters( [
2197  '4::oldmodel' => $oldModel,
2198  '5::newmodel' => $newModel
2199  ] );
2200  $logid = $log->insert();
2201  $log->publish( $logid );
2202  }
2203 
2207  protected function updateWatchlist() {
2208  global $wgUser;
2209 
2210  if ( !$wgUser->isLoggedIn() ) {
2211  return;
2212  }
2213 
2214  $user = $wgUser;
2216  $watch = $this->watchthis;
2217  // Do this in its own transaction to reduce contention...
2218  DeferredUpdates::addCallableUpdate( function () use ( $user, $title, $watch ) {
2219  if ( $watch == $user->isWatched( $title, User::IGNORE_USER_RIGHTS ) ) {
2220  return; // nothing to change
2221  }
2223  } );
2224  }
2225 
2237  private function mergeChangesIntoContent( &$editContent ) {
2238 
2239  $db = wfGetDB( DB_MASTER );
2240 
2241  // This is the revision the editor started from
2242  $baseRevision = $this->getBaseRevision();
2243  $baseContent = $baseRevision ? $baseRevision->getContent() : null;
2244 
2245  if ( is_null( $baseContent ) ) {
2246  return false;
2247  }
2248 
2249  // The current state, we want to merge updates into it
2250  $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
2251  $currentContent = $currentRevision ? $currentRevision->getContent() : null;
2252 
2253  if ( is_null( $currentContent ) ) {
2254  return false;
2255  }
2256 
2257  $handler = ContentHandler::getForModelID( $baseContent->getModel() );
2258 
2259  $result = $handler->merge3( $baseContent, $editContent, $currentContent );
2260 
2261  if ( $result ) {
2262  $editContent = $result;
2263  // Update parentRevId to what we just merged.
2264  $this->parentRevId = $currentRevision->getId();
2265  return true;
2266  }
2267 
2268  return false;
2269  }
2270 
2276  function getBaseRevision() {
2277  if ( !$this->mBaseRevision ) {
2278  $db = wfGetDB( DB_MASTER );
2279  $this->mBaseRevision = $this->editRevId
2280  ? Revision::newFromId( $this->editRevId, Revision::READ_LATEST )
2281  : Revision::loadFromTimestamp( $db, $this->mTitle, $this->edittime );
2282  }
2283  return $this->mBaseRevision;
2284  }
2285 
2293  public static function matchSpamRegex( $text ) {
2294  global $wgSpamRegex;
2295  // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
2296  $regexes = (array)$wgSpamRegex;
2297  return self::matchSpamRegexInternal( $text, $regexes );
2298  }
2299 
2307  public static function matchSummarySpamRegex( $text ) {
2308  global $wgSummarySpamRegex;
2309  $regexes = (array)$wgSummarySpamRegex;
2310  return self::matchSpamRegexInternal( $text, $regexes );
2311  }
2312 
2318  protected static function matchSpamRegexInternal( $text, $regexes ) {
2319  foreach ( $regexes as $regex ) {
2320  $matches = [];
2321  if ( preg_match( $regex, $text, $matches ) ) {
2322  return $matches[0];
2323  }
2324  }
2325  return false;
2326  }
2327 
2328  function setHeaders() {
2329  global $wgOut, $wgUser, $wgAjaxEditStash;
2330 
2331  $wgOut->addModules( 'mediawiki.action.edit' );
2332  $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
2333 
2334  if ( $wgUser->getOption( 'showtoolbar' ) ) {
2335  // The addition of default buttons is handled by getEditToolbar() which
2336  // has its own dependency on this module. The call here ensures the module
2337  // is loaded in time (it has position "top") for other modules to register
2338  // buttons (e.g. extensions, gadgets, user scripts).
2339  $wgOut->addModules( 'mediawiki.toolbar' );
2340  }
2341 
2342  if ( $wgUser->getOption( 'uselivepreview' ) ) {
2343  $wgOut->addModules( 'mediawiki.action.edit.preview' );
2344  }
2345 
2346  if ( $wgUser->getOption( 'useeditwarning' ) ) {
2347  $wgOut->addModules( 'mediawiki.action.edit.editWarning' );
2348  }
2349 
2350  # Enabled article-related sidebar, toplinks, etc.
2351  $wgOut->setArticleRelated( true );
2352 
2353  $contextTitle = $this->getContextTitle();
2354  if ( $this->isConflict ) {
2355  $msg = 'editconflict';
2356  } elseif ( $contextTitle->exists() && $this->section != '' ) {
2357  $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
2358  } else {
2359  $msg = $contextTitle->exists()
2360  || ( $contextTitle->getNamespace() == NS_MEDIAWIKI
2361  && $contextTitle->getDefaultMessageText() !== false
2362  )
2363  ? 'editing'
2364  : 'creating';
2365  }
2366 
2367  # Use the title defined by DISPLAYTITLE magic word when present
2368  # NOTE: getDisplayTitle() returns HTML while getPrefixedText() returns plain text.
2369  # setPageTitle() treats the input as wikitext, which should be safe in either case.
2370  $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
2371  if ( $displayTitle === false ) {
2372  $displayTitle = $contextTitle->getPrefixedText();
2373  }
2374  $wgOut->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
2375  # Transmit the name of the message to JavaScript for live preview
2376  # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
2377  $wgOut->addJsConfigVars( [
2378  'wgEditMessage' => $msg,
2379  'wgAjaxEditStash' => $wgAjaxEditStash,
2380  ] );
2381  }
2382 
2386  protected function showIntro() {
2388  if ( $this->suppressIntro ) {
2389  return;
2390  }
2391 
2392  $namespace = $this->mTitle->getNamespace();
2393 
2394  if ( $namespace == NS_MEDIAWIKI ) {
2395  # Show a warning if editing an interface message
2396  $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
2397  # If this is a default message (but not css or js),
2398  # show a hint that it is translatable on translatewiki.net
2399  if ( !$this->mTitle->hasContentModel( CONTENT_MODEL_CSS )
2400  && !$this->mTitle->hasContentModel( CONTENT_MODEL_JAVASCRIPT )
2401  ) {
2402  $defaultMessageText = $this->mTitle->getDefaultMessageText();
2403  if ( $defaultMessageText !== false ) {
2404  $wgOut->wrapWikiMsg( "<div class='mw-translateinterface'>\n$1\n</div>",
2405  'translateinterface' );
2406  }
2407  }
2408  } elseif ( $namespace == NS_FILE ) {
2409  # Show a hint to shared repo
2410  $file = wfFindFile( $this->mTitle );
2411  if ( $file && !$file->isLocal() ) {
2412  $descUrl = $file->getDescriptionUrl();
2413  # there must be a description url to show a hint to shared repo
2414  if ( $descUrl ) {
2415  if ( !$this->mTitle->exists() ) {
2416  $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
2417  'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
2418  ] );
2419  } else {
2420  $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
2421  'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
2422  ] );
2423  }
2424  }
2425  }
2426  }
2427 
2428  # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2429  # Show log extract when the user is currently blocked
2430  if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
2431  $username = explode( '/', $this->mTitle->getText(), 2 )[0];
2432  $user = User::newFromName( $username, false /* allow IP users*/ );
2433  $ip = User::isIP( $username );
2434  $block = Block::newFromTarget( $user, $user );
2435  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
2436  $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2437  [ 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ] );
2438  } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
2439  # Show log extract if the user is currently blocked
2441  $wgOut,
2442  'block',
2443  MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
2444  '',
2445  [
2446  'lim' => 1,
2447  'showIfEmpty' => false,
2448  'msgKey' => [
2449  'blocked-notice-logextract',
2450  $user->getName() # Support GENDER in notice
2451  ]
2452  ]
2453  );
2454  }
2455  }
2456  # Try to add a custom edit intro, or use the standard one if this is not possible.
2457  if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
2459  $this->context->msg( 'helppage' )->inContentLanguage()->text()
2460  ) );
2461  if ( $wgUser->isLoggedIn() ) {
2462  $wgOut->wrapWikiMsg(
2463  // Suppress the external link icon, consider the help url an internal one
2464  "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2465  [
2466  'newarticletext',
2467  $helpLink
2468  ]
2469  );
2470  } else {
2471  $wgOut->wrapWikiMsg(
2472  // Suppress the external link icon, consider the help url an internal one
2473  "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2474  [
2475  'newarticletextanon',
2476  $helpLink
2477  ]
2478  );
2479  }
2480  }
2481  # Give a notice if the user is editing a deleted/moved page...
2482  if ( !$this->mTitle->exists() ) {
2483  LogEventsList::showLogExtract( $wgOut, [ 'delete', 'move' ], $this->mTitle,
2484  '',
2485  [
2486  'lim' => 10,
2487  'conds' => [ "log_action != 'revision'" ],
2488  'showIfEmpty' => false,
2489  'msgKey' => [ 'recreate-moveddeleted-warn' ]
2490  ]
2491  );
2492  }
2493  }
2494 
2500  protected function showCustomIntro() {
2501  if ( $this->editintro ) {
2502  $title = Title::newFromText( $this->editintro );
2503  if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
2504  global $wgOut;
2505  // Added using template syntax, to take <noinclude>'s into account.
2506  $wgOut->addWikiTextTitleTidy(
2507  '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>',
2509  );
2510  return true;
2511  }
2512  }
2513  return false;
2514  }
2515 
2534  protected function toEditText( $content ) {
2535  if ( $content === null || $content === false || is_string( $content ) ) {
2536  return $content;
2537  }
2538 
2539  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2540  throw new MWException( 'This content model is not supported: ' . $content->getModel() );
2541  }
2542 
2543  return $content->serialize( $this->contentFormat );
2544  }
2545 
2562  protected function toEditContent( $text ) {
2563  if ( $text === false || $text === null ) {
2564  return $text;
2565  }
2566 
2567  $content = ContentHandler::makeContent( $text, $this->getTitle(),
2568  $this->contentModel, $this->contentFormat );
2569 
2570  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2571  throw new MWException( 'This content model is not supported: ' . $content->getModel() );
2572  }
2573 
2574  return $content;
2575  }
2576 
2585  function showEditForm( $formCallback = null ) {
2587 
2588  # need to parse the preview early so that we know which templates are used,
2589  # otherwise users with "show preview after edit box" will get a blank list
2590  # we parse this near the beginning so that setHeaders can do the title
2591  # setting work instead of leaving it in getPreviewText
2592  $previewOutput = '';
2593  if ( $this->formtype == 'preview' ) {
2594  $previewOutput = $this->getPreviewText();
2595  }
2596 
2597  Hooks::run( 'EditPage::showEditForm:initial', [ &$this, &$wgOut ] );
2598 
2599  $this->setHeaders();
2600 
2601  if ( $this->showHeader() === false ) {
2602  return;
2603  }
2604 
2605  $wgOut->addHTML( $this->editFormPageTop );
2606 
2607  if ( $wgUser->getOption( 'previewontop' ) ) {
2608  $this->displayPreviewArea( $previewOutput, true );
2609  }
2610 
2611  $wgOut->addHTML( $this->editFormTextTop );
2612 
2613  $showToolbar = true;
2614  if ( $this->wasDeletedSinceLastEdit() ) {
2615  if ( $this->formtype == 'save' ) {
2616  // Hide the toolbar and edit area, user can click preview to get it back
2617  // Add an confirmation checkbox and explanation.
2618  $showToolbar = false;
2619  } else {
2620  $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2621  'deletedwhileediting' );
2622  }
2623  }
2624 
2625  // @todo add EditForm plugin interface and use it here!
2626  // search for textarea1 and textares2, and allow EditForm to override all uses.
2627  $wgOut->addHTML( Html::openElement(
2628  'form',
2629  [
2630  'id' => self::EDITFORM_ID,
2631  'name' => self::EDITFORM_ID,
2632  'method' => 'post',
2633  'action' => $this->getActionURL( $this->getContextTitle() ),
2634  'enctype' => 'multipart/form-data'
2635  ]
2636  ) );
2637 
2638  if ( is_callable( $formCallback ) ) {
2639  wfWarn( 'The $formCallback parameter to ' . __METHOD__ . 'is deprecated' );
2640  call_user_func_array( $formCallback, [ &$wgOut ] );
2641  }
2642 
2643  // Add an empty field to trip up spambots
2644  $wgOut->addHTML(
2645  Xml::openElement( 'div', [ 'id' => 'antispam-container', 'style' => 'display: none;' ] )
2646  . Html::rawElement(
2647  'label',
2648  [ 'for' => 'wpAntispam' ],
2649  $this->context->msg( 'simpleantispam-label' )->parse()
2650  )
2651  . Xml::element(
2652  'input',
2653  [
2654  'type' => 'text',
2655  'name' => 'wpAntispam',
2656  'id' => 'wpAntispam',
2657  'value' => ''
2658  ]
2659  )
2660  . Xml::closeElement( 'div' )
2661  );
2662 
2663  Hooks::run( 'EditPage::showEditForm:fields', [ &$this, &$wgOut ] );
2664 
2665  // Put these up at the top to ensure they aren't lost on early form submission
2666  $this->showFormBeforeText();
2667 
2668  if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
2669  $username = $this->lastDelete->user_name;
2670  $comment = $this->lastDelete->log_comment;
2671 
2672  // It is better to not parse the comment at all than to have templates expanded in the middle
2673  // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
2674  $key = $comment === ''
2675  ? 'confirmrecreate-noreason'
2676  : 'confirmrecreate';
2677  $wgOut->addHTML(
2678  '<div class="mw-confirm-recreate">' .
2679  $this->context->msg( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
2680  Xml::checkLabel( $this->context->msg( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
2681  [ 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' ]
2682  ) .
2683  '</div>'
2684  );
2685  }
2686 
2687  # When the summary is hidden, also hide them on preview/show changes
2688  if ( $this->nosummary ) {
2689  $wgOut->addHTML( Html::hidden( 'nosummary', true ) );
2690  }
2691 
2692  # If a blank edit summary was previously provided, and the appropriate
2693  # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2694  # user being bounced back more than once in the event that a summary
2695  # is not required.
2696  # ####
2697  # For a bit more sophisticated detection of blank summaries, hash the
2698  # automatic one and pass that in the hidden field wpAutoSummary.
2699  if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
2700  $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
2701  }
2702 
2703  if ( $this->undidRev ) {
2704  $wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
2705  }
2706 
2707  if ( $this->selfRedirect ) {
2708  $wgOut->addHTML( Html::hidden( 'wpIgnoreSelfRedirect', true ) );
2709  }
2710 
2711  if ( $this->hasPresetSummary ) {
2712  // If a summary has been preset using &summary= we don't want to prompt for
2713  // a different summary. Only prompt for a summary if the summary is blanked.
2714  // (Bug 17416)
2715  $this->autoSumm = md5( '' );
2716  }
2717 
2718  $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
2719  $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
2720 
2721  $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
2722  $wgOut->addHTML( Html::hidden( 'parentRevId', $this->getParentRevId() ) );
2723 
2724  $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
2725  $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
2726 
2727  if ( $this->section == 'new' ) {
2728  $this->showSummaryInput( true, $this->summary );
2729  $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
2730  }
2731 
2732  $wgOut->addHTML( $this->editFormTextBeforeContent );
2733 
2734  if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
2735  $wgOut->addHTML( EditPage::getEditToolbar( $this->mTitle ) );
2736  }
2737 
2738  if ( $this->blankArticle ) {
2739  $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) );
2740  }
2741 
2742  if ( $this->isConflict ) {
2743  // In an edit conflict bypass the overridable content form method
2744  // and fallback to the raw wpTextbox1 since editconflicts can't be
2745  // resolved between page source edits and custom ui edits using the
2746  // custom edit ui.
2747  $this->textbox2 = $this->textbox1;
2748 
2749  $content = $this->getCurrentContent();
2750  $this->textbox1 = $this->toEditText( $content );
2751 
2752  $this->showTextbox1();
2753  } else {
2754  $this->showContentForm();
2755  }
2756 
2757  $wgOut->addHTML( $this->editFormTextAfterContent );
2758 
2759  $this->showStandardInputs();
2760 
2761  $this->showFormAfterText();
2762 
2763  $this->showTosSummary();
2764 
2765  $this->showEditTools();
2766 
2767  $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
2768 
2769  $wgOut->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
2770 
2771  $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'hiddencats' ],
2772  Linker::formatHiddenCategories( $this->page->getHiddenCategories() ) ) );
2773 
2774  $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'limitreport' ],
2775  self::getPreviewLimitReport( $this->mParserOutput ) ) );
2776 
2777  $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
2778 
2779  if ( $this->isConflict ) {
2780  try {
2781  $this->showConflict();
2782  } catch ( MWContentSerializationException $ex ) {
2783  // this can't really happen, but be nice if it does.
2784  $msg = $this->context->msg(
2785  'content-failed-to-parse',
2786  $this->contentModel,
2787  $this->contentFormat,
2788  $ex->getMessage()
2789  );
2790  $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
2791  }
2792  }
2793 
2794  // Set a hidden field so JS knows what edit form mode we are in
2795  if ( $this->isConflict ) {
2796  $mode = 'conflict';
2797  } elseif ( $this->preview ) {
2798  $mode = 'preview';
2799  } elseif ( $this->diff ) {
2800  $mode = 'diff';
2801  } else {
2802  $mode = 'text';
2803  }
2804  $wgOut->addHTML( Html::hidden( 'mode', $mode, [ 'id' => 'mw-edit-mode' ] ) );
2805 
2806  // Marker for detecting truncated form data. This must be the last
2807  // parameter sent in order to be of use, so do not move me.
2808  $wgOut->addHTML( Html::hidden( 'wpUltimateParam', true ) );
2809  $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
2810 
2811  if ( !$wgUser->getOption( 'previewontop' ) ) {
2812  $this->displayPreviewArea( $previewOutput, false );
2813  }
2814 
2815  }
2816 
2824  protected function makeTemplatesOnThisPageList( array $templates ) {
2825  $templateListFormatter = new TemplatesOnThisPageFormatter(
2826  $this->context, MediaWikiServices::getInstance()->getLinkRenderer()
2827  );
2828 
2829  // preview if preview, else section if section, else false
2830  $type = false;
2831  if ( $this->preview ) {
2832  $type = 'preview';
2833  } elseif ( $this->section != '' ) {
2834  $type = 'section';
2835  }
2836 
2837  return Html::rawElement( 'div', [ 'class' => 'templatesUsed' ],
2838  $templateListFormatter->format( $templates, $type )
2839  );
2840 
2841  }
2842 
2849  public static function extractSectionTitle( $text ) {
2850  preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
2851  if ( !empty( $matches[2] ) ) {
2852  global $wgParser;
2853  return $wgParser->stripSectionName( trim( $matches[2] ) );
2854  } else {
2855  return false;
2856  }
2857  }
2858 
2862  protected function showHeader() {
2864  global $wgAllowUserCss, $wgAllowUserJs;
2865 
2866  if ( $this->mTitle->isTalkPage() ) {
2867  $wgOut->addWikiMsg( 'talkpagetext' );
2868  }
2869 
2870  // Add edit notices
2871  $editNotices = $this->mTitle->getEditNotices( $this->oldid );
2872  if ( count( $editNotices ) ) {
2873  $wgOut->addHTML( implode( "\n", $editNotices ) );
2874  } else {
2875  $msg = $this->context->msg( 'editnotice-notext' );
2876  if ( !$msg->isDisabled() ) {
2877  $wgOut->addHTML(
2878  '<div class="mw-editnotice-notext">'
2879  . $msg->parseAsBlock()
2880  . '</div>'
2881  );
2882  }
2883  }
2884 
2885  if ( $this->isConflict ) {
2886  $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
2887  $this->editRevId = $this->page->getLatest();
2888  } else {
2889  if ( $this->section != '' && !$this->isSectionEditSupported() ) {
2890  // We use $this->section to much before this and getVal('wgSection') directly in other places
2891  // at this point we can't reset $this->section to '' to fallback to non-section editing.
2892  // Someone is welcome to try refactoring though
2893  $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
2894  return false;
2895  }
2896 
2897  if ( $this->section != '' && $this->section != 'new' ) {
2898  if ( !$this->summary && !$this->preview && !$this->diff ) {
2899  $sectionTitle = self::extractSectionTitle( $this->textbox1 ); // FIXME: use Content object
2900  if ( $sectionTitle !== false ) {
2901  $this->summary = "/* $sectionTitle */ ";
2902  }
2903  }
2904  }
2905 
2906  if ( $this->missingComment ) {
2907  $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
2908  }
2909 
2910  if ( $this->missingSummary && $this->section != 'new' ) {
2911  $wgOut->wrapWikiMsg( "<div id='mw-missingsummary'>\n$1\n</div>", 'missingsummary' );
2912  }
2913 
2914  if ( $this->missingSummary && $this->section == 'new' ) {
2915  $wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1\n</div>", 'missingcommentheader' );
2916  }
2917 
2918  if ( $this->blankArticle ) {
2919  $wgOut->wrapWikiMsg( "<div id='mw-blankarticle'>\n$1\n</div>", 'blankarticle' );
2920  }
2921 
2922  if ( $this->selfRedirect ) {
2923  $wgOut->wrapWikiMsg( "<div id='mw-selfredirect'>\n$1\n</div>", 'selfredirect' );
2924  }
2925 
2926  if ( $this->hookError !== '' ) {
2927  $wgOut->addWikiText( $this->hookError );
2928  }
2929 
2930  if ( !$this->checkUnicodeCompliantBrowser() ) {
2931  $wgOut->addWikiMsg( 'nonunicodebrowser' );
2932  }
2933 
2934  if ( $this->section != 'new' ) {
2935  $revision = $this->mArticle->getRevisionFetched();
2936  if ( $revision ) {
2937  // Let sysop know that this will make private content public if saved
2938 
2939  if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) {
2940  $wgOut->wrapWikiMsg(
2941  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
2942  'rev-deleted-text-permission'
2943  );
2944  } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
2945  $wgOut->wrapWikiMsg(
2946  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
2947  'rev-deleted-text-view'
2948  );
2949  }
2950 
2951  if ( !$revision->isCurrent() ) {
2952  $this->mArticle->setOldSubtitle( $revision->getId() );
2953  $wgOut->addWikiMsg( 'editingold' );
2954  }
2955  } elseif ( $this->mTitle->exists() ) {
2956  // Something went wrong
2957 
2958  $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
2959  [ 'missing-revision', $this->oldid ] );
2960  }
2961  }
2962  }
2963 
2964  if ( wfReadOnly() ) {
2965  $wgOut->wrapWikiMsg(
2966  "<div id=\"mw-read-only-warning\">\n$1\n</div>",
2967  [ 'readonlywarning', wfReadOnlyReason() ]
2968  );
2969  } elseif ( $wgUser->isAnon() ) {
2970  if ( $this->formtype != 'preview' ) {
2971  $wgOut->wrapWikiMsg(
2972  "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
2973  [ 'anoneditwarning',
2974  // Log-in link
2975  SpecialPage::getTitleFor( 'Userlogin' )->getFullURL( [
2976  'returnto' => $this->getTitle()->getPrefixedDBkey()
2977  ] ),
2978  // Sign-up link
2979  SpecialPage::getTitleFor( 'CreateAccount' )->getFullURL( [
2980  'returnto' => $this->getTitle()->getPrefixedDBkey()
2981  ] )
2982  ]
2983  );
2984  } else {
2985  $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
2986  'anonpreviewwarning'
2987  );
2988  }
2989  } else {
2990  if ( $this->isCssJsSubpage ) {
2991  # Check the skin exists
2992  if ( $this->isWrongCaseCssJsPage ) {
2993  $wgOut->wrapWikiMsg(
2994  "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>",
2995  [ 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ]
2996  );
2997  }
2998  if ( $this->getTitle()->isSubpageOf( $wgUser->getUserPage() ) ) {
2999  $wgOut->wrapWikiMsg( '<div class="mw-usercssjspublic">$1</div>',
3000  $this->isCssSubpage ? 'usercssispublic' : 'userjsispublic'
3001  );
3002  if ( $this->formtype !== 'preview' ) {
3003  if ( $this->isCssSubpage && $wgAllowUserCss ) {
3004  $wgOut->wrapWikiMsg(
3005  "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
3006  [ 'usercssyoucanpreview' ]
3007  );
3008  }
3009 
3010  if ( $this->isJsSubpage && $wgAllowUserJs ) {
3011  $wgOut->wrapWikiMsg(
3012  "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
3013  [ 'userjsyoucanpreview' ]
3014  );
3015  }
3016  }
3017  }
3018  }
3019  }
3020 
3021  if ( $this->mTitle->isProtected( 'edit' ) &&
3022  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [ '' ]
3023  ) {
3024  # Is the title semi-protected?
3025  if ( $this->mTitle->isSemiProtected() ) {
3026  $noticeMsg = 'semiprotectedpagewarning';
3027  } else {
3028  # Then it must be protected based on static groups (regular)
3029  $noticeMsg = 'protectedpagewarning';
3030  }
3031  LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
3032  [ 'lim' => 1, 'msgKey' => [ $noticeMsg ] ] );
3033  }
3034  if ( $this->mTitle->isCascadeProtected() ) {
3035  # Is this page under cascading protection from some source pages?
3036 
3037  list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
3038  $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
3039  $cascadeSourcesCount = count( $cascadeSources );
3040  if ( $cascadeSourcesCount > 0 ) {
3041  # Explain, and list the titles responsible
3042  foreach ( $cascadeSources as $page ) {
3043  $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
3044  }
3045  }
3046  $notice .= '</div>';
3047  $wgOut->wrapWikiMsg( $notice, [ 'cascadeprotectedwarning', $cascadeSourcesCount ] );
3048  }
3049  if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
3050  LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
3051  [ 'lim' => 1,
3052  'showIfEmpty' => false,
3053  'msgKey' => [ 'titleprotectedwarning' ],
3054  'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ] );
3055  }
3056 
3057  if ( $this->contentLength === false ) {
3058  $this->contentLength = strlen( $this->textbox1 );
3059  }
3060 
3061  if ( $this->tooBig || $this->contentLength > $wgMaxArticleSize * 1024 ) {
3062  $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
3063  [
3064  'longpageerror',
3065  $wgLang->formatNum( round( $this->contentLength / 1024, 3 ) ),
3066  $wgLang->formatNum( $wgMaxArticleSize )
3067  ]
3068  );
3069  } else {
3070  if ( !$this->context->msg( 'longpage-hint' )->isDisabled() ) {
3071  $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
3072  [
3073  'longpage-hint',
3074  $wgLang->formatSize( strlen( $this->textbox1 ) ),
3075  strlen( $this->textbox1 )
3076  ]
3077  );
3078  }
3079  }
3080  # Add header copyright warning
3081  $this->showHeaderCopyrightWarning();
3082 
3083  return true;
3084  }
3085 
3100  function getSummaryInput( $summary = "", $labelText = null,
3101  $inputAttrs = null, $spanLabelAttrs = null
3102  ) {
3103  // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
3104  $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
3105  'id' => 'wpSummary',
3106  'maxlength' => '200',
3107  'tabindex' => '1',
3108  'size' => 60,
3109  'spellcheck' => 'true',
3110  ] + Linker::tooltipAndAccesskeyAttribs( 'summary' );
3111 
3112  $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : [] ) + [
3113  'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
3114  'id' => "wpSummaryLabel"
3115  ];
3116 
3117  $label = null;
3118  if ( $labelText ) {
3119  $label = Xml::tags(
3120  'label',
3121  $inputAttrs['id'] ? [ 'for' => $inputAttrs['id'] ] : null,
3122  $labelText
3123  );
3124  $label = Xml::tags( 'span', $spanLabelAttrs, $label );
3125  }
3126 
3127  $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
3128 
3129  return [ $label, $input ];
3130  }
3131 
3138  protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
3139  global $wgOut;
3140  # Add a class if 'missingsummary' is triggered to allow styling of the summary line
3141  $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
3142  if ( $isSubjectPreview ) {
3143  if ( $this->nosummary ) {
3144  return;
3145  }
3146  } else {
3147  if ( !$this->mShowSummaryField ) {
3148  return;
3149  }
3150  }
3151  $labelText = $this->context->msg( $isSubjectPreview ? 'subject' : 'summary' )->parse();
3152  list( $label, $input ) = $this->getSummaryInput(
3153  $summary,
3154  $labelText,
3155  [ 'class' => $summaryClass ],
3156  []
3157  );
3158  $wgOut->addHTML( "{$label} {$input}" );
3159  }
3160 
3168  protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
3169  // avoid spaces in preview, gets always trimmed on save
3170  $summary = trim( $summary );
3171  if ( !$summary || ( !$this->preview && !$this->diff ) ) {
3172  return "";
3173  }
3174 
3175  global $wgParser;
3176 
3177  if ( $isSubjectPreview ) {
3178  $summary = $this->context->msg( 'newsectionsummary' )
3179  ->rawParams( $wgParser->stripSectionName( $summary ) )
3180  ->inContentLanguage()->text();
3181  }
3182 
3183  $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
3184 
3185  $summary = $this->context->msg( $message )->parse()
3186  . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
3187  return Xml::tags( 'div', [ 'class' => 'mw-summary-preview' ], $summary );
3188  }
3189 
3190  protected function showFormBeforeText() {
3191  global $wgOut;
3192  $section = htmlspecialchars( $this->section );
3193  $wgOut->addHTML( <<<HTML
3194 <input type='hidden' value="{$section}" name="wpSection"/>
3195 <input type='hidden' value="{$this->starttime}" name="wpStarttime" />
3196 <input type='hidden' value="{$this->edittime}" name="wpEdittime" />
3197 <input type='hidden' value="{$this->editRevId}" name="editRevId" />
3198 <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
3199 
3200 HTML
3201  );
3202  if ( !$this->checkUnicodeCompliantBrowser() ) {
3203  $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
3204  }
3205  }
3206 
3207  protected function showFormAfterText() {
3221  $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
3222  }
3223 
3232  protected function showContentForm() {
3233  $this->showTextbox1();
3234  }
3235 
3244  protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
3245  if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
3246  $attribs = [ 'style' => 'display:none;' ];
3247  } else {
3248  $classes = []; // Textarea CSS
3249  if ( $this->mTitle->isProtected( 'edit' ) &&
3250  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [ '' ]
3251  ) {
3252  # Is the title semi-protected?
3253  if ( $this->mTitle->isSemiProtected() ) {
3254  $classes[] = 'mw-textarea-sprotected';
3255  } else {
3256  # Then it must be protected based on static groups (regular)
3257  $classes[] = 'mw-textarea-protected';
3258  }
3259  # Is the title cascade-protected?
3260  if ( $this->mTitle->isCascadeProtected() ) {
3261  $classes[] = 'mw-textarea-cprotected';
3262  }
3263  }
3264 
3265  $attribs = [ 'tabindex' => 1 ];
3266 
3267  if ( is_array( $customAttribs ) ) {
3269  }
3270 
3271  if ( count( $classes ) ) {
3272  if ( isset( $attribs['class'] ) ) {
3273  $classes[] = $attribs['class'];
3274  }
3275  $attribs['class'] = implode( ' ', $classes );
3276  }
3277  }
3278 
3279  $this->showTextbox(
3280  $textoverride !== null ? $textoverride : $this->textbox1,
3281  'wpTextbox1',
3282  $attribs
3283  );
3284  }
3285 
3286  protected function showTextbox2() {
3287  $this->showTextbox( $this->textbox2, 'wpTextbox2', [ 'tabindex' => 6, 'readonly' ] );
3288  }
3289 
3290  protected function showTextbox( $text, $name, $customAttribs = [] ) {
3292 
3293  $wikitext = $this->safeUnicodeOutput( $text );
3294  if ( strval( $wikitext ) !== '' ) {
3295  // Ensure there's a newline at the end, otherwise adding lines
3296  // is awkward.
3297  // But don't add a newline if the ext is empty, or Firefox in XHTML
3298  // mode will show an extra newline. A bit annoying.
3299  $wikitext .= "\n";
3300  }
3301 
3302  $attribs = $customAttribs + [
3303  'accesskey' => ',',
3304  'id' => $name,
3305  'cols' => $wgUser->getIntOption( 'cols' ),
3306  'rows' => $wgUser->getIntOption( 'rows' ),
3307  // Avoid PHP notices when appending preferences
3308  // (appending allows customAttribs['style'] to still work).
3309  'style' => ''
3310  ];
3311 
3312  // The following classes can be used here:
3313  // * mw-editfont-default
3314  // * mw-editfont-monospace
3315  // * mw-editfont-sans-serif
3316  // * mw-editfont-serif
3317  $class = 'mw-editfont-' . $wgUser->getOption( 'editfont' );
3318 
3319  if ( isset( $attribs['class'] ) ) {
3320  if ( is_string( $attribs['class'] ) ) {
3321  $attribs['class'] .= ' ' . $class;
3322  } elseif ( is_array( $attribs['class'] ) ) {
3323  $attribs['class'][] = $class;
3324  }
3325  } else {
3326  $attribs['class'] = $class;
3327  }
3328 
3329  $pageLang = $this->mTitle->getPageLanguage();
3330  $attribs['lang'] = $pageLang->getHtmlCode();
3331  $attribs['dir'] = $pageLang->getDir();
3332 
3333  $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
3334  }
3335 
3336  protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
3337  global $wgOut;
3338  $classes = [];
3339  if ( $isOnTop ) {
3340  $classes[] = 'ontop';
3341  }
3342 
3343  $attribs = [ 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) ];
3344 
3345  if ( $this->formtype != 'preview' ) {
3346  $attribs['style'] = 'display: none;';
3347  }
3348 
3349  $wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
3350 
3351  if ( $this->formtype == 'preview' ) {
3352  $this->showPreview( $previewOutput );
3353  } else {
3354  // Empty content container for LivePreview
3355  $pageViewLang = $this->mTitle->getPageViewLanguage();
3356  $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3357  'class' => 'mw-content-' . $pageViewLang->getDir() ];
3358  $wgOut->addHTML( Html::rawElement( 'div', $attribs ) );
3359  }
3360 
3361  $wgOut->addHTML( '</div>' );
3362 
3363  if ( $this->formtype == 'diff' ) {
3364  try {
3365  $this->showDiff();
3366  } catch ( MWContentSerializationException $ex ) {
3367  $msg = $this->context->msg(
3368  'content-failed-to-parse',
3369  $this->contentModel,
3370  $this->contentFormat,
3371  $ex->getMessage()
3372  );
3373  $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
3374  }
3375  }
3376  }
3377 
3384  protected function showPreview( $text ) {
3385  global $wgOut;
3386  if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
3387  $this->mArticle->openShowCategory();
3388  }
3389  # This hook seems slightly odd here, but makes things more
3390  # consistent for extensions.
3391  Hooks::run( 'OutputPageBeforeHTML', [ &$wgOut, &$text ] );
3392  $wgOut->addHTML( $text );
3393  if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
3394  $this->mArticle->closeShowCategory();
3395  }
3396  }
3397 
3405  function showDiff() {
3407 
3408  $oldtitlemsg = 'currentrev';
3409  # if message does not exist, show diff against the preloaded default
3410  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
3411  $oldtext = $this->mTitle->getDefaultMessageText();
3412  if ( $oldtext !== false ) {
3413  $oldtitlemsg = 'defaultmessagetext';
3414  $oldContent = $this->toEditContent( $oldtext );
3415  } else {
3416  $oldContent = null;
3417  }
3418  } else {
3419  $oldContent = $this->getCurrentContent();
3420  }
3421 
3422  $textboxContent = $this->toEditContent( $this->textbox1 );
3423  if ( $this->editRevId !== null ) {
3424  $newContent = $this->page->replaceSectionAtRev(
3425  $this->section, $textboxContent, $this->summary, $this->editRevId
3426  );
3427  } else {
3428  $newContent = $this->page->replaceSectionContent(
3429  $this->section, $textboxContent, $this->summary, $this->edittime
3430  );
3431  }
3432 
3433  if ( $newContent ) {
3434  ContentHandler::runLegacyHooks( 'EditPageGetDiffText', [ $this, &$newContent ], '1.21' );
3435  Hooks::run( 'EditPageGetDiffContent', [ $this, &$newContent ] );
3436 
3438  $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
3439  }
3440 
3441  if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
3442  $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
3443  $newtitle = $this->context->msg( 'yourtext' )->parse();
3444 
3445  if ( !$oldContent ) {
3446  $oldContent = $newContent->getContentHandler()->makeEmptyContent();
3447  }
3448 
3449  if ( !$newContent ) {
3450  $newContent = $oldContent->getContentHandler()->makeEmptyContent();
3451  }
3452 
3453  $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
3454  $de->setContent( $oldContent, $newContent );
3455 
3456  $difftext = $de->getDiff( $oldtitle, $newtitle );
3457  $de->showDiffStyle();
3458  } else {
3459  $difftext = '';
3460  }
3461 
3462  $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
3463  }
3464 
3468  protected function showHeaderCopyrightWarning() {
3469  $msg = 'editpage-head-copy-warn';
3470  if ( !$this->context->msg( $msg )->isDisabled() ) {
3471  global $wgOut;
3472  $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
3473  'editpage-head-copy-warn' );
3474  }
3475  }
3476 
3485  protected function showTosSummary() {
3486  $msg = 'editpage-tos-summary';
3487  Hooks::run( 'EditPageTosSummary', [ $this->mTitle, &$msg ] );
3488  if ( !$this->context->msg( $msg )->isDisabled() ) {
3489  global $wgOut;
3490  $wgOut->addHTML( '<div class="mw-tos-summary">' );
3491  $wgOut->addWikiMsg( $msg );
3492  $wgOut->addHTML( '</div>' );
3493  }
3494  }
3495 
3496  protected function showEditTools() {
3497  global $wgOut;
3498  $wgOut->addHTML( '<div class="mw-editTools">' .
3499  $this->context->msg( 'edittools' )->inContentLanguage()->parse() .
3500  '</div>' );
3501  }
3502 
3509  protected function getCopywarn() {
3510  return self::getCopyrightWarning( $this->mTitle );
3511  }
3512 
3520  public static function getCopyrightWarning( $title, $format = 'plain', $langcode = null ) {
3521  global $wgRightsText;
3522  if ( $wgRightsText ) {
3523  $copywarnMsg = [ 'copyrightwarning',
3524  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]',
3525  $wgRightsText ];
3526  } else {
3527  $copywarnMsg = [ 'copyrightwarning2',
3528  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' ];
3529  }
3530  // Allow for site and per-namespace customization of contribution/copyright notice.
3531  Hooks::run( 'EditPageCopyrightWarning', [ $title, &$copywarnMsg ] );
3532 
3533  $msg = call_user_func_array( 'wfMessage', $copywarnMsg )->title( $title );
3534  if ( $langcode ) {
3535  $msg->inLanguage( $langcode );
3536  }
3537  return "<div id=\"editpage-copywarn\">\n" .
3538  $msg->$format() . "\n</div>";
3539  }
3540 
3548  public static function getPreviewLimitReport( $output ) {
3549  if ( !$output || !$output->getLimitReportData() ) {
3550  return '';
3551  }
3552 
3553  $limitReport = Html::rawElement( 'div', [ 'class' => 'mw-limitReportExplanation' ],
3554  wfMessage( 'limitreport-title' )->parseAsBlock()
3555  );
3556 
3557  // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
3558  $limitReport .= Html::openElement( 'div', [ 'class' => 'preview-limit-report-wrapper' ] );
3559 
3560  $limitReport .= Html::openElement( 'table', [
3561  'class' => 'preview-limit-report wikitable'
3562  ] ) .
3563  Html::openElement( 'tbody' );
3564 
3565  foreach ( $output->getLimitReportData() as $key => $value ) {
3566  if ( Hooks::run( 'ParserLimitReportFormat',
3567  [ $key, &$value, &$limitReport, true, true ]
3568  ) ) {
3569  $keyMsg = wfMessage( $key );
3570  $valueMsg = wfMessage( [ "$key-value-html", "$key-value" ] );
3571  if ( !$valueMsg->exists() ) {
3572  $valueMsg = new RawMessage( '$1' );
3573  }
3574  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3575  $limitReport .= Html::openElement( 'tr' ) .
3576  Html::rawElement( 'th', null, $keyMsg->parse() ) .
3577  Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) .
3578  Html::closeElement( 'tr' );
3579  }
3580  }
3581  }
3582 
3583  $limitReport .= Html::closeElement( 'tbody' ) .
3584  Html::closeElement( 'table' ) .
3585  Html::closeElement( 'div' );
3586 
3587  return $limitReport;
3588  }
3589 
3590  protected function showStandardInputs( &$tabindex = 2 ) {
3591  global $wgOut;
3592  $wgOut->addHTML( "<div class='editOptions'>\n" );
3593 
3594  if ( $this->section != 'new' ) {
3595  $this->showSummaryInput( false, $this->summary );
3596  $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
3597  }
3598 
3599  $checkboxes = $this->getCheckboxes( $tabindex,
3600  [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ] );
3601  $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" );
3602 
3603  // Show copyright warning.
3604  $wgOut->addWikiText( $this->getCopywarn() );
3605  $wgOut->addHTML( $this->editFormTextAfterWarn );
3606 
3607  $wgOut->addHTML( "<div class='editButtons'>\n" );
3608  $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
3609 
3610  $cancel = $this->getCancelLink();
3611  if ( $cancel !== '' ) {
3612  $cancel .= Html::element( 'span',
3613  [ 'class' => 'mw-editButtons-pipe-separator' ],
3614  $this->context->msg( 'pipe-separator' )->text() );
3615  }
3616 
3617  $message = $this->context->msg( 'edithelppage' )->inContentLanguage()->text();
3618  $edithelpurl = Skin::makeInternalOrExternalUrl( $message );
3619  $attrs = [
3620  'target' => 'helpwindow',
3621  'href' => $edithelpurl,
3622  ];
3623  $edithelp = Html::linkButton( $this->context->msg( 'edithelp' )->text(),
3624  $attrs, [ 'mw-ui-quiet' ] ) .
3625  $this->context->msg( 'word-separator' )->escaped() .
3626  $this->context->msg( 'newwindow' )->parse();
3627 
3628  $wgOut->addHTML( " <span class='cancelLink'>{$cancel}</span>\n" );
3629  $wgOut->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" );
3630  $wgOut->addHTML( "</div><!-- editButtons -->\n" );
3631 
3632  Hooks::run( 'EditPage::showStandardInputs:options', [ $this, $wgOut, &$tabindex ] );
3633 
3634  $wgOut->addHTML( "</div><!-- editOptions -->\n" );
3635  }
3636 
3641  protected function showConflict() {
3642  global $wgOut;
3643 
3644  if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$this, &$wgOut ] ) ) {
3645  $stats = $wgOut->getContext()->getStats();
3646  $stats->increment( 'edit.failures.conflict' );
3647  // Only include 'standard' namespaces to avoid creating unknown numbers of statsd metrics
3648  if (
3649  $this->mTitle->getNamespace() >= NS_MAIN &&
3650  $this->mTitle->getNamespace() <= NS_CATEGORY_TALK
3651  ) {
3652  $stats->increment( 'edit.failures.conflict.byNamespaceId.' . $this->mTitle->getNamespace() );
3653  }
3654 
3655  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
3656 
3657  $content1 = $this->toEditContent( $this->textbox1 );
3658  $content2 = $this->toEditContent( $this->textbox2 );
3659 
3660  $handler = ContentHandler::getForModelID( $this->contentModel );
3661  $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
3662  $de->setContent( $content2, $content1 );
3663  $de->showDiff(
3664  $this->context->msg( 'yourtext' )->parse(),
3665  $this->context->msg( 'storedversion' )->text()
3666  );
3667 
3668  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
3669  $this->showTextbox2();
3670  }
3671  }
3672 
3676  public function getCancelLink() {
3677  $cancelParams = [];
3678  if ( !$this->isConflict && $this->oldid > 0 ) {
3679  $cancelParams['oldid'] = $this->oldid;
3680  } elseif ( $this->getContextTitle()->isRedirect() ) {
3681  $cancelParams['redirect'] = 'no';
3682  }
3683  $attrs = [ 'id' => 'mw-editform-cancel' ];
3684 
3685  return Linker::linkKnown(
3686  $this->getContextTitle(),
3687  $this->context->msg( 'cancel' )->parse(),
3688  Html::buttonAttributes( $attrs, [ 'mw-ui-quiet' ] ),
3689  $cancelParams
3690  );
3691  }
3692 
3702  protected function getActionURL( Title $title ) {
3703  return $title->getLocalURL( [ 'action' => $this->action ] );
3704  }
3705 
3713  protected function wasDeletedSinceLastEdit() {
3714  if ( $this->deletedSinceEdit !== null ) {
3715  return $this->deletedSinceEdit;
3716  }
3717 
3718  $this->deletedSinceEdit = false;
3719 
3720  if ( !$this->mTitle->exists() && $this->mTitle->isDeletedQuick() ) {
3721  $this->lastDelete = $this->getLastDelete();
3722  if ( $this->lastDelete ) {
3723  $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
3724  if ( $deleteTime > $this->starttime ) {
3725  $this->deletedSinceEdit = true;
3726  }
3727  }
3728  }
3729 
3730  return $this->deletedSinceEdit;
3731  }
3732 
3736  protected function getLastDelete() {
3737  $dbr = wfGetDB( DB_REPLICA );
3738  $data = $dbr->selectRow(
3739  [ 'logging', 'user' ],
3740  [
3741  'log_type',
3742  'log_action',
3743  'log_timestamp',
3744  'log_user',
3745  'log_namespace',
3746  'log_title',
3747  'log_comment',
3748  'log_params',
3749  'log_deleted',
3750  'user_name'
3751  ], [
3752  'log_namespace' => $this->mTitle->getNamespace(),
3753  'log_title' => $this->mTitle->getDBkey(),
3754  'log_type' => 'delete',
3755  'log_action' => 'delete',
3756  'user_id=log_user'
3757  ],
3758  __METHOD__,
3759  [ 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ]
3760  );
3761  // Quick paranoid permission checks...
3762  if ( is_object( $data ) ) {
3763  if ( $data->log_deleted & LogPage::DELETED_USER ) {
3764  $data->user_name = $this->context->msg( 'rev-deleted-user' )->escaped();
3765  }
3766 
3767  if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
3768  $data->log_comment = $this->context->msg( 'rev-deleted-comment' )->escaped();
3769  }
3770  }
3771 
3772  return $data;
3773  }
3774 
3780  function getPreviewText() {
3781  global $wgOut, $wgRawHtml, $wgLang;
3782  global $wgAllowUserCss, $wgAllowUserJs;
3783 
3784  $stats = $wgOut->getContext()->getStats();
3785 
3786  if ( $wgRawHtml && !$this->mTokenOk ) {
3787  // Could be an offsite preview attempt. This is very unsafe if
3788  // HTML is enabled, as it could be an attack.
3789  $parsedNote = '';
3790  if ( $this->textbox1 !== '' ) {
3791  // Do not put big scary notice, if previewing the empty
3792  // string, which happens when you initially edit
3793  // a category page, due to automatic preview-on-open.
3794  $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
3795  $this->context->msg( 'session_fail_preview_html' )->text() . "</div>",
3796  true, /* interface */true );
3797  }
3798  $stats->increment( 'edit.failures.session_loss' );
3799  return $parsedNote;
3800  }
3801 
3802  $note = '';
3803 
3804  try {
3805  $content = $this->toEditContent( $this->textbox1 );
3806 
3807  $previewHTML = '';
3808  if ( !Hooks::run(
3809  'AlternateEditPreview',
3810  [ $this, &$content, &$previewHTML, &$this->mParserOutput ] )
3811  ) {
3812  return $previewHTML;
3813  }
3814 
3815  # provide a anchor link to the editform
3816  $continueEditing = '<span class="mw-continue-editing">' .
3817  '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' .
3818  $this->context->msg( 'continue-editing' )->text() . ']]</span>';
3819  if ( $this->mTriedSave && !$this->mTokenOk ) {
3820  if ( $this->mTokenOkExceptSuffix ) {
3821  $note = $this->context->msg( 'token_suffix_mismatch' )->plain();
3822  $stats->increment( 'edit.failures.bad_token' );
3823  } else {
3824  $note = $this->context->msg( 'session_fail_preview' )->plain();
3825  $stats->increment( 'edit.failures.session_loss' );
3826  }
3827  } elseif ( $this->incompleteForm ) {
3828  $note = $this->context->msg( 'edit_form_incomplete' )->plain();
3829  if ( $this->mTriedSave ) {
3830  $stats->increment( 'edit.failures.incomplete_form' );
3831  }
3832  } else {
3833  $note = $this->context->msg( 'previewnote' )->plain() . ' ' . $continueEditing;
3834  }
3835 
3836  # don't parse non-wikitext pages, show message about preview
3837  if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
3838  if ( $this->mTitle->isCssJsSubpage() ) {
3839  $level = 'user';
3840  } elseif ( $this->mTitle->isCssOrJsPage() ) {
3841  $level = 'site';
3842  } else {
3843  $level = false;
3844  }
3845 
3846  if ( $content->getModel() == CONTENT_MODEL_CSS ) {
3847  $format = 'css';
3848  if ( $level === 'user' && !$wgAllowUserCss ) {
3849  $format = false;
3850  }
3851  } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
3852  $format = 'js';
3853  if ( $level === 'user' && !$wgAllowUserJs ) {
3854  $format = false;
3855  }
3856  } else {
3857  $format = false;
3858  }
3859 
3860  # Used messages to make sure grep find them:
3861  # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
3862  if ( $level && $format ) {
3863  $note = "<div id='mw-{$level}{$format}preview'>" .
3864  $this->context->msg( "{$level}{$format}preview" )->text() .
3865  ' ' . $continueEditing . "</div>";
3866  }
3867  }
3868 
3869  # If we're adding a comment, we need to show the
3870  # summary as the headline
3871  if ( $this->section === "new" && $this->summary !== "" ) {
3872  $content = $content->addSectionHeader( $this->summary );
3873  }
3874 
3875  $hook_args = [ $this, &$content ];
3876  ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args, '1.25' );
3877  Hooks::run( 'EditPageGetPreviewContent', $hook_args );
3878 
3879  $parserResult = $this->doPreviewParse( $content );
3880  $parserOutput = $parserResult['parserOutput'];
3881  $previewHTML = $parserResult['html'];
3882  $this->mParserOutput = $parserOutput;
3883  $wgOut->addParserOutputMetadata( $parserOutput );
3884 
3885  if ( count( $parserOutput->getWarnings() ) ) {
3886  $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
3887  }
3888 
3889  } catch ( MWContentSerializationException $ex ) {
3890  $m = $this->context->msg(
3891  'content-failed-to-parse',
3892  $this->contentModel,
3893  $this->contentFormat,
3894  $ex->getMessage()
3895  );
3896  $note .= "\n\n" . $m->parse();
3897  $previewHTML = '';
3898  }
3899 
3900  if ( $this->isConflict ) {
3901  $conflict = '<h2 id="mw-previewconflict">'
3902  . $this->context->msg( 'previewconflict' )->escaped() . "</h2>\n";
3903  } else {
3904  $conflict = '<hr />';
3905  }
3906 
3907  $previewhead = "<div class='previewnote'>\n" .
3908  '<h2 id="mw-previewheader">' . $this->context->msg( 'preview' )->escaped() . "</h2>" .
3909  $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
3910 
3911  $pageViewLang = $this->mTitle->getPageViewLanguage();
3912  $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3913  'class' => 'mw-content-' . $pageViewLang->getDir() ];
3914  $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
3915 
3916  return $previewhead . $previewHTML . $this->previewTextAfterContent;
3917  }
3918 
3923  protected function getPreviewParserOptions() {
3924  $parserOptions = $this->page->makeParserOptions( $this->mArticle->getContext() );
3925  $parserOptions->setIsPreview( true );
3926  $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
3927  $parserOptions->enableLimitReport();
3928  return $parserOptions;
3929  }
3930 
3940  protected function doPreviewParse( Content $content ) {
3941  global $wgUser;
3942  $parserOptions = $this->getPreviewParserOptions();
3943  $pstContent = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
3944  $scopedCallback = $parserOptions->setupFakeRevision(
3945  $this->mTitle, $pstContent, $wgUser );
3946  $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions );
3947  ScopedCallback::consume( $scopedCallback );
3948  $parserOutput->setEditSectionTokens( false ); // no section edit links
3949  return [
3950  'parserOutput' => $parserOutput,
3951  'html' => $parserOutput->getText() ];
3952  }
3953 
3957  function getTemplates() {
3958  if ( $this->preview || $this->section != '' ) {
3959  $templates = [];
3960  if ( !isset( $this->mParserOutput ) ) {
3961  return $templates;
3962  }
3963  foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
3964  foreach ( array_keys( $template ) as $dbk ) {
3965  $templates[] = Title::makeTitle( $ns, $dbk );
3966  }
3967  }
3968  return $templates;
3969  } else {
3970  return $this->mTitle->getTemplateLinksFrom();
3971  }
3972  }
3973 
3981  static function getEditToolbar( $title = null ) {
3984 
3985  $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
3986  $showSignature = true;
3987  if ( $title ) {
3988  $showSignature = MWNamespace::wantSignatures( $title->getNamespace() );
3989  }
3990 
4000  $toolarray = [
4001  [
4002  'id' => 'mw-editbutton-bold',
4003  'open' => '\'\'\'',
4004  'close' => '\'\'\'',
4005  'sample' => wfMessage( 'bold_sample' )->text(),
4006  'tip' => wfMessage( 'bold_tip' )->text(),
4007  ],
4008  [
4009  'id' => 'mw-editbutton-italic',
4010  'open' => '\'\'',
4011  'close' => '\'\'',
4012  'sample' => wfMessage( 'italic_sample' )->text(),
4013  'tip' => wfMessage( 'italic_tip' )->text(),
4014  ],
4015  [
4016  'id' => 'mw-editbutton-link',
4017  'open' => '[[',
4018  'close' => ']]',
4019  'sample' => wfMessage( 'link_sample' )->text(),
4020  'tip' => wfMessage( 'link_tip' )->text(),
4021  ],
4022  [
4023  'id' => 'mw-editbutton-extlink',
4024  'open' => '[',
4025  'close' => ']',
4026  'sample' => wfMessage( 'extlink_sample' )->text(),
4027  'tip' => wfMessage( 'extlink_tip' )->text(),
4028  ],
4029  [
4030  'id' => 'mw-editbutton-headline',
4031  'open' => "\n== ",
4032  'close' => " ==\n",
4033  'sample' => wfMessage( 'headline_sample' )->text(),
4034  'tip' => wfMessage( 'headline_tip' )->text(),
4035  ],
4036  $imagesAvailable ? [
4037  'id' => 'mw-editbutton-image',
4038  'open' => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
4039  'close' => ']]',
4040  'sample' => wfMessage( 'image_sample' )->text(),
4041  'tip' => wfMessage( 'image_tip' )->text(),
4042  ] : false,
4043  $imagesAvailable ? [
4044  'id' => 'mw-editbutton-media',
4045  'open' => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
4046  'close' => ']]',
4047  'sample' => wfMessage( 'media_sample' )->text(),
4048  'tip' => wfMessage( 'media_tip' )->text(),
4049  ] : false,
4050  [
4051  'id' => 'mw-editbutton-nowiki',
4052  'open' => "<nowiki>",
4053  'close' => "</nowiki>",
4054  'sample' => wfMessage( 'nowiki_sample' )->text(),
4055  'tip' => wfMessage( 'nowiki_tip' )->text(),
4056  ],
4057  $showSignature ? [
4058  'id' => 'mw-editbutton-signature',
4059  'open' => wfMessage( 'sig-text', '~~~~' )->inContentLanguage()->text(),
4060  'close' => '',
4061  'sample' => '',
4062  'tip' => wfMessage( 'sig_tip' )->text(),
4063  ] : false,
4064  [
4065  'id' => 'mw-editbutton-hr',
4066  'open' => "\n----\n",
4067  'close' => '',
4068  'sample' => '',
4069  'tip' => wfMessage( 'hr_tip' )->text(),
4070  ]
4071  ];
4072 
4073  $script = 'mw.loader.using("mediawiki.toolbar", function () {';
4074  foreach ( $toolarray as $tool ) {
4075  if ( !$tool ) {
4076  continue;
4077  }
4078 
4079  $params = [
4080  // Images are defined in ResourceLoaderEditToolbarModule
4081  false,
4082  // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
4083  // Older browsers show a "speedtip" type message only for ALT.
4084  // Ideally these should be different, realistically they
4085  // probably don't need to be.
4086  $tool['tip'],
4087  $tool['open'],
4088  $tool['close'],
4089  $tool['sample'],
4090  $tool['id'],
4091  ];
4092 
4093  $script .= Xml::encodeJsCall(
4094  'mw.toolbar.addButton',
4095  $params,
4096  ResourceLoader::inDebugMode()
4097  );
4098  }
4099 
4100  $script .= '});';
4101  $wgOut->addScript( ResourceLoader::makeInlineScript( $script ) );
4102 
4103  $toolbar = '<div id="toolbar"></div>';
4104 
4105  Hooks::run( 'EditPageBeforeEditToolbar', [ &$toolbar ] );
4106 
4107  return $toolbar;
4108  }
4109 
4120  public function getCheckboxes( &$tabindex, $checked ) {
4121  global $wgUser, $wgUseMediaWikiUIEverywhere;
4122 
4123  $checkboxes = [];
4124 
4125  // don't show the minor edit checkbox if it's a new page or section
4126  if ( !$this->isNew ) {
4127  $checkboxes['minor'] = '';
4128  $minorLabel = $this->context->msg( 'minoredit' )->parse();
4129  if ( $wgUser->isAllowed( 'minoredit' ) ) {
4130  $attribs = [
4131  'tabindex' => ++$tabindex,
4132  'accesskey' => $this->context->msg( 'accesskey-minoredit' )->text(),
4133  'id' => 'wpMinoredit',
4134  ];
4135  $minorEditHtml =
4136  Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
4137  "&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
4138  Xml::expandAttributes( [ 'title' => Linker::titleAttrib( 'minoredit', 'withaccess' ) ] ) .
4139  ">{$minorLabel}</label>";
4140 
4141  if ( $wgUseMediaWikiUIEverywhere ) {
4142  $checkboxes['minor'] = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) .
4143  $minorEditHtml .
4144  Html::closeElement( 'div' );
4145  } else {
4146  $checkboxes['minor'] = $minorEditHtml;
4147  }
4148  }
4149  }
4150 
4151  $watchLabel = $this->context->msg( 'watchthis' )->parse();
4152  $checkboxes['watch'] = '';
4153  if ( $wgUser->isLoggedIn() ) {
4154  $attribs = [
4155  'tabindex' => ++$tabindex,
4156  'accesskey' => $this->context->msg( 'accesskey-watch' )->text(),
4157  'id' => 'wpWatchthis',
4158  ];
4159  $watchThisHtml =
4160  Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
4161  "&#160;<label for='wpWatchthis' id='mw-editpage-watch'" .
4162  Xml::expandAttributes( [ 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ] ) .
4163  ">{$watchLabel}</label>";
4164  if ( $wgUseMediaWikiUIEverywhere ) {
4165  $checkboxes['watch'] = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) .
4166  $watchThisHtml .
4167  Html::closeElement( 'div' );
4168  } else {
4169  $checkboxes['watch'] = $watchThisHtml;
4170  }
4171  }
4172  Hooks::run( 'EditPageBeforeEditChecks', [ &$this, &$checkboxes, &$tabindex ] );
4173  return $checkboxes;
4174  }
4175 
4184  public function getEditButtons( &$tabindex ) {
4185  $buttons = [];
4186 
4187  $labelAsPublish =
4188  $this->mArticle->getContext()->getConfig()->get( 'EditSubmitButtonLabelPublish' );
4189 
4190  // Can't use $this->isNew as that's also true if we're adding a new section to an extant page
4191  if ( $labelAsPublish ) {
4192  $buttonLabelKey = !$this->mTitle->exists() ? 'publishpage' : 'publishchanges';
4193  } else {
4194  $buttonLabelKey = !$this->mTitle->exists() ? 'savearticle' : 'savechanges';
4195  }
4196  $buttonLabel = $this->context->msg( $buttonLabelKey )->text();
4197  $attribs = [
4198  'id' => 'wpSave',
4199  'name' => 'wpSave',
4200  'tabindex' => ++$tabindex,
4201  ] + Linker::tooltipAndAccesskeyAttribs( 'save' );
4202  $buttons['save'] = Html::submitButton( $buttonLabel, $attribs, [ 'mw-ui-progressive' ] );
4203 
4204  ++$tabindex; // use the same for preview and live preview
4205  $attribs = [
4206  'id' => 'wpPreview',
4207  'name' => 'wpPreview',
4208  'tabindex' => $tabindex,
4209  ] + Linker::tooltipAndAccesskeyAttribs( 'preview' );
4210  $buttons['preview'] = Html::submitButton( $this->context->msg( 'showpreview' )->text(),
4211  $attribs );
4212  $buttons['live'] = '';
4213 
4214  $attribs = [
4215  'id' => 'wpDiff',
4216  'name' => 'wpDiff',
4217  'tabindex' => ++$tabindex,
4218  ] + Linker::tooltipAndAccesskeyAttribs( 'diff' );
4219  $buttons['diff'] = Html::submitButton( $this->context->msg( 'showdiff' )->text(),
4220  $attribs );
4221 
4222  Hooks::run( 'EditPageBeforeEditButtons', [ &$this, &$buttons, &$tabindex ] );
4223  return $buttons;
4224  }
4225 
4230  function noSuchSectionPage() {
4231  global $wgOut;
4232 
4233  $wgOut->prepareErrorPage( $this->context->msg( 'nosuchsectiontitle' ) );
4234 
4235  $res = $this->context->msg( 'nosuchsectiontext', $this->section )->parseAsBlock();
4236  Hooks::run( 'EditPageNoSuchSection', [ &$this, &$res ] );
4237  $wgOut->addHTML( $res );
4238 
4239  $wgOut->returnToMain( false, $this->mTitle );
4240  }
4241 
4247  public function spamPageWithContent( $match = false ) {
4249  $this->textbox2 = $this->textbox1;
4250 
4251  if ( is_array( $match ) ) {
4252  $match = $wgLang->listToText( $match );
4253  }
4254  $wgOut->prepareErrorPage( $this->context->msg( 'spamprotectiontitle' ) );
4255 
4256  $wgOut->addHTML( '<div id="spamprotected">' );
4257  $wgOut->addWikiMsg( 'spamprotectiontext' );
4258  if ( $match ) {
4259  $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
4260  }
4261  $wgOut->addHTML( '</div>' );
4262 
4263  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
4264  $this->showDiff();
4265 
4266  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
4267  $this->showTextbox2();
4268 
4269  $wgOut->addReturnTo( $this->getContextTitle(), [ 'action' => 'edit' ] );
4270  }
4271 
4278  private function checkUnicodeCompliantBrowser() {
4279  global $wgBrowserBlackList, $wgRequest;
4280 
4281  $currentbrowser = $wgRequest->getHeader( 'User-Agent' );
4282  if ( $currentbrowser === false ) {
4283  // No User-Agent header sent? Trust it by default...
4284  return true;
4285  }
4286 
4287  foreach ( $wgBrowserBlackList as $browser ) {
4288  if ( preg_match( $browser, $currentbrowser ) ) {
4289  return false;
4290  }
4291  }
4292  return true;
4293  }
4294 
4303  protected function safeUnicodeInput( $request, $field ) {
4304  $text = rtrim( $request->getText( $field ) );
4305  return $request->getBool( 'safemode' )
4306  ? $this->unmakeSafe( $text )
4307  : $text;
4308  }
4309 
4317  protected function safeUnicodeOutput( $text ) {
4318  return $this->checkUnicodeCompliantBrowser()
4319  ? $text
4320  : $this->makeSafe( $text );
4321  }
4322 
4335  private function makeSafe( $invalue ) {
4336  // Armor existing references for reversibility.
4337  $invalue = strtr( $invalue, [ "&#x" => "&#x0" ] );
4338 
4339  $bytesleft = 0;
4340  $result = "";
4341  $working = 0;
4342  $valueLength = strlen( $invalue );
4343  for ( $i = 0; $i < $valueLength; $i++ ) {
4344  $bytevalue = ord( $invalue[$i] );
4345  if ( $bytevalue <= 0x7F ) { // 0xxx xxxx
4346  $result .= chr( $bytevalue );
4347  $bytesleft = 0;
4348  } elseif ( $bytevalue <= 0xBF ) { // 10xx xxxx
4349  $working = $working << 6;
4350  $working += ( $bytevalue & 0x3F );
4351  $bytesleft--;
4352  if ( $bytesleft <= 0 ) {
4353  $result .= "&#x" . strtoupper( dechex( $working ) ) . ";";
4354  }
4355  } elseif ( $bytevalue <= 0xDF ) { // 110x xxxx
4356  $working = $bytevalue & 0x1F;
4357  $bytesleft = 1;
4358  } elseif ( $bytevalue <= 0xEF ) { // 1110 xxxx
4359  $working = $bytevalue & 0x0F;
4360  $bytesleft = 2;
4361  } else { // 1111 0xxx
4362  $working = $bytevalue & 0x07;
4363  $bytesleft = 3;
4364  }
4365  }
4366  return $result;
4367  }
4368 
4377  private function unmakeSafe( $invalue ) {
4378  $result = "";
4379  $valueLength = strlen( $invalue );
4380  for ( $i = 0; $i < $valueLength; $i++ ) {
4381  if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) {
4382  $i += 3;
4383  $hexstring = "";
4384  do {
4385  $hexstring .= $invalue[$i];
4386  $i++;
4387  } while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
4388 
4389  // Do some sanity checks. These aren't needed for reversibility,
4390  // but should help keep the breakage down if the editor
4391  // breaks one of the entities whilst editing.
4392  if ( ( substr( $invalue, $i, 1 ) == ";" ) && ( strlen( $hexstring ) <= 6 ) ) {
4393  $codepoint = hexdec( $hexstring );
4394  $result .= UtfNormal\Utils::codepointToUtf8( $codepoint );
4395  } else {
4396  $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
4397  }
4398  } else {
4399  $result .= substr( $invalue, $i, 1 );
4400  }
4401  }
4402  // reverse the transform that we made for reversibility reasons.
4403  return strtr( $result, [ "&#x0" => "&#x" ] );
4404  }
4405 }
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:414
EditPage\$editFormTextBeforeContent
$editFormTextBeforeContent
Definition: EditPage.php:381
EditPage\$mTriedSave
bool $mTriedSave
Definition: EditPage.php:257
save
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a save
Definition: deferred.txt:4
ContentHandler\getForModelID
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
Definition: ContentHandler.php:334
$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:805
diff
also included in $newHeader if any indicating whether we should show just the diff
Definition: hooks.txt:1214
$wgUser
$wgUser
Definition: Setup.php:806
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:161
EditPage\showFormBeforeText
showFormBeforeText()
Definition: EditPage.php:3190
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:262
EditPage\internalAttemptSave
internalAttemptSave(&$result, $bot=false)
Attempt submission (no UI)
Definition: EditPage.php:1720
EditPage\$lastDelete
bool stdClass $lastDelete
Definition: EditPage.php:248
EditPage\tokenOk
tokenOk(&$request)
Make sure the form isn't faking a user's credentials.
Definition: EditPage.php:1418
EditPage\$editFormPageTop
string $editFormPageTop
Before even the preview.
Definition: EditPage.php:379
EditPage\AS_BLANK_ARTICLE
const AS_BLANK_ARTICLE
Status: user tried to create a blank page and wpIgnoreBlankArticle == false.
Definition: EditPage.php:108
EditPage\showContentForm
showContentForm()
Subpage overridable method for printing the form for page content editing By default this simply outp...
Definition: EditPage.php:3232
EditPage\$mTitle
Title $mTitle
Definition: EditPage.php:212
Html\textarea
static textarea( $name, $value='', array $attribs=[])
Convenience function to produce a <textarea> element.
Definition: Html.php:774
EditPage\spamPageWithContent
spamPageWithContent( $match=false)
Show "your edit contains spam" page with your diff and text.
Definition: EditPage.php:4247
Xml\expandAttributes
static expandAttributes( $attribs)
Given an array of ('attributename' => 'value'), it generates the code to set the XML attributes : att...
Definition: Xml.php:67
EditPage\$section
string $section
Definition: EditPage.php:343
ParserOutput
Definition: ParserOutput.php:24
WikiPage\getRedirectTarget
getRedirectTarget()
If this page is a redirect, get its target.
Definition: WikiPage.php:871
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:3
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:110
EditPage\$scrolltop
null $scrolltop
Definition: EditPage.php:361
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kilobytes.
Definition: DefaultSettings.php:2170
content
per default it will return the text for text based content
Definition: contenthandler.txt:108
EditPage\mergeChangesIntoContent
mergeChangesIntoContent(&$editContent)
Attempts to do 3-way merge of edit content with a base revision and current content,...
Definition: EditPage.php:2237
EditPage\displayPermissionsError
displayPermissionsError(array $permErrors)
Display a permissions error page, like OutputPage::showPermissionsErrorPage(), but with the following...
Definition: EditPage.php:682
$wgParser
$wgParser
Definition: Setup.php:821
EditPage\$editFormTextAfterContent
$editFormTextAfterContent
Definition: EditPage.php:385
ContextSource\msg
msg()
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:186
EditPage\displayPreviewArea
displayPreviewArea( $previewOutput, $isOnTop=false)
Definition: EditPage.php:3336
EditPage\$blankArticle
bool $blankArticle
Definition: EditPage.php:275
EditPage\$allowBlankSummary
bool $allowBlankSummary
Definition: EditPage.php:272
Xml\tags
static tags( $element, $attribs=null, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:131
EditPage\$editFormTextBottom
$editFormTextBottom
Definition: EditPage.php:384
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:380
$response
this hook is for auditing only $response
Definition: hooks.txt:805
array
the array() calling protocol came about after MediaWiki 1.4rc1.
EditPage\$editintro
string $editintro
Definition: EditPage.php:358
plain
either a plain
Definition: hooks.txt:1990
EditPage\showTextbox2
showTextbox2()
Definition: EditPage.php:3286
EDIT_FORCE_BOT
const EDIT_FORCE_BOT
Definition: Defines.php:150
EditPage\$summary
string $summary
Definition: EditPage.php:331
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:76
EditPage\$textbox2
string $textbox2
Definition: EditPage.php:328
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:1354
EditPage\$mTokenOk
bool $mTokenOk
Definition: EditPage.php:251
$timestamp
if( $limit) $timestamp
Definition: importImages.php:119
CONTENT_MODEL_CSS
const CONTENT_MODEL_CSS
Definition: Defines.php:241
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:2007
EditPage\$oldid
int $oldid
Definition: EditPage.php:352
EditPage\getContextTitle
getContextTitle()
Get the context title object.
Definition: EditPage.php:465
$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:805
EditPage\showTosSummary
showTosSummary()
Give a chance for site and per-namespace customizations of terms of service summary link that might e...
Definition: EditPage.php:3485
EditPage\isWrongCaseCssJsPage
isWrongCaseCssJsPage()
Checks whether the user entered a skin name in uppercase, e.g.
Definition: EditPage.php:800
EditPage\$page
WikiPage $page
Definition: EditPage.php:209
EditPage\$save
bool $save
Definition: EditPage.php:307
EditPage\setContextTitle
setContextTitle( $title)
Set the context Title object.
Definition: EditPage.php:454
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:32
$customAttribs
null means default & $customAttribs
Definition: hooks.txt:1939
EditPage\edit
edit()
This is the function that gets called for "action=edit".
Definition: EditPage.php:511
EditPage\$mBaseRevision
bool $mBaseRevision
Definition: EditPage.php:299
EditPage\$autoSumm
string $autoSumm
Definition: EditPage.php:287
NS_FILE
const NS_FILE
Definition: Defines.php:62
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:4303
$params
$params
Definition: styleTest.css.php:40
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:956
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:1082
EditPage\getLastDelete
getLastDelete()
Definition: EditPage.php:3736
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1273
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:525
Linker\linkKnown
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition: Linker.php:255
EditPage\$editFormTextAfterTools
$editFormTextAfterTools
Definition: EditPage.php:383
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:56
$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:78
EditPage\$editFormTextAfterWarn
$editFormTextAfterWarn
Definition: EditPage.php:382
ContentHandler\getForTitle
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
Definition: ContentHandler.php:281
$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:1339
EditPage\AS_CONTENT_TOO_BIG
const AS_CONTENT_TOO_BIG
Status: Content too big (> $wgMaxArticleSize)
Definition: EditPage.php:71
$wgTitle
if(! $wgRequest->checkUrlExtension()) if(isset( $_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] !='') if(! $wgEnableAPI) $wgTitle
Definition: api.php:68
Hooks\clear
static clear( $name)
Clears hooks registered via Hooks::register().
Definition: Hooks.php:66
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:28
EditPage\showHeaderCopyrightWarning
showHeaderCopyrightWarning()
Show the header copyright warning.
Definition: EditPage.php:3468
EditPage\getEditPermissionErrors
getEditPermissionErrors( $rigor='secure')
Definition: EditPage.php:641
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Associative array mapping language codes to prefixed links of the form "language:title". & $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:1937
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2706
EditPage\$context
IContextSource $context
Definition: EditPage.php:409
EditPage\$didSave
$didSave
Definition: EditPage.php:390
EditPage\AS_BLOCKED_PAGE_FOR_USER
const AS_BLOCKED_PAGE_FOR_USER
Status: User is blocked from editing this page.
Definition: EditPage.php:66
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:119
$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:1379
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:103
Html\buttonAttributes
static buttonAttributes(array $attrs, array $modifiers=[])
Modifies a set of attributes meant for button elements and apply a set of default attributes when $wg...
Definition: Html.php:109
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:1090
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:1939
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
Linker\formatHiddenCategories
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition: Linker.php:1972
EditPage\$mArticle
Article $mArticle
Definition: EditPage.php:207
EditPage\$contentFormat
null string $contentFormat
Definition: EditPage.php:370
Html\input
static input( $name, $value='', $type='text', array $attribs=[])
Convenience function to produce an "<input>" element.
Definition: Html.php:675
Skin\getSkinNames
static getSkinNames()
Fetch the set of available skins.
Definition: Skin.php:49
EditPage\$isWrongCaseCssJsPage
bool $isWrongCaseCssJsPage
Definition: EditPage.php:233
EditPage\POST_EDIT_COOKIE_DURATION
const POST_EDIT_COOKIE_DURATION
Duration of PostEdit cookie, in seconds.
Definition: EditPage.php:204
$dbr
$dbr
Definition: testCompression.php:50
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
Revision\FOR_THIS_USER
const FOR_THIS_USER
Definition: Revision.php:93
NS_MAIN
const NS_MAIN
Definition: Defines.php:56
EditPage\getPreviewLimitReport
static getPreviewLimitReport( $output)
Get the Limit report for page previews.
Definition: EditPage.php:3548
EditPage\$watchthis
bool $watchthis
Definition: EditPage.php:319
EditPage\$previewTextAfterContent
$previewTextAfterContent
Definition: EditPage.php:386
Html\closeElement
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:305
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:3405
Xml\encodeJsCall
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:682
EditPage\$tooBig
bool $tooBig
Definition: EditPage.php:263
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, IDatabase $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:105
EditPage\AS_SUCCESS_NEW_ARTICLE
const AS_SUCCESS_NEW_ARTICLE
Status: Article successfully created.
Definition: EditPage.php:51
codepointToUtf8
codepointToUtf8( $codepoint)
Return UTF-8 sequence for a given Unicode code point.
Definition: UtfNormalUtil.php:39
MWException
MediaWiki exception.
Definition: MWException.php:26
EditPage\$editRevId
integer $editRevId
Definition: EditPage.php:340
EditPage\addContentModelChangeLogEntry
addContentModelChangeLogEntry(User $user, $oldModel, $newModel, $reason)
Definition: EditPage.php:2190
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:2562
EditPage\getEditButtons
getEditButtons(&$tabindex)
Returns an array of html code of the following buttons: save, diff, preview and live.
Definition: EditPage.php:4184
$parserOutput
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context $parserOutput
Definition: hooks.txt:1049
EditPage\AS_END
const AS_END
Status: WikiPage::doEdit() was unsuccessful.
Definition: EditPage.php:134
LogPage\DELETED_COMMENT
const DELETED_COMMENT
Definition: LogPage.php:34
EditPage\showSummaryInput
showSummaryInput( $isSubjectPreview, $summary="")
Definition: EditPage.php:3138
EditPage\getParentRevId
getParentRevId()
Get the edit's parent revision ID.
Definition: EditPage.php:1273
ParserOptions\newFromUserAndLang
static newFromUserAndLang(User $user, Language $lang)
Get a ParserOptions object from a given user and language.
Definition: ParserOptions.php:716
wfArrayDiff2
wfArrayDiff2( $a, $b)
Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
Definition: GlobalFunctions.php:176
EditPage\isSectionEditSupported
isSectionEditSupported()
Returns whether section editing is supported for the current page.
Definition: EditPage.php:821
EditPage\importFormData
importFormData(&$request)
This function collects the form data and uses it to populate various member variables.
Definition: EditPage.php:831
EditPage\getActionURL
getActionURL(Title $title)
Returns the URL to use in the form's action attribute.
Definition: EditPage.php:3702
LogPage\DELETED_USER
const DELETED_USER
Definition: LogPage.php:35
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:3073
EditPage\showIntro
showIntro()
Show all applicable editing introductions.
Definition: EditPage.php:2386
EditPage\$firsttime
bool $firsttime
Definition: EditPage.php:245
ContentHandler\runLegacyHooks
static runLegacyHooks( $event, $args=[], $deprecatedVersion=null)
Call a legacy hook that uses text instead of Content objects.
Definition: ContentHandler.php:1147
$matches
$matches
Definition: NoLocalSettings.php:24
EditPage\$isCssSubpage
bool $isCssSubpage
Definition: EditPage.php:227
Xml\check
static check( $name, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox.
Definition: Xml.php:324
EditPage\$missingComment
bool $missingComment
Definition: EditPage.php:266
MWContentSerializationException
Exception representing a failure to serialize or unserialize a content object.
Definition: ContentHandler.php:36
EditPage\attemptSave
attemptSave(&$resultDetails=false)
Attempt submission.
Definition: EditPage.php:1465
WikiPage\getContent
getContent( $audience=Revision::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
Definition: WikiPage.php:680
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
not
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired 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 inclusive false for true for descending in case the handler function wants to provide a converted Content object Note that $result getContentModel() must return $toModel. 'CustomEditor' $rcid is used in generating this variable which contains information about the new such as the revision s whether the revision was marked as a minor edit or not
Definition: hooks.txt:1159
Article\getPage
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:183
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:788
EditPage\getArticle
getArticle()
Definition: EditPage.php:429
EditPage\getCheckboxes
getCheckboxes(&$tabindex, $checked)
Returns an array of html code of the following checkboxes: minor and watch.
Definition: EditPage.php:4120
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:178
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:155
EditPage\previewOnOpen
previewOnOpen()
Should we show a preview when the edit form is first shown?
Definition: EditPage.php:767
WatchAction\doWatchOrUnwatch
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:84
$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:86
EditPage\AS_SPAM_ERROR
const AS_SPAM_ERROR
Status: summary contained spam according to one of the regexes in $wgSummarySpamRegex.
Definition: EditPage.php:139
EditPage\$allowSelfRedirect
bool $allowSelfRedirect
Definition: EditPage.php:284
EditPage\showEditForm
showEditForm( $formCallback=null)
Send the edit form and related headers to $wgOut.
Definition: EditPage.php:2585
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:511
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:559
EditPage\$contentModel
null string $contentModel
Definition: EditPage.php:367
EditPage\wasDeletedSinceLastEdit
wasDeletedSinceLastEdit()
Check if a page was deleted while the user was editing it, before submit.
Definition: EditPage.php:3713
DB_REPLICA
const DB_REPLICA
Definition: defines.php:22
$comment
$comment
Definition: importImages.php:122
EditPage\getTemplates
getTemplates()
Definition: EditPage.php:3957
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2036
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:70
EditPage\getPreviewParserOptions
getPreviewParserOptions()
Get parser options for a preview.
Definition: EditPage.php:3923
EditPage\runPostMergeFilters
runPostMergeFilters(Content $content, Status $status, User $user)
Run hooks that can filter edits just before they get saved.
Definition: EditPage.php:1617
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:129
DB_MASTER
const DB_MASTER
Definition: defines.php:23
EditPage\$mContextTitle
null Title $mContextTitle
Definition: EditPage.php:215
EditPage\$isCssJsSubpage
bool $isCssJsSubpage
Definition: EditPage.php:224
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:452
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:997
EditPage\showFormAfterText
showFormAfterText()
Definition: EditPage.php:3207
EditPage\showPreview
showPreview( $text)
Append preview output to $wgOut.
Definition: EditPage.php:3384
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:1066
EditPage\getEditToolbar
static getEditToolbar( $title=null)
Shows a bulletin board style toolbar for common editing functions.
Definition: EditPage.php:3981
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:164
$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:805
$type
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2259
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:4317
$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:246
EditPage\matchSpamRegex
static matchSpamRegex( $text)
Check given input text against $wgSpamRegex, and return the text of the first match.
Definition: EditPage.php:2293
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:758
EditPage\$recreate
bool $recreate
Definition: EditPage.php:322
$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:1939
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:1867
EditPage\$contentLength
bool int $contentLength
Definition: EditPage.php:399
EditPage\AS_SUCCESS_UPDATE
const AS_SUCCESS_UPDATE
Status: Article successfully updated.
Definition: EditPage.php:46
EditPage\showTextbox1
showTextbox1( $customAttribs=null, $textoverride=null)
Method to output wpTextbox1 The $textoverride method can be used by subclasses overriding showContent...
Definition: EditPage.php:3244
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:59
EditPage\getSummaryPreview
getSummaryPreview( $isSubjectPreview, $summary="")
Definition: EditPage.php:3168
EditPage\importContentFormData
importContentFormData(&$request)
Subpage overridable method for extracting the page content data from the posted form to be placed in ...
Definition: EditPage.php:1057
EditPage\$minoredit
bool $minoredit
Definition: EditPage.php:316
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:404
$value
$value
Definition: styleTest.css.php:45
EDIT_UPDATE
const EDIT_UPDATE
Definition: Defines.php:147
Linker\tooltipAndAccesskeyAttribs
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[])
Returns the attributes for the tooltip and access key.
Definition: Linker.php:2184
EditPage\showHeader
showHeader()
Definition: EditPage.php:2862
EditPage\getBaseRevision
getBaseRevision()
Definition: EditPage.php:2276
ContentHandler\getLocalizedName
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
Definition: ContentHandler.php:389
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:44
EditPage\getSummaryInput
getSummaryInput( $summary="", $labelText=null, $inputAttrs=null, $spanLabelAttrs=null)
Standard summary input and label (wgSummary), abstracted so EditPage subclasses may reorganize the fo...
Definition: EditPage.php:3100
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:76
MWNamespace\wantSignatures
static wantSignatures( $index)
Might pages in this namespace require the use of the Signature button on the edit toolbar?
Definition: MWNamespace.php:310
EditPage\$edittime
string $edittime
Definition: EditPage.php:337
EditPage\matchSummarySpamRegex
static matchSummarySpamRegex( $text)
Check given input text against $wgSummarySpamRegex, and return the text of the first match.
Definition: EditPage.php:2307
EditPage\$mTokenOkExceptSuffix
bool $mTokenOkExceptSuffix
Definition: EditPage.php:254
EditPage\showEditTools
showEditTools()
Definition: EditPage.php:3496
EditPage\$preview
bool $preview
Definition: EditPage.php:310
EditPage\$isNew
bool $isNew
New page or new section.
Definition: EditPage.php:236
etc
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add etc
Definition: design.txt:12
First
The First
Definition: primes.txt:1
EditPage\getCopywarn
getCopywarn()
Get the copyright warning.
Definition: EditPage.php:3509
EditPage\setApiEditOverride
setApiEditOverride( $enableOverride)
Allow editing of content that supports API direct editing, but not general direct editing.
Definition: EditPage.php:492
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1667
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:149
Revision\RAW
const RAW
Definition: Revision.php:94
EditPage\getCancelLink
getCancelLink()
Definition: EditPage.php:3676
EditPage\unmakeSafe
unmakeSafe( $invalue)
Reverse the previously applied transliteration of non-ASCII characters back to UTF-8.
Definition: EditPage.php:4377
EditPage\showCustomIntro
showCustomIntro()
Attempt to show a custom editing introduction, if supplied.
Definition: EditPage.php:2500
EditPage\getContext
getContext()
Definition: EditPage.php:437
Html\submitButton
static submitButton( $contents, array $attrs, array $modifiers=[])
Returns an HTML link element in a string styled as a button (when $wgUseMediaWikiUIEverywhere is enab...
Definition: Html.php:185
EditPage\AS_PARSE_ERROR
const AS_PARSE_ERROR
Status: can't parse content.
Definition: EditPage.php:172
EditPage\EDITFORM_ID
const EDITFORM_ID
HTML id and name for the beginning of the edit form.
Definition: EditPage.php:183
EditPage\extractSectionTitle
static extractSectionTitle( $text)
Extract the section title from current section text, if any.
Definition: EditPage.php:2849
Block\TYPE_AUTO
const TYPE_AUTO
Definition: Block.php:81
RequestContext\getMain
static getMain()
Static methods.
Definition: RequestContext.php:468
EditPage\makeTemplatesOnThisPageList
makeTemplatesOnThisPageList(array $templates)
Wrapper around TemplatesOnThisPageFormatter to make a "templates on this page" list.
Definition: EditPage.php:2824
EditPage\$textbox1
string $textbox1
Definition: EditPage.php:325
EditPage\$parentRevId
int $parentRevId
Definition: EditPage.php:355
EditPage\$undidRev
$undidRev
Definition: EditPage.php:391
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:2024
wfFindFile
wfFindFile( $title, $options=[])
Find a file.
Definition: GlobalFunctions.php:3114
EditPage\$changeTags
null array $changeTags
Definition: EditPage.php:373
EditPage
The edit page/HTML interface (split from Article) The actual database and text munging is still in Ar...
Definition: EditPage.php:42
EditPage\noSuchSectionPage
noSuchSectionPage()
Creates a basic error page which informs the user that they have attempted to edit a nonexistent sect...
Definition: EditPage.php:4230
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:55
EditPage\$formtype
string $formtype
Definition: EditPage.php:242
Content
Base interface for content objects.
Definition: Content.php:34
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:146
EditPage\$hasPresetSummary
bool $hasPresetSummary
Has a summary been preset using GET parameter &summary= ?
Definition: EditPage.php:296
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:392
$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:1724
EditPage\$mParserOutput
ParserOutput $mParserOutput
Definition: EditPage.php:293
Title
Represents a title within MediaWiki.
Definition: Title.php:36
EDIT_AUTOSUMMARY
const EDIT_AUTOSUMMARY
Definition: Defines.php:152
EditPage\$mShowSummaryField
bool $mShowSummaryField
Definition: EditPage.php:302
EditPage\$sectiontitle
string $sectiontitle
Definition: EditPage.php:346
EditPage\$starttime
string $starttime
Definition: EditPage.php:349
EditPage\$suppressIntro
$suppressIntro
Definition: EditPage.php:393
ContentHandler\getContentText
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
Definition: ContentHandler.php:114
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:118
type
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres as and are nearing end of but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN type
Definition: postgres.txt:22
wfReadOnlyReason
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
Definition: GlobalFunctions.php:1285
EditPage\$deletedSinceEdit
bool $deletedSinceEdit
Definition: EditPage.php:239
TS_MW
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition: defines.php:11
EditPage\checkUnicodeCompliantBrowser
checkUnicodeCompliantBrowser()
Check if the browser is on a blacklist of user-agents known to mangle UTF-8 data on form submission.
Definition: EditPage.php:4278
EditPage\$selfRedirect
bool $selfRedirect
Definition: EditPage.php:281
EditPage\$edit
bool $edit
Definition: EditPage.php:396
EditPage\isSupportedContentModel
isSupportedContentModel( $modelId)
Returns if the given content model is editable.
Definition: EditPage.php:481
EditPage\$mPreloadContent
$mPreloadContent
Definition: EditPage.php:387
EditPage\showConflict
showConflict()
Show an edit conflict.
Definition: EditPage.php:3641
EditPage\makeSafe
makeSafe( $invalue)
A number of web browsers are known to corrupt non-ASCII characters in a UTF-8 text editing environmen...
Definition: EditPage.php:4335
$output
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object & $output
Definition: hooks.txt:1049
EditPage\$diff
bool $diff
Definition: EditPage.php:313
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
EditPage\doPreviewParse
doPreviewParse(Content $content)
Parse the page for a preview.
Definition: EditPage.php:3940
EditPage\newSectionSummary
newSectionSummary(&$sectionanchor=null)
Return the summary to be used for a new section.
Definition: EditPage.php:1672
EditPage\$action
string $action
Definition: EditPage.php:218
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:247
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:301
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:81
$wgEnableUploads
$wgEnableUploads
Uploads have to be specially set up to be secure.
Definition: DefaultSettings.php:378
Linker\commentBlock
static commentBlock( $comment, $title=null, $local=false, $wikiId=null)
Wrap a comment in standard punctuation and formatting if it's non-empty, otherwise return empty strin...
Definition: Linker.php:1525
ParserOutput\getLimitReportData
getLimitReportData()
Definition: ParserOutput.php:410
NS_USER
const NS_USER
Definition: Defines.php:58
EditPage\showTextbox
showTextbox( $text, $name, $customAttribs=[])
Definition: EditPage.php:3290
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:805
EditPage\getTitle
getTitle()
Definition: EditPage.php:445
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304
ManualLogEntry
Class for creating log entries manually, to inject them into the database.
Definition: LogEntry.php:394
EditPage\AS_CONFLICT_DETECTED
const AS_CONFLICT_DETECTED
Status: (non-resolvable) edit conflict.
Definition: EditPage.php:113
in
null for the wiki Added in
Definition: hooks.txt:1558
EditPage\AS_RATE_LIMITED
const AS_RATE_LIMITED
Status: rate limiter for action 'edit' was tripped.
Definition: EditPage.php:91
MWUnknownContentModelException
Exception thrown when an unregistered content model is requested.
Definition: ContentHandler.php:47
of
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
Definition: globals.txt:10
$request
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2576
EditPage\getCurrentContent
getCurrentContent()
Get the current content of the page.
Definition: EditPage.php:1289
Title\setContentModel
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition: Title.php:969
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:1140
EditPage\$isConflict
bool $isConflict
Definition: EditPage.php:221
name
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 name
Definition: design.txt:12
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:64
CONTENT_MODEL_JAVASCRIPT
const CONTENT_MODEL_JAVASCRIPT
Definition: Defines.php:240
EditPage\displayViewSourcePage
displayViewSourcePage(Content $content, $errorMessage='')
Display a read-only View Source page.
Definition: EditPage.php:713
EDIT_MINOR
const EDIT_MINOR
Definition: Defines.php:148
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:34
EditPage\getContentObject
getContentObject( $def_content=null)
Definition: EditPage.php:1105
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:85
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:144
EditPage\showStandardInputs
showStandardInputs(&$tabindex=2)
Definition: EditPage.php:3590
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:664
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:229
MediaWikiServices
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
$wgOut
$wgOut
Definition: Setup.php:816
NS_CATEGORY_TALK
const NS_CATEGORY_TALK
Definition: Defines.php:71
$content
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition: hooks.txt:1049
EditPage\matchSpamRegexInternal
static matchSpamRegexInternal( $text, $regexes)
Definition: EditPage.php:2318
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:481
$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:1558
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:276
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:189
EditPage\getOriginalContent
getOriginalContent(User $user)
Get the content of the wanted revision, without section extraction.
Definition: EditPage.php:1244
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:97
EditPage\setPostEditCookie
setPostEditCookie( $statusValue)
Sets post-edit cookie indicating the user just saved a particular revision.
Definition: EditPage.php:1442
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:48
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
EditPage\$incompleteForm
bool $incompleteForm
Definition: EditPage.php:260
$wgForeignFileRepos
$wgForeignFileRepos
Definition: DefaultSettings.php:523
EditPage\$missingSummary
bool $missingSummary
Definition: EditPage.php:269
EditPage\$isJsSubpage
bool $isJsSubpage
Definition: EditPage.php:230
EditPage\$bot
bool $bot
Definition: EditPage.php:364
EditPage\getCopyrightWarning
static getCopyrightWarning( $title, $format='plain', $langcode=null)
Get the copyright warning, by default returns wikitext.
Definition: EditPage.php:3520
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
MWNamespace\getCanonicalName
static getCanonicalName( $index)
Returns the canonical (English) name for a given index.
Definition: MWNamespace.php:229
Revision\DELETED_TEXT
const DELETED_TEXT
Definition: Revision.php:85
EditPage\AS_HOOK_ERROR_EXPECTED
const AS_HOOK_ERROR_EXPECTED
Status: A hook function returned an error.
Definition: EditPage.php:61
$status
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1049
Skin\makeInternalOrExternalUrl
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition: Skin.php:1098
EditPage\updateWatchlist
updateWatchlist()
Register the change of watch status.
Definition: EditPage.php:2207
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:550
EditPage\handleStatus
handleStatus(Status $status, $resultDetails)
Handle status, such as after attempt save.
Definition: EditPage.php:1486
ParserOptions\newFromUser
static newFromUser( $user)
Get a ParserOptions object from a given user.
Definition: ParserOptions.php:705
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2105
EditPage\$hookError
string $hookError
Definition: EditPage.php:290
EditPage\$allowBlankArticle
bool $allowBlankArticle
Definition: EditPage.php:278
EditPage\toEditText
toEditText( $content)
Gets an editable textual representation of $content.
Definition: EditPage.php:2534
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:2328
EditPage\AS_TEXTBOX_EMPTY
const AS_TEXTBOX_EMPTY
Status: user tried to create a new section without content.
Definition: EditPage.php:124
EditPage\AS_CHANGE_TAG_ERROR
const AS_CHANGE_TAG_ERROR
Status: an error relating to change tagging.
Definition: EditPage.php:167
$wgContLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the content language as $wgContLang
Definition: design.txt:56
page
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk page
Definition: hooks.txt:2259
EditPage\submit
submit()
Definition: EditPage.php:496
EditPage\$nosummary
bool $nosummary
Definition: EditPage.php:334
EditPage\getPreviewText
getPreviewText()
Get the rendered text for previewing.
Definition: EditPage.php:3780