MediaWiki  1.29.1
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 
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  private $isOldRev = false;
415 
419  private $oouiEnabled = false;
420 
424  public function __construct( Article $article ) {
425  global $wgOOUIEditPage;
426 
427  $this->mArticle = $article;
428  $this->page = $article->getPage(); // model object
429  $this->mTitle = $article->getTitle();
430  $this->context = $article->getContext();
431 
432  $this->contentModel = $this->mTitle->getContentModel();
433 
434  $handler = ContentHandler::getForModelID( $this->contentModel );
435  $this->contentFormat = $handler->getDefaultFormat();
436 
437  $this->oouiEnabled = $wgOOUIEditPage;
438  }
439 
443  public function getArticle() {
444  return $this->mArticle;
445  }
446 
451  public function getContext() {
452  return $this->context;
453  }
454 
459  public function getTitle() {
460  return $this->mTitle;
461  }
462 
468  public function setContextTitle( $title ) {
469  $this->mContextTitle = $title;
470  }
471 
479  public function getContextTitle() {
480  if ( is_null( $this->mContextTitle ) ) {
482  return $wgTitle;
483  } else {
484  return $this->mContextTitle;
485  }
486  }
487 
492  public function isOouiEnabled() {
493  return $this->oouiEnabled;
494  }
495 
503  public function isSupportedContentModel( $modelId ) {
504  return $this->enableApiEditOverride === true ||
505  ContentHandler::getForModelID( $modelId )->supportsDirectEditing();
506  }
507 
514  public function setApiEditOverride( $enableOverride ) {
515  $this->enableApiEditOverride = $enableOverride;
516  }
517 
521  public function submit() {
522  $this->edit();
523  }
524 
536  public function edit() {
538  // Allow extensions to modify/prevent this form or submission
539  if ( !Hooks::run( 'AlternateEdit', [ $this ] ) ) {
540  return;
541  }
542 
543  wfDebug( __METHOD__ . ": enter\n" );
544 
545  // If they used redlink=1 and the page exists, redirect to the main article
546  if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
547  $wgOut->redirect( $this->mTitle->getFullURL() );
548  return;
549  }
550 
551  $this->importFormData( $wgRequest );
552  $this->firsttime = false;
553 
554  if ( wfReadOnly() && $this->save ) {
555  // Force preview
556  $this->save = false;
557  $this->preview = true;
558  }
559 
560  if ( $this->save ) {
561  $this->formtype = 'save';
562  } elseif ( $this->preview ) {
563  $this->formtype = 'preview';
564  } elseif ( $this->diff ) {
565  $this->formtype = 'diff';
566  } else { # First time through
567  $this->firsttime = true;
568  if ( $this->previewOnOpen() ) {
569  $this->formtype = 'preview';
570  } else {
571  $this->formtype = 'initial';
572  }
573  }
574 
575  $permErrors = $this->getEditPermissionErrors( $this->save ? 'secure' : 'full' );
576  if ( $permErrors ) {
577  wfDebug( __METHOD__ . ": User can't edit\n" );
578  // Auto-block user's IP if the account was "hard" blocked
579  if ( !wfReadOnly() ) {
580  $user = $wgUser;
581  DeferredUpdates::addCallableUpdate( function () use ( $user ) {
582  $user->spreadAnyEditBlock();
583  } );
584  }
585  $this->displayPermissionsError( $permErrors );
586 
587  return;
588  }
589 
590  $revision = $this->mArticle->getRevisionFetched();
591  // Disallow editing revisions with content models different from the current one
592  // Undo edits being an exception in order to allow reverting content model changes.
593  if ( $revision
594  && $revision->getContentModel() !== $this->contentModel
595  ) {
596  $prevRev = null;
597  if ( $this->undidRev ) {
598  $undidRevObj = Revision::newFromId( $this->undidRev );
599  $prevRev = $undidRevObj ? $undidRevObj->getPrevious() : null;
600  }
601  if ( !$this->undidRev
602  || !$prevRev
603  || $prevRev->getContentModel() !== $this->contentModel
604  ) {
605  $this->displayViewSourcePage(
606  $this->getContentObject(),
607  $this->context->msg(
608  'contentmodelediterror',
609  $revision->getContentModel(),
611  )->plain()
612  );
613  return;
614  }
615  }
616 
617  $this->isConflict = false;
618  // css / js subpages of user pages get a special treatment
619  $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
620  $this->isCssSubpage = $this->mTitle->isCssSubpage();
621  $this->isJsSubpage = $this->mTitle->isJsSubpage();
622  // @todo FIXME: Silly assignment.
623  $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
624 
625  # Show applicable editing introductions
626  if ( $this->formtype == 'initial' || $this->firsttime ) {
627  $this->showIntro();
628  }
629 
630  # Attempt submission here. This will check for edit conflicts,
631  # and redundantly check for locked database, blocked IPs, etc.
632  # that edit() already checked just in case someone tries to sneak
633  # in the back door with a hand-edited submission URL.
634 
635  if ( 'save' == $this->formtype ) {
636  $resultDetails = null;
637  $status = $this->attemptSave( $resultDetails );
638  if ( !$this->handleStatus( $status, $resultDetails ) ) {
639  return;
640  }
641  }
642 
643  # First time through: get contents, set time for conflict
644  # checking, etc.
645  if ( 'initial' == $this->formtype || $this->firsttime ) {
646  if ( $this->initialiseForm() === false ) {
647  $this->noSuchSectionPage();
648  return;
649  }
650 
651  if ( !$this->mTitle->getArticleID() ) {
652  Hooks::run( 'EditFormPreloadText', [ &$this->textbox1, &$this->mTitle ] );
653  } else {
654  Hooks::run( 'EditFormInitialText', [ $this ] );
655  }
656 
657  }
658 
659  $this->showEditForm();
660  }
661 
666  protected function getEditPermissionErrors( $rigor = 'secure' ) {
667  global $wgUser;
668 
669  $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser, $rigor );
670  # Can this title be created?
671  if ( !$this->mTitle->exists() ) {
672  $permErrors = array_merge(
673  $permErrors,
674  wfArrayDiff2(
675  $this->mTitle->getUserPermissionsErrors( 'create', $wgUser, $rigor ),
676  $permErrors
677  )
678  );
679  }
680  # Ignore some permissions errors when a user is just previewing/viewing diffs
681  $remove = [];
682  foreach ( $permErrors as $error ) {
683  if ( ( $this->preview || $this->diff )
684  && (
685  $error[0] == 'blockedtext' ||
686  $error[0] == 'autoblockedtext' ||
687  $error[0] == 'systemblockedtext'
688  )
689  ) {
690  $remove[] = $error;
691  }
692  }
693  $permErrors = wfArrayDiff2( $permErrors, $remove );
694 
695  return $permErrors;
696  }
697 
711  protected function displayPermissionsError( array $permErrors ) {
713 
714  if ( $wgRequest->getBool( 'redlink' ) ) {
715  // The edit page was reached via a red link.
716  // Redirect to the article page and let them click the edit tab if
717  // they really want a permission error.
718  $wgOut->redirect( $this->mTitle->getFullURL() );
719  return;
720  }
721 
722  $content = $this->getContentObject();
723 
724  # Use the normal message if there's nothing to display
725  if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
726  $action = $this->mTitle->exists() ? 'edit' :
727  ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
728  throw new PermissionsError( $action, $permErrors );
729  }
730 
731  $this->displayViewSourcePage(
732  $content,
733  $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' )
734  );
735  }
736 
742  protected function displayViewSourcePage( Content $content, $errorMessage = '' ) {
743  global $wgOut;
744 
745  Hooks::run( 'EditPage::showReadOnlyForm:initial', [ $this, &$wgOut ] );
746 
747  $wgOut->setRobotPolicy( 'noindex,nofollow' );
748  $wgOut->setPageTitle( $this->context->msg(
749  'viewsource-title',
750  $this->getContextTitle()->getPrefixedText()
751  ) );
752  $wgOut->addBacklinkSubtitle( $this->getContextTitle() );
753  $wgOut->addHTML( $this->editFormPageTop );
754  $wgOut->addHTML( $this->editFormTextTop );
755 
756  if ( $errorMessage !== '' ) {
757  $wgOut->addWikiText( $errorMessage );
758  $wgOut->addHTML( "<hr />\n" );
759  }
760 
761  # If the user made changes, preserve them when showing the markup
762  # (This happens when a user is blocked during edit, for instance)
763  if ( !$this->firsttime ) {
764  $text = $this->textbox1;
765  $wgOut->addWikiMsg( 'viewyourtext' );
766  } else {
767  try {
768  $text = $this->toEditText( $content );
769  } catch ( MWException $e ) {
770  # Serialize using the default format if the content model is not supported
771  # (e.g. for an old revision with a different model)
772  $text = $content->serialize();
773  }
774  $wgOut->addWikiMsg( 'viewsourcetext' );
775  }
776 
777  $wgOut->addHTML( $this->editFormTextBeforeContent );
778  $this->showTextbox( $text, 'wpTextbox1', [ 'readonly' ] );
779  $wgOut->addHTML( $this->editFormTextAfterContent );
780 
781  $wgOut->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
782 
783  $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
784 
785  $wgOut->addHTML( $this->editFormTextBottom );
786  if ( $this->mTitle->exists() ) {
787  $wgOut->returnToMain( null, $this->mTitle );
788  }
789  }
790 
796  protected function previewOnOpen() {
797  global $wgRequest, $wgUser, $wgPreviewOnOpenNamespaces;
798  if ( $wgRequest->getVal( 'preview' ) == 'yes' ) {
799  // Explicit override from request
800  return true;
801  } elseif ( $wgRequest->getVal( 'preview' ) == 'no' ) {
802  // Explicit override from request
803  return false;
804  } elseif ( $this->section == 'new' ) {
805  // Nothing *to* preview for new sections
806  return false;
807  } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() )
808  && $wgUser->getOption( 'previewonfirst' )
809  ) {
810  // Standard preference behavior
811  return true;
812  } elseif ( !$this->mTitle->exists()
813  && isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
814  && $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()]
815  ) {
816  // Categories are special
817  return true;
818  } else {
819  return false;
820  }
821  }
822 
829  protected function isWrongCaseCssJsPage() {
830  if ( $this->mTitle->isCssJsSubpage() ) {
831  $name = $this->mTitle->getSkinFromCssJsSubpage();
832  $skins = array_merge(
833  array_keys( Skin::getSkinNames() ),
834  [ 'common' ]
835  );
836  return !in_array( $name, $skins )
837  && in_array( strtolower( $name ), $skins );
838  } else {
839  return false;
840  }
841  }
842 
850  protected function isSectionEditSupported() {
851  $contentHandler = ContentHandler::getForTitle( $this->mTitle );
852  return $contentHandler->supportsSections();
853  }
854 
860  public function importFormData( &$request ) {
862 
863  # Allow users to change the mode for testing
864  $this->oouiEnabled = $request->getFuzzyBool( 'ooui', $this->oouiEnabled );
865 
866  # Section edit can come from either the form or a link
867  $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
868 
869  if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) {
870  throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
871  }
872 
873  $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
874 
875  if ( $request->wasPosted() ) {
876  # These fields need to be checked for encoding.
877  # Also remove trailing whitespace, but don't remove _initial_
878  # whitespace from the text boxes. This may be significant formatting.
879  $this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' );
880  if ( !$request->getCheck( 'wpTextbox2' ) ) {
881  // Skip this if wpTextbox2 has input, it indicates that we came
882  // from a conflict page with raw page text, not a custom form
883  // modified by subclasses
885  if ( $textbox1 !== null ) {
886  $this->textbox1 = $textbox1;
887  }
888  }
889 
890  # Truncate for whole multibyte characters
891  $this->summary = $wgContLang->truncate( $request->getText( 'wpSummary' ), 255 );
892 
893  # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
894  # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
895  # section titles.
896  $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
897 
898  # Treat sectiontitle the same way as summary.
899  # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
900  # currently doing double duty as both edit summary and section title. Right now this
901  # is just to allow API edits to work around this limitation, but this should be
902  # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
903  $this->sectiontitle = $wgContLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
904  $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
905 
906  $this->edittime = $request->getVal( 'wpEdittime' );
907  $this->editRevId = $request->getIntOrNull( 'editRevId' );
908  $this->starttime = $request->getVal( 'wpStarttime' );
909 
910  $undidRev = $request->getInt( 'wpUndidRevision' );
911  if ( $undidRev ) {
912  $this->undidRev = $undidRev;
913  }
914 
915  $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
916 
917  if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
918  // wpTextbox1 field is missing, possibly due to being "too big"
919  // according to some filter rules such as Suhosin's setting for
920  // suhosin.request.max_value_length (d'oh)
921  $this->incompleteForm = true;
922  } else {
923  // If we receive the last parameter of the request, we can fairly
924  // claim the POST request has not been truncated.
925 
926  // TODO: softened the check for cutover. Once we determine
927  // that it is safe, we should complete the transition by
928  // removing the "edittime" clause.
929  $this->incompleteForm = ( !$request->getVal( 'wpUltimateParam' )
930  && is_null( $this->edittime ) );
931  }
932  if ( $this->incompleteForm ) {
933  # If the form is incomplete, force to preview.
934  wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
935  wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
936  $this->preview = true;
937  } else {
938  $this->preview = $request->getCheck( 'wpPreview' );
939  $this->diff = $request->getCheck( 'wpDiff' );
940 
941  // Remember whether a save was requested, so we can indicate
942  // if we forced preview due to session failure.
943  $this->mTriedSave = !$this->preview;
944 
945  if ( $this->tokenOk( $request ) ) {
946  # Some browsers will not report any submit button
947  # if the user hits enter in the comment box.
948  # The unmarked state will be assumed to be a save,
949  # if the form seems otherwise complete.
950  wfDebug( __METHOD__ . ": Passed token check.\n" );
951  } elseif ( $this->diff ) {
952  # Failed token check, but only requested "Show Changes".
953  wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
954  } else {
955  # Page might be a hack attempt posted from
956  # an external site. Preview instead of saving.
957  wfDebug( __METHOD__ . ": Failed token check; forcing preview\n" );
958  $this->preview = true;
959  }
960  }
961  $this->save = !$this->preview && !$this->diff;
962  if ( !preg_match( '/^\d{14}$/', $this->edittime ) ) {
963  $this->edittime = null;
964  }
965 
966  if ( !preg_match( '/^\d{14}$/', $this->starttime ) ) {
967  $this->starttime = null;
968  }
969 
970  $this->recreate = $request->getCheck( 'wpRecreate' );
971 
972  $this->minoredit = $request->getCheck( 'wpMinoredit' );
973  $this->watchthis = $request->getCheck( 'wpWatchthis' );
974 
975  # Don't force edit summaries when a user is editing their own user or talk page
976  if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK )
977  && $this->mTitle->getText() == $wgUser->getName()
978  ) {
979  $this->allowBlankSummary = true;
980  } else {
981  $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' )
982  || !$wgUser->getOption( 'forceeditsummary' );
983  }
984 
985  $this->autoSumm = $request->getText( 'wpAutoSummary' );
986 
987  $this->allowBlankArticle = $request->getBool( 'wpIgnoreBlankArticle' );
988  $this->allowSelfRedirect = $request->getBool( 'wpIgnoreSelfRedirect' );
989 
990  $changeTags = $request->getVal( 'wpChangeTags' );
991  if ( is_null( $changeTags ) || $changeTags === '' ) {
992  $this->changeTags = [];
993  } else {
994  $this->changeTags = array_filter( array_map( 'trim', explode( ',',
995  $changeTags ) ) );
996  }
997  } else {
998  # Not a posted form? Start with nothing.
999  wfDebug( __METHOD__ . ": Not a posted form.\n" );
1000  $this->textbox1 = '';
1001  $this->summary = '';
1002  $this->sectiontitle = '';
1003  $this->edittime = '';
1004  $this->editRevId = null;
1005  $this->starttime = wfTimestampNow();
1006  $this->edit = false;
1007  $this->preview = false;
1008  $this->save = false;
1009  $this->diff = false;
1010  $this->minoredit = false;
1011  // Watch may be overridden by request parameters
1012  $this->watchthis = $request->getBool( 'watchthis', false );
1013  $this->recreate = false;
1014 
1015  // When creating a new section, we can preload a section title by passing it as the
1016  // preloadtitle parameter in the URL (T15100)
1017  if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
1018  $this->sectiontitle = $request->getVal( 'preloadtitle' );
1019  // Once wpSummary isn't being use for setting section titles, we should delete this.
1020  $this->summary = $request->getVal( 'preloadtitle' );
1021  } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
1022  $this->summary = $request->getText( 'summary' );
1023  if ( $this->summary !== '' ) {
1024  $this->hasPresetSummary = true;
1025  }
1026  }
1027 
1028  if ( $request->getVal( 'minor' ) ) {
1029  $this->minoredit = true;
1030  }
1031  }
1032 
1033  $this->oldid = $request->getInt( 'oldid' );
1034  $this->parentRevId = $request->getInt( 'parentRevId' );
1035 
1036  $this->bot = $request->getBool( 'bot', true );
1037  $this->nosummary = $request->getBool( 'nosummary' );
1038 
1039  // May be overridden by revision.
1040  $this->contentModel = $request->getText( 'model', $this->contentModel );
1041  // May be overridden by revision.
1042  $this->contentFormat = $request->getText( 'format', $this->contentFormat );
1043 
1044  try {
1045  $handler = ContentHandler::getForModelID( $this->contentModel );
1046  } catch ( MWUnknownContentModelException $e ) {
1047  throw new ErrorPageError(
1048  'editpage-invalidcontentmodel-title',
1049  'editpage-invalidcontentmodel-text',
1050  [ wfEscapeWikiText( $this->contentModel ) ]
1051  );
1052  }
1053 
1054  if ( !$handler->isSupportedFormat( $this->contentFormat ) ) {
1055  throw new ErrorPageError(
1056  'editpage-notsupportedcontentformat-title',
1057  'editpage-notsupportedcontentformat-text',
1058  [
1059  wfEscapeWikiText( $this->contentFormat ),
1060  wfEscapeWikiText( ContentHandler::getLocalizedName( $this->contentModel ) )
1061  ]
1062  );
1063  }
1064 
1071  $this->editintro = $request->getText( 'editintro',
1072  // Custom edit intro for new sections
1073  $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
1074 
1075  // Allow extensions to modify form data
1076  Hooks::run( 'EditPage::importFormData', [ $this, $request ] );
1077  }
1078 
1088  protected function importContentFormData( &$request ) {
1089  return; // Don't do anything, EditPage already extracted wpTextbox1
1090  }
1091 
1097  public function initialiseForm() {
1098  global $wgUser;
1099  $this->edittime = $this->page->getTimestamp();
1100  $this->editRevId = $this->page->getLatest();
1101 
1102  $content = $this->getContentObject( false ); # TODO: track content object?!
1103  if ( $content === false ) {
1104  return false;
1105  }
1106  $this->textbox1 = $this->toEditText( $content );
1107 
1108  // activate checkboxes if user wants them to be always active
1109  # Sort out the "watch" checkbox
1110  if ( $wgUser->getOption( 'watchdefault' ) ) {
1111  # Watch all edits
1112  $this->watchthis = true;
1113  } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
1114  # Watch creations
1115  $this->watchthis = true;
1116  } elseif ( $wgUser->isWatched( $this->mTitle ) ) {
1117  # Already watched
1118  $this->watchthis = true;
1119  }
1120  if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
1121  $this->minoredit = true;
1122  }
1123  if ( $this->textbox1 === false ) {
1124  return false;
1125  }
1126  return true;
1127  }
1128 
1136  protected function getContentObject( $def_content = null ) {
1138 
1139  $content = false;
1140 
1141  // For message page not locally set, use the i18n message.
1142  // For other non-existent articles, use preload text if any.
1143  if ( !$this->mTitle->exists() || $this->section == 'new' ) {
1144  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
1145  # If this is a system message, get the default text.
1146  $msg = $this->mTitle->getDefaultMessageText();
1147 
1148  $content = $this->toEditContent( $msg );
1149  }
1150  if ( $content === false ) {
1151  # If requested, preload some text.
1152  $preload = $wgRequest->getVal( 'preload',
1153  // Custom preload text for new sections
1154  $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
1155  $params = $wgRequest->getArray( 'preloadparams', [] );
1156 
1157  $content = $this->getPreloadedContent( $preload, $params );
1158  }
1159  // For existing pages, get text based on "undo" or section parameters.
1160  } else {
1161  if ( $this->section != '' ) {
1162  // Get section edit text (returns $def_text for invalid sections)
1163  $orig = $this->getOriginalContent( $wgUser );
1164  $content = $orig ? $orig->getSection( $this->section ) : null;
1165 
1166  if ( !$content ) {
1167  $content = $def_content;
1168  }
1169  } else {
1170  $undoafter = $wgRequest->getInt( 'undoafter' );
1171  $undo = $wgRequest->getInt( 'undo' );
1172 
1173  if ( $undo > 0 && $undoafter > 0 ) {
1174  $undorev = Revision::newFromId( $undo );
1175  $oldrev = Revision::newFromId( $undoafter );
1176 
1177  # Sanity check, make sure it's the right page,
1178  # the revisions exist and they were not deleted.
1179  # Otherwise, $content will be left as-is.
1180  if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
1181  !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
1182  !$oldrev->isDeleted( Revision::DELETED_TEXT )
1183  ) {
1184  $content = $this->page->getUndoContent( $undorev, $oldrev );
1185 
1186  if ( $content === false ) {
1187  # Warn the user that something went wrong
1188  $undoMsg = 'failure';
1189  } else {
1190  $oldContent = $this->page->getContent( Revision::RAW );
1192  $newContent = $content->preSaveTransform( $this->mTitle, $wgUser, $popts );
1193  if ( $newContent->getModel() !== $oldContent->getModel() ) {
1194  // The undo may change content
1195  // model if its reverting the top
1196  // edit. This can result in
1197  // mismatched content model/format.
1198  $this->contentModel = $newContent->getModel();
1199  $this->contentFormat = $oldrev->getContentFormat();
1200  }
1201 
1202  if ( $newContent->equals( $oldContent ) ) {
1203  # Tell the user that the undo results in no change,
1204  # i.e. the revisions were already undone.
1205  $undoMsg = 'nochange';
1206  $content = false;
1207  } else {
1208  # Inform the user of our success and set an automatic edit summary
1209  $undoMsg = 'success';
1210 
1211  # If we just undid one rev, use an autosummary
1212  $firstrev = $oldrev->getNext();
1213  if ( $firstrev && $firstrev->getId() == $undo ) {
1214  $userText = $undorev->getUserText();
1215  if ( $userText === '' ) {
1216  $undoSummary = $this->context->msg(
1217  'undo-summary-username-hidden',
1218  $undo
1219  )->inContentLanguage()->text();
1220  } else {
1221  $undoSummary = $this->context->msg(
1222  'undo-summary',
1223  $undo,
1224  $userText
1225  )->inContentLanguage()->text();
1226  }
1227  if ( $this->summary === '' ) {
1228  $this->summary = $undoSummary;
1229  } else {
1230  $this->summary = $undoSummary . $this->context->msg( 'colon-separator' )
1231  ->inContentLanguage()->text() . $this->summary;
1232  }
1233  $this->undidRev = $undo;
1234  }
1235  $this->formtype = 'diff';
1236  }
1237  }
1238  } else {
1239  // Failed basic sanity checks.
1240  // Older revisions may have been removed since the link
1241  // was created, or we may simply have got bogus input.
1242  $undoMsg = 'norev';
1243  }
1244 
1245  // Messages: undo-success, undo-failure, undo-norev, undo-nochange
1246  $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
1247  $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
1248  $this->context->msg( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
1249  }
1250 
1251  if ( $content === false ) {
1252  $content = $this->getOriginalContent( $wgUser );
1253  }
1254  }
1255  }
1256 
1257  return $content;
1258  }
1259 
1275  private function getOriginalContent( User $user ) {
1276  if ( $this->section == 'new' ) {
1277  return $this->getCurrentContent();
1278  }
1279  $revision = $this->mArticle->getRevisionFetched();
1280  if ( $revision === null ) {
1281  $handler = ContentHandler::getForModelID( $this->contentModel );
1282  return $handler->makeEmptyContent();
1283  }
1284  $content = $revision->getContent( Revision::FOR_THIS_USER, $user );
1285  return $content;
1286  }
1287 
1300  public function getParentRevId() {
1301  if ( $this->parentRevId ) {
1302  return $this->parentRevId;
1303  } else {
1304  return $this->mArticle->getRevIdFetched();
1305  }
1306  }
1307 
1316  protected function getCurrentContent() {
1317  $rev = $this->page->getRevision();
1318  $content = $rev ? $rev->getContent( Revision::RAW ) : null;
1319 
1320  if ( $content === false || $content === null ) {
1321  $handler = ContentHandler::getForModelID( $this->contentModel );
1322  return $handler->makeEmptyContent();
1323  } elseif ( !$this->undidRev ) {
1324  // Content models should always be the same since we error
1325  // out if they are different before this point (in ->edit()).
1326  // The exception being, during an undo, the current revision might
1327  // differ from the prior revision.
1328  $logger = LoggerFactory::getInstance( 'editpage' );
1329  if ( $this->contentModel !== $rev->getContentModel() ) {
1330  $logger->warning( "Overriding content model from current edit {prev} to {new}", [
1331  'prev' => $this->contentModel,
1332  'new' => $rev->getContentModel(),
1333  'title' => $this->getTitle()->getPrefixedDBkey(),
1334  'method' => __METHOD__
1335  ] );
1336  $this->contentModel = $rev->getContentModel();
1337  }
1338 
1339  // Given that the content models should match, the current selected
1340  // format should be supported.
1341  if ( !$content->isSupportedFormat( $this->contentFormat ) ) {
1342  $logger->warning( "Current revision content format unsupported. Overriding {prev} to {new}", [
1343 
1344  'prev' => $this->contentFormat,
1345  'new' => $rev->getContentFormat(),
1346  'title' => $this->getTitle()->getPrefixedDBkey(),
1347  'method' => __METHOD__
1348  ] );
1349  $this->contentFormat = $rev->getContentFormat();
1350  }
1351  }
1352  return $content;
1353  }
1354 
1362  public function setPreloadedContent( Content $content ) {
1363  $this->mPreloadContent = $content;
1364  }
1365 
1377  protected function getPreloadedContent( $preload, $params = [] ) {
1378  global $wgUser;
1379 
1380  if ( !empty( $this->mPreloadContent ) ) {
1381  return $this->mPreloadContent;
1382  }
1383 
1384  $handler = ContentHandler::getForModelID( $this->contentModel );
1385 
1386  if ( $preload === '' ) {
1387  return $handler->makeEmptyContent();
1388  }
1389 
1390  $title = Title::newFromText( $preload );
1391  # Check for existence to avoid getting MediaWiki:Noarticletext
1392  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
1393  // TODO: somehow show a warning to the user!
1394  return $handler->makeEmptyContent();
1395  }
1396 
1398  if ( $page->isRedirect() ) {
1400  # Same as before
1401  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
1402  // TODO: somehow show a warning to the user!
1403  return $handler->makeEmptyContent();
1404  }
1406  }
1407 
1408  $parserOptions = ParserOptions::newFromUser( $wgUser );
1410 
1411  if ( !$content ) {
1412  // TODO: somehow show a warning to the user!
1413  return $handler->makeEmptyContent();
1414  }
1415 
1416  if ( $content->getModel() !== $handler->getModelID() ) {
1417  $converted = $content->convert( $handler->getModelID() );
1418 
1419  if ( !$converted ) {
1420  // TODO: somehow show a warning to the user!
1421  wfDebug( "Attempt to preload incompatible content: " .
1422  "can't convert " . $content->getModel() .
1423  " to " . $handler->getModelID() );
1424 
1425  return $handler->makeEmptyContent();
1426  }
1427 
1428  $content = $converted;
1429  }
1430 
1431  return $content->preloadTransform( $title, $parserOptions, $params );
1432  }
1433 
1441  public function tokenOk( &$request ) {
1442  global $wgUser;
1443  $token = $request->getVal( 'wpEditToken' );
1444  $this->mTokenOk = $wgUser->matchEditToken( $token );
1445  $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
1446  return $this->mTokenOk;
1447  }
1448 
1465  protected function setPostEditCookie( $statusValue ) {
1466  $revisionId = $this->page->getLatest();
1467  $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1468 
1469  $val = 'saved';
1470  if ( $statusValue == self::AS_SUCCESS_NEW_ARTICLE ) {
1471  $val = 'created';
1472  } elseif ( $this->oldid ) {
1473  $val = 'restored';
1474  }
1475 
1476  $response = RequestContext::getMain()->getRequest()->response();
1477  $response->setCookie( $postEditKey, $val, time() + self::POST_EDIT_COOKIE_DURATION, [
1478  'httpOnly' => false,
1479  ] );
1480  }
1481 
1488  public function attemptSave( &$resultDetails = false ) {
1489  global $wgUser;
1490 
1491  # Allow bots to exempt some edits from bot flagging
1492  $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
1493  $status = $this->internalAttemptSave( $resultDetails, $bot );
1494 
1495  Hooks::run( 'EditPage::attemptSave:after', [ $this, $status, $resultDetails ] );
1496 
1497  return $status;
1498  }
1499 
1509  private function handleStatus( Status $status, $resultDetails ) {
1511 
1516  if ( $status->value == self::AS_SUCCESS_UPDATE
1517  || $status->value == self::AS_SUCCESS_NEW_ARTICLE
1518  ) {
1519  $this->didSave = true;
1520  if ( !$resultDetails['nullEdit'] ) {
1521  $this->setPostEditCookie( $status->value );
1522  }
1523  }
1524 
1525  // "wpExtraQueryRedirect" is a hidden input to modify
1526  // after save URL and is not used by actual edit form
1527  $request = RequestContext::getMain()->getRequest();
1528  $extraQueryRedirect = $request->getVal( 'wpExtraQueryRedirect' );
1529 
1530  switch ( $status->value ) {
1538  case self::AS_END:
1541  return true;
1542 
1543  case self::AS_HOOK_ERROR:
1544  return false;
1545 
1547  case self::AS_PARSE_ERROR:
1548  $wgOut->addWikiText( '<div class="error">' . "\n" . $status->getWikiText() . '</div>' );
1549  return true;
1550 
1552  $query = $resultDetails['redirect'] ? 'redirect=no' : '';
1553  if ( $extraQueryRedirect ) {
1554  if ( $query === '' ) {
1555  $query = $extraQueryRedirect;
1556  } else {
1557  $query = $query . '&' . $extraQueryRedirect;
1558  }
1559  }
1560  $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
1561  $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
1562  return false;
1563 
1565  $extraQuery = '';
1566  $sectionanchor = $resultDetails['sectionanchor'];
1567 
1568  // Give extensions a chance to modify URL query on update
1569  Hooks::run(
1570  'ArticleUpdateBeforeRedirect',
1571  [ $this->mArticle, &$sectionanchor, &$extraQuery ]
1572  );
1573 
1574  if ( $resultDetails['redirect'] ) {
1575  if ( $extraQuery == '' ) {
1576  $extraQuery = 'redirect=no';
1577  } else {
1578  $extraQuery = 'redirect=no&' . $extraQuery;
1579  }
1580  }
1581  if ( $extraQueryRedirect ) {
1582  if ( $extraQuery === '' ) {
1583  $extraQuery = $extraQueryRedirect;
1584  } else {
1585  $extraQuery = $extraQuery . '&' . $extraQueryRedirect;
1586  }
1587  }
1588 
1589  $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1590  return false;
1591 
1592  case self::AS_SPAM_ERROR:
1593  $this->spamPageWithContent( $resultDetails['spam'] );
1594  return false;
1595 
1597  throw new UserBlockedError( $wgUser->getBlock() );
1598 
1601  throw new PermissionsError( 'upload' );
1602 
1605  throw new PermissionsError( 'edit' );
1606 
1608  throw new ReadOnlyError;
1609 
1610  case self::AS_RATE_LIMITED:
1611  throw new ThrottledError();
1612 
1614  $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
1615  throw new PermissionsError( $permission );
1616 
1618  throw new PermissionsError( 'editcontentmodel' );
1619 
1620  default:
1621  // We don't recognize $status->value. The only way that can happen
1622  // is if an extension hook aborted from inside ArticleSave.
1623  // Render the status object into $this->hookError
1624  // FIXME this sucks, we should just use the Status object throughout
1625  $this->hookError = '<div class="error">' ."\n" . $status->getWikiText() .
1626  '</div>';
1627  return true;
1628  }
1629  }
1630 
1641  // Run old style post-section-merge edit filter
1642  if ( $this->hookError != '' ) {
1643  # ...or the hook could be expecting us to produce an error
1644  $status->fatal( 'hookaborted' );
1646  return false;
1647  }
1648 
1649  // Run new style post-section-merge edit filter
1650  if ( !Hooks::run( 'EditFilterMergedContent',
1651  [ $this->mArticle->getContext(), $content, $status, $this->summary,
1652  $user, $this->minoredit ] )
1653  ) {
1654  # Error messages etc. could be handled within the hook...
1655  if ( $status->isGood() ) {
1656  $status->fatal( 'hookaborted' );
1657  // Not setting $this->hookError here is a hack to allow the hook
1658  // to cause a return to the edit page without $this->hookError
1659  // being set. This is used by ConfirmEdit to display a captcha
1660  // without any error message cruft.
1661  } else {
1662  $this->hookError = $status->getWikiText();
1663  }
1664  // Use the existing $status->value if the hook set it
1665  if ( !$status->value ) {
1666  $status->value = self::AS_HOOK_ERROR;
1667  }
1668  return false;
1669  } elseif ( !$status->isOK() ) {
1670  # ...or the hook could be expecting us to produce an error
1671  // FIXME this sucks, we should just use the Status object throughout
1672  $this->hookError = $status->getWikiText();
1673  $status->fatal( 'hookaborted' );
1675  return false;
1676  }
1677 
1678  return true;
1679  }
1680 
1687  private function newSectionSummary( &$sectionanchor = null ) {
1688  global $wgParser;
1689 
1690  if ( $this->sectiontitle !== '' ) {
1691  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
1692  // If no edit summary was specified, create one automatically from the section
1693  // title and have it link to the new section. Otherwise, respect the summary as
1694  // passed.
1695  if ( $this->summary === '' ) {
1696  $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
1697  return $this->context->msg( 'newsectionsummary' )
1698  ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1699  }
1700  } elseif ( $this->summary !== '' ) {
1701  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
1702  # This is a new section, so create a link to the new section
1703  # in the revision summary.
1704  $cleanSummary = $wgParser->stripSectionName( $this->summary );
1705  return $this->context->msg( 'newsectionsummary' )
1706  ->rawParams( $cleanSummary )->inContentLanguage()->text();
1707  }
1708  return $this->summary;
1709  }
1710 
1735  public function internalAttemptSave( &$result, $bot = false ) {
1737  global $wgContentHandlerUseDB;
1738 
1740 
1741  if ( !Hooks::run( 'EditPage::attemptSave', [ $this ] ) ) {
1742  wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
1743  $status->fatal( 'hookaborted' );
1744  $status->value = self::AS_HOOK_ERROR;
1745  return $status;
1746  }
1747 
1748  $spam = $wgRequest->getText( 'wpAntispam' );
1749  if ( $spam !== '' ) {
1750  wfDebugLog(
1751  'SimpleAntiSpam',
1752  $wgUser->getName() .
1753  ' editing "' .
1754  $this->mTitle->getPrefixedText() .
1755  '" submitted bogus field "' .
1756  $spam .
1757  '"'
1758  );
1759  $status->fatal( 'spamprotectionmatch', false );
1760  $status->value = self::AS_SPAM_ERROR;
1761  return $status;
1762  }
1763 
1764  try {
1765  # Construct Content object
1766  $textbox_content = $this->toEditContent( $this->textbox1 );
1767  } catch ( MWContentSerializationException $ex ) {
1768  $status->fatal(
1769  'content-failed-to-parse',
1770  $this->contentModel,
1771  $this->contentFormat,
1772  $ex->getMessage()
1773  );
1774  $status->value = self::AS_PARSE_ERROR;
1775  return $status;
1776  }
1777 
1778  # Check image redirect
1779  if ( $this->mTitle->getNamespace() == NS_FILE &&
1780  $textbox_content->isRedirect() &&
1781  !$wgUser->isAllowed( 'upload' )
1782  ) {
1784  $status->setResult( false, $code );
1785 
1786  return $status;
1787  }
1788 
1789  # Check for spam
1790  $match = self::matchSummarySpamRegex( $this->summary );
1791  if ( $match === false && $this->section == 'new' ) {
1792  # $wgSpamRegex is enforced on this new heading/summary because, unlike
1793  # regular summaries, it is added to the actual wikitext.
1794  if ( $this->sectiontitle !== '' ) {
1795  # This branch is taken when the API is used with the 'sectiontitle' parameter.
1796  $match = self::matchSpamRegex( $this->sectiontitle );
1797  } else {
1798  # This branch is taken when the "Add Topic" user interface is used, or the API
1799  # is used with the 'summary' parameter.
1800  $match = self::matchSpamRegex( $this->summary );
1801  }
1802  }
1803  if ( $match === false ) {
1804  $match = self::matchSpamRegex( $this->textbox1 );
1805  }
1806  if ( $match !== false ) {
1807  $result['spam'] = $match;
1808  $ip = $wgRequest->getIP();
1809  $pdbk = $this->mTitle->getPrefixedDBkey();
1810  $match = str_replace( "\n", '', $match );
1811  wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
1812  $status->fatal( 'spamprotectionmatch', $match );
1813  $status->value = self::AS_SPAM_ERROR;
1814  return $status;
1815  }
1816  if ( !Hooks::run(
1817  'EditFilter',
1818  [ $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ] )
1819  ) {
1820  # Error messages etc. could be handled within the hook...
1821  $status->fatal( 'hookaborted' );
1822  $status->value = self::AS_HOOK_ERROR;
1823  return $status;
1824  } elseif ( $this->hookError != '' ) {
1825  # ...or the hook could be expecting us to produce an error
1826  $status->fatal( 'hookaborted' );
1828  return $status;
1829  }
1830 
1831  if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) {
1832  // Auto-block user's IP if the account was "hard" blocked
1833  if ( !wfReadOnly() ) {
1834  $wgUser->spreadAnyEditBlock();
1835  }
1836  # Check block state against master, thus 'false'.
1837  $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
1838  return $status;
1839  }
1840 
1841  $this->contentLength = strlen( $this->textbox1 );
1842  if ( $this->contentLength > $wgMaxArticleSize * 1024 ) {
1843  // Error will be displayed by showEditForm()
1844  $this->tooBig = true;
1845  $status->setResult( false, self::AS_CONTENT_TOO_BIG );
1846  return $status;
1847  }
1848 
1849  if ( !$wgUser->isAllowed( 'edit' ) ) {
1850  if ( $wgUser->isAnon() ) {
1851  $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
1852  return $status;
1853  } else {
1854  $status->fatal( 'readonlytext' );
1856  return $status;
1857  }
1858  }
1859 
1860  $changingContentModel = false;
1861  if ( $this->contentModel !== $this->mTitle->getContentModel() ) {
1862  if ( !$wgContentHandlerUseDB ) {
1863  $status->fatal( 'editpage-cannot-use-custom-model' );
1865  return $status;
1866  } elseif ( !$wgUser->isAllowed( 'editcontentmodel' ) ) {
1867  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
1868  return $status;
1869  }
1870  // Make sure the user can edit the page under the new content model too
1871  $titleWithNewContentModel = clone $this->mTitle;
1872  $titleWithNewContentModel->setContentModel( $this->contentModel );
1873  if ( !$titleWithNewContentModel->userCan( 'editcontentmodel', $wgUser )
1874  || !$titleWithNewContentModel->userCan( 'edit', $wgUser )
1875  ) {
1876  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
1877  return $status;
1878  }
1879 
1880  $changingContentModel = true;
1881  $oldContentModel = $this->mTitle->getContentModel();
1882  }
1883 
1884  if ( $this->changeTags ) {
1885  $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange(
1886  $this->changeTags, $wgUser );
1887  if ( !$changeTagsStatus->isOK() ) {
1888  $changeTagsStatus->value = self::AS_CHANGE_TAG_ERROR;
1889  return $changeTagsStatus;
1890  }
1891  }
1892 
1893  if ( wfReadOnly() ) {
1894  $status->fatal( 'readonlytext' );
1896  return $status;
1897  }
1898  if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 )
1899  || ( $changingContentModel && $wgUser->pingLimiter( 'editcontentmodel' ) )
1900  ) {
1901  $status->fatal( 'actionthrottledtext' );
1902  $status->value = self::AS_RATE_LIMITED;
1903  return $status;
1904  }
1905 
1906  # If the article has been deleted while editing, don't save it without
1907  # confirmation
1908  if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
1909  $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
1910  return $status;
1911  }
1912 
1913  # Load the page data from the master. If anything changes in the meantime,
1914  # we detect it by using page_latest like a token in a 1 try compare-and-swap.
1915  $this->page->loadPageData( 'fromdbmaster' );
1916  $new = !$this->page->exists();
1917 
1918  if ( $new ) {
1919  // Late check for create permission, just in case *PARANOIA*
1920  if ( !$this->mTitle->userCan( 'create', $wgUser ) ) {
1921  $status->fatal( 'nocreatetext' );
1923  wfDebug( __METHOD__ . ": no create permission\n" );
1924  return $status;
1925  }
1926 
1927  // Don't save a new page if it's blank or if it's a MediaWiki:
1928  // message with content equivalent to default (allow empty pages
1929  // in this case to disable messages, see T52124)
1930  $defaultMessageText = $this->mTitle->getDefaultMessageText();
1931  if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false ) {
1932  $defaultText = $defaultMessageText;
1933  } else {
1934  $defaultText = '';
1935  }
1936 
1937  if ( !$this->allowBlankArticle && $this->textbox1 === $defaultText ) {
1938  $this->blankArticle = true;
1939  $status->fatal( 'blankarticle' );
1940  $status->setResult( false, self::AS_BLANK_ARTICLE );
1941  return $status;
1942  }
1943 
1944  if ( !$this->runPostMergeFilters( $textbox_content, $status, $wgUser ) ) {
1945  return $status;
1946  }
1947 
1948  $content = $textbox_content;
1949 
1950  $result['sectionanchor'] = '';
1951  if ( $this->section == 'new' ) {
1952  if ( $this->sectiontitle !== '' ) {
1953  // Insert the section title above the content.
1954  $content = $content->addSectionHeader( $this->sectiontitle );
1955  } elseif ( $this->summary !== '' ) {
1956  // Insert the section title above the content.
1957  $content = $content->addSectionHeader( $this->summary );
1958  }
1959  $this->summary = $this->newSectionSummary( $result['sectionanchor'] );
1960  }
1961 
1963 
1964  } else { # not $new
1965 
1966  # Article exists. Check for edit conflict.
1967 
1968  $this->page->clear(); # Force reload of dates, etc.
1969  $timestamp = $this->page->getTimestamp();
1970  $latest = $this->page->getLatest();
1971 
1972  wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
1973 
1974  // Check editRevId if set, which handles same-second timestamp collisions
1975  if ( $timestamp != $this->edittime
1976  || ( $this->editRevId !== null && $this->editRevId != $latest )
1977  ) {
1978  $this->isConflict = true;
1979  if ( $this->section == 'new' ) {
1980  if ( $this->page->getUserText() == $wgUser->getName() &&
1981  $this->page->getComment() == $this->newSectionSummary()
1982  ) {
1983  // Probably a duplicate submission of a new comment.
1984  // This can happen when CDN resends a request after
1985  // a timeout but the first one actually went through.
1986  wfDebug( __METHOD__
1987  . ": duplicate new section submission; trigger edit conflict!\n" );
1988  } else {
1989  // New comment; suppress conflict.
1990  $this->isConflict = false;
1991  wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
1992  }
1993  } elseif ( $this->section == ''
1995  DB_MASTER, $this->mTitle->getArticleID(),
1996  $wgUser->getId(), $this->edittime
1997  )
1998  ) {
1999  # Suppress edit conflict with self, except for section edits where merging is required.
2000  wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
2001  $this->isConflict = false;
2002  }
2003  }
2004 
2005  // If sectiontitle is set, use it, otherwise use the summary as the section title.
2006  if ( $this->sectiontitle !== '' ) {
2007  $sectionTitle = $this->sectiontitle;
2008  } else {
2009  $sectionTitle = $this->summary;
2010  }
2011 
2012  $content = null;
2013 
2014  if ( $this->isConflict ) {
2015  wfDebug( __METHOD__
2016  . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
2017  . " (id '{$this->editRevId}') (article time '{$timestamp}')\n" );
2018  // @TODO: replaceSectionAtRev() with base ID (not prior current) for ?oldid=X case
2019  // ...or disable section editing for non-current revisions (not exposed anyway).
2020  if ( $this->editRevId !== null ) {
2021  $content = $this->page->replaceSectionAtRev(
2022  $this->section,
2023  $textbox_content,
2024  $sectionTitle,
2025  $this->editRevId
2026  );
2027  } else {
2028  $content = $this->page->replaceSectionContent(
2029  $this->section,
2030  $textbox_content,
2031  $sectionTitle,
2032  $this->edittime
2033  );
2034  }
2035  } else {
2036  wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
2037  $content = $this->page->replaceSectionContent(
2038  $this->section,
2039  $textbox_content,
2040  $sectionTitle
2041  );
2042  }
2043 
2044  if ( is_null( $content ) ) {
2045  wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
2046  $this->isConflict = true;
2047  $content = $textbox_content; // do not try to merge here!
2048  } elseif ( $this->isConflict ) {
2049  # Attempt merge
2050  if ( $this->mergeChangesIntoContent( $content ) ) {
2051  // Successful merge! Maybe we should tell the user the good news?
2052  $this->isConflict = false;
2053  wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
2054  } else {
2055  $this->section = '';
2056  $this->textbox1 = ContentHandler::getContentText( $content );
2057  wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
2058  }
2059  }
2060 
2061  if ( $this->isConflict ) {
2062  $status->setResult( false, self::AS_CONFLICT_DETECTED );
2063  return $status;
2064  }
2065 
2066  if ( !$this->runPostMergeFilters( $content, $status, $wgUser ) ) {
2067  return $status;
2068  }
2069 
2070  if ( $this->section == 'new' ) {
2071  // Handle the user preference to force summaries here
2072  if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) {
2073  $this->missingSummary = true;
2074  $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
2076  return $status;
2077  }
2078 
2079  // Do not allow the user to post an empty comment
2080  if ( $this->textbox1 == '' ) {
2081  $this->missingComment = true;
2082  $status->fatal( 'missingcommenttext' );
2084  return $status;
2085  }
2086  } elseif ( !$this->allowBlankSummary
2087  && !$content->equals( $this->getOriginalContent( $wgUser ) )
2088  && !$content->isRedirect()
2089  && md5( $this->summary ) == $this->autoSumm
2090  ) {
2091  $this->missingSummary = true;
2092  $status->fatal( 'missingsummary' );
2094  return $status;
2095  }
2096 
2097  # All's well
2098  $sectionanchor = '';
2099  if ( $this->section == 'new' ) {
2100  $this->summary = $this->newSectionSummary( $sectionanchor );
2101  } elseif ( $this->section != '' ) {
2102  # Try to get a section anchor from the section source, redirect
2103  # to edited section if header found.
2104  # XXX: Might be better to integrate this into Article::replaceSectionAtRev
2105  # for duplicate heading checking and maybe parsing.
2106  $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
2107  # We can't deal with anchors, includes, html etc in the header for now,
2108  # headline would need to be parsed to improve this.
2109  if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
2110  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
2111  }
2112  }
2113  $result['sectionanchor'] = $sectionanchor;
2114 
2115  // Save errors may fall down to the edit form, but we've now
2116  // merged the section into full text. Clear the section field
2117  // so that later submission of conflict forms won't try to
2118  // replace that into a duplicated mess.
2119  $this->textbox1 = $this->toEditText( $content );
2120  $this->section = '';
2121 
2123  }
2124 
2125  if ( !$this->allowSelfRedirect
2126  && $content->isRedirect()
2127  && $content->getRedirectTarget()->equals( $this->getTitle() )
2128  ) {
2129  // If the page already redirects to itself, don't warn.
2130  $currentTarget = $this->getCurrentContent()->getRedirectTarget();
2131  if ( !$currentTarget || !$currentTarget->equals( $this->getTitle() ) ) {
2132  $this->selfRedirect = true;
2133  $status->fatal( 'selfredirect' );
2135  return $status;
2136  }
2137  }
2138 
2139  // Check for length errors again now that the section is merged in
2140  $this->contentLength = strlen( $this->toEditText( $content ) );
2141  if ( $this->contentLength > $wgMaxArticleSize * 1024 ) {
2142  $this->tooBig = true;
2143  $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
2144  return $status;
2145  }
2146 
2148  ( $new ? EDIT_NEW : EDIT_UPDATE ) |
2149  ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
2150  ( $bot ? EDIT_FORCE_BOT : 0 );
2151 
2152  $doEditStatus = $this->page->doEditContent(
2153  $content,
2154  $this->summary,
2155  $flags,
2156  false,
2157  $wgUser,
2158  $content->getDefaultFormat(),
2161  );
2162 
2163  if ( !$doEditStatus->isOK() ) {
2164  // Failure from doEdit()
2165  // Show the edit conflict page for certain recognized errors from doEdit(),
2166  // but don't show it for errors from extension hooks
2167  $errors = $doEditStatus->getErrorsArray();
2168  if ( in_array( $errors[0][0],
2169  [ 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ] )
2170  ) {
2171  $this->isConflict = true;
2172  // Destroys data doEdit() put in $status->value but who cares
2173  $doEditStatus->value = self::AS_END;
2174  }
2175  return $doEditStatus;
2176  }
2177 
2178  $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
2179  if ( $result['nullEdit'] ) {
2180  // We don't know if it was a null edit until now, so increment here
2181  $wgUser->pingLimiter( 'linkpurge' );
2182  }
2183  $result['redirect'] = $content->isRedirect();
2184 
2185  $this->updateWatchlist();
2186 
2187  // If the content model changed, add a log entry
2188  if ( $changingContentModel ) {
2190  $wgUser,
2191  $new ? false : $oldContentModel,
2192  $this->contentModel,
2193  $this->summary
2194  );
2195  }
2196 
2197  return $status;
2198  }
2199 
2206  protected function addContentModelChangeLogEntry( User $user, $oldModel, $newModel, $reason ) {
2207  $new = $oldModel === false;
2208  $log = new ManualLogEntry( 'contentmodel', $new ? 'new' : 'change' );
2209  $log->setPerformer( $user );
2210  $log->setTarget( $this->mTitle );
2211  $log->setComment( $reason );
2212  $log->setParameters( [
2213  '4::oldmodel' => $oldModel,
2214  '5::newmodel' => $newModel
2215  ] );
2216  $logid = $log->insert();
2217  $log->publish( $logid );
2218  }
2219 
2223  protected function updateWatchlist() {
2224  global $wgUser;
2225 
2226  if ( !$wgUser->isLoggedIn() ) {
2227  return;
2228  }
2229 
2230  $user = $wgUser;
2232  $watch = $this->watchthis;
2233  // Do this in its own transaction to reduce contention...
2234  DeferredUpdates::addCallableUpdate( function () use ( $user, $title, $watch ) {
2235  if ( $watch == $user->isWatched( $title, User::IGNORE_USER_RIGHTS ) ) {
2236  return; // nothing to change
2237  }
2239  } );
2240  }
2241 
2253  private function mergeChangesIntoContent( &$editContent ) {
2254  $db = wfGetDB( DB_MASTER );
2255 
2256  // This is the revision the editor started from
2257  $baseRevision = $this->getBaseRevision();
2258  $baseContent = $baseRevision ? $baseRevision->getContent() : null;
2259 
2260  if ( is_null( $baseContent ) ) {
2261  return false;
2262  }
2263 
2264  // The current state, we want to merge updates into it
2265  $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
2266  $currentContent = $currentRevision ? $currentRevision->getContent() : null;
2267 
2268  if ( is_null( $currentContent ) ) {
2269  return false;
2270  }
2271 
2272  $handler = ContentHandler::getForModelID( $baseContent->getModel() );
2273 
2274  $result = $handler->merge3( $baseContent, $editContent, $currentContent );
2275 
2276  if ( $result ) {
2277  $editContent = $result;
2278  // Update parentRevId to what we just merged.
2279  $this->parentRevId = $currentRevision->getId();
2280  return true;
2281  }
2282 
2283  return false;
2284  }
2285 
2291  public function getBaseRevision() {
2292  if ( !$this->mBaseRevision ) {
2293  $db = wfGetDB( DB_MASTER );
2294  $this->mBaseRevision = $this->editRevId
2295  ? Revision::newFromId( $this->editRevId, Revision::READ_LATEST )
2296  : Revision::loadFromTimestamp( $db, $this->mTitle, $this->edittime );
2297  }
2298  return $this->mBaseRevision;
2299  }
2300 
2308  public static function matchSpamRegex( $text ) {
2309  global $wgSpamRegex;
2310  // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
2311  $regexes = (array)$wgSpamRegex;
2312  return self::matchSpamRegexInternal( $text, $regexes );
2313  }
2314 
2322  public static function matchSummarySpamRegex( $text ) {
2323  global $wgSummarySpamRegex;
2324  $regexes = (array)$wgSummarySpamRegex;
2325  return self::matchSpamRegexInternal( $text, $regexes );
2326  }
2327 
2333  protected static function matchSpamRegexInternal( $text, $regexes ) {
2334  foreach ( $regexes as $regex ) {
2335  $matches = [];
2336  if ( preg_match( $regex, $text, $matches ) ) {
2337  return $matches[0];
2338  }
2339  }
2340  return false;
2341  }
2342 
2343  public function setHeaders() {
2344  global $wgOut, $wgUser, $wgAjaxEditStash;
2345 
2346  $wgOut->addModules( 'mediawiki.action.edit' );
2347  $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
2348 
2349  if ( $wgUser->getOption( 'showtoolbar' ) ) {
2350  // The addition of default buttons is handled by getEditToolbar() which
2351  // has its own dependency on this module. The call here ensures the module
2352  // is loaded in time (it has position "top") for other modules to register
2353  // buttons (e.g. extensions, gadgets, user scripts).
2354  $wgOut->addModules( 'mediawiki.toolbar' );
2355  }
2356 
2357  if ( $wgUser->getOption( 'uselivepreview' ) ) {
2358  $wgOut->addModules( 'mediawiki.action.edit.preview' );
2359  }
2360 
2361  if ( $wgUser->getOption( 'useeditwarning' ) ) {
2362  $wgOut->addModules( 'mediawiki.action.edit.editWarning' );
2363  }
2364 
2365  # Enabled article-related sidebar, toplinks, etc.
2366  $wgOut->setArticleRelated( true );
2367 
2368  $contextTitle = $this->getContextTitle();
2369  if ( $this->isConflict ) {
2370  $msg = 'editconflict';
2371  } elseif ( $contextTitle->exists() && $this->section != '' ) {
2372  $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
2373  } else {
2374  $msg = $contextTitle->exists()
2375  || ( $contextTitle->getNamespace() == NS_MEDIAWIKI
2376  && $contextTitle->getDefaultMessageText() !== false
2377  )
2378  ? 'editing'
2379  : 'creating';
2380  }
2381 
2382  # Use the title defined by DISPLAYTITLE magic word when present
2383  # NOTE: getDisplayTitle() returns HTML while getPrefixedText() returns plain text.
2384  # setPageTitle() treats the input as wikitext, which should be safe in either case.
2385  $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
2386  if ( $displayTitle === false ) {
2387  $displayTitle = $contextTitle->getPrefixedText();
2388  }
2389  $wgOut->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
2390  # Transmit the name of the message to JavaScript for live preview
2391  # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
2392  $wgOut->addJsConfigVars( [
2393  'wgEditMessage' => $msg,
2394  'wgAjaxEditStash' => $wgAjaxEditStash,
2395  ] );
2396  }
2397 
2401  protected function showIntro() {
2403  if ( $this->suppressIntro ) {
2404  return;
2405  }
2406 
2407  $namespace = $this->mTitle->getNamespace();
2408 
2409  if ( $namespace == NS_MEDIAWIKI ) {
2410  # Show a warning if editing an interface message
2411  $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
2412  # If this is a default message (but not css or js),
2413  # show a hint that it is translatable on translatewiki.net
2414  if ( !$this->mTitle->hasContentModel( CONTENT_MODEL_CSS )
2415  && !$this->mTitle->hasContentModel( CONTENT_MODEL_JAVASCRIPT )
2416  ) {
2417  $defaultMessageText = $this->mTitle->getDefaultMessageText();
2418  if ( $defaultMessageText !== false ) {
2419  $wgOut->wrapWikiMsg( "<div class='mw-translateinterface'>\n$1\n</div>",
2420  'translateinterface' );
2421  }
2422  }
2423  } elseif ( $namespace == NS_FILE ) {
2424  # Show a hint to shared repo
2425  $file = wfFindFile( $this->mTitle );
2426  if ( $file && !$file->isLocal() ) {
2427  $descUrl = $file->getDescriptionUrl();
2428  # there must be a description url to show a hint to shared repo
2429  if ( $descUrl ) {
2430  if ( !$this->mTitle->exists() ) {
2431  $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", [
2432  'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
2433  ] );
2434  } else {
2435  $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", [
2436  'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
2437  ] );
2438  }
2439  }
2440  }
2441  }
2442 
2443  # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2444  # Show log extract when the user is currently blocked
2445  if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
2446  $username = explode( '/', $this->mTitle->getText(), 2 )[0];
2447  $user = User::newFromName( $username, false /* allow IP users */ );
2448  $ip = User::isIP( $username );
2449  $block = Block::newFromTarget( $user, $user );
2450  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
2451  $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2452  [ 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ] );
2453  } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
2454  # Show log extract if the user is currently blocked
2456  $wgOut,
2457  'block',
2458  MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
2459  '',
2460  [
2461  'lim' => 1,
2462  'showIfEmpty' => false,
2463  'msgKey' => [
2464  'blocked-notice-logextract',
2465  $user->getName() # Support GENDER in notice
2466  ]
2467  ]
2468  );
2469  }
2470  }
2471  # Try to add a custom edit intro, or use the standard one if this is not possible.
2472  if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
2474  $this->context->msg( 'helppage' )->inContentLanguage()->text()
2475  ) );
2476  if ( $wgUser->isLoggedIn() ) {
2477  $wgOut->wrapWikiMsg(
2478  // Suppress the external link icon, consider the help url an internal one
2479  "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2480  [
2481  'newarticletext',
2482  $helpLink
2483  ]
2484  );
2485  } else {
2486  $wgOut->wrapWikiMsg(
2487  // Suppress the external link icon, consider the help url an internal one
2488  "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2489  [
2490  'newarticletextanon',
2491  $helpLink
2492  ]
2493  );
2494  }
2495  }
2496  # Give a notice if the user is editing a deleted/moved page...
2497  if ( !$this->mTitle->exists() ) {
2498  $dbr = wfGetDB( DB_REPLICA );
2499 
2500  LogEventsList::showLogExtract( $wgOut, [ 'delete', 'move' ], $this->mTitle,
2501  '',
2502  [
2503  'lim' => 10,
2504  'conds' => [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ],
2505  'showIfEmpty' => false,
2506  'msgKey' => [ 'recreate-moveddeleted-warn' ]
2507  ]
2508  );
2509  }
2510  }
2511 
2517  protected function showCustomIntro() {
2518  if ( $this->editintro ) {
2519  $title = Title::newFromText( $this->editintro );
2520  if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
2521  global $wgOut;
2522  // Added using template syntax, to take <noinclude>'s into account.
2523  $wgOut->addWikiTextTitleTidy(
2524  '<div class="mw-editintro">{{:' . $title->getFullText() . '}}</div>',
2526  );
2527  return true;
2528  }
2529  }
2530  return false;
2531  }
2532 
2551  protected function toEditText( $content ) {
2552  if ( $content === null || $content === false || is_string( $content ) ) {
2553  return $content;
2554  }
2555 
2556  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2557  throw new MWException( 'This content model is not supported: ' . $content->getModel() );
2558  }
2559 
2560  return $content->serialize( $this->contentFormat );
2561  }
2562 
2579  protected function toEditContent( $text ) {
2580  if ( $text === false || $text === null ) {
2581  return $text;
2582  }
2583 
2584  $content = ContentHandler::makeContent( $text, $this->getTitle(),
2585  $this->contentModel, $this->contentFormat );
2586 
2587  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2588  throw new MWException( 'This content model is not supported: ' . $content->getModel() );
2589  }
2590 
2591  return $content;
2592  }
2593 
2602  public function showEditForm( $formCallback = null ) {
2604 
2605  # need to parse the preview early so that we know which templates are used,
2606  # otherwise users with "show preview after edit box" will get a blank list
2607  # we parse this near the beginning so that setHeaders can do the title
2608  # setting work instead of leaving it in getPreviewText
2609  $previewOutput = '';
2610  if ( $this->formtype == 'preview' ) {
2611  $previewOutput = $this->getPreviewText();
2612  }
2613 
2614  // Avoid PHP 7.1 warning of passing $this by reference
2615  $editPage = $this;
2616  Hooks::run( 'EditPage::showEditForm:initial', [ &$editPage, &$wgOut ] );
2617 
2618  $this->setHeaders();
2619 
2620  $this->addTalkPageText();
2621  $this->addEditNotices();
2622 
2623  if ( !$this->isConflict &&
2624  $this->section != '' &&
2625  !$this->isSectionEditSupported() ) {
2626  // We use $this->section to much before this and getVal('wgSection') directly in other places
2627  // at this point we can't reset $this->section to '' to fallback to non-section editing.
2628  // Someone is welcome to try refactoring though
2629  $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
2630  return;
2631  }
2632 
2633  $this->showHeader();
2634 
2635  $wgOut->addHTML( $this->editFormPageTop );
2636 
2637  if ( $wgUser->getOption( 'previewontop' ) ) {
2638  $this->displayPreviewArea( $previewOutput, true );
2639  }
2640 
2641  $wgOut->addHTML( $this->editFormTextTop );
2642 
2643  $showToolbar = true;
2644  if ( $this->wasDeletedSinceLastEdit() ) {
2645  if ( $this->formtype == 'save' ) {
2646  // Hide the toolbar and edit area, user can click preview to get it back
2647  // Add an confirmation checkbox and explanation.
2648  $showToolbar = false;
2649  } else {
2650  $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2651  'deletedwhileediting' );
2652  }
2653  }
2654 
2655  // @todo add EditForm plugin interface and use it here!
2656  // search for textarea1 and textarea2, and allow EditForm to override all uses.
2657  $wgOut->addHTML( Html::openElement(
2658  'form',
2659  [
2660  'class' => $this->oouiEnabled ? 'mw-editform-ooui' : 'mw-editform-legacy',
2661  'id' => self::EDITFORM_ID,
2662  'name' => self::EDITFORM_ID,
2663  'method' => 'post',
2664  'action' => $this->getActionURL( $this->getContextTitle() ),
2665  'enctype' => 'multipart/form-data'
2666  ]
2667  ) );
2668 
2669  if ( is_callable( $formCallback ) ) {
2670  wfWarn( 'The $formCallback parameter to ' . __METHOD__ . 'is deprecated' );
2671  call_user_func_array( $formCallback, [ &$wgOut ] );
2672  }
2673 
2674  // Add an empty field to trip up spambots
2675  $wgOut->addHTML(
2676  Xml::openElement( 'div', [ 'id' => 'antispam-container', 'style' => 'display: none;' ] )
2677  . Html::rawElement(
2678  'label',
2679  [ 'for' => 'wpAntispam' ],
2680  $this->context->msg( 'simpleantispam-label' )->parse()
2681  )
2682  . Xml::element(
2683  'input',
2684  [
2685  'type' => 'text',
2686  'name' => 'wpAntispam',
2687  'id' => 'wpAntispam',
2688  'value' => ''
2689  ]
2690  )
2691  . Xml::closeElement( 'div' )
2692  );
2693 
2694  // Avoid PHP 7.1 warning of passing $this by reference
2695  $editPage = $this;
2696  Hooks::run( 'EditPage::showEditForm:fields', [ &$editPage, &$wgOut ] );
2697 
2698  // Put these up at the top to ensure they aren't lost on early form submission
2699  $this->showFormBeforeText();
2700 
2701  if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
2702  $username = $this->lastDelete->user_name;
2703  $comment = $this->lastDelete->log_comment;
2704 
2705  // It is better to not parse the comment at all than to have templates expanded in the middle
2706  // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
2707  $key = $comment === ''
2708  ? 'confirmrecreate-noreason'
2709  : 'confirmrecreate';
2710  $wgOut->addHTML(
2711  '<div class="mw-confirm-recreate">' .
2712  $this->context->msg( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
2713  Xml::checkLabel( $this->context->msg( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
2714  [ 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' ]
2715  ) .
2716  '</div>'
2717  );
2718  }
2719 
2720  # When the summary is hidden, also hide them on preview/show changes
2721  if ( $this->nosummary ) {
2722  $wgOut->addHTML( Html::hidden( 'nosummary', true ) );
2723  }
2724 
2725  # If a blank edit summary was previously provided, and the appropriate
2726  # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2727  # user being bounced back more than once in the event that a summary
2728  # is not required.
2729  # ####
2730  # For a bit more sophisticated detection of blank summaries, hash the
2731  # automatic one and pass that in the hidden field wpAutoSummary.
2732  if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
2733  $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
2734  }
2735 
2736  if ( $this->undidRev ) {
2737  $wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
2738  }
2739 
2740  if ( $this->selfRedirect ) {
2741  $wgOut->addHTML( Html::hidden( 'wpIgnoreSelfRedirect', true ) );
2742  }
2743 
2744  if ( $this->hasPresetSummary ) {
2745  // If a summary has been preset using &summary= we don't want to prompt for
2746  // a different summary. Only prompt for a summary if the summary is blanked.
2747  // (T19416)
2748  $this->autoSumm = md5( '' );
2749  }
2750 
2751  $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
2752  $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
2753 
2754  $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
2755  $wgOut->addHTML( Html::hidden( 'parentRevId', $this->getParentRevId() ) );
2756 
2757  $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
2758  $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
2759 
2760  // following functions will need OOUI, enable it only once; here.
2761  if ( $this->oouiEnabled ) {
2762  $wgOut->enableOOUI();
2763  }
2764 
2765  if ( $this->section == 'new' ) {
2766  $this->showSummaryInput( true, $this->summary );
2767  $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
2768  }
2769 
2770  $wgOut->addHTML( $this->editFormTextBeforeContent );
2771 
2772  if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
2773  $wgOut->addHTML( EditPage::getEditToolbar( $this->mTitle ) );
2774  }
2775 
2776  if ( $this->blankArticle ) {
2777  $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankArticle', true ) );
2778  }
2779 
2780  if ( $this->isConflict ) {
2781  // In an edit conflict bypass the overridable content form method
2782  // and fallback to the raw wpTextbox1 since editconflicts can't be
2783  // resolved between page source edits and custom ui edits using the
2784  // custom edit ui.
2785  $this->textbox2 = $this->textbox1;
2786 
2787  $content = $this->getCurrentContent();
2788  $this->textbox1 = $this->toEditText( $content );
2789 
2790  $this->showTextbox1();
2791  } else {
2792  $this->showContentForm();
2793  }
2794 
2795  $wgOut->addHTML( $this->editFormTextAfterContent );
2796 
2797  $this->showStandardInputs();
2798 
2799  $this->showFormAfterText();
2800 
2801  $this->showTosSummary();
2802 
2803  $this->showEditTools();
2804 
2805  $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
2806 
2807  $wgOut->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
2808 
2809  $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'hiddencats' ],
2810  Linker::formatHiddenCategories( $this->page->getHiddenCategories() ) ) );
2811 
2812  $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'limitreport' ],
2813  self::getPreviewLimitReport( $this->mParserOutput ) ) );
2814 
2815  $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
2816 
2817  if ( $this->isConflict ) {
2818  try {
2819  $this->showConflict();
2820  } catch ( MWContentSerializationException $ex ) {
2821  // this can't really happen, but be nice if it does.
2822  $msg = $this->context->msg(
2823  'content-failed-to-parse',
2824  $this->contentModel,
2825  $this->contentFormat,
2826  $ex->getMessage()
2827  );
2828  $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
2829  }
2830  }
2831 
2832  // Set a hidden field so JS knows what edit form mode we are in
2833  if ( $this->isConflict ) {
2834  $mode = 'conflict';
2835  } elseif ( $this->preview ) {
2836  $mode = 'preview';
2837  } elseif ( $this->diff ) {
2838  $mode = 'diff';
2839  } else {
2840  $mode = 'text';
2841  }
2842  $wgOut->addHTML( Html::hidden( 'mode', $mode, [ 'id' => 'mw-edit-mode' ] ) );
2843 
2844  // Marker for detecting truncated form data. This must be the last
2845  // parameter sent in order to be of use, so do not move me.
2846  $wgOut->addHTML( Html::hidden( 'wpUltimateParam', true ) );
2847  $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
2848 
2849  if ( !$wgUser->getOption( 'previewontop' ) ) {
2850  $this->displayPreviewArea( $previewOutput, false );
2851  }
2852  }
2853 
2861  public function makeTemplatesOnThisPageList( array $templates ) {
2862  $templateListFormatter = new TemplatesOnThisPageFormatter(
2863  $this->context, MediaWikiServices::getInstance()->getLinkRenderer()
2864  );
2865 
2866  // preview if preview, else section if section, else false
2867  $type = false;
2868  if ( $this->preview ) {
2869  $type = 'preview';
2870  } elseif ( $this->section != '' ) {
2871  $type = 'section';
2872  }
2873 
2874  return Html::rawElement( 'div', [ 'class' => 'templatesUsed' ],
2875  $templateListFormatter->format( $templates, $type )
2876  );
2877  }
2878 
2885  public static function extractSectionTitle( $text ) {
2886  preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
2887  if ( !empty( $matches[2] ) ) {
2888  global $wgParser;
2889  return $wgParser->stripSectionName( trim( $matches[2] ) );
2890  } else {
2891  return false;
2892  }
2893  }
2894 
2895  protected function showHeader() {
2897  global $wgAllowUserCss, $wgAllowUserJs;
2898 
2899  if ( $this->isConflict ) {
2900  $this->addExplainConflictHeader( $wgOut );
2901  $this->editRevId = $this->page->getLatest();
2902  } else {
2903  if ( $this->section != '' && $this->section != 'new' ) {
2904  if ( !$this->summary && !$this->preview && !$this->diff ) {
2905  $sectionTitle = self::extractSectionTitle( $this->textbox1 ); // FIXME: use Content object
2906  if ( $sectionTitle !== false ) {
2907  $this->summary = "/* $sectionTitle */ ";
2908  }
2909  }
2910  }
2911 
2912  if ( $this->missingComment ) {
2913  $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
2914  }
2915 
2916  if ( $this->missingSummary && $this->section != 'new' ) {
2917  $wgOut->wrapWikiMsg( "<div id='mw-missingsummary'>\n$1\n</div>", 'missingsummary' );
2918  }
2919 
2920  if ( $this->missingSummary && $this->section == 'new' ) {
2921  $wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1\n</div>", 'missingcommentheader' );
2922  }
2923 
2924  if ( $this->blankArticle ) {
2925  $wgOut->wrapWikiMsg( "<div id='mw-blankarticle'>\n$1\n</div>", 'blankarticle' );
2926  }
2927 
2928  if ( $this->selfRedirect ) {
2929  $wgOut->wrapWikiMsg( "<div id='mw-selfredirect'>\n$1\n</div>", 'selfredirect' );
2930  }
2931 
2932  if ( $this->hookError !== '' ) {
2933  $wgOut->addWikiText( $this->hookError );
2934  }
2935 
2936  if ( !$this->checkUnicodeCompliantBrowser() ) {
2937  $wgOut->addWikiMsg( 'nonunicodebrowser' );
2938  }
2939 
2940  if ( $this->section != 'new' ) {
2941  $revision = $this->mArticle->getRevisionFetched();
2942  if ( $revision ) {
2943  // Let sysop know that this will make private content public if saved
2944 
2945  if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) {
2946  $wgOut->wrapWikiMsg(
2947  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
2948  'rev-deleted-text-permission'
2949  );
2950  } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
2951  $wgOut->wrapWikiMsg(
2952  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
2953  'rev-deleted-text-view'
2954  );
2955  }
2956 
2957  if ( !$revision->isCurrent() ) {
2958  $this->mArticle->setOldSubtitle( $revision->getId() );
2959  $wgOut->addWikiMsg( 'editingold' );
2960  $this->isOldRev = true;
2961  }
2962  } elseif ( $this->mTitle->exists() ) {
2963  // Something went wrong
2964 
2965  $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
2966  [ 'missing-revision', $this->oldid ] );
2967  }
2968  }
2969  }
2970 
2971  if ( wfReadOnly() ) {
2972  $wgOut->wrapWikiMsg(
2973  "<div id=\"mw-read-only-warning\">\n$1\n</div>",
2974  [ 'readonlywarning', wfReadOnlyReason() ]
2975  );
2976  } elseif ( $wgUser->isAnon() ) {
2977  if ( $this->formtype != 'preview' ) {
2978  $wgOut->wrapWikiMsg(
2979  "<div id='mw-anon-edit-warning' class='warningbox'>\n$1\n</div>",
2980  [ 'anoneditwarning',
2981  // Log-in link
2982  SpecialPage::getTitleFor( 'Userlogin' )->getFullURL( [
2983  'returnto' => $this->getTitle()->getPrefixedDBkey()
2984  ] ),
2985  // Sign-up link
2986  SpecialPage::getTitleFor( 'CreateAccount' )->getFullURL( [
2987  'returnto' => $this->getTitle()->getPrefixedDBkey()
2988  ] )
2989  ]
2990  );
2991  } else {
2992  $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\" class=\"warningbox\">\n$1</div>",
2993  'anonpreviewwarning'
2994  );
2995  }
2996  } else {
2997  if ( $this->isCssJsSubpage ) {
2998  # Check the skin exists
2999  if ( $this->isWrongCaseCssJsPage ) {
3000  $wgOut->wrapWikiMsg(
3001  "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>",
3002  [ 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ]
3003  );
3004  }
3005  if ( $this->getTitle()->isSubpageOf( $wgUser->getUserPage() ) ) {
3006  $wgOut->wrapWikiMsg( '<div class="mw-usercssjspublic">$1</div>',
3007  $this->isCssSubpage ? 'usercssispublic' : 'userjsispublic'
3008  );
3009  if ( $this->formtype !== 'preview' ) {
3010  if ( $this->isCssSubpage && $wgAllowUserCss ) {
3011  $wgOut->wrapWikiMsg(
3012  "<div id='mw-usercssyoucanpreview'>\n$1\n</div>",
3013  [ 'usercssyoucanpreview' ]
3014  );
3015  }
3016 
3017  if ( $this->isJsSubpage && $wgAllowUserJs ) {
3018  $wgOut->wrapWikiMsg(
3019  "<div id='mw-userjsyoucanpreview'>\n$1\n</div>",
3020  [ 'userjsyoucanpreview' ]
3021  );
3022  }
3023  }
3024  }
3025  }
3026  }
3027 
3029 
3030  $this->addLongPageWarningHeader();
3031 
3032  # Add header copyright warning
3033  $this->showHeaderCopyrightWarning();
3034  }
3035 
3043  private function getSummaryInputAttributes( array $inputAttrs = null ) {
3044  // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
3045  return ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
3046  'id' => 'wpSummary',
3047  'name' => 'wpSummary',
3048  'maxlength' => '200',
3049  'tabindex' => '1',
3050  'size' => 60,
3051  'spellcheck' => 'true',
3052  ] + Linker::tooltipAndAccesskeyAttribs( 'summary' );
3053  }
3054 
3069  public function getSummaryInput( $summary = "", $labelText = null,
3070  $inputAttrs = null, $spanLabelAttrs = null
3071  ) {
3072  $inputAttrs = $this->getSummaryInputAttributes( $inputAttrs );
3073 
3074  $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : [] ) + [
3075  'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
3076  'id' => "wpSummaryLabel"
3077  ];
3078 
3079  $label = null;
3080  if ( $labelText ) {
3081  $label = Xml::tags(
3082  'label',
3083  $inputAttrs['id'] ? [ 'for' => $inputAttrs['id'] ] : null,
3084  $labelText
3085  );
3086  $label = Xml::tags( 'span', $spanLabelAttrs, $label );
3087  }
3088 
3089  $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
3090 
3091  return [ $label, $input ];
3092  }
3093 
3104  function getSummaryInputOOUI( $summary = "", $labelText = null, $inputAttrs = null ) {
3105  $inputAttrs = OOUI\Element::configFromHtmlAttributes(
3106  $this->getSummaryInputAttributes( $inputAttrs )
3107  );
3108 
3109  return new OOUI\FieldLayout(
3110  new OOUI\TextInputWidget( [
3111  'value' => $summary,
3112  'infusable' => true,
3113  ] + $inputAttrs ),
3114  [
3115  'label' => new OOUI\HtmlSnippet( $labelText ),
3116  'align' => 'top',
3117  'id' => 'wpSummaryLabel',
3118  'classes' => [ $this->missingSummary ? 'mw-summarymissed' : 'mw-summary' ],
3119  ]
3120  );
3121  }
3122 
3129  protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
3130  global $wgOut;
3131 
3132  # Add a class if 'missingsummary' is triggered to allow styling of the summary line
3133  $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
3134  if ( $isSubjectPreview ) {
3135  if ( $this->nosummary ) {
3136  return;
3137  }
3138  } else {
3139  if ( !$this->mShowSummaryField ) {
3140  return;
3141  }
3142  }
3143 
3144  $labelText = $this->context->msg( $isSubjectPreview ? 'subject' : 'summary' )->parse();
3145  if ( $this->oouiEnabled ) {
3146  $wgOut->addHTML( $this->getSummaryInputOOUI(
3147  $summary,
3148  $labelText,
3149  [ 'class' => $summaryClass ]
3150  ) );
3151  } else {
3152  list( $label, $input ) = $this->getSummaryInput(
3153  $summary,
3154  $labelText,
3155  [ 'class' => $summaryClass ]
3156  );
3157  $wgOut->addHTML( "{$label} {$input}" );
3158  }
3159 
3160  }
3161 
3169  protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
3170  // avoid spaces in preview, gets always trimmed on save
3171  $summary = trim( $summary );
3172  if ( !$summary || ( !$this->preview && !$this->diff ) ) {
3173  return "";
3174  }
3175 
3176  global $wgParser;
3177 
3178  if ( $isSubjectPreview ) {
3179  $summary = $this->context->msg( 'newsectionsummary' )
3180  ->rawParams( $wgParser->stripSectionName( $summary ) )
3181  ->inContentLanguage()->text();
3182  }
3183 
3184  $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
3185 
3186  $summary = $this->context->msg( $message )->parse()
3187  . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
3188  return Xml::tags( 'div', [ 'class' => 'mw-summary-preview' ], $summary );
3189  }
3190 
3191  protected function showFormBeforeText() {
3192  global $wgOut;
3193  $section = htmlspecialchars( $this->section );
3194  $wgOut->addHTML( <<<HTML
3195 <input type='hidden' value="{$section}" name="wpSection"/>
3196 <input type='hidden' value="{$this->starttime}" name="wpStarttime" />
3197 <input type='hidden' value="{$this->edittime}" name="wpEdittime" />
3198 <input type='hidden' value="{$this->editRevId}" name="editRevId" />
3199 <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
3200 
3201 HTML
3202  );
3203  if ( !$this->checkUnicodeCompliantBrowser() ) {
3204  $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
3205  }
3206  }
3207 
3208  protected function showFormAfterText() {
3222  $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
3223  }
3224 
3233  protected function showContentForm() {
3234  $this->showTextbox1();
3235  }
3236 
3245  protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
3246  if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
3247  $attribs = [ 'style' => 'display:none;' ];
3248  } else {
3249  $classes = []; // Textarea CSS
3250  if ( $this->mTitle->isProtected( 'edit' ) &&
3251  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [ '' ]
3252  ) {
3253  # Is the title semi-protected?
3254  if ( $this->mTitle->isSemiProtected() ) {
3255  $classes[] = 'mw-textarea-sprotected';
3256  } else {
3257  # Then it must be protected based on static groups (regular)
3258  $classes[] = 'mw-textarea-protected';
3259  }
3260  # Is the title cascade-protected?
3261  if ( $this->mTitle->isCascadeProtected() ) {
3262  $classes[] = 'mw-textarea-cprotected';
3263  }
3264  }
3265  # Is an old revision being edited?
3266  if ( $this->isOldRev ) {
3267  $classes[] = 'mw-textarea-oldrev';
3268  }
3269 
3270  $attribs = [ 'tabindex' => 1 ];
3271 
3272  if ( is_array( $customAttribs ) ) {
3274  }
3275 
3276  if ( count( $classes ) ) {
3277  if ( isset( $attribs['class'] ) ) {
3278  $classes[] = $attribs['class'];
3279  }
3280  $attribs['class'] = implode( ' ', $classes );
3281  }
3282  }
3283 
3284  $this->showTextbox(
3285  $textoverride !== null ? $textoverride : $this->textbox1,
3286  'wpTextbox1',
3287  $attribs
3288  );
3289  }
3290 
3291  protected function showTextbox2() {
3292  $this->showTextbox( $this->textbox2, 'wpTextbox2', [ 'tabindex' => 6, 'readonly' ] );
3293  }
3294 
3295  protected function showTextbox( $text, $name, $customAttribs = [] ) {
3297 
3298  $wikitext = $this->safeUnicodeOutput( $text );
3299  $wikitext = $this->addNewLineAtEnd( $wikitext );
3300 
3301  $attribs = $this->buildTextboxAttribs( $name, $customAttribs, $wgUser );
3302 
3303  $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
3304  }
3305 
3306  protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
3307  global $wgOut;
3308  $classes = [];
3309  if ( $isOnTop ) {
3310  $classes[] = 'ontop';
3311  }
3312 
3313  $attribs = [ 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) ];
3314 
3315  if ( $this->formtype != 'preview' ) {
3316  $attribs['style'] = 'display: none;';
3317  }
3318 
3319  $wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
3320 
3321  if ( $this->formtype == 'preview' ) {
3322  $this->showPreview( $previewOutput );
3323  } else {
3324  // Empty content container for LivePreview
3325  $pageViewLang = $this->mTitle->getPageViewLanguage();
3326  $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3327  'class' => 'mw-content-' . $pageViewLang->getDir() ];
3328  $wgOut->addHTML( Html::rawElement( 'div', $attribs ) );
3329  }
3330 
3331  $wgOut->addHTML( '</div>' );
3332 
3333  if ( $this->formtype == 'diff' ) {
3334  try {
3335  $this->showDiff();
3336  } catch ( MWContentSerializationException $ex ) {
3337  $msg = $this->context->msg(
3338  'content-failed-to-parse',
3339  $this->contentModel,
3340  $this->contentFormat,
3341  $ex->getMessage()
3342  );
3343  $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
3344  }
3345  }
3346  }
3347 
3354  protected function showPreview( $text ) {
3355  global $wgOut;
3356  if ( $this->mArticle instanceof CategoryPage ) {
3357  $this->mArticle->openShowCategory();
3358  }
3359  # This hook seems slightly odd here, but makes things more
3360  # consistent for extensions.
3361  Hooks::run( 'OutputPageBeforeHTML', [ &$wgOut, &$text ] );
3362  $wgOut->addHTML( $text );
3363  if ( $this->mArticle instanceof CategoryPage ) {
3364  $this->mArticle->closeShowCategory();
3365  }
3366  }
3367 
3375  public function showDiff() {
3377 
3378  $oldtitlemsg = 'currentrev';
3379  # if message does not exist, show diff against the preloaded default
3380  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
3381  $oldtext = $this->mTitle->getDefaultMessageText();
3382  if ( $oldtext !== false ) {
3383  $oldtitlemsg = 'defaultmessagetext';
3384  $oldContent = $this->toEditContent( $oldtext );
3385  } else {
3386  $oldContent = null;
3387  }
3388  } else {
3389  $oldContent = $this->getCurrentContent();
3390  }
3391 
3392  $textboxContent = $this->toEditContent( $this->textbox1 );
3393  if ( $this->editRevId !== null ) {
3394  $newContent = $this->page->replaceSectionAtRev(
3395  $this->section, $textboxContent, $this->summary, $this->editRevId
3396  );
3397  } else {
3398  $newContent = $this->page->replaceSectionContent(
3399  $this->section, $textboxContent, $this->summary, $this->edittime
3400  );
3401  }
3402 
3403  if ( $newContent ) {
3404  Hooks::run( 'EditPageGetDiffContent', [ $this, &$newContent ] );
3405 
3407  $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
3408  }
3409 
3410  if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
3411  $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
3412  $newtitle = $this->context->msg( 'yourtext' )->parse();
3413 
3414  if ( !$oldContent ) {
3415  $oldContent = $newContent->getContentHandler()->makeEmptyContent();
3416  }
3417 
3418  if ( !$newContent ) {
3419  $newContent = $oldContent->getContentHandler()->makeEmptyContent();
3420  }
3421 
3422  $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
3423  $de->setContent( $oldContent, $newContent );
3424 
3425  $difftext = $de->getDiff( $oldtitle, $newtitle );
3426  $de->showDiffStyle();
3427  } else {
3428  $difftext = '';
3429  }
3430 
3431  $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
3432  }
3433 
3437  protected function showHeaderCopyrightWarning() {
3438  $msg = 'editpage-head-copy-warn';
3439  if ( !$this->context->msg( $msg )->isDisabled() ) {
3440  global $wgOut;
3441  $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
3442  'editpage-head-copy-warn' );
3443  }
3444  }
3445 
3454  protected function showTosSummary() {
3455  $msg = 'editpage-tos-summary';
3456  Hooks::run( 'EditPageTosSummary', [ $this->mTitle, &$msg ] );
3457  if ( !$this->context->msg( $msg )->isDisabled() ) {
3458  global $wgOut;
3459  $wgOut->addHTML( '<div class="mw-tos-summary">' );
3460  $wgOut->addWikiMsg( $msg );
3461  $wgOut->addHTML( '</div>' );
3462  }
3463  }
3464 
3465  protected function showEditTools() {
3466  global $wgOut;
3467  $wgOut->addHTML( '<div class="mw-editTools">' .
3468  $this->context->msg( 'edittools' )->inContentLanguage()->parse() .
3469  '</div>' );
3470  }
3471 
3478  protected function getCopywarn() {
3479  return self::getCopyrightWarning( $this->mTitle );
3480  }
3481 
3490  public static function getCopyrightWarning( $title, $format = 'plain', $langcode = null ) {
3491  global $wgRightsText;
3492  if ( $wgRightsText ) {
3493  $copywarnMsg = [ 'copyrightwarning',
3494  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]',
3495  $wgRightsText ];
3496  } else {
3497  $copywarnMsg = [ 'copyrightwarning2',
3498  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' ];
3499  }
3500  // Allow for site and per-namespace customization of contribution/copyright notice.
3501  Hooks::run( 'EditPageCopyrightWarning', [ $title, &$copywarnMsg ] );
3502 
3503  $msg = call_user_func_array( 'wfMessage', $copywarnMsg )->title( $title );
3504  if ( $langcode ) {
3505  $msg->inLanguage( $langcode );
3506  }
3507  return "<div id=\"editpage-copywarn\">\n" .
3508  $msg->$format() . "\n</div>";
3509  }
3510 
3518  public static function getPreviewLimitReport( $output ) {
3519  if ( !$output || !$output->getLimitReportData() ) {
3520  return '';
3521  }
3522 
3523  $limitReport = Html::rawElement( 'div', [ 'class' => 'mw-limitReportExplanation' ],
3524  wfMessage( 'limitreport-title' )->parseAsBlock()
3525  );
3526 
3527  // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
3528  $limitReport .= Html::openElement( 'div', [ 'class' => 'preview-limit-report-wrapper' ] );
3529 
3530  $limitReport .= Html::openElement( 'table', [
3531  'class' => 'preview-limit-report wikitable'
3532  ] ) .
3533  Html::openElement( 'tbody' );
3534 
3535  foreach ( $output->getLimitReportData() as $key => $value ) {
3536  if ( Hooks::run( 'ParserLimitReportFormat',
3537  [ $key, &$value, &$limitReport, true, true ]
3538  ) ) {
3539  $keyMsg = wfMessage( $key );
3540  $valueMsg = wfMessage( [ "$key-value-html", "$key-value" ] );
3541  if ( !$valueMsg->exists() ) {
3542  $valueMsg = new RawMessage( '$1' );
3543  }
3544  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3545  $limitReport .= Html::openElement( 'tr' ) .
3546  Html::rawElement( 'th', null, $keyMsg->parse() ) .
3547  Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) .
3548  Html::closeElement( 'tr' );
3549  }
3550  }
3551  }
3552 
3553  $limitReport .= Html::closeElement( 'tbody' ) .
3554  Html::closeElement( 'table' ) .
3555  Html::closeElement( 'div' );
3556 
3557  return $limitReport;
3558  }
3559 
3560  protected function showStandardInputs( &$tabindex = 2 ) {
3561  global $wgOut;
3562  $wgOut->addHTML( "<div class='editOptions'>\n" );
3563 
3564  if ( $this->section != 'new' ) {
3565  $this->showSummaryInput( false, $this->summary );
3566  $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
3567  }
3568 
3569  if ( $this->oouiEnabled ) {
3570  $checkboxes = $this->getCheckboxesOOUI(
3571  $tabindex,
3572  [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ]
3573  );
3574  $checkboxesHTML = new OOUI\HorizontalLayout( [ 'items' => $checkboxes ] );
3575  } else {
3576  $checkboxes = $this->getCheckboxes(
3577  $tabindex,
3578  [ 'minor' => $this->minoredit, 'watch' => $this->watchthis ]
3579  );
3580  $checkboxesHTML = implode( $checkboxes, "\n" );
3581  }
3582 
3583  $wgOut->addHTML( "<div class='editCheckboxes'>" . $checkboxesHTML . "</div>\n" );
3584 
3585  // Show copyright warning.
3586  $wgOut->addWikiText( $this->getCopywarn() );
3587  $wgOut->addHTML( $this->editFormTextAfterWarn );
3588 
3589  $wgOut->addHTML( "<div class='editButtons'>\n" );
3590  $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
3591 
3592  $cancel = $this->getCancelLink();
3593  if ( $cancel !== '' ) {
3594  $cancel .= Html::element( 'span',
3595  [ 'class' => 'mw-editButtons-pipe-separator' ],
3596  $this->context->msg( 'pipe-separator' )->text() );
3597  }
3598 
3599  $message = $this->context->msg( 'edithelppage' )->inContentLanguage()->text();
3600  $edithelpurl = Skin::makeInternalOrExternalUrl( $message );
3601  $edithelp =
3603  $this->context->msg( 'edithelp' )->text(),
3604  [ 'target' => 'helpwindow', 'href' => $edithelpurl ],
3605  [ 'mw-ui-quiet' ]
3606  ) .
3607  $this->context->msg( 'word-separator' )->escaped() .
3608  $this->context->msg( 'newwindow' )->parse();
3609 
3610  $wgOut->addHTML( " <span class='cancelLink'>{$cancel}</span>\n" );
3611  $wgOut->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" );
3612  $wgOut->addHTML( "</div><!-- editButtons -->\n" );
3613 
3614  Hooks::run( 'EditPage::showStandardInputs:options', [ $this, $wgOut, &$tabindex ] );
3615 
3616  $wgOut->addHTML( "</div><!-- editOptions -->\n" );
3617  }
3618 
3623  protected function showConflict() {
3624  global $wgOut;
3625 
3626  // Avoid PHP 7.1 warning of passing $this by reference
3627  $editPage = $this;
3628  if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$editPage, &$wgOut ] ) ) {
3629  $this->incrementConflictStats();
3630 
3631  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
3632 
3633  $content1 = $this->toEditContent( $this->textbox1 );
3634  $content2 = $this->toEditContent( $this->textbox2 );
3635 
3636  $handler = ContentHandler::getForModelID( $this->contentModel );
3637  $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
3638  $de->setContent( $content2, $content1 );
3639  $de->showDiff(
3640  $this->context->msg( 'yourtext' )->parse(),
3641  $this->context->msg( 'storedversion' )->text()
3642  );
3643 
3644  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
3645  $this->showTextbox2();
3646  }
3647  }
3648 
3649  protected function incrementConflictStats() {
3650  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
3651  $stats->increment( 'edit.failures.conflict' );
3652  // Only include 'standard' namespaces to avoid creating unknown numbers of statsd metrics
3653  if (
3654  $this->mTitle->getNamespace() >= NS_MAIN &&
3655  $this->mTitle->getNamespace() <= NS_CATEGORY_TALK
3656  ) {
3657  $stats->increment( 'edit.failures.conflict.byNamespaceId.' . $this->mTitle->getNamespace() );
3658  }
3659  }
3660 
3664  public function getCancelLink() {
3665  $cancelParams = [];
3666  if ( !$this->isConflict && $this->oldid > 0 ) {
3667  $cancelParams['oldid'] = $this->oldid;
3668  } elseif ( $this->getContextTitle()->isRedirect() ) {
3669  $cancelParams['redirect'] = 'no';
3670  }
3671  if ( $this->oouiEnabled ) {
3672  return new OOUI\ButtonWidget( [
3673  'id' => 'mw-editform-cancel',
3674  'href' => $this->getContextTitle()->getLinkUrl( $cancelParams ),
3675  'label' => new OOUI\HtmlSnippet( $this->context->msg( 'cancel' )->parse() ),
3676  'framed' => false,
3677  'infusable' => true,
3678  'flags' => 'destructive',
3679  ] );
3680  } else {
3681  return MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
3682  $this->getContextTitle(),
3683  new HtmlArmor( $this->context->msg( 'cancel' )->parse() ),
3684  Html::buttonAttributes( [ 'id' => 'mw-editform-cancel' ], [ 'mw-ui-quiet' ] ),
3685  $cancelParams
3686  );
3687  }
3688  }
3689 
3699  protected function getActionURL( Title $title ) {
3700  return $title->getLocalURL( [ 'action' => $this->action ] );
3701  }
3702 
3710  protected function wasDeletedSinceLastEdit() {
3711  if ( $this->deletedSinceEdit !== null ) {
3712  return $this->deletedSinceEdit;
3713  }
3714 
3715  $this->deletedSinceEdit = false;
3716 
3717  if ( !$this->mTitle->exists() && $this->mTitle->isDeletedQuick() ) {
3718  $this->lastDelete = $this->getLastDelete();
3719  if ( $this->lastDelete ) {
3720  $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
3721  if ( $deleteTime > $this->starttime ) {
3722  $this->deletedSinceEdit = true;
3723  }
3724  }
3725  }
3726 
3727  return $this->deletedSinceEdit;
3728  }
3729 
3733  protected function getLastDelete() {
3734  $dbr = wfGetDB( DB_REPLICA );
3735  $data = $dbr->selectRow(
3736  [ 'logging', 'user' ],
3737  [
3738  'log_type',
3739  'log_action',
3740  'log_timestamp',
3741  'log_user',
3742  'log_namespace',
3743  'log_title',
3744  'log_comment',
3745  'log_params',
3746  'log_deleted',
3747  'user_name'
3748  ], [
3749  'log_namespace' => $this->mTitle->getNamespace(),
3750  'log_title' => $this->mTitle->getDBkey(),
3751  'log_type' => 'delete',
3752  'log_action' => 'delete',
3753  'user_id=log_user'
3754  ],
3755  __METHOD__,
3756  [ 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ]
3757  );
3758  // Quick paranoid permission checks...
3759  if ( is_object( $data ) ) {
3760  if ( $data->log_deleted & LogPage::DELETED_USER ) {
3761  $data->user_name = $this->context->msg( 'rev-deleted-user' )->escaped();
3762  }
3763 
3764  if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
3765  $data->log_comment = $this->context->msg( 'rev-deleted-comment' )->escaped();
3766  }
3767  }
3768 
3769  return $data;
3770  }
3771 
3777  public function getPreviewText() {
3778  global $wgOut, $wgRawHtml, $wgLang;
3779  global $wgAllowUserCss, $wgAllowUserJs;
3780 
3781  if ( $wgRawHtml && !$this->mTokenOk ) {
3782  // Could be an offsite preview attempt. This is very unsafe if
3783  // HTML is enabled, as it could be an attack.
3784  $parsedNote = '';
3785  if ( $this->textbox1 !== '' ) {
3786  // Do not put big scary notice, if previewing the empty
3787  // string, which happens when you initially edit
3788  // a category page, due to automatic preview-on-open.
3789  $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
3790  $this->context->msg( 'session_fail_preview_html' )->text() . "</div>",
3791  true, /* interface */true );
3792  }
3793  $this->incrementEditFailureStats( 'session_loss' );
3794  return $parsedNote;
3795  }
3796 
3797  $note = '';
3798 
3799  try {
3800  $content = $this->toEditContent( $this->textbox1 );
3801 
3802  $previewHTML = '';
3803  if ( !Hooks::run(
3804  'AlternateEditPreview',
3805  [ $this, &$content, &$previewHTML, &$this->mParserOutput ] )
3806  ) {
3807  return $previewHTML;
3808  }
3809 
3810  # provide a anchor link to the editform
3811  $continueEditing = '<span class="mw-continue-editing">' .
3812  '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' .
3813  $this->context->msg( 'continue-editing' )->text() . ']]</span>';
3814  if ( $this->mTriedSave && !$this->mTokenOk ) {
3815  if ( $this->mTokenOkExceptSuffix ) {
3816  $note = $this->context->msg( 'token_suffix_mismatch' )->plain();
3817  $this->incrementEditFailureStats( 'bad_token' );
3818  } else {
3819  $note = $this->context->msg( 'session_fail_preview' )->plain();
3820  $this->incrementEditFailureStats( 'session_loss' );
3821  }
3822  } elseif ( $this->incompleteForm ) {
3823  $note = $this->context->msg( 'edit_form_incomplete' )->plain();
3824  if ( $this->mTriedSave ) {
3825  $this->incrementEditFailureStats( 'incomplete_form' );
3826  }
3827  } else {
3828  $note = $this->context->msg( 'previewnote' )->plain() . ' ' . $continueEditing;
3829  }
3830 
3831  # don't parse non-wikitext pages, show message about preview
3832  if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
3833  if ( $this->mTitle->isCssJsSubpage() ) {
3834  $level = 'user';
3835  } elseif ( $this->mTitle->isCssOrJsPage() ) {
3836  $level = 'site';
3837  } else {
3838  $level = false;
3839  }
3840 
3841  if ( $content->getModel() == CONTENT_MODEL_CSS ) {
3842  $format = 'css';
3843  if ( $level === 'user' && !$wgAllowUserCss ) {
3844  $format = false;
3845  }
3846  } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
3847  $format = 'js';
3848  if ( $level === 'user' && !$wgAllowUserJs ) {
3849  $format = false;
3850  }
3851  } else {
3852  $format = false;
3853  }
3854 
3855  # Used messages to make sure grep find them:
3856  # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
3857  if ( $level && $format ) {
3858  $note = "<div id='mw-{$level}{$format}preview'>" .
3859  $this->context->msg( "{$level}{$format}preview" )->text() .
3860  ' ' . $continueEditing . "</div>";
3861  }
3862  }
3863 
3864  # If we're adding a comment, we need to show the
3865  # summary as the headline
3866  if ( $this->section === "new" && $this->summary !== "" ) {
3867  $content = $content->addSectionHeader( $this->summary );
3868  }
3869 
3870  $hook_args = [ $this, &$content ];
3871  Hooks::run( 'EditPageGetPreviewContent', $hook_args );
3872 
3873  $parserResult = $this->doPreviewParse( $content );
3874  $parserOutput = $parserResult['parserOutput'];
3875  $previewHTML = $parserResult['html'];
3876  $this->mParserOutput = $parserOutput;
3877  $wgOut->addParserOutputMetadata( $parserOutput );
3878 
3879  if ( count( $parserOutput->getWarnings() ) ) {
3880  $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
3881  }
3882 
3883  } catch ( MWContentSerializationException $ex ) {
3884  $m = $this->context->msg(
3885  'content-failed-to-parse',
3886  $this->contentModel,
3887  $this->contentFormat,
3888  $ex->getMessage()
3889  );
3890  $note .= "\n\n" . $m->parse();
3891  $previewHTML = '';
3892  }
3893 
3894  if ( $this->isConflict ) {
3895  $conflict = '<h2 id="mw-previewconflict">'
3896  . $this->context->msg( 'previewconflict' )->escaped() . "</h2>\n";
3897  } else {
3898  $conflict = '<hr />';
3899  }
3900 
3901  $previewhead = "<div class='previewnote'>\n" .
3902  '<h2 id="mw-previewheader">' . $this->context->msg( 'preview' )->escaped() . "</h2>" .
3903  $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
3904 
3905  $pageViewLang = $this->mTitle->getPageViewLanguage();
3906  $attribs = [ 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3907  'class' => 'mw-content-' . $pageViewLang->getDir() ];
3908  $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
3909 
3910  return $previewhead . $previewHTML . $this->previewTextAfterContent;
3911  }
3912 
3913  private function incrementEditFailureStats( $failureType ) {
3914  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
3915  $stats->increment( 'edit.failures.' . $failureType );
3916  }
3917 
3922  protected function getPreviewParserOptions() {
3923  $parserOptions = $this->page->makeParserOptions( $this->mArticle->getContext() );
3924  $parserOptions->setIsPreview( true );
3925  $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
3926  $parserOptions->enableLimitReport();
3927  return $parserOptions;
3928  }
3929 
3939  protected function doPreviewParse( Content $content ) {
3940  global $wgUser;
3941  $parserOptions = $this->getPreviewParserOptions();
3942  $pstContent = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
3943  $scopedCallback = $parserOptions->setupFakeRevision(
3944  $this->mTitle, $pstContent, $wgUser );
3945  $parserOutput = $pstContent->getParserOutput( $this->mTitle, null, $parserOptions );
3946  ScopedCallback::consume( $scopedCallback );
3947  $parserOutput->setEditSectionTokens( false ); // no section edit links
3948  return [
3949  'parserOutput' => $parserOutput,
3950  'html' => $parserOutput->getText() ];
3951  }
3952 
3956  public function getTemplates() {
3957  if ( $this->preview || $this->section != '' ) {
3958  $templates = [];
3959  if ( !isset( $this->mParserOutput ) ) {
3960  return $templates;
3961  }
3962  foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
3963  foreach ( array_keys( $template ) as $dbk ) {
3964  $templates[] = Title::makeTitle( $ns, $dbk );
3965  }
3966  }
3967  return $templates;
3968  } else {
3969  return $this->mTitle->getTemplateLinksFrom();
3970  }
3971  }
3972 
3980  public static function getEditToolbar( $title = null ) {
3983 
3984  $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
3985  $showSignature = true;
3986  if ( $title ) {
3987  $showSignature = MWNamespace::wantSignatures( $title->getNamespace() );
3988  }
3989 
3999  $toolarray = [
4000  [
4001  'id' => 'mw-editbutton-bold',
4002  'open' => '\'\'\'',
4003  'close' => '\'\'\'',
4004  'sample' => wfMessage( 'bold_sample' )->text(),
4005  'tip' => wfMessage( 'bold_tip' )->text(),
4006  ],
4007  [
4008  'id' => 'mw-editbutton-italic',
4009  'open' => '\'\'',
4010  'close' => '\'\'',
4011  'sample' => wfMessage( 'italic_sample' )->text(),
4012  'tip' => wfMessage( 'italic_tip' )->text(),
4013  ],
4014  [
4015  'id' => 'mw-editbutton-link',
4016  'open' => '[[',
4017  'close' => ']]',
4018  'sample' => wfMessage( 'link_sample' )->text(),
4019  'tip' => wfMessage( 'link_tip' )->text(),
4020  ],
4021  [
4022  'id' => 'mw-editbutton-extlink',
4023  'open' => '[',
4024  'close' => ']',
4025  'sample' => wfMessage( 'extlink_sample' )->text(),
4026  'tip' => wfMessage( 'extlink_tip' )->text(),
4027  ],
4028  [
4029  'id' => 'mw-editbutton-headline',
4030  'open' => "\n== ",
4031  'close' => " ==\n",
4032  'sample' => wfMessage( 'headline_sample' )->text(),
4033  'tip' => wfMessage( 'headline_tip' )->text(),
4034  ],
4035  $imagesAvailable ? [
4036  'id' => 'mw-editbutton-image',
4037  'open' => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
4038  'close' => ']]',
4039  'sample' => wfMessage( 'image_sample' )->text(),
4040  'tip' => wfMessage( 'image_tip' )->text(),
4041  ] : false,
4042  $imagesAvailable ? [
4043  'id' => 'mw-editbutton-media',
4044  'open' => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
4045  'close' => ']]',
4046  'sample' => wfMessage( 'media_sample' )->text(),
4047  'tip' => wfMessage( 'media_tip' )->text(),
4048  ] : false,
4049  [
4050  'id' => 'mw-editbutton-nowiki',
4051  'open' => "<nowiki>",
4052  'close' => "</nowiki>",
4053  'sample' => wfMessage( 'nowiki_sample' )->text(),
4054  'tip' => wfMessage( 'nowiki_tip' )->text(),
4055  ],
4056  $showSignature ? [
4057  'id' => 'mw-editbutton-signature',
4058  'open' => wfMessage( 'sig-text', '~~~~' )->inContentLanguage()->text(),
4059  'close' => '',
4060  'sample' => '',
4061  'tip' => wfMessage( 'sig_tip' )->text(),
4062  ] : false,
4063  [
4064  'id' => 'mw-editbutton-hr',
4065  'open' => "\n----\n",
4066  'close' => '',
4067  'sample' => '',
4068  'tip' => wfMessage( 'hr_tip' )->text(),
4069  ]
4070  ];
4071 
4072  $script = 'mw.loader.using("mediawiki.toolbar", function () {';
4073  foreach ( $toolarray as $tool ) {
4074  if ( !$tool ) {
4075  continue;
4076  }
4077 
4078  $params = [
4079  // Images are defined in ResourceLoaderEditToolbarModule
4080  false,
4081  // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
4082  // Older browsers show a "speedtip" type message only for ALT.
4083  // Ideally these should be different, realistically they
4084  // probably don't need to be.
4085  $tool['tip'],
4086  $tool['open'],
4087  $tool['close'],
4088  $tool['sample'],
4089  $tool['id'],
4090  ];
4091 
4092  $script .= Xml::encodeJsCall(
4093  'mw.toolbar.addButton',
4094  $params,
4095  ResourceLoader::inDebugMode()
4096  );
4097  }
4098 
4099  $script .= '});';
4100 
4101  $toolbar = '<div id="toolbar"></div>';
4102 
4103  if ( Hooks::run( 'EditPageBeforeEditToolbar', [ &$toolbar ] ) ) {
4104  // Only add the old toolbar cruft to the page payload if the toolbar has not
4105  // been over-written by a hook caller
4106  $wgOut->addScript( ResourceLoader::makeInlineScript( $script ) );
4107  };
4108 
4109  return $toolbar;
4110  }
4111 
4130  protected function getCheckboxesDefinition( $checked ) {
4131  global $wgUser;
4132  $checkboxes = [];
4133 
4134  // don't show the minor edit checkbox if it's a new page or section
4135  if ( !$this->isNew && $wgUser->isAllowed( 'minoredit' ) ) {
4136  $checkboxes['wpMinoredit'] = [
4137  'id' => 'wpMinoredit',
4138  'label-message' => 'minoredit',
4139  // Uses messages: tooltip-minoredit, accesskey-minoredit
4140  'tooltip' => 'minoredit',
4141  'label-id' => 'mw-editpage-minoredit',
4142  'legacy-name' => 'minor',
4143  'default' => $checked['minor'],
4144  ];
4145  }
4146 
4147  if ( $wgUser->isLoggedIn() ) {
4148  $checkboxes['wpWatchthis'] = [
4149  'id' => 'wpWatchthis',
4150  'label-message' => 'watchthis',
4151  // Uses messages: tooltip-watch, accesskey-watch
4152  'tooltip' => 'watch',
4153  'label-id' => 'mw-editpage-watch',
4154  'legacy-name' => 'watch',
4155  'default' => $checked['watch'],
4156  ];
4157  }
4158 
4159  $editPage = $this;
4160  Hooks::run( 'EditPageGetCheckboxesDefinition', [ $editPage, &$checkboxes ] );
4161 
4162  return $checkboxes;
4163  }
4164 
4173  public function getCheckboxes( &$tabindex, $checked ) {
4174  global $wgUseMediaWikiUIEverywhere;
4175 
4176  $checkboxes = [];
4177  $checkboxesDef = $this->getCheckboxesDefinition( $checked );
4178 
4179  // Backwards-compatibility for the EditPageBeforeEditChecks hook
4180  if ( !$this->isNew ) {
4181  $checkboxes['minor'] = '';
4182  }
4183  $checkboxes['watch'] = '';
4184 
4185  foreach ( $checkboxesDef as $name => $options ) {
4186  $legacyName = isset( $options['legacy-name'] ) ? $options['legacy-name'] : $name;
4187  $label = $this->context->msg( $options['label-message'] )->parse();
4188  $attribs = [
4189  'tabindex' => ++$tabindex,
4190  'id' => $options['id'],
4191  ];
4192  $labelAttribs = [
4193  'for' => $options['id'],
4194  ];
4195  if ( isset( $options['tooltip'] ) ) {
4196  $attribs['accesskey'] = $this->context->msg( "accesskey-{$options['tooltip']}" )->text();
4197  $labelAttribs['title'] = Linker::titleAttrib( $options['tooltip'], 'withaccess' );
4198  }
4199  if ( isset( $options['title-message'] ) ) {
4200  $labelAttribs['title'] = $this->context->msg( $options['title-message'] )->text();
4201  }
4202  if ( isset( $options['label-id'] ) ) {
4203  $labelAttribs['id'] = $options['label-id'];
4204  }
4205  $checkboxHtml =
4206  Xml::check( $name, $options['default'], $attribs ) .
4207  '&#160;' .
4208  Xml::tags( 'label', $labelAttribs, $label );
4209 
4210  if ( $wgUseMediaWikiUIEverywhere ) {
4211  $checkboxHtml = Html::rawElement( 'div', [ 'class' => 'mw-ui-checkbox' ], $checkboxHtml );
4212  }
4213 
4214  $checkboxes[ $legacyName ] = $checkboxHtml;
4215  }
4216 
4217  // Avoid PHP 7.1 warning of passing $this by reference
4218  $editPage = $this;
4219  Hooks::run( 'EditPageBeforeEditChecks', [ &$editPage, &$checkboxes, &$tabindex ], '1.29' );
4220  return $checkboxes;
4221  }
4222 
4233  public function getCheckboxesOOUI( &$tabindex, $checked ) {
4234  $checkboxes = [];
4235  $checkboxesDef = $this->getCheckboxesDefinition( $checked );
4236 
4237  $origTabindex = $tabindex;
4238 
4239  foreach ( $checkboxesDef as $name => $options ) {
4240  $legacyName = isset( $options['legacy-name'] ) ? $options['legacy-name'] : $name;
4241 
4242  $title = null;
4243  $accesskey = null;
4244  if ( isset( $options['tooltip'] ) ) {
4245  $accesskey = $this->context->msg( "accesskey-{$options['tooltip']}" )->text();
4246  $title = Linker::titleAttrib( $options['tooltip'], 'withaccess' );
4247  }
4248  if ( isset( $options['title-message'] ) ) {
4249  $title = $this->context->msg( $options['title-message'] )->text();
4250  }
4251  if ( isset( $options['label-id'] ) ) {
4252  $labelAttribs['id'] = $options['label-id'];
4253  }
4254 
4255  $checkboxes[ $legacyName ] = new OOUI\FieldLayout(
4256  new OOUI\CheckboxInputWidget( [
4257  'tabIndex' => ++$tabindex,
4258  'accessKey' => $accesskey,
4259  'id' => $options['id'],
4260  'name' => $name,
4261  'selected' => $options['default'],
4262  'infusable' => true,
4263  ] ),
4264  [
4265  'align' => 'inline',
4266  'label' => new OOUI\HtmlSnippet( $this->context->msg( $options['label-message'] )->parse() ),
4267  'title' => $title,
4268  'id' => isset( $options['label-id'] ) ? $options['label-id'] : null,
4269  ]
4270  );
4271  }
4272 
4273  // Backwards-compatibility hack to run the EditPageBeforeEditChecks hook. It's important,
4274  // people have used it for the weirdest things completely unrelated to checkboxes...
4275  // And if we're gonna run it, might as well allow its legacy checkboxes to be shown.
4276  $legacyCheckboxes = $this->getCheckboxes( $origTabindex, $checked );
4277  foreach ( $legacyCheckboxes as $name => $html ) {
4278  if ( $html && !isset( $checkboxes[$name] ) ) {
4279  $checkboxes[$name] = new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $html ) ] );
4280  }
4281  }
4282 
4283  return $checkboxes;
4284  }
4285 
4294  public function getEditButtons( &$tabindex ) {
4295  $buttons = [];
4296 
4297  $labelAsPublish =
4298  $this->mArticle->getContext()->getConfig()->get( 'EditSubmitButtonLabelPublish' );
4299 
4300  // Can't use $this->isNew as that's also true if we're adding a new section to an extant page
4301  if ( $labelAsPublish ) {
4302  $buttonLabelKey = !$this->mTitle->exists() ? 'publishpage' : 'publishchanges';
4303  } else {
4304  $buttonLabelKey = !$this->mTitle->exists() ? 'savearticle' : 'savechanges';
4305  }
4306  $attribs = [
4307  'id' => 'wpSave',
4308  'name' => 'wpSave',
4309  'tabindex' => ++$tabindex,
4310  ] + Linker::tooltipAndAccesskeyAttribs( 'save' );
4311 
4312  if ( $this->oouiEnabled ) {
4313  $saveConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
4314  $buttons['save'] = new OOUI\ButtonInputWidget( [
4315  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4316  'useInputTag' => true,
4317  'flags' => [ 'constructive', 'primary' ],
4318  'label' => $this->context->msg( $buttonLabelKey )->text(),
4319  'infusable' => true,
4320  'type' => 'submit',
4321  ] + $saveConfig );
4322  } else {
4323  $buttons['save'] = Html::submitButton(
4324  $this->context->msg( $buttonLabelKey )->text(),
4325  $attribs,
4326  [ 'mw-ui-progressive' ]
4327  );
4328  }
4329 
4330  $attribs = [
4331  'id' => 'wpPreview',
4332  'name' => 'wpPreview',
4333  'tabindex' => ++$tabindex,
4334  ] + Linker::tooltipAndAccesskeyAttribs( 'preview' );
4335  if ( $this->oouiEnabled ) {
4336  $previewConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
4337  $buttons['preview'] = new OOUI\ButtonInputWidget( [
4338  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4339  'useInputTag' => true,
4340  'label' => $this->context->msg( 'showpreview' )->text(),
4341  'infusable' => true,
4342  'type' => 'submit'
4343  ] + $previewConfig );
4344  } else {
4345  $buttons['preview'] = Html::submitButton(
4346  $this->context->msg( 'showpreview' )->text(),
4347  $attribs
4348  );
4349  }
4350  $attribs = [
4351  'id' => 'wpDiff',
4352  'name' => 'wpDiff',
4353  'tabindex' => ++$tabindex,
4354  ] + Linker::tooltipAndAccesskeyAttribs( 'diff' );
4355  if ( $this->oouiEnabled ) {
4356  $diffConfig = OOUI\Element::configFromHtmlAttributes( $attribs );
4357  $buttons['diff'] = new OOUI\ButtonInputWidget( [
4358  // Support: IE 6 – Use <input>, otherwise it can't distinguish which button was clicked
4359  'useInputTag' => true,
4360  'label' => $this->context->msg( 'showdiff' )->text(),
4361  'infusable' => true,
4362  'type' => 'submit',
4363  ] + $diffConfig );
4364  } else {
4365  $buttons['diff'] = Html::submitButton(
4366  $this->context->msg( 'showdiff' )->text(),
4367  $attribs
4368  );
4369  }
4370 
4371  // Avoid PHP 7.1 warning of passing $this by reference
4372  $editPage = $this;
4373  Hooks::run( 'EditPageBeforeEditButtons', [ &$editPage, &$buttons, &$tabindex ] );
4374 
4375  return $buttons;
4376  }
4377 
4382  public function noSuchSectionPage() {
4383  global $wgOut;
4384 
4385  $wgOut->prepareErrorPage( $this->context->msg( 'nosuchsectiontitle' ) );
4386 
4387  $res = $this->context->msg( 'nosuchsectiontext', $this->section )->parseAsBlock();
4388 
4389  // Avoid PHP 7.1 warning of passing $this by reference
4390  $editPage = $this;
4391  Hooks::run( 'EditPageNoSuchSection', [ &$editPage, &$res ] );
4392  $wgOut->addHTML( $res );
4393 
4394  $wgOut->returnToMain( false, $this->mTitle );
4395  }
4396 
4402  public function spamPageWithContent( $match = false ) {
4404  $this->textbox2 = $this->textbox1;
4405 
4406  if ( is_array( $match ) ) {
4407  $match = $wgLang->listToText( $match );
4408  }
4409  $wgOut->prepareErrorPage( $this->context->msg( 'spamprotectiontitle' ) );
4410 
4411  $wgOut->addHTML( '<div id="spamprotected">' );
4412  $wgOut->addWikiMsg( 'spamprotectiontext' );
4413  if ( $match ) {
4414  $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
4415  }
4416  $wgOut->addHTML( '</div>' );
4417 
4418  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
4419  $this->showDiff();
4420 
4421  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
4422  $this->showTextbox2();
4423 
4424  $wgOut->addReturnTo( $this->getContextTitle(), [ 'action' => 'edit' ] );
4425  }
4426 
4433  private function checkUnicodeCompliantBrowser() {
4434  global $wgBrowserBlackList, $wgRequest;
4435 
4436  $currentbrowser = $wgRequest->getHeader( 'User-Agent' );
4437  if ( $currentbrowser === false ) {
4438  // No User-Agent header sent? Trust it by default...
4439  return true;
4440  }
4441 
4442  foreach ( $wgBrowserBlackList as $browser ) {
4443  if ( preg_match( $browser, $currentbrowser ) ) {
4444  return false;
4445  }
4446  }
4447  return true;
4448  }
4449 
4458  protected function safeUnicodeInput( $request, $field ) {
4459  $text = rtrim( $request->getText( $field ) );
4460  return $request->getBool( 'safemode' )
4461  ? $this->unmakeSafe( $text )
4462  : $text;
4463  }
4464 
4472  protected function safeUnicodeOutput( $text ) {
4473  return $this->checkUnicodeCompliantBrowser()
4474  ? $text
4475  : $this->makeSafe( $text );
4476  }
4477 
4490  private function makeSafe( $invalue ) {
4491  // Armor existing references for reversibility.
4492  $invalue = strtr( $invalue, [ "&#x" => "&#x0" ] );
4493 
4494  $bytesleft = 0;
4495  $result = "";
4496  $working = 0;
4497  $valueLength = strlen( $invalue );
4498  for ( $i = 0; $i < $valueLength; $i++ ) {
4499  $bytevalue = ord( $invalue[$i] );
4500  if ( $bytevalue <= 0x7F ) { // 0xxx xxxx
4501  $result .= chr( $bytevalue );
4502  $bytesleft = 0;
4503  } elseif ( $bytevalue <= 0xBF ) { // 10xx xxxx
4504  $working = $working << 6;
4505  $working += ( $bytevalue & 0x3F );
4506  $bytesleft--;
4507  if ( $bytesleft <= 0 ) {
4508  $result .= "&#x" . strtoupper( dechex( $working ) ) . ";";
4509  }
4510  } elseif ( $bytevalue <= 0xDF ) { // 110x xxxx
4511  $working = $bytevalue & 0x1F;
4512  $bytesleft = 1;
4513  } elseif ( $bytevalue <= 0xEF ) { // 1110 xxxx
4514  $working = $bytevalue & 0x0F;
4515  $bytesleft = 2;
4516  } else { // 1111 0xxx
4517  $working = $bytevalue & 0x07;
4518  $bytesleft = 3;
4519  }
4520  }
4521  return $result;
4522  }
4523 
4532  private function unmakeSafe( $invalue ) {
4533  $result = "";
4534  $valueLength = strlen( $invalue );
4535  for ( $i = 0; $i < $valueLength; $i++ ) {
4536  if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) {
4537  $i += 3;
4538  $hexstring = "";
4539  do {
4540  $hexstring .= $invalue[$i];
4541  $i++;
4542  } while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
4543 
4544  // Do some sanity checks. These aren't needed for reversibility,
4545  // but should help keep the breakage down if the editor
4546  // breaks one of the entities whilst editing.
4547  if ( ( substr( $invalue, $i, 1 ) == ";" ) && ( strlen( $hexstring ) <= 6 ) ) {
4548  $codepoint = hexdec( $hexstring );
4549  $result .= UtfNormal\Utils::codepointToUtf8( $codepoint );
4550  } else {
4551  $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
4552  }
4553  } else {
4554  $result .= substr( $invalue, $i, 1 );
4555  }
4556  }
4557  // reverse the transform that we made for reversibility reasons.
4558  return strtr( $result, [ "&#x0" => "&#x" ] );
4559  }
4560 
4564  protected function addEditNotices() {
4565  global $wgOut;
4566 
4567  $editNotices = $this->mTitle->getEditNotices( $this->oldid );
4568  if ( count( $editNotices ) ) {
4569  $wgOut->addHTML( implode( "\n", $editNotices ) );
4570  } else {
4571  $msg = $this->context->msg( 'editnotice-notext' );
4572  if ( !$msg->isDisabled() ) {
4573  $wgOut->addHTML(
4574  '<div class="mw-editnotice-notext">'
4575  . $msg->parseAsBlock()
4576  . '</div>'
4577  );
4578  }
4579  }
4580  }
4581 
4585  protected function addTalkPageText() {
4586  global $wgOut;
4587 
4588  if ( $this->mTitle->isTalkPage() ) {
4589  $wgOut->addWikiMsg( 'talkpagetext' );
4590  }
4591  }
4592 
4596  protected function addLongPageWarningHeader() {
4598 
4599  if ( $this->contentLength === false ) {
4600  $this->contentLength = strlen( $this->textbox1 );
4601  }
4602 
4603  if ( $this->tooBig || $this->contentLength > $wgMaxArticleSize * 1024 ) {
4604  $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
4605  [
4606  'longpageerror',
4607  $wgLang->formatNum( round( $this->contentLength / 1024, 3 ) ),
4608  $wgLang->formatNum( $wgMaxArticleSize )
4609  ]
4610  );
4611  } else {
4612  if ( !$this->context->msg( 'longpage-hint' )->isDisabled() ) {
4613  $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
4614  [
4615  'longpage-hint',
4616  $wgLang->formatSize( strlen( $this->textbox1 ) ),
4617  strlen( $this->textbox1 )
4618  ]
4619  );
4620  }
4621  }
4622  }
4623 
4627  protected function addPageProtectionWarningHeaders() {
4628  global $wgOut;
4629 
4630  if ( $this->mTitle->isProtected( 'edit' ) &&
4631  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== [ '' ]
4632  ) {
4633  # Is the title semi-protected?
4634  if ( $this->mTitle->isSemiProtected() ) {
4635  $noticeMsg = 'semiprotectedpagewarning';
4636  } else {
4637  # Then it must be protected based on static groups (regular)
4638  $noticeMsg = 'protectedpagewarning';
4639  }
4640  LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
4641  [ 'lim' => 1, 'msgKey' => [ $noticeMsg ] ] );
4642  }
4643  if ( $this->mTitle->isCascadeProtected() ) {
4644  # Is this page under cascading protection from some source pages?
4645 
4646  list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
4647  $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
4648  $cascadeSourcesCount = count( $cascadeSources );
4649  if ( $cascadeSourcesCount > 0 ) {
4650  # Explain, and list the titles responsible
4651  foreach ( $cascadeSources as $page ) {
4652  $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
4653  }
4654  }
4655  $notice .= '</div>';
4656  $wgOut->wrapWikiMsg( $notice, [ 'cascadeprotectedwarning', $cascadeSourcesCount ] );
4657  }
4658  if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
4659  LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
4660  [ 'lim' => 1,
4661  'showIfEmpty' => false,
4662  'msgKey' => [ 'titleprotectedwarning' ],
4663  'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ] );
4664  }
4665  }
4666 
4671  protected function addExplainConflictHeader( OutputPage $out ) {
4672  $out->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
4673  }
4674 
4683  $attribs = $customAttribs + [
4684  'accesskey' => ',',
4685  'id' => $name,
4686  'cols' => 80,
4687  'rows' => 25,
4688  // Avoid PHP notices when appending preferences
4689  // (appending allows customAttribs['style'] to still work).
4690  'style' => ''
4691  ];
4692 
4693  // The following classes can be used here:
4694  // * mw-editfont-default
4695  // * mw-editfont-monospace
4696  // * mw-editfont-sans-serif
4697  // * mw-editfont-serif
4698  $class = 'mw-editfont-' . $user->getOption( 'editfont' );
4699 
4700  if ( isset( $attribs['class'] ) ) {
4701  if ( is_string( $attribs['class'] ) ) {
4702  $attribs['class'] .= ' ' . $class;
4703  } elseif ( is_array( $attribs['class'] ) ) {
4704  $attribs['class'][] = $class;
4705  }
4706  } else {
4707  $attribs['class'] = $class;
4708  }
4709 
4710  $pageLang = $this->mTitle->getPageLanguage();
4711  $attribs['lang'] = $pageLang->getHtmlCode();
4712  $attribs['dir'] = $pageLang->getDir();
4713 
4714  return $attribs;
4715  }
4716 
4722  protected function addNewLineAtEnd( $wikitext ) {
4723  if ( strval( $wikitext ) !== '' ) {
4724  // Ensure there's a newline at the end, otherwise adding lines
4725  // is awkward.
4726  // But don't add a newline if the text is empty, or Firefox in XHTML
4727  // mode will show an extra newline. A bit annoying.
4728  $wikitext .= "\n";
4729  return $wikitext;
4730  }
4731  return $wikitext;
4732  }
4733 }
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:424
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:293
$wgUser
$wgUser
Definition: Setup.php:781
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\$contentModel
string $contentModel
Definition: EditPage.php:367
EditPage\showFormBeforeText
showFormBeforeText()
Definition: EditPage.php:3191
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:265
EditPage\internalAttemptSave
internalAttemptSave(&$result, $bot=false)
Attempt submission (no UI)
Definition: EditPage.php:1735
EditPage\$lastDelete
bool stdClass $lastDelete
Definition: EditPage.php:248
$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:783
EditPage\tokenOk
tokenOk(&$request)
Make sure the form isn't faking a user's credentials.
Definition: EditPage.php:1441
EditPage\$editFormPageTop
string $editFormPageTop
Before even the preview.
Definition: EditPage.php:379
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:28
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:3233
EditPage\$mTitle
Title $mTitle
Definition: EditPage.php:212
EditPage\getSummaryInputOOUI
getSummaryInputOOUI( $summary="", $labelText=null, $inputAttrs=null)
Same as self::getSummaryInput, but uses OOUI, instead of plain HTML.
Definition: EditPage.php:3104
$request
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2612
Html\textarea
static textarea( $name, $value='', array $attribs=[])
Convenience function to produce a <textarea> element.
Definition: Html.php:762
EditPage\spamPageWithContent
spamPageWithContent( $match=false)
Show "your edit contains spam" page with your diff and text.
Definition: EditPage.php:4402
EditPage\$section
string $section
Definition: EditPage.php:343
ParserOutput
Definition: ParserOutput.php:24
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
WikiPage\getRedirectTarget
getRedirectTarget()
If this page is a redirect, get its target.
Definition: WikiPage.php:832
UserBlockedError
Show an error when the user tries to do something whilst blocked.
Definition: UserBlockedError.php:27
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:116
EditPage\$scrolltop
null $scrolltop
Definition: EditPage.php:361
$wgMaxArticleSize
$wgMaxArticleSize
Maximum article size in kilobytes.
Definition: DefaultSettings.php:2174
content
per default it will return the text for text based content
Definition: contenthandler.txt:104
EditPage\mergeChangesIntoContent
mergeChangesIntoContent(&$editContent)
Attempts to do 3-way merge of edit content with a base revision and current content,...
Definition: EditPage.php:2253
EditPage\displayPermissionsError
displayPermissionsError(array $permErrors)
Display a permissions error page, like OutputPage::showPermissionsErrorPage(), but with the following...
Definition: EditPage.php:711
$wgParser
$wgParser
Definition: Setup.php:796
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:187
EditPage\displayPreviewArea
displayPreviewArea( $previewOutput, $isOnTop=false)
Definition: EditPage.php:3306
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
EditPage\getSummaryInputAttributes
getSummaryInputAttributes(array $inputAttrs=null)
Helper function for summary input functions, which returns the neccessary attributes for the input.
Definition: EditPage.php:3043
EditPage\$editFormTextTop
$editFormTextTop
Definition: EditPage.php:380
EditPage\$editintro
string $editintro
Definition: EditPage.php:358
EditPage\showTextbox2
showTextbox2()
Definition: EditPage.php:3291
EDIT_FORCE_BOT
const EDIT_FORCE_BOT
Definition: Defines.php:154
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\buildTextboxAttribs
buildTextboxAttribs( $name, array $customAttribs, User $user)
Definition: EditPage.php:4682
captcha-old.count
count
Definition: captcha-old.py:225
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:1377
EditPage\$mTokenOk
bool $mTokenOk
Definition: EditPage.php:251
$regexes
if( $wgSpamBlacklistFiles) $regexes
Definition: cleanup.php:88
CONTENT_MODEL_CSS
const CONTENT_MODEL_CSS
Definition: Defines.php:235
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1954
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1994
EditPage\$oldid
int $oldid
Definition: EditPage.php:352
EditPage\getContextTitle
getContextTitle()
Get the context title object.
Definition: EditPage.php:479
$status
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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
EditPage\showTosSummary
showTosSummary()
Give a chance for site and per-namespace customizations of terms of service summary link that might e...
Definition: EditPage.php:3454
EditPage\isWrongCaseCssJsPage
isWrongCaseCssJsPage()
Checks whether the user entered a skin name in uppercase, e.g.
Definition: EditPage.php:829
CategoryPage
Special handling for category description pages, showing pages, subcategories and file that belong to...
Definition: CategoryPage.php:28
EditPage\$page
WikiPage $page
Definition: EditPage.php:209
EditPage\$save
bool $save
Definition: EditPage.php:307
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
EditPage\addPageProtectionWarningHeaders
addPageProtectionWarningHeaders()
Definition: EditPage.php:4627
EditPage\setContextTitle
setContextTitle( $title)
Set the context Title object.
Definition: EditPage.php:468
$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
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:36
EditPage\edit
edit()
This is the function that gets called for "action=edit".
Definition: EditPage.php:536
EditPage\$autoSumm
string $autoSumm
Definition: EditPage.php:287
NS_FILE
const NS_FILE
Definition: Defines.php:68
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:4458
$params
$params
Definition: styleTest.css.php:40
Block\newFromTarget
static newFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target's type, get an existing Block object if possible.
Definition: Block.php:1113
EditPage\getLastDelete
getLastDelete()
Definition: EditPage.php:3733
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1277
EditPage\incrementConflictStats
incrementConflictStats()
Definition: EditPage.php:3649
EditPage\addEditNotices
addEditNotices()
Definition: EditPage.php:4564
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:556
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
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:240
diff
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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 please use GetContentModels hook to make them known to core 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' also included in $newHeader if any indicating whether we should show just the diff
Definition: hooks.txt:1167
$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:1362
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304
EditPage\AS_CONTENT_TOO_BIG
const AS_CONTENT_TOO_BIG
Status: Content too big (> $wgMaxArticleSize)
Definition: EditPage.php:71
$wgTitle
if(! $wgRequest->checkUrlExtension()) if(! $wgEnableAPI) $wgTitle
Definition: api.php:57
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:28
EditPage\showHeaderCopyrightWarning
showHeaderCopyrightWarning()
Show the header copyright warning.
Definition: EditPage.php:3437
EditPage\getEditPermissionErrors
getEditPermissionErrors( $rigor='secure')
Definition: EditPage.php:666
$type
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 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:2536
EditPage\addLongPageWarningHeader
addLongPageWarningHeader()
Definition: EditPage.php:4596
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
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:1092
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:1884
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:663
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
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
Revision\FOR_THIS_USER
const FOR_THIS_USER
Definition: Revision.php:99
Revision
Definition: Revision.php:33
NS_MAIN
const NS_MAIN
Definition: Defines.php:62
EditPage\getPreviewLimitReport
static getPreviewLimitReport( $output)
Get the Limit report for page previews.
Definition: EditPage.php:3518
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:309
EditPage\showDiff
showDiff()
Get a diff between the current contents of the edit box and the version of the page we're editing fro...
Definition: EditPage.php:3375
$customAttribs
null means default & $customAttribs
Definition: hooks.txt:1956
Xml\encodeJsCall
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:645
EditPage\$tooBig
bool $tooBig
Definition: EditPage.php:263
$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:1572
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, IDatabase $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:111
EditPage\AS_SUCCESS_NEW_ARTICLE
const AS_SUCCESS_NEW_ARTICLE
Status: Article successfully created.
Definition: EditPage.php:51
codepointToUtf8
codepointToUtf8( $codepoint)
Return UTF-8 sequence for a given Unicode code point.
Definition: UtfNormalUtil.php:39
$html
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:1956
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:2206
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:934
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:120
EditPage\toEditContent
toEditContent( $text)
Turns the given text into a Content object by unserializing it.
Definition: EditPage.php:2579
EditPage\getEditButtons
getEditButtons(&$tabindex)
Returns an array of html code of the following buttons: save, diff and preview.
Definition: EditPage.php:4294
EditPage\AS_END
const AS_END
Status: WikiPage::doEdit() was unsuccessful.
Definition: EditPage.php:134
LogPage\DELETED_COMMENT
const DELETED_COMMENT
Definition: LogPage.php:33
EditPage\showSummaryInput
showSummaryInput( $isSubjectPreview, $summary="")
Definition: EditPage.php:3129
EditPage\getParentRevId
getParentRevId()
Get the edit's parent revision ID.
Definition: EditPage.php:1300
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:178
EditPage\isSectionEditSupported
isSectionEditSupported()
Returns whether section editing is supported for the current page.
Definition: EditPage.php:850
EditPage\importFormData
importFormData(&$request)
This function collects the form data and uses it to populate various member variables.
Definition: EditPage.php:860
EditPage\getActionURL
getActionURL(Title $title)
Returns the URL to use in the form's action attribute.
Definition: EditPage.php:3699
EditPage\addExplainConflictHeader
addExplainConflictHeader(OutputPage $out)
Definition: EditPage.php:4671
LogPage\DELETED_USER
const DELETED_USER
Definition: LogPage.php:34
$content
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:3060
EditPage\showIntro
showIntro()
Show all applicable editing introductions.
Definition: EditPage.php:2401
$input
if(is_array( $mode)) switch( $mode) $input
Definition: postprocess-phan.php:141
EditPage\$firsttime
bool $firsttime
Definition: EditPage.php:245
$matches
$matches
Definition: NoLocalSettings.php:24
in
null for the wiki Added in
Definition: hooks.txt:1572
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:323
EditPage\$missingComment
bool $missingComment
Definition: EditPage.php:266
$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:1956
not
if not
Definition: COPYING.txt:307
MWContentSerializationException
Exception representing a failure to serialize or unserialize a content object.
Definition: MWContentSerializationException.php:7
EditPage\attemptSave
attemptSave(&$resultDetails=false)
Attempt submission.
Definition: EditPage.php:1488
WikiPage\getContent
getContent( $audience=Revision::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
Definition: WikiPage.php:663
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:39
$tabindex
in this case you re responsible for computing and outputting the entire conflict i the difference between revisions and your text headers and sections and Diff & $tabindex
Definition: hooks.txt:1398
Article\getPage
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:184
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:819
EditPage\getArticle
getArticle()
Definition: EditPage.php:443
EditPage\getCheckboxes
getCheckboxes(&$tabindex, $checked)
Returns an array of html code of the following checkboxes old style: minor and watch.
Definition: EditPage.php:4173
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\$mBaseRevision
Revision bool $mBaseRevision
Definition: EditPage.php:299
EditPage\previewOnOpen
previewOnOpen()
Should we show a preview when the edit form is first shown?
Definition: EditPage.php:796
WatchAction\doWatchOrUnwatch
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:93
EditPage\incrementEditFailureStats
incrementEditFailureStats( $failureType)
Definition: EditPage.php:3913
$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
$output
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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\$allowSelfRedirect
bool $allowSelfRedirect
Definition: EditPage.php:284
EditPage\showEditForm
showEditForm( $formCallback=null)
Send the edit form and related headers to $wgOut.
Definition: EditPage.php:2602
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:514
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:564
EditPage\wasDeletedSinceLastEdit
wasDeletedSinceLastEdit()
Check if a page was deleted while the user was editing it, before submit.
Definition: EditPage.php:3710
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
EditPage\getTemplates
getTemplates()
Definition: EditPage.php:3956
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2023
EditPage\getPreviewParserOptions
getPreviewParserOptions()
Get parser options for a preview.
Definition: EditPage.php:3922
EditPage\runPostMergeFilters
runPostMergeFilters(Content $content, Status $status, User $user)
Run hooks that can filter edits just before they get saved.
Definition: EditPage.php:1640
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:26
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:451
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:999
EditPage\showFormAfterText
showFormAfterText()
Definition: EditPage.php:3208
OutputPage
This class should be covered by a general architecture document which does not exist as of January 20...
Definition: OutputPage.php:44
EditPage\showPreview
showPreview( $text)
Append preview output to $wgOut.
Definition: EditPage.php:3354
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:1097
EditPage\getEditToolbar
static getEditToolbar( $title=null)
Shows a bulletin board style toolbar for common editing functions.
Definition: EditPage.php:3980
ContentHandler\makeContent
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Definition: ContentHandler.php:129
captcha-old.action
action
Definition: captcha-old.py:189
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:4472
EditPage\matchSpamRegex
static matchSpamRegex( $text)
Check given input text against $wgSpamRegex, and return the text of the first match.
Definition: EditPage.php:2308
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:746
EditPage\$recreate
bool $recreate
Definition: EditPage.php:322
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:1883
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:3245
EditPage\addTalkPageText
addTalkPageText()
Definition: EditPage.php:4585
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:65
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2122
EditPage\getSummaryPreview
getSummaryPreview( $isSubjectPreview, $summary="")
Definition: EditPage.php:3169
EditPage\importContentFormData
importContentFormData(&$request)
Subpage overridable method for extracting the page content data from the posted form to be placed in ...
Definition: EditPage.php:1088
value
$status value
Definition: SyntaxHighlight_GeSHi.class.php:309
EditPage\$minoredit
bool $minoredit
Definition: EditPage.php:316
EditPage\$isOldRev
bool $isOldRev
Whether an old revision is edited.
Definition: EditPage.php:414
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:151
Linker\tooltipAndAccesskeyAttribs
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[])
Returns the attributes for the tooltip and access key.
Definition: Linker.php:2096
EditPage\showHeader
showHeader()
Definition: EditPage.php:2895
EditPage\getBaseRevision
getBaseRevision()
Definition: EditPage.php:2291
ContentHandler\getLocalizedName
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
Definition: ContentHandler.php:348
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:50
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:3069
EditPage\addNewLineAtEnd
addNewLineAtEnd( $wikitext)
Definition: EditPage.php:4722
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:309
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:2322
EditPage\$mTokenOkExceptSuffix
bool $mTokenOkExceptSuffix
Definition: EditPage.php:254
EditPage\showEditTools
showEditTools()
Definition: EditPage.php:3465
EditPage\$preview
bool $preview
Definition: EditPage.php:310
EditPage\$isNew
bool $isNew
New page or new section.
Definition: EditPage.php:236
EditPage\getCheckboxesDefinition
getCheckboxesDefinition( $checked)
Return an array of checkbox definitions.
Definition: EditPage.php:4130
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:3478
EditPage\setApiEditOverride
setApiEditOverride( $enableOverride)
Allow editing of content that supports API direct editing, but not general direct editing.
Definition: EditPage.php:514
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1657
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:100
EditPage\getCancelLink
getCancelLink()
Definition: EditPage.php:3664
EditPage\unmakeSafe
unmakeSafe( $invalue)
Reverse the previously applied transliteration of non-ASCII characters back to UTF-8.
Definition: EditPage.php:4532
EditPage\showCustomIntro
showCustomIntro()
Attempt to show a custom editing introduction, if supplied.
Definition: EditPage.php:2517
EditPage\getContext
getContext()
Definition: EditPage.php:451
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:2885
Block\TYPE_AUTO
const TYPE_AUTO
Definition: Block.php:86
$handler
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:783
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:2861
EditPage\$textbox1
string $textbox1
Definition: EditPage.php:325
plain
either a plain
Definition: hooks.txt:2007
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:1936
wfFindFile
wfFindFile( $title, $options=[])
Find a file.
Definition: GlobalFunctions.php:3101
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
$response
this hook is for auditing only $response
Definition: hooks.txt:783
EditPage\noSuchSectionPage
noSuchSectionPage()
Creates a basic error page which informs the user that they have attempted to edit a nonexistent sect...
Definition: EditPage.php:4382
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:150
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:395
page
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 my talk page
Definition: hooks.txt:2536
EditPage\$mParserOutput
ParserOutput $mParserOutput
Definition: EditPage.php:293
Title
Represents a title within MediaWiki.
Definition: Title.php:39
EDIT_AUTOSUMMARY
const EDIT_AUTOSUMMARY
Definition: Defines.php:156
EditPage\$mShowSummaryField
bool $mShowSummaryField
Definition: EditPage.php:302
EditPage\$sectiontitle
string $sectiontitle
Definition: EditPage.php:346
EditPage\getCheckboxesOOUI
getCheckboxesOOUI(&$tabindex, $checked)
Returns an array of html code of the following checkboxes: minor and watch.
Definition: EditPage.php:4233
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:79
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:1290
$dbr
if(! $regexes) $dbr
Definition: cleanup.php:94
EditPage\$deletedSinceEdit
bool $deletedSinceEdit
Definition: EditPage.php:239
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:4433
EditPage\$selfRedirect
bool $selfRedirect
Definition: EditPage.php:281
EditPage\$edit
bool $edit
Definition: EditPage.php:396
$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:783
EditPage\isSupportedContentModel
isSupportedContentModel( $modelId)
Returns if the given content model is editable.
Definition: EditPage.php:503
EditPage\$mPreloadContent
$mPreloadContent
Definition: EditPage.php:387
EditPage\showConflict
showConflict()
Show an edit conflict.
Definition: EditPage.php:3623
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:4490
$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:1741
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:3939
EditPage\newSectionSummary
newSectionSummary(&$sectionanchor=null)
Return the summary to be used for a new section.
Definition: EditPage.php:1687
EditPage\$action
string $action
Definition: EditPage.php:218
EditPage\$oouiEnabled
bool $oouiEnabled
Whether OOUI should be enabled here.
Definition: EditPage.php:419
Html\openElement
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:251
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:307
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
$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
$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:1439
NS_USER
const NS_USER
Definition: Defines.php:64
EditPage\showTextbox
showTextbox( $text, $name, $customAttribs=[])
Definition: EditPage.php:3295
LoggerFactory
MediaWiki Logger LoggerFactory implements a PSR[0] compatible message logging system Named Psr Log LoggerInterface instances can be obtained from the MediaWiki Logger LoggerFactory::getInstance() static method. MediaWiki\Logger\LoggerFactory expects a class implementing the MediaWiki\Logger\Spi interface to act as a factory for new Psr\Log\LoggerInterface instances. The "Spi" in MediaWiki\Logger\Spi stands for "service provider interface". An SPI is an API intended to be implemented or extended by a third party. This software design pattern is intended to enable framework extension and replaceable components. It is specifically used in the MediaWiki\Logger\LoggerFactory service to allow alternate PSR-3 logging implementations to be easily integrated with MediaWiki. The service provider interface allows the backend logging library to be implemented in multiple ways. The $wgMWLoggerDefaultSpi global provides the classname of the default MediaWiki\Logger\Spi implementation to be loaded at runtime. This can either be the name of a class implementing the MediaWiki\Logger\Spi with a zero argument const ructor or a callable that will return an MediaWiki\Logger\Spi instance. Alternately the MediaWiki\Logger\LoggerFactory MediaWiki Logger LoggerFactory
Definition: logger.txt:5
EditPage\getTitle
getTitle()
Definition: EditPage.php:459
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:1956
ManualLogEntry
Class for creating log entries manually, to inject them into the database.
Definition: LogEntry.php:396
EditPage\AS_CONFLICT_DETECTED
const AS_CONFLICT_DETECTED
Status: (non-resolvable) edit conflict.
Definition: EditPage.php:113
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: MWUnknownContentModelException.php:10
of
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
Definition: globals.txt:10
wfMessage
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
EditPage\getCurrentContent
getCurrentContent()
Get the current content of the page.
Definition: EditPage.php:1316
Title\setContentModel
setContentModel( $model)
Set a proposed content model for the page for permissions checking.
Definition: Title.php:972
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:1142
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:70
CONTENT_MODEL_JAVASCRIPT
const CONTENT_MODEL_JAVASCRIPT
Definition: Defines.php:234
EditPage\displayViewSourcePage
displayViewSourcePage(Content $content, $errorMessage='')
Display a read-only View Source page.
Definition: EditPage.php:742
EDIT_MINOR
const EDIT_MINOR
Definition: Defines.php:152
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:35
EditPage\getContentObject
getContentObject( $def_content=null)
Definition: EditPage.php:1136
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:87
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
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:639
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
MediaWikiServices
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
EditPage\showStandardInputs
showStandardInputs(&$tabindex=2)
Definition: EditPage.php:3560
EditPage\isOouiEnabled
isOouiEnabled()
Check if the edit page is using OOUI controls.
Definition: EditPage.php:492
$wgOut
$wgOut
Definition: Setup.php:791
NS_CATEGORY_TALK
const NS_CATEGORY_TALK
Definition: Defines.php:77
EditPage\matchSpamRegexInternal
static matchSpamRegexInternal( $text, $regexes)
Definition: EditPage.php:2333
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:489
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:282
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:1275
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:1465
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:50
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
$username
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:783
$wgForeignFileRepos
$wgForeignFileRepos
Definition: DefaultSettings.php:522
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:3490
$options
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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 $options
Definition: hooks.txt:1049
MWNamespace\getCanonicalName
static getCanonicalName( $index)
Returns the canonical (English) name for a given index.
Definition: MWNamespace.php:228
Revision\DELETED_TEXT
const DELETED_TEXT
Definition: Revision.php:90
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2749
EditPage\AS_HOOK_ERROR_EXPECTED
const AS_HOOK_ERROR_EXPECTED
Status: A hook function returned an error.
Definition: EditPage.php:61
$parserOutput
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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
Skin\makeInternalOrExternalUrl
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition: Skin.php:1132
EditPage\updateWatchlist
updateWatchlist()
Register the change of watch status.
Definition: EditPage.php:2223
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:552
array
the array() calling protocol came about after MediaWiki 1.4rc1.
EditPage\handleStatus
handleStatus(Status $status, $resultDetails)
Handle status, such as after attempt save.
Definition: EditPage.php:1509
ParserOptions\newFromUser
static newFromUser( $user)
Get a ParserOptions object from a given user.
Definition: ParserOptions.php:705
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:2551
Xml\checkLabel
static checkLabel( $label, $name, $id, $checked=false, $attribs=[])
Convenience function to build an HTML checkbox with a label.
Definition: Xml.php:419
EditPage\setHeaders
setHeaders()
Definition: EditPage.php:2343
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
$out
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:783
EditPage\submit
submit()
Definition: EditPage.php:521
EditPage\$nosummary
bool $nosummary
Definition: EditPage.php:334
EditPage\getPreviewText
getPreviewText()
Get the rendered text for previewing.
Definition: EditPage.php:3777