Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 174
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
FormulaInfo
0.00% covered (danger)
0.00%
0 / 174
0.00% covered (danger)
0.00%
0 / 14
1980
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 InfoTex
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 DisplayTranslations
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
20
 GetTranslation
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 PrintTranslationResult
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 DisplayInfo
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 1
156
 printSource
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getlengh
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 formatBytes
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 hasMathMLSupport
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasSvgSupport
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 DisplayRendering
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 1
90
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\Extension\Math\MathConfig;
4use MediaWiki\Extension\Math\MathRenderer;
5use MediaWiki\MediaWikiServices;
6
7/**
8 * MediaWiki MathSearch extension
9 *
10 * (c) 2012 Moritz Schubotz
11 * GPLv2 license; info in main package.
12 *
13 * @file
14 * @ingroup extensions
15 */
16class FormulaInfo extends SpecialPage {
17
18    /** @var bool */
19    private $purge = false;
20    /** @var MathConfig */
21    private $mathConfig;
22
23    function __construct(
24        MathConfig $mathConfig
25    ) {
26        parent::__construct( 'FormulaInfo' );
27        $this->mathConfig = $mathConfig;
28    }
29
30    /**
31     * @param string|null $par
32     */
33    function execute( $par ) {
34        $pid = $this->getRequest()->getVal( 'pid' ); // Page ID
35        $eid = $this->getRequest()->getVal( 'eid' ); // Equation ID
36        $this->purge = $this->getRequest()->getVal( 'purge', false );
37        if ( $pid === null || $eid === null ) {
38            $tex = $this->getRequest()->getVal( 'tex', '' );
39            if ( $tex == '' ) {
40                $this->getOutput()->addHTML( '<b>Please specify page and equation id</b>' );
41            } else {
42                $this->InfoTex( $tex );
43            }
44        } else {
45            $this->DisplayInfo( $pid, $eid );
46        }
47    }
48
49    public function InfoTex( $tex ) {
50        if ( !$this->getConfig()->get( 'MathDebug' ) ) {
51            $this->getOutput()->addWikiTextAsInterface( "tex queries only supported in debug mode" );
52            return false;
53        }
54        $this->getOutput()->addWikiTextAsInterface( "Info for <code>" . $tex . '</code>' );
55
56        $mo = new MathObject( $tex );
57        $allPages = $mo->getAllOccurrences();
58        if ( $allPages ) {
59            $this->DisplayInfo( $allPages[0]->getRevisionID(), $allPages[0]->getAnchorID() );
60        } else {
61            $this->getOutput()->addWikiTextAsInterface(
62                "No occurrences found clean up the database to remove unused formulae"
63            );
64        }
65
66        self::DisplayTranslations( $tex );
67    }
68
69    /**
70     * @param string $tex
71     * @return bool
72     */
73    public static function DisplayTranslations( $tex ) {
74        global $wgOut, $wgMathSearchTranslationUrl;
75
76        if ( $wgMathSearchTranslationUrl === false ) {
77            return false;
78        }
79
80        $resultMaple = self::GetTranslation( 'Maple', $tex );
81        $resultMathe = self::GetTranslation( 'Mathematica', $tex );
82
83        $wgOut->addWikiTextAsInterface( '==Translations to Computer Algebra Systems==' );
84
85        if ( $resultMaple === false || $resultMathe === false ) {
86            $wgOut->addWikiTextAsInterface( 'An error occurred during translation.' );
87            return false;
88        }
89
90        self::PrintTranslationResult( 'Maple', $resultMaple );
91        self::PrintTranslationResult( 'Mathematica', $resultMathe );
92        return true;
93    }
94
95    private static function GetTranslation( $cas, $tex ) {
96        global $wgMathSearchTranslationUrl;
97        $params = [ 'cas' => $cas, 'latex' => $tex ];
98        return MediaWikiServices::getInstance()->getHttpRequestFactory()->post(
99            $wgMathSearchTranslationUrl, [ "postData" => $params, "timeout" => 60 ]
100        );
101    }
102
103    private static function PrintTranslationResult( $cas, $result ) {
104        global $wgOut;
105
106        $jsonResult = json_decode( $result, true );
107        $wgOut->addWikiTextAsInterface( '=== Translation to ' . $cas . '===' );
108
109        $wgOut->addHTML(
110            '<div class="toccolours mw-collapsible mw-collapsed"  style="text-align: left">'
111        );
112        $wgOut->addWikiTextAsInterface( 'In ' . $cas . ': <code>' . $jsonResult['result'] . '</code>' );
113
114        $wgOut->addHTML( '<div class="mw-collapsible-content">' );
115        $wgOut->addWikiTextAsInterface( str_replace( "\n", "\n\n", $jsonResult['log'] ) );
116        $wgOut->addHTML( '</div></div>' );
117    }
118
119    public function DisplayInfo( int $oldID, string $eid ): bool {
120        $out = $this->getOutput();
121        $out->addModuleStyles( [ 'ext.mathsearch.styles' ] );
122        $out->addWikiTextAsInterface( '==General==' );
123        $out->addWikiTextAsInterface(
124            'Display information for equation id:' . $eid . ' on revision:' . $oldID
125        );
126        $revisionRecord = MediaWikiServices::getInstance()
127            ->getRevisionLookup()
128            ->getRevisionById( $oldID );
129        if ( !$revisionRecord ) {
130            $out->addWikiTextAsInterface( 'There is no revision with id:' . $oldID . ' in the database.' );
131            return false;
132        }
133
134        $title = Title::newFromLinkTarget( $revisionRecord->getPageAsLinkTarget() );
135        $pageName = (string)$title;
136        $out->addWikiTextAsInterface( "* Page found: [[$pageName#$eid|$pageName]] (eq $eid)  ", false );
137        $link = $title->getLinkURL( [
138            'action' => 'purge',
139            'mathpurge' => 'true'
140        ] );
141        $out->addHTML( "<a href=\"$link\">(force rerendering)</a>" );
142        $mo = MathObject::constructformpage( $oldID, $eid );
143        if ( !$mo ) {
144            $out->addWikiTextAsInterface( 'Cannot find the equation data in the database.' .
145                ' Fetching from revision text.' );
146            $mo = MathObject::newFromRevisionText( $oldID, $eid );
147        }
148        $out->addWikiTextAsInterface( "Occurrences on the following pages:" );
149        $all = $mo->getAllOccurrences();
150        foreach ( $all as $occ ) {
151            $out->addWikiTextAsInterface( '*' . $occ->printLink2Page( false ) );
152        }
153        $out->addWikiTextAsInterface( 'Hash: ' . $mo->getInputHash() );
154        $this->printSource( $mo->getUserInputTex(), 'TeX (original user input)', 'latex' );
155        $texInfo = $mo->getTexInfo();
156        if ( $texInfo ) {
157            $this->printSource( $texInfo->getChecked(), 'TeX (checked)', 'latex' );
158        }
159        $this->DisplayRendering( $mo->getUserInputTex(), 'latexml' );
160        $this->DisplayRendering( $mo->getUserInputTex(), 'mathml' );
161        $this->DisplayRendering( $mo->getUserInputTex(), 'native' );
162
163        self::DisplayTranslations( $mo->getUserInputTex() );
164
165        $out->addWikiTextAsInterface( '==Similar pages==' );
166        $out->addWikiTextAsInterface(
167            'Calculated based on the variables occurring on the entire ' . $pageName . ' page'
168        );
169        $pid = $title->getArticleID();
170        MathObject::findSimilarPages( $pid );
171        $out->addWikiTextAsInterface( '==Identifiers==' );
172        $relations = $mo->getRelations();
173        if ( $texInfo ) {
174            foreach ( $texInfo->getIdentifiers() as $x ) {
175                $line = '* <math>' . $x . '</math>';
176                if ( isset( $relations[$x] ) ) {
177                    foreach ( $relations[$x] as $r ) {
178                        $line .= "{$r->definition} ($r->score)";
179                    }
180                }
181                $out->addWikiTextAsInterface( $line );
182            }
183        }
184        $out->addWikiTextAsInterface( '=== MathML observations ===' );
185        $mo->getObservations();
186        if ( $this->getConfig()->get( 'MathDebug' ) ) {
187            $out->addWikiTextAsInterface( '==LOG and Debug==' );
188            $this->printSource( $mo->getTimestamp(), 'Rendered at', 'text', false );
189            $this->printSource( $mo->getIndexTimestamp(), 'and indexed at', 'text', false );
190            $is_valid = $mo->getMathml() && $mo->isValidMathML( $mo->getMathml() );
191            $this->printSource( $is_valid, 'validxml', 'text', false );
192            $out->addHTML( $is_valid ? "valid" : "invalid" );
193            $this->printSource( $mo->getStatusCode(), 'status' );
194            $out->addHTML( htmlspecialchars( $mo->getLog() ) );
195        }
196        return true;
197    }
198
199    private function printSource( $source, $description = "", $language = "text", $linestart = true ) {
200        if ( $description ) {
201            $description .= ": ";
202        }
203        $this->getOutput()->addWikiTextAsInterface( "$description<syntaxhighlight lang=\"$language\">" .
204            $source . '</syntaxhighlight>', $linestart );
205    }
206
207    private static function getlengh( $binray ) {
208        $uncompressed = strlen( $binray );
209        $compressed = strlen( gzcompress( $binray ) );
210        return self::formatBytes( $uncompressed ) . " / " . self::formatBytes( $compressed );
211    }
212
213    private static function formatBytes( $bytes, $precision = 3 ) {
214        $units = [ 'B', 'KB', 'MB', 'GB', 'TB' ];
215
216        $bytes = max( $bytes, 0 );
217        $pow = floor( ( $bytes ? log( $bytes ) : 0 ) / log( 1024 ) );
218        $pow = min( $pow, count( $units ) - 1 );
219
220        // Uncomment one of the following alternatives
221        $bytes /= pow( 1024, $pow );
222        // $bytes /= (1 << (10 * $pow));
223
224        return round( $bytes, $precision ) . ' ' . $units[$pow];
225    }
226
227    public static function hasMathMLSupport( $mode ) {
228        return in_array( $mode, [ 'latexml', 'mathml', 'native' ] );
229    }
230
231    public static function hasSvgSupport( $mode ) {
232        return ( $mode === 'latexml' || $mode === 'mathml' );
233    }
234
235    private function DisplayRendering( string $tex, string $mode ) {
236        if ( !in_array( $mode, $this->getConfig()->get( 'MathValidModes' ) ) ) {
237            return;
238        }
239        $out = $this->getOutput();
240        $names = $this->mathConfig->getValidRenderingModeNames();
241        $name = $names[$mode];
242        $out->addWikiTextAsInterface( "=== $name rendering === " );
243        $renderer = MathRenderer::getRenderer( $tex, [], $mode );
244        if ( $this->purge ) {
245            $renderer->render( true );
246        } elseif ( $mode == 'mathml' || !$renderer->isInDatabase() ) {
247            // workaround for restbase mathml mode that does not support database access
248            // $out->addWikiTextAsInterface( "No database entry. Start rendering" );
249            $renderer->render();
250        }
251        if ( self::hasMathMLSupport( $mode ) ) {
252            $out->addHTML(
253                '<div class="toccolours mw-collapsible mw-collapsed"  style="text-align: left">'
254            );
255            $out->addWikiTextAsInterface(
256                'MathML (' . self::getlengh( $renderer->getMathml() ) . ') :', false
257            );
258            $imgUrl = $this->getConfig()->get( 'ExtensionAssetsPath' ) .
259                '/MathSearch/resources/images/math_search_logo.png';
260            $mathSearchImg = Html::element(
261                'img', [ 'src' => $imgUrl, 'width' => 15, 'height' => 15 ]
262            );
263            $out->addHTML( '<a href="/wiki/Special:MathSearch?mathpattern=' . urlencode( $tex ) .
264                '&searchx=Search">' . $mathSearchImg . '</a>' );
265            $out->addHTML( $renderer->getMathml() );
266            $out->addHTML( '<div class="mw-collapsible-content">' );
267            $out->addWikiTextAsInterface(
268                '<syntaxhighlight lang="xml">' . ( $renderer->getMathml() ) . '</syntaxhighlight>'
269            );
270            $out->addHTML( '</div></div>' );
271        }
272        if ( self::hasSvgSupport( $mode ) ) {
273            try {
274                $svg = $renderer->getSvg( 'cached' );
275                if ( $svg === '' ) {
276                    $out->addWikiTextAsInterface( 'SVG image empty. Force Re-Rendering' );
277                    $renderer->render( true );
278                    $svg = $renderer->getSvg( 'render' );
279                }
280                $out->addWikiTextAsInterface( 'SVG (' . self::getlengh( $svg ) . ') :', false );
281                $out->addHTML( $svg ); // FALSE, 'mwe-math-demo' ) );
282                $out->addHTML( "<br />\n" );
283            } catch ( Exception $e ) {
284                $out->addHTML( 'Failed to get svg.' );
285            }
286        }
287        $renderer->writeCache();
288    }
289
290    protected function getGroupName() {
291        return 'mathsearch';
292    }
293}