Problem/Motivation

Currently the relationship between a membership_type and a Drupal role is tracked by role ID in the redhen_membership_type.role_id field. When used in Features, a membership type is mapped to a given role by role ID, which may differ from the role on the site on which the membership type was generated.

The problem has been fixed by the introduction of role machine names for Drupal 8, but meantime there is no consistent model to follow here in Drupal 7. While the problem is widely noted, each module maintainer has taken a different approach. See #2 for various examples.

Proposed resolution

At a minimum, document the issue and point to potential solutions, including:

Remaining tasks

User interface changes

none

API changes

CommentFileSizeAuthor
#6 rid-export-readme-1679808-6.patch2.35 KBnedjo

Comments

levelos’s picture

Thanks for opening the discussion @nedjo. Can you point to other projects that are following this approach? I also noticed that http://drupal.org/project/role_export might do a good job of solving this problem. Not that it helps us at the moment, but this roles have already been given a machine name in D8.

nedjo’s picture

There is no consistent model to follow here in Drupal 7. While the problem is widely noted, each module maintainer has taken a different approach.

  • Features provides "faux" exportables for Drupal core configuration that isn't natively exportable. Roles are exported by name. The roles for user permissions are exported as role names and converted to rid values on save.
  • Like Features, Workflow exports roles as names and dynamically converts them to rids on save as of #558378-140: Make workflows exportable with Features (D6). Quickbar provides an admin setting that can trigger export by name instead of rid, #1301056: Allow the usage of faux machine names.
  • The Flag maintainer is considering a switch to role names, #1204918-4: Export roles as names rather than IDs.
  • Views exports role access restrictions by rid; see #692616: Add support for exporting role related data correctly in views. As does Rules. The rules maintainer says (#893140-2: "Get RoleID of Role with name ..." to workaround missing role machine names): "Indeed, but that's no problem of Rules, but of Drupal. Roles have no machine names, but are just identified by ids. Yes, that's bad - but Rules cannot change that." Ditto for Content Access, see #871770-27: Content Access support for Features exportables and later comments.
  • Personally in feature authoring I've left components with rids out of features exports and instead handled them manually. Example (content access):
    
      // Set content_access view permissions for the member_page content type.
      // While these roles will be created by Features, they will not be available
      // yet when hook_install() is run, so we need to test for and create them
      // here.
      $roles = array();
      foreach (array('member', 'membership administrator') as $name) {
        // If there isn't an existing role by this name, create one.
        if (!$role = user_role_load_by_name($name)) {
          $role = new stdClass();
          $role->name = $name;
          user_role_save($role);
        }
        $roles[] = $role->rid;
      }
    
      // Don't create an administrator role, but use one if it exists.
      if ($admin_role = user_role_load_by_name('administrator')) {
        $roles[] = $admin_role->rid;
      }
    
      // Set only the 'view' and 'view_own' access. Other access will be managed by
      // regular user permissions.
      $settings = array(
        'view' => $roles,
        'view_own' => $roles,
      );
    
      variable_set('content_access_member_page', $settings);
    
    
  • Role export generates a machine name that's exported with a feature and used to generate a role ID.

Is Role export the answer? It's tempting--a fix that can fix up any other module's problems. But at a cost. My view on Role export is that It's a clever hack and useful e.g. when Features is being used as a tool for dev > staging > live deployment on a single site. But it's not a sufficient solution, particularly for use with features that are being used in distributions or apps. Problems:

  • It's still perfectly possible to export broken role ids--in fact, that's what will usually occur. To avoid doing so, the feature author has to somehow know that, prior to building the feature, s/he has to download and install Role export and edit all roles--and manually add Role export as a dependency of all features created.
  • Apparently, even after enabling and configuring Role export, a site admin needs to remember to go separately to each place that role-related information is stored and edit and resave. Until that's done, the old (now nonexistent) role IDs will be in place and the site as well as its exports will be broken.
  • It introduces a dependency for all features involving role data. Without Role export, the feature will be broken.
  • Roles are already probably the single most problematic component in apps, because of the dependencies they create. See #1698446: Remove roles from debut feature; instead, have each feature programmatically create roles. One solution is to use non-features methods for role handling--meaning that Role export is not an option.

If broken role export were a minor concern, perhaps it could be safely ignored--but that's far from being the case. Typically, roles are used to handle access control. Any breakage can leave a site wide open to security breaches.

So my feeling is that D7 modules should solve role export issues--in a way that prepares for D8 contrib, where it will be possible to build on core role machine names.

levelos’s picture

@nedjo, thanks so much for presenting this information and explaining the options!

I do think we'll more or less stick with our current model for the time being and link memberships directly to role id's rather than change to role names. I'd be open to role_export, or at least the approach it takes, in that it adds a machine name column to the role schema and then relates that to role machine names in other places. But your're right that any 3rd party module would need to be aware of that approach / dependency for it to be successful within an ecosystem. But it will work on individual projects where all components are aware of the dependency.

Without that, I worry about what would happen if a role name changes and we lose our connection between the related role. I realize we have hook_user_role_update(), but I'm not sure that will be sufficient in all cases.

Certainly open to more thoughts and further discussion.

nedjo’s picture

Title: Membership type role_id breaks on export to features » Define approach to handling role id, which can break on export to features
Category: bug » task

On second thought, yeah, there's not a lot of sense in switching now to key on role name and then having to switch back for D8.

Maybe this is a documentation task. A possible approach would be, if you're trying to make a portable feature or app, manually edit the exported code to:

  • Remove the role_id value.
  • Implement hook_default_redhen_membership_type_alter():
    /**
     * Implements hook_default_redhen_membership_type_alter().
     *
     * If there is a 'member' role, tie it to the 'standard' membership type.
     */
    function example_default_redhen_membership_type_alter_items(&$items) {
      if (isset($items['standard']) && $role = user_role_load_by_name('member')) {
        $items['standard']->role_id = $role->rid;
      }
    }
    
levelos’s picture

Status: Active » Fixed

That's a great solution. I wasn't familiar with that hook, but looks really handy. If you want to add something along those lines to the README, I'll be happy to credit the commit ;)

nedjo’s picture

StatusFileSize
new2.35 KB

For API documentation on the hook, which is available for all entity types made exportable with Entity API, see http://drupal.org/node/999942.

Draft README patch attached.

nedjo’s picture

Status: Fixed » Needs review
levelos’s picture

Status: Needs review » Fixed

@nedjo, you are truly me hero. Seriously, that's very awesome of you to take the time to submit a documentation patch!

nedjo’s picture

Adapted this issue to a features handbook page, exportables and user role IDs in features.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

arancedisicilia’s picture

I had this problem recently, and exporting was a headache everytime.
However, since I need to export very often in my current project, so that #4 doesn't seem to be handy (I should edit manually the code for each export, if I'm not mistaken), I came up with some "maybe dirty" but very simple workaround, that is:

- define a custom access rule in my module. It looks like this:

function MYMODULE_access() {
  global $user;

  return (user_access('administer') OR array_intersect(array('role1', 'role2'), $user->roles));
}

- in my view, define a PHP based access rule, that would be as much as this line:

return MYMODULE_access();

and that should be all. It works in my case, and I don't see any drawbacks (please tell me more about it if you think I'm wrong!)

Hope this may help someone.

arancedisicilia’s picture

Issue summary: View changes

Updated issue summary.