Advanced Open Atrium customization
Make your Open Atrium behave as you want, the code-driven way
Open Atrium comes, out of the box, with a rather complete set of features which allow to bootstrap a functional intranet solution in a few minutes. Still, to be actually usable in a real life context, an Open Atrium installation must be adapted to each organization's needs and structure.
In this article we will learn how to customize an Open Atrium installation in a code-driven fashion, without hacking its core and making sure that an upgrade to a newer version will not swipe away our valuable changes; we will cover:
- How to add custom user profile fields;
- How to override Atrium's core configuration;
- How to create custom group types.
For a smooth reading you should be familiar with Features and Exportables concepts. If you are not, just check out our Code Drive Development blog post series.
1. How to add custom user profile fields
One of the greatest changes introduced with the Features module 1.0 stable release is that, when exporting, you can actually decouple CCK fields definition from the node type they belong to, or in other words: CCK fields and node type can be exported to different features.
As a direct consequence of this approach we can store our custom user profile fields in a feature (call it custom_profile
) that extends the Atrium's core atrium_profile
. Let's have a look to its .info file:
core = "6.x"<br>dependencies[] = "features"<br>dependencies[] = "text"<br>description = "Custom profile feature."<br>features[content][] = "profile-field_profile_city"<br>features[content][] = "profile-field_profile_country"<br>name = "Custom Profile"<br>package = "Features"<br>project = "custom_profile"<br>version = "6.x-1.0"
This makes sure that, when Open Atrium will be upgraded, we will still find our custom fields (namely "City" and "Country") since they belong to the custom_profile
feature.
2. How to override Atrium's core configuration
The Features module exposes several hooks that alter the default settings of exported components, like variables, permissions, contexts, spaces, etc... A correct use of these hooks makes it possible to safely override default component settings being sure that they will not get lost after an Atrium core upgrade.
Let's say we want to change the system's default user picture: Open Atrium stores it into a variable, so all we need to do is to implement hook_strongarm_alter()
and override its value:
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Implementation of hook_strongarm_alter()<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">custom_profile_strongarm_alter</span><span style="color: #007700">(&</span><span style="color: #0000BB">$items</span><span style="color: #007700">) {<br> if (isset(</span><span style="color: #0000BB">$items</span><span style="color: #007700">[</span><span style="color: #DD0000">'user_picture_default'</span><span style="color: #007700">])) {<br> </span><span style="color: #0000BB">$items</span><span style="color: #007700">[</span><span style="color: #DD0000">'user_picture_default'</span><span style="color: #007700">]-></span><span style="color: #0000BB">value </span><span style="color: #007700">= </span><span style="color: #DD0000">'sites/all/themes/custom/user.png'</span><span style="color: #007700">;<br> }<br>}<br></span><span style="color: #0000BB">?></span></span>
After clearing the cache we will see our new user picture appearing everywhere on the site. Same goes for the default blocks we have on the dashboard of a newly created group: we can choose, for example, to replace the "Welcome" video with the "Latest discussion" block. Here is how to do that:
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Implementation of hook_spaces_presets_alter()<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">custom_group_spaces_presets_alter</span><span style="color: #007700">(&</span><span style="color: #0000BB">$items</span><span style="color: #007700">) {<br><br> </span><span style="color: #FF8000">// Store a reference to our target block section.<br> </span><span style="color: #0000BB">$blocks </span><span style="color: #007700">= &</span><span style="color: #0000BB">$items</span><span style="color: #007700">[</span><span style="color: #DD0000">'atrium_groups_private'</span><span style="color: #007700">]-></span><span style="color: #0000BB">value</span><span style="color: #007700">[</span><span style="color: #DD0000">'context'</span><span style="color: #007700">][</span><span style="color: #DD0000">'spaces_dashboard-custom-1:reaction:block'</span><span style="color: #007700">][</span><span style="color: #DD0000">'blocks'</span><span style="color: #007700">];<br> <br> </span><span style="color: #FF8000">// Remove "Welcome" block.<br> </span><span style="color: #007700">unset(</span><span style="color: #0000BB">$blocks</span><span style="color: #007700">[</span><span style="color: #DD0000">'atrium-welcome_member'</span><span style="color: #007700">]);<br><br> </span><span style="color: #FF8000">// Add "Latest discussions" block.<br> </span><span style="color: #0000BB">$blocks</span><span style="color: #007700">[</span><span style="color: #DD0000">'views-blog_listing-block_1'</span><span style="color: #007700">] = array(<br> </span><span style="color: #DD0000">'module' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'views'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'delta' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'blog_listing-block_2'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'region' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'content'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'weight' </span><span style="color: #007700">=> </span><span style="color: #0000BB">1</span><span style="color: #007700">,<br> );<br>}<br></span><span style="color: #0000BB">?></span></span>
After clearing the cache all new groups (and those that didn't have their dashboard customized by an user) will inherit the new settings. If we now have a look at our features status we'll see that several Atrium core features have been marked as "Overridden":
$ drush features<br><br> Name Feature Status State <br> Atrium atrium Enabled Overridden <br> Atrium Activity atrium_activity Enabled <br> Atrium Blog atrium_blog Enabled <br> Atrium Notebook atrium_book Enabled <br> Atrium Calendar atrium_calendar Enabled <br> Atrium Case Tracker atrium_casetracker Enabled <br> Atrium Groups atrium_groups Enabled Overridden <br> Atrium Members atrium_members Enabled Overridden <br> Atrium News atrium_news Enabled <br> Atrium Pages atrium_pages Enabled <br> Atrium Profile atrium_profile Enabled
This is a totally safe situation since your configuration has been altered via code, thus it is 100% upgrade-safe. To learn more about altering default hook have a look at Feature's feature.api.php file.
3. How to create custom group types
In the example above we learnt how to replace a block in the default "Private group" space preset. What if we need a completely new group type, with custom features and specific configurations, something that we cannot really map into one of the two group types ("Public group" and "Private group") provided by Open Atrium? Easy, we just need to provide a custom space preset that describes the kind of group we need.
For example: we want to have a "Portal group" which will be used as the public website of our organization, it will have news and static pages and it must look slightly different than the intranet. The Spaces module is powerful enough to allow you to turn each group into a completely different website, customizing settings, menus, theme, etc...
To do that, we start by cloning the "Controlled group" space preset into our new "Portal group" preset. We then export it to a new feature called atrium_portal
, having an .info
file similar to:
core = "6.x"<br>dependencies[] = "atrium_news"<br>dependencies[] = "atrium_pages"<br>dependencies[] = "context"<br>dependencies[] = "menu"<br>dependencies[] = "spaces"<br>features[context][] = "layout_portal"<br>features[ctools][] = "context:context:3"<br>features[ctools][] = "spaces:spaces:3"<br>features[menu_custom][] = "menu-portal"<br>features[menu_links][] = "menu-portal:calendar"<br>features[menu_links][] = "menu-portal:dashboard"<br>features[spaces_presets][] = "atrium_portal"<br>name = "atrium_portal"<br>package = "Features"
As you might have noticed, the file contains a dependence on two features that are not part of the Open Atrium core: atrium_news
and atrium_pages
, which respectively add news publishing and usual static pages to our new portal.
So let's have now a closer look at the heart of this feature, the atrium_portal
Spaces preset;
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #FF8000">/**<br> * Implementation of hook_spaces_presets().<br> */<br></span><span style="color: #007700">function </span><span style="color: #0000BB">atrium_portal_spaces_presets</span><span style="color: #007700">() {<br> </span><span style="color: #0000BB">$export </span><span style="color: #007700">= array();<br> </span><span style="color: #0000BB">$spaces_presets </span><span style="color: #007700">= new </span><span style="color: #0000BB">stdClass</span><span style="color: #007700">;<br> </span><span style="color: #0000BB">$spaces_presets</span><span style="color: #007700">-></span><span style="color: #0000BB">disabled </span><span style="color: #007700">= </span><span style="color: #0000BB">FALSE</span><span style="color: #007700">; <br> </span><span style="color: #0000BB">$spaces_presets</span><span style="color: #007700">-></span><span style="color: #0000BB">api_version </span><span style="color: #007700">= </span><span style="color: #0000BB">3</span><span style="color: #007700">;<br> </span><span style="color: #0000BB">$spaces_presets</span><span style="color: #007700">-></span><span style="color: #0000BB">name </span><span style="color: #007700">= </span><span style="color: #DD0000">'atrium_portal'</span><span style="color: #007700">;<br> </span><span style="color: #0000BB">$spaces_presets</span><span style="color: #007700">-></span><span style="color: #0000BB">title </span><span style="color: #007700">= </span><span style="color: #DD0000">'Portal group'</span><span style="color: #007700">;<br> </span><span style="color: #0000BB">$spaces_presets</span><span style="color: #007700">-></span><span style="color: #0000BB">description </span><span style="color: #007700">= </span><span style="color: #DD0000">'Turn a group into a public portal. All users may view public content from this group. Users must request to join this group.'</span><span style="color: #007700">;<br> </span><span style="color: #0000BB">$spaces_presets</span><span style="color: #007700">-></span><span style="color: #0000BB">space_type </span><span style="color: #007700">= </span><span style="color: #DD0000">'og'</span><span style="color: #007700">;<br> </span><span style="color: #0000BB">$spaces_presets</span><span style="color: #007700">-></span><span style="color: #0000BB">value </span><span style="color: #007700">= array(<br> </span><span style="color: #DD0000">'variable' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'spaces_og_selective' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'1'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'spaces_og_register' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'spaces_og_directory' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'spaces_og_private' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'spaces_features' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'atrium_blog' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'0'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_book' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'0'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_calendar' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'1'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_casetracker' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'0'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_members' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'1'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_news' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'1'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_pages' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'1'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_shoutbox' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'0'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'spaces_dashboard' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'1'</span><span style="color: #007700">,<br> ),<br> </span><span style="color: #DD0000">'spaces_setting_home' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'dashboard'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'site_frontpage' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'dashboard'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'menu_primary_links_source' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'menu-portal'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'menu_default_node_menu' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'menu-portal'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'designkit_color' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'background' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'#3399aa'</span><span style="color: #007700">,<br> ),<br> </span><span style="color: #DD0000">'designkit_image' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'logo' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> ),<br> ),<br> </span><span style="color: #DD0000">'context' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'spaces_dashboard-custom-1:reaction:block' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'blocks' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'views-atrium_news-block_3' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'module' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'views'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'delta' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'atrium_news-block_3'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'region' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'content'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'weight' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> ),<br> </span><span style="color: #DD0000">'views-calendar_listing-block_1' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'module' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'views'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'delta' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'calendar_listing-block_1'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'region' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'right'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'weight' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> ),<br> ),<br> ),<br> </span><span style="color: #DD0000">'spaces_dashboard-custom-2:reaction:block' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'blocks' </span><span style="color: #007700">=> array(),<br> ),<br> </span><span style="color: #DD0000">'spaces_dashboard-custom-3:reaction:block' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'blocks' </span><span style="color: #007700">=> array(),<br> ),<br> </span><span style="color: #DD0000">'spaces_dashboard-custom-4:reaction:block' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'blocks' </span><span style="color: #007700">=> array(),<br> ),<br> </span><span style="color: #DD0000">'spaces_dashboard-custom-5:reaction:block' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'blocks' </span><span style="color: #007700">=> array(),<br> ),<br> ),<br> );<br><br> </span><span style="color: #FF8000">// Translatables<br> // Included for use with string extractors like potx.<br> </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Portal group'</span><span style="color: #007700">);<br> </span><span style="color: #0000BB">t</span><span style="color: #007700">(</span><span style="color: #DD0000">'Turn a group into a public portal. All users may view public content from this group. Users must request to join this group.'</span><span style="color: #007700">);<br><br> </span><span style="color: #0000BB">$export</span><span style="color: #007700">[</span><span style="color: #DD0000">'atrium_portal'</span><span style="color: #007700">] = </span><span style="color: #0000BB">$spaces_presets</span><span style="color: #007700">;<br> return </span><span style="color: #0000BB">$export</span><span style="color: #007700">;<br>}<br></span><span style="color: #0000BB">?></span></span>
The first important customization is the set of features we are enabling for our new Portal preset. Since the set of features available to each group is stored into the spaces_features
variable the Spaces module can override its value when we are browsing a Portal group. Here what we have enabled:
<span style="color: #000000"><span style="color: #0000BB"><?php<br></span><span style="color: #007700">...<br></span><span style="color: #DD0000">'spaces_features' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'atrium_blog' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'0'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_book' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'0'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_calendar' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'1'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_casetracker' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'0'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_members' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'0'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_news' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'1'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_pages' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'1'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'atrium_shoutbox' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'0'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'spaces_dashboard' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'1'</span><span style="color: #007700">,<br>),<br>...<br></span><span style="color: #0000BB">?></span></span>
This is generally true with all those settings that are stored into variables, like default theme, primary links menu, default user picture, etc...
With a space preset we can also override the default block settings of the Open Atrium dashbaord. For example, for our Portal we want to show a news block on the content region and a calendar with the latest events on the right sidebar region. Here is how to do that:
<span style="color: #000000"><span style="color: #0000BB"><?php<br><br></span><span style="color: #007700">...<br></span><span style="color: #DD0000">'context' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'spaces_dashboard-custom-1:reaction:block' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'blocks' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'views-atrium_news-block_3' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'module' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'views'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'delta' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'atrium_news-block_3'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'region' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'content'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'weight' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> ),<br> </span><span style="color: #DD0000">'views-calendar_listing-block_1' </span><span style="color: #007700">=> array(<br> </span><span style="color: #DD0000">'module' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'views'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'delta' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'calendar_listing-block_1'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'region' </span><span style="color: #007700">=> </span><span style="color: #DD0000">'right'</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'weight' </span><span style="color: #007700">=> </span><span style="color: #0000BB">0</span><span style="color: #007700">,<br> ),<br> ),<br> ),<br>...<br></span><span style="color: #0000BB">?></span></span>
With this simple space preset we can actually turn an Open Atrium group into a real public portal for our organization.
A different way to look at Open Atrium
A stable code-driven customization joint to the powerful Spaces module could push Open Atrium much far behind its original private-intranet. This great distribution, in fact, can be seen as a versatile platform that can answer to the most diverse needs of an organization providing, for example, groups that can be turn into full featured public portals or used to organize events, etc... With the right customization each group could also be heavily customizable, letting the administrator to completely change their look and feel to serve the most different purposes.
If you are interested in learning more about the topic covered in this blog post you can check out our Drupal Con Chicago 2011 session proposal here.
Tags: Code Driven Development, Drupal Planet, DrupalCon, Open AtriumImage: