How many times as developers have we been working on a project, and ran across an issue that seriously affected the way we were interacting with the website? Maybe it was something related simply to User Experience, or a functionality that may have been unclear to us, and so would be very unclear to the standard client. This is a very common problem, not just with Drupal, but with ANY Content Mangement System. What sets Drupal apart is when this "functionality gap" is discovered, its extensible nature allows us as developers to build modules to fill that gap, and add in new functionality, or modify existing workflow in Drupal. This post will walk through a problem I came across multiple times in previous Drupal sites I've worked on, including the sites of my own that I maintain. After describing the problem, we will walk through creating a module to solve the issue, and how to contribute back to the community. This demonstration is using Drupal 6. This is really focused for those developers that have recently converted to Drupal for one or multiple projects, and are attempting to overcome limitations by the core system.

The Problem

The problem for me made itself known while beginning work on the first blog post I was asked to do for CommonPlaces. I was typing away (on a topic unrelated to this) in my development Drupal environment. I've never found an offline blogging software that I really like, and I prefer being right inside the site where the post will belong to really preview how a blog post will look. When it comes to creating a long, thought-out blog post on any topic, I'm sure I'm not the only one that has noticed in Drupal that there is not an ability to Save & Continue Editing. We are forced, in order to save our current progress, to Save a post, be redirected to the node view page, and then click on "edit" again in order to get back to our topic. This is definitely a problem. And we can create a solution without too much effort.

The Solution

In order to solve this issue, we are going to create a simple Drupal module and build in the new features needed, but first, let's get an idea of what we need.

Must have features:

  • Create a Save & Edit buttonon Drupal Nodes
    • This will need to modify the default Drupal functionality by redirecting the user back to the node edit form if this custom button is used.
  • Provide the ability to have the new functionality modify the default "Published" optionon nodes.
    • A potential issue with this would be when creating new nodes in Drupal, we are ready to "Save & Edit", but we aren't ready to publish a node, but the default option for the specified content type IS to set the new node as published.
    • With this in mind, if we alter how a node is published based on preferences here, we will need to provide an additional "Publish" button to enhance the user experience.

Extra features:

  • Allow site admins to set permissions on who can use and administer the new module we are creating
  • Provide some default help text
  • Allow site admins to customize the buttons we are creating AND the default Drupal "Save" button on nodes

Getting Your Hands Dirty With Writing a Module

In order to get started, we need to create a .info file for our module. Drupal uses .info files to describe the module and its contents. For this module, the file will be named save_edit.info

Creating our .info file

name = Save & Edit
description = Gives a "<a href="http://drupal.org/project/save_edit">Save & Edit</a>" button on node pages. No Kittens were harmed during the creation of this module.
package = Administration
core = 6.x

; Information added by drupal.org packaging script on 2009-06-03
version = "6.x-1.2"
core = "6.x"
project = "save_edit"
datestamp = "1244049400"

The .info file needs to provide at least three of the first four items in this example: name, description, and core. The name is the short, friendly name we will use for the module. Description can be a bit longer explanation of what the module does. Core provides Drupal with a descriptor to know what version of the Drupal system this module works with. The additional package item allows it to be classified with other modules on the administration page in Drupal. If you do not include a package name, it will be classified in the "Other" category. The lower sections of this .info file are inserted when the module is packaged and released on Drupal.org.

Creating our .module file

Now we will create out module file, which for this case will be save_edit.module. Drupal uses .module to name the main PHP file in any module. There are situations where .inc files can be included by a module. Any .module file would encompass different functionality and would include a .info file with it. The following examples follow the flow of the .module file that I have created, and not the priority of the features.

hook_help()

/**
 * Provide online user help.
 *
 * @param $path
 * @param $arg
 * @return
 *   Help text
 */
function save_edit_help($path, $arg) {
  switch ($path) {
      case 'admin/settings/save-edit':
          return '

'. t('Save & Edit adds a "Save and edit" button to the node add and node configure forms. The module also provides options to modify the way the "Publish" feature works when using Save & Edit. If the modifications are enabled to the Publish feature, when a node is unpublished, it will also create a "Publish" button that will obviously Save & Publish a node that was previously marked as Unpublished.') .'

';
    case 'admin/help#save_edit':
      return '

'. t('Save & Edit adds a "Save and edit" button to the node add and node configure forms. The module also provides options to modify the way the "Publish" feature works when using Save & Edit. If the modifications are enabled to the Publish feature, when a node is unpublished, it will also create a "Publish" button that will obviously Save & Publish a node that was previously marked as Unpublished.') .'

';
    default:
      return '';
  }
}

The usage of hook_help() provides us with a way to give the administrative users of this module some friendly information regarding what the module does, possibly how to use it, or anything they might need to know. In the above example, we are using the $path parameter in order to determine if we should show the help text. Many times, you will see modules use both of the sections as above. This is going to provide a help section on both the module settings page for this new module, and in the default help page. In most cases, the information on the module settings page would be more concise, and link to the admin/help#your_module page for more detailed help information. I have used the same text in both situations in this case.

hook_perm()

/**
 * Implementation of hook_perm().
 */
function save_edit_perm() {
  return array('use save and edit', 'administer save and edit');
}

hook_perm() is an easy section to throw into any module, but it is very important. In most cases, you want to provide permissions with your module that will allow the admin to configure ways to limit access to the new features you provide, or possibly even who may administer the custom settings your module provides. The function hook_perm() takes no arguments, and simply returns an array of permissions. In this case, it provides "use save and edit" and "administer save and edit". This is going to allow our module to later determine if a user has access to use the features of the module, and if they can change admin settings for the module.

hook_menu()

/**
 * Implementation of hook_menu().
 */
function save_edit_menu() {
  $items = array();
  $items['admin/settings/save-edit'] = array(
    'title' => t('Save & Edit Settings'),
    'description' => t('Administer settings related to the Save & Edit module.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('save_edit_admin_settings'),
    'access arguments' => array('administer save and edit'),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

hook_menu() allows us to create menu callbacks to built in functionality and our custom module's information to render a page. In the example for this module, we only have one menu callback, which is to create our administration page for the module. The important parts to know here are the page callback which calls a specific function to render the page in question. In this situation, we must use drupal_get_form, which will build our form for us with the Drupal FAPI (Form API) based on the arguments we send to the function in the page arguments section. Here we have save_edit_admin_settings as the page argument. This tells Drupal to look in our custom function save_edit_admin_settings() to find information about what we want to do with the form we will be building with drupal_get_form(). Lastly, the access arguments provide an array of permissions that need to be true in order to render the content to the user trying to visit our new page. In this module, we are limiting this administrative menu item to only users we have defined in the permission section based on the "administer save and edit" we created using hook_perm() above.

save_edit_admin_settings()

/**
 * Admin Settings form for Save & Edit
 */
function save_edit_admin_settings() {
  $form['where_to_save_edit'] = array(
    '#type' => 'fieldset',
    '#title' => t('Node Types'),
    '#description' => t('Set the node types you want to display links for.'),
    '#collapsible' => FALSE,
    '#collapsed' => FALSE
  );
  $form['where_to_save_edit']['save_edit_node_types'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Node types'),
    '#default_value' => variable_get('save_edit_node_types', array()),
    '#options' => node_get_types('names')
  );
  return system_settings_form($form);
}

Our custom function save_edit_admin_settings() is what will be used in the drupal_get_form() defined in hook_menu() above when presenting our administration form to users with the administer save and edit permission defined in hook_perm(). I have actually cut out quite a few of the implemented features in this section to keep the example short. The code included above creates a "setting group" in the first portion of the array, defining a fieldset that will group up multiple form items that are related to each other. Once we have defined all of our menu items/options, we return the form elements using system_settings_form($form) to send our data back to the Form API to be rendered by Drupal. I'll save a long winded example of the Form API for another time, as there are tons of examples of the various form element types and how to render them properly on Drupal.org.

hook_form_alter()

This is where things start to get fun. In order to accomplish our goals here, we need to be able to modify the form Drupal is rendering on node add and edit pages to provide our new functionality.

/**
 * Implementation of hook_form_alter().
 */
function save_edit_form_alter(&$form, $form_state, $form_id) {
  $node_types = variable_get('save_edit_node_types', array());
  $form_type = $form['type']['#value'];
  if ($form['#id'] == 'node-form' && $node_types[$form_type] && user_access('use save and edit')) {
    //add save and edit btn
    $form['buttons']['save_edit'] = array(
      '#type' => 'submit',
      '#access' => user_access('use save and edit'),
      '#value' => t(variable_get('save_edit_button_value', 'Save & Edit')),
      '#weight' => 4,
      '#submit' => array('redirect_save_edit_submit'),
    );
    // now if we have chosen to use the auto-unpublish feature, we should
    // create a Publish button to add a clear workflow
    if((variable_get('save_edit_unpublish', 0) || variable_get('save_edit_unpublish_new_only', 0)) && !$form['#node']->status) {
        $form['buttons']['save_edit_publish'] = array(
          '#type' => 'submit',
          '#access' => user_access('use save and edit'),
          '#value' => t(variable_get('save_edit_publish_button_value', 'Save & Publish')),
          '#weight' => 7,
          '#submit' => array('redirect_save_edit_submit'),
        );
    }
    // this allows us to modify the default Save button to something we like more
    $form['buttons']['submit'] = array(
        '#type' => 'submit',
        '#access' => !variable_get('node_preview', 0) || (!form_get_errors() && isset($form_state['node_preview'])),
        '#value' => t(variable_get('save_edit_default_save_button_value', 'Save')),
        '#weight' => 5,
        '#submit' => array('node_form_submit'),
      );
  }
}

This function makes the following modifications to the Drupal node form:

  • IF we are viewing a node form AND IF we are on a node type selected to make use of this module AND IF the user has permission to use this feature,
    • Create out Save & Edit Button
      • Define the custom text for the button
      • Define our custom submit handler to provide the new functionality when this button is used.
    • IF admin has selected to automatically unpublish nodes that use the Save & Edit button OR IF they selected to do that on new nodes only AND IF the node is currently NOT published,
      • Create our Publish Button
        • Define the custom text for the button
        • Define our custom submit handler again to be used for this function
    • Modify the default Drupal Save button
      • Customize the text of the button to use our custom text
      • Remainder of default submit button functionality is not modified, just re-declared

And finally, we can move on to the custom submit handler that really does the legwork for this simple module. This is where we make the major modifications to how things are saved & redirected. In this example, we will just be worrying about publishing/unpublishing the node, and redirecting back to the node edit form.

redirect_save_edit_submit()

/**
 * Custom Submit Handler for Redirecting Save & Edit's to the node form after saving
 *
 * @param $form
 * @param $form_state
 */
function redirect_save_edit_submit($form, &$form_state) {
  // we will first check to see if they want to auto-unpublish, and make modifications if so
  // before submitting the node
  // ONLY do something on new nodes
  if(variable_get('save_edit_unpublish_new_only', 0) && !$form_state['values']['nid']) {
    $form_state['values']['status'] = 0;
  }
  // DO IT EVERY TIME Save & Edit is used. (Seems like a rare case)
  elseif(variable_get('save_edit_unpublish', 0) && !variable_get('save_edit_unpublish_new_only', 0)) {
    $form_state['values']['status'] = 0;
  }
  // WAIT... if someone clicked the Publish button, maybe we should retick that option now
  if($form_state['clicked_button']['#id'] == 'edit-save-edit-publish') {
    $form_state['values']['status'] = 1;
  }
  // call default node save/submit function
  node_form_submit($form, $form_state);
  // only redirect if using the Save & Edit button
  if ($form_state['clicked_button']['#id'] == 'edit-save-edit') {
    // change redirect location
    $form_state['redirect'] = 'node/'. $form_state['nid']. '/edit';
  }
}

Here's a breakdown of what this function is doing. It is being called when the custom Save & Edit button is clicked, AND when the Publish button is clicked.

  • IF admin has selected to auto-unpublish ONLY new nodes AND the form data does not return an node id yet,
    • Set the status value to zero to make the node unpublished prior to saving
  • IF admin has selected to auto-unpublish EVERY time the Save & Edit button is used
    • Set the status value to zero to make the node unpublished prior to saving
  • IF the Publish button was clicked
    • Set the status value to one to make the node published prior to saving
  • Call the default node submit function, node_form_submit()
  • IF the Save & Edit button was clicked
    • Set the redirect value to return to the node edit form rather than the default of returning to the node view page.

Conclusion

Hopefully this example can help some developers new to Drupal, or new developers that have adopted Drupal understand one of the many ways it is possible to extend the core functionality of Drupal using Modules. For those new to Drupal, please note that it is NEVER acceptable to hack core. There are many reasons behind this, but any functionality you need to add to a Drupal site can be added through the use of modules.

Download the Save & Edit Module

This code in complete form has been released back to the community as the Save & Edit module available for Drupal 6. There are still features & issues that need to be addressed with this. If you find those, and want to help make this module better, post an issue.

Contributing Back to the Drupal Community

Drupal is not just a CMS, but also a vast community of developers that are out there willing to contribute code and time to this powerful open source project. In most cases, if you have a simple feature that needs to be added to your site, there is likely a Drupal Module out there that already does it. If you do stumble across something simple that is not covered by a current module, it is always best to contribute back to the community when you have a working solution. There are many times where a client may not allow you to contribute proprietary code back to the community, but in a lot of cases, simple functionality can easily be given back to benefit anyone using Drupal that needs your solution for their own problem. The following list of resources provides valuable information regarding creating and contributing your addition to the Drupal community.

Leave Your Comment