[Mini Tutorial] CASL Compliance for Newsletter Subscribers

Effective July 1st of this year (2014), Canadian Anti-Spam Legislation (CASL) imposes heavy restrictions on companies using email newsletters. Since most eCommerce retailers use email newsletters to market to customers, many had to make an effort to get in line with CASL regulations, which included gathering subscribers’ express consent to receive marketing emails. Part of CASL compliance, as specified in our CASL compliance ebook, includes tracking the manner in which express consent was granted by the subscriber. According to our book, “this means tracking the date, IP address, form used, or link clicked to obtain consent.” In this tutorial, I will demonstrate how to extend Magento to automatically log the relevant information in a custom table when a new newsletter subscriber is created.

How to Automatically Log Relevant Information in Magento

To achieve this, we need to build the following into a local module that we will call Demac_Casl.

1) Create a table to store the information

2) Use an observer to inject our tracking function when a newsletter subscriber is saved

3) Display the relevant information in a grid on the backend

Instantiate the Module Framework

In order to save subscriber CASL information, a table should be created in the database. To do this, the module must first be declared in app/etc/modules/Demac_Casl.xml

<?xml version="1.0"?>
<config>
<modules>
<Demac_Casl>
<active>true</active>
<depends>
<Mage_Newsletter/>
</depends>
</Demac_Casl>
</modules>
</config>
view raw gistfile1.xml hosted with ❤ by GitHub

See the depends node in there? The module will work with the native Magento newsletter subscriber functionality, so we need to make sure that module is enabled as well.

Next, the module’s configuration in app/code/local/Demac/Casl/etc/config.xml. For the sake of clarity, the whole config will be set up at the beginning. We’ll be making use of models, blocks, a setup resource, and an observer, so we will declare it all up front.

<?xml version="1.0"?>
<config>
<modules>
<Demac_Casl>
<version>0.1.0</version>
</Demac_Casl>
</modules>
<global>
<models>
<demac_casl>
<class>Demac_Casl_Model</class>
<resourceModel>demac_casl_resource</resourceModel>
</demac_casl>
<demac_casl_resource>
<class>Demac_Casl_Model_Resource</class>
<entities>
<subscriber>
<table>demac_casl_subscriber</table>
</subscriber>
</entities>
</demac_casl_resource>
</models>
<blocks>
<demac_casl>
<class>Demac_Casl_Block</class>
</demac_casl>
<adminhtml>
<rewrite>
<newsletter_subscriber_grid>Demac_Casl_Block_AdminHtml_Newsletter_Subscriber_Grid</newsletter_subscriber_grid>
</rewrite>
</adminhtml>
</blocks>
<resources>
<demac_casl_setup>
<setup>
<module>Demac_Casl</module>
</setup>
</demac_casl_setup>
</resources>
<events>
<newsletter_subscriber_save_after>
<observers>
<demac_casl_observer>
<type>model</type>
<class>Demac_Casl_Model_Observer</class>
<method>addCaslDataToSubscriber</method>
</demac_casl_observer>
</observers>
</newsletter_subscriber_save_after>
</events>
</global>
</config>
view raw gistfile1.xml hosted with ❤ by GitHub

Now that configuration is in order, the install script is the next step. Using Magento’s ORM, the install script will create the demac_casl_subscriber table with the necessary fields. So in app/code/local/Demac/Casl/sql/mysql4-install-0.1.0.php:

<?php
$installer = $this;
$installer->startSetup();
$installer->run("DROP TABLE IF EXISTS {$installer->getTable('demac_casl/subscriber')};");
$table = $installer->getConnection()
->newTable($installer->getTable('demac_casl/subscriber'))
->addColumn('entity_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
'identity' => true,
'unsigned' => true,
'nullable' => false,
'primary' => true,
), 'ID')
->addColumn('subscriber_id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
'nullable' => false,
), 'Subscriber Id')
->addColumn('ip_address', Varien_Db_Ddl_Table::TYPE_VARCHAR, null, array(
'nullable' => false,
), 'IP Address')
->addColumn('date_subscribed', Varien_Db_Ddl_Table::TYPE_VARCHAR, null, array(
'nullable' => false,
), 'Date Subscribed')
->addColumn('referrer_url', Varien_Db_Ddl_Table::TYPE_VARCHAR, null, array(
'nullable' => false,
), 'Referrer Url')
->addForeignKey('FK_SUBSCRIBER_ID',
'subscriber_id',
$installer->getTable('newsletter/subscriber'),
'subscriber_id',
'CASCADE',
'CASCADE');
$installer->getConnection()->createTable($table);
$installer->endSetup();
view raw gistfile1.php hosted with ❤ by GitHub

Nothing too complicated on the above script. It checked to see if there was an existing table, then added the table with the specified columns and associated itself with the newsletter/subscriber table via the foreign key of subscriber_id.

Refreshing the config cache and loading the site should now trigger the install script successfully. We now have a table to work with.

Create the Model Classes

The last step in terms of set up is to create the Model classes associated with the Subscriber entity. They are as follows:

app/code/local/Demac/Casl/Model/Subscriber.php:

<?php
class Demac_Casl_Model_Subscriber extends Mage_Core_Model_Abstract
{
protected function _construct()
{
$this->_init('demac_casl/subscriber');
}
public function addCaslData()
{
$ip = Mage::helper('core/http')->getRemoteAddr();
$currentTimestamp = Mage::getModel('core/date')->timestamp(time());
$currentDate = date('Y-m-d', $currentTimestamp);
$referrerUrl = Mage::app()->getRequest()->getServer('HTTP_REFERER');
$this->setIpAddress($ip);
$this->setDateSubscribed($currentDate);
$this->setReferrerUrl($referrerUrl);
}
}
view raw gistfile1.php hosted with ❤ by GitHub

app/code/local/Demac/Casl/Model/Resource/Subscriber.php:

<?php
class Demac_Casl_Model_Resource_Subscriber extends Mage_Core_Model_Resource_Db_Abstract
{
protected function _construct()
{
$this->_init('demac_casl/subscriber', 'entity_id');
}
public function loadBySubscriberId($subscriberId)
{
return Mage::getModel('demac_casl/subscriber')->load($subscriberId ,'subscriber_id');
}
}
view raw gistfile1.php hosted with ❤ by GitHub

That loadBySubscriberId() will be used later on.

app/code/local/Demac/Casl/Model/Resource/Subscriber/Collection.php:

<?php
class Demac_Casl_Model_Resource_Subscriber_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract
{
public function _construct()
{
$this->_init('demac_casl/subscriber');
}
}
view raw gistfile1.php hosted with ❤ by GitHub

So now that the table and models are set up, we can start manipulating the data.

Inject Casl Data with an Observer

The observer will listen on the newsletter_subscriber_save_after event, called, unsurprisingly, after a newsletter subscriber is saved. The function, located in app/code/local/Demac/Casl/Model/Observer.php should be as follows:

<?php
class Demac_Casl_Model_Observer
{
public function addCaslDataToSubscriber($observer)
{
$event = $observer->getEvent();
$subscriber = $event->getSubscriber();
$subscriberId = $subscriber->getId();
if ($subscriber && $subscriberId) {
$model = Mage::getModel('demac_casl/subscriber');
$existingCheck = $model->getResource()->loadBySubscriberId($subscriberId);
if (!$existingCheck->getId()){
$model->addCaslData();
$model->setSubscriberId($subscriber->getId());
$model->save();
}
}
}
}
view raw gistfile1.php hosted with ❤ by GitHub

The function above is relatively simple. It gets the subscriber from the subscribe event, uses the loadBySubscriberId() function I mentioned earlier to ensure that the subscriber’s Casl data has not already been stored, and then adds the Casl data as specified in Demac_Casl_Model_Subscriber::addCaslData() function that was pasted above. That function collects the IP address of the user, the current date, and the url from which the subscribe request was made. It then saves that data to our table.

Now we are able to store the necessary Casl data in the database for each new subscriber. If that is all the functionality you need, we can stop there. However, you may want to actually display this data on the backend.

Displaying the Data on the Magento Backend

You may have already noticed that, according to lines 25-29 of our config.xml, we are rewriting the Newsletter Subscriber grid block. With that rewrite, all that needs to be done is to add our Casl model to the collection loaded for the grid, and then display it. As such, we will rewrite _prepareCollection() and _prepareColumns() to achieve the desired functionality.

<?php
class Demac_Casl_Block_Adminhtml_Newsletter_Subscriber_Grid extends Mage_Adminhtml_Block_Newsletter_Subscriber_Grid
{
/**
* Prepare collection for grid
*
* @return Mage_Adminhtml_Block_Widget_Grid
*/
protected function _prepareCollection()
{
$collection = Mage::getResourceSingleton('newsletter/subscriber_collection');
/* @var $collection Mage_Newsletter_Model_Mysql4_Subscriber_Collection */
$collection
->showCustomerInfo(true)
->addSubscriberTypeField()
->showStoreInfo();
$collection->join(array('casl' => 'demac_casl/subscriber'), 'casl.subscriber_id = main_table.subscriber_id', array('casl.ip_address', 'casl.date_subscribed', 'casl.referrer_url'));
if($this->getRequest()->getParam('queue', false)) {
$collection->useQueue(Mage::getModel('newsletter/queue')
->load($this->getRequest()->getParam('queue')));
}
$this->setCollection($collection);
return Mage_Adminhtml_Block_Widget_Grid::_prepareColumns();
}
protected function _prepareColumns()
{
$this->addColumn('subscriber_id', array(
'header' => Mage::helper('newsletter')->__('ID'),
'index' => 'subscriber_id'
));
$this->addColumn('email', array(
'header' => Mage::helper('newsletter')->__('Email'),
'index' => 'subscriber_email'
));
$this->addColumn('type', array(
'header' => Mage::helper('newsletter')->__('Type'),
'index' => 'type',
'type' => 'options',
'options' => array(
1 => Mage::helper('newsletter')->__('Guest'),
2 => Mage::helper('newsletter')->__('Customer')
)
));
$this->addColumn('firstname', array(
'header' => Mage::helper('newsletter')->__('Customer First Name'),
'index' => 'customer_firstname',
'default' => '----'
));
$this->addColumn('lastname', array(
'header' => Mage::helper('newsletter')->__('Customer Last Name'),
'index' => 'customer_lastname',
'default' => '----'
));
$this->addColumn('status', array(
'header' => Mage::helper('newsletter')->__('Status'),
'index' => 'subscriber_status',
'type' => 'options',
'options' => array(
Mage_Newsletter_Model_Subscriber::STATUS_NOT_ACTIVE => Mage::helper('newsletter')->__('Not Activated'),
Mage_Newsletter_Model_Subscriber::STATUS_SUBSCRIBED => Mage::helper('newsletter')->__('Subscribed'),
Mage_Newsletter_Model_Subscriber::STATUS_UNSUBSCRIBED => Mage::helper('newsletter')->__('Unsubscribed'),
Mage_Newsletter_Model_Subscriber::STATUS_UNCONFIRMED => Mage::helper('newsletter')->__('Unconfirmed'),
)
));
$this->addColumn('website', array(
'header' => Mage::helper('newsletter')->__('Website'),
'index' => 'website_id',
'type' => 'options',
'options' => $this->_getWebsiteOptions()
));
$this->addColumn('group', array(
'header' => Mage::helper('newsletter')->__('Store'),
'index' => 'group_id',
'type' => 'options',
'options' => $this->_getStoreGroupOptions()
));
$this->addColumn('store', array(
'header' => Mage::helper('newsletter')->__('Store View'),
'index' => 'store_id',
'type' => 'options',
'options' => $this->_getStoreOptions()
));
$this->addColumn('ip_address', array(
'header' => Mage::helper('newsletter')->__('IP Address'),
'index' => 'ip_address',
));
$this->addColumn('date_subscribed', array(
'header' => Mage::helper('newsletter')->__('Date Subscribed'),
'index' => 'date_subscribed',
));
$this->addColumn('referrer_url', array(
'header' => Mage::helper('newsletter')->__('Referrer Url'),
'index' => 'referrer_url',
));
$this->addExportType('*/*/exportCsv', Mage::helper('customer')->__('CSV'));
$this->addExportType('*/*/exportXml', Mage::helper('customer')->__('Excel XML'));
return parent::_prepareColumns();
}
}
view raw gistfile1.php hosted with ❤ by GitHub

What we did there is joined the Casl collection data onto the subscriber collection, and added our three fields to the end of the subscriber grid.

Congratulations, your site is now abiding by Canadian Law!