Modify img bbcode

Jeremy

Well-known member
OK, I'm going to explain how to accomplish this while detailing how the system works for you. If I go over your head, please tell me and I will spend some time attempting to clarify. In XenForo_BbCode_Formatter_Base you will find a function called getTags(); (found on line 261). Within, you will find a multi-dimensional array like so (this is pulling out the exact code you'll need to look at):
PHP:
array(
	'img' => array(
			'hasOption' => false,
			'plainChildren' => true,
			'callback' => array($this, 'renderTagImage')
	),
);
If you noticed, $array['img']['callback'] is an array with two members. The first member, $this, is the class in which the rendering function takes place and the second, 'renderTagImage' (in this case at least), is the function name in which rendering actually takes place. From this, we know that there is a function located within the same class and file that we want to extend. So, you'll create your own class as such:

PHP:
<?php
class CamoUrl_BbCode_Formatter_Base extends XFCP_CamoUrl_BbCode_Formatter_Base
{
	/**
	 * Renders a img tag.
	 *
	 * @param array $tag Information about the tag reference; keys: tag, option, children
	 * @param array $rendererStates Renderer states to push down
	 *
	 * @return string Rendered tag
	 */
	public function renderTagImage(array $tag, array $rendererStates)
	{
		// Do your special handling here, making sure the return statement will have the correct inputs…
		
		return sprintf($this->_imageTemplate, htmlspecialchars($validUrl), ($rendererStates['lightBox'] ? ' LbImage' : ''));
	}
}
In this function, you have to create the $validUrl variable and handle smilies in the same function. I would suggest looking at and reading XenForo_BbCode_Formatter_Base::renderTagImage(); before adding your additions, and copying the code you won't be changing over. Note, XenForo coding standards leaves out the closing ?>.

Next, you'll set up your listener, just like you had before. That part was correct.
 

anonymous

Member
Patiently explaining rocket science in terms a 12 year old would understand.
HOLY * you are awesome.

PHP:
<?php
class CamoUrl_BbCode_Formatter_Base extends XFCP_CamoUrl_BbCode_Formatter_Base
{
      /**
        * Renders a img tag.
        *
        * @param array $tag Information about the tag reference; keys: tag, option, children
        * @param array $rendererStates Renderer states to push down
        *
        * @return string Rendered tag
        */
        public function renderTagImage(array $tag, array $rendererStates)
        {
                $url = $this->stringifyTree($tag['children']);
                                        $prefix = 'https://domain/asset-proxy/';
                                        $camokey = 'secret';
                $validUrl = "$prefix" . hash_hmac('sha1', $this->_getValidUrl($url), $camokey) . '/'. bin2hex($this->_getValidUrl($url));
 
 
 
                if (!$validUrl)
                {
                        return $this->filterString($url, $rendererStates);
                }
 
                $censored = XenForo_Helper_String::censorString($validUrl);
                if ($censored != $validUrl)
                {
                        return $this->filterString($url, $rendererStates);
                }
 
                // attempts to convert smilies posted as [IMG] tags back into smilies
                if ($rendererStates['imgToSmilie'])
                {
                        foreach ($this->_smiliePaths AS $smiliePath => $smilieId)
                        {
                                if (strpos($url, $smiliePath) !== false && substr($url, strlen($smiliePath) * -1) == $smiliePath)
                                {
                                        return $this->_smilieReverse[$smilieId];
                    }
                        }
                }
 
                return sprintf($this->_imageTemplate, htmlspecialchars($validUrl), ($rendererStates['lightBox'] ? ' LbImage' : ''));
        }
 
}
This works! I love you, now all i need is find out how to make :
$prefix and $camokey configurable options, since the method i use right now is not very elegant, it works for me but if i'm ever going to write the full https guide (as I should, it's only fair to contribute back after the help i got) I ought include a fully functional addon.

Thank you a million, I would suggest you get hammered on me, but it's monday.
 

anonymous

Member
Ugh, spoke to soon, ALMOST there.

After posting it is fine, viewing the post with the attached image is fine, but just after inserting, "wysiwygImage"is throwing the mixed content warning, I fear this is a tinymce thing which might mean a complete new level of complexity and .. ugh. So close.


Works - view:





Breaks after inserting image using tinymce and in preview (tinymce too i guess):

 

Jeremy

Well-known member
HOLY * you are awesome.

PHP:
<?php
class CamoUrl_BbCode_Formatter_Base extends XFCP_CamoUrl_BbCode_Formatter_Base
{
      /**
        * Renders a img tag.
        *
        * @param array $tag Information about the tag reference; keys: tag, option, children
        * @param array $rendererStates Renderer states to push down
        *
        * @return string Rendered tag
        */
        public function renderTagImage(array $tag, array $rendererStates)
        {
                $url = $this->stringifyTree($tag['children']);
                                        $prefix = 'https://domain/asset-proxy/';
                                        $camokey = 'secret';
                $validUrl = "$prefix" . hash_hmac('sha1', $this->_getValidUrl($url), $camokey) . '/'. bin2hex($this->_getValidUrl($url));
 
 
 
                if (!$validUrl)
                {
                        return $this->filterString($url, $rendererStates);
                }
 
                $censored = XenForo_Helper_String::censorString($validUrl);
                if ($censored != $validUrl)
                {
                        return $this->filterString($url, $rendererStates);
                }
 
                // attempts to convert smilies posted as [IMG] tags back into smilies
                if ($rendererStates['imgToSmilie'])
                {
                        foreach ($this->_smiliePaths AS $smiliePath => $smilieId)
                        {
                                if (strpos($url, $smiliePath) !== false && substr($url, strlen($smiliePath) * -1) == $smiliePath)
                                {
                                        return $this->_smilieReverse[$smilieId];
                    }
                        }
                }
 
                return sprintf($this->_imageTemplate, htmlspecialchars($validUrl), ($rendererStates['lightBox'] ? ' LbImage' : ''));
        }
 
}
This works! I love you, now all i need is find out how to make :
$prefix and $camokey configurable options, since the method i use right now is not very elegant, it works for me but if i'm ever going to write the full https guide (as I should, it's only fair to contribute back after the help i got) I ought include a fully functional addon.

Thank you a million, I would suggest you get hammered on me, but it's monday.
You mean $prefix and $camokey as options? If you're system is in debug mode, go to ACP -> Options -> "option group" (I'd suggest "Messages" because that's what you're dealing with) -> + Add Option. Set up the option correctly and then switch your code to be the following:

PHP:
        public function renderTagImage(array $tag, array $rendererStates)
        {
                $url = $this->stringifyTree($tag['children']);
                                        $prefix = Xenforo_Application::get('options')->yourOptionId;
                                        $camokey = Xenforo_Application::get('options')->yourOptionId2;
                $validUrl = "$prefix" . hash_hmac('sha1', $this->_getValidUrl($url), $camokey) . '/'. bin2hex($this->_getValidUrl($url));



                if (!$validUrl)
                {
                        return $this->filterString($url, $rendererStates);
                }

                $censored = XenForo_Helper_String::censorString($validUrl);
                if ($censored != $validUrl)
                {
                        return $this->filterString($url, $rendererStates);
                }

                // attempts to convert smilies posted as [IMG] tags back into smilies
                if ($rendererStates['imgToSmilie'])
                {
                        foreach ($this->_smiliePaths AS $smiliePath => $smilieId)
                        {
                                if (strpos($url, $smiliePath) !== false && substr($url, strlen($smiliePath) * -1) == $smiliePath)
                                {
                                        return $this->_smilieReverse[$smilieId];
                    }
                        }
                }

                return sprintf($this->_imageTemplate, htmlspecialchars($validUrl), ($rendererStates['lightBox'] ? ' LbImage' : ''));
        }

}
They shall now be configurable through the options panel in the ACP. Can you explain when you are getting warnings in a little more detail, I'm slightly confused.
 

anonymous

Member
You mean $prefix and $camokey as options? If you're system is in debug mode, go to ACP -> Options -> "option group" (I'd suggest "Messages" because that's what you're dealing with) -> + Add Option. Set up the option correctly and then switch your code to be the following:

They shall now be configurable through the options panel in the ACP.
Will do this, thank you.
Can you explain when you are getting warnings in a little more detail, I'm slightly confused.
The warnings appear after inserting an image in the wysiswg editor, or when seeing the wysiwyg editor with an inserted image in it. I think this is tinymce related.

uneducated rambling:

An option might be to hook into the tinymce function that renders the image tags in the editor and send the url to a serverside url encoding script, then send the url back encoded for tinymce to embed. We can't use client side encoding (for the url) since we want the private key to stay... well private. The problem I have been hurting my head with is how to do this without opening the addon up to application level denial of service. Hashing url's is not super expensive but if it is not rate limited to user session and max requests /s /h it can and will be abused.

Or am I over thinking this? Breaking things is so much easier...
 

Jeremy

Well-known member
Will do this, thank you.

The warning appear after inserting an image in the wysiswg editor, or when seeing the wysiwyg editor. I think this is tinymce related.

uneducated rambling:

An option might be to hook into the tinymce function that renders the image tags in the editor and send the url to a serverside url encoding script, then send the url back encoded for tinymce to embed. We can't use client side encoding (for the url) since we want the private key to stay... well private. The problem I have been hurting my head with is how to do this without opening the addon up to application level denial of service. Hashing url's is not super expensive but if it is not rate limited to user session and max requests /s /h it can and will be abused.

Or am I over thinking this? Breaking things is so much easier...
Modify your listener and replace the file with this:
PHP:
<?php
class CamoUrl_Listener_LoadClassController
{
	public static function loadClassListener($class, &$extend)
	{
		if ($class == 'XenForo_BbCode_Formatter_Base' || $class == 'XenForo_BbCode_Formatter_Wysiwyg')
		{
			$extend[] = 'CamoUrl_BbCode_Formatter_Base';
		}
	}
}
If this works, I think we may have found a bug or issue within the system.
 

anonymous

Member
If this works, I think we may have found a bug or issue within the system.
No dice, it still gives mixed content warnings when inserting the image.

I think the display of images in the editor right after inserting them (before post) are a client side thing.

It did fix the display of images in the editor when editing an existing post with images (this gave mixed content warnings too).

However, when going into the "more options" and / or saving a post that was edited. I think the image url in the editor is encoded twice, so that's a problem.

Bug or Feature?
 

xfrocks

Well-known member
I think the display of images in the editor right after inserting them (before post) are a client side thing.
Yup, it's a client script. You can read the code here: js/tinymce/themes/xenforo/js/image.js. I don't think we can change its behavior :(
 

Jeremy

Well-known member
No dice, it still gives mixed content warnings when inserting the image.

I think the display of images in the editor right after inserting them (before post) are a client side thing.

It did fix the display of images in the editor when editing an existing post with images (this gave mixed content warnings too).

However, when going into the "more options" and / or saving a post that was edited. I think the image url in the editor is encoded twice, so that's a problem.

Bug or Feature?
Add some logic to see if the URL is coming from your site (should be https already) or from the previous URL. If so, you could return it, if not, you'd do your processing. I can't really delve into much more without seeing actual code.
 

anonymous

Member
Add some logic to see if the URL is coming from your site (should be https already) or from the previous URL. If so, you could return it, if not, you'd do your processing. I can't really delve into much more without seeing actual code.
Solved the looping edit / more options img encoding with preg_match as you suggested.

PHP:
<?php
class CamoUrl_BbCode_Formatter_Base extends XFCP_CamoUrl_BbCode_Formatter_Base
{
      /**
        * Renders a img tag.
        *
        * @param array $tag Information about the tag reference; keys: tag, option, children
        * @param array $rendererStates Renderer states to push down
        *
        * @return string Rendered tag
        */
        public function renderTagImage(array $tag, array $rendererStates)
        {
                $url = $this->stringifyTree($tag['children']);
                $prefix = Xenforo_Application::get('options')->camoPrefix;
 
                if (preg_match("~($prefix)~", $url))
                        {
                                $validUrl = $url;
                        }
                else  {
                                $camokey = Xenforo_Application::get('options')->camoKey;
                                $validUrl = "$prefix" . hash_hmac('sha1', $this->_getValidUrl($url), $camokey) . '/'. bin2hex($this->_getValidUrl($url));
                }
 
 
                if (!$validUrl)
 
                {
                        return $this->filterString($url, $rendererStates);
                }
 
                $censored = XenForo_Helper_String::censorString($validUrl);
                if ($censored != $validUrl)
                {
                        return $this->filterString($url, $rendererStates);
                }
 
                // attempts to convert smilies posted as [IMG] tags back into smilies
                if ($rendererStates['imgToSmilie'])
                {
                        foreach ($this->_smiliePaths AS $smiliePath => $smilieId)
                        {
                                if (strpos($url, $smiliePath) !== false && substr($url, strlen($smiliePath) * -1) == $smiliePath)
                                {
                                        return $this->_smilieReverse[$smilieId];
                    }
                        }
                }
 
                return sprintf($this->_imageTemplate, htmlspecialchars($validUrl), ($rendererStates['lightBox'] ? ' LbImage' : ''));
        }
 
}
This leaves (still) the initial problem with inserting images with tinymce. I'll look around what I can find on the subject.
 

Jeremy

Well-known member
Solved the looping edit / more options img encoding with preg_match as you suggested.

PHP:
<?php
class CamoUrl_BbCode_Formatter_Base extends XFCP_CamoUrl_BbCode_Formatter_Base
{
      /**
        * Renders a img tag.
        *
        * @param array $tag Information about the tag reference; keys: tag, option, children
        * @param array $rendererStates Renderer states to push down
        *
        * @return string Rendered tag
        */
        public function renderTagImage(array $tag, array $rendererStates)
        {
                $url = $this->stringifyTree($tag['children']);
                $prefix = Xenforo_Application::get('options')->camoPrefix;
 
                if (preg_match("~($prefix)~", $url))
                        {
                                $validUrl = $url;
                        }
                else  {
                                $camokey = Xenforo_Application::get('options')->camoKey;
                                $validUrl = "$prefix" . hash_hmac('sha1', $this->_getValidUrl($url), $camokey) . '/'. bin2hex($this->_getValidUrl($url));
                }
 
 
                if (!$validUrl)
 
                {
                        return $this->filterString($url, $rendererStates);
                }
 
                $censored = XenForo_Helper_String::censorString($validUrl);
                if ($censored != $validUrl)
                {
                        return $this->filterString($url, $rendererStates);
                }
 
                // attempts to convert smilies posted as [IMG] tags back into smilies
                if ($rendererStates['imgToSmilie'])
                {
                        foreach ($this->_smiliePaths AS $smiliePath => $smilieId)
                        {
                                if (strpos($url, $smiliePath) !== false && substr($url, strlen($smiliePath) * -1) == $smiliePath)
                                {
                                        return $this->_smilieReverse[$smilieId];
                    }
                        }
                }
 
                return sprintf($this->_imageTemplate, htmlspecialchars($validUrl), ($rendererStates['lightBox'] ? ' LbImage' : ''));
        }
 
}
This leaves (still) the initial problem with inserting images with tinymce. I'll look around what I can find on the subject.
I'll look into the JS issue tomorrow. You may actually want to change the listener the following to ensure img is ALWAYS handled via your system:
PHP:
<?php
class CamoUrl_Listener_LoadClassController
{
    public static function loadClassListener($class, &$extend)
    {
        if ($class == 'XenForo_BbCode_Formatter_Base' || $class == 'XenForo_BbCode_Formatter_Wysiwyg' || $class == 'XenForo_BbCode_Formatter_Text' || $class == 'XenForo_BbCode_Formatter_ImageCount' || $class == 'XenForo_BbCode_Formatter_HtmlEmail')
        {
            $extend[] = 'CamoUrl_BbCode_Formatter_Base';
        }
    }
}
This'll always ensure emails are routed through your proxy... However, the last check could be left out (I don't think mixed content warnings are ever given out via an email client...).
 

anonymous

Member
I'll look into the JS issue tomorrow. You may actually want to change the listener the following to ensure img is ALWAYS handled via your system:

This'll always ensure emails are routed through your proxy... However, the last check could be left out (I don't think mixed content warnings are ever given out via an email client...).
One of the classes that gets extended by the above modification breaks the addon. I haven't narrowed it down to any of them, it get error messages on post, but the JS error console doesn't say anything.
 

Jeremy

Well-known member
One of the classes that gets extended by the above modification breaks the addon. I haven't narrowed it down to any of them, it get error messages on post, but the JS error console doesn't say anything.
What's the exact error so I can look into why my code is causing it?
 

anonymous

Member
The following error occurred

The server responded with an error. The error message is in the JavaScript console.

JS console doesn't show up. but nginx / php-fpm is so friendly to tell us :

2011/12/06 18:21:34 [error] 9401#0: *2077 FastCGI sent in stderr: "PHP Fatal error: Cannot redeclare class XFCP_CamoUrl_BbCode_Formatter_Base in /var/www/domain.net/community/library/XenForo/Application.php(396) : eval()'d code on line 1" while reading response header from upstream, client: *, server: domain.net, request: "POST /community/posts/14/save-inline HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: "domain.net", referrer: "https://domain.net/community/threads/gggggggg.5/"
~
Edit: Tomorrow is going to be a long day and it's going to be night here. If i don't reply any time soon i'm sleeping.
 

Jeremy

Well-known member
This was working with just:
PHP:
$class == 'XenForo_BbCode_Formatter_Base' || $class == 'XenForo_BbCode_Formatter_Wysiwyg'
Correct? The error implies the possibility of the system creating instances of more than one of the classes in the same function and attempting to extend them both... This could get sticky...
 

anonymous

Member
This was working with just:
PHP:
$class == 'XenForo_BbCode_Formatter_Base' || $class == 'XenForo_BbCode_Formatter_Wysiwyg'
Correct? The error implies the possibility of the system creating instances of more than one of the classes in the same function and attempting to extend them both... This could get sticky...
yes, that works.
 
Top