You are here

content.module in Content Construction Kit (CCK) 5

Same filename and directory in other branches
  1. 6.3 content.module
  2. 6 content.module
  3. 6.2 content.module

Allows administrators to associate custom fields to content types.

File

content.module
View source
<?php

/**
 * @file
 * Allows administrators to associate custom fields to content types.
 */
define('CONTENT_DB_STORAGE_PER_FIELD', 0);
define('CONTENT_DB_STORAGE_PER_CONTENT_TYPE', 1);
define('CONTENT_CALLBACK_NONE', 0x1);
define('CONTENT_CALLBACK_DEFAULT', 0x2);
define('CONTENT_CALLBACK_CUSTOM', 0x4);
function content_help($section) {
  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)',
  );
}

/**
 * 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');
  }
  $items = array();
  $access = user_access('administer content types');
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/content/types/fields',
      'title' => t('Fields'),
      'callback' => '_content_admin_type_fields',
      'access' => $access,
      'type' => MENU_LOCAL_TASK,
    );
  }
  else {
    if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'types') {
      $content_type = content_types(arg(3));
      $type = node_get_types('type', $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',
          'title' => t('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'),
          'callback' => 'drupal_get_form',
          'access' => $access,
          'callback arguments' => array(
            'content_admin_field_overview_form',
            $content_type['type'],
          ),
          'type' => MENU_LOCAL_TASK,
          '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',
          'title' => t('Add field'),
          'callback' => '_content_admin_field_add',
          'access' => $access,
          'callback arguments' => array(
            $content_type['type'],
          ),
          'type' => MENU_LOCAL_TASK,
          'weight' => 2,
        );
        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'),
            'callback' => 'drupal_get_form',
            'access' => $access,
            'callback arguments' => array(
              '_content_admin_field_remove',
              $content_type['type'],
              arg(5),
            ),
            'type' => MENU_CALLBACK,
          );
        }
      }
    }
  }
  return $items;
}

/**
 * Load data for a node type's fields.
 *
 * 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.
 */
function content_load(&$node) {
  $cid = 'content:' . $node->nid . ':' . $node->vid;
  if ($cached = cache_get($cid, 'cache_content')) {
    $extra = unserialize($cached->data);
    foreach ($extra as $key => $value) {
      $node->{$key} = $value;
    }
    return $extra;
  }
  else {
    $default_additions = _content_field_invoke_default('load', $node);
    if ($default_additions) {
      foreach ($default_additions as $key => $value) {
        $node->{$key} = $value;
      }
    }
    $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));
    return $default_additions;
  }
}

/**
 * Create fields' form for a content type.
 *
 * Each field defines its own component of the content entry form, via its
 * chosen widget.
 */
function content_form(&$node) {
  $form = array();
  $type = content_types($node->type);

  // Set form parameters so we can accept file uploads.
  if (count($type['fields'])) {
    $form['#attributes'] = array(
      "enctype" => "multipart/form-data",
    );
  }
  _content_widget_invoke('prepare form values', $node);
  $form = array_merge($form, _content_widget_invoke('form', $node));
  return $form;
}

/**
 * Validate form callback to handle node type fields.
 *
 * Both widgets and fields have a chance to raise error flags when a node is
 * being validated.
 */
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);
}

/**
 * Submit form callback for node type fields.
 *
 * 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.
 */
function content_submit(&$node) {
  _content_widget_invoke('submit', $node);
  _content_widget_invoke('process form values', $node);
  _content_field_invoke('submit', $node);
  _content_field_invoke_default('submit', $node);
}

/**
 * Insert node type fields.
 */
function content_insert(&$node) {
  _content_field_invoke('insert', $node);
  _content_field_invoke_default('insert', $node);
}

/**
 * Update node type fields.
 */
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');
}

/**
 * Delete node type fields.
 */
function content_delete(&$node) {
  $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');
}

/**
 * Generate field render arrays.
 */
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);
}

/**
 * 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);
      break;
    case 'submit':
      content_submit($node);
      break;
    case 'insert':
      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':
    case 'print':
      content_view($node, $teaser, $page);
      break;
  }
}

/**
 *  Implementation of hook_form_alter().
 */
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';
      if ($field['multiple']) {
        $result = db_query($query . ' ORDER BY delta', $node->vid);
      }
      else {
        $result = db_query_range($query, $node->vid, 0, 1);
      }
      $additions = array();
      while ($value = db_fetch_array($result)) {
        $additions[$field['field_name']][] = $value;
      }
      return $additions;
    case 'insert':
      foreach ($node_field as $delta => $item) {
        $data = array();
        $column_names = array();
        $column_placeholders = array();
        $column_assignments = 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 'tinyint':
              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[] = $item[$column];
        }
        $data[] = $node->vid;
        $data[] = $node->nid;
        if ($field['multiple']) {
          $data[] = $delta;
        }
        if ($field['multiple']) {
          db_query('INSERT INTO {' . $db_info['table'] . '} (' . implode(', ', $column_names) . ', vid, nid, delta) VALUES (' . implode(', ', $column_placeholders) . ', %d, %d, %d)', $data);
        }
        else {
          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 'update':
      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) {
        $data = array();
        $column_names = array();
        $column_placeholders = array();
        $column_assignments = 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 'tinyint':
              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[] = $item[$column];
        }
        $data[] = $node->vid;
        $data[] = $node->nid;
        if ($field['multiple']) {
          $data[] = $delta;
        }
        if ($field['multiple']) {
          db_query('INSERT INTO {' . $db_info['table'] . '} (' . implode(', ', $column_names) . ', vid, nid, delta) VALUES (' . implode(', ', $column_placeholders) . ', %d, %d, %d)', $data);
        }
        else {
          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);
      return;
    case 'delete revision':
      db_query('DELETE FROM {' . $db_info['table'] . '} WHERE vid = %d', $node->vid);
      return;
  }
}

/**
 * Invoke a field hook.
 *
 * For each operation, both this function and _content_field_invoke_default() are
 * called so that the default database handling can occur.
 */
function _content_field_invoke($op, &$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();
  $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);
        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 on insert
      if (isset($node->{$field}['field_name']) || count($node_field)) {
        $node->{$field}['field_name'] = $node_field;
      }
    }
  }
  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);
  $type = content_types($type_name);
  $return = array();
  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);
        if (is_array($result)) {
          $return = array_merge($return, $result);
        }
        else {
          if (isset($result)) {
            $return[] = $result;
          }
        }
      }
      if (isset($node->{$field}['field_name'])) {
        $node->{$field}['field_name'] = $node_field;
      }
    }
  }
  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);
          }
        }
        else {
          foreach ($node_field as $delta => $item) {
            $item['#delta'] = $delta;
            $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;
      }
    }
  }
  return $return;
}

/**
 * Invoke a widget hook.
 */
function _content_widget_invoke($op, &$node) {
  $type_name = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
  $type = content_types($type_name);
  $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;
      }
    }
  }
  return $return;
}

/**
 * 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;
  $info = _content_type_info();
  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) {
  $info = _content_type_info();

  // If there is no field name specified, return the entire fields array.
  if (!isset($field_name)) {
    return $info['fields'];
  }
  elseif (!isset($info['fields'][$field_name])) {
    return NULL;
  }
  elseif (!isset($content_type_name)) {
    return $info['fields'][$field_name];
  }
  elseif (isset($info['content types'][$content_type_name]['fields'][$field_name])) {
    return $info['content types'][$content_type_name]['fields'][$field_name];
  }

  // If all else fails, return NULL.
  return NULL;
}

/**
 * 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.
 *
 * @param $reset
 *   If TRUE, clear the cache and fetch the information from the database again.
 */
function _content_type_info($reset = FALSE) {
  static $info;
  if ($reset || !isset($info)) {
    if ($cached = cache_get('content_type_info', 'cache_content')) {
      $info = unserialize($cached->data);
    }
    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);
            }
          }
        }
      }
      foreach (module_list() as $module) {
        $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']);
        $type['fields'] = array();
        $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']);
        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['type_name'] = $type['type'];
          $field['display_settings'] = $field['display_settings'] ? unserialize($field['display_settings']) : array();
          $type['fields'][$field['field_name']] = $field;
        }
        $info['content types'][$type['type']] = $type;
      }
      cache_set('content_type_info', 'cache_content', serialize($info));
    }
  }
  return $info;
}

/**
 *  Implementation of hook_node_type()
 *  React to change in node types
 */
function content_node_type($op, $info) {
  switch ($op) {
    case 'insert':
      include_once './' . drupal_get_path('module', 'content') . '/content_crud.inc';
      content_type_create($info);
      break;
    case 'update':
      include_once './' . drupal_get_path('module', 'content') . '/content_crud.inc';
      content_type_update($info);
      break;
    case 'delete':
      include_once './' . drupal_get_path('module', 'content') . '/content_crud.inc';
      content_type_delete($info);
      break;
  }
}

/**
 * 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);
  _content_type_info(TRUE);
  if (module_exists('views')) {

    // Needed because this can be called from .install files
    include_once './' . drupal_get_path('module', 'views') . '/views.module';
    views_invalidate_cache();
  }
}

/**
 * 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);
  $db_info = array();
  if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) {
    $db_info['table'] = _content_tablename($field['field_name'], CONTENT_DB_STORAGE_PER_FIELD);
  }
  else {
    $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;
    }
  }
  else {
    $db_info['columns'] = array();
  }
  return $db_info;
}

/**
 * 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;
        }
      }
    }
  }
  return $result;
}

/**
 * Like filter_xss_admin(), but with a shorter list of allowed tags.
 *
 * Used for items entered by administrators, like field descriptions,
 * allowed values, where some (mainly inline) mark-up may be desired
 * (so check_plain() is not acceptable).
 */
function content_filter_xss($string) {
  return filter_xss($string, _content_filter_xss_allowed_tags());
}

/**
 * List of tags allowed by content_filter_xss().
 */
function _content_filter_xss_allowed_tags() {
  return array(
    'a',
    'b',
    'big',
    'code',
    'del',
    'em',
    'i',
    'ins',
    'pre',
    'q',
    'small',
    'span',
    'strong',
    'sub',
    'sup',
    'tt',
    'ol',
    'ul',
    'li',
    'p',
    'br',
    'img',
  );
}

/**
 * Human-readable list of allowed tags, for display in help texts.
 */
function _content_filter_xss_display_allowed_tags() {
  return '<' . implode('> <', _content_filter_xss_allowed_tags()) . '>';
}

/**
 * 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, $formatters[$formatter])) {
    $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).
 * @param $field
 *   The field that is to be displayed (for information about the label, field
 *   type, and so forth).
 * @param $items
 *   The actual items to theme. This will be a linear array, each element of
 *   which has a "view" property which contains the filtered, formatted contents
 *   of the item.
 * @param $teaser
 *   Whether the node is being displayed as a teaser or full version.
 * @param $page
 *   Whether the node is being displayed as a full web page.
 *
 * @return
 *   An HTML string containing the fully themed field.
 */
function theme_field(&$node, &$field, &$items, $teaser, $page) {
  $label_display = isset($field['display_settings']['label']['format']) ? $field['display_settings']['label']['format'] : 'above';
  $label = check_plain(t($field['widget']['label']));
  $items_output = '';
  $count = 1;
  foreach ($items as $delta => $item) {
    if (!empty($item['view']) || $item['view'] === "0") {
      $items_output .= '<div class="field-item ' . ($count % 2 ? 'odd' : 'even') . '">';
      if ($label_display == 'inline') {
        $items_output .= '<div class="field-label-inline' . ($delta ? '' : '-first') . '">';
        $items_output .= $label . ':&nbsp;</div>';
      }
      $items_output .= $item['view'] . '</div>';
      $count++;
    }
  }
  $output = '';
  if (!empty($items_output)) {
    $output .= '<div class="field field-type-' . strtr($field['type'], '_', '-') . ' field-' . strtr($field['field_name'], '_', '-') . '">';
    if ($label_display == 'above') {
      $output .= '<div class="field-label">' . $label . ':&nbsp;</div>';
    }
    $output .= '<div class="field-items">' . $items_output . '</div>';
    $output .= '</div>';
  }
  return $output;
}

/**
 * Generate a table name for a field or a content type.
 *
 * @param $name
 *   The name of the content type or content field
 * @param $entity
 *   'field' or 'type'
 *
 * @return
 *   A string containing the generated name for the database table
 */
function _content_tablename($name, $entity, $version = NULL) {
  if (is_null($version)) {
    $version = variable_get('content_schema_version', 0);
  }
  if ($version < 1003) {
    $version = 0;
  }
  else {
    $version = 1003;
  }
  $name = str_replace('-', '_', $name);
  switch ("{$version}-{$entity}") {
    case '0-' . CONTENT_DB_STORAGE_PER_CONTENT_TYPE:
      return "node_{$name}";
    case '0-' . CONTENT_DB_STORAGE_PER_FIELD:
      return "node_data_{$name}";
    case '1003-' . CONTENT_DB_STORAGE_PER_CONTENT_TYPE:
      return "content_type_{$name}";
    case '1003-' . CONTENT_DB_STORAGE_PER_FIELD:
      return "content_{$name}";
  }
}

/**
 *  Helper function for determining the behaviour of a field or a widget
 *  with respect to a given operation
 *  (currently only used for 'view' field op and 'default value' widegt op)
 *
 *  @param $entity
 *    'field' or 'widget'
 *
 *  @param $op
 *    the name of the operation ('view', 'validate'...)
 *
 *  @param $field
 *    The field array, including widget info.
 * @return
 *    CONTENT_CALLBACK_NONE    - do nothing for this operation
 *    CONTENT_CALLBACK_CUSTOM  - use the module's callback function.
 *    CONTENT_CALLBACK_DEFAULT - use content module default behaviour
 *
 */
function content_handle($entity, $op, $field) {
  $entity_types = $entity == 'field' ? _content_field_types() : _content_widget_types();
  $entity_type = $entity == 'field' ? $field['type'] : $field['widget']['type'];
  $module = $entity_types[$entity_type]['module'];
  if ($op == 'default value' && $module == 'computed_field') {
    $callback_value = CONTENT_CALLBACK_NONE;
  }
  else {
    $callbacks = module_invoke($module, "{$entity}_settings", 'callbacks', $field);
    $callback_value = isset($callbacks[$op]) ? $callbacks[$op] : CONTENT_CALLBACK_DEFAULT;
  }
  return $callback_value;
}

/**
 *  Helper function to return the correct default value for a field.
 *
 *  @param $node
 *    The node.
 *
 *  @param $field
 *    The field array.
 *
 *  @param $items
 *    The value of the field in the node.
 *
 *  @return
 *    The default value for that field.
 */
function content_default_value($node, $field, $items) {
  $widget_types = _content_widget_types();
  $module = $widget_types[$field['widget']['type']]['module'];
  $default_value = array();
  if (content_handle('widget', 'default value', $field) == CONTENT_CALLBACK_CUSTOM) {
    $function = $module . '_widget';
    $default_value = $function('default value', $node, $field, $items);
  }
  elseif (!empty($field['widget']['default_value_php'])) {
    ob_start();
    $result = eval($field['widget']['default_value_php']);
    ob_end_clean();
    if (is_array($result)) {
      $default_value = $result;
    }
  }
  elseif (!empty($field['widget']['default_value'])) {
    $default_value = $field['widget']['default_value'];
  }
  return (array) $default_value;
}

Functions

Namesort descending Description
content_clear_type_cache Clear the cache of content_types; called in several places when content information is changed.
content_database_info Retrieve the database storage location(s) for a field.
content_default_value Helper function to return the correct default value for a field.
content_delete Delete node type fields.
content_delete_revision delete node type fields for a revision.
content_devel_caches Implementation of hook_devel_caches(). Include {cache_content} in the list of tables cleared by devel's 'empty cache'
content_enable Make sure that CCK content type info is synched with node type data any time the content module is enabled.
content_field Implementation of hook_field(). Handles common field housekeeping.
content_fields Return a list of all fields.
content_filter_xss Like filter_xss_admin(), but with a shorter list of allowed tags.
content_form Create fields' form for a content type.
content_format Format a field item for display.
content_form_alter Implementation of hook_form_alter().
content_handle Helper function for determining the behaviour of a field or a widget with respect to a given operation (currently only used for 'view' field op and 'default value' widegt op)
content_help
content_init Implementation of hook_init().
content_insert Insert node type fields.
content_load Load data for a node type's fields.
content_menu Implementation of hook_menu().
content_nodeapi Implementation of hook_nodeapi().
content_node_type Implementation of hook_node_type() React to change in node types
content_perm Implementation of hook_perm().
content_submit Submit form callback for node type fields.
content_transpose_array_rows_cols Manipulate a 2D array to reverse rows and columns.
content_types Return a list of all content types.
content_update Update node type fields.
content_validate Validate form callback to handle node type fields.
content_view Generate field render arrays.
theme_field Format an individual field for display.
_content_field_invoke Invoke a field hook.
_content_field_invoke_default Invoke content.module's version of a field hook.
_content_field_types Return a list of field types.
_content_field_view Format field output based on display settings.
_content_filter_xss_allowed_tags List of tags allowed by content_filter_xss().
_content_filter_xss_display_allowed_tags Human-readable list of allowed tags, for display in help texts.
_content_tablename Generate a table name for a field or a content type.
_content_type_info Collate all information on content types, fields, and related structures.
_content_widget_invoke Invoke a widget hook.
_content_widget_types Return a list of widget types.

Constants

Namesort descending Description
CONTENT_CALLBACK_CUSTOM
CONTENT_CALLBACK_DEFAULT
CONTENT_CALLBACK_NONE
CONTENT_DB_STORAGE_PER_CONTENT_TYPE
CONTENT_DB_STORAGE_PER_FIELD @file Allows administrators to associate custom fields to content types.