Sunday, June 21, 2009

Sending tokenized SMS with drupal to CiviCRM contacts and/or Views of... numbers

Drupal SMSframework to the rescue



For a I project I'm working on I had the need to organize huge lists of mobile phone numbers to send SMS to. Actually there's a nice drupal module, smsframework that lets you plug in your favourite SMS gateway to accomplish such a task. Merely there are still some limitations with smsframework:

  1. there is an interface to send SMS to drupal users (sms blast module), but you cannot apply any filter on it (you just can send SMS to all the users at once)

  2. it lacks an interface to send SMS to contact lists (ie. simple lists of numbers created with cck and views for example)

  3. there's no integration with CiviCRM so you cannot send SMS to civicrm contacts

  4. you cannot schedule jobs for sending SMS

  5. it lacks integration with token module


Send tokenized SMS to contact lists and civicrm users


To overcome this shortages I've written a small module that lets you send tokenized SMS to contact lists. It depends on smsframework module, views and views bulk operations for the GUI. Optionally you can install the token module.

Steps follow:

  1. install the modules then create a View with a field that contains the phone numbers (ie. a field of the user profile or a cck field)

  2. add some fields to the view that eventually you can use as tokens in the SMS text (ie. the name of the user)

  3. set the display mode to "views bulk operation" and select the action "Send SMS to..."

  4. optionally add some exposed filters to the view

  5. visit the view page, select the desidered rows, type the SMS text and send it


(it's easier to do than to explain :)

As you can create views of civicrm users with their mobile phone numbers, you can also use this with civicrm contacts.

I've applied for a cvs account in drupal contrib repository but the mantainers didn't answer me so I publish this here who is interested:

download smsbulk module

Please read the included README.txt first.

p.s. I've written a note to the mantainer of smsframework module asking for integration and I'm wating for a feedback.

Saturday, June 20, 2009

drupal SEO primer for the lazy

The state of the art of drupal SEO



I've read several articles about drupal SEO practices. The best one i met is
basic drupal seo on site optimization which is not really "basic" as it covers all the crucial aspects of drupal SEO. I add here some notes to that post:

1. the suggestion to "NOT install the Drupal Sitemap Module" sounds actually obsolete as the xmlsitemap project is now mature and ready for drupal 6.

2. the author is using rewrite rules to deal with canonical urls. at the time of writing, search engines didn't yet support the attribute rel="canonical". the meta tags drupal module now addresses that.

3. he doesn't list the brand new module seo checklist

Drupal SEO quickstart



If you don't have time to read SEO best practices or simply don't want to install a bunch of modules to cope with it, there is a simple shortcut I sometimes have used to achieve some simple improvements. Put this code in your theme template.php:


function __truncate($s) {
return htmlentities(drupal_html_to_text(truncate_utf8($s, 256, TRUE, TRUE)));
}

function phptemplate_preprocess_page(&$vars) {
$vars['meta'] = '';

if ($vars['is_front'] && $vars['mission'] != '') {
$description = $vars['mission'];
}
else if (!Empty($vars['node']->teaser)) {
$description = $vars['node']->teaser);
}
else if (!Empty($vars['node']->body)) {
$description = $vars['node']->body;
}
elseif (arg(0) == 'taxonomy' and arg(1) == 'term' and is_numeric(arg(2))) {
$description = db_result(db_query("SELECT description FROM {term_data} WHERE tid = %d", arg(1));
}

if (!Empty($description)) {
$vars['meta'] .= '<meta name="description" content="'. __truncate($description) .'" />'."\n";
}

if (isset($vars['node']->taxonomy)) {
$keywords = array();
foreach ($vars['node']->taxonomy as $term) {
$keywords[] = $term->name;
}
$vars['meta'] .= '<meta name="keywords" content="'. implode(',', $keywords) .'" />'."\n";
}

if (isset($_GET['page']) || isset($_GET['sort'])) {
$vars['meta'] .= '<meta name="robots" content="noindex, follow" />'. "\n";
}

if ($vars['node']->title) {
drupal_set_title($node->title .' | '. $vars['site_name']);
}
}


This is mostly inspired by the blueprint drupal theme template.php with a couple of twists. What it does is self explanatory

1. set up page title with node title if any
2. set up meta description with the node description or term description if any
3. if there are related tags then use those ones as meta keywords
4. add "noindex" if the page comes from a pager link to avoid indexing of duplicate content

After that just put



<?php print $meta; ?>



in the head section of your page.tpl.php.

--

Wednesday, June 3, 2009

ajax pagination and slideshow effects in drupal 6

The state of the art of drupal slideshow modules



As you can read in the well made comparison of slideshow modules in drupal there are at least ten modules that practically aim to fulfil nearly the same task: implement a slideshow of content with ajax pagination and easing transition effects.

My personal fav is views slideshow, which is simple and does the right things. Dynamic display block seems also promising but i don't like the fact that you must add templates (tpl) and styles in your theme folder to make it work. I prefer views slidehow because it works in a moment just out of the box.

Ajax calling



When i made my last project there was no support for ajax pagination in any of the summentioned modules. This trend is changing rapidly as ajax loading of content with the ability to add easing effects is being requested more and more.

In the best case this will go straight into the views module in a near future.

The quick workaround



As i couldn't wait, I had to use I small fix for my project. What I needed was a simple ajax loading of content with a fadeIn/fadeOut effect applied every time the user clicks on next/previous link in some blocks of content.

First i reviewed all the modules above, then i decided to use none: the views module in drupal 6 has an excellent ajax pager, the only thing it lacks are easing effects applied to the slide transition. I thought that a small workaround around this could be more convenient than wait.

This is what I reached:


if (Drupal && Drupal.jsEnabled && Drupal.Views) {

Drupal.Views.Ajax.ajaxViewResponse = function(target, response) {
if (response.debug) {
alert(response.debug);
}
var $view = $(target);

if (response.status && response.display) {
var $newView = $(response.display);
$view.fadeOut('fast', function() {
$view.replaceWith($newView);
$view = $newView;
$view.fadeIn('fast', function() { Drupal.attachBehaviors($view.parent()) });
});
}
if (response.messages) {
$view.find('.views-messages').remove().end().prepend(response.messages);
}
};



With this code placed in my custom theme javascript I've been able to simply overwrite
the views module handling of the ajax pager transition and to use the fadeIn/fadeOut effects.

You can put whatever effect you like in the code, in particular this is useful if you plan to use the jquery easing plugin for the dynamic effects.

Hint: if you, like me, have several blocks with different pagers in a single page, you must set a unique pager id for each one to make them work together. This is easily accomplished via panels module or using the pager properties when editing the views.


my 3¢

Monday, June 1, 2009

drupal nodereference 2.0 - tagging with reviews

Cascade delete referencing nodes



One of the most frequently requested feature about drupal nodereference is "on delete cascade" of referred nodes when you remove the referenced one. This is a pretty easy task to accomplish if you are using the nodereferrer module:


function mymodule_nodeapi(&$node, $op, $params = NULL, $page = NULL) {
switch ($op) {
case 'delete':
if ($node->type == 'my_content_type_name') {
$referrers = array_keys(nodereferrer_referrers($node->nid));
foreach ($referrers as $referrer) {
node_delete($referrer);
}
}
break;
}
}


you can obtain the same effect without nodereferrer module: you have to query the database to obtain the referencing nodes nids like this:


function mymodule_nodeapi(&$node, $op, $params = NULL, $page = NULL) {
switch ($op) {
case 'delete':
if ($node->type == 'my_referenced_content_type_name') {
$result = db_query("SELECT nid FROM {content_type_referrer_name} r WHERE
r.field_content_reference_nid = %d", $node->nid);
while ($referrer = db_fetch_object($result)) {
node_delete($referrer->nid);
}
}
break;
}
}


Ok i guess you already knew those ones :]

Notifications when adding referencing nodes (ie. reviews)



Now the pulp: another task I had to implement for a website was "notify users about new nodes added referencing a content type". In other words: notify users when a review is written about an article. "Review" is a content type referencing another content type "article". I use the notifications module and afaik there's nothing implemented there that take care of this issue.

I added this snippet in my custom module:


function _oninsert_notification(&$review, $op) {
if(module_exists('notifications')) {
$article = node_load($review->field_article_ref[0]['nid']);
$event = array(
'module' => 'node',
'uid' => $article->uid,
'oid' => $article->nid,
'type' => 'node',
'action' => $op,
'node' => $article,
'params' => array('nid' => $article->nid),
);
notifications_event($event);
}
}


and hooked this one into the nodeapi (review) insert operation.

This adds an event in the queue and will send an email to users that have subscribed an article once a review is written about it.

Tagging with referencing nodes



Last but not least: tagging. What if you have to tag an article writing a review ? You have to add the tags to the referenced node (the article) but they normally are assigned to the review only. To get this behaviour I added this code to my module:


function mymodule_nodeapi(&$node, $op, $params = NULL, $page = NULL) {
switch ($op) {
case 'insert':
if (isset($node->field_article_ref[0]['nid'])) {
$article = node_load($node->field_article_ref[0]['nid']);
$tags = array_keys(taxonomy_node_get_terms($node));
foreach ($tags as $tag_tid) {
db_query('INSERT IGNORE INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)',
$article->nid, $article->vid, $tag_tid);
}
break;
}
}


This works if you have a tag vocabulary assigned to both content types (this will save at least some joins :)

So now you have got tags assigned to both reviews and articles. Does it worth ? Sure !
You can now count how many times an article is tagged with a specified tag and create suggested tags and tag clouds:


/*
* nid is the article node nid
*/
function _most_used_tags($nid = NULL) {
$terms = array();
if (is_numeric($nid)) {
$referrers = array_keys(nodereferrer_referrers($nid));
if ($referrers) {
$result = db_query_range("SELECT t.*, COUNT(t.name) AS rweight FROM {term_data} t
INNER JOIN {term_node} n ON t.tid = n.tid WHERE t.vid = %d
AND n.vid IN (". db_placeholders($referrers) .")
GROUP BY t.tid ORDER BY rweight DESC",
array_merge(array(VID_TAGS) /* vocabulary vid */, $referrers),
0 /* starting offset */, 10 /* number of tags returned */);
while ($term = db_fetch_object($result)) {
$terms[$term->tid] = $term;
}
}
}
return $terms;
}


this will return the most used tags about the article. Those tags are added when inserting reviews.

Can the community tags module do this ? Absolutely it can't. It does not let you associate tags inline with content editing/writing. This is the reason i didn't use it in my project.

my 3¢