Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 119
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
QueryCoordinates
0.00% covered (danger)
0.00%
0 / 119
0.00% covered (danger)
0.00%
0 / 7
812
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 / 47
0.00% covered (danger)
0.00%
0 / 1
182
 getFromCoord
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
110
 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 / 33
0.00% covered (danger)
0.00%
0 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 4
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
3namespace GeoData\Api;
4
5use ApiBase;
6use ApiQuery;
7use ApiQueryBase;
8use GeoData\Coord;
9use GeoData\GeoData;
10use GeoData\Globe;
11use GeoData\Math;
12use MediaWiki\Page\WikiPageFactory;
13use MediaWiki\Title\Title;
14use Wikimedia\ParamValidator\ParamValidator;
15use Wikimedia\ParamValidator\TypeDef\IntegerDef;
16
17/**
18 * This query adds an <coordinates> subelement to all pages with the list of coordinated
19 * present on those pages.
20 */
21class QueryCoordinates extends ApiQueryBase {
22
23    /** @var WikiPageFactory */
24    private $wikiPageFactory;
25
26    /**
27     * @param ApiQuery $query
28     * @param string $moduleName
29     * @param WikiPageFactory $wikiPageFactory
30     */
31    public function __construct( ApiQuery $query, $moduleName, WikiPageFactory $wikiPageFactory ) {
32        parent::__construct( $query, $moduleName, 'co' );
33        $this->wikiPageFactory = $wikiPageFactory;
34    }
35
36    public function execute(): void {
37        $titles = $this->getPageSet()->getGoodTitles();
38        if ( $titles === [] ) {
39            return;
40        }
41
42        $params = $this->extractRequestParams();
43        $from = $this->getFromCoord( $params );
44        $this->addTables( 'geo_tags' );
45        $this->addFields( [ 'gt_id', 'gt_page_id', 'gt_lat', 'gt_lon', 'gt_primary', 'gt_globe' ] );
46        foreach ( $params['prop'] as $prop ) {
47            if ( isset( Coord::FIELD_MAPPING[$prop] ) ) {
48                $this->addFields( Coord::FIELD_MAPPING[$prop] );
49            }
50        }
51        $this->addWhereFld( 'gt_page_id', array_keys( $titles ) );
52        $primary = $params['primary'];
53        $this->addWhereIf(
54            [ 'gt_primary' => intval( $primary === 'primary' ) ], $primary !== 'all'
55        );
56
57        if ( isset( $params['continue'] ) ) {
58            $parts = $this->parseContinueParamOrDie( $params['continue'], [ 'int', 'int' ] );
59            $this->addWhere( $this->getDB()->buildComparison( '>=', [
60                'gt_page_id' => $parts[0],
61                'gt_id' => $parts[1],
62            ] ) );
63        } else {
64            $this->addOption( 'USE INDEX', 'gt_page_id' );
65        }
66
67        $this->addOption( 'ORDER BY', [ 'gt_page_id', 'gt_id' ] );
68        $this->addOption( 'LIMIT', $params['limit'] + 1 );
69
70        $res = $this->select( __METHOD__ );
71
72        $count = 0;
73        foreach ( $res as $row ) {
74            if ( ++$count > $params['limit'] ) {
75                $this->setContinueEnumParameter( 'continue', $row->gt_page_id . '|' . $row->gt_id );
76                break;
77            }
78            $vals = [
79                'lat' => floatval( $row->gt_lat ),
80                'lon' => floatval( $row->gt_lon ),
81                'primary' => boolval( $row->gt_primary ),
82            ];
83            foreach ( $params['prop'] as $prop ) {
84                $column = Coord::FIELD_MAPPING[$prop] ?? null;
85                if ( $column && isset( $row->$column ) ) {
86                    $vals[$prop] = $row->$column;
87                }
88            }
89            if ( $from && $row->gt_globe == $from->globe ) {
90                $vals['dist'] = round(
91                    Math::distance( $from->lat, $from->lon, $row->gt_lat, $row->gt_lon ),
92                    1
93                );
94            }
95            $fit = $this->addPageSubItem( $row->gt_page_id, $vals );
96            if ( !$fit ) {
97                $this->setContinueEnumParameter( 'continue', $row->gt_page_id . '|' . $row->gt_id );
98            }
99        }
100    }
101
102    /**
103     * @param array $params
104     * @return Coord|null
105     */
106    private function getFromCoord( array $params ): ?Coord {
107        $this->requireMaxOneParameter( $params, 'distancefrompoint', 'distancefrompage' );
108        $globe = new Globe( 'earth' );
109        if ( $params['distancefrompoint'] !== null ) {
110            $arr = explode( '|', $params['distancefrompoint'] );
111            if ( count( $arr ) != 2 || !$globe->coordinatesAreValid( $arr[0], $arr[1] ) ) {
112                $this->dieWithError( 'apierror-geodata-badcoord', 'invalid-coord' );
113            }
114            return new Coord( (float)$arr[0], (float)$arr[1], 'earth' );
115        }
116        if ( $params['distancefrompage'] !== null ) {
117            $title = Title::newFromText( $params['distancefrompage'] );
118            if ( !$title || !$title->exists() ) {
119                $this->dieWithError( [
120                    'apierror-invalidtitle',
121                    wfEscapeWikiText( $params['distancefrompage'] )
122                ] );
123            }
124            // @phan-suppress-next-line PhanTypeMismatchArgumentNullable T240141
125            $page = $this->wikiPageFactory->newFromTitle( $title );
126            $redirectTarget = $page->getRedirectTarget();
127            if ( $redirectTarget ) {
128                $title = $redirectTarget;
129            }
130            $coord = GeoData::getPageCoordinates( $title->getArticleID() );
131            if ( !$coord ) {
132                $this->dieWithError(
133                    [
134                        'apierror-geodata-noprimarycoord',
135                        wfEscapeWikiText( $title->getPrefixedText() )
136                    ],
137                    'no-coordinates'
138                );
139            }
140            if ( $coord->globe != 'earth' ) {
141                $this->dieWithError( 'apierror-geodata-notonearth', 'notonearth' );
142            }
143            return $coord;
144        }
145        return null;
146    }
147
148    /** @inheritDoc */
149    public function getCacheMode( $params ) {
150        return 'public';
151    }
152
153    /** @inheritDoc */
154    public function getAllowedParams() {
155        return [
156            'limit' => [
157                ParamValidator::PARAM_DEFAULT => 10,
158                ParamValidator::PARAM_TYPE => 'limit',
159                IntegerDef::PARAM_MIN => 1,
160                IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
161                IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
162            ],
163            'continue' => [
164                ParamValidator::PARAM_TYPE => 'string',
165                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
166            ],
167            'prop' => [
168                ParamValidator::PARAM_TYPE => [ 'type', 'name', 'dim', 'country', 'region', 'globe' ],
169                ParamValidator::PARAM_DEFAULT => 'globe',
170                ParamValidator::PARAM_ISMULTI => true,
171                ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
172            ],
173            'primary' => [
174                ParamValidator::PARAM_TYPE => [ 'primary', 'secondary', 'all' ],
175                ParamValidator::PARAM_DEFAULT => 'primary',
176                ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
177            ],
178            'distancefrompoint' => [
179                ParamValidator::PARAM_TYPE => 'string',
180                ApiBase::PARAM_HELP_MSG_APPEND => [
181                    'geodata-api-help-coordinates-format',
182                ],
183            ],
184            'distancefrompage' => [
185                ParamValidator::PARAM_TYPE => 'string',
186            ],
187        ];
188    }
189
190    /** @inheritDoc */
191    protected function getExamplesMessages() {
192        return [
193            'action=query&prop=coordinates&titles=Main%20Page'
194                => 'apihelp-query+coordinates-example-1',
195        ];
196    }
197
198    /** @inheritDoc */
199    public function getHelpUrls() {
200        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:GeoData#prop.3Dcoordinates';
201    }
202}