Skip to content
content.module 44.4 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);

define('CONTENT_HANDLE_CORE', 0x0001);
define('CONTENT_HANDLE_MODULE', 0x0002);

function content_help($path, $arg) {
  if (preg_match('!^admin/content/types/.*/display$!', $path)) {
    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() {
  drupal_add_css(drupal_get_path('module', 'content') .'/content.css');
  if (module_exists('views')) {
    module_load_include('inc', 'content', 'content_views');
  }
  if (module_exists('pathauto')) {
    module_load_include('inc', 'content', 'content_pathauto');
  }
John VanDyk's avatar
John VanDyk committed
}

/**
 * Implementation of hook_menu().
 */
function content_menu() {
John VanDyk's avatar
John VanDyk committed
  $items = array();
  $items['admin/content/types/fields'] = array(
    'page callback' => '_content_admin_type_fields',
    'access arguments' => array('administer content types'),
    'file' => 'content_admin.inc',
    'file path' => drupal_get_path('module', 'content'),
    'type' => MENU_LOCAL_TASK,
  );
  foreach (node_get_types() as $type) {
    $type_name = $type->type;
    $content_type = content_types($type_name);
    $type_url_str = $content_type['url_str'];

    $items['admin/content/types/'. $type_url_str .'/fields'] = array(
      'page callback' => 'drupal_get_form',
      'page arguments' => array('content_admin_field_overview_form', $type_name),
      'access arguments' => array('administer content types'),
      'file' => 'content_admin.inc',
      'file path' => drupal_get_path('module', 'content'),
      'type' => MENU_LOCAL_TASK,
      'weight' => 0,
    );
    $items['admin/content/types/'. $type_url_str .'/display'] = array(
      'page callback' => 'drupal_get_form',
      'page arguments' => array('content_admin_display_overview_form', $type_name),
      'access arguments' => array('administer content types'),
      'file' => 'content_admin.inc',
      'file path' => drupal_get_path('module', 'content'),
      'type' => MENU_LOCAL_TASK,
      'weight' => 1,
    );
    $items['admin/content/types/'. $type_url_str .'/add_field'] = array(
      'page callback' => '_content_admin_field_add',
      'page arguments' => array($type_name),
      'access arguments' => array('administer content types'),
      'file' => 'content_admin.inc',
      'file path' => drupal_get_path('module', 'content'),
      'type' => MENU_LOCAL_TASK,
      'weight' => 2,
    );
    foreach ($content_type['fields'] as $field) {
      $field_name = $field['field_name'];
      $items['admin/content/types/'. $type_url_str .'/fields/'. $field_name] = array(
        'page callback' => 'drupal_get_form',
        'page arguments' => array('_content_admin_field', $type_name, $field_name),
        'access arguments' => array('administer content types'),
        'file' => 'content_admin.inc',
        'file path' => drupal_get_path('module', 'content'),
        'type' => MENU_CALLBACK,
      );
      $items['admin/content/types/'. $type_url_str .'/fields/'. $field_name .'/remove'] = array(
        'page callback' => 'drupal_get_form',
        'page arguments' => array('_content_admin_field_remove', $type_name, $field_name),
        'access arguments' => array('administer content types'),
        'file' => 'content_admin.inc',
        'file path' => drupal_get_path('module', 'content'),
        'type' => MENU_CALLBACK,
John VanDyk's avatar
John VanDyk committed
    }
  }
  return $items;
}

/**
 * Implementation of hook_theme().
 */
function content_theme() {
  return array(
      'arguments' => array('node' => NULL, 'field' => NULL, 'items' => NULL, 'teaser' => FALSE, 'page' => FALSE),
    'content_admin_field_overview_form' => array(
    'content_admin_display_overview_form' => array(
    'content_admin_field_add_new_field_widget_type' => array(
    'content_view_multiple_field' => array(
      'arguments' => array('items' => NULL, 'field' => NULL, 'data' => NULL),
    'content_multiple_values' => array(
      'arguments' => array('element' => NULL),
    ),
John VanDyk's avatar
John VanDyk committed
/**
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
  }
  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) {
    cache_set($cid, $default_additions, 'cache_content');
Jonathan Chaffer's avatar
Jonathan Chaffer committed
  }
John VanDyk's avatar
John VanDyk committed
}

/**
 * Create fields' form for a content type.
 * Widget_invoke() is gone, this is the only place the widget function
 * is called.
 *
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
 */
function content_form(&$form, &$form_state) {
  $node = $form['#node'];
  $type_name = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
  $type = content_types($type_name);
  if (count($type['fields'])) {
    foreach ($type['fields'] as $field) {
      $form += content_form_field($form, $form_state, $field);
    }
  }
John VanDyk's avatar
John VanDyk committed
}

/**
 * Create a separate form element for each field.
 *
 * Extracted from content_form() to make it possible to get a subform
 * for just a single field at a time.
 *
 * Hook_widget() picks up two new values, $count and $delta, to help
 * widgets know what information to return since multiple values are
 * sometimes controlled by the content module.
 * @param $form
 *   the form to add this field element to
 * @param $form_state
 *   the form_state for the above form
 * @param $field
 *   the field array to use to create the form element
function content_form_field(&$form, &$form_state, $field) {
  $node = (object) $form['#node'];

  $widget_types = _content_widget_types();
  $module = $widget_types[$field['widget']['type']]['module'];
  $function = $module .'_widget';
  if (function_exists($function)) {
Yves Chedemois's avatar
Yves Chedemois committed
    // Prepare the values to be filled in the widget.
    // We look in the following places :
    // - Form submitted values
    // - Node values (when editing an existing node), or pre-filled values (when
    //   creating a new node translation)
    // - Default values set for the field (when creating a new node).
    $items = array();
    if (!empty($form_state['values'][$field['field_name']])) {
      $items = $form_state['values'][$field['field_name']];
    }
    elseif (!empty($node->$field['field_name'])) {
      $items = $node->$field['field_name'];
    }
Yves Chedemois's avatar
Yves Chedemois committed
    elseif (empty($node->nid)) {
      if (content_callback('widget', 'default value', $field) != CONTENT_CALLBACK_NONE) {
        $callback = content_callback('widget', 'default value', $field) == CONTENT_CALLBACK_CUSTOM ? $module .'_default_value' : 'content_default_value';
        if (function_exists($callback)) {
Yves Chedemois's avatar
Yves Chedemois committed
// TODO : what's the need to pass $items as an argument ?
          $items = $callback($form, $form_state, $field, $items, 0);
Yves Chedemois's avatar
Yves Chedemois committed
        }
      }
    }
    $columns = array_keys($db_info['columns']);

    $form_element = array();

    // If content module handles multiple values for this form element,
    // make this a content_multiple_values element type and iterate through
    // the multiple elements to add them to the form.
    if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
      $form_element['#type'] = 'content_multiple_values';

      // @TODO
      // Here's where we can intervene to control the number of items
      // or add javascript to dynamically add more items,
      // or take control over the delta values in some way.
      $max = !empty($field['multiple']) ? count($items) + 2 : 0;
      for ($delta = 0; $delta <= $max; $delta++) {
        if ($element = $function($form, $form_state, $field, $items, $delta)) {
          $defaults = array(
            '#weight'   => $delta,
            '#required' => $delta == 0 && $field['required'],
            // TODO : should we add some logic for title and description too ?
            '#delta'    => $delta,
            '#columns'  => $columns,
            '#field'    => $field,
          );
          $form_element[$delta] = array_merge($element, $defaults);
        }
      }
      // If the widget is handling multiple values (e.g optionwidgets),
      // just get the field's form element and make it the zero value.
      if ($element = $function($form, $form_state, $field, $items)) {
        $defaults = array(
          '#required' => $field['required'],
          // TODO : should we add some logic for title and description too ?
          '#columns'  => $columns,
          '#field'    => $field,
        );
        $form_element[0] = array_merge($element, $defaults);

  if ($form_element) {
    $defaults = array(
      '#tree' => TRUE,
      '#weight' => $field['widget']['weight'],
      '#field' => $field,
      // TODO : those could probably go ?
      //        '#columns' => array_keys($db_info['columns']),
    );
    $addition[$field['field_name']] = array_merge($form_element, $defaults);
/**
 * Nodeapi 'validate' op.
 *
 */
function content_validate(&$node) {
  _content_field_invoke('validate', $node);
  _content_field_invoke_default('validate', $node);
}

/**
 * Nodeapi 'presave' op.
 *
 */
function content_presave(&$node) {
  _content_field_invoke('presave', $node);
  _content_field_invoke_default('presave', $node);
}

Yves Chedemois's avatar
Yves Chedemois committed
 * Nodeapi 'insert' op.
 *
 */
function content_insert(&$node) {
  _content_field_invoke('insert', $node);
  _content_field_invoke_default('insert', $node);
Yves Chedemois's avatar
Yves Chedemois committed
 * Nodeapi 'update' op.
 *
 */
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');
Yves Chedemois's avatar
Yves Chedemois committed
 * Nodeapi 'delete' op.
 *
  $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);
Yves Chedemois's avatar
Yves Chedemois committed
 * Nodeapi 'delete_revision' op.
 *
 * 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
/**
Yves Chedemois's avatar
Yves Chedemois committed
 * Nodeapi 'view' op.
 *
John VanDyk's avatar
John VanDyk committed
 */
function content_view(&$node, $teaser = FALSE, $page = FALSE) {
  $additions = _content_field_invoke_default('view', $node);
  $node->content = array_merge((array) $node->content, $additions);
John VanDyk's avatar
John VanDyk committed
}

/**
 * Nodeapi 'prepare translation' op.
 *
 * Generate field render arrays.
 */
Yves Chedemois's avatar
Yves Chedemois committed
function content_prepare_translation(&$node) {
  $default_additions = _content_field_invoke_default('prepare translation', $node);
  $node = (object) array_merge((array) $node, $default_additions);
/**
 * Implementation of hook_nodeapi().
 */
function content_nodeapi(&$node, $op, $teaser, $page) {
  switch ($op) {
Yves Chedemois's avatar
Yves Chedemois committed

Yves Chedemois's avatar
Yves Chedemois committed

    case 'insert':
      content_insert($node);
      break;

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

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

Yves Chedemois's avatar
Yves Chedemois committed
    case 'delete revision':
      content_delete_revision($node);
      break;

    case 'view':
      content_view($node, $teaser, $page);
Yves Chedemois's avatar
Yves Chedemois committed
    case 'prepare translation':
      content_prepare_translation($node);
/**
 *  Implementation of hook_form_alter().
 */
function content_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type'])) {
    if ($form['type']['#value'] .'_node_form' == $form_id) {
      $form = array_merge($form, content_form($form, $form_state));
 *  Make sure that CCK content type info is synched with node type data
 *  any time the content module is enabled.
function content_enable() {
  module_load_include('inc', 'content', 'content_admin');
  module_load_include('inc', 'content', 'content_crud');
  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
Yves Chedemois's avatar
Yves Chedemois committed
 * takes care of default stuff common to all field types.
function content_field($op, &$node, $field, &$node_field, $teaser, $page) {
  $db_info = content_database_info($field);
Yves Chedemois's avatar
Yves Chedemois committed

  switch ($op) {
    case 'validate':
      // TODO : here we could validate that the number of multiple data is correct ?
      break;

    case 'view':
      if ($node->build_mode == NODE_BUILD_NORMAL) {
        $context = $teaser ? 'teaser' : 'full';
      }
      else {
        $context = $node->build_mode;
      }

      $formatter = isset($field['display_settings'][$context]['format']) ? $field['display_settings'][$context]['format'] : 'default';
      $value = '';

      if ($formatter != 'hidden') {
        foreach ($node_field as $delta => $item) {
          $node_field[$delta]['view'] = content_format($field, $item, $formatter, $node);
        }
        // Do not include field labels when indexing content.
        if ($context == NODE_BUILD_SEARCH_INDEX) {
          $field['display_settings']['label']['format'] = 'hidden';
        }
        $value = theme('field', $node, $field, $node_field, $teaser, $page);
      }
      $addition[$field['field_name']] = array(
        '#weight' => $field['widget']['weight'],
        '#value' => $value,
        '#access' => $formatter != 'hidden',
      );
      return $addition;

Yves Chedemois's avatar
Yves Chedemois committed
    case 'prepare translation':
      $addition = array();
      if (isset($node->translation_source->$field['field_name'])) {
        $addition[$field['field_name']] = $node->translation_source->$field['field_name'];
      }
Yves Chedemois's avatar
Yves Chedemois committed
  }
}
Yves Chedemois's avatar
Yves Chedemois committed
/**
 * TODO : PHPdoc
 */
function content_storage($op, $node) {
  $type_name = $node->type;
  $type = content_types($type_name);

Yves Chedemois's avatar
Yves Chedemois committed
      // OPTIMIZE : load all non multiple fields in a single JOIN query ?
      // warning : 61-join limit in MySQL ?
Yves Chedemois's avatar
Yves Chedemois committed
      // For each table used by this content type,
      foreach ($type['tables'] as $table) {
        $schema = drupal_get_schema($table);
        $query = 'SELECT * FROM {'. $table .'} WHERE vid = %d';

        // If we're loading a table for a multiple field,
        // we fetch all rows (values) ordered by delta,
        // else we only fetch one row.
        $result = isset($schema['fields']['delta']) ? db_query($query .' ORDER BY delta', $node->vid) : db_query_range($query, $node->vid, 0, 1);

        // For each table row, populate the fields.
        while ($row = db_fetch_array($result)) {
          // For each field stored in the table, add the field item.
          foreach ($schema['content fields'] as $field_name) {
            $item = array();
            $field = content_fields($field_name, $type_name);
            $db_info = content_database_info($field);

            // For each column declared by the field, populate the item.
            foreach ($db_info['columns'] as $column => $attributes) {
              $item[$column] = $row[$attributes['column']];
            }

            // Add the item to the field values for the node.
            if (!isset($additions[$field_name])) {
              $additions[$field_name] = array();
            }
            $additions[$field_name][] = $item;
          }
        }
Yves Chedemois's avatar
Yves Chedemois committed
      foreach ($type['tables'] as $table) {
        $schema = drupal_get_schema($table);
Yves Chedemois's avatar
Yves Chedemois committed
        $record = array();
Yves Chedemois's avatar
Yves Chedemois committed
        foreach ($schema['content fields'] as $field_name) {
          $field = content_fields($field_name, $type_name);

          // Multiple fields need specific handling, we'll deal with them later on.
          if ($field['multiple']) {
            continue;
Yves Chedemois's avatar
Yves Chedemois committed

          $db_info = content_database_info($field);
          foreach ($db_info['columns'] as $column => $attributes) {
Yves Chedemois's avatar
Yves Chedemois committed
            $record[$attributes['column']] = $node->{$field_name}[0][$column];
Yves Chedemois's avatar
Yves Chedemois committed

Yves Chedemois's avatar
Yves Chedemois committed
        if (count($record)) {
          $record['nid'] = $node->nid;
          $record['vid'] = $node->vid;
          content_write_record($table, $record, $op == 'update' ? 'vid' : FALSE);
Yves Chedemois's avatar
Yves Chedemois committed
      }
Yves Chedemois's avatar
Yves Chedemois committed
      // Handle multiple fields.
      foreach ($type['fields'] as $field) {
Yves Chedemois's avatar
Yves Chedemois committed
          $db_info = content_database_info($field);

          // Delete and insert, rather than update, in case a value was added.
          if ($op == 'update') {
            db_query('DELETE FROM {'. $db_info['table'] .'} WHERE vid = %d', $node->vid);
Yves Chedemois's avatar
Yves Chedemois committed
          foreach ($node->$field['field_name'] as $delta => $item) {
Yves Chedemois's avatar
Yves Chedemois committed
            $record = array();
Yves Chedemois's avatar
Yves Chedemois committed
            foreach ($db_info['columns'] as $column => $attributes) {
Yves Chedemois's avatar
Yves Chedemois committed
              $record[$attributes['column']] = $item[$column];
Yves Chedemois's avatar
Yves Chedemois committed
            }
Yves Chedemois's avatar
Yves Chedemois committed
            $record['nid'] = $node->nid;
            $record['vid'] = $node->vid;
            $record['delta'] = $delta;
            content_write_record($db_info['table'], $record);
Yves Chedemois's avatar
Yves Chedemois committed
      break;
Yves Chedemois's avatar
Yves Chedemois committed
      foreach ($type['tables'] as $table) {
        db_query('DELETE FROM {'. $table .'} WHERE nid = %d', $node->nid);
      }
      break;
Yves Chedemois's avatar
Yves Chedemois committed
      foreach ($type['tables'] as $table) {
        db_query('DELETE FROM {'. $table .'} WHERE vid = %d', $node->vid);
      }
      break;
Yves Chedemois's avatar
Yves Chedemois committed
/**
 * Save a record to the database based upon the schema. Default values are
 * filled in for missing items, and 'serial' (auto increment) types are
 * filled in with IDs.
 *
 * Stolen from http://drupal.org/node/169982#comment-303480
 *
 * @param $table
 *   The name of the table; this must exist in schema API.
 * @param $object
 *   The object to write. This is a reference, as defaults according to
 *   the schema may be filled in on the object, as well as ID on the serial
 *   type(s).
 * @param update
 *   If this is an update, specify the primary keys' field names. It is the
 *   caller's responsibility to know if a record for this object already
 *   exists in the database. If there is only 1 key, you may pass a simple string.
 * @return (boolean) Failure to write a record will return FALSE. Otherwise,
 *   TRUE is returned. The $object parameter contains values for any serial
 *   fields defined by the $table. For example, $object->nid will be populated
 *   after inserting a new node.
 */
Yves Chedemois's avatar
Yves Chedemois committed
function content_write_record($table, &$object, $update = array()) {
Yves Chedemois's avatar
Yves Chedemois committed
  // Standardize $update to an array.
  if (is_string($update)) {
    $update = array($update);
  }

Yves Chedemois's avatar
Yves Chedemois committed
  // Convert to an object if needed.
  if (is_array($object)) {
    $object = (object) $object;
    $array = TRUE;
  }
  else {
    $array = FALSE;
  }

Yves Chedemois's avatar
Yves Chedemois committed
  $schema = drupal_get_schema($table);
  if (empty($schema)) {
    return FALSE;
  }

  $fields = $defs = $values = $serials = array();

Yves Chedemois's avatar
Yves Chedemois committed
  // Go through our schema, build SQL, and when inserting, fill in defaults for
Yves Chedemois's avatar
Yves Chedemois committed
  // fields that are not set.
  foreach ($schema['fields'] as $field => $info) {
    // Special case -- skip serial types if we are updating.
    if ($info['type'] == 'serial' && count($update)) {
      continue;
    }

    // For inserts, populate defaults from Schema if not already provided
    if (!isset($object->$field)  && !count($update) && isset($info['default'])) {
      $object->$field = $info['default'];
    }

Yves Chedemois's avatar
Yves Chedemois committed
    // Track serial fields so we can helpfully populate them after the query.
Yves Chedemois's avatar
Yves Chedemois committed
    if ($info['type'] == 'serial') {
      $serials[] = $field;
Yves Chedemois's avatar
Yves Chedemois committed
      // Ignore values for serials when inserting data. Unsupported.
      $object->$field = 'NULL';
Yves Chedemois's avatar
Yves Chedemois committed
    }

    // Build arrays for the fields, placeholders, and values in our query.
    if (isset($object->$field)) {
      $fields[] = $field;
      $placeholders[] = _db_type_placeholder($info['type']);

      if (empty($info['serialize'])) {
        $values[] = $object->$field;
      }
      else {
        $values[] = serialize($object->$field);
      }
    }
  }

  // Build the SQL.
  $query = '';
  if (!count($update)) {
    $query = "INSERT INTO {$table} (" . implode(', ', $fields) . ') VALUES (' . implode(', ', $placeholders) . ')';
Yves Chedemois's avatar
Yves Chedemois committed
    $return = SAVED_NEW;
Yves Chedemois's avatar
Yves Chedemois committed
  }
  else {
    $query = '';
    foreach ($fields as $id => $field) {
      if ($query) {
        $query .= ', ';
      }
      $query .= $field . ' = ' . $placeholders[$id];
    }

    foreach ($update as $key){
Yves Chedemois's avatar
Yves Chedemois committed
      $conditions[] = "$key = ". _db_type_placeholder($schema['fields'][$key]['type']);
Yves Chedemois's avatar
Yves Chedemois committed
      $values[] = $object->$key;
    }

Yves Chedemois's avatar
Yves Chedemois committed
    $query = "UPDATE {$table} SET $query WHERE ". implode(' AND ', $conditions);
    $return = SAVED_UPDATED;
Yves Chedemois's avatar
Yves Chedemois committed
  }
  db_query($query, $values);

  if ($serials) {
    // Get last insert ids and fill them in.
    foreach ($serials as $field) {
      $object->$field = db_last_insert_id($table, $field);
    }
  }
Yves Chedemois's avatar
Yves Chedemois committed

  // If we began with an array, convert back so we don't surprise the caller.
  if ($array) {
    $object = (array)$object;
  }

  return $return;
Yves Chedemois's avatar
Yves Chedemois committed
}

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);
Yves Chedemois's avatar
Yves Chedemois committed
  // The operations involving database queries are better off handled by table
  // rather than by field.
  if (in_array($op, array('load', 'insert', 'update', 'delete','delete revision'))) {
    return content_storage($op, $node);
  }
  elseif (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;
}

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) {
            $info['field types'][$name] = $field_info;
            $info['field types'][$name]['module'] = $module;
            $info['field types'][$name]['formatters'] = array();
        $module_widgets = module_invoke($module, 'widget_info');
        if ($module_widgets) {
          foreach ($module_widgets as $name => $widget_info) {
            $info['widget types'][$name] = $widget_info;
            $info['widget types'][$name]['module'] = $module;
          }
        }
        $module_formatters = module_invoke($module, 'field_formatter_info');
Yves Chedemois's avatar
Yves Chedemois committed
        if ($module_formatters) {
          foreach ($module_formatters as $name => $formatter_info) {
            foreach ($formatter_info['field types'] as $field_type) {
              $info['field types'][$field_type]['formatters'][$name] = $formatter_info;
              $info['field types'][$field_type]['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;
Yves Chedemois's avatar
Yves Chedemois committed

          $db_info = content_database_info($field);
          $type['tables'][$db_info['table']] = $db_info['table'];
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', $info, 'cache_content');
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) {
Yves Chedemois's avatar
Yves Chedemois committed
    case 'insert':
      module_load_include('inc', 'content', 'content_crud');
Yves Chedemois's avatar
Yves Chedemois committed
    case 'update':
      module_load_include('inc', 'content', 'content_crud');
Yves Chedemois's avatar
Yves Chedemois committed
    case 'delete':
      module_load_include('inc', 'content', 'content_crud');