Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
76.47% covered (warning)
76.47%
39 / 51
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
RenderDebugInfo
76.47% covered (warning)
76.47%
39 / 51
75.00% covered (warning)
75.00%
3 / 4
23.70
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 shouldRun
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 transformText
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 debugInfo
73.91% covered (warning)
73.91%
34 / 46
0.00% covered (danger)
0.00%
0 / 1
20.54
1<?php
2
3namespace MediaWiki\OutputTransform\Stages;
4
5use MediaWiki\Config\ServiceOptions;
6use MediaWiki\HookContainer\HookContainer;
7use MediaWiki\HookContainer\HookRunner;
8use MediaWiki\Language\RawMessage;
9use MediaWiki\Message\Message;
10use MediaWiki\OutputTransform\ContentTextTransformStage;
11use MediaWiki\Parser\ParserOutput;
12use ParserOptions;
13use Psr\Log\LoggerInterface;
14
15/**
16 * Adds debug info to the output
17 * @internal
18 */
19class RenderDebugInfo extends ContentTextTransformStage {
20
21    private HookRunner $hookRunner;
22
23    public function __construct(
24        ServiceOptions $options, LoggerInterface $logger, HookContainer $hookContainer
25    ) {
26        parent::__construct( $options, $logger );
27        $this->hookRunner = new HookRunner( $hookContainer );
28    }
29
30    public function shouldRun( ParserOutput $po, ?ParserOptions $popts, array $options = [] ): bool {
31        return $options['includeDebugInfo'] ?? false;
32    }
33
34    protected function transformText( string $text, ParserOutput $po, ?ParserOptions $popts, array &$options ): string {
35        $debugInfo = $this->debugInfo( $po );
36        return $text . $debugInfo;
37    }
38
39    private function debugInfo( ParserOutput $po ): string {
40        $text = '';
41
42        $limitReportData = $po->getLimitReportData();
43        // If nothing set it, we can't get it.
44        if ( $limitReportData ) {
45            $limitReport = "NewPP limit report\n";
46
47            if ( array_key_exists( 'cachereport-origin', $limitReportData ) ) {
48                $limitReport .= "Parsed by {$limitReportData['cachereport-origin']}\n";
49            }
50
51            if ( array_key_exists( 'cachereport-timestamp', $limitReportData ) ) {
52                $limitReport .= "Cached time: {$limitReportData['cachereport-timestamp']}\n";
53            }
54
55            if ( array_key_exists( 'cachereport-ttl', $limitReportData ) ) {
56                $limitReport .= "Cache expiry: {$limitReportData['cachereport-ttl']}\n";
57            }
58
59            if ( array_key_exists( 'cachereport-transientcontent', $limitReportData ) ) {
60                $transient = $limitReportData['cachereport-transientcontent'] ? 'true' : 'false';
61                $limitReport .= "Reduced expiry: $transient\n";
62            }
63
64            // TODO: flags should go into limit report too.
65            $limitReport .= 'Complications: [' . implode( ', ', $po->getAllFlags() ) . "]\n";
66
67            foreach ( $limitReportData as $key => $value ) {
68                if ( in_array( $key, [
69                    'cachereport-origin',
70                    'cachereport-timestamp',
71                    'cachereport-ttl',
72                    'cachereport-transientcontent',
73                    'limitreport-timingprofile',
74                ] ) ) {
75                    // These keys are processed separately.
76                    continue;
77                }
78
79                // TODO inject MessageFormatter instead of Message::newFromSpecifier
80                if ( $this->hookRunner->onParserLimitReportFormat(
81                    $key, $value, $limitReport, false, false )
82                ) {
83                    $keyMsg = Message::newFromSpecifier( $key )->inLanguage( 'en' )->useDatabase( false );
84                    $valueMsg = Message::newFallbackSequence( [ "$key-value-text", "$key-value" ] )
85                        ->inLanguage( 'en' )->useDatabase( false );
86                    if ( !$valueMsg->exists() ) {
87                        $valueMsg = new RawMessage( '$1' );
88                    }
89                    if ( !$keyMsg->isDisabled() && !$valueMsg->isDisabled() ) {
90                        $valueMsg->params( $value );
91                        $limitReport .= "{$keyMsg->text()}{$valueMsg->text()}\n";
92                    }
93                }
94            }
95            // Since we're not really outputting HTML, decode the entities and
96            // then re-encode the things that need hiding inside HTML comments.
97            $limitReport = htmlspecialchars_decode( $limitReport );
98
99            // Sanitize for comment. Note '‐' in the replacement is U+2010,
100            // which looks much like the problematic '-'.
101            $limitReport = str_replace( [ '-', '&' ], [ '‐', '&amp;' ], $limitReport );
102            $text = "\n<!-- \n$limitReport-->\n";
103
104            $profileReport = $limitReportData['limitreport-timingprofile'] ?? null;
105            if ( $profileReport ) {
106                $text .= "<!--\nTransclusion expansion time report (%,ms,calls,template)\n";
107                $text .= implode( "\n", $profileReport ) . "\n-->\n";
108            }
109        }
110
111        if ( $po->getCacheMessage() ) {
112            $text .= "\n<!-- " . $po->getCacheMessage() . "\n -->\n";
113        }
114
115        $parsoidVersion = $po->getExtensionData( 'core:parsoid-version' );
116        if ( $parsoidVersion ) {
117            $text .= "\n<!--Parsoid $parsoidVersion-->\n";
118        }
119
120        return $text;
121    }
122}