MediaWiki  1.23.8
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->formtype !== 'preview' ) {
2522  if ( $this->isCssSubpage ) {
2523  $wgOut->wrapWikiMsg( "<div id='mw-usercssyoucanpreview'>\n$1\n</div>", array( 'usercssyoucanpreview' ) );
2524  }
2525 
2526  if ( $this->isJsSubpage ) {
2527  $wgOut->wrapWikiMsg( "<div id='mw-userjsyoucanpreview'>\n$1\n</div>", array( 'userjsyoucanpreview' ) );
2528  }
2529  }
2530  }
2531  }
2532 
2533  if ( $this->mTitle->isProtected( 'edit' ) &&
2534  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
2535  ) {
2536  # Is the title semi-protected?
2537  if ( $this->mTitle->isSemiProtected() ) {
2538  $noticeMsg = 'semiprotectedpagewarning';
2539  } else {
2540  # Then it must be protected based on static groups (regular)
2541  $noticeMsg = 'protectedpagewarning';
2542  }
2543  LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
2544  array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) );
2545  }
2546  if ( $this->mTitle->isCascadeProtected() ) {
2547  # Is this page under cascading protection from some source pages?
2548  list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
2549  $notice = "<div class='mw-cascadeprotectedwarning'>\n$1\n";
2550  $cascadeSourcesCount = count( $cascadeSources );
2551  if ( $cascadeSourcesCount > 0 ) {
2552  # Explain, and list the titles responsible
2553  foreach ( $cascadeSources as $page ) {
2554  $notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
2555  }
2556  }
2557  $notice .= '</div>';
2558  $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', $cascadeSourcesCount ) );
2559  }
2560  if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
2561  LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
2562  array( 'lim' => 1,
2563  'showIfEmpty' => false,
2564  'msgKey' => array( 'titleprotectedwarning' ),
2565  'wrap' => "<div class=\"mw-titleprotectedwarning\">\n$1</div>" ) );
2566  }
2567 
2568  if ( $this->kblength === false ) {
2569  $this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
2570  }
2571 
2572  if ( $this->tooBig || $this->kblength > $wgMaxArticleSize ) {
2573  $wgOut->wrapWikiMsg( "<div class='error' id='mw-edit-longpageerror'>\n$1\n</div>",
2574  array( 'longpageerror', $wgLang->formatNum( $this->kblength ), $wgLang->formatNum( $wgMaxArticleSize ) ) );
2575  } else {
2576  if ( !wfMessage( 'longpage-hint' )->isDisabled() ) {
2577  $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
2578  array( 'longpage-hint', $wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) )
2579  );
2580  }
2581  }
2582  # Add header copyright warning
2583  $this->showHeaderCopyrightWarning();
2584  }
2585 
2600  function getSummaryInput( $summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null ) {
2601  // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
2602  $inputAttrs = ( is_array( $inputAttrs ) ? $inputAttrs : array() ) + array(
2603  'id' => 'wpSummary',
2604  'maxlength' => '200',
2605  'tabindex' => '1',
2606  'size' => 60,
2607  'spellcheck' => 'true',
2608  ) + Linker::tooltipAndAccesskeyAttribs( 'summary' );
2609 
2610  $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : array() ) + array(
2611  'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
2612  'id' => "wpSummaryLabel"
2613  );
2614 
2615  $label = null;
2616  if ( $labelText ) {
2617  $label = Xml::tags( 'label', $inputAttrs['id'] ? array( 'for' => $inputAttrs['id'] ) : null, $labelText );
2618  $label = Xml::tags( 'span', $spanLabelAttrs, $label );
2619  }
2620 
2621  $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
2622 
2623  return array( $label, $input );
2624  }
2625 
2633  protected function showSummaryInput( $isSubjectPreview, $summary = "" ) {
2635  # Add a class if 'missingsummary' is triggered to allow styling of the summary line
2636  $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
2637  if ( $isSubjectPreview ) {
2638  if ( $this->nosummary ) {
2639  return;
2640  }
2641  } else {
2642  if ( !$this->mShowSummaryField ) {
2643  return;
2644  }
2645  }
2646  $summary = $wgContLang->recodeForEdit( $summary );
2647  $labelText = wfMessage( $isSubjectPreview ? 'subject' : 'summary' )->parse();
2648  list( $label, $input ) = $this->getSummaryInput( $summary, $labelText, array( 'class' => $summaryClass ), array() );
2649  $wgOut->addHTML( "{$label} {$input}" );
2650  }
2651 
2659  protected function getSummaryPreview( $isSubjectPreview, $summary = "" ) {
2660  // avoid spaces in preview, gets always trimmed on save
2661  $summary = trim( $summary );
2662  if ( !$summary || ( !$this->preview && !$this->diff ) ) {
2663  return "";
2664  }
2665 
2666  global $wgParser;
2667 
2668  if ( $isSubjectPreview ) {
2669  $summary = wfMessage( 'newsectionsummary' )->rawParams( $wgParser->stripSectionName( $summary ) )
2670  ->inContentLanguage()->text();
2671  }
2672 
2673  $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
2674 
2675  $summary = wfMessage( $message )->parse() . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
2676  return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary );
2677  }
2678 
2679  protected function showFormBeforeText() {
2680  global $wgOut;
2681  $section = htmlspecialchars( $this->section );
2682  $wgOut->addHTML( <<<HTML
2683 <input type='hidden' value="{$section}" name="wpSection" />
2684 <input type='hidden' value="{$this->starttime}" name="wpStarttime" />
2685 <input type='hidden' value="{$this->edittime}" name="wpEdittime" />
2686 <input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
2687 
2688 HTML
2689  );
2690  if ( !$this->checkUnicodeCompliantBrowser() ) {
2691  $wgOut->addHTML( Html::hidden( 'safemode', '1' ) );
2692  }
2693  }
2694 
2695  protected function showFormAfterText() {
2709  $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
2710  }
2711 
2720  protected function showContentForm() {
2721  $this->showTextbox1();
2722  }
2723 
2732  protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
2733  if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
2734  $attribs = array( 'style' => 'display:none;' );
2735  } else {
2736  $classes = array(); // Textarea CSS
2737  if ( $this->mTitle->isProtected( 'edit' ) &&
2738  MWNamespace::getRestrictionLevels( $this->mTitle->getNamespace() ) !== array( '' )
2739  ) {
2740  # Is the title semi-protected?
2741  if ( $this->mTitle->isSemiProtected() ) {
2742  $classes[] = 'mw-textarea-sprotected';
2743  } else {
2744  # Then it must be protected based on static groups (regular)
2745  $classes[] = 'mw-textarea-protected';
2746  }
2747  # Is the title cascade-protected?
2748  if ( $this->mTitle->isCascadeProtected() ) {
2749  $classes[] = 'mw-textarea-cprotected';
2750  }
2751  }
2752 
2753  $attribs = array( 'tabindex' => 1 );
2754 
2755  if ( is_array( $customAttribs ) ) {
2757  }
2758 
2759  if ( count( $classes ) ) {
2760  if ( isset( $attribs['class'] ) ) {
2761  $classes[] = $attribs['class'];
2762  }
2763  $attribs['class'] = implode( ' ', $classes );
2764  }
2765  }
2766 
2767  $this->showTextbox( $textoverride !== null ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
2768  }
2769 
2770  protected function showTextbox2() {
2771  $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
2772  }
2773 
2774  protected function showTextbox( $text, $name, $customAttribs = array() ) {
2776 
2777  $wikitext = $this->safeUnicodeOutput( $text );
2778  if ( strval( $wikitext ) !== '' ) {
2779  // Ensure there's a newline at the end, otherwise adding lines
2780  // is awkward.
2781  // But don't add a newline if the ext is empty, or Firefox in XHTML
2782  // mode will show an extra newline. A bit annoying.
2783  $wikitext .= "\n";
2784  }
2785 
2787  'accesskey' => ',',
2788  'id' => $name,
2789  'cols' => $wgUser->getIntOption( 'cols' ),
2790  'rows' => $wgUser->getIntOption( 'rows' ),
2791  'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work
2792  );
2793 
2794  $pageLang = $this->mTitle->getPageLanguage();
2795  $attribs['lang'] = $pageLang->getCode();
2796  $attribs['dir'] = $pageLang->getDir();
2797 
2798  $wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
2799  }
2800 
2801  protected function displayPreviewArea( $previewOutput, $isOnTop = false ) {
2802  global $wgOut;
2803  $classes = array();
2804  if ( $isOnTop ) {
2805  $classes[] = 'ontop';
2806  }
2807 
2808  $attribs = array( 'id' => 'wikiPreview', 'class' => implode( ' ', $classes ) );
2809 
2810  if ( $this->formtype != 'preview' ) {
2811  $attribs['style'] = 'display: none;';
2812  }
2813 
2814  $wgOut->addHTML( Xml::openElement( 'div', $attribs ) );
2815 
2816  if ( $this->formtype == 'preview' ) {
2817  $this->showPreview( $previewOutput );
2818  }
2819 
2820  $wgOut->addHTML( '</div>' );
2821 
2822  if ( $this->formtype == 'diff' ) {
2823  try {
2824  $this->showDiff();
2825  } catch ( MWContentSerializationException $ex ) {
2826  $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
2827  $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
2828  }
2829  }
2830  }
2831 
2838  protected function showPreview( $text ) {
2839  global $wgOut;
2840  if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
2841  $this->mArticle->openShowCategory();
2842  }
2843  # This hook seems slightly odd here, but makes things more
2844  # consistent for extensions.
2845  wfRunHooks( 'OutputPageBeforeHTML', array( &$wgOut, &$text ) );
2846  $wgOut->addHTML( $text );
2847  if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
2848  $this->mArticle->closeShowCategory();
2849  }
2850  }
2851 
2859  function showDiff() {
2861 
2862  $oldtitlemsg = 'currentrev';
2863  # if message does not exist, show diff against the preloaded default
2864  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !$this->mTitle->exists() ) {
2865  $oldtext = $this->mTitle->getDefaultMessageText();
2866  if ( $oldtext !== false ) {
2867  $oldtitlemsg = 'defaultmessagetext';
2868  $oldContent = $this->toEditContent( $oldtext );
2869  } else {
2870  $oldContent = null;
2871  }
2872  } else {
2873  $oldContent = $this->getCurrentContent();
2874  }
2875 
2876  $textboxContent = $this->toEditContent( $this->textbox1 );
2877 
2878  $newContent = $this->mArticle->replaceSectionContent(
2879  $this->section, $textboxContent,
2880  $this->summary, $this->edittime );
2881 
2882  if ( $newContent ) {
2883  ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
2884  wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
2885 
2887  $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
2888  }
2889 
2890  if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
2891  $oldtitle = wfMessage( $oldtitlemsg )->parse();
2892  $newtitle = wfMessage( 'yourtext' )->parse();
2893 
2894  if ( !$oldContent ) {
2895  $oldContent = $newContent->getContentHandler()->makeEmptyContent();
2896  }
2897 
2898  if ( !$newContent ) {
2899  $newContent = $oldContent->getContentHandler()->makeEmptyContent();
2900  }
2901 
2902  $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
2903  $de->setContent( $oldContent, $newContent );
2904 
2905  $difftext = $de->getDiff( $oldtitle, $newtitle );
2906  $de->showDiffStyle();
2907  } else {
2908  $difftext = '';
2909  }
2910 
2911  $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
2912  }
2913 
2917  protected function showHeaderCopyrightWarning() {
2918  $msg = 'editpage-head-copy-warn';
2919  if ( !wfMessage( $msg )->isDisabled() ) {
2920  global $wgOut;
2921  $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
2922  'editpage-head-copy-warn' );
2923  }
2924  }
2925 
2934  protected function showTosSummary() {
2935  $msg = 'editpage-tos-summary';
2936  wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
2937  if ( !wfMessage( $msg )->isDisabled() ) {
2938  global $wgOut;
2939  $wgOut->addHTML( '<div class="mw-tos-summary">' );
2940  $wgOut->addWikiMsg( $msg );
2941  $wgOut->addHTML( '</div>' );
2942  }
2943  }
2944 
2945  protected function showEditTools() {
2946  global $wgOut;
2947  $wgOut->addHTML( '<div class="mw-editTools">' .
2948  wfMessage( 'edittools' )->inContentLanguage()->parse() .
2949  '</div>' );
2950  }
2951 
2957  protected function getCopywarn() {
2958  return self::getCopyrightWarning( $this->mTitle );
2959  }
2960 
2969  public static function getCopyrightWarning( $title, $format = 'plain' ) {
2970  global $wgRightsText;
2971  if ( $wgRightsText ) {
2972  $copywarnMsg = array( 'copyrightwarning',
2973  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]',
2974  $wgRightsText );
2975  } else {
2976  $copywarnMsg = array( 'copyrightwarning2',
2977  '[[' . wfMessage( 'copyrightpage' )->inContentLanguage()->text() . ']]' );
2978  }
2979  // Allow for site and per-namespace customization of contribution/copyright notice.
2980  wfRunHooks( 'EditPageCopyrightWarning', array( $title, &$copywarnMsg ) );
2981 
2982  return "<div id=\"editpage-copywarn\">\n" .
2983  call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>";
2984  }
2985 
2993  public static function getPreviewLimitReport( $output ) {
2994  if ( !$output || !$output->getLimitReportData() ) {
2995  return '';
2996  }
2997 
2998  wfProfileIn( __METHOD__ );
2999 
3000  $limitReport = Html::rawElement( 'div', array( 'class' => 'mw-limitReportExplanation' ),
3001  wfMessage( 'limitreport-title' )->parseAsBlock()
3002  );
3003 
3004  // Show/hide animation doesn't work correctly on a table, so wrap it in a div.
3005  $limitReport .= Html::openElement( 'div', array( 'class' => 'preview-limit-report-wrapper' ) );
3006 
3007  $limitReport .= Html::openElement( 'table', array(
3008  'class' => 'preview-limit-report wikitable'
3009  ) ) .
3010  Html::openElement( 'tbody' );
3011 
3012  foreach ( $output->getLimitReportData() as $key => $value ) {
3013  if ( wfRunHooks( 'ParserLimitReportFormat',
3014  array( $key, &$value, &$limitReport, true, true )
3015  ) ) {
3016  $keyMsg = wfMessage( $key );
3017  $valueMsg = wfMessage( array( "$key-value-html", "$key-value" ) );
3018  if ( !$valueMsg->exists() ) {
3019  $valueMsg = new RawMessage( '$1' );
3020  }
3021  if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
3022  $limitReport .= Html::openElement( 'tr' ) .
3023  Html::rawElement( 'th', null, $keyMsg->parse() ) .
3024  Html::rawElement( 'td', null, $valueMsg->params( $value )->parse() ) .
3025  Html::closeElement( 'tr' );
3026  }
3027  }
3028  }
3029 
3030  $limitReport .= Html::closeElement( 'tbody' ) .
3031  Html::closeElement( 'table' ) .
3032  Html::closeElement( 'div' );
3033 
3034  wfProfileOut( __METHOD__ );
3035 
3036  return $limitReport;
3037  }
3038 
3039  protected function showStandardInputs( &$tabindex = 2 ) {
3040  global $wgOut;
3041  $wgOut->addHTML( "<div class='editOptions'>\n" );
3042 
3043  if ( $this->section != 'new' ) {
3044  $this->showSummaryInput( false, $this->summary );
3045  $wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
3046  }
3047 
3048  $checkboxes = $this->getCheckboxes( $tabindex,
3049  array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) );
3050  $wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" );
3051 
3052  // Show copyright warning.
3053  $wgOut->addWikiText( $this->getCopywarn() );
3054  $wgOut->addHTML( $this->editFormTextAfterWarn );
3055 
3056  $wgOut->addHTML( "<div class='editButtons'>\n" );
3057  $wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
3058 
3059  $cancel = $this->getCancelLink();
3060  if ( $cancel !== '' ) {
3061  $cancel .= Html::element( 'span',
3062  array( 'class' => 'mw-editButtons-pipe-separator' ),
3063  wfMessage( 'pipe-separator' )->text() );
3064  }
3065  $edithelpurl = Skin::makeInternalOrExternalUrl( wfMessage( 'edithelppage' )->inContentLanguage()->text() );
3066  $edithelp = '<a target="helpwindow" href="' . $edithelpurl . '">' .
3067  wfMessage( 'edithelp' )->escaped() . '</a> ' .
3068  wfMessage( 'newwindow' )->parse();
3069  $wgOut->addHTML( " <span class='cancelLink'>{$cancel}</span>\n" );
3070  $wgOut->addHTML( " <span class='editHelp'>{$edithelp}</span>\n" );
3071  $wgOut->addHTML( "</div><!-- editButtons -->\n" );
3072  wfRunHooks( 'EditPage::showStandardInputs:options', array( $this, $wgOut, &$tabindex ) );
3073  $wgOut->addHTML( "</div><!-- editOptions -->\n" );
3074  }
3075 
3080  protected function showConflict() {
3081  global $wgOut;
3082 
3083  if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
3084  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
3085 
3086  $content1 = $this->toEditContent( $this->textbox1 );
3087  $content2 = $this->toEditContent( $this->textbox2 );
3088 
3089  $handler = ContentHandler::getForModelID( $this->contentModel );
3090  $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
3091  $de->setContent( $content2, $content1 );
3092  $de->showDiff(
3093  wfMessage( 'yourtext' )->parse(),
3094  wfMessage( 'storedversion' )->text()
3095  );
3096 
3097  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
3098  $this->showTextbox2();
3099  }
3100  }
3101 
3105  public function getCancelLink() {
3106  $cancelParams = array();
3107  if ( !$this->isConflict && $this->oldid > 0 ) {
3108  $cancelParams['oldid'] = $this->oldid;
3109  }
3110 
3111  return Linker::linkKnown(
3112  $this->getContextTitle(),
3113  wfMessage( 'cancel' )->parse(),
3114  array( 'id' => 'mw-editform-cancel' ),
3115  $cancelParams
3116  );
3117  }
3118 
3128  protected function getActionURL( Title $title ) {
3129  return $title->getLocalURL( array( 'action' => $this->action ) );
3130  }
3131 
3138  protected function wasDeletedSinceLastEdit() {
3139  if ( $this->deletedSinceEdit !== null ) {
3140  return $this->deletedSinceEdit;
3141  }
3142 
3143  $this->deletedSinceEdit = false;
3144 
3145  if ( $this->mTitle->isDeletedQuick() ) {
3146  $this->lastDelete = $this->getLastDelete();
3147  if ( $this->lastDelete ) {
3148  $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
3149  if ( $deleteTime > $this->starttime ) {
3150  $this->deletedSinceEdit = true;
3151  }
3152  }
3153  }
3154 
3155  return $this->deletedSinceEdit;
3156  }
3157 
3158  protected function getLastDelete() {
3159  $dbr = wfGetDB( DB_SLAVE );
3160  $data = $dbr->selectRow(
3161  array( 'logging', 'user' ),
3162  array(
3163  'log_type',
3164  'log_action',
3165  'log_timestamp',
3166  'log_user',
3167  'log_namespace',
3168  'log_title',
3169  'log_comment',
3170  'log_params',
3171  'log_deleted',
3172  'user_name'
3173  ), array(
3174  'log_namespace' => $this->mTitle->getNamespace(),
3175  'log_title' => $this->mTitle->getDBkey(),
3176  'log_type' => 'delete',
3177  'log_action' => 'delete',
3178  'user_id=log_user'
3179  ),
3180  __METHOD__,
3181  array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' )
3182  );
3183  // Quick paranoid permission checks...
3184  if ( is_object( $data ) ) {
3185  if ( $data->log_deleted & LogPage::DELETED_USER ) {
3186  $data->user_name = wfMessage( 'rev-deleted-user' )->escaped();
3187  }
3188 
3189  if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
3190  $data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped();
3191  }
3192  }
3193  return $data;
3194  }
3195 
3201  function getPreviewText() {
3202  global $wgOut, $wgUser, $wgRawHtml, $wgLang;
3203 
3204  wfProfileIn( __METHOD__ );
3205 
3206  if ( $wgRawHtml && !$this->mTokenOk ) {
3207  // Could be an offsite preview attempt. This is very unsafe if
3208  // HTML is enabled, as it could be an attack.
3209  $parsedNote = '';
3210  if ( $this->textbox1 !== '' ) {
3211  // Do not put big scary notice, if previewing the empty
3212  // string, which happens when you initially edit
3213  // a category page, due to automatic preview-on-open.
3214  $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
3215  wfMessage( 'session_fail_preview_html' )->text() . "</div>", true, /* interface */true );
3216  }
3217  wfProfileOut( __METHOD__ );
3218  return $parsedNote;
3219  }
3220 
3221  $note = '';
3222 
3223  try {
3224  $content = $this->toEditContent( $this->textbox1 );
3225 
3226  $previewHTML = '';
3227  if ( !wfRunHooks( 'AlternateEditPreview', array( $this, &$content, &$previewHTML, &$this->mParserOutput ) ) ) {
3228  wfProfileOut( __METHOD__ );
3229  return $previewHTML;
3230  }
3231 
3232  # provide a anchor link to the editform
3233  $continueEditing = '<span class="mw-continue-editing">' .
3234  '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' .
3235  wfMessage( 'continue-editing' )->text() . ']]</span>';
3236  if ( $this->mTriedSave && !$this->mTokenOk ) {
3237  if ( $this->mTokenOkExceptSuffix ) {
3238  $note = wfMessage( 'token_suffix_mismatch' )->plain();
3239 
3240  } else {
3241  $note = wfMessage( 'session_fail_preview' )->plain();
3242  }
3243  } elseif ( $this->incompleteForm ) {
3244  $note = wfMessage( 'edit_form_incomplete' )->plain();
3245  } else {
3246  $note = wfMessage( 'previewnote' )->plain() . ' ' . $continueEditing;
3247  }
3248 
3249  $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
3250  $parserOptions->setEditSection( false );
3251  $parserOptions->setIsPreview( true );
3252  $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
3253 
3254  # don't parse non-wikitext pages, show message about preview
3255  if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
3256  if ( $this->mTitle->isCssJsSubpage() ) {
3257  $level = 'user';
3258  } elseif ( $this->mTitle->isCssOrJsPage() ) {
3259  $level = 'site';
3260  } else {
3261  $level = false;
3262  }
3263 
3264  if ( $content->getModel() == CONTENT_MODEL_CSS ) {
3265  $format = 'css';
3266  } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
3267  $format = 'js';
3268  } else {
3269  $format = false;
3270  }
3271 
3272  # Used messages to make sure grep find them:
3273  # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
3274  if ( $level && $format ) {
3275  $note = "<div id='mw-{$level}{$format}preview'>" .
3276  wfMessage( "{$level}{$format}preview" )->text() .
3277  ' ' . $continueEditing . "</div>";
3278  }
3279  }
3280 
3281  $rt = $content->getRedirectChain();
3282  if ( $rt ) {
3283  $previewHTML = $this->mArticle->viewRedirect( $rt, false );
3284  } else {
3285 
3286  # If we're adding a comment, we need to show the
3287  # summary as the headline
3288  if ( $this->section === "new" && $this->summary !== "" ) {
3289  $content = $content->addSectionHeader( $this->summary );
3290  }
3291 
3292  $hook_args = array( $this, &$content );
3293  ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
3294  wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
3295 
3296  $parserOptions->enableLimitReport();
3297 
3298  # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
3299  # But it's now deprecated, so never mind
3300 
3301  $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
3302  $parserOutput = $content->getParserOutput( $this->getArticle()->getTitle(), null, $parserOptions );
3303 
3304  $previewHTML = $parserOutput->getText();
3305  $this->mParserOutput = $parserOutput;
3306  $wgOut->addParserOutputNoText( $parserOutput );
3307 
3308  if ( count( $parserOutput->getWarnings() ) ) {
3309  $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
3310  }
3311  }
3312  } catch ( MWContentSerializationException $ex ) {
3313  $m = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
3314  $note .= "\n\n" . $m->parse();
3315  $previewHTML = '';
3316  }
3317 
3318  if ( $this->isConflict ) {
3319  $conflict = '<h2 id="mw-previewconflict">' . wfMessage( 'previewconflict' )->escaped() . "</h2>\n";
3320  } else {
3321  $conflict = '<hr />';
3322  }
3323 
3324  $previewhead = "<div class='previewnote'>\n" .
3325  '<h2 id="mw-previewheader">' . wfMessage( 'preview' )->escaped() . "</h2>" .
3326  $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
3327 
3328  $pageViewLang = $this->mTitle->getPageViewLanguage();
3329  $attribs = array( 'lang' => $pageViewLang->getHtmlCode(), 'dir' => $pageViewLang->getDir(),
3330  'class' => 'mw-content-' . $pageViewLang->getDir() );
3331  $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
3332 
3333  wfProfileOut( __METHOD__ );
3334  return $previewhead . $previewHTML . $this->previewTextAfterContent;
3335  }
3336 
3340  function getTemplates() {
3341  if ( $this->preview || $this->section != '' ) {
3342  $templates = array();
3343  if ( !isset( $this->mParserOutput ) ) {
3344  return $templates;
3345  }
3346  foreach ( $this->mParserOutput->getTemplates() as $ns => $template ) {
3347  foreach ( array_keys( $template ) as $dbk ) {
3348  $templates[] = Title::makeTitle( $ns, $dbk );
3349  }
3350  }
3351  return $templates;
3352  } else {
3353  return $this->mTitle->getTemplateLinksFrom();
3354  }
3355  }
3356 
3364  static function getEditToolbar() {
3365  global $wgStylePath, $wgContLang, $wgLang, $wgOut;
3366  global $wgEnableUploads, $wgForeignFileRepos;
3367 
3368  $imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
3369 
3383  $toolarray = array(
3384  array(
3385  'image' => $wgLang->getImageFile( 'button-bold' ),
3386  'id' => 'mw-editbutton-bold',
3387  'open' => '\'\'\'',
3388  'close' => '\'\'\'',
3389  'sample' => wfMessage( 'bold_sample' )->text(),
3390  'tip' => wfMessage( 'bold_tip' )->text(),
3391  'key' => 'B'
3392  ),
3393  array(
3394  'image' => $wgLang->getImageFile( 'button-italic' ),
3395  'id' => 'mw-editbutton-italic',
3396  'open' => '\'\'',
3397  'close' => '\'\'',
3398  'sample' => wfMessage( 'italic_sample' )->text(),
3399  'tip' => wfMessage( 'italic_tip' )->text(),
3400  'key' => 'I'
3401  ),
3402  array(
3403  'image' => $wgLang->getImageFile( 'button-link' ),
3404  'id' => 'mw-editbutton-link',
3405  'open' => '[[',
3406  'close' => ']]',
3407  'sample' => wfMessage( 'link_sample' )->text(),
3408  'tip' => wfMessage( 'link_tip' )->text(),
3409  'key' => 'L'
3410  ),
3411  array(
3412  'image' => $wgLang->getImageFile( 'button-extlink' ),
3413  'id' => 'mw-editbutton-extlink',
3414  'open' => '[',
3415  'close' => ']',
3416  'sample' => wfMessage( 'extlink_sample' )->text(),
3417  'tip' => wfMessage( 'extlink_tip' )->text(),
3418  'key' => 'X'
3419  ),
3420  array(
3421  'image' => $wgLang->getImageFile( 'button-headline' ),
3422  'id' => 'mw-editbutton-headline',
3423  'open' => "\n== ",
3424  'close' => " ==\n",
3425  'sample' => wfMessage( 'headline_sample' )->text(),
3426  'tip' => wfMessage( 'headline_tip' )->text(),
3427  'key' => 'H'
3428  ),
3429  $imagesAvailable ? array(
3430  'image' => $wgLang->getImageFile( 'button-image' ),
3431  'id' => 'mw-editbutton-image',
3432  'open' => '[[' . $wgContLang->getNsText( NS_FILE ) . ':',
3433  'close' => ']]',
3434  'sample' => wfMessage( 'image_sample' )->text(),
3435  'tip' => wfMessage( 'image_tip' )->text(),
3436  'key' => 'D',
3437  ) : false,
3438  $imagesAvailable ? array(
3439  'image' => $wgLang->getImageFile( 'button-media' ),
3440  'id' => 'mw-editbutton-media',
3441  'open' => '[[' . $wgContLang->getNsText( NS_MEDIA ) . ':',
3442  'close' => ']]',
3443  'sample' => wfMessage( 'media_sample' )->text(),
3444  'tip' => wfMessage( 'media_tip' )->text(),
3445  'key' => 'M'
3446  ) : false,
3447  array(
3448  'image' => $wgLang->getImageFile( 'button-nowiki' ),
3449  'id' => 'mw-editbutton-nowiki',
3450  'open' => "<nowiki>",
3451  'close' => "</nowiki>",
3452  'sample' => wfMessage( 'nowiki_sample' )->text(),
3453  'tip' => wfMessage( 'nowiki_tip' )->text(),
3454  'key' => 'N'
3455  ),
3456  array(
3457  'image' => $wgLang->getImageFile( 'button-sig' ),
3458  'id' => 'mw-editbutton-signature',
3459  'open' => '--~~~~',
3460  'close' => '',
3461  'sample' => '',
3462  'tip' => wfMessage( 'sig_tip' )->text(),
3463  'key' => 'Y'
3464  ),
3465  array(
3466  'image' => $wgLang->getImageFile( 'button-hr' ),
3467  'id' => 'mw-editbutton-hr',
3468  'open' => "\n----\n",
3469  'close' => '',
3470  'sample' => '',
3471  'tip' => wfMessage( 'hr_tip' )->text(),
3472  'key' => 'R'
3473  )
3474  );
3475 
3476  $script = 'mw.loader.using("mediawiki.action.edit", function() {';
3477  foreach ( $toolarray as $tool ) {
3478  if ( !$tool ) {
3479  continue;
3480  }
3481 
3482  $params = array(
3483  $wgStylePath . '/common/images/' . $tool['image'],
3484  // Note that we use the tip both for the ALT tag and the TITLE tag of the image.
3485  // Older browsers show a "speedtip" type message only for ALT.
3486  // Ideally these should be different, realistically they
3487  // probably don't need to be.
3488  $tool['tip'],
3489  $tool['open'],
3490  $tool['close'],
3491  $tool['sample'],
3492  $tool['id'],
3493  );
3494 
3495  $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
3496  }
3497 
3498  // This used to be called on DOMReady from mediawiki.action.edit, which
3499  // ended up causing race conditions with the setup code above.
3500  $script .= "\n" .
3501  "// Create button bar\n" .
3502  "$(function() { mw.toolbar.init(); } );\n";
3503 
3504  $script .= '});';
3506 
3507  $toolbar = '<div id="toolbar"></div>';
3508 
3509  wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
3510 
3511  return $toolbar;
3512  }
3513 
3524  public function getCheckboxes( &$tabindex, $checked ) {
3525  global $wgUser;
3526 
3527  $checkboxes = array();
3528 
3529  // don't show the minor edit checkbox if it's a new page or section
3530  if ( !$this->isNew ) {
3531  $checkboxes['minor'] = '';
3532  $minorLabel = wfMessage( 'minoredit' )->parse();
3533  if ( $wgUser->isAllowed( 'minoredit' ) ) {
3534  $attribs = array(
3535  'tabindex' => ++$tabindex,
3536  'accesskey' => wfMessage( 'accesskey-minoredit' )->text(),
3537  'id' => 'wpMinoredit',
3538  );
3539  $checkboxes['minor'] =
3540  Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
3541  "&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
3542  Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'minoredit', 'withaccess' ) ) ) .
3543  ">{$minorLabel}</label>";
3544  }
3545  }
3546 
3547  $watchLabel = wfMessage( 'watchthis' )->parse();
3548  $checkboxes['watch'] = '';
3549  if ( $wgUser->isLoggedIn() ) {
3550  $attribs = array(
3551  'tabindex' => ++$tabindex,
3552  'accesskey' => wfMessage( 'accesskey-watch' )->text(),
3553  'id' => 'wpWatchthis',
3554  );
3555  $checkboxes['watch'] =
3556  Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
3557  "&#160;<label for='wpWatchthis' id='mw-editpage-watch'" .
3558  Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ) ) .
3559  ">{$watchLabel}</label>";
3560  }
3561  wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
3562  return $checkboxes;
3563  }
3564 
3573  public function getEditButtons( &$tabindex ) {
3574  $buttons = array();
3575 
3576  $temp = array(
3577  'id' => 'wpSave',
3578  'name' => 'wpSave',
3579  'type' => 'submit',
3580  'tabindex' => ++$tabindex,
3581  'value' => wfMessage( 'savearticle' )->text(),
3582  'accesskey' => wfMessage( 'accesskey-save' )->text(),
3583  'title' => wfMessage( 'tooltip-save' )->text() . ' [' . wfMessage( 'accesskey-save' )->text() . ']',
3584  );
3585  $buttons['save'] = Xml::element( 'input', $temp, '' );
3586 
3587  ++$tabindex; // use the same for preview and live preview
3588  $temp = array(
3589  'id' => 'wpPreview',
3590  'name' => 'wpPreview',
3591  'type' => 'submit',
3592  'tabindex' => $tabindex,
3593  'value' => wfMessage( 'showpreview' )->text(),
3594  'accesskey' => wfMessage( 'accesskey-preview' )->text(),
3595  'title' => wfMessage( 'tooltip-preview' )->text() . ' [' . wfMessage( 'accesskey-preview' )->text() . ']',
3596  );
3597  $buttons['preview'] = Xml::element( 'input', $temp, '' );
3598  $buttons['live'] = '';
3599 
3600  $temp = array(
3601  'id' => 'wpDiff',
3602  'name' => 'wpDiff',
3603  'type' => 'submit',
3604  'tabindex' => ++$tabindex,
3605  'value' => wfMessage( 'showdiff' )->text(),
3606  'accesskey' => wfMessage( 'accesskey-diff' )->text(),
3607  'title' => wfMessage( 'tooltip-diff' )->text() . ' [' . wfMessage( 'accesskey-diff' )->text() . ']',
3608  );
3609  $buttons['diff'] = Xml::element( 'input', $temp, '' );
3610 
3611  wfRunHooks( 'EditPageBeforeEditButtons', array( &$this, &$buttons, &$tabindex ) );
3612  return $buttons;
3613  }
3614 
3627  function livePreview() {
3628  global $wgOut;
3629  $wgOut->disable();
3630  header( 'Content-type: text/xml; charset=utf-8' );
3631  header( 'Cache-control: no-cache' );
3632 
3633  $previewText = $this->getPreviewText();
3634  #$categories = $skin->getCategoryLinks();
3635 
3636  $s =
3637  '<?xml version="1.0" encoding="UTF-8" ?>' . "\n" .
3638  Xml::tags( 'livepreview', null,
3639  Xml::element( 'preview', null, $previewText )
3640  #. Xml::element( 'category', null, $categories )
3641  );
3642  echo $s;
3643  }
3644 
3650  function blockedPage() {
3651  wfDeprecated( __METHOD__, '1.19' );
3652  global $wgUser;
3653 
3654  throw new UserBlockedError( $wgUser->getBlock() );
3655  }
3656 
3662  function userNotLoggedInPage() {
3663  wfDeprecated( __METHOD__, '1.19' );
3664  throw new PermissionsError( 'edit' );
3665  }
3666 
3673  function noCreatePermission() {
3674  wfDeprecated( __METHOD__, '1.19' );
3675  $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
3676  throw new PermissionsError( $permission );
3677  }
3678 
3683  function noSuchSectionPage() {
3684  global $wgOut;
3685 
3686  $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
3687 
3688  $res = wfMessage( 'nosuchsectiontext', $this->section )->parseAsBlock();
3689  wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
3690  $wgOut->addHTML( $res );
3691 
3692  $wgOut->returnToMain( false, $this->mTitle );
3693  }
3694 
3700  public function spamPageWithContent( $match = false ) {
3702  $this->textbox2 = $this->textbox1;
3703 
3704  if ( is_array( $match ) ) {
3705  $match = $wgLang->listToText( $match );
3706  }
3707  $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
3708 
3709  $wgOut->addHTML( '<div id="spamprotected">' );
3710  $wgOut->addWikiMsg( 'spamprotectiontext' );
3711  if ( $match ) {
3712  $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
3713  }
3714  $wgOut->addHTML( '</div>' );
3715 
3716  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
3717  $this->showDiff();
3718 
3719  $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
3720  $this->showTextbox2();
3721 
3722  $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) );
3723  }
3724 
3731  private function checkUnicodeCompliantBrowser() {
3732  global $wgBrowserBlackList, $wgRequest;
3733 
3734  $currentbrowser = $wgRequest->getHeader( 'User-Agent' );
3735  if ( $currentbrowser === false ) {
3736  // No User-Agent header sent? Trust it by default...
3737  return true;
3738  }
3739 
3740  foreach ( $wgBrowserBlackList as $browser ) {
3741  if ( preg_match( $browser, $currentbrowser ) ) {
3742  return false;
3743  }
3744  }
3745  return true;
3746  }
3747 
3756  protected function safeUnicodeInput( $request, $field ) {
3757  $text = rtrim( $request->getText( $field ) );
3758  return $request->getBool( 'safemode' )
3759  ? $this->unmakeSafe( $text )
3760  : $text;
3761  }
3762 
3770  protected function safeUnicodeOutput( $text ) {
3772  $codedText = $wgContLang->recodeForEdit( $text );
3773  return $this->checkUnicodeCompliantBrowser()
3774  ? $codedText
3775  : $this->makeSafe( $codedText );
3776  }
3777 
3790  private function makeSafe( $invalue ) {
3791  // Armor existing references for reversibility.
3792  $invalue = strtr( $invalue, array( "&#x" => "&#x0" ) );
3793 
3794  $bytesleft = 0;
3795  $result = "";
3796  $working = 0;
3797  for ( $i = 0; $i < strlen( $invalue ); $i++ ) {
3798  $bytevalue = ord( $invalue[$i] );
3799  if ( $bytevalue <= 0x7F ) { // 0xxx xxxx
3800  $result .= chr( $bytevalue );
3801  $bytesleft = 0;
3802  } elseif ( $bytevalue <= 0xBF ) { // 10xx xxxx
3803  $working = $working << 6;
3804  $working += ( $bytevalue & 0x3F );
3805  $bytesleft--;
3806  if ( $bytesleft <= 0 ) {
3807  $result .= "&#x" . strtoupper( dechex( $working ) ) . ";";
3808  }
3809  } elseif ( $bytevalue <= 0xDF ) { // 110x xxxx
3810  $working = $bytevalue & 0x1F;
3811  $bytesleft = 1;
3812  } elseif ( $bytevalue <= 0xEF ) { // 1110 xxxx
3813  $working = $bytevalue & 0x0F;
3814  $bytesleft = 2;
3815  } else { // 1111 0xxx
3816  $working = $bytevalue & 0x07;
3817  $bytesleft = 3;
3818  }
3819  }
3820  return $result;
3821  }
3822 
3831  private function unmakeSafe( $invalue ) {
3832  $result = "";
3833  $valueLength = strlen( $invalue );
3834  for ( $i = 0; $i < $valueLength; $i++ ) {
3835  if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i + 3] != '0' ) ) {
3836  $i += 3;
3837  $hexstring = "";
3838  do {
3839  $hexstring .= $invalue[$i];
3840  $i++;
3841  } while ( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
3842 
3843  // Do some sanity checks. These aren't needed for reversibility,
3844  // but should help keep the breakage down if the editor
3845  // breaks one of the entities whilst editing.
3846  if ( ( substr( $invalue, $i, 1 ) == ";" ) and ( strlen( $hexstring ) <= 6 ) ) {
3847  $codepoint = hexdec( $hexstring );
3848  $result .= codepointToUtf8( $codepoint );
3849  } else {
3850  $result .= "&#x" . $hexstring . substr( $invalue, $i, 1 );
3851  }
3852  } else {
3853  $result .= substr( $invalue, $i, 1 );
3854  }
3855  }
3856  // reverse the transform that we made for reversibility reasons.
3857  return strtr( $result, array( "&#x0" => "&#x" ) );
3858  }
3859 }
ReadOnlyError
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
Definition: ReadOnlyError.php:28
Xml\checkLabel
static checkLabel( $label, $name, $id, $checked=false, $attribs=array())
Convenience function to build an HTML checkbox with a label.
Definition: Xml.php:433
ResourceLoader\makeLoaderConditionalScript
static makeLoaderConditionalScript( $script)
Returns JS code which runs given JS code if the client-side framework is present.
Definition: ResourceLoader.php:1138
ContentHandler\deprecated
static deprecated( $func, $version, $component=false)
Logs a deprecation warning, visible if $wgDevelopmentWarnings, but only if self::$enableDeprecationWa...
Definition: ContentHandler.php:1030
Title\makeTitle
static & makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:398
save
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a save
Definition: deferred.txt:4
ContentHandler\getForModelID
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
Definition: ContentHandler.php:311
$wgUser
$wgUser
Definition: Setup.php:552
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. $reader:XMLReader object $logInfo:Array of information Return false to stop further processing of the tag 'ImportHandlePageXMLTag':When parsing a XML tag in a page. $reader:XMLReader object $pageInfo:Array of information Return false to stop further processing of the tag 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information Return false to stop further processing of the tag 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. $reader:XMLReader object Return false to stop further processing of the tag 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. $reader:XMLReader object $revisionInfo:Array of information Return false to stop further processing of the tag 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. $title:Title object for the current page $request:WebRequest $ignoreRedirect:boolean to skip redirect check $target:Title/string of redirect target $article:Article object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) $article:article(object) being checked 'IsTrustedProxy':Override the result of wfIsTrustedProxy() $ip:IP being check $result:Change this value to override the result of wfIsTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of User::isValidEmailAddr(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetMagic':DEPRECATED, use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetSpecialPageAliases':DEPRECATED, use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Associative array mapping language codes to prefixed links of the form "language:title". & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LinkBegin':Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1528
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:189
Linker\commentBlock
static commentBlock( $comment, $title=null, $local=false)
Wrap a comment in standard punctuation and formatting if it's non-empty, otherwise return empty strin...
Definition: Linker.php:1556
DB_MASTER
const DB_MASTER
Definition: Defines.php:56
Content\isRedirect
isRedirect()
Returns whether this Content represents a redirect.
of
globals txt Globals are evil The original MediaWiki code relied on globals for processing context far too often MediaWiki development since then has been a story of slowly moving context out of global variables and into objects Storing processing context in object member variables allows those objects to be reused in a much more flexible way Consider the elegance of
Definition: globals.txt:10
Content\serialize
serialize( $format=null)
Convenience method for serializing this Content object.
Xml\expandAttributes
static expandAttributes( $attribs)
Given an array of ('attributename' => 'value'), it generates the code to set the XML attributes : att...
Definition: Xml.php:65
ParserOutput
Definition: ParserOutput.php:24
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
UserBlockedError
Show an error when the user tries to do something whilst blocked.
Definition: UserBlockedError.php:27
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:88
content
per default it will return the text for text based content
Definition: contenthandler.txt:107
Xml\tags
static tags( $element, $attribs=null, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:131
$response
$response
Definition: opensearch_desc.php:32
Html\textarea
static textarea( $name, $value='', $attribs=array())
Convenience function to produce a <textarea> element.
Definition: Html.php:678
Status\fatal
fatal( $message)
Add an error and set OK to false, indicating that the operation as a whole was fatal.
Definition: Status.php:146
is
We use the convention $dbr for read and $dbw for write to help you keep track of whether the database object is a the world will explode Or to be a subsequent write query which succeeded on the master may fail when replicated to the slave due to a unique key collision Replication on the slave will stop and it may take hours to repair the database and get it back online Setting read_only in my cnf on the slave will avoid this but given the dire we prefer to have as many checks as possible We provide a but the wrapper functions like please read the documentation for except in special pages derived from QueryPage It s a common pitfall for new developers to submit code containing SQL queries which examine huge numbers of rows Remember that COUNT * is(N), counting rows in atable is like counting beans in a bucket.------------------------------------------------------------------------ Replication------------------------------------------------------------------------The largest installation of MediaWiki, Wikimedia, uses a large set ofslave MySQL servers replicating writes made to a master MySQL server. Itis important to understand the issues associated with this setup if youwant to write code destined for Wikipedia.It 's often the case that the best algorithm to use for a given taskdepends on whether or not replication is in use. Due to our unabashedWikipedia-centrism, we often just use the replication-friendly version, but if you like, you can use wfGetLB() ->getServerCount() > 1 tocheck to see if replication is in use.===Lag===Lag primarily occurs when large write queries are sent to the master.Writes on the master are executed in parallel, but they are executed inserial when they are replicated to the slaves. The master writes thequery to the binlog when the transaction is committed. The slaves pollthe binlog and start executing the query as soon as it appears. They canservice reads while they are performing a write query, but will not readanything more from the binlog and thus will perform no more writes. Thismeans that if the write query runs for a long time, the slaves will lagbehind the master for the time it takes for the write query to complete.Lag can be exacerbated by high read load. MediaWiki 's load balancer willstop sending reads to a slave when it is lagged by more than 30 seconds.If the load ratios are set incorrectly, or if there is too much loadgenerally, this may lead to a slave permanently hovering around 30seconds lag.If all slaves are lagged by more than 30 seconds, MediaWiki will stopwriting to the database. All edits and other write operations will berefused, with an error returned to the user. This gives the slaves achance to catch up. Before we had this mechanism, the slaves wouldregularly lag by several minutes, making review of recent editsdifficult.In addition to this, MediaWiki attempts to ensure that the user seesevents occurring on the wiki in chronological order. A few seconds of lagcan be tolerated, as long as the user sees a consistent picture fromsubsequent requests. This is done by saving the master binlog positionin the session, and then at the start of each request, waiting for theslave to catch up to that position before doing any reads from it. Ifthis wait times out, reads are allowed anyway, but the request isconsidered to be in "lagged slave mode". Lagged slave mode can bechecked by calling wfGetLB() ->getLaggedSlaveMode(). The onlypractical consequence at present is a warning displayed in the pagefooter.===Lag avoidance===To avoid excessive lag, queries which write large numbers of rows shouldbe split up, generally to write one row at a time. Multi-row INSERT ...SELECT queries are the worst offenders should be avoided altogether.Instead do the select first and then the insert.===Working with lag===Despite our best efforts, it 's not practical to guarantee a low-lagenvironment. Lag will usually be less than one second, but mayoccasionally be up to 30 seconds. For scalability, it 's very importantto keep load on the master low, so simply sending all your queries tothe master is not the answer. So when you have a genuine need forup-to-date data, the following approach is advised:1) Do a quick query to the master for a sequence number or timestamp 2) Run the full query on the slave and check if it matches the data you gotfrom the master 3) If it doesn 't, run the full query on the masterTo avoid swamping the master every time the slaves lag, use of thisapproach should be kept to a minimum. In most cases you should just readfrom the slave and let the user deal with the delay.------------------------------------------------------------------------ Lock contention------------------------------------------------------------------------Due to the high write rate on Wikipedia(and some other wikis), MediaWiki developers need to be very careful to structure their writesto avoid long-lasting locks. By default, MediaWiki opens a transactionat the first query, and commits it before the output is sent. Locks willbe held from the time when the query is done until the commit. So youcan reduce lock time by doing as much processing as possible before youdo your write queries.Often this approach is not good enough, and it becomes necessary toenclose small groups of queries in their own transaction. Use thefollowing syntax:$dbw=wfGetDB(DB_MASTER
EDIT_FORCE_BOT
const EDIT_FORCE_BOT
Definition: Defines.php:193
wfGetDB
& wfGetDB( $db, $groups=array(), $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:3659
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
$timestamp
if( $limit) $timestamp
Definition: importImages.php:104
CONTENT_MODEL_CSS
const CONTENT_MODEL_CSS
Definition: Defines.php:285
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:2483
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all')
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1040
wfProfileIn
wfProfileIn( $functionname)
Begin profiling of a function.
Definition: Profiler.php:33
Content\convert
convert( $toModel, $lossy='')
Converts this content object into another content object with the given content model,...
$customAttribs
null means default & $customAttribs
Definition: hooks.txt:1530
wfArrayDiff2
if(!defined( 'MEDIAWIKI')) wfArrayDiff2( $a, $b)
Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
Definition: GlobalFunctions.php:113
Status\newGood
static newGood( $value=null)
Factory function for good results.
Definition: Status.php:77
$fname
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition: Setup.php:35
NS_FILE
const NS_FILE
Definition: Defines.php:85
$params
$params
Definition: styleTest.css.php:40
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1313
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php: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:662
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:28
$wgContLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the content language as $wgContLang
Definition: design.txt:56
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2113
$tabindex
in this case you re responsible for computing and outputting the entire conflict i the difference between revisions and your text headers and sections and Diff & $tabindex
Definition: hooks.txt:1038
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:109
Html\inlineScript
static inlineScript( $contents)
Output a "<script>" tag with the given contents.
Definition: Html.php:570
Linker\linkKnown
static linkKnown( $target, $html=null, $customAttribs=array(), $query=array(), $options=array( 'known', 'noclasses'))
Identical to link(), except $options defaults to 'known'.
Definition: Linker.php:264
Linker\formatHiddenCategories
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition: Linker.php:2029
ContentHandler\runLegacyHooks
static runLegacyHooks( $event, $args=array(), $warn=null)
Call a legacy hook that uses text instead of Content objects.
Definition: ContentHandler.php:1053
Skin\getSkinNames
static getSkinNames()
Fetch the set of available skins.
Definition: Skin.php:44
$dbr
$dbr
Definition: testCompression.php:48
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:40
ContentHandler\getLocalizedName
static getLocalizedName( $name)
Returns the localized name for a given content model.
Definition: ContentHandler.php:360
Revision\FOR_THIS_USER
const FOR_THIS_USER
Definition: Revision.php:73
Html\closeElement
static closeElement( $element)
Returns "</$element>", except if $wgWellFormedXml is off, in which case it returns the empty string w...
Definition: Html.php:235
Xml\encodeJsCall
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:665
Linker\tooltipAndAccesskeyAttribs
static tooltipAndAccesskeyAttribs( $name)
Returns the attributes for the tooltip and access key.
Definition: Linker.php:2298
Html\openElement
static openElement( $element, $attribs=array())
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:166
codepointToUtf8
codepointToUtf8( $codepoint)
Return UTF-8 sequence for a given Unicode code point.
Definition: UtfNormalUtil.php:36
MWException
MediaWiki exception.
Definition: MWException.php:26
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:103
$starttime
$starttime
Definition: api.php:46
Content\getSection
getSection( $sectionId)
Returns the section with the given ID.
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1127
LogPage\DELETED_COMMENT
const DELETED_COMMENT
Definition: LogPage.php:34
ParserOptions\newFromUserAndLang
static newFromUserAndLang(User $user, Language $lang)
Get a ParserOptions object from a given user and language.
Definition: ParserOptions.php:386
Html\element
static element( $element, $attribs=array(), $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:148
LogPage\DELETED_USER
const DELETED_USER
Definition: LogPage.php:35
WatchedItem\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Constant to specify that user rights 'editmywatchlist' and 'viewmywatchlist' should not be checked.
Definition: WatchedItem.php:35
MWContentSerializationException
Exception representing a failure to serialize or unserialize a content object.
Definition: ContentHandler.php:33
wfProfileOut
wfProfileOut( $functionname='missing')
Stop profiling of a function.
Definition: Profiler.php:46
$wgOut
$wgOut
Definition: Setup.php:562
wfMessage
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form externallinks including delete and has completed for all link tables default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:39
wfRunHooks
wfRunHooks( $event, array $args=array(), $deprecatedVersion=null)
Call hook functions defined in $wgHooks.
Definition: GlobalFunctions.php:4010
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:2514
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:93
Content\isEmpty
isEmpty()
Returns true if this Content object represents empty content.
MWNamespace\getRestrictionLevels
static getRestrictionLevels( $index, User $user=null)
Determine which restriction levels it makes sense to use in a namespace, optionally filtered by a use...
Definition: Namespace.php:446
Html\input
static input( $name, $value='', $type='text', $attribs=array())
Convenience function to produce an "<input>" element.
Definition: Html.php:645
list
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:188
ContentHandler\makeContent
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Definition: ContentHandler.php:144
$ok
$ok
Definition: UtfNormalTest.php:71
$section
$section
Definition: Utf8Test.php:88
Revision\userWasLastToEdit
static userWasLastToEdit( $db, $pageId, $userId, $since)
Check if no edits were made by other users since the time a user started editing the page.
Definition: Revision.php:1731
TS_MW
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition: GlobalFunctions.php:2431
wfDebug
wfDebug( $text, $dest='all')
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:933
Content\equals
equals(Content $that=null)
Returns true if this Content objects is conceptually equivalent to the given Content object.
Content\addSectionHeader
addSectionHeader( $header)
Returns a new WikitextContent object with the given section heading prepended, if supported.
$title
presenting them properly to the user as errors is done by the caller $title
Definition: hooks.txt:1324
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:82
user
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same user
Definition: distributors.txt:9
Content\getRedirectChain
getRedirectChain()
Construct the redirect destination from this content and return an array of Titles,...
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:336
action
action
Definition: parserTests.txt:378
$matches
if(!defined( 'MEDIAWIKI')) if(!isset( $wgVersion)) $matches
Definition: NoLocalSettings.php:33
$value
$value
Definition: styleTest.css.php:45
EDIT_UPDATE
const EDIT_UPDATE
Definition: Defines.php:190
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:67
Xml\check
static check( $name, $checked=false, $attribs=array())
Convenience function to build an HTML checkbox.
Definition: Xml.php:339
etc
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add etc
Definition: design.txt:12
TextContentHandler
Base content handler implementation for flat text contents.
Definition: TextContentHandler.php:31
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:2077
EDIT_DEFER_UPDATES
const EDIT_DEFER_UPDATES
Definition: Defines.php:194
Revision\RAW
const RAW
Definition: Revision.php:74
Linker\formatTemplates
static formatTemplates( $templates, $preview=false, $section=false, $more=null)
Returns HTML for the "templates used on this page" list.
Definition: Linker.php:1936
RequestContext\getMain
static getMain()
Static methods.
Definition: RequestContext.php:420
$user
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a account $user
Definition: hooks.txt:237
Content\preSaveTransform
preSaveTransform(Title $title, User $user, ParserOptions $parserOptions)
Returns a Content object with pre-save transformations applied (or this object if no transformations ...
$summary
$summary
Definition: importImages.php:120
Content
Base interface for content objects.
Definition: Content.php:34
$file
if(PHP_SAPI !='cli') $file
Definition: UtfNormalTest2.php:30
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:189
$rev
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1337
DB_SLAVE
const DB_SLAVE
Definition: Defines.php:55
Title
Represents a title within MediaWiki.
Definition: Title.php:35
EDIT_AUTOSUMMARY
const EDIT_AUTOSUMMARY
Definition: Defines.php:195
$wgLang
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as $wgLang
Definition: design.txt:56
ContentHandler\getContentText
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
Definition: ContentHandler.php:94
Xml\closeElement
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:118
type
This document describes the state of Postgres support in and is fairly well maintained The main code is very well while extensions are very hit and miss it is probably the most supported database after MySQL Much of the work in making MediaWiki database agnostic came about through the work of creating Postgres as and are nearing end of but without copying over all the usage comments General notes on the but these can almost always be programmed around *Although Postgres has a true BOOLEAN type
Definition: postgres.txt:22
wfReadOnlyReason
wfReadOnlyReason()
Get the value of $wgReadOnly or the contents of $wgReadOnlyFile.
Definition: GlobalFunctions.php:1322
$wgParser
$wgParser
Definition: Setup.php:567
in
Prior to maintenance scripts were a hodgepodge of code that had no cohesion or formal method of action Beginning in
Definition: maintenance.txt:1
$output
& $output
Definition: hooks.txt:375
Linker\titleAttrib
static titleAttrib( $name, $options=null)
Given the id of an interface element, constructs the appropriate title attribute from the system mess...
Definition: Linker.php:2072
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
wfFindFile
wfFindFile( $title, $options=array())
Find a file.
Definition: GlobalFunctions.php:3702
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:277
NS_USER
const NS_USER
Definition: Defines.php:81
$source
if(PHP_SAPI !='cli') $source
Definition: mwdoc-filter.php:18
Content\getModel
getModel()
Returns the ID of the content model used by this Content object.
name
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at name
Definition: design.txt:12
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:87
CONTENT_MODEL_JAVASCRIPT
const CONTENT_MODEL_JAVASCRIPT
Definition: Defines.php:284
EDIT_MINOR
const EDIT_MINOR
Definition: Defines.php:191
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:36
Content\getParserOutput
getParserOutput(Title $title, $revId=null, ParserOptions $options=null, $generateHtml=true)
Parse the Content object and generate a ParserOutput from the result.
$error
usually copyright or history_copyright This message must be in HTML not wikitext $subpages will be ignored and the rest of subPageSubtitle() will run. 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink' whether MediaWiki currently thinks this is a CSS JS page Hooks may change this value to override the return value of Title::isCssOrJsPage(). 'TitleIsAlwaysKnown' whether MediaWiki currently thinks this page is known isMovable() always returns false. $title whether MediaWiki currently thinks this page is movable Hooks may change this value to override the return value of Title::isMovable(). 'TitleIsWikitextPage' whether MediaWiki currently thinks this is a wikitext page Hooks may change this value to override the return value of Title::isWikitextPage() 'TitleMove' use UploadVerification and UploadVerifyFile instead where the first element is the message key and the remaining elements are used as parameters to the message based on mime etc Preferred in most cases over UploadVerification object with all info about the upload string as detected by MediaWiki Handlers will typically only apply for specific mime types object & $error
Definition: hooks.txt:2573
Html\rawElement
static rawElement( $element, $attribs=array(), $contents='')
Returns an HTML element in a string.
Definition: Html.php:124
ErrorPageError
An error page which can definitely be safely rendered using the OutputPage.
Definition: ErrorPageError.php:27
$query
return true to allow those checks to and false if checking is done use this to change the tables headers temp or archived zone change it to an object instance and return false override the list derivative used the name of the old file when set the default code will be skipped add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1105
Revision\loadFromTitle
static loadFromTitle( $db, $title, $id=0)
Load either the current, or a specified, revision that's attached to a given page.
Definition: Revision.php:252
$attribs
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition: hooks.txt:1530
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:59
$article
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function array $article
Definition: hooks.txt:78
$res
$res
Definition: database.txt:21
section
section
Definition: parserTests.txt:378
Revision\DELETED_TEXT
const DELETED_TEXT
Definition: Revision.php:65
Skin\makeInternalOrExternalUrl
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition: Skin.php:1158
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:497
ParserOptions\newFromUser
static newFromUser( $user)
Get a ParserOptions object from a given user.
Definition: ParserOptions.php:375
Status\getWikiText
getWikiText( $shortContext=false, $longContext=false)
Get the error list as a wikitext formatted list.
Definition: Status.php:185
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=array(), $page='', $user='', $param=array())
Show log extract.
Definition: LogEventsList.php:507
$wgTitle
if(! $wgRequest->checkUrlExtension()) if(! $wgEnableAPI) $wgTitle
Definition: api.php:63