Update docs to caution against use of create_function(). Per bug 15476.
[lhc/web/wiklou.git] / includes / Linker.php
index 79794d4..ea7c387 100644 (file)
@@ -124,7 +124,9 @@ class Linker {
                if ( $t->isRedirect() ) {
                        # Page is a redirect
                        $colour = 'mw-redirect';
-               } elseif ( $threshold > 0 && $t->getLength() < $threshold && MWNamespace::isContent( $t->getNamespace() ) ) {
+               } elseif ( $threshold > 0 && 
+                          $t->exists() && $t->getLength() < $threshold &&
+                          MWNamespace::isContent( $t->getNamespace() ) ) {
                        # Page is a stub
                        $colour = 'stub';
                }
@@ -149,14 +151,14 @@ class Linker {
         *   the link text.  This is raw HTML and will not be escaped.  If null,
         *   defaults to the prefixed text of the Title; or if the Title is just a
         *   fragment, the contents of the fragment.
-        * @param $query         array  The query string to append to the URL
-        *   you're linking to, in key => value array form.  Query keys and values
-        *   will be URL-encoded.
         * @param $customAttribs array  A key => value array of extra HTML attri-
         *   butes, such as title and class.  (href is ignored.)  Classes will be
         *   merged with the default classes, while other attributes will replace
         *   default attributes.  All passed attribute values will be HTML-escaped.
         *   A false attribute value means to suppress that attribute.
+        * @param $query         array  The query string to append to the URL
+        *   you're linking to, in key => value array form.  Query keys and values
+        *   will be URL-encoded.
         * @param $options       mixed  String or array of strings:
         *     'known': Page is known to exist, so don't check if it does.
         *     'broken': Page is known not to exist, so don't check if it does.
@@ -176,7 +178,7 @@ class Linker {
                }
 
                if( !$target instanceof Title ) {
-                       throw new MWException( 'Linker::link passed invalid target' );
+                       return "<!-- ERROR -->$text";
                }
                $options = (array)$options;
 
@@ -270,7 +272,7 @@ class Linker {
                        } elseif( $target->isContentPage() ) {
                                # Check for stub.
                                $threshold = $wgUser->getOption( 'stubthreshold' );
-                               if( $threshold > 0 and $target->getLength() < $threshold ) {
+                               if( $threshold > 0 and $target->exists() and $target->getLength() < $threshold ) {
                                        $classes[] = 'stub';
                                }
                        }
@@ -303,6 +305,11 @@ class Linker {
        }
 
        private function linkText( $target ) {
+               # We might be passed a non-Title by make*LinkObj().  Fail gracefully.
+               if( !$target instanceof Title ) {
+                       return '';
+               }
+
                # If the target is just a fragment, with no title, we return the frag-
                # ment text.  Otherwise, we return the title text itself.
                if( $target->getPrefixedText() === '' and $target->getFragment() !== '' ) {
@@ -424,7 +431,7 @@ class Linker {
         *                      the end of the link.
         * @param $prefix String: optional prefix. As trail, only before instead of after.
         */
-       function makeLinkObj( Title $nt, $text= '', $query = '', $trail = '', $prefix = '' ) {
+       function makeLinkObj( $nt, $text= '', $query = '', $trail = '', $prefix = '' ) {
                global $wgUser;
                wfProfileIn( __METHOD__ );
 
@@ -456,7 +463,7 @@ class Linker {
         * @param $style  String: style to apply - if empty, use getInternalLinkAttributesObj instead
         * @return the a-element
         */
-       function makeKnownLinkObj( Title $title, $text = '', $query = '', $trail = '', $prefix = '' , $aprops = '', $style = '' ) {
+       function makeKnownLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' , $aprops = '', $style = '' ) {
                wfProfileIn( __METHOD__ );
 
                if ( $text == '' ) {
@@ -488,7 +495,7 @@ class Linker {
         *                      be included in the link text. Other characters will be appended after
         *                      the end of the link.
         */
-       function makeBrokenLinkObj( Title $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
+       function makeBrokenLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
                wfProfileIn( __METHOD__ );
 
                list( $inside, $trail ) = Linker::splitTrail( $trail );
@@ -1158,7 +1165,8 @@ class Linker {
 
                # Sanitize text a bit:
                $comment = str_replace( "\n", " ", $comment );
-               $comment = htmlspecialchars( $comment );
+               # Allow HTML entities (for bug 13815)
+               $comment = Sanitizer::escapeHtmlAllowEntities( $comment );
 
                # Render autocomments and make links:
                $comment = $this->formatAutoComments( $comment, $title, $local );
@@ -1182,46 +1190,62 @@ class Linker {
         * @todo Document the $local parameter.
         */
        private function formatAutocomments( $comment, $title = null, $local = false ) {
-               $match = array();
-               while (preg_match('!(.*)/\*\s*(.*?)\s*\*/(.*)!', $comment,$match)) {
-                       $pre=$match[1];
-                       $auto=$match[2];
-                       $post=$match[3];
-                       $link='';
-                       if( $title ) {
-                               $section = $auto;
-
-                               # Generate a valid anchor name from the section title.
-                               # Hackish, but should generally work - we strip wiki
-                               # syntax, including the magic [[: that is used to
-                               # "link rather than show" in case of images and
-                               # interlanguage links.
-                               $section = str_replace( '[[:', '', $section );
-                               $section = str_replace( '[[', '', $section );
-                               $section = str_replace( ']]', '', $section );
-                               if ( $local ) {
-                                       $sectionTitle = Title::newFromText( '#' . $section );
-                               } else {
-                                       $sectionTitle = clone( $title );
-                                       $sectionTitle->mFragment = $section;
-                               }
+               // Bah!
+               $this->autocommentTitle = $title;
+               $this->autocommentLocal = $local;
+               $comment = preg_replace_callback(
+                       '!(.*)/\*\s*(.*?)\s*\*/(.*)!',
+                       array( $this, 'formatAutocommentsCallback' ),
+                       $comment );
+               unset( $this->autocommentTitle );
+               unset( $this->autocommentLocal );
+               return $comment;
+       }
+       
+       private function formatAutocommentsCallback( $match ) {
+               $title = $this->autocommentTitle;
+               $local = $this->autocommentLocal;
+               
+               $pre=$match[1];
+               $auto=$match[2];
+               $post=$match[3];
+               $link='';
+               if( $title ) {
+                       $section = $auto;
+
+                       # Generate a valid anchor name from the section title.
+                       # Hackish, but should generally work - we strip wiki
+                       # syntax, including the magic [[: that is used to
+                       # "link rather than show" in case of images and
+                       # interlanguage links.
+                       $section = str_replace( '[[:', '', $section );
+                       $section = str_replace( '[[', '', $section );
+                       $section = str_replace( ']]', '', $section );
+                       if ( $local ) {
+                               $sectionTitle = Title::newFromText( '#' . $section );
+                       } else {
+                               $sectionTitle = Title::makeTitleSafe( $title->getNamespace(), 
+                                       $title->getDBkey(), $section );
+                       }
+                       if ( $sectionTitle ) {
                                $link = $this->link( $sectionTitle,
                                        wfMsgForContent( 'sectionlink' ), array(), array(),
                                        'noclasses' );
+                       } else {
+                               $link = '';
                        }
-                       $auto = $link . $auto;
-                       if( $pre ) {
-                               # written summary $presep autocomment (summary /* section */)
-                               $auto = wfMsgExt( 'autocomment-prefix', array( 'escapenoentities', 'content' ) ) . $auto;
-                       }
-                       if( $post ) {
-                               # autocomment $postsep written summary (/* section */ summary)
-                               $auto .= wfMsgExt( 'colon-separator', array( 'escapenoentities', 'content' ) );
-                       }
-                       $auto = '<span class="autocomment">' . $auto . '</span>';
-                       $comment = $pre . $auto . $post;
                }
-
+               $auto = "$link$auto";
+               if( $pre ) {
+                       # written summary $presep autocomment (summary /* section */)
+                       $auto = wfMsgExt( 'autocomment-prefix', array( 'escapenoentities', 'content' ) ) . $auto;
+               }
+               if( $post ) {
+                       # autocomment $postsep written summary (/* section */ summary)
+                       $auto .= wfMsgExt( 'colon-separator', array( 'escapenoentities', 'content' ) );
+               }
+               $auto = '<span class="autocomment">' . $auto . '</span>';
+               $comment = $pre . $auto . $post;
                return $comment;
        }
 
@@ -1428,7 +1452,7 @@ class Linker {
                if( !is_null( $tooltip ) ) {
                        $attribs['title'] = wfMsg( 'editsectionhint', $tooltip );
                }
-               $url = $this->link( $nt, wfMsg('editsection'),
+               $link = $this->link( $nt, wfMsg('editsection'),
                        $attribs,
                        array( 'action' => 'edit', 'section' => $section ),
                        array( 'noclasses', 'known' )
@@ -1442,19 +1466,19 @@ class Linker {
                        $attribs = " title=\"$attribs\"";
                }
                $result = null;
-               wfRunHooks( 'EditSectionLink', array( &$this, $nt, $section, $attribs, $url, &$result ) );
+               wfRunHooks( 'EditSectionLink', array( &$this, $nt, $section, $attribs, $link, &$result ) );
                if( !is_null( $result ) ) {
                        # For reverse compatibility, add the brackets *after* the hook is
                        # run, and even add them to hook-provided text.  (This is the main
                        # reason that the EditSectionLink hook is deprecated in favor of
                        # DoEditSectionLink: it can't change the brackets or the span.)
-                       $result = wfMsgHtml( 'editsection-brackets', $url );
+                       $result = wfMsgHtml( 'editsection-brackets', $result );
                        return "<span class=\"editsection\">$result</span>";
                }
 
                # Add the brackets and the span, and *then* run the nice new hook, with
                # clean and non-redundant arguments.
-               $result = wfMsgHtml( 'editsection-brackets', $url );
+               $result = wfMsgHtml( 'editsection-brackets', $link );
                $result = "<span class=\"editsection\">$result</span>";
 
                wfRunHooks( 'DoEditSectionLink', array( $this, $nt, $section, $tooltip, &$result ) );
@@ -1537,7 +1561,8 @@ class Linker {
                }
                $query['token'] = $wgUser->editToken( array( $title->getPrefixedText(),
                        $rev->getUserText() ) );
-               return $this->link( $title, wfMsgHtml( 'rollbacklink' ), array(),
+               return $this->link( $title, wfMsgHtml( 'rollbacklink' ),
+                       array( 'title' => wfMsg( 'tooltip-rollback' ) ),
                        $query, array( 'known', 'noclasses' ) );
        }
 
@@ -1645,26 +1670,29 @@ class Linker {
         * @return string title and accesskey attributes, ready to drop in an
         *   element (e.g., ' title="This does something [x]" accesskey="x"').
         */
-       public function tooltipAndAccesskey($name) {
-               $fname="Linker::tooltipAndAccesskey";
-               wfProfileIn($fname);
-               $out = '';
+       public function tooltipAndAccesskey( $name ) {
+               wfProfileIn( __METHOD__ );
+               $attribs = array();
 
-               $tooltip = wfMsg('tooltip-'.$name);
-               if (!wfEmptyMsg('tooltip-'.$name, $tooltip) && $tooltip != '-') {
+               $tooltip = wfMsg( "tooltip-$name" );
+               if( !wfEmptyMsg( "tooltip-$name", $tooltip ) && $tooltip != '-' ) {
                        // Compatibility: formerly some tooltips had [alt-.] hardcoded
                        $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
-                       $out .= ' title="'.htmlspecialchars($tooltip);
+                       $attribs['title'] = $tooltip;
                }
-               $accesskey = wfMsg('accesskey-'.$name);
-               if ($accesskey && $accesskey != '-' && !wfEmptyMsg('accesskey-'.$name, $accesskey)) {
-                       if ($out) $out .= " [$accesskey]\" accesskey=\"$accesskey\"";
-                       else $out .= " title=\"[$accesskey]\" accesskey=\"$accesskey\"";
-               } elseif ($out) {
-                       $out .= '"';
+
+               $accesskey = wfMsg( "accesskey-$name" );
+               if( $accesskey && $accesskey != '-' &&
+               !wfEmptyMsg( "accesskey-$name", $accesskey ) ) {
+                       if( isset( $attribs['title'] ) ) {
+                               $attribs['title'] .= " [$accesskey]";
+                       }
+                       $attribs['accesskey'] = $accesskey;
                }
-               wfProfileOut($fname);
-               return $out;
+
+               $ret = Xml::expandAttributes( $attribs );
+               wfProfileOut( __METHOD__ );
+               return $ret;
        }
 
        /**
@@ -1673,18 +1701,32 @@ class Linker {
         * isn't always, because sometimes the accesskey needs to go on a different
         * element than the id, for reverse-compatibility, etc.)
         *
-        * @param string $name Id of the element, minus prefixes.
+        * @param string $name    Id of the element, minus prefixes.
+        * @param mixed  $options null or the string 'withaccess' to add an access-
+        *   key hint
         * @return string title attribute, ready to drop in an element
         * (e.g., ' title="This does something"').
         */
-       public function tooltip($name) {
-               $out = '';
+       public function tooltip( $name, $options = null ) {
+               wfProfileIn( __METHOD__ );
 
-               $tooltip = wfMsg('tooltip-'.$name);
-               if (!wfEmptyMsg('tooltip-'.$name, $tooltip) && $tooltip != '-') {
-                       $out = ' title="'.htmlspecialchars($tooltip).'"';
+               $attribs = array();
+
+               $tooltip = wfMsg( "tooltip-$name" );
+               if( !wfEmptyMsg( "tooltip-$name", $tooltip ) && $tooltip != '-' ) {
+                       $attribs['title'] = $tooltip;
                }
 
-               return $out;
+               if( isset( $attribs['title'] ) && $options == 'withaccess' ) {
+                       $accesskey = wfMsg( "accesskey-$name" );
+                       if( $accesskey && $accesskey != '-' &&
+                       !wfEmptyMsg( "accesskey-$name", $accesskey ) ) {
+                               $attribs['title'] .= " [$accesskey]";
+                       }
+               }
+
+               $ret = Xml::expandAttributes( $attribs );
+               wfProfileOut( __METHOD__ );
+               return $ret;
        }
 }