1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
use SMW\UrlEncoder; |
4
|
|
|
use SMW\Message; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* @ingroup SMWDataValues |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
define( 'SMW_URI_MODE_EMAIL', 1 ); |
11
|
|
|
define( 'SMW_URI_MODE_URI', 3 ); |
12
|
|
|
define( 'SMW_URI_MODE_ANNOURI', 4 ); |
13
|
|
|
define( 'SMW_URI_MODE_TEL', 5 ); |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* This datavalue implements URL/URI/ANNURI/PHONE/EMAIL datavalues suitable for |
17
|
|
|
* defining the respective types of properties. |
18
|
|
|
* |
19
|
|
|
* @author Nikolas Iwan |
20
|
|
|
* @author Markus Krötzsch |
21
|
|
|
* @ingroup SMWDataValues |
22
|
|
|
* @bug Correctly create safe HTML and Wiki text. |
23
|
|
|
*/ |
24
|
|
|
class SMWURIValue extends SMWDataValue { |
|
|
|
|
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Raw value without encoding |
28
|
|
|
*/ |
29
|
|
|
const VALUE_RAW = 'uri.value.raw'; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* The value as returned by getWikitext() and getLongText(). |
33
|
|
|
* @var string |
34
|
|
|
*/ |
35
|
|
|
protected $m_wikitext; |
36
|
|
|
/** |
37
|
|
|
* One of the basic modes of operation for this class (emails, URL, |
38
|
|
|
* telephone number URI, ...). |
39
|
|
|
* @var integer |
40
|
|
|
*/ |
41
|
|
|
private $m_mode; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var boolean |
45
|
|
|
*/ |
46
|
|
|
private $showUrlContextInRawFormat = true; |
47
|
|
|
|
48
|
57 |
|
public function __construct( $typeid ) { |
49
|
57 |
|
parent::__construct( $typeid ); |
50
|
|
|
switch ( $typeid ) { |
51
|
57 |
|
case '_ema': |
52
|
7 |
|
$this->m_mode = SMW_URI_MODE_EMAIL; |
53
|
7 |
|
break; |
54
|
56 |
|
case '_anu': |
55
|
23 |
|
$this->m_mode = SMW_URI_MODE_ANNOURI; |
56
|
23 |
|
break; |
57
|
39 |
|
case '_tel': |
58
|
3 |
|
$this->m_mode = SMW_URI_MODE_TEL; |
59
|
3 |
|
break; |
60
|
36 |
|
case '__spu': |
61
|
35 |
|
case '_uri': |
62
|
1 |
|
case '_url': |
63
|
|
|
default: |
64
|
36 |
|
$this->m_mode = SMW_URI_MODE_URI; |
65
|
36 |
|
break; |
66
|
|
|
} |
67
|
57 |
|
} |
68
|
|
|
|
69
|
56 |
|
protected function parseUserValue( $value ) { |
70
|
56 |
|
$value = trim( $value ); |
71
|
56 |
|
$this->m_wikitext = $value; |
72
|
56 |
|
if ( $this->m_caption === false ) { |
73
|
39 |
|
$this->m_caption = $this->m_wikitext; |
74
|
|
|
} |
75
|
|
|
|
76
|
56 |
|
$scheme = $hierpart = $query = $fragment = ''; |
77
|
56 |
|
if ( $value === '' ) { // do not accept empty strings |
78
|
|
|
$this->addErrorMsg( array( 'smw_emptystring' ) ); |
79
|
|
|
return; |
80
|
|
|
} |
81
|
|
|
|
82
|
56 |
|
switch ( $this->m_mode ) { |
83
|
56 |
|
case SMW_URI_MODE_URI: |
84
|
27 |
|
case SMW_URI_MODE_ANNOURI: |
|
|
|
|
85
|
|
|
|
86
|
|
|
// Whether the the url value was externally encoded or not |
87
|
52 |
|
if ( strpos( $value, "%" ) === false ) { |
88
|
27 |
|
$this->showUrlContextInRawFormat = false; |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
// If somehow the slash was encoded bring into one format |
92
|
52 |
|
$value = str_replace( "%2F", "/", $value ); |
93
|
|
|
|
94
|
52 |
|
$parts = explode( ':', $value, 2 ); // try to split "schema:rest" |
95
|
52 |
|
if ( count( $parts ) == 1 ) { // possibly add "http" as default |
96
|
2 |
|
$value = 'http://' . $value; |
97
|
2 |
|
$parts[1] = $parts[0]; |
98
|
2 |
|
$parts[0] = 'http'; |
99
|
|
|
} |
100
|
|
|
// check against blacklist |
101
|
52 |
|
$uri_blacklist = explode( "\n", Message::get( 'smw_uri_blacklist', Message::TEXT, Message::CONTENT_LANGUAGE ) ); |
102
|
52 |
|
foreach ( $uri_blacklist as $uri ) { |
103
|
52 |
|
$uri = trim( $uri ); |
104
|
52 |
|
if ( $uri !== '' && $uri == mb_substr( $value, 0, mb_strlen( $uri ) ) ) { // disallowed URI! |
105
|
|
|
$this->addErrorMsg( array( 'smw_baduri', $value ) ); |
106
|
52 |
|
return; |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
// decompose general URI components |
110
|
52 |
|
$scheme = $parts[0]; |
111
|
52 |
|
$parts = explode( '?', $parts[1], 2 ); // try to split "hier-part?queryfrag" |
112
|
52 |
|
if ( count( $parts ) == 2 ) { |
113
|
10 |
|
$hierpart = $parts[0]; |
114
|
10 |
|
$parts = explode( '#', $parts[1], 2 ); // try to split "query#frag" |
115
|
10 |
|
$query = $parts[0]; |
116
|
10 |
|
$fragment = ( count( $parts ) == 2 ) ? $parts[1] : ''; |
117
|
|
|
} else { |
118
|
43 |
|
$query = ''; |
119
|
43 |
|
$parts = explode( '#', $parts[0], 2 ); // try to split "hier-part#frag" |
120
|
43 |
|
$hierpart = $parts[0]; |
121
|
43 |
|
$fragment = ( count( $parts ) == 2 ) ? $parts[1] : ''; |
122
|
|
|
} |
123
|
|
|
// We do not validate the URI characters (the data item will do this) but we do some escaping: |
124
|
|
|
// encode most characters, but leave special symbols as given by user: |
125
|
52 |
|
$hierpart = str_replace( array( '%3A', '%2F', '%23', '%40', '%3F', '%3D', '%26', '%25' ), array( ':', '/', '#', '@', '?', '=', '&', '%' ), rawurlencode( $hierpart ) ); |
126
|
52 |
|
$query = str_replace( array( '%3A', '%2F', '%23', '%40', '%3F', '%3D', '%26', '%25' ), array( ':', '/', '#', '@', '?', '=', '&', '%' ), rawurlencode( $query ) ); |
127
|
52 |
|
$fragment = str_replace( array( '%3A', '%2F', '%23', '%40', '%3F', '%3D', '%26', '%25' ), array( ':', '/', '#', '@', '?', '=', '&', '%' ), rawurlencode( $fragment ) ); |
128
|
|
|
/// NOTE: we do not support raw [ (%5D) and ] (%5E), although they are needed for ldap:// (but rarely in a wiki) |
129
|
|
|
/// NOTE: "+" gets encoded, as it is interpreted as space by most browsers when part of a URL; |
130
|
|
|
/// this prevents tel: from working directly, but we have a datatype for this anyway. |
131
|
|
|
|
132
|
52 |
|
if ( substr( $hierpart, 0, 2 ) === '//' ) { |
133
|
52 |
|
$hierpart = substr( $hierpart, 2 ); |
134
|
|
|
} |
135
|
|
|
|
136
|
52 |
|
break; |
137
|
9 |
|
case SMW_URI_MODE_TEL: |
138
|
3 |
|
$scheme = 'tel'; |
139
|
|
|
|
140
|
3 |
|
if ( substr( $value, 0, 4 ) === 'tel:' ) { // accept optional "tel" |
141
|
1 |
|
$value = substr( $value, 4 ); |
142
|
1 |
|
$this->m_wikitext = $value; |
143
|
|
|
} |
144
|
|
|
|
145
|
3 |
|
$hierpart = preg_replace( '/(?<=[0-9]) (?=[0-9])/', '\1-\2', $value ); |
146
|
3 |
|
$hierpart = str_replace( ' ', '', $hierpart ); |
147
|
3 |
|
if ( substr( $hierpart, 0, 2 ) == '00' ) { |
148
|
|
|
$hierpart = '+' . substr( $hierpart, 2 ); |
149
|
|
|
} |
150
|
|
|
|
151
|
3 |
|
if ( !$this->getOption( self::OPT_QUERY_CONTEXT ) && ( ( strlen( preg_replace( '/[^0-9]/', '', $hierpart ) ) < 6 ) || |
|
|
|
|
152
|
3 |
|
( preg_match( '<[-+./][-./]>', $hierpart ) ) || |
153
|
3 |
|
( !self::isValidTelURI( 'tel:' . $hierpart ) ) ) ) { /// TODO: introduce error-message for "bad" phone number |
154
|
|
|
$this->addErrorMsg( array( 'smw_baduri', $this->m_wikitext ) ); |
155
|
|
|
return; |
156
|
|
|
} |
157
|
3 |
|
break; |
158
|
7 |
|
case SMW_URI_MODE_EMAIL: |
159
|
7 |
|
$scheme = 'mailto'; |
160
|
7 |
|
if ( strpos( $value, 'mailto:' ) === 0 ) { // accept optional "mailto" |
161
|
1 |
|
$value = substr( $value, 7 ); |
162
|
1 |
|
$this->m_wikitext = $value; |
163
|
|
|
} |
164
|
|
|
|
165
|
7 |
|
if ( !$this->getOption( self::OPT_QUERY_CONTEXT ) && !Sanitizer::validateEmail( $value ) ) { |
|
|
|
|
166
|
|
|
/// TODO: introduce error-message for "bad" email |
167
|
|
|
$this->addErrorMsg( array( 'smw_baduri', $value ) ); |
168
|
|
|
return; |
169
|
|
|
} |
170
|
7 |
|
$hierpart = str_replace( array( '%3A', '%2F', '%23', '%40', '%3F', '%3D', '%26', '%25' ), array( ':', '/', '#', '@', '?', '=', '&', '%' ), rawurlencode( $value ) ); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
// Now create the URI data item: |
174
|
|
|
try { |
175
|
56 |
|
$this->m_dataitem = new SMWDIUri( $scheme, $hierpart, $query, $fragment, $this->m_typeid ); |
|
|
|
|
176
|
|
|
} catch ( SMWDataItemException $e ) { |
177
|
|
|
$this->addErrorMsg( array( 'smw_baduri', $this->m_wikitext ) ); |
178
|
|
|
} |
179
|
56 |
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Returns true if the argument is a valid RFC 3966 phone number. |
183
|
|
|
* Only global phone numbers are supported, and no full validation |
184
|
|
|
* of parameters (appended via ;param=value) is performed. |
185
|
|
|
*/ |
186
|
3 |
|
protected static function isValidTelURI( $s ) { |
187
|
3 |
|
$tel_uri_regex = '<^tel:\+[0-9./-]*[0-9][0-9./-]*(;[0-9a-zA-Z-]+=(%[0-9a-zA-Z][0-9a-zA-Z]|[0-9a-zA-Z._~:/?#[\]@!$&\'()*+,;=-])*)*$>'; |
188
|
3 |
|
return (bool) preg_match( $tel_uri_regex, $s ); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* @see SMWDataValue::loadDataItem() |
193
|
|
|
* @param $dataitem SMWDataItem |
194
|
|
|
* @return boolean |
195
|
|
|
*/ |
196
|
10 |
|
protected function loadDataItem( SMWDataItem $dataItem ) { |
197
|
|
|
|
198
|
10 |
|
if ( $dataItem->getDIType() !== SMWDataItem::TYPE_URI ) { |
199
|
|
|
return false; |
200
|
|
|
} |
201
|
|
|
|
202
|
10 |
|
$this->m_dataitem = $dataItem; |
203
|
10 |
|
if ( $this->m_mode == SMW_URI_MODE_EMAIL ) { |
204
|
1 |
|
$this->m_wikitext = substr( $dataItem->getURI(), 7 ); |
|
|
|
|
205
|
10 |
|
} elseif ( $this->m_mode == SMW_URI_MODE_TEL ) { |
206
|
1 |
|
$this->m_wikitext = substr( $dataItem->getURI(), 4 ); |
|
|
|
|
207
|
|
|
} else { |
208
|
9 |
|
$this->m_wikitext = $dataItem->getURI(); |
|
|
|
|
209
|
|
|
} |
210
|
|
|
|
211
|
10 |
|
$this->m_caption = $this->m_wikitext; |
212
|
10 |
|
$this->showUrlContextInRawFormat = false; |
213
|
|
|
|
214
|
10 |
|
return true; |
215
|
|
|
} |
216
|
|
|
|
217
|
55 |
|
public function getShortWikiText( $linked = null ) { |
218
|
|
|
|
219
|
55 |
|
list( $url, $caption ) = $this->decodeUriContext( $this->m_caption, $linked ); |
220
|
|
|
|
221
|
55 |
|
if ( is_null( $linked ) || ( $linked === false ) || ( $url === '' ) || |
222
|
55 |
|
( $this->m_outformat == '-' ) || ( $this->m_caption === '' ) ) { |
223
|
21 |
|
return $caption; |
224
|
34 |
|
} elseif ( $this->m_outformat == 'nowiki' ) { |
225
|
|
|
return $this->makeNonlinkedWikiText( $caption ); |
226
|
|
|
} else { |
227
|
34 |
|
return '[' . $url . ' ' . $caption . ']'; |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
39 |
|
public function getShortHTMLText( $linker = null ) { |
232
|
|
|
|
233
|
39 |
|
list( $url, $caption ) = $this->decodeUriContext( $this->m_caption, $linker ); |
234
|
|
|
|
235
|
39 |
|
if ( is_null( $linker ) || ( !$this->isValid() ) || ( $url === '' ) || |
236
|
19 |
|
( $this->m_outformat == '-' ) || ( $this->m_outformat == 'nowiki' ) || |
237
|
39 |
|
( $this->m_caption === '' ) || $linker === false ) { |
238
|
20 |
|
return $caption; |
239
|
|
|
} else { |
240
|
19 |
|
return $linker->makeExternalLink( $url, $caption ); |
241
|
|
|
} |
242
|
|
|
} |
243
|
|
|
|
244
|
36 |
|
public function getLongWikiText( $linked = null ) { |
245
|
|
|
|
246
|
36 |
|
if ( !$this->isValid() ) { |
247
|
|
|
return $this->getErrorText(); |
248
|
|
|
} |
249
|
|
|
|
250
|
36 |
|
list( $url, $wikitext ) = $this->decodeUriContext( $this->m_wikitext, $linked ); |
251
|
|
|
|
252
|
36 |
|
if ( is_null( $linked ) || ( $linked === false ) || ( $url === '' ) || |
253
|
36 |
|
( $this->m_outformat == '-' ) || $linked === false ) { |
254
|
20 |
|
return $wikitext; |
255
|
16 |
|
} elseif ( $this->m_outformat == 'nowiki' ) { |
256
|
|
|
return $this->makeNonlinkedWikiText( $wikitext ); |
257
|
|
|
} else { |
258
|
16 |
|
return '[' . $url . ' ' . $wikitext . ']'; |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
|
262
|
37 |
|
public function getLongHTMLText( $linker = null ) { |
263
|
|
|
|
264
|
37 |
|
if ( !$this->isValid() ) { |
265
|
|
|
return $this->getErrorText(); |
266
|
|
|
} |
267
|
|
|
|
268
|
37 |
|
list( $url, $wikitext ) = $this->decodeUriContext( $this->m_wikitext, $linker ); |
269
|
|
|
|
270
|
37 |
|
if ( is_null( $linker ) || ( !$this->isValid() ) || ( $url === '' ) || |
271
|
37 |
|
( $this->m_outformat == '-' ) || ( $this->m_outformat == 'nowiki' ) || $linker === false ) { |
272
|
20 |
|
return $wikitext; |
273
|
|
|
} else { |
274
|
17 |
|
return $linker->makeExternalLink( $url, $wikitext ); |
275
|
|
|
} |
276
|
|
|
} |
277
|
|
|
|
278
|
45 |
|
public function getWikiValue() { |
279
|
|
|
|
280
|
45 |
|
if ( $this->getOption( self::VALUE_RAW ) ) { |
|
|
|
|
281
|
3 |
|
return rawurldecode( $this->m_wikitext ); |
282
|
|
|
} |
283
|
|
|
|
284
|
45 |
|
return $this->m_wikitext; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
public function getURI() { |
288
|
|
|
return $this->getUriDataitem()->getURI(); |
289
|
|
|
} |
290
|
|
|
|
291
|
1 |
|
protected function getServiceLinkParams() { |
292
|
|
|
// Create links to mapping services based on a wiki-editable message. The parameters |
293
|
|
|
// available to the message are: |
294
|
|
|
// $1: urlencoded version of URI/URL value (includes mailto: for emails) |
295
|
1 |
|
return array( rawurlencode( $this->getUriDataitem()->getURI() ) ); |
|
|
|
|
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* Get a URL for hyperlinking this URI, or the empty string if this URI |
300
|
|
|
* is not hyperlinked in MediaWiki. |
301
|
|
|
* @return string |
302
|
|
|
*/ |
303
|
56 |
|
public function getURL() { |
304
|
56 |
|
global $wgUrlProtocols; |
|
|
|
|
305
|
|
|
|
306
|
56 |
|
foreach ( $wgUrlProtocols as $prot ) { |
307
|
56 |
|
if ( ( $prot == $this->getUriDataitem()->getScheme() . ':' ) || ( $prot == $this->getUriDataitem()->getScheme() . '://' ) ) { |
308
|
56 |
|
return $this->getUriDataitem()->getURI(); |
309
|
|
|
} |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
return ''; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Helper function to get the current dataitem, or some dummy URI |
317
|
|
|
* dataitem if the dataitem was not set. This makes it easier to |
318
|
|
|
* write code that avoids errors even if the data was not |
319
|
|
|
* initialized properly. |
320
|
|
|
* @return SMWDIUri |
321
|
|
|
*/ |
322
|
56 |
|
protected function getUriDataitem() { |
323
|
56 |
|
if ( isset( $this->m_dataitem ) ) { |
324
|
56 |
|
return $this->m_dataitem; |
325
|
|
|
} else { // note: use "noprotocol" to avoid accidental use in an MW link, see getURL() |
326
|
|
|
return new SMWDIUri( 'noprotocol', 'x', '', '', $this->m_typeid ); |
|
|
|
|
327
|
|
|
} |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* Helper function that changes a URL string in such a way that it |
332
|
|
|
* can be used in wikitext without being turned into a hyperlink, |
333
|
|
|
* while still displaying the same characters. The use of |
334
|
|
|
* <nowiki> is avoided, since the resulting strings may be |
335
|
|
|
* inserted during parsing, after this has been stripped. |
336
|
|
|
* |
337
|
|
|
* @since 1.8 |
338
|
|
|
*/ |
339
|
|
|
protected function makeNonlinkedWikiText( $url ) { |
340
|
|
|
return str_replace( ':', ':', $url ); |
341
|
|
|
} |
342
|
|
|
|
343
|
56 |
|
private function decodeUriContext( $context, $linker ) { |
344
|
|
|
|
345
|
|
|
// Prior to decoding turn any `-` into an internal representation to avoid |
346
|
|
|
// potential breakage |
347
|
56 |
|
if ( !$this->showUrlContextInRawFormat ) { |
348
|
26 |
|
$context = UrlEncoder::decode( str_replace( '-', '-2D', $context ) ); |
349
|
|
|
} |
350
|
|
|
|
351
|
56 |
|
if ( $this->m_mode !== SMW_URI_MODE_EMAIL && $linker !== null ) { |
352
|
|
|
$context = str_replace( '_', ' ', $context ); |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
// Allow the display without `_` so that URIs can be split |
356
|
|
|
// during the outout by the browser without breaking the URL itself |
357
|
|
|
// as it contains the `_` for spaces |
358
|
|
|
return array( $this->getURL(), $context ); |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
} |
362
|
|
|
|
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.