You are here

hide_submit.module in Hide submit button 7

Same filename and directory in other branches
  1. 5 hide_submit.module
  2. 6 hide_submit.module
  3. 7.2 hide_submit.module

Hide the submit button after clicked to prevent/reduce duplicate postings.

Using a small jQuery snippet, this module will hide submit buttons afted clicked to prevent/reduce duplicate postings. A message and a nice loader animation will be displayed So the user will know its request is being processed

File

hide_submit.module
View source
<?php

/**
 * @file
 * Hide the submit button after clicked to prevent/reduce duplicate postings.
 *
 * Using a small jQuery snippet, this module will hide submit buttons
 * afted clicked to prevent/reduce duplicate postings.
 * A message and a nice loader animation will be displayed
 * So the user will know its request is being processed
 */

// --- Constants ---
define('HIDE_SUBMIT_IN_CONTENT_ADD_EDIT', 0);
define('HIDE_SUBMIT_EXCLUDE_LISTED_PAGES', 1);
define('HIDE_SUBMIT_IN_LISTED_PAGES', 2);
define('HIDE_SUBMIT_MODE_HIDE', 0);
define('HIDE_SUBMIT_MODE_DISABLE', 1);
define('HIDE_SUBMIT_BUTTON_ONLY', 0);
define('HIDE_SUBMIT_BUTTON_AND_KEY', 1);
define('HIDE_SUBMIT_DEFAULT_JS_LOAD', HIDE_SUBMIT_IN_CONTENT_ADD_EDIT);
define('HIDE_SUBMIT_DEFAULT_MESSAGE', t("Please wait..."));
define('HIDE_SUBMIT_ADMIN_SETTINGS_JS', drupal_get_path('module', 'hide_submit') . '/hide_submit_admin_settings.js');
define('HIDE_SUBMIT_MODULE_DIRECTORY', drupal_get_path('module', 'hide_submit'));
define('HIDE_SUBMIT_IMG_DIRECTORY', 'public://hide_submit');

//define('HIDE_SUBMIT_IMG_DIRECTORY', file_directory_path() . '/hide_submit');
define('HIDE_SUBMIT_DEFAULT_IMAGE_BASENAME', 'loader.gif');
define('HIDE_SUBMIT_DEFAULT_IMAGE', HIDE_SUBMIT_MODULE_DIRECTORY . '/images/' . HIDE_SUBMIT_DEFAULT_IMAGE_BASENAME);
define('HIDE_SUBMIT_EXCLUDE_CLASS', 'hide-submit-exclude');
define('HIDE_SUBMIT_EXCLUDE_LISTED_FORMS', 0);
define('HIDE_SUBMIT_EXCLUDE_UNLISTED_FORMS', 1);

/**
 * Implements hook_help().
 */
function hide_submit_help($path, $arg) {
  $output = '';
  switch ($path) {
    case 'admin/help#hide_submit':
    case 'admin/config/hide-submit':
      $output = '<p>' . t('The hide_submit module provides a way to hide the submit button in a form after it has been clicked. This will indicate to the user that his or her request is being processed and helps to prevent duplicate postings from impatient "double clickers".') . '</p>' . '<p>' . t('For more information please visit the !project page or the !documentation page', array(
        '!documentation' => l('documentation', 'http://drupal.org/node/338935'),
        '!project' => l('hide submit project', 'http://drupal.org/project/hide_submit'),
      )) . '</p>';
      if (module_exists('advanced_help')) {

        // TODO Please change this theme call to use an associative array for the $variables parameter.
        $output .= '<p>' . theme('advanced_help_topic', 'hide_submit', 'using-hide-submit') . '&nbsp;' . t('Click the icon to read the extended help.');
      }
      break;
  }
  return $output;
}

/**
 * Implements hook_menu().
 */
function hide_submit_menu() {
  $items = array();
  $items['admin/config/content/hide-submit'] = array(
    'title' => 'Hide Submit',
    'description' => 'Configure hide submit',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'hide_submit_admin_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'hide_submit_admin.inc',
  );
  $items['admin/config/content/hide-submit/settings'] = array(
    'title' => 'Hide Submit',
    'file' => 'hide_submit_admin.inc',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => 10,
  );
  $items['admin/config/content/hide-submit/delete/%'] = array(
    'title' => 'Confirm delete',
    'description' => 'Delete an image file',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'hide_submit_admin_delete_image',
      5,
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'hide_submit_admin.inc',
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_init().
 */
function hide_submit_init() {
  if (!hide_submit_js_loaded() && _hide_submit_load_js_check_condition()) {
    drupal_add_js(_hide_submit_get_javascript_settings(), array(
      'type' => 'setting',
      'scope' => JS_DEFAULT,
    ));
    drupal_add_js(drupal_get_path('module', 'hide_submit') . '/hide_submit.js');
    hide_submit_js_loaded(TRUE);
  }
}

/**
 * Memory function for checking or setting script status
 *
 * @param mixed $status - new status or NULL to leave status unchanged
 * @returns TRUE or FALSE indicating script was loaded
 */
function hide_submit_js_loaded($status = NULL) {
  static $hide_submit_js_loaded = FALSE;
  if ($status !== NULL) {
    $hide_submit_js_loaded = $status;
  }
  return $hide_submit_js_loaded;
}

/**
 * Implements hook_form_alter().
 *
 * Used to add "hide-submit-exclude" class to some forms
 * Avoid conflicts and refine hiding
 */
function hide_submit_form_alter(&$form, $form_state, $form_id) {

  // Act only if script was loaded
  if (!hide_submit_js_loaded()) {
    return;
  }
  $exclude_forms = explode("\r\n", variable_get('hide_submit_form_exclusion', ''));
  $found = FALSE;
  $mode = variable_get('hide_submit_form_exclusion_mode', HIDE_SUBMIT_EXCLUDE_LISTED_FORMS);

  // Prevent known conflicts by adding form ids of known ajax forms
  if (variable_get('hide_submit_fix_known_conflicts', TRUE) && $mode == HIDE_SUBMIT_EXCLUDE_LISTED_FORMS) {

    // shoutbox module
    if (module_exists('shoutbox')) {
      $exclude_forms[] = 'shoutbox_add_form';
      if (_hide_submit_debug_on()) {
        drupal_set_message("hide submit: Possible conflict detected and handled - shoutbox module");
      }
    }
  }
  foreach ($exclude_forms as $exclude_form_id) {
    if ($form_id == $exclude_form_id) {
      $found = TRUE;
      if ($mode == HIDE_SUBMIT_EXCLUDE_LISTED_FORMS) {
        _hide_submit_add_exclude_class($form);
        if (_hide_submit_debug_on()) {
          drupal_set_message("hide_submit: Excluding form " . $exclude_form_id);
        }
      }
    }
  }
  if (!$found && $mode == HIDE_SUBMIT_EXCLUDE_UNLISTED_FORMS) {
    _hide_submit_add_exclude_class($form);
    if (_hide_submit_debug_on()) {
      drupal_set_message("hide_submit: Excluding form " . $form_id);
    }
  }

  // Comment workaround
  // When disable button mode is selected
  // Comments won't submit because disabled button value is not included in the $_POST[] data
  // This workaround should fix this
  if (($form_id == 'comment_form' || preg_match('/node_form$/', $form_id)) && variable_get('hide_submit_script_mode', HIDE_SUBMIT_MODE_HIDE) == HIDE_SUBMIT_MODE_DISABLE) {
    $form['hide_submit_fake_op'] = array(
      '#type' => 'hidden',
      '#value' => "",
    );
  }
}

/**
 * Implements hook_theme().
 */
function hide_submit_theme() {
  $theme = array(
    'hide_submit_images_fieldset' => array(
      'render element' => 'form',
    ),
  );
  return $theme;
}

//------------------------------------------------------------------------

//                        Private functions

//------------------------------------------------------------------------

/**
 * A recursive function to add class to all excluded submit buttons
 */
function _hide_submit_add_exclude_class(&$elements) {

  // Recurse through all children.
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {
      _hide_submit_add_exclude_class($elements[$key]);
    }
  }

  // Add class to submit buttons
  if (isset($elements['#type']) && $elements['#type'] == 'submit') {
    $elements['#attributes']['class'][] = HIDE_SUBMIT_EXCLUDE_CLASS;
  }
}

/**
 * Check if JS should be loaded for current page
 */
function _hide_submit_load_js_check_condition() {
  $js_load_option = variable_get('hide_submit_js_load_option', HIDE_SUBMIT_DEFAULT_JS_LOAD);
  switch ($js_load_option) {
    case HIDE_SUBMIT_IN_LISTED_PAGES:
    case HIDE_SUBMIT_EXCLUDE_LISTED_PAGES:
      $pages = variable_get('hide_submit_js_load_pages', '');
      break;
    case HIDE_SUBMIT_IN_CONTENT_ADD_EDIT:
    default:
      $pages = "node/add/*\r\nnode/*/edit";
      break;
  }
  if ($js_load_option != HIDE_SUBMIT_EXCLUDE_LISTED_PAGES) {
    $pages .= "\r\nadmin/settings/hide-submit";
  }
  $path = drupal_get_path_alias($_GET['q']);
  $regexp = '/^(' . preg_replace(array(
    '/(\\r\\n?|\\n)/',
    '/\\\\\\*/',
    '/(^|\\|)\\\\<front\\\\>($|\\|)/',
  ), array(
    '|',
    '.*',
    '\\1' . preg_quote(variable_get('site_frontpage', 'node'), '/') . '\\2',
  ), preg_quote($pages, '/')) . ')$/';

  // Compare with the internal and path alias (if any).
  $page_match = preg_match($regexp, $path);
  if ($path != $_GET['q']) {
    $page_match = $page_match || preg_match($regexp, $_GET['q']);
  }

  // Do we have a match on our page list?
  if ($js_load_option == HIDE_SUBMIT_EXCLUDE_LISTED_PAGES) {
    return !($page_match == 1);
  }
  return $page_match == 1;
}

/**
 * scrub the format strings for things that make bad javascript.
 *
 * @param $format_string
 * @return
 *   cleaned $format_string
 */
function _hide_submit_clean_for_javascript($format_string = '') {
  $patterns = array(
    '/\\n/',
    '/\\r/',
    '/\'/',
  );
  $replacements = array(
    '<br/>',
    '',
    '`',
  );
  return preg_replace($patterns, $replacements, $format_string);
}

/**
 * get the image according to user selection, default, random etc...
 */
function _hide_submit_get_image() {

  // Use static to assure the same image is returned for the current page
  static $image = FALSE;
  if ($image !== FALSE) {
    return $image;
  }
  if (variable_get('hide_submit_text_only', FALSE)) {
    $image = FALSE;
  }
  else {
    $image = variable_get('hide_submit_image', HIDE_SUBMIT_DEFAULT_IMAGE);
    if (is_array($image)) {
      list($usec, $sec) = explode(' ', microtime());
      srand((double) $sec + (double) $usec * 100000);
      $image = $image[rand() % count($image)];
    }
  }

  // Allow modules to modify the image.
  drupal_alter('hide_submit_image', $image);

  // Theme should be able to play with those settings too.
  // Theme may not be initialized at this time, so we have to force its
  // initialization if $GLOBALS['theme'] is NULL.
  // Because of this initialisation, the module weight is heavy, to let
  // others modules (like theme_keys or section) do their job before.
  if (empty($GLOBALS['theme'])) {
    init_theme();
  }
  $function = $GLOBALS['theme'] . '_hide_submit_image_alter';
  if (function_exists($function)) {
    $function($image);
  }
  return $image;
}

/**
 * Get javascript code for injection
 */
function _hide_submit_get_message() {
  global $language;
  $message = variable_get('hide_submit_message_' . $language->language, "");
  if ($message == "") {
    $message = variable_get('hide_submit_message', HIDE_SUBMIT_DEFAULT_MESSAGE);
  }
  $image = _hide_submit_get_image();
  if ($image !== FALSE) {
    $image = '<img src="' . file_create_url($image) . '" alt="" title=""  /> &nbsp;';
  }
  return '<p class="hide_submit">' . $image . ' ' . $message . ' </p>';
}

/**
 * Get javascript code for injection
 */
function _hide_submit_get_javascript_settings() {

  // Set base path for local image, none for external
  $image = _hide_submit_get_image();
  if ($image !== FALSE) {
    $image = url($image) == $image ? $image : base_path() . $image;
    $image = check_url($image);
  }

  // Assemble jQuery selector
  $selector = 'input:submit:not(.' . HIDE_SUBMIT_EXCLUDE_CLASS . ')';
  $selector .= implode("", explode("\r\n", filter_xss_admin(variable_get('hide_submit_attribute_filter', ''))));

  // Prevent known conflicts by using jquery attributes
  if (variable_get('hide_submit_fix_known_conflicts', TRUE)) {

    // Draft module
    if (module_exists('draft')) {
      $selector .= check_plain('[value!=' . t('Save As Draft') . ']');
      if (_hide_submit_debug_on()) {
        drupal_set_message("hide submit: Possible conflict detected and handled - draft module");
      }
    }
  }
  $mode = variable_get('hide_submit_script_mode', HIDE_SUBMIT_MODE_HIDE) == HIDE_SUBMIT_MODE_HIDE ? "hide" : "disable";
  $settings = array(
    'hide_submit' => array(
      'selector' => $selector,
      'mode' => $mode,
      'keypress' => variable_get('hide_submit_keypress', HIDE_SUBMIT_BUTTON_AND_KEY),
      'dbg' => _hide_submit_debug_on(),
    ),
  );

  // Add Message and image only when needed
  if ($mode == 'hide') {
    $settings['hide_submit']['message'] = filter_xss_admin(_hide_submit_clean_for_javascript(_hide_submit_get_message()));
    $settings['hide_submit']['image'] = $image;
  }
  return $settings;
}

/**
 * Debug mode check
 */
function _hide_submit_debug_on() {
  return variable_get('hide_submit_debug', FALSE) && user_access('administer site configuration');
}