How to write a new configurable action?
| Project: | Views Bulk Operations (VBO) |
| Version: | 6.x-1.5 |
| Component: | Code |
| Category: | support request |
| Priority: | normal |
| Assigned: | Unassigned |
| Status: | closed |
Jump to:
I'm trying to set up a way for group admins to copy nodes to other groups, where first you select which nodes in the group to copy, and then select which groups to copy to, based on which groups the user is a member of and a few other things.
I set up VBO to select the nodes, then defined a configurable node operation as described in http://drupal.org/node/321625 comment #5. I defined [node_operation_callback]_form and [node_operation_callback]_submit functions. After selecting nodes and executing the copy operation:
- The form built in [node_operation_callback]_form is displayed; I select the target groups and click on "Next",
- The confirmation form is displayed; I click to accept this,
- Instead of calling [node_operation_callback]_submit, I get this memory error:
Fatal error: Unsupported operand types in /path/to/drupal/sites/all/modules/views_bulk_operations/views_bulk_operations.module on line 456
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 33436635 bytes) in /path/to/drupal/includes/database.mysql.inc on line 321
I assume something's being called recursively until it runs out of memory. This happens with 6.x-1.5 and 6.x-1.x-dev.
I notice that [node_operation_callback]_form seems to take a single argument, which is an array containing a view ($arg['view']) and an array with the selections ($arg['selection']). My callback function generates a $form and returns it.
Is this the "right" way to set up a process to select nodes to copy, then select groups to copy to? Or is there a better way to implement this?
Or, if this is a bug, how can I help?
Thanks!

#1
In general, if you don't need to act upon all the nodes *at once*, it makes more sense to set up an action to perform the function you want, rather than a node_operation. The main difference is that the action is called once for each selected node, instead of only once with an array of selected nids. You can check out the many actions bundled with VBO for examples.
Concerning your problem, the error:
indicates that your submit function is not returning an array. Are you sure your code is functioning properly? Also make sure that there are no typos in naming the submit function which would prevent VBO from finding it.
Finally, if you stick with a node_operation, be aware that your parameters, as returned by your submit function, will be passed as separate arguments in the node_operation_callback. So for example, if your submit function does this:
return array('arg1' => 'foo', 'arg2' => 'bar');you should write your callback function to receive the following arguments:
function node_operation_callback($nodes, $arg1, $arg2)On the other hand, if you opt for an action, the callback function should be written as such:
function node_action_callback(&$node, $context)where
$contextis an array containing keys 'arg1' and 'arg2'.Hope that helps.
#2
Hi, kratib,
Thanks for the quick reply -- this is very helpful!
I'm using node operations instead of actions because I do need to process all nodes at once. I'd like to let the user select nodes to copy, then proceed to a second form where they select the groups to copy the nodes to. If I used actions, that second form would be presented repeatedly for each node; I'd rather the user be able to mark a set of nodes, then a set of target groups -- and then the nodes get copied to the target groups.
I'm confused about submit functions returning an array; I thought the submit function would simply do the copying and complete the operation. I'm not familiar with actions, so it's possible I don't understand the processing flow. Here's what I set up:
In example_node_operations:
[example_copy] => Array(
[label] => Copy to another group
[callback] => example_copy_node_op
[configurable] => 1
)
If I understand correctly, the callback function for configurable node operations is the callback I specified with "_form" added to the end:
function example_copy_node_op_form($parms) {$nids = $parms['selection'];
$form = ... build out form here ...
return $form;
}
VBO calls this and displays the form just fine, and then proceeds to the confirmation prompt. After confirming the operation, control should pass to the submit function, shouldn't it?
function example_copy_node_op_submit($form, &$form_state) {drupal_set_message("Did we get this far?");
.. do the copy here ...
drupal_set_message("Copying completed.");
drupal_goto($back_to_home);
}
Am I setting this up properly? If not, if you could point me in the right direction, I'd appreciate it.
Thanks much!
#3
Concerning actions vs node operations: in both cases, the form is just called once. So as far as I understand your requirement, you can use either.
Concerning the submit function: actions are a bit different than standard forms. That's why I was suggesting to study one of the actions that are bundled with VBO. In general, the submit function receives the $form_state array and returns a new array made up of key => value pairs of the parameters you want to pass to your operation. The actual execution occurs in the callback that you specify. In the case of node operations, the parameters array is expanded to separate arguments passed to your callback function. In the case of actions, this array is passed as is to the callback function. That's what I was explaining in the first reply.
#4
Ahhh ... now I understand!
It sounds like actions are the way to go, since the form is called only once in this case. And I hadn't seen how the submit function passes control to the original callback ... that's the gap in my knowledge.
I'll give it a try and report back ... many, many thanks!
#5
#6
#7
Here's how we wrote a configurable action to use VBO to copy a node from one group to another. The user selects one or more nodes to copy from a VBO view, then this code presents a configuration form that lets the user select one or more groups to which the node is to be copied.
First, define the action in hook_action_info:
/*** Define action to copy nodes to multiple target groups
*/
function mymodule_action_info() {
$actions = array('mymodule_copy_action' =>
array(
'type' => 'node',
'description' => t('Copy to another group'),
'configurable' => TRUE,
'weight' => 2,
)
);
return $actions;
}
Each key in the returned array $actions specifies a callback function that'll actually carry out the copy, but it can also define a form to be presented to get additional data from the user. So, to present a form for selecting what groups to copy to, we implement "mymodule_copy_action_form":
/*** Return a form for selecting groups to which a node should be copied
*/
function mymodule_copy_action_form($context) {
// build form here ...
return $form;
}
$context is an array containing a view, but in our case, we didn't need anything in this array to build our form.
Then, implement handlers for the form:
function mymodule_copy_action_validate($form, $form_state) {
// validation code here ...
}
function mymodule_copy_action_submit($form, $form_state) {
return array(
'targets' => $form_state['values']['targets'],
);
}
The submit handler function should return an array of values to be passed to the callback function that actually carries out the action.
Finally, the callback function to actually do the copy. This will be called for every node selected in the VBO view, so you'll write this to act on a single node:
/*** Callback function to copy a node to one or more groups
*/
function mymodule_copy_action(&$node, $context) {
_mymodule_copy_to_groups($node->nid, $context['targets']);
}
The callback takes two parameters: $node is one of the nodes selected in the VBO form; $context is an array of data. Note that $context['targets'] is the list of target groups which we returned from mymodule_copy_action_submit().
In our case, we wrote a helper function (_mymodule_copy_to_groups() to copy a single node to multiple groups, but you can also carry out the operation here.
Not hard at all ... please weigh in if anything here needs correcting.
#8
SOLVED
@jshuster
your explanation works well, sorta. Everything works except I can't get the form to show up anywhere. Here is my code:
<?php
/**
* Return a form for selecting groups to act upon
*/
function views_bulk_operations_checkout_action_form($context) {
$form['check'] = array(
'#type' => 'radios',
'#title' => 'Value',
'#description' => 'What do you want to set checkout to?',
'#options' => array(
t('Home'),
t('Loaded'),
t('Competition'),
),
);
return $form;
}
?>
#9
@Vendetta501: Is your action really named "views_bulk_operations_checkout_action" ? This seems to indicate you have placed it in the VBO module itself. Is that the case?
#10
Yes, it is. I made a new file called checkout.action.inc, and wrote what I needed to in there. Is that an organizational faux-pas?
#11
Well, every time VBO has a new release you will need to make sure your file isn't lost. Also, if I ever decide to change the way the VBO actions are discovered, your code will need to be updated.
Since you marked your issue as solved, would you mind updating this thread with the solution?
#12
oh, it was total user error with me not naming all the hooks correctly. not a bug of any sort.
Solution: name all the hooks correctly.
haha.
The issues you cited make me think that hacking it into VBO is not a good idea. However, it seems silly to write an entire module whose core functionality is 3-4 lines of code. Is there another way?
One idea I had was to write a "custom actions" module where I could put a bunch of misc actions I need to write.
#13
If you're creating an action that is specific to a site, then I suggest creating a single module whose job is to contain all application-specific code, including this action.
If you're creating a generic action, then packaging it as a standalone module makes more sense. You might also want to contribute it to VBO, in which case I would review it and eventually add it to the module package.