Posts tagged ‘Drupal’

June 7, 2011

Customising CiviCRM Mailing Labels

So, today I had a break from spatial stuff to do some volunteer I.T. work for a local not-for-profit organisation. They use CiviCRM as their contact management system, and they wanted to modify the way that it produces mailing labels for mass mailings (i.e. real paper mailings, not the e-mailshot kind).

Adding a Custom Field to a Mailing Label

The first task was to modify the default fields displayed on the mailing label (name, address, postcode etc.) to include a custom field. This organisation delivers a lot of its local mail by hand rather than through the royal mail, so the custom field was used to assign addresses into a delivery walk, and this was to be printed on the label so it could be given to the appropriate deliverer.

Fortunately, CiviCRM uses a token replacement system, and every user-created custom field is assigned a unique token. The only problem in including the deliverer custom field on the mailing label  was finding out what the token corresponding to the appropriate field was.

I’m sure there must be a better way, but the quick solution that worked for me was to bring up the “Print PDF Letter for Contacts” action from a search result

image

On the next screen, click the “Insert Tokens” link at the top right to be presented with a modal dialog box listing all the names of all the available tokens in your system. Clicking on the name of a token will insert it into the PDF letter, allowing you to see the token behind it. In the screenshot below, for example, the Geo Code 1 field associated with each individual contact can be inserted into any mailing (or mailing label) using the {contact.geo_code_1} token.

image

Having identified the appropriate token for a custom field (in my case, it was {contact.custom_4} ), you can then add this to the mailing label template by going to Administer >> Configure >> Global Settings >> Address Settings and editing the Mailing Label Format to include the relevant token, as below:

{contact.addressee}
{contact.street_address}
{contact.supplemental_address_1}
{contact.supplemental_address_2}
{contact.city}{, }{contact.state_province}{ }{contact.postal_code}
{contact.country}
{contact.custom_4}

Sorting the Mailing List

Having ensured that the custom field was printed onto each label, the next step was to sort the list of labels based on the value of that field. As explained before, the mailings were to be hand-delivered, and it would save a lot of time sorting through the envelopes if they could be printed ready-sorted into batches for the appropriate deliverer, as specified by the custom field. A quick search of the internet reveals that sorted mailings are also required in other circumstances as well, such as sorting by ZIP code when submitting batch mailings to the U.S. Postal Service. However, although reported as a major bug in CiviCRM, the ability to sort mailing lists in anything other than alphabetical surname order of addressee is currently not possible. So I had to hack something together.

A snoop around revealed that the bulk of the work in preparing the mailing labels occurred in the script located at  \sites\all\modules\civicrm\CRM\Contact\Form\Task\Label.php. Within the CRM_Contact_Form_Task_Label class in this file, I created a new comparison function which would sort an array of the items to be included in the mailing list based on the custom_4 field, as follows:

function cmpCustomField($a, $b) {
  if ($a['custom_4'] == $b['custom_4']) {
    return 0;
  }
  return ($a['custom_4'] < $b['custom_4']) ? -1 : 1;
}

Through a bit of experimentation, I determined the best place to sort the records was after they had been run through the “Merge same address”/“Merge same household” scripts, but before being formatted for display. So I inserted a call to the PHP uasort function (sorts an associative array while keeping indexes) that used my comparison function at around line 330, as follows:

...
if ( isset( $fv['merge_same_household'] ) ) {
  $rows = $this->mergeSameHousehold( $rows );
  $individualFormat = true;
}

// INSERT CUSTOM SORT HERE
uasort ( $rows , array($this, 'cmpCustomField'));
    
// format the addresses according to CIVICRM_ADDRESS_FORMAT (CRM-1327)
require_once 'CRM/Utils/Address.php';
foreach ($rows as $id => $row) {
...

The mailing list labels were now produced in nicely ascending order based on the custom field.

Changing the Mailing Label Paper Template

CiviCRM supports a handful of common avery label formats out-of-the-box, but this organisation had recently purchased a bulk load of non-standard labels (A4, 24 labels per page, 8x3) which did not fit any of the supplied templates. They therefore couldn’t be used.

To create a new paper template, changes to two files must be made (both called “Label.php”, but in different directories).

Firstly, in the \sites\all\modules\civicrm\CRM\Utils\PDF\Label.php look for the $averyLabels array and add a new element to the array with the margins and other dimensions of your label sheet. Here’s the settings I used for my new paper (NX and NY are the number of columns and rows of labels on the sheet, width and height determined by dividing the overall dimensions of the page by the number of labels, and the margins determined mostly by guesswork):

$averyLabels = array (
  ...
  'Custom_3x8' => array(
    'name' => 'NGP_3x8',
    'paper-size' => 'A4',
    'metric' => 'mm',
    'lMargin' => 5,
    'tMargin' => 10,
    'NX' => 3,
    'NY' => 8,
    'SpaceX' => 0,
    'SpaceY' => 0,
    'width' => 70,
    'height' => 37.125,
    'font-size' => 9
  )
);

Having defined the settings for the Custom paper template, to actually get these settings to be selectable when producing mailing labels, you also need to edit the file at \sites\all\modules\civicrm\CRM\Contact\Form\Task\Label.php

Near the top is a buildQuickForm() function that contains an array of templates to populate a select list. Add the name of your new paper template to the end of the array, exactly as you defined it in the $averyLabels array above:

$label = array(
  "5160" => "5160",
  "5161" => "5161",
  "5162" => "5162", 
  ...,
  "Custom_3x8" => "Custom_3x8"
);

Save the file, and your new template will magically become available for use:

image

A few tweaks…

Finally, there were just a few tweaks to be made. The new paper template should be always be selected by default for mailing labels (they’d bought reams of the stuff, so it was going to be used for all labels for quite a while yet), and also the “merge contacts with the same address” should always be selected. Adding these defaults was a simple matter of amending the $defaults array declared in the setDefaultValues() function near the top of the \sites\all\modules\civicrm\CRM\Contact\Form\Task\Label.php file.

function setDefaultValues()
{
  $defaults = array();
  $defaults['do_not_mail'] = 1;
        
  // Modified by AA
  $defaults['merge_same_address'] = 1;
  $defaults['label_id'] = "Custom_3x8";
        
  return $defaults;
}

I’m no CiviCRM expert, and I’ve certainly no idea if the approach I took to solving any of these problems represents “best practice” in any way, but it seems to have got the job done and met the immediate needs of my client, so hopefully it might be helpful to somebody else too!

Tags: , ,
February 9, 2011

Editing the field type of an existing CiviCRM field

CiviCRM will only let you edit certain properties of a field after it’s been created. Namely, although you can edit things like the field label and the options set mapped to that field, you can’t edit the data and input field type.

The corresponding field is greyed-out and unselectable on the custom fields list, as shown below:

image

Now this is understandable to a degree – once you’ve created a field in the database and inserted some data into it, you wouldn’t expect to e.g. change a int datatype into a datetime, or alphanumeric datatype. However, what is slightly annoying is that CiviCRM ties you in not only to the datatype used to store data in the backend database, but also the front-end widget used to input that data in the CiviCRM interface.

In my case, I had a (multiple) checkbox that I wanted to change into a (multiple) select list – with no underlying changes to the type of data, or range of possible values that could be stored in that field – I just wanted to change the front-end widget, and there didn’t seem to be any way to do this in CiviCRM itself short of dropping the field (and all the data held in it) and recreating a new field.

Fortunately, editing the database table directly as follows seemed to do the trick, and hasn’t (yet) to any unwanted side-effects:

UPDATE civicrm_custom_field
SET 'html_type' = 'Multi-Select'
WHERE 'name' = 'Ward'
Tags: ,
January 18, 2011

Drupal 7.x hook_menu not displaying page title

Since Drupal 7 was in development for a long time, I, like I imagine many others, started building sites using the various alpha- and beta- releases that have been available since about the summer of 2009. I didn’t want to start a project built on the soon-to-be-superceded 6.x codebase, and 7.x had a number of improvements I was keen to use. Each development release was actually amazingly stable – I’ve been using an alpha6 release in a near-production environment for many months with absolutely no issues whatsoever. Since I was following the development of HEAD relatively closely, I could monitor most changes between versions as they affected me. But then, other projects came up (as they often do), and I lost track of some of the development changes that had happened.

Following the official release of Drupal 7, I then decided to “upgrade” all my sites to use the official version 7.0, and noticed that, right up to the last minute before release, breaking changes were being introduced to the API that I wasn’t aware of. My latest headache was caused by discovering that some of my pages had mysteriously lost their page titles:

image

Shown above is a page from a development site in Drupal 7.x-alpha6

image

And, above, after upgrading my codebase to Drupal 7.0. Notice how the page “Alcohol Quiz” has mysteriously been retitled as just “Home”?

To cut a very long story short, after having checked my code lots of times, my theme files etc., I discovered that there had been a change made to the behaviour of breadcrumbs of MENU_CALLBACK items in http://drupal.org/node/907690, and this has a knock-on effect for page titles. So, under Drupal 6.x and early releases of Drupal 7.x, the following implementation of hook_menu() defines both a url callback and page title (but not a menu item link):


function test_menu() {
 $items = array();
 $items['menupath'] =  array(
 'title' => 'Page Title',
 'type' =>  MENU_CALLBACK,
 …  );

return $items;
}

Now it only registers the url calback. So you can go to the page at /menupath, but there won’t be a title set for that page, and it will therefore default to “Home” as seen in the above screenshots.

The solution if you want to register both a callback and a page title (but still no menu item) is to define your menu item as type MENU_VISIBLE_IN_BREADCRUMB instead:


function test_menu() {
 $items = array();
 $items['menupath'] =  array(
 'title' => 'Page Title',
 'type' =>  MENU_VISIBLE_IN_BREADCRUMB,
 …  );

return $items;
}

(this option does not appear to be documented at http://api.drupal.org/api/drupal/modules–system–system.api.php/function/hook_menu/7)

Tags:
January 7, 2011

Customising the Search Results Snippet in Drupal 7

I use the synonyms module in several Drupal websites. This excellent, simple module allows you to define multiple synonyms for each taxonomy term. There are various reasons why you may want to do this, but the main reason for me is to allow searches for synonymous taxonomy terms to all return the same set of matching node results.

For example, on the www.teenagehealthfreak.net website, young people can search for health information on a number of topics. Considering the nature of medical taxonomy, and the target audience, we wanted to include a wide variety of synonym, slang, and misspelled versions of each taxonomy term. That way, searches for “acne”, “blackheads”, “zits”, etc. all return the page about spots (which is tagged with the primary term “spots”, to which all those synonyms are attached), even if none of those alternative words are actually used on the page itself.

One problem I came across when using the synonyms module is the snippet of text displayed in the search results listing, which is created by the core search module’s search_excerpt() function. By default, search_excerpt()  scans the text of the node and creates a snippet highlighting the matching text found from the search input. However, when using the synonyms module, the supplied search input might not actually appear anywhere in the text of a node at all (since it may just be a synonym of a term that is attached to the page). In these cases, the search result listing was just displaying ‘…’

There is no way of overriding search_excerpt(), and there is no search_excerpt_alter() function that I could find, so instead I used template_preprocess_search_result() to modify search results that had ‘…’ snippets to instead include the first 160 characters of the body text of the relevant node, which is available in the $variables['result']['node'] object, as follows:

function thf_preprocess_search_result(&$variables) {
$result = $variables['result'];
if(strlen($result['snippet']) < 10) {
    $variables['snippet'] = drupal_substr($result['node']->body['und'][0]['value'], 160);
  }
}
Tags:
Follow

Get every new post delivered to your Inbox.

Join 53 other followers