"Click to activate" -
A general PHP based on-the-fly work-around for existing PHP/HTML code

© Gero Zahn, gero-at-gerozahn.de


Have a look at the at the tiny Flash element on the left and touch it with the mouse: Not worldshaking at all - a simple roll-over button, without even a function when clicked. Gee, you'll never believe that I can do much better than that. :)

But if you're using a fully updated Microsoft Internet Explorer V6 (or later) or Opera V9 (or later), this isn't self-evident at all. To see this Flash element in its regular, unchanged habitat, have a look at the iframe on the right: This copy of exactly the same Flash element won't react on your mouse pointer unless you click it once.

Hold your mouse pointer over the right flash element for a few seconds and you will see the pop-up message "Click to activate".


The problem

Both Microsoft and Opera seem to have decided not to pay license fees to a company called Eolas that hold the patent 5,838,906 titled "Distributed hypermedia method for automatically invoking external application providing interaction and display of embedded objects within a hypermedia document". Read more about this in the corresponding Wikipedia article.

Remember: This is not just about Flash, it's as well about JAVA Applets, multimedia elements like QuickTime or MS-MediaPlayer media, embedded PDF elements and much more: Nowadays all plug-in elements have to be clicked before you can use them, creating a terrible breach in web-wide usability.

You can read much more about this entire matter at various spots on the internet, most informative I found the following two pages:

No doubt: Microsoft's work-around solution is valid, it works in Opera 9+ as well. But it's hardly tolerable for us web-designers: We all have old, reliable HTML code, propably deeply embedded in older websites, content management systems or even script-generated by PHP code. It would be a humdrum and therefore rather error-prone approach to get this JavaScript embedding and external scripting into all those old pages overnight.

And: What if this whole issue will be addressed otherwise some day? Worst case: What if the current work-around will cease to work eventually (though this is kind of impropable)? Best case: What if the work-around becomes obsolete? Shall we web-designers get back into our code and change all this back to get rid of this then-unnecessary work-around, again risking code errors while doing so? Questions over questions ...


The idea

So - I was wondering if there might be a more elegant way, something that could do an on-the-fly conversion of "regular" object / applet /embed code and transform it automatically and therefore implicitely error-free as advised. This way we web-designers could stick to our usual code created by almost any web authoring software when embedding media content. By the way, the regular code is much more coherent and easier to administer - to say the least.

One proposed solution is to use User JavaScript to modify the code at the client-side. ... OK, this works, but it won't be accepted by the broad mass of internet surfers. As well as one would expect that less competent computer users or web surfers would never be able or willing to install or use such a tweak. No - we web-masters have to act. An acceptable solution has to be server-sided, transparent - and, of course, browser-independent.

Before getting into my solution, let me be honest: I've was very much inspired by Justin Koivisto's "KOIVI PNG Alpha IMG Tag Replacer for PHP" (you can find it here).


The requirements

Concluding: We have

We want:

The transformation has to work transparently - on-demand taking the existing, unchanged HTML code and delivering the "mangled" one to the client's browser.

Requirement: We will definitely need PHP for this. PHP4 or PHP5 are both fine.

If your HTML output derives from a PHP web application (like a Blog or CMS), everything is ready for action already.

If you are using plain .html files, you have to persuade your web-server to parse HTML files for PHP code. In case of Apache (1.x/2.x) this is pretty simple: Just create a .htaccess file, place it in the root folder of your webspace, and put the following line in it:

AddType application/x-httpd-php .html

This will teach your Apache to handle .html files like .php files. If this doesn't work, try this:

AddType x-mapp-php4 .html

This has proven to work on PHP-capable web-servers at 1&1 and Schlund+Partner (both German ISPs).


The solution

It is all about transforming the already generated code (either static HTML or PHP-generated). So the magic spell is output buffering. Meaning: The PHP output (and a HTML file parsed as PHP basically means PHP output as well) shall not start the data transfer from the server to the client until it has been rendered completely.

So, add the following three lines of code at the very beginning of your HTML or PHP file:

<?php
  ob_start();
?>

This is all: ob_start(); prevents the data delivery to the client. If one wouldn't take further actions, the accumulated data would be sent automatically at the end of the source file.

But the general idea is to transform the accumulated data - ie. the entire HTML output. So add four more lines to the very end of your HTML or PHP file:

<?php
  include_once "replaceObjEmbed.php";
  echo replaceObjEmbed(ob_get_clean());
?>

The main element is ob_get_clean(): It delivers the entire accumulated HTML output into a single string variable.

The first line include_once line will include the code described later. The second line takes the HTML output, processes it with the function replaceObjEmbed() (defined in the above included PHP file) and outputs the magically transformed HTML code with the echo statement.

... And basically, this is it! Those of you who just want the "out-of-the-box" solution without knowing more about it:

  1. Get the download at the lower end of this page.
  2. Unzip the two files replaceObjEmbed.php and replaceObjEmbed.js next to your HTML or PHP files.
  3. If you want to use it on plain HTML files, be sure that your Apache parses them for PHP code via the correct .htaccess setting (see above).
  4. Add those 7 lines to the beginning and the end of your HTML or PHP file.
    <?php
      ob_start();
    ?>
    <html>
    <head>...</head>
    <body>
      ...
      <!-- original code, meaning:
           as much HTML and PHP code as you like,
           containing objects/applets/embeds -->
      ...
    </body>
    </html>
    <?php
      include_once "replaceObjEmbed.php";
      echo replaceObjEmbed(ob_get_clean());
    ?>
  5. You're done!

Be assured: This is it! The underlying index.php you are actually reading has been manufactured exactly like this - which is the reason, why the embedded Flash element to the left works exactly like intended.


The technique

Ah, here you are - you're actually interested how it is done ... ;)

I've already named the concept of the output buffering. I took this from Justin's PNG Alpha IMG Tag Replacer (see above). If you want to combine the object transformation with the PNG alpha replacement, this can be done like follows. Start again like this:

<?php
  ob_start();
?>

The end of the PHP or HTML file should similar to this:

<?php
  include_once "replacePngTags.php";
  include_once "replaceObjEmbed.php";
  echo replaceObjEmbed(replacePngTags(ob_get_clean()));
?>

No magic here: Pipe the original output through Justin's script and his output afterwards through mine - or the other way around. Works fine!

But into depth: Of course we have to replace <object ...>...</object>, <applet ...>...</applet> or <embed ...>...</embed> areas with their JavaScript wrapped substitute, call a little bit of external JavaScript to actually create the embeddning - added with a <noscript>...</noscript> area as a fall-back in case the surfer has disabled scripting.

The search for the occurrences of those tag groups can easily be done via regular expressions. I still hate those like going to the dentist, but gladly Justin's script contained pretty much exactly what I needed. :)

So we're dealing with these regexps:

It is essential to search for these in the correct order and hiding the former search results from the next search process: Transforming unclosed embed tags first would disturb the search for closed ones, as well as transforming closed <embed ...>...</embed> areas would disturb the search for <object ...>...</object> or <applet ...>...</applet> areas containing open or closed embed tags.

So - top to bottom: objects and applets first, than closed embeds without (objects or applets around), then unclosed embeds.

  $tags=array('`<object[^>]*>(.*)</object>`isU', # <object ...>...</object> areas
              '`<applet[^>]*>(.*)</applet>`isU', # <applet ...>...</applet> areas
              '`<embed[^>]*>(.*)</embed>`isU',   # <embed ...>..</embed> areas
              '`<embed[^>]*>`isU');              # single, unclosed <embed ...> tags outsite object areas

  $replacements=array(); # Storage for the elements found to be processed

  foreach(array_keys($tags) as $idx) { # Handle all kings of tag areas and tags, one by one

    $tmptags=array(); # Storage for the found occurrences
    preg_match_all($tags[$idx],$x,$tmptags); # And here they are

    if ($tmptags) { # Found some?

      foreach(array_keys($tmptags[0]) as $secidx) { # Deal with them, one by one

        # We have to move them apart -- especially <object ...>...</object> areas with an internal
        # <embed ...>..</embed> area or an unclosed <embed ...> tag -- otherwise they'd be found again.

        $tagval=$tmptags[0][$secidx]; # This is the current occurrence to be processed later on
        $tagkey="replacetag_".$idx."_".$secidx."_"; # Temporarily replace it by "replacetag_x_y_"
        # ... where x is 0..3 (object/applet/embed/s.embed) and y is the corresponding number.
        # The "_" suffix is necessary as otherwise "replacetag_x_10" would be replaced by
        # "replacetag_x_1" later

        $replacements[$tagkey]=$tagval; # Store the occurrence beside it's unique key ...
        $x=str_replace($tagval,$tagkey,$x); # ... and actually replace the occurrence with the key
      }
    }

    unset($tmptags); # A bit of dirty work

  }

The red pref_match_all does the trick and stores the above defined occurrences in $tmptags. From there the green line generates a unique key for each occurrence (of each tag group), all of them are being stored together in one array to be processed in the second step. The main trick in blue is to temporarily remove the already found occurrences from the HTML data $x - as <embed ...> parts of it would be found errornously again later in the search. They are replaced by placeholders that won't disturb the further search and can be found again later on. (Again: Thanks to Justin's PNG Alpha IMG Tag Replacer which does a similar trick to entire script sections.)

OK - now all occurrences are together in the $replacements array: The keys are those temporary HTML contents that have to be replaced again, the values are the original occurrences to be transformed into JavaScript. Let's go for it:

  foreach($replacements as $tagkey => $tagval) { # Handle all occurrences, one by one

    $jsval=addslashes($tagval); # Handle special characters properly
    $jsval=str_replace(chr(13),"",$jsval); # remove CRs - all in one line
    $jsval=str_replace(chr(10),"",$jsval); # remove LFs - all in one line

    # 1. Embed that tiny little external JS to work as actual embedder.
    # 2. Embed the original occurrence inside a JS variable -- 
    # 3. Call the tiny little embedder to dynamically output the variable
    # 4. Embed the original, unchanged occurrence in a <noscript>...</noscript> area as fall-back
    $jsval= "<script src=\"$jsdirprefix/replaceObjEmbed.js\" type=\"text/javascript\"></script>\n".
            "<script language=\"JavaScript\">\n".
            "<!--\n".
            "var jsval = '$jsval';\n".
#           "//document.write(jsval);". # This doesn't work as it's an internal document.write(...)
            "writethis(jsval);". # So: Use the external one-liner function to perform the trick
            "//-->\n".
            "</script>\n".
            "<noscript>$tagval</noscript>";

    # The original occurrence has been replaced with its unique "key" beforehanded,
    # now replace this stored key with is JS wrapper and noscript fallback.
    $x=str_replace($tagkey,$jsval,$x);

  }

The red code transforms the HTML code of the occurrence into JavaScript: It has to be "slashed" (the quotation marks and various other characters), and it has to fit into one line.

The green lines create the functional core of the auto-generated JavaScript code: First include a tiny little external script (which is essential for the whole method - an internal JavaScript call wouldn't work), then putting the slashed, one-lined HTML code in one JavaScript variable, afterwards creating the actual external function call - and finally trapping the original, unchanged occurrence into a <noscript>...</noscript> area for fall-back purposes.

And just one more thing in blue: Here the occurrence key that has been placed temporarily in the HTML code $x is removed and replaced by everything we just created.

Oh, I almost forgot: This nifty external replaceObjEmbed.js is of course basically a one-liner:

function writethis(what) {
  document.write(what);
}

If you prefer other embedding methods like innerHTML or DOM (proposed by Microsoft and described more clearly on Mark Wilton-Jones' page), you'd have to modify this one. I decided to go the simplemost way as <noscript>...</noscript> and document.write() is supported by virtually any browser brand and version.

This way I was able to omit any kind of browser detection: The above method works both with IE6+ and Opera 9+ where it is intended to do the job as well as with Mozilla/Firefox (let's hope Firefox never needs this work-around, but you can never be sure). The auto-generated JavaScript is so simple that there shouldn't be a problem with any script enabled browser. And other browsers without scripting will still display the <noscript>...</noscript> area - then of course with the obligation to click the plug-in content before you can use it. In that case there's nothing one can do.


The download

There we are - the code quoted above pretty much covers it all. So the resulting ZIP file containing both the PHP wrapper and the JavaScript one-liner is as small as 2,42 KB. - Enjoy!

Unfortunately the last version 2006-07-01 had bug that prevented more than 9 media embeddings to be displayed correctly. This bug has been found and even fixed rather long ago, though I've been extremely busy since then and so it somehow fell under the table to upload the fixed version. My apologies!

 

 


Disclaimer: All quoted product or company names are or might be trademarks or registered trademarks owned by the correspondent companies. These names are used here solely for documentation purposes.
Any information on this page is given "as is". Though it is copyrighted by Gero Zahn, gero-at-gerozahn.de, the author refuses to take any responsibility for the described functions or side-effects using his software in unchanged or changed form.