Tag Archives: BBCODE

Use tinymce in CakePHP with a behavior

Hey !
I went through the problem of using WYSIWYG to have more nicer news, comments … well giving a new possibility to the users to personalize what they want to write.

I tried two possibilities : making my own BBCODE or using a WYSIWYG editor. I will talk a “bit” about these two options, if you only want the explanations about how to set it for your CakePHP, skip it and go to this section Tinymce, HtmlPurifier and CakePHP behavior.

BBCODE
It is a very good way to be protected from xss or any other malicious content because you convert directly the bbcode into (x)html. Though, it’s a bit annoying to use/do and limits the possibilities. Also, one good point against is the fact that you have to “decode” (i.e to convert it in (x)html) each time you want to display it, if you do it once when you save the content, you can’t reverse the process, and you get (x)html when you want to edit your content. To be true, you could reverse it, but it’s not the wisest way.

WYSIWYG
Well, it is pretty much better here. We directly work with (x)html which is easier. We save directly (x)html in the database when we create our news, comment, … and we just display the content when we want, no process. BUT ! it’s plain (x)html we have and it means the users can write what they want, your layout can be broken, or worst they can write some javascript and mess your website.

Obviously I ended up by using a WYSIWYG editor, tinymce but as I said previously, you have some good advantages, the good looking, the ease of use but a vulnerability : xss attack. So the goal is to prevent it and secure the content the users will write. There we won’t use htmlentities because the goal is to “execute” the html by the browser, so we have to get some tags, and get rid of the others. We could use strip_tags, but let’s see it’s not the safer way.

Let’s admit we allow the tag <p>, here is what we could do.

    $data = "<p onmouseover=alert("xss")>blablabla</p>";
    $data = strip_tags($data, '<p>');
    echo $data;

Put your mouseover the <p> tag (i.e blablabla) and you will see that “xss” pop up. Well it’s logic because we allow the <p> tag, would be the same with <span> and so on. It means we can allow some tags but not be totally safe.

HtmlPurifier

So let’s use HtmlPurifier, it’s a filtering PHP library which “purifies” your html, it even deals with malformed tags and follows the specifications ! It’s THE massive weapon ! You can do a whitelist of the acceptable tags and even decide which attributes of these tags are allowed. Well I discovered it few days ago, so I’m no expert but we’ll get down to some configuration.

Tinymce, HtmlPurifier and CakePHP behavior

I’ll explain here how to set tinymce and htmlpurifier with CakePHP using a behavior. First we need a copy of tinymce, you can find it on their website : tinymce, take the latest production version. Then you can put it in your webroot/js or you create your own folder “lib” in the webroot, do as you wish it doesn’t matter as long as it is in the webroot. Next step is to configure the tinymce editor to fit our needs. Create a file “config.js” in your tinymce folder and put what follows into.

 
tinyMCE.init
(
    {
        // General options
        mode : "textareas",
        theme : "advanced",
        plugins : "paste",
        width : "100%",
        height : "100%",

       // Theme options
       theme_advanced_buttons1 : "bold,italic,underline,strikethrough,forecolor,|,justifyleft,justifycenter,justifyright,justifyfull,|,bullist,numlist,|,undo,redo",
       theme_advanced_toolbar_location : "top",
       theme_advanced_toolbar_align : "left",
       theme_advanced_statusbar_location : "bottom",
       theme_advanced_resizing : false,

      // Paste plugins configuration
      paste_auto_cleanup_on_paste : true,
      paste_remove_styles: true,
      paste_remove_styles_if_webkit: true,
      paste_strip_class_attributes: true,
      paste_retain_style_properties : "none",
      theme_advanced_disable : "styleselect",
      convert_fonts_to_spans: false,

      // No css
      content_css : false
    }
);

Here what’s important is the field “theme_advanced_buttons1” which is the list of the tags we display in our editor, so the ones we let our users use. It has to fit with the configuration of HtmlPurifier we will do just after. What we accept is basic things such as list, text position, underline, bold, etc …

P.S : I added the plugin “paste”, very useful when u copy/paste stuff in the text editor, there it will clean the content, or keep what’s allowed.

P.S2 : Tinymce has a lot of parameters, you can really set a lot of things. There I show a “little” example.

Now HtmlPurifier ! First, we need the latest copy that we can download here. I decided to wrap it in a CakePHP behavior.  Why? Because I think it’s “smarter”, when we add/edit a comment, post, … we purify the content before to save it in the database. If you read well, you saw before and save. And yes it’s a callback method of the CakePHP model. Consequently, we will create a behavior that we will link to all our models that need it and through the beforeSave callback, we will purify.

Thus, let’s put our HtmlPurifier folder in the vendors. Rename your folder “HtmlPurifier-blabla” to only “htmlpurifier”, it’s just more convenient.

We can create a behavior in app/Model/Behavior, under the name of PurifyBehavior.php, and it should look like this.

 
class PurifyBehavior extends ModelBehavior
{
    function setup(Model $model, $settings = array())
    {
        //field is the field we purify by default it is called content
        $field = (isset($settings['field']))?$settings['field']:'content';
        $this->settings[$model->alias] = array('field' => $field);
    }

    /*
        cleaning before saving
    */
    function beforeSave(Model $model)
    {
        //convenient to get the name of the field to clean
        $field = $this->settings[$model->alias]['field'];

        //check if we are working on the field
        if(isset($model->data[$model->alias][$field]))
        {
            //get htmlpurifier => http://htmlpurifier.org/
            App::import('Vendor', null, array
            (
                'file' => 'htmlpurifier'.DS.'library'.DS.'HTMLPurifier.auto.php'
             ));

            //set its configuration
            $config = HTMLPurifier_Config::createDefault();
            $config->set('Core.Encoding', 'UTF-8');
            $config->set('AutoFormat.RemoveEmpty', true);
            $config->set('Core.EscapeNonASCIICharacters', false);

            /*
            allowed HTML elements according to tinymce's configuration => p[align] to allow p and the attribute align
            */
            $allowedHTML = "em,strong,p[style],ul,ol,li,span[style]";
            $config->set('HTML.Allowed', $allowedHTML);

            //cleaning
            $purifier = new HTMLPurifier($config);
            $model->data[$model->alias][$field] = $purifier->purify($model->data[$model->alias][$field]);
        }

        return true;
    }
}

We use the setup method to tell the behavior which field we want to purify, let’s say you do a news, you call the content field “body” and if you do a media, you could call it “description”, it’s why I did it this way. We will see how to set it in the model below. By default, the field value would be “content”. Then we use the callback beforeSave to purify. First we put in a variable the name of the field, it’s “more readable”. We check that the field exists. Why? For one good reason. The callback is fired each time you save something. Let’s say you want to save one field only with saveField, the behavior will fail because the field of the PurifyBehavior will not be set. We import the htmlpurifier library from the vendors.  Then create a default configuration. We set few parameters, encoding, removeEmpty (let’s say you have a span looking this <span></span>, htmlpurifier will erase it), etc … check the documentation for more parameters. Important there is the allowedHTML where we write all the tags and theirs attributes according to tinymce’s previous configuration ! It’s pretty easy to understand and to change, right? Finally we purify our field of the current model. Ensure of returning true or the save will fail !

P.S : You can of course add some conditions to check and fail the save by returning false. You may use $model->invalidate(‘name of the field’, ‘message to display’) to invalidate the field in your form and display an error message.

Last but not least, we have to do a small modification of all our models using the behavior. To tell them they have to behave with the purifybehavior. Let’s see how to do this, it’s the easiest and smallest part.

    public $actsAs = array('Purify' => array('field' => 'description'));

Pretty easy ! We just tell our model to use the PurifyBehavior and we set the field to “description” here. If your field’s name is “content” you don’t even have to set the array ! It would give something like this.

    public $actsAs = array('Purify');

Et voilà, you have proper html saved in your database, your users are less limited and your website is safer.

P.S : I didn’t say totally safe !