Drupal, HTML Purifier, and embedding IFRAMES from YouTube

I know, I know. I shouldn’t allow IFRAMEs at all. But the client’s prospective users were really excited about images and video, and Drupal’s Media module wasn’t going to be quite enough. So I’ve been fighting with CKEditor, IMCE, and HTML Purifier to figure out how to make it easier. I’m hoping that this will be like practically all my other Drupal posts and someone will comment with a much better way to do things right after I describe what I’ve done. =)

First: images. There doesn’t seem to be a cleaner way than the “Browse server” – “Upload” combination using CKEditor and IMCE. I tried using WYSIWYG, TinyMCE and IMCE. I tried ImageBrowser, but I couldn’t get it to work. I tried FCKEditor, which looked promising, but I got tangled in figuring out how to control other parts of it. I’m just going to leave it as CKEditor and IMCE at the moment, and we can come back to that if it turns out to be higher priority than all the other things I’m working on. This is almost certainly my limitation rather than the packages’ limitations, but I don’t have the time to exhaustively tweak this until it’s right. Someday I may finally learn how to make a CKEditor plugin, but it will not be in the final week of this Drupal project.

Next: HTMLPurifier and Youtube. You see, Youtube switched to using IFRAMEs instead of Flash embeds. Allowing IFRAMEs is like allowing people to put arbitrary content on your webpage, because it is. The HTML Purifier folks seem firmly against it because it’s a bad idea, which it also is. But you’ve got to work around what you’ve got to workaround. Based on the Allow iframes thread in the HTMLPurifier forum, this is what I came up with:

Step 1. Create a custom filter in htmlpurifier/library/myiframe.php.

<?php
// Iframe filter that does some primitive whitelisting in a
// somewhat recognizable and tweakable way
class HTMLPurifier_Filter_MyIframe extends HTMLPurifier_Filter
{
  public $name = 'MyIframe';
  public function preFilter($html, $config, $context) {
    $html = preg_replace('/<iframe/i', '<img class="MyIframe"', $html);
    $html = preg_replace('#</iframe>#i', '', $html);
    return $html;
  }
  public function postFilter($html, $config, $context) {
    $post_regex = '#<img class="MyIframe"([^>]+?)>#';
    return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html);
  }
  protected function postFilterCallback($matches) {
    // Whitelist the domains we like
    $ok = (preg_match('#src="http://www.youtube.com/#i', $matches[1]));
    if ($ok) {
      return '<iframe ' . $matches[1] . '></iframe>';
    } else {
      return '';
    }
  }
}

Step 2. Include the filter in HTMLPurifier_DefinitionCache_Drupal.php. I don’t know if this is the right place, but I saw it briefly mentioned somewhere.

// ... rest of file
require_once 'myiframe.php';

Step 3. Create the HTML Purifier config file. In this case, I was changing the config for “Filtered HTML”, which had the input format ID of 1. I copied config/sample.php to config/1.php and set the following:

function htmlpurifier_config_1($config) {
  $config->set('HTML.SafeObject', true);
  $config->set('Output.FlashCompat', true);
  $config->set('URI.DisableExternalResources', false);
  $config->set('Filter.Custom', array(new HTMLPurifier_Filter_MyIframe()));
}

Now I can switch to the source view in CKEditor, paste in my IFRAME code from Youtube, and view the results. Mostly. I still need to track down why I sometimes need to refresh the page in order to see it, but this is promising.

2011-08-05 Fri 16:34

  • http://www.jehzlau-concepts.com/ jehzlau

    Good thing, even if Youtube switched to IFRAMEs, the old embed code still works. I think there’s a better way in Drupal, perhaps replicating the auto-embed featured of WordPress? :)

  • http://sachachua.com Sacha Chua

    jehzlau: Given my client’s target users, I’ll be lucky if I can get most of them to copy the embed code, much less click on the classic embed code checkbox and grab that instead… =) Darn Facebook for raising expectations when it comes to autoembeds! ;)

  • http://www.jehzlau-concepts.com/ jehzlau

    Oh yes… the clients. They don’t care about the embed code as long as they can embed it. Haha! I think even if the embed code includes an external link to a malicious site, they won’t bother. We need to look for a workaround to fix it for them… Geez.. Oh well. :)

  • Arjan

    How about Video filter?

  • MacRonin

    Would this issue/module help tackle the CKEditor MediaEmbed plugin challenge ?

    http://drupal.org/node/872868

    Plus a video on how to configure the mediaembed plugin in ckeditor

    http://jesox.com/posts/how-add-youtube-vimeo-or-any-other-embed-content-ckeditor

  • Joe

    Duuuude… Embedfilter module by KarenS. Then search the issue cue for the iframe patch.

    • http://sachachua.com Sacha Chua

      Can I get that to play nicely with HTMLPurifier without going crazy? Might be useful for future projects. =)

  • Joe

    You would have to get iframes to pass through the HTML Purifier filter. Or just don’t use that behemoth :). Then put the patched Embedfilter after it in the processing order. Embedfilter will then strip all iframes except the ones from the domains that you specify in the mod settings.

  • sbandyopadhyay

    Instead of using a custom filter, you can use what will be included in the next version of HTML purifier: HTML.SafeIframe (http://htmlpurifier.org/phorum/read.php?5,5319).

    As for the issue with needing to refresh the page, are you using Chrome? If so, see here: http://www.rahulsingla.com/blog/2011/06/drupal-and-chrome-refused-to-execute-a-javascript-script-source-code-of-script-found-wi

    I had the same issue, except it was with the iframe instead of the javascript.

    • http://sachachua.com Sacha Chua

      Glad to hear HTML Purifier will support it better next time! Thanks for the Chrome tips too. =)

  • Edward Savage

    This is a very clever solution, but the problem I have with it is that these to necessary attributes are stripped out: frameborder=”0″ allowfullscreen

    Do you have a work-around for that?

  • Edward Savage

    Sacha, I poked around and made some changes to put necessary attributes back in and also added Vimeo support. You can see my code on this Stack Overflow answer: http://stackoverflow.com/a/8581864/244826

  • http://sachachua.com Sacha Chua

    Yay! Thanks for sharing.

  • http://uk.linkedin.com/in/nikosstylianou Nikos Stylianou

    I created this sandbox project which adds a youtube and vimeo button to ckeditor which embeds iframes.
    http://drupal.org/sandbox/thetoast/1438500

  • http://uk.linkedin.com/in/nikosstylianou Nikos Stylianou

    I just read the bit again about how you’re having to refresh the page to see it. Using full html and my sandbox module I saw the same effect.

  • http://gravatar.com/trkest trkest

    I found this was not necessary – this config worked for me:

    http://drupal.org/node/711728#comment-5600344

    toby

  • Sacha Chua

    That’s good to hear. Thanks for the update!