MediaWiki  1.23.15
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( $this->contentFormat, ContentHandler::getLocalizedName( $this->contentModel ) )
805  );
806  }
807  #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
808 
809  $this->live = $request->getCheck( 'live' );
810  $this->editintro = $request->getText( 'editintro',
811  // Custom edit intro for new sections
812  $this->section === 'new' ? 'MediaWiki:addsection-editintro' : '' );
813 
814  // Allow extensions to modify form data
815  wfRunHooks( 'EditPage::importFormData', array( $this, $request ) );
816 
817  wfProfileOut( __METHOD__ );
818  }
819 
828  protected function importContentFormData( &$request ) {
829  return; // Don't do anything, EditPage already extracted wpTextbox1
830  }
831 
837  function initialiseForm() {
838  global $wgUser;
839  $this->edittime = $this->mArticle->getTimestamp();
840 
841  $content = $this->getContentObject( false ); #TODO: track content object?!
842  if ( $content === false ) {
843  return false;
844  }
845  $this->textbox1 = $this->toEditText( $content );
846 
847  // activate checkboxes if user wants them to be always active
848  # Sort out the "watch" checkbox
849  if ( $wgUser->getOption( 'watchdefault' ) ) {
850  # Watch all edits
851  $this->watchthis = true;
852  } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
853  # Watch creations
854  $this->watchthis = true;
855  } elseif ( $wgUser->isWatched( $this->mTitle ) ) {
856  # Already watched
857  $this->watchthis = true;
858  }
859  if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
860  $this->minoredit = true;
861  }
862  if ( $this->textbox1 === false ) {
863  return false;
864  }
865  return true;
866  }
867 
876  function getContent( $def_text = false ) {
877  ContentHandler::deprecated( __METHOD__, '1.21' );
878 
879  if ( $def_text !== null && $def_text !== false && $def_text !== '' ) {
880  $def_content = $this->toEditContent( $def_text );
881  } else {
882  $def_content = false;
883  }
884 
885  $content = $this->getContentObject( $def_content );
886 
887  // Note: EditPage should only be used with text based content anyway.
888  return $this->toEditText( $content );
889  }
890 
898  protected function getContentObject( $def_content = null ) {
899  global $wgOut, $wgRequest, $wgUser, $wgContLang;
900 
901  wfProfileIn( __METHOD__ );
902 
903  $content = false;
904 
905  // For message page not locally set, use the i18n message.
906  // For other non-existent articles, use preload text if any.
907  if ( !$this->mTitle->exists() || $this->section == 'new' ) {
908  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
909  # If this is a system message, get the default text.
910  $msg = $this->mTitle->getDefaultMessageText();
911 
912  $content = $this->toEditContent( $msg );
913  }
914  if ( $content === false ) {
915  # If requested, preload some text.
916  $preload = $wgRequest->getVal( 'preload',
917  // Custom preload text for new sections
918  $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
919  $params = $wgRequest->getArray( 'preloadparams', array() );
920 
921  $content = $this->getPreloadedContent( $preload, $params );
922  }
923  // For existing pages, get text based on "undo" or section parameters.
924  } else {
925  if ( $this->section != '' ) {
926  // Get section edit text (returns $def_text for invalid sections)
927  $orig = $this->getOriginalContent( $wgUser );
928  $content = $orig ? $orig->getSection( $this->section ) : null;
929 
930  if ( !$content ) {
931  $content = $def_content;
932  }
933  } else {
934  $undoafter = $wgRequest->getInt( 'undoafter' );
935  $undo = $wgRequest->getInt( 'undo' );
936 
937  if ( $undo > 0 && $undoafter > 0 ) {
938 
939  $undorev = Revision::newFromId( $undo );
940  $oldrev = Revision::newFromId( $undoafter );
941 
942  # Sanity check, make sure it's the right page,
943  # the revisions exist and they were not deleted.
944  # Otherwise, $content will be left as-is.
945  if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
946  !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
947  !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
948 
949  $content = $this->mArticle->getUndoContent( $undorev, $oldrev );
950 
951  if ( $content === false ) {
952  # Warn the user that something went wrong
953  $undoMsg = 'failure';
954  } else {
955  $oldContent = $this->mArticle->getPage()->getContent( Revision::RAW );
957  $newContent = $content->preSaveTransform( $this->mTitle, $wgUser, $popts );
958 
959  if ( $newContent->equals( $oldContent ) ) {
960  # Tell the user that the undo results in no change,
961  # i.e. the revisions were already undone.
962  $undoMsg = 'nochange';
963  $content = false;
964  } else {
965  # Inform the user of our success and set an automatic edit summary
966  $undoMsg = 'success';
967 
968  # If we just undid one rev, use an autosummary
969  $firstrev = $oldrev->getNext();
970  if ( $firstrev && $firstrev->getId() == $undo ) {
971  $userText = $undorev->getUserText();
972  if ( $userText === '' ) {
973  $undoSummary = wfMessage(
974  'undo-summary-username-hidden',
975  $undo
976  )->inContentLanguage()->text();
977  } else {
978  $undoSummary = wfMessage(
979  'undo-summary',
980  $undo,
981  $userText
982  )->inContentLanguage()->text();
983  }
984  if ( $this->summary === '' ) {
985  $this->summary = $undoSummary;
986  } else {
987  $this->summary = $undoSummary . wfMessage( 'colon-separator' )
988  ->inContentLanguage()->text() . $this->summary;
989  }
990  $this->undidRev = $undo;
991  }
992  $this->formtype = 'diff';
993  }
994  }
995  } else {
996  // Failed basic sanity checks.
997  // Older revisions may have been removed since the link
998  // was created, or we may simply have got bogus input.
999  $undoMsg = 'norev';
1000  }
1001 
1002  // Messages: undo-success, undo-failure, undo-norev, undo-nochange
1003  $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
1004  $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
1005  wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
1006  }
1007 
1008  if ( $content === false ) {
1009  $content = $this->getOriginalContent( $wgUser );
1010  }
1011  }
1012  }
1013 
1014  wfProfileOut( __METHOD__ );
1015  return $content;
1016  }
1017 
1033  private function getOriginalContent( User $user ) {
1034  if ( $this->section == 'new' ) {
1035  return $this->getCurrentContent();
1036  }
1037  $revision = $this->mArticle->getRevisionFetched();
1038  if ( $revision === null ) {
1039  if ( !$this->contentModel ) {
1040  $this->contentModel = $this->getTitle()->getContentModel();
1041  }
1042  $handler = ContentHandler::getForModelID( $this->contentModel );
1043 
1044  return $handler->makeEmptyContent();
1045  }
1046  $content = $revision->getContent( Revision::FOR_THIS_USER, $user );
1047  return $content;
1048  }
1049 
1058  protected function getCurrentContent() {
1059  $rev = $this->mArticle->getRevision();
1060  $content = $rev ? $rev->getContent( Revision::RAW ) : null;
1061 
1062  if ( $content === false || $content === null ) {
1063  if ( !$this->contentModel ) {
1064  $this->contentModel = $this->getTitle()->getContentModel();
1065  }
1066  $handler = ContentHandler::getForModelID( $this->contentModel );
1067 
1068  return $handler->makeEmptyContent();
1069  } else {
1070  # nasty side-effect, but needed for consistency
1071  $this->contentModel = $rev->getContentModel();
1072  $this->contentFormat = $rev->getContentFormat();
1073 
1074  return $content;
1075  }
1076  }
1077 
1084  public function setPreloadedText( $text ) {
1085  ContentHandler::deprecated( __METHOD__, "1.21" );
1086 
1087  $content = $this->toEditContent( $text );
1088 
1089  $this->setPreloadedContent( $content );
1090  }
1091 
1099  public function setPreloadedContent( Content $content ) {
1100  $this->mPreloadContent = $content;
1101  }
1102 
1113  protected function getPreloadedText( $preload ) {
1114  ContentHandler::deprecated( __METHOD__, "1.21" );
1115 
1116  $content = $this->getPreloadedContent( $preload );
1117  $text = $this->toEditText( $content );
1118 
1119  return $text;
1120  }
1121 
1133  protected function getPreloadedContent( $preload, $params = array() ) {
1134  global $wgUser;
1135 
1136  if ( !empty( $this->mPreloadContent ) ) {
1137  return $this->mPreloadContent;
1138  }
1139 
1140  $handler = ContentHandler::getForTitle( $this->getTitle() );
1141 
1142  if ( $preload === '' ) {
1143  return $handler->makeEmptyContent();
1144  }
1145 
1146  $title = Title::newFromText( $preload );
1147  # Check for existence to avoid getting MediaWiki:Noarticletext
1148  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
1149  //TODO: somehow show a warning to the user!
1150  return $handler->makeEmptyContent();
1151  }
1152 
1153  $page = WikiPage::factory( $title );
1154  if ( $page->isRedirect() ) {
1155  $title = $page->getRedirectTarget();
1156  # Same as before
1157  if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
1158  //TODO: somehow show a warning to the user!
1159  return $handler->makeEmptyContent();
1160  }
1161  $page = WikiPage::factory( $title );
1162  }
1163 
1164  $parserOptions = ParserOptions::newFromUser( $wgUser );
1165  $content = $page->getContent( Revision::RAW );
1166 
1167  if ( !$content ) {
1168  //TODO: somehow show a warning to the user!
1169  return $handler->makeEmptyContent();
1170  }
1171 
1172  if ( $content->getModel() !== $handler->getModelID() ) {
1173  $converted = $content->convert( $handler->getModelID() );
1174 
1175  if ( !$converted ) {
1176  //TODO: somehow show a warning to the user!
1177  wfDebug( "Attempt to preload incompatible content: "
1178  . "can't convert " . $content->getModel()
1179  . " to " . $handler->getModelID() );
1180 
1181  return $handler->makeEmptyContent();
1182  }
1183 
1184  $content = $converted;
1185  }
1186 
1187  return $content->preloadTransform( $title, $parserOptions, $params );
1188  }
1189 
1197  function tokenOk( &$request ) {
1198  global $wgUser;
1199  $token = $request->getVal( 'wpEditToken' );
1200  $this->mTokenOk = $wgUser->matchEditToken( $token );
1201  $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
1202  return $this->mTokenOk;
1203  }
1204 
1221  protected function setPostEditCookie() {
1222  $revisionId = $this->mArticle->getLatest();
1223  $postEditKey = self::POST_EDIT_COOKIE_KEY_PREFIX . $revisionId;
1224 
1225  $response = RequestContext::getMain()->getRequest()->response();
1226  $response->setcookie( $postEditKey, '1', time() + self::POST_EDIT_COOKIE_DURATION, array(
1227  'path' => '/',
1228  'httpOnly' => false,
1229  ) );
1230  }
1231 
1237  public function attemptSave() {
1238  global $wgUser;
1239 
1240  $resultDetails = false;
1241  # Allow bots to exempt some edits from bot flagging
1242  $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
1243  $status = $this->internalAttemptSave( $resultDetails, $bot );
1244 
1245  return $this->handleStatus( $status, $resultDetails );
1246  }
1247 
1257  private function handleStatus( Status $status, $resultDetails ) {
1259 
1260  // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
1261  if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
1262  $this->didSave = true;
1263  if ( !$resultDetails['nullEdit'] ) {
1264  $this->setPostEditCookie();
1265  }
1266  }
1267 
1268  switch ( $status->value ) {
1269  case self::AS_HOOK_ERROR_EXPECTED:
1270  case self::AS_CONTENT_TOO_BIG:
1271  case self::AS_ARTICLE_WAS_DELETED:
1272  case self::AS_CONFLICT_DETECTED:
1273  case self::AS_SUMMARY_NEEDED:
1274  case self::AS_TEXTBOX_EMPTY:
1275  case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
1276  case self::AS_END:
1277  return true;
1278 
1279  case self::AS_HOOK_ERROR:
1280  return false;
1281 
1282  case self::AS_PARSE_ERROR:
1283  $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>' );
1284  return true;
1285 
1286  case self::AS_SUCCESS_NEW_ARTICLE:
1287  $query = $resultDetails['redirect'] ? 'redirect=no' : '';
1288  $anchor = isset( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
1289  $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
1290  return false;
1291 
1292  case self::AS_SUCCESS_UPDATE:
1293  $extraQuery = '';
1294  $sectionanchor = $resultDetails['sectionanchor'];
1295 
1296  // Give extensions a chance to modify URL query on update
1297  wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this->mArticle, &$sectionanchor, &$extraQuery ) );
1298 
1299  if ( $resultDetails['redirect'] ) {
1300  if ( $extraQuery == '' ) {
1301  $extraQuery = 'redirect=no';
1302  } else {
1303  $extraQuery = 'redirect=no&' . $extraQuery;
1304  }
1305  }
1306  $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
1307  return false;
1308 
1309  case self::AS_BLANK_ARTICLE:
1310  $wgOut->redirect( $this->getContextTitle()->getFullURL() );
1311  return false;
1312 
1313  case self::AS_SPAM_ERROR:
1314  $this->spamPageWithContent( $resultDetails['spam'] );
1315  return false;
1316 
1317  case self::AS_BLOCKED_PAGE_FOR_USER:
1318  throw new UserBlockedError( $wgUser->getBlock() );
1319 
1320  case self::AS_IMAGE_REDIRECT_ANON:
1321  case self::AS_IMAGE_REDIRECT_LOGGED:
1322  throw new PermissionsError( 'upload' );
1323 
1324  case self::AS_READ_ONLY_PAGE_ANON:
1325  case self::AS_READ_ONLY_PAGE_LOGGED:
1326  throw new PermissionsError( 'edit' );
1327 
1328  case self::AS_READ_ONLY_PAGE:
1329  throw new ReadOnlyError;
1330 
1331  case self::AS_RATE_LIMITED:
1332  throw new ThrottledError();
1333 
1334  case self::AS_NO_CREATE_PERMISSION:
1335  $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
1336  throw new PermissionsError( $permission );
1337 
1338  case self::AS_NO_CHANGE_CONTENT_MODEL:
1339  throw new PermissionsError( 'editcontentmodel' );
1340 
1341  default:
1342  // We don't recognize $status->value. The only way that can happen
1343  // is if an extension hook aborted from inside ArticleSave.
1344  // Render the status object into $this->hookError
1345  // FIXME this sucks, we should just use the Status object throughout
1346  $this->hookError = '<div class="error">' . $status->getWikitext() .
1347  '</div>';
1348  return true;
1349  }
1350  }
1351 
1361  protected function runPostMergeFilters( Content $content, Status $status, User $user ) {
1362  // Run old style post-section-merge edit filter
1363  if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged',
1364  array( $this, $content, &$this->hookError, $this->summary ) ) ) {
1365 
1366  # Error messages etc. could be handled within the hook...
1367  $status->fatal( 'hookaborted' );
1368  $status->value = self::AS_HOOK_ERROR;
1369  return false;
1370  } elseif ( $this->hookError != '' ) {
1371  # ...or the hook could be expecting us to produce an error
1372  $status->fatal( 'hookaborted' );
1373  $status->value = self::AS_HOOK_ERROR_EXPECTED;
1374  return false;
1375  }
1376 
1377  // Run new style post-section-merge edit filter
1378  if ( !wfRunHooks( 'EditFilterMergedContent',
1379  array( $this->mArticle->getContext(), $content, $status, $this->summary,
1380  $user, $this->minoredit ) ) ) {
1381 
1382  # Error messages etc. could be handled within the hook...
1383  // XXX: $status->value may already be something informative...
1384  $this->hookError = $status->getWikiText();
1385  $status->fatal( 'hookaborted' );
1386  $status->value = self::AS_HOOK_ERROR;
1387  return false;
1388  } elseif ( !$status->isOK() ) {
1389  # ...or the hook could be expecting us to produce an error
1390  // FIXME this sucks, we should just use the Status object throughout
1391  $this->hookError = $status->getWikiText();
1392  $status->fatal( 'hookaborted' );
1393  $status->value = self::AS_HOOK_ERROR_EXPECTED;
1394  return false;
1395  }
1396 
1397  return true;
1398  }
1399 
1416  function internalAttemptSave( &$result, $bot = false ) {
1417  global $wgUser, $wgRequest, $wgParser, $wgMaxArticleSize;
1418 
1419  $status = Status::newGood();
1420 
1421  wfProfileIn( __METHOD__ );
1422  wfProfileIn( __METHOD__ . '-checks' );
1423 
1424  if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
1425  wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
1426  $status->fatal( 'hookaborted' );
1427  $status->value = self::AS_HOOK_ERROR;
1428  wfProfileOut( __METHOD__ . '-checks' );
1429  wfProfileOut( __METHOD__ );
1430  return $status;
1431  }
1432 
1433  $spam = $wgRequest->getText( 'wpAntispam' );
1434  if ( $spam !== '' ) {
1435  wfDebugLog(
1436  'SimpleAntiSpam',
1437  $wgUser->getName() .
1438  ' editing "' .
1439  $this->mTitle->getPrefixedText() .
1440  '" submitted bogus field "' .
1441  $spam .
1442  '"'
1443  );
1444  $status->fatal( 'spamprotectionmatch', false );
1445  $status->value = self::AS_SPAM_ERROR;
1446  wfProfileOut( __METHOD__ . '-checks' );
1447  wfProfileOut( __METHOD__ );
1448  return $status;
1449  }
1450 
1451  try {
1452  # Construct Content object
1453  $textbox_content = $this->toEditContent( $this->textbox1 );
1454  } catch ( MWContentSerializationException $ex ) {
1455  $status->fatal( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
1456  $status->value = self::AS_PARSE_ERROR;
1457  wfProfileOut( __METHOD__ . '-checks' );
1458  wfProfileOut( __METHOD__ );
1459  return $status;
1460  }
1461 
1462  # Check image redirect
1463  if ( $this->mTitle->getNamespace() == NS_FILE &&
1464  $textbox_content->isRedirect() &&
1465  !$wgUser->isAllowed( 'upload' ) ) {
1466  $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
1467  $status->setResult( false, $code );
1468 
1469  wfProfileOut( __METHOD__ . '-checks' );
1470  wfProfileOut( __METHOD__ );
1471 
1472  return $status;
1473  }
1474 
1475  # Check for spam
1476  $match = self::matchSummarySpamRegex( $this->summary );
1477  if ( $match === false && $this->section == 'new' ) {
1478  # $wgSpamRegex is enforced on this new heading/summary because, unlike
1479  # regular summaries, it is added to the actual wikitext.
1480  if ( $this->sectiontitle !== '' ) {
1481  # This branch is taken when the API is used with the 'sectiontitle' parameter.
1482  $match = self::matchSpamRegex( $this->sectiontitle );
1483  } else {
1484  # This branch is taken when the "Add Topic" user interface is used, or the API
1485  # is used with the 'summary' parameter.
1486  $match = self::matchSpamRegex( $this->summary );
1487  }
1488  }
1489  if ( $match === false ) {
1490  $match = self::matchSpamRegex( $this->textbox1 );
1491  }
1492  if ( $match !== false ) {
1493  $result['spam'] = $match;
1494  $ip = $wgRequest->getIP();
1495  $pdbk = $this->mTitle->getPrefixedDBkey();
1496  $match = str_replace( "\n", '', $match );
1497  wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
1498  $status->fatal( 'spamprotectionmatch', $match );
1499  $status->value = self::AS_SPAM_ERROR;
1500  wfProfileOut( __METHOD__ . '-checks' );
1501  wfProfileOut( __METHOD__ );
1502  return $status;
1503  }
1504  if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) {
1505  # Error messages etc. could be handled within the hook...
1506  $status->fatal( 'hookaborted' );
1507  $status->value = self::AS_HOOK_ERROR;
1508  wfProfileOut( __METHOD__ . '-checks' );
1509  wfProfileOut( __METHOD__ );
1510  return $status;
1511  } elseif ( $this->hookError != '' ) {
1512  # ...or the hook could be expecting us to produce an error
1513  $status->fatal( 'hookaborted' );
1514  $status->value = self::AS_HOOK_ERROR_EXPECTED;
1515  wfProfileOut( __METHOD__ . '-checks' );
1516  wfProfileOut( __METHOD__ );
1517  return $status;
1518  }
1519 
1520  if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) {
1521  // Auto-block user's IP if the account was "hard" blocked
1522  $wgUser->spreadAnyEditBlock();
1523  # Check block state against master, thus 'false'.
1524  $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
1525  wfProfileOut( __METHOD__ . '-checks' );
1526  wfProfileOut( __METHOD__ );
1527  return $status;
1528  }
1529 
1530  $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
1531  if ( $this->kblength > $wgMaxArticleSize ) {
1532  // Error will be displayed by showEditForm()
1533  $this->tooBig = true;
1534  $status->setResult( false, self::AS_CONTENT_TOO_BIG );
1535  wfProfileOut( __METHOD__ . '-checks' );
1536  wfProfileOut( __METHOD__ );
1537  return $status;
1538  }
1539 
1540  if ( !$wgUser->isAllowed( 'edit' ) ) {
1541  if ( $wgUser->isAnon() ) {
1542  $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
1543  wfProfileOut( __METHOD__ . '-checks' );
1544  wfProfileOut( __METHOD__ );
1545  return $status;
1546  } else {
1547  $status->fatal( 'readonlytext' );
1548  $status->value = self::AS_READ_ONLY_PAGE_LOGGED;
1549  wfProfileOut( __METHOD__ . '-checks' );
1550  wfProfileOut( __METHOD__ );
1551  return $status;
1552  }
1553  }
1554 
1555  if ( $this->contentModel !== $this->mTitle->getContentModel()
1556  && !$wgUser->isAllowed( 'editcontentmodel' )
1557  ) {
1558  $status->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL );
1559  wfProfileOut( __METHOD__ . '-checks' );
1560  wfProfileOut( __METHOD__ );
1561  return $status;
1562  }
1563 
1564  if ( wfReadOnly() ) {
1565  $status->fatal( 'readonlytext' );
1566  $status->value = self::AS_READ_ONLY_PAGE;
1567  wfProfileOut( __METHOD__ . '-checks' );
1568  wfProfileOut( __METHOD__ );
1569  return $status;
1570  }
1571  if ( $wgUser->pingLimiter() || $wgUser->pingLimiter( 'linkpurge', 0 ) ) {
1572  $status->fatal( 'actionthrottledtext' );
1573  $status->value = self::AS_RATE_LIMITED;
1574  wfProfileOut( __METHOD__ . '-checks' );
1575  wfProfileOut( __METHOD__ );
1576  return $status;
1577  }
1578 
1579  # If the article has been deleted while editing, don't save it without
1580  # confirmation
1581  if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
1582  $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
1583  wfProfileOut( __METHOD__ . '-checks' );
1584  wfProfileOut( __METHOD__ );
1585  return $status;
1586  }
1587 
1588  wfProfileOut( __METHOD__ . '-checks' );
1589 
1590  # Load the page data from the master. If anything changes in the meantime,
1591  # we detect it by using page_latest like a token in a 1 try compare-and-swap.
1592  $this->mArticle->loadPageData( 'fromdbmaster' );
1593  $new = !$this->mArticle->exists();
1594 
1595  if ( $new ) {
1596  // Late check for create permission, just in case *PARANOIA*
1597  if ( !$this->mTitle->userCan( 'create', $wgUser ) ) {
1598  $status->fatal( 'nocreatetext' );
1599  $status->value = self::AS_NO_CREATE_PERMISSION;
1600  wfDebug( __METHOD__ . ": no create permission\n" );
1601  wfProfileOut( __METHOD__ );
1602  return $status;
1603  }
1604 
1605  // Don't save a new page if it's blank or if it's a MediaWiki:
1606  // message with content equivalent to default (allow empty pages
1607  // in this case to disable messages, see bug 50124)
1608  $defaultMessageText = $this->mTitle->getDefaultMessageText();
1609  if ( $this->mTitle->getNamespace() === NS_MEDIAWIKI && $defaultMessageText !== false ) {
1610  $defaultText = $defaultMessageText;
1611  } else {
1612  $defaultText = '';
1613  }
1614 
1615  if ( $this->textbox1 === $defaultText ) {
1616  $status->setResult( false, self::AS_BLANK_ARTICLE );
1617  wfProfileOut( __METHOD__ );
1618  return $status;
1619  }
1620 
1621  if ( !$this->runPostMergeFilters( $textbox_content, $status, $wgUser ) ) {
1622  wfProfileOut( __METHOD__ );
1623  return $status;
1624  }
1625 
1626  $content = $textbox_content;
1627 
1628  $result['sectionanchor'] = '';
1629  if ( $this->section == 'new' ) {
1630  if ( $this->sectiontitle !== '' ) {
1631  // Insert the section title above the content.
1632  $content = $content->addSectionHeader( $this->sectiontitle );
1633 
1634  // Jump to the new section
1635  $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
1636 
1637  // If no edit summary was specified, create one automatically from the section
1638  // title and have it link to the new section. Otherwise, respect the summary as
1639  // passed.
1640  if ( $this->summary === '' ) {
1641  $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
1642  $this->summary = wfMessage( 'newsectionsummary' )
1643  ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1644  }
1645  } elseif ( $this->summary !== '' ) {
1646  // Insert the section title above the content.
1647  $content = $content->addSectionHeader( $this->summary );
1648 
1649  // Jump to the new section
1650  $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
1651 
1652  // Create a link to the new section from the edit summary.
1653  $cleanSummary = $wgParser->stripSectionName( $this->summary );
1654  $this->summary = wfMessage( 'newsectionsummary' )
1655  ->rawParams( $cleanSummary )->inContentLanguage()->text();
1656  }
1657  }
1658 
1659  $status->value = self::AS_SUCCESS_NEW_ARTICLE;
1660 
1661  } else { # not $new
1662 
1663  # Article exists. Check for edit conflict.
1664 
1665  $this->mArticle->clear(); # Force reload of dates, etc.
1666  $timestamp = $this->mArticle->getTimestamp();
1667 
1668  wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
1669 
1670  if ( $timestamp != $this->edittime ) {
1671  $this->isConflict = true;
1672  if ( $this->section == 'new' ) {
1673  if ( $this->mArticle->getUserText() == $wgUser->getName() &&
1674  $this->mArticle->getComment() == $this->summary ) {
1675  // Probably a duplicate submission of a new comment.
1676  // This can happen when squid resends a request after
1677  // a timeout but the first one actually went through.
1678  wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
1679  } else {
1680  // New comment; suppress conflict.
1681  $this->isConflict = false;
1682  wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
1683  }
1684  } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER, $this->mTitle->getArticleID(),
1685  $wgUser->getId(), $this->edittime ) ) {
1686  # Suppress edit conflict with self, except for section edits where merging is required.
1687  wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
1688  $this->isConflict = false;
1689  }
1690  }
1691 
1692  // If sectiontitle is set, use it, otherwise use the summary as the section title.
1693  if ( $this->sectiontitle !== '' ) {
1694  $sectionTitle = $this->sectiontitle;
1695  } else {
1696  $sectionTitle = $this->summary;
1697  }
1698 
1699  $content = null;
1700 
1701  if ( $this->isConflict ) {
1702  wfDebug( __METHOD__ . ": conflict! getting section '{$this->section}' for time '{$this->edittime}'"
1703  . " (article time '{$timestamp}')\n" );
1704 
1705  $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime );
1706  } else {
1707  wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
1708  $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
1709  }
1710 
1711  if ( is_null( $content ) ) {
1712  wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
1713  $this->isConflict = true;
1714  $content = $textbox_content; // do not try to merge here!
1715  } elseif ( $this->isConflict ) {
1716  # Attempt merge
1717  if ( $this->mergeChangesIntoContent( $content ) ) {
1718  // Successful merge! Maybe we should tell the user the good news?
1719  $this->isConflict = false;
1720  wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
1721  } else {
1722  $this->section = '';
1723  $this->textbox1 = ContentHandler::getContentText( $content );
1724  wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
1725  }
1726  }
1727 
1728  if ( $this->isConflict ) {
1729  $status->setResult( false, self::AS_CONFLICT_DETECTED );
1730  wfProfileOut( __METHOD__ );
1731  return $status;
1732  }
1733 
1734  if ( !$this->runPostMergeFilters( $content, $status, $wgUser ) ) {
1735  wfProfileOut( __METHOD__ );
1736  return $status;
1737  }
1738 
1739  if ( $this->section == 'new' ) {
1740  // Handle the user preference to force summaries here
1741  if ( !$this->allowBlankSummary && trim( $this->summary ) == '' ) {
1742  $this->missingSummary = true;
1743  $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
1744  $status->value = self::AS_SUMMARY_NEEDED;
1745  wfProfileOut( __METHOD__ );
1746  return $status;
1747  }
1748 
1749  // Do not allow the user to post an empty comment
1750  if ( $this->textbox1 == '' ) {
1751  $this->missingComment = true;
1752  $status->fatal( 'missingcommenttext' );
1753  $status->value = self::AS_TEXTBOX_EMPTY;
1754  wfProfileOut( __METHOD__ );
1755  return $status;
1756  }
1757  } elseif ( !$this->allowBlankSummary
1758  && !$content->equals( $this->getOriginalContent( $wgUser ) )
1759  && !$content->isRedirect()
1760  && md5( $this->summary ) == $this->autoSumm
1761  ) {
1762  $this->missingSummary = true;
1763  $status->fatal( 'missingsummary' );
1764  $status->value = self::AS_SUMMARY_NEEDED;
1765  wfProfileOut( __METHOD__ );
1766  return $status;
1767  }
1768 
1769  # All's well
1770  wfProfileIn( __METHOD__ . '-sectionanchor' );
1771  $sectionanchor = '';
1772  if ( $this->section == 'new' ) {
1773  if ( $this->sectiontitle !== '' ) {
1774  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
1775  // If no edit summary was specified, create one automatically from the section
1776  // title and have it link to the new section. Otherwise, respect the summary as
1777  // passed.
1778  if ( $this->summary === '' ) {
1779  $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
1780  $this->summary = wfMessage( 'newsectionsummary' )
1781  ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
1782  }
1783  } elseif ( $this->summary !== '' ) {
1784  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
1785  # This is a new section, so create a link to the new section
1786  # in the revision summary.
1787  $cleanSummary = $wgParser->stripSectionName( $this->summary );
1788  $this->summary = wfMessage( 'newsectionsummary' )
1789  ->rawParams( $cleanSummary )->inContentLanguage()->text();
1790  }
1791  } elseif ( $this->section != '' ) {
1792  # Try to get a section anchor from the section source, redirect to edited section if header found
1793  # XXX: might be better to integrate this into Article::replaceSection
1794  # for duplicate heading checking and maybe parsing
1795  $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
1796  # we can't deal with anchors, includes, html etc in the header for now,
1797  # headline would need to be parsed to improve this
1798  if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
1799  $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
1800  }
1801  }
1802  $result['sectionanchor'] = $sectionanchor;
1803  wfProfileOut( __METHOD__ . '-sectionanchor' );
1804 
1805  // Save errors may fall down to the edit form, but we've now
1806  // merged the section into full text. Clear the section field
1807  // so that later submission of conflict forms won't try to
1808  // replace that into a duplicated mess.
1809  $this->textbox1 = $this->toEditText( $content );
1810  $this->section = '';
1811 
1812  $status->value = self::AS_SUCCESS_UPDATE;
1813  }
1814 
1815  // Check for length errors again now that the section is merged in
1816  $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
1817  if ( $this->kblength > $wgMaxArticleSize ) {
1818  $this->tooBig = true;
1819  $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
1820  wfProfileOut( __METHOD__ );
1821  return $status;
1822  }
1823 
1825  ( $new ? EDIT_NEW : EDIT_UPDATE ) |
1826  ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
1827  ( $bot ? EDIT_FORCE_BOT : 0 );
1828 
1829  $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary, $flags,
1830  false, null, $this->contentFormat );
1831 
1832  if ( !$doEditStatus->isOK() ) {
1833  // Failure from doEdit()
1834  // Show the edit conflict page for certain recognized errors from doEdit(),
1835  // but don't show it for errors from extension hooks
1836  $errors = $doEditStatus->getErrorsArray();
1837  if ( in_array( $errors[0][0],
1838  array( 'edit-gone-missing', 'edit-conflict', 'edit-already-exists' ) )
1839  ) {
1840  $this->isConflict = true;
1841  // Destroys data doEdit() put in $status->value but who cares
1842  $doEditStatus->value = self::AS_END;
1843  }
1844  wfProfileOut( __METHOD__ );
1845  return $doEditStatus;
1846  }
1847 
1848  $result['nullEdit'] = $doEditStatus->hasMessage( 'edit-no-change' );
1849  if ( $result['nullEdit'] ) {
1850  // We don't know if it was a null edit until now, so increment here
1851  $wgUser->pingLimiter( 'linkpurge' );
1852  }
1853  $result['redirect'] = $content->isRedirect();
1854  $this->updateWatchlist();
1855  wfProfileOut( __METHOD__ );
1856  return $status;
1857  }
1858 
1862  protected function updateWatchlist() {
1863  global $wgUser;
1864 
1865  if ( $wgUser->isLoggedIn()
1866  && $this->watchthis != $wgUser->isWatched( $this->mTitle, WatchedItem::IGNORE_USER_RIGHTS )
1867  ) {
1868  $fname = __METHOD__;
1869  $title = $this->mTitle;
1870  $watch = $this->watchthis;
1871 
1872  // Do this in its own transaction to reduce contention...
1873  $dbw = wfGetDB( DB_MASTER );
1874  $dbw->onTransactionIdle( function() use ( $dbw, $title, $watch, $wgUser, $fname ) {
1875  $dbw->begin( $fname );
1877  $dbw->commit( $fname );
1878  } );
1879  }
1880  }
1881 
1890  function mergeChangesInto( &$editText ) {
1891  ContentHandler::deprecated( __METHOD__, "1.21" );
1892 
1893  $editContent = $this->toEditContent( $editText );
1894 
1895  $ok = $this->mergeChangesIntoContent( $editContent );
1896 
1897  if ( $ok ) {
1898  $editText = $this->toEditText( $editContent );
1899  return true;
1900  }
1901  return false;
1902  }
1903 
1915  private function mergeChangesIntoContent( &$editContent ) {
1916  wfProfileIn( __METHOD__ );
1917 
1918  $db = wfGetDB( DB_MASTER );
1919 
1920  // This is the revision the editor started from
1921  $baseRevision = $this->getBaseRevision();
1922  $baseContent = $baseRevision ? $baseRevision->getContent() : null;
1923 
1924  if ( is_null( $baseContent ) ) {
1925  wfProfileOut( __METHOD__ );
1926  return false;
1927  }
1928 
1929  // The current state, we want to merge updates into it
1930  $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
1931  $currentContent = $currentRevision ? $currentRevision->getContent() : null;
1932 
1933  if ( is_null( $currentContent ) ) {
1934  wfProfileOut( __METHOD__ );
1935  return false;
1936  }
1937 
1938  $handler = ContentHandler::getForModelID( $baseContent->getModel() );
1939 
1940  $result = $handler->merge3( $baseContent, $editContent, $currentContent );
1941 
1942  if ( $result ) {
1943  $editContent = $result;
1944  wfProfileOut( __METHOD__ );
1945  return true;
1946  }
1947 
1948  wfProfileOut( __METHOD__ );
1949  return false;
1950  }
1951 
1955  function getBaseRevision() {
1956  if ( !$this->mBaseRevision ) {
1957  $db = wfGetDB( DB_MASTER );
1958  $this->mBaseRevision = Revision::loadFromTimestamp(
1959  $db, $this->mTitle, $this->edittime );
1960  }
1961  return $this->mBaseRevision;
1962  }
1963 
1971  public static function matchSpamRegex( $text ) {
1972  global $wgSpamRegex;
1973  // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
1974  $regexes = (array)$wgSpamRegex;
1975  return self::matchSpamRegexInternal( $text, $regexes );
1976  }
1977 
1985  public static function matchSummarySpamRegex( $text ) {
1986  global $wgSummarySpamRegex;
1987  $regexes = (array)$wgSummarySpamRegex;
1988  return self::matchSpamRegexInternal( $text, $regexes );
1989  }
1990 
1996  protected static function matchSpamRegexInternal( $text, $regexes ) {
1997  foreach ( $regexes as $regex ) {
1998  $matches = array();
1999  if ( preg_match( $regex, $text, $matches ) ) {
2000  return $matches[0];
2001  }
2002  }
2003  return false;
2004  }
2005 
2006  function setHeaders() {
2008 
2009  $wgOut->addModules( 'mediawiki.action.edit' );
2010  $wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
2011 
2012  if ( $wgUser->getOption( 'uselivepreview', false ) ) {
2013  $wgOut->addModules( 'mediawiki.action.edit.preview' );
2014  }
2015 
2016  if ( $wgUser->getOption( 'useeditwarning', false ) ) {
2017  $wgOut->addModules( 'mediawiki.action.edit.editWarning' );
2018  }
2019 
2020  $wgOut->setRobotPolicy( 'noindex,nofollow' );
2021 
2022  # Enabled article-related sidebar, toplinks, etc.
2023  $wgOut->setArticleRelated( true );
2024 
2025  $contextTitle = $this->getContextTitle();
2026  if ( $this->isConflict ) {
2027  $msg = 'editconflict';
2028  } elseif ( $contextTitle->exists() && $this->section != '' ) {
2029  $msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
2030  } else {
2031  $msg = $contextTitle->exists() || ( $contextTitle->getNamespace() == NS_MEDIAWIKI && $contextTitle->getDefaultMessageText() !== false ) ?
2032  'editing' : 'creating';
2033  }
2034  # Use the title defined by DISPLAYTITLE magic word when present
2035  $displayTitle = isset( $this->mParserOutput ) ? $this->mParserOutput->getDisplayTitle() : false;
2036  if ( $displayTitle === false ) {
2037  $displayTitle = $contextTitle->getPrefixedText();
2038  }
2039  $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) );
2040  }
2041 
2045  protected function showIntro() {
2047  if ( $this->suppressIntro ) {
2048  return;
2049  }
2050 
2051  $namespace = $this->mTitle->getNamespace();
2052 
2053  if ( $namespace == NS_MEDIAWIKI ) {
2054  # Show a warning if editing an interface message
2055  $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
2056  } elseif ( $namespace == NS_FILE ) {
2057  # Show a hint to shared repo
2058  $file = wfFindFile( $this->mTitle );
2059  if ( $file && !$file->isLocal() ) {
2060  $descUrl = $file->getDescriptionUrl();
2061  # there must be a description url to show a hint to shared repo
2062  if ( $descUrl ) {
2063  if ( !$this->mTitle->exists() ) {
2064  $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-create\">\n$1\n</div>", array(
2065  'sharedupload-desc-create', $file->getRepo()->getDisplayName(), $descUrl
2066  ) );
2067  } else {
2068  $wgOut->wrapWikiMsg( "<div class=\"mw-sharedupload-desc-edit\">\n$1\n</div>", array(
2069  'sharedupload-desc-edit', $file->getRepo()->getDisplayName(), $descUrl
2070  ) );
2071  }
2072  }
2073  }
2074  }
2075 
2076  # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
2077  # Show log extract when the user is currently blocked
2078  if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
2079  $parts = explode( '/', $this->mTitle->getText(), 2 );
2080  $username = $parts[0];
2081  $user = User::newFromName( $username, false /* allow IP users*/ );
2082  $ip = User::isIP( $username );
2083  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
2084  $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
2085  array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
2086  } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
2088  $wgOut,
2089  'block',
2090  $user->getUserPage(),
2091  '',
2092  array(
2093  'lim' => 1,
2094  'showIfEmpty' => false,
2095  'msgKey' => array(
2096  'blocked-notice-logextract',
2097  $user->getName() # Support GENDER in notice
2098  )
2099  )
2100  );
2101  }
2102  }
2103  # Try to add a custom edit intro, or use the standard one if this is not possible.
2104  if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
2106  wfMessage( 'helppage' )->inContentLanguage()->text()
2107  ) );
2108  if ( $wgUser->isLoggedIn() ) {
2109  $wgOut->wrapWikiMsg(
2110  // Suppress the external link icon, consider the help url an internal one
2111  "<div class=\"mw-newarticletext plainlinks\">\n$1\n</div>",
2112  array(
2113  'newarticletext',
2114  $helpLink
2115  )
2116  );
2117  } else {
2118  $wgOut->wrapWikiMsg(
2119  // Suppress the external link icon, consider the help url an internal one
2120  "<div class=\"mw-newarticletextanon plainlinks\">\n$1\n</div>",
2121  array(
2122  'newarticletextanon',
2123  $helpLink
2124  )
2125  );
2126  }
2127  }
2128  # Give a notice if the user is editing a deleted/moved page...
2129  if ( !$this->mTitle->exists() ) {
2130  LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle,
2131  '',
2132  array(
2133  'lim' => 10,
2134  'conds' => array( "log_action != 'revision'" ),
2135  'showIfEmpty' => false,
2136  'msgKey' => array( 'recreate-moveddeleted-warn' )
2137  )
2138  );
2139  }
2140  }
2141 
2147  protected function showCustomIntro() {
2148  if ( $this->editintro ) {
2149  $title = Title::newFromText( $this->editintro );
2150  if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
2151  global $wgOut;
2152  // Added using template syntax, to take <noinclude>'s into account.
2153  $wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
2154  return true;
2155  }
2156  }
2157  return false;
2158  }
2159 
2176  protected function toEditText( $content ) {
2177  if ( $content === null || $content === false ) {
2178  return $content;
2179  }
2180 
2181  if ( is_string( $content ) ) {
2182  return $content;
2183  }
2184 
2185  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2186  throw new MWException( 'This content model is not supported: '
2187  . ContentHandler::getLocalizedName( $content->getModel() ) );
2188  }
2189 
2190  return $content->serialize( $this->contentFormat );
2191  }
2192 
2207  protected function toEditContent( $text ) {
2208  if ( $text === false || $text === null ) {
2209  return $text;
2210  }
2211 
2212  $content = ContentHandler::makeContent( $text, $this->getTitle(),
2213  $this->contentModel, $this->contentFormat );
2214 
2215  if ( !$this->isSupportedContentModel( $content->getModel() ) ) {
2216  throw new MWException( 'This content model is not supported: '
2217  . ContentHandler::getLocalizedName( $content->getModel() ) );
2218  }
2219 
2220  return $content;
2221  }
2222 
2228  function showEditForm( $formCallback = null ) {
2230 
2231  wfProfileIn( __METHOD__ );
2232 
2233  # need to parse the preview early so that we know which templates are used,
2234  # otherwise users with "show preview after edit box" will get a blank list
2235  # we parse this near the beginning so that setHeaders can do the title
2236  # setting work instead of leaving it in getPreviewText
2237  $previewOutput = '';
2238  if ( $this->formtype == 'preview' ) {
2239  $previewOutput = $this->getPreviewText();
2240  }
2241 
2242  wfRunHooks( 'EditPage::showEditForm:initial', array( &$this, &$wgOut ) );
2243 
2244  $this->setHeaders();
2245 
2246  if ( $this->showHeader() === false ) {
2247  wfProfileOut( __METHOD__ );
2248  return;
2249  }
2250 
2251  $wgOut->addHTML( $this->editFormPageTop );
2252 
2253  if ( $wgUser->getOption( 'previewontop' ) ) {
2254  $this->displayPreviewArea( $previewOutput, true );
2255  }
2256 
2257  $wgOut->addHTML( $this->editFormTextTop );
2258 
2259  $showToolbar = true;
2260  if ( $this->wasDeletedSinceLastEdit() ) {
2261  if ( $this->formtype == 'save' ) {
2262  // Hide the toolbar and edit area, user can click preview to get it back
2263  // Add an confirmation checkbox and explanation.
2264  $showToolbar = false;
2265  } else {
2266  $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
2267  'deletedwhileediting' );
2268  }
2269  }
2270 
2271  // @todo add EditForm plugin interface and use it here!
2272  // search for textarea1 and textares2, and allow EditForm to override all uses.
2273  $wgOut->addHTML( Html::openElement( 'form', array( 'id' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID,
2274  'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
2275  'enctype' => 'multipart/form-data' ) ) );
2276 
2277  if ( is_callable( $formCallback ) ) {
2278  call_user_func_array( $formCallback, array( &$wgOut ) );
2279  }
2280 
2281  // Add an empty field to trip up spambots
2282  $wgOut->addHTML(
2283  Xml::openElement( 'div', array( 'id' => 'antispam-container', 'style' => 'display: none;' ) )
2284  . Html::rawElement( 'label', array( 'for' => 'wpAntiSpam' ), wfMessage( 'simpleantispam-label' )->parse() )
2285  . Xml::element( 'input', array( 'type' => 'text', 'name' => 'wpAntispam', 'id' => 'wpAntispam', 'value' => '' ) )
2286  . Xml::closeElement( 'div' )
2287  );
2288 
2289  wfRunHooks( 'EditPage::showEditForm:fields', array( &$this, &$wgOut ) );
2290 
2291  // Put these up at the top to ensure they aren't lost on early form submission
2292  $this->showFormBeforeText();
2293 
2294  if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
2295  $username = $this->lastDelete->user_name;
2296  $comment = $this->lastDelete->log_comment;
2297 
2298  // It is better to not parse the comment at all than to have templates expanded in the middle
2299  // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
2300  $key = $comment === ''
2301  ? 'confirmrecreate-noreason'
2302  : 'confirmrecreate';
2303  $wgOut->addHTML(
2304  '<div class="mw-confirm-recreate">' .
2305  wfMessage( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
2306  Xml::checkLabel( wfMessage( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
2307  array( 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
2308  ) .
2309  '</div>'
2310  );
2311  }
2312 
2313  # When the summary is hidden, also hide them on preview/show changes
2314  if ( $this->nosummary ) {
2315  $wgOut->addHTML( Html::hidden( 'nosummary', true ) );
2316  }
2317 
2318  # If a blank edit summary was previously provided, and the appropriate
2319  # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
2320  # user being bounced back more than once in the event that a summary
2321  # is not required.
2322  #####
2323  # For a bit more sophisticated detection of blank summaries, hash the
2324  # automatic one and pass that in the hidden field wpAutoSummary.
2325  if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
2326  $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
2327  }
2328 
2329  if ( $this->undidRev ) {
2330  $wgOut->addHTML( Html::hidden( 'wpUndidRevision', $this->undidRev ) );
2331  }
2332 
2333  if ( $this->hasPresetSummary ) {
2334  // If a summary has been preset using &summary= we don't want to prompt for
2335  // a different summary. Only prompt for a summary if the summary is blanked.
2336  // (Bug 17416)
2337  $this->autoSumm = md5( '' );
2338  }
2339 
2340  $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
2341  $wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
2342 
2343  $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
2344 
2345  $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
2346  $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
2347 
2348  if ( $this->section == 'new' ) {
2349  $this->showSummaryInput( true, $this->summary );
2350  $wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
2351  }
2352 
2353  $wgOut->addHTML( $this->editFormTextBeforeContent );
2354 
2355  if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
2356  $wgOut->addHTML( EditPage::getEditToolbar() );
2357  }
2358 
2359  if ( $this->isConflict ) {
2360  // In an edit conflict bypass the overridable content form method
2361  // and fallback to the raw wpTextbox1 since editconflicts can't be
2362  // resolved between page source edits and custom ui edits using the
2363  // custom edit ui.
2364  $this->textbox2 = $this->textbox1;
2365 
2366  $content = $this->getCurrentContent();
2367  $this->textbox1 = $this->toEditText( $content );
2368 
2369  $this->showTextbox1();
2370  } else {
2371  $this->showContentForm();
2372  }
2373 
2374  $wgOut->addHTML( $this->editFormTextAfterContent );
2375 
2376  $this->showStandardInputs();
2377 
2378  $this->showFormAfterText();
2379 
2380  $this->showTosSummary();
2381 
2382  $this->showEditTools();
2383 
2384  $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
2385 
2386  $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
2387  Linker::formatTemplates( $this->getTemplates(), $this->preview, $this->section != '' ) ) );
2388 
2389  $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'hiddencats' ),
2390  Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
2391 
2392  $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'limitreport' ),
2393  self::getPreviewLimitReport( $this->mParserOutput ) ) );
2394 
2395  $wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
2396 
2397  if ( $this->isConflict ) {
2398  try {
2399  $this->showConflict();
2400  } catch ( MWContentSerializationException $ex ) {
2401  // this can't really happen, but be nice if it does.
2402  $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
2403  $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
2404  }
2405  }
2406 
2407  $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
2408 
2409  if ( !$wgUser->getOption( 'previewontop' ) ) {
2410  $this->displayPreviewArea( $previewOutput, false );
2411  }
2412 
2413  wfProfileOut( __METHOD__ );
2414  }
2415 
2422  public static function extractSectionTitle( $text ) {
2423  preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
2424  if ( !empty( $matches[2] ) ) {
2425  global $wgParser;
2426  return $wgParser->stripSectionName( trim( $matches[2] ) );
2427  } else {
2428  return false;
2429  }
2430  }
2431 
2432  protected function showHeader() {
2433  global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang;
2434 
2435  if ( $this->mTitle->isTalkPage() ) {
2436  $wgOut->addWikiMsg( 'talkpagetext' );
2437  }
2438 
2439  // Add edit notices
2440  $wgOut->addHTML( implode( "\n", $this->mTitle->getEditNotices( $this->oldid ) ) );
2441 
2442  if ( $this->isConflict ) {
2443  $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
2444  $this->edittime = $this->mArticle->getTimestamp();
2445  } else {
2446  if ( $this->section != '' && !$this->isSectionEditSupported() ) {
2447  // We use $this->section to much before this and getVal('wgSection') directly in other places
2448  // at this point we can't reset $this->section to '' to fallback to non-section editing.
2449  // Someone is welcome to try refactoring though
2450  $wgOut->showErrorPage( 'sectioneditnotsupported-title', 'sectioneditnotsupported-text' );
2451  return false;
2452  }
2453 
2454  if ( $this->section != '' && $this->section != 'new' ) {
2455  if ( !$this->summary && !$this->preview && !$this->diff ) {
2456  $sectionTitle = self::extractSectionTitle( $this->textbox1 ); //FIXME: use Content object
2457  if ( $sectionTitle !== false ) {
2458  $this->summary = "/* $sectionTitle */ ";
2459  }
2460  }
2461  }
2462 
2463  if ( $this->missingComment ) {
2464  $wgOut->wrapWikiMsg( "<div id='mw-missingcommenttext'>\n$1\n</div>", 'missingcommenttext' );
2465  }
2466 
2467  if ( $this->missingSummary && $this->section != 'new' ) {
2468  $wgOut->wrapWikiMsg( "<div id='mw-missingsummary'>\n$1\n</div>", 'missingsummary' );
2469  }
2470 
2471  if ( $this->missingSummary && $this->section == 'new' ) {
2472  $wgOut->wrapWikiMsg( "<div id='mw-missingcommentheader'>\n$1\n</div>", 'missingcommentheader' );
2473  }
2474 
2475  if ( $this->hookError !== '' ) {
2476  $wgOut->addWikiText( $this->hookError );
2477  }
2478 
2479  if ( !$this->checkUnicodeCompliantBrowser() ) {
2480  $wgOut->addWikiMsg( 'nonunicodebrowser' );
2481  }
2482 
2483  if ( $this->section != 'new' ) {
2484  $revision = $this->mArticle->getRevisionFetched();
2485  if ( $revision ) {
2486  // Let sysop know that this will make private content public if saved
2487 
2488  if ( !$revision->userCan( Revision::DELETED_TEXT, $wgUser ) ) {
2489  $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
2490  } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
2491  $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
2492  }
2493 
2494  if ( !$revision->isCurrent() ) {
2495  $this->mArticle->setOldSubtitle( $revision->getId() );
2496  $wgOut->addWikiMsg( 'editingold' );
2497  }
2498  } elseif ( $this->mTitle->exists() ) {
2499  // Something went wrong
2500 
2501  $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
2502  array( 'missing-revision', $this->oldid ) );
2503  }
2504  }
2505  }
2506 
2507  if ( wfReadOnly() ) {
2508  $wgOut->wrapWikiMsg( "<div id=\"mw-read-only-warning\">\n$1\n</div>", array( 'readonlywarning', wfReadOnlyReason() ) );
2509  } elseif ( $wgUser->isAnon() ) {
2510  if ( $this->formtype != 'preview' ) {
2511  $wgOut->wrapWikiMsg( "<div id=\"mw-anon-edit-warning\">\n$1</div>", 'anoneditwarning' );
2512  } else {
2513  $wgOut->wrapWikiMsg( "<div id=\"mw-anon-preview-warning\">\n$1</div>", 'anonpreviewwarning' );
2514  }
2515  } else {
2516  if ( $this->isCssJsSubpage ) {
2517  # Check the skin exists
2518  if ( $this->isWrongCaseCssJsPage ) {
2519  $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) );
2520  }
2521  if ( $this->getTitle()->isSubpageOf( $wgUser->getUserPage() ) ) {
2522  if ( $this->formtype !== 'preview' ) {
2523  if ( $this->isCssSubpage ) {
2524  $wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) );
2525  }
2526 
2527  if ( $this->isJsSubpage ) {
2528  $wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) );
2529  }
2530  }
2531  }
2532  }
2533  }
2534 
2535  if ( $this->mTitle->isProtected( 'edit' ) &&
2536  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
2537  ) {
2538  # Is the title semi-protected?
2539  if ( $this->mTitle->isSemiProtected() ) {
2540  $noticeMsg = 'semiprotectedpagewarning';
2541  } else {
2542  # Then it must be protected based on static groups (regular)
2543  $noticeMsg = 'protectedpagewarning';
2544  }
2545  LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
2546  array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) );
2547  }
2548  if ( $this->mTitle->isCascadeProtected() ) {
2549  # Is this page under cascading protection from some source pages?
2550  list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
2551  $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
2552  $cascadeSourcesCount = count( $cascadeSources );
2553  if ( $cascadeSourcesCount > 0 ) {
2554  # Explain, and list the titles responsible
2555  foreach ( $cascadeSources as $page ) {
2556  $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
2557  }
2558  }
2559  $notice .= '</div>';
2560  $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', $cascadeSourcesCount ) );
2561  }
2562  if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
2563  LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
2564  array( 'lim' => 1,
2565  'showIfEmpty' => false,
2566  'msgKey' => array( 'titleprotectedwarning' ),
2567  'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ) );
2568  }
2569 
2570  if ( $this->kblength === false ) {
2571  $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
2572  }
2573 
2574  if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
2575  $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
2576  array( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) ) );
2577  } else {
2578  if ( !wfMessage( 'longpage-hint' )->isDisabled() ) {
2579  $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
2580  array( 'longpage-hint', $wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) )
2581  );
2582  }
2583  }
2584  # Add header copyright warning
2585  $this->showHeaderCopyrightWarning();
2586  }
2587 
2602  function getSummaryInput( $summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null ) {
2603  // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
2604  $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : array() ) + array(
2605  'id' => 'wpSummary',
2606  'maxlength' => '200',
2607  'tabindex' => '1',
2608  'size' => 60,
2609  'spellcheck' => 'true',
2610  ) + Linker::tooltipAndAccesskeyAttribs( 'summary' );
2611 
2612  $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : array() ) + array(
2613  'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
2614  'id' => "wpSummaryLabel"
2615  );
2616 
2617  $label = null;
2618  if ( $labelText ) {
2619  $label = Xml::tags( 'label', $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null, $labelText );
2620  $label = Xml::tags( 'span', $spanLabelAttrs, $label );
2621  }
2622 
2623  $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
2624 
2625  return array( $label, $input );
2626  }
2627 
2635  protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
2637  # Add a class if 'missingsummary' is triggered to allow styling of the summary line
2638  $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
2639  if ( $isSubjectPreview ) {
2640  if ( $this->nosummary ) {
2641  return;
2642  }
2643  } else {
2644  if ( !$this->mShowSummaryField ) {
2645  return;
2646  }
2647  }
2648  $summary = $wgContLang->recodeForEdit( $summary );
2649  $labelText = wfMessage( $isSubjectPreview ? 'subject' : 'summary' )->parse();
2650  list( $label, $input ) = $this->getSummaryInput( $summary, $labelText, array( 'class' => $summaryClass ), array() );
2651  $wgOut->addHTML( "{$label} {$input}" );
2652  }
2653 
2661  protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
2662  // avoid spaces in preview, gets always trimmed on save
2663  $summary = trim( $summary );
2664  if ( !$summary || ( !$this->preview && !$this->diff ) ) {
2665  return "";
2666  }
2667 
2668  global $wgParser;
2669 
2670  if ( $isSubjectPreview ) {
2671  $summary = wfMessage( 'newsectionsummary' )->rawParams( $wgParser->stripSectionName( $summary ) )
2672  ->inContentLanguage()->text();
2673  }
2674 
2675  $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
2676 
2677  $summary = wfMessage( $message )->parse() . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
2678  return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary );
2679  }
2680 
2681  protected function showFormBeforeText() {
2682  global $wgOut;
2683  $section = htmlspecialchars( $this->section );
2684  $wgOut->addHTML( <<<HTML
2685 <input type='hidden' value="{$section}" name="wpSection" />
2686 <input type='hidden' value="{$this->starttime}" name="wpStarttime" />
2687 <input type='hidden' value="{$this->edittime}" name="wpEdittime" />
2688 <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
2689 
2690 HTML
2691  );
2692  if ( !$this->checkUnicodeCompliantBrowser() ) {
2693  $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
2694  }
2695  }
2696 
2697  protected function showFormAfterText() {
2711  $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
2712  }
2713 
2722  protected function showContentForm() {
2723  $this->showTextbox1();
2724  }
2725 
2734  protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
2735  if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
2736  $attribs = array( 'style' => 'display:none;' );
2737  } else {
2738  $classes = array(); // Textarea CSS
2739  if ( $this->mTitle->isProtected( 'edit' ) &&
2740  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
2741  ) {
2742  # Is the title semi-protected?
2743  if ( $this->mTitle->isSemiProtected() ) {
2744  $classes[] = 'mw-textarea-sprotected';
2745  } else {
2746  # Then it must be protected based on static groups (regular)
2747  $classes[] = 'mw-textarea-protected';
2748  }
2749  # Is the title cascade-protected?
2750  if ( $this->mTitle->isCascadeProtected() ) {
2751  $classes[] = 'mw-textarea-cprotected';
2752  }
2753  }
2754 
2755  $attribs = array( 'tabindex' => 1 );
2756 
2757  if ( is_array( $customAttribs ) ) {
2759  }
2760 
2761  if ( count( $classes ) ) {
2762  if ( isset( $attribs['class'] ) ) {
2763  $classes[] = $attribs['class'];
2764  }
2765  $attribs['class'] = implode( ' ', $classes );
2766  }
2767  }
2768 
2769  $this->showTextbox( $textoverride !== null ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
2770  }
2771 
2772  protected function showTextbox2() {
2773  $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
2774  }
2775 
2776  protected function showTextbox( $text, $name, $customAttribs = array() ) {
2778 
2779  $wikitext = $this->safeUnicodeOutput( $text );
2780  if ( strval( $wikitext ) !== '' ) {
2781  // Ensure there's a newline at the end, otherwise adding lines
2782  // is awkward.
2783  // But don't add a newline if the ext is empty, or Firefox in XHTML
2784  // mode will show an extra newline. A bit annoying.
2785  $wikitext .= "\n";
2786  }
2787 
2789  'accesskey' => ',',
2790  'id' => $name,
2791  'cols' => $wgUser->getIntOption( 'cols' ),
2792  'rows' => $wgUser->getIntOption( 'rows' ),
2793  'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work
2794  );
2795 
2796  $pageLang = $this->mTitle->getPageLanguage();
2797  $attribs['lang'] = $pageLang->getCode();
2798  $attribs['dir'] = $pageLang->getDir();
2799 
2800  $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
2801  }
2802 
2803  protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
2804  global $wgOut;
2805  $classes = array();
2806  if ( $isOnTop ) {
2807  $classes[] = 'ontop';
2808  }
2809 
2810  $attribs = array( 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) );
2811 
2812  if ( $this->formtype != 'preview' ) {
2813  $attribs['style'] = 'display: none;';
2814  }
2815 
2816  $wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
2817 
2818  if ( $this->formtype == 'preview' ) {
2819  $this->showPreview( $previewOutput );
2820  }
2821 
2822  $wgOut->addHTML( '</div>' );
2823 
2824  if ( $this->formtype == 'diff' ) {
2825  try {
2826  $this->showDiff();
2827  } catch ( MWContentSerializationException $ex ) {
2828  $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
2829  $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
2830  }
2831  }
2832  }
2833 
2840  protected function showPreview( $text ) {
2841  global $wgOut;
2842  if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
2843  $this->mArticle->openShowCategory();
2844  }
2845  # This hook seems slightly odd here, but makes things more
2846  # consistent for extensions.
2847  wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) );
2848  $wgOut->addHTML( $text );
2849  if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
2850  $this->mArticle->closeShowCategory();
2851  }
2852  }
2853 
2861  function showDiff() {
2863 
2864  $oldtitlemsg = 'currentrev';
2865  # if message does not exist, show diff against the preloaded default
2866  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
2867  $oldtext = $this->mTitle->getDefaultMessageText();
2868  if ( $oldtext !== false ) {
2869  $oldtitlemsg = 'defaultmessagetext';
2870  $oldContent = $this->toEditContent( $oldtext );
2871  } else {
2872  $oldContent = null;
2873  }
2874  } else {
2875  $oldContent = $this->getCurrentContent();
2876  }
2877 
2878  $textboxContent = $this->toEditContent( $this->textbox1 );
2879 
2880  $newContent = $this->mArticle->replaceSectionContent(
2881  $this->section, $textboxContent,
2882  $this->summary, $this->edittime );
2883 
2884  if ( $newContent ) {
2885  ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
2886  wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
2887 
2889  $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
2890  }
2891 
2892  if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
2893  $oldtitle = wfMessage( $oldtitlemsg )->parse();
2894  $newtitle = wfMessage( 'yourtext' )->parse();
2895 
2896  if ( !$oldContent ) {
2897  $oldContent = $newContent->getContentHandler()->makeEmptyContent();
2898  }
2899 
2900  if ( !$newContent ) {
2901  $newContent = $oldContent->getContentHandler()->makeEmptyContent();
2902  }
2903 
2904  $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
2905  $de->setContent( $oldContent, $newContent );
2906 
2907  $difftext = $de->getDiff( $oldtitle, $newtitle );
2908  $de->showDiffStyle();
2909  } else {
2910  $difftext = '';
2911  }
2912 
2913  $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
2914  }
2915 
2919  protected function showHeaderCopyrightWarning() {
2920  $msg = 'editpage-head-copy-warn';
2921  if ( !wfMessage( $msg )->isDisabled() ) {
2922  global $wgOut;
2923  $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
2924  'editpage-head-copy-warn' );
2925  }
2926  }
2927 
2936  protected function showTosSummary() {
2937  $msg = 'editpage-tos-summary';
2938  wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
2939  if ( !wfMessage( $msg )->isDisabled() ) {
2940  global $wgOut;
2941  $wgOut->addHTML( '<div class="mw-tos-summary">' );
2942  $wgOut->addWikiMsg( $msg );
2943  $wgOut->addHTML( '</div>' );
2944  }
2945  }
2946 
2947  protected function showEditTools() {
2948  global $wgOut;
2949  $wgOut->addHTML( '<div class="mw-editTools">' .
2950  wfMessage( 'edittools' )->inContentLanguage()->parse() .
2951  '</div>' );
2952  }
2953 
2959  protected function getCopywarn() {
2960  return self::getCopyrightWarning( $this->mTitle );
2961  }
2962 
2971  public static function getCopyrightWarning( $title, $format = 'plain' ) {
2972  global $wgRightsText;
2973  if ( $wgRightsText ) {
2974  $copywarnMsg = array( 'copyrightwarning',
2975  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]',
2976  $wgRightsText );
2977  } else {
2978  $copywarnMsg = array( 'copyrightwarning2',
2979  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' );
2980  }
2981  // Allow for site and per-namespace customization of contribution/copyright notice.
2982  wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
2983 
2984  return "<div id=\"editpage-copywarn\">\n" .
2985  call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>";
2986  }
2987 
2995  public static function getPreviewLimitReport( $output ) {
2996  if ( !$output || !$output->getLimitReportData() ) {
2997  return '';
2998  }
2999 
3000  wfProfileIn( __METHOD__ );
3001 
3002  $limitReport = Html::rawElement( 'div', array( 'class' => 'mw-limitReportExplanation' ),
3003  wfMessage( 'limitreport-title' )->parseAsBlock()
3004  );
3005 
3006  // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
3007  $limitReport .= Html::openElement( 'div', array( 'class' => 'preview-limit-report-wrapper' ) );
3008 
3009  $limitReport .= Html::openElement( 'table', array(
3010  'class' => 'preview-limit-report wikitable'
3011  ) ) .
3012  Html::openElement( 'tbody' );
3013 
3014  foreach ( $output->getLimitReportData() as $key => $value ) {
3015  if ( wfRunHooks( 'ParserLimitReportFormat',
3016  array( $key, &$value, &$limitReport, true, true )
3017  ) ) {
3018  $keyMsg = wfMessage( $key );
3019  $valueMsg = wfMessage( array( "$key-value-html", "$key-value" ) );
3020  if ( !$valueMsg->exists() ) {
3021  $valueMsg = new RawMessage( '$1' );
3022  }
3023  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3024  $limitReport .= Html::openElement( 'tr' ) .
3025  Html::rawElement( 'th', null, $keyMsg->parse() ) .
3026  Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) .
3027  Html::closeElement( 'tr' );
3028  }
3029  }
3030  }
3031 
3032  $limitReport .= Html::closeElement( 'tbody' ) .
3033  Html::closeElement( 'table' ) .
3034  Html::closeElement( 'div' );
3035 
3036  wfProfileOut( __METHOD__ );
3037 
3038  return $limitReport;
3039  }
3040 
3041  protected function showStandardInputs( &$tabindex = 2 ) {
3042  global $wgOut;
3043  $wgOut->addHTML( "<div class='editOptions'>\n" );
3044 
3045  if ( $this->section != 'new' ) {
3046  $this->showSummaryInput( false, $this->summary );
3047  $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
3048  }
3049 
3050  $checkboxes = $this->getCheckboxes( $tabindex,
3051  array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) );
3052  $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" );
3053 
3054  // Show copyright warning.
3055  $wgOut->addWikiText( $this->getCopywarn() );
3056  $wgOut->addHTML( $this->editFormTextAfterWarn );
3057 
3058  $wgOut->addHTML( "<div class='editButtons'>\n" );
3059  $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
3060 
3061  $cancel = $this->getCancelLink();
3062  if ( $cancel !== '' ) {
3063  $cancel .= Html::element( 'span',
3064  array( 'class' => 'mw-editButtons-pipe-separator' ),
3065  wfMessage( 'pipe-separator' )->text() );
3066  }
3067  $edithelpurl = Skin::makeInternalOrExternalUrl( wfMessage( 'edithelppage' )->inContentLanguage()->text() );
3068  $edithelp = '<a target="helpwindow" href="' . $edithelpurl . '">' .
3069  wfMessage( 'edithelp' )->escaped() . '</a> ' .
3070  wfMessage( 'newwindow' )->parse();
3071  $wgOut->addHTML( " <span class='cancelLink'>{$cancel}</span>\n" );
3072  $wgOut->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" );
3073  $wgOut->addHTML( "</div><!-- editButtons -->\n" );
3074  wfRunHooks( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) );
3075  $wgOut->addHTML( "</div><!-- editOptions -->\n" );
3076  }
3077 
3082  protected function showConflict() {
3083  global $wgOut;
3084 
3085  if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
3086  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
3087 
3088  $content1 = $this->toEditContent( $this->textbox1 );
3089  $content2 = $this->toEditContent( $this->textbox2 );
3090 
3091  $handler = ContentHandler::getForModelID( $this->contentModel );
3092  $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
3093  $de->setContent( $content2, $content1 );
3094  $de->showDiff(
3095  wfMessage( 'yourtext' )->parse(),
3096  wfMessage( 'storedversion' )->text()
3097  );
3098 
3099  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
3100  $this->showTextbox2();
3101  }
3102  }
3103 
3107  public function getCancelLink() {
3108  $cancelParams = array();
3109  if ( !$this->isConflict && $this->oldid > 0 ) {
3110  $cancelParams['oldid'] = $this->oldid;
3111  }
3112 
3113  return Linker::linkKnown(
3114  $this->getContextTitle(),
3115  wfMessage( 'cancel' )->parse(),
3116  array( 'id' => 'mw-editform-cancel' ),
3117  $cancelParams
3118  );
3119  }
3120 
3130  protected function getActionURL( Title $title ) {
3131  return $title->getLocalURL( array( 'action' => $this->action ) );
3132  }
3133 
3140  protected function wasDeletedSinceLastEdit() {
3141  if ( $this->deletedSinceEdit !== null ) {
3142  return $this->deletedSinceEdit;
3143  }
3144 
3145  $this->deletedSinceEdit = false;
3146 
3147  if ( $this->mTitle->isDeletedQuick() ) {
3148  $this->lastDelete = $this->getLastDelete();
3149  if ( $this->lastDelete ) {
3150  $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
3151  if ( $deleteTime > $this->starttime ) {
3152  $this->deletedSinceEdit = true;
3153  }
3154  }
3155  }
3156 
3157  return $this->deletedSinceEdit;
3158  }
3159 
3160  protected function getLastDelete() {
3161  $dbr = wfGetDB( DB_SLAVE );
3162  $data = $dbr->selectRow(
3163  array( 'logging', 'user' ),
3164  array(
3165  'log_type',
3166  'log_action',
3167  'log_timestamp',
3168  'log_user',
3169  'log_namespace',
3170  'log_title',
3171  'log_comment',
3172  'log_params',
3173  'log_deleted',
3174  'user_name'
3175  ), array(
3176  'log_namespace' => $this->mTitle->getNamespace(),
3177  'log_title' => $this->mTitle->getDBkey(),
3178  'log_type' => 'delete',
3179  'log_action' => 'delete',
3180  'user_id=log_user'
3181  ),
3182  __METHOD__,
3183  array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' )
3184  );
3185  // Quick paranoid permission checks...
3186  if ( is_object( $data ) ) {
3187  if ( $data->log_deleted & LogPage::DELETED_USER ) {
3188  $data->user_name = wfMessage( 'rev-deleted-user' )->escaped();
3189  }
3190 
3191  if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
3192  $data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped();
3193  }
3194  }
3195  return $data;
3196  }
3197 
3203  function getPreviewText() {
3204  global $wgOut, $wgUser, $wgRawHtml, $wgLang;
3205 
3206  wfProfileIn( __METHOD__ );
3207 
3208  if ( $wgRawHtml && !$this->mTokenOk ) {
3209  // Could be an offsite preview attempt. This is very unsafe if
3210  // HTML is enabled, as it could be an attack.
3211  $parsedNote = '';
3212  if ( $this->textbox1 !== '' ) {
3213  // Do not put big scary notice, if previewing the empty
3214  // string, which happens when you initially edit
3215  // a category page, due to automatic preview-on-open.
3216  $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
3217  wfMessage( 'session_fail_preview_html' )->text() . "</div>", true, /* interface */true );
3218  }
3219  wfProfileOut( __METHOD__ );
3220  return $parsedNote;
3221  }
3222 
3223  $note = '';
3224 
3225  try {
3226  $content = $this->toEditContent( $this->textbox1 );
3227 
3228  $previewHTML = '';
3229  if ( !wfRunHooks( 'AlternateEditPreview', array( $this, &$content, &$previewHTML, &$this->mParserOutput ) ) ) {
3230  wfProfileOut( __METHOD__ );
3231  return $previewHTML;
3232  }
3233 
3234  # provide a anchor link to the editform
3235  $continueEditing = '<span class="mw-continue-editing">' .
3236  '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' .
3237  wfMessage( 'continue-editing' )->text() . ']]</span>';
3238  if ( $this->mTriedSave && !$this->mTokenOk ) {
3239  if ( $this->mTokenOkExceptSuffix ) {
3240  $note = wfMessage( 'token_suffix_mismatch' )->plain();
3241 
3242  } else {
3243  $note = wfMessage( 'session_fail_preview' )->plain();
3244  }
3245  } elseif ( $this->incompleteForm ) {
3246  $note = wfMessage( 'edit_form_incomplete' )->plain();
3247  } else {
3248  $note = wfMessage( 'previewnote' )->plain() . ' ' . $continueEditing;
3249  }
3250 
3251  $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
3252  $parserOptions->setEditSection( false );
3253  $parserOptions->setIsPreview( true );
3254  $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
3255 
3256  # don't parse non-wikitext pages, show message about preview
3257  if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
3258  if ( $this->mTitle->isCssJsSubpage() ) {
3259  $level = 'user';
3260  } elseif ( $this->mTitle->isCssOrJsPage() ) {
3261  $level = 'site';
3262  } else {
3263  $level = false;
3264  }
3265 
3266  if ( $content->getModel() == CONTENT_MODEL_CSS ) {
3267  $format = 'css';
3268  } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
3269  $format = 'js';
3270  } else {
3271  $format = false;
3272  }
3273 
3274  # Used messages to make sure grep find them:
3275  # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
3276  if ( $level && $format ) {
3277  $note = "<div id='mw-{$level}{$format}preview'>" .
3278  wfMessage( "{$level}{$format}preview" )->text() .
3279  ' ' . $continueEditing . "</div>";
3280  }
3281  }
3282 
3283  $rt = $content->getRedirectChain();
3284  if ( $rt ) {
3285  $previewHTML = $this->mArticle->viewRedirect( $rt, false );
3286  } else {
3287 
3288  # If we're adding a comment, we need to show the
3289  # summary as the headline
3290  if ( $this->section === "new" && $this->summary !== "" ) {
3291  $content = $content->addSectionHeader( $this->summary );
3292  }
3293 
3294  $hook_args = array( $this, &$content );
3295  ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
3296  wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
3297 
3298  $parserOptions->enableLimitReport();
3299 
3300  # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
3301  # But it's now deprecated, so never mind
3302 
3303  $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
3304  $parserOutput = $content->getParserOutput( $this->getArticle()->getTitle(), null, $parserOptions );
3305 
3306  $previewHTML = $parserOutput->getText();
3307  $this->mParserOutput = $parserOutput;
3308  $wgOut->addParserOutputNoText( $parserOutput );
3309 
3310  if ( count( $parserOutput->getWarnings() ) ) {
3311  $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
3312  }
3313  }
3314  } catch ( MWContentSerializationException $ex ) {
3315  $m = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
3316  $note .= "\n\n" . $m->parse();
3317  $previewHTML = '';
3318  }
3319 
3320  if ( $this->isConflict ) {
3321  $conflict = '<h2 id="mw-previewconflict">' . wfMessage( 'previewconflict' )->escaped() . "</h2>\n";
3322  } else {
3323  $conflict = '<hr />';
3324  }
3325 
3326  $previewhead = "<div class='previewnote'>\n" .
3327  '<h2 id="mw-previewheader">' . wfMessage( 'preview' )->escaped() . "</h2>" .
3328  $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
3329 
3330  $pageViewLang = $this->mTitle->getPageViewLanguage();
3331  $attribs = array( 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3332  'class' => 'mw-content-' . $pageViewLang->getDir() );
3333  $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
3334 
3335  wfProfileOut( __METHOD__ );
3336  return $previewhead . $previewHTML . $this->previewTextAfterContent;
3337  }
3338 
3342  function getTemplates() {
3343  if ( $this->preview || $this->section != '' ) {
3344  $templates = array();
3345  if ( !isset( $this->mParserOutput ) ) {
3346  return $templates;
3347  }
3348  foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
3349  foreach ( array_keys( $template ) as $dbk ) {
3350  $templates[] = Title::makeTitle( $ns, $dbk );
3351  }
3352  }
3353  return $templates;
3354  } else {
3355  return $this->mTitle->getTemplateLinksFrom();
3356  }
3357  }
3358 
3366  static function getEditToolbar() {
3367  global $wgStylePath, $wgContLang, $wgLang, $wgOut;
3368  global $wgEnableUploads, $wgForeignFileRepos;
3369 
3370  $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
3371 
3385  $toolarray = array(
3386  array(
3387  'image' => $wgLang->getImageFile( 'button-bold' ),
3388  'id' => 'mw-editbutton-bold',
3389  'open' => '\'\'\'',
3390  'close' => '\'\'\'',
3391  'sample' => wfMessage( 'bold_sample' )->text(),
3392  'tip' => wfMessage( 'bold_tip' )->text(),
3393  'key' => 'B'
3394  ),
3395  array(
3396  'image' => $wgLang->getImageFile( 'button-italic' ),
3397  'id' => 'mw-editbutton-italic',
3398  'open' => '\'\'',
3399  'close' => '\'\'',
3400  'sample' => wfMessage( 'italic_sample' )->text(),
3401  'tip' => wfMessage( 'italic_tip' )->text(),
3402  'key' => 'I'
3403  ),
3404  array(
3405  'image' => $wgLang->getImageFile( 'button-link' ),
3406  'id' => 'mw-editbutton-link',
3407  'open' => '[[',
3408  'close' => ']]',
3409  'sample' => wfMessage( 'link_sample' )->text(),
3410  'tip' => wfMessage( 'link_tip' )->text(),
3411  'key' => 'L'
3412  ),
3413  array(
3414  'image' => $wgLang->getImageFile( 'button-extlink' ),
3415  'id' => 'mw-editbutton-extlink',
3416  'open' => '[',
3417  'close' => ']',
3418  'sample' => wfMessage( 'extlink_sample' )->text(),
3419  'tip' => wfMessage( 'extlink_tip' )->text(),
3420  'key' => 'X'
3421  ),
3422  array(
3423  'image' => $wgLang->getImageFile( 'button-headline' ),
3424  'id' => 'mw-editbutton-headline',
3425  'open' => "\n== ",
3426  'close' => " ==\n",
3427  'sample' => wfMessage( 'headline_sample' )->text(),
3428  'tip' => wfMessage( 'headline_tip' )->text(),
3429  'key' => 'H'
3430  ),
3431  $imagesAvailable ? array(
3432  'image' => $wgLang->getImageFile( 'button-image' ),
3433  'id' => 'mw-editbutton-image',
3434  'open' => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
3435  'close' => ']]',
3436  'sample' => wfMessage( 'image_sample' )->text(),
3437  'tip' => wfMessage( 'image_tip' )->text(),
3438  'key' => 'D',
3439  ) : false,
3440  $imagesAvailable ? array(
3441  'image' => $wgLang->getImageFile( 'button-media' ),
3442  'id' => 'mw-editbutton-media',
3443  'open' => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
3444  'close' => ']]',
3445  'sample' => wfMessage( 'media_sample' )->text(),
3446  'tip' => wfMessage( 'media_tip' )->text(),
3447  'key' => 'M'
3448  ) : false,
3449  array(
3450  'image' => $wgLang->getImageFile( 'button-nowiki' ),
3451  'id' => 'mw-editbutton-nowiki',
3452  'open' => "<nowiki>",
3453  'close' => "</nowiki>",
3454  'sample' => wfMessage( 'nowiki_sample' )->text(),
3455  'tip' => wfMessage( 'nowiki_tip' )->text(),
3456  'key' => 'N'
3457  ),
3458  array(
3459  'image' => $wgLang->getImageFile( 'button-sig' ),
3460  'id' => 'mw-editbutton-signature',
3461  'open' => '--~~~~',
3462  'close' => '',
3463  'sample' => '',
3464  'tip' => wfMessage( 'sig_tip' )->text(),
3465  'key' => 'Y'
3466  ),
3467  array(
3468  'image' => $wgLang->getImageFile( 'button-hr' ),
3469  'id' => 'mw-editbutton-hr',
3470  'open' => "\n----\n",
3471  'close' => '',
3472  'sample' => '',
3473  'tip' => wfMessage( 'hr_tip' )->text(),
3474  'key' => 'R'
3475  )
3476  );
3477 
3478  $script = 'mw.loader.using("mediawiki.action.edit", function() {';
3479  foreach ( $toolarray as $tool ) {
3480  if ( !$tool ) {
3481  continue;
3482  }
3483 
3484  $params = array(
3485  $wgStylePath . '/common/images/' . $tool['image'],
3486  // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
3487  // Older browsers show a "speedtip" type message only for ALT.
3488  // Ideally these should be different, realistically they
3489  // probably don't need to be.
3490  $tool['tip'],
3491  $tool['open'],
3492  $tool['close'],
3493  $tool['sample'],
3494  $tool['id'],
3495  );
3496 
3497  $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
3498  }
3499 
3500  // This used to be called on DOMReady from mediawiki.action.edit, which
3501  // ended up causing race conditions with the setup code above.
3502  $script .= "\n" .
3503  "// Create button bar\n" .
3504  "$(function() { mw.toolbar.init(); } );\n";
3505 
3506  $script .= '});';
3508 
3509  $toolbar = '<div id="toolbar"></div>';
3510 
3511  wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
3512 
3513  return $toolbar;
3514  }
3515 
3526  public function getCheckboxes( &$tabindex, $checked ) {
3527  global $wgUser;
3528 
3529  $checkboxes = array();
3530 
3531  // don't show the minor edit checkbox if it's a new page or section
3532  if ( !$this->isNew ) {
3533  $checkboxes['minor'] = '';
3534  $minorLabel = wfMessage( 'minoredit' )->parse();
3535  if ( $wgUser->isAllowed( 'minoredit' ) ) {
3536  $attribs = array(
3537  'tabindex' => ++$tabindex,
3538  'accesskey' => wfMessage( 'accesskey-minoredit' )->text(),
3539  'id' => 'wpMinoredit',
3540  );
3541  $checkboxes['minor'] =
3542  Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
3543  "&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
3544  Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'minoredit', 'withaccess' ) ) ) .
3545  ">{$minorLabel}</label>";
3546  }
3547  }
3548 
3549  $watchLabel = wfMessage( 'watchthis' )->parse();
3550  $checkboxes['watch'] = '';
3551  if ( $wgUser->isLoggedIn() ) {
3552  $attribs = array(
3553  'tabindex' => ++$tabindex,
3554  'accesskey' => wfMessage( 'accesskey-watch' )->text(),
3555  'id' => 'wpWatchthis',
3556  );
3557  $checkboxes['watch'] =
3558  Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
3559  "&#160;<label for='wpWatchthis' id='mw-editpage-watch'" .
3560  Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ) ) .
3561  ">{$watchLabel}</label>";
3562  }
3563  wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
3564  return $checkboxes;
3565  }
3566 
3575  public function getEditButtons( &$tabindex ) {
3576  $buttons = array();
3577 
3578  $temp = array(
3579  'id' => 'wpSave',
3580  'name' => 'wpSave',
3581  'type' => 'submit',
3582  'tabindex' => ++$tabindex,
3583  'value' => wfMessage( 'savearticle' )->text(),
3584  'accesskey' => wfMessage( 'accesskey-save' )->text(),
3585  'title' => wfMessage( 'tooltip-save' )->text() . ' [' . wfMessage( 'accesskey-save' )->text() . ']',
3586  );
3587  $buttons['save'] = Xml::element( 'input', $temp, '' );
3588 
3589  ++$tabindex; // use the same for preview and live preview
3590  $temp = array(
3591  'id' => 'wpPreview',
3592  'name' => 'wpPreview',
3593  'type' => 'submit',
3594  'tabindex' => $tabindex,
3595  'value' => wfMessage( 'showpreview' )->text(),
3596  'accesskey' => wfMessage( 'accesskey-preview' )->text(),
3597  'title' => wfMessage( 'tooltip-preview' )->text() . ' [' . wfMessage( 'accesskey-preview' )->text() . ']',
3598  );
3599  $buttons['preview'] = Xml::element( 'input', $temp, '' );
3600  $buttons['live'] = '';
3601 
3602  $temp = array(
3603  'id' => 'wpDiff',
3604  'name' => 'wpDiff',
3605  'type' => 'submit',
3606  'tabindex' => ++$tabindex,
3607  'value' => wfMessage( 'showdiff' )->text(),
3608  'accesskey' => wfMessage( 'accesskey-diff' )->text(),
3609  'title' => wfMessage( 'tooltip-diff' )->text() . ' [' . wfMessage( 'accesskey-diff' )->text() . ']',
3610  );
3611  $buttons['diff'] = Xml::element( 'input', $temp, '' );
3612 
3613  wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) );
3614  return $buttons;
3615  }
3616 
3629  function livePreview() {
3630  global $wgOut;
3631  $wgOut->disable();
3632  header( 'Content-type: text/xml; charset=utf-8' );
3633  header( 'Cache-control: no-cache' );
3634 
3635  $previewText = $this->getPreviewText();
3636  #$categories = $skin->getCategoryLinks();
3637 
3638  $s =
3639  '<?xml version="1.0" encoding="UTF-8" ?>' . "\n" .
3640  Xml::tags( 'livepreview', null,
3641  Xml::element( 'preview', null, $previewText )
3642  #. Xml::element( 'category', null, $categories )
3643  );
3644  echo $s;
3645  }
3646 
3652  function blockedPage() {
3653  wfDeprecated( __METHOD__, '1.19' );
3654  global $wgUser;
3655 
3656  throw new UserBlockedError( $wgUser->getBlock() );
3657  }
3658 
3664  function userNotLoggedInPage() {
3665  wfDeprecated( __METHOD__, '1.19' );
3666  throw new PermissionsError( 'edit' );
3667  }
3668 
3675  function noCreatePermission() {
3676  wfDeprecated( __METHOD__, '1.19' );
3677  $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
3678  throw new PermissionsError( $permission );
3679  }
3680 
3685  function noSuchSectionPage() {
3686  global $wgOut;
3687 
3688  $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
3689 
3690  $res = wfMessage( 'nosuchsectiontext', $this->section )->parseAsBlock();
3691  wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
3692  $wgOut->addHTML( $res );
3693 
3694  $wgOut->returnToMain( false, $this->mTitle );
3695  }
3696 
3702  public function spamPageWithContent( $match = false ) {
3704  $this->textbox2 = $this->textbox1;
3705 
3706  if ( is_array( $match ) ) {
3707  $match = $wgLang->listToText( $match );
3708  }
3709  $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
3710 
3711  $wgOut->addHTML( '<div id="spamprotected">' );
3712  $wgOut->addWikiMsg( 'spamprotectiontext' );
3713  if ( $match ) {
3714  $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
3715  }
3716  $wgOut->addHTML( '</div>' );
3717 
3718  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
3719  $this->showDiff();
3720 
3721  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
3722  $this->showTextbox2();
3723 
3724  $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) );
3725  }
3726 
3733  private function checkUnicodeCompliantBrowser() {
3734  global $wgBrowserBlackList, $wgRequest;
3735 
3736  $currentbrowser = $wgRequest->getHeader( 'User-Agent' );
3737  if ( $currentbrowser === false ) {
3738  // No User-Agent header sent? Trust it by default...
3739  return true;
3740  }
3741 
3742  foreach ( $wgBrowserBlackList as $browser ) {
3743  if ( preg_match( $browser, $currentbrowser ) ) {
3744  return false;
3745  }
3746  }
3747  return true;
3748  }
3749 
3758  protected function safeUnicodeInput( $request, $field ) {
3759  $text = rtrim( $request->getText( $field ) );
3760  return $request->getBool( 'safemode' )
3761  ? $this->unmakeSafe( $text )
3762  : $text;
3763  }
3764 
3772  protected function safeUnicodeOutput( $text ) {
3774  $codedText = $wgContLang->recodeForEdit( $text );
3775  return $this->checkUnicodeCompliantBrowser()
3776  ? $codedText
3777  : $this->makeSafe( $codedText );
3778  }
3779 
3792  private function makeSafe( $invalue ) {
3793  // Armor existing references for reversibility.
3794  $invalue = strtr( $invalue, array( "&#x" => "&#x0" ) );
3795 
3796  $bytesleft = 0;
3797  $result = "";
3798  $working = 0;
3799  for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
3800  $bytevalue = ord( $invalue[$i] );
3801  if ( $bytevalue <= 0x7F ) { // 0xxx xxxx
3802  $result .= chr( $bytevalue );
3803  $bytesleft = 0;
3804  } elseif ( $bytevalue <= 0xBF ) { // 10xx xxxx
3805  $working = $working << 6;
3806  $working += ( $bytevalue & 0x3F );
3807  $bytesleft--;
3808  if ( $bytesleft <= 0 ) {
3809  $result .= "&#x" . strtoupper( dechex( $working ) ) . ";";
3810  }
3811  } elseif ( $bytevalue <= 0xDF ) { // 110x xxxx
3812  $working = $bytevalue & 0x1F;
3813  $bytesleft = 1;
3814  } elseif ( $bytevalue <= 0xEF ) { // 1110 xxxx
3815  $working = $bytevalue & 0x0F;
3816  $bytesleft = 2;
3817  } else { // 1111 0xxx
3818  $working = $bytevalue & 0x07;
3819  $bytesleft = 3;
3820  }
3821  }
3822  return $result;
3823  }
3824 
3833  private function unmakeSafe( $invalue ) {
3834  $result = "";
3835  $valueLength = strlen( $invalue );
3836  for ( $i = 0; $i < $valueLength; $i++ ) {
3837  if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) {
3838  $i += 3;
3839  $hexstring = "";
3840  do {
3841  $hexstring .= $invalue[$i];
3842  $i++;
3843  } while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
3844 
3845  // Do some sanity checks. These aren't needed for reversibility,
3846  // but should help keep the breakage down if the editor
3847  // breaks one of the entities whilst editing.
3848  if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) {
3849  $codepoint = hexdec( $hexstring );
3850  $result .= codepointToUtf8( $codepoint );
3851  } else {
3852  $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
3853  }
3854  } else {
3855  $result .= substr( $invalue, $i, 1 );
3856  }
3857  }
3858  // reverse the transform that we made for reversibility reasons.
3859  return strtr( $result, array( "&#x0" => "&#x" ) );
3860  }
3861 }
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:386
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:375
Status\getWikiText
getWikiText( $shortContext=false, $longContext=false)
Get the error list as a wikitext formatted list.
Definition: Status.php:185
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=array(), $page='', $user='', $param=array())
Show log extract.
Definition: LogEventsList.php:507
$wgTitle
if(! $wgRequest->checkUrlExtension()) if(! $wgEnableAPI) $wgTitle
Definition: api.php:63