MediaWiki  1.23.16
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 
161  const AS_NO_CHANGE_CONTENT_MODEL = 235;
162 
166  const AS_PARSE_ERROR = 240;
167 
171  const EDITFORM_ID = 'editform';
172 
177  const POST_EDIT_COOKIE_KEY_PREFIX = 'PostEditRevision';
178 
191  const POST_EDIT_COOKIE_DURATION = 1200;
192 
196  var $mArticle;
197 
201  var $mTitle;
202  private $mContextTitle = null;
203  var $action = 'submit';
204  var $isConflict = false;
205  var $isCssJsSubpage = false;
206  var $isCssSubpage = false;
207  var $isJsSubpage = false;
208  var $isWrongCaseCssJsPage = false;
209  var $isNew = false; // new page or new section
210  var $deletedSinceEdit;
211  var $formtype;
212  var $firsttime;
213  var $lastDelete;
214  var $mTokenOk = false;
215  var $mTokenOkExceptSuffix = false;
216  var $mTriedSave = false;
217  var $incompleteForm = false;
218  var $tooBig = false;
219  var $kblength = false;
220  var $missingComment = false;
221  var $missingSummary = false;
222  var $allowBlankSummary = false;
223  var $autoSumm = '';
224  var $hookError = '';
225  #var $mPreviewTemplates;
226 
230  var $mParserOutput;
231 
236  var $hasPresetSummary = false;
237 
238  var $mBaseRevision = false;
239  var $mShowSummaryField = true;
240 
241  # Form values
242  var $save = false, $preview = false, $diff = false;
243  var $minoredit = false, $watchthis = false, $recreate = false;
244  var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
245  var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
246  var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
247  var $contentModel = null, $contentFormat = null;
248 
249  # Placeholders for text injection by hooks (must be HTML)
250  # extensions should take care to _append_ to the present value
251  public $editFormPageTop = ''; // Before even the preview
252  public $editFormTextTop = '';
253  public $editFormTextBeforeContent = '';
254  public $editFormTextAfterWarn = '';
255  public $editFormTextAfterTools = '';
256  public $editFormTextBottom = '';
257  public $editFormTextAfterContent = '';
258  public $previewTextAfterContent = '';
259  public $mPreloadContent = null;
260 
261  /* $didSave should be set to true whenever an article was successfully altered. */
262  public $didSave = false;
263  public $undidRev = 0;
264 
265  public $suppressIntro = false;
266 
272  public $allowNonTextContent = false;
273 
277  public function __construct( Article $article ) {
278  $this->mArticle = $article;
279  $this->mTitle = $article->getTitle();
280 
281  $this->contentModel = $this->mTitle->getContentModel();
282 
283  $handler = ContentHandler::getForModelID( $this->contentModel );
284  $this->contentFormat = $handler->getDefaultFormat();
285  }
286 
290  public function getArticle() {
291  return $this->mArticle;
292  }
293 
298  public function getTitle() {
299  return $this->mTitle;
300  }
301 
307  public function setContextTitle( $title ) {
308  $this->mContextTitle = $title;
309  }
310 
318  public function getContextTitle() {
319  if ( is_null( $this->mContextTitle ) ) {
321  return $wgTitle;
322  } else {
323  return $this->mContextTitle;
324  }
325  }
326 
334  public function isSupportedContentModel( $modelId ) {
335  return $this->allowNonTextContent ||
336  ContentHandler::getForModelID( $modelId ) instanceof TextContentHandler;
337  }
338 
339  function submit() {
340  $this->edit();
341  }
342 
354  function edit() {
355  global $wgOut, $wgRequest, $wgUser;
356  // Allow extensions to modify/prevent this form or submission
357  if ( !wfRunHooks( 'AlternateEdit', array( $this ) ) ) {
358  return;
359  }
360 
361  wfProfileIn( __METHOD__ );
362  wfDebug( __METHOD__ . ": enter\n" );
363 
364  // If they used redlink=1 and the page exists, redirect to the main article
365  if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
366  $wgOut->redirect( $this->mTitle->getFullURL() );
367  wfProfileOut( __METHOD__ );
368  return;
369  }
370 
371  $this->importFormData( $wgRequest );
372  $this->firsttime = false;
373 
374  if ( $this->live ) {
375  $this->livePreview();
376  wfProfileOut( __METHOD__ );
377  return;
378  }
379 
380  if ( wfReadOnly() && $this->save ) {
381  // Force preview
382  $this->save = false;
383  $this->preview = true;
384  }
385 
386  if ( $this->save ) {
387  $this->formtype = 'save';
388  } elseif ( $this->preview ) {
389  $this->formtype = 'preview';
390  } elseif ( $this->diff ) {
391  $this->formtype = 'diff';
392  } else { # First time through
393  $this->firsttime = true;
394  if ( $this->previewOnOpen() ) {
395  $this->formtype = 'preview';
396  } else {
397  $this->formtype = 'initial';
398  }
399  }
400 
401  $permErrors = $this->getEditPermissionErrors();
402  if ( $permErrors ) {
403  wfDebug( __METHOD__ . ": User can't edit\n" );
404  // Auto-block user's IP if the account was "hard" blocked
405  $wgUser->spreadAnyEditBlock();
406 
407  $this->displayPermissionsError( $permErrors );
408 
409  wfProfileOut( __METHOD__ );
410  return;
411  }
412 
413  wfProfileIn( __METHOD__ . "-business-end" );
414 
415  $this->isConflict = false;
416  // css / js subpages of user pages get a special treatment
417  $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
418  $this->isCssSubpage = $this->mTitle->isCssSubpage();
419  $this->isJsSubpage = $this->mTitle->isJsSubpage();
420  $this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
421 
422  # Show applicable editing introductions
423  if ( $this->formtype == 'initial' || $this->firsttime ) {
424  $this->showIntro();
425  }
426 
427  # Attempt submission here. This will check for edit conflicts,
428  # and redundantly check for locked database, blocked IPs, etc.
429  # that edit() already checked just in case someone tries to sneak
430  # in the back door with a hand-edited submission URL.
431 
432  if ( 'save' == $this->formtype ) {
433  if ( !$this->attemptSave() ) {
434  wfProfileOut( __METHOD__ . "-business-end" );
435  wfProfileOut( __METHOD__ );
436  return;
437  }
438  }
439 
440  # First time through: get contents, set time for conflict
441  # checking, etc.
442  if ( 'initial' == $this->formtype || $this->firsttime ) {
443  if ( $this->initialiseForm() === false ) {
444  $this->noSuchSectionPage();
445  wfProfileOut( __METHOD__ . "-business-end" );
446  wfProfileOut( __METHOD__ );
447  return;
448  }
449 
450  if ( !$this->mTitle->getArticleID() ) {
451  wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
452  } else {
453  wfRunHooks( 'EditFormInitialText', array( $this ) );
454  }
455 
456  }
457 
458  $this->showEditForm();
459  wfProfileOut( __METHOD__ . "-business-end" );
460  wfProfileOut( __METHOD__ );
461  }
462 
466  protected function getEditPermissionErrors() {
467  global $wgUser;
468  $permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
469  # Can this title be created?
470  if ( !$this->mTitle->exists() ) {
471  $permErrors = array_merge( $permErrors,
472  wfArrayDiff2( $this->mTitle->getUserPermissionsErrors( 'create', $wgUser ), $permErrors ) );
473  }
474  # Ignore some permissions errors when a user is just previewing/viewing diffs
475  $remove = array();
476  foreach ( $permErrors as $error ) {
477  if ( ( $this->preview || $this->diff )
478  && ( $error[0] == 'blockedtext' || $error[0] == 'autoblockedtext' )
479  ) {
480  $remove[] = $error;
481  }
482  }
483  $permErrors = wfArrayDiff2( $permErrors, $remove );
484  return $permErrors;
485  }
486 
500  protected function displayPermissionsError( array $permErrors ) {
501  global $wgRequest, $wgOut;
502 
503  if ( $wgRequest->getBool( 'redlink' ) ) {
504  // The edit page was reached via a red link.
505  // Redirect to the article page and let them click the edit tab if
506  // they really want a permission error.
507  $wgOut->redirect( $this->mTitle->getFullURL() );
508  return;
509  }
510 
511  $content = $this->getContentObject();
512 
513  # Use the normal message if there's nothing to display
514  if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
515  $action = $this->mTitle->exists() ? 'edit' :
516  ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
517  throw new PermissionsError( $action, $permErrors );
518  }
519 
520  $wgOut->setRobotPolicy( 'noindex,nofollow' );
521  $wgOut->setPageTitle( wfMessage( 'viewsource-title', $this->getContextTitle()->getPrefixedText() ) );
522  $wgOut->addBacklinkSubtitle( $this->getContextTitle() );
523  $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) );
524  $wgOut->addHTML( "<hr />\n" );
525 
526  # If the user made changes, preserve them when showing the markup
527  # (This happens when a user is blocked during edit, for instance)
528  if ( !$this->firsttime ) {
529  $text = $this->textbox1;
530  $wgOut->addWikiMsg( 'viewyourtext' );
531  } else {
532  $text = $this->toEditText( $content );
533  $wgOut->addWikiMsg( 'viewsourcetext' );
534  }
535 
536  $this->showTextbox( $text, 'wpTextbox1', array( 'readonly' ) );
537 
538  $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
539  Linker::formatTemplates( $this->getTemplates() ) ) );
540 
541  $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
542 
543  if ( $this->mTitle->exists() ) {
544  $wgOut->returnToMain( null, $this->mTitle );
545  }
546  }
547 
554  function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
555  wfDeprecated( __METHOD__, '1.19' );
556 
557  global $wgRequest, $wgOut;
558  if ( $wgRequest->getBool( 'redlink' ) ) {
559  // The edit page was reached via a red link.
560  // Redirect to the article page and let them click the edit tab if
561  // they really want a permission error.
562  $wgOut->redirect( $this->mTitle->getFullURL() );
563  } else {
564  $wgOut->readOnlyPage( $source, $protected, $reasons, $action );
565  }
566  }
567 
573  protected function previewOnOpen() {
574  global $wgRequest, $wgUser, $wgPreviewOnOpenNamespaces;
575  if ( $wgRequest->getVal( 'preview' ) == 'yes' ) {
576  // Explicit override from request
577  return true;
578  } elseif ( $wgRequest->getVal( 'preview' ) == 'no' ) {
579  // Explicit override from request
580  return false;
581  } elseif ( $this->section == 'new' ) {
582  // Nothing *to* preview for new sections
583  return false;
584  } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) {
585  // Standard preference behavior
586  return true;
587  } elseif ( !$this->mTitle->exists()
588  && isset( $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()] )
589  && $wgPreviewOnOpenNamespaces[$this->mTitle->getNamespace()]
590  ) {
591  // Categories are special
592  return true;
593  } else {
594  return false;
595  }
596  }
597 
604  protected function isWrongCaseCssJsPage() {
605  if ( $this->mTitle->isCssJsSubpage() ) {
606  $name = $this->mTitle->getSkinFromCssJsSubpage();
607  $skins = array_merge(
608  array_keys( Skin::getSkinNames() ),
609  array( 'common' )
610  );
611  return !in_array( $name, $skins )
612  && in_array( strtolower( $name ), $skins );
613  } else {
614  return false;
615  }
616  }
617 
625  protected function isSectionEditSupported() {
626  $contentHandler = ContentHandler::getForTitle( $this->mTitle );
627  return $contentHandler->supportsSections();
628  }
629 
635  function importFormData( &$request ) {
637 
638  wfProfileIn( __METHOD__ );
639 
640  # Section edit can come from either the form or a link
641  $this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
642 
643  if ( $this->section !== null && $this->section !== '' && !$this->isSectionEditSupported() ) {
644  wfProfileOut( __METHOD__ );
645  throw new ErrorPageError( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
646  }
647 
648  $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
649 
650  if ( $request->wasPosted() ) {
651  # These fields need to be checked for encoding.
652  # Also remove trailing whitespace, but don't remove _initial_
653  # whitespace from the text boxes. This may be significant formatting.
654  $this->textbox1 = $this->safeUnicodeInput( $request, 'wpTextbox1' );
655  if ( !$request->getCheck( 'wpTextbox2' ) ) {
656  // Skip this if wpTextbox2 has input, it indicates that we came
657  // from a conflict page with raw page text, not a custom form
658  // modified by subclasses
659  wfProfileIn( get_class( $this ) . "::importContentFormData" );
660  $textbox1 = $this->importContentFormData( $request );
661  if ( $textbox1 !== null ) {
662  $this->textbox1 = $textbox1;
663  }
664 
665  wfProfileOut( get_class( $this ) . "::importContentFormData" );
666  }
667 
668  # Truncate for whole multibyte characters
669  $this->summary = $wgContLang->truncate( $request->getText( 'wpSummary' ), 255 );
670 
671  # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
672  # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
673  # section titles.
674  $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
675 
676  # Treat sectiontitle the same way as summary.
677  # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
678  # currently doing double duty as both edit summary and section title. Right now this
679  # is just to allow API edits to work around this limitation, but this should be
680  # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
681  $this->sectiontitle = $wgContLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
682  $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
683 
684  $this->edittime = $request->getVal( 'wpEdittime' );
685  $this->starttime = $request->getVal( 'wpStarttime' );
686 
687  $undidRev = $request->getInt( 'wpUndidRevision' );
688  if ( $undidRev ) {
689  $this->undidRev = $undidRev;
690  }
691 
692  $this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
693 
694  if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
695  // wpTextbox1 field is missing, possibly due to being "too big"
696  // according to some filter rules such as Suhosin's setting for
697  // suhosin.request.max_value_length (d'oh)
698  $this->incompleteForm = true;
699  } else {
700  // edittime should be one of our last fields; if it's missing,
701  // the submission probably broke somewhere in the middle.
702  $this->incompleteForm = is_null( $this->edittime );
703  }
704  if ( $this->incompleteForm ) {
705  # If the form is incomplete, force to preview.
706  wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
707  wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
708  $this->preview = true;
709  } else {
710  /* Fallback for live preview */
711  $this->preview = $request->getCheck( 'wpPreview' ) || $request->getCheck( 'wpLivePreview' );
712  $this->diff = $request->getCheck( 'wpDiff' );
713 
714  // Remember whether a save was requested, so we can indicate
715  // if we forced preview due to session failure.
716  $this->mTriedSave = !$this->preview;
717 
718  if ( $this->tokenOk( $request ) ) {
719  # Some browsers will not report any submit button
720  # if the user hits enter in the comment box.
721  # The unmarked state will be assumed to be a save,
722  # if the form seems otherwise complete.
723  wfDebug( __METHOD__ . ": Passed token check.\n" );
724  } elseif ( $this->diff ) {
725  # Failed token check, but only requested "Show Changes".
726  wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
727  } else {
728  # Page might be a hack attempt posted from
729  # an external site. Preview instead of saving.
730  wfDebug( __METHOD__ . ": Failed token check; forcing preview\n" );
731  $this->preview = true;
732  }
733  }
734  $this->save = !$this->preview && !$this->diff;
735  if ( !preg_match( '/^\d{14}$/', $this->edittime ) ) {
736  $this->edittime = null;
737  }
738 
739  if ( !preg_match( '/^\d{14}$/', $this->starttime ) ) {
740  $this->starttime = null;
741  }
742 
743  $this->recreate = $request->getCheck( 'wpRecreate' );
744 
745  $this->minoredit = $request->getCheck( 'wpMinoredit' );
746  $this->watchthis = $request->getCheck( 'wpWatchthis' );
747 
748  # Don't force edit summaries when a user is editing their own user or talk page
749  if ( ( $this->mTitle->mNamespace == NS_USER || $this->mTitle->mNamespace == NS_USER_TALK )
750  && $this->mTitle->getText() == $wgUser->getName()
751  ) {
752  $this->allowBlankSummary = true;
753  } else {
754  $this->allowBlankSummary = $request->getBool( 'wpIgnoreBlankSummary' ) || !$wgUser->getOption( 'forceeditsummary' );
755  }
756 
757  $this->autoSumm = $request->getText( 'wpAutoSummary' );
758  } else {
759  # Not a posted form? Start with nothing.
760  wfDebug( __METHOD__ . ": Not a posted form.\n" );
761  $this->textbox1 = '';
762  $this->summary = '';
763  $this->sectiontitle = '';
764  $this->edittime = '';
765  $this->starttime = wfTimestampNow();
766  $this->edit = false;
767  $this->preview = false;
768  $this->save = false;
769  $this->diff = false;
770  $this->minoredit = false;
771  $this->watchthis = $request->getBool( 'watchthis', false ); // Watch may be overridden by request parameters
772  $this->recreate = false;
773 
774  // When creating a new section, we can preload a section title by passing it as the
775  // preloadtitle parameter in the URL (Bug 13100)
776  if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
777  $this->sectiontitle = $request->getVal( 'preloadtitle' );
778  // Once wpSummary isn't being use for setting section titles, we should delete this.
779  $this->summary = $request->getVal( 'preloadtitle' );
780  } elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
781  $this->summary = $request->getText( 'summary' );
782  if ( $this->summary !== '' ) {
783  $this->hasPresetSummary = true;
784  }
785  }
786 
787  if ( $request->getVal( 'minor' ) ) {
788  $this->minoredit = true;
789  }
790  }
791 
792  $this->oldid = $request->getInt( 'oldid' );
793 
794  $this->bot = $request->getBool( 'bot', true );
795  $this->nosummary = $request->getBool( 'nosummary' );
796 
797  $this->contentModel = $request->getText( 'model', $this->contentModel ); #may be overridden by revision
798  $this->contentFormat = $request->getText( 'format', $this->contentFormat ); #may be overridden by revision
799 
800  if ( !ContentHandler::getForModelID( $this->contentModel )->isSupportedFormat( $this->contentFormat ) ) {
801  throw new ErrorPageError(
802  'editpage-notsupportedcontentformat-title',
803  'editpage-notsupportedcontentformat-text',
804  array(
805  wfEscapeWikiText( $this->contentFormat ),
806  wfEscapeWikiText( ContentHandler::getLocalizedName( $this->contentModel ) )
807  )
808  );
809  }
810  #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
811 
812  $this->live = $request->getCheck( 'live' );
813  $this->editintro = $request->getText( 'editintro',
814  // Custom edit intro for new sections
815  $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
816 
817  // Allow extensions to modify form data
818  wfRunHooks( 'EditPage::importFormData', array( $this, $request ) );
819 
820  wfProfileOut( __METHOD__ );
821  }
822 
831  protected function importContentFormData( &$request ) {
832  return; // Don't do anything, EditPage already extracted wpTextbox1
833  }
834 
840  function initialiseForm() {
841  global $wgUser;
842  $this->edittime = $this->mArticle->getTimestamp();
843 
844  $content = $this->getContentObject( false ); #TODO: track content object?!
845  if ( $content === false ) {
846  return false;
847  }
848  $this->textbox1 = $this->toEditText( $content );
849 
850  // activate checkboxes if user wants them to be always active
851  # Sort out the "watch" checkbox
852  if ( $wgUser->getOption( 'watchdefault' ) ) {
853  # Watch all edits
854  $this->watchthis = true;
855  } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
856  # Watch creations
857  $this->watchthis = true;
858  } elseif ( $wgUser->isWatched( $this->mTitle ) ) {
859  # Already watched
860  $this->watchthis = true;
861  }
862  if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
863  $this->minoredit = true;
864  }
865  if ( $this->textbox1 === false ) {
866  return false;
867  }
868  return true;
869  }
870 
879  function getContent( $def_text = false ) {
880  ContentHandler::deprecated( __METHOD__, '1.21' );
881 
882  if ( $def_text !== null && $def_text !== false && $def_text !== '' ) {
883  $def_content = $this->toEditContent( $def_text );
884  } else {
885  $def_content = false;
886  }
887 
888  $content = $this->getContentObject( $def_content );
889 
890  // Note: EditPage should only be used with text based content anyway.
891  return $this->toEditText( $content );
892  }
893 
901  protected function getContentObject( $def_content = null ) {
902  global $wgOut, $wgRequest, $wgUser, $wgContLang;
903 
904  wfProfileIn( __METHOD__ );
905 
906  $content = false;
907 
908  // For message page not locally set, use the i18n message.
909  // For other non-existent articles, use preload text if any.
910  if ( !$this->mTitle->exists() || $this->section == 'new' ) {
911  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
912  # If this is a system message, get the default text.
913  $msg = $this->mTitle->getDefaultMessageText();
914 
915  $content = $this->toEditContent( $msg );
916  }
917  if ( $content === false ) {
918  # If requested, preload some text.
919  $preload = $wgRequest->getVal( 'preload',
920  // Custom preload text for new sections
921  $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
922  $params = $wgRequest->getArray( 'preloadparams', array() );
923 
924  $content = $this->getPreloadedContent( $preload, $params );
925  }
926  // For existing pages, get text based on "undo" or section parameters.
927  } else {
928  if ( $this->section != '' ) {
929  // Get section edit text (returns $def_text for invalid sections)
930  $orig = $this->getOriginalContent( $wgUser );
931  $content = $orig ? $orig->getSection( $this->section ) : null;
932 
933  if ( !$content ) {
934  $content = $def_content;
935  }
936  } else {
937  $undoafter = $wgRequest->getInt( 'undoafter' );
938  $undo = $wgRequest->getInt( 'undo' );
939 
940  if ( $undo > 0 && $undoafter > 0 ) {
941 
942  $undorev = Revision::newFromId( $undo );
943  $oldrev = Revision::newFromId( $undoafter );
944 
945  # Sanity check, make sure it's the right page,
946  # the revisions exist and they were not deleted.
947  # Otherwise, $content will be left as-is.
948  if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
949  !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
950  !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
951 
952  $content = $this->mArticle->getUndoContent( $undorev, $oldrev );
953 
954  if ( $content === false ) {
955  # Warn the user that something went wrong
956  $undoMsg = 'failure';
957  } else {
958  $oldContent = $this->mArticle->getPage()->getContent( Revision::RAW );
960  $newContent = $content->preSaveTransform( $this->mTitle, $wgUser, $popts );
961 
962  if ( $newContent->equals( $oldContent ) ) {
963  # Tell the user that the undo results in no change,
964  # i.e. the revisions were already undone.
965  $undoMsg = 'nochange';
966  $content = false;
967  } else {
968  # Inform the user of our success and set an automatic edit summary
969  $undoMsg = 'success';
970 
971  # If we just undid one rev, use an autosummary
972  $firstrev = $oldrev->getNext();
973  if ( $firstrev && $firstrev->getId() == $undo ) {
974  $userText = $undorev->getUserText();
975  if ( $userText === '' ) {
976  $undoSummary = wfMessage(
977  'undo-summary-username-hidden',
978  $undo
979  )->inContentLanguage()->text();
980  } else {
981  $undoSummary = wfMessage(
982  'undo-summary',
983  $undo,
984  $userText
985  )->inContentLanguage()->text();
986  }
987  if ( $this->summary === '' ) {
988  $this->summary = $undoSummary;
989  } else {
990  $this->summary = $undoSummary . wfMessage( 'colon-separator' )
991  ->inContentLanguage()->text() . $this->summary;
992  }
993  $this->undidRev = $undo;
994  }
995  $this->formtype = 'diff';
996  }
997  }
998  } else {
999  // Failed basic sanity checks.
1000  // Older revisions may have been removed since the link
1001  // was created, or we may simply have got bogus input.
1002  $undoMsg = 'norev';
1003  }
1004 
1005  // Messages: undo-success, undo-failure, undo-norev, undo-nochange
1006  $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
1007  $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
1008  wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
1009  }
1010 
1011  if ( $content === false ) {
1012  $content = $this->getOriginalContent( $wgUser );
1013  }
1014  }
1015  }
1016 
1017  wfProfileOut( __METHOD__ );
1018  return $content;
1019  }
1020 
1036  private function getOriginalContent( User $user ) {
1037  if ( $this->section == 'new' ) {
1038  return $this->getCurrentContent();
1039  }
1040  $revision = $this->mArticle->getRevisionFetched();
1041  if ( $revision === null ) {
1042  if ( !$this->contentModel ) {
1043  $this->contentModel = $this->getTitle()->getContentModel();
1044  }
1045  $handler = ContentHandler::getForModelID( $this->contentModel );
1046 
1047  return $handler->makeEmptyContent();
1048  }
1049  $content = $revision->getContent( Revision::FOR_THIS_USER, $user );
1050  return $content;
1051  }
1052 
1061  protected function getCurrentContent() {
1062  $rev = $this->mArticle->getRevision();
1063  $content = $rev ? $rev->getContent( Revision::RAW ) : null;
1064 
1065  if ( $content === false || $content === null ) {
1066  if ( !$this->contentModel ) {
1067  $this->contentModel = $this->getTitle()->getContentModel();
1068  }
1069  $handler = ContentHandler::getForModelID( $this->contentModel );
1070 
1071  return $handler->makeEmptyContent();
1072  } else {
1073  # nasty side-effect, but needed for consistency
1074  $this->contentModel = $rev->getContentModel();
1075  $this->contentFormat = $rev->getContentFormat();
1076 
1077  return $content;
1078  }
1079  }
1080 
1087  public function setPreloadedText( $text ) {
1088  ContentHandler::deprecated( __METHOD__, "1.21" );
1089 
1090  $content = $this->toEditContent( $text );
1091 
1092  $this->setPreloadedContent( $content );
1093  }
1094 
1102  public function setPreloadedContent( Content $content ) {
1103  $this->mPreloadContent = $content;
1104  }
1105 
1116  protected function getPreloadedText( $preload ) {
1117  ContentHandler::deprecated( __METHOD__, "1.21" );
1118 
1119  $content = $this->getPreloadedContent( $preload );
1120  $text = $this->toEditText( $content );
1121 
1122  return $text;
1123  }
1124 
1136  protected function getPreloadedContent( $preload, $params = array() ) {
1137  global $wgUser;
1138 
1139  if ( !empty( $this->mPreloadContent ) ) {
1140  return $this->mPreloadContent;
1141  }
1142 
1143  $handler = ContentHandler::getForTitle( $this->getTitle() );
1144 
1145  if ( $preload === '' ) {
1146  return $handler->makeEmptyContent();
1147  }
1148 
1149  $title = Title::newFromText( $preload );
1150  # Check for existence to avoid getting MediaWiki:Noarticletext
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 
1156  $page = WikiPage::factory( $title );
1157  if ( $page->isRedirect() ) {
1158  $title = $page->getRedirectTarget();
1159  # Same as before
1160  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
1161  //TODO: somehow show a warning to the user!
1162  return $handler->makeEmptyContent();
1163  }
1164  $page = WikiPage::factory( $title );
1165  }
1166 
1167  $parserOptions = ParserOptions::newFromUser( $wgUser );
1168  $content = $page->getContent( Revision::RAW );
1169 
1170  if ( !$content ) {
1171  //TODO: somehow show a warning to the user!
1172  return $handler->makeEmptyContent();
1173  }
1174 
1175  if ( $content->getModel() !== $handler->getModelID() ) {
1176  $converted = $content->convert( $handler->getModelID() );
1177 
1178  if ( !$converted ) {
1179  //TODO: somehow show a warning to the user!
1180  wfDebug( "Attempt to preload incompatible content: "
1181  . "can't convert " . $content->getModel()
1182  . " to " . $handler->getModelID() );
1183 
1184  return $handler->makeEmptyContent();
1185  }
1186 
1187  $content = $converted;
1188  }
1189 
1190  return $content->preloadTransform( $title, $parserOptions, $params );
1191  }
1192 
1200  function tokenOk( &$request ) {
1201  global $wgUser;
1202  $token = $request->getVal( 'wpEditToken' );
1203  $this->mTokenOk = $wgUser->matchEditToken( $token );
1204  $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
1205  return $this->mTokenOk;
1206  }
1207 
1224  protected function setPostEditCookie() {
1225  $revisionId = $this->mArticle->getLatest();
1226  $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1227 
1228  $response = RequestContext::getMain()->getRequest()->response();
1229  $response->setcookie( $postEditKey, '1', time() + self::POST_EDIT_COOKIE_DURATION, array(
1230  'path' => '/',
1231  'httpOnly' => false,
1232  ) );
1233  }
1234 
1240  public function attemptSave() {
1241  global $wgUser;
1242 
1243  $resultDetails = false;
1244  # Allow bots to exempt some edits from bot flagging
1245  $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
1246  $status = $this->internalAttemptSave( $resultDetails, $bot );
1247 
1248  return $this->handleStatus( $status, $resultDetails );
1249  }
1250 
1260  private function handleStatus( Status $status, $resultDetails ) {
1262 
1263  // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
1264  if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
1265  $this->didSave = true;
1266  if ( !$resultDetails['nullEdit'] ) {
1267  $this->setPostEditCookie();
1268  }
1269  }
1270 
1271  switch ( $status->value ) {
1272  case self::AS_HOOK_ERROR_EXPECTED:
1273  case self::AS_CONTENT_TOO_BIG:
1274  case self::AS_ARTICLE_WAS_DELETED:
1275  case self::AS_CONFLICT_DETECTED:
1276  case self::AS_SUMMARY_NEEDED:
1277  case self::AS_TEXTBOX_EMPTY:
1278  case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
1279  case self::AS_END:
1280  return true;
1281 
1282  case self::AS_HOOK_ERROR:
1283  return false;
1284 
1285  case self::AS_PARSE_ERROR:
1286  $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>' );
1287  return true;
1288 
1289  case self::AS_SUCCESS_NEW_ARTICLE:
1290  $query = $resultDetails['redirect'] ? 'redirect=no' : '';
1291  $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
1292  $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
1293  return false;
1294 
1295  case self::AS_SUCCESS_UPDATE:
1296  $extraQuery = '';
1297  $sectionanchor = $resultDetails['sectionanchor'];
1298 
1299  // Give extensions a chance to modify URL query on update
1300  wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this->mArticle, &$sectionanchor, &$extraQuery ) );
1301 
1302  if ( $resultDetails['redirect'] ) {
1303  if ( $extraQuery == '' ) {
1304  $extraQuery = 'redirect=no';
1305  } else {
1306  $extraQuery = 'redirect=no&' . $extraQuery;
1307  }
1308  }
1309  $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1310  return false;
1311 
1312  case self::AS_BLANK_ARTICLE:
1313  $wgOut->redirect( $this->getContextTitle()->getFullURL() );
1314  return false;
1315 
1316  case self::AS_SPAM_ERROR:
1317  $this->spamPageWithContent( $resultDetails['spam'] );
1318  return false;
1319 
1320  case self::AS_BLOCKED_PAGE_FOR_USER:
1321  throw new UserBlockedError( $wgUser->getBlock() );
1322 
1323  case self::AS_IMAGE_REDIRECT_ANON:
1324  case self::AS_IMAGE_REDIRECT_LOGGED:
1325  throw new PermissionsError( 'upload' );
1326 
1327  case self::AS_READ_ONLY_PAGE_ANON:
1328  case self::AS_READ_ONLY_PAGE_LOGGED:
1329  throw new PermissionsError( 'edit' );
1330 
1331  case self::AS_READ_ONLY_PAGE:
1332  throw new ReadOnlyError;
1333 
1334  case self::AS_RATE_LIMITED:
1335  throw new ThrottledError();
1336 
1337  case self::AS_NO_CREATE_PERMISSION:
1338  $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
1339  throw new PermissionsError( $permission );
1340 
1341  case self::AS_NO_CHANGE_CONTENT_MODEL:
1342  throw new PermissionsError( 'editcontentmodel' );
1343 
1344  default:
1345  // We don't recognize $status->value. The only way that can happen
1346  // is if an extension hook aborted from inside ArticleSave.
1347  // Render the status object into $this->hookError
1348  // FIXME this sucks, we should just use the Status object throughout
1349  $this->hookError = '<div class="error">' . $status->getWikitext() .
1350  '</div>';
1351  return true;
1352  }
1353  }
1354 
1364  protected function runPostMergeFilters( Content $content, Status $status, User $user ) {
1365  // Run old style post-section-merge edit filter
1366  if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged',
1367  array( $this, $content, &$this->hookError, $this->summary ) ) ) {
1368 
1369  # Error messages etc. could be handled within the hook...
1370  $status->fatal( 'hookaborted' );
1371  $status->value = self::AS_HOOK_ERROR;
1372  return false;
1373  } elseif ( $this->hookError != '' ) {
1374  # ...or the hook could be expecting us to produce an error
1375  $status->fatal( 'hookaborted' );
1376  $status->value = self::AS_HOOK_ERROR_EXPECTED;
1377  return false;
1378  }
1379 
1380  // Run new style post-section-merge edit filter
1381  if ( !wfRunHooks( 'EditFilterMergedContent',
1382  array( $this->mArticle->getContext(), $content, $status, $this->summary,
1383  $user, $this->minoredit ) ) ) {
1384 
1385  # Error messages etc. could be handled within the hook...
1386  // XXX: $status->value may already be something informative...
1387  $this->hookError = $status->getWikiText();
1388  $status->fatal( 'hookaborted' );
1389  $status->value = self::AS_HOOK_ERROR;
1390  return false;
1391  } elseif ( !$status->isOK() ) {
1392  # ...or the hook could be expecting us to produce an error
1393  // FIXME this sucks, we should just use the Status object throughout
1394  $this->hookError = $status->getWikiText();
1395  $status->fatal( 'hookaborted' );
1396  $status->value = self::AS_HOOK_ERROR_EXPECTED;
1397  return false;
1398  }
1399 
1400  return true;
1401  }
1402 
1419  function internalAttemptSave( &$result, $bot = false ) {
1420  global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize;
1421 
1422  $status = Status::newGood();
1423 
1424  wfProfileIn( __METHOD__ );
1425  wfProfileIn( __METHOD__ . '-checks' );
1426 
1427  if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
1428  wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
1429  $status->fatal( 'hookaborted' );
1430  $status->value = self::AS_HOOK_ERROR;
1431  wfProfileOut( __METHOD__ . '-checks' );
1432  wfProfileOut( __METHOD__ );
1433  return $status;
1434  }
1435 
1436  $spam = $wgRequest->getText( 'wpAntispam' );
1437  if ( $spam !== '' ) {
1438  wfDebugLog(
1439  'SimpleAntiSpam',
1440  $wgUser->getName() .
1441  ' editing "' .
1442  $this->mTitle->getPrefixedText() .
1443  '" submitted bogus field "' .
1444  $spam .
1445  '"'
1446  );
1447  $status->fatal( 'spamprotectionmatch', false );
1448  $status->value = self::AS_SPAM_ERROR;
1449  wfProfileOut( __METHOD__ . '-checks' );
1450  wfProfileOut( __METHOD__ );
1451  return $status;
1452  }
1453 
1454  try {
1455  # Construct Content object
1456  $textbox_content = $this->toEditContent( $this->textbox1 );
1457  } catch ( MWContentSerializationException $ex ) {
1458  $status->fatal( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
1459  $status->value = self::AS_PARSE_ERROR;
1460  wfProfileOut( __METHOD__ . '-checks' );
1461  wfProfileOut( __METHOD__ );
1462  return $status;
1463  }
1464 
1465  # Check image redirect
1466  if ( $this->mTitle->getNamespace() == NS_FILE &&
1467  $textbox_content->isRedirect() &&
1468  !$wgUser->isAllowed( 'upload' ) ) {
1469  $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
1470  $status->setResult( false, $code );
1471 
1472  wfProfileOut( __METHOD__ . '-checks' );
1473  wfProfileOut( __METHOD__ );
1474 
1475  return $status;
1476  }
1477 
1478  # Check for spam
1479  $match = self::matchSummarySpamRegex( $this->summary );
1480  if ( $match === false && $this->section == 'new' ) {
1481  # $wgSpamRegex is enforced on this new heading/summary because, unlike
1482  # regular summaries, it is added to the actual wikitext.
1483  if ( $this->sectiontitle !== '' ) {
1484  # This branch is taken when the API is used with the 'sectiontitle' parameter.
1485  $match = self::matchSpamRegex( $this->sectiontitle );
1486  } else {
1487  # This branch is taken when the "Add Topic" user interface is used, or the API
1488  # is used with the 'summary' parameter.
1489  $match = self::matchSpamRegex( $this->summary );
1490  }
1491  }
1492  if ( $match === false ) {
1493  $match = self::matchSpamRegex( $this->textbox1 );
1494  }
1495  if ( $match !== false ) {
1496  $result['spam'] = $match;
1497  $ip = $wgRequest->getIP();
1498  $pdbk = $this->mTitle->getPrefixedDBkey();
1499  $match = str_replace( "\n", '', $match );
1500  wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
1501  $status->fatal( 'spamprotectionmatch', $match );
1502  $status->value = self::AS_SPAM_ERROR;
1503  wfProfileOut( __METHOD__ . '-checks' );
1504  wfProfileOut( __METHOD__ );
1505  return $status;
1506  }
1507  if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) {
1508  # Error messages etc. could be handled within the hook...
1509  $status->fatal( 'hookaborted' );
1510  $status->value = self::AS_HOOK_ERROR;
1511  wfProfileOut( __METHOD__ . '-checks' );
1512  wfProfileOut( __METHOD__ );
1513  return $status;
1514  } elseif ( $this->hookError != '' ) {
1515  # ...or the hook could be expecting us to produce an error
1516  $status->fatal( 'hookaborted' );
1517  $status->value = self::AS_HOOK_ERROR_EXPECTED;
1518  wfProfileOut( __METHOD__ . '-checks' );
1519  wfProfileOut( __METHOD__ );
1520  return $status;
1521  }
1522 
1523  if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) {
1524  // Auto-block user's IP if the account was "hard" blocked
1525  $wgUser->spreadAnyEditBlock();
1526  # Check block state against master, thus 'false'.
1527  $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
1528  wfProfileOut( __METHOD__ . '-checks' );
1529  wfProfileOut( __METHOD__ );
1530  return $status;
1531  }
1532 
1533  $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
1534  if ( $this->kblength > $wgMaxArticleSize ) {
1535  // Error will be displayed by showEditForm()
1536  $this->tooBig = true;
1537  $status->setResult( false, self::AS_CONTENT_TOO_BIG );
1538  wfProfileOut( __METHOD__ . '-checks' );
1539  wfProfileOut( __METHOD__ );
1540  return $status;
1541  }
1542 
1543  if ( !$wgUser->isAllowed( 'edit' ) ) {
1544  if ( $wgUser->isAnon() ) {
1545  $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
1546  wfProfileOut( __METHOD__ . '-checks' );
1547  wfProfileOut( __METHOD__ );
1548  return $status;
1549  } else {
1550  $status->fatal( 'readonlytext' );
1551  $status->value = self::AS_READ_ONLY_PAGE_LOGGED;
1552  wfProfileOut( __METHOD__ . '-checks' );
1553  wfProfileOut( __METHOD__ );
1554  return $status;
1555  }
1556  }
1557 
1558  if ( $this->contentModel !== $this->mTitle->getContentModel()
1559  && !$wgUser->isAllowed( 'editcontentmodel' )
1560  ) {
1561  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
1562  wfProfileOut( __METHOD__ . '-checks' );
1563  wfProfileOut( __METHOD__ );
1564  return $status;
1565  }
1566 
1567  if ( wfReadOnly() ) {
1568  $status->fatal( 'readonlytext' );
1569  $status->value = self::AS_READ_ONLY_PAGE;
1570  wfProfileOut( __METHOD__ . '-checks' );
1571  wfProfileOut( __METHOD__ );
1572  return $status;
1573  }
1574  if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 ) ) {
1575  $status->fatal( 'actionthrottledtext' );
1576  $status->value = self::AS_RATE_LIMITED;
1577  wfProfileOut( __METHOD__ . '-checks' );
1578  wfProfileOut( __METHOD__ );
1579  return $status;
1580  }
1581 
1582  # If the article has been deleted while editing, don't save it without
1583  # confirmation
1584  if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
1585  $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
1586  wfProfileOut( __METHOD__ . '-checks' );
1587  wfProfileOut( __METHOD__ );
1588  return $status;
1589  }
1590 
1591  wfProfileOut( __METHOD__ . '-checks' );
1592 
1593  # Load the page data from the master. If anything changes in the meantime,
1594  # we detect it by using page_latest like a token in a 1 try compare-and-swap.
1595  $this->mArticle->loadPageData( 'fromdbmaster' );
1596  $new = !$this->mArticle->exists();
1597 
1598  if ( $new ) {
1599  // Late check for create permission, just in case *PARANOIA*
1600  if ( !$this->mTitle->userCan( 'create', $wgUser ) ) {
1601  $status->fatal( 'nocreatetext' );
1602  $status->value = self::AS_NO_CREATE_PERMISSION;
1603  wfDebug( __METHOD__ . ": no create permission\n" );
1604  wfProfileOut( __METHOD__ );
1605  return $status;
1606  }
1607 
1608  // Don't save a new page if it's blank or if it's a MediaWiki:
1609  // message with content equivalent to default (allow empty pages
1610  // in this case to disable messages, see bug 50124)
1611  $defaultMessageText = $this->mTitle->getDefaultMessageText();
1612  if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false ) {
1613  $defaultText = $defaultMessageText;
1614  } else {
1615  $defaultText = '';
1616  }
1617 
1618  if ( $this->textbox1 === $defaultText ) {
1619  $status->setResult( false, self::AS_BLANK_ARTICLE );
1620  wfProfileOut( __METHOD__ );
1621  return $status;
1622  }
1623 
1624  if ( !$this->runPostMergeFilters( $textbox_content, $status, $wgUser ) ) {
1625  wfProfileOut( __METHOD__ );
1626  return $status;
1627  }
1628 
1629  $content = $textbox_content;
1630 
1631  $result['sectionanchor'] = '';
1632  if ( $this->section == 'new' ) {
1633  if ( $this->sectiontitle !== '' ) {
1634  // Insert the section title above the content.
1635  $content = $content->addSectionHeader( $this->sectiontitle );
1636 
1637  // Jump to the new section
1638  $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
1639 
1640  // If no edit summary was specified, create one automatically from the section
1641  // title and have it link to the new section. Otherwise, respect the summary as
1642  // passed.
1643  if ( $this->summary === '' ) {
1644  $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
1645  $this->summary = wfMessage( 'newsectionsummary' )
1646  ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1647  }
1648  } elseif ( $this->summary !== '' ) {
1649  // Insert the section title above the content.
1650  $content = $content->addSectionHeader( $this->summary );
1651 
1652  // Jump to the new section
1653  $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
1654 
1655  // Create a link to the new section from the edit summary.
1656  $cleanSummary = $wgParser->stripSectionName( $this->summary );
1657  $this->summary = wfMessage( 'newsectionsummary' )
1658  ->rawParams( $cleanSummary )->inContentLanguage()->text();
1659  }
1660  }
1661 
1662  $status->value = self::AS_SUCCESS_NEW_ARTICLE;
1663 
1664  } else { # not $new
1665 
1666  # Article exists. Check for edit conflict.
1667 
1668  $this->mArticle->clear(); # Force reload of dates, etc.
1669  $timestamp = $this->mArticle->getTimestamp();
1670 
1671  wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
1672 
1673  if ( $timestamp != $this->edittime ) {
1674  $this->isConflict = true;
1675  if ( $this->section == 'new' ) {
1676  if ( $this->mArticle->getUserText() == $wgUser->getName() &&
1677  $this->mArticle->getComment() == $this->summary ) {
1678  // Probably a duplicate submission of a new comment.
1679  // This can happen when squid resends a request after
1680  // a timeout but the first one actually went through.
1681  wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
1682  } else {
1683  // New comment; suppress conflict.
1684  $this->isConflict = false;
1685  wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
1686  }
1687  } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER, $this->mTitle->getArticleID(),
1688  $wgUser->getId(), $this->edittime ) ) {
1689  # Suppress edit conflict with self, except for section edits where merging is required.
1690  wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
1691  $this->isConflict = false;
1692  }
1693  }
1694 
1695  // If sectiontitle is set, use it, otherwise use the summary as the section title.
1696  if ( $this->sectiontitle !== '' ) {
1697  $sectionTitle = $this->sectiontitle;
1698  } else {
1699  $sectionTitle = $this->summary;
1700  }
1701 
1702  $content = null;
1703 
1704  if ( $this->isConflict ) {
1705  wfDebug( __METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
1706  . " (article time '{$timestamp}')\n" );
1707 
1708  $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime );
1709  } else {
1710  wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
1711  $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
1712  }
1713 
1714  if ( is_null( $content ) ) {
1715  wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
1716  $this->isConflict = true;
1717  $content = $textbox_content; // do not try to merge here!
1718  } elseif ( $this->isConflict ) {
1719  # Attempt merge
1720  if ( $this->mergeChangesIntoContent( $content ) ) {
1721  // Successful merge! Maybe we should tell the user the good news?
1722  $this->isConflict = false;
1723  wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
1724  } else {
1725  $this->section = '';
1726  $this->textbox1 = ContentHandler::getContentText( $content );
1727  wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
1728  }
1729  }
1730 
1731  if ( $this->isConflict ) {
1732  $status->setResult( false, self::AS_CONFLICT_DETECTED );
1733  wfProfileOut( __METHOD__ );
1734  return $status;
1735  }
1736 
1737  if ( !$this->runPostMergeFilters( $content, $status, $wgUser ) ) {
1738  wfProfileOut( __METHOD__ );
1739  return $status;
1740  }
1741 
1742  if ( $this->section == 'new' ) {
1743  // Handle the user preference to force summaries here
1744  if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) {
1745  $this->missingSummary = true;
1746  $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
1747  $status->value = self::AS_SUMMARY_NEEDED;
1748  wfProfileOut( __METHOD__ );
1749  return $status;
1750  }
1751 
1752  // Do not allow the user to post an empty comment
1753  if ( $this->textbox1 == '' ) {
1754  $this->missingComment = true;
1755  $status->fatal( 'missingcommenttext' );
1756  $status->value = self::AS_TEXTBOX_EMPTY;
1757  wfProfileOut( __METHOD__ );
1758  return $status;
1759  }
1760  } elseif ( !$this->allowBlankSummary
1761  && !$content->equals( $this->getOriginalContent( $wgUser ) )
1762  && !$content->isRedirect()
1763  && md5( $this->summary ) == $this->autoSumm
1764  ) {
1765  $this->missingSummary = true;
1766  $status->fatal( 'missingsummary' );
1767  $status->value = self::AS_SUMMARY_NEEDED;
1768  wfProfileOut( __METHOD__ );
1769  return $status;
1770  }
1771 
1772  # All's well
1773  wfProfileIn( __METHOD__ . '-sectionanchor' );
1774  $sectionanchor = '';
1775  if ( $this->section == 'new' ) {
1776  if ( $this->sectiontitle !== '' ) {
1777  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
1778  // If no edit summary was specified, create one automatically from the section
1779  // title and have it link to the new section. Otherwise, respect the summary as
1780  // passed.
1781  if ( $this->summary === '' ) {
1782  $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
1783  $this->summary = wfMessage( 'newsectionsummary' )
1784  ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1785  }
1786  } elseif ( $this->summary !== '' ) {
1787  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
1788  # This is a new section, so create a link to the new section
1789  # in the revision summary.
1790  $cleanSummary = $wgParser->stripSectionName( $this->summary );
1791  $this->summary = wfMessage( 'newsectionsummary' )
1792  ->rawParams( $cleanSummary )->inContentLanguage()->text();
1793  }
1794  } elseif ( $this->section != '' ) {
1795  # Try to get a section anchor from the section source, redirect to edited section if header found
1796  # XXX: might be better to integrate this into Article::replaceSection
1797  # for duplicate heading checking and maybe parsing
1798  $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
1799  # we can't deal with anchors, includes, html etc in the header for now,
1800  # headline would need to be parsed to improve this
1801  if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
1802  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
1803  }
1804  }
1805  $result['sectionanchor'] = $sectionanchor;
1806  wfProfileOut( __METHOD__ . '-sectionanchor' );
1807 
1808  // Save errors may fall down to the edit form, but we've now
1809  // merged the section into full text. Clear the section field
1810  // so that later submission of conflict forms won't try to
1811  // replace that into a duplicated mess.
1812  $this->textbox1 = $this->toEditText( $content );
1813  $this->section = '';
1814 
1815  $status->value = self::AS_SUCCESS_UPDATE;
1816  }
1817 
1818  // Check for length errors again now that the section is merged in
1819  $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
1820  if ( $this->kblength > $wgMaxArticleSize ) {
1821  $this->tooBig = true;
1822  $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
1823  wfProfileOut( __METHOD__ );
1824  return $status;
1825  }
1826 
1828  ( $new ? EDIT_NEW : EDIT_UPDATE ) |
1829  ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
1830  ( $bot ? EDIT_FORCE_BOT : 0 );
1831 
1832  $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary, $flags,
1833  false, null, $this->contentFormat );
1834 
1835  if ( !$doEditStatus->isOK() ) {
1836  // Failure from doEdit()
1837  // Show the edit conflict page for certain recognized errors from doEdit(),
1838  // but don't show it for errors from extension hooks
1839  $errors = $doEditStatus->getErrorsArray();
1840  if ( in_array( $errors[0][0],
1841  array( 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ) )
1842  ) {
1843  $this->isConflict = true;
1844  // Destroys data doEdit() put in $status->value but who cares
1845  $doEditStatus->value = self::AS_END;
1846  }
1847  wfProfileOut( __METHOD__ );
1848  return $doEditStatus;
1849  }
1850 
1851  $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
1852  if ( $result['nullEdit'] ) {
1853  // We don't know if it was a null edit until now, so increment here
1854  $wgUser->pingLimiter( 'linkpurge' );
1855  }
1856  $result['redirect'] = $content->isRedirect();
1857  $this->updateWatchlist();
1858  wfProfileOut( __METHOD__ );
1859  return $status;
1860  }
1861 
1865  protected function updateWatchlist() {
1866  global $wgUser;
1867 
1868  if ( $wgUser->isLoggedIn()
1869  && $this->watchthis != $wgUser->isWatched( $this->mTitle, WatchedItem::IGNORE_USER_RIGHTS )
1870  ) {
1871  $fname = __METHOD__;
1872  $title = $this->mTitle;
1873  $watch = $this->watchthis;
1874 
1875  // Do this in its own transaction to reduce contention...
1876  $dbw = wfGetDB( DB_MASTER );
1877  $dbw->onTransactionIdle( function() use ( $dbw, $title, $watch, $wgUser, $fname ) {
1878  $dbw->begin( $fname );
1880  $dbw->commit( $fname );
1881  } );
1882  }
1883  }
1884 
1893  function mergeChangesInto( &$editText ) {
1894  ContentHandler::deprecated( __METHOD__, "1.21" );
1895 
1896  $editContent = $this->toEditContent( $editText );
1897 
1898  $ok = $this->mergeChangesIntoContent( $editContent );
1899 
1900  if ( $ok ) {
1901  $editText = $this->toEditText( $editContent );
1902  return true;
1903  }
1904  return false;
1905  }
1906 
1918  private function mergeChangesIntoContent( &$editContent ) {
1919  wfProfileIn( __METHOD__ );
1920 
1921  $db = wfGetDB( DB_MASTER );
1922 
1923  // This is the revision the editor started from
1924  $baseRevision = $this->getBaseRevision();
1925  $baseContent = $baseRevision ? $baseRevision->getContent() : null;
1926 
1927  if ( is_null( $baseContent ) ) {
1928  wfProfileOut( __METHOD__ );
1929  return false;
1930  }
1931 
1932  // The current state, we want to merge updates into it
1933  $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
1934  $currentContent = $currentRevision ? $currentRevision->getContent() : null;
1935 
1936  if ( is_null( $currentContent ) ) {
1937  wfProfileOut( __METHOD__ );
1938  return false;
1939  }
1940 
1941  $handler = ContentHandler::getForModelID( $baseContent->getModel() );
1942 
1943  $result = $handler->merge3( $baseContent, $editContent, $currentContent );
1944 
1945  if ( $result ) {
1946  $editContent = $result;
1947  wfProfileOut( __METHOD__ );
1948  return true;
1949  }
1950 
1951  wfProfileOut( __METHOD__ );
1952  return false;
1953  }
1954 
1958  function getBaseRevision() {
1959  if ( !$this->mBaseRevision ) {
1960  $db = wfGetDB( DB_MASTER );
1961  $this->mBaseRevision = Revision::loadFromTimestamp(
1962  $db, $this->mTitle, $this->edittime );
1963  }
1964  return $this->mBaseRevision;
1965  }
1966 
1974  public static function matchSpamRegex( $text ) {
1975  global $wgSpamRegex;
1976  // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
1977  $regexes = (array)$wgSpamRegex;
1978  return self::matchSpamRegexInternal( $text, $regexes );
1979  }
1980 
1988  public static function matchSummarySpamRegex( $text ) {
1989  global $wgSummarySpamRegex;
1990  $regexes = (array)$wgSummarySpamRegex;
1991  return self::matchSpamRegexInternal( $text, $regexes );
1992  }
1993 
1999  protected static function matchSpamRegexInternal( $text, $regexes ) {
2000  foreach ( $regexes as $regex ) {
2001  $matches = array();
2002  if ( preg_match( $regex, $text, $matches ) ) {
2003  return $matches[0];
2004  }
2005  }
2006  return false;
2007  }
2008 
2009  function setHeaders() {
2011 
2012  $wgOut->addModules( 'mediawiki.action.edit' );
2013  $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
2014 
2015  if ( $wgUser->getOption( 'uselivepreview', false ) ) {
2016  $wgOut->addModules( 'mediawiki.action.edit.preview' );
2017  }
2018 
2019  if ( $wgUser->getOption( 'useeditwarning', false ) ) {
2020  $wgOut->addModules( 'mediawiki.action.edit.editWarning' );
2021  }
2022 
2023  $wgOut->setRobotPolicy( 'noindex,nofollow' );
2024 
2025  # Enabled article-related sidebar, toplinks, etc.
2026  $wgOut->setArticleRelated( true );
2027 
2028  $contextTitle = $this->getContextTitle();
2029  if ( $this->isConflict ) {
2030  $msg = 'editconflict';
2031  } elseif ( $contextTitle->exists() && $this->section != '' ) {
2032  $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
2033  } else {
2034  $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() == NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !== false ) ?
2035  'editing' : 'creating';
2036  }
2037  # Use the title defined by DISPLAYTITLE magic word when present
2038  $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
2039  if ( $displayTitle === false ) {
2040  $displayTitle = $contextTitle->getPrefixedText();
2041  }
2042  $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) );
2043  }
2044 
2048  protected function showIntro() {
2050  if ( $this->suppressIntro ) {
2051  return;
2052  }
2053 
2054  $namespace = $this->mTitle->getNamespace();
2055 
2056  if ( $namespace == NS_MEDIAWIKI ) {
2057  # Show a warning if editing an interface message
2058  $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
2059  } elseif ( $namespace == NS_FILE ) {
2060  # Show a hint to shared repo
2061  $file = wfFindFile( $this->mTitle );
2062  if ( $file && !$file->isLocal() ) {
2063  $descUrl = $file->getDescriptionUrl();
2064  # there must be a description url to show a hint to shared repo
2065  if ( $descUrl ) {
2066  if ( !$this->mTitle->exists() ) {
2067  $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", array(
2068  'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
2069  ) );
2070  } else {
2071  $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", array(
2072  'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
2073  ) );
2074  }
2075  }
2076  }
2077  }
2078 
2079  # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2080  # Show log extract when the user is currently blocked
2081  if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
2082  $parts = explode( '/', $this->mTitle->getText(), 2 );
2083  $username = $parts[0];
2084  $user = User::newFromName( $username, false /* allow IP users*/ );
2085  $ip = User::isIP( $username );
2086  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
2087  $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2088  array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
2089  } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
2091  $wgOut,
2092  'block',
2093  $user->getUserPage(),
2094  '',
2095  array(
2096  'lim' => 1,
2097  'showIfEmpty' => false,
2098  'msgKey' => array(
2099  'blocked-notice-logextract',
2100  $user->getName() # Support GENDER in notice
2101  )
2102  )
2103  );
2104  }
2105  }
2106  # Try to add a custom edit intro, or use the standard one if this is not possible.
2107  if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
2109  wfMessage( 'helppage' )->inContentLanguage()->text()
2110  ) );
2111  if ( $wgUser->isLoggedIn() ) {
2112  $wgOut->wrapWikiMsg(
2113  // Suppress the external link icon, consider the help url an internal one
2114  "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2115  array(
2116  'newarticletext',
2117  $helpLink
2118  )
2119  );
2120  } else {
2121  $wgOut->wrapWikiMsg(
2122  // Suppress the external link icon, consider the help url an internal one
2123  "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2124  array(
2125  'newarticletextanon',
2126  $helpLink
2127  )
2128  );
2129  }
2130  }
2131  # Give a notice if the user is editing a deleted/moved page...
2132  if ( !$this->mTitle->exists() ) {
2133  LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle,
2134  '',
2135  array(
2136  'lim' => 10,
2137  'conds' => array( "log_action != 'revision'" ),
2138  'showIfEmpty' => false,
2139  'msgKey' => array( 'recreate-moveddeleted-warn' )
2140  )
2141  );
2142  }
2143  }
2144 
2150  protected function showCustomIntro() {
2151  if ( $this->editintro ) {
2152  $title = Title::newFromText( $this->editintro );
2153  if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
2154  global $wgOut;
2155  // Added using template syntax, to take <noinclude>'s into account.
2156  $wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
2157  return true;
2158  }
2159  }
2160  return false;
2161  }
2162 
2179  protected function toEditText( $content ) {
2180  if ( $content === null || $content === false ) {
2181  return $content;
2182  }
2183 
2184  if ( is_string( $content ) ) {
2185  return $content;
2186  }
2187 
2188  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2189  throw new MWException( 'This content model is not supported: '
2190  . ContentHandler::getLocalizedName( $content->getModel() ) );
2191  }
2192 
2193  return $content->serialize( $this->contentFormat );
2194  }
2195 
2210  protected function toEditContent( $text ) {
2211  if ( $text === false || $text === null ) {
2212  return $text;
2213  }
2214 
2215  $content = ContentHandler::makeContent( $text, $this->getTitle(),
2216  $this->contentModel, $this->contentFormat );
2217 
2218  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2219  throw new MWException( 'This content model is not supported: '
2220  . ContentHandler::getLocalizedName( $content->getModel() ) );
2221  }
2222 
2223  return $content;
2224  }
2225 
2231  function showEditForm( $formCallback = null ) {
2233 
2234  wfProfileIn( __METHOD__ );
2235 
2236  # need to parse the preview early so that we know which templates are used,
2237  # otherwise users with "show preview after edit box" will get a blank list
2238  # we parse this near the beginning so that setHeaders can do the title
2239  # setting work instead of leaving it in getPreviewText
2240  $previewOutput = '';
2241  if ( $this->formtype == 'preview' ) {
2242  $previewOutput = $this->getPreviewText();
2243  }
2244 
2245  wfRunHooks( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) );
2246 
2247  $this->setHeaders();
2248 
2249  if ( $this->showHeader() === false ) {
2250  wfProfileOut( __METHOD__ );
2251  return;
2252  }
2253 
2254  $wgOut->addHTML( $this->editFormPageTop );
2255 
2256  if ( $wgUser->getOption( 'previewontop' ) ) {
2257  $this->displayPreviewArea( $previewOutput, true );
2258  }
2259 
2260  $wgOut->addHTML( $this->editFormTextTop );
2261 
2262  $showToolbar = true;
2263  if ( $this->wasDeletedSinceLastEdit() ) {
2264  if ( $this->formtype == 'save' ) {
2265  // Hide the toolbar and edit area, user can click preview to get it back
2266  // Add an confirmation checkbox and explanation.
2267  $showToolbar = false;
2268  } else {
2269  $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2270  'deletedwhileediting' );
2271  }
2272  }
2273 
2274  // @todo add EditForm plugin interface and use it here!
2275  // search for textarea1 and textares2, and allow EditForm to override all uses.
2276  $wgOut->addHTML( Html::openElement( 'form', array( 'id' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID,
2277  'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
2278  'enctype' => 'multipart/form-data' ) ) );
2279 
2280  if ( is_callable( $formCallback ) ) {
2281  call_user_func_array( $formCallback, array( &$wgOut ) );
2282  }
2283 
2284  // Add an empty field to trip up spambots
2285  $wgOut->addHTML(
2286  Xml::openElement( 'div', array( 'id' => 'antispam-container', 'style' => 'display: none;' ) )
2287  . Html::rawElement( 'label', array( 'for' => 'wpAntiSpam' ), wfMessage( 'simpleantispam-label' )->parse() )
2288  . Xml::element( 'input', array( 'type' => 'text', 'name' => 'wpAntispam', 'id' => 'wpAntispam', 'value' => '' ) )
2289  . Xml::closeElement( 'div' )
2290  );
2291 
2292  wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
2293 
2294  // Put these up at the top to ensure they aren't lost on early form submission
2295  $this->showFormBeforeText();
2296 
2297  if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
2298  $username = $this->lastDelete->user_name;
2299  $comment = $this->lastDelete->log_comment;
2300 
2301  // It is better to not parse the comment at all than to have templates expanded in the middle
2302  // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
2303  $key = $comment === ''
2304  ? 'confirmrecreate-noreason'
2305  : 'confirmrecreate';
2306  $wgOut->addHTML(
2307  '<div class="mw-confirm-recreate">' .
2308  wfMessage( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
2309  Xml::checkLabel( wfMessage( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
2310  array( 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
2311  ) .
2312  '</div>'
2313  );
2314  }
2315 
2316  # When the summary is hidden, also hide them on preview/show changes
2317  if ( $this->nosummary ) {
2318  $wgOut->addHTML( Html::hidden( 'nosummary', true ) );
2319  }
2320 
2321  # If a blank edit summary was previously provided, and the appropriate
2322  # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2323  # user being bounced back more than once in the event that a summary
2324  # is not required.
2325  #####
2326  # For a bit more sophisticated detection of blank summaries, hash the
2327  # automatic one and pass that in the hidden field wpAutoSummary.
2328  if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
2329  $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
2330  }
2331 
2332  if ( $this->undidRev ) {
2333  $wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
2334  }
2335 
2336  if ( $this->hasPresetSummary ) {
2337  // If a summary has been preset using &summary= we don't want to prompt for
2338  // a different summary. Only prompt for a summary if the summary is blanked.
2339  // (Bug 17416)
2340  $this->autoSumm = md5( '' );
2341  }
2342 
2343  $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
2344  $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
2345 
2346  $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
2347 
2348  $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
2349  $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
2350 
2351  if ( $this->section == 'new' ) {
2352  $this->showSummaryInput( true, $this->summary );
2353  $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
2354  }
2355 
2356  $wgOut->addHTML( $this->editFormTextBeforeContent );
2357 
2358  if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
2359  $wgOut->addHTML( EditPage::getEditToolbar() );
2360  }
2361 
2362  if ( $this->isConflict ) {
2363  // In an edit conflict bypass the overridable content form method
2364  // and fallback to the raw wpTextbox1 since editconflicts can't be
2365  // resolved between page source edits and custom ui edits using the
2366  // custom edit ui.
2367  $this->textbox2 = $this->textbox1;
2368 
2369  $content = $this->getCurrentContent();
2370  $this->textbox1 = $this->toEditText( $content );
2371 
2372  $this->showTextbox1();
2373  } else {
2374  $this->showContentForm();
2375  }
2376 
2377  $wgOut->addHTML( $this->editFormTextAfterContent );
2378 
2379  $this->showStandardInputs();
2380 
2381  $this->showFormAfterText();
2382 
2383  $this->showTosSummary();
2384 
2385  $this->showEditTools();
2386 
2387  $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
2388 
2389  $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
2390  Linker::formatTemplates( $this->getTemplates(), $this->preview, $this->section != '' ) ) );
2391 
2392  $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'hiddencats' ),
2393  Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
2394 
2395  $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'limitreport' ),
2396  self::getPreviewLimitReport( $this->mParserOutput ) ) );
2397 
2398  $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
2399 
2400  if ( $this->isConflict ) {
2401  try {
2402  $this->showConflict();
2403  } catch ( MWContentSerializationException $ex ) {
2404  // this can't really happen, but be nice if it does.
2405  $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
2406  $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
2407  }
2408  }
2409 
2410  $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
2411 
2412  if ( !$wgUser->getOption( 'previewontop' ) ) {
2413  $this->displayPreviewArea( $previewOutput, false );
2414  }
2415 
2416  wfProfileOut( __METHOD__ );
2417  }
2418 
2425  public static function extractSectionTitle( $text ) {
2426  preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
2427  if ( !empty( $matches[2] ) ) {
2428  global $wgParser;
2429  return $wgParser->stripSectionName( trim( $matches[2] ) );
2430  } else {
2431  return false;
2432  }
2433  }
2434 
2435  protected function showHeader() {
2436  global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang;
2437 
2438  if ( $this->mTitle->isTalkPage() ) {
2439  $wgOut->addWikiMsg( 'talkpagetext' );
2440  }
2441 
2442  // Add edit notices
2443  $wgOut->addHTML( implode( "\n", $this->mTitle->getEditNotices( $this->oldid ) ) );
2444 
2445  if ( $this->isConflict ) {
2446  $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
2447  $this->edittime = $this->mArticle->getTimestamp();
2448  } else {
2449  if ( $this->section != '' && !$this->isSectionEditSupported() ) {
2450  // We use $this->section to much before this and getVal('wgSection') directly in other places
2451  // at this point we can't reset $this->section to '' to fallback to non-section editing.
2452  // Someone is welcome to try refactoring though
2453  $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
2454  return false;
2455  }
2456 
2457  if ( $this->section != '' && $this->section != 'new' ) {
2458  if ( !$this->summary && !$this->preview && !$this->diff ) {
2459  $sectionTitle = self::extractSectionTitle( $this->textbox1 ); //FIXME: use Content object
2460  if ( $sectionTitle !== false ) {
2461  $this->summary = "/* $sectionTitle */ ";
2462  }
2463  }
2464  }
2465 
2466  if ( $this->missingComment ) {
2467  $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
2468  }
2469 
2470  if ( $this->missingSummary && $this->section != 'new' ) {
2471  $wgOut->wrapWikiMsg( "<div id='mw-missingsummary'>\n$1\n</div>", 'missingsummary' );
2472  }
2473 
2474  if ( $this->missingSummary && $this->section == 'new' ) {
2475  $wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1\n</div>", 'missingcommentheader' );
2476  }
2477 
2478  if ( $this->hookError !== '' ) {
2479  $wgOut->addWikiText( $this->hookError );
2480  }
2481 
2482  if ( !$this->checkUnicodeCompliantBrowser() ) {
2483  $wgOut->addWikiMsg( 'nonunicodebrowser' );
2484  }
2485 
2486  if ( $this->section != 'new' ) {
2487  $revision = $this->mArticle->getRevisionFetched();
2488  if ( $revision ) {
2489  // Let sysop know that this will make private content public if saved
2490 
2491  if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) {
2492  $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
2493  } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
2494  $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
2495  }
2496 
2497  if ( !$revision->isCurrent() ) {
2498  $this->mArticle->setOldSubtitle( $revision->getId() );
2499  $wgOut->addWikiMsg( 'editingold' );
2500  }
2501  } elseif ( $this->mTitle->exists() ) {
2502  // Something went wrong
2503 
2504  $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
2505  array( 'missing-revision', $this->oldid ) );
2506  }
2507  }
2508  }
2509 
2510  if ( wfReadOnly() ) {
2511  $wgOut->wrapWikiMsg( "<div id=\"mw-read-only-warning\">\n$1\n</div>", array( 'readonlywarning', wfReadOnlyReason() ) );
2512  } elseif ( $wgUser->isAnon() ) {
2513  if ( $this->formtype != 'preview' ) {
2514  $wgOut->wrapWikiMsg( "<div id=\"mw-anon-edit-warning\">\n$1</div>", 'anoneditwarning' );
2515  } else {
2516  $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>", 'anonpreviewwarning' );
2517  }
2518  } else {
2519  if ( $this->isCssJsSubpage ) {
2520  # Check the skin exists
2521  if ( $this->isWrongCaseCssJsPage ) {
2522  $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) );
2523  }
2524  if ( $this->getTitle()->isSubpageOf( $wgUser->getUserPage() ) ) {
2525  if ( $this->formtype !== 'preview' ) {
2526  if ( $this->isCssSubpage ) {
2527  $wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) );
2528  }
2529 
2530  if ( $this->isJsSubpage ) {
2531  $wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) );
2532  }
2533  }
2534  }
2535  }
2536  }
2537 
2538  if ( $this->mTitle->isProtected( 'edit' ) &&
2539  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
2540  ) {
2541  # Is the title semi-protected?
2542  if ( $this->mTitle->isSemiProtected() ) {
2543  $noticeMsg = 'semiprotectedpagewarning';
2544  } else {
2545  # Then it must be protected based on static groups (regular)
2546  $noticeMsg = 'protectedpagewarning';
2547  }
2548  LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
2549  array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) );
2550  }
2551  if ( $this->mTitle->isCascadeProtected() ) {
2552  # Is this page under cascading protection from some source pages?
2553  list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
2554  $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
2555  $cascadeSourcesCount = count( $cascadeSources );
2556  if ( $cascadeSourcesCount > 0 ) {
2557  # Explain, and list the titles responsible
2558  foreach ( $cascadeSources as $page ) {
2559  $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
2560  }
2561  }
2562  $notice .= '</div>';
2563  $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', $cascadeSourcesCount ) );
2564  }
2565  if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
2566  LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
2567  array( 'lim' => 1,
2568  'showIfEmpty' => false,
2569  'msgKey' => array( 'titleprotectedwarning' ),
2570  'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ) );
2571  }
2572 
2573  if ( $this->kblength === false ) {
2574  $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
2575  }
2576 
2577  if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
2578  $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
2579  array( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) ) );
2580  } else {
2581  if ( !wfMessage( 'longpage-hint' )->isDisabled() ) {
2582  $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
2583  array( 'longpage-hint', $wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) )
2584  );
2585  }
2586  }
2587  # Add header copyright warning
2588  $this->showHeaderCopyrightWarning();
2589  }
2590 
2605  function getSummaryInput( $summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null ) {
2606  // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
2607  $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : array() ) + array(
2608  'id' => 'wpSummary',
2609  'maxlength' => '200',
2610  'tabindex' => '1',
2611  'size' => 60,
2612  'spellcheck' => 'true',
2613  ) + Linker::tooltipAndAccesskeyAttribs( 'summary' );
2614 
2615  $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : array() ) + array(
2616  'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
2617  'id' => "wpSummaryLabel"
2618  );
2619 
2620  $label = null;
2621  if ( $labelText ) {
2622  $label = Xml::tags( 'label', $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null, $labelText );
2623  $label = Xml::tags( 'span', $spanLabelAttrs, $label );
2624  }
2625 
2626  $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
2627 
2628  return array( $label, $input );
2629  }
2630 
2638  protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
2640  # Add a class if 'missingsummary' is triggered to allow styling of the summary line
2641  $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
2642  if ( $isSubjectPreview ) {
2643  if ( $this->nosummary ) {
2644  return;
2645  }
2646  } else {
2647  if ( !$this->mShowSummaryField ) {
2648  return;
2649  }
2650  }
2651  $summary = $wgContLang->recodeForEdit( $summary );
2652  $labelText = wfMessage( $isSubjectPreview ? 'subject' : 'summary' )->parse();
2653  list( $label, $input ) = $this->getSummaryInput( $summary, $labelText, array( 'class' => $summaryClass ), array() );
2654  $wgOut->addHTML( "{$label} {$input}" );
2655  }
2656 
2664  protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
2665  // avoid spaces in preview, gets always trimmed on save
2666  $summary = trim( $summary );
2667  if ( !$summary || ( !$this->preview && !$this->diff ) ) {
2668  return "";
2669  }
2670 
2671  global $wgParser;
2672 
2673  if ( $isSubjectPreview ) {
2674  $summary = wfMessage( 'newsectionsummary' )->rawParams( $wgParser->stripSectionName( $summary ) )
2675  ->inContentLanguage()->text();
2676  }
2677 
2678  $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
2679 
2680  $summary = wfMessage( $message )->parse() . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
2681  return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary );
2682  }
2683 
2684  protected function showFormBeforeText() {
2685  global $wgOut;
2686  $section = htmlspecialchars( $this->section );
2687  $wgOut->addHTML( <<<HTML
2688 <input type='hidden' value="{$section}" name="wpSection" />
2689 <input type='hidden' value="{$this->starttime}" name="wpStarttime" />
2690 <input type='hidden' value="{$this->edittime}" name="wpEdittime" />
2691 <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
2692 
2693 HTML
2694  );
2695  if ( !$this->checkUnicodeCompliantBrowser() ) {
2696  $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
2697  }
2698  }
2699 
2700  protected function showFormAfterText() {
2714  $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
2715  }
2716 
2725  protected function showContentForm() {
2726  $this->showTextbox1();
2727  }
2728 
2737  protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
2738  if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
2739  $attribs = array( 'style' => 'display:none;' );
2740  } else {
2741  $classes = array(); // Textarea CSS
2742  if ( $this->mTitle->isProtected( 'edit' ) &&
2743  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
2744  ) {
2745  # Is the title semi-protected?
2746  if ( $this->mTitle->isSemiProtected() ) {
2747  $classes[] = 'mw-textarea-sprotected';
2748  } else {
2749  # Then it must be protected based on static groups (regular)
2750  $classes[] = 'mw-textarea-protected';
2751  }
2752  # Is the title cascade-protected?
2753  if ( $this->mTitle->isCascadeProtected() ) {
2754  $classes[] = 'mw-textarea-cprotected';
2755  }
2756  }
2757 
2758  $attribs = array( 'tabindex' => 1 );
2759 
2760  if ( is_array( $customAttribs ) ) {
2762  }
2763 
2764  if ( count( $classes ) ) {
2765  if ( isset( $attribs['class'] ) ) {
2766  $classes[] = $attribs['class'];
2767  }
2768  $attribs['class'] = implode( ' ', $classes );
2769  }
2770  }
2771 
2772  $this->showTextbox( $textoverride !== null ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
2773  }
2774 
2775  protected function showTextbox2() {
2776  $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
2777  }
2778 
2779  protected function showTextbox( $text, $name, $customAttribs = array() ) {
2781 
2782  $wikitext = $this->safeUnicodeOutput( $text );
2783  if ( strval( $wikitext ) !== '' ) {
2784  // Ensure there's a newline at the end, otherwise adding lines
2785  // is awkward.
2786  // But don't add a newline if the ext is empty, or Firefox in XHTML
2787  // mode will show an extra newline. A bit annoying.
2788  $wikitext .= "\n";
2789  }
2790 
2792  'accesskey' => ',',
2793  'id' => $name,
2794  'cols' => $wgUser->getIntOption( 'cols' ),
2795  'rows' => $wgUser->getIntOption( 'rows' ),
2796  'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work
2797  );
2798 
2799  $pageLang = $this->mTitle->getPageLanguage();
2800  $attribs['lang'] = $pageLang->getCode();
2801  $attribs['dir'] = $pageLang->getDir();
2802 
2803  $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
2804  }
2805 
2806  protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
2807  global $wgOut;
2808  $classes = array();
2809  if ( $isOnTop ) {
2810  $classes[] = 'ontop';
2811  }
2812 
2813  $attribs = array( 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) );
2814 
2815  if ( $this->formtype != 'preview' ) {
2816  $attribs['style'] = 'display: none;';
2817  }
2818 
2819  $wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
2820 
2821  if ( $this->formtype == 'preview' ) {
2822  $this->showPreview( $previewOutput );
2823  }
2824 
2825  $wgOut->addHTML( '</div>' );
2826 
2827  if ( $this->formtype == 'diff' ) {
2828  try {
2829  $this->showDiff();
2830  } catch ( MWContentSerializationException $ex ) {
2831  $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
2832  $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
2833  }
2834  }
2835  }
2836 
2843  protected function showPreview( $text ) {
2844  global $wgOut;
2845  if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
2846  $this->mArticle->openShowCategory();
2847  }
2848  # This hook seems slightly odd here, but makes things more
2849  # consistent for extensions.
2850  wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) );
2851  $wgOut->addHTML( $text );
2852  if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
2853  $this->mArticle->closeShowCategory();
2854  }
2855  }
2856 
2864  function showDiff() {
2866 
2867  $oldtitlemsg = 'currentrev';
2868  # if message does not exist, show diff against the preloaded default
2869  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
2870  $oldtext = $this->mTitle->getDefaultMessageText();
2871  if ( $oldtext !== false ) {
2872  $oldtitlemsg = 'defaultmessagetext';
2873  $oldContent = $this->toEditContent( $oldtext );
2874  } else {
2875  $oldContent = null;
2876  }
2877  } else {
2878  $oldContent = $this->getCurrentContent();
2879  }
2880 
2881  $textboxContent = $this->toEditContent( $this->textbox1 );
2882 
2883  $newContent = $this->mArticle->replaceSectionContent(
2884  $this->section, $textboxContent,
2885  $this->summary, $this->edittime );
2886 
2887  if ( $newContent ) {
2888  ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
2889  wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
2890 
2892  $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
2893  }
2894 
2895  if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
2896  $oldtitle = wfMessage( $oldtitlemsg )->parse();
2897  $newtitle = wfMessage( 'yourtext' )->parse();
2898 
2899  if ( !$oldContent ) {
2900  $oldContent = $newContent->getContentHandler()->makeEmptyContent();
2901  }
2902 
2903  if ( !$newContent ) {
2904  $newContent = $oldContent->getContentHandler()->makeEmptyContent();
2905  }
2906 
2907  $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
2908  $de->setContent( $oldContent, $newContent );
2909 
2910  $difftext = $de->getDiff( $oldtitle, $newtitle );
2911  $de->showDiffStyle();
2912  } else {
2913  $difftext = '';
2914  }
2915 
2916  $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
2917  }
2918 
2922  protected function showHeaderCopyrightWarning() {
2923  $msg = 'editpage-head-copy-warn';
2924  if ( !wfMessage( $msg )->isDisabled() ) {
2925  global $wgOut;
2926  $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
2927  'editpage-head-copy-warn' );
2928  }
2929  }
2930 
2939  protected function showTosSummary() {
2940  $msg = 'editpage-tos-summary';
2941  wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
2942  if ( !wfMessage( $msg )->isDisabled() ) {
2943  global $wgOut;
2944  $wgOut->addHTML( '<div class="mw-tos-summary">' );
2945  $wgOut->addWikiMsg( $msg );
2946  $wgOut->addHTML( '</div>' );
2947  }
2948  }
2949 
2950  protected function showEditTools() {
2951  global $wgOut;
2952  $wgOut->addHTML( '<div class="mw-editTools">' .
2953  wfMessage( 'edittools' )->inContentLanguage()->parse() .
2954  '</div>' );
2955  }
2956 
2962  protected function getCopywarn() {
2963  return self::getCopyrightWarning( $this->mTitle );
2964  }
2965 
2974  public static function getCopyrightWarning( $title, $format = 'plain' ) {
2975  global $wgRightsText;
2976  if ( $wgRightsText ) {
2977  $copywarnMsg = array( 'copyrightwarning',
2978  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]',
2979  $wgRightsText );
2980  } else {
2981  $copywarnMsg = array( 'copyrightwarning2',
2982  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' );
2983  }
2984  // Allow for site and per-namespace customization of contribution/copyright notice.
2985  wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
2986 
2987  return "<div id=\"editpage-copywarn\">\n" .
2988  call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>";
2989  }
2990 
2998  public static function getPreviewLimitReport( $output ) {
2999  if ( !$output || !$output->getLimitReportData() ) {
3000  return '';
3001  }
3002 
3003  wfProfileIn( __METHOD__ );
3004 
3005  $limitReport = Html::rawElement( 'div', array( 'class' => 'mw-limitReportExplanation' ),
3006  wfMessage( 'limitreport-title' )->parseAsBlock()
3007  );
3008 
3009  // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
3010  $limitReport .= Html::openElement( 'div', array( 'class' => 'preview-limit-report-wrapper' ) );
3011 
3012  $limitReport .= Html::openElement( 'table', array(
3013  'class' => 'preview-limit-report wikitable'
3014  ) ) .
3015  Html::openElement( 'tbody' );
3016 
3017  foreach ( $output->getLimitReportData() as $key => $value ) {
3018  if ( wfRunHooks( 'ParserLimitReportFormat',
3019  array( $key, &$value, &$limitReport, true, true )
3020  ) ) {
3021  $keyMsg = wfMessage( $key );
3022  $valueMsg = wfMessage( array( "$key-value-html", "$key-value" ) );
3023  if ( !$valueMsg->exists() ) {
3024  $valueMsg = new RawMessage( '$1' );
3025  }
3026  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3027  $limitReport .= Html::openElement( 'tr' ) .
3028  Html::rawElement( 'th', null, $keyMsg->parse() ) .
3029  Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) .
3030  Html::closeElement( 'tr' );
3031  }
3032  }
3033  }
3034 
3035  $limitReport .= Html::closeElement( 'tbody' ) .
3036  Html::closeElement( 'table' ) .
3037  Html::closeElement( 'div' );
3038 
3039  wfProfileOut( __METHOD__ );
3040 
3041  return $limitReport;
3042  }
3043 
3044  protected function showStandardInputs( &$tabindex = 2 ) {
3045  global $wgOut;
3046  $wgOut->addHTML( "<div class='editOptions'>\n" );
3047 
3048  if ( $this->section != 'new' ) {
3049  $this->showSummaryInput( false, $this->summary );
3050  $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
3051  }
3052 
3053  $checkboxes = $this->getCheckboxes( $tabindex,
3054  array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) );
3055  $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" );
3056 
3057  // Show copyright warning.
3058  $wgOut->addWikiText( $this->getCopywarn() );
3059  $wgOut->addHTML( $this->editFormTextAfterWarn );
3060 
3061  $wgOut->addHTML( "<div class='editButtons'>\n" );
3062  $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
3063 
3064  $cancel = $this->getCancelLink();
3065  if ( $cancel !== '' ) {
3066  $cancel .= Html::element( 'span',
3067  array( 'class' => 'mw-editButtons-pipe-separator' ),
3068  wfMessage( 'pipe-separator' )->text() );
3069  }
3070  $edithelpurl = Skin::makeInternalOrExternalUrl( wfMessage( 'edithelppage' )->inContentLanguage()->text() );
3071  $edithelp = '<a target="helpwindow" href="' . $edithelpurl . '">' .
3072  wfMessage( 'edithelp' )->escaped() . '</a> ' .
3073  wfMessage( 'newwindow' )->parse();
3074  $wgOut->addHTML( " <span class='cancelLink'>{$cancel}</span>\n" );
3075  $wgOut->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" );
3076  $wgOut->addHTML( "</div><!-- editButtons -->\n" );
3077  wfRunHooks( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) );
3078  $wgOut->addHTML( "</div><!-- editOptions -->\n" );
3079  }
3080 
3085  protected function showConflict() {
3086  global $wgOut;
3087 
3088  if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
3089  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
3090 
3091  $content1 = $this->toEditContent( $this->textbox1 );
3092  $content2 = $this->toEditContent( $this->textbox2 );
3093 
3094  $handler = ContentHandler::getForModelID( $this->contentModel );
3095  $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
3096  $de->setContent( $content2, $content1 );
3097  $de->showDiff(
3098  wfMessage( 'yourtext' )->parse(),
3099  wfMessage( 'storedversion' )->text()
3100  );
3101 
3102  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
3103  $this->showTextbox2();
3104  }
3105  }
3106 
3110  public function getCancelLink() {
3111  $cancelParams = array();
3112  if ( !$this->isConflict && $this->oldid > 0 ) {
3113  $cancelParams['oldid'] = $this->oldid;
3114  }
3115 
3116  return Linker::linkKnown(
3117  $this->getContextTitle(),
3118  wfMessage( 'cancel' )->parse(),
3119  array( 'id' => 'mw-editform-cancel' ),
3120  $cancelParams
3121  );
3122  }
3123 
3133  protected function getActionURL( Title $title ) {
3134  return $title->getLocalURL( array( 'action' => $this->action ) );
3135  }
3136 
3143  protected function wasDeletedSinceLastEdit() {
3144  if ( $this->deletedSinceEdit !== null ) {
3145  return $this->deletedSinceEdit;
3146  }
3147 
3148  $this->deletedSinceEdit = false;
3149 
3150  if ( $this->mTitle->isDeletedQuick() ) {
3151  $this->lastDelete = $this->getLastDelete();
3152  if ( $this->lastDelete ) {
3153  $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
3154  if ( $deleteTime > $this->starttime ) {
3155  $this->deletedSinceEdit = true;
3156  }
3157  }
3158  }
3159 
3160  return $this->deletedSinceEdit;
3161  }
3162 
3163  protected function getLastDelete() {
3164  $dbr = wfGetDB( DB_SLAVE );
3165  $data = $dbr->selectRow(
3166  array( 'logging', 'user' ),
3167  array(
3168  'log_type',
3169  'log_action',
3170  'log_timestamp',
3171  'log_user',
3172  'log_namespace',
3173  'log_title',
3174  'log_comment',
3175  'log_params',
3176  'log_deleted',
3177  'user_name'
3178  ), array(
3179  'log_namespace' => $this->mTitle->getNamespace(),
3180  'log_title' => $this->mTitle->getDBkey(),
3181  'log_type' => 'delete',
3182  'log_action' => 'delete',
3183  'user_id=log_user'
3184  ),
3185  __METHOD__,
3186  array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' )
3187  );
3188  // Quick paranoid permission checks...
3189  if ( is_object( $data ) ) {
3190  if ( $data->log_deleted & LogPage::DELETED_USER ) {
3191  $data->user_name = wfMessage( 'rev-deleted-user' )->escaped();
3192  }
3193 
3194  if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
3195  $data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped();
3196  }
3197  }
3198  return $data;
3199  }
3200 
3206  function getPreviewText() {
3207  global $wgOut, $wgUser, $wgRawHtml, $wgLang;
3208 
3209  wfProfileIn( __METHOD__ );
3210 
3211  if ( $wgRawHtml && !$this->mTokenOk ) {
3212  // Could be an offsite preview attempt. This is very unsafe if
3213  // HTML is enabled, as it could be an attack.
3214  $parsedNote = '';
3215  if ( $this->textbox1 !== '' ) {
3216  // Do not put big scary notice, if previewing the empty
3217  // string, which happens when you initially edit
3218  // a category page, due to automatic preview-on-open.
3219  $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
3220  wfMessage( 'session_fail_preview_html' )->text() . "</div>", true, /* interface */true );
3221  }
3222  wfProfileOut( __METHOD__ );
3223  return $parsedNote;
3224  }
3225 
3226  $note = '';
3227 
3228  try {
3229  $content = $this->toEditContent( $this->textbox1 );
3230 
3231  $previewHTML = '';
3232  if ( !wfRunHooks( 'AlternateEditPreview', array( $this, &$content, &$previewHTML, &$this->mParserOutput ) ) ) {
3233  wfProfileOut( __METHOD__ );
3234  return $previewHTML;
3235  }
3236 
3237  # provide a anchor link to the editform
3238  $continueEditing = '<span class="mw-continue-editing">' .
3239  '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' .
3240  wfMessage( 'continue-editing' )->text() . ']]</span>';
3241  if ( $this->mTriedSave && !$this->mTokenOk ) {
3242  if ( $this->mTokenOkExceptSuffix ) {
3243  $note = wfMessage( 'token_suffix_mismatch' )->plain();
3244 
3245  } else {
3246  $note = wfMessage( 'session_fail_preview' )->plain();
3247  }
3248  } elseif ( $this->incompleteForm ) {
3249  $note = wfMessage( 'edit_form_incomplete' )->plain();
3250  } else {
3251  $note = wfMessage( 'previewnote' )->plain() . ' ' . $continueEditing;
3252  }
3253 
3254  $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
3255  $parserOptions->setEditSection( false );
3256  $parserOptions->setIsPreview( true );
3257  $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
3258 
3259  # don't parse non-wikitext pages, show message about preview
3260  if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
3261  if ( $this->mTitle->isCssJsSubpage() ) {
3262  $level = 'user';
3263  } elseif ( $this->mTitle->isCssOrJsPage() ) {
3264  $level = 'site';
3265  } else {
3266  $level = false;
3267  }
3268 
3269  if ( $content->getModel() == CONTENT_MODEL_CSS ) {
3270  $format = 'css';
3271  } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
3272  $format = 'js';
3273  } else {
3274  $format = false;
3275  }
3276 
3277  # Used messages to make sure grep find them:
3278  # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
3279  if ( $level && $format ) {
3280  $note = "<div id='mw-{$level}{$format}preview'>" .
3281  wfMessage( "{$level}{$format}preview" )->text() .
3282  ' ' . $continueEditing . "</div>";
3283  }
3284  }
3285 
3286  $rt = $content->getRedirectChain();
3287  if ( $rt ) {
3288  $previewHTML = $this->mArticle->viewRedirect( $rt, false );
3289  } else {
3290 
3291  # If we're adding a comment, we need to show the
3292  # summary as the headline
3293  if ( $this->section === "new" && $this->summary !== "" ) {
3294  $content = $content->addSectionHeader( $this->summary );
3295  }
3296 
3297  $hook_args = array( $this, &$content );
3298  ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
3299  wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
3300 
3301  $parserOptions->enableLimitReport();
3302 
3303  # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
3304  # But it's now deprecated, so never mind
3305 
3306  $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
3307  $parserOutput = $content->getParserOutput( $this->getArticle()->getTitle(), null, $parserOptions );
3308 
3309  $previewHTML = $parserOutput->getText();
3310  $this->mParserOutput = $parserOutput;
3311  $wgOut->addParserOutputNoText( $parserOutput );
3312 
3313  if ( count( $parserOutput->getWarnings() ) ) {
3314  $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
3315  }
3316  }
3317  } catch ( MWContentSerializationException $ex ) {
3318  $m = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
3319  $note .= "\n\n" . $m->parse();
3320  $previewHTML = '';
3321  }
3322 
3323  if ( $this->isConflict ) {
3324  $conflict = '<h2 id="mw-previewconflict">' . wfMessage( 'previewconflict' )->escaped() . "</h2>\n";
3325  } else {
3326  $conflict = '<hr />';
3327  }
3328 
3329  $previewhead = "<div class='previewnote'>\n" .
3330  '<h2 id="mw-previewheader">' . wfMessage( 'preview' )->escaped() . "</h2>" .
3331  $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
3332 
3333  $pageViewLang = $this->mTitle->getPageViewLanguage();
3334  $attribs = array( 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3335  'class' => 'mw-content-' . $pageViewLang->getDir() );
3336  $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
3337 
3338  wfProfileOut( __METHOD__ );
3339  return $previewhead . $previewHTML . $this->previewTextAfterContent;
3340  }
3341 
3345  function getTemplates() {
3346  if ( $this->preview || $this->section != '' ) {
3347  $templates = array();
3348  if ( !isset( $this->mParserOutput ) ) {
3349  return $templates;
3350  }
3351  foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
3352  foreach ( array_keys( $template ) as $dbk ) {
3353  $templates[] = Title::makeTitle( $ns, $dbk );
3354  }
3355  }
3356  return $templates;
3357  } else {
3358  return $this->mTitle->getTemplateLinksFrom();
3359  }
3360  }
3361 
3369  static function getEditToolbar() {
3370  global $wgStylePath, $wgContLang, $wgLang, $wgOut;
3371  global $wgEnableUploads, $wgForeignFileRepos;
3372 
3373  $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
3374 
3388  $toolarray = array(
3389  array(
3390  'image' => $wgLang->getImageFile( 'button-bold' ),
3391  'id' => 'mw-editbutton-bold',
3392  'open' => '\'\'\'',
3393  'close' => '\'\'\'',
3394  'sample' => wfMessage( 'bold_sample' )->text(),
3395  'tip' => wfMessage( 'bold_tip' )->text(),
3396  'key' => 'B'
3397  ),
3398  array(
3399  'image' => $wgLang->getImageFile( 'button-italic' ),
3400  'id' => 'mw-editbutton-italic',
3401  'open' => '\'\'',
3402  'close' => '\'\'',
3403  'sample' => wfMessage( 'italic_sample' )->text(),
3404  'tip' => wfMessage( 'italic_tip' )->text(),
3405  'key' => 'I'
3406  ),
3407  array(
3408  'image' => $wgLang->getImageFile( 'button-link' ),
3409  'id' => 'mw-editbutton-link',
3410  'open' => '[[',
3411  'close' => ']]',
3412  'sample' => wfMessage( 'link_sample' )->text(),
3413  'tip' => wfMessage( 'link_tip' )->text(),
3414  'key' => 'L'
3415  ),
3416  array(
3417  'image' => $wgLang->getImageFile( 'button-extlink' ),
3418  'id' => 'mw-editbutton-extlink',
3419  'open' => '[',
3420  'close' => ']',
3421  'sample' => wfMessage( 'extlink_sample' )->text(),
3422  'tip' => wfMessage( 'extlink_tip' )->text(),
3423  'key' => 'X'
3424  ),
3425  array(
3426  'image' => $wgLang->getImageFile( 'button-headline' ),
3427  'id' => 'mw-editbutton-headline',
3428  'open' => "\n== ",
3429  'close' => " ==\n",
3430  'sample' => wfMessage( 'headline_sample' )->text(),
3431  'tip' => wfMessage( 'headline_tip' )->text(),
3432  'key' => 'H'
3433  ),
3434  $imagesAvailable ? array(
3435  'image' => $wgLang->getImageFile( 'button-image' ),
3436  'id' => 'mw-editbutton-image',
3437  'open' => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
3438  'close' => ']]',
3439  'sample' => wfMessage( 'image_sample' )->text(),
3440  'tip' => wfMessage( 'image_tip' )->text(),
3441  'key' => 'D',
3442  ) : false,
3443  $imagesAvailable ? array(
3444  'image' => $wgLang->getImageFile( 'button-media' ),
3445  'id' => 'mw-editbutton-media',
3446  'open' => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
3447  'close' => ']]',
3448  'sample' => wfMessage( 'media_sample' )->text(),
3449  'tip' => wfMessage( 'media_tip' )->text(),
3450  'key' => 'M'
3451  ) : false,
3452  array(
3453  'image' => $wgLang->getImageFile( 'button-nowiki' ),
3454  'id' => 'mw-editbutton-nowiki',
3455  'open' => "<nowiki>",
3456  'close' => "</nowiki>",
3457  'sample' => wfMessage( 'nowiki_sample' )->text(),
3458  'tip' => wfMessage( 'nowiki_tip' )->text(),
3459  'key' => 'N'
3460  ),
3461  array(
3462  'image' => $wgLang->getImageFile( 'button-sig' ),
3463  'id' => 'mw-editbutton-signature',
3464  'open' => '--~~~~',
3465  'close' => '',
3466  'sample' => '',
3467  'tip' => wfMessage( 'sig_tip' )->text(),
3468  'key' => 'Y'
3469  ),
3470  array(
3471  'image' => $wgLang->getImageFile( 'button-hr' ),
3472  'id' => 'mw-editbutton-hr',
3473  'open' => "\n----\n",
3474  'close' => '',
3475  'sample' => '',
3476  'tip' => wfMessage( 'hr_tip' )->text(),
3477  'key' => 'R'
3478  )
3479  );
3480 
3481  $script = 'mw.loader.using("mediawiki.action.edit", function() {';
3482  foreach ( $toolarray as $tool ) {
3483  if ( !$tool ) {
3484  continue;
3485  }
3486 
3487  $params = array(
3488  $wgStylePath . '/common/images/' . $tool['image'],
3489  // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
3490  // Older browsers show a "speedtip" type message only for ALT.
3491  // Ideally these should be different, realistically they
3492  // probably don't need to be.
3493  $tool['tip'],
3494  $tool['open'],
3495  $tool['close'],
3496  $tool['sample'],
3497  $tool['id'],
3498  );
3499 
3500  $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
3501  }
3502 
3503  // This used to be called on DOMReady from mediawiki.action.edit, which
3504  // ended up causing race conditions with the setup code above.
3505  $script .= "\n" .
3506  "// Create button bar\n" .
3507  "$(function() { mw.toolbar.init(); } );\n";
3508 
3509  $script .= '});';
3511 
3512  $toolbar = '<div id="toolbar"></div>';
3513 
3514  wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
3515 
3516  return $toolbar;
3517  }
3518 
3529  public function getCheckboxes( &$tabindex, $checked ) {
3530  global $wgUser;
3531 
3532  $checkboxes = array();
3533 
3534  // don't show the minor edit checkbox if it's a new page or section
3535  if ( !$this->isNew ) {
3536  $checkboxes['minor'] = '';
3537  $minorLabel = wfMessage( 'minoredit' )->parse();
3538  if ( $wgUser->isAllowed( 'minoredit' ) ) {
3539  $attribs = array(
3540  'tabindex' => ++$tabindex,
3541  'accesskey' => wfMessage( 'accesskey-minoredit' )->text(),
3542  'id' => 'wpMinoredit',
3543  );
3544  $checkboxes['minor'] =
3545  Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
3546  "&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
3547  Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'minoredit', 'withaccess' ) ) ) .
3548  ">{$minorLabel}</label>";
3549  }
3550  }
3551 
3552  $watchLabel = wfMessage( 'watchthis' )->parse();
3553  $checkboxes['watch'] = '';
3554  if ( $wgUser->isLoggedIn() ) {
3555  $attribs = array(
3556  'tabindex' => ++$tabindex,
3557  'accesskey' => wfMessage( 'accesskey-watch' )->text(),
3558  'id' => 'wpWatchthis',
3559  );
3560  $checkboxes['watch'] =
3561  Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
3562  "&#160;<label for='wpWatchthis' id='mw-editpage-watch'" .
3563  Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ) ) .
3564  ">{$watchLabel}</label>";
3565  }
3566  wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
3567  return $checkboxes;
3568  }
3569 
3578  public function getEditButtons( &$tabindex ) {
3579  $buttons = array();
3580 
3581  $temp = array(
3582  'id' => 'wpSave',
3583  'name' => 'wpSave',
3584  'type' => 'submit',
3585  'tabindex' => ++$tabindex,
3586  'value' => wfMessage( 'savearticle' )->text(),
3587  'accesskey' => wfMessage( 'accesskey-save' )->text(),
3588  'title' => wfMessage( 'tooltip-save' )->text() . ' [' . wfMessage( 'accesskey-save' )->text() . ']',
3589  );
3590  $buttons['save'] = Xml::element( 'input', $temp, '' );
3591 
3592  ++$tabindex; // use the same for preview and live preview
3593  $temp = array(
3594  'id' => 'wpPreview',
3595  'name' => 'wpPreview',
3596  'type' => 'submit',
3597  'tabindex' => $tabindex,
3598  'value' => wfMessage( 'showpreview' )->text(),
3599  'accesskey' => wfMessage( 'accesskey-preview' )->text(),
3600  'title' => wfMessage( 'tooltip-preview' )->text() . ' [' . wfMessage( 'accesskey-preview' )->text() . ']',
3601  );
3602  $buttons['preview'] = Xml::element( 'input', $temp, '' );
3603  $buttons['live'] = '';
3604 
3605  $temp = array(
3606  'id' => 'wpDiff',
3607  'name' => 'wpDiff',
3608  'type' => 'submit',
3609  'tabindex' => ++$tabindex,
3610  'value' => wfMessage( 'showdiff' )->text(),
3611  'accesskey' => wfMessage( 'accesskey-diff' )->text(),
3612  'title' => wfMessage( 'tooltip-diff' )->text() . ' [' . wfMessage( 'accesskey-diff' )->text() . ']',
3613  );
3614  $buttons['diff'] = Xml::element( 'input', $temp, '' );
3615 
3616  wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) );
3617  return $buttons;
3618  }
3619 
3632  function livePreview() {
3633  global $wgOut;
3634  $wgOut->disable();
3635  header( 'Content-type: text/xml; charset=utf-8' );
3636  header( 'Cache-control: no-cache' );
3637 
3638  $previewText = $this->getPreviewText();
3639  #$categories = $skin->getCategoryLinks();
3640 
3641  $s =
3642  '<?xml version="1.0" encoding="UTF-8" ?>' . "\n" .
3643  Xml::tags( 'livepreview', null,
3644  Xml::element( 'preview', null, $previewText )
3645  #. Xml::element( 'category', null, $categories )
3646  );
3647  echo $s;
3648  }
3649 
3655  function blockedPage() {
3656  wfDeprecated( __METHOD__, '1.19' );
3657  global $wgUser;
3658 
3659  throw new UserBlockedError( $wgUser->getBlock() );
3660  }
3661 
3667  function userNotLoggedInPage() {
3668  wfDeprecated( __METHOD__, '1.19' );
3669  throw new PermissionsError( 'edit' );
3670  }
3671 
3678  function noCreatePermission() {
3679  wfDeprecated( __METHOD__, '1.19' );
3680  $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
3681  throw new PermissionsError( $permission );
3682  }
3683 
3688  function noSuchSectionPage() {
3689  global $wgOut;
3690 
3691  $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
3692 
3693  $res = wfMessage( 'nosuchsectiontext', $this->section )->parseAsBlock();
3694  wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
3695  $wgOut->addHTML( $res );
3696 
3697  $wgOut->returnToMain( false, $this->mTitle );
3698  }
3699 
3705  public function spamPageWithContent( $match = false ) {
3707  $this->textbox2 = $this->textbox1;
3708 
3709  if ( is_array( $match ) ) {
3710  $match = $wgLang->listToText( $match );
3711  }
3712  $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
3713 
3714  $wgOut->addHTML( '<div id="spamprotected">' );
3715  $wgOut->addWikiMsg( 'spamprotectiontext' );
3716  if ( $match ) {
3717  $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
3718  }
3719  $wgOut->addHTML( '</div>' );
3720 
3721  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
3722  $this->showDiff();
3723 
3724  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
3725  $this->showTextbox2();
3726 
3727  $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) );
3728  }
3729 
3736  private function checkUnicodeCompliantBrowser() {
3737  global $wgBrowserBlackList, $wgRequest;
3738 
3739  $currentbrowser = $wgRequest->getHeader( 'User-Agent' );
3740  if ( $currentbrowser === false ) {
3741  // No User-Agent header sent? Trust it by default...
3742  return true;
3743  }
3744 
3745  foreach ( $wgBrowserBlackList as $browser ) {
3746  if ( preg_match( $browser, $currentbrowser ) ) {
3747  return false;
3748  }
3749  }
3750  return true;
3751  }
3752 
3761  protected function safeUnicodeInput( $request, $field ) {
3762  $text = rtrim( $request->getText( $field ) );
3763  return $request->getBool( 'safemode' )
3764  ? $this->unmakeSafe( $text )
3765  : $text;
3766  }
3767 
3775  protected function safeUnicodeOutput( $text ) {
3777  $codedText = $wgContLang->recodeForEdit( $text );
3778  return $this->checkUnicodeCompliantBrowser()
3779  ? $codedText
3780  : $this->makeSafe( $codedText );
3781  }
3782 
3795  private function makeSafe( $invalue ) {
3796  // Armor existing references for reversibility.
3797  $invalue = strtr( $invalue, array( "&#x" => "&#x0" ) );
3798 
3799  $bytesleft = 0;
3800  $result = "";
3801  $working = 0;
3802  for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
3803  $bytevalue = ord( $invalue[$i] );
3804  if ( $bytevalue <= 0x7F ) { // 0xxx xxxx
3805  $result .= chr( $bytevalue );
3806  $bytesleft = 0;
3807  } elseif ( $bytevalue <= 0xBF ) { // 10xx xxxx
3808  $working = $working << 6;
3809  $working += ( $bytevalue & 0x3F );
3810  $bytesleft--;
3811  if ( $bytesleft <= 0 ) {
3812  $result .= "&#x" . strtoupper( dechex( $working ) ) . ";";
3813  }
3814  } elseif ( $bytevalue <= 0xDF ) { // 110x xxxx
3815  $working = $bytevalue & 0x1F;
3816  $bytesleft = 1;
3817  } elseif ( $bytevalue <= 0xEF ) { // 1110 xxxx
3818  $working = $bytevalue & 0x0F;
3819  $bytesleft = 2;
3820  } else { // 1111 0xxx
3821  $working = $bytevalue & 0x07;
3822  $bytesleft = 3;
3823  }
3824  }
3825  return $result;
3826  }
3827 
3836  private function unmakeSafe( $invalue ) {
3837  $result = "";
3838  $valueLength = strlen( $invalue );
3839  for ( $i = 0; $i < $valueLength; $i++ ) {
3840  if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) {
3841  $i += 3;
3842  $hexstring = "";
3843  do {
3844  $hexstring .= $invalue[$i];
3845  $i++;
3846  } while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
3847 
3848  // Do some sanity checks. These aren't needed for reversibility,
3849  // but should help keep the breakage down if the editor
3850  // breaks one of the entities whilst editing.
3851  if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) {
3852  $codepoint = hexdec( $hexstring );
3853  $result .= codepointToUtf8( $codepoint );
3854  } else {
3855  $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
3856  }
3857  } else {
3858  $result .= substr( $invalue, $i, 1 );
3859  }
3860  }
3861  // reverse the transform that we made for reversibility reasons.
3862  return strtr( $result, array( "&#x0" => "&#x" ) );
3863  }
3864 }
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:572
$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:1565
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
$request
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled also a ContextSource error or success you ll probably need to make sure the header is varied on WebRequest $request
Definition: hooks.txt:1961
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:623
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:3714
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:2530
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:1087
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:160
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:1360
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:389
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:607
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:2124
$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:509
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:2038
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>".
Definition: Html.php:218
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:2307
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:159
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:1174
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:419
Html\element
static element( $element, $attribs=array(), $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:141
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:582
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:4066
User\isIP
static isIP( $name)
Does the string match an anonymous IPv4 address?
Definition: User.php:555
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:2561
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:590
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:2478
wfDebug
wfDebug( $text, $dest='all')
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:980
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:2124
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:1945
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:1369
$wgParser
$wgParser
Definition: Setup.php:587
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:2081
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:3757
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:2584
Html\rawElement
static rawElement( $element, $attribs=array(), $contents='')
Returns an HTML element in a string.
Definition: Html.php:121
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:544
ParserOptions\newFromUser
static newFromUser( $user)
Get a ParserOptions object from a given user.
Definition: ParserOptions.php:408
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