New Linker::link() function, intended to replace Linker::make*Link*() functions....
[lhc/web/wiklou.git] / includes / Linker.php
1 <?php
2 /**
3 * Split off some of the internal bits from Skin.php. These functions are used
4 * for primarily page content: links, embedded images, table of contents. Links
5 * are also used in the skin. For the moment, Skin is a descendent class of
6 * Linker. In the future, it should probably be further split so that every
7 * other bit of the wiki doesn't have to go loading up Skin to get at it.
8 *
9 * @ingroup Skins
10 */
11 class Linker {
12
13 /**
14 * Flags for userToolLinks()
15 */
16 const TOOL_LINKS_NOBLOCK = 1;
17
18 function __construct() {}
19
20 /**
21 * @deprecated
22 */
23 function postParseLinkColour( $s = null ) {
24 wfDeprecated( __METHOD__ );
25 return null;
26 }
27
28 /**
29 * Get the appropriate HTML attributes to add to the "a" element of an ex-
30 * ternal link, as created by [wikisyntax].
31 *
32 * @param string $title The (unescaped) title text for the link
33 * @param string $unused Unused
34 * @param string $class The contents of the class attribute; if an empty
35 * string is passed, which is the default value, defaults to 'external'.
36 */
37 function getExternalLinkAttributes( $title, $unused = null, $class='' ) {
38 return $this->getLinkAttributesInternal( $title, $class, 'external' );
39 }
40
41 /**
42 * Get the appropriate HTML attributes to add to the "a" element of an in-
43 * terwiki link.
44 *
45 * @param string $title The title text for the link, URL-encoded (???) but
46 * not HTML-escaped
47 * @param string $unused Unused
48 * @param string $class The contents of the class attribute; if an empty
49 * string is passed, which is the default value, defaults to 'external'.
50 */
51 function getInterwikiLinkAttributes( $title, $unused = null, $class='' ) {
52 global $wgContLang;
53
54 # FIXME: We have a whole bunch of handling here that doesn't happen in
55 # getExternalLinkAttributes, why?
56 $title = urldecode( $title );
57 $title = $wgContLang->checkTitleEncoding( $title );
58 $title = preg_replace( '/[\\x00-\\x1f]/', ' ', $title );
59
60 return $this->getLinkAttributesInternal( $title, $class, 'external' );
61 }
62
63 /**
64 * Get the appropriate HTML attributes to add to the "a" element of an in-
65 * ternal link.
66 *
67 * @param string $title The title text for the link, URL-encoded (???) but
68 * not HTML-escaped
69 * @param string $unused Unused
70 * @param string $class The contents of the class attribute, default none
71 */
72 function getInternalLinkAttributes( $title, $unused = null, $class='' ) {
73 $title = urldecode( $title );
74 $title = str_replace( '_', ' ', $title );
75 return $this->getLinkAttributesInternal( $title, $class );
76 }
77
78 /**
79 * Get the appropriate HTML attributes to add to the "a" element of an in-
80 * ternal link, given the Title object for the page we want to link to.
81 *
82 * @param Title $nt The Title object
83 * @param string $unused Unused
84 * @param string $class The contents of the class attribute, default none
85 * @param mixed $title Optional (unescaped) string to use in the title
86 * attribute; if false, default to the name of the page we're linking to
87 */
88 function getInternalLinkAttributesObj( $nt, $unused = null, $class = '', $title = false ) {
89 if( $title === false ) {
90 $title = $nt->getPrefixedText();
91 }
92 return $this->getLinkAttributesInternal( $title, $class );
93 }
94
95 /**
96 * Common code for getLinkAttributesX functions
97 */
98 private function getLinkAttributesInternal( $title, $class, $classDefault = false ) {
99 $title = htmlspecialchars( $title );
100 if( $class === '' and $classDefault !== false ) {
101 # FIXME: Parameter defaults the hard way! We should just have
102 # $class = 'external' or whatever as the default in the externally-
103 # exposed functions, not $class = ''.
104 $class = $classDefault;
105 }
106 $class = htmlspecialchars( $class );
107 $r = '';
108 if( $class !== '' ) {
109 $r .= " class=\"$class\"";
110 }
111 $r .= " title=\"$title\"";
112 return $r;
113 }
114
115 /**
116 * Return the CSS colour of a known link
117 *
118 * @param Title $t
119 * @param integer $threshold user defined threshold
120 * @return string CSS class
121 */
122 function getLinkColour( $t, $threshold ) {
123 $colour = '';
124 if ( $t->isRedirect() ) {
125 # Page is a redirect
126 $colour = 'mw-redirect';
127 } elseif ( $threshold > 0 && $t->getLength() < $threshold && MWNamespace::isContent( $t->getNamespace() ) ) {
128 # Page is a stub
129 $colour = 'stub';
130 }
131 return $colour;
132 }
133
134 /**
135 * This function returns an HTML link to the given target. It serves a few purposes:
136 * 1) If $target is a Title, the correct URL to link to will be figured out automatically.
137 * 2) It automatically adds the usual classes for various types of link targets: "new" for red links, "extern" for external links, etc.
138 * 3) It escapes all attribute values safely so there's no risk of XSS.
139 * 4) It provides a default tooltip if the target is a Title (the page name of the target).
140 *
141 * @param $target Title Can currently only be a Title, but this may change.
142 * @param $text string The HTML contents of the <a> element, i.e., the link text. This is raw HTML and will not be escaped. If null, defaults to the page name of the Title or Image, or the text of the URL if $target is a URL.
143 * @param $query array The query string to append to the URL you're linking to, in key => value array form. Useful mainly for Titles and Images. Query keys and values will be URL-encoded.
144 * @param $customAttribs array A key => value array of extra HTML attributes, such as title and class. (href is ignored.) Classes will be merged with the default classes, while other attributes will replace default attributes. All passed attribute values will be HTML-escaped. A false attribute value means to suppress that attribute.
145 * @param $options mixed String or array of strings:
146 * 'known': Page is known to exist, so don't check if it does.
147 * 'broken': Page is known not to exist, so don't check if it does.
148 * 'noclasses': Don't add any classes automatically (includes "new", "stub", "mw-redirect"). Only use the class attribute provided, if any.
149 * @return string HTML <a> attribute
150 */
151 public function link( $target, $text = null, $customAttribs = array(), $query = array(), $options = array() ) {
152 wfProfileIn( __METHOD__ );
153 if( !($target instanceof Title) ) {
154 throw new MWException( 'Linker::link passed invalid target' );
155 }
156 $options = (array)$options;
157
158 # Normalize the Title if it's a special page
159 if( $target->getNamespace() == NS_SPECIAL ) {
160 list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $target->getDBkey() );
161 if( $name ) {
162 $target = SpecialPage::getTitleFor( $name, $subpage );
163 }
164 }
165
166 # If we don't know whether the page exists, let's find out.
167 if( !in_array( 'known', $options ) and !in_array( 'broken', $options ) ) {
168 if( $target->getNamespace() == NS_SPECIAL ) {
169 if( SpecialPage::exists( $target->getDbKey() ) ) {
170 $options []= 'known';
171 } else {
172 $options []= 'broken';
173 }
174 } elseif( $target->isAlwaysKnown() or
175 ($target->getPrefixedText() == '' and $target->getFragment() != '')
176 or $target->exists() ) {
177 $options []= 'known';
178 } else {
179 # Either it exists
180 $options []= 'broken';
181 }
182 }
183
184 # Note: we want the href attribute first, for prettiness.
185 $attribs = array( 'href' => $this->linkUrl( $target, $query, $options ) );
186 $attribs = array_merge(
187 $attribs,
188 $this->linkAttribs( $target, $customAttribs, $options )
189 );
190 if( is_null( $text ) ) {
191 $text = $this->linkText( $target, $options );
192 }
193
194 $ret = Xml::element( 'a', $attribs, $text, false );
195
196 wfProfileOut( __METHOD__ );
197 return $ret;
198 }
199
200 private function linkUrl( $target, $query, $options ) {
201 # If it's a broken link, add the appropriate query pieces. This over-
202 # writes the default action!
203 if( in_array( 'broken', $options ) ) {
204 $query['action'] = 'edit';
205 $query['redlink'] = '1';
206 }
207
208 $queryString = array();
209 foreach( $query as $key => $val ) {
210 $queryString []= urlencode( $key ) . '=' . urlencode( $val );
211 }
212 $queryString = implode( '&', $queryString );
213
214 if( $target->isExternal() ) {
215 return $target->getFullURL( $queryString );
216 }
217 return $target->getLocalURL( $queryString );
218 }
219
220 private function linkAttribs( $target, $attribs, $options ) {
221 global $wgUser;
222 $defaults = array();
223
224 # First get a default title attribute.
225 if( in_array( 'known', $options ) ) {
226 $defaults['title'] = $target->getPrefixedText();
227 } else {
228 $defaults['title'] = wfMsg( 'red-link-title', $target->getPrefixedText() );
229 }
230
231 if( !in_array( 'noclasses', $options ) ) {
232 # Now build the classes. This is the bulk of what we're doing.
233 $classes = array();
234
235 if( in_array( 'broken', $options ) ) {
236 $classes []= 'new';
237 }
238
239 # Note that redirects never count as stubs here.
240 if ( $target->isRedirect() ) {
241 $classes []= 'mw-redirect';
242 } elseif( $target->isContentPage() ) {
243 $threshold = $wgUser->getOption( 'stubthreshold' );
244 if( $threshold > 0 and $target->getLength() < $threshold ) {
245 $classes []= 'stub';
246 }
247 }
248 if( $classes != array() ) {
249 $defaults['class'] = implode( ' ', $classes );
250 }
251 }
252
253 # Finally, merge the custom attribs with the default ones, and iterate
254 # over that, deleting all "false" attributes.
255 if( !empty( $attribs['class'] ) and !empty( $defaults['class'] ) ) {
256 $attribs['class'] .= ' '.$defaults['class'];
257 }
258 $ret = array();
259 foreach( array_merge( $defaults, $attribs ) as $key => $val ) {
260 if( $key != 'href' and $val !== false ) {
261 $ret[$key] = $val;
262 }
263 }
264 return $ret;
265 }
266
267 private function linkText( $target, $options ) {
268 # If the target is just a fragment, with no title, we return the frag-
269 # ment text. Otherwise, we return the title text itself.
270 if( $target->getPrefixedText() === '' and $target->getFragment() !== '' ) {
271 return htmlspecialchars( $target->getFragment() );
272 }
273 return htmlspecialchars( $target->getPrefixedText() );
274 }
275
276 /**
277 * This function is a shortcut to makeLinkObj(Title::newFromText($title),...). Do not call
278 * it if you already have a title object handy. See makeLinkObj for further documentation.
279 *
280 * @param $title String: the text of the title
281 * @param $text String: link text
282 * @param $query String: optional query part
283 * @param $trail String: optional trail. Alphabetic characters at the start of this string will
284 * be included in the link text. Other characters will be appended after
285 * the end of the link.
286 */
287 function makeLink( $title, $text = '', $query = '', $trail = '' ) {
288 wfProfileIn( __METHOD__ );
289 $nt = Title::newFromText( $title );
290 if ( $nt instanceof Title ) {
291 $result = $this->makeLinkObj( $nt, $text, $query, $trail );
292 } else {
293 wfDebug( 'Invalid title passed to Linker::makeLink(): "'.$title."\"\n" );
294 $result = $text == "" ? $title : $text;
295 }
296
297 wfProfileOut( __METHOD__ );
298 return $result;
299 }
300
301 /**
302 * This function is a shortcut to makeKnownLinkObj(Title::newFromText($title),...). Do not call
303 * it if you already have a title object handy. See makeKnownLinkObj for further documentation.
304 *
305 * @param $title String: the text of the title
306 * @param $text String: link text
307 * @param $query String: optional query part
308 * @param $trail String: optional trail. Alphabetic characters at the start of this string will
309 * be included in the link text. Other characters will be appended after
310 * the end of the link.
311 */
312 function makeKnownLink( $title, $text = '', $query = '', $trail = '', $prefix = '',$aprops = '') {
313 $nt = Title::newFromText( $title );
314 if ( $nt instanceof Title ) {
315 return $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix , $aprops );
316 } else {
317 wfDebug( 'Invalid title passed to Linker::makeKnownLink(): "'.$title."\"\n" );
318 return $text == '' ? $title : $text;
319 }
320 }
321
322 /**
323 * This function is a shortcut to makeBrokenLinkObj(Title::newFromText($title),...). Do not call
324 * it if you already have a title object handy. See makeBrokenLinkObj for further documentation.
325 *
326 * @param string $title The text of the title
327 * @param string $text Link text
328 * @param string $query Optional query part
329 * @param string $trail Optional trail. Alphabetic characters at the start of this string will
330 * be included in the link text. Other characters will be appended after
331 * the end of the link.
332 */
333 function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) {
334 $nt = Title::newFromText( $title );
335 if ( $nt instanceof Title ) {
336 return $this->makeBrokenLinkObj( $nt, $text, $query, $trail );
337 } else {
338 wfDebug( 'Invalid title passed to Linker::makeBrokenLink(): "'.$title."\"\n" );
339 return $text == '' ? $title : $text;
340 }
341 }
342
343 /**
344 * @deprecated use makeColouredLinkObj
345 *
346 * This function is a shortcut to makeStubLinkObj(Title::newFromText($title),...). Do not call
347 * it if you already have a title object handy. See makeStubLinkObj for further documentation.
348 *
349 * @param $title String: the text of the title
350 * @param $text String: link text
351 * @param $query String: optional query part
352 * @param $trail String: optional trail. Alphabetic characters at the start of this string will
353 * be included in the link text. Other characters will be appended after
354 * the end of the link.
355 */
356 function makeStubLink( $title, $text = '', $query = '', $trail = '' ) {
357 $nt = Title::newFromText( $title );
358 if ( $nt instanceof Title ) {
359 return $this->makeStubLinkObj( $nt, $text, $query, $trail );
360 } else {
361 wfDebug( 'Invalid title passed to Linker::makeStubLink(): "'.$title."\"\n" );
362 return $text == '' ? $title : $text;
363 }
364 }
365
366 /**
367 * Make a link for a title which may or may not be in the database. If you need to
368 * call this lots of times, pre-fill the link cache with a LinkBatch, otherwise each
369 * call to this will result in a DB query.
370 *
371 * @param $nt Title: the title object to make the link from, e.g. from
372 * Title::newFromText.
373 * @param $text String: link text
374 * @param $query String: optional query part
375 * @param $trail String: optional trail. Alphabetic characters at the start of this string will
376 * be included in the link text. Other characters will be appended after
377 * the end of the link.
378 * @param $prefix String: optional prefix. As trail, only before instead of after.
379 */
380 function makeLinkObj( Title $nt, $text= '', $query = '', $trail = '', $prefix = '' ) {
381 global $wgUser;
382 wfProfileIn( __METHOD__ );
383
384 if ( $nt->isExternal() ) {
385 $u = $nt->getFullURL();
386 $link = $nt->getPrefixedURL();
387 if ( '' == $text ) { $text = $nt->getPrefixedText(); }
388 $style = $this->getInterwikiLinkAttributes( $link, $text, 'extiw' );
389
390 $inside = '';
391 if ( '' != $trail ) {
392 $m = array();
393 if ( preg_match( '/^([a-z]+)(.*)$$/sD', $trail, $m ) ) {
394 $inside = $m[1];
395 $trail = $m[2];
396 }
397 }
398 $t = "<a href=\"{$u}\"{$style}>{$text}{$inside}</a>";
399
400 wfProfileOut( __METHOD__ );
401 return $t;
402 } elseif ( $nt->isAlwaysKnown() ) {
403 # Image links, special page links and self-links with fragments are always known.
404 $retVal = $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix );
405 } else {
406 wfProfileIn( __METHOD__.'-immediate' );
407
408 # Handles links to special pages which do not exist in the database:
409 if( $nt->getNamespace() == NS_SPECIAL ) {
410 if( SpecialPage::exists( $nt->getDBkey() ) ) {
411 $retVal = $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix );
412 } else {
413 $retVal = $this->makeBrokenLinkObj( $nt, $text, $query, $trail, $prefix );
414 }
415 wfProfileOut( __METHOD__.'-immediate' );
416 wfProfileOut( __METHOD__ );
417 return $retVal;
418 }
419
420 # Work out link colour immediately
421 $aid = $nt->getArticleID() ;
422 if ( 0 == $aid ) {
423 $retVal = $this->makeBrokenLinkObj( $nt, $text, $query, $trail, $prefix );
424 } else {
425 $colour = '';
426 if ( $nt->isContentPage() ) {
427 $threshold = $wgUser->getOption('stubthreshold');
428 $colour = $this->getLinkColour( $nt, $threshold );
429 }
430 $retVal = $this->makeColouredLinkObj( $nt, $colour, $text, $query, $trail, $prefix );
431 }
432 wfProfileOut( __METHOD__.'-immediate' );
433 }
434 wfProfileOut( __METHOD__ );
435 return $retVal;
436 }
437
438 /**
439 * Make a link for a title which definitely exists. This is faster than makeLinkObj because
440 * it doesn't have to do a database query. It's also valid for interwiki titles and special
441 * pages.
442 *
443 * @param $nt Title object of target page
444 * @param $text String: text to replace the title
445 * @param $query String: link target
446 * @param $trail String: text after link
447 * @param $prefix String: text before link text
448 * @param $aprops String: extra attributes to the a-element
449 * @param $style String: style to apply - if empty, use getInternalLinkAttributesObj instead
450 * @return the a-element
451 */
452 function makeKnownLinkObj( Title $title, $text = '', $query = '', $trail = '', $prefix = '' , $aprops = '', $style = '' ) {
453 wfProfileIn( __METHOD__ );
454
455 $nt = $this->normaliseSpecialPage( $title );
456
457 $u = $nt->escapeLocalURL( $query );
458 if ( $nt->getFragment() != '' ) {
459 if( $nt->getPrefixedDbkey() == '' ) {
460 $u = '';
461 if ( '' == $text ) {
462 $text = htmlspecialchars( $nt->getFragment() );
463 }
464 }
465 $u .= $nt->getFragmentForURL();
466 }
467 if ( $text == '' ) {
468 $text = htmlspecialchars( $nt->getPrefixedText() );
469 }
470 if ( $style == '' ) {
471 $style = $this->getInternalLinkAttributesObj( $nt, $text );
472 }
473
474 if ( $aprops !== '' ) $aprops = " $aprops";
475
476 list( $inside, $trail ) = Linker::splitTrail( $trail );
477 $r = "<a href=\"{$u}\"{$style}{$aprops}>{$prefix}{$text}{$inside}</a>{$trail}";
478 wfProfileOut( __METHOD__ );
479 return $r;
480 }
481
482 /**
483 * Make a red link to the edit page of a given title.
484 *
485 * @param $nt Title object of the target page
486 * @param $text String: Link text
487 * @param $query String: Optional query part
488 * @param $trail String: Optional trail. Alphabetic characters at the start of this string will
489 * be included in the link text. Other characters will be appended after
490 * the end of the link.
491 */
492 function makeBrokenLinkObj( Title $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
493 wfProfileIn( __METHOD__ );
494
495 $nt = $this->normaliseSpecialPage( $title );
496
497 if( $nt->getNamespace() == NS_SPECIAL ) {
498 $q = $query;
499 } else if ( '' == $query ) {
500 $q = 'action=edit&redlink=1';
501 } else {
502 $q = 'action=edit&redlink=1&'.$query;
503 }
504 $u = $nt->escapeLocalURL( $q );
505
506 $titleText = $nt->getPrefixedText();
507 if ( '' == $text ) {
508 $text = htmlspecialchars( $titleText );
509 }
510 $titleAttr = wfMsg( 'red-link-title', $titleText );
511 $style = $this->getInternalLinkAttributesObj( $nt, $text, 'new', $titleAttr );
512 list( $inside, $trail ) = Linker::splitTrail( $trail );
513
514 wfRunHooks( 'BrokenLink', array( &$this, $nt, $query, &$u, &$style, &$prefix, &$text, &$inside, &$trail ) );
515 $s = "<a href=\"{$u}\"{$style}>{$prefix}{$text}{$inside}</a>{$trail}";
516
517 wfProfileOut( __METHOD__ );
518 return $s;
519 }
520
521 /**
522 * @deprecated use makeColouredLinkObj
523 *
524 * Make a brown link to a short article.
525 *
526 * @param $nt Title object of the target page
527 * @param $text String: link text
528 * @param $query String: optional query part
529 * @param $trail String: optional trail. Alphabetic characters at the start of this string will
530 * be included in the link text. Other characters will be appended after
531 * the end of the link.
532 */
533 function makeStubLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
534 wfDeprecated( __METHOD__ );
535 return $this->makeColouredLinkObj( $nt, 'stub', $text, $query, $trail, $prefix );
536 }
537
538 /**
539 * Make a coloured link.
540 *
541 * @param $nt Title object of the target page
542 * @param $colour Integer: colour of the link
543 * @param $text String: link text
544 * @param $query String: optional query part
545 * @param $trail String: optional trail. Alphabetic characters at the start of this string will
546 * be included in the link text. Other characters will be appended after
547 * the end of the link.
548 */
549 function makeColouredLinkObj( $nt, $colour, $text = '', $query = '', $trail = '', $prefix = '' ) {
550 if($colour != ''){
551 $style = $this->getInternalLinkAttributesObj( $nt, $text, $colour );
552 } else $style = '';
553 return $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix, '', $style );
554 }
555
556 /**
557 * Generate either a normal exists-style link or a stub link, depending
558 * on the given page size.
559 *
560 * @param $size Integer
561 * @param $nt Title object.
562 * @param $text String
563 * @param $query String
564 * @param $trail String
565 * @param $prefix String
566 * @return string HTML of link
567 */
568 function makeSizeLinkObj( $size, $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
569 global $wgUser;
570 $threshold = intval( $wgUser->getOption( 'stubthreshold' ) );
571 $colour = ( $size < $threshold ) ? 'stub' : '';
572 return $this->makeColouredLinkObj( $nt, $colour, $text, $query, $trail, $prefix );
573 }
574
575 /**
576 * Make appropriate markup for a link to the current article. This is currently rendered
577 * as the bold link text. The calling sequence is the same as the other make*LinkObj functions,
578 * despite $query not being used.
579 */
580 function makeSelfLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
581 if ( '' == $text ) {
582 $text = htmlspecialchars( $nt->getPrefixedText() );
583 }
584 list( $inside, $trail ) = Linker::splitTrail( $trail );
585 return "<strong class=\"selflink\">{$prefix}{$text}{$inside}</strong>{$trail}";
586 }
587
588 function normaliseSpecialPage( Title $title ) {
589 if ( $title->getNamespace() == NS_SPECIAL ) {
590 list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $title->getDBkey() );
591 if ( !$name ) return $title;
592 return SpecialPage::getTitleFor( $name, $subpage );
593 } else {
594 return $title;
595 }
596 }
597
598 /** @todo document */
599 function fnamePart( $url ) {
600 $basename = strrchr( $url, '/' );
601 if ( false === $basename ) {
602 $basename = $url;
603 } else {
604 $basename = substr( $basename, 1 );
605 }
606 return $basename;
607 }
608
609 /** Obsolete alias */
610 function makeImage( $url, $alt = '' ) {
611 wfDeprecated( __METHOD__ );
612 return $this->makeExternalImage( $url, $alt );
613 }
614
615 /** @todo document */
616 function makeExternalImage( $url, $alt = '' ) {
617 if ( '' == $alt ) {
618 $alt = $this->fnamePart( $url );
619 }
620 $img = '';
621 $success = wfRunHooks('LinkerMakeExternalImage', array( &$url, &$alt, &$img ) );
622 if(!$success) {
623 wfDebug("Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}", true);
624 return $img;
625 }
626 return Xml::element( 'img',
627 array(
628 'src' => $url,
629 'alt' => $alt ) );
630 }
631
632 /**
633 * Creates the HTML source for images
634 * @deprecated use makeImageLink2
635 *
636 * @param object $title
637 * @param string $label label text
638 * @param string $alt alt text
639 * @param string $align horizontal alignment: none, left, center, right)
640 * @param array $handlerParams Parameters to be passed to the media handler
641 * @param boolean $framed shows image in original size in a frame
642 * @param boolean $thumb shows image as thumbnail in a frame
643 * @param string $manualthumb image name for the manual thumbnail
644 * @param string $valign vertical alignment: baseline, sub, super, top, text-top, middle, bottom, text-bottom
645 * @param string $time, timestamp of the file, set as false for current
646 * @return string
647 */
648 function makeImageLinkObj( $title, $label, $alt, $align = '', $handlerParams = array(), $framed = false,
649 $thumb = false, $manualthumb = '', $valign = '', $time = false )
650 {
651 $frameParams = array( 'alt' => $alt, 'caption' => $label );
652 if ( $align ) {
653 $frameParams['align'] = $align;
654 }
655 if ( $framed ) {
656 $frameParams['framed'] = true;
657 }
658 if ( $thumb ) {
659 $frameParams['thumbnail'] = true;
660 }
661 if ( $manualthumb ) {
662 $frameParams['manualthumb'] = $manualthumb;
663 }
664 if ( $valign ) {
665 $frameParams['valign'] = $valign;
666 }
667 $file = wfFindFile( $title, $time );
668 return $this->makeImageLink2( $title, $file, $frameParams, $handlerParams, $time );
669 }
670
671 /**
672 * Given parameters derived from [[Image:Foo|options...]], generate the
673 * HTML that that syntax inserts in the page.
674 *
675 * @param Title $title Title object
676 * @param File $file File object, or false if it doesn't exist
677 *
678 * @param array $frameParams Associative array of parameters external to the media handler.
679 * Boolean parameters are indicated by presence or absence, the value is arbitrary and
680 * will often be false.
681 * thumbnail If present, downscale and frame
682 * manualthumb Image name to use as a thumbnail, instead of automatic scaling
683 * framed Shows image in original size in a frame
684 * frameless Downscale but don't frame
685 * upright If present, tweak default sizes for portrait orientation
686 * upright_factor Fudge factor for "upright" tweak (default 0.75)
687 * border If present, show a border around the image
688 * align Horizontal alignment (left, right, center, none)
689 * valign Vertical alignment (baseline, sub, super, top, text-top, middle,
690 * bottom, text-bottom)
691 * alt Alternate text for image (i.e. alt attribute). Plain text.
692 * caption HTML for image caption.
693 *
694 * @param array $handlerParams Associative array of media handler parameters, to be passed
695 * to transform(). Typical keys are "width" and "page".
696 * @param string $time, timestamp of the file, set as false for current
697 * @param string $query, query params for desc url
698 * @return string HTML for an image, with links, wrappers, etc.
699 */
700 function makeImageLink2( Title $title, $file, $frameParams = array(), $handlerParams = array(), $time = false, $query = "" ) {
701 $res = null;
702 if( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$this, &$title,
703 &$file, &$frameParams, &$handlerParams, &$time, &$res ) ) ) {
704 return $res;
705 }
706
707 global $wgContLang, $wgUser, $wgThumbLimits, $wgThumbUpright;
708 if ( $file && !$file->allowInlineDisplay() ) {
709 wfDebug( __METHOD__.': '.$title->getPrefixedDBkey()." does not allow inline display\n" );
710 return $this->link( $title );
711 }
712
713 // Shortcuts
714 $fp =& $frameParams;
715 $hp =& $handlerParams;
716
717 // Clean up parameters
718 $page = isset( $hp['page'] ) ? $hp['page'] : false;
719 if ( !isset( $fp['align'] ) ) $fp['align'] = '';
720 if ( !isset( $fp['alt'] ) ) $fp['alt'] = '';
721
722 $prefix = $postfix = '';
723
724 if ( 'center' == $fp['align'] )
725 {
726 $prefix = '<div class="center">';
727 $postfix = '</div>';
728 $fp['align'] = 'none';
729 }
730 if ( $file && !isset( $hp['width'] ) ) {
731 $hp['width'] = $file->getWidth( $page );
732
733 if( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
734 $wopt = $wgUser->getOption( 'thumbsize' );
735
736 if( !isset( $wgThumbLimits[$wopt] ) ) {
737 $wopt = User::getDefaultOption( 'thumbsize' );
738 }
739
740 // Reduce width for upright images when parameter 'upright' is used
741 if ( isset( $fp['upright'] ) && $fp['upright'] == 0 ) {
742 $fp['upright'] = $wgThumbUpright;
743 }
744 // Use width which is smaller: real image width or user preference width
745 // For caching health: If width scaled down due to upright parameter, round to full __0 pixel to avoid the creation of a lot of odd thumbs
746 $prefWidth = isset( $fp['upright'] ) ?
747 round( $wgThumbLimits[$wopt] * $fp['upright'], -1 ) :
748 $wgThumbLimits[$wopt];
749 if ( $hp['width'] <= 0 || $prefWidth < $hp['width'] ) {
750 $hp['width'] = $prefWidth;
751 }
752 }
753 }
754
755 if ( isset( $fp['thumbnail'] ) || isset( $fp['manualthumb'] ) || isset( $fp['framed'] ) ) {
756
757 # Create a thumbnail. Alignment depends on language
758 # writing direction, # right aligned for left-to-right-
759 # languages ("Western languages"), left-aligned
760 # for right-to-left-languages ("Semitic languages")
761 #
762 # If thumbnail width has not been provided, it is set
763 # to the default user option as specified in Language*.php
764 if ( $fp['align'] == '' ) {
765 $fp['align'] = $wgContLang->isRTL() ? 'left' : 'right';
766 }
767 return $prefix.$this->makeThumbLink2( $title, $file, $fp, $hp, $time, $query ).$postfix;
768 }
769
770 if ( $file && isset( $fp['frameless'] ) ) {
771 $srcWidth = $file->getWidth( $page );
772 # For "frameless" option: do not present an image bigger than the source (for bitmap-style images)
773 # This is the same behaviour as the "thumb" option does it already.
774 if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
775 $hp['width'] = $srcWidth;
776 }
777 }
778
779 if ( $file && $hp['width'] ) {
780 # Create a resized image, without the additional thumbnail features
781 $thumb = $file->transform( $hp );
782 } else {
783 $thumb = false;
784 }
785
786 if ( !$thumb ) {
787 $s = $this->makeBrokenImageLinkObj( $title, '', '', '', '', $time==true );
788 } else {
789 $s = $thumb->toHtml( array(
790 'desc-link' => true,
791 'desc-query' => $query,
792 'alt' => $fp['alt'],
793 'valign' => isset( $fp['valign'] ) ? $fp['valign'] : false ,
794 'img-class' => isset( $fp['border'] ) ? 'thumbborder' : false ) );
795 }
796 if ( '' != $fp['align'] ) {
797 $s = "<div class=\"float{$fp['align']}\"><span>{$s}</span></div>";
798 }
799 return str_replace("\n", ' ',$prefix.$s.$postfix);
800 }
801
802 /**
803 * Make HTML for a thumbnail including image, border and caption
804 * @param Title $title
805 * @param File $file File object or false if it doesn't exist
806 */
807 function makeThumbLinkObj( Title $title, $file, $label = '', $alt, $align = 'right', $params = array(), $framed=false , $manualthumb = "" ) {
808 $frameParams = array(
809 'alt' => $alt,
810 'caption' => $label,
811 'align' => $align
812 );
813 if ( $framed ) $frameParams['framed'] = true;
814 if ( $manualthumb ) $frameParams['manualthumb'] = $manualthumb;
815 return $this->makeThumbLink2( $title, $file, $frameParams, $params );
816 }
817
818 function makeThumbLink2( Title $title, $file, $frameParams = array(), $handlerParams = array(), $time = false, $query = "" ) {
819 global $wgStylePath, $wgContLang;
820 $exists = $file && $file->exists();
821
822 # Shortcuts
823 $fp =& $frameParams;
824 $hp =& $handlerParams;
825
826 $page = isset( $hp['page'] ) ? $hp['page'] : false;
827 if ( !isset( $fp['align'] ) ) $fp['align'] = 'right';
828 if ( !isset( $fp['alt'] ) ) $fp['alt'] = '';
829 if ( !isset( $fp['caption'] ) ) $fp['caption'] = '';
830
831 if ( empty( $hp['width'] ) ) {
832 // Reduce width for upright images when parameter 'upright' is used
833 $hp['width'] = isset( $fp['upright'] ) ? 130 : 180;
834 }
835 $thumb = false;
836
837 if ( !$exists ) {
838 $outerWidth = $hp['width'] + 2;
839 } else {
840 if ( isset( $fp['manualthumb'] ) ) {
841 # Use manually specified thumbnail
842 $manual_title = Title::makeTitleSafe( NS_IMAGE, $fp['manualthumb'] );
843 if( $manual_title ) {
844 $manual_img = wfFindFile( $manual_title );
845 if ( $manual_img ) {
846 $thumb = $manual_img->getUnscaledThumb();
847 } else {
848 $exists = false;
849 }
850 }
851 } elseif ( isset( $fp['framed'] ) ) {
852 // Use image dimensions, don't scale
853 $thumb = $file->getUnscaledThumb( $page );
854 } else {
855 # Do not present an image bigger than the source, for bitmap-style images
856 # This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour
857 $srcWidth = $file->getWidth( $page );
858 if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
859 $hp['width'] = $srcWidth;
860 }
861 $thumb = $file->transform( $hp );
862 }
863
864 if ( $thumb ) {
865 $outerWidth = $thumb->getWidth() + 2;
866 } else {
867 $outerWidth = $hp['width'] + 2;
868 }
869 }
870
871 if( $page ) {
872 $query = $query ? '&page=' . urlencode( $page ) : 'page=' . urlencode( $page );
873 }
874 $url = $title->getLocalURL( $query );
875
876 $more = htmlspecialchars( wfMsg( 'thumbnail-more' ) );
877
878 $s = "<div class=\"thumb t{$fp['align']}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
879 if( !$exists ) {
880 $s .= $this->makeBrokenImageLinkObj( $title, '', '', '', '', $time==true );
881 $zoomicon = '';
882 } elseif ( !$thumb ) {
883 $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
884 $zoomicon = '';
885 } else {
886 $s .= $thumb->toHtml( array(
887 'alt' => $fp['alt'],
888 'img-class' => 'thumbimage',
889 'desc-link' => true,
890 'desc-query' => $query ) );
891 if ( isset( $fp['framed'] ) ) {
892 $zoomicon="";
893 } else {
894 $zoomicon = '<div class="magnify">'.
895 '<a href="'.$url.'" class="internal" title="'.$more.'">'.
896 '<img src="'.$wgStylePath.'/common/images/magnify-clip.png" ' .
897 'width="15" height="11" alt="" /></a></div>';
898 }
899 }
900 $s .= ' <div class="thumbcaption">'.$zoomicon.$fp['caption']."</div></div></div>";
901 return str_replace("\n", ' ', $s);
902 }
903
904 /**
905 * Make a "broken" link to an image
906 *
907 * @param Title $title Image title
908 * @param string $text Link label
909 * @param string $query Query string
910 * @param string $trail Link trail
911 * @param string $prefix Link prefix
912 * @param bool $time, a file of a certain timestamp was requested
913 * @return string
914 */
915 public function makeBrokenImageLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '', $time = false ) {
916 global $wgEnableUploads;
917 if( $title instanceof Title ) {
918 wfProfileIn( __METHOD__ );
919 $currentExists = $time ? ( wfFindFile( $title ) != false ) : false;
920 if( $wgEnableUploads && !$currentExists ) {
921 $upload = SpecialPage::getTitleFor( 'Upload' );
922 if( $text == '' )
923 $text = htmlspecialchars( $title->getPrefixedText() );
924 $redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
925 if( $redir ) {
926 return $this->makeKnownLinkObj( $title, $text, $query, $trail, $prefix );
927 }
928 $q = 'wpDestFile=' . $title->getPartialUrl();
929 if( $query != '' )
930 $q .= '&' . $query;
931 list( $inside, $trail ) = self::splitTrail( $trail );
932 $style = $this->getInternalLinkAttributesObj( $title, $text, 'new' );
933 wfProfileOut( __METHOD__ );
934 return '<a href="' . $upload->escapeLocalUrl( $q ) . '"'
935 . $style . '>' . $prefix . $text . $inside . '</a>' . $trail;
936 } else {
937 wfProfileOut( __METHOD__ );
938 return $this->makeKnownLinkObj( $title, $text, $query, $trail, $prefix );
939 }
940 } else {
941 return "<!-- ERROR -->{$prefix}{$text}{$trail}";
942 }
943 }
944
945 /** @deprecated use Linker::makeMediaLinkObj() */
946 function makeMediaLink( $name, $unused = '', $text = '', $time = false ) {
947 $nt = Title::makeTitleSafe( NS_IMAGE, $name );
948 return $this->makeMediaLinkObj( $nt, $text, $time );
949 }
950
951 /**
952 * Create a direct link to a given uploaded file.
953 *
954 * @param $title Title object.
955 * @param $text String: pre-sanitized HTML
956 * @param $time string: time image was created
957 * @return string HTML
958 *
959 * @public
960 * @todo Handle invalid or missing images better.
961 */
962 function makeMediaLinkObj( $title, $text = '', $time = false ) {
963 if( is_null( $title ) ) {
964 ### HOTFIX. Instead of breaking, return empty string.
965 return $text;
966 } else {
967 $img = wfFindFile( $title, $time );
968 if( $img ) {
969 $url = $img->getURL();
970 $class = 'internal';
971 } else {
972 $upload = SpecialPage::getTitleFor( 'Upload' );
973 $url = $upload->getLocalUrl( 'wpDestFile=' . urlencode( $title->getDBkey() ) );
974 $class = 'new';
975 }
976 $alt = htmlspecialchars( $title->getText() );
977 if( $text == '' ) {
978 $text = $alt;
979 }
980 $u = htmlspecialchars( $url );
981 return "<a href=\"{$u}\" class=\"$class\" title=\"{$alt}\">{$text}</a>";
982 }
983 }
984
985 /** @todo document */
986 function specialLink( $name, $key = '' ) {
987 global $wgContLang;
988
989 if ( '' == $key ) { $key = strtolower( $name ); }
990 $pn = $wgContLang->ucfirst( $name );
991 return $this->makeKnownLink( $wgContLang->specialPage( $pn ),
992 wfMsg( $key ) );
993 }
994
995 /** @todo document */
996 function makeExternalLink( $url, $text, $escape = true, $linktype = '', $ns = null ) {
997 $style = $this->getExternalLinkAttributes( $url, $text, 'external ' . $linktype );
998 global $wgNoFollowLinks, $wgNoFollowNsExceptions;
999 if( $wgNoFollowLinks && !(isset($ns) && in_array($ns, $wgNoFollowNsExceptions)) ) {
1000 $style .= ' rel="nofollow"';
1001 }
1002 $url = htmlspecialchars( $url );
1003 if( $escape ) {
1004 $text = htmlspecialchars( $text );
1005 }
1006 $link = '';
1007 $success = wfRunHooks('LinkerMakeExternalLink', array( &$url, &$text, &$link ) );
1008 if(!$success) {
1009 wfDebug("Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}", true);
1010 return $link;
1011 }
1012 return '<a href="'.$url.'"'.$style.'>'.$text.'</a>';
1013 }
1014
1015 /**
1016 * Make user link (or user contributions for unregistered users)
1017 * @param $userId Integer: user id in database.
1018 * @param $userText String: user name in database
1019 * @return string HTML fragment
1020 * @private
1021 */
1022 function userLink( $userId, $userText ) {
1023 $encName = htmlspecialchars( $userText );
1024 if( $userId == 0 ) {
1025 $page = SpecialPage::getTitleFor( 'Contributions', $userText );
1026 } else {
1027 $page = Title::makeTitle( NS_USER, $userText );
1028 }
1029 return $this->link( $page, $encName );
1030 }
1031
1032 /**
1033 * Generate standard user tool links (talk, contributions, block link, etc.)
1034 *
1035 * @param int $userId User identifier
1036 * @param string $userText User name or IP address
1037 * @param bool $redContribsWhenNoEdits Should the contributions link be red if the user has no edits?
1038 * @param int $flags Customisation flags (e.g. self::TOOL_LINKS_NOBLOCK)
1039 * @param int $edits, user edit count (optional, for performance)
1040 * @return string
1041 */
1042 public function userToolLinks( $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits=null ) {
1043 global $wgUser, $wgDisableAnonTalk, $wgSysopUserBans;
1044 $talkable = !( $wgDisableAnonTalk && 0 == $userId );
1045 $blockable = ( $wgSysopUserBans || 0 == $userId ) && !$flags & self::TOOL_LINKS_NOBLOCK;
1046
1047 $items = array();
1048 if( $talkable ) {
1049 $items[] = $this->userTalkLink( $userId, $userText );
1050 }
1051 if( $userId ) {
1052 // check if the user has an edit
1053 $attribs = array();
1054 if( $redContribsWhenNoEdits ) {
1055 $count = !is_null($edits) ? $edits : User::edits( $userId );
1056 if( $count == 0 ) {
1057 $attribs['class'] = 'new';
1058 }
1059 }
1060 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
1061
1062 $items[] = $this->link( $contribsPage, wfMsgHtml( 'contribslink' ), $attribs );
1063 }
1064 if( $blockable && $wgUser->isAllowed( 'block' ) ) {
1065 $items[] = $this->blockLink( $userId, $userText );
1066 }
1067
1068 if( $items ) {
1069 return ' (' . implode( ' | ', $items ) . ')';
1070 } else {
1071 return '';
1072 }
1073 }
1074
1075 /**
1076 * Alias for userToolLinks( $userId, $userText, true );
1077 * @param int $userId User identifier
1078 * @param string $userText User name or IP address
1079 * @param int $edits, user edit count (optional, for performance)
1080 */
1081 public function userToolLinksRedContribs( $userId, $userText, $edits=null ) {
1082 return $this->userToolLinks( $userId, $userText, true, 0, $edits );
1083 }
1084
1085
1086 /**
1087 * @param $userId Integer: user id in database.
1088 * @param $userText String: user name in database.
1089 * @return string HTML fragment with user talk link
1090 * @private
1091 */
1092 function userTalkLink( $userId, $userText ) {
1093 $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
1094 $userTalkLink = $this->link( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) );
1095 return $userTalkLink;
1096 }
1097
1098 /**
1099 * @param $userId Integer: userid
1100 * @param $userText String: user name in database.
1101 * @return string HTML fragment with block link
1102 * @private
1103 */
1104 function blockLink( $userId, $userText ) {
1105 $blockPage = SpecialPage::getTitleFor( 'Blockip', $userText );
1106 $blockLink = $this->link( $blockPage, wfMsgHtml( 'blocklink' ) );
1107 return $blockLink;
1108 }
1109
1110 /**
1111 * Generate a user link if the current user is allowed to view it
1112 * @param $rev Revision object.
1113 * @param $isPublic, bool, show only if all users can see it
1114 * @return string HTML
1115 */
1116 function revUserLink( $rev, $isPublic = false ) {
1117 if( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
1118 $link = wfMsgHtml( 'rev-deleted-user' );
1119 } else if( $rev->userCan( Revision::DELETED_USER ) ) {
1120 $link = $this->userLink( $rev->getRawUser(), $rev->getRawUserText() );
1121 } else {
1122 $link = wfMsgHtml( 'rev-deleted-user' );
1123 }
1124 if( $rev->isDeleted( Revision::DELETED_USER ) ) {
1125 return '<span class="history-deleted">' . $link . '</span>';
1126 }
1127 return $link;
1128 }
1129
1130 /**
1131 * Generate a user tool link cluster if the current user is allowed to view it
1132 * @param $rev Revision object.
1133 * @param $isPublic, bool, show only if all users can see it
1134 * @return string HTML
1135 */
1136 function revUserTools( $rev, $isPublic = false ) {
1137 if( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
1138 $link = wfMsgHtml( 'rev-deleted-user' );
1139 } else if( $rev->userCan( Revision::DELETED_USER ) ) {
1140 $link = $this->userLink( $rev->getRawUser(), $rev->getRawUserText() ) .
1141 ' ' . $this->userToolLinks( $rev->getRawUser(), $rev->getRawUserText() );
1142 } else {
1143 $link = wfMsgHtml( 'rev-deleted-user' );
1144 }
1145 if( $rev->isDeleted( Revision::DELETED_USER ) ) {
1146 return ' <span class="history-deleted">' . $link . '</span>';
1147 }
1148 return $link;
1149 }
1150
1151 /**
1152 * This function is called by all recent changes variants, by the page history,
1153 * and by the user contributions list. It is responsible for formatting edit
1154 * comments. It escapes any HTML in the comment, but adds some CSS to format
1155 * auto-generated comments (from section editing) and formats [[wikilinks]].
1156 *
1157 * @author Erik Moeller <moeller@scireview.de>
1158 *
1159 * Note: there's not always a title to pass to this function.
1160 * Since you can't set a default parameter for a reference, I've turned it
1161 * temporarily to a value pass. Should be adjusted further. --brion
1162 *
1163 * @param string $comment
1164 * @param mixed $title Title object (to generate link to the section in autocomment) or null
1165 * @param bool $local Whether section links should refer to local page
1166 */
1167 function formatComment($comment, $title = NULL, $local = false) {
1168 wfProfileIn( __METHOD__ );
1169
1170 # Sanitize text a bit:
1171 $comment = str_replace( "\n", " ", $comment );
1172 $comment = htmlspecialchars( $comment );
1173
1174 # Render autocomments and make links:
1175 $comment = $this->formatAutoComments( $comment, $title, $local );
1176 $comment = $this->formatLinksInComment( $comment );
1177
1178 wfProfileOut( __METHOD__ );
1179 return $comment;
1180 }
1181
1182 /**
1183 * The pattern for autogen comments is / * foo * /, which makes for
1184 * some nasty regex.
1185 * We look for all comments, match any text before and after the comment,
1186 * add a separator where needed and format the comment itself with CSS
1187 * Called by Linker::formatComment.
1188 *
1189 * @param string $comment Comment text
1190 * @param object $title An optional title object used to links to sections
1191 * @return string $comment formatted comment
1192 *
1193 * @todo Document the $local parameter.
1194 */
1195 private function formatAutocomments( $comment, $title = NULL, $local = false ) {
1196 $match = array();
1197 while (preg_match('!(.*)/\*\s*(.*?)\s*\*/(.*)!', $comment,$match)) {
1198 $pre=$match[1];
1199 $auto=$match[2];
1200 $post=$match[3];
1201 $link='';
1202 if( $title ) {
1203 $section = $auto;
1204
1205 # Generate a valid anchor name from the section title.
1206 # Hackish, but should generally work - we strip wiki
1207 # syntax, including the magic [[: that is used to
1208 # "link rather than show" in case of images and
1209 # interlanguage links.
1210 $section = str_replace( '[[:', '', $section );
1211 $section = str_replace( '[[', '', $section );
1212 $section = str_replace( ']]', '', $section );
1213 if ( $local ) {
1214 $sectionTitle = Title::newFromText( '#' . $section);
1215 } else {
1216 $sectionTitle = wfClone( $title );
1217 $sectionTitle->mFragment = $section;
1218 }
1219 $link = $this->link( $sectionTitle, wfMsgForContent( 'sectionlink' ) );
1220 }
1221 $auto = $link . $auto;
1222 if( $pre ) {
1223 # written summary $presep autocomment (summary /* section */)
1224 $auto = wfMsgExt( 'autocomment-prefix', array( 'escapenoentities', 'content' ) ) . $auto;
1225 }
1226 if( $post ) {
1227 # autocomment $postsep written summary (/* section */ summary)
1228 $auto .= wfMsgExt( 'colon-separator', array( 'escapenoentities', 'content' ) );
1229 }
1230 $auto = '<span class="autocomment">' . $auto . '</span>';
1231 $comment = $pre . $auto . $post;
1232 }
1233
1234 return $comment;
1235 }
1236
1237 /**
1238 * Formats wiki links and media links in text; all other wiki formatting
1239 * is ignored
1240 *
1241 * @fixme doesn't handle sub-links as in image thumb texts like the main parser
1242 * @param string $comment Text to format links in
1243 * @return string
1244 */
1245 public function formatLinksInComment( $comment ) {
1246 return preg_replace_callback(
1247 '/\[\[:?(.*?)(\|(.*?))*\]\]([^[]*)/',
1248 array( $this, 'formatLinksInCommentCallback' ),
1249 $comment );
1250 }
1251
1252 protected function formatLinksInCommentCallback( $match ) {
1253 global $wgContLang;
1254
1255 $medians = '(?:' . preg_quote( MWNamespace::getCanonicalName( NS_MEDIA ), '/' ) . '|';
1256 $medians .= preg_quote( $wgContLang->getNsText( NS_MEDIA ), '/' ) . '):';
1257
1258 $comment = $match[0];
1259
1260 # fix up urlencoded title texts (copied from Parser::replaceInternalLinks)
1261 if( strpos( $match[1], '%' ) !== false ) {
1262 $match[1] = str_replace( array('<', '>'), array('&lt;', '&gt;'), urldecode($match[1]) );
1263 }
1264
1265 # Handle link renaming [[foo|text]] will show link as "text"
1266 if( "" != $match[3] ) {
1267 $text = $match[3];
1268 } else {
1269 $text = $match[1];
1270 }
1271 $submatch = array();
1272 if( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
1273 # Media link; trail not supported.
1274 $linkRegexp = '/\[\[(.*?)\]\]/';
1275 $thelink = $this->makeMediaLink( $submatch[1], "", $text );
1276 } else {
1277 # Other kind of link
1278 if( preg_match( $wgContLang->linkTrail(), $match[4], $submatch ) ) {
1279 $trail = $submatch[1];
1280 } else {
1281 $trail = "";
1282 }
1283 $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
1284 if (isset($match[1][0]) && $match[1][0] == ':')
1285 $match[1] = substr($match[1], 1);
1286 $thelink = $this->makeLink( $match[1], $text, "", $trail );
1287 }
1288 $comment = preg_replace( $linkRegexp, StringUtils::escapeRegexReplacement( $thelink ), $comment, 1 );
1289
1290 return $comment;
1291 }
1292
1293 /**
1294 * Wrap a comment in standard punctuation and formatting if
1295 * it's non-empty, otherwise return empty string.
1296 *
1297 * @param string $comment
1298 * @param mixed $title Title object (to generate link to section in autocomment) or null
1299 * @param bool $local Whether section links should refer to local page
1300 *
1301 * @return string
1302 */
1303 function commentBlock( $comment, $title = NULL, $local = false ) {
1304 // '*' used to be the comment inserted by the software way back
1305 // in antiquity in case none was provided, here for backwards
1306 // compatability, acc. to brion -ævar
1307 if( $comment == '' || $comment == '*' ) {
1308 return '';
1309 } else {
1310 $formatted = $this->formatComment( $comment, $title, $local );
1311 return " <span class=\"comment\">($formatted)</span>";
1312 }
1313 }
1314
1315 /**
1316 * Wrap and format the given revision's comment block, if the current
1317 * user is allowed to view it.
1318 *
1319 * @param Revision $rev
1320 * @param bool $local Whether section links should refer to local page
1321 * @param $isPublic, show only if all users can see it
1322 * @return string HTML
1323 */
1324 function revComment( Revision $rev, $local = false, $isPublic = false ) {
1325 if( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) {
1326 $block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
1327 } else if( $rev->userCan( Revision::DELETED_COMMENT ) ) {
1328 $block = $this->commentBlock( $rev->getRawComment(), $rev->getTitle(), $local );
1329 } else {
1330 $block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
1331 }
1332 if( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
1333 return " <span class=\"history-deleted\">$block</span>";
1334 }
1335 return $block;
1336 }
1337
1338 public function formatRevisionSize( $size ) {
1339 if ( $size == 0 ) {
1340 $stxt = wfMsgExt( 'historyempty', 'parsemag' );
1341 } else {
1342 global $wgLang;
1343 $stxt = wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $size ) );
1344 $stxt = "($stxt)";
1345 }
1346 $stxt = htmlspecialchars( $stxt );
1347 return "<span class=\"history-size\">$stxt</span>";
1348 }
1349
1350 /** @todo document */
1351 function tocIndent() {
1352 return "\n<ul>";
1353 }
1354
1355 /** @todo document */
1356 function tocUnindent($level) {
1357 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level>0 ? $level : 0 );
1358 }
1359
1360 /**
1361 * parameter level defines if we are on an indentation level
1362 */
1363 function tocLine( $anchor, $tocline, $tocnumber, $level ) {
1364 return "\n<li class=\"toclevel-$level\"><a href=\"#" .
1365 $anchor . '"><span class="tocnumber">' .
1366 $tocnumber . '</span> <span class="toctext">' .
1367 $tocline . '</span></a>';
1368 }
1369
1370 /** @todo document */
1371 function tocLineEnd() {
1372 return "</li>\n";
1373 }
1374
1375 /** @todo document */
1376 function tocList($toc) {
1377 global $wgJsMimeType;
1378 $title = wfMsgHtml('toc') ;
1379 return
1380 '<table id="toc" class="toc" summary="' . $title .'"><tr><td>'
1381 . '<div id="toctitle"><h2>' . $title . "</h2></div>\n"
1382 . $toc
1383 # no trailing newline, script should not be wrapped in a
1384 # paragraph
1385 . "</ul>\n</td></tr></table>"
1386 . '<script type="' . $wgJsMimeType . '">'
1387 . ' if (window.showTocToggle) {'
1388 . ' var tocShowText = "' . wfEscapeJsString( wfMsg('showtoc') ) . '";'
1389 . ' var tocHideText = "' . wfEscapeJsString( wfMsg('hidetoc') ) . '";'
1390 . ' showTocToggle();'
1391 . ' } '
1392 . "</script>\n";
1393 }
1394
1395 /**
1396 * Used to generate section edit links that point to "other" pages
1397 * (sections that are really part of included pages).
1398 *
1399 * @param $title Title string.
1400 * @param $section Integer: section number.
1401 */
1402 public function editSectionLinkForOther( $title, $section ) {
1403 wfDeprecated( __METHOD__ );
1404 $title = Title::newFromText( $title );
1405 return $this->doEditSectionLink( $title, $section );
1406 }
1407
1408 /**
1409 * @param $nt Title object.
1410 * @param $section Integer: section number.
1411 * @param $hint Link String: title, or default if omitted or empty
1412 */
1413 public function editSectionLink( Title $nt, $section, $hint = '' ) {
1414 wfDeprecated( __METHOD__ );
1415 if( $hint === '' ) {
1416 # No way to pass an actual empty $hint here! The new interface al-
1417 # lows this, so we have to do this for compatibility.
1418 $hint = null;
1419 }
1420 return $this->doEditSectionLink( $nt, $section, $hint );
1421 }
1422
1423 /**
1424 * Create a section edit link. This supersedes editSectionLink() and
1425 * editSectionLinkForOther().
1426 *
1427 * @param $nt Title The title being linked to (may not be the same as
1428 * $wgTitle, if the section is included from a template)
1429 * @param $section string The designation of the section being pointed to,
1430 * to be included in the link, like "&section=$section"
1431 * @param $tooltip string The tooltip to use for the link: will be escaped
1432 * and wrapped in the 'editsectionhint' message
1433 * @return string HTML to use for edit link
1434 */
1435 public function doEditSectionLink( Title $nt, $section, $tooltip = null ) {
1436 $attribs = array();
1437 if( !is_null( $tooltip ) ) {
1438 $attribs['title'] = wfMsg( 'editsectionhint', $tooltip );
1439 }
1440 $url = $this->link( $nt, wfMsg('editsection'),
1441 $attribs,
1442 array( 'action' => 'edit', 'section' => $section ),
1443 array( 'noclasses', 'known' )
1444 );
1445
1446 # Run the old hook. This takes up half of the function . . . hopefully
1447 # we can rid of it someday.
1448 $attribs = '';
1449 if( $tooltip ) {
1450 $attribs = wfMsgHtml( 'editsectionhint', htmlspecialchars( $tooltip ) );
1451 $attribs = " title=\"$attribs\"";
1452 }
1453 $result = null;
1454 wfRunHooks( 'EditSectionLink', array( &$this, $nt, $section, $attribs, $url, &$result ) );
1455 if( !is_null( $result ) ) {
1456 # For reverse compatibility, add the brackets *after* the hook is
1457 # run, and even add them to hook-provided text. (This is the main
1458 # reason that the EditSectionLink hook is deprecated in favor of
1459 # DoEditSectionLink: it can't change the brackets or the span.)
1460 $result = wfMsgHtml( 'editsection-brackets', $url );
1461 return "<span class=\"editsection\">$result</span>";
1462 }
1463
1464 # Add the brackets and the span, and *then* run the nice new hook, with
1465 # clean and non-redundant arguments.
1466 $result = wfMsgHtml( 'editsection-brackets', $url );
1467 $result = "<span class=\"editsection\">$result</span>";
1468
1469 wfRunHooks( 'DoEditSectionLink', array( $this, $nt, $section, $tooltip, &$result ) );
1470 return $result;
1471 }
1472
1473 /**
1474 * Create a headline for content
1475 *
1476 * @param int $level The level of the headline (1-6)
1477 * @param string $attribs Any attributes for the headline, starting with a space and ending with '>'
1478 * This *must* be at least '>' for no attribs
1479 * @param string $anchor The anchor to give the headline (the bit after the #)
1480 * @param string $text The text of the header
1481 * @param string $link HTML to add for the section edit link
1482 *
1483 * @return string HTML headline
1484 */
1485 public function makeHeadline( $level, $attribs, $anchor, $text, $link ) {
1486 return "<a name=\"$anchor\"></a><h$level$attribs$link <span class=\"mw-headline\">$text</span></h$level>";
1487 }
1488
1489 /**
1490 * Split a link trail, return the "inside" portion and the remainder of the trail
1491 * as a two-element array
1492 *
1493 * @static
1494 */
1495 static function splitTrail( $trail ) {
1496 static $regex = false;
1497 if ( $regex === false ) {
1498 global $wgContLang;
1499 $regex = $wgContLang->linkTrail();
1500 }
1501 $inside = '';
1502 if ( '' != $trail ) {
1503 $m = array();
1504 if ( preg_match( $regex, $trail, $m ) ) {
1505 $inside = $m[1];
1506 $trail = $m[2];
1507 }
1508 }
1509 return array( $inside, $trail );
1510 }
1511
1512 /**
1513 * Generate a rollback link for a given revision. Currently it's the
1514 * caller's responsibility to ensure that the revision is the top one. If
1515 * it's not, of course, the user will get an error message.
1516 *
1517 * If the calling page is called with the parameter &bot=1, all rollback
1518 * links also get that parameter. It causes the edit itself and the rollback
1519 * to be marked as "bot" edits. Bot edits are hidden by default from recent
1520 * changes, so this allows sysops to combat a busy vandal without bothering
1521 * other users.
1522 *
1523 * @param Revision $rev
1524 */
1525 function generateRollback( $rev ) {
1526 return '<span class="mw-rollback-link">['
1527 . $this->buildRollbackLink( $rev )
1528 . ']</span>';
1529 }
1530
1531 /**
1532 * Build a raw rollback link, useful for collections of "tool" links
1533 *
1534 * @param Revision $rev
1535 * @return string
1536 */
1537 public function buildRollbackLink( $rev ) {
1538 global $wgRequest, $wgUser;
1539 $title = $rev->getTitle();
1540 $query = array( 'action' => 'rollback' );
1541 if( $wgRequest->getBool( 'bot' ) ) {
1542 $query['bot'] = '1';
1543 }
1544 $query['token'] = $wgUser->editToken( array( $title->getPrefixedText(),
1545 $rev->getUserText() ) );
1546 return $this->link( $title, wfMsgHtml( 'rollbacklink' ), array(),
1547 $query, 'known' );
1548 }
1549
1550 /**
1551 * Returns HTML for the "templates used on this page" list.
1552 *
1553 * @param array $templates Array of templates from Article::getUsedTemplate
1554 * or similar
1555 * @param bool $preview Whether this is for a preview
1556 * @param bool $section Whether this is for a section edit
1557 * @return string HTML output
1558 */
1559 public function formatTemplates( $templates, $preview = false, $section = false) {
1560 global $wgUser;
1561 wfProfileIn( __METHOD__ );
1562
1563 $sk = $wgUser->getSkin();
1564
1565 $outText = '';
1566 if ( count( $templates ) > 0 ) {
1567 # Do a batch existence check
1568 $batch = new LinkBatch;
1569 foreach( $templates as $title ) {
1570 $batch->addObj( $title );
1571 }
1572 $batch->execute();
1573
1574 # Construct the HTML
1575 $outText = '<div class="mw-templatesUsedExplanation">';
1576 if ( $preview ) {
1577 $outText .= wfMsgExt( 'templatesusedpreview', array( 'parse' ) );
1578 } elseif ( $section ) {
1579 $outText .= wfMsgExt( 'templatesusedsection', array( 'parse' ) );
1580 } else {
1581 $outText .= wfMsgExt( 'templatesused', array( 'parse' ) );
1582 }
1583 $outText .= '</div><ul>';
1584
1585 usort( $templates, array( 'Title', 'compare' ) );
1586 foreach ( $templates as $titleObj ) {
1587 $r = $titleObj->getRestrictions( 'edit' );
1588 if ( in_array( 'sysop', $r ) ) {
1589 $protected = wfMsgExt( 'template-protected', array( 'parseinline' ) );
1590 } elseif ( in_array( 'autoconfirmed', $r ) ) {
1591 $protected = wfMsgExt( 'template-semiprotected', array( 'parseinline' ) );
1592 } else {
1593 $protected = '';
1594 }
1595 $outText .= '<li>' . $sk->link( $titleObj ) . ' ' . $protected . '</li>';
1596 }
1597 $outText .= '</ul>';
1598 }
1599 wfProfileOut( __METHOD__ );
1600 return $outText;
1601 }
1602
1603 /**
1604 * Returns HTML for the "hidden categories on this page" list.
1605 *
1606 * @param array $hiddencats Array of hidden categories from Article::getHiddenCategories
1607 * or similar
1608 * @return string HTML output
1609 */
1610 public function formatHiddenCategories( $hiddencats) {
1611 global $wgUser, $wgLang;
1612 wfProfileIn( __METHOD__ );
1613
1614 $sk = $wgUser->getSkin();
1615
1616 $outText = '';
1617 if ( count( $hiddencats ) > 0 ) {
1618 # Construct the HTML
1619 $outText = '<div class="mw-hiddenCategoriesExplanation">';
1620 $outText .= wfMsgExt( 'hiddencategories', array( 'parse' ), $wgLang->formatnum( count( $hiddencats ) ) );
1621 $outText .= '</div><ul>';
1622
1623 foreach ( $hiddencats as $titleObj ) {
1624 $outText .= '<li>' . $sk->link( $titleObj, null, array(), array(), 'known' ) . '</li>'; # If it's hidden, it must exist - no need to check with a LinkBatch
1625 }
1626 $outText .= '</ul>';
1627 }
1628 wfProfileOut( __METHOD__ );
1629 return $outText;
1630 }
1631
1632 /**
1633 * Format a size in bytes for output, using an appropriate
1634 * unit (B, KB, MB or GB) according to the magnitude in question
1635 *
1636 * @param $size Size to format
1637 * @return string
1638 */
1639 public function formatSize( $size ) {
1640 global $wgLang;
1641 return htmlspecialchars( $wgLang->formatSize( $size ) );
1642 }
1643
1644 /**
1645 * Given the id of an interface element, constructs the appropriate title
1646 * and accesskey attributes from the system messages. (Note, this is usu-
1647 * ally the id but isn't always, because sometimes the accesskey needs to
1648 * go on a different element than the id, for reverse-compatibility, etc.)
1649 *
1650 * @param string $name Id of the element, minus prefixes.
1651 * @return string title and accesskey attributes, ready to drop in an
1652 * element (e.g., ' title="This does something [x]" accesskey="x"').
1653 */
1654 public function tooltipAndAccesskey($name) {
1655 $fname="Linker::tooltipAndAccesskey";
1656 wfProfileIn($fname);
1657 $out = '';
1658
1659 $tooltip = wfMsg('tooltip-'.$name);
1660 if (!wfEmptyMsg('tooltip-'.$name, $tooltip) && $tooltip != '-') {
1661 // Compatibility: formerly some tooltips had [alt-.] hardcoded
1662 $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
1663 $out .= ' title="'.htmlspecialchars($tooltip);
1664 }
1665 $accesskey = wfMsg('accesskey-'.$name);
1666 if ($accesskey && $accesskey != '-' && !wfEmptyMsg('accesskey-'.$name, $accesskey)) {
1667 if ($out) $out .= " [$accesskey]\" accesskey=\"$accesskey\"";
1668 else $out .= " title=\"[$accesskey]\" accesskey=\"$accesskey\"";
1669 } elseif ($out) {
1670 $out .= '"';
1671 }
1672 wfProfileOut($fname);
1673 return $out;
1674 }
1675
1676 /**
1677 * Given the id of an interface element, constructs the appropriate title
1678 * attribute from the system messages. (Note, this is usually the id but
1679 * isn't always, because sometimes the accesskey needs to go on a different
1680 * element than the id, for reverse-compatibility, etc.)
1681 *
1682 * @param string $name Id of the element, minus prefixes.
1683 * @return string title attribute, ready to drop in an element
1684 * (e.g., ' title="This does something"').
1685 */
1686 public function tooltip($name) {
1687 $out = '';
1688
1689 $tooltip = wfMsg('tooltip-'.$name);
1690 if (!wfEmptyMsg('tooltip-'.$name, $tooltip) && $tooltip != '-') {
1691 $out = ' title="'.htmlspecialchars($tooltip).'"';
1692 }
1693
1694 return $out;
1695 }
1696 }