Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 114
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQueryLangLinks
0.00% covered (danger)
0.00%
0 / 113
0.00% covered (danger)
0.00%
0 / 6
552
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 1
342
 getCacheMode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getHelpUrls
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace MediaWiki\Api;
10
11use MediaWiki\Deferred\LinksUpdate\LangLinksTable;
12use MediaWiki\Language\Language;
13use MediaWiki\Language\LanguageNameUtils;
14use MediaWiki\MainConfigNames;
15use MediaWiki\Title\Title;
16use MediaWiki\Utils\UrlUtils;
17use Wikimedia\ParamValidator\ParamValidator;
18use Wikimedia\ParamValidator\TypeDef\IntegerDef;
19
20/**
21 * A query module to list all langlinks (links to corresponding foreign language pages).
22 *
23 * @ingroup API
24 */
25class ApiQueryLangLinks extends ApiQueryBase {
26
27    public function __construct(
28        ApiQuery $query,
29        string $moduleName,
30        private readonly LanguageNameUtils $languageNameUtils,
31        private readonly Language $contentLanguage,
32        private readonly UrlUtils $urlUtils,
33    ) {
34        parent::__construct( $query, $moduleName, 'll' );
35    }
36
37    public function execute() {
38        $pages = $this->getPageSet()->getGoodPages();
39        if ( $pages === [] ) {
40            return;
41        }
42
43        $params = $this->extractRequestParams();
44        $prop = array_fill_keys( (array)$params['prop'], true );
45
46        if ( isset( $params['title'] ) && !isset( $params['lang'] ) ) {
47            $this->dieWithError(
48                [
49                    'apierror-invalidparammix-mustusewith',
50                    $this->encodeParamName( 'title' ),
51                    $this->encodeParamName( 'lang' ),
52                ],
53                'invalidparammix'
54            );
55        }
56
57        // Handle deprecated param
58        $this->requireMaxOneParameter( $params, 'url', 'prop' );
59        if ( $params['url'] ) {
60            $prop = [ 'url' => 1 ];
61        }
62
63        $this->addFields( [
64            'll_from',
65            'll_lang',
66            'll_title'
67        ] );
68
69        $this->addTables( 'langlinks' );
70        $this->addWhereFld( 'll_from', array_keys( $pages ) );
71        if ( $params['continue'] !== null ) {
72            $db = $this->getDB();
73            $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'int', 'string' ] );
74            $op = $params['dir'] == 'descending' ? '<=' : '>=';
75            $this->addWhere( $db->buildComparison( $op, [
76                'll_from' => $cont[0],
77                'll_lang' => $cont[1],
78            ] ) );
79        }
80
81        // FIXME: (follow-up) To allow extensions to add to the language links, we need
82        //       to load them all, add the extra links, then apply paging.
83        //       Should not be terrible, it's not going to be more than a few hundred links.
84
85        // Note that, since (ll_from, ll_lang) is a unique key, we don't need
86        // to sort by ll_title to ensure deterministic ordering.
87        $sort = ( $params['dir'] == 'descending' ? ' DESC' : '' );
88        if ( isset( $params['lang'] ) ) {
89            $this->addWhereFld( 'll_lang', $params['lang'] );
90            if ( isset( $params['title'] ) ) {
91                $this->addWhereFld( 'll_title', $params['title'] );
92            }
93            $this->addOption( 'ORDER BY', 'll_from' . $sort );
94        } else {
95            // Don't order by ll_from if it's constant in the WHERE clause
96            if ( count( $pages ) === 1 ) {
97                $this->addOption( 'ORDER BY', 'll_lang' . $sort );
98            } else {
99                $this->addOption( 'ORDER BY', [
100                    'll_from' . $sort,
101                    'll_lang' . $sort
102                ] );
103            }
104        }
105
106        $this->addOption( 'LIMIT', $params['limit'] + 1 );
107
108        $this->setVirtualDomain( LangLinksTable::VIRTUAL_DOMAIN );
109        $res = $this->select( __METHOD__ );
110
111        $count = 0;
112        foreach ( $res as $row ) {
113            if ( ++$count > $params['limit'] ) {
114                // We've reached the one extra which shows that
115                // there are additional pages to be had. Stop here...
116                $this->setContinueEnumParameter( 'continue', "{$row->ll_from}|{$row->ll_lang}" );
117                break;
118            }
119
120            $languageNameMap = $this->getConfig()->get( MainConfigNames::InterlanguageLinkCodeMap );
121            $displayLanguageCode = $languageNameMap[ $row->ll_lang ] ?? $row->ll_lang;
122
123            // This is potentially risky and confusing (request `no`, but get `nb` in the result).
124            $entry = [ 'lang' => $displayLanguageCode ];
125            if ( isset( $prop['url'] ) ) {
126                $title = Title::newFromText( "{$row->ll_lang}:{$row->ll_title}" );
127                if ( $title ) {
128                    $entry['url'] = (string)$this->urlUtils->expand( $title->getFullURL(), PROTO_CURRENT );
129                }
130            }
131
132            if ( isset( $prop['langname'] ) ) {
133                $entry['langname'] = $this->languageNameUtils
134                    ->getLanguageName( $displayLanguageCode, $params['inlanguagecode'] );
135            }
136            if ( isset( $prop['autonym'] ) ) {
137                $entry['autonym'] = $this->languageNameUtils->getLanguageName( $displayLanguageCode );
138            }
139            ApiResult::setContentValue( $entry, 'title', $row->ll_title );
140            $fit = $this->addPageSubItem( $row->ll_from, $entry );
141            if ( !$fit ) {
142                $this->setContinueEnumParameter( 'continue', "{$row->ll_from}|{$row->ll_lang}" );
143                break;
144            }
145        }
146    }
147
148    /** @inheritDoc */
149    public function getCacheMode( $params ) {
150        return 'public';
151    }
152
153    /** @inheritDoc */
154    public function getAllowedParams() {
155        return [
156            'prop' => [
157                ParamValidator::PARAM_ISMULTI => true,
158                ParamValidator::PARAM_TYPE => [
159                    'url',
160                    'langname',
161                    'autonym',
162                ],
163                ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
164            ],
165            'lang' => null,
166            'title' => null,
167            'dir' => [
168                ParamValidator::PARAM_DEFAULT => 'ascending',
169                ParamValidator::PARAM_TYPE => [
170                    'ascending',
171                    'descending'
172                ]
173            ],
174            'inlanguagecode' => $this->contentLanguage->getCode(),
175            'limit' => [
176                ParamValidator::PARAM_DEFAULT => 10,
177                ParamValidator::PARAM_TYPE => 'limit',
178                IntegerDef::PARAM_MIN => 1,
179                IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
180                IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
181            ],
182            'continue' => [
183                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
184            ],
185            'url' => [
186                ParamValidator::PARAM_DEFAULT => false,
187                ParamValidator::PARAM_DEPRECATED => true,
188            ],
189        ];
190    }
191
192    /** @inheritDoc */
193    protected function getExamplesMessages() {
194        $title = Title::newMainPage()->getPrefixedText();
195        $mp = rawurlencode( $title );
196
197        return [
198            "action=query&prop=langlinks&titles={$mp}&redirects="
199                => 'apihelp-query+langlinks-example-simple',
200        ];
201    }
202
203    /** @inheritDoc */
204    public function getHelpUrls() {
205        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Langlinks';
206    }
207}
208
209/** @deprecated class alias since 1.43 */
210class_alias( ApiQueryLangLinks::class, 'ApiQueryLangLinks' );