You are here

values.module in Values 6

Same filename and directory in other branches
  1. 7 values.module

API for managing reusable value sets.

File

values.module
View source
<?php

/**
 * @file
 * API for managing reusable value sets.
 */

/**
 * Implementation of hook_perm().
 */
function values_perm() {
  return array(
    'administer values',
  );
}

/**
 * Implementation of hook_menu().
 */
function values_menu() {
  $items = array();
  $items['admin/content/values'] = array(
    'title' => 'Values',
    'description' => 'Manage reusable value lists.',
    'page callback' => 'values_list',
    'access arguments' => array(
      'administer values',
    ),
  );
  $items['admin/content/values/list'] = array(
    'title' => 'List',
    'page callback' => 'values_list',
    'access arguments' => array(
      'administer values',
    ),
    'weight' => -10,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/content/values/add'] = array(
    'title' => 'Add',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'values_form',
      'add',
    ),
    'access arguments' => array(
      'administer values',
    ),
    'weight' => -9,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/content/values/import'] = array(
    'title' => 'Import',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'values_import_form',
    ),
    'access arguments' => array(
      'administer values',
    ),
    'weight' => -8,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/content/values/%values'] = array(
    'title' => 'Values',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'values_form',
      'edit',
      3,
    ),
    'access arguments' => array(
      'administer values',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/content/values/%values/edit'] = array(
    'title' => 'Edit',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'values_form',
      'edit',
      3,
    ),
    'access arguments' => array(
      'administer values',
    ),
    'weight' => -9,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/content/values/%values/delete'] = array(
    'title' => 'Delete',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'values_delete_confirm',
      3,
    ),
    'access arguments' => array(
      'administer values',
    ),
    'weight' => -8,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/content/values/%values/export'] = array(
    'title' => 'Export',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'values_export_values',
      3,
    ),
    'access arguments' => array(
      'administer values',
    ),
    'weight' => -7,
    'type' => MENU_LOCAL_TASK,
  );
  $items['values/js'] = array(
    'title' => 'Javascript Values Form',
    'page callback' => 'values_form_js',
    'access arguments' => array(
      'administer values',
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Displays a list of existing value sets.
 */
function values_list() {
  if (module_exists('ctools')) {
    ctools_include('export');
  }
  $header = array(
    t('Description'),
    t('Storage'),
    array(
      'data' => t('Operations'),
      'colspan' => 2,
    ),
  );
  $rows = array();

  // Get all the configured value sets and create a nice table
  $values_lists = values_load_all();
  foreach ($values_lists as $values) {

    // Determine database delete operation
    switch ($values->export_type) {
      case EXPORT_IN_CODE:
        $db_delete = FALSE;
        break;
      case EXPORT_IN_CODE | EXPORT_IN_DATABASE:
        $db_delete = t('revert');
        break;
      case EXPORT_IN_DATABASE:
      default:
        $db_delete = t('delete');
        break;
    }

    // Create table row for display
    $rows[] = array(
      $values->description,
      $values->type ? $values->type : t('Normal'),
      l(t('edit'), 'admin/content/values/' . $values->name . '/edit'),
      $db_delete ? l($db_delete, 'admin/content/values/' . $values->name . '/delete') : '',
    );
  }
  return theme_table($header, $rows);
}

/**
 * Form for adding a new value set.
 */
function values_form(&$form_state, $action = 'edit', $values = NULL) {
  if ($form_state['values_count']) {
    $values->data = $form_state['values']['data'];
  }
  else {
    $values = values_load($values, TRUE);
  }
  $form = array();
  $form['action'] = array(
    '#type' => 'value',
    '#value' => $action,
  );

  // Don't change machine names if we're editing a set
  if (isset($values->name)) {
    $form['name'] = array(
      '#type' => 'hidden',
      '#value' => $values->name,
    );
  }
  $form['description'] = array(
    '#type' => 'textfield',
    '#title' => t('Value set description'),
    '#description' => t('This description will appear on the administrative table to tell you what the values are about.'),
    '#default_value' => $values ? $values->description : '',
    '#required' => TRUE,
    '#weight' => -9,
  );

  // Count values
  if (isset($form_state['values_count'])) {
    $values_count = $form_state['values_count'];
  }
  else {
    $values_count = max(2, empty($values->data) ? 2 : count($values->data));
  }

  // Wrapper for values
  $form['values_wrapper'] = array(
    '#tree' => FALSE,
    '#title' => t('Values'),
    '#description' => t('These are the actual values associated with this value set.'),
    '#prefix' => '<div class="clear-block" id="values-value-wrapper">',
    '#suffix' => '</div>',
    '#weight' => -8,
  );

  // Container for value fields
  $form['values_wrapper']['data'] = array(
    '#tree' => TRUE,
    '#prefix' => '<div id="values-values">',
    '#suffix' => '</div>',
    '#theme' => 'values_value_fields',
    '#cache' => TRUE,
  );

  // Add the current values to the form.
  for ($delta = 0; $delta < $values_count; $delta++) {
    $form['values_wrapper']['data'][$delta] = array(
      'value' => array(
        '#type' => 'textfield',
        '#title' => t('Value @n', array(
          '@n' => $delta + 1,
        )),
        '#default_value' => isset($values->data[$delta]['value']) ? $values->data[$delta]['value'] : '',
        '#size' => 4,
        '#maxlength' => 32,
      ),
      'label' => array(
        '#type' => 'textfield',
        '#title' => t('Label for value @n', array(
          '@n' => $delta + 1,
        )),
        '#default_value' => isset($values->data[$delta]['label']) ? $values->data[$delta]['label'] : '',
        '#access' => user_access('administer values'),
      ),
      'weight' => array(
        '#type' => 'weight',
        '#delta' => $values_count,
        '#default_value' => isset($values->data[$delta]['weight']) ? intval($values->data[$delta]['weight']) : $delta,
      ),
    );
  }

  // AHAH-enabled "Add more" button
  $form['values_wrapper']['values_add_more'] = array(
    '#type' => 'submit',
    '#value' => t('Add more'),
    '#description' => t("If the amount of options above isn't enough, click here to add more."),
    '#weight' => 1,
    '#submit' => array(
      'values_add_more_submit',
    ),
    // If no javascript action.
    '#ahah' => array(
      'path' => 'values/js',
      'wrapper' => 'values-values',
      'method' => 'replace',
      'effect' => 'fade',
    ),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
    '#weight' => 10,
  );
  return $form;
}

/**
 * Submit handler to add more values to a value set. This handler is used when
 * javascript is not available. It makes changes to the form state and the
 * entire form is rebuilt during the page reload.
 */
function values_add_more_submit($form, &$form_state) {

  // Make the changes we want to the form state.
  if ($form_state['values']['values_add_more']) {
    $n = $_GET['q'] == 'values/js' ? 1 : 5;
    $form_state['values_count'] = count($form_state['values']['data']) + $n;
  }
}

/**
 * Menu callback for AHAH additions.
 */
function values_form_js() {
  $form_state = array(
    'storage' => NULL,
    'submitted' => FALSE,
  );
  $form_build_id = $_POST['form_build_id'];

  // Get the form from the cache.
  $form = form_get_cache($form_build_id, $form_state);
  $args = $form['#parameters'];
  $form_id = array_shift($args);

  // We will run some of the submit handlers so we need to disable redirecting.
  $form['#redirect'] = FALSE;

  // We need to process the form, prepare for that by setting a few internals
  // variables.
  $form['#post'] = $_POST;
  $form['#programmed'] = FALSE;
  $form_state['post'] = $_POST;

  // Build, validate and if possible, submit the form.
  drupal_process_form($form_id, $form, $form_state);

  // This call recreates the form relying solely on the form_state that the
  // drupal_process_form set up.
  $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);

  // Render the new output.
  $values_form = $form['values_wrapper']['data'];

  // Prevent duplicate wrappers.
  unset($values_form['#prefix'], $values_form['#suffix']);
  $output = theme('status_messages') . drupal_render($values_form);
  drupal_json(array(
    'status' => TRUE,
    'data' => $output,
  ));
}

/**
 * Validates the values form.
 */
function values_form_validate(&$form, &$form_state) {

  // Values name must be unique
  if ($form_state['values']['action'] == 'add') {
    $machine_name = values_machine_name($form_state['values']['description']);
    $values = values_load($machine_name);
    if ($values) {
      form_set_error('name', t('You must use a unique name for this value set.'));
    }
  }
}

/**
 * Submits the values form.
 */
function values_form_submit(&$form, &$form_state) {
  $values = new stdClass();
  $values->name = !isset($form_state['values']['name']) ? values_machine_name($form_state['values']['description']) : $form_state['values']['name'];
  $values->description = $form_state['values']['description'];
  $values->data = array();
  foreach ($form_state['values']['data'] as $value) {
    $values->data[] = array(
      'value' => $value['value'],
      'label' => $value['label'],
      'weight' => $value['weight'],
    );
  }
  values_save($values);
  $form_state['redirect'] = 'admin/content/values';
}

/**
 * Automatically generate a machine name for the values object.
 */
function values_machine_name($description) {
  $machine_name = trim(preg_replace('/[^a-z0-9]+/', '_', strtolower($description)), '_');
  return $machine_name;
}

/**
 * Loads values object from the database.
 */
function values_load($name, $reset = FALSE) {
  if (is_object($name)) {
    $name = $name->name;
  }
  if (module_exists('ctools')) {

    // Try using Chaos tools suite for exporting and caching
    ctools_include('export');
    $values = ctools_export_load_object('values_list', 'names', array(
      $name,
    ));
  }
  else {

    // In the absence of ctools, use our own basic static caching
    static $values = array();
    if ($reset || !isset($values[$name])) {
      $values[$name] = db_fetch_object(db_query("SELECT * FROM {values_list} WHERE name = '%s'", $name));
      if (isset($values[$name]->data)) {
        $values[$name]->data = unserialize($values[$name]->data);
      }
    }
  }
  if ($name && isset($values[$name])) {
    if (is_array($values[$name]->data)) {
      usort($values[$name]->data, 'values_sort_by_weight');
    }
    return $values[$name];
  }
  return FALSE;
}

/**
 * Sort list of values by weight.
 */
function values_sort_by_weight($a, $b) {
  $a_weight = is_array($a) && isset($a['weight']) ? $a['weight'] : 0;
  $b_weight = is_array($b) && isset($b['weight']) ? $b['weight'] : 0;
  if ($a_weight == $b_weight) {
    return 0;
  }
  return $a_weight < $b_weight ? -1 : 1;
}

/**
 * Loads all value sets.
 */
function values_load_all() {
  $values_sets = array();
  if (module_exists('ctools')) {

    // Try using Chaos tools suite for exporting and caching
    ctools_include('export');
    $values_sets = ctools_export_crud_load_all('values_list', TRUE);
    asort($values_sets);
  }
  else {
    $query = db_query('SELECT name FROM {values_list} ORDER BY description ASC');
    while ($values = db_fetch_object($query)) {
      $values_sets[] = values_load($values->name);
    }
  }
  return $values_sets;
}

/**
 * Saves a values object to the database.
 */
function values_save($values) {

  // Delete existing values
  values_delete($values->name);

  // Create a new value set object
  $set = new stdClass();
  $set->name = $values->name;
  $set->description = $values->description;
  usort($values->data, 'values_sort_by_weight');
  $set->data = $values->data;

  // Write to the database
  if ($success = drupal_write_record('values_list', $set)) {
    drupal_set_message(t('Value set !name was saved.', array(
      '!name' => '<em>' . $values->description . '</em>',
    )));
  }
}

/**
 * Confirmation form to delete a value object from the database.
 */
function values_delete_confirm(&$form_state, $values) {
  $form['name'] = array(
    '#type' => 'value',
    '#value' => $values->name,
  );
  $action = $values->export_type & EXPORT_IN_CODE ? 'revert' : 'delete';
  $form['action'] = array(
    '#type' => 'value',
    '#value' => $action,
  );
  return confirm_form($form, t('Are you sure you want to !action !name?', array(
    '!action' => $action,
    '!name' => '<em>' . $values->description . '</em>',
  )), 'admin/content/values', t('This action cannot be undone.'), t('!action', array(
    '!action' => ucfirst($action),
  )), t('Cancel'));
}

/**
 * Calls deletion of a value object.
 */
function values_delete_confirm_submit(&$form, &$form_state) {
  values_delete($form_state['values']['name']);
  $action = $form_state['values']['action'] == 'delete' ? 'deleted' : 'reverted';
  drupal_set_message(t('Value list was !action.', array(
    '!action' => $action,
  )));
  $form_state['redirect'] = 'admin/content/values';
}

/**
 * Deletes a value object from the database.
 */
function values_delete($name) {
  if (is_object($name)) {
    $name = $name->name;
  }
  db_query("DELETE FROM {values_list} WHERE name = '%s'", $name);
}

/**
 * Implementation of hook_theme().
 */
function values_theme() {
  return array(
    'values_value_fields' => array(
      'arguments' => array(
        'form' => NULL,
      ),
    ),
  );
}

/**
 * Theme the admin values form.
 *
 * @ingroup themeable
 */
function theme_values_value_fields($form) {

  // Define table headers
  $headers = array(
    t('Key'),
    t('Label'),
    t('Weight'),
  );

  // Build table rows
  $rows = array();
  foreach (element_children($form) as $key) {

    // No need to print the field title every time
    unset($form[$key]['value']['#title'], $form[$key]['label']['#title']);
    $row = array();
    $row[] = drupal_render($form[$key]['value']);
    $row[] = drupal_render($form[$key]['label']);
    $form[$key]['weight']['#attributes']['class'] = 'values-weight-group';
    $row[] = drupal_render($form[$key]['weight']);
    $rows[] = array(
      'data' => $row,
      'class' => 'draggable',
    );
  }
  drupal_add_css(drupal_get_path('module', 'values') . '/values.css');
  drupal_add_tabledrag('values-value-list', 'order', 'sibling', 'values-weight-group');
  return theme('table', $headers, $rows, array(
    'id' => 'values-value-list',
  )) . drupal_render($form);
}

/**
 * Export a value list and display it in a form.
 */
function values_export_values(&$form_state, $values) {
  if (!module_exists('ctools')) {
    return array(
      'message' => array(
        '#value' => t('For exporting capabilities, please install the !ctools module.', array(
          '!ctools' => l('Chaos tools suite', 'http://drupal.org/project/ctools'),
        )),
      ),
    );
  }
  drupal_set_title(check_plain($values->description));
  $code = values_export($values);
  $lines = substr_count($code, "\n");
  $form['export'] = array(
    '#title' => t('Export data'),
    '#type' => 'textarea',
    '#value' => $code,
    '#rows' => $lines,
    '#description' => t('Copy the export text and paste it into another value list using the import function.'),
  );
  return $form;
}

/**
 * Export a values list.
 */
function values_export($values, $indent = '') {
  ctools_include('export');
  $output = ctools_export_object('values_list', $values, $indent);
  return $output;
}

/**
 * Import a value list
 */
function values_import_form(&$form_state) {
  drupal_set_message(t('Importing a value list with a conflicting name will override it.'), 'warning');
  $form['import_type'] = array(
    '#type' => 'radios',
    '#title' => t('Import Type'),
    '#options' => array(
      'ctools' => 'Ctools Export',
      'values' => 'key|value pairs',
    ),
    '#default_value' => 'ctools',
  );
  $form['import'] = array(
    '#type' => 'textarea',
    '#title' => t('Import data'),
    '#description' => t('Copy the text from an previously exported value list and paste it here.'),
    '#default_value' => isset($form_state['values']['import']) ? $form_state['values']['import'] : '',
    '#rows' => 10,
  );
  $form['import_values'] = array(
    '#type' => 'fieldset',
    '#title' => t('Import Values'),
    '#description' => 'Import a new value set as a flat set of key|value pairs. Useful for migrating from allowed values lists to value sets.',
    '#collapsible' => FALSE,
    '#attributes' => array(
      'id' => 'import-values-wrapper',
    ),
  );
  $form['import_values']['description'] = array(
    '#type' => 'textfield',
    '#title' => t('Value set description'),
    '#description' => t('This description will appear on the administrative table to tell you what the values are about.'),
    '#default_value' => '',
    '#required' => TRUE,
  );

  // Wrapper for values
  $form['import_values']['values'] = array(
    '#type' => 'textarea',
    '#title' => t('Import data'),
    '#description' => t('Enter one value per line. Use the format key|value.'),
    '#default_value' => '',
    '#rows' => 10,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Import'),
  );
  $form['#after_build'] = array(
    'values_import_form_add_js',
  );
  return $form;
}

/**
 * Submit function for value list import.
 */
function values_import_form_submit(&$form, &$form_state) {
  if ($form_state['values']['import_type'] == 'values') {
    $value_set = new stdClass();
    $value_set->name = !isset($form_state['values']['name']) ? values_machine_name($form_state['values']['description']) : $form_state['values']['name'];
    $value_set->description = $form_state['values']['description'];

    // Process the key|value pairs
    $list = explode("\n", $form_state['values']['values']);
    $list = array_map('trim', $list);
    $list = array_filter($list, 'strlen');
    foreach ($list as $position => $text) {
      $value_set->data[$position]['weight'] = $position;

      // Check for an explicit key.
      $matches = array();
      if (preg_match('/(.*)\\|(.*)/', $text, $matches)) {
        $value_set->data[$position]['value'] = $matches[1];
        $value_set->data[$position]['label'] = $matches[2];
      }
    }
  }
  else {
    $code = $form_state['values']['import'] . "\nreturn \$value_set;";
    $value_set = eval($code);
  }
  values_save($value_set);
  $form_state['redirect'] = 'admin/content/values/' . $value_set->name;
}
function values_import_form_add_js($form, &$form_state) {
  drupal_add_js(drupal_get_path('module', 'values') . '/values.js');
  return $form;
}

/**
 * Implementation of hook_ctools_plugin_api().
 *
 * Tell CTools that we support the default_values_list API.
 */
function values_ctools_plugin_api($owner, $api) {
  if ($owner == 'values' && $api == 'default_values_list') {
    return array(
      'version' => 1,
    );
  }
}

Functions

Namesort descending Description
theme_values_value_fields Theme the admin values form.
values_add_more_submit Submit handler to add more values to a value set. This handler is used when javascript is not available. It makes changes to the form state and the entire form is rebuilt during the page reload.
values_ctools_plugin_api Implementation of hook_ctools_plugin_api().
values_delete Deletes a value object from the database.
values_delete_confirm Confirmation form to delete a value object from the database.
values_delete_confirm_submit Calls deletion of a value object.
values_export Export a values list.
values_export_values Export a value list and display it in a form.
values_form Form for adding a new value set.
values_form_js Menu callback for AHAH additions.
values_form_submit Submits the values form.
values_form_validate Validates the values form.
values_import_form Import a value list
values_import_form_add_js
values_import_form_submit Submit function for value list import.
values_list Displays a list of existing value sets.
values_load Loads values object from the database.
values_load_all Loads all value sets.
values_machine_name Automatically generate a machine name for the values object.
values_menu Implementation of hook_menu().
values_perm Implementation of hook_perm().
values_save Saves a values object to the database.
values_sort_by_weight Sort list of values by weight.
values_theme Implementation of hook_theme().