Skip to content
content.module 41.3 KiB
Newer Older
John VanDyk's avatar
John VanDyk committed
<?php
// $Id$

/**
 * @file
 * Allows administrators to associate custom fields to content types.
John VanDyk's avatar
John VanDyk committed
 */

define('CONTENT_DB_STORAGE_PER_FIELD', 0);
define('CONTENT_DB_STORAGE_PER_CONTENT_TYPE', 1);
define('CONTENT_CALLBACK_NONE', 0x0001);
define('CONTENT_CALLBACK_DEFAULT', 0x0002);
define('CONTENT_CALLBACK_CUSTOM', 0x0004);

  switch ($section) {
    case 'admin/help#content':
      $output = '<p>'. t('The content module, a required component of the Content Construction Kit (CCK), allows administrators to associate custom fields with content types. In Drupal, content types are used to define the characteristics of a post, including the title and description of the fields displayed on its add and edit pages. Using the content module (and the other helper modules included in CCK), custom fields beyond the default "Title" and "Body" may be added. CCK features are accessible through tabs on the <a href="@content-types">content types administration page</a>. (See the <a href="@node-help">node module help page</a> for more information about content types.)', array('@content-types' => url('admin/content/types'), '@node-help' => url('admin/help/node'))) .'</p>';
      $output .= '<p>'. t('When adding a custom field to a content type, you determine its type (whether it will contain text, numbers, or references to other objects) and how it will be displayed (either as a text field or area, a select box, checkbox, radio button, or autocompleting field). A field may have multiple values (i.e., a "person" may have multiple e-mail addresses) or a single value (i.e., an "employee" has a single employee identification number). As you add and edit fields, CCK automatically adjusts the structure of the database as necessary. CCK also provides a number of other features, including intelligent caching for your custom data, an import and export facility for content type definitions, and integration with other contributed modules.') .'</p>';
      $output .= '<p>'. t('Custom field types are provided by a set of optional modules included with CCK (each module provides a different type). The <a href="@modules">modules page</a> allows you to enable or disable CCK components. A default installation of CCK includes:', array('@modules' => url('admin/build/modules'))) .'</p>';
      $output .= '<ul>';
      $output .= '<li>'. t('<em>number</em>, which adds numeric field types, in integer, decimal or floating point form. You may define a set of allowed inputs, or specify an allowable range of values. A variety of common formats for displaying numeric data are available.') .'</li>';
      $output .= '<li>'. t("<em>text</em>, which adds text field types. A text field may contain plain text only, or optionally, may use Drupal's input format filters to securely manage rich text input. Text input fields may be either a single line (text field), multiple lines (text area), or for greater input control, a select box, checkbox, or radio buttons. If desired, CCK can validate the input to a set of allowed values.") .'</li>';
      $output .= '<li>'. t('<em>nodereference</em>, which creates custom references between Drupal nodes. By adding a <em>nodereference</em> field and two different content types, for instance, you can easily create complex parent/child relationships between data (multiple "employee" nodes may contain a <em>nodereference</em> field linking to an "employer" node).') .'</li>';
      $output .= '<li>'. t('<em>userreference</em>, which creates custom references to your sites\' user accounts. By adding a <em>userreference</em> field, you can create complex relationships between your site\'s users and posts. To track user involvement in a post beyond Drupal\'s standard <em>Authored by</em> field, for instance, add a <em>userreference</em> field named "Edited by" to a content type to store a link to an editor\'s user account page.') .'</li>';
      $output .= '<li>'. t('<em>fieldgroup</em>, which creates collapsible fieldsets to hold a group of related fields. A fieldset may either be open or closed by default. The order of your fieldsets, and the order of fields within a fieldset, is managed via a drag-and-drop interface provided by content module.') .'</li>';
      $output .= '</ul>';
      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@handbook-cck">CCK</a> or the <a href="@project-cck">CCK project page</a>.', array('@handbook-cck' => 'http://drupal.org/handbook/modules/cck', '@project-cck' => 'http://drupal.org/project/cck')) .'</p>';
      return $output;
  }

  if (preg_match('!^admin/content/types/.*/display$!', $section)) {
    return t("Configure how this content type's fields and field labels should be displayed when it's viewed in teaser and full-page mode.");
  }
}

/**
 * Implementation of hook_devel_caches().
 * Include {cache_content} in the list of tables cleared by devel's 'empty cache'
 */
function content_devel_caches() {
  return array('cache_content');
}

/**
 * Implementation of hook_init().
 */
function content_init() {
  // ensure we are not serving a cached page
  if (function_exists('drupal_set_content')) {
    // we don't do this in hook_menu to ensure the files are already included when
    // views_menu is executed
    if (module_exists('views')) {
      include_once('./'. drupal_get_path('module', 'content') .'/content_views.inc');
    }
    if (module_exists('panels')) {
      include_once('./'. drupal_get_path('module', 'content') .'/content_panels.inc');
    }
    // according to http://drupal.org/node/60526, this should not go in hook_menu
    if (module_exists('pathauto')) {
      include_once('./'. drupal_get_path('module', 'content') .'/content_pathauto.inc');
    }
  }
}

/**
 * Implementation of hook_perm().
 */
function content_perm() {
  return array('Use PHP input for field settings (dangerous - grant with care)');
}

John VanDyk's avatar
John VanDyk committed
/**
 * Implementation of hook_menu().
 */
function content_menu($may_cache) {
  if (!$may_cache) {
    // Only include administrative callbacks if we are viewing an admin page.
    if (arg(0) == 'admin') {
      include_once('./'. drupal_get_path('module', 'content') .'/content_admin.inc');
    }
    // Unconditionally include css exactly once per page.
    drupal_add_css(drupal_get_path('module', 'content') .'/content.css');
  }

John VanDyk's avatar
John VanDyk committed
  $items = array();
  $access = user_access('administer content types');
John VanDyk's avatar
John VanDyk committed
  if ($may_cache) {
      $items[] = array(
        'path' => 'admin/content/types/fields',
        'callback' => '_content_admin_type_fields',
        'access' => $access,
        'type' => MENU_LOCAL_TASK,
      );
John VanDyk's avatar
John VanDyk committed
  }
  else {
    if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'types') {
John VanDyk's avatar
John VanDyk committed

      $type = node_get_types('types', $content_type['type']);
      if (!empty($type) && arg(3) && arg(3) == $content_type['url_str']) {
        $items[] = array(
          'path' => 'admin/content/types/'. $content_type['url_str'] .'/edit',
          'callback' => 'drupal_get_form',
          'callback arguments' => array('node_type_form', $type),
          'type' => MENU_DEFAULT_LOCAL_TASK,
        );
        $items[] = array(
          'path' => 'admin/content/types/'. $content_type['url_str'] .'/fields',
          'title' => t('Manage fields'),
          'access' => $access,
          'callback arguments' => array('content_admin_field_overview_form', $content_type['type']),
          'weight' => 0,
        );
        $items[] = array(
          'path' => 'admin/content/types/'. $content_type['url_str'] .'/display',
          'title' => t('Display fields'),
          'callback' => 'drupal_get_form',
          'access' => $access,
          'callback arguments' => array('content_admin_display_overview_form', $content_type['type']),
          'type' => MENU_LOCAL_TASK,
          'weight' => 1,
        );
        $items[] = array(
          'path' => 'admin/content/types/'. $content_type['url_str'] .'/add_field',
          'callback' => '_content_admin_field_add',
          'access' => $access,
          'callback arguments' => array($content_type['type']),
John VanDyk's avatar
John VanDyk committed

        if (arg(4) == 'fields' && arg(5) && isset($content_type['fields'][arg(5)])) {
          $items[] = array(
            'path' => 'admin/content/types/'. $content_type['url_str'] .'/fields/'. arg(5),
            'title' => t($content_type['fields'][arg(5)]['widget']['label']),
            'callback' => 'drupal_get_form',
            'access' => $access,
            'callback arguments' => array('_content_admin_field', $content_type['type'], arg(5)),
            'type' => MENU_CALLBACK,
          );
          $items[] = array(
            'path' => 'admin/content/types/'. $content_type['url_str'] .'/fields/'. arg(5) .'/remove',
            'title' => t('Remove field'),
            'access' => $access,
            'callback arguments' => array('_content_admin_field_remove', $content_type['type'], arg(5)),
            'type' => MENU_CALLBACK,
          );
John VanDyk's avatar
John VanDyk committed
  return $items;
}

/**
Jonathan Chaffer's avatar
Jonathan Chaffer committed
 *
 * When loading one of the content.module nodes, we need to let each field handle
 * its own loading. This can make for a number of queries in some cases, so we
 * cache the loaded object structure and invalidate it during the update process.
John VanDyk's avatar
John VanDyk committed
 */
function content_load($node) {
  $cid = 'content:'. $node->nid .':'. $node->vid;
  if ($cached = cache_get($cid, 'cache_content')) {
Jonathan Chaffer's avatar
Jonathan Chaffer committed
    return unserialize($cached->data);
  }
  else {
    $default_additions = _content_field_invoke_default('load', $node);
    if ($default_additions) {
      foreach ($default_additions as $key => $value) {
        $node->$key = $value;
      }
    }
Jonathan Chaffer's avatar
Jonathan Chaffer committed
    $additions = _content_field_invoke('load', $node);
    if ($additions) {
      foreach ($additions as $key => $value) {
        $default_additions[$key] = $value;
      }
    }
    cache_set($cid, 'cache_content', serialize($default_additions));
Jonathan Chaffer's avatar
Jonathan Chaffer committed
  }
John VanDyk's avatar
John VanDyk committed
}

/**
 * Create fields' form for a content type.
Jonathan Chaffer's avatar
Jonathan Chaffer committed
 *
 * Each field defines its own component of the content entry form, via its
 * chosen widget.
John VanDyk's avatar
John VanDyk committed
 */
John VanDyk's avatar
John VanDyk committed

  // Set form parameters so we can accept file uploads.
  if (count($type['fields'])) {
    $form['#attributes'] = array("enctype" => "multipart/form-data");
  }
John VanDyk's avatar
John VanDyk committed

  _content_widget_invoke('prepare form values', $node);
  $form = array_merge($form, _content_widget_invoke('form', $node));
John VanDyk's avatar
John VanDyk committed
}

/**
 * Validate form callback to handle node type fields.
Jonathan Chaffer's avatar
Jonathan Chaffer committed
 *
 * Both widgets and fields have a chance to raise error flags when a node is
 * being validated.
John VanDyk's avatar
John VanDyk committed
 */
function content_validate(&$node) {
  _content_widget_invoke('validate', $node);
  _content_widget_invoke('process form values', $node);
  _content_field_invoke('validate', $node);
  _content_field_invoke_default('validate', $node);
John VanDyk's avatar
John VanDyk committed
}

 * Submit form callback for node type fields.
Jonathan Chaffer's avatar
Jonathan Chaffer committed
 *
 * At submit time, the widget does whatever data massaging is necessary so that
 * the field has the content in the expected format and can commit the changes
 * to the database.
  _content_widget_invoke('submit', $node);
  _content_widget_invoke('process form values', $node);
  _content_field_invoke('submit', $node);
  _content_field_invoke_default('submit', $node);
 */
function content_insert(&$node) {
  _content_field_invoke('insert', $node);
  _content_field_invoke_default('insert', $node);
 */
function content_update(&$node) {
  _content_field_invoke('update', $node);
  _content_field_invoke_default('update', $node);
  cache_clear_all('content:'. $node->nid .':'. $node->vid, 'cache_content');
  $type = content_types($node->type);
  if (!empty($type['fields'])) {
    _content_field_invoke('delete', $node);
    _content_field_invoke_default('delete', $node);
  }
  $table = _content_tablename($type['type'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
  if (db_table_exists($table)) {
    db_query('DELETE FROM {'. $table .'} WHERE nid = %d', $node->nid);
  cache_clear_all('content:'. $node->nid, 'cache_content', TRUE);
}

/**
 * delete node type fields for a revision.
 */
function content_delete_revision(&$node) {
  $type = content_types($node->type);
  if (!empty($type['fields'])) {
    _content_field_invoke('delete revision', $node);
    _content_field_invoke_default('delete revision', $node);
  }
  $table = _content_tablename($type['type'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
  if (db_table_exists($table)) {
    db_query('DELETE FROM {'. $table .'} WHERE vid = %d', $node->vid);
  cache_clear_all('content:'. $node->nid .':'. $node->vid, 'cache_content');
John VanDyk's avatar
John VanDyk committed
/**
John VanDyk's avatar
John VanDyk committed
 */
function content_view(&$node, $teaser = FALSE, $page = FALSE) {
  if ($node->in_preview) {
    _content_widget_invoke('process form values', $node);
  }
  $content = _content_field_view($node, $teaser, $page);
  $node->content = array_merge((array) $node->content, $content);
John VanDyk's avatar
John VanDyk committed
}

/**
 * Implementation of hook_nodeapi().
 *
 * When a revision is deleted, make sure the appropriate cache item is cleared.
 * @todo: deprecate op==validate & op==submit in favor of form callbacks.
 */
function content_nodeapi(&$node, $op, $teaser, $page) {
  switch ($op) {
    case 'load':
      return content_load($node);

    case 'validate':
      content_validate($node);
      if ($node->devel_generate) {
        include_once('./'. drupal_get_path('module', 'content') .'/content.devel.inc');
        content_generate_fields($node);
      }
      content_insert($node);
      break;

    case 'update':
      content_update($node);
      break;

    case 'delete':
      content_delete($node);
      break;

    case 'delete revision':
      content_delete_revision($node);
      break;

    case 'view':
      content_view($node, $teaser, $page);
function content_form_alter($form_id, &$form) {
  if (isset($form['type'])) {
    $node = $form['#node'];
    if ($form['type']['#value'] .'_node_form' == $form_id) {
      $form = array_merge($form, content_form($node));
    }
 *  Make sure that CCK content type info is synched with node type data
 *  any time the content module is enabled.
function content_enable() {
  include_once('./'. drupal_get_path('module', 'content') .'/content_admin.inc');
  include_once('./'. drupal_get_path('module', 'content') .'/content_crud.inc');
  content_types_rebuild();
/**
 * Implementation of hook_field(). Handles common field housekeeping.
 *
 * This implementation is special, as content.module does not define any field
 * types. Instead, this function gets called after the type-specific hook, and
 * takes care of the database interface for field types that do not choose to
 * do their own storage.
 */
function content_field($op, &$node, $field, &$node_field, $teaser, $page) {
  $db_info = content_database_info($field);

  switch ($op) {
    case 'load':
      $column_names = array();
      foreach ($db_info['columns'] as $column => $attributes) {
        $column_names[] = $attributes['column'] .' AS '. $column;
      $query = 'SELECT '. implode(', ', $column_names) .' FROM {'. $db_info['table'] .'} WHERE vid = %d';
        $result = db_query($query .' ORDER BY delta', $node->vid);
        $result = db_query_range($query, $node->vid, 0, 1);
      }
      $additions = array();
      while ($value = db_fetch_array($result)) {
        $additions[$field['field_name']][] = $value;
      foreach ($node_field as $delta => $item) {
        $column_names = array();
        $column_placeholders = array();
        foreach ($db_info['columns'] as $column => $attributes) {
          $column_names[] = $attributes['column'];
          if ($item[$column] == '' && !$attributes['not null'] && !$field['required']) {
            $column_placeholders[] = '%s';
            $column_assignments[] = $attributes['column'] .' = %s';
            $item[$column] = 'NULL';
          }
          else {
            switch ($attributes['type']) {
            case 'bigint':
              $column_placeholders[] = '%d';
              $column_assignments[] = $attributes['column'] .' = %d';
              break;
            case 'float':
              $column_placeholders[] = '%f';
              $column_assignments[] = $attributes['column'] .' = %f';
              break;
            default:
              $column_placeholders[] = "'%s'";
              $column_assignments[] = $attributes['column'] ." = '%s'";
        $data[] = $node->vid;
        $data[] = $node->nid;
        if ($field['multiple']) {
          $data[] = $delta;
        }

          db_query('INSERT INTO {'. $db_info['table'] .'} ('. implode(', ', $column_names) .', vid, nid, delta) VALUES ('. implode(', ', $column_placeholders) .', %d, %d, %d)', $data);
          if (db_result(db_query('SELECT COUNT(*) FROM {'. $db_info['table'] .'} WHERE vid = %d AND nid = %d', $node->vid, $node->nid))) {
            db_query('UPDATE {'. $db_info['table'] .'} SET '. implode(', ', $column_assignments) .' WHERE vid = %d AND nid = %d', $data);
          }
          else {
            db_query('INSERT INTO {'. $db_info['table'] .'} ('. implode(', ', $column_names) .', vid, nid) VALUES ('. implode(', ', $column_placeholders) .', %d, %d)', $data);
          }
      if ($field['multiple']) {
        // Delete and insert, rather than update, in case a field was added.
        db_query('DELETE FROM {'. $db_info['table'] .'} WHERE vid = %d', $node->vid);
      }
      foreach ($node_field as $delta => $item) {
        $column_names = array();
        $column_placeholders = array();
        foreach ($db_info['columns'] as $column => $attributes) {
          $column_names[] = $attributes['column'];
          if ($item[$column] == '' && !$attributes['not null'] && !$field['required']) {
            $column_placeholders[] = '%s';
            $column_assignments[] = $attributes['column'] .' = %s';
            $item[$column] = 'NULL';
          }
          else {
            switch ($attributes['type']) {
            case 'int':
            case 'mediumint':
            case 'bigint':
              $column_placeholders[] = '%d';
              $column_assignments[] = $attributes['column'] .' = %d';
              break;
            case 'float':
              $column_placeholders[] = '%f';
              $column_assignments[] = $attributes['column'] .' = %f';
              break;
            default:
              $column_placeholders[] = "'%s'";
              $column_assignments[] = $attributes['column'] ." = '%s'";
        $data[] = $node->vid;
        $data[] = $node->nid;
        if ($field['multiple']) {
          $data[] = $delta;
        }

          db_query('INSERT INTO {'. $db_info['table'] .'} ('. implode(', ', $column_names) .', vid, nid, delta) VALUES ('. implode(', ', $column_placeholders) .', %d, %d, %d)', $data);
          if (db_result(db_query('SELECT COUNT(*) FROM {'. $db_info['table'] .'} WHERE vid = %d AND nid = %d', $node->vid, $node->nid))) {
            db_query('UPDATE {'. $db_info['table'] .'} SET '. implode(', ', $column_assignments) .' WHERE vid = %d AND nid = %d', $data);
          }
          else {
            db_query('INSERT INTO {'. $db_info['table'] .'} ('. implode(', ', $column_names) .', vid, nid) VALUES ('. implode(', ', $column_placeholders) .', %d, %d)', $data);
          }
        }
      }
      return;

    case 'delete':
      // Delete using nid rather than vid to purge all revisions.
      db_query('DELETE FROM {'. $db_info['table'] .'} WHERE nid = %d', $node->nid);

    case 'delete revision':
      db_query('DELETE FROM {'. $db_info['table'] .'} WHERE vid = %d', $node->vid);
      return;
John VanDyk's avatar
John VanDyk committed
/**
 * For each operation, both this function and _content_field_invoke_default() are
 * called so that the default database handling can occur.
John VanDyk's avatar
John VanDyk committed
 */
function _content_field_invoke($op, &$node, $teaser = NULL, $page = NULL) {
John VanDyk's avatar
John VanDyk committed
  $type_name = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
John VanDyk's avatar
John VanDyk committed
  $field_types = _content_field_types();

  $return = array();
  if (count($type['fields'])) {
    foreach ($type['fields'] as $field) {
      $node_field = isset($node->$field['field_name']) ? $node->$field['field_name'] : array();
      $module = $field_types[$field['type']]['module'];
      $function = $module .'_field';
      if (function_exists($function)) {
        $result = $function($op, $node, $field, $node_field, $teaser, $page);
          $return = array_merge($return, $result);
        }
        else if (isset($result)) {
          $return[] = $result;
        }
John VanDyk's avatar
John VanDyk committed
      }
      // test for values in $node_field in case modules added items on insert
      if (isset($node->$field['field_name']) || count($node_field)) {
        $node->$field['field_name'] = $node_field;
John VanDyk's avatar
John VanDyk committed
      }
    }
  }
  return $return;
}

/**
 * Invoke content.module's version of a field hook.
 */
function _content_field_invoke_default($op, &$node, $teaser = NULL, $page = NULL) {
  $type_name = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
  if (count($type['fields'])) {
    foreach ($type['fields'] as $field) {
      $node_field = isset($node->$field['field_name']) ? $node->$field['field_name'] : array();
      $db_info = content_database_info($field);
      if (count($db_info['columns'])) {
        $result = content_field($op, $node, $field, $node_field, $teaser, $page);
          $return = array_merge($return, $result);
        }
        else if (isset($result)) {
          $return[] = $result;
        }
      if (isset($node->$field['field_name'])) {
        $node->$field['field_name'] = $node_field;
John VanDyk's avatar
John VanDyk committed
  }

  return $return;
}

/**
 * Format field output based on display settings.
 */
function _content_field_view(&$node, $teaser = NULL, $page = NULL) {
  $type_name = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
  $type = content_types($type_name);
  $field_types = _content_field_types();
  $context = $teaser ? 'teaser' : 'full';

  $return = array();
  if (count($type['fields'])) {
    foreach ($type['fields'] as $field) {
      $node_field = isset($node->$field['field_name']) ? $node->$field['field_name'] : array();
      $formatter = isset($field['display_settings'][$context]['format']) ? $field['display_settings'][$context]['format'] : 'default';
      $value = '';

      if ($formatter != 'hidden') {
        if (content_handle('field', 'view', $field) == CONTENT_CALLBACK_CUSTOM) {
          $module = $field_types[$field['type']]['module'];
          $function = $module .'_field';
          if (function_exists($function)) {
            $value = $function('view', $node, $field, $node_field, $teaser, $page);
          }
          foreach ($node_field as $delta => $item) {
            $node_field[$delta]['view'] = content_format($field, $item, $formatter, $node);
          $value = theme('field', $node, $field, $node_field, $teaser, $page);
        }
      }
      $return[$field['field_name']] = array(
        '#weight' => $field['widget']['weight'],
        '#value' => $value,
        '#access' => $formatter != 'hidden',
      );
      // test for values in $node_field in case modules added items
      if (isset($node->$field['field_name']) || count($node_field)) {
        $node->$field['field_name'] = $node_field;
      }
Jonathan Chaffer's avatar
Jonathan Chaffer committed
 * Invoke a widget hook.
 */
function _content_widget_invoke($op, &$node) {
  $type_name = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
  $widget_types = _content_widget_types();

  $return = array();
  if (count($type['fields'])) {
    foreach ($type['fields'] as $field) {
      $node_field = isset($node->$field['field_name']) ? $node->$field['field_name'] : array();

      $module = $widget_types[$field['widget']['type']]['module'];
      $function = $module .'_widget';
      if (function_exists($function)) {
        // If we're building a node creation form, pre-fill with default values
        if ($op == 'prepare form values' && empty($node->nid)) {
          $node_field = array_merge($node_field, content_default_value($node, $field, $node_field));
        }

        $result = $function($op, $node, $field, $node_field);
        if (is_array($result) && $op == 'form') {
          $result[$field['field_name']]['#weight'] = $field['widget']['weight'];
        }
        if (is_array($result)) {
          $return = array_merge($return, $result);
        }
        else if (isset($result)) {
          $return[] = $result;
        }
      // test for values in $node_field in case modules added items
      if (is_object($node) && (isset($node->$field['field_name']) || count($node_field))) {
        $node->$field['field_name'] = $node_field;
John VanDyk's avatar
John VanDyk committed
/**
 * Return a list of all content types.
 * @param $content_type_name
 *   If set, return information on just this type.
 */
function content_types($type_name = NULL) {
  // handle type name with either an underscore or a dash
  $type_name = !empty($type_name) ? str_replace('-', '_', $type_name) : NULL;
  if (isset($type_name)) {
    if (isset($info['content types'][$type_name])) {
      return $info['content types'][$type_name];
    }
    else {
      return NULL;
    }
  }
  return $info['content types'];
}

/**
 * Return a list of all fields.
 *
 * @param $field_name
 *   If set, return information on just this field.
 * @param $content_type_name
 *   If set, return information of the field within the context of this content
 *   type.
function content_fields($field_name = NULL, $content_type_name = NULL) {
  if (isset($field_name)) {
    if (isset($info['fields'][$field_name])) {
      if (isset($content_type_name)) {
        if (isset($info['content types'][$content_type_name]['fields'][$field_name])) {
          return $info['content types'][$content_type_name]['fields'][$field_name];
        }
        else {
          return NULL;
        }
      }
      else {
        return $info['fields'][$field_name];
      }
    }
    else {
      return NULL;
    }
  }
  return $info['fields'];
}

/**
 * Return a list of field types.
 */
function _content_field_types() {
  $info = _content_type_info();
  return $info['field types'];
}

/**
 * Return a list of widget types.
 */
function _content_widget_types() {
  $info = _content_type_info();
  return $info['widget types'];
}

/**
 * Collate all information on content types, fields, and related structures.
 *
Jonathan Chaffer's avatar
Jonathan Chaffer committed
 * @param $reset
 *   If TRUE, clear the cache and fetch the information from the database again.
John VanDyk's avatar
John VanDyk committed
 */
function _content_type_info($reset = FALSE) {
  static $info;
    if ($cached = cache_get('content_type_info', 'cache_content')) {
John VanDyk's avatar
John VanDyk committed
    }
    else {
      $info = array(
        'field types' => array(),
        'widget types' => array(),
        'fields' => array(),
        'content types' => array(),
      );

      foreach (module_list() as $module) {
        $module_field_types = module_invoke($module, 'field_info');
        if ($module_field_types) {
          foreach ($module_field_types as $name => $field_info) {
            // Truncate names to match the value that is stored in the database.
            $db_name = substr($name, 0, 32);
            $info['field types'][$db_name] = $field_info;
            $info['field types'][$db_name]['module'] = $module;
            $info['field types'][$db_name]['formatters'] = array();
        $module_widgets = module_invoke($module, 'widget_info');
        if ($module_widgets) {
          foreach ($module_widgets as $name => $widget_info) {
            // Truncate names to match the value that is stored in the database.
            $db_name = substr($name, 0, 32);
            $info['widget types'][$db_name] = $widget_info;
            $info['widget types'][$db_name]['module'] = $module;
            foreach ($widget_info['field types'] as $delta => $type) {
              $info['widget types'][$db_name][$delta] = substr($type, 0, 32);
            }
        $module_formatters = module_invoke($module, 'field_formatter_info');
        if ($module_formatters) {
          foreach ($module_formatters as $name => $formatter_info) {
            foreach ($formatter_info['field types'] as $field_type) {
              // Truncate names to match the value that is stored in the database.
              $db_name = substr($field_type, 0, 32);
              $info['field types'][$db_name]['formatters'][$name] = $formatter_info;
              $info['field types'][$db_name]['formatters'][$name]['module'] = $module;
      $field_result = db_query('SELECT * FROM {node_field}');
      while ($field = db_fetch_array($field_result)) {
        $global_settings = $field['global_settings'] ? unserialize($field['global_settings']) : array();
        unset($field['global_settings']);
        // Preventative error handling for PHP5 if field nodule hasn't created an arrray.
        if (is_array($global_settings)) {
          $field = array_merge($field, $global_settings);
        }
        $instance_info = db_fetch_array(db_query("SELECT type_name, label FROM {node_field_instance} WHERE field_name = '%s'", $field['field_name']));
        $field['widget']['label'] = $instance_info['label'];
        $field['type_name'] = $instance_info['type_name'];
        $info['fields'][$field['field_name']] = $field;
      }

      $type_result = db_query('SELECT * FROM {node_type} ORDER BY type ASC');
      while ($type = db_fetch_array($type_result)) {
        $type['url_str'] = str_replace('_', '-', $type['type']);
        $field_result = db_query("SELECT * FROM {node_field_instance} nfi WHERE nfi.type_name = '%s' ORDER BY nfi.weight ASC, nfi.label ASC", $type['type']);
John VanDyk's avatar
John VanDyk committed
        while ($field = db_fetch_array($field_result)) {
          // Overwrite global field information with specific information
          $field = array_merge($info['fields'][$field['field_name']], $field);

          $widget_settings = $field['widget_settings'] ? unserialize($field['widget_settings']) : array();
          unset($field['widget_settings']);
          $field['widget'] = $widget_settings;
          $field['widget']['type'] = $field['widget_type'];
          unset($field['widget_type']);
          $field['widget']['weight'] = $field['weight'];
          unset($field['weight']);
          $field['widget']['label'] = $field['label'];
          unset($field['label']);
          $field['widget']['description'] = $field['description'];
          unset($field['description']);
          $field['display_settings'] = $field['display_settings'] ? unserialize($field['display_settings']) : array();
          $type['fields'][$field['field_name']] = $field;
John VanDyk's avatar
John VanDyk committed
        }
        $info['content types'][$type['type']] = $type;
John VanDyk's avatar
John VanDyk committed
      }
      cache_set('content_type_info', 'cache_content', serialize($info));
John VanDyk's avatar
John VanDyk committed
    }
  }
/**
 *  Implementation of hook_node_type()
 *  React to change in node types
 */
function content_node_type($op, $info) {
  switch ($op) {
      include_once('./'. drupal_get_path('module', 'content') .'/content_crud.inc');
      include_once('./'. drupal_get_path('module', 'content') .'/content_crud.inc');
      include_once('./'. drupal_get_path('module', 'content') .'/content_crud.inc');
Jonathan Chaffer's avatar
Jonathan Chaffer committed
 * Clear the cache of content_types; called in several places when content
 * information is changed.
 */
function content_clear_type_cache() {
  // If type type cache needs to be cleared because type information has
  // changed, the cached node values also need to be emptied so they
  // don't carry stale values.
  // TODO see if there is a less destructive way to do this.
  cache_clear_all('*', 'cache_content', TRUE);
    // Needed because this can be called from .install files
    include_once('./'. drupal_get_path('module', 'views') .'/views.module');
/**
 * Retrieve the database storage location(s) for a field.
 *
 * @param $field
 *   The field whose database information is requested.
 * @return
 *   An array with the keys:
 *     "table": The name of the database table where the field data is stored.
 *     "columns": An array of columns stored for this field. Each is a collection
 *       of information returned from hook_field_settings('database columns'),
 *       with the addition of a "column" attribute which holds the name of the
 *       database column that stores the data.
 */
function content_database_info($field) {
  $field_types = _content_field_types();
  $module = $field_types[$field['type']]['module'];
  $columns = module_invoke($module, 'field_settings', 'database columns', $field);
  if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) {
    $db_info['table'] = _content_tablename($field['field_name'], CONTENT_DB_STORAGE_PER_FIELD);
    $db_info['table'] = _content_tablename($field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);

  if (is_array($columns) && count($columns)) {
    $db_info['columns'] = $columns;
    foreach ($columns as $column_name => $attributes) {
      $db_info['columns'][$column_name]['column'] = $field['field_name'] .'_'. $column_name;
/**
 * Manipulate a 2D array to reverse rows and columns.
 *
 * The default data storage for fields is delta first, column names second.
 * This is sometimes inconvenient for field modules, so this function can be
 * used to present the data in an alternate format.
 *
 * @param $array
 *   The array to be transposed. It must be at least two-dimensional, and
 *   the subarrays must all have the same keys or behavior is undefined.
 *
 * @return
 *   The transposed array.
 */
function content_transpose_array_rows_cols($array) {
  $result = array();
  if (is_array($array)) {
    foreach ($array as $key1 => $value1) {
      if (is_array($value1)) {
        foreach ($value1 as $key2 => $value2) {
          if (!isset($result[$key2])) {
            $result[$key2] = array();
          }
          $result[$key2][$key1] = $value2;
        }
/**
 * Format a field item for display.
 *
 * @param $field
 *   Either a field array or the name of the field.
 * @param $item
 *   The field item to be formatted (such as $node->field_foo[0]).
 * @param $formatter
 *   The name of the formatter to use.
 * @param $node
 *   Optionally, the containing node object for context purposes.
 *
 * @return
 *   A string containing the contents of the field item sanitized for display.
 *   It will have been passed through the necessary check_plain() or check_markup()
 *   functions as necessary.
 */
function content_format($field, $item, $formatter = 'default', $node = NULL) {
  if (!is_array($field)) {
    $field = content_fields($field);
  }
  $field_types = _content_field_types();
  $formatters = $field_types[$field['type']]['formatters'];
  if (!isset($formatter, $formatters)) {
    $formatter = 'default';
  }
  return module_invoke($formatters[$formatter]['module'], 'field_formatter', $field, $item, $formatter, $node);
}

/**
 * Format an individual field for display.
 *
 * @param $node
 *   The node being displayed (provided for context).