Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
XAnalytics
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 5
72
0.00% covered (danger)
0.00%
0 / 1
 onBeforePageDisplay
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 generateHeader
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 createHeader
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 onAPIAfterExecute
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 addItem
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace MediaWiki\Extension\XAnalytics;
4
5use MediaWiki\Api\ApiBase;
6use MediaWiki\Api\Hook\APIAfterExecuteHook;
7use MediaWiki\Extension\XAnalytics\Hooks\HookRunner;
8use MediaWiki\MediaWikiServices;
9use MediaWiki\Output\Hook\BeforePageDisplayHook;
10use MediaWiki\Output\OutputPage;
11use MediaWiki\Request\WebResponse;
12use Skin;
13
14class XAnalytics implements
15    BeforePageDisplayHook,
16    APIAfterExecuteHook
17{
18
19    /**
20     * Whether the header has already been added
21     *
22     * @var bool
23     */
24    private static $addedHeader = false;
25
26    /**
27     * Set X-Analytics header before the output buffer is flushed.
28     *
29     * The PHP output buffer is flushed from multiple places in the MediaWiki
30     * codebase (and the codebase of MediaWiki extensions), making it difficult to
31     * ensure that the header is reliably injected into every response generated by
32     * MediaWiki. This should be fixed. The output buffer of normal page view
33     * responses is done in one place, however, so for that use-case, the code is
34     * reliable.
35     *
36     * X-Analytics items can be declared by hooking into 'XAnalyticsSetHeader' or
37     * by calling XAnalytics;:addItem().
38     *
39     * @see https://wikitech.wikimedia.org/wiki/X-Analytics
40     * @param OutputPage $out
41     * @param Skin $skin
42     */
43    public function onBeforePageDisplay( $out, $skin ): void {
44        self::generateHeader( $out );
45    }
46
47    /**
48     * Runs the XAnalyticsSetHeader hook and adds the header if necessary
49     * @param OutputPage $out
50     */
51    private static function generateHeader( OutputPage $out ) {
52        if ( self::$addedHeader === true ) {
53            // Only run once for API requests that use OutputPage
54            return;
55        }
56        self::$addedHeader = true;
57        $response = $out->getRequest()->response();
58        $headerItems = [];
59        ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
60            ->onXAnalyticsSetHeader( $out, $headerItems );
61        if ( count( $headerItems ) ) {
62            self::createHeader( $response, $headerItems );
63        }
64    }
65
66    /**
67     * Checks to see if the X-Analytics header is already set, and add
68     * the new items to the header and set it
69     *
70     * @param WebResponse $response
71     * @param array $newItems
72     */
73    private static function createHeader( WebResponse $response, array $newItems ) {
74        $currentHeader = $response->getHeader( 'X-Analytics' ) ?? '';
75        parse_str( preg_replace( '/; */', '&', $currentHeader ), $headerItems );
76        $headerItems = array_merge( $headerItems, $newItems );
77
78        $headerValue = http_build_query( $headerItems, '', ';' );
79        $response->header( 'X-Analytics: ' . $headerValue, true );
80    }
81
82    /**
83     * @param ApiBase $module
84     */
85    public function onAPIAfterExecute( $module ) {
86        self::generateHeader( $module->getOutput() );
87    }
88
89    /**
90     * Add an item to the X-Analytics header that will be output
91     * @param string $name
92     * @param string $value
93     */
94    public static function addItem( $name, $value ) {
95        if ( self::$addedHeader ) {
96            // If the header is already set, we need to append to it and replace it
97            global $wgRequest;
98            self::createHeader( $wgRequest->response(), [ $name => $value ] );
99        }
100    }
101}