Moved image metadata to the database. Changed Image object to have lightweight constr...
[lhc/web/wiklou.git] / includes / Linker.php
1 <?php
2 /**
3 * Split off some of the internal bits from Skin.php.
4 * These functions are used for primarily page content:
5 * links, embedded images, table of contents. Links are
6 * also used in the skin.
7 * @package MediaWiki
8 */
9
10 /**
11 * For the moment, Skin is a descendent class of Linker.
12 * In the future, it should probably be further split
13 * so that ever other bit of the wiki doesn't have to
14 * go loading up Skin to get at it.
15 *
16 * @package MediaWiki
17 */
18 class Linker {
19 var $linktrail ; # linktrail regexp
20 var $postParseLinkColour = false;
21
22 /** @todo document */
23 function Linker() {
24 global $wgContLang;
25 $this->linktrail = $wgContLang->linkTrail();
26 }
27
28 /**
29 * Get/set accessor for delayed link colouring
30 */
31 function postParseLinkColour( $setting = NULL ) {
32 return wfSetVar( $this->postParseLinkColour, $setting );
33 }
34
35 /** @todo document */
36 function getExternalLinkAttributes( $link, $text, $class='' ) {
37 global $wgContLang;
38
39 $same = ($link == $text);
40 $link = urldecode( $link );
41 $link = $wgContLang->checkTitleEncoding( $link );
42 $link = preg_replace( '/[\\x00-\\x1f_]/', ' ', $link );
43 $link = htmlspecialchars( $link );
44
45 $r = ($class != '') ? " class='$class'" : " class='external'";
46
47 $r .= " title=\"{$link}\"";
48 return $r;
49 }
50
51 /** @todo document */
52 function getInternalLinkAttributes( $link, $text, $broken = false ) {
53 $link = urldecode( $link );
54 $link = str_replace( '_', ' ', $link );
55 $link = htmlspecialchars( $link );
56
57 if( $broken == 'stub' ) {
58 $r = ' class="stub"';
59 } else if ( $broken == 'yes' ) {
60 $r = ' class="new"';
61 } else {
62 $r = '';
63 }
64
65 $r .= " title=\"{$link}\"";
66 return $r;
67 }
68
69 /**
70 * @param bool $broken
71 */
72 function getInternalLinkAttributesObj( &$nt, $text, $broken = false ) {
73 if( $broken == 'stub' ) {
74 $r = ' class="stub"';
75 } else if ( $broken == 'yes' ) {
76 $r = ' class="new"';
77 } else {
78 $r = '';
79 }
80
81 $r .= ' title="' . $nt->getEscapedText() . '"';
82 return $r;
83 }
84
85 /**
86 * Note: This function MUST call getArticleID() on the link,
87 * otherwise the cache won't get updated properly. See LINKCACHE.DOC.
88 */
89 function makeLink( $title, $text = '', $query = '', $trail = '' ) {
90 wfProfileIn( 'Skin::makeLink' );
91 $nt = Title::newFromText( $title );
92 if ($nt) {
93 $result = $this->makeLinkObj( Title::newFromText( $title ), $text, $query, $trail );
94 } else {
95 wfDebug( 'Invalid title passed to Skin::makeLink(): "'.$title."\"\n" );
96 $result = $text == "" ? $title : $text;
97 }
98
99 wfProfileOut( 'Skin::makeLink' );
100 return $result;
101 }
102
103 /** @todo document */
104 function makeKnownLink( $title, $text = '', $query = '', $trail = '', $prefix = '',$aprops = '') {
105 $nt = Title::newFromText( $title );
106 if ($nt) {
107 return $this->makeKnownLinkObj( Title::newFromText( $title ), $text, $query, $trail, $prefix , $aprops );
108 } else {
109 wfDebug( 'Invalid title passed to Skin::makeKnownLink(): "'.$title."\"\n" );
110 return $text == '' ? $title : $text;
111 }
112 }
113
114 /** @todo document */
115 function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) {
116 $nt = Title::newFromText( $title );
117 if ($nt) {
118 return $this->makeBrokenLinkObj( Title::newFromText( $title ), $text, $query, $trail );
119 } else {
120 wfDebug( 'Invalid title passed to Skin::makeBrokenLink(): "'.$title."\"\n" );
121 return $text == '' ? $title : $text;
122 }
123 }
124
125 /** @todo document */
126 function makeStubLink( $title, $text = '', $query = '', $trail = '' ) {
127 $nt = Title::newFromText( $title );
128 if ($nt) {
129 return $this->makeStubLinkObj( Title::newFromText( $title ), $text, $query, $trail );
130 } else {
131 wfDebug( 'Invalid title passed to Skin::makeStubLink(): "'.$title."\"\n" );
132 return $text == '' ? $title : $text;
133 }
134 }
135
136 /**
137 * Pass a title object, not a title string
138 */
139 function makeLinkObj( &$nt, $text= '', $query = '', $trail = '', $prefix = '' ) {
140 global $wgOut, $wgUser, $wgLinkHolders;
141 $fname = 'Skin::makeLinkObj';
142 wfProfileIn( $fname );
143
144 # Fail gracefully
145 if ( ! isset($nt) ) {
146 # wfDebugDieBacktrace();
147 wfProfileOut( $fname );
148 return "<!-- ERROR -->{$prefix}{$text}{$trail}";
149 }
150
151 $ns = $nt->getNamespace();
152 $dbkey = $nt->getDBkey();
153 if ( $nt->isExternal() ) {
154 $u = $nt->getFullURL();
155 $link = $nt->getPrefixedURL();
156 if ( '' == $text ) { $text = $nt->getPrefixedText(); }
157 $style = $this->getExternalLinkAttributes( $link, $text, 'extiw' );
158
159 $inside = '';
160 if ( '' != $trail ) {
161 if ( preg_match( '/^([a-z]+)(.*)$$/sD', $trail, $m ) ) {
162 $inside = $m[1];
163 $trail = $m[2];
164 }
165 }
166 $t = "<a href=\"{$u}\"{$style}>{$text}{$inside}</a>";
167 if( $this->postParseLinkColour ) {
168 # There's no existence check, but this will prevent
169 # interwiki links from being parsed as external links.
170 global $wgInterwikiLinkHolders;
171 $nr = array_push($wgInterwikiLinkHolders, $t);
172 $retVal = '<!--IWLINK '. ($nr-1) ."-->{$trail}";
173 } else {
174 return $t;
175 }
176 } elseif ( 0 == $ns && "" == $dbkey ) {
177 # A self-link with a fragment; skip existence check.
178 $retVal = $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix );
179 } elseif ( ( NS_SPECIAL == $ns ) || ( NS_IMAGE == $ns ) ) {
180 # These are always shown as existing, currently.
181 # Special pages don't exist in the database; images may
182 # occasionally be present when there is no description
183 # page per se, so we always shown them.
184 $retVal = $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix );
185 } elseif ( $this->postParseLinkColour ) {
186 wfProfileIn( $fname.'-postparse' );
187 # Insert a placeholder, and we'll work out the existence checks
188 # in a big lump later.
189 $inside = '';
190 if ( '' != $trail ) {
191 if ( preg_match( $this->linktrail, $trail, $m ) ) {
192 $inside = $m[1];
193 $trail = $m[2];
194 }
195 }
196
197 # These get picked up by Parser::replaceLinkHolders()
198 $nr = array_push( $wgLinkHolders['namespaces'], $nt->getNamespace() );
199 $wgLinkHolders['dbkeys'][] = $dbkey;
200 $wgLinkHolders['queries'][] = $query;
201 $wgLinkHolders['texts'][] = $prefix.$text.$inside;
202 $wgLinkHolders['titles'][] =& $nt;
203
204 $retVal = '<!--LINK '. ($nr-1) ."-->{$trail}";
205 wfProfileOut( $fname.'-postparse' );
206 } else {
207 wfProfileIn( $fname.'-immediate' );
208 # Work out link colour immediately
209 $aid = $nt->getArticleID() ;
210 if ( 0 == $aid ) {
211 $retVal = $this->makeBrokenLinkObj( $nt, $text, $query, $trail, $prefix );
212 } else {
213 $threshold = $wgUser->getOption('stubthreshold') ;
214 if ( $threshold > 0 ) {
215 $dbr =& wfGetDB( DB_SLAVE );
216 $s = $dbr->selectRow(
217 array( 'page' ),
218 array( 'page_len',
219 'page_namespace',
220 'page_is_redirect' ),
221 array( 'page_id' => $aid ), $fname ) ;
222 if ( $s !== false ) {
223 $size = $s->page_len;
224 if ( $s->page_is_redirect OR $s->page_namespace != NS_MAIN ) {
225 $size = $threshold*2 ; # Really big
226 }
227 } else {
228 $size = $threshold*2 ; # Really big
229 }
230 } else {
231 $size = 1 ;
232 }
233 if ( $size < $threshold ) {
234 $retVal = $this->makeStubLinkObj( $nt, $text, $query, $trail, $prefix );
235 } else {
236 $retVal = $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix );
237 }
238 }
239 wfProfileOut( $fname.'-immediate' );
240 }
241 wfProfileOut( $fname );
242 return $retVal;
243 }
244
245 /**
246 * Pass a title object, not a title string
247 */
248 function makeKnownLinkObj( &$nt, $text = '', $query = '', $trail = '', $prefix = '' , $aprops = '' ) {
249 global $wgOut, $wgTitle, $wgInputEncoding;
250
251 $fname = 'Skin::makeKnownLinkObj';
252 wfProfileIn( $fname );
253
254 if ( !is_object( $nt ) ) {
255 wfProfileIn( $fname );
256 return $text;
257 }
258
259 $u = $nt->escapeLocalURL( $query );
260 if ( '' != $nt->getFragment() ) {
261 if( $nt->getPrefixedDbkey() == '' ) {
262 $u = '';
263 if ( '' == $text ) {
264 $text = htmlspecialchars( $nt->getFragment() );
265 }
266 }
267 $anchor = urlencode( do_html_entity_decode( str_replace(' ', '_', $nt->getFragment()), ENT_COMPAT, $wgInputEncoding ) );
268 $replacearray = array(
269 '%3A' => ':',
270 '%' => '.'
271 );
272 $u .= '#' . str_replace(array_keys($replacearray),array_values($replacearray),$anchor);
273 }
274 if ( '' == $text ) {
275 $text = htmlspecialchars( $nt->getPrefixedText() );
276 }
277 $style = $this->getInternalLinkAttributesObj( $nt, $text );
278
279 $inside = '';
280 if ( '' != $trail ) {
281 if ( preg_match( $this->linktrail, $trail, $m ) ) {
282 $inside = $m[1];
283 $trail = $m[2];
284 }
285 }
286 $r = "<a href=\"{$u}\"{$style}{$aprops}>{$prefix}{$text}{$inside}</a>{$trail}";
287 wfProfileOut( $fname );
288 return $r;
289 }
290
291 /**
292 * Pass a title object, not a title string
293 */
294 function makeBrokenLinkObj( &$nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
295 # Fail gracefully
296 if ( ! isset($nt) ) {
297 # wfDebugDieBacktrace();
298 return "<!-- ERROR -->{$prefix}{$text}{$trail}";
299 }
300
301 $fname = 'Skin::makeBrokenLinkObj';
302 wfProfileIn( $fname );
303
304 if ( '' == $query ) {
305 $q = 'action=edit';
306 } else {
307 $q = 'action=edit&'.$query;
308 }
309 $u = $nt->escapeLocalURL( $q );
310
311 if ( '' == $text ) {
312 $text = htmlspecialchars( $nt->getPrefixedText() );
313 }
314 $style = $this->getInternalLinkAttributesObj( $nt, $text, "yes" );
315
316 $inside = '';
317 if ( '' != $trail ) {
318 if ( preg_match( $this->linktrail, $trail, $m ) ) {
319 $inside = $m[1];
320 $trail = $m[2];
321 }
322 }
323 $s = "<a href=\"{$u}\"{$style}>{$prefix}{$text}{$inside}</a>{$trail}";
324
325 wfProfileOut( $fname );
326 return $s;
327 }
328
329 /**
330 * Pass a title object, not a title string
331 */
332 function makeStubLinkObj( &$nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
333 $link = $nt->getPrefixedURL();
334
335 $u = $nt->escapeLocalURL( $query );
336
337 if ( '' == $text ) {
338 $text = htmlspecialchars( $nt->getPrefixedText() );
339 }
340 $style = $this->getInternalLinkAttributesObj( $nt, $text, 'stub' );
341
342 $inside = '';
343 if ( '' != $trail ) {
344 if ( preg_match( $this->linktrail, $trail, $m ) ) {
345 $inside = $m[1];
346 $trail = $m[2];
347 }
348 }
349 $s = "<a href=\"{$u}\"{$style}>{$prefix}{$text}{$inside}</a>{$trail}";
350 return $s;
351 }
352
353 /** @todo document */
354 function makeSelfLinkObj( &$nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
355 $u = $nt->escapeLocalURL( $query );
356 if ( '' == $text ) {
357 $text = htmlspecialchars( $nt->getPrefixedText() );
358 }
359 $inside = '';
360 if ( '' != $trail ) {
361 if ( preg_match( $this->linktrail, $trail, $m ) ) {
362 $inside = $m[1];
363 $trail = $m[2];
364 }
365 }
366 return "<strong>{$prefix}{$text}{$inside}</strong>{$trail}";
367 }
368
369 /** @todo document */
370 function fnamePart( $url ) {
371 $basename = strrchr( $url, '/' );
372 if ( false === $basename ) {
373 $basename = $url;
374 } else {
375 $basename = substr( $basename, 1 );
376 }
377 return htmlspecialchars( $basename );
378 }
379
380 /** @todo document */
381 function makeImage( $url, $alt = '' ) {
382 global $wgOut;
383 if ( '' == $alt ) {
384 $alt = $this->fnamePart( $url );
385 }
386 $s = '<img src="'.$url.'" alt="'.$alt.'" />';
387 return $s;
388 }
389
390 /** @todo document */
391 function makeImageLink( $name, $url, $alt = '' ) {
392 $nt = Title::makeTitleSafe( NS_IMAGE, $name );
393 return $this->makeImageLinkObj( $nt, $alt );
394 }
395
396 /** @todo document */
397 function makeImageLinkObj( $nt, $alt = '' ) {
398 global $wgContLang, $wgUseImageResize;
399 $img = new Image( $nt );
400 $url = $img->getViewURL();
401
402 $align = '';
403 $prefix = $postfix = '';
404
405 # Check if the alt text is of the form "options|alt text"
406 # Options are:
407 # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
408 # * left no resizing, just left align. label is used for alt= only
409 # * right same, but right aligned
410 # * none same, but not aligned
411 # * ___px scale to ___ pixels width, no aligning. e.g. use in taxobox
412 # * center center the image
413 # * framed Keep original image size, no magnify-button.
414
415 $part = explode( '|', $alt);
416
417 $mwThumb =& MagicWord::get( MAG_IMG_THUMBNAIL );
418 $mwLeft =& MagicWord::get( MAG_IMG_LEFT );
419 $mwRight =& MagicWord::get( MAG_IMG_RIGHT );
420 $mwNone =& MagicWord::get( MAG_IMG_NONE );
421 $mwWidth =& MagicWord::get( MAG_IMG_WIDTH );
422 $mwCenter =& MagicWord::get( MAG_IMG_CENTER );
423 $mwFramed =& MagicWord::get( MAG_IMG_FRAMED );
424 $alt = '';
425
426 $height = $framed = $thumb = false;
427 $manual_thumb = "" ;
428
429 foreach( $part as $key => $val ) {
430 $val_parts = explode ( "=" , $val , 2 ) ;
431 $left_part = array_shift ( $val_parts ) ;
432 if ( $wgUseImageResize && ! is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
433 $thumb=true;
434 } elseif ( $wgUseImageResize && count ( $val_parts ) == 1 && ! is_null( $mwThumb->matchVariableStartToEnd($left_part) ) ) {
435 # use manually specified thumbnail
436 $thumb=true;
437 $manual_thumb = array_shift ( $val_parts ) ;
438 } elseif ( ! is_null( $mwRight->matchVariableStartToEnd($val) ) ) {
439 # remember to set an alignment, don't render immediately
440 $align = 'right';
441 } elseif ( ! is_null( $mwLeft->matchVariableStartToEnd($val) ) ) {
442 # remember to set an alignment, don't render immediately
443 $align = 'left';
444 } elseif ( ! is_null( $mwCenter->matchVariableStartToEnd($val) ) ) {
445 # remember to set an alignment, don't render immediately
446 $align = 'center';
447 } elseif ( ! is_null( $mwNone->matchVariableStartToEnd($val) ) ) {
448 # remember to set an alignment, don't render immediately
449 $align = 'none';
450 } elseif ( $wgUseImageResize && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
451 # $match is the image width in pixels
452 if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) {
453 $width = intval( $m[1] );
454 $height = intval( $m[2] );
455 } else {
456 $width = intval($match);
457 }
458 } elseif ( ! is_null( $mwFramed->matchVariableStartToEnd($val) ) ) {
459 $framed=true;
460 } else {
461 $alt = $val;
462 }
463 }
464 if ( 'center' == $align )
465 {
466 $prefix = '<div class="center">';
467 $postfix = '</div>';
468 $align = 'none';
469 }
470
471 if ( $thumb || $framed ) {
472
473 # Create a thumbnail. Alignment depends on language
474 # writing direction, # right aligned for left-to-right-
475 # languages ("Western languages"), left-aligned
476 # for right-to-left-languages ("Semitic languages")
477 #
478 # If thumbnail width has not been provided, it is set
479 # here to 180 pixels
480 if ( $align == '' ) {
481 $align = $wgContLang->isRTL() ? 'left' : 'right';
482 }
483 if ( ! isset($width) ) {
484 $width = 180;
485 }
486 return $prefix.$this->makeThumbLinkObj( $img, $alt, $align, $width, $height, $framed, $manual_thumb ).$postfix;
487
488 } elseif ( isset($width) ) {
489
490 # Create a resized image, without the additional thumbnail
491 # features
492
493 if ( ( ! $height === false )
494 && ( $img->getHeight() * $width / $img->getWidth() > $height ) ) {
495 $width = $img->getWidth() * $height / $img->getHeight();
496 }
497 if ( '' == $manual_thumb ) $url = $img->createThumb( $width );
498 }
499
500 # FIXME: This is a gross hack using a global.
501 # Replace link color holders in the caption text so the
502 # text portion can be placed int the alt/title attributes.
503 global $wgParser;
504 $wgParser->replaceLinkHolders( $alt );
505
506 $alt = Sanitizer::stripAllTags( $alt );
507
508 $u = $nt->escapeLocalURL();
509 if ( $url == '' ) {
510 $s = wfMsg( 'missingimage', $img->getName() );
511 $s .= "<br />{$alt}<br />{$url}<br />\n";
512 } else {
513 $s = '<a href="'.$u.'" class="image" title="'.$alt.'">' .
514 '<img src="'.$url.'" alt="'.$alt.'" longdesc="'.$u.'" /></a>';
515 }
516 if ( '' != $align ) {
517 $s = "<div class=\"float{$align}\"><span>{$s}</span></div>";
518 }
519 return str_replace("\n", ' ',$prefix.$s.$postfix);
520 }
521
522 /**
523 * Make HTML for a thumbnail including image, border and caption
524 * $img is an Image object
525 */
526 function makeThumbLinkObj( $img, $label = '', $align = 'right', $boxwidth = 180, $boxheight=false, $framed=false , $manual_thumb = "" ) {
527 global $wgStylePath, $wgContLang;
528 # $image = Title::makeTitleSafe( NS_IMAGE, $name );
529 $url = $img->getViewURL();
530
531 #$label = htmlspecialchars( $label );
532 $alt = Sanitizer::stripAllTags( $label );
533
534 $width = $height = 0;
535 if ( $img->exists() )
536 {
537 $width = $img->getWidth();
538 $height = $img->getHeight();
539 }
540 if ( 0 == $width || 0 == $height )
541 {
542 $width = $height = 200;
543 }
544 if ( $boxwidth == 0 )
545 {
546 $boxwidth = 200;
547 }
548 if ( $framed )
549 {
550 // Use image dimensions, don't scale
551 $boxwidth = $width;
552 $oboxwidth = $boxwidth + 2;
553 $boxheight = $height;
554 $thumbUrl = $url;
555 } else {
556 $h = intval( $height/($width/$boxwidth) );
557 $oboxwidth = $boxwidth + 2;
558 if ( ( ! $boxheight === false ) && ( $h > $boxheight ) )
559 {
560 $boxwidth *= $boxheight/$h;
561 } else {
562 $boxheight = $h;
563 }
564 if ( '' == $manual_thumb ) $thumbUrl = $img->createThumb( $boxwidth );
565 }
566
567 if ( $manual_thumb != '' ) # Use manually specified thumbnail
568 {
569 $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb ); #new Title ( $manual_thumb ) ;
570 $manual_img = new Image( $manual_title );
571 $thumbUrl = $manual_img->getViewURL();
572 if ( $manual_img->exists() )
573 {
574 $width = $manual_img->getWidth();
575 $height = $manual_img->getHeight();
576 $boxwidth = $width ;
577 $boxheight = $height ;
578 $oboxwidth = $boxwidth + 2 ;
579 }
580 }
581
582 $u = $img->getEscapeLocalURL();
583
584 $more = htmlspecialchars( wfMsg( 'thumbnail-more' ) );
585 $magnifyalign = $wgContLang->isRTL() ? 'left' : 'right';
586 $textalign = $wgContLang->isRTL() ? ' style="text-align:right"' : '';
587
588 $s = "<div class=\"thumb t{$align}\"><div style=\"width:{$oboxwidth}px;\">";
589 if ( $thumbUrl == '' ) {
590 $s .= wfMsg( 'missingimage', $img->getName() );
591 $zoomicon = '';
592 } else {
593 $s .= '<a href="'.$u.'" class="internal" title="'.$alt.'">'.
594 '<img src="'.$thumbUrl.'" alt="'.$alt.'" ' .
595 'width="'.$boxwidth.'" height="'.$boxheight.'" ' .
596 'longdesc="'.$u.'" /></a>';
597 if ( $framed ) {
598 $zoomicon="";
599 } else {
600 $zoomicon = '<div class="magnify" style="float:'.$magnifyalign.'">'.
601 '<a href="'.$u.'" class="internal" title="'.$more.'">'.
602 '<img src="'.$wgStylePath.'/common/images/magnify-clip.png" ' .
603 'width="15" height="11" alt="'.$more.'" /></a></div>';
604 }
605 }
606 $s .= ' <div class="thumbcaption" '.$textalign.'>'.$zoomicon.$label."</div></div></div>";
607 return str_replace("\n", ' ', $s);
608 }
609
610 /** @todo document */
611 function makeMediaLink( $name, $url, $alt = '' ) {
612 $nt = Title::makeTitleSafe( NS_IMAGE, $name );
613 return $this->makeMediaLinkObj( $nt, $alt );
614 }
615
616 /**
617 * Create a direct link to a given uploaded file.
618 *
619 * @param Title $title
620 * @param string $text pre-sanitized HTML
621 * @param bool $nourl Mask absolute URLs, so the parser doesn't
622 * linkify them (it is currently not context-aware)
623 * @return string HTML
624 *
625 * @access public
626 * @todo Handle invalid or missing images better.
627 */
628 function makeMediaLinkObj( $title, $text = '', $nourl=false ) {
629 if( is_null( $title ) ) {
630 ### HOTFIX. Instead of breaking, return empty string.
631 return $text;
632 } else {
633 $name = $title->getDBKey();
634 $img = new Image( $title );
635 $url = $img->getURL();
636 if( $nourl ) {
637 $url = str_replace( "http://", "http-noparse://", $url );
638 }
639 $alt = htmlspecialchars( $title->getText() );
640 if( $text == '' ) {
641 $text = $alt;
642 }
643 $u = htmlspecialchars( $url );
644 return "<a href=\"{$u}\" class='internal' title=\"{$alt}\">{$text}</a>";
645 }
646 }
647
648 /** @todo document */
649 function specialLink( $name, $key = '' ) {
650 global $wgContLang;
651
652 if ( '' == $key ) { $key = strtolower( $name ); }
653 $pn = $wgContLang->ucfirst( $name );
654 return $this->makeKnownLink( $wgContLang->specialPage( $pn ),
655 wfMsg( $key ) );
656 }
657
658 /** @todo document */
659 function makeExternalLink( $url, $text, $escape = true, $linktype = '' ) {
660 $style = $this->getExternalLinkAttributes( $url, $text, 'external ' . $linktype );
661 global $wgNoFollowLinks;
662 if( $wgNoFollowLinks ) {
663 $style .= ' rel="nofollow"';
664 }
665 $url = htmlspecialchars( $url );
666 if( $escape ) {
667 $text = htmlspecialchars( $text );
668 }
669 return '<a href="'.$url.'"'.$style.'>'.$text.'</a>';
670 }
671
672 /**
673 * This function is called by all recent changes variants, by the page history,
674 * and by the user contributions list. It is responsible for formatting edit
675 * comments. It escapes any HTML in the comment, but adds some CSS to format
676 * auto-generated comments (from section editing) and formats [[wikilinks]].
677 *
678 * The &$title parameter must be a title OBJECT. It is used to generate a
679 * direct link to the section in the autocomment.
680 * @author Erik Moeller <moeller@scireview.de>
681 *
682 * Note: there's not always a title to pass to this function.
683 * Since you can't set a default parameter for a reference, I've turned it
684 * temporarily to a value pass. Should be adjusted further. --brion
685 */
686 function formatComment($comment, $title = NULL) {
687 $fname = 'Skin::formatComment';
688 wfProfileIn( $fname );
689
690 global $wgContLang;
691 $comment = str_replace( "\n", " ", $comment );
692 $comment = htmlspecialchars( $comment );
693
694 # The pattern for autogen comments is / * foo * /, which makes for
695 # some nasty regex.
696 # We look for all comments, match any text before and after the comment,
697 # add a separator where needed and format the comment itself with CSS
698 while (preg_match('/(.*)\/\*\s*(.*?)\s*\*\/(.*)/', $comment,$match)) {
699 $pre=$match[1];
700 $auto=$match[2];
701 $post=$match[3];
702 $link='';
703 if($title) {
704 $section=$auto;
705
706 # This is hackish but should work in most cases.
707 $section=str_replace('[[','',$section);
708 $section=str_replace(']]','',$section);
709 $title->mFragment=$section;
710 $link=$this->makeKnownLinkObj($title,wfMsg('sectionlink'));
711 }
712 $sep='-';
713 $auto=$link.$auto;
714 if($pre) { $auto = $sep.' '.$auto; }
715 if($post) { $auto .= ' '.$sep; }
716 $auto='<span class="autocomment">'.$auto.'</span>';
717 $comment=$pre.$auto.$post;
718 }
719
720 # format regular and media links - all other wiki formatting
721 # is ignored
722 $medians = $wgContLang->getNsText( NS_MEDIA ) . ':';
723 while(preg_match('/\[\[(.*?)(\|(.*?))*\]\](.*)$/',$comment,$match)) {
724 # Handle link renaming [[foo|text]] will show link as "text"
725 if( "" != $match[3] ) {
726 $text = $match[3];
727 } else {
728 $text = $match[1];
729 }
730 if( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
731 # Media link; trail not supported.
732 $linkRegexp = '/\[\[(.*?)\]\]/';
733 $thelink = $this->makeMediaLink( $submatch[1], "", $text );
734 } else {
735 # Other kind of link
736 if( preg_match( wfMsgForContent( "linktrail" ), $match[4], $submatch ) ) {
737 $trail = $submatch[1];
738 } else {
739 $trail = "";
740 }
741 $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
742 if ($match[1][0] == ':')
743 $match[1] = substr($match[1], 1);
744 $thelink = $this->makeLink( $match[1], $text, "", $trail );
745 }
746 $comment = preg_replace( $linkRegexp, $thelink, $comment, 1 );
747 }
748 wfProfileOut( $fname );
749 return $comment;
750 }
751
752 /**
753 * Wrap a comment in standard punctuation and formatting if
754 * it's non-empty, otherwise return empty string.
755 *
756 * @param string $comment
757 * @param Title $title
758 * @return string
759 * @access public
760 */
761 function commentBlock( $comment, $title = NULL ) {
762 if( $comment == '' || $comment == '*' ) {
763 return '';
764 } else {
765 $formatted = $this->formatComment( $comment, $title );
766 return " <span class='comment'>($formatted)</span>";
767 }
768 }
769
770 /** @todo document */
771 function tocIndent() {
772 return "\n<ul>";
773 }
774
775 /** @todo document */
776 function tocUnindent($level) {
777 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level>0 ? $level : 0 );
778 }
779
780 /**
781 * parameter level defines if we are on an indentation level
782 */
783 function tocLine( $anchor, $tocline, $tocnumber, $level ) {
784 return "\n<li class='toclevel-$level'><a href=\"#" .
785 $anchor . '"><span class="tocnumber">' .
786 $tocnumber . '</span> <span class="toctext">' .
787 $tocline . '</span></a>';
788 }
789
790 /** @todo document */
791 function tocLineEnd() {
792 return "</li>\n";
793 }
794
795 /** @todo document */
796 function tocList($toc) {
797 return "<div id='toc'>\n"
798 . "<div id='toctitle'><h2>" . wfMsg('toc') . "</h2></div>\n"
799 . $toc
800 . "</ul>\n</div>\n"
801 . '<script type="text/javascript">'
802 . ' if (window.showTocToggle) {'
803 . ' var tocShowText = "' . addslashes( wfMsg('showtoc') ) . '";'
804 . ' var tocHideText = "' . addslashes( wfMsg('hidetoc') ) . '"; '
805 . ' showTocToggle();'
806 . ' } '
807 . '</script>'
808 . "<div class='visualClear'></div>\n";
809 }
810
811 /** @todo document */
812 function editSectionLinkForOther( $title, $section ) {
813 global $wgRequest;
814 global $wgContLang;
815
816 $title = Title::newFromText($title);
817 $editurl = '&section='.$section;
818 $url = $this->makeKnownLink($title->getPrefixedText(),wfMsg('editsection'),'action=edit'.$editurl);
819
820 if( $wgContLang->isRTL() ) {
821 $farside = 'left';
822 $nearside = 'right';
823 } else {
824 $farside = 'right';
825 $nearside = 'left';
826 }
827 return "<div class=\"editsection\" style=\"float:$farside;margin-$nearside:5px;\">[".$url."]</div>";
828
829 }
830
831 /** @todo document */
832 function editSectionLink( $nt, $section ) {
833 global $wgRequest;
834 global $wgContLang;
835
836 if( $wgRequest->getInt( 'oldid' ) && ( $wgRequest->getVal( 'diff' ) != '0' ) ) {
837 # Section edit links would be out of sync on an old page.
838 # But, if we're diffing to the current page, they'll be
839 # correct.
840 return '';
841 }
842
843 $editurl = '&section='.$section;
844 $url = $this->makeKnownLink($nt->getPrefixedText(),wfMsg('editsection'),'action=edit'.$editurl);
845
846 if( $wgContLang->isRTL() ) {
847 $farside = 'left';
848 $nearside = 'right';
849 } else {
850 $farside = 'right';
851 $nearside = 'left';
852 }
853 return "<div class=\"editsection\" style=\"float:$farside;margin-$nearside:5px;\">[".$url."]</div>";
854 }
855 }
856 ?>