Exemplo n.º 1
0
 /**
  * @see Content::replaceSection()
  */
 public function replaceSection($section, Content $with, $sectionTitle = '')
 {
     wfProfileIn(__METHOD__);
     $myModelId = $this->getModel();
     $sectionModelId = $with->getModel();
     if ($sectionModelId != $myModelId) {
         wfProfileOut(__METHOD__);
         throw new MWException("Incompatible content model for section: " . "document uses {$myModelId} but " . "section uses {$sectionModelId}.");
     }
     $oldtext = $this->getNativeData();
     $text = $with->getNativeData();
     if ($section === '') {
         wfProfileOut(__METHOD__);
         return $with;
         # XXX: copy first?
     }
     if ($section == 'new') {
         # Inserting a new section
         $subject = $sectionTitle ? wfMessage('newsectionheaderdefaultlevel')->rawParams($sectionTitle)->inContentLanguage()->text() . "\n\n" : '';
         if (wfRunHooks('PlaceNewSection', array($this, $oldtext, $subject, &$text))) {
             $text = strlen(trim($oldtext)) > 0 ? "{$oldtext}\n\n{$subject}{$text}" : "{$subject}{$text}";
         }
     } else {
         # Replacing an existing section; roll out the big guns
         global $wgParser;
         $text = $wgParser->replaceSection($oldtext, $section, $text);
     }
     $newContent = new WikitextContent($text);
     wfProfileOut(__METHOD__);
     return $newContent;
 }
 /**
  * Attempts to merge differences between three versions. Returns a new
  * Content object for a clean merge and false for failure or a conflict.
  *
  * All three Content objects passed as parameters must have the same
  * content model.
  *
  * This text-based implementation uses wfMerge().
  *
  * @param Content $oldContent The page's previous content.
  * @param Content $myContent One of the page's conflicting contents.
  * @param Content $yourContent One of the page's conflicting contents.
  *
  * @return Content|bool
  */
 public function merge3(Content $oldContent, Content $myContent, Content $yourContent)
 {
     $this->checkModelID($oldContent->getModel());
     $this->checkModelID($myContent->getModel());
     $this->checkModelID($yourContent->getModel());
     $format = $this->getDefaultFormat();
     $old = $this->serializeContent($oldContent, $format);
     $mine = $this->serializeContent($myContent, $format);
     $yours = $this->serializeContent($yourContent, $format);
     $ok = wfMerge($old, $mine, $yours, $result);
     if (!$ok) {
         return false;
     }
     if (!$result) {
         return $this->makeEmptyContent();
     }
     $mergedContent = $this->unserializeContent($result, $format);
     return $mergedContent;
 }
Exemplo n.º 3
0
 public function execute()
 {
     // The data is hot but user-dependent, like page views, so we set vary cookies
     $this->getMain()->setCacheMode('anon-public-user-private');
     // Get parameters
     $params = $this->extractRequestParams();
     $text = $params['text'];
     $title = $params['title'];
     $page = $params['page'];
     $pageid = $params['pageid'];
     $oldid = $params['oldid'];
     $model = $params['contentmodel'];
     $format = $params['contentformat'];
     if (!is_null($page) && (!is_null($text) || $title != 'API')) {
         $this->dieUsage('The page parameter cannot be used together with the text and title parameters', 'params');
     }
     $prop = array_flip($params['prop']);
     if (isset($params['section'])) {
         $this->section = $params['section'];
     } else {
         $this->section = false;
     }
     // The parser needs $wgTitle to be set, apparently the
     // $title parameter in Parser::parse isn't enough *sigh*
     // TODO: Does this still need $wgTitle?
     global $wgParser, $wgTitle;
     // Currently unnecessary, code to act as a safeguard against any change in current behaviour of uselang breaks
     $oldLang = null;
     if (isset($params['uselang']) && $params['uselang'] != $this->getContext()->getLanguage()->getCode()) {
         $oldLang = $this->getContext()->getLanguage();
         // Backup language
         $this->getContext()->setLanguage(Language::factory($params['uselang']));
     }
     $redirValues = null;
     // Return result
     $result = $this->getResult();
     if (!is_null($oldid) || !is_null($pageid) || !is_null($page)) {
         if (!is_null($oldid)) {
             // Don't use the parser cache
             $rev = Revision::newFromID($oldid);
             if (!$rev) {
                 $this->dieUsage("There is no revision ID {$oldid}", 'missingrev');
             }
             if (!$rev->userCan(Revision::DELETED_TEXT, $this->getUser())) {
                 $this->dieUsage("You don't have permission to view deleted revisions", 'permissiondenied');
             }
             $titleObj = $rev->getTitle();
             $wgTitle = $titleObj;
             $pageObj = WikiPage::factory($titleObj);
             $popts = $pageObj->makeParserOptions($this->getContext());
             $popts->enableLimitReport(!$params['disablepp']);
             // If for some reason the "oldid" is actually the current revision, it may be cached
             if ($rev->isCurrent()) {
                 // May get from/save to parser cache
                 $p_result = $this->getParsedContent($pageObj, $popts, $pageid, isset($prop['wikitext']));
             } else {
                 // This is an old revision, so get the text differently
                 $this->content = $rev->getContent(Revision::FOR_THIS_USER, $this->getUser());
                 if ($this->section !== false) {
                     $this->content = $this->getSectionContent($this->content, 'r' . $rev->getId());
                 }
                 // Should we save old revision parses to the parser cache?
                 $p_result = $this->content->getParserOutput($titleObj, $rev->getId(), $popts);
             }
         } else {
             // Not $oldid, but $pageid or $page
             if ($params['redirects']) {
                 $reqParams = array('action' => 'query', 'redirects' => '');
                 if (!is_null($pageid)) {
                     $reqParams['pageids'] = $pageid;
                 } else {
                     // $page
                     $reqParams['titles'] = $page;
                 }
                 $req = new FauxRequest($reqParams);
                 $main = new ApiMain($req);
                 $main->execute();
                 $data = $main->getResultData();
                 $redirValues = isset($data['query']['redirects']) ? $data['query']['redirects'] : array();
                 $to = $page;
                 foreach ((array) $redirValues as $r) {
                     $to = $r['to'];
                 }
                 $pageParams = array('title' => $to);
             } elseif (!is_null($pageid)) {
                 $pageParams = array('pageid' => $pageid);
             } else {
                 // $page
                 $pageParams = array('title' => $page);
             }
             $pageObj = $this->getTitleOrPageId($pageParams, 'fromdb');
             $titleObj = $pageObj->getTitle();
             if (!$titleObj || !$titleObj->exists()) {
                 $this->dieUsage("The page you specified doesn't exist", 'missingtitle');
             }
             $wgTitle = $titleObj;
             if (isset($prop['revid'])) {
                 $oldid = $pageObj->getLatest();
             }
             $popts = $pageObj->makeParserOptions($this->getContext());
             $popts->enableLimitReport(!$params['disablepp']);
             // Potentially cached
             $p_result = $this->getParsedContent($pageObj, $popts, $pageid, isset($prop['wikitext']));
         }
     } else {
         // Not $oldid, $pageid, $page. Hence based on $text
         $titleObj = Title::newFromText($title);
         if (!$titleObj) {
             $this->dieUsageMsg(array('invalidtitle', $title));
         }
         if (!$titleObj->canExist()) {
             $this->dieUsage("Namespace doesn't allow actual pages", 'pagecannotexist');
         }
         $wgTitle = $titleObj;
         $pageObj = WikiPage::factory($titleObj);
         $popts = $pageObj->makeParserOptions($this->getContext());
         $popts->enableLimitReport(!$params['disablepp']);
         if (is_null($text)) {
             $this->dieUsage('The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params');
         }
         try {
             $this->content = ContentHandler::makeContent($text, $titleObj, $model, $format);
         } catch (MWContentSerializationException $ex) {
             $this->dieUsage($ex->getMessage(), 'parseerror');
         }
         if ($this->section !== false) {
             $this->content = $this->getSectionContent($this->content, $titleObj->getText());
         }
         if ($params['pst'] || $params['onlypst']) {
             $this->pstContent = $this->content->preSaveTransform($titleObj, $this->getUser(), $popts);
         }
         if ($params['onlypst']) {
             // Build a result and bail out
             $result_array = array();
             $result_array['text'] = array();
             $result->setContent($result_array['text'], $this->pstContent->serialize($format));
             if (isset($prop['wikitext'])) {
                 $result_array['wikitext'] = array();
                 $result->setContent($result_array['wikitext'], $this->content->serialize($format));
             }
             $result->addValue(null, $this->getModuleName(), $result_array);
             return;
         }
         // Not cached (save or load)
         if ($params['pst']) {
             $p_result = $this->pstContent->getParserOutput($titleObj, null, $popts);
         } else {
             $p_result = $this->content->getParserOutput($titleObj, null, $popts);
         }
     }
     $result_array = array();
     $result_array['title'] = $titleObj->getPrefixedText();
     if (!is_null($oldid)) {
         $result_array['revid'] = intval($oldid);
     }
     if ($params['redirects'] && !is_null($redirValues)) {
         $result_array['redirects'] = $redirValues;
     }
     if (isset($prop['text'])) {
         $result_array['text'] = array();
         $result->setContent($result_array['text'], $p_result->getText());
     }
     if (!is_null($params['summary'])) {
         $result_array['parsedsummary'] = array();
         $result->setContent($result_array['parsedsummary'], Linker::formatComment($params['summary'], $titleObj));
     }
     if (isset($prop['langlinks'])) {
         $result_array['langlinks'] = $this->formatLangLinks($p_result->getLanguageLinks());
     }
     if (isset($prop['languageshtml'])) {
         $languagesHtml = $this->languagesHtml($p_result->getLanguageLinks());
         $result_array['languageshtml'] = array();
         $result->setContent($result_array['languageshtml'], $languagesHtml);
     }
     if (isset($prop['categories'])) {
         $result_array['categories'] = $this->formatCategoryLinks($p_result->getCategories());
     }
     if (isset($prop['categorieshtml'])) {
         $categoriesHtml = $this->categoriesHtml($p_result->getCategories());
         $result_array['categorieshtml'] = array();
         $result->setContent($result_array['categorieshtml'], $categoriesHtml);
     }
     if (isset($prop['links'])) {
         $result_array['links'] = $this->formatLinks($p_result->getLinks());
     }
     if (isset($prop['templates'])) {
         $result_array['templates'] = $this->formatLinks($p_result->getTemplates());
     }
     if (isset($prop['images'])) {
         $result_array['images'] = array_keys($p_result->getImages());
     }
     if (isset($prop['externallinks'])) {
         $result_array['externallinks'] = array_keys($p_result->getExternalLinks());
     }
     if (isset($prop['sections'])) {
         $result_array['sections'] = $p_result->getSections();
     }
     if (isset($prop['displaytitle'])) {
         $result_array['displaytitle'] = $p_result->getDisplayTitle() ? $p_result->getDisplayTitle() : $titleObj->getPrefixedText();
     }
     if (isset($prop['headitems']) || isset($prop['headhtml'])) {
         $context = $this->getContext();
         $context->setTitle($titleObj);
         $context->getOutput()->addParserOutputNoText($p_result);
         if (isset($prop['headitems'])) {
             $headItems = $this->formatHeadItems($p_result->getHeadItems());
             $css = $this->formatCss($context->getOutput()->buildCssLinksArray());
             $scripts = array($context->getOutput()->getHeadScripts());
             $result_array['headitems'] = array_merge($headItems, $css, $scripts);
         }
         if (isset($prop['headhtml'])) {
             $result_array['headhtml'] = array();
             $result->setContent($result_array['headhtml'], $context->getOutput()->headElement($context->getSkin()));
         }
     }
     if (isset($prop['iwlinks'])) {
         $result_array['iwlinks'] = $this->formatIWLinks($p_result->getInterwikiLinks());
     }
     if (isset($prop['wikitext'])) {
         $result_array['wikitext'] = array();
         $result->setContent($result_array['wikitext'], $this->content->serialize($format));
         if (!is_null($this->pstContent)) {
             $result_array['psttext'] = array();
             $result->setContent($result_array['psttext'], $this->pstContent->serialize($format));
         }
     }
     if (isset($prop['properties'])) {
         $result_array['properties'] = $this->formatProperties($p_result->getProperties());
     }
     if ($params['generatexml']) {
         if ($this->content->getModel() != CONTENT_MODEL_WIKITEXT) {
             $this->dieUsage("generatexml is only supported for wikitext content", "notwikitext");
         }
         $wgParser->startExternalParse($titleObj, $popts, OT_PREPROCESS);
         $dom = $wgParser->preprocessToDom($this->content->getNativeData());
         if (is_callable(array($dom, 'saveXML'))) {
             $xml = $dom->saveXML();
         } else {
             $xml = $dom->__toString();
         }
         $result_array['parsetree'] = array();
         $result->setContent($result_array['parsetree'], $xml);
     }
     $result_mapping = array('redirects' => 'r', 'langlinks' => 'll', 'categories' => 'cl', 'links' => 'pl', 'templates' => 'tl', 'images' => 'img', 'externallinks' => 'el', 'iwlinks' => 'iw', 'sections' => 's', 'headitems' => 'hi', 'properties' => 'pp');
     $this->setIndexedTagNames($result_array, $result_mapping);
     $result->addValue(null, $this->getModuleName(), $result_array);
     if (!is_null($oldLang)) {
         $this->getContext()->setLanguage($oldLang);
         // Reset language to $oldLang
     }
 }
Exemplo n.º 4
0
	/**
	 * Change an existing article or create a new article. Updates RC and all necessary caches,
	 * optionally via the deferred update array.
	 *
	 * @param $content Content: new content
	 * @param string $summary edit summary
	 * @param $flags Integer bitfield:
	 *      EDIT_NEW
	 *          Article is known or assumed to be non-existent, create a new one
	 *      EDIT_UPDATE
	 *          Article is known or assumed to be pre-existing, update it
	 *      EDIT_MINOR
	 *          Mark this edit minor, if the user is allowed to do so
	 *      EDIT_SUPPRESS_RC
	 *          Do not log the change in recentchanges
	 *      EDIT_FORCE_BOT
	 *          Mark the edit a "bot" edit regardless of user rights
	 *      EDIT_DEFER_UPDATES
	 *          Defer some of the updates until the end of index.php
	 *      EDIT_AUTOSUMMARY
	 *          Fill in blank summaries with generated text where possible
	 *
	 * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
	 * If EDIT_UPDATE is specified and the article doesn't exist, the function will return an
	 * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
	 * edit-already-exists error will be returned. These two conditions are also possible with
	 * auto-detection due to MediaWiki's performance-optimised locking strategy.
	 *
	 * @param bool|int $baseRevId the revision ID this edit was based off, if any
	 * @param $user User the user doing the edit
	 * @param $serialisation_format String: format for storing the content in the database
	 *
	 * @throws MWException
	 * @return Status object. Possible errors:
	 *     edit-hook-aborted:       The ArticleSave hook aborted the edit but didn't set the fatal flag of $status
	 *     edit-gone-missing:       In update mode, but the article didn't exist
	 *     edit-conflict:           In update mode, the article changed unexpectedly
	 *     edit-no-change:          Warning that the text was the same as before
	 *     edit-already-exists:     In creation mode, but the article already exists
	 *
	 *  Extensions may define additional errors.
	 *
	 *  $return->value will contain an associative array with members as follows:
	 *     new:                     Boolean indicating if the function attempted to create a new article
	 *     revision:                The revision object for the inserted revision, or null
	 *
	 * @since 1.21
	 */
	public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
								   User $user = null, $serialisation_format = null ) {
		global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol;

		// Low-level sanity check
		if ( $this->mTitle->getText() === '' ) {
			throw new MWException( 'Something is trying to edit an article with an empty title' );
		}

		wfProfileIn( __METHOD__ );

		if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) {
			wfProfileOut( __METHOD__ );
			return Status::newFatal( 'content-not-allowed-here',
				ContentHandler::getLocalizedName( $content->getModel() ),
				$this->getTitle()->getPrefixedText() );
		}

		$user = is_null( $user ) ? $wgUser : $user;
		$status = Status::newGood( array() );

		// Load the data from the master database if needed.
		// The caller may already loaded it from the master or even loaded it using
		// SELECT FOR UPDATE, so do not override that using clear().
		$this->loadPageData( 'fromdbmaster' );

		$flags = $this->checkFlags( $flags );

		// handle hook
		$hook_args = array( &$this, &$user, &$content, &$summary,
							$flags & EDIT_MINOR, null, null, &$flags, &$status );

		if ( !wfRunHooks( 'PageContentSave', $hook_args )
			|| !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) {

			wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" );

			if ( $status->isOK() ) {
				$status->fatal( 'edit-hook-aborted' );
			}

			wfProfileOut( __METHOD__ );
			return $status;
		}

		// Silently ignore EDIT_MINOR if not allowed
		$isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
		$bot = $flags & EDIT_FORCE_BOT;

		$old_content = $this->getContent( Revision::RAW ); // current revision's content

		$oldsize = $old_content ? $old_content->getSize() : 0;
		$oldid = $this->getLatest();
		$oldIsRedirect = $this->isRedirect();
		$oldcountable = $this->isCountable();

		$handler = $content->getContentHandler();

		// Provide autosummaries if one is not provided and autosummaries are enabled.
		if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
			if ( !$old_content ) {
				$old_content = null;
			}
			$summary = $handler->getAutosummary( $old_content, $content, $flags );
		}

		$editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
		$serialized = $editInfo->pst;

		/**
		 * @var Content $content
		 */
		$content = $editInfo->pstContent;
		$newsize = $content->getSize();

		$dbw = wfGetDB( DB_MASTER );
		$now = wfTimestampNow();
		$this->mTimestamp = $now;

		if ( $flags & EDIT_UPDATE ) {
			// Update article, but only if changed.
			$status->value['new'] = false;

			if ( !$oldid ) {
				// Article gone missing
				wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
				$status->fatal( 'edit-gone-missing' );

				wfProfileOut( __METHOD__ );
				return $status;
			} elseif ( !$old_content ) {
				// Sanity check for bug 37225
				wfProfileOut( __METHOD__ );
				throw new MWException( "Could not find text for current revision {$oldid}." );
			}

			$revision = new Revision( array(
				'page'       => $this->getId(),
				'title'      => $this->getTitle(), // for determining the default content model
				'comment'    => $summary,
				'minor_edit' => $isminor,
				'text'       => $serialized,
				'len'        => $newsize,
				'parent_id'  => $oldid,
				'user'       => $user->getId(),
				'user_text'  => $user->getName(),
				'timestamp'  => $now,
				'content_model' => $content->getModel(),
				'content_format' => $serialisation_format,
			) ); // XXX: pass content object?!

			$changed = !$content->equals( $old_content );

			if ( $changed ) {
				if ( !$content->isValid() ) {
					wfProfileOut( __METHOD__ );
					throw new MWException( "New content failed validity check!" );
				}

				$dbw->begin( __METHOD__ );

				$prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
				$status->merge( $prepStatus );

				if ( !$status->isOK() ) {
					$dbw->rollback( __METHOD__ );

					wfProfileOut( __METHOD__ );
					return $status;
				}

				$revisionId = $revision->insertOn( $dbw );

				// Update page
				//
				// Note that we use $this->mLatest instead of fetching a value from the master DB
				// during the course of this function. This makes sure that EditPage can detect
				// edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
				// before this function is called. A previous function used a separate query, this
				// creates a window where concurrent edits can cause an ignored edit conflict.
				$ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect );

				if ( !$ok ) {
					// Belated edit conflict! Run away!!
					$status->fatal( 'edit-conflict' );

					$dbw->rollback( __METHOD__ );

					wfProfileOut( __METHOD__ );
					return $status;
				}

				wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
				// Update recentchanges
				if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
					// Mark as patrolled if the user can do so
					$patrolled = $wgUseRCPatrol && !count(
						$this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
					// Add RC row to the DB
					$rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
						$oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
						$revisionId, $patrolled
					);

					// Log auto-patrolled edits
					if ( $patrolled ) {
						PatrolLog::record( $rc, true, $user );
					}
				}
				$user->incEditCount();
				$dbw->commit( __METHOD__ );
			} else {
				// Bug 32948: revision ID must be set to page {{REVISIONID}} and
				// related variables correctly
				$revision->setId( $this->getLatest() );
			}

			// Update links tables, site stats, etc.
			$this->doEditUpdates(
				$revision,
				$user,
				array(
					'changed' => $changed,
					'oldcountable' => $oldcountable
				)
			);

			if ( !$changed ) {
				$status->warning( 'edit-no-change' );
				$revision = null;
				// Update page_touched, this is usually implicit in the page update
				// Other cache updates are done in onArticleEdit()
				$this->mTitle->invalidateCache();
			}
		} else {
			// Create new article
			$status->value['new'] = true;

			$dbw->begin( __METHOD__ );

			$prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
			$status->merge( $prepStatus );

			if ( !$status->isOK() ) {
				$dbw->rollback( __METHOD__ );

				wfProfileOut( __METHOD__ );
				return $status;
			}

			$status->merge( $prepStatus );

			// Add the page record; stake our claim on this title!
			// This will return false if the article already exists
			$newid = $this->insertOn( $dbw );

			if ( $newid === false ) {
				$dbw->rollback( __METHOD__ );
				$status->fatal( 'edit-already-exists' );

				wfProfileOut( __METHOD__ );
				return $status;
			}

			// Save the revision text...
			$revision = new Revision( array(
				'page'       => $newid,
				'title'      => $this->getTitle(), // for determining the default content model
				'comment'    => $summary,
				'minor_edit' => $isminor,
				'text'       => $serialized,
				'len'        => $newsize,
				'user'       => $user->getId(),
				'user_text'  => $user->getName(),
				'timestamp'  => $now,
				'content_model' => $content->getModel(),
				'content_format' => $serialisation_format,
			) );
			$revisionId = $revision->insertOn( $dbw );

			// Bug 37225: use accessor to get the text as Revision may trim it
			$content = $revision->getContent(); // sanity; get normalized version

			if ( $content ) {
				$newsize = $content->getSize();
			}

			// Update the page record with revision data
			$this->updateRevisionOn( $dbw, $revision, 0 );

			wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );

			// Update recentchanges
			if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
				// Mark as patrolled if the user can do so
				$patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count(
					$this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
				// Add RC row to the DB
				$rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
					'', $newsize, $revisionId, $patrolled );

				// Log auto-patrolled edits
				if ( $patrolled ) {
					PatrolLog::record( $rc, true, $user );
				}
			}
			$user->incEditCount();
			$dbw->commit( __METHOD__ );

			// Update links, etc.
			$this->doEditUpdates( $revision, $user, array( 'created' => true ) );

			$hook_args = array( &$this, &$user, $content, $summary,
								$flags & EDIT_MINOR, null, null, &$flags, $revision );

			ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args );
			wfRunHooks( 'PageContentInsertComplete', $hook_args );
		}

		// Do updates right now unless deferral was requested
		if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
			DeferredUpdates::doUpdates();
		}

		// Return the new revision (or null) to the caller
		$status->value['revision'] = $revision;

		$hook_args = array( &$this, &$user, $content, $summary,
							$flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId );

		ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
		wfRunHooks( 'PageContentSaveComplete', $hook_args );

		// Promote user to any groups they meet the criteria for
		$user->addAutopromoteOnceGroups( 'onEdit' );

		wfProfileOut( __METHOD__ );
		return $status;
	}
Exemplo n.º 5
0
 /**
  * @param Content $content Pre-save transform content
  * @param integer $flags
  * @param User $user
  * @param string $summary
  * @param array $meta
  * @return Status
  * @throws DBUnexpectedError
  * @throws Exception
  * @throws FatalError
  * @throws MWException
  */
 private function doCreate(Content $content, $flags, User $user, $summary, array $meta)
 {
     global $wgUseRCPatrol, $wgUseNPPatrol;
     $status = Status::newGood(['new' => true, 'revision' => null]);
     $now = wfTimestampNow();
     $newsize = $content->getSize();
     $prepStatus = $content->prepareSave($this, $flags, $meta['oldId'], $user);
     $status->merge($prepStatus);
     if (!$status->isOK()) {
         return $status;
     }
     $dbw = wfGetDB(DB_MASTER);
     $dbw->startAtomic(__METHOD__);
     // Add the page record unless one already exists for the title
     $newid = $this->insertOn($dbw);
     if ($newid === false) {
         $dbw->endAtomic(__METHOD__);
         // nothing inserted
         $status->fatal('edit-already-exists');
         return $status;
         // nothing done
     }
     // At this point we are now comitted to returning an OK
     // status unless some DB query error or other exception comes up.
     // This way callers don't have to call rollback() if $status is bad
     // unless they actually try to catch exceptions (which is rare).
     // @TODO: pass content object?!
     $revision = new Revision(['page' => $newid, 'title' => $this->mTitle, 'comment' => $summary, 'minor_edit' => $meta['minor'], 'text' => $meta['serialized'], 'len' => $newsize, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), 'content_format' => $meta['serialFormat']]);
     // Save the revision text...
     $revisionId = $revision->insertOn($dbw);
     // Update the page record with revision data
     if (!$this->updateRevisionOn($dbw, $revision, 0)) {
         throw new MWException("Failed to update page row to use new revision.");
     }
     Hooks::run('NewRevisionFromEditComplete', [$this, $revision, false, $user]);
     // Update recentchanges
     if (!($flags & EDIT_SUPPRESS_RC)) {
         // Mark as patrolled if the user can do so
         $patrolled = ($wgUseRCPatrol || $wgUseNPPatrol) && !count($this->mTitle->getUserPermissionsErrors('autopatrol', $user));
         // Add RC row to the DB
         RecentChange::notifyNew($now, $this->mTitle, $revision->isMinor(), $user, $summary, $meta['bot'], '', $newsize, $revisionId, $patrolled, $meta['tags']);
     }
     $user->incEditCount();
     $dbw->endAtomic(__METHOD__);
     $this->mTimestamp = $now;
     // Return the new revision to the caller
     $status->value['revision'] = $revision;
     // Do secondary updates once the main changes have been committed...
     DeferredUpdates::addUpdate(new AtomicSectionUpdate($dbw, __METHOD__, function () use($revision, &$user, $content, $summary, &$flags, $meta, &$status) {
         // Update links, etc.
         $this->doEditUpdates($revision, $user, ['created' => true]);
         // Trigger post-create hook
         $params = [&$this, &$user, $content, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision];
         ContentHandler::runLegacyHooks('ArticleInsertComplete', $params, '1.21');
         Hooks::run('PageContentInsertComplete', $params);
         // Trigger post-save hook
         $params = array_merge($params, [&$status, $meta['baseRevId']]);
         ContentHandler::runLegacyHooks('ArticleSaveComplete', $params, '1.21');
         Hooks::run('PageContentSaveComplete', $params);
     }), DeferredUpdates::PRESEND);
     return $status;
 }
Exemplo n.º 6
0
 public function execute()
 {
     // The data is hot but user-dependent, like page views, so we set vary cookies
     $this->getMain()->setCacheMode('anon-public-user-private');
     // Get parameters
     $params = $this->extractRequestParams();
     $text = $params['text'];
     $title = $params['title'];
     if ($title === null) {
         $titleProvided = false;
         // A title is needed for parsing, so arbitrarily choose one
         $title = 'API';
     } else {
         $titleProvided = true;
     }
     $page = $params['page'];
     $pageid = $params['pageid'];
     $oldid = $params['oldid'];
     $model = $params['contentmodel'];
     $format = $params['contentformat'];
     if (!is_null($page) && (!is_null($text) || $titleProvided)) {
         $this->dieUsage('The page parameter cannot be used together with the text and title parameters', 'params');
     }
     $prop = array_flip($params['prop']);
     if (isset($params['section'])) {
         $this->section = $params['section'];
         if (!preg_match('/^((T-)?\\d+|new)$/', $this->section)) {
             $this->dieUsage("The section parameter must be a valid section id or 'new'", "invalidsection");
         }
     } else {
         $this->section = false;
     }
     // The parser needs $wgTitle to be set, apparently the
     // $title parameter in Parser::parse isn't enough *sigh*
     // TODO: Does this still need $wgTitle?
     global $wgParser, $wgTitle;
     $redirValues = null;
     // Return result
     $result = $this->getResult();
     if (!is_null($oldid) || !is_null($pageid) || !is_null($page)) {
         if ($this->section === 'new') {
             $this->dieUsage('section=new cannot be combined with oldid, pageid or page parameters. ' . 'Please use text', 'params');
         }
         if (!is_null($oldid)) {
             // Don't use the parser cache
             $rev = Revision::newFromId($oldid);
             if (!$rev) {
                 $this->dieUsage("There is no revision ID {$oldid}", 'missingrev');
             }
             if (!$rev->userCan(Revision::DELETED_TEXT, $this->getUser())) {
                 $this->dieUsage("You don't have permission to view deleted revisions", 'permissiondenied');
             }
             $titleObj = $rev->getTitle();
             $wgTitle = $titleObj;
             $pageObj = WikiPage::factory($titleObj);
             $popts = $this->makeParserOptions($pageObj, $params);
             // If for some reason the "oldid" is actually the current revision, it may be cached
             // Deliberately comparing $pageObj->getLatest() with $rev->getId(), rather than
             // checking $rev->isCurrent(), because $pageObj is what actually ends up being used,
             // and if its ->getLatest() is outdated, $rev->isCurrent() won't tell us that.
             if ($rev->getId() == $pageObj->getLatest()) {
                 // May get from/save to parser cache
                 $p_result = $this->getParsedContent($pageObj, $popts, $pageid, isset($prop['wikitext']));
             } else {
                 // This is an old revision, so get the text differently
                 $this->content = $rev->getContent(Revision::FOR_THIS_USER, $this->getUser());
                 if ($this->section !== false) {
                     $this->content = $this->getSectionContent($this->content, 'r' . $rev->getId());
                 }
                 // Should we save old revision parses to the parser cache?
                 $p_result = $this->content->getParserOutput($titleObj, $rev->getId(), $popts);
             }
         } else {
             // Not $oldid, but $pageid or $page
             if ($params['redirects']) {
                 $reqParams = array('redirects' => '');
                 if (!is_null($pageid)) {
                     $reqParams['pageids'] = $pageid;
                 } else {
                     // $page
                     $reqParams['titles'] = $page;
                 }
                 $req = new FauxRequest($reqParams);
                 $main = new ApiMain($req);
                 $pageSet = new ApiPageSet($main);
                 $pageSet->execute();
                 $redirValues = $pageSet->getRedirectTitlesAsResult($this->getResult());
                 $to = $page;
                 foreach ($pageSet->getRedirectTitles() as $title) {
                     $to = $title->getFullText();
                 }
                 $pageParams = array('title' => $to);
             } elseif (!is_null($pageid)) {
                 $pageParams = array('pageid' => $pageid);
             } else {
                 // $page
                 $pageParams = array('title' => $page);
             }
             $pageObj = $this->getTitleOrPageId($pageParams, 'fromdb');
             $titleObj = $pageObj->getTitle();
             if (!$titleObj || !$titleObj->exists()) {
                 $this->dieUsage("The page you specified doesn't exist", 'missingtitle');
             }
             $wgTitle = $titleObj;
             if (isset($prop['revid'])) {
                 $oldid = $pageObj->getLatest();
             }
             $popts = $this->makeParserOptions($pageObj, $params);
             // Don't pollute the parser cache when setting options that aren't
             // in ParserOptions::optionsHash()
             /// @todo: This should be handled closer to the actual cache instead of here, see T110269
             $suppressCache = $params['disablepp'] || $params['disablelimitreport'] || $params['preview'] || $params['sectionpreview'] || $params['disabletidy'];
             if ($suppressCache) {
                 $this->content = $this->getContent($pageObj, $pageid);
                 $p_result = $this->content->getParserOutput($titleObj, null, $popts);
             } else {
                 // Potentially cached
                 $p_result = $this->getParsedContent($pageObj, $popts, $pageid, isset($prop['wikitext']));
             }
         }
     } else {
         // Not $oldid, $pageid, $page. Hence based on $text
         $titleObj = Title::newFromText($title);
         if (!$titleObj || $titleObj->isExternal()) {
             $this->dieUsageMsg(array('invalidtitle', $title));
         }
         $wgTitle = $titleObj;
         if ($titleObj->canExist()) {
             $pageObj = WikiPage::factory($titleObj);
         } else {
             // Do like MediaWiki::initializeArticle()
             $article = Article::newFromTitle($titleObj, $this->getContext());
             $pageObj = $article->getPage();
         }
         $popts = $this->makeParserOptions($pageObj, $params);
         $textProvided = !is_null($text);
         if (!$textProvided) {
             if ($titleProvided && ($prop || $params['generatexml'])) {
                 $this->setWarning("'title' used without 'text', and parsed page properties were requested " . "(did you mean to use 'page' instead of 'title'?)");
             }
             // Prevent warning from ContentHandler::makeContent()
             $text = '';
         }
         // If we are parsing text, do not use the content model of the default
         // API title, but default to wikitext to keep BC.
         if ($textProvided && !$titleProvided && is_null($model)) {
             $model = CONTENT_MODEL_WIKITEXT;
             $this->setWarning("No 'title' or 'contentmodel' was given, assuming {$model}.");
         }
         try {
             $this->content = ContentHandler::makeContent($text, $titleObj, $model, $format);
         } catch (MWContentSerializationException $ex) {
             $this->dieUsage($ex->getMessage(), 'parseerror');
         }
         if ($this->section !== false) {
             if ($this->section === 'new') {
                 // Insert the section title above the content.
                 if (!is_null($params['sectiontitle']) && $params['sectiontitle'] !== '') {
                     $this->content = $this->content->addSectionHeader($params['sectiontitle']);
                 }
             } else {
                 $this->content = $this->getSectionContent($this->content, $titleObj->getPrefixedText());
             }
         }
         if ($params['pst'] || $params['onlypst']) {
             $this->pstContent = $this->content->preSaveTransform($titleObj, $this->getUser(), $popts);
         }
         if ($params['onlypst']) {
             // Build a result and bail out
             $result_array = array();
             $result_array['text'] = $this->pstContent->serialize($format);
             $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
             if (isset($prop['wikitext'])) {
                 $result_array['wikitext'] = $this->content->serialize($format);
                 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
             }
             if (!is_null($params['summary']) || !is_null($params['sectiontitle']) && $this->section === 'new') {
                 $result_array['parsedsummary'] = $this->formatSummary($titleObj, $params);
                 $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
             }
             $result->addValue(null, $this->getModuleName(), $result_array);
             return;
         }
         // Not cached (save or load)
         if ($params['pst']) {
             $p_result = $this->pstContent->getParserOutput($titleObj, null, $popts);
         } else {
             $p_result = $this->content->getParserOutput($titleObj, null, $popts);
         }
     }
     $result_array = array();
     $result_array['title'] = $titleObj->getPrefixedText();
     $result_array['pageid'] = $pageid ? $pageid : $pageObj->getId();
     if (!is_null($oldid)) {
         $result_array['revid'] = intval($oldid);
     }
     if ($params['redirects'] && !is_null($redirValues)) {
         $result_array['redirects'] = $redirValues;
     }
     if ($params['disabletoc']) {
         $p_result->setTOCEnabled(false);
     }
     if (isset($prop['text'])) {
         $result_array['text'] = $p_result->getText();
         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
     }
     if (!is_null($params['summary']) || !is_null($params['sectiontitle']) && $this->section === 'new') {
         $result_array['parsedsummary'] = $this->formatSummary($titleObj, $params);
         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsedsummary';
     }
     if (isset($prop['langlinks'])) {
         $langlinks = $p_result->getLanguageLinks();
         if ($params['effectivelanglinks']) {
             // Link flags are ignored for now, but may in the future be
             // included in the result.
             $linkFlags = array();
             Hooks::run('LanguageLinks', array($titleObj, &$langlinks, &$linkFlags));
         }
     } else {
         $langlinks = false;
     }
     if (isset($prop['langlinks'])) {
         $result_array['langlinks'] = $this->formatLangLinks($langlinks);
     }
     if (isset($prop['categories'])) {
         $result_array['categories'] = $this->formatCategoryLinks($p_result->getCategories());
     }
     if (isset($prop['categorieshtml'])) {
         $result_array['categorieshtml'] = $this->categoriesHtml($p_result->getCategories());
         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'categorieshtml';
     }
     if (isset($prop['links'])) {
         $result_array['links'] = $this->formatLinks($p_result->getLinks());
     }
     if (isset($prop['templates'])) {
         $result_array['templates'] = $this->formatLinks($p_result->getTemplates());
     }
     if (isset($prop['images'])) {
         $result_array['images'] = array_keys($p_result->getImages());
     }
     if (isset($prop['externallinks'])) {
         $result_array['externallinks'] = array_keys($p_result->getExternalLinks());
     }
     if (isset($prop['sections'])) {
         $result_array['sections'] = $p_result->getSections();
     }
     if (isset($prop['displaytitle'])) {
         $result_array['displaytitle'] = $p_result->getDisplayTitle() ? $p_result->getDisplayTitle() : $titleObj->getPrefixedText();
     }
     if (isset($prop['headitems']) || isset($prop['headhtml'])) {
         $context = $this->getContext();
         $context->setTitle($titleObj);
         $context->getOutput()->addParserOutputMetadata($p_result);
         if (isset($prop['headitems'])) {
             $headItems = $this->formatHeadItems($p_result->getHeadItems());
             $css = $this->formatCss($context->getOutput()->buildCssLinksArray());
             $scripts = array($context->getOutput()->getHeadScripts());
             $result_array['headitems'] = array_merge($headItems, $css, $scripts);
         }
         if (isset($prop['headhtml'])) {
             $result_array['headhtml'] = $context->getOutput()->headElement($context->getSkin());
             $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'headhtml';
         }
     }
     if (isset($prop['modules'])) {
         $result_array['modules'] = array_values(array_unique($p_result->getModules()));
         $result_array['modulescripts'] = array_values(array_unique($p_result->getModuleScripts()));
         $result_array['modulestyles'] = array_values(array_unique($p_result->getModuleStyles()));
         // To be removed in 1.27
         $result_array['modulemessages'] = array();
         $this->setWarning('modulemessages is deprecated since MediaWiki 1.26');
     }
     if (isset($prop['jsconfigvars'])) {
         $result_array['jsconfigvars'] = ApiResult::addMetadataToResultVars($p_result->getJsConfigVars());
     }
     if (isset($prop['encodedjsconfigvars'])) {
         $result_array['encodedjsconfigvars'] = FormatJson::encode($p_result->getJsConfigVars(), false, FormatJson::ALL_OK);
         $result_array[ApiResult::META_SUBELEMENTS][] = 'encodedjsconfigvars';
     }
     if (isset($prop['modules']) && !isset($prop['jsconfigvars']) && !isset($prop['encodedjsconfigvars'])) {
         $this->setWarning("Property 'modules' was set but not 'jsconfigvars' " . "or 'encodedjsconfigvars'. Configuration variables are necessary " . "for proper module usage.");
     }
     if (isset($prop['indicators'])) {
         $result_array['indicators'] = (array) $p_result->getIndicators();
         ApiResult::setArrayType($result_array['indicators'], 'BCkvp', 'name');
     }
     if (isset($prop['iwlinks'])) {
         $result_array['iwlinks'] = $this->formatIWLinks($p_result->getInterwikiLinks());
     }
     if (isset($prop['wikitext'])) {
         $result_array['wikitext'] = $this->content->serialize($format);
         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'wikitext';
         if (!is_null($this->pstContent)) {
             $result_array['psttext'] = $this->pstContent->serialize($format);
             $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'psttext';
         }
     }
     if (isset($prop['properties'])) {
         $result_array['properties'] = (array) $p_result->getProperties();
         ApiResult::setArrayType($result_array['properties'], 'BCkvp', 'name');
     }
     if (isset($prop['limitreportdata'])) {
         $result_array['limitreportdata'] = $this->formatLimitReportData($p_result->getLimitReportData());
     }
     if (isset($prop['limitreporthtml'])) {
         $result_array['limitreporthtml'] = EditPage::getPreviewLimitReport($p_result);
         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'limitreporthtml';
     }
     if (isset($prop['parsetree']) || $params['generatexml']) {
         if ($this->content->getModel() != CONTENT_MODEL_WIKITEXT) {
             $this->dieUsage("parsetree is only supported for wikitext content", "notwikitext");
         }
         $wgParser->startExternalParse($titleObj, $popts, Parser::OT_PREPROCESS);
         $dom = $wgParser->preprocessToDom($this->content->getNativeData());
         if (is_callable(array($dom, 'saveXML'))) {
             $xml = $dom->saveXML();
         } else {
             $xml = $dom->__toString();
         }
         $result_array['parsetree'] = $xml;
         $result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
     }
     $result_mapping = array('redirects' => 'r', 'langlinks' => 'll', 'categories' => 'cl', 'links' => 'pl', 'templates' => 'tl', 'images' => 'img', 'externallinks' => 'el', 'iwlinks' => 'iw', 'sections' => 's', 'headitems' => 'hi', 'modules' => 'm', 'indicators' => 'ind', 'modulescripts' => 'm', 'modulestyles' => 'm', 'modulemessages' => 'm', 'properties' => 'pp', 'limitreportdata' => 'lr');
     $this->setIndexedTagNames($result_array, $result_mapping);
     $result->addValue(null, $this->getModuleName(), $result_array);
 }
Exemplo n.º 7
0
 /**
  * Diff this content object with another content object.
  *
  * @since 1.21diff
  *
  * @param $that Content: The other content object to compare this content
  * object to.
  * @param $lang Language: The language object to use for text segmentation.
  *    If not given, $wgContentLang is used.
  *
  * @return DiffResult: A diff representing the changes that would have to be
  *    made to this content object to make it equal to $that.
  */
 public function diff(Content $that, Language $lang = null)
 {
     global $wgContLang;
     $this->checkModelID($that->getModel());
     # @todo: could implement this in DifferenceEngine and just delegate here?
     if (!$lang) {
         $lang = $wgContLang;
     }
     $otext = $this->getNativeData();
     $ntext = $this->getNativeData();
     # Note: Use native PHP diff, external engines don't give us abstract output
     $ota = explode("\n", $wgContLang->segmentForDiff($otext));
     $nta = explode("\n", $wgContLang->segmentForDiff($ntext));
     $diff = new Diff($ota, $nta);
     return $diff;
 }
Exemplo n.º 8
0
 /**
  * Returns the appropriate ContentHandler singleton for the given Content
  * object.
  *
  * @since 1.21
  *
  * @param Content $content
  *
  * @return ContentHandler
  */
 public static function getForContent(Content $content)
 {
     $modelId = $content->getModel();
     return ContentHandler::getForModelID($modelId);
 }
 /**
  * Hook into Content::getParserOutput to provide syntax highlighting for
  * script content.
  *
  * @return bool
  * @since MW 1.21
  */
 public static function onContentGetParserOutput(Content $content, Title $title, $revId, ParserOptions $options, $generateHtml, ParserOutput &$output)
 {
     global $wgParser, $wgTextModelsToParse;
     if (!$generateHtml) {
         // Nothing special for us to do, let MediaWiki handle this.
         return true;
     }
     // Determine the language
     $extension = ExtensionRegistry::getInstance();
     $models = $extension->getAttribute('SyntaxHighlightModels');
     $model = $content->getModel();
     if (!isset($models[$model])) {
         // We don't care about this model, carry on.
         return true;
     }
     $lexer = $models[$model];
     // Hope that $wgSyntaxHighlightModels does not contain silly types.
     $text = ContentHandler::getContentText($content);
     if (!$text) {
         // Oops! Non-text content? Let MediaWiki handle this.
         return true;
     }
     // Parse using the standard parser to get links etc. into the database, HTML is replaced below.
     // We could do this using $content->fillParserOutput(), but alas it is 'protected'.
     if ($content instanceof TextContent && in_array($model, $wgTextModelsToParse)) {
         $output = $wgParser->parse($text, $title, $options, true, true, $revId);
     }
     $status = self::highlight($text, $lexer);
     if (!$status->isOK()) {
         return true;
     }
     $out = $status->getValue();
     $output->addModuleStyles('ext.pygments');
     $output->setText('<div dir="ltr">' . $out . '</div>');
     // Inform MediaWiki that we have parsed this page and it shouldn't mess with it.
     return false;
 }
Exemplo n.º 10
0
 /**
  * Get the temporary prepared edit stash key for a user
  *
  * This key can be used for caching prepared edits provided:
  *   - a) The $user was used for PST options
  *   - b) The parser output was made from the PST using cannonical matching options
  *
  * @param Title $title
  * @param Content $content
  * @param User $user User to get parser options from
  * @return string
  */
 protected static function getStashKey(Title $title, Content $content, User $user)
 {
     $hash = sha1(implode(':', array($content->getModel(), $content->getDefaultFormat(), sha1($content->serialize($content->getDefaultFormat())), $user->getId() ?: md5($user->getName()), $user->getId() ? $user->getDBTouched() : '-')));
     return wfMemcKey('prepared-edit', md5($title->getPrefixedDBkey()), $hash);
 }
Exemplo n.º 11
0
 /**
  * Constructor
  *
  * @param $row Mixed: either a database row or an array
  * @throws MWException
  * @access private
  */
 function __construct($row)
 {
     if (is_object($row)) {
         $this->mId = intval($row->rev_id);
         $this->mPage = intval($row->rev_page);
         $this->mTextId = intval($row->rev_text_id);
         $this->mComment = $row->rev_comment;
         $this->mUser = intval($row->rev_user);
         $this->mMinorEdit = intval($row->rev_minor_edit);
         $this->mTimestamp = $row->rev_timestamp;
         $this->mDeleted = intval($row->rev_deleted);
         if (!isset($row->rev_parent_id)) {
             $this->mParentId = null;
         } else {
             $this->mParentId = intval($row->rev_parent_id);
         }
         if (!isset($row->rev_len)) {
             $this->mSize = null;
         } else {
             $this->mSize = intval($row->rev_len);
         }
         if (!isset($row->rev_sha1)) {
             $this->mSha1 = null;
         } else {
             $this->mSha1 = $row->rev_sha1;
         }
         if (isset($row->page_latest)) {
             $this->mCurrent = $row->rev_id == $row->page_latest;
             $this->mTitle = Title::newFromRow($row);
         } else {
             $this->mCurrent = false;
             $this->mTitle = null;
         }
         if (!isset($row->rev_content_model) || is_null($row->rev_content_model)) {
             $this->mContentModel = null;
             # determine on demand if needed
         } else {
             $this->mContentModel = strval($row->rev_content_model);
         }
         if (!isset($row->rev_content_format) || is_null($row->rev_content_format)) {
             $this->mContentFormat = null;
             # determine on demand if needed
         } else {
             $this->mContentFormat = strval($row->rev_content_format);
         }
         // Lazy extraction...
         $this->mText = null;
         if (isset($row->old_text)) {
             $this->mTextRow = $row;
         } else {
             // 'text' table row entry will be lazy-loaded
             $this->mTextRow = null;
         }
         // Use user_name for users and rev_user_text for IPs...
         $this->mUserText = null;
         // lazy load if left null
         if ($this->mUser == 0) {
             $this->mUserText = $row->rev_user_text;
             // IP user
         } elseif (isset($row->user_name)) {
             $this->mUserText = $row->user_name;
             // logged-in user
         }
         $this->mOrigUserText = $row->rev_user_text;
     } elseif (is_array($row)) {
         // Build a new revision to be saved...
         global $wgUser;
         // ugh
         # if we have a content object, use it to set the model and type
         if (!empty($row['content'])) {
             // @todo when is that set? test with external store setup! check out insertOn() [dk]
             if (!empty($row['text_id'])) {
                 throw new MWException("Text already stored in external store (id {$row['text_id']}), " . "can't serialize content object");
             }
             $row['content_model'] = $row['content']->getModel();
             # note: mContentFormat is initializes later accordingly
             # note: content is serialized later in this method!
             # also set text to null?
         }
         $this->mId = isset($row['id']) ? intval($row['id']) : null;
         $this->mPage = isset($row['page']) ? intval($row['page']) : null;
         $this->mTextId = isset($row['text_id']) ? intval($row['text_id']) : null;
         $this->mUserText = isset($row['user_text']) ? strval($row['user_text']) : $wgUser->getName();
         $this->mUser = isset($row['user']) ? intval($row['user']) : $wgUser->getId();
         $this->mMinorEdit = isset($row['minor_edit']) ? intval($row['minor_edit']) : 0;
         $this->mTimestamp = isset($row['timestamp']) ? strval($row['timestamp']) : wfTimestampNow();
         $this->mDeleted = isset($row['deleted']) ? intval($row['deleted']) : 0;
         $this->mSize = isset($row['len']) ? intval($row['len']) : null;
         $this->mParentId = isset($row['parent_id']) ? intval($row['parent_id']) : null;
         $this->mSha1 = isset($row['sha1']) ? strval($row['sha1']) : null;
         $this->mContentModel = isset($row['content_model']) ? strval($row['content_model']) : null;
         $this->mContentFormat = isset($row['content_format']) ? strval($row['content_format']) : null;
         // Enforce spacing trimming on supplied text
         $this->mComment = isset($row['comment']) ? trim(strval($row['comment'])) : null;
         $this->mText = isset($row['text']) ? rtrim(strval($row['text'])) : null;
         $this->mTextRow = null;
         $this->mTitle = isset($row['title']) ? $row['title'] : null;
         // if we have a Content object, override mText and mContentModel
         if (!empty($row['content'])) {
             if (!$row['content'] instanceof Content) {
                 throw new MWException('`content` field must contain a Content object.');
             }
             $handler = $this->getContentHandler();
             $this->mContent = $row['content'];
             $this->mContentModel = $this->mContent->getModel();
             $this->mContentHandler = null;
             $this->mText = $handler->serializeContent($row['content'], $this->getContentFormat());
         } elseif (!is_null($this->mText)) {
             $handler = $this->getContentHandler();
             $this->mContent = $handler->unserializeContent($this->mText);
         }
         // If we have a Title object, make sure it is consistent with mPage.
         if ($this->mTitle && $this->mTitle->exists()) {
             if ($this->mPage === null) {
                 // if the page ID wasn't known, set it now
                 $this->mPage = $this->mTitle->getArticleID();
             } elseif ($this->mTitle->getArticleID() !== $this->mPage) {
                 // Got different page IDs. This may be legit (e.g. during undeletion),
                 // but it seems worth mentioning it in the log.
                 wfDebug("Page ID " . $this->mPage . " mismatches the ID " . $this->mTitle->getArticleID() . " provided by the Title object.");
             }
         }
         $this->mCurrent = false;
         // If we still have no length, see it we have the text to figure it out
         if (!$this->mSize) {
             if (!is_null($this->mContent)) {
                 $this->mSize = $this->mContent->getSize();
             } else {
                 #NOTE: this should never happen if we have either text or content object!
                 $this->mSize = null;
             }
         }
         // Same for sha1
         if ($this->mSha1 === null) {
             $this->mSha1 = is_null($this->mText) ? null : self::base36Sha1($this->mText);
         }
         // force lazy init
         $this->getContentModel();
         $this->getContentFormat();
     } else {
         throw new MWException('Revision constructor passed invalid row format.');
     }
     $this->mUnpatrolled = null;
 }
Exemplo n.º 12
0
 /**
  * Change an existing article or create a new article. Updates RC and all necessary caches,
  * optionally via the deferred update array.
  *
  * @param Content $content New content
  * @param string $summary Edit summary
  * @param int $flags Bitfield:
  *      EDIT_NEW
  *          Article is known or assumed to be non-existent, create a new one
  *      EDIT_UPDATE
  *          Article is known or assumed to be pre-existing, update it
  *      EDIT_MINOR
  *          Mark this edit minor, if the user is allowed to do so
  *      EDIT_SUPPRESS_RC
  *          Do not log the change in recentchanges
  *      EDIT_FORCE_BOT
  *          Mark the edit a "bot" edit regardless of user rights
  *      EDIT_AUTOSUMMARY
  *          Fill in blank summaries with generated text where possible
  *
  * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the
  * article will be detected. If EDIT_UPDATE is specified and the article
  * doesn't exist, the function will return an edit-gone-missing error. If
  * EDIT_NEW is specified and the article does exist, an edit-already-exists
  * error will be returned. These two conditions are also possible with
  * auto-detection due to MediaWiki's performance-optimised locking strategy.
  *
  * @param bool|int $baseRevId The revision ID this edit was based off, if any.
  *   This is not the parent revision ID, rather the revision ID for older
  *   content used as the source for a rollback, for example.
  * @param User $user The user doing the edit
  * @param string $serialFormat Format for storing the content in the
  *   database.
  *
  * @throws MWException
  * @return Status Possible errors:
  *     edit-hook-aborted: The ArticleSave hook aborted the edit but didn't
  *       set the fatal flag of $status.
  *     edit-gone-missing: In update mode, but the article didn't exist.
  *     edit-conflict: In update mode, the article changed unexpectedly.
  *     edit-no-change: Warning that the text was the same as before.
  *     edit-already-exists: In creation mode, but the article already exists.
  *
  *  Extensions may define additional errors.
  *
  *  $return->value will contain an associative array with members as follows:
  *     new: Boolean indicating if the function attempted to create a new article.
  *     revision: The revision object for the inserted revision, or null.
  *
  * @since 1.21
  * @throws MWException
  */
 public function doEditContent(Content $content, $summary, $flags = 0, $baseRevId = false, User $user = null, $serialFormat = null)
 {
     global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol;
     // Low-level sanity check
     if ($this->mTitle->getText() === '') {
         throw new MWException('Something is trying to edit an article with an empty title');
     }
     if (!$content->getContentHandler()->canBeUsedOn($this->getTitle())) {
         return Status::newFatal('content-not-allowed-here', ContentHandler::getLocalizedName($content->getModel()), $this->getTitle()->getPrefixedText());
     }
     $user = is_null($user) ? $wgUser : $user;
     $status = Status::newGood(array());
     // Load the data from the master database if needed.
     // The caller may already loaded it from the master or even loaded it using
     // SELECT FOR UPDATE, so do not override that using clear().
     $this->loadPageData('fromdbmaster');
     $flags = $this->checkFlags($flags);
     // handle hook
     $hook_args = array(&$this, &$user, &$content, &$summary, $flags & EDIT_MINOR, null, null, &$flags, &$status);
     if (!Hooks::run('PageContentSave', $hook_args) || !ContentHandler::runLegacyHooks('ArticleSave', $hook_args)) {
         wfDebug(__METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n");
         if ($status->isOK()) {
             $status->fatal('edit-hook-aborted');
         }
         return $status;
     }
     // Silently ignore EDIT_MINOR if not allowed
     $isminor = $flags & EDIT_MINOR && $user->isAllowed('minoredit');
     $bot = $flags & EDIT_FORCE_BOT;
     $old_revision = $this->getRevision();
     // current revision
     $old_content = $this->getContent(Revision::RAW);
     // current revision's content
     $oldsize = $old_content ? $old_content->getSize() : 0;
     $oldid = $this->getLatest();
     $oldIsRedirect = $this->isRedirect();
     $oldcountable = $this->isCountable();
     $handler = $content->getContentHandler();
     // Provide autosummaries if one is not provided and autosummaries are enabled.
     if ($wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '') {
         if (!$old_content) {
             $old_content = null;
         }
         $summary = $handler->getAutosummary($old_content, $content, $flags);
     }
     $editInfo = $this->prepareContentForEdit($content, null, $user, $serialFormat);
     $serialized = $editInfo->pst;
     /**
      * @var Content $content
      */
     $content = $editInfo->pstContent;
     $newsize = $content->getSize();
     $dbw = wfGetDB(DB_MASTER);
     $now = wfTimestampNow();
     if ($flags & EDIT_UPDATE) {
         // Update article, but only if changed.
         $status->value['new'] = false;
         if (!$oldid) {
             // Article gone missing
             wfDebug(__METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n");
             $status->fatal('edit-gone-missing');
             return $status;
         } elseif (!$old_content) {
             // Sanity check for bug 37225
             throw new MWException("Could not find text for current revision {$oldid}.");
         }
         $revision = new Revision(array('page' => $this->getId(), 'title' => $this->getTitle(), 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $serialized, 'len' => $newsize, 'parent_id' => $oldid, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), 'content_format' => $serialFormat));
         // XXX: pass content object?!
         $changed = !$content->equals($old_content);
         if ($changed) {
             $prepStatus = $content->prepareSave($this, $flags, $oldid, $user);
             $status->merge($prepStatus);
             if (!$status->isOK()) {
                 return $status;
             }
             $dbw->begin(__METHOD__);
             // Get the latest page_latest value while locking it.
             // Do a CAS style check to see if it's the same as when this method
             // started. If it changed then bail out before touching the DB.
             $latestNow = $this->lockAndGetLatest();
             if ($latestNow != $oldid) {
                 $dbw->commit(__METHOD__);
                 // Page updated or deleted in the mean time
                 $status->fatal('edit-conflict');
                 return $status;
             }
             // At this point we are now comitted to returning an OK
             // status unless some DB query error or other exception comes up.
             // This way callers don't have to call rollback() if $status is bad
             // unless they actually try to catch exceptions (which is rare).
             $revisionId = $revision->insertOn($dbw);
             // Update page_latest and friends to reflect the new revision
             if (!$this->updateRevisionOn($dbw, $revision, null, $oldIsRedirect)) {
                 $dbw->rollback(__METHOD__);
                 throw new MWException("Failed to update page row to use new revision.");
             }
             Hooks::run('NewRevisionFromEditComplete', array($this, $revision, $baseRevId, $user));
             // Update recentchanges
             if (!($flags & EDIT_SUPPRESS_RC)) {
                 // Mark as patrolled if the user can do so
                 $patrolled = $wgUseRCPatrol && !count($this->mTitle->getUserPermissionsErrors('autopatrol', $user));
                 // Add RC row to the DB
                 RecentChange::notifyEdit($now, $this->mTitle, $isminor, $user, $summary, $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize, $revisionId, $patrolled);
             }
             $user->incEditCount();
             $dbw->commit(__METHOD__);
             $this->mTimestamp = $now;
         } else {
             // Bug 32948: revision ID must be set to page {{REVISIONID}} and
             // related variables correctly
             $revision->setId($this->getLatest());
         }
         // Update links tables, site stats, etc.
         $this->doEditUpdates($revision, $user, array('changed' => $changed, 'oldcountable' => $oldcountable, 'oldrevision' => $old_revision));
         if (!$changed) {
             $status->warning('edit-no-change');
             $revision = null;
             // Update page_touched, this is usually implicit in the page update
             // Other cache updates are done in onArticleEdit()
             $this->mTitle->invalidateCache($now);
         }
     } else {
         // Create new article
         $status->value['new'] = true;
         $prepStatus = $content->prepareSave($this, $flags, $oldid, $user);
         $status->merge($prepStatus);
         if (!$status->isOK()) {
             return $status;
         }
         $dbw->begin(__METHOD__);
         // Add the page record unless one already exists for the title
         $newid = $this->insertOn($dbw);
         if ($newid === false) {
             $dbw->commit(__METHOD__);
             // nothing inserted
             $status->fatal('edit-already-exists');
             return $status;
             // nothing done
         }
         // At this point we are now comitted to returning an OK
         // status unless some DB query error or other exception comes up.
         // This way callers don't have to call rollback() if $status is bad
         // unless they actually try to catch exceptions (which is rare).
         // Save the revision text...
         $revision = new Revision(array('page' => $newid, 'title' => $this->getTitle(), 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $serialized, 'len' => $newsize, 'user' => $user->getId(), 'user_text' => $user->getName(), 'timestamp' => $now, 'content_model' => $content->getModel(), 'content_format' => $serialFormat));
         $revisionId = $revision->insertOn($dbw);
         // Bug 37225: use accessor to get the text as Revision may trim it
         $content = $revision->getContent();
         // sanity; get normalized version
         if ($content) {
             $newsize = $content->getSize();
         }
         // Update the page record with revision data
         if (!$this->updateRevisionOn($dbw, $revision, 0)) {
             $dbw->rollback(__METHOD__);
             throw new MWException("Failed to update page row to use new revision.");
         }
         Hooks::run('NewRevisionFromEditComplete', array($this, $revision, false, $user));
         // Update recentchanges
         if (!($flags & EDIT_SUPPRESS_RC)) {
             // Mark as patrolled if the user can do so
             $patrolled = ($wgUseRCPatrol || $wgUseNPPatrol) && !count($this->mTitle->getUserPermissionsErrors('autopatrol', $user));
             // Add RC row to the DB
             RecentChange::notifyNew($now, $this->mTitle, $isminor, $user, $summary, $bot, '', $newsize, $revisionId, $patrolled);
         }
         $user->incEditCount();
         $dbw->commit(__METHOD__);
         $this->mTimestamp = $now;
         // Update links, etc.
         $this->doEditUpdates($revision, $user, array('created' => true, 'oldrevision' => $old_revision));
         $hook_args = array(&$this, &$user, $content, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision);
         ContentHandler::runLegacyHooks('ArticleInsertComplete', $hook_args);
         Hooks::run('PageContentInsertComplete', $hook_args);
     }
     // Return the new revision (or null) to the caller
     $status->value['revision'] = $revision;
     $hook_args = array(&$this, &$user, $content, $summary, $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId);
     ContentHandler::runLegacyHooks('ArticleSaveComplete', $hook_args);
     Hooks::run('PageContentSaveComplete', $hook_args);
     // Promote user to any groups they meet the criteria for
     DeferredUpdates::addCallableUpdate(function () use($user) {
         $user->addAutopromoteOnceGroups('onEdit');
         $user->addAutopromoteOnceGroups('onView');
         // b/c
     });
     return $status;
 }
	/**
	 * Hook into Content::getParserOutput to provide syntax highlighting for
	 * script content.
	 *
	 * @return bool
	 * @since MW 1.21
	 */
	public static function renderHook( Content $content, Title $title,
			ParserOptions $options, $generateHtml, ParserOutput &$output
	) {

		global $wgSyntaxHighlightModels, $wgUseSiteCss;

		// Determine the language
		$model = $content->getModel();
		if ( !isset( $wgSyntaxHighlightModels[$model] ) ) {
			// We don't care about this model, carry on.
			return true;
		}

		if ( !$generateHtml ) {
			// Nothing to do.
			return false;
		}

		// Hope that $wgSyntaxHighlightModels does not contain silly types.
		$text = Contenthandler::getContentText( $content );

		if ( $text === null || $text === false ) {
			// Oops! Non-text content?
			return false;
		}

		$lang = $wgSyntaxHighlightModels[$model];

		// Attempt to format
		$geshi = self::prepare( $text, $lang );
		if( $geshi instanceof GeSHi ) {

			$out = $geshi->parse_code();
			if( !$geshi->error() ) {
				// Done
				$output->addHeadItem( self::buildHeadItem( $geshi ), "source-$lang" );
				$output->setText( "<div dir=\"ltr\">{$out}</div>" );

				if( $wgUseSiteCss ) {
					$output->addModuleStyles( 'ext.geshi.local' );
				}
				return false;
			}
		}

		// Bottle out
		return true;
	}
 /**
  * Hook into Content::getParserOutput to provide syntax highlighting for
  * script content.
  *
  * @return bool
  * @since MW 1.21
  */
 public static function renderHook(Content $content, Title $title, $revId, ParserOptions $options, $generateHtml, ParserOutput &$output)
 {
     global $wgSyntaxHighlightModels, $wgUseSiteCss, $wgParser, $wgTextModelsToParse;
     // Determine the language
     $model = $content->getModel();
     if (!isset($wgSyntaxHighlightModels[$model])) {
         // We don't care about this model, carry on.
         return true;
     }
     if (!$generateHtml) {
         // Nothing special for us to do, let MediaWiki handle this.
         return true;
     }
     // Hope that $wgSyntaxHighlightModels does not contain silly types.
     $text = ContentHandler::getContentText($content);
     if ($text === null || $text === false) {
         // Oops! Non-text content? Let MediaWiki handle this.
         return true;
     }
     // Parse using the standard parser to get links etc. into the database, HTML is replaced below.
     // We could do this using $content->fillParserOutput(), but alas it is 'protected'.
     if ($content instanceof TextContent && in_array($model, $wgTextModelsToParse)) {
         $output = $wgParser->parse($text, $title, $options, true, true, $revId);
     }
     $lang = $wgSyntaxHighlightModels[$model];
     // Attempt to format
     $geshi = self::prepare($text, $lang);
     if ($geshi instanceof GeSHi) {
         $out = $geshi->parse_code();
         if (!$geshi->error()) {
             // Done
             $output->addModuleStyles("ext.geshi.language.{$lang}");
             $output->setText("<div dir=\"ltr\">{$out}</div>");
             if ($wgUseSiteCss) {
                 $output->addModuleStyles('ext.geshi.local');
             }
             // Inform MediaWiki that we have parsed this page and it shouldn't mess with it.
             return false;
         }
     }
     // Bottle out
     return true;
 }
Exemplo n.º 15
0
 /**
  * Get hash of the content, factoring in model/format
  *
  * @param Content $content
  * @return string
  */
 private static function getContentHash(Content $content)
 {
     return sha1(implode("\n", [$content->getModel(), $content->getDefaultFormat(), $content->serialize($content->getDefaultFormat())]));
 }