MediaWiki  1.23.2
EditPage.php
Go to the documentation of this file.
1 <?php
38 class EditPage {
42  const AS_SUCCESS_UPDATE = 200;
43 
47  const AS_SUCCESS_NEW_ARTICLE = 201;
48 
52  const AS_HOOK_ERROR = 210;
53 
57  const AS_HOOK_ERROR_EXPECTED = 212;
58 
62  const AS_BLOCKED_PAGE_FOR_USER = 215;
63 
67  const AS_CONTENT_TOO_BIG = 216;
68 
72  const AS_USER_CANNOT_EDIT = 217;
73 
77  const AS_READ_ONLY_PAGE_ANON = 218;
78 
82  const AS_READ_ONLY_PAGE_LOGGED = 219;
83 
87  const AS_READ_ONLY_PAGE = 220;
88 
92  const AS_RATE_LIMITED = 221;
93 
98  const AS_ARTICLE_WAS_DELETED = 222;
99 
104  const AS_NO_CREATE_PERMISSION = 223;
105 
109  const AS_BLANK_ARTICLE = 224;
110 
114  const AS_CONFLICT_DETECTED = 225;
115 
120  const AS_SUMMARY_NEEDED = 226;
121 
125  const AS_TEXTBOX_EMPTY = 228;
126 
130  const AS_MAX_ARTICLE_SIZE_EXCEEDED = 229;
131 
135  const AS_OK = 230;
136 
140  const AS_END = 231;
141 
145  const AS_SPAM_ERROR = 232;
146 
150  const AS_IMAGE_REDIRECT_ANON = 233;
151 
155  const AS_IMAGE_REDIRECT_LOGGED = 234;
156 
160  const AS_PARSE_ERROR = 240;
161 
165  const EDITFORM_ID = 'editform';
166 
171  const POST_EDIT_COOKIE_KEY_PREFIX = 'PostEditRevision';
172 
185  const POST_EDIT_COOKIE_DURATION = 1200;
186 
190  var $mArticle;
191 
195  var $mTitle;
196  private $mContextTitle = null;
197  var $action = 'submit';
198  var $isConflict = false;
199  var $isCssJsSubpage = false;
200  var $isCssSubpage = false;
201  var $isJsSubpage = false;
202  var $isWrongCaseCssJsPage = false;
203  var $isNew = false; // new page or new section
204  var $deletedSinceEdit;
205  var $formtype;
206  var $firsttime;
207  var $lastDelete;
208  var $mTokenOk = false;
209  var $mTokenOkExceptSuffix = false;
210  var $mTriedSave = false;
211  var $incompleteForm = false;
212  var $tooBig = false;
213  var $kblength = false;
214  var $missingComment = false;
215  var $missingSummary = false;
216  var $allowBlankSummary = false;
217  var $autoSumm = '';
218  var $hookError = '';
219  #var $mPreviewTemplates;
220 
224  var $mParserOutput;
225 
230  var $hasPresetSummary = false;
231 
232  var $mBaseRevision = false;
233  var $mShowSummaryField = true;
234 
235  # Form values
236  var $save = false, $preview = false, $diff = false;
237  var $minoredit = false, $watchthis = false, $recreate = false;
238  var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
239  var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
240  var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
241  var $contentModel = null, $contentFormat = null;
242 
243  # Placeholders for text injection by hooks (must be HTML)
244  # extensions should take care to _append_ to the present value
245  public $editFormPageTop = ''; // Before even the preview
246  public $editFormTextTop = '';
247  public $editFormTextBeforeContent = '';
248  public $editFormTextAfterWarn = '';
249  public $editFormTextAfterTools = '';
250  public $editFormTextBottom = '';
251  public $editFormTextAfterContent = '';
252  public $previewTextAfterContent = '';
253  public $mPreloadContent = null;
254 
255  /* $didSave should be set to true whenever an article was successfully altered. */
256  public $didSave = false;
257  public $undidRev = 0;
258 
259  public $suppressIntro = false;
260 
266  public $allowNonTextContent = false;
267 
271  public function __construct( Article $article ) {
272  $this->mArticle = $article;
273  $this->mTitle = $article->getTitle();
274 
275  $this->contentModel = $this->mTitle->getContentModel();
276 
277  $handler = ContentHandler::getForModelID( $this->contentModel );
278  $this->contentFormat = $handler->getDefaultFormat();
279  }
280 
284  public function getArticle() {
285  return $this->mArticle;
286  }
287 
292  public function getTitle() {
293  return $this->mTitle;
294  }
295 
301  public function setContextTitle( $title ) {
302  $this->mContextTitle = $title;
303  }
304 
312  public function getContextTitle() {
313  if ( is_null( $this->mContextTitle ) ) {
315  return $wgTitle;
316  } else {
317  return $this->mContextTitle;
318  }
319  }
320 
328  public function isSupportedContentModel( $modelId ) {
329  return $this->allowNonTextContent ||
330  ContentHandler::getForModelID( $modelId ) instanceof TextContentHandler;
331  }
332 
333  function submit() {
334  $this->edit();
335  }
336 
348  function edit() {
349  global $wgOut, $wgRequest, $wgUser;
350  // Allow extensions to modify/prevent this form or submission
351  if ( !wfRunHooks( 'AlternateEdit', array( $this ) ) ) {
352  return;
353  }
354 
355  wfProfileIn( __METHOD__ );
356  wfDebug( __METHOD__ . ": enter\n" );
357 
358  // If they used redlink=1 and the page exists, redirect to the main article
359  if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
360  $wgOut->redirect( $this->mTitle->getFullURL() );
361  wfProfileOut( __METHOD__ );
362  return;
363  }
364 
365  $this->importFormData( $wgRequest );
366  $this->firsttime = false;
367 
368  if ( $this->live ) {
369  $this->livePreview();
370  wfProfileOut( __METHOD__ );
371  return;
372  }
373 
374  if ( wfReadOnly() && $this->save ) {
375  // Force preview
376  $this->save = false;
377  $this->preview = true;
378  }
379 
380  if ( $this->save ) {
381  $this->formtype = 'save';
382  } elseif ( $this->preview ) {
383  $this->formtype = 'preview';
384  } elseif ( $this->diff ) {
385  $this->formtype = 'diff';
386  } else { # First time through
387  $this->firsttime = true;
388  if ( $this->previewOnOpen() ) {
389  $this->formtype = 'preview';
390  } else {
391  $this->formtype = 'initial';
392  }
393  }
394 
395  $permErrors = $this->getEditPermissionErrors();
396  if ( $permErrors ) {
397  wfDebug( __METHOD__ . ": User can't edit\n" );
398  // Auto-block user's IP if the account was "hard" blocked
399  $wgUser->spreadAnyEditBlock();
400 
401  $this->displayPermissionsError( $permErrors );
402 
403  wfProfileOut( __METHOD__ );
404  return;
405  }
406 
407  wfProfileIn( __METHOD__ . "-business-end" );
408 
409  $this->isConflict = false;
410  // css / js subpages of user pages get a special treatment
411  $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
412  $this->isCssSubpage = $this->mTitle->isCssSubpage();
413  $this->isJsSubpage = $this->mTitle->isJsSubpage();
414  $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
415 
416  # Show applicable editing introductions
417  if ( $this->formtype == 'initial' || $this->firsttime ) {
418  $this->showIntro();
419  }
420 
421  # Attempt submission here. This will check for edit conflicts,
422  # and redundantly check for locked database, blocked IPs, etc.
423  # that edit() already checked just in case someone tries to sneak
424  # in the back door with a hand-edited submission URL.
425 
426  if ( 'save' == $this->formtype ) {
427  if ( !$this->attemptSave() ) {
428  wfProfileOut( __METHOD__ . "-business-end" );
429  wfProfileOut( __METHOD__ );
430  return;
431  }
432  }
433 
434  # First time through: get contents, set time for conflict
435  # checking, etc.
436  if ( 'initial' == $this->formtype || $this->firsttime ) {
437  if ( $this->initialiseForm() === false ) {
438  $this->noSuchSectionPage();
439  wfProfileOut( __METHOD__ . "-business-end" );
440  wfProfileOut( __METHOD__ );
441  return;
442  }
443 
444  if ( !$this->mTitle->getArticleID() ) {
445  wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
446  } else {
447  wfRunHooks( 'EditFormInitialText', array( $this ) );
448  }
449 
450  }
451 
452  $this->showEditForm();
453  wfProfileOut( __METHOD__ . "-business-end" );
454  wfProfileOut( __METHOD__ );
455  }
456 
460  protected function getEditPermissionErrors() {
461  global $wgUser;
462  $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
463  # Can this title be created?
464  if ( !$this->mTitle->exists() ) {
465  $permErrors = array_merge( $permErrors,
466  wfArrayDiff2( $this->mTitle->getUserPermissionsErrors( 'create', $wgUser ), $permErrors ) );
467  }
468  # Ignore some permissions errors when a user is just previewing/viewing diffs
469  $remove = array();
470  foreach ( $permErrors as $error ) {
471  if ( ( $this->preview || $this->diff )
472  && ( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' )
473  ) {
474  $remove[] = $error;
475  }
476  }
477  $permErrors = wfArrayDiff2( $permErrors, $remove );
478  return $permErrors;
479  }
480 
494  protected function displayPermissionsError( array $permErrors ) {
495  global $wgRequest, $wgOut;
496 
497  if ( $wgRequest->getBool( 'redlink' ) ) {
498  // The edit page was reached via a red link.
499  // Redirect to the article page and let them click the edit tab if
500  // they really want a permission error.
501  $wgOut->redirect( $this->mTitle->getFullURL() );
502  return;
503  }
504 
505  $content = $this->getContentObject();
506 
507  # Use the normal message if there's nothing to display
508  if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
509  $action = $this->mTitle->exists() ? 'edit' :
510  ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
511  throw new PermissionsError( $action, $permErrors );
512  }
513 
514  $wgOut->setRobotPolicy( 'noindex,nofollow' );
515  $wgOut->setPageTitle( wfMessage( 'viewsource-title', $this->getContextTitle()->getPrefixedText() ) );
516  $wgOut->addBacklinkSubtitle( $this->getContextTitle() );
517  $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) );
518  $wgOut->addHTML( "<hr />\n" );
519 
520  # If the user made changes, preserve them when showing the markup
521  # (This happens when a user is blocked during edit, for instance)
522  if ( !$this->firsttime ) {
523  $text = $this->textbox1;
524  $wgOut->addWikiMsg( 'viewyourtext' );
525  } else {
526  $text = $this->toEditText( $content );
527  $wgOut->addWikiMsg( 'viewsourcetext' );
528  }
529 
530  $this->showTextbox( $text, 'wpTextbox1', array( 'readonly' ) );
531 
532  $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
533  Linker::formatTemplates( $this->getTemplates() ) ) );
534 
535  $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
536 
537  if ( $this->mTitle->exists() ) {
538  $wgOut->returnToMain( null, $this->mTitle );
539  }
540  }
541 
548  function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
549  wfDeprecated( __METHOD__, '1.19' );
550 
551  global $wgRequest, $wgOut;
552  if ( $wgRequest->getBool( 'redlink' ) ) {
553  // The edit page was reached via a red link.
554  // Redirect to the article page and let them click the edit tab if
555  // they really want a permission error.
556  $wgOut->redirect( $this->mTitle->getFullURL() );
557  } else {
558  $wgOut->readOnlyPage( $source, $protected, $reasons, $action );
559  }
560  }
561 
567  protected function previewOnOpen() {
568  global $wgRequest, $wgUser, $wgPreviewOnOpenNamespaces;
569  if ( $wgRequest->getVal( 'preview' ) == 'yes' ) {
570  // Explicit override from request
571  return true;
572  } elseif ( $wgRequest->getVal( 'preview' ) == 'no' ) {
573  // Explicit override from request
574  return false;
575  } elseif ( $this->section == 'new' ) {
576  // Nothing *to* preview for new sections
577  return false;
578  } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) {
579  // Standard preference behavior
580  return true;
581  } elseif ( !$this->mTitle->exists()
582  && isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
583  && $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()]
584  ) {
585  // Categories are special
586  return true;
587  } else {
588  return false;
589  }
590  }
591 
598  protected function isWrongCaseCssJsPage() {
599  if ( $this->mTitle->isCssJsSubpage() ) {
600  $name = $this->mTitle->getSkinFromCssJsSubpage();
601  $skins = array_merge(
602  array_keys( Skin::getSkinNames() ),
603  array( 'common' )
604  );
605  return !in_array( $name, $skins )
606  && in_array( strtolower( $name ), $skins );
607  } else {
608  return false;
609  }
610  }
611 
619  protected function isSectionEditSupported() {
620  $contentHandler = ContentHandler::getForTitle( $this->mTitle );
621  return $contentHandler->supportsSections();
622  }
623 
629  function importFormData( &$request ) {
631 
632  wfProfileIn( __METHOD__ );
633 
634  # Section edit can come from either the form or a link
635  $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
636 
637  if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) {
638  wfProfileOut( __METHOD__ );
639  throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
640  }
641 
642  $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
643 
644  if ( $request->wasPosted() ) {
645  # These fields need to be checked for encoding.
646  # Also remove trailing whitespace, but don't remove _initial_
647  # whitespace from the text boxes. This may be significant formatting.
648  $this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' );
649  if ( !$request->getCheck( 'wpTextbox2' ) ) {
650  // Skip this if wpTextbox2 has input, it indicates that we came
651  // from a conflict page with raw page text, not a custom form
652  // modified by subclasses
653  wfProfileIn( get_class( $this ) . "::importContentFormData" );
654  $textbox1 = $this->importContentFormData( $request );
655  if ( $textbox1 !== null ) {
656  $this->textbox1 = $textbox1;
657  }
658 
659  wfProfileOut( get_class( $this ) . "::importContentFormData" );
660  }
661 
662  # Truncate for whole multibyte characters
663  $this->summary = $wgContLang->truncate( $request->getText( 'wpSummary' ), 255 );
664 
665  # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
666  # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
667  # section titles.
668  $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
669 
670  # Treat sectiontitle the same way as summary.
671  # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
672  # currently doing double duty as both edit summary and section title. Right now this
673  # is just to allow API edits to work around this limitation, but this should be
674  # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
675  $this->sectiontitle = $wgContLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
676  $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
677 
678  $this->edittime = $request->getVal( 'wpEdittime' );
679  $this->starttime = $request->getVal( 'wpStarttime' );
680 
681  $undidRev = $request->getInt( 'wpUndidRevision' );
682  if ( $undidRev ) {
683  $this->undidRev = $undidRev;
684  }
685 
686  $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
687 
688  if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
689  // wpTextbox1 field is missing, possibly due to being "too big"
690  // according to some filter rules such as Suhosin's setting for
691  // suhosin.request.max_value_length (d'oh)
692  $this->incompleteForm = true;
693  } else {
694  // edittime should be one of our last fields; if it's missing,
695  // the submission probably broke somewhere in the middle.
696  $this->incompleteForm = is_null( $this->edittime );
697  }
698  if ( $this->incompleteForm ) {
699  # If the form is incomplete, force to preview.
700  wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
701  wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
702  $this->preview = true;
703  } else {
704  /* Fallback for live preview */
705  $this->preview = $request->getCheck( 'wpPreview' ) || $request->getCheck( 'wpLivePreview' );
706  $this->diff = $request->getCheck( 'wpDiff' );
707 
708  // Remember whether a save was requested, so we can indicate
709  // if we forced preview due to session failure.
710  $this->mTriedSave = !$this->preview;
711 
712  if ( $this->tokenOk( $request ) ) {
713  # Some browsers will not report any submit button
714  # if the user hits enter in the comment box.
715  # The unmarked state will be assumed to be a save,
716  # if the form seems otherwise complete.
717  wfDebug( __METHOD__ . ": Passed token check.\n" );
718  } elseif ( $this->diff ) {
719  # Failed token check, but only requested "Show Changes".
720  wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
721  } else {
722  # Page might be a hack attempt posted from
723  # an external site. Preview instead of saving.
724  wfDebug( __METHOD__ . ": Failed token check; forcing preview\n" );
725  $this->preview = true;
726  }
727  }
728  $this->save = !$this->preview && !$this->diff;
729  if ( !preg_match( '/^\d{14}$/', $this->edittime ) ) {
730  $this->edittime = null;
731  }
732 
733  if ( !preg_match( '/^\d{14}$/', $this->starttime ) ) {
734  $this->starttime = null;
735  }
736 
737  $this->recreate = $request->getCheck( 'wpRecreate' );
738 
739  $this->minoredit = $request->getCheck( 'wpMinoredit' );
740  $this->watchthis = $request->getCheck( 'wpWatchthis' );
741 
742  # Don't force edit summaries when a user is editing their own user or talk page
743  if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK )
744  && $this->mTitle->getText() == $wgUser->getName()
745  ) {
746  $this->allowBlankSummary = true;
747  } else {
748  $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ) || !$wgUser->getOption( 'forceeditsummary' );
749  }
750 
751  $this->autoSumm = $request->getText( 'wpAutoSummary' );
752  } else {
753  # Not a posted form? Start with nothing.
754  wfDebug( __METHOD__ . ": Not a posted form.\n" );
755  $this->textbox1 = '';
756  $this->summary = '';
757  $this->sectiontitle = '';
758  $this->edittime = '';
759  $this->starttime = wfTimestampNow();
760  $this->edit = false;
761  $this->preview = false;
762  $this->save = false;
763  $this->diff = false;
764  $this->minoredit = false;
765  $this->watchthis = $request->getBool( 'watchthis', false ); // Watch may be overridden by request parameters
766  $this->recreate = false;
767 
768  // When creating a new section, we can preload a section title by passing it as the
769  // preloadtitle parameter in the URL (Bug 13100)
770  if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
771  $this->sectiontitle = $request->getVal( 'preloadtitle' );
772  // Once wpSummary isn't being use for setting section titles, we should delete this.
773  $this->summary = $request->getVal( 'preloadtitle' );
774  } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
775  $this->summary = $request->getText( 'summary' );
776  if ( $this->summary !== '' ) {
777  $this->hasPresetSummary = true;
778  }
779  }
780 
781  if ( $request->getVal( 'minor' ) ) {
782  $this->minoredit = true;
783  }
784  }
785 
786  $this->oldid = $request->getInt( 'oldid' );
787 
788  $this->bot = $request->getBool( 'bot', true );
789  $this->nosummary = $request->getBool( 'nosummary' );
790 
791  $this->contentModel = $request->getText( 'model', $this->contentModel ); #may be overridden by revision
792  $this->contentFormat = $request->getText( 'format', $this->contentFormat ); #may be overridden by revision
793 
794  if ( !ContentHandler::getForModelID( $this->contentModel )->isSupportedFormat( $this->contentFormat ) ) {
795  throw new ErrorPageError(
796  'editpage-notsupportedcontentformat-title',
797  'editpage-notsupportedcontentformat-text',
798  array( $this->contentFormat, ContentHandler::getLocalizedName( $this->contentModel ) )
799  );
800  }
801  #TODO: check if the desired model is allowed in this namespace, and if a transition from the page's current model to the new model is allowed
802 
803  $this->live = $request->getCheck( 'live' );
804  $this->editintro = $request->getText( 'editintro',
805  // Custom edit intro for new sections
806  $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
807 
808  // Allow extensions to modify form data
809  wfRunHooks( 'EditPage::importFormData', array( $this, $request ) );
810 
811  wfProfileOut( __METHOD__ );
812  }
813 
822  protected function importContentFormData( &$request ) {
823  return; // Don't do anything, EditPage already extracted wpTextbox1
824  }
825 
831  function initialiseForm() {
832  global $wgUser;
833  $this->edittime = $this->mArticle->getTimestamp();
834 
835  $content = $this->getContentObject( false ); #TODO: track content object?!
836  if ( $content === false ) {
837  return false;
838  }
839  $this->textbox1 = $this->toEditText( $content );
840 
841  // activate checkboxes if user wants them to be always active
842  # Sort out the "watch" checkbox
843  if ( $wgUser->getOption( 'watchdefault' ) ) {
844  # Watch all edits
845  $this->watchthis = true;
846  } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
847  # Watch creations
848  $this->watchthis = true;
849  } elseif ( $wgUser->isWatched( $this->mTitle ) ) {
850  # Already watched
851  $this->watchthis = true;
852  }
853  if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
854  $this->minoredit = true;
855  }
856  if ( $this->textbox1 === false ) {
857  return false;
858  }
859  return true;
860  }
861 
870  function getContent( $def_text = false ) {
871  ContentHandler::deprecated( __METHOD__, '1.21' );
872 
873  if ( $def_text !== null && $def_text !== false && $def_text !== '' ) {
874  $def_content = $this->toEditContent( $def_text );
875  } else {
876  $def_content = false;
877  }
878 
879  $content = $this->getContentObject( $def_content );
880 
881  // Note: EditPage should only be used with text based content anyway.
882  return $this->toEditText( $content );
883  }
884 
892  protected function getContentObject( $def_content = null ) {
893  global $wgOut, $wgRequest, $wgUser, $wgContLang;
894 
895  wfProfileIn( __METHOD__ );
896 
897  $content = false;
898 
899  // For message page not locally set, use the i18n message.
900  // For other non-existent articles, use preload text if any.
901  if ( !$this->mTitle->exists() || $this->section == 'new' ) {
902  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
903  # If this is a system message, get the default text.
904  $msg = $this->mTitle->getDefaultMessageText();
905 
906  $content = $this->toEditContent( $msg );
907  }
908  if ( $content === false ) {
909  # If requested, preload some text.
910  $preload = $wgRequest->getVal( 'preload',
911  // Custom preload text for new sections
912  $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
913  $params = $wgRequest->getArray( 'preloadparams', array() );
914 
915  $content = $this->getPreloadedContent( $preload, $params );
916  }
917  // For existing pages, get text based on "undo" or section parameters.
918  } else {
919  if ( $this->section != '' ) {
920  // Get section edit text (returns $def_text for invalid sections)
921  $orig = $this->getOriginalContent( $wgUser );
922  $content = $orig ? $orig->getSection( $this->section ) : null;
923 
924  if ( !$content ) {
925  $content = $def_content;
926  }
927  } else {
928  $undoafter = $wgRequest->getInt( 'undoafter' );
929  $undo = $wgRequest->getInt( 'undo' );
930 
931  if ( $undo > 0 && $undoafter > 0 ) {
932 
933  $undorev = Revision::newFromId( $undo );
934  $oldrev = Revision::newFromId( $undoafter );
935 
936  # Sanity check, make sure it's the right page,
937  # the revisions exist and they were not deleted.
938  # Otherwise, $content will be left as-is.
939  if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
940  !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
941  !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
942 
943  $content = $this->mArticle->getUndoContent( $undorev, $oldrev );
944 
945  if ( $content === false ) {
946  # Warn the user that something went wrong
947  $undoMsg = 'failure';
948  } else {
949  $oldContent = $this->mArticle->getPage()->getContent( Revision::RAW );
951  $newContent = $content->preSaveTransform( $this->mTitle, $wgUser, $popts );
952 
953  if ( $newContent->equals( $oldContent ) ) {
954  # Tell the user that the undo results in no change,
955  # i.e. the revisions were already undone.
956  $undoMsg = 'nochange';
957  $content = false;
958  } else {
959  # Inform the user of our success and set an automatic edit summary
960  $undoMsg = 'success';
961 
962  # If we just undid one rev, use an autosummary
963  $firstrev = $oldrev->getNext();
964  if ( $firstrev && $firstrev->getId() == $undo ) {
965  $userText = $undorev->getUserText();
966  if ( $userText === '' ) {
967  $undoSummary = wfMessage(
968  'undo-summary-username-hidden',
969  $undo
970  )->inContentLanguage()->text();
971  } else {
972  $undoSummary = wfMessage(
973  'undo-summary',
974  $undo,
975  $userText
976  )->inContentLanguage()->text();
977  }
978  if ( $this->summary === '' ) {
979  $this->summary = $undoSummary;
980  } else {
981  $this->summary = $undoSummary . wfMessage( 'colon-separator' )
982  ->inContentLanguage()->text() . $this->summary;
983  }
984  $this->undidRev = $undo;
985  }
986  $this->formtype = 'diff';
987  }
988  }
989  } else {
990  // Failed basic sanity checks.
991  // Older revisions may have been removed since the link
992  // was created, or we may simply have got bogus input.
993  $undoMsg = 'norev';
994  }
995 
996  // Messages: undo-success, undo-failure, undo-norev, undo-nochange
997  $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
998  $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
999  wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
1000  }
1001 
1002  if ( $content === false ) {
1003  $content = $this->getOriginalContent( $wgUser );
1004  }
1005  }
1006  }
1007 
1008  wfProfileOut( __METHOD__ );
1009  return $content;
1010  }
1011 
1027  private function getOriginalContent( User $user ) {
1028  if ( $this->section == 'new' ) {
1029  return $this->getCurrentContent();
1030  }
1031  $revision = $this->mArticle->getRevisionFetched();
1032  if ( $revision === null ) {
1033  if ( !$this->contentModel ) {
1034  $this->contentModel = $this->getTitle()->getContentModel();
1035  }
1036  $handler = ContentHandler::getForModelID( $this->contentModel );
1037 
1038  return $handler->makeEmptyContent();
1039  }
1040  $content = $revision->getContent( Revision::FOR_THIS_USER, $user );
1041  return $content;
1042  }
1043 
1052  protected function getCurrentContent() {
1053  $rev = $this->mArticle->getRevision();
1054  $content = $rev ? $rev->getContent( Revision::RAW ) : null;
1055 
1056  if ( $content === false || $content === null ) {
1057  if ( !$this->contentModel ) {
1058  $this->contentModel = $this->getTitle()->getContentModel();
1059  }
1060  $handler = ContentHandler::getForModelID( $this->contentModel );
1061 
1062  return $handler->makeEmptyContent();
1063  } else {
1064  # nasty side-effect, but needed for consistency
1065  $this->contentModel = $rev->getContentModel();
1066  $this->contentFormat = $rev->getContentFormat();
1067 
1068  return $content;
1069  }
1070  }
1071 
1078  public function setPreloadedText( $text ) {
1079  ContentHandler::deprecated( __METHOD__, "1.21" );
1080 
1081  $content = $this->toEditContent( $text );
1082 
1083  $this->setPreloadedContent( $content );
1084  }
1085 
1093  public function setPreloadedContent( Content $content ) {
1094  $this->mPreloadContent = $content;
1095  }
1096 
1107  protected function getPreloadedText( $preload ) {
1108  ContentHandler::deprecated( __METHOD__, "1.21" );
1109 
1110  $content = $this->getPreloadedContent( $preload );
1111  $text = $this->toEditText( $content );
1112 
1113  return $text;
1114  }
1115 
1127  protected function getPreloadedContent( $preload, $params = array() ) {
1128  global $wgUser;
1129 
1130  if ( !empty( $this->mPreloadContent ) ) {
1131  return $this->mPreloadContent;
1132  }
1133 
1134  $handler = ContentHandler::getForTitle( $this->getTitle() );
1135 
1136  if ( $preload === '' ) {
1137  return $handler->makeEmptyContent();
1138  }
1139 
1140  $title = Title::newFromText( $preload );
1141  # Check for existence to avoid getting MediaWiki:Noarticletext
1142  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
1143  //TODO: somehow show a warning to the user!
1144  return $handler->makeEmptyContent();
1145  }
1146 
1147  $page = WikiPage::factory( $title );
1148  if ( $page->isRedirect() ) {
1149  $title = $page->getRedirectTarget();
1150  # Same as before
1151  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
1152  //TODO: somehow show a warning to the user!
1153  return $handler->makeEmptyContent();
1154  }
1155  $page = WikiPage::factory( $title );
1156  }
1157 
1158  $parserOptions = ParserOptions::newFromUser( $wgUser );
1159  $content = $page->getContent( Revision::RAW );
1160 
1161  if ( !$content ) {
1162  //TODO: somehow show a warning to the user!
1163  return $handler->makeEmptyContent();
1164  }
1165 
1166  if ( $content->getModel() !== $handler->getModelID() ) {
1167  $converted = $content->convert( $handler->getModelID() );
1168 
1169  if ( !$converted ) {
1170  //TODO: somehow show a warning to the user!
1171  wfDebug( "Attempt to preload incompatible content: "
1172  . "can't convert " . $content->getModel()
1173  . " to " . $handler->getModelID() );
1174 
1175  return $handler->makeEmptyContent();
1176  }
1177 
1178  $content = $converted;
1179  }
1180 
1181  return $content->preloadTransform( $title, $parserOptions, $params );
1182  }
1183 
1191  function tokenOk( &$request ) {
1192  global $wgUser;
1193  $token = $request->getVal( 'wpEditToken' );
1194  $this->mTokenOk = $wgUser->matchEditToken( $token );
1195  $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
1196  return $this->mTokenOk;
1197  }
1198 
1215  protected function setPostEditCookie() {
1216  $revisionId = $this->mArticle->getLatest();
1217  $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1218 
1219  $response = RequestContext::getMain()->getRequest()->response();
1220  $response->setcookie( $postEditKey, '1', time() + self::POST_EDIT_COOKIE_DURATION, array(
1221  'path' => '/',
1222  'httpOnly' => false,
1223  ) );
1224  }
1225 
1231  public function attemptSave() {
1232  global $wgUser;
1233 
1234  $resultDetails = false;
1235  # Allow bots to exempt some edits from bot flagging
1236  $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
1237  $status = $this->internalAttemptSave( $resultDetails, $bot );
1238 
1239  return $this->handleStatus( $status, $resultDetails );
1240  }
1241 
1251  private function handleStatus( Status $status, $resultDetails ) {
1253 
1254  // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
1255  if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
1256  $this->didSave = true;
1257  if ( !$resultDetails['nullEdit'] ) {
1258  $this->setPostEditCookie();
1259  }
1260  }
1261 
1262  switch ( $status->value ) {
1263  case self::AS_HOOK_ERROR_EXPECTED:
1264  case self::AS_CONTENT_TOO_BIG:
1265  case self::AS_ARTICLE_WAS_DELETED:
1266  case self::AS_CONFLICT_DETECTED:
1267  case self::AS_SUMMARY_NEEDED:
1268  case self::AS_TEXTBOX_EMPTY:
1269  case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
1270  case self::AS_END:
1271  return true;
1272 
1273  case self::AS_HOOK_ERROR:
1274  return false;
1275 
1276  case self::AS_PARSE_ERROR:
1277  $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>' );
1278  return true;
1279 
1280  case self::AS_SUCCESS_NEW_ARTICLE:
1281  $query = $resultDetails['redirect'] ? 'redirect=no' : '';
1282  $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
1283  $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
1284  return false;
1285 
1286  case self::AS_SUCCESS_UPDATE:
1287  $extraQuery = '';
1288  $sectionanchor = $resultDetails['sectionanchor'];
1289 
1290  // Give extensions a chance to modify URL query on update
1291  wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this->mArticle, &$sectionanchor, &$extraQuery ) );
1292 
1293  if ( $resultDetails['redirect'] ) {
1294  if ( $extraQuery == '' ) {
1295  $extraQuery = 'redirect=no';
1296  } else {
1297  $extraQuery = 'redirect=no&' . $extraQuery;
1298  }
1299  }
1300  $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1301  return false;
1302 
1303  case self::AS_BLANK_ARTICLE:
1304  $wgOut->redirect( $this->getContextTitle()->getFullURL() );
1305  return false;
1306 
1307  case self::AS_SPAM_ERROR:
1308  $this->spamPageWithContent( $resultDetails['spam'] );
1309  return false;
1310 
1311  case self::AS_BLOCKED_PAGE_FOR_USER:
1312  throw new UserBlockedError( $wgUser->getBlock() );
1313 
1314  case self::AS_IMAGE_REDIRECT_ANON:
1315  case self::AS_IMAGE_REDIRECT_LOGGED:
1316  throw new PermissionsError( 'upload' );
1317 
1318  case self::AS_READ_ONLY_PAGE_ANON:
1319  case self::AS_READ_ONLY_PAGE_LOGGED:
1320  throw new PermissionsError( 'edit' );
1321 
1322  case self::AS_READ_ONLY_PAGE:
1323  throw new ReadOnlyError;
1324 
1325  case self::AS_RATE_LIMITED:
1326  throw new ThrottledError();
1327 
1328  case self::AS_NO_CREATE_PERMISSION:
1329  $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
1330  throw new PermissionsError( $permission );
1331 
1332  default:
1333  // We don't recognize $status->value. The only way that can happen
1334  // is if an extension hook aborted from inside ArticleSave.
1335  // Render the status object into $this->hookError
1336  // FIXME this sucks, we should just use the Status object throughout
1337  $this->hookError = '<div class="error">' . $status->getWikitext() .
1338  '</div>';
1339  return true;
1340  }
1341  }
1342 
1352  protected function runPostMergeFilters( Content $content, Status $status, User $user ) {
1353  // Run old style post-section-merge edit filter
1354  if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged',
1355  array( $this, $content, &$this->hookError, $this->summary ) ) ) {
1356 
1357  # Error messages etc. could be handled within the hook...
1358  $status->fatal( 'hookaborted' );
1359  $status->value = self::AS_HOOK_ERROR;
1360  return false;
1361  } elseif ( $this->hookError != '' ) {
1362  # ...or the hook could be expecting us to produce an error
1363  $status->fatal( 'hookaborted' );
1364  $status->value = self::AS_HOOK_ERROR_EXPECTED;
1365  return false;
1366  }
1367 
1368  // Run new style post-section-merge edit filter
1369  if ( !wfRunHooks( 'EditFilterMergedContent',
1370  array( $this->mArticle->getContext(), $content, $status, $this->summary,
1371  $user, $this->minoredit ) ) ) {
1372 
1373  # Error messages etc. could be handled within the hook...
1374  // XXX: $status->value may already be something informative...
1375  $this->hookError = $status->getWikiText();
1376  $status->fatal( 'hookaborted' );
1377  $status->value = self::AS_HOOK_ERROR;
1378  return false;
1379  } elseif ( !$status->isOK() ) {
1380  # ...or the hook could be expecting us to produce an error
1381  // FIXME this sucks, we should just use the Status object throughout
1382  $this->hookError = $status->getWikiText();
1383  $status->fatal( 'hookaborted' );
1384  $status->value = self::AS_HOOK_ERROR_EXPECTED;
1385  return false;
1386  }
1387 
1388  return true;
1389  }
1390 
1407  function internalAttemptSave( &$result, $bot = false ) {
1408  global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize;
1409 
1410  $status = Status::newGood();
1411 
1412  wfProfileIn( __METHOD__ );
1413  wfProfileIn( __METHOD__ . '-checks' );
1414 
1415  if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
1416  wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
1417  $status->fatal( 'hookaborted' );
1418  $status->value = self::AS_HOOK_ERROR;
1419  wfProfileOut( __METHOD__ . '-checks' );
1420  wfProfileOut( __METHOD__ );
1421  return $status;
1422  }
1423 
1424  $spam = $wgRequest->getText( 'wpAntispam' );
1425  if ( $spam !== '' ) {
1426  wfDebugLog(
1427  'SimpleAntiSpam',
1428  $wgUser->getName() .
1429  ' editing "' .
1430  $this->mTitle->getPrefixedText() .
1431  '" submitted bogus field "' .
1432  $spam .
1433  '"'
1434  );
1435  $status->fatal( 'spamprotectionmatch', false );
1436  $status->value = self::AS_SPAM_ERROR;
1437  wfProfileOut( __METHOD__ . '-checks' );
1438  wfProfileOut( __METHOD__ );
1439  return $status;
1440  }
1441 
1442  try {
1443  # Construct Content object
1444  $textbox_content = $this->toEditContent( $this->textbox1 );
1445  } catch ( MWContentSerializationException $ex ) {
1446  $status->fatal( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
1447  $status->value = self::AS_PARSE_ERROR;
1448  wfProfileOut( __METHOD__ . '-checks' );
1449  wfProfileOut( __METHOD__ );
1450  return $status;
1451  }
1452 
1453  # Check image redirect
1454  if ( $this->mTitle->getNamespace() == NS_FILE &&
1455  $textbox_content->isRedirect() &&
1456  !$wgUser->isAllowed( 'upload' ) ) {
1457  $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
1458  $status->setResult( false, $code );
1459 
1460  wfProfileOut( __METHOD__ . '-checks' );
1461  wfProfileOut( __METHOD__ );
1462 
1463  return $status;
1464  }
1465 
1466  # Check for spam
1467  $match = self::matchSummarySpamRegex( $this->summary );
1468  if ( $match === false && $this->section == 'new' ) {
1469  # $wgSpamRegex is enforced on this new heading/summary because, unlike
1470  # regular summaries, it is added to the actual wikitext.
1471  if ( $this->sectiontitle !== '' ) {
1472  # This branch is taken when the API is used with the 'sectiontitle' parameter.
1473  $match = self::matchSpamRegex( $this->sectiontitle );
1474  } else {
1475  # This branch is taken when the "Add Topic" user interface is used, or the API
1476  # is used with the 'summary' parameter.
1477  $match = self::matchSpamRegex( $this->summary );
1478  }
1479  }
1480  if ( $match === false ) {
1481  $match = self::matchSpamRegex( $this->textbox1 );
1482  }
1483  if ( $match !== false ) {
1484  $result['spam'] = $match;
1485  $ip = $wgRequest->getIP();
1486  $pdbk = $this->mTitle->getPrefixedDBkey();
1487  $match = str_replace( "\n", '', $match );
1488  wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
1489  $status->fatal( 'spamprotectionmatch', $match );
1490  $status->value = self::AS_SPAM_ERROR;
1491  wfProfileOut( __METHOD__ . '-checks' );
1492  wfProfileOut( __METHOD__ );
1493  return $status;
1494  }
1495  if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) {
1496  # Error messages etc. could be handled within the hook...
1497  $status->fatal( 'hookaborted' );
1498  $status->value = self::AS_HOOK_ERROR;
1499  wfProfileOut( __METHOD__ . '-checks' );
1500  wfProfileOut( __METHOD__ );
1501  return $status;
1502  } elseif ( $this->hookError != '' ) {
1503  # ...or the hook could be expecting us to produce an error
1504  $status->fatal( 'hookaborted' );
1505  $status->value = self::AS_HOOK_ERROR_EXPECTED;
1506  wfProfileOut( __METHOD__ . '-checks' );
1507  wfProfileOut( __METHOD__ );
1508  return $status;
1509  }
1510 
1511  if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) {
1512  // Auto-block user's IP if the account was "hard" blocked
1513  $wgUser->spreadAnyEditBlock();
1514  # Check block state against master, thus 'false'.
1515  $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
1516  wfProfileOut( __METHOD__ . '-checks' );
1517  wfProfileOut( __METHOD__ );
1518  return $status;
1519  }
1520 
1521  $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
1522  if ( $this->kblength > $wgMaxArticleSize ) {
1523  // Error will be displayed by showEditForm()
1524  $this->tooBig = true;
1525  $status->setResult( false, self::AS_CONTENT_TOO_BIG );
1526  wfProfileOut( __METHOD__ . '-checks' );
1527  wfProfileOut( __METHOD__ );
1528  return $status;
1529  }
1530 
1531  if ( !$wgUser->isAllowed( 'edit' ) ) {
1532  if ( $wgUser->isAnon() ) {
1533  $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
1534  wfProfileOut( __METHOD__ . '-checks' );
1535  wfProfileOut( __METHOD__ );
1536  return $status;
1537  } else {
1538  $status->fatal( 'readonlytext' );
1539  $status->value = self::AS_READ_ONLY_PAGE_LOGGED;
1540  wfProfileOut( __METHOD__ . '-checks' );
1541  wfProfileOut( __METHOD__ );
1542  return $status;
1543  }
1544  }
1545 
1546  if ( wfReadOnly() ) {
1547  $status->fatal( 'readonlytext' );
1548  $status->value = self::AS_READ_ONLY_PAGE;
1549  wfProfileOut( __METHOD__ . '-checks' );
1550  wfProfileOut( __METHOD__ );
1551  return $status;
1552  }
1553  if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 ) ) {
1554  $status->fatal( 'actionthrottledtext' );
1555  $status->value = self::AS_RATE_LIMITED;
1556  wfProfileOut( __METHOD__ . '-checks' );
1557  wfProfileOut( __METHOD__ );
1558  return $status;
1559  }
1560 
1561  # If the article has been deleted while editing, don't save it without
1562  # confirmation
1563  if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
1564  $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
1565  wfProfileOut( __METHOD__ . '-checks' );
1566  wfProfileOut( __METHOD__ );
1567  return $status;
1568  }
1569 
1570  wfProfileOut( __METHOD__ . '-checks' );
1571 
1572  # Load the page data from the master. If anything changes in the meantime,
1573  # we detect it by using page_latest like a token in a 1 try compare-and-swap.
1574  $this->mArticle->loadPageData( 'fromdbmaster' );
1575  $new = !$this->mArticle->exists();
1576 
1577  if ( $new ) {
1578  // Late check for create permission, just in case *PARANOIA*
1579  if ( !$this->mTitle->userCan( 'create', $wgUser ) ) {
1580  $status->fatal( 'nocreatetext' );
1581  $status->value = self::AS_NO_CREATE_PERMISSION;
1582  wfDebug( __METHOD__ . ": no create permission\n" );
1583  wfProfileOut( __METHOD__ );
1584  return $status;
1585  }
1586 
1587  // Don't save a new page if it's blank or if it's a MediaWiki:
1588  // message with content equivalent to default (allow empty pages
1589  // in this case to disable messages, see bug 50124)
1590  $defaultMessageText = $this->mTitle->getDefaultMessageText();
1591  if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false ) {
1592  $defaultText = $defaultMessageText;
1593  } else {
1594  $defaultText = '';
1595  }
1596 
1597  if ( $this->textbox1 === $defaultText ) {
1598  $status->setResult( false, self::AS_BLANK_ARTICLE );
1599  wfProfileOut( __METHOD__ );
1600  return $status;
1601  }
1602 
1603  if ( !$this->runPostMergeFilters( $textbox_content, $status, $wgUser ) ) {
1604  wfProfileOut( __METHOD__ );
1605  return $status;
1606  }
1607 
1608  $content = $textbox_content;
1609 
1610  $result['sectionanchor'] = '';
1611  if ( $this->section == 'new' ) {
1612  if ( $this->sectiontitle !== '' ) {
1613  // Insert the section title above the content.
1614  $content = $content->addSectionHeader( $this->sectiontitle );
1615 
1616  // Jump to the new section
1617  $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
1618 
1619  // If no edit summary was specified, create one automatically from the section
1620  // title and have it link to the new section. Otherwise, respect the summary as
1621  // passed.
1622  if ( $this->summary === '' ) {
1623  $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
1624  $this->summary = wfMessage( 'newsectionsummary' )
1625  ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1626  }
1627  } elseif ( $this->summary !== '' ) {
1628  // Insert the section title above the content.
1629  $content = $content->addSectionHeader( $this->summary );
1630 
1631  // Jump to the new section
1632  $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
1633 
1634  // Create a link to the new section from the edit summary.
1635  $cleanSummary = $wgParser->stripSectionName( $this->summary );
1636  $this->summary = wfMessage( 'newsectionsummary' )
1637  ->rawParams( $cleanSummary )->inContentLanguage()->text();
1638  }
1639  }
1640 
1641  $status->value = self::AS_SUCCESS_NEW_ARTICLE;
1642 
1643  } else { # not $new
1644 
1645  # Article exists. Check for edit conflict.
1646 
1647  $this->mArticle->clear(); # Force reload of dates, etc.
1648  $timestamp = $this->mArticle->getTimestamp();
1649 
1650  wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
1651 
1652  if ( $timestamp != $this->edittime ) {
1653  $this->isConflict = true;
1654  if ( $this->section == 'new' ) {
1655  if ( $this->mArticle->getUserText() == $wgUser->getName() &&
1656  $this->mArticle->getComment() == $this->summary ) {
1657  // Probably a duplicate submission of a new comment.
1658  // This can happen when squid resends a request after
1659  // a timeout but the first one actually went through.
1660  wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
1661  } else {
1662  // New comment; suppress conflict.
1663  $this->isConflict = false;
1664  wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
1665  }
1666  } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER, $this->mTitle->getArticleID(),
1667  $wgUser->getId(), $this->edittime ) ) {
1668  # Suppress edit conflict with self, except for section edits where merging is required.
1669  wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
1670  $this->isConflict = false;
1671  }
1672  }
1673 
1674  // If sectiontitle is set, use it, otherwise use the summary as the section title.
1675  if ( $this->sectiontitle !== '' ) {
1676  $sectionTitle = $this->sectiontitle;
1677  } else {
1678  $sectionTitle = $this->summary;
1679  }
1680 
1681  $content = null;
1682 
1683  if ( $this->isConflict ) {
1684  wfDebug( __METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
1685  . " (article time '{$timestamp}')\n" );
1686 
1687  $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime );
1688  } else {
1689  wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
1690  $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
1691  }
1692 
1693  if ( is_null( $content ) ) {
1694  wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
1695  $this->isConflict = true;
1696  $content = $textbox_content; // do not try to merge here!
1697  } elseif ( $this->isConflict ) {
1698  # Attempt merge
1699  if ( $this->mergeChangesIntoContent( $content ) ) {
1700  // Successful merge! Maybe we should tell the user the good news?
1701  $this->isConflict = false;
1702  wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
1703  } else {
1704  $this->section = '';
1705  $this->textbox1 = ContentHandler::getContentText( $content );
1706  wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
1707  }
1708  }
1709 
1710  if ( $this->isConflict ) {
1711  $status->setResult( false, self::AS_CONFLICT_DETECTED );
1712  wfProfileOut( __METHOD__ );
1713  return $status;
1714  }
1715 
1716  if ( !$this->runPostMergeFilters( $content, $status, $wgUser ) ) {
1717  wfProfileOut( __METHOD__ );
1718  return $status;
1719  }
1720 
1721  if ( $this->section == 'new' ) {
1722  // Handle the user preference to force summaries here
1723  if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) {
1724  $this->missingSummary = true;
1725  $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
1726  $status->value = self::AS_SUMMARY_NEEDED;
1727  wfProfileOut( __METHOD__ );
1728  return $status;
1729  }
1730 
1731  // Do not allow the user to post an empty comment
1732  if ( $this->textbox1 == '' ) {
1733  $this->missingComment = true;
1734  $status->fatal( 'missingcommenttext' );
1735  $status->value = self::AS_TEXTBOX_EMPTY;
1736  wfProfileOut( __METHOD__ );
1737  return $status;
1738  }
1739  } elseif ( !$this->allowBlankSummary
1740  && !$content->equals( $this->getOriginalContent( $wgUser ) )
1741  && !$content->isRedirect()
1742  && md5( $this->summary ) == $this->autoSumm
1743  ) {
1744  $this->missingSummary = true;
1745  $status->fatal( 'missingsummary' );
1746  $status->value = self::AS_SUMMARY_NEEDED;
1747  wfProfileOut( __METHOD__ );
1748  return $status;
1749  }
1750 
1751  # All's well
1752  wfProfileIn( __METHOD__ . '-sectionanchor' );
1753  $sectionanchor = '';
1754  if ( $this->section == 'new' ) {
1755  if ( $this->sectiontitle !== '' ) {
1756  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
1757  // If no edit summary was specified, create one automatically from the section
1758  // title and have it link to the new section. Otherwise, respect the summary as
1759  // passed.
1760  if ( $this->summary === '' ) {
1761  $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
1762  $this->summary = wfMessage( 'newsectionsummary' )
1763  ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1764  }
1765  } elseif ( $this->summary !== '' ) {
1766  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
1767  # This is a new section, so create a link to the new section
1768  # in the revision summary.
1769  $cleanSummary = $wgParser->stripSectionName( $this->summary );
1770  $this->summary = wfMessage( 'newsectionsummary' )
1771  ->rawParams( $cleanSummary )->inContentLanguage()->text();
1772  }
1773  } elseif ( $this->section != '' ) {
1774  # Try to get a section anchor from the section source, redirect to edited section if header found
1775  # XXX: might be better to integrate this into Article::replaceSection
1776  # for duplicate heading checking and maybe parsing
1777  $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
1778  # we can't deal with anchors, includes, html etc in the header for now,
1779  # headline would need to be parsed to improve this
1780  if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
1781  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
1782  }
1783  }
1784  $result['sectionanchor'] = $sectionanchor;
1785  wfProfileOut( __METHOD__ . '-sectionanchor' );
1786 
1787  // Save errors may fall down to the edit form, but we've now
1788  // merged the section into full text. Clear the section field
1789  // so that later submission of conflict forms won't try to
1790  // replace that into a duplicated mess.
1791  $this->textbox1 = $this->toEditText( $content );
1792  $this->section = '';
1793 
1794  $status->value = self::AS_SUCCESS_UPDATE;
1795  }
1796 
1797  // Check for length errors again now that the section is merged in
1798  $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
1799  if ( $this->kblength > $wgMaxArticleSize ) {
1800  $this->tooBig = true;
1801  $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
1802  wfProfileOut( __METHOD__ );
1803  return $status;
1804  }
1805 
1807  ( $new ? EDIT_NEW : EDIT_UPDATE ) |
1808  ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
1809  ( $bot ? EDIT_FORCE_BOT : 0 );
1810 
1811  $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary, $flags,
1812  false, null, $this->contentFormat );
1813 
1814  if ( !$doEditStatus->isOK() ) {
1815  // Failure from doEdit()
1816  // Show the edit conflict page for certain recognized errors from doEdit(),
1817  // but don't show it for errors from extension hooks
1818  $errors = $doEditStatus->getErrorsArray();
1819  if ( in_array( $errors[0][0],
1820  array( 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ) )
1821  ) {
1822  $this->isConflict = true;
1823  // Destroys data doEdit() put in $status->value but who cares
1824  $doEditStatus->value = self::AS_END;
1825  }
1826  wfProfileOut( __METHOD__ );
1827  return $doEditStatus;
1828  }
1829 
1830  $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
1831  if ( $result['nullEdit'] ) {
1832  // We don't know if it was a null edit until now, so increment here
1833  $wgUser->pingLimiter( 'linkpurge' );
1834  }
1835  $result['redirect'] = $content->isRedirect();
1836  $this->updateWatchlist();
1837  wfProfileOut( __METHOD__ );
1838  return $status;
1839  }
1840 
1844  protected function updateWatchlist() {
1845  global $wgUser;
1846 
1847  if ( $wgUser->isLoggedIn()
1848  && $this->watchthis != $wgUser->isWatched( $this->mTitle, WatchedItem::IGNORE_USER_RIGHTS )
1849  ) {
1850  $fname = __METHOD__;
1851  $title = $this->mTitle;
1852  $watch = $this->watchthis;
1853 
1854  // Do this in its own transaction to reduce contention...
1855  $dbw = wfGetDB( DB_MASTER );
1856  $dbw->onTransactionIdle( function() use ( $dbw, $title, $watch, $wgUser, $fname ) {
1857  $dbw->begin( $fname );
1859  $dbw->commit( $fname );
1860  } );
1861  }
1862  }
1863 
1872  function mergeChangesInto( &$editText ) {
1873  ContentHandler::deprecated( __METHOD__, "1.21" );
1874 
1875  $editContent = $this->toEditContent( $editText );
1876 
1877  $ok = $this->mergeChangesIntoContent( $editContent );
1878 
1879  if ( $ok ) {
1880  $editText = $this->toEditText( $editContent );
1881  return true;
1882  }
1883  return false;
1884  }
1885 
1897  private function mergeChangesIntoContent( &$editContent ) {
1898  wfProfileIn( __METHOD__ );
1899 
1900  $db = wfGetDB( DB_MASTER );
1901 
1902  // This is the revision the editor started from
1903  $baseRevision = $this->getBaseRevision();
1904  $baseContent = $baseRevision ? $baseRevision->getContent() : null;
1905 
1906  if ( is_null( $baseContent ) ) {
1907  wfProfileOut( __METHOD__ );
1908  return false;
1909  }
1910 
1911  // The current state, we want to merge updates into it
1912  $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
1913  $currentContent = $currentRevision ? $currentRevision->getContent() : null;
1914 
1915  if ( is_null( $currentContent ) ) {
1916  wfProfileOut( __METHOD__ );
1917  return false;
1918  }
1919 
1920  $handler = ContentHandler::getForModelID( $baseContent->getModel() );
1921 
1922  $result = $handler->merge3( $baseContent, $editContent, $currentContent );
1923 
1924  if ( $result ) {
1925  $editContent = $result;
1926  wfProfileOut( __METHOD__ );
1927  return true;
1928  }
1929 
1930  wfProfileOut( __METHOD__ );
1931  return false;
1932  }
1933 
1937  function getBaseRevision() {
1938  if ( !$this->mBaseRevision ) {
1939  $db = wfGetDB( DB_MASTER );
1940  $this->mBaseRevision = Revision::loadFromTimestamp(
1941  $db, $this->mTitle, $this->edittime );
1942  }
1943  return $this->mBaseRevision;
1944  }
1945 
1953  public static function matchSpamRegex( $text ) {
1954  global $wgSpamRegex;
1955  // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
1956  $regexes = (array)$wgSpamRegex;
1957  return self::matchSpamRegexInternal( $text, $regexes );
1958  }
1959 
1967  public static function matchSummarySpamRegex( $text ) {
1968  global $wgSummarySpamRegex;
1969  $regexes = (array)$wgSummarySpamRegex;
1970  return self::matchSpamRegexInternal( $text, $regexes );
1971  }
1972 
1978  protected static function matchSpamRegexInternal( $text, $regexes ) {
1979  foreach ( $regexes as $regex ) {
1980  $matches = array();
1981  if ( preg_match( $regex, $text, $matches ) ) {
1982  return $matches[0];
1983  }
1984  }
1985  return false;
1986  }
1987 
1988  function setHeaders() {
1990 
1991  $wgOut->addModules( 'mediawiki.action.edit' );
1992  $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
1993 
1994  if ( $wgUser->getOption( 'uselivepreview', false ) ) {
1995  $wgOut->addModules( 'mediawiki.action.edit.preview' );
1996  }
1997 
1998  if ( $wgUser->getOption( 'useeditwarning', false ) ) {
1999  $wgOut->addModules( 'mediawiki.action.edit.editWarning' );
2000  }
2001 
2002  $wgOut->setRobotPolicy( 'noindex,nofollow' );
2003 
2004  # Enabled article-related sidebar, toplinks, etc.
2005  $wgOut->setArticleRelated( true );
2006 
2007  $contextTitle = $this->getContextTitle();
2008  if ( $this->isConflict ) {
2009  $msg = 'editconflict';
2010  } elseif ( $contextTitle->exists() && $this->section != '' ) {
2011  $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
2012  } else {
2013  $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() == NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !== false ) ?
2014  'editing' : 'creating';
2015  }
2016  # Use the title defined by DISPLAYTITLE magic word when present
2017  $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
2018  if ( $displayTitle === false ) {
2019  $displayTitle = $contextTitle->getPrefixedText();
2020  }
2021  $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) );
2022  }
2023 
2027  protected function showIntro() {
2029  if ( $this->suppressIntro ) {
2030  return;
2031  }
2032 
2033  $namespace = $this->mTitle->getNamespace();
2034 
2035  if ( $namespace == NS_MEDIAWIKI ) {
2036  # Show a warning if editing an interface message
2037  $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
2038  } elseif ( $namespace == NS_FILE ) {
2039  # Show a hint to shared repo
2040  $file = wfFindFile( $this->mTitle );
2041  if ( $file && !$file->isLocal() ) {
2042  $descUrl = $file->getDescriptionUrl();
2043  # there must be a description url to show a hint to shared repo
2044  if ( $descUrl ) {
2045  if ( !$this->mTitle->exists() ) {
2046  $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", array(
2047  'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
2048  ) );
2049  } else {
2050  $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", array(
2051  'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
2052  ) );
2053  }
2054  }
2055  }
2056  }
2057 
2058  # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2059  # Show log extract when the user is currently blocked
2060  if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
2061  $parts = explode( '/', $this->mTitle->getText(), 2 );
2062  $username = $parts[0];
2063  $user = User::newFromName( $username, false /* allow IP users*/ );
2064  $ip = User::isIP( $username );
2065  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
2066  $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2067  array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
2068  } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
2070  $wgOut,
2071  'block',
2072  $user->getUserPage(),
2073  '',
2074  array(
2075  'lim' => 1,
2076  'showIfEmpty' => false,
2077  'msgKey' => array(
2078  'blocked-notice-logextract',
2079  $user->getName() # Support GENDER in notice
2080  )
2081  )
2082  );
2083  }
2084  }
2085  # Try to add a custom edit intro, or use the standard one if this is not possible.
2086  if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
2088  wfMessage( 'helppage' )->inContentLanguage()->text()
2089  ) );
2090  if ( $wgUser->isLoggedIn() ) {
2091  $wgOut->wrapWikiMsg(
2092  // Suppress the external link icon, consider the help url an internal one
2093  "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2094  array(
2095  'newarticletext',
2096  $helpLink
2097  )
2098  );
2099  } else {
2100  $wgOut->wrapWikiMsg(
2101  // Suppress the external link icon, consider the help url an internal one
2102  "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2103  array(
2104  'newarticletextanon',
2105  $helpLink
2106  )
2107  );
2108  }
2109  }
2110  # Give a notice if the user is editing a deleted/moved page...
2111  if ( !$this->mTitle->exists() ) {
2112  LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle,
2113  '',
2114  array(
2115  'lim' => 10,
2116  'conds' => array( "log_action != 'revision'" ),
2117  'showIfEmpty' => false,
2118  'msgKey' => array( 'recreate-moveddeleted-warn' )
2119  )
2120  );
2121  }
2122  }
2123 
2129  protected function showCustomIntro() {
2130  if ( $this->editintro ) {
2131  $title = Title::newFromText( $this->editintro );
2132  if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
2133  global $wgOut;
2134  // Added using template syntax, to take <noinclude>'s into account.
2135  $wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
2136  return true;
2137  }
2138  }
2139  return false;
2140  }
2141 
2158  protected function toEditText( $content ) {
2159  if ( $content === null || $content === false ) {
2160  return $content;
2161  }
2162 
2163  if ( is_string( $content ) ) {
2164  return $content;
2165  }
2166 
2167  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2168  throw new MWException( 'This content model is not supported: '
2169  . ContentHandler::getLocalizedName( $content->getModel() ) );
2170  }
2171 
2172  return $content->serialize( $this->contentFormat );
2173  }
2174 
2189  protected function toEditContent( $text ) {
2190  if ( $text === false || $text === null ) {
2191  return $text;
2192  }
2193 
2194  $content = ContentHandler::makeContent( $text, $this->getTitle(),
2195  $this->contentModel, $this->contentFormat );
2196 
2197  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2198  throw new MWException( 'This content model is not supported: '
2199  . ContentHandler::getLocalizedName( $content->getModel() ) );
2200  }
2201 
2202  return $content;
2203  }
2204 
2210  function showEditForm( $formCallback = null ) {
2212 
2213  wfProfileIn( __METHOD__ );
2214 
2215  # need to parse the preview early so that we know which templates are used,
2216  # otherwise users with "show preview after edit box" will get a blank list
2217  # we parse this near the beginning so that setHeaders can do the title
2218  # setting work instead of leaving it in getPreviewText
2219  $previewOutput = '';
2220  if ( $this->formtype == 'preview' ) {
2221  $previewOutput = $this->getPreviewText();
2222  }
2223 
2224  wfRunHooks( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) );
2225 
2226  $this->setHeaders();
2227 
2228  if ( $this->showHeader() === false ) {
2229  wfProfileOut( __METHOD__ );
2230  return;
2231  }
2232 
2233  $wgOut->addHTML( $this->editFormPageTop );
2234 
2235  if ( $wgUser->getOption( 'previewontop' ) ) {
2236  $this->displayPreviewArea( $previewOutput, true );
2237  }
2238 
2239  $wgOut->addHTML( $this->editFormTextTop );
2240 
2241  $showToolbar = true;
2242  if ( $this->wasDeletedSinceLastEdit() ) {
2243  if ( $this->formtype == 'save' ) {
2244  // Hide the toolbar and edit area, user can click preview to get it back
2245  // Add an confirmation checkbox and explanation.
2246  $showToolbar = false;
2247  } else {
2248  $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2249  'deletedwhileediting' );
2250  }
2251  }
2252 
2253  // @todo add EditForm plugin interface and use it here!
2254  // search for textarea1 and textares2, and allow EditForm to override all uses.
2255  $wgOut->addHTML( Html::openElement( 'form', array( 'id' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID,
2256  'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
2257  'enctype' => 'multipart/form-data' ) ) );
2258 
2259  if ( is_callable( $formCallback ) ) {
2260  call_user_func_array( $formCallback, array( &$wgOut ) );
2261  }
2262 
2263  // Add an empty field to trip up spambots
2264  $wgOut->addHTML(
2265  Xml::openElement( 'div', array( 'id' => 'antispam-container', 'style' => 'display: none;' ) )
2266  . Html::rawElement( 'label', array( 'for' => 'wpAntiSpam' ), wfMessage( 'simpleantispam-label' )->parse() )
2267  . Xml::element( 'input', array( 'type' => 'text', 'name' => 'wpAntispam', 'id' => 'wpAntispam', 'value' => '' ) )
2268  . Xml::closeElement( 'div' )
2269  );
2270 
2271  wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
2272 
2273  // Put these up at the top to ensure they aren't lost on early form submission
2274  $this->showFormBeforeText();
2275 
2276  if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
2277  $username = $this->lastDelete->user_name;
2278  $comment = $this->lastDelete->log_comment;
2279 
2280  // It is better to not parse the comment at all than to have templates expanded in the middle
2281  // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
2282  $key = $comment === ''
2283  ? 'confirmrecreate-noreason'
2284  : 'confirmrecreate';
2285  $wgOut->addHTML(
2286  '<div class="mw-confirm-recreate">' .
2287  wfMessage( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
2288  Xml::checkLabel( wfMessage( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
2289  array( 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
2290  ) .
2291  '</div>'
2292  );
2293  }
2294 
2295  # When the summary is hidden, also hide them on preview/show changes
2296  if ( $this->nosummary ) {
2297  $wgOut->addHTML( Html::hidden( 'nosummary', true ) );
2298  }
2299 
2300  # If a blank edit summary was previously provided, and the appropriate
2301  # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2302  # user being bounced back more than once in the event that a summary
2303  # is not required.
2304  #####
2305  # For a bit more sophisticated detection of blank summaries, hash the
2306  # automatic one and pass that in the hidden field wpAutoSummary.
2307  if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
2308  $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
2309  }
2310 
2311  if ( $this->undidRev ) {
2312  $wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
2313  }
2314 
2315  if ( $this->hasPresetSummary ) {
2316  // If a summary has been preset using &summary= we don't want to prompt for
2317  // a different summary. Only prompt for a summary if the summary is blanked.
2318  // (Bug 17416)
2319  $this->autoSumm = md5( '' );
2320  }
2321 
2322  $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
2323  $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
2324 
2325  $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
2326 
2327  $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
2328  $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
2329 
2330  if ( $this->section == 'new' ) {
2331  $this->showSummaryInput( true, $this->summary );
2332  $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
2333  }
2334 
2335  $wgOut->addHTML( $this->editFormTextBeforeContent );
2336 
2337  if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
2338  $wgOut->addHTML( EditPage::getEditToolbar() );
2339  }
2340 
2341  if ( $this->isConflict ) {
2342  // In an edit conflict bypass the overridable content form method
2343  // and fallback to the raw wpTextbox1 since editconflicts can't be
2344  // resolved between page source edits and custom ui edits using the
2345  // custom edit ui.
2346  $this->textbox2 = $this->textbox1;
2347 
2348  $content = $this->getCurrentContent();
2349  $this->textbox1 = $this->toEditText( $content );
2350 
2351  $this->showTextbox1();
2352  } else {
2353  $this->showContentForm();
2354  }
2355 
2356  $wgOut->addHTML( $this->editFormTextAfterContent );
2357 
2358  $this->showStandardInputs();
2359 
2360  $this->showFormAfterText();
2361 
2362  $this->showTosSummary();
2363 
2364  $this->showEditTools();
2365 
2366  $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
2367 
2368  $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
2369  Linker::formatTemplates( $this->getTemplates(), $this->preview, $this->section != '' ) ) );
2370 
2371  $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'hiddencats' ),
2372  Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
2373 
2374  $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'limitreport' ),
2375  self::getPreviewLimitReport( $this->mParserOutput ) ) );
2376 
2377  $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
2378 
2379  if ( $this->isConflict ) {
2380  try {
2381  $this->showConflict();
2382  } catch ( MWContentSerializationException $ex ) {
2383  // this can't really happen, but be nice if it does.
2384  $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
2385  $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
2386  }
2387  }
2388 
2389  $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
2390 
2391  if ( !$wgUser->getOption( 'previewontop' ) ) {
2392  $this->displayPreviewArea( $previewOutput, false );
2393  }
2394 
2395  wfProfileOut( __METHOD__ );
2396  }
2397 
2404  public static function extractSectionTitle( $text ) {
2405  preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
2406  if ( !empty( $matches[2] ) ) {
2407  global $wgParser;
2408  return $wgParser->stripSectionName( trim( $matches[2] ) );
2409  } else {
2410  return false;
2411  }
2412  }
2413 
2414  protected function showHeader() {
2415  global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang;
2416 
2417  if ( $this->mTitle->isTalkPage() ) {
2418  $wgOut->addWikiMsg( 'talkpagetext' );
2419  }
2420 
2421  // Add edit notices
2422  $wgOut->addHTML( implode( "\n", $this->mTitle->getEditNotices( $this->oldid ) ) );
2423 
2424  if ( $this->isConflict ) {
2425  $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
2426  $this->edittime = $this->mArticle->getTimestamp();
2427  } else {
2428  if ( $this->section != '' && !$this->isSectionEditSupported() ) {
2429  // We use $this->section to much before this and getVal('wgSection') directly in other places
2430  // at this point we can't reset $this->section to '' to fallback to non-section editing.
2431  // Someone is welcome to try refactoring though
2432  $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
2433  return false;
2434  }
2435 
2436  if ( $this->section != '' && $this->section != 'new' ) {
2437  if ( !$this->summary && !$this->preview && !$this->diff ) {
2438  $sectionTitle = self::extractSectionTitle( $this->textbox1 ); //FIXME: use Content object
2439  if ( $sectionTitle !== false ) {
2440  $this->summary = "/* $sectionTitle */ ";
2441  }
2442  }
2443  }
2444 
2445  if ( $this->missingComment ) {
2446  $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
2447  }
2448 
2449  if ( $this->missingSummary && $this->section != 'new' ) {
2450  $wgOut->wrapWikiMsg( "<div id='mw-missingsummary'>\n$1\n</div>", 'missingsummary' );
2451  }
2452 
2453  if ( $this->missingSummary && $this->section == 'new' ) {
2454  $wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1\n</div>", 'missingcommentheader' );
2455  }
2456 
2457  if ( $this->hookError !== '' ) {
2458  $wgOut->addWikiText( $this->hookError );
2459  }
2460 
2461  if ( !$this->checkUnicodeCompliantBrowser() ) {
2462  $wgOut->addWikiMsg( 'nonunicodebrowser' );
2463  }
2464 
2465  if ( $this->section != 'new' ) {
2466  $revision = $this->mArticle->getRevisionFetched();
2467  if ( $revision ) {
2468  // Let sysop know that this will make private content public if saved
2469 
2470  if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) {
2471  $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
2472  } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
2473  $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
2474  }
2475 
2476  if ( !$revision->isCurrent() ) {
2477  $this->mArticle->setOldSubtitle( $revision->getId() );
2478  $wgOut->addWikiMsg( 'editingold' );
2479  }
2480  } elseif ( $this->mTitle->exists() ) {
2481  // Something went wrong
2482 
2483  $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
2484  array( 'missing-revision', $this->oldid ) );
2485  }
2486  }
2487  }
2488 
2489  if ( wfReadOnly() ) {
2490  $wgOut->wrapWikiMsg( "<div id=\"mw-read-only-warning\">\n$1\n</div>", array( 'readonlywarning', wfReadOnlyReason() ) );
2491  } elseif ( $wgUser->isAnon() ) {
2492  if ( $this->formtype != 'preview' ) {
2493  $wgOut->wrapWikiMsg( "<div id=\"mw-anon-edit-warning\">\n$1</div>", 'anoneditwarning' );
2494  } else {
2495  $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>", 'anonpreviewwarning' );
2496  }
2497  } else {
2498  if ( $this->isCssJsSubpage ) {
2499  # Check the skin exists
2500  if ( $this->isWrongCaseCssJsPage ) {
2501  $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) );
2502  }
2503  if ( $this->formtype !== 'preview' ) {
2504  if ( $this->isCssSubpage ) {
2505  $wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) );
2506  }
2507 
2508  if ( $this->isJsSubpage ) {
2509  $wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) );
2510  }
2511  }
2512  }
2513  }
2514 
2515  if ( $this->mTitle->isProtected( 'edit' ) &&
2516  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
2517  ) {
2518  # Is the title semi-protected?
2519  if ( $this->mTitle->isSemiProtected() ) {
2520  $noticeMsg = 'semiprotectedpagewarning';
2521  } else {
2522  # Then it must be protected based on static groups (regular)
2523  $noticeMsg = 'protectedpagewarning';
2524  }
2525  LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
2526  array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) );
2527  }
2528  if ( $this->mTitle->isCascadeProtected() ) {
2529  # Is this page under cascading protection from some source pages?
2530  list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
2531  $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
2532  $cascadeSourcesCount = count( $cascadeSources );
2533  if ( $cascadeSourcesCount > 0 ) {
2534  # Explain, and list the titles responsible
2535  foreach ( $cascadeSources as $page ) {
2536  $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
2537  }
2538  }
2539  $notice .= '</div>';
2540  $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', $cascadeSourcesCount ) );
2541  }
2542  if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
2543  LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
2544  array( 'lim' => 1,
2545  'showIfEmpty' => false,
2546  'msgKey' => array( 'titleprotectedwarning' ),
2547  'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ) );
2548  }
2549 
2550  if ( $this->kblength === false ) {
2551  $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
2552  }
2553 
2554  if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
2555  $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
2556  array( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) ) );
2557  } else {
2558  if ( !wfMessage( 'longpage-hint' )->isDisabled() ) {
2559  $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
2560  array( 'longpage-hint', $wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) )
2561  );
2562  }
2563  }
2564  # Add header copyright warning
2565  $this->showHeaderCopyrightWarning();
2566  }
2567 
2582  function getSummaryInput( $summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null ) {
2583  // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
2584  $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : array() ) + array(
2585  'id' => 'wpSummary',
2586  'maxlength' => '200',
2587  'tabindex' => '1',
2588  'size' => 60,
2589  'spellcheck' => 'true',
2590  ) + Linker::tooltipAndAccesskeyAttribs( 'summary' );
2591 
2592  $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : array() ) + array(
2593  'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
2594  'id' => "wpSummaryLabel"
2595  );
2596 
2597  $label = null;
2598  if ( $labelText ) {
2599  $label = Xml::tags( 'label', $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null, $labelText );
2600  $label = Xml::tags( 'span', $spanLabelAttrs, $label );
2601  }
2602 
2603  $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
2604 
2605  return array( $label, $input );
2606  }
2607 
2615  protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
2617  # Add a class if 'missingsummary' is triggered to allow styling of the summary line
2618  $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
2619  if ( $isSubjectPreview ) {
2620  if ( $this->nosummary ) {
2621  return;
2622  }
2623  } else {
2624  if ( !$this->mShowSummaryField ) {
2625  return;
2626  }
2627  }
2628  $summary = $wgContLang->recodeForEdit( $summary );
2629  $labelText = wfMessage( $isSubjectPreview ? 'subject' : 'summary' )->parse();
2630  list( $label, $input ) = $this->getSummaryInput( $summary, $labelText, array( 'class' => $summaryClass ), array() );
2631  $wgOut->addHTML( "{$label} {$input}" );
2632  }
2633 
2641  protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
2642  // avoid spaces in preview, gets always trimmed on save
2643  $summary = trim( $summary );
2644  if ( !$summary || ( !$this->preview && !$this->diff ) ) {
2645  return "";
2646  }
2647 
2648  global $wgParser;
2649 
2650  if ( $isSubjectPreview ) {
2651  $summary = wfMessage( 'newsectionsummary' )->rawParams( $wgParser->stripSectionName( $summary ) )
2652  ->inContentLanguage()->text();
2653  }
2654 
2655  $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
2656 
2657  $summary = wfMessage( $message )->parse() . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
2658  return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary );
2659  }
2660 
2661  protected function showFormBeforeText() {
2662  global $wgOut;
2663  $section = htmlspecialchars( $this->section );
2664  $wgOut->addHTML( <<<HTML
2665 <input type='hidden' value="{$section}" name="wpSection" />
2666 <input type='hidden' value="{$this->starttime}" name="wpStarttime" />
2667 <input type='hidden' value="{$this->edittime}" name="wpEdittime" />
2668 <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
2669 
2670 HTML
2671  );
2672  if ( !$this->checkUnicodeCompliantBrowser() ) {
2673  $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
2674  }
2675  }
2676 
2677  protected function showFormAfterText() {
2691  $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
2692  }
2693 
2702  protected function showContentForm() {
2703  $this->showTextbox1();
2704  }
2705 
2714  protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
2715  if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
2716  $attribs = array( 'style' => 'display:none;' );
2717  } else {
2718  $classes = array(); // Textarea CSS
2719  if ( $this->mTitle->isProtected( 'edit' ) &&
2720  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
2721  ) {
2722  # Is the title semi-protected?
2723  if ( $this->mTitle->isSemiProtected() ) {
2724  $classes[] = 'mw-textarea-sprotected';
2725  } else {
2726  # Then it must be protected based on static groups (regular)
2727  $classes[] = 'mw-textarea-protected';
2728  }
2729  # Is the title cascade-protected?
2730  if ( $this->mTitle->isCascadeProtected() ) {
2731  $classes[] = 'mw-textarea-cprotected';
2732  }
2733  }
2734 
2735  $attribs = array( 'tabindex' => 1 );
2736 
2737  if ( is_array( $customAttribs ) ) {
2739  }
2740 
2741  if ( count( $classes ) ) {
2742  if ( isset( $attribs['class'] ) ) {
2743  $classes[] = $attribs['class'];
2744  }
2745  $attribs['class'] = implode( ' ', $classes );
2746  }
2747  }
2748 
2749  $this->showTextbox( $textoverride !== null ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
2750  }
2751 
2752  protected function showTextbox2() {
2753  $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
2754  }
2755 
2756  protected function showTextbox( $text, $name, $customAttribs = array() ) {
2758 
2759  $wikitext = $this->safeUnicodeOutput( $text );
2760  if ( strval( $wikitext ) !== '' ) {
2761  // Ensure there's a newline at the end, otherwise adding lines
2762  // is awkward.
2763  // But don't add a newline if the ext is empty, or Firefox in XHTML
2764  // mode will show an extra newline. A bit annoying.
2765  $wikitext .= "\n";
2766  }
2767 
2769  'accesskey' => ',',
2770  'id' => $name,
2771  'cols' => $wgUser->getIntOption( 'cols' ),
2772  'rows' => $wgUser->getIntOption( 'rows' ),
2773  'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work
2774  );
2775 
2776  $pageLang = $this->mTitle->getPageLanguage();
2777  $attribs['lang'] = $pageLang->getCode();
2778  $attribs['dir'] = $pageLang->getDir();
2779 
2780  $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
2781  }
2782 
2783  protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
2784  global $wgOut;
2785  $classes = array();
2786  if ( $isOnTop ) {
2787  $classes[] = 'ontop';
2788  }
2789 
2790  $attribs = array( 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) );
2791 
2792  if ( $this->formtype != 'preview' ) {
2793  $attribs['style'] = 'display: none;';
2794  }
2795 
2796  $wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
2797 
2798  if ( $this->formtype == 'preview' ) {
2799  $this->showPreview( $previewOutput );
2800  }
2801 
2802  $wgOut->addHTML( '</div>' );
2803 
2804  if ( $this->formtype == 'diff' ) {
2805  try {
2806  $this->showDiff();
2807  } catch ( MWContentSerializationException $ex ) {
2808  $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
2809  $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
2810  }
2811  }
2812  }
2813 
2820  protected function showPreview( $text ) {
2821  global $wgOut;
2822  if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
2823  $this->mArticle->openShowCategory();
2824  }
2825  # This hook seems slightly odd here, but makes things more
2826  # consistent for extensions.
2827  wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) );
2828  $wgOut->addHTML( $text );
2829  if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
2830  $this->mArticle->closeShowCategory();
2831  }
2832  }
2833 
2841  function showDiff() {
2843 
2844  $oldtitlemsg = 'currentrev';
2845  # if message does not exist, show diff against the preloaded default
2846  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
2847  $oldtext = $this->mTitle->getDefaultMessageText();
2848  if ( $oldtext !== false ) {
2849  $oldtitlemsg = 'defaultmessagetext';
2850  $oldContent = $this->toEditContent( $oldtext );
2851  } else {
2852  $oldContent = null;
2853  }
2854  } else {
2855  $oldContent = $this->getCurrentContent();
2856  }
2857 
2858  $textboxContent = $this->toEditContent( $this->textbox1 );
2859 
2860  $newContent = $this->mArticle->replaceSectionContent(
2861  $this->section, $textboxContent,
2862  $this->summary, $this->edittime );
2863 
2864  if ( $newContent ) {
2865  ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
2866  wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
2867 
2869  $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
2870  }
2871 
2872  if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
2873  $oldtitle = wfMessage( $oldtitlemsg )->parse();
2874  $newtitle = wfMessage( 'yourtext' )->parse();
2875 
2876  if ( !$oldContent ) {
2877  $oldContent = $newContent->getContentHandler()->makeEmptyContent();
2878  }
2879 
2880  if ( !$newContent ) {
2881  $newContent = $oldContent->getContentHandler()->makeEmptyContent();
2882  }
2883 
2884  $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
2885  $de->setContent( $oldContent, $newContent );
2886 
2887  $difftext = $de->getDiff( $oldtitle, $newtitle );
2888  $de->showDiffStyle();
2889  } else {
2890  $difftext = '';
2891  }
2892 
2893  $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
2894  }
2895 
2899  protected function showHeaderCopyrightWarning() {
2900  $msg = 'editpage-head-copy-warn';
2901  if ( !wfMessage( $msg )->isDisabled() ) {
2902  global $wgOut;
2903  $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
2904  'editpage-head-copy-warn' );
2905  }
2906  }
2907 
2916  protected function showTosSummary() {
2917  $msg = 'editpage-tos-summary';
2918  wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
2919  if ( !wfMessage( $msg )->isDisabled() ) {
2920  global $wgOut;
2921  $wgOut->addHTML( '<div class="mw-tos-summary">' );
2922  $wgOut->addWikiMsg( $msg );
2923  $wgOut->addHTML( '</div>' );
2924  }
2925  }
2926 
2927  protected function showEditTools() {
2928  global $wgOut;
2929  $wgOut->addHTML( '<div class="mw-editTools">' .
2930  wfMessage( 'edittools' )->inContentLanguage()->parse() .
2931  '</div>' );
2932  }
2933 
2939  protected function getCopywarn() {
2940  return self::getCopyrightWarning( $this->mTitle );
2941  }
2942 
2951  public static function getCopyrightWarning( $title, $format = 'plain' ) {
2952  global $wgRightsText;
2953  if ( $wgRightsText ) {
2954  $copywarnMsg = array( 'copyrightwarning',
2955  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]',
2956  $wgRightsText );
2957  } else {
2958  $copywarnMsg = array( 'copyrightwarning2',
2959  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' );
2960  }
2961  // Allow for site and per-namespace customization of contribution/copyright notice.
2962  wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
2963 
2964  return "<div id=\"editpage-copywarn\">\n" .
2965  call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>";
2966  }
2967 
2975  public static function getPreviewLimitReport( $output ) {
2976  if ( !$output || !$output->getLimitReportData() ) {
2977  return '';
2978  }
2979 
2980  wfProfileIn( __METHOD__ );
2981 
2982  $limitReport = Html::rawElement( 'div', array( 'class' => 'mw-limitReportExplanation' ),
2983  wfMessage( 'limitreport-title' )->parseAsBlock()
2984  );
2985 
2986  // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
2987  $limitReport .= Html::openElement( 'div', array( 'class' => 'preview-limit-report-wrapper' ) );
2988 
2989  $limitReport .= Html::openElement( 'table', array(
2990  'class' => 'preview-limit-report wikitable'
2991  ) ) .
2992  Html::openElement( 'tbody' );
2993 
2994  foreach ( $output->getLimitReportData() as $key => $value ) {
2995  if ( wfRunHooks( 'ParserLimitReportFormat',
2996  array( $key, &$value, &$limitReport, true, true )
2997  ) ) {
2998  $keyMsg = wfMessage( $key );
2999  $valueMsg = wfMessage( array( "$key-value-html", "$key-value" ) );
3000  if ( !$valueMsg->exists() ) {
3001  $valueMsg = new RawMessage( '$1' );
3002  }
3003  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3004  $limitReport .= Html::openElement( 'tr' ) .
3005  Html::rawElement( 'th', null, $keyMsg->parse() ) .
3006  Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) .
3007  Html::closeElement( 'tr' );
3008  }
3009  }
3010  }
3011 
3012  $limitReport .= Html::closeElement( 'tbody' ) .
3013  Html::closeElement( 'table' ) .
3014  Html::closeElement( 'div' );
3015 
3016  wfProfileOut( __METHOD__ );
3017 
3018  return $limitReport;
3019  }
3020 
3021  protected function showStandardInputs( &$tabindex = 2 ) {
3022  global $wgOut;
3023  $wgOut->addHTML( "<div class='editOptions'>\n" );
3024 
3025  if ( $this->section != 'new' ) {
3026  $this->showSummaryInput( false, $this->summary );
3027  $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
3028  }
3029 
3030  $checkboxes = $this->getCheckboxes( $tabindex,
3031  array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) );
3032  $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" );
3033 
3034  // Show copyright warning.
3035  $wgOut->addWikiText( $this->getCopywarn() );
3036  $wgOut->addHTML( $this->editFormTextAfterWarn );
3037 
3038  $wgOut->addHTML( "<div class='editButtons'>\n" );
3039  $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
3040 
3041  $cancel = $this->getCancelLink();
3042  if ( $cancel !== '' ) {
3043  $cancel .= Html::element( 'span',
3044  array( 'class' => 'mw-editButtons-pipe-separator' ),
3045  wfMessage( 'pipe-separator' )->text() );
3046  }
3047  $edithelpurl = Skin::makeInternalOrExternalUrl( wfMessage( 'edithelppage' )->inContentLanguage()->text() );
3048  $edithelp = '<a target="helpwindow" href="' . $edithelpurl . '">' .
3049  wfMessage( 'edithelp' )->escaped() . '</a> ' .
3050  wfMessage( 'newwindow' )->parse();
3051  $wgOut->addHTML( " <span class='cancelLink'>{$cancel}</span>\n" );
3052  $wgOut->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" );
3053  $wgOut->addHTML( "</div><!-- editButtons -->\n" );
3054  wfRunHooks( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) );
3055  $wgOut->addHTML( "</div><!-- editOptions -->\n" );
3056  }
3057 
3062  protected function showConflict() {
3063  global $wgOut;
3064 
3065  if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
3066  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
3067 
3068  $content1 = $this->toEditContent( $this->textbox1 );
3069  $content2 = $this->toEditContent( $this->textbox2 );
3070 
3071  $handler = ContentHandler::getForModelID( $this->contentModel );
3072  $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
3073  $de->setContent( $content2, $content1 );
3074  $de->showDiff(
3075  wfMessage( 'yourtext' )->parse(),
3076  wfMessage( 'storedversion' )->text()
3077  );
3078 
3079  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
3080  $this->showTextbox2();
3081  }
3082  }
3083 
3087  public function getCancelLink() {
3088  $cancelParams = array();
3089  if ( !$this->isConflict && $this->oldid > 0 ) {
3090  $cancelParams['oldid'] = $this->oldid;
3091  }
3092 
3093  return Linker::linkKnown(
3094  $this->getContextTitle(),
3095  wfMessage( 'cancel' )->parse(),
3096  array( 'id' => 'mw-editform-cancel' ),
3097  $cancelParams
3098  );
3099  }
3100 
3110  protected function getActionURL( Title $title ) {
3111  return $title->getLocalURL( array( 'action' => $this->action ) );
3112  }
3113 
3120  protected function wasDeletedSinceLastEdit() {
3121  if ( $this->deletedSinceEdit !== null ) {
3122  return $this->deletedSinceEdit;
3123  }
3124 
3125  $this->deletedSinceEdit = false;
3126 
3127  if ( $this->mTitle->isDeletedQuick() ) {
3128  $this->lastDelete = $this->getLastDelete();
3129  if ( $this->lastDelete ) {
3130  $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
3131  if ( $deleteTime > $this->starttime ) {
3132  $this->deletedSinceEdit = true;
3133  }
3134  }
3135  }
3136 
3137  return $this->deletedSinceEdit;
3138  }
3139 
3140  protected function getLastDelete() {
3141  $dbr = wfGetDB( DB_SLAVE );
3142  $data = $dbr->selectRow(
3143  array( 'logging', 'user' ),
3144  array(
3145  'log_type',
3146  'log_action',
3147  'log_timestamp',
3148  'log_user',
3149  'log_namespace',
3150  'log_title',
3151  'log_comment',
3152  'log_params',
3153  'log_deleted',
3154  'user_name'
3155  ), array(
3156  'log_namespace' => $this->mTitle->getNamespace(),
3157  'log_title' => $this->mTitle->getDBkey(),
3158  'log_type' => 'delete',
3159  'log_action' => 'delete',
3160  'user_id=log_user'
3161  ),
3162  __METHOD__,
3163  array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' )
3164  );
3165  // Quick paranoid permission checks...
3166  if ( is_object( $data ) ) {
3167  if ( $data->log_deleted & LogPage::DELETED_USER ) {
3168  $data->user_name = wfMessage( 'rev-deleted-user' )->escaped();
3169  }
3170 
3171  if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
3172  $data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped();
3173  }
3174  }
3175  return $data;
3176  }
3177 
3183  function getPreviewText() {
3184  global $wgOut, $wgUser, $wgRawHtml, $wgLang;
3185 
3186  wfProfileIn( __METHOD__ );
3187 
3188  if ( $wgRawHtml && !$this->mTokenOk ) {
3189  // Could be an offsite preview attempt. This is very unsafe if
3190  // HTML is enabled, as it could be an attack.
3191  $parsedNote = '';
3192  if ( $this->textbox1 !== '' ) {
3193  // Do not put big scary notice, if previewing the empty
3194  // string, which happens when you initially edit
3195  // a category page, due to automatic preview-on-open.
3196  $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
3197  wfMessage( 'session_fail_preview_html' )->text() . "</div>", true, /* interface */true );
3198  }
3199  wfProfileOut( __METHOD__ );
3200  return $parsedNote;
3201  }
3202 
3203  $note = '';
3204 
3205  try {
3206  $content = $this->toEditContent( $this->textbox1 );
3207 
3208  $previewHTML = '';
3209  if ( !wfRunHooks( 'AlternateEditPreview', array( $this, &$content, &$previewHTML, &$this->mParserOutput ) ) ) {
3210  wfProfileOut( __METHOD__ );
3211  return $previewHTML;
3212  }
3213 
3214  # provide a anchor link to the editform
3215  $continueEditing = '<span class="mw-continue-editing">' .
3216  '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' .
3217  wfMessage( 'continue-editing' )->text() . ']]</span>';
3218  if ( $this->mTriedSave && !$this->mTokenOk ) {
3219  if ( $this->mTokenOkExceptSuffix ) {
3220  $note = wfMessage( 'token_suffix_mismatch' )->plain();
3221 
3222  } else {
3223  $note = wfMessage( 'session_fail_preview' )->plain();
3224  }
3225  } elseif ( $this->incompleteForm ) {
3226  $note = wfMessage( 'edit_form_incomplete' )->plain();
3227  } else {
3228  $note = wfMessage( 'previewnote' )->plain() . ' ' . $continueEditing;
3229  }
3230 
3231  $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
3232  $parserOptions->setEditSection( false );
3233  $parserOptions->setIsPreview( true );
3234  $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
3235 
3236  # don't parse non-wikitext pages, show message about preview
3237  if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
3238  if ( $this->mTitle->isCssJsSubpage() ) {
3239  $level = 'user';
3240  } elseif ( $this->mTitle->isCssOrJsPage() ) {
3241  $level = 'site';
3242  } else {
3243  $level = false;
3244  }
3245 
3246  if ( $content->getModel() == CONTENT_MODEL_CSS ) {
3247  $format = 'css';
3248  } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
3249  $format = 'js';
3250  } else {
3251  $format = false;
3252  }
3253 
3254  # Used messages to make sure grep find them:
3255  # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
3256  if ( $level && $format ) {
3257  $note = "<div id='mw-{$level}{$format}preview'>" .
3258  wfMessage( "{$level}{$format}preview" )->text() .
3259  ' ' . $continueEditing . "</div>";
3260  }
3261  }
3262 
3263  $rt = $content->getRedirectChain();
3264  if ( $rt ) {
3265  $previewHTML = $this->mArticle->viewRedirect( $rt, false );
3266  } else {
3267 
3268  # If we're adding a comment, we need to show the
3269  # summary as the headline
3270  if ( $this->section === "new" && $this->summary !== "" ) {
3271  $content = $content->addSectionHeader( $this->summary );
3272  }
3273 
3274  $hook_args = array( $this, &$content );
3275  ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
3276  wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
3277 
3278  $parserOptions->enableLimitReport();
3279 
3280  # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
3281  # But it's now deprecated, so never mind
3282 
3283  $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
3284  $parserOutput = $content->getParserOutput( $this->getArticle()->getTitle(), null, $parserOptions );
3285 
3286  $previewHTML = $parserOutput->getText();
3287  $this->mParserOutput = $parserOutput;
3288  $wgOut->addParserOutputNoText( $parserOutput );
3289 
3290  if ( count( $parserOutput->getWarnings() ) ) {
3291  $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
3292  }
3293  }
3294  } catch ( MWContentSerializationException $ex ) {
3295  $m = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
3296  $note .= "\n\n" . $m->parse();
3297  $previewHTML = '';
3298  }
3299 
3300  if ( $this->isConflict ) {
3301  $conflict = '<h2 id="mw-previewconflict">' . wfMessage( 'previewconflict' )->escaped() . "</h2>\n";
3302  } else {
3303  $conflict = '<hr />';
3304  }
3305 
3306  $previewhead = "<div class='previewnote'>\n" .
3307  '<h2 id="mw-previewheader">' . wfMessage( 'preview' )->escaped() . "</h2>" .
3308  $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
3309 
3310  $pageViewLang = $this->mTitle->getPageViewLanguage();
3311  $attribs = array( 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3312  'class' => 'mw-content-' . $pageViewLang->getDir() );
3313  $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
3314 
3315  wfProfileOut( __METHOD__ );
3316  return $previewhead . $previewHTML . $this->previewTextAfterContent;
3317  }
3318 
3322  function getTemplates() {
3323  if ( $this->preview || $this->section != '' ) {
3324  $templates = array();
3325  if ( !isset( $this->mParserOutput ) ) {
3326  return $templates;
3327  }
3328  foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
3329  foreach ( array_keys( $template ) as $dbk ) {
3330  $templates[] = Title::makeTitle( $ns, $dbk );
3331  }
3332  }
3333  return $templates;
3334  } else {
3335  return $this->mTitle->getTemplateLinksFrom();
3336  }
3337  }
3338 
3346  static function getEditToolbar() {
3347  global $wgStylePath, $wgContLang, $wgLang, $wgOut;
3348  global $wgEnableUploads, $wgForeignFileRepos;
3349 
3350  $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
3351 
3365  $toolarray = array(
3366  array(
3367  'image' => $wgLang->getImageFile( 'button-bold' ),
3368  'id' => 'mw-editbutton-bold',
3369  'open' => '\'\'\'',
3370  'close' => '\'\'\'',
3371  'sample' => wfMessage( 'bold_sample' )->text(),
3372  'tip' => wfMessage( 'bold_tip' )->text(),
3373  'key' => 'B'
3374  ),
3375  array(
3376  'image' => $wgLang->getImageFile( 'button-italic' ),
3377  'id' => 'mw-editbutton-italic',
3378  'open' => '\'\'',
3379  'close' => '\'\'',
3380  'sample' => wfMessage( 'italic_sample' )->text(),
3381  'tip' => wfMessage( 'italic_tip' )->text(),
3382  'key' => 'I'
3383  ),
3384  array(
3385  'image' => $wgLang->getImageFile( 'button-link' ),
3386  'id' => 'mw-editbutton-link',
3387  'open' => '[[',
3388  'close' => ']]',
3389  'sample' => wfMessage( 'link_sample' )->text(),
3390  'tip' => wfMessage( 'link_tip' )->text(),
3391  'key' => 'L'
3392  ),
3393  array(
3394  'image' => $wgLang->getImageFile( 'button-extlink' ),
3395  'id' => 'mw-editbutton-extlink',
3396  'open' => '[',
3397  'close' => ']',
3398  'sample' => wfMessage( 'extlink_sample' )->text(),
3399  'tip' => wfMessage( 'extlink_tip' )->text(),
3400  'key' => 'X'
3401  ),
3402  array(
3403  'image' => $wgLang->getImageFile( 'button-headline' ),
3404  'id' => 'mw-editbutton-headline',
3405  'open' => "\n== ",
3406  'close' => " ==\n",
3407  'sample' => wfMessage( 'headline_sample' )->text(),
3408  'tip' => wfMessage( 'headline_tip' )->text(),
3409  'key' => 'H'
3410  ),
3411  $imagesAvailable ? array(
3412  'image' => $wgLang->getImageFile( 'button-image' ),
3413  'id' => 'mw-editbutton-image',
3414  'open' => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
3415  'close' => ']]',
3416  'sample' => wfMessage( 'image_sample' )->text(),
3417  'tip' => wfMessage( 'image_tip' )->text(),
3418  'key' => 'D',
3419  ) : false,
3420  $imagesAvailable ? array(
3421  'image' => $wgLang->getImageFile( 'button-media' ),
3422  'id' => 'mw-editbutton-media',
3423  'open' => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
3424  'close' => ']]',
3425  'sample' => wfMessage( 'media_sample' )->text(),
3426  'tip' => wfMessage( 'media_tip' )->text(),
3427  'key' => 'M'
3428  ) : false,
3429  array(
3430  'image' => $wgLang->getImageFile( 'button-nowiki' ),
3431  'id' => 'mw-editbutton-nowiki',
3432  'open' => "<nowiki>",
3433  'close' => "</nowiki>",
3434  'sample' => wfMessage( 'nowiki_sample' )->text(),
3435  'tip' => wfMessage( 'nowiki_tip' )->text(),
3436  'key' => 'N'
3437  ),
3438  array(
3439  'image' => $wgLang->getImageFile( 'button-sig' ),
3440  'id' => 'mw-editbutton-signature',
3441  'open' => '--~~~~',
3442  'close' => '',
3443  'sample' => '',
3444  'tip' => wfMessage( 'sig_tip' )->text(),
3445  'key' => 'Y'
3446  ),
3447  array(
3448  'image' => $wgLang->getImageFile( 'button-hr' ),
3449  'id' => 'mw-editbutton-hr',
3450  'open' => "\n----\n",
3451  'close' => '',
3452  'sample' => '',
3453  'tip' => wfMessage( 'hr_tip' )->text(),
3454  'key' => 'R'
3455  )
3456  );
3457 
3458  $script = 'mw.loader.using("mediawiki.action.edit", function() {';
3459  foreach ( $toolarray as $tool ) {
3460  if ( !$tool ) {
3461  continue;
3462  }
3463 
3464  $params = array(
3465  $wgStylePath . '/common/images/' . $tool['image'],
3466  // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
3467  // Older browsers show a "speedtip" type message only for ALT.
3468  // Ideally these should be different, realistically they
3469  // probably don't need to be.
3470  $tool['tip'],
3471  $tool['open'],
3472  $tool['close'],
3473  $tool['sample'],
3474  $tool['id'],
3475  );
3476 
3477  $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
3478  }
3479 
3480  // This used to be called on DOMReady from mediawiki.action.edit, which
3481  // ended up causing race conditions with the setup code above.
3482  $script .= "\n" .
3483  "// Create button bar\n" .
3484  "$(function() { mw.toolbar.init(); } );\n";
3485 
3486  $script .= '});';
3488 
3489  $toolbar = '<div id="toolbar"></div>';
3490 
3491  wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
3492 
3493  return $toolbar;
3494  }
3495 
3506  public function getCheckboxes( &$tabindex, $checked ) {
3507  global $wgUser;
3508 
3509  $checkboxes = array();
3510 
3511  // don't show the minor edit checkbox if it's a new page or section
3512  if ( !$this->isNew ) {
3513  $checkboxes['minor'] = '';
3514  $minorLabel = wfMessage( 'minoredit' )->parse();
3515  if ( $wgUser->isAllowed( 'minoredit' ) ) {
3516  $attribs = array(
3517  'tabindex' => ++$tabindex,
3518  'accesskey' => wfMessage( 'accesskey-minoredit' )->text(),
3519  'id' => 'wpMinoredit',
3520  );
3521  $checkboxes['minor'] =
3522  Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
3523  "&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
3524  Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'minoredit', 'withaccess' ) ) ) .
3525  ">{$minorLabel}</label>";
3526  }
3527  }
3528 
3529  $watchLabel = wfMessage( 'watchthis' )->parse();
3530  $checkboxes['watch'] = '';
3531  if ( $wgUser->isLoggedIn() ) {
3532  $attribs = array(
3533  'tabindex' => ++$tabindex,
3534  'accesskey' => wfMessage( 'accesskey-watch' )->text(),
3535  'id' => 'wpWatchthis',
3536  );
3537  $checkboxes['watch'] =
3538  Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
3539  "&#160;<label for='wpWatchthis' id='mw-editpage-watch'" .
3540  Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ) ) .
3541  ">{$watchLabel}</label>";
3542  }
3543  wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
3544  return $checkboxes;
3545  }
3546 
3555  public function getEditButtons( &$tabindex ) {
3556  $buttons = array();
3557 
3558  $temp = array(
3559  'id' => 'wpSave',
3560  'name' => 'wpSave',
3561  'type' => 'submit',
3562  'tabindex' => ++$tabindex,
3563  'value' => wfMessage( 'savearticle' )->text(),
3564  'accesskey' => wfMessage( 'accesskey-save' )->text(),
3565  'title' => wfMessage( 'tooltip-save' )->text() . ' [' . wfMessage( 'accesskey-save' )->text() . ']',
3566  );
3567  $buttons['save'] = Xml::element( 'input', $temp, '' );
3568 
3569  ++$tabindex; // use the same for preview and live preview
3570  $temp = array(
3571  'id' => 'wpPreview',
3572  'name' => 'wpPreview',
3573  'type' => 'submit',
3574  'tabindex' => $tabindex,
3575  'value' => wfMessage( 'showpreview' )->text(),
3576  'accesskey' => wfMessage( 'accesskey-preview' )->text(),
3577  'title' => wfMessage( 'tooltip-preview' )->text() . ' [' . wfMessage( 'accesskey-preview' )->text() . ']',
3578  );
3579  $buttons['preview'] = Xml::element( 'input', $temp, '' );
3580  $buttons['live'] = '';
3581 
3582  $temp = array(
3583  'id' => 'wpDiff',
3584  'name' => 'wpDiff',
3585  'type' => 'submit',
3586  'tabindex' => ++$tabindex,
3587  'value' => wfMessage( 'showdiff' )->text(),
3588  'accesskey' => wfMessage( 'accesskey-diff' )->text(),
3589  'title' => wfMessage( 'tooltip-diff' )->text() . ' [' . wfMessage( 'accesskey-diff' )->text() . ']',
3590  );
3591  $buttons['diff'] = Xml::element( 'input', $temp, '' );
3592 
3593  wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) );
3594  return $buttons;
3595  }
3596 
3609  function livePreview() {
3610  global $wgOut;
3611  $wgOut->disable();
3612  header( 'Content-type: text/xml; charset=utf-8' );
3613  header( 'Cache-control: no-cache' );
3614 
3615  $previewText = $this->getPreviewText();
3616  #$categories = $skin->getCategoryLinks();
3617 
3618  $s =
3619  '<?xml version="1.0" encoding="UTF-8" ?>' . "\n" .
3620  Xml::tags( 'livepreview', null,
3621  Xml::element( 'preview', null, $previewText )
3622  #. Xml::element( 'category', null, $categories )
3623  );
3624  echo $s;
3625  }
3626 
3632  function blockedPage() {
3633  wfDeprecated( __METHOD__, '1.19' );
3634  global $wgUser;
3635 
3636  throw new UserBlockedError( $wgUser->getBlock() );
3637  }
3638 
3644  function userNotLoggedInPage() {
3645  wfDeprecated( __METHOD__, '1.19' );
3646  throw new PermissionsError( 'edit' );
3647  }
3648 
3655  function noCreatePermission() {
3656  wfDeprecated( __METHOD__, '1.19' );
3657  $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
3658  throw new PermissionsError( $permission );
3659  }
3660 
3665  function noSuchSectionPage() {
3666  global $wgOut;
3667 
3668  $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
3669 
3670  $res = wfMessage( 'nosuchsectiontext', $this->section )->parseAsBlock();
3671  wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
3672  $wgOut->addHTML( $res );
3673 
3674  $wgOut->returnToMain( false, $this->mTitle );
3675  }
3676 
3682  public function spamPageWithContent( $match = false ) {
3684  $this->textbox2 = $this->textbox1;
3685 
3686  if ( is_array( $match ) ) {
3687  $match = $wgLang->listToText( $match );
3688  }
3689  $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
3690 
3691  $wgOut->addHTML( '<div id="spamprotected">' );
3692  $wgOut->addWikiMsg( 'spamprotectiontext' );
3693  if ( $match ) {
3694  $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
3695  }
3696  $wgOut->addHTML( '</div>' );
3697 
3698  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
3699  $this->showDiff();
3700 
3701  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
3702  $this->showTextbox2();
3703 
3704  $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) );
3705  }
3706 
3713  private function checkUnicodeCompliantBrowser() {
3714  global $wgBrowserBlackList, $wgRequest;
3715 
3716  $currentbrowser = $wgRequest->getHeader( 'User-Agent' );
3717  if ( $currentbrowser === false ) {
3718  // No User-Agent header sent? Trust it by default...
3719  return true;
3720  }
3721 
3722  foreach ( $wgBrowserBlackList as $browser ) {
3723  if ( preg_match( $browser, $currentbrowser ) ) {
3724  return false;
3725  }
3726  }
3727  return true;
3728  }
3729 
3738  protected function safeUnicodeInput( $request, $field ) {
3739  $text = rtrim( $request->getText( $field ) );
3740  return $request->getBool( 'safemode' )
3741  ? $this->unmakeSafe( $text )
3742  : $text;
3743  }
3744 
3752  protected function safeUnicodeOutput( $text ) {
3754  $codedText = $wgContLang->recodeForEdit( $text );
3755  return $this->checkUnicodeCompliantBrowser()
3756  ? $codedText
3757  : $this->makeSafe( $codedText );
3758  }
3759 
3772  private function makeSafe( $invalue ) {
3773  // Armor existing references for reversibility.
3774  $invalue = strtr( $invalue, array( "&#x" => "&#x0" ) );
3775 
3776  $bytesleft = 0;
3777  $result = "";
3778  $working = 0;
3779  for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
3780  $bytevalue = ord( $invalue[$i] );
3781  if ( $bytevalue <= 0x7F ) { // 0xxx xxxx
3782  $result .= chr( $bytevalue );
3783  $bytesleft = 0;
3784  } elseif ( $bytevalue <= 0xBF ) { // 10xx xxxx
3785  $working = $working << 6;
3786  $working += ( $bytevalue & 0x3F );
3787  $bytesleft--;
3788  if ( $bytesleft <= 0 ) {
3789  $result .= "&#x" . strtoupper( dechex( $working ) ) . ";";
3790  }
3791  } elseif ( $bytevalue <= 0xDF ) { // 110x xxxx
3792  $working = $bytevalue & 0x1F;
3793  $bytesleft = 1;
3794  } elseif ( $bytevalue <= 0xEF ) { // 1110 xxxx
3795  $working = $bytevalue & 0x0F;
3796  $bytesleft = 2;
3797  } else { // 1111 0xxx
3798  $working = $bytevalue & 0x07;
3799  $bytesleft = 3;
3800  }
3801  }
3802  return $result;
3803  }
3804 
3813  private function unmakeSafe( $invalue ) {
3814  $result = "";
3815  $valueLength = strlen( $invalue );
3816  for ( $i = 0; $i < $valueLength; $i++ ) {
3817  if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) {
3818  $i += 3;
3819  $hexstring = "";
3820  do {
3821  $hexstring .= $invalue[$i];
3822  $i++;
3823  } while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
3824 
3825  // Do some sanity checks. These aren't needed for reversibility,
3826  // but should help keep the breakage down if the editor
3827  // breaks one of the entities whilst editing.
3828  if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) {
3829  $codepoint = hexdec( $hexstring );
3830  $result .= codepointToUtf8( $codepoint );
3831  } else {
3832  $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
3833  }
3834  } else {
3835  $result .= substr( $invalue, $i, 1 );
3836  }
3837  }
3838  // reverse the transform that we made for reversibility reasons.
3839  return strtr( $result, array( "&#x0" => "&#x" ) );
3840  }
3841 }
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
Xml\checkLabel
static checkLabel( $label, $name, $id, $checked=false, $attribs=array())
Convenience function to build an HTML checkbox with a label.
Definition: Xml.php:433
ResourceLoader\makeLoaderConditionalScript
static makeLoaderConditionalScript( $script)
Returns JS code which runs given JS code if the client-side framework is present.
Definition: ResourceLoader.php:1138
ContentHandler\deprecated
static deprecated( $func, $version, $component=false)
Logs a deprecation warning, visible if $wgDevelopmentWarnings, but only if self::$enableDeprecationWa...
Definition: ContentHandler.php:1030
Title\makeTitle
static & makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:398
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:311
$wgUser
$wgUser
Definition: Setup.php:552
$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. $reader:XMLReader object $logInfo:Array of information Return false to stop further processing of the tag 'ImportHandlePageXMLTag':When parsing a XML tag in a page. $reader:XMLReader object $pageInfo:Array of information Return false to stop further processing of the tag 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information Return false to stop further processing of the tag 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. $reader:XMLReader object Return false to stop further processing of the tag 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. $reader:XMLReader object $revisionInfo:Array of information Return false to stop further processing of the tag '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 '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. '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 '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 '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 wfIsTrustedProxy() $ip:IP being check $result:Change this value to override the result of wfIsTrustedProxy() '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 User::isValidEmailAddr(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers '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) 'LanguageGetSpecialPageAliases':DEPRECATED, use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Associative array mapping language codes to prefixed links of the form "language:title". & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LinkBegin':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:1528
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:189
Linker\commentBlock
static commentBlock( $comment, $title=null, $local=false)
Wrap a comment in standard punctuation and formatting if it's non-empty, otherwise return empty strin...
Definition: Linker.php:1556
DB_MASTER
const DB_MASTER
Definition: Defines.php:56
Content\isRedirect
isRedirect()
Returns whether this Content represents a redirect.
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
Content\serialize
serialize( $format=null)
Convenience method for serializing this Content object.
Xml\expandAttributes
static expandAttributes( $attribs)
Given an array of ('attributename' => 'value'), it generates the code to set the XML attributes : att...
Definition: Xml.php:65
ParserOutput
Definition: ParserOutput.php:24
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
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:88
content
per default it will return the text for text based content
Definition: contenthandler.txt:107
Xml\tags
static tags( $element, $attribs=null, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:131
$response
$response
Definition: opensearch_desc.php:32
Html\textarea
static textarea( $name, $value='', $attribs=array())
Convenience function to produce a <textarea> element.
Definition: Html.php:678
Status\fatal
fatal( $message)
Add an error and set OK to false, indicating that the operation as a whole was fatal.
Definition: Status.php:146
is
We use the convention $dbr for read and $dbw for write to help you keep track of whether the database object is a the world will explode Or to be a subsequent write query which succeeded on the master may fail when replicated to the slave due to a unique key collision Replication on the slave will stop and it may take hours to repair the database and get it back online Setting read_only in my cnf on the slave will avoid this but given the dire we prefer to have as many checks as possible We provide a but the wrapper functions like please read the documentation for except in special pages derived from QueryPage It s a common pitfall for new developers to submit code containing SQL queries which examine huge numbers of rows Remember that COUNT * is(N), counting rows in atable is like counting beans in a bucket.------------------------------------------------------------------------ Replication------------------------------------------------------------------------The largest installation of MediaWiki, Wikimedia, uses a large set ofslave MySQL servers replicating writes made to a master MySQL server. Itis important to understand the issues associated with this setup if youwant to write code destined for Wikipedia.It 's often the case that the best algorithm to use for a given taskdepends on whether or not replication is in use. Due to our unabashedWikipedia-centrism, we often just use the replication-friendly version, but if you like, you can use wfGetLB() ->getServerCount() > 1 tocheck to see if replication is in use.===Lag===Lag primarily occurs when large write queries are sent to the master.Writes on the master are executed in parallel, but they are executed inserial when they are replicated to the slaves. The master writes thequery to the binlog when the transaction is committed. The slaves pollthe binlog and start executing the query as soon as it appears. They canservice reads while they are performing a write query, but will not readanything more from the binlog and thus will perform no more writes. Thismeans that if the write query runs for a long time, the slaves will lagbehind the master for the time it takes for the write query to complete.Lag can be exacerbated by high read load. MediaWiki 's load balancer willstop sending reads to a slave when it is lagged by more than 30 seconds.If the load ratios are set incorrectly, or if there is too much loadgenerally, this may lead to a slave permanently hovering around 30seconds lag.If all slaves are lagged by more than 30 seconds, MediaWiki will stopwriting to the database. All edits and other write operations will berefused, with an error returned to the user. This gives the slaves achance to catch up. Before we had this mechanism, the slaves wouldregularly lag by several minutes, making review of recent editsdifficult.In addition to this, MediaWiki attempts to ensure that the user seesevents occurring on the wiki in chronological order. A few seconds of lagcan be tolerated, as long as the user sees a consistent picture fromsubsequent requests. This is done by saving the master binlog positionin the session, and then at the start of each request, waiting for theslave to catch up to that position before doing any reads from it. Ifthis wait times out, reads are allowed anyway, but the request isconsidered to be in "lagged slave mode". Lagged slave mode can bechecked by calling wfGetLB() ->getLaggedSlaveMode(). The onlypractical consequence at present is a warning displayed in the pagefooter.===Lag avoidance===To avoid excessive lag, queries which write large numbers of rows shouldbe split up, generally to write one row at a time. Multi-row INSERT ...SELECT queries are the worst offenders should be avoided altogether.Instead do the select first and then the insert.===Working with lag===Despite our best efforts, it 's not practical to guarantee a low-lagenvironment. Lag will usually be less than one second, but mayoccasionally be up to 30 seconds. For scalability, it 's very importantto keep load on the master low, so simply sending all your queries tothe master is not the answer. So when you have a genuine need forup-to-date data, the following approach is advised:1) Do a quick query to the master for a sequence number or timestamp 2) Run the full query on the slave and check if it matches the data you gotfrom the master 3) If it doesn 't, run the full query on the masterTo avoid swamping the master every time the slaves lag, use of thisapproach should be kept to a minimum. In most cases you should just readfrom the slave and let the user deal with the delay.------------------------------------------------------------------------ Lock contention------------------------------------------------------------------------Due to the high write rate on Wikipedia(and some other wikis), MediaWiki developers need to be very careful to structure their writesto avoid long-lasting locks. By default, MediaWiki opens a transactionat the first query, and commits it before the output is sent. Locks willbe held from the time when the query is done until the commit. So youcan reduce lock time by doing as much processing as possible before youdo your write queries.Often this approach is not good enough, and it becomes necessary toenclose small groups of queries in their own transaction. Use thefollowing syntax:$dbw=wfGetDB(DB_MASTER
EDIT_FORCE_BOT
const EDIT_FORCE_BOT
Definition: Defines.php:193
wfGetDB
& wfGetDB( $db, $groups=array(), $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:3650
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
$timestamp
if( $limit) $timestamp
Definition: importImages.php:104
CONTENT_MODEL_CSS
const CONTENT_MODEL_CSS
Definition: Defines.php:285
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:2483
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all')
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1040
wfProfileIn
wfProfileIn( $functionname)
Begin profiling of a function.
Definition: Profiler.php:33
Content\convert
convert( $toModel, $lossy='')
Converts this content object into another content object with the given content model,...
$customAttribs
null means default & $customAttribs
Definition: hooks.txt:1530
wfArrayDiff2
if(!defined( 'MEDIAWIKI')) wfArrayDiff2( $a, $b)
Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
Definition: GlobalFunctions.php:113
Status\newGood
static newGood( $value=null)
Factory function for good results.
Definition: Status.php:77
$fname
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition: Setup.php:35
NS_FILE
const NS_FILE
Definition: Defines.php:85
$params
$params
Definition: styleTest.css.php:40
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1313
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:388
Content\preloadTransform
preloadTransform(Title $title, ParserOptions $parserOptions, $params=array())
Returns a Content object with preload transformations applied (or this object if no transformations a...
$s
$s
Definition: mergeMessageFileList.php:156
ContentHandler\getForTitle
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
Definition: ContentHandler.php:259
Html\hidden
static hidden( $name, $value, $attribs=array())
Convenience function to produce an input element with type=hidden.
Definition: Html.php:662
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:28
$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
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2113
$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:1038
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:109
Html\inlineScript
static inlineScript( $contents)
Output a "<script>" tag with the given contents.
Definition: Html.php:570
Linker\linkKnown
static linkKnown( $target, $html=null, $customAttribs=array(), $query=array(), $options=array( 'known', 'noclasses'))
Identical to link(), except $options defaults to 'known'.
Definition: Linker.php:264
Linker\formatHiddenCategories
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition: Linker.php:2029
ContentHandler\runLegacyHooks
static runLegacyHooks( $event, $args=array(), $warn=null)
Call a legacy hook that uses text instead of Content objects.
Definition: ContentHandler.php:1053
Skin\getSkinNames
static getSkinNames()
Fetch the set of available skins.
Definition: Skin.php:44
$dbr
$dbr
Definition: testCompression.php:48
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
ContentHandler\getLocalizedName
static getLocalizedName( $name)
Returns the localized name for a given content model.
Definition: ContentHandler.php:360
Revision\FOR_THIS_USER
const FOR_THIS_USER
Definition: Revision.php:73
Html\closeElement
static closeElement( $element)
Returns "</$element>", except if $wgWellFormedXml is off, in which case it returns the empty string w...
Definition: Html.php:235
Xml\encodeJsCall
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:665
Linker\tooltipAndAccesskeyAttribs
static tooltipAndAccesskeyAttribs( $name)
Returns the attributes for the tooltip and access key.
Definition: Linker.php:2298
Html\openElement
static openElement( $element, $attribs=array())
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:166
codepointToUtf8
codepointToUtf8( $codepoint)
Return UTF-8 sequence for a given Unicode code point.
Definition: UtfNormalUtil.php:36
MWException
MediaWiki exception.
Definition: MWException.php:26
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:103
$starttime
$starttime
Definition: api.php:46
Content\getSection
getSection( $sectionId)
Returns the section with the given ID.
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1127
LogPage\DELETED_COMMENT
const DELETED_COMMENT
Definition: LogPage.php:34
ParserOptions\newFromUserAndLang
static newFromUserAndLang(User $user, Language $lang)
Get a ParserOptions object from a given user and language.
Definition: ParserOptions.php:386
Html\element
static element( $element, $attribs=array(), $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:148
LogPage\DELETED_USER
const DELETED_USER
Definition: LogPage.php:35
WatchedItem\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Constant to specify that user rights 'editmywatchlist' and 'viewmywatchlist' should not be checked.
Definition: WatchedItem.php:35
MWContentSerializationException
Exception representing a failure to serialize or unserialize a content object.
Definition: ContentHandler.php:33
wfProfileOut
wfProfileOut( $functionname='missing')
Stop profiling of a function.
Definition: Profiler.php:46
$wgOut
$wgOut
Definition: Setup.php:562
wfMessage
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 after in associative array form externallinks including delete and has completed for all link tables 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
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
wfRunHooks
wfRunHooks( $event, array $args=array(), $deprecatedVersion=null)
Call hook functions defined in $wgHooks.
Definition: GlobalFunctions.php:4001
User\isIP
static isIP( $name)
Does the string match an anonymous IPv4 address?
Definition: User.php:554
ThrottledError
Show an error when the user hits a rate limit.
Definition: ThrottledError.php:27
WatchAction\doWatchOrUnwatch
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:106
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
$comment
$comment
Definition: importImages.php:107
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:2514
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:93
Content\isEmpty
isEmpty()
Returns true if this Content object represents empty content.
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: Namespace.php:446
Html\input
static input( $name, $value='', $type='text', $attribs=array())
Convenience function to produce an "<input>" element.
Definition: Html.php:645
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
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:188
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:144
$ok
$ok
Definition: UtfNormalTest.php:71
$section
$section
Definition: Utf8Test.php:88
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:1731
TS_MW
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition: GlobalFunctions.php:2431
wfDebug
wfDebug( $text, $dest='all')
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:933
Content\equals
equals(Content $that=null)
Returns true if this Content objects is conceptually equivalent to the given Content object.
Content\addSectionHeader
addSectionHeader( $header)
Returns a new WikitextContent object with the given section heading prepended, if supported.
$title
presenting them properly to the user as errors is done by the caller $title
Definition: hooks.txt:1324
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:82
user
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 and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same user
Definition: distributors.txt:9
Content\getRedirectChain
getRedirectChain()
Construct the redirect destination from this content and return an array of Titles,...
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:336
action
action
Definition: parserTests.txt:378
$matches
if(!defined( 'MEDIAWIKI')) if(!isset( $wgVersion)) $matches
Definition: NoLocalSettings.php:33
$value
$value
Definition: styleTest.css.php:45
EDIT_UPDATE
const EDIT_UPDATE
Definition: Defines.php:190
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:67
Xml\check
static check( $name, $checked=false, $attribs=array())
Convenience function to build an HTML checkbox.
Definition: Xml.php:339
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
TextContentHandler
Base content handler implementation for flat text contents.
Definition: TextContentHandler.php:31
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:2077
EDIT_DEFER_UPDATES
const EDIT_DEFER_UPDATES
Definition: Defines.php:194
Revision\RAW
const RAW
Definition: Revision.php:74
Linker\formatTemplates
static formatTemplates( $templates, $preview=false, $section=false, $more=null)
Returns HTML for the "templates used on this page" list.
Definition: Linker.php:1936
RequestContext\getMain
static getMain()
Static methods.
Definition: RequestContext.php:420
$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:237
Content\preSaveTransform
preSaveTransform(Title $title, User $user, ParserOptions $parserOptions)
Returns a Content object with pre-save transformations applied (or this object if no transformations ...
$summary
$summary
Definition: importImages.php:120
Content
Base interface for content objects.
Definition: Content.php:34
$file
if(PHP_SAPI !='cli') $file
Definition: UtfNormalTest2.php:30
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:189
$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:1337
DB_SLAVE
const DB_SLAVE
Definition: Defines.php:55
Title
Represents a title within MediaWiki.
Definition: Title.php:35
EDIT_AUTOSUMMARY
const EDIT_AUTOSUMMARY
Definition: Defines.php:195
$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
ContentHandler\getContentText
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
Definition: ContentHandler.php:94
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()
Get the value of $wgReadOnly or the contents of $wgReadOnlyFile.
Definition: GlobalFunctions.php:1322
$wgParser
$wgParser
Definition: Setup.php:567
in
Prior to maintenance scripts were a hodgepodge of code that had no cohesion or formal method of action Beginning in
Definition: maintenance.txt:1
$output
& $output
Definition: hooks.txt:375
Linker\titleAttrib
static titleAttrib( $name, $options=null)
Given the id of an interface element, constructs the appropriate title attribute from the system mess...
Definition: Linker.php:2072
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
wfFindFile
wfFindFile( $title, $options=array())
Find a file.
Definition: GlobalFunctions.php:3693
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:277
NS_USER
const NS_USER
Definition: Defines.php:81
$source
if(PHP_SAPI !='cli') $source
Definition: mwdoc-filter.php:18
Content\getModel
getModel()
Returns the ID of the content model used by this Content object.
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:87
CONTENT_MODEL_JAVASCRIPT
const CONTENT_MODEL_JAVASCRIPT
Definition: Defines.php:284
EDIT_MINOR
const EDIT_MINOR
Definition: Defines.php:191
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:36
Content\getParserOutput
getParserOutput(Title $title, $revId=null, ParserOptions $options=null, $generateHtml=true)
Parse the Content object and generate a ParserOutput from the result.
$error
usually copyright or history_copyright This message must be in HTML not wikitext $subpages will be ignored and the rest of subPageSubtitle() will run. 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink' whether MediaWiki currently thinks this is a CSS JS page Hooks may change this value to override the return value of Title::isCssOrJsPage(). 'TitleIsAlwaysKnown' whether MediaWiki currently thinks this page is known isMovable() always returns false. $title whether MediaWiki currently thinks this page is movable Hooks may change this value to override the return value of Title::isMovable(). 'TitleIsWikitextPage' whether MediaWiki currently thinks this is a wikitext page Hooks may change this value to override the return value of Title::isWikitextPage() 'TitleMove' use UploadVerification and UploadVerifyFile instead where the first element is the message key and the remaining elements are used as parameters to the message based on mime etc Preferred in most cases over UploadVerification object with all info about the upload string as detected by MediaWiki Handlers will typically only apply for specific mime types object & $error
Definition: hooks.txt:2573
Html\rawElement
static rawElement( $element, $attribs=array(), $contents='')
Returns an HTML element in a string.
Definition: Html.php:124
ErrorPageError
An error page which can definitely be safely rendered using the OutputPage.
Definition: ErrorPageError.php:27
$query
return true to allow those checks to and false if checking is done use this to change the tables headers temp or archived zone change it to an object instance and return false override the list derivative used the name of the old file when set the default code will be skipped add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1105
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:252
$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:1530
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:59
$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
$res
$res
Definition: database.txt:21
section
section
Definition: parserTests.txt:378
Revision\DELETED_TEXT
const DELETED_TEXT
Definition: Revision.php:65
Skin\makeInternalOrExternalUrl
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition: Skin.php:1158
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:497
ParserOptions\newFromUser
static newFromUser( $user)
Get a ParserOptions object from a given user.
Definition: ParserOptions.php:375
Status\getWikiText
getWikiText( $shortContext=false, $longContext=false)
Get the error list as a wikitext formatted list.
Definition: Status.php:185
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=array(), $page='', $user='', $param=array())
Show log extract.
Definition: LogEventsList.php:507
$wgTitle
if(! $wgRequest->checkUrlExtension()) if(! $wgEnableAPI) $wgTitle
Definition: api.php:63