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