Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 95 |
|
0.00% |
0 / 14 |
CRAP | |
0.00% |
0 / 1 |
SpecialUrlShortener | |
0.00% |
0 / 95 |
|
0.00% |
0 / 14 |
552 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
displayRestrictionError | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDisplayFormat | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getApprovedDomainsMessage | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
alterForm | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
validateURL | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getFormFields | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
2 | |||
getSubpageField | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onSubmit | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
setShortenedUrlResultField | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
6 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
doesWrites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isListed | |
0.00% |
0 / 1 |
|
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 | |
12 | namespace MediaWiki\Extension\UrlShortener; |
13 | |
14 | use HTMLForm; |
15 | use MediaWiki\Html\Html; |
16 | use MediaWiki\SpecialPage\FormSpecialPage; |
17 | use MediaWiki\Status\Status; |
18 | use Message; |
19 | use OOUI\FieldLayout; |
20 | use OOUI\HtmlSnippet; |
21 | use OOUI\Tag; |
22 | use OOUI\TextInputWidget; |
23 | use PermissionsError; |
24 | |
25 | class 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 | } |