Friday, October 26, 2012

adding a city autotagging field to an user by creating a custom drupal 7 module

This posts tells you about how to programmatically create a field, instance, taxonomy library and create an autotagging widget in the form with it.

First in your module.install file create a hook_enable() function.
In the final version this would be hook_install() but while testing the module I find it easier to use hook enable so you can do: drush en mymodule -y and drush dis my module -y to enable and disable the module.

My module is called popover so that's the name I'll be using next.

 * Implements hook_enable
 * runs every time this module is enabled, to run only at install use hook_install
 * Once the module has been enabled ( even only once ) hook_install won't be called anymore.
function popover_enable() {
        // starting with the taxonomy library ( vocabulary ) first:

$vocabulary = (object) array(
'name' => 'City',
'description' => 'Pick your city :)',
'machine_name' => 'city',
$terms = array(
        // I've created a custom function to save multiple terms to one vocabulary:

        // building the field
$field = array(
'field_name' => 'field_' .$vocabulary->machine_name,
'type' => 'taxonomy_term_reference',
'settings' => array(
'allowed_values' => array(
'vocabulary' => $vocabulary->machine_name,
'parent' => 0,

        // connecting the user to the field I've just created
$instance = array(
'field_name' => 'field_' . $vocabulary->machine_name,
'entity_type' => 'user',
'label' => $vocabulary->name,
'bundle' => 'user',
'required' => TRUE,
'widget' => array(
'type' => 'taxonomy_autocomplete',
'weight' => -4,
'display' => array(
'default' => array(
'type' => 'taxonomy_term_reference_link',
'weight' => 10,
'teaser' => array(
'type' => 'taxonomy_term_reference_link',
'weight' => 10,
        // another custom field creator function

And the function I'm calling from the code above:

 * save multiple terms to one taxonomy Vocabulary
 * @param  array $terms array of term names
 * @param  int   $vid   Vocabulary id
function _popover_taxonomy_terms_save($terms,$vid) {
foreach($terms as $term) {
(object) array(
'name' => $term,
'vid' => $vid,

 * field creater helper function checks if field already is in this bundle if so do update instead of create
function popover_field_create_update_instance($instance) {
if( field_info_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']) == NULL) {
return field_create_instance($instance);
return field_update_instance($instance);

Now because the instance is in the user bundle the field should now be showing up on the user edit form.

To delete the instance, field and vocabulary upon uninstall of the module. I've written the following:

 * Implements hook_disable()
 * TODO: needs to be hook_uninstall in the future but again for testing using the hook_disable
function popover_disable() {
// create array with instances/fields/vocabularies to cleanup
  $cleanups = array(
  // you can add more fields here...
  foreach($cleanups as $name) {

  // force field purge ( so it is really deleted without waiting for cron )
  // I think I would remove this in the final version as well since your not going to uninstall a
  // module and then reinstall it again immediately after. besides opening the module page 
  // and then the unistall page calls cron if I remember correctly but not sure about that... anyway:
field_purge_batch(5000); // not sure if this is a to high number...

 * cleanup helper function
 * @param  String $name machine_name of the vocabulary ( 'field_' is added for the field/instance)
function _popover_cleanup_instance_field_vocabulary($name) {
$instance = field_info_instance('user','field_' . $name,'user');
// removing instance of field_$name in the user bundle
field_delete_instance($instance, true);
// deleting field
field_delete_field('field_' . $name);

// delete city vocabulary
$vid = db_select('taxonomy_vocabulary', 'v')
    ->fields('v', array('vid'))
    ->condition("v.machine_name", $name)

You can see the whole module on my github But I'm still working on it so don't expect it to work 100%