Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 95
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialUrlShortener
0.00% covered (danger)
0.00%
0 / 95
0.00% covered (danger)
0.00%
0 / 14
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 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 displayRestrictionError
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDisplayFormat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getApprovedDomainsMessage
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 alterForm
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 validateURL
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getFormFields
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
2
 getSubpageField
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onSubmit
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 setShortenedUrlResultField
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
6
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 doesWrites
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isListed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * A special page that creates redirects to arbitrary URLs
4 *
5 * @file
6 * @ingroup Extensions
7 * @author Yuvi Panda, http://yuvi.in
8 * @copyright © 2014 Yuvaraj Pandian (yuvipanda@gmail.com)
9 * @license Apache-2.0
10 */
11
12namespace MediaWiki\Extension\UrlShortener;
13
14use HTMLForm;
15use MediaWiki\Html\Html;
16use MediaWiki\SpecialPage\FormSpecialPage;
17use MediaWiki\Status\Status;
18use Message;
19use OOUI\FieldLayout;
20use OOUI\HtmlSnippet;
21use OOUI\Tag;
22use OOUI\TextInputWidget;
23use PermissionsError;
24
25class SpecialUrlShortener extends FormSpecialPage {
26
27    protected FieldLayout $resultField;
28    protected Status $resultStatus;
29
30    /**
31     * @param string $name
32     * @param string $restriction
33     */
34    public function __construct( $name = 'UrlShortener', $restriction = 'urlshortener-create-url' ) {
35        parent::__construct( $name, $restriction );
36    }
37
38    /**
39     * @inheritDoc
40     */
41    public function execute( $par ) {
42        $this->addHelpLink( 'Help:UrlShortener' );
43
44        $this->checkPermissions();
45
46        if ( $this->getConfig()->get( 'UrlShortenerReadOnly' ) ) {
47            $this->setHeaders();
48            $this->getOutput()->addWikiMsg( 'urlshortener-disabled' );
49            return;
50        }
51
52        parent::execute( $par );
53
54        // @phan-suppress-next-line PhanRedundantCondition
55        if ( isset( $this->resultStatus ) ) {
56            // Always show form, as in JS mode.
57            $form = $this->getForm();
58            $form->showAlways();
59            $form->addPostHtml( Html::closeElement( 'div' ) );
60        }
61    }
62
63    /**
64     * @inheritDoc
65     */
66    public function displayRestrictionError() {
67        throw new PermissionsError( 'urlshortener-create-url', [ 'urlshortener-badaccessgroups' ] );
68    }
69
70    /**
71     * @inheritDoc
72     */
73    protected function getDisplayFormat() {
74        return 'ooui';
75    }
76
77    /**
78     * @return Message
79     */
80    protected function getApprovedDomainsMessage(): Message {
81        $urlShortenerApprovedDomains = $this->getConfig()->get( 'UrlShortenerApprovedDomains' );
82        if ( $urlShortenerApprovedDomains ) {
83            $domains = $urlShortenerApprovedDomains;
84        } else {
85            $parsed = wfParseUrl( $this->getConfig()->get( 'Server' ) );
86            $domains = [ $parsed['host'] ];
87        }
88
89        $lang = $this->getLanguage();
90        return $this->msg( 'urlshortener-approved-domains' )
91            ->numParams( count( $domains ) )
92            ->params( $lang->listToText( array_map( static function ( $i ) {
93                return "<code>$i</code>";
94            }, $domains ) ) );
95    }
96
97    /**
98     * @param HTMLForm $form
99     * @param string $module
100     */
101    protected function alterForm( HTMLForm $form, string $module = 'ext.urlShortener.special' ) {
102        // HACK: There's apparently no way to add a container around the form, barring more
103        // egregious tactics like manipulating the whole OutputPage. Here we leave an open <div>
104        // in the pre-HTML, and close the tag later both in this class and SpecialQrCode,
105        // as the latter needs to append more content before closing the tag.
106        $form->addPreHtml(
107            Html::openElement( 'div', [ 'class' => 'ext-urlshortener-container' ] )
108        );
109        $form->suppressDefaultSubmit();
110        $this->getOutput()->addModules( $module );
111        $this->getOutput()->addJsConfigVars( [
112            'wgUrlShortenerAllowedDomains' => UrlShortenerUtils::getAllowedDomainsRegex(),
113            'wgUrlShortenerAllowArbitraryPorts' => $this->getConfig()->get( 'UrlShortenerAllowArbitraryPorts' ),
114        ] );
115        // @phan-suppress-next-line PhanRedundantCondition
116        if ( isset( $this->resultField ) ) {
117            $form->addFooterHtml( $this->resultField );
118        }
119    }
120
121    /**
122     * Validate the URL to ensure that we are allowed to create a shorturl for this.
123     *
124     * @param string|null $url The URL to validate
125     * @return bool|string true if url is valid, error message otherwise
126     */
127    public function validateURL( ?string $url ) {
128        // $url is null when the form hasn't been posted
129        if ( $url === null ) {
130            // No input
131            return true;
132        }
133        $validity_check = UrlShortenerUtils::validateUrl( $url );
134        if ( $validity_check === true ) {
135            return true;
136        }
137        return $validity_check->text();
138    }
139
140    /**
141     * Generate the form used to input the URL to shorten.
142     * @return array A form definition that can be used by HTMLForm
143     */
144    protected function getFormFields() {
145        return [
146            'url' => [
147                'validation-callback' => [ $this, 'validateURL' ],
148                'required' => true,
149                'type' => 'textwithbutton',
150                'inputtype' => 'url',
151                'buttontype' => 'submit',
152                'buttondefault' => $this->msg( 'urlshortener-url-input-submit' )->text(),
153                'buttonflags' => [ 'primary', 'progressive' ],
154                'buttonid' => 'mw-urlshortener-submit',
155                'name' => 'url',
156                'label-message' => 'urlshortener-form-header',
157                'autofocus' => true,
158                'id' => 'ext-urlshortener-url-input',
159                'help' => $this->getApprovedDomainsMessage()->parse(),
160                'placeholder' => $this->msg( 'urlshortener-url-input-label' )->text(),
161            ],
162        ];
163    }
164
165    /**
166     * @return string
167     */
168    protected function getSubpageField() {
169        return 'url';
170    }
171
172    /**
173     * Process the form on POST submission.
174     *
175     * Creates the short URL and displays it back to the user.
176     *
177     * @param array $data
178     * @return bool|Status True for success, false for didn't-try, Status (with errors) on failure
179     */
180    public function onSubmit( array $data ) {
181        $out = $this->getOutput();
182        $out->enableOOUI();
183        if ( $data['url'] === null ) {
184            return false;
185        }
186        $status = UrlShortenerUtils::maybeCreateShortCode( $data['url'], $this->getUser() );
187        if ( !$status->isOK() ) {
188            return $status;
189        }
190        $this->setShortenedUrlResultField( $status->getValue() );
191        $this->resultStatus = $status;
192        return true;
193    }
194
195    /**
196     * @param array $result
197     */
198    protected function setShortenedUrlResultField( array $result ): void {
199        if ( !isset( $result['url'] ) ) {
200            // 'url' is only present if it was shortened, which isn't always the case
201            // when using Special:QrCode which extends this class.
202            return;
203        }
204        $altUrl = UrlShortenerUtils::makeUrl( $result[ 'alt' ] );
205        $altLink = new Tag( 'a' );
206        $altLink->setAttributes( [ 'href' => $altUrl ] );
207        $altLink->appendContent( $altUrl );
208        $this->resultField = new FieldLayout(
209            new TextInputWidget( [
210                'value' => UrlShortenerUtils::makeUrl( $result[ 'url' ] ),
211                'readOnly' => true,
212            ] ),
213            [
214                'align' => 'top',
215                'classes' => [ 'ext-urlshortener-result' ],
216                'label' => $this->msg( 'urlshortener-shortened-url-label' )->text(),
217                'help' => new HtmlSnippet(
218                    $this->msg( 'urlshortener-shortened-url-alt' )->escaped() . ' ' . $altLink
219                ),
220                'helpInline' => true,
221            ]
222        );
223    }
224
225    /**
226     * @inheritDoc
227     */
228    protected function getGroupName() {
229        return 'pagetools';
230    }
231
232    public function doesWrites() {
233        return true;
234    }
235
236    /**
237     * Don't list this page if in read only mode
238     *
239     * @return bool
240     */
241    public function isListed() {
242        return !$this->getConfig()->get( 'UrlShortenerReadOnly' );
243    }
244}