Wednesday, July 8, 2015

Handling Acrobat fillable forms

Acrobat : 10.1.14
Acrobat Reader:  11.0.11
Server Side Processing : PHP

Recently I was creating a PDF fillable form in Acrobat Pro and wanted to find an easier method to receive the filled contents. Easiest method seems to be through an email. I was looking for a solution bit better than JavaScript mailto: method, which opens the default email client and populate the mail with filled information.

The form I was working with , did in fact used JavaScript mailto: method. But when I tried to edit javascript to insert some additional code, it always complain with an error:

SyntaxError: unterminated string literal


These are some tips for fixing it. But nothing worked for me.


According to few sources , this seems to be a bug in the JavaScript processing engine in Acrobat. Since none of the "tips" worked in my case , I tried using a different method to process the form.

1. Creating Forms

I’m not going to describe how I created the form in here. Lynda.com has a great tutorial on creating Acrobat Forms. Have a look (if you have access) and you'll find it really informative and helpful.

2. Submit Button Properties

I have used xfdf format to send data back as it uses plain text xml. Easy to debug and work with.



3. Form submission PHP script - submitForms.php

<?php
 //Send header of type - Adobe FDF
 header ("Content-Type: application/vnd.fdf"); 

 /* Get submitted Contents - Could not use  $_POST variable as the url rewrite is active in htaccess. In that case $_POST variable will be always empty */
 $content = file_get_contents("php://input");

//-----------------------------------------
/* Code from : http://blog.rubypdf.com/2008/12/22/parsing-xfdf-in-php/
 Modified to use a variable instead  a file */


    /* BEGIN VARIABLE DECLARATIONS */
    //global variables for XML parsing
    $values = array();
    $field = "";
    $curTag = "";
     
    /* BEGIN XML PROCESSING */
    // XML Parser element start function
    function startElement($parser, $name, $attrs)
    {
        global $curTag, $field;
        
        //track the tag we're currently in
        $curTag .= "^$name";
        
        if( $curTag == "^XFDF^FIELDS^FIELD" )
        {
            //save the name of the field in a global var
            $field = $attrs['NAME'];
        }
    }
     
    // XML Parser element end function
    function endElement($parser, $name)
    {
        global $curTag;
        // remove the tag we're ending from the "tag tracker"
        $caret_pos = strrpos($curTag,'^');
        $curTag = substr($curTag, 0, $caret_pos);
    }
     
    // XML Parser characterData function
    function characterData( $parser, $data )
    {
        global $curTag, $values, $field;
        
        $valueTag = "^XFDF^FIELDS^FIELD^VALUE";
        
        if( $curTag == $valueTag )
        {
            // we're in the value tag, 
            //so put the value in the array
            $values[$field] = $data;
        }
    }
     
    // Create the parser and parse the file
    $xml_parser = xml_parser_create();
    xml_set_element_handler($xml_parser,  "startElement", "endElement");
    xml_set_character_data_handler($xml_parser, "characterData");
     

        if (!xml_parse($xml_parser, $content ))
        {
            die(sprintf("XML error: %s at line %d", 
            xml_error_string(xml_get_error_code($xml_parser)), 
            xml_get_current_line_number($xml_parser)));
        }
    
     
    xml_parser_free($xml_parser);
    fclose($fp);
    /* END XML PROCESSING */ 
    
//-----------------------------------------
// Compose Message with form data $message = '<html><body>'; foreach ($values as $key => $value) { $message .= "<b>$key</b> : $value <br><br>"; } $message .= '</body></html>'; // Mail Message $to = "tiraj@XXXXX.onmicrosoft.com"; $subject = "Survey Form"; $header = "From:donotreply@XXXXX.org.au \r\n"; $header .= "MIME-Version: 1.0\r\n"; $header .= "Content-Type: text/html; charset=ISO-8859-1\r\n"; $retval = mail ($to,$subject,$message,$header); if( $retval == true ) { /* FDF response taken from : http://leeboyge.blogspot.com.au/2011/07/sending-fdf-response-back-to-pdf.html */ echo "%FDF-1.2 \n 1 0 obj \n << \n /FDF \n << \n /Status (Your form has been successfully submitted.) >> \n >> \n >> \n endobj \n trailer \n << \n /Root 1 0 R \n >> \n %%EOF"; } else { echo "%FDF-1.2 \n 1 0 obj \n << \n /FDF \n << \n /Status (Form Submission failed) >> \n >> \n >> \n endobj \n trailer \n << \n /Root 1 0 R \n >> \n %%EOF"; } ?>

4. Receiving mails in Office 365

Mails sent from a PHP script on any reputable server will reach gmail or any other typical mail service in seconds. But with Office 365 , these mails does not get delivered to user’s inbox unless you do some additional tweaking.

1. White list the IP address.
On Exchange ECP go to Protection , then to  Connection Filter tab and insert the server IP in to IP Allow List

2. Add the domain or server email address to safe sender list.
On Exchange ECP go to Protection , then to Spam Filter tab and insert the sender and sender domain to Allow Lists.

3. Use onmicrosoft.com email address instead of your corporate address.
Your Office 365 Business account always has a default email address which has the domain name part similar to : @yourorg.onmicrosoft.com. You MUST use this email address in your scrip to receive emails.

5. Return value from PHP script

This cannot be text. You have to set the content type of the header to be Acrobat Form Data Format (FDF). If your script return anything apart from FDF headers , Acrobat reader will complain with :

An error occurred during the submit process. Can not process content type text/html


Once you specify the correct return type in:  header ("Content-Type: application/vnd.fdf");  and construct the FDF return data properly , Acrobat Reader will happily accept it. Please note that its important to place the \n characters on right places as they are part of the FDF definition.


References: