You are here

ds.module in Display Suite 6.2

Core functions for the Display Suite module.

File

ds.module
View source
<?php

/**
 * @file
 * Core functions for the Display Suite module.
 */

/**
 * Constants for settings status.
 */
define('DS_SETTINGS_UI', 1);
define('DS_SETTINGS_DEFAULT', 2);
define('DS_SETTINGS_OVERRIDDEN', 3);

/**
 * Rendering pipelines
 */
define('DS_RENDER_DEFAULT', 'default');
define('DS_RENDER_DRUPAL', 'drupal');
define('DS_RENDER_CCK', 'cck');
define('DS_RENDER_NON_DS', 'other');

/**
 * Constants for field types.
 */
define('DS_FIELD_TYPE_NON_DS', 0);
define('DS_FIELD_TYPE_THEME', 1);
define('DS_FIELD_TYPE_FUNCTION', 2);
define('DS_FIELD_TYPE_PREPROCESS', 3);
define('DS_FIELD_TYPE_IGNORE', 4);
define('DS_FIELD_TYPE_CODE', 5);
define('DS_FIELD_TYPE_BLOCK', 6);
define('DS_FIELD_TYPE_GROUP', 7);
define('DS_FIELD_TYPE_MULTIGROUP', 8);

/**
 * Constants for field statuses.
 */
define('DS_FIELD_STATUS_STATIC', 1);
define('DS_FIELD_STATUS_DEFAULT', 2);
define('DS_FIELD_STATUS_CUSTOM', 3);
define('DS_FIELD_STATUS_OVERRIDDEN', 4);

/**
 * Constants for block fields rendering.
 */
define('DS_BLOCK_TEMPLATE', 1);
define('DS_BLOCK_TITLE_CONTENT', 2);
define('DS_BLOCK_CONTENT', 3);

/**
 * Constants for content field default values
 */
define('DS_DEFAULT_REGION', 'disabled');
define('DS_DISABLED_REGION', 'disabled');
define('DS_DEFAULT_FORMAT', 'default');
define('DS_DEFAULT_LABEL_FORMAT', 'hidden');
define('DS_DEFAULT_WEIGHT', -19);

/**
 * Constants for theme callback defaults
 */
define('DS_DEFAULT_THEME_REGIONS', 'ds_regions');
define('DS_DEFAULT_THEME_FIELD', 'ds_field');
define('DS_DEFAULT_THEME_FIELDSET', 'ds_group_fieldset_open');

/**
 * Constants for menu paths
 */
define('DS_PATH_BASE', 'admin/build/ds');
define('DS_PATH_LAYOUT', 'admin/build/ds/layout');
define('DS_PATH_TOOLS', 'admin/build/ds/tools');
define('DS_PATH_STYLES', 'admin/build/ds/styles');
define('DS_PATH_MODULES', 'admin/build/ds/modules');

/**
 * Implements hook_theme().
 */
function ds_theme() {
  require_once 'includes/ds.registry.inc';
  return _ds_theme();
}

/**
 * Implements hook_views_api().
 */
function ds_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'ds') . '/views',
  );
}

/**
 * Implements hook_ds_plugins().
 */
function ds_ds_plugins() {
  require_once 'includes/ds.registry.inc';
  return _ds_plugins();
}

/**
 * Implements hook_features_api().
 */
function ds_features_api() {
  require_once 'includes/ds.features.inc';
  return _ds_features_api();
}

/**
 * Implements hook_flush_caches().
 */
function ds_flush_caches() {

  // Reset fields cache.
  ds_reset_fields_cache();

  // Import default settings.
  ds_import_default_data();
  return array();
}

/**
 * Implements hook_block().
 */
function ds_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
      require_once 'includes/ds.registry.inc';
      return _ds_block_list();
    case 'view':
      $content = array();
      $ds_blocks = variable_get('ds_blocks', array());
      if (isset($ds_blocks[$delta])) {
        $info = $ds_blocks[$delta];
        $data = $info['data'];
        if (isset($data['filename']) && isset($data['class'])) {
          require_once $data['filename'];
          $class = $data['class'];
          $plugin = new $class();
          if (method_exists($plugin, 'block_view')) {
            $content = $plugin
              ->block_view($info);
          }
        }
      }
      return $content;
  }
}

/**
 * Module-aware version of variable_get.
 *
 * This searches through several modules trying to find a set variable,
 * eventually defaulting to core. It also checks the unreleased 'variable'
 * module, which is currently only available by building from its repository.
 *
 * @param string $var_name
 *   The variable to find
 * @param string $default
 *   (Optional) An optional default value
 */
function ds_variable_get($var_name, $default = '') {
  if (ds_static_variables($var_name)) {
    $value = ds_static_variables($var_name);
  }
  elseif (module_exists('variable') && function_exists('variable_get_value')) {
    global $language;
    $value = variable_get_value($var_name, array(
      'default' => $default,
      'language' => $language,
    ));
  }
  else {
    $value = variable_get($var_name, $default);
  }
  return $value;
}

/**
 * Set or get static variables at runtime.
 */
function ds_static_variables($key, $data = NULL) {
  static $variables = array();
  if (!isset($data) && isset($variables[$key])) {
    return $variables[$key];
  }
  elseif (isset($data)) {
    if (!isset($variables[$key])) {
      $variables[$key] = array();
    }
    $variables[$key][] = $data;
  }
}

/**
 * Return API info about a module and type.
 *
 * @param string $module
 *    The module to get the API info from.
 * @param string $type_name
 *    (Optional) The object type name.
 */
function ds_api_info($module, $type_name = 'all') {
  static $api_info = array();
  if (!isset($api_info[$module][$type_name])) {

    // Gather information.
    $api_data = module_invoke($module, 'ds_api');
    if (!empty($api_data)) {
      $api_info[$module][$type_name] = $api_data;
      if (!isset($api_info[$module][$type_name]['extra'])) {
        $api_info[$module][$type_name]['extra'] = array();
      }

      // Extra info needed or not.
      if ($type_name != 'all' && !empty($api_data['extra'])) {
        $extra = array();
        $types = $api_data['types']();
        $type_info = $types[$type_name];
        foreach ($api_data['extra'] as $key) {
          $extra[$key] = $type_info->{$key};
        }
        $api_info[$module][$type_name]['extra'] = $extra;
      }
    }
  }
  return $api_info[$module][$type_name];
}

/**
 * API function to build and render a build mode for a given object
 *
 * This is not used internally by ds, but can be called by other modules
 * wishing to call a build mode programatically.
 *
 * Note that ds normally steps in during the normal build process and expects
 * certain values to be available in $vars as a result, so you will need to
 * determine what those properties are and provide them, or ensure that all
 * your fields do not require values from that array. In a lot of cases, calling
 * this function outside of a page load workflow will fail as a result.
 *
 * @param $key
 *  Key of the object, e.g. NID
 * @param $object
 *  the object to be manipulated
 * @param $module
 *  the ds module providing the display
 * @param $vars
 *  an array of values to use in the render
 * @param $build_mode (optional)
 *  the build mode to use
 *
 * @return
 *  a string containing the fully rendered display
 */
function ds_make($key, $module, &$object, &$vars, $build_mode = NULL) {
  if (isset($build_mode) && !is_null($build_mode)) {
    $object->build_mode = $build_mode;
  }
  $data = array(
    'object' => $object,
    'module' => $module,
    'vars' => $vars,
  );
  drupal_alter('ds_render', $data);
  ds_build_fields_and_regions($data['object'], $module);
  $render = ds_render($key, $data['object'], $module, $data['vars']);
  return $render;
}

/**
 * API function to return rendered content for an item
 *
 * @param string $key
 *  A key for the object, e.g. an NID
 * @param stdClass $object
 *  The object to manipulate, e.g. a $node object
 * @param string $module
 *  The name of the module requesting the render
 * @param array $vars
 *  The variables required for rendering
 *
 * @return
 *  a string containing the fully rendered display
 */
function ds_render($key, $object, $module, $vars) {
  static $renders = array();
  if (isset($renders[$module][$object->build_mode][$key])) {
    return $renders[$module][$object->build_mode][$key];
  }

  // ds_render_content in this context is correct.
  // The function is marked deprecated as THIS function is the
  // replacement.
  $renders[$module][$object->build_mode][$key] = ds_render_content($object, $module, $vars);
  return $renders[$module][$object->build_mode][$key];
}

/**
 * Function to reset fields cache.
 */
function ds_reset_fields_cache() {
  db_query("UPDATE {ds_settings} set fields = ''");
}

/**
 * Get fields and regions for an object.
 *
 * @param object $object
 *   The object to manipulate.
 * @param string $module
 *   The module that is requesting.
 */
function ds_build_fields_and_regions(&$object, $module) {

  // API info for this module and type.
  $api_info = ds_api_info($module, $object->type);

  // See if rendering is needed later on.
  // There are two ways of excluding: global exclude on object type
  // or per build mode per object type.
  // We don't return here, because we want to add all fields on the object
  // so themers can use it in their template.
  $exclude_build_modes = variable_get($module . '_buildmodes_exclude', array());
  $object->render_by_ds = isset($exclude_build_modes[$object->type][$object->build_mode]) && $exclude_build_modes[$object->type][$object->build_mode] == TRUE || variable_get($module . '_type_' . $object->type, FALSE) == TRUE ? FALSE : TRUE;

  // Setup the object.
  $object->regions = array();
  $object->ds_fields = array();
  $object->ds_groups = array();
  $object->preprocess_fields = array();

  // Get settings for this display/build mode combination.
  $display_settings = ds_get_settings($module, $object->type, $object->build_mode);
  if (!empty($display_settings['fields'])) {

    // Get all fields and settings for this build mode (this doesnt load
    // CCK fields, only groups).
    $available_fields = ds_get_fields($module, $object->type, $object->build_mode, $api_info['extra']);

    // The node Body can sometimes be populated with other content when empty
    // @todo - we do this here so the fields populate correctly, but find
    // somewhere logical for this.
    if (isset($object->has_empty_body) && $object->has_empty_body == 1) {
      $object->body = '';
    }

    // Iterate over fields listed on the display.
    foreach ($display_settings['fields'] as $field_key => $field_defaults) {

      // Don't render fields which are set to disabled.
      $region = isset($field_defaults['region']) ? $field_defaults['region'] : DS_DEFAULT_REGION;
      if ($region != DS_DISABLED_REGION) {

        // @todo Settings per field should be retrieved from a single
        // cached function call to ds_get_settings (or similar).
        if (isset($available_fields[$field_key])) {
          $field_settings = $available_fields[$field_key];
          if (!isset($field_settings['pipeline'])) {
            $field_settings['pipeline'] = DS_RENDER_DEFAULT;
          }
        }
        else {
          $field_settings = array();
          $field_settings['pipeline'] = DS_RENDER_NON_DS;
        }
        $field_settings = array_merge($field_defaults, $field_settings);

        // Additional field properties (which should be included a normalised
        // settings function).
        $field_settings['field_type'] = isset($field_settings['type']) ? $field_settings['type'] : DS_FIELD_TYPE_NON_DS;

        // fix key collision
        $field_settings['module'] = $module;
        $field_settings['object'] =& $object;
        $field_settings['object_type'] = $api_info['object'];

        // end @todo
        // Build the field.
        $object->ds_fields[$field_key] = ds_build_field($field_key, $field_settings);

        // Process the field into a group or region as required.
        if (!empty($object->ds_fields[$field_key]['parent'])) {
          $parent = $object->ds_fields[$field_key]['parent'];
          $object->ds_groups[$parent][$field_key] = $field_settings['weight'];
        }
        else {
          $object->regions[$region][$field_key] = $field_settings['weight'];
        }
      }
    }
  }

  // Reset render_by_ds property if needed.
  if (empty($object->regions)) {
    $object->render_by_ds = FALSE;
  }
}

/**
 * Build an individual field value.
 *
 * Prepares a field for ds to pass to ds_render_content. This does not resolve
 * parent relationships.
 *
 * @param string $field_key
 *   The field to build
 * @param array $field_settings
 *   An array of field settings
 *
 * @return array
 *   A field settings array ready to pass to ds_render_content
 */
function ds_build_field($field_key, $field_settings) {

  // Default class and extra class from the UI.
  $classes = array();
  $classes[] = 'field-' . strtr($field_key, '_', '-');
  if (isset($field_settings['properties']['css-class']) && !empty($field_settings['properties']['css-class'])) {
    $classes[] = $field_settings['properties']['css-class'];
    unset($field_settings['properties']['css-class']);
  }
  if (isset($field_settings['css-class']) && !empty($field_settings['css-class'])) {
    $classes[] = $field_settings['css-class'];
    unset($field_settings['css-class']);
  }

  // Field defaults - all fields get these.
  // @todo Abstract field types into config functions which returns defaults for
  // that type.
  $field_defaults = array(
    'labelformat' => DS_DEFAULT_LABEL_FORMAT,
    'label' => '',
    'theme' => DS_DEFAULT_THEME_FIELD,
    'weight' => DS_DEFAULT_WEIGHT,
    'content' => NULL,
  );

  // Merge defaults and settings to produce the field array.
  $field = array_merge($field_defaults, $field_settings);
  $field['key'] = $field_key;
  $field['type'] = empty($field_settings) ? 'other' : 'ds';

  // Check for weight in region and parent (if any). If a parent key is found,
  // we'll unset the original field from the region it might be set in and
  // we'll add that field to the group array.
  if (isset($field_settings['weight'])) {
    $field['weight'] = $field_settings['weight'];
  }
  $field['parent'] = isset($field_settings['parent']) ? $field_settings['parent'] : NULL;

  // Process groups (fieldsets).
  if ($field['field_type'] == DS_FIELD_TYPE_GROUP || $field['field_type'] == DS_FIELD_TYPE_MULTIGROUP) {
    if (isset($field_settings['format'])) {
      $field['theme'] = $field_settings['format'];
    }
    else {
      $field['theme'] = DS_DEFAULT_THEME_FIELDSET;
    }

    // Additional formatting settings for fieldsets.
    if ($field_settings['field_type'] == DS_FIELD_TYPE_GROUP) {
      $classes[] = 'field-group';
    }

    // Additional formatting settings for CCK multigroups.
    if ($field_settings['field_type'] == DS_FIELD_TYPE_MULTIGROUP) {
      $field['subgroup_theme'] = isset($field_settings['subgroup_format']) ? $field_settings['subgroup_format'] : DS_DEFAULT_THEME_FIELDSET;
      $classes[] = 'field-multigroup';
    }
  }
  $field['class'] = implode(' ', $classes);

  // If the field is not to be rendered by ds, just continue to the next field,
  // the content will come from another module (most likely CCK).
  if ($field_settings['pipeline'] = DS_RENDER_DEFAULT) {

    // Change the title if this is configured and label is not hidden.
    if (isset($field_settings['label_value']) && $field['labelformat'] != DS_DEFAULT_LABEL_FORMAT) {
      $field['title'] = t('%label', array(
        '%label' => $field_settings['label_value'],
      ));
    }

    // Add extra properties to be used in themeing.
    $field['key'] = $field_key;

    // Theming can either be done in preprocess, with a custom function or an
    // existing formatter theming function. Always pass the $field_settings as
    // parameter.
    // @todo: some of these should break earlier as no processing is required.
    switch ($field['field_type']) {
      case DS_FIELD_TYPE_PREPROCESS:
      case DS_FIELD_TYPE_IGNORE:
        if (isset($field_settings['properties']['key']) && !empty($field_settings['properties']['key'])) {
          $field['preprocess_settings'] = array(
            'type' => $field['type'],
            'key' => $field['properties']['key'],
          );
        }
        else {
          $field['preprocess_settings'] = array(
            'type' => $field['type'],
          );
        }
        break;
      case DS_FIELD_TYPE_CODE:
        $field['formatter'] = isset($field['format']) ? $field['format'] : 'ds_eval_code';
        break;
      case DS_FIELD_TYPE_BLOCK:
        $field['formatter'] = 'ds_eval_block';
        break;
      case DS_FIELD_TYPE_FUNCTION:
        $field['function'] = isset($field_settings['format']) ? $field_settings['format'] : key($field_settings['properties']['formatters']);
        break;
      case DS_FIELD_TYPE_THEME:
        $field['formatter'] = isset($field_settings['format']) ? $field_settings['format'] : key($field_settings['properties']['formatters']);
        break;
    }

    // Format content for the field.
    $field['content'] = ds_field_format_content($field);
  }
  return $field;
}

/**
 * Render content for an object.
 *
 * @deprecated This function is a builder, use the statically cached ds_render
 *
 * @param object $object
 *   The object to manipulate.
 * @param string $module
 *   The module that is requesting.
 * @param array $vars
 *   The variables required for rendering.
 * @param string $theme_function
 *   The theming function for a field.
 *
 * @return mixed
 *   Result of the render.
 */
function ds_render_content(&$object, $module, $vars, $theme_function = DS_DEFAULT_THEME_FIELD) {

  /*
   * Basic object set up
   */

  // Object to hold our complete rendered object.
  $object_display = new stdClass();

  // Sort regions to get them in the right order for rendering.
  $object_display->active_regions = array();
  $object_display->all_regions = ds_regions('all', TRUE);
  foreach ($object_display->all_regions as $region_name => $region) {
    if (isset($vars['regions'][$region_name]) && !empty($vars['regions'][$region_name])) {
      $object_display->active_regions[$region_name] = $vars['regions'][$region_name];
    }
  }

  // Only themed regions.
  $object_display->themed_regions = array();

  // Classes for regions.
  $object_display->region_classes = array();

  // Build mode for this object.
  $object_display->build_mode = $object->build_mode;

  // API info for this module and type. This doesn't cost a lot
  // since it will be cached already in ds_build_fields_and_regions().
  $object_display->api_info = ds_api_info($module, $object->type);

  // Display settings for this module and object.
  $object_display->display_settings = ds_get_settings($module, $object->type, $object->build_mode);
  $object_display->region_styles = ds_default_value($object_display->display_settings, 'region_styles');

  /*
   * Give field types an opportunity to change settings before we sort groups
   */
  foreach ($object->ds_fields as $key => $field) {
    switch ($field['field_type']) {
      case DS_FIELD_TYPE_PREPROCESS:
        if (!empty($field['preprocess_settings']['key'])) {
          $object->ds_fields[$key]['content'] = $vars[$key][$object->preprocess_fields[$key]['key']];
        }
        else {
          $object->ds_fields[$key]['content'] = $vars[$key];
        }
        break;
      case DS_FIELD_TYPE_IGNORE:
        $object->ds_fields[$key]['content'] = isset($object->content[$key]['#value']) ? $object->content[$key]['#value'] : '';
        break;
    }
  }

  /*
   * Pack groups
   */
  if (!empty($object->ds_groups)) {
    foreach ($object->ds_groups as $group_key => $group) {
      foreach ($group as $field_key => $field) {
        $object->ds_fields[$group_key]['fields'][$field_key] = $object->ds_fields[$field_key];
        unset($object->ds_fields[$field_key]);
      }
    }
  }

  /*
   * Iterate over the active regions to build the display object
   *
   * Instead of iterating over groups first, we iterate over active regions
   * and build groups only when required.
   */
  $count = 0;
  foreach (array_keys($object_display->active_regions) as $region_name) {
    $region_fields = array();

    // Loop through all fields after ordering on weight.
    asort($object_display->active_regions[$region_name]);
    foreach (array_keys($object_display->active_regions[$region_name]) as $key) {
      $region_fields[$key] = $object->ds_fields[$key];

      /*
       * Choose a field rendering pipeline based on the type.
       *
       * Groups get content and render within wrapper functions.
       * Fields get content then call the renderer directly.
       */
      switch ($object->ds_fields[$key]['field_type']) {
        case DS_FIELD_TYPE_GROUP:
          $region_fields[$key]['rendered'] = ds_render_group($object, $key, $vars);
          break;
        case DS_FIELD_TYPE_MULTIGROUP:
          $region_fields[$key]['rendered'] = ds_render_multigroup($object, $key, $vars);
          break;
        default:

          // Set content for this item.
          $object->ds_fields[$key]['content'] = ds_get_content($object->ds_fields[$key], $vars, $key);
          $region_fields[$key]['rendered'] = ds_render_item($object->ds_fields[$key]);
          break;
      }

      // Support for tabs. We need to create an additional field and unset
      // the existing group field.
      if (!empty($object->ds_fields[$key]['theme'])) {
        if ($object->ds_fields[$key]['theme'] == 'ds_tabs') {
          $object_display->active_regions[$region_name]['tab_' . $region_name] = $object->ds_fields[$key]['weight'];
          $region_fields['tab_' . $region_name] = array(
            'tab' => TRUE,
            'name' => $region_name,
            'fields' => array(
              $object->ds_fields[$key]['weight'] => $object->ds_fields[$key],
            ),
          );
          $region_fields['tab_' . $region_name]['fields'][$object->ds_fields[$key]['weight']]['content'] = $object->ds_fields[$key]['rendered'];
          unset($object->ds_fields[$key]);
          unset($region_fields[$key]);
        }
      }
    }

    // Store content from a region and add classes if necessary.
    if (is_array($region_fields) && !empty($region_fields)) {
      if ($region_name == 'left' || $region_name == 'right') {
        $object_display->region_classes[$region_name] = $region_name;
      }
      $style = isset($object_display->region_styles[$region_name]) ? ' ' . $object_display->region_styles[$region_name] : '';
      $rendered_region = '';
      foreach ($region_fields as $region_field) {
        if (!empty($region_field['rendered'])) {
          $rendered_region .= $region_field['rendered'];
        }
      }
      $object_display->themed_regions[$region_name] = array(
        'content' => $rendered_region,
        'extra_class' => $style,
        'count' => $count,
        'fields' => $region_fields,
        'hidden' => FALSE,
      );
      if (empty($rendered_region)) {
        $object_display->themed_regions[$region_name]['hidden'] = TRUE;
      }
    }
    $count++;
  }

  /*
   * Do some final configuration and pass the complete display object off to
   * theme_ds_regions for final rendering.
   */

  // Plugins.
  ds_plugins_process($object_display, $object, $vars);

  // Cleanup:
  // Remove any regions which are hidden before rendering.
  foreach ($object_display->themed_regions as $key => $region) {
    if ($region['hidden'] == TRUE) {
      unset($object_display->themed_regions[$key]);
    }
  }

  // Add classes based on regions.
  if (isset($object_display->themed_regions['middle'])) {
    $middle_class = $module . '-no-sidebars';
    if (isset($object_display->themed_regions['left']) && isset($object_display->themed_regions['right'])) {
      $middle_class = $module . '-two-sidebars';
    }
    elseif (isset($object_display->themed_regions['left'])) {
      $middle_class = $module . '-one-sidebar ' . $module . '-sidebar-left';
    }
    elseif (isset($object_display->themed_regions['right'])) {
      $middle_class = $module . '-one-sidebar ' . $module . '-sidebar-right';
    }
    $object_display->themed_regions['middle']['extra_class'] .= ' ' . $middle_class;
  }

  // Theme the regions with their content.
  return theme(DS_DEFAULT_THEME_REGIONS, $object_display, $module);
}

/**
 * Format content for use in an item.
 */
function ds_field_format_content($field) {
  $content = NULL;
  switch ($field['pipeline']) {
    case DS_RENDER_DRUPAL:

      // Does nothing, as drupal_render will take care of the field later.
      break;
    case DS_RENDER_NON_DS:

      // Does nothing, because we are ignoring it.
      break;
    default:
    case DS_RENDER_DEFAULT:
      if (isset($field['formatter']) || isset($field['function'])) {

        // Load includes.
        if (isset($field['file'])) {
          include_once $field['file'];
        }

        // If its a function, call it.
        if (isset($field['function']) && !empty($field['function'])) {
          if (function_exists($field['function'])) {
            $content = call_user_func($field['function'], $field);
          }
        }
        else {
          $content = theme($field['formatter'], $field);
        }
      }
      break;
  }
  return $content;
}

/**
 * Return a value for a field or group.
 *
 * @param array $item
 *   The item to render. The item requires a 'content' key.
 *
 * @return string
 *   A rendered item, or FALSE if no content was found
 */
function ds_render_item($item) {
  $content = FALSE;
  switch ($item['pipeline']) {

    // Render the content using Drupal's default render function.
    case DS_RENDER_DRUPAL:
      $content = drupal_render($item['vars']);
      break;

    // This is a non-DS field and has done its own rendering
    // e.g. CCK.
    case DS_RENDER_NON_DS:
      if (!empty($item['content'])) {
        $content = $item['content'];
      }
      break;

    // Use ds's built-in rendering.
    default:
    case DS_RENDER_DEFAULT:
      if (!empty($item['content'])) {

        // Tabs must use the ds_tabs themeing function.
        if (isset($item['tab'])) {
          $item['theme'] = 'ds_tabs';
        }
        elseif (empty($item['theme'])) {
          $item['theme'] = DS_DEFAULT_THEME_FIELD;
        }
        $content = theme($item['theme'], $item);
      }
      break;
  }

  // theme() can return anything, but we only want to return content where
  // there is 1 or more characters.
  if (isset($content) && strlen($content) > 0) {
    return $content;
  }
  return FALSE;
}

/**
 * Return a rendered fieldset group.
 *
 * @param object $object
 *   The object (e.g. a node) containing the group to be rendered
 * @param string $group_key
 *   The key of the group to render
 * @param array $vars
 *   (optional) The $vars array
 *
 * @return string|bool
 *   a string containing the fully-rendered group, or FALSE if one is not
 *   rendered
 */
function ds_render_group(&$object, $group_key, $vars = array()) {
  $object->ds_fields[$group_key]['content'] = '';
  if (array_key_exists($group_key, $object->ds_groups)) {

    // Sort group items by weight, and pass them to the render function.
    asort($object->ds_groups[$group_key]);
    foreach (array_keys($object->ds_groups[$group_key]) as $field_key) {

      // Items inside groups wont have a content value set, so assign it now
      // @todo find a way to move ds_set_content into a consistent place for
      // all fields, e.g. ds_build_fields_and_objects.
      $object->ds_fields[$group_key]['fields'][$field_key]['content'] = ds_get_content($object->ds_fields[$group_key]['fields'][$field_key], $vars, $field_key);
      $object->ds_fields[$group_key]['fields'][$field_key]['rendered'] = ds_render_item($object->ds_fields[$group_key]['fields'][$field_key]);
      $object->ds_fields[$group_key]['content'] .= $object->ds_fields[$group_key]['fields'][$field_key]['rendered'];
    }
    $object->ds_fields[$group_key]['count'] = count($object->ds_fields[$group_key]['fields']);

    // Render the complete group.
    if (!empty($object->ds_fields[$group_key]['content'])) {
      $object->ds_fields[$group_key]['rendered'] = ds_render_item($object->ds_fields[$group_key]);
    }
  }
  return $object->ds_fields[$group_key]['rendered'];
}

/**
 * Return a rendered multigroup group.
 *
 * Multigroups are not fieldsets but multi-value compound fields. The
 * individual field values must be rebuilt across fields to produce the
 * correct structure..
 *
 * @param object $object
 *   The object (e.g. a node) containing the group to be rendered
 * @param string $group_key
 *   The key of the group to render
 * @param array $vars (optional)
 *   The $vars array
 *
 * @return bool
 *   a string containing the fully-rendered group, or FALSE if one is not
 */
function ds_render_multigroup(&$object, $group_key, $vars = array()) {

  // Add CSS for multigroups.
  // It would be nice to avoid this, however when returning individual CCK
  // rendered rows from a multigroup, every label after the first is hidden.
  drupal_add_css(drupal_get_path('module', 'ds') . '/css/ds-multigroup.css');

  // Initialise storage for fieldset subgroups.
  $object->ds_fields[$group_key]['subgroups'] = array();

  // Iterate over the number of properties ($fields) in the multigroup, and
  // construct a single numerically keyed, compound value array representing
  // the field. This is complicated because we are building individually
  // rendered fields for fieldset implementations, as well as array keys for
  // things like tables.
  $field_keys = array_keys($object->ds_fields[$group_key]['fields']);
  $subgroup_count = count($vars[$field_keys[0]]);
  for ($i = 0; $i < $subgroup_count; $i++) {
    $object->ds_fields[$group_key]['subgroups']['rows'][$i] = array(
      'index' => $group_key . '_delta_' . $i,
      'class' => 'content-multigroup-wrapper field-multigroup-row ds-multigroup-' . $group_key . '-delta-' . $i . ($i % 2 ? ' odd' : ' even'),
      'weight' => $i - 100,
    );

    // Iterate over fields in the group and move their values into subgroups.
    $subgroup_content = FALSE;
    foreach ($object->ds_fields[$group_key]['fields'] as $field_key => $field_weight) {
      $object->ds_fields[$group_key]['subgroups']['rows'][$i]['fields'][$field_key] = $object->ds_fields[$group_key]['fields'][$field_key];
      $object->ds_fields[$group_key]['subgroups']['rows'][$i]['fields'][$field_key]['view'] = ds_get_content($object->ds_fields[$group_key]['fields'][$field_key], $vars[$field_key][$i], 'view');

      // The field title may not be stored on the field, so retrieve it from
      // CCK via nd_cck
      if (empty($object->ds_fields[$group_key]['fields'][$field_key]['title'])) {
        $cck_field = content_fields($field_key, $object->type);
        $object->ds_fields[$group_key]['fields'][$field_key]['title'] = nd_cck_get_label_value($cck_field, $object->build_mode);
      }
      $object->ds_fields[$group_key]['subgroups']['rows'][$i]['fields'][$field_key]['title'] = $object->ds_fields[$group_key]['fields'][$field_key]['title'];

      // CCK returns different content depending on its default display
      // settings, so we fallback on the default rendered view and pass it
      // through the ds rendering pipeline
      $field_content = $object->ds_fields[$group_key]['subgroups']['rows'][$i]['fields'][$field_key]['view'];
      if ($field_content != FALSE || strlen($field_content) > 0) {
        $object->ds_fields[$group_key]['subgroups']['rows'][$i]['fields'][$field_key]['content'] = $field_content;
      }
      else {
        $object->ds_fields[$group_key]['subgroups']['rows'][$i]['fields'][$field_key]['content'] = FALSE;
      }
      $object->ds_fields[$group_key]['subgroups']['rows'][$i]['fields'][$field_key]['type'] = 'ds';

      // Because CCK fields normally ignore ds formatters, we need to explicitly
      // set the pipeline for multigroups to use DS, or they dont get formatted
      $object->ds_fields[$group_key]['subgroups']['rows'][$i]['fields'][$field_key]['pipeline'] = DS_RENDER_DEFAULT;
      $object->ds_fields[$group_key]['subgroups']['rows'][$i]['fields'][$field_key]['rendered'] = ds_render_item($object->ds_fields[$group_key]['subgroups']['rows'][$i]['fields'][$field_key]);
      if ($object->ds_fields[$group_key]['subgroups']['rows'][$i]['fields'][$field_key]['rendered'] != FALSE) {
        $subgroup_content = TRUE;
      }

      // Add subgroup row information
      $object->ds_fields[$group_key]['subgroups']['rows'][$i]['title'] = $object->content[$group_key]['group'][$i]['#title'];

      // Add the rendered value to its parent subgroup content
      $object->ds_fields[$group_key]['subgroups']['rows'][$i]['content'] .= $object->ds_fields[$group_key]['subgroups']['rows'][$i]['fields'][$field_key]['rendered'];
    }

    // Build the rendered subgroup row for use in fieldsets etc.
    if ($subgroup_content == TRUE) {
      $object->ds_fields[$group_key]['subgroups']['rows'][$i]['rendered'] = ds_render_item($object->ds_fields[$group_key]['subgroups']['rows'][$i]);
    }
    else {
      $object->ds_fields[$group_key]['subgroups']['rows'][$i]['rendered'] = FALSE;
    }
  }

  // Add fields for reference
  $object->ds_fields[$group_key]['subgroups']['fields'] = $object->ds_fields[$group_key]['fields'];

  // Subgroups may be rendered as if they are a single field, so we must construct
  // a wrapping field object to pass to ds_render_item()
  $object->ds_fields[$group_key]['subgroups']['key'] = $group_key . '_subgroup';
  $object->ds_fields[$group_key]['subgroups']['theme'] = isset($object->ds_fields[$group_key]['subgroup_theme']) ? $object->ds_fields[$group_key]['subgroup_theme'] : 'ds_multigroup_fieldset';

  // To be passed to a rendering function, the 'content' key must exist,
  // however the subgroup has not rendered content at this point
  $object->ds_fields[$group_key]['subgroups']['content'] = TRUE;

  // Render the subgroups
  $object->ds_fields[$group_key]['content'] = ds_render_item($object->ds_fields[$group_key]['subgroups']);

  // Render the group wrapper
  if (!empty($object->ds_fields[$group_key]['content'])) {
    $object->ds_fields[$group_key]['rendered'] = ds_render_item($object->ds_fields[$group_key]);
    return $object->ds_fields[$group_key]['rendered'];
  }
  return FALSE;
}

/**
 * Helper function to get content for a field
 *
 * @param stdClass $item
 *  the item to set content for
 * @param array $content_vars (optional)
 *  the $vars array, or an array containing possible values
 * @param string $vars_key
 *  the key within $content_vars to retrieve
 *
 * @return
 *  the content value as a string, or FALSE if none was found
 */
function ds_get_content($item, $content_vars = array(), $vars_key = NULL) {
  if (!is_array($item)) {
    return FALSE;
  }
  $content = '';

  // If content has been prerendered by ds_build_fields_and_objects use that
  if (!empty($item['content'])) {
    $content = $item['content'];
  }
  elseif (!empty($content_vars)) {

    // CCK fields will use $vars_key.'_rendered'
    // @todo: this is a bit of a hacky way to get CCK content, refactor
    if (isset($content_vars[$vars_key . '_rendered'])) {
      $content = $content_vars[$vars_key . '_rendered'];
    }
    elseif (isset($content_vars[$vars_key])) {
      $content = $content_vars[$vars_key];
    }
  }
  if (!empty($content)) {
    return $content;
  }
  return FALSE;
}

/**
 * Get settings
 *
 * @param string $module The name of the module
 * @param string $type The name of the type
 * @param string $build_mode The name of the build mode
 * @param string $return The name of the record to return
 * @param string $reset Whether to reset the static cache or not.
 */
function ds_get_settings($module, $type, $build_mode, $return = 'settings', $reset = FALSE) {
  static $settings = array();
  if ($reset) {
    $settings = array();
  }
  if (!isset($settings[$module][$type][$build_mode])) {
    $settings[$module][$type][$build_mode] = array();
    $record = db_fetch_array(db_query("SELECT module, type, build_mode, settings, fields FROM {ds_settings} WHERE module = '%s' AND type = '%s' AND build_mode = '%s'", $module, $type, $build_mode));
    if (isset($record['module'])) {
      $settings[$module][$type][$build_mode]['settings'] = unserialize($record['settings']);
      $settings[$module][$type][$build_mode]['fields'] = unserialize($record['fields']);
    }
  }
  return isset($settings[$module][$type][$build_mode][$return]) ? $settings[$module][$type][$build_mode][$return] : array();
}

/**
 * Return a value or return the default if empty.
 *
 * @param array $settings The settings loaded for a type.
 * @param string $type The name of the type to search (ie fields, regions)
 * @param string $key The name of the key to search in $type.
 * @param string $search_key The name of the key to search in $key.
 * @param string $default The default value.
 * @param mixed default value.
 */
function ds_default_value($settings, $type, $key = NULL, $search_key = NULL, $default = NULL) {
  if ($key == NULL) {
    return isset($settings[$type]) ? $settings[$type] : NULL;
  }
  return isset($settings[$type][$key][$search_key]) ? $settings[$type][$key][$search_key] : $default;
}

/**
 * API function to get fields.
 *
 * @param string $module The name of the module.
 * @param string $type_name The name of object (ie, page or story for node types, profile for users)
 * @param string $build_mode The build mode.
 * @param array $extra Extra properties we might want to check on (ie has_body property).
 * @param boolean $reset Whether we need to reset the fields cache or not.
 * @param boolean $cache Whether we need to cache the fields or not.
 *
 * @return array of fields.
 */
function ds_get_fields($module, $type_name, $build_mode, $extra = array(), $reset = FALSE, $cache = TRUE) {
  static $static_fields = array();
  if (!isset($static_fields[$module][$type_name][$build_mode])) {

    // Do we have them cached or not ?
    $settings = ds_get_settings($module, $type_name, $build_mode, 'fields');
    if (!isset($settings) || empty($settings) || $reset) {

      // Fields in code.
      $fields = array();
      foreach (module_implements('ds_fields') as $prefix) {
        $function = $prefix . '_ds_fields';
        $all_fields = $function($type_name, $build_mode, $extra);
        if (!empty($all_fields)) {
          foreach ($all_fields as $key => $field_results) {
            if ($key === $module) {
              $fields = array_merge($field_results, $fields);
              foreach ($fields as $key => $field) {
                $exclude = isset($field['exclude'][$type_name]) && $field['exclude'][$type_name] === $type_name ? TRUE : FALSE;
                if ($exclude) {
                  unset($fields[$key]);
                }
              }
            }
          }
        }
      }

      // Fields via the UI.
      $db_fields = variable_get($module . '_fields', array());
      if (!empty($db_fields)) {
        foreach ($db_fields as $key => $field) {
          $fields[$key] = array(
            'title' => check_plain($field['title']),
            'type' => $field['type'],
            'status' => $field['status'],
            'properties' => $field['properties'],
          );
          $exclude = isset($field['exclude'][$type_name]) && $field['exclude'][$type_name] === $type_name ? TRUE : FALSE;
          if ($exclude) {
            unset($fields[$key]);
          }
        }
      }

      // Do not save the CCK fields.
      if ($cache) {
        foreach ($fields as $field_key => $field_value) {
          if (isset($field_value['storage'])) {
            unset($fields[$field_key]);
          }
        }
      }

      // Give modules a change to alter the fields.
      drupal_alter('ds_fields', $fields);

      // Do we cache or not ?
      if ($cache) {
        db_query("UPDATE {ds_settings} set fields = '%s' WHERE module = '%s' AND type = '%s' AND build_mode = '%s'", serialize($fields), $module, $type_name, $build_mode);
      }
    }
    else {
      $fields = $settings;
    }

    // Store the fields.
    $static_fields[$module][$type_name][$build_mode] = $fields;
  }
  return $static_fields[$module][$type_name][$build_mode];
}

/**
 * Api function to return all build modes.
 *
 * @param string $module Return build modes for a module.
 * @param boolean $reset Whether to reset the build modes.
 *
 * @return array Collection of build modes.
 */
function ds_get_build_modes($module = NULL, $reset = FALSE) {
  $build_modes = variable_get('ds_all_build_modes', array());
  if (empty($build_modes) || $reset) {
    require_once 'includes/ds.registry.inc';
    $build_modes = _ds_register_build_modes();
  }
  if ($module != NULL) {
    return $build_modes[$module];
  }
  else {
    return $build_modes;
  }
}

/**
 * Process plugins.
 *
 * @param stdClass $display
 *  The display built by ds_render_content
 * @param stdClass $object
 *  The original object being processed
 * @param array $vars
 *  The variables used to build that display
 */
function ds_plugins_process(&$display, $object, $vars) {
  $plugins = variable_get($display->api_info['module'] . '_plugin_settings', array());
  if (!empty($plugins)) {
    foreach ($plugins as $key => $data) {
      if (isset($data['filename']) && isset($data['class'])) {
        require_once $data['filename'];
        $class = $data['class'];
        $plugin = new $class();
        $plugin
          ->execute($vars, $display, $display->display_settings, $object->type, $display->api_info['module']);
      }
    }
  }
}

/**
 * Wrapper function around PHP eval(). We don't use drupal_eval
 * because custom fields might need properties from the current
 * object.
 *
 * @param string $code The code to evaluate from the custom field.
 * @param stdClass $object An object to use for evaluation.
 *
 * @return string $output The output from eval.
 */
function ds_eval($code, $object) {
  global $theme_path, $theme_info, $conf;

  // Store current theme path.
  $old_theme_path = $theme_path;

  // Restore theme_path to the theme, as long as ds_eval() executes,
  // so code evaluted will not see the caller module as the current theme.
  // If theme info is not initialized get the path from theme_default.
  if (!isset($theme_info)) {
    $theme_path = drupal_get_path('theme', $conf['theme_default']);
  }
  else {
    $theme_path = dirname($theme_info->filename);
  }
  ob_start();
  print eval('?>' . $code);
  $output = ob_get_contents();
  ob_end_clean();

  // Recover original theme path.
  $theme_path = $old_theme_path;
  return $output;
}

/**
 * Return array of available regions.
 *
 * This is a multi dimensional array because when ordering fields on the
 * display, we want them to order in a logical order. However, when
 * rendering the HTML, we want left-right-middle for easy css practice.
 *
 * @param string $regions Whether to return all regions or not.
 *
 * @return array $regions Collection of regions.
 */
function ds_regions($regions = 'all', $render = FALSE) {
  if ($regions == 'all') {
    if ($render == FALSE) {
      return array(
        'header' => t('Header'),
        'left' => t('Left'),
        'middle' => t('Middle'),
        'right' => t('Right'),
        'footer' => t('Footer'),
        'disabled' => t('Disabled'),
      );
    }
    else {
      return array(
        'header' => t('Header'),
        'middle' => t('Middle'),
        'left' => t('Left'),
        'right' => t('Right'),
        'footer' => t('Footer'),
        'disabled' => t('Disabled'),
      );
    }
  }
  else {
    return array(
      'middle' => t('Enabled'),
      'disabled' => t('Disabled'),
    );
  }
}

/**
 * Function to check if a field is set for a type and build mode.
 *
 * @param string $module The module to get the settings for.
 * @param string $type_name The name of the object type.
 * @param string $build_mode The key of the build mode.
 * @param string $field The name of the field to check for.
 */
function ds_show_field($module, $type_name, $build_mode, $field) {
  static $show_fields = array();
  if (!isset($show_fields[$module][$type_name][$build_mode][$field])) {
    $display_settings = ds_get_settings($module, $type_name, $build_mode);
    $show_fields[$module][$type_name][$build_mode][$field] = isset($display_settings['fields'][$field]) && $display_settings['fields'][$field]['region'] != 'disabled' ? TRUE : FALSE;
  }
  return $show_fields[$module][$type_name][$build_mode][$field];
}

/**
 * Import default display data from modules.
 *
 * @param string $module The module to import.
 * @param string $type The object type to import.
 * @param string $build_mode The build mode to import.
 */
function ds_import_default_data($module = '', $type = '', $build_mode = '') {
  $data = module_invoke_all('ds_default_settings');
  if (!empty($data)) {
    module_load_include('inc', 'ds', 'includes/ds.tools');
    ds_import_data($data, FALSE, FALSE, $module, $type, $build_mode);
  }
}

/**
 * Return elements of an array which are children (i.e. not starting with #)
 *
 * This is a direct port of element_children from Drupal 7
 *
 * @param $elements
 *  The element array whose children are to be identified.
 * @param $sort
 *  Boolean to indicate whether the children should be sorted by weight.
 *
 * @return
 *  The array keys of the element's children.
 */
function ds_element_children(&$elements, $sort = FALSE) {

  // Do not attempt to sort elements which have already been sorted.
  $sort = isset($elements['#sorted']) ? !$elements['#sorted'] : $sort;

  // Filter out properties from the element, leaving only children.
  $children = array();
  $sortable = FALSE;
  foreach ($elements as $key => $value) {
    if ($key === '' || $key[0] !== '#') {
      $children[$key] = $value;
      if (is_array($value) && isset($value['#weight'])) {
        $sortable = TRUE;
      }
    }
  }

  // Sort the children if necessary.
  if ($sort && $sortable) {
    uasort($children, 'element_sort');

    // Put the sorted children back into $elements in the correct order, to
    // preserve sorting if the same element is passed through
    // element_children() twice.
    foreach ($children as $key => $child) {
      unset($elements[$key]);
      $elements[$key] = $child;
    }
    $elements['#sorted'] = TRUE;
  }
  return array_keys($children);
}

/**
 * API function to retrieve ACTIVE build modes for a module
 */
function ds_get_active_build_modes($module, $type = NULL) {
  $api_info = ds_api_info($module);
  $build_modes = ds_get_build_modes($module);
  $filtered_bm = array();
  if (!empty($build_modes)) {
    if (function_exists($api_info['types'])) {
      $types = call_user_func($api_info['types']);
    }
    else {
      $types = array();
    }
    foreach ($types as $type_name => $type_info) {

      // Global exclude.
      if (variable_get($module . '_type_' . $type_info->type, FALSE)) {
        continue;
      }
      $exclude_build_modes = variable_get($module . '_buildmodes_exclude', array());
      foreach ($build_modes as $key => $value) {

        // Check if build mode is excluded for this object type.
        $excluded = isset($exclude_build_modes[$type_name][$key]) && $exclude_build_modes[$type_name][$key] == TRUE ? TRUE : FALSE;
        if ($excluded) {
          continue;
        }
        $filtered_bm[$type_name][$key] = $value;
      }
    }
  }
  if (isset($type) && !empty($type)) {
    if (isset($filtered_bm[$type])) {
      return $filtered_bm[$type];
    }
    else {
      return FALSE;
    }
  }
  return $filtered_bm;
}

Functions

Namesort descending Description
ds_api_info Return API info about a module and type.
ds_block Implements hook_block().
ds_build_field Build an individual field value.
ds_build_fields_and_regions Get fields and regions for an object.
ds_default_value Return a value or return the default if empty.
ds_ds_plugins Implements hook_ds_plugins().
ds_element_children Return elements of an array which are children (i.e. not starting with #)
ds_eval Wrapper function around PHP eval(). We don't use drupal_eval because custom fields might need properties from the current object.
ds_features_api Implements hook_features_api().
ds_field_format_content Format content for use in an item.
ds_flush_caches Implements hook_flush_caches().
ds_get_active_build_modes API function to retrieve ACTIVE build modes for a module
ds_get_build_modes Api function to return all build modes.
ds_get_content Helper function to get content for a field
ds_get_fields API function to get fields.
ds_get_settings Get settings
ds_import_default_data Import default display data from modules.
ds_make API function to build and render a build mode for a given object
ds_plugins_process Process plugins.
ds_regions Return array of available regions.
ds_render API function to return rendered content for an item
ds_render_content Deprecated Render content for an object.
ds_render_group Return a rendered fieldset group.
ds_render_item Return a value for a field or group.
ds_render_multigroup Return a rendered multigroup group.
ds_reset_fields_cache Function to reset fields cache.
ds_show_field Function to check if a field is set for a type and build mode.
ds_static_variables Set or get static variables at runtime.
ds_theme Implements hook_theme().
ds_variable_get Module-aware version of variable_get.
ds_views_api Implements hook_views_api().

Constants