Oct 09 2014
Oct 09

Field collections is a nice contributed module that extends the default Drupal entity functionality by creating a new entity field that can be composed by other fields. With this module we solve problems like creating complex entities where we want to store multiple different values into one single field. This works because Drupal lets us assign unlimited values for a field, which links to the field collection entity where we have multiple fields, thats how we "grouped" multiple fields into one field.

At a basic usage, field collections is not difficult to add to our entity, but when we want to do some advanced stuff, this module can be very complex. Here I will show how to do some magic with it, programatically.

Obtain the values of the child fields

How to access the field collection values of an entity? Field collections store field collection items, which are the values of this kind of field. The field collection items use an ID, and with this ID we can find other fields. It also defines a new entity type called “field_collection_item”.

  1. Access to all values of a field collection on an existing entity, using Field API // Get the items of a field collection, from a node
    $field_node_example_values = field_get_items('node', $node, 'field_node_example');

    // Use a field collection helper function to get all the field collection item ids
    $field_node_example_ids = field_collection_field_item_to_ids($field_node_example_values);
    // By default, Drupal don't load the field collection items
    $field_node_example_fc_items = field_collection_item_load_multiple($field_node_example_ids);

    // Loop over every field collection item and get the values for each field
    foreach ( field_node_example_fc_items as $item) {
        $fc_field_values = field_get_items('field_collection_item', $item, 'field_inside_fc');
    }

  2. Access to all values of a field collection on an existing entity, using an entity wrapper

    $node_wrapper = entity_metadata_wrapper('node', $nid);
    // Loop over the collections until we find which we have to modify
    foreach ($node_wrapper->field_example->value() as $field_example_value) {
       // Wrap it with Entity API
       $fc_wrapper = entity_metadata_wrapper('field_collection_item', $field_example_value);
       $fc_field_value = $fc_wrapper->field_example_child->value();
    }

Alter the child fields

Now let's see how we can modify or remove items from an existing field collection of an entity. To do things easily, I prefer to use here an entity wrapper.

  1. Alter the value of one of the fields of a concrete field collection item $fc_item = field_collection_item_load($item_id);

    // Check there's no problem with the item
    if (!$fc_item) {
      return;
    }

    // Wrap it before modifying
    $fc_item_wrapper = entity_metadata_wrapper('field_collection_item', $fc_item);
    $field_value = $fc_item_wrapper->field_example->value();
       ... change $field_value ...
    $fc_item_wrapper->field_example->set($field_value);
    $fc_item_wrapper->save();

  2. Delete a field collection item

    $fc_item_wrapper = entity_metadata_wrapper('field_collection_item', $item_id);
    $fc_item_wrapper->delete();

Create a field_collection_item

Field collection items use a “entity host”, which is the entity to whom the field collection is attached. To add a new field collection item to an existing field collection field, we have to create it and set its entity host, then we can assing values to every single field of the field collection item and save it.

// Create a field_collection_item entity
$fc_item = entity_create('field_collection_item', array('field_name' => 'field_example_is_a_field_collection'));

// Attach it to the node
$fc_item->setHostEntity('node', $node);

// Check there's no problem with the item
if (!$fc_item) {
  return;
}

// Wrap it with Entity API
$fc_item_wrapper = entity_metadata_wrapper('field_collection_item', $fc_item);

// Assign values to its fields
foreach($fc_item_values as $field_name => $field_value){
  $fc_item_wrapper->$field_name->set($field_value);
}

$fc_item_wrapper->save();


Alter a field_collection field into a form

When we are creating or editing a node (or any other entity) we use a form, and to modify it usually the hook_form_alter is used. Here are some interesting examples of how to alter the field collection widget.

  1. Change the options for a select box on all the field collection items and don't display the remove button for the first item. $field_name = 'field_collection_example';
    $fp_langcode = $form[$field_name]['#language'];
    $options = array( … );
    foreach (element_children($form[$field_name][$fp_langcode]) as $child) {
      // All the unlimited values fields have the “Add more” button as a child, but it's not a field collection item
      if (is_numeric($child)) {
        $form[$field_name][$fp_langcode][$child]['field_selectable'][$fp_langcode]['#options'] = $options;

        // Don't display the remove button for the first field collection item
        if ($child == 0) {
          unset($form[$field_name][$fp_langcode][$child]['remove_button']);
        }
      }
    }

  2. The field collection field stores some state information about it on $form_state. With this information we can know, realtime, how many items we have on the form and take decisions about it. For example, we have an unlimited values field collection field, but we want to limit the number of items depending on some certain circunstances. With this example, we can control a field to not display more than 3 field collection items on the form. $field_name = 'field_collection_example';
    $fp_langcode = $form[$field_name]['#language'];

    // Get state information about this field
    $fp_parents = $form[$field_name][$fp_langcode]['#field_parents'];
    $field_state = field_form_get_state($fp_parents, $field_name, $fp_langcode, $form_state);

    // The field state will change everytime we click on “Add more”, after the ajax call is triggered and the form is re-rendered to display the changes.

    // The user can see a maximum of 3 field collection items
    $items_limit = 3;
    if ($field_state['items_count'] < $items_limit) {
      // I want to change the “Add more” button title
      $form[$field_name][$fp_langcode]['add_more']['#value'] = t('Add another');
    }
    else {
      unset($form[$field_name][$fp_langcode]['add_more']);
    }

Clone field_collections from an existing node to a new node

Finally, here is an example of how to assign some default values to a field collection on a node creation form. As you can suspect, this is tricky if the value to clone is only one field collection item, but if we want to clone more than one, it's a problem because Drupal by default will only provide the form widget to enter the first item values (and wait for you to click on “Add more”).

The technique is to reproduce what Drupal does when the Field API attaches the fields to the node form. Using this way, Drupal will provide all the form structure we need to see all the values that come from the node we want to use as origin, but this has a problem and is that doing it this way we are assigning the same field collection items to two different nodes and if one of the nodes changes the values of one of the items, this will be reflected on the other node. The solution is to reset the item_id and revision_id values for the items on the new node, so we get the default values we want and they will be stored as new field collection items attached to the new node.

    $original_node = node_load(123);
    $node_type = 'my_custom_type';
   
    // Get the fields defined for this node type;
    $node_fields =  field_info_instances('node', $node_type);

    // Re-create the fields for the original node, like when editing it
    $tmpform = array();
    $tmpform['#node'] = $original_node;
    $tmpform['type']['#value'] = $node_type;
    $tmpform_state = array( 'build_info' => array('form_id' => $form['#form_id']) );
    field_attach_form('node', $original_node, $tmpform, $tmpform_state, entity_language('node', $original_node));

    // Here we have on $tmpform the form structure we need and with the default values.
    // We can choose what fields to clone, but in this example we will loop over all the node fields and clone all of them
    foreach($node_fields as $field_name => $field_settings) {
      // Copy the form structure
      $form[$field_name] = $tmpform[$field_name];
      // Copy state information about this field
      $form_state['field'][$field_name] = $tmpform_state['field'][$field_name];

      // When copying the field_collection structure, reset the id of the entities and
      // they will be created again with a new id.
      $langcode = field_language('node', $original_node, $field_name);
      if ($form_state['field'][$field_name][$langcode]['field']['type'] == 'field_collection') {
        $field_childs = &$form_state['field'][$field_name][$langcode]['entity'];
        foreach(element_children($field_childs) as $idx => $fc_entity) {
          $field_childs[$idx]->item_id = NULL;
          $field_childs[$idx]->revision_id = NULL;
        }
      }
   }

Apr 01 2009
Apr 01

Developers are all familiar with the default behavior of the drupal menu systems "local tasks" (aka tabs). These appear throughout most Drupal sites, primarily in the administration area, but also on other pages like the user profile.

Generally, developers are pretty good about creating logical local tasks, meaning only those menu items which logically live under another menu item (like view, edit, revisions, workflow, etc... live under the node/% menu item).

But sometimes, these tabs either don't really make sense as tabs or you simply want to have the flexibility of working with the items as "normal menu items", or those menu items which appear under admin/build/menu.

I recently wanted to move some of the tabs on the user profile page (user/UID) into the main menu so that I could include them as blocks.

For some reason, developers think the user profile page is a great place to put tabs for user related pages such as friendslist, tracker, bookmarks, notifications and so on. But these types of items are less a part of the user's account information than they are resources for specific users. Personally, I would not think to look at my account information on a site to find stuff like favorites or buddies. I'd expect those items to be presented somewhere much more obvious like a navigation block.

Initially, this may seem like a trivial task. My first thought was to simply use hook_menu_alter() and change the 'type' value of the menu item from MENU_LOCAL_TASK to MENU_NORMAL_ITEM. However, for reasons I don't understand well enough to explain in detail, this does not work.

In order to achieve the desired result, you must change the path of the menu item and incorporate the '%user_uid_optional' argument, replacing the default '%user' argument.

All very confusing, I know. Let's look at an example.

The notifications module (which provides notification on changes to subscribed to content) uses the user profile page rather heavily. I don't want its links there, I want them in the sidebar where users can always see them.

<?php
/**
* Implementation of hook_menu_alter().
*/
function MODULENAME_menu_alter(&amp;$callbacks) {
 
// NOTIFICATIONS MODULE
 
$callbacks['notifications/%user_uid_optional'] = $callbacks['user/%user/notifications'];
 
$callbacks['notifications/%user_uid_optional']['type'] = MENU_NORMAL_ITEM;
  unset(
$callbacks['user/%user/notifications']);
  <
SNIP>
}
?>

So I have moved the notifications menu into my own menu, changed the type, used %user_uid_optional instead of %user, and unset the original menu item.

This works fine except for the fact that you'll lose all of the other menu items under user/%user/notifications! You need to account for all menu items in the hierarchy to properly reproduce the tabs in the main menu system, so we add the following:

<?php
    $callbacks
['notifications/%user_uid_optional/thread'] = $callbacks['user/%user/notifications/thread'];
    unset(
$callbacks['user/%user/notifications/thread']); $callbacks['notifications/%user_uid_optional/nodetype'] = $callbacks['user/%user/notifications/nodetype'];
    unset(
$callbacks['user/%user/notifications/nodetype']); $callbacks['notifications/%user_uid_optional/author'] = $callbacks['user/%user/notifications/author'];
    unset(
$callbacks['user/%user/notifications/author']);
?>

And of course, we don't want this code executing at all if our module is not enabled, so you'd want to wrap the whole thing in:

<?php
 
if (module_exists('notifications')) {
 
  <
SNIP>

  }

?>

Keep in mind that not all modules implement menu items using hook_menu(). It's becoming more and more common for developers to rely on the views module to generate menu items, and this is a wise choice. Menus generated using views (ala bookmark module) can be modified to get the desired result without any custom code.

Feb 16 2008
Feb 16

The importance of project management tools is almost never fully appreciated. I am shocked at how common it is for a group of developers to go working without version control, ticket tracking, development documentation and so on. The very first thing I do when working with a new client is to make sure that they get these tools in place if they haven't already.

Those who are used to working without a complete set of project management tools never fail to appreciate the benefits of them once they are introduced. I consider it next to impossible for a team to work together without managing code and tasks in an efficient and highly organized way.[img_assist|nid=155|title=|desc=|link=none|align=right|width=250|height=156]

Hopefully you do not need to be sold on this idea and are using CVS or SVN to manage your project already. You likely have some sort of ticket system. It is a little less likely that you have both of these components integrated with each other.

When it comes to choosing a solution for project management software, a die-hard Drupal user has a dilemna. On one hand, Drupal seems as though it should be the perfect solution. It's fully customizable, has lots of nifty project management related modules and, most importantly, it's Drupal! Why would you not use it? "Eating your own dogfood" is the way to go, right? Meh...

Drupal is generally considered a content management system. Personally, I like to refer to it as a website management system. It is great at managing website related stuff like users, posts, permissions, categorization, and so on. Using contrib modules, you can customize and enhance this core functionality to almost no end. But at the end of the day, Drupal is designed to handle web content and the users that are accessing it. That's what a content management system is (and if content is king, that would make Drupal... well... God).

Managing a project, on the other hand, is a much different business from managing a website. Yes, you have many shared properties such as content and users. But the essence of project management involves things that have nothing to do with website management such as a revision controlled code base edited by multiple users, a need for efficient ticket management, and ideally full integration of everything. Essentials also include stuff like a nice repository browser, user management interface for repository access, fancy reporting for tickets, organization of tasks by milestone, date, person, severity, etc...

It's a very tall order. Yes, you can do all this in Drupal, but not very well. You can piece together something that sorta kinda resembles a project management solution, but in the end, you need to invest a relatively large amount of time to create something that is less than ideal and will require ongoing tweaking and modification. Unless your business is creating an effective project management solution in Drupal (something I dream of!), you should not be using Drupal for project management.

I'm a one man shop, and I do not have time to spare. I cannot justify spending any time at all kludging together a project management solution for a client when there are already far superior solutions available at low cost. I would much rather pay someone a few bucks a month and be done with it. Let them deal with SVN administration and enhancements; let me focus on my primary task which is building cool sites with Drupal.

While there are numerous project management related service providers out there (Fogbugz, Basecamp , Beanstalk to name a few), I want to talk about my personal favorite, Unfuddle. Unfuddle has taken obvious inspiration from the folks over at 37signals, innovators of the simple, clean, effective, it-just-works web application. Unfuddle is an instant project management solution that takes minutes to set up and costs a few dollars a month. The time you'll save in not having to set up SVN and manage SVN users alone makes it worth every penny.

[img_assist|nid=156|title=|desc=|link=none|align=left|width=250|height=221]What you get with a solution such as unfuddle is a ready-to-use repository with integrated documentation, ticketing and reporting. It takes seconds to set up a new user account with permission levels fit for everyone from a developer (gimme root!) or a suit (look but don't touch).

From a single interface, you can browse code, tickets and documentation. Every component integrates with the others. You can even resolve a ticket with an SVN commit message, saving you the trouble of having to go and edit the ticket after your commit! Users can individually subscribe to whatever level of email notificaton they would like to recieve and how often. The developer can shut off all notifications while the manager can get a nice daily summary each morning of milestone completion progress, new tickets, added documentation and so on. The project manager can glance over one of the ticket reports and group tickets into milestones for reasonable short vs long term goals.

SVN comments link back to the tickets they are related to. Tickets contain links to the changesets that resolved them. Viewing these changesets, you can see a beautiful code diff and quickly see what fixed the problem. Senior team members can quickly and easily review code changes submitted by junior staff.

With tools like this available these days, it's just not worth it spending any effort whatever on a lesser solution.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web