Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.78% covered (warning)
77.78%
21 / 27
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
SectionTitleFetcher
77.78% covered (warning)
77.78%
21 / 27
50.00% covered (danger)
50.00%
1 / 2
11.10
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fetchSectionTitles
76.92% covered (warning)
76.92%
20 / 26
0.00% covered (danger)
0.00%
0 / 1
10.00
1<?php
2
3declare( strict_types=1 );
4
5namespace ContentTranslation\Service;
6
7use ContentTranslation\SiteMapper;
8use FormatJson;
9use InvalidArgumentException;
10use MediaWiki\Http\HttpRequestFactory;
11use MediaWiki\Title\Title;
12
13class SectionTitleFetcher {
14
15    private HttpRequestFactory $httpRequestFactory;
16
17    public function __construct( HttpRequestFactory $httpRequestFactory ) {
18        $this->httpRequestFactory = $httpRequestFactory;
19    }
20
21    /**
22     * Given a target language code and a page title or a revision id (at least one of them
23     * should be provided), this method fetches the target page sections using the MediaWiki
24     * Action Api and returns an array containing the titles for all the first-level page sections,
25     * indexed by their section numbers.
26     *
27     * If the HTTP request for fetching the section titles fails, the method returns an
28     * empty array.
29     *
30     * If the sections cannot be successfully retrieved from the API (i.e. when the request is
31     * completed but the requested page doesn't exist), the method returns null.
32     *
33     * If both page title and page revision are provided, the page revision is ignored and only
34     * the page title is used.
35     *
36     * @param string $targetLanguage
37     * @param Title|null $pageTitle The title of the page
38     * @param int|null $revision
39     * @return string[]|null e.g. [ 1 => "Section 1", 2 => "Section 2" ]
40     */
41    public function fetchSectionTitles( string $targetLanguage, ?Title $pageTitle, ?int $revision = null ): ?array {
42        if ( !$pageTitle && !$revision ) {
43            throw new InvalidArgumentException( 'Either page title or page revision should be provided' );
44        }
45
46        $params = [
47            'action' => 'parse',
48            'prop' => 'sections',
49            'format' => 'json',
50            'formatversion' => 2
51        ];
52
53        if ( $pageTitle ) {
54            $params['page'] = $pageTitle->getPrefixedDBKey();
55        } else {
56            $params['oldid'] = $revision;
57        }
58        // Example URLs:
59        // https://en.wikipedia.org/w/api.php?action=parse&format=json&prop=sections&formatversion=2&page=Football
60        // https://en.wikipedia.org/w/api.php?action=parse&format=json&prop=sections&formatversion=2&oldid=1161269327
61        $url = SiteMapper::getApiURL( $targetLanguage, $params );
62
63        try {
64            $response = $this->httpRequestFactory->get( $url );
65        } catch ( \Exception $exception ) {
66            return [];
67        }
68
69        if ( !$response ) {
70            return null;
71        }
72        $json = FormatJson::decode( $response, true );
73
74        // if an invalid title is provided the response is a json containing an error
75        // (e.g. { error: { code "missingtitle" } }
76        // successful responses are json objects with "parse.sections" property set
77        if ( !isset( $json['parse']['sections'] ) ) {
78            return null;
79        }
80
81        $sections = $json['parse']['sections'];
82
83        $out = [];
84        foreach ( $sections as $section ) {
85            if ( $section['toclevel'] === 1 ) {
86                $out[(int)$section['index']] = $section['line'];
87            }
88        }
89
90        return $out;
91    }
92}