You are here

fasttoggle.module in Fasttoggle 7

Same filename and directory in other branches
  1. 8.3 fasttoggle.module
  2. 5 fasttoggle.module
  3. 6 fasttoggle.module

Enables fast toggling of binary or not so binary settings.

File

fasttoggle.module
View source
<?php

/**
 * @file
 * Enables fast toggling of binary or not so binary settings.
 */
define('FASTTOGGLE_ACCESS_DENIED', -1);
define('FASTTOGGLE_ACCESS_UNDECIDED', 0);
define('FASTTOGGLE_ACCESS_ALLOWED', 1);

/**
 * Implements hook_menu().
 *
 * This function creates the menu links for all fasttoggle items, so modules
 * implementing fasttoggle support don't need to do anything in this area.
 */
function fasttoggle_menu() {
  $items = array();
  $items['admin/config/system/fasttoggle'] = array(
    'title' => 'Fasttoggle',
    'description' => 'Configure what fast toggling options are available.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'fasttoggle_settings_form',
    ),
    'file' => 'fasttoggle.admin.inc',
    'access arguments' => array(
      'administer fasttoggle',
    ),
  );
  $configs = fasttoggle_get_available_links();

  // For each available link, create a menu item.
  // The config for an item can override the object type we're expecting, but
  // the general pattern is /fasttoggle/{obj_type}/{obj_id}/{group}/{instance}.
  if (!empty($configs)) {
    foreach ($configs as $key => $data) {
      $object_type = $key;
      $path_item = "%{$key}";
      if (isset($data['override_object_type'])) {
        if ($data['override_object_type'] > '') {
          $path_item = "%{$data['override_object_type']}";
        }
        else {
          $path_item = "%";
        }
      }
      else {
        if (isset($data['object_type'])) {
          $path_item = "%{$data['object_type']}";
        }
      }
      $path = "fasttoggle/{$object_type}/{$path_item}/%/%";
      $items[$path] = array(
        'title' => 'Toggle',
        'page callback' => 'fasttoggle_do_toggle_option',
        'page arguments' => [
          1,
          2,
          3,
          4,
        ],
        'access callback' => TRUE,
        // Access checking is handled in hook_fasttoggle_allowed_links().
        'type' => MENU_CALLBACK,
        'theme callback' => 'ajax_base_page_theme',
      );
    }
  }
  return $items;
}

/**
 * Implements hook_perm().
 *
 * Individual modules are responsible for implementing any additional
 * permissions, but they should generally use the same permissions
 * that are normally used to access the field, plus any system wide
 * and content-type specific options for en/disabling the links.
 */
function fasttoggle_permission() {
  return array(
    'administer fasttoggle' => array(
      'title' => t('Administer Fasttoggle'),
    ),
  );
}

/**
 * Implements hook_help().
 */
function fasttoggle_help($path, $arg) {
  if ($path == 'admin/config/system/fasttoggle') {
    return t('Configure what fast toggling options are available.');
  }
}

/**
 * Get a value from an object.
 *
 * Get the value of an object's attribute. There are three options:
 *  - A group specific function
 *  - An attribute of the object with the name overridden at instance level
 *  - $object->$instance without modification.
 *
 * @param array $options
 *   An array containing the configuration data for the object type.
 * @param string $group
 *   A string, containing the group to which the attribute belongs.
 * @param string $instance
 *   The name of the attribute being accessed (string).
 * @param object $object
 *   An instance of the object containing the value to be returned.
 *
 * @return mixed
 *   The value of the attribute.
 */
function fasttoggle_get_object_value(array $options, $group, $instance, $object) {
  if (isset($options['fields'][$group]['value_fn'])) {
    return $options['fields'][$group]['value_fn']($options, $group, $instance, $object);
  }
  else {
    if (isset($options['fields'][$group]['instances'][$instance]['value_key'])) {
      return $object->{$options['fields'][$group]['instances'][$instance]['value_key']};
    }
    else {
      return $object->{$instance};
    }
  }
}

/**
 * Add fasttoggle abilities to a link.
 *
 * @param array $option_info
 *   The configuration that controls this link's content and appearance.
 * @param string $group
 *   The group of configuration options to which the link to be rendered
 *   belongs.
 * @param string $instance
 *   The instance name of the link to be rendered.
 * @param object $object
 *   An instance of the object to be modified by the link, used to get the
 *   current value for the link.
 * @param int $format
 *   The type of rendering of the link to provide to the caller.
 * @param string $view
 *   (optional; defaults to NULL) An extra parameter to be passed to an ajax
 *   renderers, telling them how to render a modified object.
 *
 * @return mixed
 *   A complete HTML link, a form API array or a link array structure for use
 *   in hook_link.
 */
function fasttoggle(array $option_info, $group, $instance, $object, $format, $view = NULL) {
  static $sent = FALSE;
  static $label_style = -1;
  if ($label_style == -1) {
    $label_style = variable_get('fasttoggle_label_style', FASTTOGGLE_LABEL_STATUS);
  }
  $current_value = fasttoggle_get_object_value($option_info, $group, $instance, $object);
  $title = $option_info['fields'][$group]['instances'][$instance]['labels'][$label_style][$current_value];
  if (empty($option_info['fields']['callback_fn'])) {
    $view = is_null($view) ? '' : "/{$view}";
    $callback = "fasttoggle/{$option_info['object_type']}/{$object->{$option_info['id_field']}}/{$group}/{$instance}{$view}";
  }
  else {
    $callback = $option_info['fields']['callback_fn']($option_info, $group, $instance, $object, $view);
  }

  // Allow any rewriting of the URL to be done now, so our POST doesn't get
  // turned into a GET.
  if (module_exists('spaces')) {
    $space = spaces_get_space_from_object($option_info['object_type'], $object);
    $options = array();
    if ($space && module_exists('purl')) {
      $original_path = $callback;
      $purl_params = array(
        'query' => drupal_get_query_parameters($_GET, [
          'q',
        ]),
        'purl' => [
          'provider' => "spaces_{$space->type}",
          'id' => $object->{$option_info['id_field']},
          'absolute' => TRUE,
        ],
      );
      $callback = url($callback, $purl_params);

      // Strip the leading slash.
      $callback = substr($callback, 1);
    }
  }
  $token = $group . '_' . $instance . '_' . $object->{$option_info['id_field']};
  $selector_class = 'fasttoggle-status-' . $option_info['object_type'] . '-' . $object->{$option_info['id_field']} . '-' . $group . '-' . $instance;
  $status_class = 'fasttoggle-status-' . $option_info['object_type'] . '-' . $group . '-' . $instance . '-' . $current_value;

  // Only include the support files once.
  if (!$sent) {
    $sent = TRUE;
    drupal_add_library('system', 'drupal.ajax');
    drupal_add_library('system', 'jquery.form');
  }
  $attributes = array(
    'class' => array(
      'fasttoggle',
      'use-ajax',
      $selector_class,
      $status_class,
    ),
    'title' => t('Toggle this setting'),
  );
  $query = drupal_get_destination() + array(
    'token' => drupal_get_token($token),
  );
  switch ($format) {
    case FASTTOGGLE_FORMAT_HTML:
      return l($title, $callback, array(
        'attributes' => $attributes,
        'query' => $query,
      ));
    case FASTTOGGLE_FORMAT_LINK_ARRAY:
      return array(
        'title' => $title,
        'href' => $callback,
        'query' => $query,
        'attributes' => $attributes,
      );
    case FASTTOGGLE_FORMAT_FORM:
      return array(
        '#type' => 'link',
        '#title' => $title,
        '#href' => $callback,
        '#options' => array(
          'query' => $query,
          'attributes' => $attributes,
        ),
      );
  }
}

/**
 * Return a link.
 *
 * @param string $type
 *   The type of object for which a link is wanted.
 * @param object $obj
 *   The instance of the object (eg user ID)
 * @param int $teaser
 *   Whether the link is to a teaser.
 *
 * @return array
 *   The link render array.
 */
function fasttoggle_link($type, $obj, $teaser = FALSE) {
  $links = array();
  $options = fasttoggle_get_allowed_links($type, $obj);
  if (!empty($options)) {
    switch ($type) {
      case 'node':
        foreach (array_keys($options['fields']['status']['instances']) as $key) {
          $links['fasttoggle_' . $key] = fasttoggle($options, 'status', $key, $obj, FASTTOGGLE_FORMAT_LINK_ARRAY);
        }
        break;
      case 'comment':
        fasttoggle_load_comment($obj);
        foreach (array_keys($options['fields']['status']['instances']) as $key) {
          $links['fasttoggle_' . $key] = fasttoggle($options, 'status', $key, $obj, FASTTOGGLE_FORMAT_LINK_ARRAY);
        }
        break;

      // User is not one of the standard types for hook_link(). This
      // use enables adding of user links to a user profile.
      case 'user':
        if (!empty($options['fields']['status']['instances'])) {
          foreach (array_keys($options['fields']['status']['instances']) as $key) {
            $links['fasttoggle_' . $key] = fasttoggle($options, 'status', $key, $obj, FASTTOGGLE_FORMAT_LINK_ARRAY);
          }
        }
        break;
    }
  }
  return $links;
}

/**
 * Get all potential links for an object type.
 *
 * @param string $type
 *   (optional) The object type for which available links are being sought.
 * @param object $obj
 *   (optional) An object instance for which available links are being sought.
 *   The object's subtype (where applicable) may be used to further filter
 *   the links which are returned.
 *
 * @return mixed
 *   If the parameter is NULL, an array containing all available links, indexed
 *   by type, is returned. If $type if not NULL, only the subtree for that type
 *   is returned. In the later case, hooks not supporting the chosen type may
 *   skip the work of doing any calculations or calls, saving time andmemory.
 */
function fasttoggle_get_available_links($type = NULL, $obj = NULL) {
  static $config = array();
  if (empty($config) || !is_null($type) && empty($config[$type])) {

    // The cache is completely empty or the type hasn't been cached.
    if (is_null($type)) {

      // Get all available links.
      $config = module_invoke_all('fasttoggle_available_links');
    }
    else {

      // Just interested in one type.
      $temp = module_invoke_all('fasttoggle_available_links', $type, $obj);
      if (!isset($temp[$type])) {
        $temp[$type] = array();
      }
      if (is_null($obj)) {
        $config[$type] = $temp[$type];
      }
      else {

        // Interested in a particular object. Don't cache.
        return $temp[$type];
      }
    }
  }
  return is_null($type) ? $config : $config[$type];
}

/**
 * Carry out a series of access checks.
 *
 * @param array $checks
 *   The list of checks to perform.
 * @param object $obj
 *   The object being considered.
 * @param string $type
 *   The type of object.
 * @param string $group
 *   The setting group.
 * @param string $instance
 *   The instance of the setting.
 *
 * @return int
 *   The tristate result (ALLOWED / DENIED / UNDECIDED).
 */
function fasttoggle_check_access(array $checks = array(), $obj = NULL, $type = NULL, $group = NULL, $instance = NULL) {
  foreach ($checks as $order => $check) {
    if (!function_exists($check)) {

      // Err on the side of caution, and say why.
      drupal_set_message("Fasttoggle access check function '{$check}' was not found. Denying access to the setting(s) affected.", 'error');
      return FASTTOGGLE_ACCESS_DENIED;
    }
    $result = $check($obj, $type, $group, $instance);
    if ($result != FASTTOGGLE_ACCESS_UNDECIDED) {
      return $result;
    }
  }
  return FASTTOGGLE_ACCESS_UNDECIDED;
}

/**
 * Get the tristate value (DENIED / UNDECIDED) matching a test outcome.
 *
 * @param bool $condition_outcome
 *   The boolean outcome of a test.
 *
 * @return int
 *   The tristate value to be used.
 */
function fasttoggle_deny_access_if($condition_outcome) {
  return $condition_outcome ? FASTTOGGLE_ACCESS_DENIED : FASTTOGGLE_ACCESS_UNDECIDED;
}

/**
 * Get the tristate value (ALLOWED / UNDECIDED) matching a test outcome.
 *
 * @param bool $condition_outcome
 *   The boolean outcome of a test.
 *
 * @return int
 *   The tristate value to be used.
 */
function fasttoggle_allow_access_if($condition_outcome) {
  return $condition_outcome ? FASTTOGGLE_ACCESS_ALLOWED : FASTTOGGLE_ACCESS_UNDECIDED;
}

/**
 * Get the list of links the current user may utilise.
 *
 * I would like to use the object subtype and ID from the object using
 * the config, but that's a chicken and the egg problem - we can't tell
 * get_available_links how to filter the config it returns until we have the
 * config it returns available to look up the field names. And we can't
 * rely on the caller having the requisite knowledge either.
 *
 * Access is applied at each level to a parent and all of its children.
 * In other words, the objectwide settings control all access, group level
 * settings control all settings within a group, and individual setting
 * access functions control access to just that setting.
 *
 * Access is also PAM-like. 'Undecided' means we haven't made a decision one
 * way or the other. If we get through the whole chain and are still on
 * 'Undecided', permission is denied. Any access checker along the list
 * may say 'Denied' or 'Allowed', thereby determining the final outcome.
 *
 * So the final access is determined by:
 * - admin/config/system/fasttoggle settings ("sitewide_current_values")
 * - object type config such as node type settings ("current_values")
 * - access hooks at the objecttype, setting group and individual toggle
 *   level
 *
 * @param string $type
 *   The typeof of object for which allowed links are being sought.
 * @param object $obj
 *   (optional) The object for which allowed links are being sought.
 * @param string $object_id
 *   The unique ID (eg nid, uid) of the object being considered.
 * @param string $setting_prefix
 *   A prefix that might be added instead of "fasttoggle_" - particularly for
 *   items not related to a particular object.
 *
 * @return array
 *   Returns an array of links to which the user has access.
 */
function fasttoggle_get_allowed_links($type, $obj = NULL, $object_id = 0, $setting_prefix = NULL) {
  static $options_cache = array();
  $setting_key = NULL;
  $objectwide_access = FASTTOGGLE_ACCESS_UNDECIDED;

  // Use caching if possible.
  if (empty($options_cache[$type][$object_id])) {

    // Start by getting all available links for the object type.
    $filtering = fasttoggle_get_available_links($type, $obj);

    /* Now begin to do access checks. */

    // Get defaults for objectwide and lower level config.
    $defaults = fasttoggle_defaults_from_config_data($filtering);

    // If there's a toplevel access check function and it fails, nothing is
    // permitted.
    if (isset($filtering['access'])) {
      $objectwide_access = fasttoggle_check_access($filtering['access'], $obj, $type);
    }

    // If access is denied at a objectwide level, we can just clear the array
    // and drop out. If it is allowed at a objectwide level, we still want to
    // remove the options that have been turned off in the objectwide and node
    // type forms.
    if ($objectwide_access === FASTTOGGLE_ACCESS_DENIED) {
      $filtering = array();
    }
    else {
      foreach ($filtering['fields'] as $group_name => $group_data) {
        $groupwide_access = FASTTOGGLE_ACCESS_UNDECIDED;
        if (!isset($sitewide_setting_key) || isset($group_data['#title'])) {

          // Site wide settings.
          $sitewide_setting_key = "fasttoggle_{$type}_{$group_name}_settings";
          $sitewide_current_values = array_filter(variable_get($sitewide_setting_key, $defaults));
        }

        // Get the setting variable, if necessary.
        if (!isset($setting_key) || isset($group_data['#title'])) {

          // More localised settings.
          $sub_type = isset($obj) && isset($filtering['subtype_field']) ? $obj->{$filtering['subtype_field']} : "";

          // For nodes, the setter adds the node type so we need to do so too
          // when getting only.
          if (isset($setting_prefix)) {
            $objfield = $filtering['subtype_field'];
            $setting_key = "{$setting_prefix}_{$type}_{$obj->{$objfield}}";
          }
          else {
            $setting_key = "fasttoggle_{$type}_{$group_name}_settings";
          }
          $current_values = array_filter(variable_get($setting_key, $defaults));

          // Some forms (node_type_admin...) do array_keys on the values saved.
          $temp = array_reverse(array_keys($current_values), TRUE);
          if (array_pop($temp) === 0) {
            $current_values = array_flip($current_values);
          }
          if (isset($filtering['write_key'])) {
            $setting_key = $filtering['write_key'];
          }
        }

        // Access at the group level? Sitewide access being allowed has
        // priority.
        if ($objectwide_access === FASTTOGGLE_ACCESS_UNDECIDED && isset($group_data['access'])) {
          $groupwide_access = fasttoggle_check_access($group_data['access'], $obj, $type, $group_name);
          if ($groupwide_access === FASTTOGGLE_ACCESS_DENIED) {
            unset($filtering['fields'][$group_name]);
            continue;
          }
        }

        // If we reach here, objectwide and groupwide access checks have
        // returned either allowed or undecided. At this point, we need to check
        // whether the settings forms have disabled the individual toggles.
        foreach ($group_data['instances'] as $instance_name => $instance_data) {
          $group_contents =& $filtering['fields'][$group_name]['instances'];

          // Access at the instance level?
          $value_key = "{$group_name}_{$instance_name}";

          // Enabled in sitewide form?
          if (!isset($sitewide_current_values[$value_key])) {
            unset($group_contents[$instance_name]);
            continue;
          }

          // Enabled in group form? (Eg node type)
          if (!isset($current_values[$value_key])) {
            unset($group_contents[$instance_name]);
            continue;
          }

          // The final check - toggle level access? If objectwide or groupwide
          // access is allowed, no check is needed.
          if ($objectwide_access === FASTTOGGLE_ACCESS_UNDECIDED && $groupwide_access === FASTTOGGLE_ACCESS_UNDECIDED) {
            $result = FASTTOGGLE_ACCESS_UNDECIDED;
            if (isset($instance_data['access'])) {
              $result = fasttoggle_check_access($instance_data['access'], $obj, $type, $group_name, $instance_name);
            }

            // If access is not explicitly permitted, it is denied.
            if ($result !== FASTTOGGLE_ACCESS_ALLOWED) {
              unset($group_contents[$instance_name]);
            }
          }
        }
        if (empty($group_contents)) {
          unset($filtering['fields'][$group_name]);
        }
      }
      if (empty($filtering['fields'])) {
        $filtering = array();
      }
    }
    $options_cache[$type][$object_id] = $filtering;
  }
  return $options_cache[$type][$object_id];
}

/**
 * Handle a request to toggle an option.
 *
 * Menu callback. Toggle options for an object if the action is confirmed via
 * POST. Otherwise, display a confirmation form.
 *
 * @param string $object_type
 *   The type of object being handled.
 * @param object $object
 *   An instance of the object to be modified.
 * @param string $group
 *   The group to which the setting being modified belongs.
 * @param string $option
 *   The option being toggled.
 * @param string $view
 *   (Optional) The view of the object to be rendered.
 *
 * @return mixed
 *   If the access check fails, returns MENU_NOT_FOUND (=> 404). If submission
 *   is non-ajax and not confirmed, a confirmation form is displayed. If the
 *   link is ajax or the confirmation is provided via the form, the value is
 *   toggled to the next in the sequence and the function either returns the
 *   ajax for updating the link (and potentially replacing the body of the
 *   rendered object with updated content) or executes drupal_goto() in the
 *   case of the form submission.
 */
function fasttoggle_do_toggle_option($object_type, $object, $group, $option, $view = NULL) {
  global $user;
  $options = fasttoggle_get_allowed_links($object_type, $object);
  $label_style = variable_get('fasttoggle_label_style', FASTTOGGLE_LABEL_STATUS);
  $id_field = $options['id_field'];
  $group_data = $options['fields'][$group];

  // Check if the action is valid. This is essential to ensure the user has
  // access to the action.
  if (!isset($group_data['instances'][$option]) || !isset($_GET['token']) || !drupal_valid_token($_GET['token'], $group . '_' . $option . '_' . $object->{$id_field}, TRUE)) {
    if (isset($_POST['js']) && $_POST['js']) {
      $ajax_commands = array(
        ajax_command_alert(t('Invalid ajax request')),
      );
      echo ajax_render($ajax_commands);
      exit;
    }
    else {
      return MENU_NOT_FOUND;
    }
  }

  // The action is confirmed: either via form submit or via AJAX/POST.
  if (isset($_POST['confirm']) && $_POST['confirm'] || isset($_POST['js']) && $_POST['js']) {
    $selectorClass = 'fasttoggle-status-' . $object_type . '-' . $object->{$id_field} . '-' . $group . '-' . $option;
    $label_data = $group_data['instances'][$option];
    if (isset($group_data['new_value_fn'])) {
      $value = $group_data['new_value_fn']($object, $label_data['value_key']);
    }
    else {
      $current_value = fasttoggle_get_object_value($options, $group, $option, $object);
      $labels = $label_data['labels'][$label_style];

      // Find the current value. We need to cast as strings to ensure
      // we stop at the right value.
      while ((string) key($labels) !== (string) $current_value) {
        next($labels);
      }
      do {

        // Get the next value and wrap back to the start if necessary.
        if (next($labels) === FALSE) {
          reset($labels);
        }

        // Don't stop on the 'unset' value if the field is required.
        if (key($labels) === '[unset]' && !$label_data['optional']) {
          if (next($labels) === FALSE) {
            reset($labels);
          }
        }

        // Go again if this value isn't allowed.
      } while (isset($label_data['allowed_values']) && !in_array((string) key($labels), $label_data['allowed_values']));

      // And get the new value we'll actually use.
      $value = key($labels);
    }

    // Save the new value.
    if (!empty($group_data['save_fn'])) {
      $group_data['save_fn']($options, $group, $option, $value, $object);
    }
    else {
      $options['save_fn']($options, $group, $option, $value, $object);
    }
    $label = $labels[$value];
    $object_label = entity_label($object_type, $object);
    $newClass = 'fasttoggle-status-' . $object_type . '-' . $group . '-' . $option . '-' . $value;
    watchdog('fasttoggle', '@type %title @option toggled to @value.', [
      '@type' => drupal_ucfirst($object_type),
      '%title' => $object_label,
      '@option' => $option == $group ? $option : $group . ' ' . $option,
      '@value' => $label,
    ]);

    // Let other modules respond.
    module_invoke_all('fasttoggle_toggle', $object_type, $object, $group, $option);

    // Output the new status for the updated link text on AJAX changes.
    if (isset($_POST['js']) && $_POST['js']) {
      drupal_add_http_header('Content-Type', 'text/javascript; charset=utf-8');

      // l() (invoked by fasttoggle()) adds the active class to our new link.
      // But the original link doesn't get it added, so clicking the link
      // results in an extra class being applied. Strip the active class here
      // (assuming it's added to the end of the classes, so we get apples for
      // apples.
      $new_link = fasttoggle($options, $group, $option, $object, FASTTOGGLE_FORMAT_HTML, $view);
      $new_link = preg_replace('/class="(.*) active"/', 'class="$1"', $new_link);
      $ajax_commands = array(
        ajax_command_replace('.' . $selectorClass, $new_link),
        ajax_command_invoke('body', 'trigger', [
          'FasttoggleTrigger',
          $newClass,
        ]),
      );
      $params = array(
        'group' => $group,
        'option' => $option,
        'view' => $view,
      );
      drupal_alter('fasttoggle_ajax', $ajax_commands, $object_type, $object, $params);
      echo ajax_render($ajax_commands);
      exit;
    }
    else {
      drupal_goto();
    }
  }
  else {

    // The action is not confirmed. The user came here through a regular link;
    // no AJAX was involved. That means, we need a confirmation form so that
    // we get a POST form.
    return drupal_get_form('fasttoggle_do_option_confirm', $object_type, $object->{$options['title_field']}, $options['fields'][$group]['instances'][$option]['labels'][$label_style][intval(!$object->{$option})]);
  }
}

/**
 * Confirmation form for the option change of a object.
 *
 * @param array $form
 *   The form render array.
 * @param array $form_state
 *   The form state array.
 * @param string $object_type
 *   The type of object being used.
 * @param string $title
 *   The title for the attribute.
 * @param string $option
 *   The potential new value for the object attribute.
 *
 * @return array
 *   The render array for the confirmation dialog.
 */
function fasttoggle_do_option_confirm(array $form, array $form_state, $object_type, $title, $option) {
  return confirm_form([], t('Are you sure you want to set the %obj_type %title to %option?', [
    '%obj_type' => $object_type,
    '%title' => $title,
    '%option' => $option,
  ]), $_GET['destination'] ? $_GET['destination'] : $object_type . '/' . $object->{$id_field}, '', t('Change'), t('Cancel'));
}

/**
 * Get default values from configuration data.
 *
 * @param array $config_data
 *   The configuration data to be checked.
 *
 * @return array
 *   The default values, keyed by group and instance concatenated with an
 *   underscore.
 */
function fasttoggle_defaults_from_config_data(array $config_data) {
  $result = array();
  foreach ($config_data['fields'] as $group_name => $group_data) {
    foreach ($group_data['instances'] as $instance_name => $instance_data) {
      if (isset($instance_data['default']) && $instance_data['default'] !== FALSE) {
        $result["{$group_name}_{$instance_name}"] = $instance_data['default'];
      }
    }
  }
  return $result;
}

/**
 * Implements hook_block_info().
 *
 * Register a block for displaying fasttoggle links.
 */
function fasttoggle_block_info() {
  $blocks['block'] = array(
    'info' => t('Fasttoggle Links'),
    'weight' => -99,
    'status' => 1,
    'region' => 'content',
    'cache' => DRUPAL_NO_CACHE,
  );
  return $blocks;
}

/**
 * Return the list of links after adding a new entry if provided.
 *
 * @param mixed $link
 *   The link.
 * @param string $group
 *   The group to which the link belongs.
 * @param string $key
 *   The key within the group.
 * @param string $instance
 *   The instance of the key.
 *
 * @return array
 *   The list of links so far, grouped by group and key.
 */
function fasttoggle_block_link_register($link = NULL, $group = NULL, $key = NULL, $instance = NULL) {
  static $links = array();
  if (!is_null($link)) {

    // Avoid duplicates by keying by the content.
    if (is_null($group)) {
      $group = '';
    }
    if (is_null($key)) {
      $key = '';
    }
    if (is_null($instance)) {
      $instance = '';
    }
    if (!isset($links[$group])) {
      $links[$group] = array();
    }
    if (!isset($links[$key])) {
      $links[$key] = array();
    }
    $links[$group][$key][$instance] = $link;
  }
  return $links;
}

/**
 * Implements hook_block_view().
 */
function fasttoggle_block_view($delta = '') {
  $items = fasttoggle_block_link_register();
  if (empty($items)) {
    return;
  }
  foreach ($items as $group => $group_content) {
    foreach ($group_content as $key => $key_content) {
      $group_content[$key] = implode("<br />", $key_content);
    }
    $items[$group] = "<div class='fasttoggle-block-key'>" . implode("</div><div class='fasttoggle-block-key'>", $group_content) . "</div>";
  }
  $items = "<div class='fasttoggle-block-group'>" . implode("</div><div class='fasttoggle-block-group'>", $items) . "</div>";
  $block = array(
    'subject' => t('Fasttoggle Links'),
    'content' => [
      '#markup' => theme('container', [
        'element' => [
          '#children' => $items,
          '#attributes' => [
            'class' => 'fasttoggle-block',
          ],
        ],
      ]),
      '#attached' => [
        'css' => [
          drupal_get_path('module', 'fasttoggle') . '/css/fasttoggle.css',
        ],
      ],
    ],
  );
  return $block;
}

Functions

Namesort descending Description
fasttoggle Add fasttoggle abilities to a link.
fasttoggle_allow_access_if Get the tristate value (ALLOWED / UNDECIDED) matching a test outcome.
fasttoggle_block_info Implements hook_block_info().
fasttoggle_block_link_register Return the list of links after adding a new entry if provided.
fasttoggle_block_view Implements hook_block_view().
fasttoggle_check_access Carry out a series of access checks.
fasttoggle_defaults_from_config_data Get default values from configuration data.
fasttoggle_deny_access_if Get the tristate value (DENIED / UNDECIDED) matching a test outcome.
fasttoggle_do_option_confirm Confirmation form for the option change of a object.
fasttoggle_do_toggle_option Handle a request to toggle an option.
fasttoggle_get_allowed_links Get the list of links the current user may utilise.
fasttoggle_get_available_links Get all potential links for an object type.
fasttoggle_get_object_value Get a value from an object.
fasttoggle_help Implements hook_help().
fasttoggle_link Return a link.
fasttoggle_menu Implements hook_menu().
fasttoggle_permission Implements hook_perm().

Constants

Namesort descending Description
FASTTOGGLE_ACCESS_ALLOWED
FASTTOGGLE_ACCESS_DENIED @file Enables fast toggling of binary or not so binary settings.
FASTTOGGLE_ACCESS_UNDECIDED