'FormSpecialPage' => __DIR__ . '/includes/specialpage/FormSpecialPage.php',
'FormatJson' => __DIR__ . '/includes/json/FormatJson.php',
'FormatMetadata' => __DIR__ . '/includes/media/FormatMetadata.php',
+ 'FormattedRCFeed' => __DIR__ . '/includes/rcfeed/FormattedRCFeed.php',
'FormlessAction' => __DIR__ . '/includes/actions/FormlessAction.php',
'GIFHandler' => __DIR__ . '/includes/media/GIF.php',
'GIFMetadataExtractor' => __DIR__ . '/includes/media/GIFMetadataExtractor.php',
'RCCacheEntry' => __DIR__ . '/includes/changes/RCCacheEntry.php',
'RCCacheEntryFactory' => __DIR__ . '/includes/changes/RCCacheEntryFactory.php',
'RCDatabaseLogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
+ 'RCFeed' => __DIR__ . '/includes/rcfeed/RCFeed.php',
'RCFeedEngine' => __DIR__ . '/includes/rcfeed/RCFeedEngine.php',
'RCFeedFormatter' => __DIR__ . '/includes/rcfeed/RCFeedFormatter.php',
'RESTBagOStuff' => __DIR__ . '/includes/libs/objectcache/RESTBagOStuff.php',
$wgRCLinkDays = [ 1, 3, 7, 14, 30 ];
/**
- * Destinations to which notifications about recent changes
- * should be sent.
- *
- * As of MediaWiki 1.22, there are 2 supported 'engine' parameter option in core:
- * * 'UDPRCFeedEngine', which is used to send recent changes over UDP to the
- * specified server.
- * * 'RedisPubSubFeedEngine', which is used to send recent changes to Redis.
- *
- * The common options are:
- * * 'uri' -- the address to which the notices are to be sent.
- * * 'formatter' -- the class name (implementing RCFeedFormatter) which will
- * produce the text to send. This can also be an object of the class.
- * * 'omit_bots' -- whether the bot edits should be in the feed
- * * 'omit_anon' -- whether anonymous edits should be in the feed
- * * 'omit_user' -- whether edits by registered users should be in the feed
- * * 'omit_minor' -- whether minor edits should be in the feed
- * * 'omit_patrolled' -- whether patrolled edits should be in the feed
- *
- * The IRC-specific options are:
- * * 'add_interwiki_prefix' -- whether the titles should be prefixed with
- * the first entry in the $wgLocalInterwikis array (or the value of
- * $wgLocalInterwiki, if set)
- *
- * The JSON-specific options are:
- * * 'channel' -- if set, the 'channel' parameter is also set in JSON values.
+ * Configuration for feeds to which notifications about recent changes will be sent.
+ *
+ * The following feed classes are available by default:
+ * - 'UDPRCFeedEngine' - sends recent changes over UDP to the specified server.
+ * - 'RedisPubSubFeedEngine' - send recent changes to Redis.
+ *
+ * Only 'class' or 'uri' is required. If 'uri' is set instead of 'class', then
+ * RecentChange::getEngine() is used to determine the class. All options are
+ * passed to the constructor.
+ *
+ * Common options:
+ * - 'class' -- The class to use for this feed (must implement RCFeed).
+ * - 'omit_bots' -- Exclude bot edits from the feed. (default: false)
+ * - 'omit_anon' -- Exclude anonymous edits from the feed. (default: false)
+ * - 'omit_user' -- Exclude edits by registered users from the feed. (default: false)
+ * - 'omit_minor' -- Exclude minor edits from the feed. (default: false)
+ * - 'omit_patrolled' -- Exclude patrolled edits from the feed. (default: false)
+ *
+ * FormattedRCFeed-specific options:
+ * - 'uri' -- [required] The address to which the messages are sent.
+ * The uri scheme of this string will be looked up in $wgRCEngines
+ * to determine which RCFeedEngine class to use.
+ * - 'formatter' -- [required] The class (implementing RCFeedFormatter) which will
+ * produce the text to send. This can also be an object of the class.
+ * Formatters available by default: JSONRCFeedFormatter, XMLRCFeedFormatter,
+ * IRCColourfulRCFeedFormatter.
+ *
+ * IRCColourfulRCFeedFormatter-specific options:
+ * - 'add_interwiki_prefix' -- whether the titles should be prefixed with
+ * the first entry in the $wgLocalInterwikis array (or the value of
+ * $wgLocalInterwiki, if set)
+ *
+ * JSONRCFeedFormatter-specific options:
+ * - 'channel' -- if set, the 'channel' parameter is also set in JSON values.
*
* @example $wgRCFeeds['example'] = [
+ * 'uri' => 'udp://localhost:1336',
* 'formatter' => 'JSONRCFeedFormatter',
- * 'uri' => "udp://localhost:1336",
* 'add_interwiki_prefix' => false,
* 'omit_bots' => true,
* ];
- * @example $wgRCFeeds['exampleirc'] = [
+ * @example $wgRCFeeds['example'] = [
+ * 'uri' => 'udp://localhost:1338',
* 'formatter' => 'IRCColourfulRCFeedFormatter',
- * 'uri' => "udp://localhost:1338",
* 'add_interwiki_prefix' => false,
* 'omit_bots' => true,
* ];
+ * @example $wgRCFeeds['example'] = [
+ * 'class' => 'ExampleRCFeed',
+ * ];
* @since 1.22
*/
$wgRCFeeds = [];
/**
- * Used by RecentChange::getEngine to find the correct engine to use for a given URI scheme.
- * Keys are scheme names, values are names of engine classes.
+ * Used by RecentChange::getEngine to find the correct engine for a given URI scheme.
+ * Keys are scheme names, values are names of FormattedRCFeed sub classes.
+ * @since 1.22
*/
$wgRCEngines = [
'redis' => 'RedisPubSubFeedEngine',
// Avoid PHP 7.1 warning of passing $this by reference
$editPage = $this;
if ( Hooks::run( 'EditPageBeforeConflictDiff', [ &$editPage, &$wgOut ] ) ) {
- $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
- $stats->increment( 'edit.failures.conflict' );
- // Only include 'standard' namespaces to avoid creating unknown numbers of statsd metrics
- if (
- $this->mTitle->getNamespace() >= NS_MAIN &&
- $this->mTitle->getNamespace() <= NS_CATEGORY_TALK
- ) {
- $stats->increment( 'edit.failures.conflict.byNamespaceId.' . $this->mTitle->getNamespace() );
- }
+ $this->incrementConflictStats();
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
}
}
+ private function incrementConflictStats() {
+ $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
+ $stats->increment( 'edit.failures.conflict' );
+ // Only include 'standard' namespaces to avoid creating unknown numbers of statsd metrics
+ if (
+ $this->mTitle->getNamespace() >= NS_MAIN &&
+ $this->mTitle->getNamespace() <= NS_CATEGORY_TALK
+ ) {
+ $stats->increment( 'edit.failures.conflict.byNamespaceId.' . $this->mTitle->getNamespace() );
+ }
+ }
+
/**
* @return string
*/
global $wgOut, $wgRawHtml, $wgLang;
global $wgAllowUserCss, $wgAllowUserJs;
- $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
-
if ( $wgRawHtml && !$this->mTokenOk ) {
// Could be an offsite preview attempt. This is very unsafe if
// HTML is enabled, as it could be an attack.
$this->context->msg( 'session_fail_preview_html' )->text() . "</div>",
true, /* interface */true );
}
- $stats->increment( 'edit.failures.session_loss' );
+ $this->incrementEditFailureStats( 'session_loss' );
return $parsedNote;
}
if ( $this->mTriedSave && !$this->mTokenOk ) {
if ( $this->mTokenOkExceptSuffix ) {
$note = $this->context->msg( 'token_suffix_mismatch' )->plain();
- $stats->increment( 'edit.failures.bad_token' );
+ $this->incrementEditFailureStats( 'bad_token' );
} else {
$note = $this->context->msg( 'session_fail_preview' )->plain();
- $stats->increment( 'edit.failures.session_loss' );
+ $this->incrementEditFailureStats( 'session_loss' );
}
} elseif ( $this->incompleteForm ) {
$note = $this->context->msg( 'edit_form_incomplete' )->plain();
if ( $this->mTriedSave ) {
- $stats->increment( 'edit.failures.incomplete_form' );
+ $this->incrementEditFailureStats( 'incomplete_form' );
}
} else {
$note = $this->context->msg( 'previewnote' )->plain() . ' ' . $continueEditing;
return $previewhead . $previewHTML . $this->previewTextAfterContent;
}
+ private function incrementEditFailureStats( $failureType ) {
+ $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
+ $stats->increment( 'edit.failures.' . $failureType );
+ }
+
/**
* Get parser options for a preview
* @return ParserOptions
$performer = $this->getPerformer();
- foreach ( $feeds as $feed ) {
- $feed += [
+ foreach ( $feeds as $params ) {
+ $params += [
'omit_bots' => false,
'omit_anon' => false,
'omit_user' => false,
];
if (
- ( $feed['omit_bots'] && $this->mAttribs['rc_bot'] ) ||
- ( $feed['omit_anon'] && $performer->isAnon() ) ||
- ( $feed['omit_user'] && !$performer->isAnon() ) ||
- ( $feed['omit_minor'] && $this->mAttribs['rc_minor'] ) ||
- ( $feed['omit_patrolled'] && $this->mAttribs['rc_patrolled'] ) ||
+ ( $params['omit_bots'] && $this->mAttribs['rc_bot'] ) ||
+ ( $params['omit_anon'] && $performer->isAnon() ) ||
+ ( $params['omit_user'] && !$performer->isAnon() ) ||
+ ( $params['omit_minor'] && $this->mAttribs['rc_minor'] ) ||
+ ( $params['omit_patrolled'] && $this->mAttribs['rc_patrolled'] ) ||
$this->mAttribs['rc_type'] == RC_EXTERNAL
) {
continue;
}
- $engine = self::getEngine( $feed['uri'] );
-
if ( isset( $this->mExtra['actionCommentIRC'] ) ) {
$actionComment = $this->mExtra['actionCommentIRC'];
} else {
$actionComment = null;
}
- /** @var $formatter RCFeedFormatter */
- $formatter = is_object( $feed['formatter'] ) ? $feed['formatter'] : new $feed['formatter']();
- $line = $formatter->getLine( $feed, $this, $actionComment );
- if ( !$line ) {
- // T109544
- // If a feed formatter returns null, this will otherwise cause an
- // error in at least RedisPubSubFeedEngine.
- // Not sure where/how this should best be handled.
- continue;
- }
-
- $engine->send( $feed, $line );
+ $feed = RCFeed::factory( $params );
+ $feed->notify( $this, $actionComment );
}
}
/**
- * Gets the stream engine object for a given URI from $wgRCEngines
- *
+ * @since 1.22
+ * @deprecated since 1.29 Use RCFeed::factory() instead
* @param string $uri URI to get the engine object for
- * @throws MWException
* @return RCFeedEngine The engine object
+ * @throws MWException
*/
public static function getEngine( $uri ) {
+ // TODO: Merge into RCFeed::factory().
global $wgRCEngines;
-
$scheme = parse_url( $uri, PHP_URL_SCHEME );
if ( !$scheme ) {
- throw new MWException( __FUNCTION__ . ": Invalid stream logger URI: '$uri'" );
+ throw new MWException( "Invalid RCFeed uri: '$uri'" );
}
-
if ( !isset( $wgRCEngines[$scheme] ) ) {
- throw new MWException( __FUNCTION__ . ": Unknown stream logger URI scheme: $scheme" );
+ throw new MWException( "Unknown RCFeedEngine scheme: '$scheme'" );
}
-
if ( defined( 'MW_PHPUNIT_TEST' ) && is_object( $wgRCEngines[$scheme] ) ) {
return $wgRCEngines[$scheme];
}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Base class for RC feed engines that send messages in a freely configurable
+ * format to a uri-addressed engine set in $wgRCEngines.
+ * @since 1.29
+ */
+abstract class FormattedRCFeed extends RCFeed {
+ private $params;
+
+ /**
+ * @param array $params
+ * - 'uri'
+ * - 'formatter'
+ * @see $wgRCFeeds
+ */
+ public function __construct( array $params ) {
+ $this->params = $params;
+ }
+
+ /**
+ * Send some text to the specified feed.
+ *
+ * @param array $feed The feed, as configured in an associative array
+ * @param string $line The text to send
+ * @return bool Success
+ */
+ abstract public function send( array $feed, $line );
+
+ /**
+ * @param RecentChange $rc
+ * @param string|null $actionComment
+ * @return bool Success
+ */
+ public function notify( RecentChange $rc, $actionComment = null ) {
+ $params = $this->params;
+ /** @var $formatter RCFeedFormatter */
+ $formatter = is_object( $params['formatter'] ) ? $params['formatter'] : new $params['formatter'];
+
+ $line = $formatter->getLine( $params, $rc, $actionComment );
+ if ( !$line ) {
+ // @codeCoverageIgnoreStart
+ // T109544 - If a feed formatter returns null, this will otherwise cause an
+ // error in at least RedisPubSubFeedEngine. Not sure best to handle this.
+ return;
+ // @codeCoverageIgnoreEnd
+ }
+ return $this->send( $params, $line );
+ }
+}
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * @see $wgRCFeeds
+ * @since 1.29
+ */
+abstract class RCFeed {
+ /**
+ * @param array $params
+ */
+ public function __construct( array $params = [] ) {
+ }
+
+ /**
+ * Dispatch the recent changes notification.
+ *
+ * @param RecentChange $rc
+ * @param string|null $actionComment
+ * @return bool Success
+ */
+ abstract public function notify( RecentChange $rc, $actionComment = null );
+
+ /**
+ * @param array $params
+ * @return RCFeed
+ * @throws Exception
+ */
+ final public static function factory( array $params ) {
+ if ( !isset( $params['class'] ) ) {
+ if ( !isset( $params['uri'] ) ) {
+ throw new Exception( "RCFeeds must have a 'class' or 'uri' set." );
+ }
+ return RecentChange::getEngine( $params['uri'] );
+ }
+ $class = $params['class'];
+ if ( !class_exists( $class ) ) {
+ throw new Exception( "Unknown class '$class'." );
+ }
+ return new $class( $params );
+ }
+}
<?php
-
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
*/
/**
- * Interface for RC feed engines, which send formatted notifications
- *
+ * Backward-compatibility alias.
* @since 1.22
+ * @deprecated since 1.29 Use FormattedRCFeed instead
*/
-interface RCFeedEngine {
- /**
- * Sends some text to the specified live feed.
- *
- * @see IRCColourfulRCFeedFormatter::cleanupForIRC
- * @param array $feed The feed, as configured in an associative array
- * @param string $line The text to send
- * @return bool Success
- */
- public function send( array $feed, $line );
+abstract class RCFeedEngine extends FormattedRCFeed {
}
<?php
-
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
*/
/**
- * Emit a recent change notification via Redis Pub/Sub
+ * Send recent change notifications via Redis Pub/Sub
*
* If the feed URI contains a path component, it will be used to generate a
* channel name by stripping the leading slash and replacing any remaining
*
* @since 1.22
*/
-class RedisPubSubFeedEngine implements RCFeedEngine {
+class RedisPubSubFeedEngine extends RCFeedEngine {
/**
- * @see RCFeedEngine::send
+ * @see FormattedRCFeed::send
*/
public function send( array $feed, $line ) {
$parsed = wfParseUrl( $feed['uri'] );
<?php
-
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
*/
/**
- * Sends the notification to the specified host in a UDP packet.
+ * Send recent change notifications in a UDP packet.
* @since 1.22
*/
-
-class UDPRCFeedEngine implements RCFeedEngine {
+class UDPRCFeedEngine extends RCFeedEngine {
/**
* @see RCFeedEngine::send
*/
/**
* @covers RecentChange::notifyRCFeeds
* @covers RecentChange::getEngine
- * @covers RCFeedEngine
+ * @covers RCFeed::factory
+ * @covers FormattedRCFeed::__construct
+ * @covers FormattedRCFeed::notify
* @covers JSONRCFeedFormatter::formatArray
* @covers MachineReadableRCFeedFormatter::getLine
*/