This article contains a programming howto for stripping unwanted fields from forms generated by the CCK module in Drupal 6. It is natural for a content type to gather as many fields as the logical data schema requires (essentially, the decision about adding a field to an existing content type or creating a new content type, is dictated by considerations about data cardinality and independence in life cycles of individual pieces of data). Real-world applications will usually require that certain subsets of these fields should not be editable by everyone, or that they should only be editable at certain times or when certain conditions are met (for example, based on another field's value within the node being edited). Understandably, CCK doesn't know anything about such special needs out-of-the-box, and so it will render all node fields as equals on a common form.
Consider a case where you have already created a complex default form for editing the entire content type, possibly with field groups and a custom theme which renders them in a fancy hierarchical way. Now let's say you have another user role which is only supposed to understand and edit a few fields from the said content type. You want a new form which displays only those fields and saves the submitted values to the node, validating them in exactly the same way as they would be in the full-node edit scenario. To make things even more interesting, suppose that one of the fields is a select box for which you want to restrict the list of allowed values when it is presented on the restricted form.
The correct solution is short in code, but rather tricky conceptually. It requires at least an intermediate level of understanding of the Drupal Form API and theming layer. The following steps can be used as a guideline:
- Choose a path for the new form. It is a logical choice to extend the default node edit path. That is, let
/node/1234/edit
refer to the default form and let/node/1234/edit/custom
refer to your custom form. In your module, implementhook_menu()
and copy the item fromnode.module
which refers to/node/%node/edit
. At this point you have an opportunity to turn it into a MENU_CALLBACK (if you don't want the new form to be accessible through the standard tabs) and also to restrict access by defining a new access callback. Don't forget to specify'file path' => drupal_get_path('module', 'node')
, otherwise the filenode.pages.inc
defined bynode.module
would not be found. The new item should look like so:$items['node/%node/edit/custom'] = array( 'title' => 'Edit custom', 'page callback' => 'node_page_edit', 'page arguments' => array(1), 'access callback' => 'yourmodule_edit_custom_access', 'weight' => 1, 'file' => 'node.pages.inc', 'file path' => drupal_get_path('module', 'node'), 'type' => MENU_CALLBACK, );
- Implement
hook_form_alter()
to disable access to unwanted fields on your custom form. You will also probably want to set a distinct theme function for the new form, so that you can arrange the wanted fields in a sensible way. The code inyourmodule_form_alter
should look like so:if ($form_id == 'yourcontenttype_node_form' && arg(3) == 'custom') { $form['#theme'] = 'yourmodule_custom_form'; $allowed_fields = array('field_first', 'field_second', 'field_third'); foreach ($form as $key => &$field) { if (strpos($key, 'field_') === 0 && !in_array($key, $allowed_fields)) $field['#access'] = FALSE; } }
The original form might also contain other form elements that need to be disabled and whose names don't begin withfield_
. You can find out what they are visually by looking at the form and by dumping the keys of$form
. One good example is thebuttons
key, which contains the submit, preview and delete buttons. It is possible that some form elements contributed by other modules are not yet present when yourhook_form_alter()
is called. In this case, you will likely have to implement anotherhook_form_alter()
in another module and set this module's weight to a big value in thesystem
table, to ensure that it executes last. - Register the theme function
yourcustom_form
specified above viahook_theme()
and also provide an actual implementation in a function calledtheme_yourmodule_custom_form
, which receives$form
as argument. Alternatively, you could declare it inhook_theme()
as a template like so:'yourmodule_custom_form' => array( 'arguments' => array('form' => NULL, 'user' => NULL), 'template' => 'custom_form', ),
and then create a template filecustom_form.tpl.php
in your theme folder. - Within your theme function or template, render the whole form or individual fields like so:
// Whole form // print drupal_render($form); // Individual fields print drupal_render($form['fieldgroup_tabs']['group_somegroup']); print drupal_render($form['buttons']['submit']);
In general, if you cannot render the whole form at once because it would result in unwanted artifacts (e.g. unwanted group headers from the original form or some such), it is a good approach to render the wanted fields individually first and then calldrupal_render($form)
within adisplay:none
div
element, which will only output the as-of-yet unrendered fields. This will also ensure that any hidden fields are output at all. Mind the possible security implications; if you failed to set#access
to FALSE on some fields in the step above, you'd get invisible, yet still updateable fields (the user might use Firebug to get at them). Accordingly, only rely on thedisplay:none
trick for visuals, but not as a method of concealing fields. - In the problem statement a requirement to restrict allowed values for a particular field was mentioned. This can be done through the
#afterbuild
callback installed on the field in question, like so:// In hook_form_alter() if branch: $form['field_first']['#after_build'][] = 'yourmodule_field_first_after_build'; // ... function yourmodule_field_first_after_build($element, &$form_state) { unset($element['value']['#options']['']); return $element; }
A word of caution: if you are using a checkbox CCK field in your content type, you might to run into a bug when you set #access
to FALSE, which would prevent the custom form from validating. As a workaround, two patches for optionwidgets.module were available at writing time (pick your favorite).
In general, when working with form data structures, remember that the content of these structures varies considerably depending on the current processing stage within the form engine. (Unfortunately, these variations and the separation between API and internals are rather badly documented, which makes it easy to write unstable code.) It's also good to realize that FAPI's "form elements" are hierarchical in nature and act as a mind-numbingly complicated powerful adapter layer between the database fields and HTML form inputs. In particular do realize that a single FAPI form element might expand into multiple form inputs and/or supply values for mutliple database fields.
For further information, I refer you to the following sources:
- Creating custom elements using Drupal 6 (besides of explaining custom element types, it also goes into the FAPI processing logic)
- FAPI workflow illustration
- FAPI reference
No comments:
Post a Comment