CommonPlaces Gazebo

A Great Opportunity for Small Businesses

Posted June 25th, 2009 · by Harry · No Comments

Today’s post won’t be a long one, but I wanted to talk for a bit about something that came into my Inbox today, courtesy of MarketingCharts. Frankly, I found this shocking:

“America’s top CEOs appear to be mostly absent from the social media community, according to an analysis by UberCEO, which discovered that only two of 2009’s Fortune 100 CEOs have Twitter accounts, none have personal blogs, 13 have LinkedIn profiles and only 19 have a personal Facebook page.”

Reallly?

Wow.

Meanwhile, a separate study shows that only 14% of executives think that CEOs have a good reputation in the eyes of the public. What better way to improve that reputation and connect with customers and stockholders on a personal level than through the above technologies?

My point today (yes, I do have one) is that their loss can be your small business’s gain. Take the (free) opportunity that sites like Twitter and Facebook give you to connect with your customers. Start a blog, and create an experience for your customers that extends beyond a financial transaction. Give your customers some insight into the inner workings of your business, and give them a little bit of the feeling that it’s you and them against the “big guy.” These are things that can create brand loyalty in a big way.

Well, that’s my two cents. If you’d like to learn more about the opportunities that exist for your business in social media, contact us here (or send us a tweet!).

→ No CommentsTags: Internet Marketing · SEO & Social Media

The Big Tweet in the News

Posted June 23rd, 2009 · by Harry · No Comments

Last week, CommonPlaces helped organize a Twitter networking event at Endicott College in Beverly, Massachusetts. The event was called The Big Tweet, and our CEO Ben Bassi spoke briefly to the group of 350 about how businesses are using Twitter today to drive traffic, increase sales, and grow their business.

We were excited to see the event covered today in the Salem News, based in Salem, Massachusetts. Here’s a link to the article. We’ve received a lot of great feedback on Ben’s speech and the event in general, and we are definitely eager to organize more events like this one in the future.

→ No CommentsTags: Press Releases · SEO & Social Media

Where Are We Using That Module Again?

Posted June 23rd, 2009 · by Mike · 3 Comments

Like many people, I’m often looking for ways to “make our lives easier.” At CommonPlaces, we have many different Drupal projects and contributed modules in use in those projects. When a new advisory is released for a module — often an important security advisory requiring immediate attention — we would have to look through all the projects to find out which ones are using the module and what version of the module is in use, so we could determine if that version needs to be upgraded.

In response, we have implemented the following pair of scripts that automate the collection of this information for us. It greatly reduces the time we need to spend tracking all of this down.

As more and more Drupal developers are moving their projects into a source control system like Subversion, we thought we would offer these scripts here in the hopes that they would help others with the same problem.

An overview:

  • A PHP script runs every six hours via cron on our development server where the Subversion repositories are stored. The script accesses the Drupal RSS feed for contributed module advisories and extracts the ones that have been issued in the last six hours, or since the last time the script was run.
  • If a new advisory is found, the PHP script needs to determine what module it was for. This actually isn’t in the RSS feed itself — but we can and do obtain it from the drupal.org node which has the complete advisory.
  • A bash script is called with the module name. The bash script, which can also be run by itself to check manually, looks in our repositories for the module and prints its location along with the version of the module from the .info file.
  • The PHP script takes the output of the bash script, packages it with the relevant information from the RSS feed and emails it to a set address which in our case is an internal engineering mailing list.

Let’s take a look at the code. First up is the PHP script, check-modules.php. Note that it is PHP5.

#!/usr/bin/env php
<?php
/**
 * Examines the Drupal contributed module RSS feed for latest security
 * alerts and sends email to notify of the alert.
 */

// Begin Configuration

// The URL to the RSS feed
$rss_url = "http://drupal.org/security/contrib/rss.xml";
// The number of hours between runs (must match cron for this to be effective)
$interval_hours = 6;
// email to_address, possibly a company mailing list
$to_address = "to-user@DOMAIN.com";
// email headers including from address
$headers = 'From: user@DOMAIN.com';

// End Configuration

// parse the rss feed
$doc = new DOMDocument();
$doc->load($rss_url);
foreach ($doc->getElementsByTagName('item') as $node) {

  $title = $node->getElementsByTagName('title')->item(0)->nodeValue;
  $desc = $node->getElementsByTagName('description')->item(0)->nodeValue;
  $link = $node->getElementsByTagName('link')->item(0)->nodeValue;
  $date = $node->getElementsByTagName('pubDate')->item(0)->nodeValue;
  // i.e.  <pubDate>Wed, 10 Jun 2009 21:07:08 +0000</pubDate>

  // Get the timestamp from the date and compare with current
  $ts = strtotime($date);
  $ts_now = strtotime("-".$interval_hours." hours");

  if ($ts + ($interval_hours*60*60) > $ts_now) {
    // This advisory was issued since the last run
    // Process it and send the alert email

    $subject = file_get_contents($link);

    // Extract the url for the project page and get the module name
    // The rss feed does not contain the url to the module project page,
    // so we have to access the vulnerability page and get the url, and
    // then the module name
    // i.e. "http://drupal.org/project/services"

    $num = preg_match_all("/http://drupal.org/project/([a-z_]+)/", $subject, $matches);
    $module = $matches[1][0];

    $output = "";
    $output .= "ATTENTION: The following Drupal contributed module advisory has been issued:nn";
    $output .= "$titlen";
    $output .= "$linkn";
    $output .= strip_tags($desc);
    $output .= "Date: ".date('D, d M Y h:i:s A',$ts)."n";
    //$output .= "Timestamp: $tsn";
    $output .= "Project page: ".$matches[0][0]."n";

    $output .= "nThis module was found in the following repositories. Click the "
      . "link at the top of this message and compare "
      . "the version of the module in the repository with the version affected by the "
      . "vulnerability to determine whether or not an upgrade is necessary. If no "
      . "repositories are listed, the module does not appear in any repositories.n";

    // Empty the output array
    $cmd_output = array();
    exec("/usr/local/bin/repo-module.sh $module", $cmd_output);
    foreach ($cmd_output as $key => $val) {
      $output .= $val . "n";
    }
    $output .= "nn";
    //print($output);

    // send mail
    if (!mail($to_address, $title, $output, $headers)) {
      // Message for testing
      //print("Error sending mailn");
    } else {
      //print("Sent mailn");
    }
  }
}
?>

Next is the bash script called by check-modules.php. This one is called repo-module.sh.

#!/bin/bash

# Takes a Drupal module name and determines what repositories it exists in,
# along with the version of the module according to the .info file.
#
# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

for repos in $(ls /your-svn-dir)
do
  for dir in $(svn ls "file:///your-svn-dir/$repos/branches" 2> /dev/null)
  do
    if svn ls "file:///your-svn-dir/$repos/branches/$dir/all/modules" 2> /dev/null | egrep -q ^$1/$
    then
      echo ""
      echo "Found in $repos : branches/$dir"
      svn cat "file:///your-svn-dir/$repos/branches/$dir/all/modules/$1/$1.info" | grep version
    fi
  done

  for dir in "trunk/all/modules" "trunk/sites/all/modules"
  do
    if svn ls "file:///your-svn-dir/$repos/$dir" 2> /dev/null | egrep -q ^$1/$
    then
      echo ""
      echo "Found in $repos : $dir"
      svn cat "file:///your-svn-dir/$repos/$dir/$1/$1.info" | grep version
    fi
  done
done

I should explain that at CommonPlaces, we have chosen to store each Drupal project in its own repository, using a two-tiered system. We do this for a variety of reasons, probably too many to go into here. But the important things to note for purposes of this discussion are:

  • Only the project sites directory is stored in a repository. Our two-tiered checkout grabs a copy of Drupal core from another repository, not referenced here.
  • The structure of those project repositories is the basic Subversion trunk-branches-tags structure. We are interested in what is in branches and trunk.
  • If your repository structure is different, or if you use another source control system such as cvs, you will need to modify the bash script accordingly. Also, be sure to replace “your-svn-dir” with the directory on your filesystem where you store your repositories.
  • To use these scripts, save the first one as “check-modules.php” and the second one as “repo-module.sh”, drop both into your /usr/local/bin, and make them executable. Also create a cron job like the following:
    • # Check drupal for module security advisories every 6 hrs and send mail
      
      0 0,6,12,18 * * * /usr/local/bin/check-modules.php > /dev/null 2>&1
    • If you decide to run the cron job at a different interval than six hours, be sure to edit check-modules.php and make $interval_hours match the cron timeframe.

Here’s what a sample email looks like:

Subject: SA-CONTRIB-2009-038 - Nodequeue - Multiple vulnerabilities
From: fromuser@DOMAIN.com
Date: June 11, 2009 12:00:36 PM EDT
To: touser@DOMAIN.com

ATTENTION: The following Drupal contributed module advisory has been issued:

SA-CONTRIB-2009-038 - Nodequeue - Multiple vulnerabilities
http://drupal.org/node/488092

Advisory ID: DRUPAL-SA-CONTRIB-2009-038
Project: Nodequeue (third-party module)
Version: 5.x, 6.x
Date: 2009-June-10
Security risk: Moderately critical
Exploitable from: Remote
Vulnerability: Multiple vulnerabilities

Date: Wed, 10 Jun 2009 06:15:59 PM
Project page: http://drupal.org/project/nodequeue

This module was found in the following repositories. Click the link at the top of this message and compare the version of the module in the repository with the version affected by the vulnerability to determine whether or not an upgrade is necessary. If no repositories are listed, the module does not appear in any repositories.

Found in repo1 : trunk/all/modules
version = "5.x-2.2″

Found in repo2 : trunk/all/modules
version = "5.x-2.6″

→ 3 CommentsTags: Drupal · Web Development

A Few Words on Rel=”NoFollow” (or, Please Don’t Comment on This Blog Post and Take My Google PageRank)

Posted June 22nd, 2009 · by Harry · 1 Comment

Earlier this month, Google employee/celebrity Matt Cutts rocked the SEO world when he stated that Google does not observe the rel=”nofollow” attribute the way we thought they did. In fact, they might not observe it at all. Since then, SEO professionals have wandered around half-dazed, questioning all of the beliefs they once thought were unassailable…Well, maybe I wouldn’t go that far, but Cutts’ comments certainly have caused a stir in the blogosphere. Rather that write about this issue right away myself, I wanted to give a bit of time for the dust to settle, as more and more issues came to light over the past two weeks. But without further ado, here is my overdue, but seemingly obligatory, post on rel=”nofollow”.

google_logo

What’s rel=”nofollow”?

Rel=”nofollow” is an HTML attribute that you can add to any link. For example:

<a href="http://commonplaces.com" rel="nofollow">Click Here</a>

When the attribute was created, Google said that if a link was marked with this attribute, they would not crawl, or “follow,” that link, and the destination URL would not recieve any authority from the link in Google’s eyes. Stated more simply, the site being linked to would, if nofollow was used, receive no SEO benefit from the link.

Furthermore, SEO’s were led to believe that Google authority flowed through the Web, almost like electricity (”Google juice”), and by plugging the holes in your site (making links on your site nofollow), you could retain more Google authority for your own site. People even went so far as to nofollow some of the internal links in their sites in order to direct more authority to the more important or desirable pages of those sites (this was called PageRank sculpting).

Google: “Surprise! It’s doesn’t really work like that.”

All of this changed when Matt Cutts announced at the SMX Advanced Conference in Seattle on June 3rd that Google PageRank doesn’t really work like we all thought it did. Cutts’ official position was that a nofollowed link does not receive any benefit from that link, but the linking site does not receive any benefit from making the link nofollow in the first place.

Previously, sites could preserve the PageRank that would flow out of an external link by making that link nofollow. Now, the PageRank that would flow to that link simply ‘exaporates’ when you make that link nofollow. It neither stays with the originating site or moves to the destination site.

Cutts also took the opportunity to maintain his position on internal PageRank sculpting, saying that a well-built site will do far more for you than any time spent sculpting Google PageRank through your site.

Why They Did It

Conspiracy theorists could offer a number of intriguing theories as to why Google made these changes to an attribute that they themselves created (in fact, Cutts helped create it). However, I think the answer is pretty simple. Google places a very high value on the integrity of their Index, and base their decisions on what will result in websites being assessed in the most accurate way possible by their algorithms. When people begin to use something like rel=”nofollow” to try to “game” the system, Google will devalue that attribute or ignore it entirely. It’s as simple as that. Because Google relies so heavily on links as a measure of a site’s value, it is critical that they can accurately assess those links.

Where Do We Go From Here?

The key question that remains for people who were using rel=”nofollow” on their sites is this: Should I make all the links on my site dofollow links, or should I leave things they way they are? Going forward, is there any value in using the nofollow attribute?

One issue that is particularly puzzling to website owners is what to do with comments in their blogs. If a blog post has 50 comments, all of the links in those comments will bring down the PageRank of the blog. Previously, this issue was addressed by using rel=”nofollow” on all links in the comment section. In fact, software like WordPress does this by default. Now, many are wondering if they should turn off commenting entirely to avoid this loss of PageRank.

Google’s official position, as always, is for everyone to continue to go about their business, and they will take care of the rest. But what is a website maintainer to do? For now, it seems like the wisest choice is to leave all nofollow links on your site as is, and continue to use it in the future as you have in the past. It probably isn’t having the effect you once thought it was, but since it’s hard to say exactly what effect it is having, this is probably the best course of action. In the meantime, keep an eye out for updates on this issue in the coming weeks.

→ 1 CommentTags: Internet Marketing · SEO & Social Media

Real Name vs. Username - On Node Creation

Posted June 11th, 2009 · by Lemuel · 1 Comment

There are a lot of requests on the Drupal.org site about how to change the places where the username appears in order to display the “real name” (it could be first name, last name, or any other information, like dog’s name).

What if in the “Authored By” field when you are first creating a node, you want to look for the real name because you don’t know what crazy things users have inserted as their usernames? Hmmm… Is there a way to look for a real name instead looking for the username? Yes. Drupal is very well structured, and allows you to improve this field in order to better fit your needs.

It’s important that you have some MySQL and Drupal knowledge in order to accomplish this change. This method is not quite as simple as installing a module, but this is the way I have implemented it in the past.

The first step is you should have a custom module that you are playing with in order to modify some things for your website. Then in this module you need to add a new menu item like this in your hook menu function:

<?php
   $items['custom/user_autocomplete'] = array(
      'title' => 'User autocomplete',
      'page callback' => 'custom_user_autocomplete',
      'access callback' => 'user_access',
      'access arguments' => array('access user profiles'),
      'type' => MENU_CALLBACK,
   );
?>

Drupal by default calls this path “user/autocomplete” in the user module and this is what you are modifying now, NOT touching the core.

The second step is to add a condition on hook form_alter that redirects the default request to the one that we created in hook menu:

<?php
// Changing the authoring information in order to call another ajax function instead of core
    if ($form['author']['name']['#autocomplete_path']) {
		$form['author']['name']['#autocomplete_path'] = 'custom/user_autocomplete';
	}
?>

Third step - “the query”. All that you need to do now is implement your query inside the function that you are calling in your custom menu item that calls custom_user_autocomplete. As I’m using the core profile module to build this example, the query that I did looks for the field 1 in the profile_values table which is my “real name” and shows it in the autocomplete choices. The good thing is that while the user sees the real name in the “Authored by” field, the field gets the username value from the users table, so you don’t need to perform other validations to ensure that Drupal will recognize the username that is being inserted as the author of the node. Take a look at the code and the images below:

<?php
function custom_user_autocomplete($string = '') {
  $matches = array();
  if ($string) {
    $result = db_query_range("SELECT u.name, pv.value FROM {users} u, {profile_values} pv WHERE pv.fid = %d AND pv.uid = u.uid AND LOWER(pv.value) LIKE LOWER('%s%%')", 1, $string, 0, 10);
    while ($user = db_fetch_object($result)) {
      $matches[$user->name] = check_plain($user->value);
    }
  }
  drupal_json($matches);
}
?>

Here I am looking for my real name, “Lemuel Santos”:

Entering a real name

After choosing a real name, it gives you back the username, in order to pass the validation with a valid username:

Drupal returns the username

→ 1 CommentTags: Drupal · Web Development

Creating “Simple” Drupal Modules, and Filling the Functionality Gap

Posted June 10th, 2009 · by Jake · 2 Comments

How many times as developers have we been working on a project, and ran across an issue that seriously affected the way we were interacting with the website? Maybe it was something related simply to User Experience, or a functionality that may have been unclear to us, and so would be very unclear to the standard client. This is a very common problem, not just with Drupal, but with ANY Content Mangement System.

What sets Drupal apart is when this “functionality gap” is discovered, its extensible nature allows us as developers to build modules to fill that gap, and add in new functionality, or modify existing workflow in Drupal.

This post will walk through a problem I came across multiple times in previous Drupal sites I’ve worked on, including the sites of my own that I maintain. After describing the problem, we will walk through creating a module to solve the issue, and how to contribute back to the community. This demonstration is using Drupal 6. This is really focused for those developers that have recently converted to Drupal for one or multiple projects, and are attempting to overcome limitations by the core system.

The Problem

The problem for me made itself known while beginning work on the first blog post I was asked to do for CommonPlaces. I was typing away (on a topic unrelated to this) in my development Drupal environment. I’ve never found an offline blogging software that I really like, and I prefer being right inside the site where the post will belong to really preview how a blog post will look. When it comes to creating a long, thought-out blog post on any topic, I’m sure I’m not the only one that has noticed in Drupal that there is not an ability to Save & Continue Editing. We are forced, in order to save our current progress, to Save a post, be redirected to the node view page, and then click on “edit” again in order to get back to our topic.

This is definitely a problem. And we can create a solution without too much effort.

The Solution

In order to solve this issue, we are going to create a simple Drupal module and build in the new features needed, but first, let’s get an idea of what we need.

Must have features:

  • Create a Save & Edit button on Drupal Nodes
    • This will need to modify the default Drupal functionality by redirecting the user back to the node edit form if this custom button is used.
  • Provide the ability to have the new functionality modify the default “Published” option on nodes.
    • A potential issue with this would be when creating new nodes in Drupal, we are ready to “Save & Edit”, but we aren’t ready to publish a node, but the default option for the specified content type IS to set the new node as published.
    • With this in mind, if we alter how a node is published based on preferences here, we will need to provide an additional “Publish” button to enhance the user experience.

Extra features:

  • Allow site admins to set permissions on who can use and administer the new module we are creating
  • Provide some default help text
  • Allow site admins to customize the buttons we are creating AND the default Drupal “Save” button on nodes

Getting Your Hands Dirty With Writing a Module

In order to get started, we need to create a .info file for our module. Drupal uses .info files to describe the module and its contents. For this module, the file will be named save_edit.info

Creating our .info file

name = Save & Edit
description = Gives a "<a href="http://drupal.org/project/save_edit">Save & Edit</a>" button on node pages. No Kittens were harmed during the creation of this module.
package = Administration
core = 6.x

; Information added by drupal.org packaging script on 2009-06-03
version = "6.x-1.2"
core = "6.x"
project = "save_edit"
datestamp = "1244049400"

The .info file needs to provide at least three of the first four items in this example: name, description, and core. The name is the short, friendly name we will use for the module. Description can be a bit longer explanation of what the module does. Core provides Drupal with a descriptor to know what version of the Drupal system this module works with. The additional package item allows it to be classified with other modules on the administration page in Drupal. If you do not include a package name, it will be classified in the “Other” category. The lower sections of this .info file are inserted when the module is packaged and released on Drupal.org.

Creating our .module file

Now we will create out module file, which for this case will be save_edit.module. Drupal uses .module to name the main PHP file in any module. There are situations where .inc files can be included by a module. Any .module file would encompass different functionality and would include a .info file with it. The following examples follow the flow of the .module file that I have created, and not the priority of the features.

hook_help()

/**
 * Provide online user help.
 *
 * @param $path
 * @param $arg
 * @return
 *   Help text
 */
function save_edit_help($path, $arg) {
  switch ($path) {
      case 'admin/settings/save-edit':
          return '<p>'. t('Save & Edit adds a "Save and edit" button to the node add and node configure forms. The module also provides options to modify the way the "Publish" feature works when using Save & Edit. If the modifications are enabled to the Publish feature, when a node is unpublished, it will also create a "Publish" button that will obviously Save & Publish a node that was previously marked as Unpublished.') .'</p>';
    case 'admin/help#save_edit':
      return '<p>'. t('Save & Edit adds a "Save and edit" button to the node add and node configure forms. The module also provides options to modify the way the "Publish" feature works when using Save & Edit. If the modifications are enabled to the Publish feature, when a node is unpublished, it will also create a "Publish" button that will obviously Save & Publish a node that was previously marked as Unpublished.') .'</p>';
    default:
      return '';
  }
}

The usage of hook_help() provides us with a way to give the administrative users of this module some friendly information regarding what the module does, possibly how to use it, or anything they might need to know. In the above example, we are using the $path parameter in order to determine if we should show the help text. Many times, you will see modules use both of the sections as above. This is going to provide a help section on both the module settings page for this new module, and in the default help page.

In most cases, the information on the module settings page would be more concise, and link to the admin/help#your_module page for more detailed help information. I have used the same text in both situations in this case.

hook_perm()

/**
 * Implementation of hook_perm().
 */
function save_edit_perm() {
  return array('use save and edit', 'administer save and edit');
}

hook_perm() is an easy section to throw into any module, but it is very important. In most cases, you want to provide permissions with your module that will allow the admin to configure ways to limit access to the new features you provide, or possibly even who may administer the custom settings your module provides.

The function hook_perm() takes no arguments, and simply returns an array of permissions. In this case, it provides “use save and edit” and “administer save and edit“. This is going to allow our module to later determine if a user has access to use the features of the module, and if they can change admin settings for the module.

hook_menu()

/**
 * Implementation of hook_menu().
 */
function save_edit_menu() {
  $items = array();
  $items['admin/settings/save-edit'] = array(
    'title' => t('Save & Edit Settings'),
    'description' => t('Administer settings related to the Save & Edit module.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('save_edit_admin_settings'),
    'access arguments' => array('administer save and edit'),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

hook_menu() allows us to create menu callbacks to built in functionality and our custom module’s information to render a page.

In the example for this module, we only have one menu callback, which is to create our administration page for the module. The important parts to know here are the page callback which calls a specific function to render the page in question. In this situation, we must use drupal_get_form, which will build our form for us with the Drupal FAPI (Form API) based on the arguments we send to the function in the page arguments section. Here we have save_edit_admin_settings as the page argument. This tells Drupal to look in our custom function save_edit_admin_settings() to find information about what we want to do with the form we will be building with drupal_get_form(). Lastly, the access arguments provide an array of permissions that need to be true in order to render the content to the user trying to visit our new page. In this module, we are limiting this administrative menu item to only users we have defined in the permission section based on the “administer save and edit” we created using hook_perm() above.

save_edit_admin_settings()

/**
 * Admin Settings form for Save & Edit
 */
function save_edit_admin_settings() {
  $form['where_to_save_edit'] = array(
    '#type' => 'fieldset',
    '#title' => t('Node Types'),
    '#description' => t('Set the node types you want to display links for.'),
    '#collapsible' => FALSE,
    '#collapsed' => FALSE
  );
  $form['where_to_save_edit']['save_edit_node_types'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Node types'),
    '#default_value' => variable_get('save_edit_node_types', array()),
    '#options' => node_get_types('names')
  );
  return system_settings_form($form);
}

Our custom function save_edit_admin_settings() is what will be used in the drupal_get_form() defined in hook_menu() above when presenting our administration form to users with the administer save and edit permission defined in hook_perm().

I have actually cut out quite a few of the implemented features in this section to keep the example short. The code included above creates a “setting group” in the first portion of the array, defining a fieldset that will group up multiple form items that are related to each other.

Once we have defined all of our menu items/options, we return the form elements using system_settings_form($form) to send our data back to the Form API to be rendered by Drupal. I’ll save a long winded example of the Form API for another time, as there are tons of examples of the various form element types and how to render them properly on Drupal.org.

hook_form_alter()

This is where things start to get fun. In order to accomplish our goals here, we need to be able to modify the form Drupal is rendering on node add and edit pages to provide our new functionality.

/**
 * Implementation of hook_form_alter().
 */
function save_edit_form_alter(&$form, $form_state, $form_id) {
  $node_types = variable_get('save_edit_node_types', array());
  $form_type = $form['type']['#value'];
  if ($form['#id'] == 'node-form' && $node_types[$form_type] && user_access('use save and edit')) {
    //add save and edit btn
    $form['buttons']['save_edit'] = array(
      '#type' => 'submit',
      '#access' => user_access('use save and edit'),
      '#value' => t(variable_get('save_edit_button_value', 'Save & Edit')),
      '#weight' => 4,
      '#submit' => array('redirect_save_edit_submit'),
    );
    // now if we have chosen to use the auto-unpublish feature, we should
    // create a Publish button to add a clear workflow
    if((variable_get('save_edit_unpublish', 0) || variable_get('save_edit_unpublish_new_only', 0)) && !$form['#node']->status) {
        $form['buttons']['save_edit_publish'] = array(
          '#type' => 'submit',
          '#access' => user_access('use save and edit'),
          '#value' => t(variable_get('save_edit_publish_button_value', 'Save & Publish')),
          '#weight' => 7,
          '#submit' => array('redirect_save_edit_submit'),
        );
    }
    // this allows us to modify the default Save button to something we like more
    $form['buttons']['submit'] = array(
        '#type' => 'submit',
        '#access' => !variable_get('node_preview', 0) || (!form_get_errors() && isset($form_state['node_preview'])),
        '#value' => t(variable_get('save_edit_default_save_button_value', 'Save')),
        '#weight' => 5,
        '#submit' => array('node_form_submit'),
      );
  }
}

This function makes the following modifications to the Drupal node form:

  • IF we are viewing a node form AND IF we are on a node type selected to make use of this module AND IF the user has permission to use this feature,
    • Create out Save & Edit Button
      • Define the custom text for the button
      • Define our custom submit handler to provide the new functionality when this button is used.
    • IF admin has selected to automatically unpublish nodes that use the Save & Edit button OR IF they selected to do that on new nodes only AND IF the node is currently NOT published,
      • Create our Publish Button
        • Define the custom text for the button
        • Define our custom submit handler again to be used for this function
    • Modify the default Drupal Save button
      • Customize the text of the button to use our custom text
      • Remainder of default submit button functionality is not modified, just re-declared

And finally, we can move on to the custom submit handler that really does the legwork for this simple module. This is where we make the major modifications to how things are saved & redirected. In this example, we will just be worrying about publishing/unpublishing the node, and redirecting back to the node edit form.

redirect_save_edit_submit()

/**
 * Custom Submit Handler for Redirecting Save & Edit's to the node form after saving
 *
 * @param $form
 * @param $form_state
 */
function redirect_save_edit_submit($form, &$form_state) {
  // we will first check to see if they want to auto-unpublish, and make modifications if so
  // before submitting the node
  // ONLY do something on new nodes
  if(variable_get('save_edit_unpublish_new_only', 0) && !$form_state['values']['nid']) {
    $form_state['values']['status'] = 0;
  }
  // DO IT EVERY TIME Save & Edit is used. (Seems like a rare case)
  elseif(variable_get('save_edit_unpublish', 0) && !variable_get('save_edit_unpublish_new_only', 0)) {
    $form_state['values']['status'] = 0;
  }
  // WAIT... if someone clicked the Publish button, maybe we should retick that option now
  if($form_state['clicked_button']['#id'] == 'edit-save-edit-publish') {
    $form_state['values']['status'] = 1;
  }
  // call default node save/submit function
  node_form_submit($form, $form_state);
  // only redirect if using the Save & Edit button
  if ($form_state['clicked_button']['#id'] == 'edit-save-edit') {
    // change redirect location
    $form_state['redirect'] = 'node/'. $form_state['nid']. '/edit';
  }
}

Here’s a breakdown of what this function is doing. It is being called when the custom Save & Edit button is clicked, AND when the Publish button is clicked.

  • IF admin has selected to auto-unpublish ONLY new nodes AND the form data does not return an node id yet,
    • Set the status value to zero to make the node unpublished prior to saving
  • IF admin has selected to auto-unpublish EVERY time the Save & Edit button is used
    • Set the status value to zero to make the node unpublished prior to saving
  • IF the Publish button was clicked
    • Set the status value to one to make the node published prior to saving
  • Call the default node submit function, node_form_submit()
  • IF the Save & Edit button was clicked
    • Set the redirect value to return to the node edit form rather than the default of returning to the node view page.

Conclusion

Hopefully this example can help some developers new to Drupal, or new developers that have adopted Drupal understand one of the many ways it is possible to extend the core functionality of Drupal using Modules. For those new to Drupal, please note that it is NEVER acceptable to hack core. There are many reasons behind this, but any functionality you need to add to a Drupal site can be added through the use of modules.

Download the Save & Edit Module

This code in complete form has been released back to the community as the Save & Edit module available for Drupal 6. There are still features & issues that need to be addressed with this. If you find those, and want to help make this module better, post an issue.

Contributing Back to the Drupal Community

Drupal is not just a CMS, but also a vast community of developers that are out there willing to contribute code and time to this powerful open source project. In most cases, if you have a simple feature that needs to be added to your site, there is likely a Drupal Module out there that already does it. If you do stumble across something simple that is not covered by a current module, it is always best to contribute back to the community when you have a working solution.

There are many times where a client may not allow you to contribute proprietary code back to the community, but in a lot of cases, simple functionality can easily be given back to benefit anyone using Drupal that needs your solution for their own problem.

The following list of resources provides valuable information regarding creating and contributing your addition to the Drupal community.

→ 2 CommentsTags: Drupal · Web Development

Big Opportunities for Pharmaceuticals in Social Marketing

Posted June 1st, 2009 · by Ben · No Comments

I came across some interesting statistics recently, and thought I would share them here. Let’s start with the big one – 113 million Americans use the Internet to find health-related information, according to the Pew Internet & American Life Project. That’s 37% of Americans. Thirty of that 113 million consider the Internet their first source for this type of information. These numbers alone would indicate that pharmaceutical companies would benefit from building their online presence.

In spite of this information, and the explosive growth of social networking and social media online, pharmaceutical companies have been hesitant to market themselves in these online channels. Michael Keriakos, co-founder and president of Waterfront Media, estimates that while companies in other industries are dedicating around 10% of their budgets to social marketing efforts, pharmaceutical companies are, on average, dedicate less than 2%. What’s the reason for this? Well as you might guess, it is not just an oversight. Many drug companies are concerned about the strict guidelines enforced by the FDA about how a drug can be marketed and messaged. The FDA’s continued message, however, is that they are concerned with how these companies market themselves, not where. So while many companies continue to shy away from this often highly effective form of marketing, some are taking the plunge.

One notable example is AstraZeneca’s MyAsthmaStory campaign. They’ve created a YouTube page of people’s success stories with using Symbicort, their asthma controller medicine. So far the page features about ten videos, but they are inviting people to submit their own success stories, and they will continue to add selected videos as they receive them. You will also notice that they have posted full Symbicort safety information in the left sidebar, so that they can unobtrusively follow all FDA regulations. I don’t believe this information takes away from the page in any way, and they have created a social campaign that could be quite successful.

If you would like to learn more about how your business can benefit from social marketing online, you can contact CommonPlaces here.

→ No CommentsTags: Internet Marketing · SEO & Social Media

Theming Template Calls in Drupal 6

Posted May 29th, 2009 · by Michelle · 1 Comment

Welcome to the first in what we hope will be a ongoing series of blogs that discuss the more technical side of things. Today I’d like to talk about how we theme template calls in Drupal 6, and how much it has changed from the way we did it in Drupal 5.

The Theory

Let’s dive right in. At its most basic level a website is built from a collection of files and database entries. These files are separated into several layers, so that it is easier to make changes to the application in the future. These layers consist of:

  • Content - the raw data that gets displayed on a page. In a CMS like Drupal, this is stored in the database.
  • Functionality - The logic that decides which content gets displayed and how it interacts with other data
  • Form - the HTML
  • Presentation - the CSS

The benefit of this separation is that we can make changes in one layer without affecting the others.

At CommonPlaces, we strive to separate markup into individual templates with the site logic (or Functionality) remaining in template.php.

How We Did It In D5

With Drupal 5, in any of our template.php functions, we would often find ourselves needing to include markup in our variables. To remove the HTML into a separate layer, we used a function and a template. In order to pass the variables to a template, we used the function _phptemplate_callback. For example:

 

function _phptemplate_variables($hook, $vars) {
  global $user;
  if ($vars['type'] == 'mytype' || $vars['type'] == 'mytype2' || $vars['type'] == 'mytype3') {
    if ($user->uid > 0) {
      $links = array(
        'add_mytype' => l(t('Create a MyType'), 'node/add/mytype', array('title'=>'Create a MyType'), 'destination='.drupal_get_destination()),
        'add_mytype2' => l(t('Share an MyType2'), 'node/add/mytype2', array('title'=>'Share an MyType2'), 'destination='.drupal_get_destination()),
        'add_mytype3' => l(t('Share an MyType3'), 'node/add/mytype2', array('title'=>'Share an MyType3'), 'destination='.drupal_get_destination())
      );
    }
    else {
      $links = array(
        'login' => l(t('Login'), 'user', array('title'=>'Login'), 'destination='.drupal_get_destination()),
        'register' => l(t('Register'), 'user/register', array('title'=>'Register'), 'destination='.drupal_get_destination()),
      );
    }
    return _phptemplate_callback('mytype_links', $links);
  }
}

We would then be able to add any sort of markup we wanted in these line templates.

In a file called mytype_links.tpl.php, we could then have the following:

 

<ul class="tools">
  <?php if ($add_mytype) : ?>
    <li class="add_mytype"><?php print $add_mytype; ?></li>
  <?php endif; ?>
  <?php if ($add_mytype2) : ?>
    <li class="add_mytype2"><?php print $add_mytype2; ?></li>
  <?php endif; ?>
  <?php if ($add_mytype3) : ?>
    <li class="add_mytype3"><?php print $add_mytype3; ?></li>
  <?php endif; ?>
</ul>

There are several instances where we would use this approach. For example, in a view template function if we want to print elements of loaded node from the node reference, we could create a template with markup for those elements.

How To Do It In D6

The fabulous _phptemplate_callback function no longer exists in Drupal 6. In fact the whole templating structure is different. D6 is based on template suggestions and if you need a line template that doesn’t fit into that hierarchy, you use theme_render_template and pass it the name of the template file and the variable array:

 

function mytheme_preprocess_node(&$vars, $hook) {
  if ($vars['type'] == 'blog') {
    $vars['blog_info'] = theme_render_template(path_to_theme().'/blog-info.tpl.php', array());
  }
}

In a template called blog-info.tpl.php, we could then have this:

 

<?php if ($terms) : ?>
  <span class="blog-terms">Posted in: <?php print $terms ?></span>
<?php endif; ?>
<span class="blog-date">by <?php print $name?> at <?php print format_date($created, 'custom', 'H:i a')?></span>
<span class="blog-comments"><?php print l('Comments', 'node/'.$nid, array('fragment'=>'comments'))?> (<?php print $comment_count?>)</span>

You can then print the variable $blog_info in your node.tpl.php file.

Reference Links

Separate Content from Functionality and Design
Separating Content from Form and Presentation
(NOTE: The above two references don’t mention the use of a CMS and its ability to add another separation layer.)

From Drupal.org
D5 _phptemplate_callback
D6 theme_render_template
D6 Template Suggestions

→ 1 CommentTags: Drupal · Web Development

Acquia and Drupal Featured in the Boston Globe

Posted May 27th, 2009 · by Harry · No Comments

You can currently read a great article about Acquia and Drupal on the Boston Globe site (thanks to Eileen for sending it to me!). Acquia is located in Andover, MA, and the article is all about how Drupal specifically, and open source software more generally, is becoming more and more accepted as reliable and cost-effective in the business world. Citing such sites as The Onion, Recovery.gov, NASA and Sony Music, the author proves the point that Drupal is becoming a very viable option for any organization, large or small.

Just seeing a feature article like this in the Globe proves that Drupal, with Acquia’s help, is moving into the mainstream. The article is definitely worth a read; you can see it here.

→ No CommentsTags: Drupal

The Value of Social Media Marketing

Posted May 26th, 2009 · by Harry · No Comments

If you don’t know MarketingCharts, you can check it out at MarketingCharts.com. It’s not too hard to guess what type of site it is - basically, it’s a lot of marketing-related charts and graphs. But if that kind of thing interests you (like it does me), you should definitely check it out. I am subscribed to the daily newsletter, so I receive four or five charts in my inbox every day.

Last week, one of the reports I received in my daily MarketingCharts email, conducted by Knowledge Networks, claimed “Social Media Generates Hype; Fails to Deliver Marketing Punch.” Well, for as big an advocate of social media marketing as I am, I don’t have to tell you that this article put me on the defensive. The basic premise of the study seems to be that most people surveyed do not go to social sites to make purchasing decisions, or at least claim that they do not. I would argue, however, that social media has much more to do with branding; something that affects consumers’ purchasing decisons in much more subtle and not necessarily conscious ways.

But in lieu of crafting a complete rebuttal to this study, I thought I would point out a MarketingChart in today’s email that proved the value of social media marketing quite nicely. It’s titled “80% of Recession Shoppers Want Companies with ‘Human Face,’” and it was put together by Euro RSCG Worldwide. How does a company give itself a human face, you ask? This is not an easily answered question, but I do know that a powerful social media campaign can go a long way towards making the consumer feel more connected with your company on a personalized level. The article goes on to state that “some 78% of respondents think the internet is a very important part of the shopping experience, even when purchases are not made online.”

Andrew Benett, co-CEO of Euro RSCG New York and global chief strategy officer of Euro RSCG Worldwide, is quoted in the article as saying “Consumers find value in feeling smart about purchase decisions and investing in brands they trust. The key [for marketers] is to break through consumer anxiety and reassure customers that they are making intelligent purchase decisions for themselves, their families and the community at large.” To my ears, this all sounds like a perfect opportunity for social media marketing.

→ No CommentsTags: Internet Marketing · SEO & Social Media