You are here

chosen.module in Chosen 7.3

General functions and hook implementations.

File

chosen.module
View source
<?php

/**
 * @file
 * General functions and hook implementations.
 *
 * @see http://harvesthq.github.com/chosen/
 */

/**
 * Define chosen library url
 */
define('CHOSEN_WEBSITE_URL', 'http://harvesthq.github.com/chosen');

/**
 * Widget that Chosen can be applied to
 */
define('CHOSEN_WIDGETS', serialize(array(
  'options_select',
  'date_select',
)));

/**
 * Implements hook_ctools_plugin_directory().
 *
 * @see hook_ctools_plugin_directory
 */
function chosen_ctools_plugin_directory($module, $plugin) {
  if ($module == 'chosen' && !empty($plugin)) {
    return "plugins/{$plugin}";
  }
}

/**
 * Implements hook_ctools_plugin_type().
 *
 * @see hook_ctools_plugin_type
 */
function chosen_ctools_plugin_type() {
  $plugins['library'] = array(
    'cache' => FALSE,
    'use hooks' => FALSE,
    'classes' => array(
      'class',
    ),
  );
  return $plugins;
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Override chosen_admin_settings submit callback with custom
 * chosen_admin_settings_form_submit() function.
 *
 * @see hook_form_FORM_ID_alter
 */
function chosen_form_chosen_admin_settings_alter(&$form, &$form_state, $form_id) {
  unset($form['#submit']);
  $form['#submit'][] = 'chosen_admin_settings_form_submit';
}

/**
 * Implementation of chosen_admin_settings_form_submit().
 *
 * Callback function for chosen_admin_settings().
 * Save the submitted values by plugin type.
 *
 * @see settings_form_submit
 * @see _chosen_devide_array_by_plugin
 */
function chosen_admin_settings_form_submit($form, &$form_state) {

  // Exclude unnecessary elements.
  form_state_values_clean($form_state);
  $values = _chosen_devide_array_by_plugin($form_state['values']);

  // Save values by plugin.
  foreach ($values as $plugin_name => $values) {
    variable_set('chosen_' . $plugin_name, $values);
  }
  drupal_set_message(t('The configuration options have been saved.'));
}

/**
 * Implementation of _chosen_devide_array_by_plugin().
 *
 * Helperfunction to seperate fields by plugin_name.
 *
 * @param $flat_array
 *    Array containing a flat array. The keys needs to contain the plugin_name
 *    in front of the original key. It will be stripped of in this process.
 *
 * @return $values
 *    Array containing the flat elements, sorted by plugin_name without the
 *    plugin_name in front of the key anymore.
 */
function _chosen_devide_array_by_plugin($flat_array) {
  $values = array();
  foreach ($flat_array as $key => $value) {
    foreach (ctools_get_plugins('chosen', 'library') as $plugin_name => $plugin_settings) {
      if (substr($key, 0, strlen($plugin_name)) == $plugin_name) {
        $field_name = substr($key, strlen($plugin_name) + 1);
        $values[$plugin_name][$field_name] = $value;
      }
    }
  }
  return $values;
}

/**
 * Implements hook_menu().
 */
function chosen_menu() {
  $items = array();
  $items['admin/config/user-interface/chosen'] = array(
    'title' => 'Chosen',
    'description' => 'Configuration for Chosen plugin',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'chosen_admin_settings',
    ),
    'access arguments' => array(
      'administer site configuration',
    ),
    'file' => 'chosen.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Attach the generated formular to the forminstance to make the field save our
 * values in it's instance data column.
 *
 * @see hook_field_widget_settings_form
 *
 * TODO: Replace CHOSEN_WIDGET constant by supported widgets by plugin.
 */
function chosen_form_field_ui_field_edit_form_alter(&$form, $form_state) {
  if (isset($form['instance']['widget']['type']['#value'])) {
    $type = $form['instance']['widget']['type']['#value'];
    if (in_array($type, unserialize(CHOSEN_WIDGETS))) {
      $field = $form['#field'];
      $instance = field_info_instance($form['instance']['entity_type']['#value'], $form['instance']['field_name']['#value'], $form['instance']['bundle']['#value']);
      $form['instance'] += chosen_field_widget_settings_form($instance);
    }
  }
}

/**
 * Implementation of chosen_field_widget_settings_form().
 *
 * Provides the form array for the field configuration.
 *
 * @param $instance
 *    Field instance definition provided by field_info_instance().
 *
 * @return $form
 *    A renderable form array containing the chosen plugin options.
 */
function chosen_field_widget_settings_form($instance) {
  $widget = $instance['widget'];
  $settings = $widget['settings'];
  $form['chosen'] = array(
    '#type' => 'fieldset',
    '#title' => t('Chosen'),
    '#collapsed' => TRUE,
    '#collapsible' => TRUE,
    '#parents' => array(
      'instance',
      'widget',
      'settings',
    ),
  );
  $form['chosen']['apply_chosen'] = array(
    '#type' => 'checkbox',
    '#title' => t('Apply Chosen on this field'),
    '#default_value' => isset($settings['apply_chosen']) ? $settings['apply_chosen'] : FALSE,
  );
  $form['chosen']['override'] = array(
    '#type' => 'checkbox',
    '#title' => t('Override chosen default settings'),
    '#default_value' => isset($settings['override']) ? $settings['override'] : FALSE,
  );
  $form['chosen']['chosen_wrapper'] = array(
    '#type' => 'fieldset',
    '#title' => t('Chosen settings'),
    '#collapsed' => TRUE,
    '#collapsible' => TRUE,
    '#weight' => 20,
    '#states' => array(
      'visible' => array(
        ':input[name="instance[widget][settings][override]"]' => array(
          'checked' => TRUE,
        ),
      ),
    ),
  );
  $default_values = _chosen_flattern_default_values($instance['chosen_wrapper']);
  $default_values = _chosen_devide_array_by_plugin($default_values);
  $settings_form = chosen_generate_forms($default_values);
  $form['chosen']['chosen_wrapper'] += $settings_form;
  return $form;
}

/**
 * Implementation of _chosen_flattern_default_values().
 *
 * Helperfunction to build a one dimensional array of all fields.
 *
 * @param $default_values
 *    A plugin sepperated array of fields.
 *
 * @return $flattern_values
 *    All fields in a one dimensional array.
 */
function _chosen_flattern_default_values($default_values) {
  $flattern_values = array();
  foreach ($default_values as $key => $value) {
    if (!is_array($value)) {
      $flattern_values[$key] = $value;

      // value
    }
    else {
      $flattern_values = array_merge($flattern_values, _chosen_flattern_default_values($value));
    }
  }
  return $flattern_values;
}

/**
 * Implements hook_field_widget_form_alter().
 *
 * TODO: This needs to be improved. This is maybe not the correct way.
 * TODO: When we replace the CHOSEN_WIDGETS constant we have to adjust this
 *       function.
 */
function chosen_field_widget_form_alter(&$element, &$form_state, $context) {
  $type = $context['instance']['widget']['type'];
  if (in_array($type, unserialize(CHOSEN_WIDGETS))) {
    $settings = $context['instance']['widget']['settings'];
    if (!empty($settings['apply_chosen'])) {
      $element['#attributes']['class'][] = 'chosen-widget';
    }
  }
}

/**
 * Implements hook_library().
 *
 * Register the chosen libraries, provided by the chosen plugins with all it's
 * custom options. The options need to be extracted from the plugin options
 * formular if no values have yet been saved, otherwise from the chosen_plugin
 * variable.
 */
function chosen_library() {
  ctools_include('plugins');
  foreach (ctools_get_plugins('chosen', 'library') as $plugin_name => $plugin_settings) {
    $plugin_class_name = ctools_plugin_load_class('chosen', 'library', $plugin_name, 'class');
    $plugin = new $plugin_class_name();
    $library_path = module_exists('libraries') ? libraries_get_path($plugin->library_name) : 'sites/all/libraries/' . $plugin->directory;

    // Registerl the library.
    $info[$plugin->library_name] = array(
      'title' => $plugin->title,
      'website' => $plugin->website,
      'version' => $plugin->version,
      'js' => array(
        $library_path . '/' . $plugin->file_name => array(
          'group' => 'JS_LIBRARY',
        ),
      ),
    );

    // TODO: Make css optional and make it possible to have multiple css files attached.
    //       Perhaps it would make sense to allow multiple js files too?
    if (variable_get('chosen_use_theme', TRUE)) {
      $info['chosen']['css'] = array(
        $library_path . '/' . $plugin->css => array(),
      );
    }

    // TODO: Refactor this.
    $values = array();
    $value = variable_get('chosen_' . $plugin_name);
    if (!empty($value)) {
      $values = variable_get('chosen_' . $plugin_name);
    }
    else {
      $options = _chosen_flattern_options($plugin
        ->options());
      foreach ($options as $key => $value) {

        // TODO: recursiv.
        $values[$key] = $value['#default_value'];
      }
    }

    // Provide settings for the library.
    // TODO: Get the settings from the plugin.
    $module_path = drupal_get_path('module', 'chosen');
    $info['drupal.' . $plugin_name] = array(
      'title' => 'Drupal Chosen integration',
      'website' => 'http://drupal.org/project/chosen',
      'version' => '1.0',
      'js' => array(
        $module_path . '/chosen.js' => array(),
        array(
          'data' => array(
            'chosen' => array(
              'selector' => variable_get('chosen_jquery_selector', 'select:visible'),
              'minimum_single' => variable_get('chosen_minimum_single', 20),
              'minimum_multiple' => variable_get('chosen_minimum_multiple', 20),
              'minimum_width' => variable_get('chosen_minimum_width', 200),
              'search_contains' => variable_get('chosen_search_contains', FALSE) ? TRUE : FALSE,
              'disable_search' => variable_get('chosen_disable_search', FALSE) ? TRUE : FALSE,
              'disable_search_threshold' => variable_get('chosen_disable_search_threshold', 0),
              'placeholder_text_multiple' => t(variable_get('chosen_placeholder_text_multiple', 'Choose some options')),
              'placeholder_text_single' => t(variable_get('chosen_placeholder_text_single', 'Choose an option')),
              'no_results_text' => t(variable_get('chosen_no_results_text', 'No results match')),
            ),
          ),
          'type' => 'setting',
        ),
      ),
      'css' => array(
        // TODO: Not sure about that.
        $module_path . '/css/chosen-drupal.css' => array(),
      ),
      'dependencies' => array(
        array(
          'chosen',
          $plugin->library_name,
        ),
      ),
    );
  }
  return $info;
}

/**
 * Implementation of _chosen_flattern_options()
 *
 * This function collects all field options out of a form array and generates a
 * one dimensional array.
 *
 * @param $options
 *    The form array provided by a plugin.
 *
 * @return
 *    The flat array of all fields.
 */
function _chosen_flattern_options($options) {
  $flat_array = array();
  foreach ($options as $key => $value) {
    if (isset($value['#default_value'])) {
      $flat_array[$key] = $value;
    }
    elseif (is_array($value)) {
      $flat_array = array_merge($flat_array, _chosen_flattern_options($value));
    }
  }
  return $flat_array;
}

/**
 * Implements hook_element_info_alter().
 *
 * Adds a prerender Function which will be called to supported widgets in order
 * to be able to provide settings per fieldinstance.
 *
 * TODO: This has to be generated out of the plugins.
 */
function chosen_element_info_alter(&$info) {
  $info['select']['#pre_render'][] = 'chosen_pre_render_select';
  if (module_exists('date')) {
    $info['date_combo']['#pre_render'][] = 'chosen_pre_render_select';
  }
}

/**
 * Implementation of chosen_pre_render_select().
 *
 * Atteches the library to the field and pass the fieldinstance related options.
 *
 * @see pre_render
 *
 * TODO: Get the field specific configuration.
 * TODO: Pass the correct cpnfiguration.
 */
function chosen_pre_render_select($element) {
  $field_info = isset($element['#field_name']) ? field_info_field($element['#field_name']) : FALSE;
  $element_name = $element['#name'];
  if (!empty($element['#multiple'])) {

    // We need to add those brackets for multivalue fields.
    $element_name = $element['#name'] . '[]';
  }
  $max_selected_options = FALSE;
  if (isset($field_info['cardinality'])) {
    if ($field_info['cardinality'] != FIELD_CARDINALITY_UNLIMITED) {
      $max_selected_options = $field_info['cardinality'];
    }
  }
  $element['#attached']['library'][] = array(
    'chosen',
    'drupal.chosen',
  );
  $element['#attached']['js'][] = array(
    'data' => array(
      'chosen' => array(
        'multiple' => array(
          $element_name => isset($element['#multiple']) ? $element['#multiple'] : FALSE,
        ),
        'max_selected_options' => array(
          $element_name => $max_selected_options,
        ),
      ),
    ),
    'type' => 'setting',
  );
  return $element;
}

/**
 * Implementation of chosen_get_chosen_path().
 *
 * Get the location of the chosen library.
 *
 * @return
 *   The location of the library, or FALSE if the library isn't installed.
 *
 * TODO: support library by plugin.
 */
function chosen_get_chosen_path() {
  $path = FALSE;
  if (function_exists('libraries_get_path')) {
    $path = libraries_get_path('chosen');
    if (!file_exists($path)) {
      $path = FALSE;
    }
  }
  elseif (file_exists('sites/all/libraries/chosen/chosen.jquery.min.js')) {
    $path = 'sites/all/libraries/chosen';
  }
  return $path;
}

/**
 * Implementation of chosen_generate_forms().
 *
 * Generates a completed configuration form out of the plugin options.
 *
 * @param $default_values
 *    The default values which will be present in the form.
 *
 * @return
 *    The completed form.
 */
function chosen_generate_forms($default_values) {
  ctools_include('plugins');
  foreach (ctools_get_plugins('chosen', 'library') as $plugin => $plugin_settings) {
    $chosen_plugins[$plugin] = $plugin_settings['name'];
    $chosen_descriptions[$plugin] = $plugin_settings['description'];
  }
  $form['chosen_plugin'] = array(
    '#type' => 'select',
    '#title' => t('Select library chosen should use'),
    '#options' => $chosen_plugins,
    '#default_value' => isset($default_values['chosen']['plugin']) ? $default_values['chosen']['plugin'] : 'chosen',
    '#description' => $chosen_descriptions[variable_get('chosen_active_plugin', 'chosen')],
    '#weight' => 20,
  );
  foreach (ctools_get_plugins('chosen', 'library') as $plugin_name => $plugin_settings) {
    $plugin_class_name = ctools_plugin_load_class('chosen', 'library', $plugin_name, 'class');
    $plugin = new $plugin_class_name();
    $form[$plugin_name] = array(
      '#type' => 'fieldset',
      '#title' => t($plugin_settings['name'] . ' options'),
      '#states' => array(
        'visible' => array(
          'select[name="chosen_plugin"]' => array(
            'value' => $plugin_name,
          ),
          'select[name="instance[widget][settings][chosen_wrapper][chosen_plugin]"]' => array(
            'value' => $plugin_name,
          ),
        ),
      ),
    );
    $fields = _chosen_add_plugin_prefix($plugin
      ->options($default_values[$plugin_name]), $plugin_name);
    foreach ($fields as $option => $values) {

      // Iterate through $values, check if it has ?
      $form[$plugin_name][$option] = $values;
      $form[$plugin_name]['#weight'] = 21;
    }
  }
  return $form;
}

/**
 * Implementation of _chosen_add_plugin_prefix().
 *
 * Helperfunction to refactor the fields array. Adds plugin namespaces to the
 * fields. To be able to group them later on.
 *
 * @param $array
 *    Field array.
 *
 * @param $plugin_name
 *    The actual plugin.
 *
 * @return $new_array
 *    Array with plugin namespaces for the fields.
 */
function _chosen_add_plugin_prefix($array, $plugin_name) {
  foreach ($array as $key => $value) {
    if (isset($value['#default_value'])) {

      // Is field.
      $new_array[$plugin_name . '_' . $key] = $value;
    }
    else {
      if (!is_array($value)) {

        // Is value.
        $new_array[$key] = $value;
      }
      else {

        // Is child.
        $new_array[$key] = _chosen_add_plugin_prefix($value, $plugin_name);
      }
    }
  }
  return $new_array;
}