Print HTML and CSS to PDF with PhantomJS from PHP

On and off over the last few months, we have been searching for a solution to print a webpage, formatted correctly by a print CSS file, into a PDF file.  One of our web projects uses print CSS heavily in order for users to have a “pretty” view of content.  The trouble was that some of those pages, by necessity, were hundreds of pages long, with some rather complex/heavy CSS formatting.  This caused some clients with old/slow printers to complain, citing very long print times.  Upon further inspection at the client’s sites, we found that the printers were choking on the data.  We didn’t want to split up the pages, or radically change this workflow, so we embarked on a journey to find a way to easily create PDF files of these troublesome pages.

Being a PHP shop, we did a quick search for ‘php html to pdf’, which gave us plenty of results.  It seemed that the leading product in this area was wkhtmltopdf.  However, after playing around with the product and attempting an installation on an Ubuntu server, we saw a lot of dependencies being installed on the machine, and encountered numerous errors trying to generate the PDF.  There were other concerns regarding the age and activity level of the project.  We had one strong contender in wkhtmltopdf, but decided to continue researching.

Our attention quickly turned to PrinceXML after viewing some samples and some great reviews, but were ultimately unable to justify the rather steep server license fee.  DocRaptor was also considered, and was more reasonable, but we tend to shy away from third-party web services only because we focus on open-source software.  With limited research, these two seemed to be very good.

After all this, we discovered the excellent PhantomJS product.  The binary was easily installed on the server with no additional dependencies required, and came with a great deal of example code, including a working ‘print to PDF’ function out-of-the-box.  With some minor tweaks, we customized the script for our use case, and had a PDF copy of our webpages in minutes.  PhantomJS has been around since 2011 and is used by a variety of open source products, listed on their website.  In a later post, we will detail how we integrate PhantomJS with Yii, expanding on the PhantomJS Yii extension.

Read More

Improving the DateTimePicker Yii Extension

The Yii extension DateTimePicker is a great extension when you want a user to be able to select a date and time within the same popup for an input.  We have recently started to use it extensively for dates & times that occur in future months.  We ran into a problem, however, in that the date would default to today’s date – a real headache for the user.  The root cause of the problem is that the Yii extension’s jQuery files are a few versions behind.  To fix it, you can upgrade the ‘assets’ folder with the new version of Trent Richardson’s jQuery-TimePicker-addon extension.  Here’s a step-by-step:

  1. Download the zipped jQuery-TimePicker-addon from github
  2. Extract the zipped contents into your protected/extensions/timepicker/assets folder
  3. Update the minified jQuery file.  You can either copy the exact same code from the main js file into this file, or you can minify it using one of the many easy-to-use JavaScript minifiers.
  4. Test it out!

Read More

Yii Framework Separate Configurations for Different Environments

Yii doesn’t have a built-in way of changing configurations based on the environment that it is running in. However, there are a number of ways to accomplish this.

Method #1

There is a great extension available that allows you to use different configuration files based on the environment that is found in the Apache configuration file.  Yii-environment does require modification of these files, so the most basic hosting plan may not be able to accommodate the extension.  However, as is true with any programming task, there is more than one way to skin a cat.

Method #2

Another method for differentiating environments is to edit the index.php file. Based on a PHP server variable, a different configuration file can be used.

switch ($_SERVER['SERVER_NAME']) {
    case "development":
        $config=dirname(__FILE__).'/protected/config/development.php';
        break;
    default:
        $config=dirname(__FILE__).'/protected/config/production.php';
        break;
}

To make things easier, you can just include the variables that will differ in your development/production configuration files. A third file can be used to hold all shared variables. For instance, your shared configuration file can look like:

return array(
    'basePath' => dirname(__FILE__) . DIRECTORY_SEPARATOR . '..',
    'name' => 'Application Name',
    // autoloading model and component classes
    'import' => array(
        'application.models.*',
        'application.components.*',
    ),
    // application components
    'components' => array(
        'user' => array(
            // enable cookie-based authentication
            'allowAutoLogin' => false,
        ),
   ),
);

Then, each of the environment-specific variables can go into the development or production.php configuration files.

return CMap::mergeArray(
        require(dirname(__FILE__) . '/shared.php'),
        array(
            'components' => array(
                'db' => array(
                    'connectionString' => 'mysql:host=mysql;dbname=databaseName',
                    'emulatePrepare' => true,
                    'username' => 'user',
                    'password' => 'password',
                    'charset' => 'utf8',
                ),
            ),
     )
);

Read More

Yii Framework – reCAPTCHA with Active Form

CAPTCHA example
CAPTCHA example

CAPTCHAs on the Internet are as prevalent as they are annoying.  Unfortunately, these squiggly words are sometimes the best protection from automated attacks.  In our applications, we try to reduce use of CAPTCHAs, but when we do have to use them, we utilize the reCAPTCHA service from Google.  The nice thing about this service is that it is used to verify text from old publications, allowing for better scans of these publications and digitizing them for the common good.

Readers of our blog know that we love the Yii Framework, and luckily, an extension for this framework allows for very easy reCAPTCHA verification.  Here are the steps:

1. Download the reCAPTCHA extension for Yii.

2. Export the ZIP file contents into your protected/extensions folder.  After you do that, you will have a recaptcha folder in your extensions folder that contains the EReCaptcha.php and EReCaptchaValidator.php files.  There is also a subdirectory that contains the reCAPTCHA PHP library.

3. Visit Google’s reCAPTCHA site to receive your public and private key. At this point, you may want to get 2 keys – one for development purposes, and one for production purposes. We apply for a production key that can only be used on a specific domain, and a global key that can be used during development on any developer’s workstation. If this is the case, see our next post that will detail how to utilize different configuration files depending on the environment the script is currently running in.

4. Add your public and private key to your configuration file in the ‘params’ section:

'recaptcha' => array(
     'publicKey' => 'INSERT_YOUR_PUBLIC_KEY_HERE',
     'privateKey' => 'INSERT_YOUR_PRIVATE_KEY_HERE',
),

5. If you want to add the reCAPTCHA to an ActiveForm, add this to your view:

echo $form->labelEx($model, 'validation');
$this->widget('application.extensions.recaptcha.EReCaptcha',
   array('model'=>$account, 'attribute'=>'validation',
         'theme'=>'red', 'language'=>'en_US',
         'publicKey'=>Yii::app()->params['recaptcha']['publicKey']));
echo $form->error($model, 'validation');

6. In your model, add a line to your ‘rules’. ‘resetPasswordWithCaptcha’ will be used below as the scenario, so keep track of what you name it!

array(
    'validation',
    'application.extensions.recaptcha.EReCaptchaValidator',
    'privateKey'=> Yii::app()->params['recaptcha']['privateKey'],
    'on' => 'resetPasswordWithCaptcha'
),

7. In your controller, add a unique scenario to your model.  For example:

$model = new User;
$model->scenario = "resetPasswordWithCaptcha";

8. Now, validate and save if necessary.

if ($model->validate()) {
    // Put false in this save action so we don't validate again - or the CAPTCHA
    //  will be invalid!
    if ($model->save(false)) {
    }
}

We hope this helps someone use the reCAPTCHA extension with the Yii Framework! Not only are you protecting your site from automated attacks, you are also helping digitize the written word!

Read More