Tuesday, January 18, 2011

Detect the Adobe Reader Plugin

Recently I had to be able to detect the Adobe Reader Plugin in Javascript. I have included the code I used to flag if the Adobe Reader Plugin is installed, as well as get the version of the current adobe reader plugin. This code will detect:

  • Adobe Reader Plugin for Firefox
  • Adobe Reader Plugin for IE ( <5 and 5+)
  • Adobe Reader Plugin for Chrome and
  • The PDF Reader for Chrome (Chrome's default alternative to the Adobe Reader Plugin)
  • Adobe Reader Plugin for most other browsers






View or Download the code here....

To call it in javascript:


var browser_info = perform_acrobat_detection();
alert(browser_info.name + " "+browser_info.acrobat + " " + browser_info.acrobat_ver);


Wednesday, December 01, 2010

SQLite and Doctrine: in memory databases

Recently for a client's project, I incorporated a SQLite in memory database into my persistence tests as its a good (and fast) alternative to managing separate unit test and system databases. Typically in my test setup / tear down methods, I drop and recreate the unit test database to ensure clean testing conditions.... however I found that Doctrine doesn't support SQLite in memory database drops.

To drop an in memory db - I don't believe the standard sql drop statement works. As this is the case, when calling Doctrine::dropDatabases(), Doctrine's SQLite Driver produces in a 'Database could not be found' Exception in its dropDatabases method.

As the SQLite in memory databases exist for the life of the connection only, a good solution is to simply reset the connection.

Burrowing through the Doctrine framework, within the SQLite Driver (Doctrine/Connection/SQLite.php) I made the following modification:


 public function dropDatabase()
    {
        if ( ! $dsn = $this->getOption('dsn')) {
            throw new Doctrine_Connection_Exception('You must create your Doctrine_Connection by using a valid Doctrine style dsn in order to use the create/drop database functionality');
        }
        
        $info = $this->getManager()->parseDsn($dsn);
        
        //
        // BENS EDIT: If the db is in memory only - simply recreate a new connection
        //
        if(strcasecmp($info["dsn"], "sqlite::memory:") == 0)
        {         
         $c = $this->getManager()->getCurrentConnection();

  $this->getManager()->closeConnection($c);
  $this->getManager()->connection("sqlite::memory:","unit_test");
     
         return;
        }
        //
        // END BENS EDIT
        //

        $this->export->dropDatabase($info['database']);
    }

There are probably better ways to do this - so feel free to leave comments - however the above worked for me...

Thursday, November 25, 2010

CodeIgniter and PHP Howto - Embedding images in Email

DOWNLOAD THE CODEIGNITER EMAIL EXTENSION HERE

I recently had an issue in a client's development which required images to be embedded in html email code (<img src='cid:embeddedImage' />), rather than referenced via a public url (i.e. <img src='http://www.mysite.com/myImage.png' />).





QUICK OVERVIEW - THE PUNCH LINE

I have written a CodeIgniter Library Extension to the basic Email class that facilitates the embedding of images in emails. You can download the code HERE.

To implement it, follow the instructions for Extending Native Libraries on the CodeIgniter website http://codeigniter.com/user_guide/general/creating_libraries.html

Finally, in the body of your email, use the following macro - making sure the class_id attribute matches the one used in the img src attribute (i.e. <img src='cid:my_image' />). An exmaple message body would be as follows


<html>
<body>
<img src="cid:my_image" />
</body>
</html>

// Macro in Windows
{embedded_image file=C:\\my_image.png class_id=my_image}{/embedded_image}

//Alternative Linux Macro
{embedded_image file=/var/my_image.png class_id=my_image}{/embedded_image}


And thats it - the library encodes the image file as a base64 string and embeds it in your email.

PROS AND CONS OF EMBEDDING IMAGES IN EMAIL:

PROS

- Images are immediately displayed when opening a message, rather than the client prompting the user to allow remote content... great for newsletters
- Emails are independent, once downloaded, they don't require a live connection to view in all their glory

CONS

- A little bit more coding and knowledge of email message format is required to get them to work if your framework does not support embedding images in email
- Some conjecture about spam filtering for bulk messages with embedded images. Some believe spam filtering is more pessimistic for emails with embedded images than not when sending to more than 100 addresses

HOW TO EMBED AN IMAGE

I'm not going to go too far into the coding of native php code to email with embedded images (unless I get requests) - so Ill give a brief overview and some references.

Like most things on the web, email message content can be defined in a series of envelopes. These envelopes are defined by content type. You can read about all of them here http://www.freesoft.org/CIE/RFC/1521/15.htm

One of the most useful images to exemplify the content structure of a html email can be found at http://www.phpeveryday.com/articles/PHP-Email-Using-Embedded-Images-in-HTML-Email-P113.html
Bit of a warning though - the article itself has buggy code and examples.

Grossly speaking - your email message if it supports html with embedded images, and a plain text alternative (for non html email clients) should render the following content in the email body



Content-Type: multipart/alternative; boundary="UNIQUE_ID_1"

--UNIQUE_ID_1
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit

.... plain text alternative content for your html email....

--UNIQUE_ID_1
Content-Type: multipart/related;
 boundary="UNIQUE_ID_2"

--UNIQUE_ID_2
Content-Type: text/html; charset=utf-8
Content-Transfer-Encoding: quoted-printable

--UNIQUE_ID_2
Content-Type: image/jpeg
 name='embedded_image.jpg'
Content-Transfer-Encoding: base64
Content-ID: <class_id_referenced_in_img_src>
Content-Disposition: inline;
 filename='embedded_image.jpg'

(Base64 encoded binary for the image)

--UNIQUE_ID_2-- 

--UNIQUE_ID_1--


NOTE on the above.

-- means open content type. Trailing -- means closing content type. When content type is 'multipart', you can define more than one content type under the same section identifier as you are either specifying alternative content, or related content. multipart/alternative content means one of the specified content sections is used. multipart/related content means that one content section references the other. We use the multipart/alternative content type to specify html, and plain text message alternatives. We use the multipart/related for relating html content with embedded image content.

Monday, November 22, 2010

noscript tag and google index

The noscript tag is indexed by google! I recently released work for a client which used the noscript tag to display the typical 'this site needs javascript' warning if javascript was disabled on the client's browser.... unfortunately I did this in the head of my page AND used a h3 element to title the warning segment....

As a result, most pages on the site were indexed as 'Javascript not enabled' as the title, and the site's metatagged description as the content. Not happy.

Best practice - if you are going to use the noscript tag for simple javascript warnings, use it at the bottom of the rendered page.... many sites do this (including StackOverflow). Might also help not to use any heading elements (i.e. h1,h2,h3) but rely on styling another element as a heading.

Sunday, November 07, 2010

Free DNS Servers: Free DNS Online

For the last few years of web development, I have had to find a couple of reliable free DNS online services to manage my, and client Domains. I thought I'd post up a few and rate my experience with them.

Free DNS Online - My Experiences


Zone Edit : A great free DNS online service, 100% private and 100% free. Accounts used to be able to serve up to 5 domains, however recently this has been restricted to 2. The User Interface / Administration feels a bit clunky and old school web - but pretty straight forward and easy to use.

FreeDNS : Another good free DNS online service, however you entries can be reviewed by other users. Other users can configure subdomains off your domain, however you can pipe this through an authorisation process (where you the administrator are emailed and confirm any config changes). Payment is required if you want to make your listing private.

DNSExit : An uncapped free DNS online service. You can have as many accounts as you require... you can even set up dynamic DNS which is nice. Very slow though - and I have some questions regarding reliability. Recently the service was brought down by a DDOS attack for a couple of days.... have a backup - secondary dns on an alternate name server if possible.

DynDns : Free Dynamic DNS online service. Been around for years - can't recommend it enough for publicly serving from your dev box. Great for limited serving (i.e. demoing sites during development). Have a range of other paid services - however, a bit of googling for these will uncover alternative free services.


Tuesday, November 02, 2010

Detect Popup Blocker: Popup Blocker Detection for Chrome + all browsers

The mother of all popup blocker detection in Javascript.... based on a few other blog posts around the below combines popup blocker detection for all major browsers including Chrome....




The below javascript can be downloaded here popup_detection.js

The chrome popup testing page used in the below fragment can be downloaded here popup_with_chrome_js.html

var PopupWarning = {
  
  init : function()
  {      
      
    if(this.popups_are_disabled() == true)
    {       
      this.redirect_to_instruction_page();
    }
  },

  redirect_to_instruction_page : function()
  {
    document.location.href = "http://thecodeabode.blogspot.com/";
  },

  popups_are_disabled : function()
  {
    var popup = window.open("http://localhost/my_chrome_popup_test.html", "popup_tester", "width=1,height=1,left=0,top=0");
    
    if(!popup || popup.closed || typeof popup == 'undefined' || typeof popup.closed=='undefined')
    {
        return true;
    }

    window.focus();
    popup.blur();    

    //
    // Chrome popup detection requires that the popup validates itself 
    // - so we need to give
    // the popup time to load, then call js on the popup itself
    //
    if(navigator && (navigator.userAgent.toLowerCase()).indexOf("chrome") > -1)
    {      
      var on_load_test = function(){PopupWarning.test_chrome_popups(popup);};    
      var timer = setTimeout(on_load_test, 60);
      return;
    }
    
    
    popup.close();
    return false;
  },

  test_chrome_popups : function(popup)
  {
    if(popup && popup.chrome_popups_permitted && popup.chrome_popups_permitted() == true)
    {      
      popup.close();
      return true;
    }
    
    //
    // If the popup js fails - popups are blocked
    //
    this.redirect_to_instruction_page();
  }
  };
  
  PopupWarning.init();

Chrome popup blocker detection is a little different to others in that Chrome returns a valid window object after calling window.open, even with popups being disabled. There are a heap of posts around all basically stating that to determine whether this window object has been blocked by Chrome, we need to embed some simple detection code in the popup html itself.


As this is the case, you will need to create an additional html page which will be called as a popup in the PopupWarning.popups_are_disabled function. We also have to give it time to load in the new window, so that we can call the js method after it has been added to the popup dom. We achieve this by setting a timeout to call the popup js. The delay between creating the popup and calling the js test in the code above is about 60 ms. This may have to be extended in an implementing page as 60ms may be too fast. This page is as follows:

<html>
  <head>
    <script type="text/javascript">
    //<![CDATA[
      function chrome_popups_permitted()
      {
        if(window.innerHeight != 0) 
        { 
          return true;
        } 
        else return false;
      }
    //]]>
    </script>
  </head>
</html>

The javascript can be downloaded here popup_detection.js

The chrome popup testing page used in the below fragment can be downloaded here popup_with_chrome_js.html

Friday, October 15, 2010

Google Analytics - multi domain named site tracking



Another quick blog... Recently I added analytics to a couple of sites with more than one domain name. Its easy to see how much of your traffic is from each hostname (Vistors->Network Properties->Hostnames), but
I wanted to be able to overlay how much traffic was arriving to the site from each domain (to see which one was most effectively being advertised).....

I was able to achieve this by creating a Custom Segment.

When first logging into the Google Analytics Dashboard for the desired site / account, just above the Date Range, there is the Advanced Segments drop down list. Clicking on that drop down I was able to create a new Advanced Segment.

In the Advanced Segment editor, I could expand the Content submenu from the Dimensions menu on the left, and drag the 'HOSTNAME' field into my segment.

I then set the Condition field to 'CONTAINS', then enter the base hostname of the additional domain I want to analyse separately (i.e. myseconddomain.com).

After naming and saving the segment, I can apply it via the Advanced Segments drop down in the dashboard and viola....