Please upgrade here. These earlier versions are no longer being updated and have security issues.
HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.

DoFollow certain domains

GermontGermont New
edited January 2018 in Vanilla 2.0 - 2.8

I was happy that some domains linked from the forum increased traffic due to the new version of Vanilla.
Well, it couldn't be. I found in the source page that every external link is nofollowed, even without pop-up link.

Is there any solution to exclude nofollow for certain external domains?
Thank you!

Comments

  • rel="nofollow" does not exist in the database! Meaning it's added on the fly, at every page request :)

  • I don't think it would be possible to do that without touching core files.

    Look at the Format class, especially those two blocks:

        /**
         * @var bool Flag which allows plugins to decide if the output should include rel="nofollow" on any <a> links.
         *
         * @example a plugin can run on "BeforeCommentBody" to check the current users role and decide if his/her post
         * should contain rel="nofollow" links. The default setting is true, meaning all links will contain
         * the rel="nofollow" attribute.
         */
        public static $DisplayNoFollow = true;
    

    and

        /**
         * Formats the anchor tags around the links in text.
         *
         * @param mixed $mixed An object, array, or string to be formatted.
         * @return string
         */
        public static function links($mixed) {
            if (!c('Garden.Format.Links', true)) {
                return $mixed;
            }
    
            if (!is_string($mixed)) {
                return self::to($mixed, 'Links');
            }
    
            $linksCallback = function($matches) {
                static $inTag = 0;
                static $inAnchor = false;
    
                $inOut = $matches[1];
                $tag = strtolower($matches[2]);
    
                if ($inOut == '<') {
                    $inTag++;
                    if ($tag == 'a') {
                        $inAnchor = true;
                    }
                } elseif ($inOut == '</') {
                    $inTag++;
                    if ($tag == 'a') {
                        $inAnchor = false;
                    }
                } elseif ($matches[3]) {
                    $inTag--;
                }
    
                if (c('Garden.Format.WarnLeaving', false) && isset($matches[4]) && $inAnchor) {
                    // This is a the href url value in an anchor tag.
                    $url = $matches[4];
                    $domain = parse_url($url, PHP_URL_HOST);
                    if (!isTrustedDomain($domain)) {
                        return url('/home/leaving?target='.urlencode($url)).'" class="Popup';
                    }
                }
    
                if (!isset($matches[4]) || $inTag || $inAnchor) {
                    return $matches[0];
                }
                // We are not in a tag and what we matched starts with //
                if (preg_match('#^//#', $matches[4])) {
                    return $matches[0];
                }
    
                $url = $matches[4];
    
                $embeddedResult = self::embedReplacement($url);
                if ($embeddedResult !== '') {
                    return $embeddedResult;
                }
    
                // Unformatted links
                if (!self::$FormatLinks) {
                    return $url;
                }
    
                // Strip punctuation off of the end of the url.
                $punc = '';
    
                // Special case where &nbsp; is right after an url and is not part of it!
                // This can happen in WYSIWYG format if the url is the last text of the body.
                while (stringEndsWith($url, '&nbsp;')) {
                    $url = substr($url, 0, -6);
                    $punc .= '&nbsp;';
                }
    
                if (preg_match('`^(.+)([.?,;:])$`', $url, $matches)) {
                    $url = $matches[1];
                    $punc = $matches[2].$punc;
                }
    
                // Get human-readable text from url.
                $text = $url;
                if (strpos($text, '%') !== false) {
                    $text = rawurldecode($text);
                    $text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
                }
    
                $nofollow = (self::$DisplayNoFollow) ? ' rel="nofollow"' : '';
    
                if (c('Garden.Format.WarnLeaving', false)) {
                    // This is a plaintext url we're converting into an anchor.
                    $domain = parse_url($url, PHP_URL_HOST);
                    if (!isTrustedDomain($domain)) {
                        return '<a href="'.url('/home/leaving?target='.urlencode($url)).'" class="Popup">'.$text.'</a>'.$punc;
                    }
                }
    
                return '<a href="'.$url.'"'.$nofollow.'>'.$text.'</a>'.$punc;
            };
    
            if (unicodeRegexSupport()) {
                $regex = "`(?:(</?)([!a-z]+))|(/?\s*>)|((?:(?:https?|ftp):)?//[@\p{L}\p{N}\x21\x23-\x27\x2a-\x2e\x3a\x3b\/\x3f-\x7a\x7e\x3d]+)`iu";
            } else {
                $regex = "`(?:(</?)([!a-z]+))|(/?\s*>)|((?:(?:https?|ftp):)?//[@a-z0-9\x21\x23-\x27\x2a-\x2e\x3a\x3b\/\x3f-\x7a\x7e\x3d]+)`i";
            }
    
            $mixed = Gdn_Format::replaceButProtectCodeBlocks(
                $regex,
                $linksCallback,
                $mixed,
                true
            );
    
            Gdn::pluginManager()->fireAs('Format')->fireEvent('Links', ['Mixed' => &$mixed]);
    
            return $mixed;
        }
    

    A plugin could change the way links are tagged, but only on a per-body basis, not on a per link basis.

    You could insert a fireEvent call before $nofollow = (self::$DisplayNoFollow) ? ' rel="nofollow"' : '';, passing in the link url so tha you can hook into it.

    It might be more simple to work with JavaScript in this case and this plugin would be a good starting point.

  • /\

    there is an event there

    Gdn::pluginManager()->fireAs('Format')->fireEvent('Links', ['Mixed' => &$mixed]);
    

    that mean you could use the hook format_links_handler it is after the fact, but you could still do a preg_replace on $mixed

    grep is your friend.

  • Unfortunately my php skills are limited to changing values from 'true' to 'false', or searching for a missing semicolon.

    I suppose Vanilla premium clients use a version with more basic features like opening links in new tab without configuring a plugin, or favoring your sites on your own forum. Or at least I hope they do.

  • I changed public static $DisplayNoFollow = true; to false, removed the line
    $nofollow = (self::$DisplayNoFollow) ? ' rel="nofollow"' : '';and the attribute it's still there.

  • Oh please don't do it like that! Get the [example plugin](https://github.com/vanilla/addons/tree/master/plugins/example and start with that.
    1. Read the little bit that is needed to name it correctly
    2. In the example plugin file delete everything that is in the curly braces from the class
    3. Add the following code between the braces:

    public function format_links_handler($sender, $args) {
        // This will print out the text in a way that only admins can see it    
        decho($args['Mixed']);
    
        // Now we do some transformation
        $args['Mixed'] = str_replace('nofollow', 'dofollow', $args['Mixed']);
    }
    

    That's the start. You would have to find out about regular expressions in order to change the behaviour the way you like it and that is not too easy but you can alwaays ask...

  • I tried this and it does not work.

  • I'm pretty confident that it can be done via injected jQuery... There are some plugins that inject jquery, so you can research their source and find which hook to use for the injection.

    It's a different approach than the one suggested by R_J (but I think his method should also work).

  • BleistivtBleistivt Moderator
    edited August 2018

    What @R_J posted should work. Just use $sender->EventArguments instead of $args if you are modifying event arguments (this changed some time ago unfortunately). <- in this case, it still works :D
    Please post your plugin code, so we can help you, @ptoone

  • I want to retract my alternate suggestion to use jquery. While it can be used to add attributes to the HTML tag in question, it wouldn't necessarily yield the desired effect because the addition of the rel=nofollow will be done at the client side and it is not guaranteed that web crawlers will see that addition.

    Bottom line - ignore the suggestion. Use @R_J method.

Sign In or Register to comment.