How to Update Several Feature Exports All At Once

When working with Features, I've often found myself needing to change several exports in the same way that they are exported in several different modules.  For example: changing the label of the 'body' field for all 13 content types, which are spread across 12 different fields. There are several ways I could do this:

  1. Go through the content type field's UI and edit each field, changing the UI, then either run drush features-update-all (drush fua) or recreate via the features UI.
  2. Edit each feature export, changing the field label, then running drush features-revert-all (drush fra).
  3. Figure out a regular expression that will match what I want to change and run that. I'm not sure how field body would work with this method, but in the case of removing a dependency from several files, I've use  "find . | xargs perl -pi -e 's/dependencies[] = "module_name"n//ig' ;"

For something as simple as field body, all three methods are fairly reasonable to use (although they are also quite prone to human error - i.e. you might forget to update one content type), however once the change gets significantly more complex, it could take quite a while to do any of these methods. I prefer a more pragmatic method: using an alter hook.

Alter hook can be used for nearly every component (excluding hook_node_info), and when a feature is updated, changes from that alter hook are reflected in the export . This is not ideal in some situations, but perfect in this one. All I have to do is implement this alter hook and change the export in the way I want to, and run drush features update to the specific features that have the changed exports.

Imagine I want to add a comment form and node comments to every node Panelizer default. (Panelizer allows customizable layouts for different node types that can be overridden on a per node basis.)

These are the steps I would take:

Step 1) Take one node type and adjust it to my needs, adding the two panes (comment form and comments) that I want.
Step 2) Update the Feature for that node type.
Step 3) Look at the diff of the module (since my module is in version control, I just use git diff on it). I have some new items added:

  1. <?php
  2. $pane = new stdClass();
  3. $pane->pid = 'new-11';
  4. $pane->panel = 'contentmain';
  5. $pane->type = 'node_comments';
  6. $pane->subtype = 'node_comments';
  7. $pane->shown = TRUE;
  8. $pane->access = array();
  9. $pane->configuration = array(
  10. 'mode' => '1',
  11. 'comments_per_page' => '50',
  12. 'context' => 'panelizer',
  13. 'override_title' => 0,
  14. 'override_title_text' => '',
  15. );
  16. $pane->cache = array();
  17. $pane->style = array();
  18. $pane->css = array();
  19. $pane->extras = array();
  20. $pane->position = 3;
  21. $pane->locks = array();
  22. $display->content['new-11'] = $pane;
  23. $display->panels['contentmain'][3] = 'new-11';
  24. $pane = new stdClass();
  25. $pane->pid = 'new-12';
  26. $pane->panel = 'contentmain';
  27. $pane->type = 'node_comment_form';
  28. $pane->subtype = 'node_comment_form';
  29. $pane->shown = TRUE;
  30. $pane->access = array();
  31. $pane->configuration = array(
  32. 'anon_links' => 0,
  33. 'context' => 'panelizer',
  34. 'override_title' => 0,
  35. 'override_title_text' => '',
  36. );
  37. $pane->cache = array();
  38. $pane->style = array();
  39. $pane->css = array();
  40. $pane->extras = array();
  41. $pane->position = 4;
  42. $pane->locks = array();
  43. $display->content['new-12'] = $pane;
  44. $display->panels['contentmain'][4] = 'new-12';
  45. ?>

Step 4) Implement the alter hook, see comments for what I did.

 

  1. <?php
  2. /**
  3. * Implements hook_panelizer_defaults_alter().
  4. */
  5. function [some custom module]_panelizer_defaults_alter(&$items) {
  6. foreach ($items as $key => $item) {
  7. // In my case I just want content types that are group content types.
  8. if ($item->panelizer_type == 'node' && og_is_group_content_type('node', $item->panelizer_key)) {
  9. // Panels is a bit odd and id's each new pane as new-X, so need to calculate a new pane id for my new panes.
  10. $id = 0;
  11. // I already know one of my panes is updates, so see if I need to skip this one
  12. $has_comments = FALSE;
  13. foreach ($item->display->content as $new_key => $pane) {
  14. list($new, $new_id) = explode('-', $new_key);
  15. $id = $new_id > $id ? $new_id : $id;
  16. if ($pane->type == 'node_comments') {
  17. // I could do break; here and skip out of this iteration instead, but I may be adding more panes later so might as well have it like this.
  18. $has_comments = TRUE;
  19. }
  20. }
  21. if (!
  22.  
  23. $has_comments) {
  24. $id++;
  25. $pane = new stdClass();
  26. $pane->pid = 'new-' . $id; // changed export from 'new-num'
  27. $pane->panel = 'contentmain';
  28. $pane->type = 'node_comments';
  29. $pane->subtype = 'node_comments';
  30. $pane->shown = TRUE;
  31. $pane->access = array();
  32. $pane->configuration = array(
  33. 'mode' => '1',
  34. 'comments_per_page' => '50',
  35. 'context' => 'panelizer',
  36. 'override_title' => 0,
  37. 'override_title_text' => '',
  38. );
  39. $pane->cache = array();
  40. $pane->style = array();
  41. $pane->css = array();
  42. $pane->extras = array();
  43. $pane->position = 3;
  44. $pane->locks = array();
  45. $item->display->content['new-' . $id] = $pane; // changed export from 'new-num and $display to $item->display
  46. $item->display->panels['contentmain'][] = 'new-' . $id; // changed export from 'new-num and $display to $item->display and changed [num] to [] so adds to end of array instead of overriding [num]
  47. // Need a new id since adding a second pane
  48. $id++;
  49. // Changes the same places as previous export.
  50. $pane = new stdClass();
  51. $pane->pid = 'new-' . $id;
  52. $pane->panel = 'contentmain';
  53. $pane->type = 'node_comment_form';
  54. $pane->subtype = 'node_comment_form';
  55. $pane->shown = TRUE;
  56. $pane->access = array();
  57. $pane->configuration = array(
  58. 'anon_links' => 0,
  59. 'context' => 'panelizer',
  60. 'override_title' => 0,
  61. 'override_title_text' => '',
  62. );
  63. $pane->cache = array();
  64. $pane->style = array();
  65. $pane->css = array();
  66. $pane->extras = array();
  67. $pane->position = 4;
  68. $pane->locks = array();
  69. $item->display->content['new-' . $id] = $pane;
  70. $item->display->panels['contentmain'][] = 'new-' . $id;
  71. }
  72. }
  73. }
  74. }
  75. ?>

Step 5) Apply drush features update to the modules that I want to update.
Step 6) Delete the alter hook, or leave it alone as it is designed to do nothing when there's nothing to add. (However, unneeded code is unnecessary).

Note:

If you are not sure what a field instance is, check out the 2.x branch of Features where field definitions have been split into field bases and field instance, so there won't be conflicting base field information, (e.g. field_body in feature_a could have different information exported field_body than feature_b, so now feature_y contains the base for field_b, and feature_a and feature_b just have the specific field instance information). ** Except content types, e.g. no hook_node_info_alter

Mike Potter has submitted a session to CapitalCamp about deploying sites using Features.  Let's hope it gets accepted, so we can all register for CapitalCamp and learn more about Features!

 

 

Hunter Fox