123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- <?php
- /**
- * Send information about this MediaWiki instance to MediaWiki.org.
- *
- * 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
- */
- use Psr\Log\LoggerInterface;
- use MediaWiki\Logger\LoggerFactory;
- /**
- * Send information about this MediaWiki instance to MediaWiki.org.
- *
- * @since 1.28
- */
- class Pingback {
- /**
- * @var int Revision ID of the JSON schema that describes the pingback
- * payload. The schema lives on MetaWiki, at
- * <https://meta.wikimedia.org/wiki/Schema:MediaWikiPingback>.
- */
- const SCHEMA_REV = 15781718;
- /** @var LoggerInterface */
- protected $logger;
- /** @var Config */
- protected $config;
- /** @var string updatelog key (also used as cache/db lock key) */
- protected $key;
- /** @var string Randomly-generated identifier for this wiki */
- protected $id;
- /**
- * @param Config $config
- * @param LoggerInterface $logger
- */
- public function __construct( Config $config = null, LoggerInterface $logger = null ) {
- $this->config = $config ?: RequestContext::getMain()->getConfig();
- $this->logger = $logger ?: LoggerFactory::getInstance( __CLASS__ );
- $this->key = 'Pingback-' . $this->config->get( 'Version' );
- }
- /**
- * Should a pingback be sent?
- * @return bool
- */
- private function shouldSend() {
- return $this->config->get( 'Pingback' ) && !$this->checkIfSent();
- }
- /**
- * Has a pingback been sent in the last month for this MediaWiki version?
- * @return bool
- */
- private function checkIfSent() {
- $dbr = wfGetDB( DB_REPLICA );
- $timestamp = $dbr->selectField(
- 'updatelog',
- 'ul_value',
- [ 'ul_key' => $this->key ],
- __METHOD__
- );
- if ( $timestamp === false ) {
- return false;
- }
- // send heartbeat ping if last ping was over a month ago
- if ( time() - (int)$timestamp > 60 * 60 * 24 * 30 ) {
- return false;
- }
- return true;
- }
- /**
- * Record the fact that we have sent a pingback for this MediaWiki version,
- * to ensure we don't submit data multiple times.
- */
- private function markSent() {
- $dbw = wfGetDB( DB_MASTER );
- $timestamp = time();
- return $dbw->upsert(
- 'updatelog',
- [ 'ul_key' => $this->key, 'ul_value' => $timestamp ],
- [ 'ul_key' ],
- [ 'ul_value' => $timestamp ],
- __METHOD__
- );
- }
- /**
- * Acquire lock for sending a pingback
- *
- * This ensures only one thread can attempt to send a pingback at any given
- * time and that we wait an hour before retrying failed attempts.
- *
- * @return bool Whether lock was acquired
- */
- private function acquireLock() {
- $cache = ObjectCache::getLocalClusterInstance();
- if ( !$cache->add( $this->key, 1, 60 * 60 ) ) {
- return false; // throttled
- }
- $dbw = wfGetDB( DB_MASTER );
- if ( !$dbw->lock( $this->key, __METHOD__, 0 ) ) {
- return false; // already in progress
- }
- return true;
- }
- /**
- * Collect basic data about this MediaWiki installation and return it
- * as an associative array conforming to the Pingback schema on MetaWiki
- * (<https://meta.wikimedia.org/wiki/Schema:MediaWikiPingback>).
- *
- * This is public so we can display it in the installer
- *
- * Developers: If you're adding a new piece of data to this, please ensure
- * that you update https://www.mediawiki.org/wiki/Manual:$wgPingback
- *
- * @return array
- */
- public function getSystemInfo() {
- $event = [
- 'database' => $this->config->get( 'DBtype' ),
- 'MediaWiki' => $this->config->get( 'Version' ),
- 'PHP' => PHP_VERSION,
- 'OS' => PHP_OS . ' ' . php_uname( 'r' ),
- 'arch' => PHP_INT_SIZE === 8 ? 64 : 32,
- 'machine' => php_uname( 'm' ),
- ];
- if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
- $event['serverSoftware'] = $_SERVER['SERVER_SOFTWARE'];
- }
- $limit = ini_get( 'memory_limit' );
- if ( $limit && $limit != -1 ) {
- $event['memoryLimit'] = $limit;
- }
- return $event;
- }
- /**
- * Get the EventLogging packet to be sent to the server
- *
- * @return array
- */
- private function getData() {
- return [
- 'schema' => 'MediaWikiPingback',
- 'revision' => self::SCHEMA_REV,
- 'wiki' => $this->getOrCreatePingbackId(),
- 'event' => $this->getSystemInfo(),
- ];
- }
- /**
- * Get a unique, stable identifier for this wiki
- *
- * If the identifier does not already exist, create it and save it in the
- * database. The identifier is randomly-generated.
- *
- * @return string 32-character hex string
- */
- private function getOrCreatePingbackId() {
- if ( !$this->id ) {
- $id = wfGetDB( DB_REPLICA )->selectField(
- 'updatelog', 'ul_value', [ 'ul_key' => 'PingBack' ] );
- if ( $id == false ) {
- $id = MWCryptRand::generateHex( 32 );
- $dbw = wfGetDB( DB_MASTER );
- $dbw->insert(
- 'updatelog',
- [ 'ul_key' => 'PingBack', 'ul_value' => $id ],
- __METHOD__,
- 'IGNORE'
- );
- if ( !$dbw->affectedRows() ) {
- $id = $dbw->selectField(
- 'updatelog', 'ul_value', [ 'ul_key' => 'PingBack' ] );
- }
- }
- $this->id = $id;
- }
- return $this->id;
- }
- /**
- * Serialize pingback data and send it to MediaWiki.org via a POST
- * to its event beacon endpoint.
- *
- * The data encoding conforms to the expectations of EventLogging,
- * a software suite used by the Wikimedia Foundation for logging and
- * processing analytic data.
- *
- * Compare:
- * <https://github.com/wikimedia/mediawiki-extensions-EventLogging/
- * blob/7e5fe4f1ef/includes/EventLogging.php#L32-L74>
- *
- * @param array $data Pingback data as an associative array
- * @return bool true on success, false on failure
- */
- private function postPingback( array $data ) {
- $json = FormatJson::encode( $data );
- $queryString = rawurlencode( str_replace( ' ', '\u0020', $json ) ) . ';';
- $url = 'https://www.mediawiki.org/beacon/event?' . $queryString;
- return Http::post( $url ) !== false;
- }
- /**
- * Send information about this MediaWiki instance to MediaWiki.org.
- *
- * The data is structured and serialized to match the expectations of
- * EventLogging, a software suite used by the Wikimedia Foundation for
- * logging and processing analytic data.
- *
- * Compare:
- * <https://github.com/wikimedia/mediawiki-extensions-EventLogging/
- * blob/7e5fe4f1ef/includes/EventLogging.php#L32-L74>
- *
- * The schema for the data is located at:
- * <https://meta.wikimedia.org/wiki/Schema:MediaWikiPingback>
- * @return bool
- */
- public function sendPingback() {
- if ( !$this->acquireLock() ) {
- $this->logger->debug( __METHOD__ . ": couldn't acquire lock" );
- return false;
- }
- $data = $this->getData();
- if ( !$this->postPingback( $data ) ) {
- $this->logger->warning( __METHOD__ . ": failed to send pingback; check 'http' log" );
- return false;
- }
- $this->markSent();
- $this->logger->debug( __METHOD__ . ": pingback sent OK ({$this->key})" );
- return true;
- }
- /**
- * Schedule a deferred callable that will check if a pingback should be
- * sent and (if so) proceed to send it.
- */
- public static function schedulePingback() {
- DeferredUpdates::addCallableUpdate( function () {
- $instance = new Pingback;
- if ( $instance->shouldSend() ) {
- $instance->sendPingback();
- }
- } );
- }
- }
|