Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.86% covered (warning)
75.86%
44 / 58
28.57% covered (danger)
28.57%
2 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiShortenUrl
75.86% covered (warning)
75.86%
44 / 58
28.57% covered (danger)
28.57%
2 / 7
24.08
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 execute
88.46% covered (warning)
88.46%
23 / 26
0.00% covered (danger)
0.00%
0 / 1
8.10
 recordInStatsD
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
4.13
 checkUserRights
25.00% covered (danger)
25.00%
1 / 4
0.00% covered (danger)
0.00%
0 / 1
3.69
 mustBePosted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAllowedParams
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * Class ApiShortenUrl
5 *
6 * Implements action=shortenurl to provide url shortening services via the API.
7 *
8 * Even though this is a write action sometimes, we still use GET so we can be
9 * cached at varnish levels very easily.
10 */
11
12namespace MediaWiki\Extension\UrlShortener;
13
14use ApiBase;
15use ApiMain;
16use ApiUsageException;
17use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
18use MediaWiki\Permissions\PermissionManager;
19use MediaWiki\Status\Status;
20use Wikimedia\ParamValidator\ParamValidator;
21
22class ApiShortenUrl extends ApiBase {
23
24    private bool $qrCodeEnabled;
25    private int $qrCodeShortenLimit;
26    private PermissionManager $permissionManager;
27    private StatsdDataFactoryInterface $statsdDataFactory;
28
29    /**
30     * @inheritDoc
31     */
32    public function __construct(
33        ApiMain $mainModule,
34        $moduleName,
35        PermissionManager $permissionManager,
36        StatsdDataFactoryInterface $statsdDataFactory
37    ) {
38        parent::__construct( $mainModule, $moduleName );
39
40        $this->qrCodeEnabled = (bool)$this->getConfig()->get( 'UrlShortenerEnableQrCode' );
41        $this->qrCodeShortenLimit = (int)$this->getConfig()->get( 'UrlShortenerQrCodeShortenLimit' );
42        $this->permissionManager = $permissionManager;
43        $this->statsdDataFactory = $statsdDataFactory;
44    }
45
46    public function execute() {
47        $this->checkUserRights();
48
49        if ( $this->getConfig()->get( 'UrlShortenerReadOnly' ) ) {
50            $this->dieWithError( 'apierror-urlshortener-disabled' );
51        }
52
53        $params = $this->extractRequestParams();
54
55        $url = $params['url'];
56        $qrCode = $this->qrCodeEnabled && $params['qrcode'];
57
58        $validityCheck = UrlShortenerUtils::validateUrl( $url );
59        if ( $validityCheck !== true ) {
60            $this->dieStatus( Status::newFatal( $validityCheck ) );
61        }
62
63        if ( $qrCode ) {
64            $status = UrlShortenerUtils::getQrCode( $url, $this->qrCodeShortenLimit, $this->getUser() );
65        } else {
66            $status = UrlShortenerUtils::maybeCreateShortCode( $url, $this->getUser() );
67        }
68
69        if ( !$status->isOK() ) {
70            $this->dieStatus( $status );
71        }
72
73        $shortUrlsOrQrCode = $status->getValue();
74        $urlShortened = isset( $shortUrlsOrQrCode[ 'url' ] );
75
76        $ret = [];
77
78        // QR codes may not have short URLs, in which case we don't want them in the response.
79        if ( $urlShortened ) {
80            $ret['shorturl'] = UrlShortenerUtils::makeUrl( $shortUrlsOrQrCode[ 'url' ] );
81            $ret['shorturlalt'] = UrlShortenerUtils::makeUrl( $shortUrlsOrQrCode[ 'alt' ] );
82        }
83
84        if ( $qrCode ) {
85            $ret['qrcode'] = $shortUrlsOrQrCode['qrcode'];
86        }
87
88        $this->recordInStatsD( $urlShortened, $qrCode );
89
90        // You get the cached response, YOU get the cached response, EVERYONE gets the cached response.
91        $this->getMain()->setCacheMode( "public" );
92        $this->getMain()->setCacheMaxAge( UrlShortenerUtils::CACHE_TTL_VALID );
93
94        $this->getResult()->addValue( null, $this->getModuleName(), $ret );
95    }
96
97    /**
98     * Record simple usage counts in statsd.
99     *
100     * @param bool $urlShortened
101     * @param bool $qrCode
102     * @return void
103     */
104    private function recordInStatsD( bool $urlShortened, bool $qrCode ): void {
105        if ( $qrCode && $urlShortened ) {
106            $this->statsdDataFactory->increment( 'extension.UrlShortener.api.shorturl_and_qrcode' );
107        } elseif ( $qrCode ) {
108            $this->statsdDataFactory->increment( 'extension.UrlShortener.api.qrcode' );
109        } else {
110            $this->statsdDataFactory->increment( 'extension.UrlShortener.api.shorturl' );
111        }
112    }
113
114    /**
115     * Check that the user can create a short url
116     * @throws ApiUsageException if the user lacks the rights
117     */
118    public function checkUserRights() {
119        if ( !$this->permissionManager->userHasRight( $this->getUser(), 'urlshortener-create-url' ) ) {
120            $this->dieWithError( [ 'apierror-permissiondenied',
121                $this->msg( "apierror-urlshortener-permissiondenied" ) ]
122            );
123        }
124    }
125
126    public function mustBePosted() {
127        return true;
128    }
129
130    /**
131     * @inheritDoc
132     */
133    protected function getAllowedParams() {
134        $params = [
135            'url' => [
136                ParamValidator::PARAM_REQUIRED => true,
137            ],
138        ];
139        if ( $this->qrCodeEnabled ) {
140            $params['qrcode'] = [
141                ParamValidator::PARAM_TYPE => 'boolean',
142                ParamValidator::PARAM_DEFAULT => false,
143            ];
144        }
145        return $params;
146    }
147
148    /**
149     * @inheritDoc
150     */
151    public function getExamplesMessages() {
152        return [
153            'action=shortenurl&url=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FArctica'
154                => 'apihelp-shortenurl-example-1',
155            'action=shortenurl&url=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FArctica&qrcode=1'
156                => 'apihelp-shortenurl-example-2',
157        ];
158    }
159}