]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/Linker.php
MediaWiki 1.17.1-scripts
[autoinstalls/mediawiki.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          * Get the appropriate HTML attributes to add to the "a" element of an ex-
22          * ternal link, as created by [wikisyntax].
23          *
24          * @param $class String: the contents of the class attribute; if an empty
25          *   string is passed, which is the default value, defaults to 'external'.
26          */
27         function getExternalLinkAttributes( $class = 'external' ) {
28                 return $this->getLinkAttributesInternal( '', $class );
29         }
30
31         /**
32          * Get the appropriate HTML attributes to add to the "a" element of an in-
33          * terwiki link.
34          *
35          * @param $title String: the title text for the link, URL-encoded (???) but
36          *   not HTML-escaped
37          * @param $unused String: unused
38          * @param $class String: the contents of the class attribute; if an empty
39          *   string is passed, which is the default value, defaults to 'external'.
40          */
41         function getInterwikiLinkAttributes( $title, $unused = null, $class = 'external' ) {
42                 global $wgContLang;
43
44                 # FIXME: We have a whole bunch of handling here that doesn't happen in
45                 # getExternalLinkAttributes, why?
46                 $title = urldecode( $title );
47                 $title = $wgContLang->checkTitleEncoding( $title );
48                 $title = preg_replace( '/[\\x00-\\x1f]/', ' ', $title );
49
50                 return $this->getLinkAttributesInternal( $title, $class );
51         }
52
53         /**
54          * Get the appropriate HTML attributes to add to the "a" element of an in-
55          * ternal link.
56          *
57          * @param $title String: the title text for the link, URL-encoded (???) but
58          *   not HTML-escaped
59          * @param $unused String: unused
60          * @param $class String: the contents of the class attribute, default none
61          */
62         function getInternalLinkAttributes( $title, $unused = null, $class = '' ) {
63                 $title = urldecode( $title );
64                 $title = str_replace( '_', ' ', $title );
65                 return $this->getLinkAttributesInternal( $title, $class );
66         }
67
68         /**
69          * Get the appropriate HTML attributes to add to the "a" element of an in-
70          * ternal link, given the Title object for the page we want to link to.
71          *
72          * @param $nt The Title object
73          * @param $unused String: unused
74          * @param $class String: the contents of the class attribute, default none
75          * @param $title Mixed: optional (unescaped) string to use in the title
76          *   attribute; if false, default to the name of the page we're linking to
77          */
78         function getInternalLinkAttributesObj( $nt, $unused = null, $class = '', $title = false ) {
79                 if ( $title === false ) {
80                         $title = $nt->getPrefixedText();
81                 }
82                 return $this->getLinkAttributesInternal( $title, $class );
83         }
84
85         /**
86          * Common code for getLinkAttributesX functions
87          */
88         private function getLinkAttributesInternal( $title, $class ) {
89                 $title = htmlspecialchars( $title );
90                 $class = htmlspecialchars( $class );
91                 $r = '';
92                 if ( $class != '' ) {
93                         $r .= " class=\"$class\"";
94                 }
95                 if ( $title != '' ) {
96                         $r .= " title=\"$title\"";
97                 }
98                 return $r;
99         }
100
101         /**
102          * Return the CSS colour of a known link
103          *
104          * @param $t Title object
105          * @param $threshold Integer: user defined threshold
106          * @return String: CSS class
107          */
108         function getLinkColour( $t, $threshold ) {
109                 $colour = '';
110                 if ( $t->isRedirect() ) {
111                         # Page is a redirect
112                         $colour = 'mw-redirect';
113                 } elseif ( $threshold > 0 &&
114                            $t->exists() && $t->getLength() < $threshold &&
115                            $t->isContentPage() ) {
116                         # Page is a stub
117                         $colour = 'stub';
118                 }
119                 return $colour;
120         }
121
122         /**
123          * This function returns an HTML link to the given target.  It serves a few
124          * purposes:
125          *   1) If $target is a Title, the correct URL to link to will be figured
126          *      out automatically.
127          *   2) It automatically adds the usual classes for various types of link
128          *      targets: "new" for red links, "stub" for short articles, etc.
129          *   3) It escapes all attribute values safely so there's no risk of XSS.
130          *   4) It provides a default tooltip if the target is a Title (the page
131          *      name of the target).
132          * link() replaces the old functions in the makeLink() family.
133          *
134          * @param $target        Title  Can currently only be a Title, but this may
135          *   change to support Images, literal URLs, etc.
136          * @param $text          string The HTML contents of the <a> element, i.e.,
137          *   the link text.  This is raw HTML and will not be escaped.  If null,
138          *   defaults to the prefixed text of the Title; or if the Title is just a
139          *   fragment, the contents of the fragment.
140          * @param $customAttribs array  A key => value array of extra HTML attri-
141          *   butes, such as title and class.  (href is ignored.)  Classes will be
142          *   merged with the default classes, while other attributes will replace
143          *   default attributes.  All passed attribute values will be HTML-escaped.
144          *   A false attribute value means to suppress that attribute.
145          * @param $query         array  The query string to append to the URL
146          *   you're linking to, in key => value array form.  Query keys and values
147          *   will be URL-encoded.
148          * @param $options       mixed  String or array of strings:
149          *     'known': Page is known to exist, so don't check if it does.
150          *     'broken': Page is known not to exist, so don't check if it does.
151          *     'noclasses': Don't add any classes automatically (includes "new",
152          *       "stub", "mw-redirect", "extiw").  Only use the class attribute
153          *       provided, if any, so you get a simple blue link with no funny i-
154          *       cons.
155          *     'forcearticlepath': Use the article path always, even with a querystring.
156          *       Has compatibility issues on some setups, so avoid wherever possible.
157          * @return string HTML <a> attribute
158          */
159         public function link( $target, $text = null, $customAttribs = array(), $query = array(), $options = array() ) {
160                 wfProfileIn( __METHOD__ );
161                 if ( !$target instanceof Title ) {
162                         wfProfileOut( __METHOD__ );
163                         return "<!-- ERROR -->$text";
164                 }
165                 $options = (array)$options;
166
167                 $ret = null;
168                 if ( !wfRunHooks( 'LinkBegin', array( $this, $target, &$text,
169                 &$customAttribs, &$query, &$options, &$ret ) ) ) {
170                         wfProfileOut( __METHOD__ );
171                         return $ret;
172                 }
173
174                 # Normalize the Title if it's a special page
175                 $target = $this->normaliseSpecialPage( $target );
176
177                 # If we don't know whether the page exists, let's find out.
178                 wfProfileIn( __METHOD__ . '-checkPageExistence' );
179                 if ( !in_array( 'known', $options ) and !in_array( 'broken', $options ) ) {
180                         if ( $target->isKnown() ) {
181                                 $options[] = 'known';
182                         } else {
183                                 $options[] = 'broken';
184                         }
185                 }
186                 wfProfileOut( __METHOD__ . '-checkPageExistence' );
187
188                 $oldquery = array();
189                 if ( in_array( "forcearticlepath", $options ) && $query ) {
190                         $oldquery = $query;
191                         $query = array();
192                 }
193
194                 # Note: we want the href attribute first, for prettiness.
195                 $attribs = array( 'href' => $this->linkUrl( $target, $query, $options ) );
196                 if ( in_array( 'forcearticlepath', $options ) && $oldquery ) {
197                         $attribs['href'] = wfAppendQuery( $attribs['href'], wfArrayToCgi( $oldquery ) );
198                 }
199
200                 $attribs = array_merge(
201                         $attribs,
202                         $this->linkAttribs( $target, $customAttribs, $options )
203                 );
204                 if ( is_null( $text ) ) {
205                         $text = $this->linkText( $target );
206                 }
207
208                 $ret = null;
209                 if ( wfRunHooks( 'LinkEnd', array( $this, $target, $options, &$text, &$attribs, &$ret ) ) ) {
210                         $ret = Html::rawElement( 'a', $attribs, $text );
211                 }
212
213                 wfProfileOut( __METHOD__ );
214                 return $ret;
215         }
216
217         /**
218          * Identical to link(), except $options defaults to 'known'.
219          */
220         public function linkKnown( $target, $text = null, $customAttribs = array(), $query = array(), $options = array( 'known', 'noclasses' ) ) {
221                 return $this->link( $target, $text, $customAttribs, $query, $options );
222         }
223
224         /**
225          * Returns the Url used to link to a Title
226          */
227         private function linkUrl( $target, $query, $options ) {
228                 wfProfileIn( __METHOD__ );
229                 # We don't want to include fragments for broken links, because they
230                 # generally make no sense.
231                 if ( in_array( 'broken', $options ) and $target->mFragment !== '' ) {
232                         $target = clone $target;
233                         $target->mFragment = '';
234                 }
235
236                 # If it's a broken link, add the appropriate query pieces, unless
237                 # there's already an action specified, or unless 'edit' makes no sense
238                 # (i.e., for a nonexistent special page).
239                 if ( in_array( 'broken', $options ) and empty( $query['action'] )
240                 and $target->getNamespace() != NS_SPECIAL ) {
241                         $query['action'] = 'edit';
242                         $query['redlink'] = '1';
243                 }
244                 $ret = $target->getLinkUrl( $query );
245                 wfProfileOut( __METHOD__ );
246                 return $ret;
247         }
248
249         /**
250          * Returns the array of attributes used when linking to the Title $target
251          */
252         private function linkAttribs( $target, $attribs, $options ) {
253                 wfProfileIn( __METHOD__ );
254                 global $wgUser;
255                 $defaults = array();
256
257                 if ( !in_array( 'noclasses', $options ) ) {
258                         wfProfileIn( __METHOD__ . '-getClasses' );
259                         # Now build the classes.
260                         $classes = array();
261
262                         if ( in_array( 'broken', $options ) ) {
263                                 $classes[] = 'new';
264                         }
265
266                         if ( $target->isExternal() ) {
267                                 $classes[] = 'extiw';
268                         }
269
270                         if ( !in_array( 'broken', $options ) ) { # Avoid useless calls to LinkCache (see r50387)
271                                 $colour = $this->getLinkColour( $target, $wgUser->getStubThreshold() );
272                                 if ( $colour !== '' ) {
273                                         $classes[] = $colour; # mw-redirect or stub
274                                 }
275                         }
276                         if ( $classes != array() ) {
277                                 $defaults['class'] = implode( ' ', $classes );
278                         }
279                         wfProfileOut( __METHOD__ . '-getClasses' );
280                 }
281
282                 # Get a default title attribute.
283                 if ( $target->getPrefixedText() == '' ) {
284                         # A link like [[#Foo]].  This used to mean an empty title
285                         # attribute, but that's silly.  Just don't output a title.
286                 } elseif ( in_array( 'known', $options ) ) {
287                         $defaults['title'] = $target->getPrefixedText();
288                 } else {
289                         $defaults['title'] = wfMsg( 'red-link-title', $target->getPrefixedText() );
290                 }
291
292                 # Finally, merge the custom attribs with the default ones, and iterate
293                 # over that, deleting all "false" attributes.
294                 $ret = array();
295                 $merged = Sanitizer::mergeAttributes( $defaults, $attribs );
296                 foreach ( $merged as $key => $val ) {
297                         # A false value suppresses the attribute, and we don't want the
298                         # href attribute to be overridden.
299                         if ( $key != 'href' and $val !== false ) {
300                                 $ret[$key] = $val;
301                         }
302                 }
303                 wfProfileOut( __METHOD__ );
304                 return $ret;
305         }
306
307         /**
308          * Default text of the links to the Title $target
309          */
310         private function linkText( $target ) {
311                 # We might be passed a non-Title by make*LinkObj().  Fail gracefully.
312                 if ( !$target instanceof Title ) {
313                         return '';
314                 }
315
316                 # If the target is just a fragment, with no title, we return the frag-
317                 # ment text.  Otherwise, we return the title text itself.
318                 if ( $target->getPrefixedText() === '' and $target->getFragment() !== '' ) {
319                         return htmlspecialchars( $target->getFragment() );
320                 }
321                 return htmlspecialchars( $target->getPrefixedText() );
322         }
323
324         /**
325          * Generate either a normal exists-style link or a stub link, depending
326          * on the given page size.
327          *
328          * @param $size Integer
329          * @param $nt Title object.
330          * @param $text String
331          * @param $query String
332          * @param $trail String
333          * @param $prefix String
334          * @return string HTML of link
335          * @deprecated
336          */
337         function makeSizeLinkObj( $size, $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
338                 global $wgUser;
339                 wfDeprecated( __METHOD__ );
340
341                 $threshold = $wgUser->getStubThreshold();
342                 $colour = ( $size < $threshold ) ? 'stub' : '';
343                 // FIXME: replace deprecated makeColouredLinkObj by link()
344                 return $this->makeColouredLinkObj( $nt, $colour, $text, $query, $trail, $prefix );
345         }
346
347         /**
348          * Make appropriate markup for a link to the current article. This is currently rendered
349          * as the bold link text. The calling sequence is the same as the other make*LinkObj functions,
350          * despite $query not being used.
351          */
352         function makeSelfLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
353                 if ( $text == '' ) {
354                         $text = htmlspecialchars( $nt->getPrefixedText() );
355                 }
356                 list( $inside, $trail ) = Linker::splitTrail( $trail );
357                 return "<strong class=\"selflink\">{$prefix}{$text}{$inside}</strong>{$trail}";
358         }
359
360         function normaliseSpecialPage( Title $title ) {
361                 if ( $title->getNamespace() == NS_SPECIAL ) {
362                         list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $title->getDBkey() );
363                         if ( !$name ) {
364                                 return $title;
365                         }
366                         $ret = SpecialPage::getTitleFor( $name, $subpage );
367                         $ret->mFragment = $title->getFragment();
368                         return $ret;
369                 } else {
370                         return $title;
371                 }
372         }
373
374         /**
375          * Returns the filename part of an url.
376          * Used as alternative text for external images.
377          */
378         function fnamePart( $url ) {
379                 $basename = strrchr( $url, '/' );
380                 if ( false === $basename ) {
381                         $basename = $url;
382                 } else {
383                         $basename = substr( $basename, 1 );
384                 }
385                 return $basename;
386         }
387
388         /**
389          * Return the code for images which were added via external links,
390          * via Parser::maybeMakeExternalImage().
391          */
392         function makeExternalImage( $url, $alt = '' ) {
393                 if ( $alt == '' ) {
394                         $alt = $this->fnamePart( $url );
395                 }
396                 $img = '';
397                 $success = wfRunHooks( 'LinkerMakeExternalImage', array( &$url, &$alt, &$img ) );
398                 if ( !$success ) {
399                         wfDebug( "Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}\n", true );
400                         return $img;
401                 }
402                 return Html::element( 'img',
403                         array(
404                                 'src' => $url,
405                                 'alt' => $alt ) );
406         }
407
408         /**
409          * Given parameters derived from [[Image:Foo|options...]], generate the
410          * HTML that that syntax inserts in the page.
411          *
412          * @param $title Title object
413          * @param $file File object, or false if it doesn't exist
414          * @param $frameParams Array: associative array of parameters external to the media handler.
415          *     Boolean parameters are indicated by presence or absence, the value is arbitrary and
416          *     will often be false.
417          *          thumbnail       If present, downscale and frame
418          *          manualthumb     Image name to use as a thumbnail, instead of automatic scaling
419          *          framed          Shows image in original size in a frame
420          *          frameless       Downscale but don't frame
421          *          upright         If present, tweak default sizes for portrait orientation
422          *          upright_factor  Fudge factor for "upright" tweak (default 0.75)
423          *          border          If present, show a border around the image
424          *          align           Horizontal alignment (left, right, center, none)
425          *          valign          Vertical alignment (baseline, sub, super, top, text-top, middle,
426          *                          bottom, text-bottom)
427          *          alt             Alternate text for image (i.e. alt attribute). Plain text.
428          *          caption         HTML for image caption.
429          *          link-url        URL to link to
430          *          link-title      Title object to link to
431          *          link-target     Value for the target attribue, only with link-url
432          *          no-link         Boolean, suppress description link
433          *
434          * @param $handlerParams Array: associative array of media handler parameters, to be passed
435          *       to transform(). Typical keys are "width" and "page".
436          * @param $time String: timestamp of the file, set as false for current
437          * @param $query String: query params for desc url
438          * @param $widthOption: Used by the parser to remember the user preference thumbnailsize
439          * @return String: HTML for an image, with links, wrappers, etc.
440          */
441         function makeImageLink2( Title $title, $file, $frameParams = array(), $handlerParams = array(), $time = false, $query = "", $widthOption = null ) {
442                 $res = null;
443                 if ( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$this, &$title,
444                 &$file, &$frameParams, &$handlerParams, &$time, &$res ) ) ) {
445                         return $res;
446                 }
447
448                 if ( $file && !$file->allowInlineDisplay() ) {
449                         wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . " does not allow inline display\n" );
450                         return $this->link( $title );
451                 }
452
453                 // Shortcuts
454                 $fp =& $frameParams;
455                 $hp =& $handlerParams;
456
457                 // Clean up parameters
458                 $page = isset( $hp['page'] ) ? $hp['page'] : false;
459                 if ( !isset( $fp['align'] ) ) $fp['align'] = '';
460                 if ( !isset( $fp['alt'] ) ) $fp['alt'] = '';
461                 if ( !isset( $fp['title'] ) ) $fp['title'] = '';
462
463                 $prefix = $postfix = '';
464
465                 if ( 'center' == $fp['align'] ) {
466                         $prefix  = '<div class="center">';
467                         $postfix = '</div>';
468                         $fp['align']   = 'none';
469                 }
470                 if ( $file && !isset( $hp['width'] ) ) {
471                         $hp['width'] = $file->getWidth( $page );
472
473                         if ( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
474                                 global $wgThumbLimits, $wgThumbUpright;
475                                 if ( !isset( $widthOption ) || !isset( $wgThumbLimits[$widthOption] ) ) {
476                                         $widthOption = User::getDefaultOption( 'thumbsize' );
477                                 }
478
479                                 // Reduce width for upright images when parameter 'upright' is used
480                                 if ( isset( $fp['upright'] ) && $fp['upright'] == 0 ) {
481                                         $fp['upright'] = $wgThumbUpright;
482                                 }
483                                 // 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
484                                 $prefWidth = isset( $fp['upright'] ) ?
485                                         round( $wgThumbLimits[$widthOption] * $fp['upright'], -1 ) :
486                                         $wgThumbLimits[$widthOption];
487
488                                 // Use width which is smaller: real image width or user preference width
489                                 // Unless image is scalable vector.
490                                 if ( !isset( $hp['height'] ) && ( $hp['width'] <= 0 ||
491                                                 $prefWidth < $hp['width'] || $file->isVectorized() ) ) {
492                                         $hp['width'] = $prefWidth;
493                                 }
494                         }
495                 }
496
497                 if ( isset( $fp['thumbnail'] ) || isset( $fp['manualthumb'] ) || isset( $fp['framed'] ) ) {
498                         global $wgContLang;
499                         # Create a thumbnail. Alignment depends on language
500                         # writing direction, # right aligned for left-to-right-
501                         # languages ("Western languages"), left-aligned
502                         # for right-to-left-languages ("Semitic languages")
503                         #
504                         # If  thumbnail width has not been provided, it is set
505                         # to the default user option as specified in Language*.php
506                         if ( $fp['align'] == '' ) {
507                                 $fp['align'] = $wgContLang->alignEnd();
508                         }
509                         return $prefix . $this->makeThumbLink2( $title, $file, $fp, $hp, $time, $query ) . $postfix;
510                 }
511
512                 if ( $file && isset( $fp['frameless'] ) ) {
513                         $srcWidth = $file->getWidth( $page );
514                         # For "frameless" option: do not present an image bigger than the source (for bitmap-style images)
515                         # This is the same behaviour as the "thumb" option does it already.
516                         if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
517                                 $hp['width'] = $srcWidth;
518                         }
519                 }
520
521                 if ( $file && isset( $hp['width'] ) ) {
522                         # Create a resized image, without the additional thumbnail features
523                         $thumb = $file->transform( $hp );
524                 } else {
525                         $thumb = false;
526                 }
527
528                 if ( !$thumb ) {
529                         $s = $this->makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
530                 } else {
531                         $params = array(
532                                 'alt' => $fp['alt'],
533                                 'title' => $fp['title'],
534                                 'valign' => isset( $fp['valign'] ) ? $fp['valign'] : false ,
535                                 'img-class' => isset( $fp['border'] ) ? 'thumbborder' : false );
536                         $params = $this->getImageLinkMTOParams( $fp, $query ) + $params;
537
538                         $s = $thumb->toHtml( $params );
539                 }
540                 if ( $fp['align'] != '' ) {
541                         $s = "<div class=\"float{$fp['align']}\">{$s}</div>";
542                 }
543                 return str_replace( "\n", ' ', $prefix . $s . $postfix );
544         }
545
546         /**
547          * Get the link parameters for MediaTransformOutput::toHtml() from given
548          * frame parameters supplied by the Parser.
549          * @param $frameParams The frame parameters
550          * @param $query An optional query string to add to description page links
551          */
552         function getImageLinkMTOParams( $frameParams, $query = '' ) {
553                 $mtoParams = array();
554                 if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
555                         $mtoParams['custom-url-link'] = $frameParams['link-url'];
556                         if ( isset( $frameParams['link-target'] ) ) {
557                                 $mtoParams['custom-target-link'] = $frameParams['link-target'];
558                         }
559                 } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
560                         $mtoParams['custom-title-link'] = $this->normaliseSpecialPage( $frameParams['link-title'] );
561                 } elseif ( !empty( $frameParams['no-link'] ) ) {
562                         // No link
563                 } else {
564                         $mtoParams['desc-link'] = true;
565                         $mtoParams['desc-query'] = $query;
566                 }
567                 return $mtoParams;
568         }
569
570         /**
571          * Make HTML for a thumbnail including image, border and caption
572          * @param $title Title object
573          * @param $file File object or false if it doesn't exist
574          * @param $label String
575          * @param $alt String
576          * @param $align String
577          * @param $params Array
578          * @param $framed Boolean
579          * @param $manualthumb String
580          */
581         function makeThumbLinkObj( Title $title, $file, $label = '', $alt, $align = 'right', $params = array(), $framed = false , $manualthumb = "" ) {
582                 $frameParams = array(
583                         'alt' => $alt,
584                         'caption' => $label,
585                         'align' => $align
586                 );
587                 if ( $framed ) $frameParams['framed'] = true;
588                 if ( $manualthumb ) $frameParams['manualthumb'] = $manualthumb;
589                 return $this->makeThumbLink2( $title, $file, $frameParams, $params );
590         }
591
592         function makeThumbLink2( Title $title, $file, $frameParams = array(), $handlerParams = array(), $time = false, $query = "" ) {
593                 global $wgStylePath;
594                 $exists = $file && $file->exists();
595
596                 # Shortcuts
597                 $fp =& $frameParams;
598                 $hp =& $handlerParams;
599
600                 $page = isset( $hp['page'] ) ? $hp['page'] : false;
601                 if ( !isset( $fp['align'] ) ) $fp['align'] = 'right';
602                 if ( !isset( $fp['alt'] ) ) $fp['alt'] = '';
603                 if ( !isset( $fp['title'] ) ) $fp['title'] = '';
604                 if ( !isset( $fp['caption'] ) ) $fp['caption'] = '';
605
606                 if ( empty( $hp['width'] ) ) {
607                         // Reduce width for upright images when parameter 'upright' is used
608                         $hp['width'] = isset( $fp['upright'] ) ? 130 : 180;
609                 }
610                 $thumb = false;
611
612                 if ( !$exists ) {
613                         $outerWidth = $hp['width'] + 2;
614                 } else {
615                         if ( isset( $fp['manualthumb'] ) ) {
616                                 # Use manually specified thumbnail
617                                 $manual_title = Title::makeTitleSafe( NS_FILE, $fp['manualthumb'] );
618                                 if ( $manual_title ) {
619                                         $manual_img = wfFindFile( $manual_title );
620                                         if ( $manual_img ) {
621                                                 $thumb = $manual_img->getUnscaledThumb( $hp );
622                                         } else {
623                                                 $exists = false;
624                                         }
625                                 }
626                         } elseif ( isset( $fp['framed'] ) ) {
627                                 // Use image dimensions, don't scale
628                                 $thumb = $file->getUnscaledThumb( $hp );
629                         } else {
630                                 # Do not present an image bigger than the source, for bitmap-style images
631                                 # This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour
632                                 $srcWidth = $file->getWidth( $page );
633                                 if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
634                                         $hp['width'] = $srcWidth;
635                                 }
636                                 $thumb = $file->transform( $hp );
637                         }
638
639                         if ( $thumb ) {
640                                 $outerWidth = $thumb->getWidth() + 2;
641                         } else {
642                                 $outerWidth = $hp['width'] + 2;
643                         }
644                 }
645
646                 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
647                 # So we don't need to pass it here in $query. However, the URL for the
648                 # zoom icon still needs it, so we make a unique query for it. See bug 14771
649                 $url = $title->getLocalURL( $query );
650                 if ( $page ) {
651                         $url = wfAppendQuery( $url, 'page=' . urlencode( $page ) );
652                 }
653
654                 $s = "<div class=\"thumb t{$fp['align']}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
655                 if ( !$exists ) {
656                         $s .= $this->makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
657                         $zoomIcon = '';
658                 } elseif ( !$thumb ) {
659                         $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
660                         $zoomIcon = '';
661                 } else {
662                         $params = array(
663                                 'alt' => $fp['alt'],
664                                 'title' => $fp['title'],
665                                 'img-class' => 'thumbimage' );
666                         $params = $this->getImageLinkMTOParams( $fp, $query ) + $params;
667                         $s .= $thumb->toHtml( $params );
668                         if ( isset( $fp['framed'] ) ) {
669                                 $zoomIcon = "";
670                         } else {
671                                 $zoomIcon =  '<div class="magnify">' .
672                                         '<a href="' . htmlspecialchars( $url ) . '" class="internal" ' .
673                                                 'title="' . htmlspecialchars( wfMsg( 'thumbnail-more' ) ) . '">' .
674                                         '<img src="' . htmlspecialchars( $wgStylePath ) . '/common/images/magnify-clip.png" ' .
675                                                 'width="15" height="11" alt="" /></a></div>';
676                         }
677                 }
678                 $s .= '  <div class="thumbcaption">' . $zoomIcon . $fp['caption'] . "</div></div></div>";
679                 return str_replace( "\n", ' ', $s );
680         }
681
682         /**
683          * Make a "broken" link to an image
684          *
685          * @param $title Title object
686          * @param $text String: link label
687          * @param $query String: query string
688          * @param $trail String: link trail
689          * @param $prefix String: link prefix
690          * @param $time Boolean: a file of a certain timestamp was requested
691          * @return String
692          */
693         public function makeBrokenImageLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '', $time = false ) {
694                 global $wgEnableUploads, $wgUploadMissingFileUrl;
695                 if ( $title instanceof Title ) {
696                         wfProfileIn( __METHOD__ );
697                         $currentExists = $time ? ( wfFindFile( $title ) != false ) : false;
698
699                         list( $inside, $trail ) = self::splitTrail( $trail );
700                         if ( $text == '' )
701                                 $text = htmlspecialchars( $title->getPrefixedText() );
702
703                         if ( ( $wgUploadMissingFileUrl || $wgEnableUploads ) && !$currentExists ) {
704                                 $redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
705
706                                 if ( $redir ) {
707                                         wfProfileOut( __METHOD__ );
708                                         return $this->linkKnown( $title, "$prefix$text$inside", array(), $query ) . $trail;
709                                 }
710
711                                 $href = $this->getUploadUrl( $title, $query );
712
713                                 wfProfileOut( __METHOD__ );
714                                 return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' .
715                                                                 htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' .
716                                                                 htmlspecialchars( $prefix . $text . $inside, ENT_NOQUOTES ) . '</a>' . $trail;
717                         } else {
718                                 wfProfileOut( __METHOD__ );
719                                 return $this->linkKnown( $title, "$prefix$text$inside", array(), $query ) . $trail;
720                         }
721                 } else {
722                         wfProfileOut( __METHOD__ );
723                         return "<!-- ERROR -->{$prefix}{$text}{$trail}";
724                 }
725         }
726
727         /**
728          * Get the URL to upload a certain file
729          *
730          * @param $destFile Title object of the file to upload
731          * @param $query String: urlencoded query string to prepend
732          * @return String: urlencoded URL
733          */
734         protected function getUploadUrl( $destFile, $query = '' ) {
735                 global $wgUploadMissingFileUrl;
736                 $q = 'wpDestFile=' . $destFile->getPartialUrl();
737                 if ( $query != '' )
738                         $q .= '&' . $query;
739
740                 if ( $wgUploadMissingFileUrl ) {
741                         return wfAppendQuery( $wgUploadMissingFileUrl, $q );
742                 } else {
743                         $upload = SpecialPage::getTitleFor( 'Upload' );
744                         return $upload->getLocalUrl( $q );
745                 }
746         }
747
748         /**
749          * Create a direct link to a given uploaded file.
750          *
751          * @param $title Title object.
752          * @param $text String: pre-sanitized HTML
753          * @param $time string: time image was created
754          * @return String: HTML
755          *
756          * @todo Handle invalid or missing images better.
757          */
758         public function makeMediaLinkObj( $title, $text = '', $time = false ) {
759                 if ( is_null( $title ) ) {
760                         # # # HOTFIX. Instead of breaking, return empty string.
761                         return $text;
762                 } else {
763                         $img  = wfFindFile( $title, array( 'time' => $time ) );
764                         if ( $img ) {
765                                 $url  = $img->getURL();
766                                 $class = 'internal';
767                         } else {
768                                 $url = $this->getUploadUrl( $title );
769                                 $class = 'new';
770                         }
771                         $alt = htmlspecialchars( $title->getText(),  ENT_QUOTES );
772                         if ( $text == '' ) {
773                                 $text = $alt;
774                         }
775                         $u = htmlspecialchars( $url );
776                         return "<a href=\"{$u}\" class=\"$class\" title=\"{$alt}\">{$text}</a>";
777                 }
778         }
779
780         /**
781          *  Make a link to a special page given its name and, optionally,
782          * a message key from the link text.
783          * Usage example: $skin->specialLink( 'recentchanges' )
784          */
785         function specialLink( $name, $key = '' ) {
786                 if ( $key == '' ) { $key = strtolower( $name ); }
787
788                 return $this->linkKnown( SpecialPage::getTitleFor( $name ) , wfMsg( $key ) );
789         }
790
791         /**
792          * Make an external link
793          * @param $url String: URL to link to
794          * @param $text String: text of link
795          * @param $escape Boolean: do we escape the link text?
796          * @param $linktype String: type of external link. Gets added to the classes
797          * @param $attribs Array of extra attributes to <a>
798          *
799          * @todo FIXME: This is a really crappy implementation. $linktype and
800          * 'external' are mashed into the class attrib for the link (which is made
801          * into a string). Then, if we've got additional params in $attribs, we
802          * add to it. People using this might want to change the classes (or other
803          * default link attributes), but passing $attribsText is just messy. Would
804          * make a lot more sense to make put the classes into $attribs, let the
805          * hook play with them, *then* expand it all at once.
806          */
807         function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array() ) {
808                 if ( isset( $attribs['class'] ) ) {
809                         # yet another hack :(
810                         $class = $attribs['class'];
811                 } else {
812                         $class =  "external $linktype";
813                 }
814
815                 $attribsText = $this->getExternalLinkAttributes( $class );
816                 $url = htmlspecialchars( $url );
817                 if ( $escape ) {
818                         $text = htmlspecialchars( $text );
819                 }
820                 $link = '';
821                 $success = wfRunHooks( 'LinkerMakeExternalLink', array( &$url, &$text, &$link, &$attribs, $linktype ) );
822                 if ( !$success ) {
823                         wfDebug( "Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}\n", true );
824                         return $link;
825                 }
826                 if ( $attribs ) {
827                         $attribsText .= Html::expandAttributes( $attribs );
828                 }
829                 return '<a href="' . $url . '"' . $attribsText . '>' . $text . '</a>';
830         }
831
832         /**
833          * Make user link (or user contributions for unregistered users)
834          * @param $userId   Integer: user id in database.
835          * @param $userText String: user name in database
836          * @return String: HTML fragment
837          * @private
838          */
839         function userLink( $userId, $userText ) {
840                 if ( $userId == 0 ) {
841                         $page = SpecialPage::getTitleFor( 'Contributions', $userText );
842                 } else {
843                         $page = Title::makeTitle( NS_USER, $userText );
844                 }
845                 return $this->link( $page, htmlspecialchars( $userText ), array( 'class' => 'mw-userlink' ) );
846         }
847
848         /**
849          * Generate standard user tool links (talk, contributions, block link, etc.)
850          *
851          * @param $userId Integer: user identifier
852          * @param $userText String: user name or IP address
853          * @param $redContribsWhenNoEdits Boolean: should the contributions link be
854          *        red if the user has no edits?
855          * @param $flags Integer: customisation flags (e.g. self::TOOL_LINKS_NOBLOCK)
856          * @param $edits Integer: user edit count (optional, for performance)
857          * @return String: HTML fragment
858          */
859         public function userToolLinks( $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null ) {
860                 global $wgUser, $wgDisableAnonTalk, $wgSysopUserBans, $wgLang;
861                 $talkable = !( $wgDisableAnonTalk && 0 == $userId );
862                 $blockable = ( $wgSysopUserBans || 0 == $userId ) && !$flags & self::TOOL_LINKS_NOBLOCK;
863
864                 $items = array();
865                 if ( $talkable ) {
866                         $items[] = $this->userTalkLink( $userId, $userText );
867                 }
868                 if ( $userId ) {
869                         // check if the user has an edit
870                         $attribs = array();
871                         if ( $redContribsWhenNoEdits ) {
872                                 $count = !is_null( $edits ) ? $edits : User::edits( $userId );
873                                 if ( $count == 0 ) {
874                                         $attribs['class'] = 'new';
875                                 }
876                         }
877                         $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
878
879                         $items[] = $this->link( $contribsPage, wfMsgHtml( 'contribslink' ), $attribs );
880                 }
881                 if ( $blockable && $wgUser->isAllowed( 'block' ) ) {
882                         $items[] = $this->blockLink( $userId, $userText );
883                 }
884
885                 if ( $items ) {
886                         return ' <span class="mw-usertoollinks">(' . $wgLang->pipeList( $items ) . ')</span>';
887                 } else {
888                         return '';
889                 }
890         }
891
892         /**
893          * Alias for userToolLinks( $userId, $userText, true );
894          * @param $userId Integer: user identifier
895          * @param $userText String: user name or IP address
896          * @param $edits Integer: user edit count (optional, for performance)
897          */
898         public function userToolLinksRedContribs( $userId, $userText, $edits = null ) {
899                 return $this->userToolLinks( $userId, $userText, true, 0, $edits );
900         }
901
902
903         /**
904          * @param $userId Integer: user id in database.
905          * @param $userText String: user name in database.
906          * @return String: HTML fragment with user talk link
907          * @private
908          */
909         function userTalkLink( $userId, $userText ) {
910                 $userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
911                 $userTalkLink = $this->link( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) );
912                 return $userTalkLink;
913         }
914
915         /**
916          * @param $userId Integer: userid
917          * @param $userText String: user name in database.
918          * @return String: HTML fragment with block link
919          * @private
920          */
921         function blockLink( $userId, $userText ) {
922                 $blockPage = SpecialPage::getTitleFor( 'Blockip', $userText );
923                 $blockLink = $this->link( $blockPage, wfMsgHtml( 'blocklink' ) );
924                 return $blockLink;
925         }
926
927         /**
928          * Generate a user link if the current user is allowed to view it
929          * @param $rev Revision object.
930          * @param $isPublic Boolean: show only if all users can see it
931          * @return String: HTML fragment
932          */
933         function revUserLink( $rev, $isPublic = false ) {
934                 if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
935                         $link = wfMsgHtml( 'rev-deleted-user' );
936                 } else if ( $rev->userCan( Revision::DELETED_USER ) ) {
937                         $link = $this->userLink( $rev->getUser( Revision::FOR_THIS_USER ),
938                                 $rev->getUserText( Revision::FOR_THIS_USER ) );
939                 } else {
940                         $link = wfMsgHtml( 'rev-deleted-user' );
941                 }
942                 if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
943                         return '<span class="history-deleted">' . $link . '</span>';
944                 }
945                 return $link;
946         }
947
948         /**
949          * Generate a user tool link cluster if the current user is allowed to view it
950          * @param $rev Revision object.
951          * @param $isPublic Boolean: show only if all users can see it
952          * @return string HTML
953          */
954         function revUserTools( $rev, $isPublic = false ) {
955                 if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
956                         $link = wfMsgHtml( 'rev-deleted-user' );
957                 } else if ( $rev->userCan( Revision::DELETED_USER ) ) {
958                         $userId = $rev->getUser( Revision::FOR_THIS_USER );
959                         $userText = $rev->getUserText( Revision::FOR_THIS_USER );
960                         $link = $this->userLink( $userId, $userText ) .
961                                 ' ' . $this->userToolLinks( $userId, $userText );
962                 } else {
963                         $link = wfMsgHtml( 'rev-deleted-user' );
964                 }
965                 if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
966                         return ' <span class="history-deleted">' . $link . '</span>';
967                 }
968                 return $link;
969         }
970
971         /**
972          * This function is called by all recent changes variants, by the page history,
973          * and by the user contributions list. It is responsible for formatting edit
974          * comments. It escapes any HTML in the comment, but adds some CSS to format
975          * auto-generated comments (from section editing) and formats [[wikilinks]].
976          *
977          * @author Erik Moeller <moeller@scireview.de>
978          *
979          * Note: there's not always a title to pass to this function.
980          * Since you can't set a default parameter for a reference, I've turned it
981          * temporarily to a value pass. Should be adjusted further. --brion
982          *
983          * @param $comment String
984          * @param $title Mixed: Title object (to generate link to the section in autocomment) or null
985          * @param $local Boolean: whether section links should refer to local page
986          */
987         function formatComment( $comment, $title = null, $local = false ) {
988                 wfProfileIn( __METHOD__ );
989
990                 # Sanitize text a bit:
991                 $comment = str_replace( "\n", " ", $comment );
992                 # Allow HTML entities (for bug 13815)
993                 $comment = Sanitizer::escapeHtmlAllowEntities( $comment );
994
995                 # Render autocomments and make links:
996                 $comment = $this->formatAutocomments( $comment, $title, $local );
997                 $comment = $this->formatLinksInComment( $comment, $title, $local );
998
999                 wfProfileOut( __METHOD__ );
1000                 return $comment;
1001         }
1002
1003         /**
1004          * The pattern for autogen comments is / * foo * /, which makes for
1005          * some nasty regex.
1006          * We look for all comments, match any text before and after the comment,
1007          * add a separator where needed and format the comment itself with CSS
1008          * Called by Linker::formatComment.
1009          *
1010          * @param $comment String: comment text
1011          * @param $title An optional title object used to links to sections
1012          * @param $local Boolean: whether section links should refer to local page
1013          * @return String: formatted comment
1014          */
1015         private function formatAutocomments( $comment, $title = null, $local = false ) {
1016                 // Bah!
1017                 $this->autocommentTitle = $title;
1018                 $this->autocommentLocal = $local;
1019                 $comment = preg_replace_callback(
1020                         '!(.*)/\*\s*(.*?)\s*\*/(.*)!',
1021                         array( $this, 'formatAutocommentsCallback' ),
1022                         $comment );
1023                 unset( $this->autocommentTitle );
1024                 unset( $this->autocommentLocal );
1025                 return $comment;
1026         }
1027
1028         private function formatAutocommentsCallback( $match ) {
1029                 $title = $this->autocommentTitle;
1030                 $local = $this->autocommentLocal;
1031
1032                 $pre = $match[1];
1033                 $auto = $match[2];
1034                 $post = $match[3];
1035                 $link = '';
1036                 if ( $title ) {
1037                         $section = $auto;
1038
1039                         # Remove links that a user may have manually put in the autosummary
1040                         # This could be improved by copying as much of Parser::stripSectionName as desired.
1041                         $section = str_replace( '[[:', '', $section );
1042                         $section = str_replace( '[[', '', $section );
1043                         $section = str_replace( ']]', '', $section );
1044
1045                         $section = Sanitizer::normalizeSectionNameWhitespace( $section ); # bug 22784
1046                         if ( $local ) {
1047                                 $sectionTitle = Title::newFromText( '#' . $section );
1048                         } else {
1049                                 $sectionTitle = Title::makeTitleSafe( $title->getNamespace(),
1050                                         $title->getDBkey(), $section );
1051                         }
1052                         if ( $sectionTitle ) {
1053                                 $link = $this->link( $sectionTitle,
1054                                         htmlspecialchars( wfMsgForContent( 'sectionlink' ) ), array(), array(),
1055                                         'noclasses' );
1056                         } else {
1057                                 $link = '';
1058                         }
1059                 }
1060                 $auto = "$link$auto";
1061                 if ( $pre ) {
1062                         # written summary $presep autocomment (summary /* section */)
1063                         $auto = wfMsgExt( 'autocomment-prefix', array( 'escapenoentities', 'content' ) ) . $auto;
1064                 }
1065                 if ( $post ) {
1066                         # autocomment $postsep written summary (/* section */ summary)
1067                         $auto .= wfMsgExt( 'colon-separator', array( 'escapenoentities', 'content' ) );
1068                 }
1069                 $auto = '<span class="autocomment">' . $auto . '</span>';
1070                 $comment = $pre . $auto . $post;
1071                 return $comment;
1072         }
1073
1074         /**
1075          * Formats wiki links and media links in text; all other wiki formatting
1076          * is ignored
1077          *
1078          * @todo Fixme: doesn't handle sub-links as in image thumb texts like the main parser
1079          * @param $comment String: text to format links in
1080          * @param $title An optional title object used to links to sections
1081          * @param $local Boolean: whether section links should refer to local page
1082          * @return String
1083          */
1084         public function formatLinksInComment( $comment, $title = null, $local = false ) {
1085                 $this->commentContextTitle = $title;
1086                 $this->commentLocal = $local;
1087                 $html = preg_replace_callback(
1088                         '/\[\[:?(.*?)(\|(.*?))*\]\]([^[]*)/',
1089                         array( $this, 'formatLinksInCommentCallback' ),
1090                         $comment );
1091                 unset( $this->commentContextTitle );
1092                 unset( $this->commentLocal );
1093                 return $html;
1094         }
1095
1096         protected function formatLinksInCommentCallback( $match ) {
1097                 global $wgContLang;
1098
1099                 $medians = '(?:' . preg_quote( MWNamespace::getCanonicalName( NS_MEDIA ), '/' ) . '|';
1100                 $medians .= preg_quote( $wgContLang->getNsText( NS_MEDIA ), '/' ) . '):';
1101
1102                 $comment = $match[0];
1103
1104                 # fix up urlencoded title texts (copied from Parser::replaceInternalLinks)
1105                 if ( strpos( $match[1], '%' ) !== false ) {
1106                         $match[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), urldecode( $match[1] ) );
1107                 }
1108
1109                 # Handle link renaming [[foo|text]] will show link as "text"
1110                 if ( $match[3] != "" ) {
1111                         $text = $match[3];
1112                 } else {
1113                         $text = $match[1];
1114                 }
1115                 $submatch = array();
1116                 $thelink = null;
1117                 if ( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
1118                         # Media link; trail not supported.
1119                         $linkRegexp = '/\[\[(.*?)\]\]/';
1120                         $title = Title::makeTitleSafe( NS_FILE, $submatch[1] );
1121                         $thelink = $this->makeMediaLinkObj( $title, $text );
1122                 } else {
1123                         # Other kind of link
1124                         if ( preg_match( $wgContLang->linkTrail(), $match[4], $submatch ) ) {
1125                                 $trail = $submatch[1];
1126                         } else {
1127                                 $trail = "";
1128                         }
1129                         $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
1130                         if ( isset( $match[1][0] ) && $match[1][0] == ':' )
1131                                 $match[1] = substr( $match[1], 1 );
1132                         list( $inside, $trail ) = Linker::splitTrail( $trail );
1133
1134                         $linkText = $text;
1135                         $linkTarget = Linker::normalizeSubpageLink( $this->commentContextTitle,
1136                                 $match[1], $linkText );
1137
1138                         $target = Title::newFromText( $linkTarget );
1139                         if ( $target ) {
1140                                 if ( $target->getText() == '' && $target->getInterwiki() === ''
1141                                         && !$this->commentLocal && $this->commentContextTitle )
1142                                 {
1143                                         $newTarget = clone ( $this->commentContextTitle );
1144                                         $newTarget->setFragment( '#' . $target->getFragment() );
1145                                         $target = $newTarget;
1146                                 }
1147                                 $thelink = $this->link(
1148                                         $target,
1149                                         $linkText . $inside
1150                                 ) . $trail;
1151                         }
1152                 }
1153                 if ( $thelink ) {
1154                         // If the link is still valid, go ahead and replace it in!
1155                         $comment = preg_replace( $linkRegexp, StringUtils::escapeRegexReplacement( $thelink ), $comment, 1 );
1156                 }
1157
1158                 return $comment;
1159         }
1160
1161         static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
1162                 # Valid link forms:
1163                 # Foobar -- normal
1164                 # :Foobar -- override special treatment of prefix (images, language links)
1165                 # /Foobar -- convert to CurrentPage/Foobar
1166                 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
1167                 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1168                 # ../Foobar -- convert to CurrentPage/Foobar, from CurrentPage/CurrentSubPage
1169
1170                 wfProfileIn( __METHOD__ );
1171                 $ret = $target; # default return value is no change
1172
1173                 # Some namespaces don't allow subpages,
1174                 # so only perform processing if subpages are allowed
1175                 if ( $contextTitle && MWNamespace::hasSubpages( $contextTitle->getNamespace() ) ) {
1176                         $hash = strpos( $target, '#' );
1177                         if ( $hash !== false ) {
1178                                 $suffix = substr( $target, $hash );
1179                                 $target = substr( $target, 0, $hash );
1180                         } else {
1181                                 $suffix = '';
1182                         }
1183                         # bug 7425
1184                         $target = trim( $target );
1185                         # Look at the first character
1186                         if ( $target != '' && $target { 0 } === '/' ) {
1187                                 # / at end means we don't want the slash to be shown
1188                                 $m = array();
1189                                 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
1190                                 if ( $trailingSlashes ) {
1191                                         $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1192                                 } else {
1193                                         $noslash = substr( $target, 1 );
1194                                 }
1195
1196                                 $ret = $contextTitle->getPrefixedText() . '/' . trim( $noslash ) . $suffix;
1197                                 if ( $text === '' ) {
1198                                         $text = $target . $suffix;
1199                                 } # this might be changed for ugliness reasons
1200                         } else {
1201                                 # check for .. subpage backlinks
1202                                 $dotdotcount = 0;
1203                                 $nodotdot = $target;
1204                                 while ( strncmp( $nodotdot, "../", 3 ) == 0 ) {
1205                                         ++$dotdotcount;
1206                                         $nodotdot = substr( $nodotdot, 3 );
1207                                 }
1208                                 if ( $dotdotcount > 0 ) {
1209                                         $exploded = explode( '/', $contextTitle->GetPrefixedText() );
1210                                         if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1211                                                 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
1212                                                 # / at the end means don't show full path
1213                                                 if ( substr( $nodotdot, -1, 1 ) === '/' ) {
1214                                                         $nodotdot = substr( $nodotdot, 0, -1 );
1215                                                         if ( $text === '' ) {
1216                                                                 $text = $nodotdot . $suffix;
1217                                                         }
1218                                                 }
1219                                                 $nodotdot = trim( $nodotdot );
1220                                                 if ( $nodotdot != '' ) {
1221                                                         $ret .= '/' . $nodotdot;
1222                                                 }
1223                                                 $ret .= $suffix;
1224                                         }
1225                                 }
1226                         }
1227                 }
1228
1229                 wfProfileOut( __METHOD__ );
1230                 return $ret;
1231         }
1232
1233         /**
1234          * Wrap a comment in standard punctuation and formatting if
1235          * it's non-empty, otherwise return empty string.
1236          *
1237          * @param $comment String
1238          * @param $title Mixed: Title object (to generate link to section in autocomment) or null
1239          * @param $local Boolean: whether section links should refer to local page
1240          *
1241          * @return string
1242          */
1243         function commentBlock( $comment, $title = null, $local = false ) {
1244                 // '*' used to be the comment inserted by the software way back
1245                 // in antiquity in case none was provided, here for backwards
1246                 // compatability, acc. to brion -ævar
1247                 if ( $comment == '' || $comment == '*' ) {
1248                         return '';
1249                 } else {
1250                         $formatted = $this->formatComment( $comment, $title, $local );
1251                         return " <span class=\"comment\">($formatted)</span>";
1252                 }
1253         }
1254
1255         /**
1256          * Wrap and format the given revision's comment block, if the current
1257          * user is allowed to view it.
1258          *
1259          * @param $rev Revision object
1260          * @param $local Boolean: whether section links should refer to local page
1261          * @param $isPublic Boolean: show only if all users can see it
1262          * @return String: HTML fragment
1263          */
1264         function revComment( Revision $rev, $local = false, $isPublic = false ) {
1265                 if ( $rev->getRawComment() == "" ) return "";
1266                 if ( $rev->isDeleted( Revision::DELETED_COMMENT ) && $isPublic ) {
1267                         $block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
1268                 } else if ( $rev->userCan( Revision::DELETED_COMMENT ) ) {
1269                         $block = $this->commentBlock( $rev->getComment( Revision::FOR_THIS_USER ),
1270                                 $rev->getTitle(), $local );
1271                 } else {
1272                         $block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
1273                 }
1274                 if ( $rev->isDeleted( Revision::DELETED_COMMENT ) ) {
1275                         return " <span class=\"history-deleted\">$block</span>";
1276                 }
1277                 return $block;
1278         }
1279
1280         public function formatRevisionSize( $size ) {
1281                 if ( $size == 0 ) {
1282                         $stxt = wfMsgExt( 'historyempty', 'parsemag' );
1283                 } else {
1284                         global $wgLang;
1285                         $stxt = wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $size ) );
1286                         $stxt = "($stxt)";
1287                 }
1288                 $stxt = htmlspecialchars( $stxt );
1289                 return "<span class=\"history-size\">$stxt</span>";
1290         }
1291
1292         /**
1293          * Add another level to the Table of Contents
1294          */
1295         function tocIndent() {
1296                 return "\n<ul>";
1297         }
1298
1299         /**
1300          * Finish one or more sublevels on the Table of Contents
1301          */
1302         function tocUnindent( $level ) {
1303                 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
1304         }
1305
1306         /**
1307          * parameter level defines if we are on an indentation level
1308          */
1309         function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
1310                 $classes = "toclevel-$level";
1311                 if ( $sectionIndex !== false )
1312                         $classes .= " tocsection-$sectionIndex";
1313                 return "\n<li class=\"$classes\"><a href=\"#" .
1314                         $anchor . '"><span class="tocnumber">' .
1315                         $tocnumber . '</span> <span class="toctext">' .
1316                         $tocline . '</span></a>';
1317         }
1318
1319         /**
1320          * End a Table Of Contents line.
1321          * tocUnindent() will be used instead if we're ending a line below
1322          * the new level.
1323          */
1324         function tocLineEnd() {
1325                 return "</li>\n";
1326         }
1327
1328         /**
1329          * Wraps the TOC in a table and provides the hide/collapse javascript.
1330          *
1331          * @param $toc String: html of the Table Of Contents
1332          * @param $lang mixed: Language code for the toc title
1333          * @return String: full html of the TOC
1334          */
1335         function tocList( $toc, $lang = false ) {
1336                 $title = wfMsgExt( 'toc', array( 'language' => $lang, 'escape' ) );
1337                 return
1338                    '<table id="toc" class="toc"><tr><td>'
1339                  . '<div id="toctitle"><h2>' . $title . "</h2></div>\n"
1340                  . $toc
1341                  . "</ul>\n</td></tr></table>\n";
1342         }
1343
1344         /**
1345          * Generate a table of contents from a section tree
1346          * Currently unused.
1347          *
1348          * @param $tree Return value of ParserOutput::getSections()
1349          * @return String: HTML fragment
1350          */
1351         public function generateTOC( $tree ) {
1352                 $toc = '';
1353                 $lastLevel = 0;
1354                 foreach ( $tree as $section ) {
1355                         if ( $section['toclevel'] > $lastLevel )
1356                                 $toc .= $this->tocIndent();
1357                         else if ( $section['toclevel'] < $lastLevel )
1358                                 $toc .= $this->tocUnindent(
1359                                         $lastLevel - $section['toclevel'] );
1360                         else
1361                                 $toc .= $this->tocLineEnd();
1362
1363                         $toc .= $this->tocLine( $section['anchor'],
1364                                 $section['line'], $section['number'],
1365                                 $section['toclevel'], $section['index'] );
1366                         $lastLevel = $section['toclevel'];
1367                 }
1368                 $toc .= $this->tocLineEnd();
1369                 return $this->tocList( $toc );
1370         }
1371
1372         /**
1373          * Create a section edit link.  This supersedes editSectionLink() and
1374          * editSectionLinkForOther().
1375          *
1376          * @param $nt      Title  The title being linked to (may not be the same as
1377          *   $wgTitle, if the section is included from a template)
1378          * @param $section string The designation of the section being pointed to,
1379          *   to be included in the link, like "&section=$section"
1380          * @param $tooltip string The tooltip to use for the link: will be escaped
1381          *   and wrapped in the 'editsectionhint' message
1382          * @param $lang    string Language code
1383          * @return         string HTML to use for edit link
1384          */
1385         public function doEditSectionLink( Title $nt, $section, $tooltip = null, $lang = false ) {
1386                 // HTML generated here should probably have userlangattributes
1387                 // added to it for LTR text on RTL pages
1388                 $attribs = array();
1389                 if ( !is_null( $tooltip ) ) {
1390                         # Bug 25462: undo double-escaping.
1391                         $tooltip = Sanitizer::decodeCharReferences( $tooltip );
1392                         $attribs['title'] = wfMsgReal( 'editsectionhint', array( $tooltip ), true, $lang );
1393                 }
1394                 $link = $this->link( $nt, wfMsgExt( 'editsection', array( 'language' => $lang ) ),
1395                         $attribs,
1396                         array( 'action' => 'edit', 'section' => $section ),
1397                         array( 'noclasses', 'known' )
1398                 );
1399
1400                 # Run the old hook.  This takes up half of the function . . . hopefully
1401                 # we can rid of it someday.
1402                 $attribs = '';
1403                 if ( $tooltip ) {
1404                         $attribs = htmlspecialchars( wfMsgReal( 'editsectionhint', array( $tooltip ), true, $lang ) );
1405                         $attribs = " title=\"$attribs\"";
1406                 }
1407                 $result = null;
1408                 wfRunHooks( 'EditSectionLink', array( &$this, $nt, $section, $attribs, $link, &$result, $lang ) );
1409                 if ( !is_null( $result ) ) {
1410                         # For reverse compatibility, add the brackets *after* the hook is
1411                         # run, and even add them to hook-provided text.  (This is the main
1412                         # reason that the EditSectionLink hook is deprecated in favor of
1413                         # DoEditSectionLink: it can't change the brackets or the span.)
1414                         $result = wfMsgExt( 'editsection-brackets', array( 'escape', 'replaceafter', 'language' => $lang ), $result );
1415                         return "<span class=\"editsection\">$result</span>";
1416                 }
1417
1418                 # Add the brackets and the span, and *then* run the nice new hook, with
1419                 # clean and non-redundant arguments.
1420                 $result = wfMsgExt( 'editsection-brackets', array( 'escape', 'replaceafter', 'language' => $lang ), $link );
1421                 $result = "<span class=\"editsection\">$result</span>";
1422
1423                 wfRunHooks( 'DoEditSectionLink', array( $this, $nt, $section, $tooltip, &$result, $lang ) );
1424                 return $result;
1425         }
1426
1427         /**
1428          * Create a headline for content
1429          *
1430          * @param $level Integer: the level of the headline (1-6)
1431          * @param $attribs String: any attributes for the headline, starting with
1432          *                 a space and ending with '>'
1433          *                 This *must* be at least '>' for no attribs
1434          * @param $anchor String: the anchor to give the headline (the bit after the #)
1435          * @param $text String: the text of the header
1436          * @param $link String: HTML to add for the section edit link
1437          * @param $legacyAnchor Mixed: a second, optional anchor to give for
1438          *   backward compatibility (false to omit)
1439          *
1440          * @return String: HTML headline
1441          */
1442         public function makeHeadline( $level, $attribs, $anchor, $text, $link, $legacyAnchor = false ) {
1443                 $ret = "<h$level$attribs"
1444                         . $link
1445                         . " <span class=\"mw-headline\" id=\"$anchor\">$text</span>"
1446                         . "</h$level>";
1447                 if ( $legacyAnchor !== false ) {
1448                         $ret = "<div id=\"$legacyAnchor\"></div>$ret";
1449                 }
1450                 return $ret;
1451         }
1452
1453         /**
1454          * Split a link trail, return the "inside" portion and the remainder of the trail
1455          * as a two-element array
1456          */
1457         static function splitTrail( $trail ) {
1458                 static $regex = false;
1459                 if ( $regex === false ) {
1460                         global $wgContLang;
1461                         $regex = $wgContLang->linkTrail();
1462                 }
1463                 $inside = '';
1464                 if ( $trail !== '' ) {
1465                         $m = array();
1466                         if ( preg_match( $regex, $trail, $m ) ) {
1467                                 $inside = $m[1];
1468                                 $trail = $m[2];
1469                         }
1470                 }
1471                 return array( $inside, $trail );
1472         }
1473
1474         /**
1475          * Generate a rollback link for a given revision.  Currently it's the
1476          * caller's responsibility to ensure that the revision is the top one. If
1477          * it's not, of course, the user will get an error message.
1478          *
1479          * If the calling page is called with the parameter &bot=1, all rollback
1480          * links also get that parameter. It causes the edit itself and the rollback
1481          * to be marked as "bot" edits. Bot edits are hidden by default from recent
1482          * changes, so this allows sysops to combat a busy vandal without bothering
1483          * other users.
1484          *
1485          * @param $rev Revision object
1486          */
1487         function generateRollback( $rev ) {
1488                 return '<span class="mw-rollback-link">['
1489                         . $this->buildRollbackLink( $rev )
1490                         . ']</span>';
1491         }
1492
1493         /**
1494          * Build a raw rollback link, useful for collections of "tool" links
1495          *
1496          * @param $rev Revision object
1497          * @return String: HTML fragment
1498          */
1499         public function buildRollbackLink( $rev ) {
1500                 global $wgRequest, $wgUser;
1501                 $title = $rev->getTitle();
1502                 $query = array(
1503                         'action' => 'rollback',
1504                         'from' => $rev->getUserText()
1505                 );
1506                 if ( $wgRequest->getBool( 'bot' ) ) {
1507                         $query['bot'] = '1';
1508                         $query['hidediff'] = '1'; // bug 15999
1509                 }
1510                 $query['token'] = $wgUser->editToken( array( $title->getPrefixedText(),
1511                         $rev->getUserText() ) );
1512                 return $this->link( $title, wfMsgHtml( 'rollbacklink' ),
1513                         array( 'title' => wfMsg( 'tooltip-rollback' ) ),
1514                         $query, array( 'known', 'noclasses' ) );
1515         }
1516
1517         /**
1518          * Returns HTML for the "templates used on this page" list.
1519          *
1520          * @param $templates Array of templates from Article::getUsedTemplate
1521          * or similar
1522          * @param $preview Boolean: whether this is for a preview
1523          * @param $section Boolean: whether this is for a section edit
1524          * @return String: HTML output
1525          */
1526         public function formatTemplates( $templates, $preview = false, $section = false ) {
1527                 wfProfileIn( __METHOD__ );
1528
1529                 $outText = '';
1530                 if ( count( $templates ) > 0 ) {
1531                         # Do a batch existence check
1532                         $batch = new LinkBatch;
1533                         foreach ( $templates as $title ) {
1534                                 $batch->addObj( $title );
1535                         }
1536                         $batch->execute();
1537
1538                         # Construct the HTML
1539                         $outText = '<div class="mw-templatesUsedExplanation">';
1540                         if ( $preview ) {
1541                                 $outText .= wfMsgExt( 'templatesusedpreview', array( 'parse' ), count( $templates ) );
1542                         } elseif ( $section ) {
1543                                 $outText .= wfMsgExt( 'templatesusedsection', array( 'parse' ), count( $templates ) );
1544                         } else {
1545                                 $outText .= wfMsgExt( 'templatesused', array( 'parse' ), count( $templates ) );
1546                         }
1547                         $outText .= "</div><ul>\n";
1548
1549                         usort( $templates, array( 'Title', 'compare' ) );
1550                         foreach ( $templates as $titleObj ) {
1551                                 $r = $titleObj->getRestrictions( 'edit' );
1552                                 if ( in_array( 'sysop', $r ) ) {
1553                                         $protected = wfMsgExt( 'template-protected', array( 'parseinline' ) );
1554                                 } elseif ( in_array( 'autoconfirmed', $r ) ) {
1555                                         $protected = wfMsgExt( 'template-semiprotected', array( 'parseinline' ) );
1556                                 } else {
1557                                         $protected = '';
1558                                 }
1559                                 if ( $titleObj->quickUserCan( 'edit' ) ) {
1560                                         $editLink = $this->link(
1561                                                 $titleObj,
1562                                                 wfMsg( 'editlink' ),
1563                                                 array(),
1564                                                 array( 'action' => 'edit' )
1565                                         );
1566                                 } else {
1567                                         $editLink = $this->link(
1568                                                 $titleObj,
1569                                                 wfMsg( 'viewsourcelink' ),
1570                                                 array(),
1571                                                 array( 'action' => 'edit' )
1572                                         );
1573                                 }
1574                                 $outText .= '<li>' . $this->link( $titleObj ) . ' (' . $editLink . ') ' . $protected . '</li>';
1575                         }
1576                         $outText .= '</ul>';
1577                 }
1578                 wfProfileOut( __METHOD__  );
1579                 return $outText;
1580         }
1581
1582         /**
1583          * Returns HTML for the "hidden categories on this page" list.
1584          *
1585          * @param $hiddencats Array of hidden categories from Article::getHiddenCategories
1586          * or similar
1587          * @return String: HTML output
1588          */
1589         public function formatHiddenCategories( $hiddencats ) {
1590                 global $wgLang;
1591                 wfProfileIn( __METHOD__ );
1592
1593                 $outText = '';
1594                 if ( count( $hiddencats ) > 0 ) {
1595                         # Construct the HTML
1596                         $outText = '<div class="mw-hiddenCategoriesExplanation">';
1597                         $outText .= wfMsgExt( 'hiddencategories', array( 'parse' ), $wgLang->formatnum( count( $hiddencats ) ) );
1598                         $outText .= "</div><ul>\n";
1599
1600                         foreach ( $hiddencats as $titleObj ) {
1601                                 $outText .= '<li>' . $this->link( $titleObj, null, array(), array(), 'known' ) . "</li>\n"; # If it's hidden, it must exist - no need to check with a LinkBatch
1602                         }
1603                         $outText .= '</ul>';
1604                 }
1605                 wfProfileOut( __METHOD__  );
1606                 return $outText;
1607         }
1608
1609         /**
1610          * Format a size in bytes for output, using an appropriate
1611          * unit (B, KB, MB or GB) according to the magnitude in question
1612          *
1613          * @param $size Size to format
1614          * @return String
1615          */
1616         public function formatSize( $size ) {
1617                 global $wgLang;
1618                 return htmlspecialchars( $wgLang->formatSize( $size ) );
1619         }
1620
1621         /**
1622          * Given the id of an interface element, constructs the appropriate title
1623          * attribute from the system messages.  (Note, this is usually the id but
1624          * isn't always, because sometimes the accesskey needs to go on a different
1625          * element than the id, for reverse-compatibility, etc.)
1626          *
1627          * @param $name String: id of the element, minus prefixes.
1628          * @param $options Mixed: null or the string 'withaccess' to add an access-
1629          *   key hint
1630          * @return String: contents of the title attribute (which you must HTML-
1631          *   escape), or false for no title attribute
1632          */
1633         public function titleAttrib( $name, $options = null ) {
1634                 wfProfileIn( __METHOD__ );
1635
1636                 if ( wfEmptyMsg( "tooltip-$name" ) ) {
1637                         $tooltip = false;
1638                 } else {
1639                         $tooltip = wfMsg( "tooltip-$name" );
1640                         # Compatibility: formerly some tooltips had [alt-.] hardcoded
1641                         $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
1642                         # Message equal to '-' means suppress it.
1643                         if (  $tooltip == '-' ) {
1644                                 $tooltip = false;
1645                         }
1646                 }
1647
1648                 if ( $options == 'withaccess' ) {
1649                         $accesskey = $this->accesskey( $name );
1650                         if ( $accesskey !== false ) {
1651                                 if ( $tooltip === false || $tooltip === '' ) {
1652                                         $tooltip = "[$accesskey]";
1653                                 } else {
1654                                         $tooltip .= " [$accesskey]";
1655                                 }
1656                         }
1657                 }
1658
1659                 wfProfileOut( __METHOD__ );
1660                 return $tooltip;
1661         }
1662
1663         /**
1664          * Given the id of an interface element, constructs the appropriate
1665          * accesskey attribute from the system messages.  (Note, this is usually
1666          * the id but isn't always, because sometimes the accesskey needs to go on
1667          * a different element than the id, for reverse-compatibility, etc.)
1668          *
1669          * @param $name String: id of the element, minus prefixes.
1670          * @return String: contents of the accesskey attribute (which you must HTML-
1671          *   escape), or false for no accesskey attribute
1672          */
1673         public function accesskey( $name ) {
1674                 if ( isset( $this->accesskeycache[$name] ) ) {
1675                         return $this->accesskeycache[$name];
1676                 }
1677                 wfProfileIn( __METHOD__ );
1678
1679                 $message = wfMessage( "accesskey-$name" );
1680
1681                 if ( !$message->exists() ) {
1682                         $accesskey = false;
1683                 } else {
1684                         $accesskey = $message->plain();
1685                         if ( $accesskey === '' || $accesskey === '-' ) {
1686                                 # FIXME: Per standard MW behavior, a value of '-' means to suppress the
1687                                 # attribute, but this is broken for accesskey: that might be a useful
1688                                 # value.
1689                                 $accesskey = false;
1690                         }
1691                 }
1692
1693                 wfProfileOut( __METHOD__ );
1694                 return $this->accesskeycache[$name] = $accesskey;
1695         }
1696
1697         /**
1698          * Creates a (show/hide) link for deleting revisions/log entries
1699          *
1700          * @param $query Array: query parameters to be passed to link()
1701          * @param $restricted Boolean: set to true to use a <strong> instead of a <span>
1702          * @param $delete Boolean: set to true to use (show/hide) rather than (show)
1703          *
1704          * @return String: HTML <a> link to Special:Revisiondelete, wrapped in a
1705          * span to allow for customization of appearance with CSS
1706          */
1707         public function revDeleteLink( $query = array(), $restricted = false, $delete = true ) {
1708                 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
1709                 $text = $delete ? wfMsgHtml( 'rev-delundel' ) : wfMsgHtml( 'rev-showdeleted' );
1710                 $tag = $restricted ? 'strong' : 'span';
1711                 $link = $this->link( $sp, $text, array(), $query, array( 'known', 'noclasses' ) );
1712                 return Xml::tags( $tag, array( 'class' => 'mw-revdelundel-link' ), "($link)" );
1713         }
1714
1715         /**
1716          * Creates a dead (show/hide) link for deleting revisions/log entries
1717          *
1718          * @param $delete Boolean: set to true to use (show/hide) rather than (show)
1719          *
1720          * @return string HTML text wrapped in a span to allow for customization
1721          * of appearance with CSS
1722          */
1723         public function revDeleteLinkDisabled( $delete = true ) {
1724                 $text = $delete ? wfMsgHtml( 'rev-delundel' ) : wfMsgHtml( 'rev-showdeleted' );
1725                 return Xml::tags( 'span', array( 'class' => 'mw-revdelundel-link' ), "($text)" );
1726         }
1727
1728         /* Deprecated methods */
1729
1730         /**
1731          * @deprecated
1732          */
1733         function postParseLinkColour( $s = null ) {
1734                 wfDeprecated( __METHOD__ );
1735                 return null;
1736         }
1737
1738
1739         /**
1740          * @deprecated Use link()
1741          *
1742          * This function is a shortcut to makeLinkObj(Title::newFromText($title),...). Do not call
1743          * it if you already have a title object handy. See makeLinkObj for further documentation.
1744          *
1745          * @param $title String: the text of the title
1746          * @param $text  String: link text
1747          * @param $query String: optional query part
1748          * @param $trail String: optional trail. Alphabetic characters at the start of this string will
1749          *                      be included in the link text. Other characters will be appended after
1750          *                      the end of the link.
1751          */
1752         function makeLink( $title, $text = '', $query = '', $trail = '' ) {
1753                 wfProfileIn( __METHOD__ );
1754                 $nt = Title::newFromText( $title );
1755                 if ( $nt instanceof Title ) {
1756                         $result = $this->makeLinkObj( $nt, $text, $query, $trail );
1757                 } else {
1758                         wfDebug( 'Invalid title passed to Linker::makeLink(): "' . $title . "\"\n" );
1759                         $result = $text == "" ? $title : $text;
1760                 }
1761
1762                 wfProfileOut( __METHOD__ );
1763                 return $result;
1764         }
1765
1766         /**
1767          * @deprecated Use link()
1768          *
1769          * This function is a shortcut to makeKnownLinkObj(Title::newFromText($title),...). Do not call
1770          * it if you already have a title object handy. See makeKnownLinkObj for further documentation.
1771          *
1772          * @param $title String: the text of the title
1773          * @param $text  String: link text
1774          * @param $query String: optional query part
1775          * @param $trail String: optional trail. Alphabetic characters at the start of this string will
1776          *                      be included in the link text. Other characters will be appended after
1777          *                      the end of the link.
1778          * @param $prefix String: Optional prefix
1779          * @param $aprops String: extra attributes to the a-element
1780          */
1781         function makeKnownLink( $title, $text = '', $query = '', $trail = '', $prefix = '', $aprops = '' ) {
1782                 $nt = Title::newFromText( $title );
1783                 if ( $nt instanceof Title ) {
1784                         return $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix , $aprops );
1785                 } else {
1786                         wfDebug( 'Invalid title passed to Linker::makeKnownLink(): "' . $title . "\"\n" );
1787                         return $text == '' ? $title : $text;
1788                 }
1789         }
1790
1791         /**
1792          * @deprecated Use link()
1793          *
1794          * This function is a shortcut to makeBrokenLinkObj(Title::newFromText($title),...). Do not call
1795          * it if you already have a title object handy. See makeBrokenLinkObj for further documentation.
1796          *
1797          * @param $title String: The text of the title
1798          * @param $text String: Link text
1799          * @param $query String: Optional query part
1800          * @param $trail String: Optional trail. Alphabetic characters at the start of this string will
1801          *               be included in the link text. Other characters will be appended after
1802          *               the end of the link.
1803          */
1804         function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) {
1805                 $nt = Title::newFromText( $title );
1806                 if ( $nt instanceof Title ) {
1807                         return $this->makeBrokenLinkObj( $nt, $text, $query, $trail );
1808                 } else {
1809                         wfDebug( 'Invalid title passed to Linker::makeBrokenLink(): "' . $title . "\"\n" );
1810                         return $text == '' ? $title : $text;
1811                 }
1812         }
1813
1814         /**
1815          * @deprecated Use link()
1816          *
1817          * This function is a shortcut to makeStubLinkObj(Title::newFromText($title),...). Do not call
1818          * it if you already have a title object handy. See makeStubLinkObj for further documentation.
1819          *
1820          * @param $title String: the text of the title
1821          * @param $text  String: link text
1822          * @param $query String: optional query part
1823          * @param $trail String: optional trail. Alphabetic characters at the start of this string will
1824          *                      be included in the link text. Other characters will be appended after
1825          *                      the end of the link.
1826          */
1827         function makeStubLink( $title, $text = '', $query = '', $trail = '' ) {
1828                 wfDeprecated( __METHOD__ );
1829                 $nt = Title::newFromText( $title );
1830                 if ( $nt instanceof Title ) {
1831                         return $this->makeStubLinkObj( $nt, $text, $query, $trail );
1832                 } else {
1833                         wfDebug( 'Invalid title passed to Linker::makeStubLink(): "' . $title . "\"\n" );
1834                         return $text == '' ? $title : $text;
1835                 }
1836         }
1837
1838         /**
1839          * @deprecated Use link()
1840          *
1841          * Make a link for a title which may or may not be in the database. If you need to
1842          * call this lots of times, pre-fill the link cache with a LinkBatch, otherwise each
1843          * call to this will result in a DB query.
1844          *
1845          * @param $nt     Title: the title object to make the link from, e.g. from
1846          *                      Title::newFromText.
1847          * @param $text  String: link text
1848          * @param $query String: optional query part
1849          * @param $trail String: optional trail. Alphabetic characters at the start of this string will
1850          *                      be included in the link text. Other characters will be appended after
1851          *                      the end of the link.
1852          * @param $prefix String: optional prefix. As trail, only before instead of after.
1853          */
1854         function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
1855                 wfProfileIn( __METHOD__ );
1856
1857                 $query = wfCgiToArray( $query );
1858                 list( $inside, $trail ) = Linker::splitTrail( $trail );
1859                 if ( $text === '' ) {
1860                         $text = $this->linkText( $nt );
1861                 }
1862
1863                 $ret = $this->link( $nt, "$prefix$text$inside", array(), $query ) . $trail;
1864
1865                 wfProfileOut( __METHOD__ );
1866                 return $ret;
1867         }
1868
1869         /**
1870          * @deprecated Use link()
1871          *
1872          * Make a link for a title which definitely exists. This is faster than makeLinkObj because
1873          * it doesn't have to do a database query. It's also valid for interwiki titles and special
1874          * pages.
1875          *
1876          * @param $title  Title object of target page
1877          * @param $text   String: text to replace the title
1878          * @param $query  String: link target
1879          * @param $trail  String: text after link
1880          * @param $prefix String: text before link text
1881          * @param $aprops String: extra attributes to the a-element
1882          * @param $style  String: style to apply - if empty, use getInternalLinkAttributesObj instead
1883          * @return the a-element
1884          */
1885         function makeKnownLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' , $aprops = '', $style = '' ) {
1886                 wfProfileIn( __METHOD__ );
1887
1888                 if ( $text == '' ) {
1889                         $text = $this->linkText( $title );
1890                 }
1891                 $attribs = Sanitizer::mergeAttributes(
1892                         Sanitizer::decodeTagAttributes( $aprops ),
1893                         Sanitizer::decodeTagAttributes( $style )
1894                 );
1895                 $query = wfCgiToArray( $query );
1896                 list( $inside, $trail ) = Linker::splitTrail( $trail );
1897
1898                 $ret = $this->link( $title, "$prefix$text$inside", $attribs, $query,
1899                         array( 'known', 'noclasses' ) ) . $trail;
1900
1901                 wfProfileOut( __METHOD__ );
1902                 return $ret;
1903         }
1904
1905         /**
1906          * @deprecated Use link()
1907          *
1908          * Make a red link to the edit page of a given title.
1909          *
1910          * @param $title Title object of the target page
1911          * @param $text  String: Link text
1912          * @param $query String: Optional query part
1913          * @param $trail String: Optional trail. Alphabetic characters at the start of this string will
1914          *                      be included in the link text. Other characters will be appended after
1915          *                      the end of the link.
1916          * @param $prefix String: Optional prefix
1917          */
1918         function makeBrokenLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
1919                 wfProfileIn( __METHOD__ );
1920
1921                 list( $inside, $trail ) = Linker::splitTrail( $trail );
1922                 if ( $text === '' ) {
1923                         $text = $this->linkText( $title );
1924                 }
1925
1926                 $ret = $this->link( $title, "$prefix$text$inside", array(),
1927                         wfCgiToArray( $query ), 'broken' ) . $trail;
1928
1929                 wfProfileOut( __METHOD__ );
1930                 return $ret;
1931         }
1932
1933         /**
1934          * @deprecated Use link()
1935          *
1936          * Make a brown link to a short article.
1937          *
1938          * @param $nt Title object of the target page
1939          * @param $text  String: link text
1940          * @param $query String: optional query part
1941          * @param $trail String: optional trail. Alphabetic characters at the start of this string will
1942          *                      be included in the link text. Other characters will be appended after
1943          *                      the end of the link.
1944          * @param $prefix String: Optional prefix
1945          */
1946         function makeStubLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
1947                 // wfDeprecated( __METHOD__ );
1948                 return $this->makeColouredLinkObj( $nt, 'stub', $text, $query, $trail, $prefix );
1949         }
1950
1951         /**
1952          * @deprecated Use link()
1953          *
1954          * Make a coloured link.
1955          *
1956          * @param $nt Title object of the target page
1957          * @param $colour Integer: colour of the link
1958          * @param $text   String:  link text
1959          * @param $query  String:  optional query part
1960          * @param $trail  String:  optional trail. Alphabetic characters at the start of this string will
1961          *                      be included in the link text. Other characters will be appended after
1962          *                      the end of the link.
1963          * @param $prefix String: Optional prefix
1964          */
1965         function makeColouredLinkObj( $nt, $colour, $text = '', $query = '', $trail = '', $prefix = '' ) {
1966                 // wfDeprecated( __METHOD__ );
1967                 if ( $colour != '' ) {
1968                         $style = $this->getInternalLinkAttributesObj( $nt, $text, $colour );
1969                 } else $style = '';
1970                 return $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix, '', $style );
1971         }
1972
1973         /** Obsolete alias */
1974         function makeImage( $url, $alt = '' ) {
1975                 wfDeprecated( __METHOD__ );
1976                 return $this->makeExternalImage( $url, $alt );
1977         }
1978
1979         /**
1980          * Creates the HTML source for images
1981          * @deprecated use makeImageLink2
1982          *
1983          * @param $title Title object
1984          * @param $label String: label text
1985          * @param $alt String: alt text
1986          * @param $align String: horizontal alignment: none, left, center, right)
1987          * @param $handlerParams Array: parameters to be passed to the media handler
1988          * @param $framed Boolean: shows image in original size in a frame
1989          * @param $thumb Boolean: shows image as thumbnail in a frame
1990          * @param $manualthumb String: image name for the manual thumbnail
1991          * @param $valign String: vertical alignment: baseline, sub, super, top, text-top, middle, bottom, text-bottom
1992          * @param $time String: timestamp of the file, set as false for current
1993          * @return String
1994          */
1995         function makeImageLinkObj( $title, $label, $alt, $align = '', $handlerParams = array(), $framed = false,
1996           $thumb = false, $manualthumb = '', $valign = '', $time = false )
1997         {
1998                 $frameParams = array( 'alt' => $alt, 'caption' => $label );
1999                 if ( $align ) {
2000                         $frameParams['align'] = $align;
2001                 }
2002                 if ( $framed ) {
2003                         $frameParams['framed'] = true;
2004                 }
2005                 if ( $thumb ) {
2006                         $frameParams['thumbnail'] = true;
2007                 }
2008                 if ( $manualthumb ) {
2009                         $frameParams['manualthumb'] = $manualthumb;
2010                 }
2011                 if ( $valign ) {
2012                         $frameParams['valign'] = $valign;
2013                 }
2014                 $file = wfFindFile( $title, array( 'time' => $time ) );
2015                 return $this->makeImageLink2( $title, $file, $frameParams, $handlerParams, $time );
2016         }
2017
2018         /** @deprecated use Linker::makeMediaLinkObj() */
2019         function makeMediaLink( $name, $unused = '', $text = '', $time = false ) {
2020                 $nt = Title::makeTitleSafe( NS_FILE, $name );
2021                 return $this->makeMediaLinkObj( $nt, $text, $time );
2022         }
2023
2024         /**
2025          * Used to generate section edit links that point to "other" pages
2026          * (sections that are really part of included pages).
2027          *
2028          * @deprecated use Linker::doEditSectionLink()
2029          * @param $title Title string.
2030          * @param $section Integer: section number.
2031          */
2032         public function editSectionLinkForOther( $title, $section ) {
2033                 wfDeprecated( __METHOD__ );
2034                 $title = Title::newFromText( $title );
2035                 return $this->doEditSectionLink( $title, $section );
2036         }
2037
2038         /**
2039          * @deprecated use Linker::doEditSectionLink()
2040          * @param $nt Title object.
2041          * @param $section Integer: section number.
2042          * @param $hint Link String: title, or default if omitted or empty
2043          */
2044         public function editSectionLink( Title $nt, $section, $hint = '' ) {
2045                 wfDeprecated( __METHOD__ );
2046                 if ( $hint === '' ) {
2047                         # No way to pass an actual empty $hint here!  The new interface al-
2048                         # lows this, so we have to do this for compatibility.
2049                         $hint = null;
2050                 }
2051                 return $this->doEditSectionLink( $nt, $section, $hint );
2052         }
2053
2054         /**
2055          * Returns the attributes for the tooltip and access key.
2056          */
2057         public function tooltipAndAccesskeyAttribs( $name ) {
2058                 global $wgEnableTooltipsAndAccesskeys;
2059                 if ( !$wgEnableTooltipsAndAccesskeys )
2060                         return array();
2061                 # FIXME: If Sanitizer::expandAttributes() treated "false" as "output
2062                 # no attribute" instead of "output '' as value for attribute", this
2063                 # would be three lines.
2064                 $attribs = array(
2065                         'title' => $this->titleAttrib( $name, 'withaccess' ),
2066                         'accesskey' => $this->accesskey( $name )
2067                 );
2068                 if ( $attribs['title'] === false ) {
2069                         unset( $attribs['title'] );
2070                 }
2071                 if ( $attribs['accesskey'] === false ) {
2072                         unset( $attribs['accesskey'] );
2073                 }
2074                 return $attribs;
2075         }
2076         /**
2077          * @deprecated Returns raw bits of HTML, use titleAttrib() and accesskey()
2078          */
2079         public function tooltipAndAccesskey( $name ) {
2080                 return Xml::expandAttributes( $this->tooltipAndAccesskeyAttribs( $name ) );
2081         }
2082
2083         /** @deprecated Returns raw bits of HTML, use titleAttrib() */
2084         public function tooltip( $name, $options = null ) {
2085                 global $wgEnableTooltipsAndAccesskeys;
2086                 if ( !$wgEnableTooltipsAndAccesskeys )
2087                         return '';
2088                 # FIXME: If Sanitizer::expandAttributes() treated "false" as "output
2089                 # no attribute" instead of "output '' as value for attribute", this
2090                 # would be two lines.
2091                 $tooltip = $this->titleAttrib( $name, $options );
2092                 if ( $tooltip === false ) {
2093                         return '';
2094                 }
2095                 return Xml::expandAttributes( array(
2096                         'title' => $this->titleAttrib( $name, $options )
2097                 ) );
2098         }
2099 }