Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 88
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQueryExternalLinks
0.00% covered (danger)
0.00%
0 / 88
0.00% covered (danger)
0.00%
0 / 7
342
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 / 52
0.00% covered (danger)
0.00%
0 / 1
132
 setContinue
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 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 / 22
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 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23use MediaWiki\ExternalLinks\LinkFilter;
24use MediaWiki\Parser\Parser;
25use MediaWiki\Title\Title;
26use MediaWiki\Utils\UrlUtils;
27use Wikimedia\ParamValidator\ParamValidator;
28use Wikimedia\ParamValidator\TypeDef\IntegerDef;
29use Wikimedia\Rdbms\IExpression;
30use Wikimedia\Rdbms\LikeValue;
31
32/**
33 * A query module to list all external URLs found on a given set of pages.
34 *
35 * @ingroup API
36 */
37class ApiQueryExternalLinks extends ApiQueryBase {
38
39    private UrlUtils $urlUtils;
40
41    /**
42     * @param ApiQuery $query
43     * @param string $moduleName
44     * @param UrlUtils $urlUtils
45     */
46    public function __construct( ApiQuery $query, $moduleName, UrlUtils $urlUtils ) {
47        parent::__construct( $query, $moduleName, 'el' );
48
49        $this->urlUtils = $urlUtils;
50    }
51
52    public function execute() {
53        $pages = $this->getPageSet()->getGoodPages();
54        if ( $pages === [] ) {
55            return;
56        }
57
58        $params = $this->extractRequestParams();
59        $db = $this->getDB();
60
61        $query = $params['query'];
62        $protocol = LinkFilter::getProtocolPrefix( $params['protocol'] );
63
64        $fields = [ 'el_from' ];
65        $fields[] = 'el_to_domain_index';
66        $fields[] = 'el_to_path';
67        $continueField = 'el_to_domain_index';
68        $this->addFields( $fields );
69
70        $this->addTables( 'externallinks' );
71        $this->addWhereFld( 'el_from', array_keys( $pages ) );
72
73        if ( $query !== null && $query !== '' ) {
74            // Normalize query to match the normalization applied for the externallinks table
75            $query = Parser::normalizeLinkUrl( $query );
76
77            $conds = LinkFilter::getQueryConditions( $query, [
78                'protocol' => $protocol,
79                'oneWildcard' => true,
80                'db' => $db
81            ] );
82            if ( !$conds ) {
83                $this->dieWithError( 'apierror-badquery' );
84            }
85            $this->addWhere( $conds );
86        } else {
87            if ( $protocol !== null ) {
88                $this->addWhere(
89                    $db->expr( $continueField, IExpression::LIKE, new LikeValue( "$protocol", $db->anyString() ) )
90                );
91            }
92        }
93
94        $orderBy = [ 'el_id' ];
95
96        $this->addOption( 'ORDER BY', $orderBy );
97        $this->addFields( $orderBy ); // Make sure
98
99        $this->addOption( 'LIMIT', $params['limit'] + 1 );
100
101        if ( $params['continue'] !== null ) {
102            $cont = $this->parseContinueParamOrDie( $params['continue'],
103                array_fill( 0, count( $orderBy ), 'string' ) );
104            $conds = array_combine( $orderBy, array_map( 'rawurldecode', $cont ) );
105            $this->addWhere( $db->buildComparison( '>=', $conds ) );
106        }
107
108        $res = $this->select( __METHOD__ );
109
110        $count = 0;
111        foreach ( $res as $row ) {
112            if ( ++$count > $params['limit'] ) {
113                // We've reached the one extra which shows that
114                // there are additional pages to be had. Stop here...
115                $this->setContinue( $orderBy, $row );
116                break;
117            }
118            $entry = [];
119            $to = LinkFilter::reverseIndexes( $row->el_to_domain_index ) . $row->el_to_path;
120            // expand protocol-relative urls
121            if ( $params['expandurl'] ) {
122                $to = (string)$this->urlUtils->expand( $to, PROTO_CANONICAL );
123            }
124            ApiResult::setContentValue( $entry, 'url', $to );
125            $fit = $this->addPageSubItem( $row->el_from, $entry );
126            if ( !$fit ) {
127                $this->setContinue( $orderBy, $row );
128                break;
129            }
130        }
131    }
132
133    private function setContinue( $orderBy, $row ) {
134        $fields = [];
135        foreach ( $orderBy as $field ) {
136            $fields[] = strtr( $row->$field, [ '%' => '%25', '|' => '%7C' ] );
137        }
138        $this->setContinueEnumParameter( 'continue', implode( '|', $fields ) );
139    }
140
141    public function getCacheMode( $params ) {
142        return 'public';
143    }
144
145    public function getAllowedParams() {
146        return [
147            'limit' => [
148                ParamValidator::PARAM_DEFAULT => 10,
149                ParamValidator::PARAM_TYPE => 'limit',
150                IntegerDef::PARAM_MIN => 1,
151                IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
152                IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
153            ],
154            'continue' => [
155                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
156            ],
157            'protocol' => [
158                ParamValidator::PARAM_TYPE => LinkFilter::prepareProtocols(),
159                ParamValidator::PARAM_DEFAULT => '',
160            ],
161            'query' => null,
162            'expandurl' => [
163                ParamValidator::PARAM_TYPE => 'boolean',
164                ParamValidator::PARAM_DEFAULT => false,
165                ParamValidator::PARAM_DEPRECATED => true,
166            ],
167        ];
168    }
169
170    protected function getExamplesMessages() {
171        $title = Title::newMainPage()->getPrefixedText();
172        $mp = rawurlencode( $title );
173
174        return [
175            "action=query&prop=extlinks&titles={$mp}"
176                => 'apihelp-query+extlinks-example-simple',
177        ];
178    }
179
180    public function getHelpUrls() {
181        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Extlinks';
182    }
183}