8

After having added a “changed by” on nodes I now need to display the user who modified each node on the node_admin_content form (at admin/content) right next to the "Author" column.

I've managed to easily solve this by altering node.admin.inc and adding just 2 lines of code:

...
'changed_by' => t('Changed By'),
...

...
'changed_by' => theme('username', array('account' => user_load($node->changed_by))),
...

This, of course, is not a proper solution since it alters the core.

So I then tried altering the form via:

function hook_form_node_admin_content_alter(&$form, &$form_state, $form_id) { 
  $form['admin']['nodes']['#header']['changed_by'] = t('Changed By');
  // ... ?
}

Using dpm I can see that the form has the nodes in options. The problem is that this is the result of rendering the nodes as table options. I don't have access to the original nodes, nor do I want to re-execute the node fetch query in order to get the "changed by" information. I guess doing this in the form hook would not be solving it in the correct layer. Or is it?

So, what is a good way to alter the node_admin_content form to add more data that exists on nodes?

18

The bad news is that having inspected the code, the form alter layer is the only place to really do this; your approach is pretty much spot on.

The good news is, Drupal implements all sorts of static caching throughout the page load, which minimises the need to go back into the database. So while altering the content table may appear cumbersome, you're not actually taking a noticeable performance hit.

The following code (or similar) should work; see the comments for more info about the caching issue:

function MYMODULE_form_node_admin_content_alter(&$form, &$form_state, $form_id) {
  // Load the nodes. This incurrs very little overhead as 
  // "$nodes = node_load_multiple($nids);" has already been run on these
  // nids in node_admin_nodes(). The static cache will be used instead of
  // another db query being invoked
  $nodes = node_load_multiple(array_keys($form['admin']['nodes']['#options']));

  // Grab a list of all user ids that have been responsible for changing the node
  $uids = array();
  foreach ($nodes as $node) {
    $uids[] = $node->changed_by;
  }

  // Filter out duplicates (one user may have been the last to change more than one node)
  $uids = array_unique($uids);

  // Load a list of all involved users in one go. This is about as performant
  // as this is going to get, as you're going to need the user objects one
  // way or the other for the call to theme_username
  $users = user_load_multiple($uids);

  // Add another column to the table header
  $form['admin']['nodes']['#header']['changed_by'] = array('data' => t('Changed by'));

  // Loop through the rows in the table and add the changed by column
  foreach ($form['admin']['nodes']['#options'] as $nid => $row) {
    // Grab the user related to this node.
    $this_user = $users[$nodes[$nid]->changed_by];

    // Add data for the new column
    $form['admin']['nodes']['#options'][$nid]['changed_by'] = theme('username', array('account' => $this_user));
  }
}

The above code produces a nice shiny new column like this on the content admin page:

enter image description here

  • 4
    Superb! Thanks a lot for providing quality documentation with your answer. – cherouvim May 9 '12 at 12:42
  • @cherouvim No worries :) – Clive May 9 '12 at 13:42
  • Thanks its working for me as well but I want to alter existing columns like I want to display Author user name or real name instead of Author email in author column. – Pranav Gandhi Jul 22 '13 at 5:56
3

Just replace admin/content with a View, and then add whichever fields you want. Admin Views will even do it for you.

  • That was my first thought too, but would Views automatically know about the new column that was added to the node table? Does it get info about entity properties from hook_schema()/ hook_schema_alter() implementations? – Clive May 9 '12 at 18:20
  • I assumed you just added a CCK field. I see now that you went with hook_schema_alter(), which is very yuck. Still, you can implement hook_views_data_alter() to expose the new column. – Bojan Zivanovic May 9 '12 at 21:50
  • Yeah it didn't feel 'right' to do that but I can't put my finger on why. Can you think of a scenario where adding the column in that way would actually cause a problem? – Clive May 9 '12 at 21:54
  • It doesn't cause any problems, just gives you a bit of extra work (like needing hook_views_data_alter() for Views, same for properties if you're on D7) while being "ideologically" wrong, it's a very Drupal 5 way of thinking. Oh well, no big deal. – Bojan Zivanovic May 9 '12 at 21:56
  • Thanks that's good to know. Personally I'd always use fields for this kind of thing but it was interesting to find out that it could be done without big side-effects. Your answer speaks volumes about that though; if you do it the proper/recommended way (i.e. with fields) you save yourself a lot of work later on – Clive May 9 '12 at 22:05
0

A little off subject, but this answer shows how you can do this programmatically (for example by adding it as a module update in the MY_MODULE.install file.)

You'll need a little more work if you want to add your new field before last existing field. Merge it in before the end of the $view->display['default']->display_options['fields'] array.

    function MY_MODULE_update_7101(){
        // update the admin/content view, need to do it manually because it's
        // set by admin_views module
        $view_name = 'admin_views_node';
        $view = views_get_view($view_name, TRUE);

        //  add the relationship
        $view->display['default']->display_options['relationships']['uid_1']['id'] = 'uid_1';
        $view->display['default']->display_options['relationships']['uid_1']['table'] = 'node_revision';
        $view->display['default']->display_options['relationships']['uid_1']['field'] = 'uid';
        $view->display['default']->display_options['relationships']['uid_1']['label'] = 'Revision User';
        // new column settings
        $new_column = array(
            'name_1' => array(
                'id' => 'name_1',
                'table' => 'users',
                'field' => 'name',
                'relationship' => 'uid_1',
                'label' => 'Updated By',
            )
        );
        // need to use this because array_splice by itself resets 'name_1' key to '0'
        // see http://php.net/manual/en/function.array-splice.php#56794
        $temp_array = array_splice( $view->display['default']->display_options['fields'] , 0, 7);
        $view->display['default']->display_options['fields'] = array_merge($temp_array , $new_column, $view->display['default']->display_options['fields']);

        views_save_view($view);
    }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.