]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/Pingback.php
MediaWiki 1.30.2-scripts2
[autoinstalls/mediawiki.git] / includes / Pingback.php
1 <?php
2 /**
3  * Send information about this MediaWiki instance to MediaWiki.org.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  * @file
21  */
22
23 use Psr\Log\LoggerInterface;
24 use MediaWiki\Logger\LoggerFactory;
25
26 /**
27  * Send information about this MediaWiki instance to MediaWiki.org.
28  *
29  * @since 1.28
30  */
31 class Pingback {
32
33         /**
34          * @var int Revision ID of the JSON schema that describes the pingback
35          *   payload. The schema lives on MetaWiki, at
36          *   <https://meta.wikimedia.org/wiki/Schema:MediaWikiPingback>.
37          */
38         const SCHEMA_REV = 15781718;
39
40         /** @var LoggerInterface */
41         protected $logger;
42
43         /** @var Config */
44         protected $config;
45
46         /** @var string updatelog key (also used as cache/db lock key) */
47         protected $key;
48
49         /** @var string Randomly-generated identifier for this wiki */
50         protected $id;
51
52         /**
53          * @param Config $config
54          * @param LoggerInterface $logger
55          */
56         public function __construct( Config $config = null, LoggerInterface $logger = null ) {
57                 $this->config = $config ?: RequestContext::getMain()->getConfig();
58                 $this->logger = $logger ?: LoggerFactory::getInstance( __CLASS__ );
59                 $this->key = 'Pingback-' . $this->config->get( 'Version' );
60         }
61
62         /**
63          * Should a pingback be sent?
64          * @return bool
65          */
66         private function shouldSend() {
67                 return $this->config->get( 'Pingback' ) && !$this->checkIfSent();
68         }
69
70         /**
71          * Has a pingback already been sent for this MediaWiki version?
72          * @return bool
73          */
74         private function checkIfSent() {
75                 $dbr = wfGetDB( DB_REPLICA );
76                 $sent = $dbr->selectField(
77                         'updatelog', '1', [ 'ul_key' => $this->key ], __METHOD__ );
78                 return $sent !== false;
79         }
80
81         /**
82          * Record the fact that we have sent a pingback for this MediaWiki version,
83          * to ensure we don't submit data multiple times.
84          */
85         private function markSent() {
86                 $dbw = wfGetDB( DB_MASTER );
87                 return $dbw->insert(
88                         'updatelog', [ 'ul_key' => $this->key ], __METHOD__, 'IGNORE' );
89         }
90
91         /**
92          * Acquire lock for sending a pingback
93          *
94          * This ensures only one thread can attempt to send a pingback at any given
95          * time and that we wait an hour before retrying failed attempts.
96          *
97          * @return bool Whether lock was acquired
98          */
99         private function acquireLock() {
100                 $cache = ObjectCache::getLocalClusterInstance();
101                 if ( !$cache->add( $this->key, 1, 60 * 60 ) ) {
102                         return false;  // throttled
103                 }
104
105                 $dbw = wfGetDB( DB_MASTER );
106                 if ( !$dbw->lock( $this->key, __METHOD__, 0 ) ) {
107                         return false;  // already in progress
108                 }
109
110                 return true;
111         }
112
113         /**
114          * Collect basic data about this MediaWiki installation and return it
115          * as an associative array conforming to the Pingback schema on MetaWiki
116          * (<https://meta.wikimedia.org/wiki/Schema:MediaWikiPingback>).
117          *
118          * This is public so we can display it in the installer
119          *
120          * Developers: If you're adding a new piece of data to this, please ensure
121          * that you update https://www.mediawiki.org/wiki/Manual:$wgPingback
122          *
123          * @return array
124          */
125         public function getSystemInfo() {
126                 $event = [
127                         'database'   => $this->config->get( 'DBtype' ),
128                         'MediaWiki'  => $this->config->get( 'Version' ),
129                         'PHP'        => PHP_VERSION,
130                         'OS'         => PHP_OS . ' ' . php_uname( 'r' ),
131                         'arch'       => PHP_INT_SIZE === 8 ? 64 : 32,
132                         'machine'    => php_uname( 'm' ),
133                 ];
134
135                 if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
136                         $event['serverSoftware'] = $_SERVER['SERVER_SOFTWARE'];
137                 }
138
139                 $limit = ini_get( 'memory_limit' );
140                 if ( $limit && $limit != -1 ) {
141                         $event['memoryLimit'] = $limit;
142                 }
143
144                 return $event;
145         }
146
147         /**
148          * Get the EventLogging packet to be sent to the server
149          *
150          * @return array
151          */
152         private function getData() {
153                 return [
154                         'schema'           => 'MediaWikiPingback',
155                         'revision'         => self::SCHEMA_REV,
156                         'wiki'             => $this->getOrCreatePingbackId(),
157                         'event'            => $this->getSystemInfo(),
158                 ];
159         }
160
161         /**
162          * Get a unique, stable identifier for this wiki
163          *
164          * If the identifier does not already exist, create it and save it in the
165          * database. The identifier is randomly-generated.
166          *
167          * @return string 32-character hex string
168          */
169         private function getOrCreatePingbackId() {
170                 if ( !$this->id ) {
171                         $id = wfGetDB( DB_REPLICA )->selectField(
172                                 'updatelog', 'ul_value', [ 'ul_key' => 'PingBack' ] );
173
174                         if ( $id == false ) {
175                                 $id = MWCryptRand::generateHex( 32 );
176                                 $dbw = wfGetDB( DB_MASTER );
177                                 $dbw->insert(
178                                         'updatelog',
179                                         [ 'ul_key' => 'PingBack', 'ul_value' => $id ],
180                                         __METHOD__,
181                                         'IGNORE'
182                                 );
183
184                                 if ( !$dbw->affectedRows() ) {
185                                         $id = $dbw->selectField(
186                                                 'updatelog', 'ul_value', [ 'ul_key' => 'PingBack' ] );
187                                 }
188                         }
189
190                         $this->id = $id;
191                 }
192
193                 return $this->id;
194         }
195
196         /**
197          * Serialize pingback data and send it to MediaWiki.org via a POST
198          * to its event beacon endpoint.
199          *
200          * The data encoding conforms to the expectations of EventLogging,
201          * a software suite used by the Wikimedia Foundation for logging and
202          * processing analytic data.
203          *
204          * Compare:
205          * <https://github.com/wikimedia/mediawiki-extensions-EventLogging/
206          *   blob/7e5fe4f1ef/includes/EventLogging.php#L32-L74>
207          *
208          * @param array $data Pingback data as an associative array
209          * @return bool true on success, false on failure
210          */
211         private function postPingback( array $data ) {
212                 $json = FormatJson::encode( $data );
213                 $queryString = rawurlencode( str_replace( ' ', '\u0020', $json ) ) . ';';
214                 $url = 'https://www.mediawiki.org/beacon/event?' . $queryString;
215                 return Http::post( $url ) !== false;
216         }
217
218         /**
219          * Send information about this MediaWiki instance to MediaWiki.org.
220          *
221          * The data is structured and serialized to match the expectations of
222          * EventLogging, a software suite used by the Wikimedia Foundation for
223          * logging and processing analytic data.
224          *
225          * Compare:
226          * <https://github.com/wikimedia/mediawiki-extensions-EventLogging/
227          *   blob/7e5fe4f1ef/includes/EventLogging.php#L32-L74>
228          *
229          * The schema for the data is located at:
230          * <https://meta.wikimedia.org/wiki/Schema:MediaWikiPingback>
231          * @return bool
232          */
233         public function sendPingback() {
234                 if ( !$this->acquireLock() ) {
235                         $this->logger->debug( __METHOD__ . ": couldn't acquire lock" );
236                         return false;
237                 }
238
239                 $data = $this->getData();
240                 if ( !$this->postPingback( $data ) ) {
241                         $this->logger->warning( __METHOD__ . ": failed to send pingback; check 'http' log" );
242                         return false;
243                 }
244
245                 $this->markSent();
246                 $this->logger->debug( __METHOD__ . ": pingback sent OK ({$this->key})" );
247                 return true;
248         }
249
250         /**
251          * Schedule a deferred callable that will check if a pingback should be
252          * sent and (if so) proceed to send it.
253          */
254         public static function schedulePingback() {
255                 DeferredUpdates::addCallableUpdate( function () {
256                         $instance = new Pingback;
257                         if ( $instance->shouldSend() ) {
258                                 $instance->sendPingback();
259                         }
260                 } );
261         }
262 }