subuser and organizational administration

Once subusers were creating, with content profiles creating and editing alongside, I also needed the ability to delete those users that I'd created. 

Administer subusers wasn't enough, since I didn't want my parent users visiting the user administration page - just cause it's one more thing to train them on.

Instead, I added a views field handler to show a delete link, plus I had to customize the access callback to ensure that users don't delete themselves.

All in all, not so bad.  I think I'll contribute that back into the module if I can...

virtual_roles module

Just implemented virtual roles module for the first time.  After some initial headache, it works well.

virtual_roles (henceforth: VR) uses contexts (oh no not again, find a new word please) to decide when to trigger some role-related action.  VR ties contexts to role processing, lets you define custom logic for when that role is to be applied, etc.  All very nice.

The key thing about virtual_roles is that it has cache baked in, and it's non-optional.  Contexts are made up of two possible $ops, cache and process...you can turn off caching in the UI, but you still have to provide a cache case in the context, or things won't work as expected.  Be safe: define a cache id in your virtual roles context.

Creating a views handler for Drupal 6 - Part I

A note about the purpose of this post:

I tend to work from the UI backward when I can.  It's helpful to mock up the process little by little, and working front-to-back lets you click and experience the work you're doing early on.  That can have good results with usability, and also can help to clarify thinking when you don't know quite what you're building yet. 

In the case of both views and custom CCK fields, I've had difficulty figuring out how to achieve that basic prototyping, as their methods of implementing the UI layer are very specific and abstracted.  Even after reading several blog posts and the views API documentation, the process of creating a filter for views was still fairly opaque to me.  Sometimes the minimum set of steps to achieve a simple result isn't top-of-mind when people blog about development tasks, so I'm making my own notes.

In Drupal, the all-important Views module can be extended with 'handlers'.  Handlers are used to implement filters, fields, and arguments.  They are php classes, typically contained in a .inc file somewhere in your module directory.  You describe your classes and how they'll be used by implementing one of several hooks.  The most lightweight views filter implementation requires 4 things: 3 hook implementations and a class. 

Hooks you must implement:

  1. hook_views_api - this declares that you're going to do something 'viewish' with your custom module.  This function returns an array specifying the lowest views api version you'll support, and where your module is located.  See http://views.doc.logrus.com/group__views__hooks.html#gc67ffd4a2f61f9814ee37b541c472c47hook_views_data
  2. hook_views_handlers - here you announce to Drupal that you're implementing an handler.  This hook specifies you'll specify the parent class and the filename your class lives in.  See http://views.doc.logrus.com/group__views__hooks.html#gbf506f44bd8d8a86876f27396f5341ed
  3. hook_views_data_alter OR hook_views_data - these hooks tell Views where your handler fits into the data model that views uses to generate queries.  For simple filters that rely on existing tables, use hook_views_data_alter to add your handler to, say, the information already related to the node module.  If you've got a whole module with its own tables, you'll want to describe them to views using hook_views_data.  The array that describes your data to views does so field by field, specifying what kind of field, argument and filter handlers your fields will use.  You'll also be able to specify relationships and joins to other data in hook_views_data.  See http://views.doc.logrus.com/group__views__hooks.html#g227057901681e4a33e33c199c7a8c989
Once you've told Drupal your module wants to work with Views (hook_views_api), that it implements handlers (hook_views_handlers) and how the handlers relate to the database schema that Views knows about, you're all set.

Well not really - with the three hooks implemented, and your cache cleared to pick up the new hook implementations, you'll be able to see your handler in the appropriate spot in the views interface.  In my case that was in the taxonomy group under 'Filters', as I was implementing a slightly customized term id filter.

With the hooks, you'll be able to select the filter and add it to your view - but the filter class doesn't exist yet.  Views will tell you that your handler is broken, but you will be able to see the handler in the Views UI.

Next you have to implement the class - I'll make more notes about that later...

subuser access

I've been implementing the subusers module to allow delegation of user creation and editing for non-admin users. 

That works ok, but I had to do a couple of things:

I've got organizations that have staff members, and a general manager who should be able to add/edit those users.

Modules that we're using to enable the org->member relationship.

CCK - defines the content types for org and contact

Content profile - associates the contact cck type with users. Had to be patched with this: http://drupal.org/node/425144#comment-3410500 to add a permission to allow content profiles to be administered by roles other than admin.

subuser - allow users to create users under their account, and edit/delete them.

Subuser required two patches to work with content profile. They're both found on this issue: http://drupal.org/node/571660#comment-3984138

Permissions required to get all this working:

  • content profile module::administer content profiles
  • subuser::administer subusers
  • subuser::create subuser

views_attach - used to put a list of content profiles on the organization node based on the 'belongs to' nodereference field on contacts.


For users who don't have the 'administer users' permission, you'd think you can visit the profile of any of their subusers, but that's not always the case. 

The core user module throws an access denied error when you try to visit the profile of a non-logged in user and you don't have administer users permissions. 

There are some cases where a non-admin user like the ones on my site might want to edit their users regardless of whether they've logged in or not.  For instance, if I type in an email address wrong, I'll want to fix that, but I won't be able to visit their user profile.  I can visit their edit screen, but not the user profile.  The subuser module overrides the user/%/edit access_callback, but not the one for the view page.  May have to adjust that in the subuser module and file a patch. 

nice menus + context

Active menu trails set by the context module aren't respected by the nice menus module. 

I found this patch: http://drupal.org/files/issues/835090-4-context_reaction_menu_trail.patch which told me the right thing to do: write a ctools plugin for context that would expose a reaction for nice menus.

However, I didn't do the right thing. It probably took me the same amount of time, but I went and found
- the theme function that nice menus uses to style its list items: {themename}_nice_menus_build
- the function in the context module that gives you the active contexts: context_active_contexts()

I get my active contexts inside the theme function, and set any menu reactions into $context_active_trail.  Later in that function, I compare them with the menu being built:

The large majority of the code you see here is stock from the theme function in nice menus - comments in the function body indicate my alterations...


<code>


function {themename}_nice_menus_build($menu, $depth = -1, $trail = NULL) {
  $output = '';
  // Prepare to count the links so we can mark first, last, odd and even.
  $index = 0;
  $count = 0;

// look up contexts, get active trail and set into array for later.

  $contexts = context_active_contexts();
  $context_active_trail = FALSE;
  foreach($contexts as $context) {
    if (array_key_exists('menu', $context->reactions) ) {
      $context_active_trail[] = $context->reactions['menu'];
    }
  }
  foreach ($menu as $menu_count) {
    if ($menu_count['link']['hidden'] == 0) {
      $count++;
    }
  }
  // Get to building the menu.
  foreach ($menu as $menu_item) {
    $mlid = $menu_item['link']['mlid'];
    // Check to see if it is a visible menu item.
    if (!isset($menu_item['link']['hidden']) || $menu_item['link']['hidden'] == 0) {
      // Check our count and build first, last, odd/even classes.
      $index++;
      $first_class = $index == 1 ? ' first ' : '';
      $oddeven_class = $index % 2 == 0 ? ' even ' : ' odd ';
      $last_class = $index == $count ? ' last ' : '';
      // Build class name based on menu path
      // e.g. to give each menu item individual style.
      // Strip funny symbols.
      $clean_path = str_replace(array('http://', 'www', '<', '>', '&', '=', '?', ':', '.'), '', $menu_item['link']['href']);
      // Convert slashes to dashes.
      $clean_path = str_replace('/', '-', $clean_path);
      $class = 'menu-path-'. $clean_path;

      // ensure context module gets a say in the setting of the active-trail class.
      if ($context_active_trail) {
        if(in_array($menu_item['link']['link_path'], $context_active_trail)){
          $trail[] = $menu_item['link']['mlid'];
        }
      }
      if ($trail && in_array($mlid, $trail)  ) {
        $class .= ' active-trail';
      }
      // If it has children build a nice little tree under it.
      if ((!empty($menu_item['link']['has_children'])) && (!empty($menu_item['below'])) && $depth != 0) {
        // Keep passing children into the function 'til we get them all.
        $children = theme('nice_menus_build', $menu_item['below'], $depth, $trail);
        // Set the class to parent only of children are displayed.
        $parent_class = ($children && ($menu_item['link']['depth'] <= $depth || $depth == -1)) ? 'menuparent ' : '';
        $output .= '<li class="menu-' . $mlid . ' ' . $parent_class . $class . $first_class . $oddeven_class . $last_class .'">'. theme('menu_item_link', $menu_item['link']);
        // Check our depth parameters.
        if ($menu_item['link']['depth'] <= $depth || $depth == -1) {
          // Build the child UL only if children are displayed for the user.
          if ($children) {
            $output .= '<ul>';
            $output .= $children;
            $output .= "</ul>\n";
          }
 }
        $output .= "</li>\n";
      }
      else {
        $output .= '<li class="menu-' . $mlid . ' ' . $class . $first_class . $oddeven_class . $last_class .'">'. theme('menu_item_link', $menu_item['link']) .'</li>'."\n";
      }
    }
  }
  return $output;
}

</code>

The trail variable is used next to test the current menu item and set the active-trail class if it matches. 

Now Nice menus respects Context's active menu assignments.  It would be nice if I went back and figured out the process to do it the right way, where the theme isn't so tightly coupled to the context module.

noderelationships and nodereferrer

Noderelationships sets up the ability to do nice js-enabled usability enhancements for cck nodereference fields.

However, I had more specific constraints...I needed to customize the view that you use when selecting nodes.  Normal arguments and filters don't work when set on noderelationships-based views, provided for your use by the module.

I tried using some instructions found on drupal.org ( issue refs here ) to filter by url arguments, etc but it didn't work...however, plain 'ol ( fancy 'ol ) hook_views_pre_view worked like a charm, using $view->default_handler->override_option('filter', $filter = array( {assoc array defining a views filter} ) );

Also, I needed to filter so that previously referenced nodes wouldn't show in the view - single noderef only please.  I had to install nodereferrer...

Now, node_relationships special treatment of views prevents filters and arguments, but it allows relationships.  So you have to set a nodereferrer relationship on the view, then add a filter such that referencing nodes is empty.  But the filter doesn't take since node_relationships is doing it's fancy thing.  So I ran a views export, copied the resulting filter array, and added another filter in my hook_views_pre_view. 

I feel like I triumphed...huzzah.

git setup for new drupal site.

I've been developing a drupal site, and am ready to add it to a git repository for initial deployment.

One of the things I've noticed is that some things you shouldn't version...sites/default/settings.php shouldn't be in git, also sites/default/files and backup/ aren't good to have in there.

So I did the following:

- created the repo out on github.
- cd'd to my site root.
- rashly ran 'git add .'
- ran 'git rm -r --cached sites/default/files/*', 'git rm -r --cached backup/*', and 'git rm --cached sites/default/settings.php'
- created a .gitignore file in the root of my drupal site.  It contains the following lines:

backup/*
sites/default/settings.php
sites/default/files/*

 That will ignore those directories in the future when I got to commit or update.

- ran 'git commit'
- ran 'git remote add origin git@github.com:{repo_path_here}' and 'git push origin master'

Jesus is the word of God

Here are some notes on the topic of Jesus as the Word of God.  A friend of mine is going to start speaking on 'hearing God', and it seems this topic might be of interest..

I was first introduced to this as a serious angle of study and major theme of the New Testament ( rather than a nice verse in John 1 ) when I read 'Prayer' by a German theologan named Hans Urs von Balthasar.  http://www.amazon.com/Prayer-Hans-Urs-von-Balthasar/dp/0898700744  This is probably as difficult a book as I've ever read - but it was transformative for me in a variety of ways.  I need to go back and find a copy and see if it stands up after 10 years.

Jesus is represented in scripture as the Word of God - a title that needs to be understood 'as the scripture says', not from the definition our church culture has put on it.  The Word of God is a title used primarily in reference to Jesus, and is not used as a reference to scripture anywhere that I'm aware of.

Two major passages unpack Jesus identity as the Word - Everyone knows John 1, but Hebrews ch. 1 and 2 are a parallel rendering of the same thoughts.  Hebrews expands upon and elaborates what is a pretty compact presentation of these ideas in John 1.

Here's a map of the relationship between those two passages:

Jesus is the Word, God's primary means of communicating to us
John 1:1
Hebrews 1:1-2

Jesus is God, and the Son of god
John 1:1
Hebrews 1:3

Jesus made the universe
John 1:3
Hebrews 1:2

Jesus came to bring many sons into the kingdom.
John 1:12-13
Hebrews 2:10-15

Jesus was God, but came as a man...
John 1:14
Hebrews 2:14

I need to cross some of this up with various bits of the book of Revelation, and get a summary together that folds the various bits of scripture in to some kind of identity statement about who Jesus is as the Word of God.