Jump to:
| Project: | Views Bulk Operations (VBO) |
| Version: | 6.x-1.9 |
| Component: | Actions |
| Category: | feature request |
| Priority: | normal |
| Assigned: | infojunkie |
| Status: | closed (fixed) |
Issue Summary
We have a need to provide a user with the ability to merge multiple nodes into one. The bulk operations interface is almost ideal, except it doesn't allow a custom confirmation screen, which is where we need to choose the "primary" node.
The attached patch makes a small change to the code to allow the custom confirmation screen, and implements the merging as a new action.
Problems with it are:
The forms theme doesn't get called
The submit is simply called before the views_bulk_operations_submit, and the drupal_goto is not really the right way to exit, it should return to the bulk handling screen.
I'd really like to sort those couple of issues, and it would enhance this module as well I believe.
| Attachment | Size |
|---|---|
| views_bulk_operations.diff | 3.54 KB |
Comments
#1
Based on node_merge_action, I think that the existing 'configurable' attribute of actions can be used to achieve what you want. When you set it to TRUE, VBO automatically calls the action's form, validate and submit functions. It then passes to the action execution function the parameters set in the form. Would you still need a custom confirmation form then? Please take a look at taxonomy.action.inc for an example.
As a side note, I see that your action needs the 'aggregate' attribute to act on all selected nodes at once. I removed that feature from the code recently because the original request for it no longer stood. If you confirm that you need that feature, I will put it back in.
#2
OK, Doesn't look like I need the aggregate ability, just me learning the various params.
While the configurable attribute does indeed show the form, it is with the list of nodes _before_ you select the ones you want.
So the flow would be:
select subset of nodes which may be duplicates & select the primary from the list
merge etc in the submit.
The flow I'm after is:
select subset of nodes which may be duplicates, submit
select which is the primary out of the subset, submit
merge etc in the submit.
As we have over 6000 nodes and building the select for the full list will be awful, making it much more usable with the additional step.
Please correct me if theres something I'm doing wrong with the configure, but I couldn't get it to work quite as required.
Thoughts?
#3
How about this:
* Select the desired nodes in the main view
* Pass the selected list to the form, which allows the user to choose among those selected which is the primary
* Merge in the submit
Would that work for you? If so, I will make a small change to pass to the form the list of selected nodes.
#4
Sounds fine to me. Love to see the small change, happy to test it out for you if you like.
#5
The fix is ready (either pull from CVS branch DRUPAL-6--1 or wait 12 hours before Drupal.org refreshes the dev release). What's available now is that VBO will call the action's form function with a $context parameter which is an array with keys 'view' = the current view object and 'selection' = an array of selected node IDs. Please try it and let me know.
PS. Note that in the case of a single action, the action form is displayed on the first page along with the nodes to be selected. In that case, the action form will be called with 'selection' = null. You can inhibit that behaviour by explicitly setting the VBO option "Merge single action's form with node selection view" to FALSE.
#6
That works quiet well. I've pasted the final code below for reference from our node_merge.action.inc Feel free to add to the tarball if you like.
<?php
function views_bulk_operations_node_merge_action_info() {
return array('views_bulk_operations_node_merge_action' => array(
'type' => 'node',
'description' => t('Merge duplicate nodes'),
'configurable' => TRUE,
'behavior' => array('changes_node_property')
));
}
function views_bulk_operations_node_merge_action(&$node, $context) {
// If this is not the primary node
if ($context['primary'] != $node->nid) {
// Update tables to now point at the new node Easy ones first.
db_query("UPDATE {comments} SET nid = %d WHERE nid = %d", $context['primary'], $node->nid);
// Now the tricky ones
// Comment statistics
$old_stats = db_fetch_array(db_query("SELECT * from {node_comment_statistics} WHERE nid = %d", $node->nid));
$new_stats = db_fetch_array(db_query("SELECT * from {node_comment_statistics} WHERE nid = %d", $context['primary']));
if ($new_stats['last_comment_timestamp'] < $old_stats['last_comment_timestamp']) {
$new_stats['last_comment_timestamp'] = $old_stats['last_comment_timestamp'];
$new_stats['last_comment_name'] = $old_stats['last_comment_name'];
$new_stats['last_comment_uid'] = $old_stats['last_comment_uid'];
}
$new_stats['comment_count'] = $new_stats['comment_count'] + $old_stats['comment_count'];
// Update the comment count and details on the new node.
db_query("UPDATE {node_comment_statistics} SET last_comment_timestamp = %d, last_comment_name = '%s', last_comment_uid = %d, comment_count = %d WHERE nid = %d",
$new_stats['last_comment_timestamp'], $new_stats['last_comment_name'], $new_stats['last_comment_uid'], $new_stats['comment_count'], $new_stats['nid']);
// Old node comment count is just left atm.
// And mark the node as unpublished, VBO does the update on return
$node->status = 0;
}
}
function views_bulk_operations_node_merge_action_submit($form, $form_state) {
// This identifies the primary node
$nodes = $form_state['values']['nodes'];
if ($nodes) {
$node = node_load($nodes);
drupal_set_message(t("Setting @title as primary", array('@title' => $node->title)));
return array(
'primary' => $form_state['values']['nodes'],
);
}
}
function views_bulk_operations_node_merge_action_form($context) {
$form = array();
$i = 0;
if ($context['selection']) {
$info = _views_bulk_operations_object_info_for_view($view);
foreach ($context['selection'] as $oid) {
if ($node = node_load($oid)) {
$nodes[$oid] = $node->title;
}
}
$form['nodes'] = array(
'#type' => 'radios',
'#options' => $nodes,
);
}
return $form;
}
#7
Automatically closed -- issue fixed for 2 weeks with no activity.
#8
UPDATE: I see now that you need to uncheck "Merge single action's form with node selection view", as mentioned above, for this to work. I have used this as a template to serve my specific needs, but it could be a good addition to the module if a few things were changed. For instance, as I mentioned, it would be great to be able to automatically change existing foreign key (node reference) values so that they reflect the new primary node. In my case, I did this by hardcoding a query to update the table for the single node reference field I'm concerned with on my site, because I'm not sure how to determine which fields *could* reference the merged nodes and update them programatically. I also added a query to update the search_node_links table.
I'd be willing to work on a more universal version of this action if anyone else is interested.
ORIGINAL COMMENT:
This does pretty much what I need, but it isn't working for me. The confirm screen does not allow me to select the primary node, as described. It just presents the default confirmation form (when I click "confirm" it appears to simply unpublish all selected nodes).
I would also like to modify the action to update any node reference fields that point to the merged nodes, so that they all point the primary. But I can figure that out. I just need help actually getting the second form working.
Thanks.
(EDIT Actually, I'm using 6.x-1.10)
#9
I added a bit to this action so that it would update node reference fields in nodes referring to the merged items, and so that it would update the search_node_links table (which would happen at the next cron anyway, but it was helpful for me to have update immediately). I figured this out by digging through the database tables, so I'm not sure if it's the best way -- I had trouble finding any documented Drupal functions to abstract this, so I'm modifying tables directly. Anyway, in case it's helpful, I put this code below the line "if($context['primary'] != $node->nid) {":
<?php
// Update tables to point at the new primary node
// find node reference fields that may reference this content type
$nr_fields = db_query("SELECT f.field_name, i.type_name, f.global_settings, f.db_storage
FROM content_node_field f
INNER JOIN content_node_field_instance i
ON f.field_name = i.field_name
WHERE f.type = 'nodereference'");
while($field = db_fetch_array($nr_fields)){
$settings = unserialize($field['global_settings']);
$node_type = (string) $node->node_type;
//determine table & column names
if($settings['referenceable_types'][$node_type] == $node_type) {
if($field['db_storage'] == 0){ //field has its own table (table_name column_name)
$node_refs[] = 'content_'.$field['field_name'].' '.$field['field_name'].'_nid';
}else{ //field is stored in content type table (table_name column_name)
$node_refs[] = 'content_type_'.$field['type_name'].' '.$field['field_name'].'_nid';
}
}
}
$nr_columns = array_unique($node_refs);
//update columns to reference primary node
foreach($nr_columns as $tbl_col){
//$tbl_col[0] = table name
//$tbl_col[1] = column name
$tbl_col = explode(' ', $tbl_col);
db_query("UPDATE {%d} SET %d = %d WHERE field_artists_nid = %d",
$tbl_col[0], $tbl_col[1], $context['primary'], $node->nid);
}
db_query("UPDATE {search_node_links} SET nid = %d WHERE nid = %d", $context['primary'], $node->nid);
?>
#10
Also, in singularo's action, this will work better at the end of the last function:
<?php
foreach ($context['selection'] as $node) {
$nodes[$node->nid] = $node->node_title;
}
$form['nodes'] = array(
'#title' => 'Select primary node',
'#description' => 'All other nodes will be merged into this one',
'#type' => 'radios',
'#options' => $nodes,
);
?>
#11
Automatically closed -- issue fixed for 2 weeks with no activity.