Custom template for block

I’m using Node Blocks to create blocks for a bunch of different content types and I thought I needed to theme the block template on a per content type basis. Turns out I don’t, but it took me a long time to figure out so I wanted to record it for posterity. You can actually use this technique to make a new block template with any logic you’d like. Any data you can grab in template_preprocess_block is up for grabs. Then, add your template name to the theme_hook_suggestions array:

function template_preprocess_block(&$variables, $hook) {
$variables['theme_hook_suggestions'][] = 'block__custom';
}

Your custom template should have the name block–custom.tpl.php. This is in Drupal 7, BTW.

Extending the Context Module

I needed a slight extension of the Context module. Treehouse Agency wrote up a great explanation of how they extended context_condition to create two new conditions. I’m doing pretty much the same thing, but my condition is slightly different so I thought it might benefit somewhat to see another slant on this.

My site has a vocabulary for event categories. The vocabulary has a hierarchy
which is one deep. For one of the parents, “Kids Programs”, I needed to be able to establish a different look & feel on each node view. I set this up via context, using node type event and path “kids/events/*” vs path “events/*” to establish the difference. That worked great until I got to the comment pages. I wanted a user who was commenting on an event to maintain the same context, even while on “comment/reply/nid”. So, I created a condition to check if a node was an event and if it had a category which was within Kids Programs. Then, I had this condition be checked via both hook_nodeapi and hook_form_comment_form_alter, which allow the context be set for all flavors of node viewing.

Here’s where I use the hook hook_context_plugins

/**
* Implementation of hook_context_plugins().
*/
function events_context_conditions_context_plugins() {
$plugins = array();
$plugins['events_context_conditions_context_condition_kids_event'] = array(
'handler' => array(
'path' => drupal_get_path('module', 'events_context_conditions'),
'file' => 'events_context_conditions_context_condition_kids_event.inc',
'class' => 'events_context_conditions_context_condition_kids_event',
'parent' => 'context_condition',
),
);
return $plugins;
}

Next you have to tell Context about your new condition, using hook_context_registry:

/**
* Implementation of hook_context_registry().
*/
function events_context_conditions_context_registry() {
return array(
'conditions' => array(
'kids_event' => array(
'title' => t('Custom Condition: Event node page'),
'plugin' => 'events_context_conditions_context_condition_kids_event',
),
),
);
}

Now we write our new class, events_context_conditions_context_condition_kids_event, extending condition_context. The class has two values, 1 means the node is tagged with at least one kids program, 2 means it isn’t.

class events_context_conditions_context_condition_kids_event extends context_condition {
function condition_values() {
$values = array(
1 => t('Event of type node in a Kids Program category'),
2 => t('Event of type node not in any Kids Program category'));
return $values;
}

function execute($node) {
//If node is an event and has any kids program terms, then meet value 1
//If node is an event and has no kids program terms, then meet value 2

//If the node is not an event, then you are done
if($node->type == 'event'){
//Assume there are no kids program terms, then set this to 1 if you find one.
$kids = 2;
//First gather all the terms in the event category vocabulary
$cats = taxonomy_node_get_terms_by_vocabulary($node, 4);
foreach ($cats as $cat) {
//This is the Kids Program term itself
if($cat->tid == '19') {
$kids = 1;
} else {
//Get all the parents of this term
$parents = taxonomy_get_parents($cat->tid);
if($parents){
//Check each parent to see if it is the Kids Program category
foreach($parents as $parent) {
if ($parent->tid == '19'){
$kids = 1;
}
}
}
}
}
foreach ($this->get_contexts($kids) as $context) {
$this->condition_met($context,$kids);
}
}
}
}

The only thing left is to make sure this condition gets checked whenever we might be displaying a node:


/**
* Implementation of hook_nodeapi().
*/
function events_context_conditions_nodeapi(&$node, $op, $teaser, $page) {
if ($op == 'view' && $page) {
if (module_exists('taxonomy')) {
if ($plugin = context_get_plugin('condition', 'kids_event')) {
$plugin->execute($node);
}
}
}
}

/**
* Implementation of hook_form_alter() for comment_form.
*/
function events_context_conditions_form_comment_form_alter(&$form, $form_state) {
if ($nid = $form['nid']['#value']) {
if (module_exists('taxonomy')) {
if ($plugin = context_get_plugin('condition', 'kids_event')) {
$plugin->execute(node_load($nid));
}
}
}
}

Add Individual Event to Calendar

A Drupal site I’m working on requires links for each event that would add the event to the user’s calendar. I’d like to provide this functionality for iCal, Outlook and Google. Drupals Calendar module offers something similar out of the box–there’s a feed which can deliver a an ics file on a per-day basis. I found some great instructions on how to deliver the ics for a single node. This method handles repeating event really well, but I just can’t make it deal with all-day events. No matter how I set my all day events up, the ics file delivers a start and end time. I’ll keep digging and hopefully find a solution.

To add the event to a Google calendar, I wrote a little code in my template.php file. Here’s my code, combined with the code from above to handle the single node ics feed:

function THEME_preprocess_node(&$vars, $hook) {
if ($vars['type'] == 'event') {
$node = $vars['node'];
$ics_link = "/calendar-single_ev_date/ical/".$node->nid;
$from = strtotime($node->field_ev_date[0]['value']);
$to = strtotime($node->field_ev_date[0]['value2']);
$event_title = str_replace(' ','+',$node->title);
$from_date = date('Ymd',$from);
$to_date = date('Ymd',$to);
$from_time_EST = date('His',strtotime($node->field_ev_date[0]['value']." UTC"));
$to_time_EST = date('His',strtotime($node->field_ev_date[0]['value2']." UTC"));
if ($from_time_EST == '000000' && $to_time_EST == '000000') {
$google_link =
'http://www.google.com/calendar/event?action=TEMPLATE&text='.
$event_title.'&dates='.$from_date.'/'.
$to_date.'&
location=CUSTOMER&trp=false&sprop=website:www.example.org&'.
'sprop;=name:CUSTOMER';
}else{
$from_time = date('His',$from);
$to_time = date('His',$to);
$google_link =
'http://www.google.com/calendar/event?action=TEMPLATE&text='.
$event_title.'&dates='.$from_date.'T'.$from_time.
'Z/'.$to_date.'T'.$to_time.
'Z&location=CUSTOMER&trp=false&'.
'sprop=website:www.example.org&sprop;=name:CUSTOMER';
}
$repeat_string = $vars['node']->content['field_ev_date']['field']['#children'];
$regex = '/^<div>(.*?)<\/div>/';
$matches = array();
if (preg_match($regex, $repeat_string, $matches)) {
$search_strings = array('Repeats',' .',' ',' ');
$replace_strings = array('repeats','.','+','+');
$repeat_string =
'&details=Note:+This+event+'.str_replace($search_strings, $replace_strings, $matches[1]).'
++Google+Calendar+does+not+allow+us+to+set+up+this+repeat+for+you.
++Please+use+the+Repeats+button+above+to+do+so,+if+desired.';
$google_link .= $repeat_string;
}
$vars['add_to_ical'] = "iCal";
$vars['add_to_outlook'] = "Outlook";
$vars['add_to_google'] = '<img src="http://www.google.com/calendar/images/ext/gc_button1.gif" border=0>';
}
}

Google has a great API on how to use this functionality. It handles all day events perfectly, but doesn’t seem to have any way to deal with repeating events, despite the “All day” checkbox you get on their site. I put a note for the user in the description box, which is unideal but will have to do.

We’re going to add some icons for iCal and Outlook, and we’ll be pretty much set.

Hey James, thanks for your help with this!

Search Form restricted to particular node type, in a block.

A site I’m working on has quite a few search forms. Basically, for each of the node types, we want to give the user an easy option to restrict search to only that type, ie events, blog posts, as well as a few custom content types. Sometimes the node types will be presented by taxonomy term, so that the user should be able to easily search through events tagged “toddler,” for example. Here’s how to construct these search boxes and place them into blocks.

First, construct a view which lists all of the content you’d like to search together. For example, I’ve created a view called Event List. In it, I filter based on node type == event. I add another filter from the Search group, “Search: Search Terms”. This filter is exposed and optional. On “empty input”, select “Show All”. I created an argument for the taxonomy term and also choose a few fields. Then, I created a page view and gave it the path “events/%”. In the Basic Settings fieldset, change the setting “Exposed form in block” to “yes.” Save!

Now, when you go to the Blocks page, you’ll see a block called “Exposed form: Events-page_1″ (or something nicer if you name your page). You can just drag it to a sidebar for testing, but later on you’ll probably want to restrict where this block appears.

When the block is displayed on /events/all, it searches all events. When it is displayed on events/toddlers, it only searches within events tagged toddlers. Just what we wanted.

Using Context and Menu Trail to establish subnavigation by taxonomy term

Context is a great module, but the settings page is a little misleading. You can set a context to operate when a node tagged with a term. You can set as a reaction that one of your menu items is made active. However, when you navigate to the page for that node with the menu loaded into a block, the active item is not indicated. Works fine if the menu is on the main part of the page, but not in a block. This is true for any menu — primary, secondary or custom.

We’re working on a site which has the subnavigation in a sidebar. It’s going to be used in a lot of different ways, but the simplest use case on this site is a blog, split into four categories. Each category will be treated as its own mini-blog. The four categories will be in the sidebar. When you’re viewing a post in the Book blog, then the Book link should be active in the sidebar.

It turns out this is really easy to do if you use Menu Trails, in addition to Context. Let context set up the display of the block holding the menu, but then associate each category with the correct menu item in Menu Trail. Together, they will present the menu when you want it and highlight the correct link.