/**
  * Writes the data in this object to the database
  * @param bool $noudp
  */
 public function save($noudp = false)
 {
     global $wgPutIPinRC, $wgUseEnotif, $wgShowUpdatedMarker, $wgContLang;
     $dbw = wfGetDB(DB_MASTER);
     if (!is_array($this->mExtra)) {
         $this->mExtra = array();
     }
     if (!$wgPutIPinRC) {
         $this->mAttribs['rc_ip'] = '';
     }
     # If our database is strict about IP addresses, use NULL instead of an empty string
     if ($dbw->strictIPs() && $this->mAttribs['rc_ip'] == '') {
         unset($this->mAttribs['rc_ip']);
     }
     # Trim spaces on user supplied text
     $this->mAttribs['rc_comment'] = trim($this->mAttribs['rc_comment']);
     # Make sure summary is truncated (whole multibyte characters)
     $this->mAttribs['rc_comment'] = $wgContLang->truncate($this->mAttribs['rc_comment'], 255);
     # Fixup database timestamps
     $this->mAttribs['rc_timestamp'] = $dbw->timestamp($this->mAttribs['rc_timestamp']);
     $this->mAttribs['rc_id'] = $dbw->nextSequenceValue('recentchanges_rc_id_seq');
     # # If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
     if ($dbw->cascadingDeletes() && $this->mAttribs['rc_cur_id'] == 0) {
         unset($this->mAttribs['rc_cur_id']);
     }
     # Insert new row
     $dbw->insert('recentchanges', $this->mAttribs, __METHOD__);
     # Set the ID
     $this->mAttribs['rc_id'] = $dbw->insertId();
     # Notify extensions
     Hooks::run('RecentChange_save', array(&$this));
     # Notify external application via UDP
     if (!$noudp) {
         $this->notifyRCFeeds();
     }
     # E-mail notifications
     if ($wgUseEnotif || $wgShowUpdatedMarker) {
         $editor = $this->getPerformer();
         $title = $this->getTitle();
         // Never send an RC notification email about categorization changes
         if ($this->mAttribs['rc_type'] != RC_CATEGORIZE) {
             if (Hooks::run('AbortEmailNotification', array($editor, $title, $this))) {
                 # @todo FIXME: This would be better as an extension hook
                 $enotif = new EmailNotification();
                 $enotif->notifyOnPageChange($editor, $title, $this->mAttribs['rc_timestamp'], $this->mAttribs['rc_comment'], $this->mAttribs['rc_minor'], $this->mAttribs['rc_last_oldid'], $this->mExtra['pageStatus']);
             }
         }
     }
     // Update the cached list of active users
     if ($this->mAttribs['rc_user'] > 0) {
         JobQueueGroup::singleton()->lazyPush(RecentChangesUpdateJob::newCacheUpdateJob());
     }
 }
Example #2
0
 /**
  * Do standard deferred updates after page edit.
  * Update links tables, site stats, search index and message cache.
  * Purges pages that include this page if the text was changed here.
  * Every 100th edit, prune the recent changes table.
  *
  * @param Revision $revision
  * @param User $user User object that did the revision
  * @param array $options Array of options, following indexes are used:
  * - changed: boolean, whether the revision changed the content (default true)
  * - created: boolean, whether the revision created the page (default false)
  * - moved: boolean, whether the page was moved (default false)
  * - oldcountable: boolean, null, or string 'no-change' (default null):
  *   - boolean: whether the page was counted as an article before that
  *     revision, only used in changed is true and created is false
  *   - null: if created is false, don't update the article count; if created
  *     is true, do update the article count
  *   - 'no-change': don't update the article count, ever
  */
 public function doEditUpdates(Revision $revision, User $user, array $options = array())
 {
     $options += array('changed' => true, 'created' => false, 'moved' => false, 'oldcountable' => null);
     $content = $revision->getContent();
     // Parse the text
     // Be careful not to do pre-save transform twice: $text is usually
     // already pre-save transformed once.
     if (!$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag('vary-revision')) {
         wfDebug(__METHOD__ . ": No prepared edit or vary-revision is set...\n");
         $editInfo = $this->prepareContentForEdit($content, $revision, $user);
     } else {
         wfDebug(__METHOD__ . ": No vary-revision, using prepared edit...\n");
         $editInfo = $this->mPreparedEdit;
     }
     // Save it to the parser cache.
     // Make sure the cache time matches page_touched to avoid double parsing.
     ParserCache::singleton()->save($editInfo->output, $this, $editInfo->popts, $revision->getTimestamp(), $editInfo->revid);
     // Update the links tables and other secondary data
     if ($content) {
         $recursive = $options['changed'];
         // bug 50785
         $updates = $content->getSecondaryDataUpdates($this->getTitle(), null, $recursive, $editInfo->output);
         foreach ($updates as $update) {
             if ($update instanceof LinksUpdate) {
                 $update->setRevision($revision);
             }
             DeferredUpdates::addUpdate($update);
         }
     }
     Hooks::run('ArticleEditUpdates', array(&$this, &$editInfo, $options['changed']));
     if (Hooks::run('ArticleEditUpdatesDeleteFromRecentchanges', array(&$this))) {
         // Flush old entries from the `recentchanges` table
         if (mt_rand(0, 9) == 0) {
             JobQueueGroup::singleton()->lazyPush(RecentChangesUpdateJob::newPurgeJob());
         }
     }
     if (!$this->exists()) {
         return;
     }
     $id = $this->getId();
     $title = $this->mTitle->getPrefixedDBkey();
     $shortTitle = $this->mTitle->getDBkey();
     if ($options['oldcountable'] === 'no-change' || !$options['changed'] && !$options['moved']) {
         $good = 0;
     } elseif ($options['created']) {
         $good = (int) $this->isCountable($editInfo);
     } elseif ($options['oldcountable'] !== null) {
         $good = (int) $this->isCountable($editInfo) - (int) $options['oldcountable'];
     } else {
         $good = 0;
     }
     $edits = $options['changed'] ? 1 : 0;
     $total = $options['created'] ? 1 : 0;
     DeferredUpdates::addUpdate(new SiteStatsUpdate(0, $edits, $good, $total));
     DeferredUpdates::addUpdate(new SearchUpdate($id, $title, $content));
     // If this is another user's talk page, update newtalk.
     // Don't do this if $options['changed'] = false (null-edits) nor if
     // it's a minor edit and the user doesn't want notifications for those.
     if ($options['changed'] && $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $user->getTitleKey() && !($revision->isMinor() && $user->isAllowed('nominornewtalk'))) {
         $recipient = User::newFromName($shortTitle, false);
         if (!$recipient) {
             wfDebug(__METHOD__ . ": invalid username\n");
         } else {
             // Allow extensions to prevent user notification
             // when a new message is added to their talk page
             if (Hooks::run('ArticleEditUpdateNewTalk', array(&$this, $recipient))) {
                 if (User::isIP($shortTitle)) {
                     // An anonymous user
                     $recipient->setNewtalk(true, $revision);
                 } elseif ($recipient->isLoggedIn()) {
                     $recipient->setNewtalk(true, $revision);
                 } else {
                     wfDebug(__METHOD__ . ": don't need to notify a nonexistent user\n");
                 }
             }
         }
     }
     if ($this->mTitle->getNamespace() == NS_MEDIAWIKI) {
         // XXX: could skip pseudo-messages like js/css here, based on content model.
         $msgtext = $content ? $content->getWikitextForTransclusion() : null;
         if ($msgtext === false || $msgtext === null) {
             $msgtext = '';
         }
         MessageCache::singleton()->replace($shortTitle, $msgtext);
     }
     if ($options['created']) {
         self::onArticleCreate($this->mTitle);
     } elseif ($options['changed']) {
         // bug 50785
         self::onArticleEdit($this->mTitle, $revision);
     }
 }
Example #3
0
 /**
  * Do standard deferred updates after page edit.
  * Update links tables, site stats, search index and message cache.
  * Purges pages that include this page if the text was changed here.
  * Every 100th edit, prune the recent changes table.
  *
  * @param Revision $revision
  * @param User $user User object that did the revision
  * @param array $options Array of options, following indexes are used:
  * - changed: boolean, whether the revision changed the content (default true)
  * - created: boolean, whether the revision created the page (default false)
  * - moved: boolean, whether the page was moved (default false)
  * - restored: boolean, whether the page was undeleted (default false)
  * - oldrevision: Revision object for the pre-update revision (default null)
  * - oldcountable: boolean, null, or string 'no-change' (default null):
  *   - boolean: whether the page was counted as an article before that
  *     revision, only used in changed is true and created is false
  *   - null: if created is false, don't update the article count; if created
  *     is true, do update the article count
  *   - 'no-change': don't update the article count, ever
  */
 public function doEditUpdates(Revision $revision, User $user, array $options = [])
 {
     global $wgRCWatchCategoryMembership, $wgContLang;
     $options += ['changed' => true, 'created' => false, 'moved' => false, 'restored' => false, 'oldrevision' => null, 'oldcountable' => null];
     $content = $revision->getContent();
     $logger = LoggerFactory::getInstance('SaveParse');
     // See if the parser output before $revision was inserted is still valid
     $editInfo = false;
     if (!$this->mPreparedEdit) {
         $logger->debug(__METHOD__ . ": No prepared edit...\n");
     } elseif ($this->mPreparedEdit->output->getFlag('vary-revision')) {
         $logger->info(__METHOD__ . ": Prepared edit has vary-revision...\n");
     } elseif ($this->mPreparedEdit->output->getFlag('vary-revision-id') && $this->mPreparedEdit->output->getSpeculativeRevIdUsed() !== $revision->getId()) {
         $logger->info(__METHOD__ . ": Prepared edit has vary-revision-id with wrong ID...\n");
     } elseif ($this->mPreparedEdit->output->getFlag('vary-user') && !$options['changed']) {
         $logger->info(__METHOD__ . ": Prepared edit has vary-user and is null...\n");
     } else {
         wfDebug(__METHOD__ . ": Using prepared edit...\n");
         $editInfo = $this->mPreparedEdit;
     }
     if (!$editInfo) {
         // Parse the text again if needed. Be careful not to do pre-save transform twice:
         // $text is usually already pre-save transformed once. Avoid using the edit stash
         // as any prepared content from there or in doEditContent() was already rejected.
         $editInfo = $this->prepareContentForEdit($content, $revision, $user, null, false);
     }
     // Save it to the parser cache.
     // Make sure the cache time matches page_touched to avoid double parsing.
     ParserCache::singleton()->save($editInfo->output, $this, $editInfo->popts, $revision->getTimestamp(), $editInfo->revid);
     // Update the links tables and other secondary data
     if ($content) {
         $recursive = $options['changed'];
         // bug 50785
         $updates = $content->getSecondaryDataUpdates($this->getTitle(), null, $recursive, $editInfo->output);
         foreach ($updates as $update) {
             if ($update instanceof LinksUpdate) {
                 $update->setRevision($revision);
                 $update->setTriggeringUser($user);
             }
             DeferredUpdates::addUpdate($update);
         }
         if ($wgRCWatchCategoryMembership && $this->getContentHandler()->supportsCategories() === true && ($options['changed'] || $options['created']) && !$options['restored']) {
             // Note: jobs are pushed after deferred updates, so the job should be able to see
             // the recent change entry (also done via deferred updates) and carry over any
             // bot/deletion/IP flags, ect.
             JobQueueGroup::singleton()->lazyPush(new CategoryMembershipChangeJob($this->getTitle(), ['pageId' => $this->getId(), 'revTimestamp' => $revision->getTimestamp()]));
         }
     }
     Hooks::run('ArticleEditUpdates', [&$this, &$editInfo, $options['changed']]);
     if (Hooks::run('ArticleEditUpdatesDeleteFromRecentchanges', [&$this])) {
         // Flush old entries from the `recentchanges` table
         if (mt_rand(0, 9) == 0) {
             JobQueueGroup::singleton()->lazyPush(RecentChangesUpdateJob::newPurgeJob());
         }
     }
     if (!$this->exists()) {
         return;
     }
     $id = $this->getId();
     $title = $this->mTitle->getPrefixedDBkey();
     $shortTitle = $this->mTitle->getDBkey();
     if ($options['oldcountable'] === 'no-change' || !$options['changed'] && !$options['moved']) {
         $good = 0;
     } elseif ($options['created']) {
         $good = (int) $this->isCountable($editInfo);
     } elseif ($options['oldcountable'] !== null) {
         $good = (int) $this->isCountable($editInfo) - (int) $options['oldcountable'];
     } else {
         $good = 0;
     }
     $edits = $options['changed'] ? 1 : 0;
     $total = $options['created'] ? 1 : 0;
     DeferredUpdates::addUpdate(new SiteStatsUpdate(0, $edits, $good, $total));
     DeferredUpdates::addUpdate(new SearchUpdate($id, $title, $content));
     // If this is another user's talk page, update newtalk.
     // Don't do this if $options['changed'] = false (null-edits) nor if
     // it's a minor edit and the user doesn't want notifications for those.
     if ($options['changed'] && $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $user->getTitleKey() && !($revision->isMinor() && $user->isAllowed('nominornewtalk'))) {
         $recipient = User::newFromName($shortTitle, false);
         if (!$recipient) {
             wfDebug(__METHOD__ . ": invalid username\n");
         } else {
             // Allow extensions to prevent user notification
             // when a new message is added to their talk page
             if (Hooks::run('ArticleEditUpdateNewTalk', [&$this, $recipient])) {
                 if (User::isIP($shortTitle)) {
                     // An anonymous user
                     $recipient->setNewtalk(true, $revision);
                 } elseif ($recipient->isLoggedIn()) {
                     $recipient->setNewtalk(true, $revision);
                 } else {
                     wfDebug(__METHOD__ . ": don't need to notify a nonexistent user\n");
                 }
             }
         }
     }
     if ($this->mTitle->getNamespace() == NS_MEDIAWIKI) {
         // XXX: could skip pseudo-messages like js/css here, based on content model.
         $msgtext = $content ? $content->getWikitextForTransclusion() : null;
         if ($msgtext === false || $msgtext === null) {
             $msgtext = '';
         }
         MessageCache::singleton()->replace($shortTitle, $msgtext);
         if ($wgContLang->hasVariants()) {
             $wgContLang->updateConversionTable($this->mTitle);
         }
     }
     if ($options['created']) {
         self::onArticleCreate($this->mTitle);
     } elseif ($options['changed']) {
         // bug 50785
         self::onArticleEdit($this->mTitle, $revision);
     }
     ResourceLoaderWikiModule::invalidateModuleCache($this->mTitle, $options['oldrevision'], $revision, wfWikiID());
 }
Example #4
0
 /**
  * Writes the data in this object to the database
  * @param bool $noudp
  */
 public function save($noudp = false)
 {
     global $wgPutIPinRC, $wgUseEnotif, $wgShowUpdatedMarker, $wgContLang;
     $dbw = wfGetDB(DB_MASTER);
     if (!is_array($this->mExtra)) {
         $this->mExtra = [];
     }
     if (!$wgPutIPinRC) {
         $this->mAttribs['rc_ip'] = '';
     }
     # Strict mode fixups (not-NULL fields)
     foreach (['minor', 'bot', 'new', 'patrolled', 'deleted'] as $field) {
         $this->mAttribs["rc_{$field}"] = (int) $this->mAttribs["rc_{$field}"];
     }
     # ...more fixups (NULL fields)
     foreach (['old_len', 'new_len'] as $field) {
         $this->mAttribs["rc_{$field}"] = isset($this->mAttribs["rc_{$field}"]) ? (int) $this->mAttribs["rc_{$field}"] : null;
     }
     # If our database is strict about IP addresses, use NULL instead of an empty string
     $strictIPs = in_array($dbw->getType(), ['oracle', 'postgres']);
     // legacy
     if ($strictIPs && $this->mAttribs['rc_ip'] == '') {
         unset($this->mAttribs['rc_ip']);
     }
     # Trim spaces on user supplied text
     $this->mAttribs['rc_comment'] = trim($this->mAttribs['rc_comment']);
     # Make sure summary is truncated (whole multibyte characters)
     $this->mAttribs['rc_comment'] = $wgContLang->truncate($this->mAttribs['rc_comment'], 255);
     # Fixup database timestamps
     $this->mAttribs['rc_timestamp'] = $dbw->timestamp($this->mAttribs['rc_timestamp']);
     $this->mAttribs['rc_id'] = $dbw->nextSequenceValue('recentchanges_rc_id_seq');
     # # If we are using foreign keys, an entry of 0 for the page_id will fail, so use NULL
     if ($this->mAttribs['rc_cur_id'] == 0) {
         unset($this->mAttribs['rc_cur_id']);
     }
     # Insert new row
     $dbw->insert('recentchanges', $this->mAttribs, __METHOD__);
     # Set the ID
     $this->mAttribs['rc_id'] = $dbw->insertId();
     # Notify extensions
     Hooks::run('RecentChange_save', [&$this]);
     if (count($this->tags)) {
         ChangeTags::addTags($this->tags, $this->mAttribs['rc_id'], $this->mAttribs['rc_this_oldid'], $this->mAttribs['rc_logid'], null, $this);
     }
     # Notify external application via UDP
     if (!$noudp) {
         $this->notifyRCFeeds();
     }
     # E-mail notifications
     if ($wgUseEnotif || $wgShowUpdatedMarker) {
         $editor = $this->getPerformer();
         $title = $this->getTitle();
         // Never send an RC notification email about categorization changes
         if ($this->mAttribs['rc_type'] != RC_CATEGORIZE && Hooks::run('AbortEmailNotification', [$editor, $title, $this])) {
             // @FIXME: This would be better as an extension hook
             // Send emails or email jobs once this row is safely committed
             $dbw->onTransactionIdle(function () use($editor, $title) {
                 $enotif = new EmailNotification();
                 $enotif->notifyOnPageChange($editor, $title, $this->mAttribs['rc_timestamp'], $this->mAttribs['rc_comment'], $this->mAttribs['rc_minor'], $this->mAttribs['rc_last_oldid'], $this->mExtra['pageStatus']);
             }, __METHOD__);
         }
     }
     // Update the cached list of active users
     if ($this->mAttribs['rc_user'] > 0) {
         JobQueueGroup::singleton()->lazyPush(RecentChangesUpdateJob::newCacheUpdateJob());
     }
 }