[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

/sites/all/modules/cck/ -> content.module (source)

   1  <?php
   2  // $Id: content.module,v 1.301.2.123 2011/01/03 11:03:47 yched Exp $
   3  /**
   4   * @file
   5   * Allows administrators to associate custom fields to content types.
   6   */
   7  
   8  define('CONTENT_DB_STORAGE_PER_FIELD', 0);
   9  define('CONTENT_DB_STORAGE_PER_CONTENT_TYPE', 1);
  10  
  11  define('CONTENT_CALLBACK_NONE', 0x0001);
  12  define('CONTENT_CALLBACK_DEFAULT', 0x0002);
  13  define('CONTENT_CALLBACK_CUSTOM', 0x0004);
  14  
  15  define('CONTENT_HANDLE_CORE', 0x0001);
  16  define('CONTENT_HANDLE_MODULE', 0x0002);
  17  
  18  function content_help($path, $arg) {
  19    switch ($path) {
  20      case 'admin/help#content':
  21        $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>';
  22        $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>';
  23        $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>';
  24        $output .= '<ul>';
  25        $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>';
  26        $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>';
  27        $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>';
  28        $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>';
  29        $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>';
  30        $output .= '</ul>';
  31        $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>';
  32        return $output;
  33    }
  34  }
  35  
  36  /**
  37   * Implementation of hook_flush_caches.
  38   */
  39  function content_flush_caches() {
  40    return array(content_cache_tablename());
  41  }
  42  
  43  /**
  44   * Implementation of hook_init().
  45   */
  46  function content_init() {
  47    drupal_add_css(drupal_get_path('module', 'content') .'/theme/content-module.css');
  48    if (module_exists('token') && !function_exists('content_token_values')) {
  49      module_load_include('inc', 'content', 'includes/content.token');
  50    }
  51    if (module_exists('diff') && !function_exists('content_diff')) {
  52      module_load_include('inc', 'content', 'includes/content.diff');
  53    }
  54  }
  55  
  56  /**
  57   * Implementation of hook_perm().
  58   */
  59  function content_perm() {
  60    return array('Use PHP input for field settings (dangerous - grant with care)');
  61  }
  62  
  63  /**
  64   * Implementation of hook_menu_alter().
  65   */
  66  function content_menu_alter(&$items) {
  67    // Customize the content types page with our own callback
  68    $items['admin/content/types']['page callback'] = 'content_types_overview';
  69    $items['admin/content/types']['file'] = 'content.admin.inc';
  70    $items['admin/content/types']['file path'] = drupal_get_path('module', 'content') .'/includes';
  71  }
  72  
  73  /**
  74   * Implementation of hook_menu().
  75   */
  76  function content_menu() {
  77    $items = array();
  78    $items['admin/content/types/fields'] = array(
  79      'title' => 'Fields',
  80      'page callback' => 'content_fields_list',
  81      'access arguments' => array('administer content types'),
  82      'file' => 'includes/content.admin.inc',
  83      'type' => MENU_LOCAL_TASK,
  84    );
  85    // Callback for AHAH add more buttons.
  86    $items['content/js_add_more'] = array(
  87      'page callback' => 'content_add_more_js',
  88      'access arguments' => array('access content'),
  89      'file' => 'includes/content.node_form.inc',
  90      'type' => MENU_CALLBACK,
  91    );
  92  
  93    // Make sure this doesn't fire until content_types is working,
  94    // and tables are updated, needed to avoid errors on initial installation.
  95    if (!defined('MAINTENANCE_MODE') && variable_get('content_schema_version', -1) >= 6007) {
  96      foreach (node_get_types() as $type) {
  97        $type_name = $type->type;
  98        $content_type = content_types($type_name);
  99        $type_url_str = $content_type['url_str'];
 100        $items['admin/content/node-type/'. $type_url_str .'/fields'] = array(
 101          'title' => 'Manage fields',
 102          'page callback' => 'drupal_get_form',
 103          'page arguments' => array('content_field_overview_form', $type_name),
 104          'access arguments' => array('administer content types'),
 105          'file' => 'includes/content.admin.inc',
 106          'type' => MENU_LOCAL_TASK,
 107          'weight' => 1,
 108        );
 109        $items['admin/content/node-type/'. $type_url_str .'/display'] = array(
 110          'title' => 'Display fields',
 111          'page callback' => 'drupal_get_form',
 112          'page arguments' => array('content_display_overview_form', $type_name),
 113          'access arguments' => array('administer content types'),
 114          'file' => 'includes/content.admin.inc',
 115          'type' => MENU_LOCAL_TASK,
 116          'weight' => 2,
 117        );
 118        $contexts = content_build_modes('_tabs');
 119        foreach ($contexts as $key => $tab) {
 120          $items['admin/content/node-type/'. $type_url_str .'/display/'. $key] = array(
 121            'title' => $tab['title'],
 122            'page arguments' => array('content_display_overview_form', $type_name, $key),
 123            'access arguments' => array('administer content types'),
 124            'type' => $key == 'basic' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
 125            'weight' => $key == 'basic' ? 0 : 1,
 126          );
 127        }
 128        // Cast as an array in case this is called before any fields have
 129        // been added, like when a new content type is created.
 130        foreach ((array) $content_type['fields'] as $field) {
 131          $field_name = $field['field_name'];
 132          $items['admin/content/node-type/'. $type_url_str .'/fields/'. $field_name] = array(
 133            'title' => $field['widget']['label'],
 134            'page callback' => 'drupal_get_form',
 135            'page arguments' => array('content_field_edit_form', $type_name, $field_name),
 136            'access arguments' => array('administer content types'),
 137            'file' => 'includes/content.admin.inc',
 138            'type' => MENU_LOCAL_TASK,
 139          );
 140          $items['admin/content/node-type/'. $type_url_str .'/fields/'. $field_name .'/remove'] = array(
 141            'title' => 'Remove field',
 142            'page callback' => 'drupal_get_form',
 143            'page arguments' => array('content_field_remove_form', $type_name, $field_name),
 144            'access arguments' => array('administer content types'),
 145            'file' => 'includes/content.admin.inc',
 146            'type' => MENU_CALLBACK,
 147          );
 148        }
 149      }
 150    }
 151    return $items;
 152  }
 153  
 154  /**
 155   * Hook elements().
 156   *
 157   * Used to add multiple value processing, validation, and themes.
 158   *
 159   * FAPI callbacks can be declared here, and the element will be
 160   * passed to those callbacks.
 161   *
 162   * Drupal will automatically theme the element using a theme with
 163   * the same name as the hook_elements key.
 164   */
 165  function content_elements() {
 166    return array(
 167      'content_multiple_values' => array(),
 168      'content_field' => array(),
 169    );
 170  }
 171  
 172  /**
 173   * Implementation of hook_theme().
 174   */
 175  function content_theme() {
 176    $path = drupal_get_path('module', 'content') .'/theme';
 177    require_once "./$path/theme.inc";
 178  
 179    return array(
 180      'content_field' => array(
 181        'template' => 'content-field',
 182        'arguments' => array('element' => NULL),
 183        'path' => $path,
 184      ),
 185      'content_overview_links' => array(
 186        'arguments' => array(),
 187      ),
 188      'content_field_overview_form' => array(
 189        'template' => 'content-admin-field-overview-form',
 190        'file' => 'theme.inc',
 191        'path' => $path,
 192        'arguments' => array('form' => NULL),
 193      ),
 194      'content_display_overview_form' => array(
 195        'template' => 'content-admin-display-overview-form',
 196        'file' => 'theme.inc',
 197        'path' => $path,
 198        'arguments' => array('form' => NULL),
 199      ),
 200      'content_exclude' => array(
 201        'arguments' => array('content' => NULL, 'object' => array(), 'context' => NULL),
 202      ),
 203      'content_view_multiple_field' => array(
 204        'arguments' => array('items' => NULL, 'field' => NULL, 'data' => NULL),
 205      ),
 206      'content_multiple_values' => array(
 207        'arguments' => array('element' => NULL),
 208      ),
 209    );
 210  }
 211  
 212  /**
 213   * Implementation of hook_views_api().
 214   */
 215  function content_views_api() {
 216    return array(
 217      'api' => 2,
 218      'path' => drupal_get_path('module', 'content') . '/includes/views',
 219    );
 220  }
 221  
 222  /**
 223   * Implementation of hook_ctools_plugin_directory().
 224   */
 225  function content_ctools_plugin_directory($module, $plugin) {
 226    if ($module == 'ctools' && $plugin == 'content_types') {
 227      return 'includes/panels/' . $plugin;
 228    }
 229  }
 230  
 231  /**
 232   * Load data for a node type's fields.
 233   * Implementation of hook_nodeapi 'load' op.
 234   *
 235   * When loading one of the content.module nodes, we need to let each field handle
 236   * its own loading. This can make for a number of queries in some cases, so we
 237   * cache the loaded object structure and invalidate it during the update process.
 238   */
 239  function content_load(&$node) {
 240    $cid = 'content:'. $node->nid .':'. $node->vid;
 241    if ($cached = cache_get($cid, content_cache_tablename())) {
 242      foreach ($cached->data as $key => $value) {
 243        $node->$key = $value;
 244      }
 245    }
 246    else {
 247      $default_additions = _content_field_invoke_default('load', $node);
 248      if ($default_additions) {
 249        foreach ($default_additions as $key => $value) {
 250          $node->$key = $value;
 251        }
 252      }
 253      $additions = _content_field_invoke('load', $node);
 254      if ($additions) {
 255        foreach ($additions as $key => $value) {
 256          $node->$key = $value;
 257          $default_additions[$key] = $value;
 258        }
 259      }
 260      cache_set($cid, $default_additions, content_cache_tablename());
 261    }
 262  }
 263  
 264  /**
 265   * Implementation of hook_nodeapi 'validate' op.
 266   *
 267   */
 268  function content_validate(&$node, $form = NULL) {
 269    _content_field_invoke('validate', $node, $form);
 270    _content_field_invoke_default('validate', $node, $form);
 271  }
 272  
 273  /**
 274   * Implementation of hook_nodeapi 'presave' op.
 275   *
 276   */
 277  function content_presave(&$node) {
 278    _content_field_invoke('presave', $node);
 279    _content_field_invoke_default('presave', $node);
 280  }
 281  
 282  /**
 283   * Implementation of hook_nodeapi 'insert' op.
 284   *
 285   * Insert node type fields.
 286   */
 287  function content_insert(&$node) {
 288    _content_field_invoke('insert', $node);
 289    _content_field_invoke_default('insert', $node);
 290  }
 291  
 292  /**
 293   * Implementation of hook_nodeapi 'update' op.
 294   *
 295   * Update node type fields.
 296   */
 297  function content_update(&$node) {
 298    _content_field_invoke('update', $node);
 299    _content_field_invoke_default('update', $node);
 300    cache_clear_all('content:'. $node->nid .':'. $node->vid, content_cache_tablename());
 301  }
 302  
 303  /**
 304   * Implementation of hook_nodeapi 'delete' op.
 305   *
 306   * Delete node type fields.
 307   */
 308  function content_delete(&$node) {
 309    _content_field_invoke('delete', $node);
 310    _content_field_invoke_default('delete', $node);
 311    cache_clear_all('content:'. $node->nid .':', content_cache_tablename(), TRUE);
 312  }
 313  
 314  /**
 315   * Implementation of hook_nodeapi 'delete_revision' op.
 316   *
 317   * Delete node type fields for a revision.
 318   */
 319  function content_delete_revision(&$node) {
 320    _content_field_invoke('delete revision', $node);
 321    _content_field_invoke_default('delete revision', $node);
 322    cache_clear_all('content:'. $node->nid .':'. $node->vid, content_cache_tablename());
 323  }
 324  
 325  /**
 326   * Implementation of hook_nodeapi 'view' op.
 327   *
 328   * Generate field render arrays.
 329   */
 330  function content_view(&$node, $teaser = FALSE, $page = FALSE) {
 331    // Let field modules sanitize their data for output.
 332    _content_field_invoke('sanitize', $node, $teaser, $page);
 333  
 334    // Merge fields.
 335    $additions = _content_field_invoke_default('view', $node, $teaser, $page);
 336    $node->content = array_merge((array) $node->content, $additions);
 337  }
 338  
 339  /**
 340   * Render a single field, fully themed with label and multiple values.
 341   *
 342   * To be used by third-party code (Views, Panels...) that needs to output
 343   * an isolated field. Do *not* use inside node templates, use the
 344   * $FIELD_NAME_rendered variables instead.
 345   *
 346   * By default, the field is displayed using the settings defined for the
 347   * 'full node' or 'teaser' contexts (depending on the value of the $teaser param).
 348   * Set $node->build_mode to a different value to use a different context.
 349   *
 350   * Different settings can be specified by adjusting $field['display_settings'].
 351   *
 352   * @param $field
 353   *   The field definition.
 354   * @param $node
 355   *   The node containing the field to display. Can be a 'pseudo-node', containing
 356   *   at least 'type', 'nid', 'vid', and the field data.
 357   * @param $teaser
 358   * @param $page
 359   *   Similar to hook_nodeapi('view')
 360   * @return
 361   *   The themed output for the field.
 362   */
 363  function content_view_field($field, $node, $teaser = FALSE, $page = FALSE) {
 364    $output = '';
 365    if (isset($node->$field['field_name'])) {
 366      $items = $node->$field['field_name'];
 367  
 368      // Use 'full'/'teaser' if not specified otherwise.
 369      $node->build_mode = isset($node->build_mode) ? $node->build_mode : NODE_BUILD_NORMAL;
 370  
 371      // One-field equivalent to _content_field_invoke('sanitize').
 372      $field_types = _content_field_types();
 373      $module = $field_types[$field['type']]['module'];
 374      $function = $module .'_field';
 375      if (function_exists($function)) {
 376        $function('sanitize', $node, $field, $items, $teaser, $page);
 377        $node->$field['field_name'] = $items;
 378      }
 379  
 380      $view = content_field('view', $node, $field, $items, $teaser, $page);
 381      // content_field('view') adds a wrapper to handle variables and 'excluded'
 382      // fields for node templates. We bypass it and render the actual field.
 383      $output = drupal_render($view[$field['field_name']]['field']);
 384    }
 385    return $output;
 386  }
 387  
 388  /**
 389   * Implementation of hook_nodeapi 'alter' op.
 390   *
 391   * Add back the formatted values in the 'view' element for all fields,
 392   * so that node templates can use it.
 393   */
 394  function content_alter(&$node, $teaser = FALSE, $page = FALSE) {
 395    _content_field_invoke_default('alter', $node, $teaser, $page);
 396  }
 397  
 398  /**
 399   * Implementation of hook_nodeapi 'prepare translation' op.
 400   *
 401   * Generate field render arrays.
 402   */
 403  function content_prepare_translation(&$node) {
 404    $default_additions = _content_field_invoke_default('prepare translation', $node);
 405    $additions = _content_field_invoke('prepare translation', $node);
 406    // Merge module additions after the default ones to enable overriding
 407    // of field values.
 408    $node = (object) array_merge((array) $node, $default_additions, $additions);
 409  }
 410  
 411  /**
 412   * Implementation of hook_nodeapi().
 413   */
 414  function content_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
 415    // Prevent against invalid 'nodes' built by broken 3rd party code.
 416    if (isset($node->type)) {
 417      $type = content_types($node->type);
 418      // Save cycles if the type has no CCK fields.
 419      if (!empty($type['fields'])) {
 420        $callback = 'content_'. str_replace(' ', '_', $op);
 421        if (function_exists($callback)) {
 422          $callback($node, $a3, $a4);
 423        }
 424      }
 425  
 426      // Special case for 'view' op, we want to adjust weights of non-cck fields
 427      // even if there are no actual fields for this type.
 428      if ($op == 'view') {
 429        $node->content['#pre_render'][] = 'content_alter_extra_weights';
 430        $node->content['#content_extra_fields'] = $type['extra'];
 431      }
 432    }
 433  }
 434  
 435  /**
 436   *  Implementation of hook_form_alter().
 437   */
 438  function content_form_alter(&$form, $form_state, $form_id) {
 439    if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
 440      $type = content_types($form['#node']->type);
 441      if (!empty($type['fields'])) {
 442        module_load_include('inc', 'content', 'includes/content.node_form');
 443        // Merge field widgets.
 444        $form = array_merge($form, content_form($form, $form_state));
 445      }
 446      $form['#pre_render'][] = 'content_alter_extra_weights';
 447      $form['#content_extra_fields'] = $type['extra'];
 448    }
 449  }
 450  
 451  /**
 452   * Pre-render callback to adjust weights of non-CCK fields.
 453   */
 454  function content_alter_extra_weights($elements) {
 455    if (isset($elements['#content_extra_fields'])) {
 456      foreach ($elements['#content_extra_fields'] as $key => $value) {
 457        // Some core 'fields' use a different key in node forms and in 'view'
 458        // render arrays. Check we're not on a form first.
 459        if (!isset($elements['#build_id']) && isset($value['view']) && isset($elements[$value['view']])) {
 460          $elements[$value['view']]['#weight'] = $value['weight'];
 461        }
 462        elseif (isset($elements[$key])) {
 463          $elements[$key]['#weight'] = $value['weight'];
 464        }
 465      }
 466    }
 467    return $elements;
 468  }
 469  
 470  /**
 471   * Proxy function to call content_add_more_submit(), because it might not be
 472   * included yet when the form is processed and invokes the callback.
 473   */
 474  function content_add_more_submit_proxy($form, &$form_state) {
 475    module_load_include('inc', 'content', 'includes/content.node_form');
 476    content_add_more_submit($form, $form_state);
 477  }
 478  
 479  /**
 480   * Theme an individual form element.
 481   *
 482   * Combine multiple values into a table with drag-n-drop reordering.
 483   */
 484  function theme_content_multiple_values($element) {
 485    $field_name = $element['#field_name'];
 486    $field = content_fields($field_name);
 487    $output = '';
 488  
 489    if ($field['multiple'] >= 1) {
 490      $table_id = $element['#field_name'] .'_values';
 491      $order_class = $element['#field_name'] .'-delta-order';
 492      $required = !empty($element['#required']) ? '<span class="form-required" title="'. t('This field is required.') .'">*</span>' : '';
 493  
 494      $header = array(
 495        array(
 496          'data' => t('!title: !required', array('!title' => $element['#title'], '!required' => $required)),
 497          'colspan' => 2
 498        ),
 499        t('Order'),
 500      );
 501      $rows = array();
 502  
 503      // Sort items according to '_weight' (needed when the form comes back after
 504      // preview or failed validation)
 505      $items = array();
 506      foreach (element_children($element) as $key) {
 507        if ($key !== $element['#field_name'] .'_add_more') {
 508          $items[] = &$element[$key];
 509        }
 510      }
 511      usort($items, '_content_sort_items_value_helper');
 512  
 513      // Add the items as table rows.
 514      foreach ($items as $key => $item) {
 515        $item['_weight']['#attributes']['class'] = $order_class;
 516        $delta_element = drupal_render($item['_weight']);
 517        $cells = array(
 518          array('data' => '', 'class' => 'content-multiple-drag'),
 519          drupal_render($item),
 520          array('data' => $delta_element, 'class' => 'delta-order'),
 521        );
 522        $rows[] = array(
 523          'data' => $cells,
 524          'class' => 'draggable',
 525        );
 526      }
 527  
 528      $output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'content-multiple-table'));
 529      $output .= $element['#description'] ? '<div class="description">'. $element['#description'] .'</div>' : '';
 530      $output .= drupal_render($element[$element['#field_name'] .'_add_more']);
 531  
 532      drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
 533    }
 534    else {
 535      foreach (element_children($element) as $key) {
 536        $output .= drupal_render($element[$key]);
 537      }
 538    }
 539  
 540    return $output;
 541  }
 542  
 543  /**
 544   * Modules notify Content module when uninstalled, disabled, etc.
 545   *
 546   * @param string $op
 547   *   the module operation: uninstall, install, enable, disable
 548   * @param string $module
 549   *   the name of the affected module.
 550   * @TODO
 551   *   figure out exactly what needs to be done by content module when
 552   *   field modules are installed, uninstalled, enabled or disabled.
 553   */
 554  function content_notify($op, $module) {
 555    switch ($op) {
 556      case 'install':
 557        content_clear_type_cache();
 558        break;
 559      case 'uninstall':
 560        module_load_include('inc', 'content', 'includes/content.crud');
 561        content_module_delete($module);
 562        break;
 563      case 'enable':
 564        content_associate_fields($module);
 565        content_clear_type_cache();
 566        break;
 567      case 'disable':
 568        // When CCK modules are disabled before content module's update is run
 569        // to add the active column, we can't do this.
 570        if (variable_get('content_schema_version', -1) < 6007) {
 571          return FALSE;
 572        }
 573        db_query("UPDATE {". content_field_tablename() ."} SET active=0 WHERE module='%s'", $module);
 574        db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=0 WHERE widget_module='%s'", $module);
 575        content_clear_type_cache(TRUE);
 576        break;
 577    }
 578  }
 579  
 580  /**
 581   * Allows a module to update the database for fields and columns it controls.
 582   *
 583   * @param string $module
 584   *   The name of the module to update on.
 585   */
 586  function content_associate_fields($module) {
 587    // When CCK modules are enabled before content module's update is run,
 588    // to add module and active columns, we can't do this.
 589    if (variable_get('content_schema_version', -1) < 6007) {
 590      return FALSE;
 591    }
 592    $module_fields = module_invoke($module, 'field_info');
 593    if ($module_fields) {
 594      foreach ($module_fields as $name => $field_info) {
 595        watchdog('content', 'Updating field type %type with module %module.', array('%type' => $name, '%module' => $module));
 596        db_query("UPDATE {". content_field_tablename() ."} SET module = '%s', active = %d WHERE type = '%s'", $module, 1, $name);
 597      }
 598    }
 599    $module_widgets = module_invoke($module, 'widget_info');
 600    if ($module_widgets) {
 601      foreach ($module_widgets as $name => $widget_info) {
 602        watchdog('content', 'Updating widget type %type with module %module.', array('%type' => $name, '%module' => $module));
 603        db_query("UPDATE {". content_instance_tablename() ."} SET widget_module = '%s', widget_active = %d WHERE widget_type = '%s'", $module, 1, $name);
 604      }
 605    }
 606    // This is called from updates and installs, so get the install-safe
 607    // version of a fields array.
 608    $fields_set = array();
 609    module_load_include('install', 'content');
 610    $types = content_types_install();
 611    foreach ($types as $type_name => $fields) {
 612      foreach ($fields as $field) {
 613        if ($field['module'] == $module && !in_array($field['field_name'], $fields_set)) {
 614          $columns = (array) module_invoke($field['module'], 'field_settings', 'database columns', $field);
 615          db_query("UPDATE {". content_field_tablename() ."} SET db_columns = '%s' WHERE field_name = '%s'", serialize($columns), $field['field_name']);
 616          $fields_set[] = $field['field_name'];
 617        }
 618      }
 619    }
 620  }
 621  
 622  /**
 623   * Implementation of hook_field(). Handles common field housekeeping.
 624   *
 625   * This implementation is special, as content.module does not define any field
 626   * types. Instead, this function gets called after the type-specific hook, and
 627   * takes care of default stuff common to all field types.
 628   *
 629   * Db-storage ops ('load', 'insert', 'update', 'delete', 'delete revisions')
 630   * are not executed field by field, and are thus handled separately in
 631   * content_storage.
 632   *
 633   * The 'view' operation constructs the $node in a way that you can use
 634   * drupal_render() to display the formatted output for an individual field.
 635   * i.e. print drupal_render($node->countent['field_foo']);
 636   *
 637   * The code now supports both single value formatters, which theme an
 638   * individual item value as has been done in previous version of CCK,
 639   * and multiple value formatters, which theme all values for the field
 640   * in a single theme. The multiple value formatters could be used, for
 641   * instance, to plot field values on a single map or display them
 642   * in a graph. Single value formatters are the default, multiple value
 643   * formatters can be designated as such in formatter_info().
 644   *
 645   * The node array will look like:
 646   *   $node->content['field_foo']['wrapper'] = array(
 647   *     '#type' => 'content_field',
 648   *     '#title' => 'label'
 649   *     '#field_name' => 'field_name',
 650   *     '#node' => $node,
 651   *     // Value of the $teaser param of hook_nodeapi('view').
 652   *     '#teaser' => $teaser,
 653   *     // Value of the $page param of hook_nodeapi('view').
 654   *     '#page' => $page,
 655   *     // The curent rendering context ('teaser', 'full', NODE_BUILD_SEARCH_INDEX...).
 656   *     '#context' => $context,
 657   *     'items' =>
 658   *       0 => array(
 659   *         '#item' => $items[0],
 660   *         // Only for 'single-value' formatters
 661   *         '#theme' => $theme,
 662   *         '#field_name' => 'field_name',
 663   *         '#type_name' => $node->type,
 664   *         '#formatter' => $formatter_name,
 665   *         '#node' => $node,
 666   *         '#delta' => 0,
 667   *       ),
 668   *       1 => array(
 669   *         '#item' => $items[1],
 670   *         // Only for 'single-value' formatters
 671   *         '#theme' => $theme,
 672   *         '#field_name' => 'field_name',
 673   *         '#type_name' => $node->type,
 674   *         '#formatter' => $formatter_name,
 675   *         '#node' => $node,
 676   *         '#delta' => 1,
 677   *       ),
 678   *       // Only for 'multiple-value' formatters
 679   *       '#theme' => $theme,
 680   *       '#field_name' => 'field_name',
 681   *       '#type_name' => $node->type,
 682   *       '#formatter' => $formatter_name,
 683   *     ),
 684   *   );
 685   */
 686  function content_field($op, &$node, $field, &$items, $teaser, $page) {
 687    switch ($op) {
 688      case 'validate':
 689        // TODO: here we could validate that the number of multiple data is correct ?
 690        // We're controlling the number of fields to fill out and saving empty
 691        // ones if a specified number is requested, so no reason to do any validation
 692        // here right now, but if later create a method to indicate whether
 693        // 'required' means all values must be filled out, we can come back
 694        // here and check that they're not empty.
 695        break;
 696  
 697      case 'presave':
 698        if (!empty($node->devel_generate)) {
 699          include_once('./'. drupal_get_path('module', 'content') .'/includes/content.devel.inc');
 700          content_generate_fields($node, $field);
 701          $items = $node->{$field['field_name']};
 702        }
 703  
 704        // Manual node_save calls might not have all fields filled in.
 705        // On node insert, we need to make sure all tables get at least an empty
 706        // record, or subsequent edits, using drupal_write_record() in update mode,
 707        // won't insert any data.
 708        // Missing fields on node update are handled in content_storage().
 709        if (empty($items) && !isset($node->nid)) {
 710          foreach (array_keys($field['columns']) as $column) {
 711            $items[0][$column] = NULL;
 712          }
 713          $node->$field['field_name'] = $items;
 714        }
 715  
 716        // If there was an AHAH add more button in this field, don't save it.
 717        // TODO: is it still needed ?
 718        unset($items[$field['field_name'] .'_add_more']);
 719  
 720        if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
 721          // Reorder items to account for drag-n-drop reordering.
 722          $items = _content_sort_items($field, $items);
 723        }
 724  
 725        // Filter out empty values.
 726        $items = content_set_empty($field, $items);
 727  
 728        break;
 729  
 730      case 'view':
 731        $addition = array();
 732  
 733        // Previewed nodes bypass the 'presave' op, so we need to some massaging.
 734        if ($node->build_mode == NODE_BUILD_PREVIEW && content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
 735          if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
 736            // Reorder items to account for drag-n-drop reordering.
 737            $items = _content_sort_items($field, $items);
 738          }
 739  
 740          // Filter out empty values.
 741          $items = content_set_empty($field, $items);
 742        }
 743  
 744        // NODE_BUILD_NORMAL is 0, and ('whatever' == 0) is TRUE, so we need a ===.
 745        if ($node->build_mode === NODE_BUILD_NORMAL || $node->build_mode == NODE_BUILD_PREVIEW) {
 746          $context = $teaser ? 'teaser' : 'full';
 747        }
 748        else {
 749          $context = $node->build_mode;
 750        }
 751        // The field may be missing info for $contexts added by modules
 752        // enabled after the field was last edited.
 753        $formatter_name = isset($field['display_settings'][$context]) && isset($field['display_settings'][$context]['format']) ? $field['display_settings'][$context]['format'] : 'default';
 754        if ($formatter = _content_get_formatter($formatter_name, $field['type'])) {
 755          $theme = $formatter['module'] .'_formatter_'. $formatter_name;
 756          $single = (content_handle('formatter', 'multiple values', $formatter) == CONTENT_HANDLE_CORE);
 757  
 758          $label_display = isset($field['display_settings']['label']['format']) ? $field['display_settings']['label']['format'] : 'above';
 759          // Do not include field labels when indexing content.
 760          if ($context == NODE_BUILD_SEARCH_INDEX) {
 761            $label_display = 'hidden';
 762          }
 763  
 764          $element = array(
 765            '#type' => 'content_field',
 766            '#title' => check_plain(t($field['widget']['label'])),
 767            '#field_name' => $field['field_name'],
 768            '#access' => $formatter_name != 'hidden' && content_access('view', $field, NULL, $node),
 769            '#label_display' => $label_display,
 770            '#node' => $node,
 771            '#teaser' => $teaser,
 772            '#page' => $page,
 773            '#context' => $context,
 774            '#single' => $single,
 775            'items' => array(),
 776          );
 777  
 778          // Fill-in items.
 779          foreach ($items as $delta => $item) {
 780            $element['items'][$delta] = array(
 781              '#item' => $item,
 782              '#weight' => $delta,
 783            );
 784          }
 785  
 786          // Append formatter information either on each item ('single-value' formatter)
 787          // or at the upper 'items' level ('multiple-value' formatter)
 788          $format_info = array(
 789            '#theme' => $theme,
 790            '#field_name' => $field['field_name'],
 791            '#type_name' => $node->type,
 792            '#formatter' => $formatter_name,
 793            '#node' => $node,
 794          );
 795          if ($single) {
 796            foreach ($items as $delta => $item) {
 797              $element['items'][$delta] += $format_info;
 798              $element['items'][$delta]['#item']['#delta'] = $delta;
 799            }
 800          }
 801          else {
 802            $element['items'] += $format_info;
 803          }
 804  
 805          // The wrapper lets us get the themed output for the whole field
 806          // to populate the $FIELD_NAME_rendered variable for node templates,
 807          // and hide it from the $content variable if needed.
 808          // See 'preprocess_node' op and theme_content_field_wrapper()?
 809          $wrapper = array(
 810            'field' => $element,
 811            '#weight' => $field['widget']['weight'],
 812            '#post_render' => array('content_field_wrapper_post_render'),
 813            '#field_name' => $field['field_name'],
 814            '#type_name' => $node->type,
 815            '#context' => $context,
 816          );
 817  
 818          $addition = array($field['field_name'] => $wrapper);
 819        }
 820        return $addition;
 821  
 822      case 'alter':
 823        // Add back the formatted values in the 'view' element,
 824        // so that tokens and node templates can use it.
 825        // Note: Doing this in 'preprocess_node' breaks token integration.
 826  
 827        // The location of the field's rendered output depends on whether the
 828        // field is in a fieldgroup or not.
 829        $wrapper = NULL;
 830        if (isset($node->content[$field['field_name']])) {
 831          $wrapper = $node->content[$field['field_name']];
 832        }
 833        elseif (module_exists('fieldgroup') && ($group_name = fieldgroup_get_group($node->type, $field['field_name'])) && isset($node->content[$group_name]['group'][$field['field_name']])) {
 834          $wrapper = $node->content[$group_name]['group'][$field['field_name']];
 835        }
 836  
 837        if ($wrapper) {
 838          $element = $wrapper['field'];
 839          // '#single' is not set if the field is hidden or inaccessible.
 840          if (isset($element['#single'])) {
 841            if (!empty($element['#single'])) {
 842              // Single value formatter.
 843              foreach (element_children($element['items']) as $delta) {
 844                // '#chilren' is not set if the field is empty.
 845                $items[$delta]['view'] = isset($element['items'][$delta]['#children']) ? $element['items'][$delta]['#children'] : '';
 846              }
 847            }
 848            elseif (isset($element['items']['#children']))  {
 849              // Multiple values formatter.
 850              $items[0]['view'] = $element['items']['#children'];
 851            }
 852          }
 853          else {
 854            // Hidden or inaccessible field.
 855            $items[0]['view'] = '';
 856          }
 857        }
 858        break;
 859  
 860      case 'preprocess_node':
 861        // Add $FIELD_NAME_rendered variables.
 862        $addition = array();
 863  
 864        // The location of the field's rendered output depends on whether the
 865        // field is in a fieldgroup or not.
 866        $wrapper = NULL;
 867        if (isset($node->content[$field['field_name']])) {
 868          $wrapper = $node->content[$field['field_name']];
 869        }
 870        elseif (module_exists('fieldgroup') && ($group_name = fieldgroup_get_group($node->type, $field['field_name'])) && isset($node->content[$group_name]['group'][$field['field_name']])) {
 871          $wrapper = $node->content[$group_name]['group'][$field['field_name']];
 872        }
 873  
 874        if ($wrapper) {
 875          // '#chilren' is not set if the field is empty.
 876          $addition[$field['field_name'] .'_rendered'] = isset($wrapper['#children']) ? $wrapper['#children'] : '';
 877        }
 878  
 879        return $addition;
 880  
 881      case 'prepare translation':
 882        $addition = array();
 883        if (isset($node->translation_source->$field['field_name'])) {
 884          $addition[$field['field_name']] = $node->translation_source->$field['field_name'];
 885        }
 886        return $addition;
 887    }
 888  }
 889  
 890  /**
 891   * Helper function to filter out empty values.
 892   *
 893   * On order to keep marker rows in the database, the function ensures
 894   * that the right number of 'all columns NULL' values is kept.
 895   *
 896   * @param array $field
 897   * @param array $items
 898   * @return array
 899   *   returns filtered and adjusted item array
 900   */
 901  function content_set_empty($field, $items) {
 902    // Filter out empty values.
 903    $filtered = array();
 904    $function = $field['module'] .'_content_is_empty';
 905    foreach ((array) $items as $delta => $item) {
 906      if (!$function($item, $field)) {
 907        $filtered[] = $item;
 908      }
 909    }
 910  
 911    // Make sure we store the right number of 'empty' values.
 912    $empty = array();
 913    foreach (array_keys($field['columns']) as $column) {
 914      $empty[$column] = NULL;
 915    }
 916    $pad = $field['multiple'] > 1 ? $field['multiple'] : 1;
 917    $filtered = array_pad($filtered, $pad, $empty);
 918  
 919    return $filtered;
 920  }
 921  
 922  /**
 923   * Helper function to sort items in a field according to
 924   * user drag-n-drop reordering.
 925   */
 926  function _content_sort_items($field, $items) {
 927    if ($field['multiple'] >= 1 && isset($items[0]['_weight'])) {
 928      usort($items, '_content_sort_items_helper');
 929      foreach ($items as $delta => $item) {
 930        if (is_array($items[$delta])) {
 931          unset($items[$delta]['_weight']);
 932        }
 933      }
 934    }
 935    return $items;
 936  }
 937  
 938  /**
 939   * Sort function for items order.
 940   * (copied form element_sort(), which acts on #weight keys)
 941   */
 942  function _content_sort_items_helper($a, $b) {
 943    $a_weight = (is_array($a) && isset($a['_weight'])) ? $a['_weight'] : 0;
 944    $b_weight = (is_array($b) && isset($b['_weight'])) ? $b['_weight'] : 0;
 945    if ($a_weight == $b_weight) {
 946      return 0;
 947    }
 948    return ($a_weight < $b_weight) ? -1 : 1;
 949  }
 950  
 951  /**
 952   * Same as above, using ['_weight']['#value']
 953   */
 954  function _content_sort_items_value_helper($a, $b) {
 955    $a_weight = (is_array($a) && isset($a['_weight']['#value'])) ? $a['_weight']['#value'] : 0;
 956    $b_weight = (is_array($b) && isset($b['_weight']['#value'])) ? $b['_weight']['#value'] : 0;
 957    if ($a_weight == $b_weight) {
 958      return 0;
 959    }
 960    return ($a_weight < $b_weight) ? -1 : 1;
 961  }
 962  
 963  /**
 964   * Handle storage ops for _content_field_invoke_default().
 965   */
 966  function content_storage($op, $node) {
 967    // Don't try this before content module's update is run to add
 968    // the active and module columns.
 969    if (variable_get('content_schema_version', -1) < 6007) {
 970      return FALSE;
 971    }
 972  
 973    $type_name = $node->type;
 974    $type = content_types($type_name);
 975  
 976    switch ($op) {
 977      case 'load':
 978        // OPTIMIZE: load all non multiple fields in a single JOIN query ?
 979        // warning: 61-join limit in MySQL ?
 980        $additions = array();
 981        // For each table used by this content type,
 982        foreach ($type['tables'] as $table) {
 983          $schema = drupal_get_schema($table);
 984          // The per-type table might not have any fields actually stored in it.
 985          if (!$schema['content fields']) {
 986            continue;
 987          }
 988          $query = 'SELECT * FROM {'. $table .'} WHERE vid = %d';
 989  
 990          // If we're loading a table for a multiple field,
 991          // we fetch all rows (values) ordered by delta,
 992          // else we only fetch one row.
 993          $result = isset($schema['fields']['delta']) ? db_query($query .' ORDER BY delta', $node->vid) : db_query_range($query, $node->vid, 0, 1);
 994  
 995          // For each table row, populate the fields.
 996          while ($row = db_fetch_array($result)) {
 997            // For each field stored in the table, add the field item.
 998            foreach ($schema['content fields'] as $field_name) {
 999              $item = array();
1000              $field = content_fields($field_name, $type_name);
1001              $db_info = content_database_info($field);
1002              // For each column declared by the field, populate the item.
1003              foreach ($db_info['columns'] as $column => $attributes) {
1004                $item[$column] = $row[$attributes['column']];
1005              }
1006  
1007              // Add the item to the field values for the node.
1008              if (!isset($additions[$field_name])) {
1009                $additions[$field_name] = array();
1010              }
1011              $additions[$field_name][] = $item;
1012            }
1013          }
1014        }
1015        return $additions;
1016  
1017      case 'insert':
1018      case 'update':
1019        foreach ($type['tables'] as $table) {
1020          $schema = drupal_get_schema($table);
1021          $record = array();
1022          foreach ($schema['content fields'] as $field_name) {
1023            if (isset($node->$field_name)) {
1024              $field = content_fields($field_name, $type_name);
1025              // Multiple fields need specific handling, we'll deal with them later on.
1026              if ($field['multiple']) {
1027                continue;
1028              }
1029              $db_info = content_database_info($field);
1030              foreach ($db_info['columns'] as $column => $attributes) {
1031                $record[$attributes['column']] = $node->{$field_name}[0][$column];
1032              }
1033            }
1034          }
1035          // $record might be empty because
1036          // - the table stores a multiple field :
1037          //   we do nothing, this is handled later on
1038          // - this is the per-type table and no field is actually stored in it :
1039          //   we still store the nid and vid
1040          if (count($record) || empty($schema['content fields'])) {
1041            $record['nid'] = $node->nid;
1042            $record['vid'] = $node->vid;
1043            // Can't rely on the insert/update op of the node to decide if this
1044            // is an insert or an update, a node or revision may have existed
1045            // before any fields were created, so there may not be an entry here.
1046  
1047            // TODO - should we auto create an entry for all existing nodes when
1048            // fields are added to content types -- either a NULL value
1049            // or the default value? May need to offer the user an option of
1050            // how to handle that.
1051            if (db_result(db_query("SELECT COUNT(*) FROM {". $table ."} WHERE vid = %d", $node->vid))) {
1052              content_write_record($table, $record, array('vid'));
1053            }
1054            else {
1055              content_write_record($table, $record);
1056            }
1057          }
1058        }
1059  
1060        // Handle multiple fields.
1061        foreach ($type['fields'] as $field) {
1062          if ($field['multiple'] && isset($node->$field['field_name'])) {
1063            $db_info = content_database_info($field);
1064            // Delete and insert, rather than update, in case a value was added.
1065            if ($op == 'update') {
1066              db_query('DELETE FROM {'. $db_info['table'] .'} WHERE vid = %d', $node->vid);
1067            }
1068            foreach ($node->$field['field_name'] as $delta => $item) {
1069              $record = array();
1070              foreach ($db_info['columns'] as $column => $attributes) {
1071                $record[$attributes['column']] = $item[$column];
1072              }
1073              $record['nid'] = $node->nid;
1074              $record['vid'] = $node->vid;
1075              $record['delta'] = $delta;
1076              content_write_record($db_info['table'], $record);
1077            }
1078          }
1079        }
1080        break;
1081  
1082      case 'delete':
1083        foreach ($type['tables'] as $table) {
1084          db_query('DELETE FROM {'. $table .'} WHERE nid = %d', $node->nid);
1085        }
1086        break;
1087  
1088      case 'delete revision':
1089        foreach ($type['tables'] as $table) {
1090          db_query('DELETE FROM {'. $table .'} WHERE vid = %d', $node->vid);
1091        }
1092        break;
1093    }
1094  }
1095  
1096  /**
1097   * Save a record to the database based upon the schema.
1098   *
1099   * Directly copied from core's drupal_write_record, which can't update a
1100   * column to NULL. See http://drupal.org/node/227677 and
1101   * http://drupal.org/node/226264 for more details about that problem.
1102   *
1103   * TODO - get rid of this function and change references back to
1104   * drupal_write_record() if the patch gets into core. Will need a method
1105   * of protecting people on older versions, though.
1106   *
1107   * Default values are filled in for missing items, and 'serial' (auto increment)
1108   * types are filled in with IDs.
1109   *
1110   * @param $table
1111   *   The name of the table; this must exist in schema API.
1112   * @param $object
1113   *   The object to write. This is a reference, as defaults according to
1114   *   the schema may be filled in on the object, as well as ID on the serial
1115   *   type(s). Both array an object types may be passed.
1116   * @param $update
1117   *   If this is an update, specify the primary keys' field names. It is the
1118   *   caller's responsibility to know if a record for this object already
1119   *   exists in the database. If there is only 1 key, you may pass a simple string.
1120   * @return
1121   *   Failure to write a record will return FALSE. Otherwise SAVED_NEW or
1122   *   SAVED_UPDATED is returned depending on the operation performed. The
1123   *   $object parameter contains values for any serial fields defined by
1124   *   the $table. For example, $object->nid will be populated after inserting
1125   *   a new node.
1126   */
1127  function content_write_record($table, &$object, $update = array()) {
1128    // Standardize $update to an array.
1129    if (is_string($update)) {
1130      $update = array($update);
1131    }
1132  
1133    // Convert to an object if needed.
1134    if (is_array($object)) {
1135      $object = (object) $object;
1136      $array = TRUE;
1137    }
1138    else {
1139      $array = FALSE;
1140    }
1141  
1142    $schema = drupal_get_schema($table);
1143    if (empty($schema)) {
1144      return FALSE;
1145    }
1146  
1147    $fields = $defs = $values = $serials = $placeholders = array();
1148  
1149    // Go through our schema, build SQL, and when inserting, fill in defaults for
1150    // fields that are not set.
1151    foreach ($schema['fields'] as $field => $info) {
1152      // Special case -- skip serial types if we are updating.
1153      if ($info['type'] == 'serial' && count($update)) {
1154        continue;
1155      }
1156  
1157      // For inserts, populate defaults from Schema if not already provided
1158      if (!isset($object->$field) && !count($update) && isset($info['default'])) {
1159        $object->$field = $info['default'];
1160      }
1161  
1162      // Track serial fields so we can helpfully populate them after the query.
1163      if ($info['type'] == 'serial') {
1164        $serials[] = $field;
1165        // Ignore values for serials when inserting data. Unsupported.
1166        unset($object->$field);
1167      }
1168  
1169      // Build arrays for the fields, placeholders, and values in our query.
1170      if (isset($object->$field) || array_key_exists($field, $object)) {
1171        $fields[] = $field;
1172        if (isset($object->$field)) {
1173          $placeholders[] = db_type_placeholder($info['type']);
1174  
1175          if (empty($info['serialize'])) {
1176            $values[] = $object->$field;
1177          }
1178          else {
1179            $values[] = serialize($object->$field);
1180          }
1181        }
1182        else {
1183          $placeholders[] = 'NULL';
1184        }
1185      }
1186    }
1187  
1188    // Build the SQL.
1189    $query = '';
1190    if (!count($update)) {
1191      $query = "INSERT INTO {". $table ."} (". implode(', ', $fields) .') VALUES ('. implode(', ', $placeholders) .')';
1192      $return = SAVED_NEW;
1193    }
1194    else {
1195      $query = '';
1196      foreach ($fields as $id => $field) {
1197        if ($query) {
1198          $query .= ', ';
1199        }
1200        $query .= $field .' = '. $placeholders[$id];
1201      }
1202  
1203      foreach ($update as $key) {
1204        $conditions[] = "$key = ". db_type_placeholder($schema['fields'][$key]['type']);
1205        $values[] = $object->$key;
1206      }
1207  
1208      $query = "UPDATE {". $table ."} SET $query WHERE ". implode(' AND ', $conditions);
1209      $return = SAVED_UPDATED;
1210    }
1211  
1212    // Execute the SQL.
1213    if (db_query($query, $values)) {
1214      if ($serials) {
1215        // Get last insert ids and fill them in.
1216        foreach ($serials as $field) {
1217          $object->$field = db_last_insert_id($table, $field);
1218        }
1219      }
1220  
1221      // If we began with an array, convert back so we don't surprise the caller.
1222      if ($array) {
1223        $object = (array) $object;
1224      }
1225  
1226      return $return;
1227    }
1228  
1229    return FALSE;
1230  }
1231  
1232  /**
1233   * Invoke a field hook.
1234   *
1235   * For each operation, both this function and _content_field_invoke_default() are
1236   * called so that the default database handling can occur.
1237   */
1238  function _content_field_invoke($op, &$node, $teaser = NULL, $page = NULL) {
1239    $type_name = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
1240    $type = content_types($type_name);
1241    $field_types = _content_field_types();
1242  
1243    $return = array();
1244    foreach ($type['fields'] as $field) {
1245      $items = isset($node->$field['field_name']) ? $node->$field['field_name'] : array();
1246  
1247      // Make sure AHAH 'add more' button isn't sent to the fields for processing.
1248      unset($items[$field['field_name'] .'_add_more']);
1249  
1250      $module = $field_types[$field['type']]['module'];
1251      $function = $module .'_field';
1252      if (function_exists($function)) {
1253        $result = $function($op, $node, $field, $items, $teaser, $page);
1254        if (is_array($result)) {
1255          $return = array_merge($return, $result);
1256        }
1257        else if (isset($result)) {
1258          $return[] = $result;
1259        }
1260      }
1261      // test for values in $items in case modules added items on insert
1262      if (isset($node->$field['field_name']) || count($items)) {
1263        $node->$field['field_name'] = $items;
1264      }
1265    }
1266    return $return;
1267  }
1268  
1269  /**
1270   * Invoke content.module's version of a field hook.
1271   */
1272  function _content_field_invoke_default($op, &$node, $teaser = NULL, $page = NULL) {
1273    $type_name = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type);
1274    $type = content_types($type_name);
1275    $field_types = _content_field_types();
1276  
1277    $return = array();
1278    // The operations involving database queries are better off handled by table
1279    // rather than by field.
1280    if (in_array($op, array('load', 'insert', 'update', 'delete', 'delete revision'))) {
1281      return content_storage($op, $node);
1282    }
1283    else {
1284      foreach ($type['fields'] as $field) {
1285        $items = isset($node->$field['field_name']) ? $node->$field['field_name'] : array();
1286        $result = content_field($op, $node, $field, $items, $teaser, $page);
1287        if (is_array($result)) {
1288          $return = array_merge($return, $result);
1289        }
1290        else if (isset($result)) {
1291          $return[] = $result;
1292        }
1293        if (isset($node->$field['field_name'])) {
1294          $node->$field['field_name'] = $items;
1295        }
1296      }
1297    }
1298    return $return;
1299  }
1300  
1301  /**
1302   * Return a list of all content types.
1303   *
1304   * @param $content_type_name
1305   *   If set, return information on just this type.
1306   *
1307   * Do some type checking and set up empty arrays for missing
1308   * info to avoid foreach errors elsewhere in the code.
1309   */
1310  function content_types($type_name = NULL) {
1311    // handle type name with either an underscore or a dash
1312    $type_name = !empty($type_name) ? str_replace('-', '_', $type_name) : NULL;
1313  
1314    $info = _content_type_info();
1315    if (isset($info['content types'])) {
1316      if (!isset($type_name)) {
1317        return $info['content types'];
1318      }
1319      if (isset($info['content types'][$type_name])) {
1320        return $info['content types'][$type_name];
1321      }
1322    }
1323    return array('tables' => array(), 'fields' => array(), 'extra' => array());
1324  }
1325  
1326  /**
1327   * Return a list of all fields.
1328   *
1329   * @param $field_name
1330   *   If not empty, return information on just this field.
1331   * @param $content_type_name
1332   *   If not empty, return information of the field within the context of this content
1333   *   type.
1334   *
1335   * Be sure to check empty() instead of isset() on field_name and
1336   * content_type_name to avoid bad results when the value is set
1337   * but empty, as sometimes happens in the formatter.
1338   */
1339  function content_fields($field_name = NULL, $content_type_name = NULL) {
1340    $info = _content_type_info();
1341    if (isset($info['fields'])) {
1342      if (empty($field_name)) {
1343        return $info['fields'];
1344      }
1345      if (isset($info['fields'][$field_name])) {
1346        if (empty($content_type_name)) {
1347          return $info['fields'][$field_name];
1348        }
1349        if (isset($info['content types'][$content_type_name]['fields'][$field_name])) {
1350          return $info['content types'][$content_type_name]['fields'][$field_name];
1351        }
1352      }
1353    }
1354  }
1355  
1356  /**
1357   * Return a list of field types.
1358   */
1359  function _content_field_types() {
1360    $info = _content_type_info();
1361    return isset($info['field types']) ? $info['field types'] : array();
1362  }
1363  
1364  /**
1365   * Return a list of widget types.
1366   */
1367  function _content_widget_types() {
1368    $info = _content_type_info();
1369    return isset($info['widget types']) ? $info['widget types'] : array();
1370  }
1371  
1372  /**
1373   * Return the formatter description corresponding to a formatter name,
1374   * defaulting to 'default' if none is found.
1375   */
1376  function _content_get_formatter($formatter_name, $field_type) {
1377    $field_types = _content_field_types();
1378    $formatters = $field_types[$field_type]['formatters'];
1379  
1380    if (!isset($formatters[$formatter_name]) && $formatter_name != 'hidden') {
1381      // This might happen when the selected formatter has been renamed in the
1382      // module, or if the module has been disabled since then.
1383      $formatter_name = 'default';
1384    }
1385  
1386    return isset($formatters[$formatter_name]) ? $formatters[$formatter_name] : FALSE;
1387  }
1388  
1389  /**
1390   * Collate all information on content types, fields, and related structures.
1391   *
1392   * @param $reset
1393   *   If TRUE, clear the cache and fetch the information from the database again.
1394   */
1395  function _content_type_info($reset = FALSE) {
1396    global $language;
1397    static $info;
1398  
1399    if ($reset || !isset($info)) {
1400      // Make sure this function doesn't run until the tables have been created,
1401      // For instance: when first enabled and called from content_menu(),
1402      // or when uninstalled and some subsequent field module uninstall
1403      // attempts to refresh the data.
1404  
1405      // Don't try this before content module's update is run
1406      // to add module and active columns to the table.
1407      if (variable_get('content_schema_version', -1) < 6007) {
1408        return array();
1409      }
1410  
1411      if (!$reset && $cached = cache_get('content_type_info:'. $language->language, content_cache_tablename())) {
1412        $info = $cached->data;
1413      }
1414      else {
1415        $info = array(
1416          'field types' => array(),
1417          'widget types' => array(),
1418          'fields' => array(),
1419          'content types' => array(),
1420        );
1421  
1422        // Populate field types.
1423        foreach (module_list() as $module) {
1424          $module_field_types = module_invoke($module, 'field_info');
1425          if ($module_field_types) {
1426            foreach ($module_field_types as $name => $field_info) {
1427              // Truncate names to match the value that is stored in the database.
1428              $db_name = substr($name, 0, 32);
1429              $info['field types'][$db_name] = $field_info;
1430              $info['field types'][$db_name]['module'] = $module;
1431              $info['field types'][$db_name]['formatters'] = array();
1432            }
1433          }
1434        }
1435  
1436        // Populate widget types and formatters for known field types.
1437        foreach (module_list() as $module) {
1438          if ($module_widgets = module_invoke($module, 'widget_info')) {
1439            foreach ($module_widgets as $name => $widget_info) {
1440              // Truncate names to match the value that is stored in the database.
1441              $db_name = substr($name, 0, 32);
1442              $info['widget types'][$db_name] = $widget_info;
1443              $info['widget types'][$db_name]['module'] = $module;
1444              // Replace field types with db_compatible version of known field types.
1445              $info['widget types'][$db_name]['field types'] = array();
1446              foreach ($widget_info['field types'] as $field_type) {
1447                $field_type_db_name = substr($field_type, 0, 32);
1448                if (isset($info['field types'][$field_type_db_name])) {
1449                  $info['widget types'][$db_name]['field types'][] = $field_type_db_name;
1450                }
1451              }
1452            }
1453          }
1454  
1455          if ($module_formatters = module_invoke($module, 'field_formatter_info')) {
1456            foreach ($module_formatters as $name => $formatter_info) {
1457              foreach ($formatter_info['field types'] as $field_type) {
1458                // Truncate names to match the value that is stored in the database.
1459                $db_name = substr($field_type, 0, 32);
1460                if (isset($info['field types'][$db_name])) {
1461                  $info['field types'][$db_name]['formatters'][$name] = $formatter_info;
1462                  $info['field types'][$db_name]['formatters'][$name]['module'] = $module;
1463                }
1464              }
1465            }
1466          }
1467        }
1468  
1469        // Populate actual field instances.
1470        module_load_include('inc', 'content', 'includes/content.crud');
1471        foreach (node_get_types('types', NULL, TRUE) as $type_name => $data) {
1472          $type = (array) $data;
1473          $type['url_str'] = str_replace('_', '-', $type['type']);
1474          $type['fields'] = array();
1475          $type['tables'] = array();
1476          if ($fields = content_field_instance_read(array('type_name' => $type_name))) {
1477            foreach ($fields as $field) {
1478              $db_info = content_database_info($field);
1479              $type['tables'][$db_info['table']] = $db_info['table'];
1480  
1481              // Allow external modules to translate field strings.
1482              $field_strings = array(
1483                'widget_label' => $field['widget']['label'],
1484                'widget_description' => $field['widget']['description'],
1485              );
1486              drupal_alter('content_field_strings', $field_strings, $field['type_name'], $field['field_name']);
1487              $field['widget']['label'] = $field_strings['widget_label'];
1488              $field['widget']['description'] = $field_strings['widget_description'];
1489  
1490              $type['fields'][$field['field_name']] = $field;
1491              // This means that content_fields($field_name) (no type name)
1492              // returns the last instance loaded.
1493              $info['fields'][$field['field_name']] = $field;
1494            }
1495            // Make sure the per-type table is added, even if no field is actually
1496            // stored in it.
1497            $table = _content_tablename($type['type'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
1498            $type['tables'][$table] = $table;
1499          }
1500  
1501          // Gather information about non-CCK 'fields'.
1502          $extra = module_invoke_all('content_extra_fields', $type_name);
1503          drupal_alter('content_extra_fields', $extra, $type_name);
1504          // Add saved weights.
1505          foreach (variable_get('content_extra_weights_'. $type_name, array()) as $key => $value) {
1506            // Some stored entries might not exist anymore, for instance if uploads
1507            // have been disabled, or vocabularies removed...
1508            if (isset($extra[$key])) {
1509              $extra[$key]['weight'] = $value;
1510            }
1511          }
1512          $type['extra'] = $extra;
1513  
1514          $info['content types'][$type_name] = $type;
1515        }
1516  
1517        cache_set('content_type_info:'. $language->language, $info, content_cache_tablename());
1518      }
1519    }
1520    return $info;
1521  }
1522  
1523  /**
1524   *  Implementation of hook_node_type()
1525   *  React to change in node types
1526   */
1527  function content_node_type($op, $info) {
1528    switch ($op) {
1529      case 'insert':
1530        module_load_include('inc', 'content', 'includes/content.crud');
1531        content_type_create($info);
1532        break;
1533      case 'update':
1534        module_load_include('inc', 'content', 'includes/content.crud');
1535        content_type_update($info);
1536        break;
1537      case 'delete':
1538        module_load_include('inc', 'content', 'includes/content.crud');
1539        content_type_delete($info);
1540        break;
1541    }
1542  }
1543  
1544  /**
1545   * Clear the cache of content_types; called in several places when content
1546   * information is changed.
1547   */
1548  function content_clear_type_cache($rebuild_schema = FALSE) {
1549    cache_clear_all('*', content_cache_tablename(), TRUE);
1550    _content_type_info(TRUE);
1551  
1552    // Refresh the schema to pick up new information.
1553    if ($rebuild_schema) {
1554      $schema = drupal_get_schema(NULL, TRUE);
1555    }
1556  
1557    if (module_exists('views')) {
1558      // Needed because this can be called from .install files
1559      module_load_include('module', 'views');
1560      views_invalidate_cache();
1561    }
1562  }
1563  
1564  /**
1565   * Retrieve the database storage location(s) for a field.
1566   *
1567   * TODO: add a word about why it's not included in the global _content_type_info array.
1568   *
1569   * @param $field
1570   *   The field whose database information is requested.
1571   * @return
1572   *   An array with the keys:
1573   *     "table": The name of the database table where the field data is stored.
1574   *     "columns": An array of columns stored for this field. Each is a collection
1575   *       of information returned from hook_field_settings('database columns'),
1576   *       with the addition of a "column" attribute which holds the name of the
1577   *       database column that stores the data.
1578   */
1579  function content_database_info($field) {
1580    $db_info = array();
1581    if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) {
1582      $db_info['table'] = _content_tablename($field['field_name'], CONTENT_DB_STORAGE_PER_FIELD);
1583    }
1584    else {
1585      $db_info['table'] = _content_tablename($field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE);
1586    }
1587  
1588    $db_info['columns'] = (array) $field['columns'];
1589    // Generate column names for this field from generic column names.
1590    foreach ($db_info['columns'] as $column_name => $attributes) {
1591      $db_info['columns'][$column_name]['column'] = $field['field_name'] .'_'. $column_name;
1592    }
1593  
1594    return $db_info;
1595  }
1596  
1597  /**
1598   * Helper function for identifying the storage type for a field.
1599   */
1600  function content_storage_type($field) {
1601    if ($field['multiple'] > 0) {
1602      return CONTENT_DB_STORAGE_PER_FIELD;
1603    }
1604    else {
1605      module_load_include('inc', 'content', 'includes/content.crud');
1606      $instances = content_field_instance_read(array('field_name' => $field['field_name']));
1607      if (count($instances) > 1) {
1608        return CONTENT_DB_STORAGE_PER_FIELD;
1609      }
1610    }
1611    return CONTENT_DB_STORAGE_PER_CONTENT_TYPE;
1612  }
1613  
1614  /**
1615   * Manipulate a 2D array to reverse rows and columns.
1616   *
1617   * The default data storage for fields is delta first, column names second.
1618   * This is sometimes inconvenient for field modules, so this function can be
1619   * used to present the data in an alternate format.
1620   *
1621   * @param $array
1622   *   The array to be transposed. It must be at least two-dimensional, and
1623   *   the subarrays must all have the same keys or behavior is undefined.
1624   * @return
1625   *   The transposed array.
1626   */
1627  function content_transpose_array_rows_cols($array) {
1628    $result = array();
1629    if (is_array($array)) {
1630      foreach ($array as $key1 => $value1) {
1631        if (is_array($value1)) {
1632          foreach ($value1 as $key2 => $value2) {
1633            if (!isset($result[$key2])) {
1634              $result[$key2] = array();
1635            }
1636            $result[$key2][$key1] = $value2;
1637          }
1638        }
1639      }
1640    }
1641    return $result;
1642  }
1643  
1644  /**
1645   * Helper function to flatten an array of allowed values.
1646   *
1647   * @param $array
1648   *   A single or multidimensional array.
1649   * @return
1650   *   A flattened array.
1651   */
1652  function content_array_flatten($array) {
1653    $result = array();
1654    if (is_array($array)) {
1655      foreach ($array as $key => $value) {
1656        if (is_array($value)) {
1657          $result += content_array_flatten($value);
1658        }
1659        else {
1660          $result[$key] = $value;
1661        }
1662      }
1663    }
1664    return $result;
1665  }
1666  
1667  /**
1668   *  Create an array of the allowed values for this field.
1669   *
1670   *  Used by number and text fields, expects to find either
1671   *  PHP code that will return the correct value, or a string
1672   *  with keys and labels separated with '|' and with each
1673   *  new value on its own line.
1674   *
1675   * @param $field
1676   *   The field whose allowed values are requested.
1677   * @param $flatten
1678   *   Optional. Use TRUE to return a flattened array (default).
1679   *   FALSE can be used to support optgroups for select widgets
1680   *   when allowed values list is generated using PHP code.
1681   */
1682  function content_allowed_values($field, $flatten = TRUE) {
1683    static $allowed_values;
1684  
1685    $cid = $field['field_name'] .':'. ($flatten ? '1' : '0');
1686    if (isset($allowed_values[$cid])) {
1687      return $allowed_values[$cid];
1688    }
1689  
1690    $allowed_values[$cid] = array();
1691  
1692    if (isset($field['allowed_values_php'])) {
1693      ob_start();
1694      $result = eval($field['allowed_values_php']);
1695      if (is_array($result)) {
1696        if ($flatten) {
1697          $result = content_array_flatten($result);
1698        }
1699        $allowed_values[$cid] = $result;
1700      }
1701      ob_end_clean();
1702    }
1703  
1704    if (empty($allowed_values[$cid]) && isset($field['allowed_values'])) {
1705      $list = explode("\n", $field['allowed_values']);
1706      $list = array_map('trim', $list);
1707      $list = array_filter($list, 'strlen');
1708      foreach ($list as $opt) {
1709        // Sanitize the user input with a permissive filter.
1710        $opt = content_filter_xss($opt);
1711        if (strpos($opt, '|') !== FALSE) {
1712          list($key, $value) = explode('|', $opt);
1713          $allowed_values[$cid][$key] = (isset($value) && $value !=='') ? $value : $key;
1714        }
1715        else {
1716          $allowed_values[$cid][$opt] = $opt;
1717        }
1718      }
1719      // Allow external modules to translate allowed values list.
1720      drupal_alter('content_allowed_values', $allowed_values[$cid], $field);
1721    }
1722    return $allowed_values[$cid];
1723  }
1724  
1725  /**
1726   * Filter out HTML from allowed values array while leaving entities unencoded.
1727   *
1728   * @see content_allowed_values()
1729   * @see optionwidgets_select_process()
1730   * @see content_handler_filter_many_to_one::allowed_values()
1731   */
1732  function content_allowed_values_filter_html(&$options) {
1733    foreach ($options as $key => $opt) {
1734      if (is_array($opt)) {
1735        content_allowed_values_filter_html($options[$key]);
1736      }
1737      else {
1738        $options[$key] = html_entity_decode(strip_tags($opt), ENT_QUOTES);
1739      }
1740    }
1741  }
1742  
1743  /**
1744   * Like filter_xss_admin(), but with a shorter list of allowed tags.
1745   *
1746   * Used for items entered by administrators, like field descriptions,
1747   * allowed values, where some (mainly inline) mark-up may be desired
1748   * (so check_plain() is not acceptable).
1749   */
1750  function content_filter_xss($string) {
1751    return filter_xss($string, _content_filter_xss_allowed_tags());
1752  }
1753  
1754  /**
1755   * List of tags allowed by content_filter_xss().
1756   */
1757  function _content_filter_xss_allowed_tags() {
1758    return array('a', 'b', 'big',  'code', 'del', 'em', 'i', 'ins',  'pre', 'q', 'small', 'span', 'strong', 'sub', 'sup', 'tt', 'ol', 'ul', 'li', 'p', 'br', 'img');
1759  }
1760  
1761  /**
1762   * Human-readable list of allowed tags, for display in help texts.
1763   */
1764  function _content_filter_xss_display_allowed_tags() {
1765    return '<'. implode('> <', _content_filter_xss_allowed_tags()) .'>';
1766  }
1767  
1768  /**
1769   * Format a field item for display.
1770   *
1771   * Used to display a field's values outside the context of the $node, as
1772   * when fields are displayed in Views, or to display a field in a template
1773   * using a different formatter than the one set up on the Display Fields tab
1774   * for the node's context.
1775   *
1776   * @param $field
1777   *   Either a field array or the name of the field.
1778   * @param $item
1779   *   The field item(s) to be formatted (such as $node->field_foo[0],
1780   *   or $node->field_foo if the formatter handles multiple values itself)
1781   * @param $formatter_name
1782   *   The name of the formatter to use.
1783   * @param $node
1784   *   Optionally, the containing node object for context purposes and
1785   *   field-instance options.
1786   *
1787   * @return
1788   *   A string containing the contents of the field item(s) sanitized for display.
1789   *   It will have been passed through the necessary check_plain() or check_markup()
1790   *   functions as necessary.
1791   */
1792  function content_format($field, $item, $formatter_name = 'default', $node = NULL) {
1793    if (!is_array($field)) {
1794      $field = content_fields($field);
1795    }
1796  
1797    if (content_access('view', $field, NULL, $node) && $formatter = _content_get_formatter($formatter_name, $field['type'])) {
1798      $theme = $formatter['module'] .'_formatter_'. $formatter_name;
1799  
1800      $element = array(
1801        '#theme' => $theme,
1802        '#field_name' => $field['field_name'],
1803        '#type_name' => isset($node->type) ? $node->type :'',
1804        '#formatter' => $formatter_name,
1805        '#node' => $node,
1806        '#delta' => isset($item['#delta']) ? $item['#delta'] : NULL,
1807      );
1808  
1809      if (content_handle('formatter', 'multiple values', $formatter) == CONTENT_HANDLE_CORE) {
1810        // Single value formatter.
1811  
1812        // hook_field('sanitize') expects an array of items, so we build one.
1813        $items = array($item);
1814        $function = $field['module'] .'_field';
1815        if (function_exists($function)) {
1816          $function('sanitize', $node, $field, $items, FALSE, FALSE);
1817        }
1818  
1819        $element['#item'] = $items[0];
1820      }
1821      else {
1822        // Multiple values formatter.
1823        $items = $item;
1824        $function = $field['module'] .'_field';
1825        if (function_exists($function)) {
1826          $function('sanitize', $node, $field, $items, FALSE, FALSE);
1827        }
1828  
1829        foreach ($items as $delta => $item) {
1830          $element[$delta] = array(
1831            '#item' => $item,
1832            '#weight' => $delta,
1833          );
1834        }
1835      }
1836  
1837      return theme($theme, $element);
1838    }
1839  }
1840  
1841  /**
1842   * Registry of available node build modes.
1843   *
1844   * @param $selector
1845   *   Determines what information should be returned.
1846   * @return
1847   *   Depending on the value of the $selector parameter:
1848   *   - NULL: a flat list of all available build modes.
1849   *   The other two options are mainly used internally by CCK's UI:
1850   *   - '_tabs': the list of tabs to be shown on the 'Display fields' screens.
1851   *   - a string tab id: the build modes in this tab.
1852   */
1853  function content_build_modes($selector = NULL) {
1854    static $info;
1855  
1856    if (!isset($info)) {
1857      $data = array();
1858      foreach (module_implements('content_build_modes') as $module) {
1859        $function = $module .'_content_build_modes';
1860        $data = array_merge($data, (array) $function());
1861      }
1862      $flat = array();
1863      foreach ($data as $tab) {
1864        // Use the + operator to preserve numeric indexes (core build modes).
1865        $flat += (array) $tab['build modes'];
1866      }
1867      $info = array('tabs' => $data, 'build modes' => $flat);
1868    }
1869  
1870    if ($selector === '_tabs') {
1871      return $info['tabs'];
1872    }
1873    elseif (isset($selector) && isset($info['tabs'][$selector])) {
1874      return isset($info['tabs'][$selector]) ? $info['tabs'][$selector]['build modes'] : array();
1875    }
1876    else {
1877      return $info['build modes'];
1878    }
1879  }
1880  
1881  /**
1882   * Implementations of hook_content_build_modes
1883   * on behalf of core modules.
1884   *
1885   * @return
1886   * An array describing the build modes used by the module.
1887   * They are grouped by secondary tabs on CCK's 'Display fields' screens.
1888   *
1889   * Expected format:
1890   * array(
1891   *   // The first level keys (tab1_url, tab2_url) will be used to generate
1892   *   // the url of the tab: admin/content/node-type/[type_name]/display/[tab1_url]
1893   *   // A module can add its render modes to a tab defined by another module.
1894   *   // In this case, there's no need to provide a 'title' for this tab.
1895   *   'tab1_url' => array(
1896   *     'title' => t('The human-readable title of the tab'),
1897   *     'build modes' => array(
1898   *       // The keys of the 'context' array are the values used in $node->build_mode.
1899   *       'mymodule_mode1' => array(
1900   *         'title' => t('The human-readable name of the build mode'),
1901   *        // The 'views style' property determines if the render mode should be
1902   *        // available as an option in Views' 'node' row style (not implemented yet).
1903   *        'views style' => TRUE,
1904   *       ),
1905   *       'mymodule_mode2' => array(
1906   *         'title' => t('Mode 2'),
1907   *         'views style' => TRUE,
1908   *       ),
1909   *     ),
1910   *   ),
1911   *   'tab2_url' => array(
1912   *     // ...
1913   *   ),
1914   * );
1915   */
1916  function node_content_build_modes() {
1917    return array(
1918      'basic' => array(
1919        'title' => t('Basic'),
1920        'build modes' => array(
1921          'teaser' => array(
1922            'title' => t('Teaser'),
1923            'views style' => TRUE,
1924          ),
1925          'full' => array(
1926            'title' => t('Full node'),
1927            'views style' => TRUE,
1928          ),
1929        ),
1930      ),
1931      'rss' => array(
1932        'title' => t('RSS'),
1933        'build modes' => array(
1934          NODE_BUILD_RSS => array(
1935            'title' => t('RSS'),
1936            'views style' => FALSE,
1937          ),
1938        ),
1939      ),
1940    );
1941  }
1942  function search_content_build_modes() {
1943    return array(
1944      'search' => array(
1945        'title' => t('Search'),
1946        'build modes' => array(
1947          NODE_BUILD_SEARCH_INDEX => array(
1948            'title' => t('Search Index'),
1949            'views style' => FALSE,
1950          ),
1951          NODE_BUILD_SEARCH_RESULT => array(
1952            'title' => t('Search Result'),
1953            'views style' => FALSE,
1954          ),
1955        ),
1956      ),
1957    );
1958  }
1959  function book_content_build_modes() {
1960    return array(
1961      'print' => array(
1962        'title' => t('Print'),
1963        'build modes' => array(
1964          NODE_BUILD_PRINT => array(
1965            'title' => t('Print'),
1966            'views style' => TRUE,
1967          ),
1968        ),
1969      ),
1970    );
1971  }
1972  
1973  /**
1974   * Generate a table name for a field or a content type.
1975   *
1976   * @param $name
1977   *   The name of the content type or content field
1978   * @param $storage
1979   *   CONTENT_DB_STORAGE_PER_FIELD or CONTENT_DB_STORAGE_PER_CONTENT_TYPE
1980   * @return
1981   *   A string containing the generated name for the database table
1982   */
1983  function _content_tablename($name, $storage, $version = NULL) {
1984    if (is_null($version)) {
1985      $version = variable_get('content_schema_version', 0);
1986    }
1987  
1988    if ($version < 1003) {
1989      $version = 0;
1990    }
1991    else {
1992      $version = 1003;
1993    }
1994  
1995    $name = str_replace('-', '_', $name);
1996    switch ("$version-$storage") {
1997      case '0-'. CONTENT_DB_STORAGE_PER_CONTENT_TYPE :
1998        return "node_$name";
1999      case '0-'. CONTENT_DB_STORAGE_PER_FIELD :
2000        return "node_data_$name";
2001      case '1003-'. CONTENT_DB_STORAGE_PER_CONTENT_TYPE :
2002        return "content_type_$name";
2003      case '1003-'. CONTENT_DB_STORAGE_PER_FIELD :
2004        return "content_$name";
2005    }
2006  }
2007  
2008  /**
2009   * Generate table name for the content field table.
2010   *
2011   * Needed because the table name changes depending on version.
2012   * Using 'content_node_field' instead of 'content_field'
2013   * to avoid conflicts with field tables that will be prefixed
2014   * with 'content_field'.
2015   */
2016  function content_field_tablename($version = NULL) {
2017    if (is_null($version)) {
2018      $version = variable_get('content_schema_version', 0);
2019    }
2020    return $version < 6001 ? 'node_field' : 'content_node_field';
2021  }
2022  
2023  /**
2024   * Generate table name for the content field instance table.
2025   *
2026   * Needed because the table name changes depending on version.
2027   */
2028  function content_instance_tablename($version = NULL) {
2029    if (is_null($version)) {
2030      $version = variable_get('content_schema_version', 0);
2031    }
2032    return $version < 6001 ? 'node_field_instance' : 'content_node_field_instance';
2033  }
2034  
2035  /**
2036   * Generate table name for the content cache table.
2037   *
2038   * Needed because the table name changes depending on version. Because of
2039   * a new database column, the content_cache table will be unusable until
2040   * update 6000 runs, so the cache table will be used instead.
2041   */
2042  function content_cache_tablename() {
2043    if (variable_get('content_schema_version', -1) < 6000) {
2044      return 'cache';
2045    }
2046    else {
2047      return 'cache_content';
2048    }
2049  }
2050  
2051  /**
2052   * A basic schema used by all field and type tables.
2053   *
2054   * This will only add the columns relevant for the specified field.
2055   * Leave $field['columns'] empty to get only the base schema,
2056   * otherwise the function will return the whole thing.
2057   */
2058  function content_table_schema($field = NULL) {
2059    $schema = array(
2060      'fields' => array(
2061        'vid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
2062        'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0)
2063      ),
2064      'primary key' => array('vid'),
2065      'indexes' => array(
2066        'nid'    => array('nid'),
2067      ),
2068    );
2069  
2070    // Add delta column if needed.
2071    if (!empty($field['multiple'])) {
2072      $schema['fields']['delta'] = array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0);
2073      $schema['primary key'][] = 'delta';
2074    }
2075    $schema['content fields'] = array();
2076  
2077    // Add field columns column if needed.
2078    // This function is called from install files where it is not safe
2079    // to use content_fields() or content_database_info(), so we
2080    // just used the column values stored in the $field.
2081    // We also need the schema to include fields from disabled modules
2082    // or there will be no way to delete those fields.
2083  
2084    if (!empty($field['columns'])) {
2085      foreach ($field['columns'] as $column => $attributes) {
2086        $column_name =  $field['field_name'] .'_'. $column;
2087        if (isset($attributes['index']) && $attributes['index']) {
2088          $schema['indexes'][$column_name] = array($column_name);
2089          unset($attributes['index']);
2090        }
2091        unset($attributes['column']);
2092        unset($attributes['sortable']);
2093        $schema['fields'][$column_name] = $attributes;
2094      }
2095      $schema['content fields'][] = $field['field_name'];
2096    }
2097    return $schema;
2098  }
2099  
2100  /**
2101   * Checks if an index exists.
2102   *
2103   * @todo: May we remove this funcion when implemented by Drupal core itself?
2104   * @link http://drupal.org/node/360854
2105   * @link http://dev.mysql.com/doc/refman/5.0/en/extended-show.html
2106   *
2107   * @param $table
2108   *   Name of the table.
2109   * @param $name
2110   *   Name of the index.
2111   * @return
2112   *   TRUE if the table exists. Otherwise FALSE.
2113   */
2114  function content_db_index_exists($table, $name) {
2115    global $db_type;
2116    if ($db_type == 'mysql' || $db_type == 'mysqli') {
2117      if (version_compare(db_version(), '5.0.3') < 0) {
2118        // Earlier versions of MySQL don't support a WHERE clause for SHOW.
2119        $result = db_query('SHOW INDEX FROM {'. $table .'}');
2120        while ($row = db_fetch_array($result)) {
2121          if ($row['Key_name'] == $name) {
2122            return TRUE;
2123          }
2124        }
2125        return FALSE;
2126      }
2127      return (bool)db_result(db_query("SHOW INDEX FROM {". $table ."} WHERE key_name = '$name'"));
2128    }
2129    elseif ($db_type == 'pgsql') {
2130      // Note that the index names in Schema API for PostgreSQL are prefixed by
2131      // the table name and suffixed by '_idx'.
2132      return (bool)db_result(db_query("SELECT COUNT(indexname) FROM pg_indexes WHERE indexname = '{". $table ."}_{$name}_idx'"));
2133    }
2134    return FALSE;
2135  }
2136  
2137  /**
2138   *  Helper function for determining the behavior of a field or a widget
2139   *  with respect to a given operation. (currently used for field 'view',
2140   *  and widget 'default values' and 'multiple values')
2141   *
2142   *  @param $entity
2143   *    'field' or 'widget'
2144   *  @param $op
2145   *    the name of the operation ('view', 'default value'...)
2146   *  @param $field
2147   *    The field array, including widget info.
2148   *  @return
2149   *    CONTENT_CALLBACK_NONE    - do nothing for this operation
2150   *    CONTENT_CALLBACK_CUSTOM  - use the module's callback function.
2151   *    CONTENT_CALLBACK_DEFAULT - use content module default behavior
2152   *
2153   */
2154  function content_callback($entity, $op, $field) {
2155    switch ($entity) {
2156      case 'field':
2157        $info = module_invoke($field['module'], "field_info");
2158        return isset($info[$field['type']]['callbacks'][$op]) ? $info[$field['type']]['callbacks'][$op] : CONTENT_CALLBACK_DEFAULT;
2159  
2160      case 'widget':
2161        $info = module_invoke($field['widget']['module'], "widget_info");
2162        return isset($info[$field['widget']['type']]['callbacks'][$op]) ? $info[$field['widget']['type']]['callbacks'][$op] : CONTENT_CALLBACK_DEFAULT;
2163    }
2164  }
2165  
2166  /**
2167   *  Helper function for determining the handling of a field, widget or
2168   *  formatter with respect to a given operation.
2169   *
2170   *  Currently used for widgets and formatters 'multiple values'.
2171   *
2172   *  @param $entity
2173   *    'field', 'widget' or 'formatter'
2174   *  @param $op
2175   *    the name of the operation ('default values'...)
2176   *  @param $object
2177   *    - if $entity is 'field' or 'widget': the field array,
2178   *      including widget info.
2179   *    - if $entity is 'formater': the formatter array.
2180   *  @return
2181   *    CONTENT_HANDLE_CORE    - the content module handles this operation.
2182   *    CONTENT_HANDLE_MODULE  - the implementing module handles this operation.
2183   */
2184  function content_handle($entity, $op, $object) {
2185    switch ($entity) {
2186      case 'field':
2187        $info = module_invoke($object['module'], "field_info");
2188        return isset($info[$object['type']][$op]) ? $info[$object['type']][$op] : CONTENT_HANDLE_CORE;
2189  
2190      case 'widget':
2191        $info = module_invoke($object['widget']['module'], "widget_info");
2192        return isset($info[$object['widget']['type']][$op]) ? $info[$object['widget']['type']][$op] : CONTENT_HANDLE_CORE;
2193  
2194      case 'formatter':
2195        // Much simpler, formatters arrays *are* the 'formatter_info' itself.
2196        // We let content_handle deal with them only for code consistency.
2197        return isset($object[$op]) ? $object[$op] : CONTENT_HANDLE_CORE;
2198    }
2199  }
2200  
2201  /**
2202   *  Helper function to return the correct default value for a field.
2203   *
2204   *  @param $node
2205   *    The node.
2206   *  @param $field
2207   *    The field array.
2208   *  @param $items
2209   *    The value of the field in the node.
2210   *  @return
2211   *    The default value for that field.
2212   */
2213  function content_default_value(&$form, &$form_state, $field, $delta) {
2214    $widget_types = _content_widget_types();
2215    $module = $widget_types[$field['widget']['type']]['module'];
2216  
2217    $default_value = array();
2218    if (!empty($field['widget']['default_value_php'])) {
2219      ob_start();
2220      $result = eval($field['widget']['default_value_php']);
2221      ob_end_clean();
2222      if (is_array($result)) {
2223        $default_value = $result;
2224      }
2225    }
2226    elseif (!empty($field['widget']['default_value'])) {
2227      $default_value = $field['widget']['default_value'];
2228    }
2229    return (array) $default_value;
2230  }
2231  
2232  /**
2233   * Determine whether the user has access to a given field.
2234   *
2235   * @param $op
2236   *   The operation to be performed. Possible values:
2237   *   - "edit"
2238   *   - "view"
2239   * @param $field
2240   *   The field on which the operation is to be performed.
2241   * @param $account
2242   *   (optional) The account to check, if not given use currently logged in user.
2243   * @param $node
2244   *   (optional) The node on which the operation is to be performed.
2245   * @return
2246   *   TRUE if the operation is allowed;
2247   *   FALSE if the operation is denied.
2248   */
2249  function content_access($op, $field, $account = NULL, $node = NULL) {
2250    global $user;
2251  
2252    if (is_null($account)) {
2253      $account = $user;
2254    }
2255    // Check for valid field data.
2256    if (!isset($field['field_name'])) {
2257      return FALSE;
2258    }
2259    $access = module_invoke_all('field_access', $op, $field, $account, $node);
2260    foreach ($access as $value) {
2261      if ($value === FALSE) {
2262        return FALSE;
2263      }
2264    }
2265    return TRUE;
2266  }
2267  
2268   /**
2269   * Hide specified fields from the $content variable in node templates.
2270   */
2271  function content_field_wrapper_post_render($content, $element) {
2272    $field = content_fields($element['#field_name'], $element['#type_name']);
2273    if (theme('content_exclude', $content, $field, $element['#context'])) {
2274      return '';
2275    }
2276    return $content;
2277  }
2278  
2279  
2280  /**
2281   * 'Theme' function for a field's addition to $content.
2282   *
2283   * Adapts the all-inclusive $content variable in node templates to allow
2284   * some field content to be excluded. This is a theme function, so it can be
2285   * overridden in different themes to produce different results.
2286   *
2287   * The html for individual fields and groups are available in the
2288   * $FIELD_NAME_rendered and $GROUP_NAME_rendered variables.
2289   *
2290   * This allows more flexibility in node templates : you can use custom markup
2291   * around a few specific fields, and print the rest of the node with $content.
2292   *
2293   * @param $content
2294   *    The themed content for this field or group.
2295   *
2296   * @param $object
2297   *    The field or group array for this item.
2298   *    $object['#type_name'] holds the content type.
2299   *    $object['#field_name'] holds the field name (if a field).
2300   *    $object['#group_name'] holds the group name (if a group).
2301   *    $object['display_settings'] holds the display settings
2302   *    for all contexts, in an array like:
2303   *      $object['display_settings'] => array(
2304   *        'full' => array(
2305   *          'format' => 'default',
2306   *          'exclude' => 0,
2307   *         ),
2308   *        'teaser' => array(
2309   *          'format' => 'default',
2310   *          'exclude' => 1,
2311   *         ),
2312   *      );
2313   *
2314   * @param $context
2315   *    The context for which the node is being rendered.
2316   *    Can be one of the following values :
2317   *    - 'teaser'
2318   *    - 'full'
2319   *    - NODE_BUILD_SEARCH_INDEX
2320   *    - NODE_BUILD_SEARCH_RESULT
2321   *    - NODE_BUILD_RSS
2322   *    - NODE_BUILD_PRINT
2323   *    - ... any other custom build mode exposed by 3rd party modules using
2324   *      hook_content_build_modes().
2325   *
2326   * @return
2327   *   Whether or not content is to be added to $content in this context.
2328   *   Uses the value of the 'Exclude' checkbox for this field
2329   *   as set on the Manage fields screen.
2330   */
2331  function theme_content_exclude($content, $object, $context) {
2332    // The field may be missing info for $contexts added by modules
2333    // enabled after the field was last edited.
2334    if (empty($object['display_settings'])
2335      || empty($object['display_settings'][$context])
2336      || !is_array($object['display_settings'][$context])
2337      || empty($object['display_settings'][$context]['exclude'])) {
2338      return FALSE;
2339    }
2340    else {
2341      return TRUE;
2342    }
2343  }
2344  
2345  /**
2346   * Theme preprocess function for field.tpl.php.
2347   *
2348   * The $variables array contains the following arguments:
2349   * - $node
2350   * - $field
2351   * - $items
2352   * - $teaser
2353   * - $page
2354   *
2355   * @see field.tpl.php
2356   *
2357   * TODO : this should live in theme/theme.inc, but then the preprocessor
2358   * doesn't get called when the theme overrides the template. Bug in theme layer ?
2359   */
2360  function template_preprocess_content_field(&$variables) {
2361    $element = $variables['element'];
2362    $field = content_fields($element['#field_name'], $element['#node']->type);
2363  
2364    $variables['node'] = $element['#node'];
2365    $variables['field'] = $field;
2366    $variables['items'] = array();
2367  
2368    if ($element['#single']) {
2369      // Single value formatter.
2370      foreach (element_children($element['items']) as $delta) {
2371        $variables['items'][$delta] = $element['items'][$delta]['#item'];
2372        // Use isset() to avoid undefined index message on #children when field values are empty.
2373        $variables['items'][$delta]['view'] = isset($element['items'][$delta]['#children']) ? $element['items'][$delta]['#children'] : '';
2374      }
2375    }
2376    else  {
2377      // Multiple values formatter.
2378      // We display the 'all items' output as $items[0], as if it was the
2379      // output of a single valued field.
2380      // Raw values are still exposed for all items.
2381      foreach (element_children($element['items']) as $delta) {
2382        $variables['items'][$delta] = $element['items'][$delta]['#item'];
2383      }
2384      $variables['items'][0]['view'] = $element['items']['#children'];
2385    }
2386  
2387    $variables['teaser'] = $element['#teaser'];
2388    $variables['page'] = $element['#page'];
2389  
2390    $field_empty = TRUE;
2391  
2392    foreach ($variables['items'] as $delta => $item) {
2393      if (!isset($item['view']) || (empty($item['view']) && (string)$item['view'] !== '0')) {
2394        $variables['items'][$delta]['empty'] = TRUE;
2395      }
2396      else {
2397        $field_empty = FALSE;
2398        $variables['items'][$delta]['empty'] = FALSE;
2399      }
2400    }
2401  
2402    $additions = array(
2403      'field_type' => $field['type'],
2404      'field_name' => $field['field_name'],
2405      'field_type_css' => strtr($field['type'], '_', '-'),
2406      'field_name_css' => strtr($field['field_name'], '_', '-'),
2407      'label' => check_plain(t($field['widget']['label'])),
2408      'label_display' => $element['#label_display'],
2409      'field_empty' => $field_empty,
2410      'template_files' => array(
2411        'content-field',
2412        'content-field-'. $element['#field_name'],
2413        'content-field-'. $element['#node']->type,
2414        'content-field-'. $element['#field_name'] .'-'. $element['#node']->type,
2415      ),
2416    );
2417    $variables = array_merge($variables, $additions);
2418  }
2419  
2420  /**
2421   * Theme preprocess function for node.
2422   *
2423   * - Adds $FIELD_NAME_rendered variables
2424   *   containing the themed output for the whole field.
2425   * - Adds the formatted values in the 'view' key of the items.
2426   */
2427  function content_preprocess_node(&$vars) {
2428    $additions = _content_field_invoke_default('preprocess_node', $vars['node']);
2429    $vars = array_merge($vars, $additions);
2430  }
2431  
2432  /**
2433   * Debugging using hook_content_fieldapi.
2434   *
2435   * @TODO remove later
2436   *
2437   * @param $op
2438   * @param $field
2439   */
2440  function content_content_fieldapi($op, $field) {
2441    if (module_exists('devel')) {
2442      //dsm($op);
2443      //dsm($field);
2444    }
2445  }
2446  
2447  /**
2448   * Implementation of hook_content_extra_fields.
2449   *
2450   * Informations for non-CCK 'node fields' defined in core.
2451   */
2452  function content_content_extra_fields($type_name) {
2453    $type = node_get_types('type', $type_name);
2454    $extra = array();
2455  
2456    if ($type->has_title) {
2457      $extra['title'] = array(
2458        'label' => $type->title_label,
2459        'description' => t('Node module form.'),
2460        'weight' => -5
2461      );
2462    }
2463    if ($type->has_body) {
2464      $extra['body_field'] = array(
2465        'label' => $type->body_label,
2466        'description' => t('Node module form.'),
2467        'weight' => 0,
2468        'view' => 'body'
2469      );
2470    }
2471    $extra['revision_information'] = array(
2472      'label' => t('Revision information'),
2473      'description' => t('Node module form.'),
2474      'weight' => 20
2475    );
2476    $extra['author'] = array(
2477      'label' => t('Authoring information'),
2478      'description' => t('Node module form.'),
2479      'weight' => 20,
2480    );
2481    $extra['options'] = array(
2482      'label' => t('Publishing options'),
2483      'description' => t('Node module form.'),
2484      'weight' => 25,
2485     );
2486    if (module_exists('comment')) {
2487      $extra['comment_settings'] = array(
2488        'label' => t('Comment settings'),
2489        'description' => t('Comment module form.'),
2490        'weight' => 30
2491      );
2492    }
2493    if (module_exists('locale') && variable_get("language_content_type_$type_name", 0)) {
2494      $extra['language'] = array(
2495        'label' => t('Language'),
2496        'description' => t('Locale module form.'),
2497        'weight' => 0
2498      );
2499    }
2500    if (module_exists('translation') && translation_supported_type($type_name)) {
2501      $extra['translation'] = array(
2502        'label' => t('Translation settings'),
2503        'description' => t('Translation module form.'),
2504        'weight' => 30
2505      );
2506    }
2507    if (module_exists('menu')) {
2508      $extra['menu'] = array(
2509        'label' => t('Menu settings'),
2510        'description' => t('Menu module form.'),
2511        'weight' => -2
2512      );
2513    }
2514    if (module_exists('taxonomy') && taxonomy_get_vocabularies($type_name)) {
2515      $extra['taxonomy'] = array(
2516        'label' => t('Taxonomy'),
2517        'description' => t('Taxonomy module form.'),
2518        'weight' => -3
2519      );
2520    }
2521    if (module_exists('book')) {
2522      $extra['book'] = array(
2523        'label' => t('Book'),
2524        'description' => t('Book module form.'),
2525        'weight' => 10
2526      );
2527    }
2528    if (module_exists('path')) {
2529      $extra['path'] = array(
2530        'label' => t('Path settings'),
2531        'description' => t('Path module form.'),
2532        'weight' => 30
2533      );
2534    }
2535    if ($type_name == 'poll' && module_exists('poll')) {
2536      $extra['title'] = array(
2537        'label' => t('Poll title'),
2538        'description' => t('Poll module title.'),
2539        'weight' => -5
2540      );
2541      $extra['choice_wrapper'] = array(
2542        'label' => t('Poll choices'),
2543        'description' => t('Poll module choices.'),
2544        'weight' => -4
2545      );
2546      $extra['settings'] = array(
2547        'label' => t('Poll settings'),
2548        'description' => t('Poll module settings.'),
2549        'weight' => -3
2550      );
2551    }
2552    if (module_exists('upload') && variable_get("upload_$type_name", TRUE)) {
2553      $extra['attachments'] = array(
2554        'label' => t('File attachments'),
2555        'description' => t('Upload module form.'),
2556        'weight' => 30,
2557        'view' => 'files'
2558      );
2559    }
2560  
2561    return $extra;
2562  }
2563  
2564  /**
2565   * Retrieve the user-defined weight for non-CCK node 'fields'.
2566   *
2567   * CCK's 'Manage fields' page lets users reorder node fields, including non-CCK
2568   * items (body, taxonomy, other hook_nodeapi-added elements by contrib modules...).
2569   * Contrib modules that want to have their 'fields' supported need to expose
2570   * them with hook_content_extra_fields, and use this function to retrieve the
2571   * user-defined weight.
2572   *
2573   * @param $type_name
2574   *   The content type name.
2575   * @param $pseudo_field_name
2576   *   The name of the 'field'.
2577   * @return
2578   *   The weight for the 'field', respecting the user settings stored
2579   *   by content.module.
2580   */
2581  function content_extra_field_weight($type_name, $pseudo_field_name) {
2582    $type = content_types($type_name);
2583  
2584    // If we don't have the requested item, this may be because the cached
2585    // information for 'extra' fields hasn't been refreshed yet.
2586    if (!isset($type['extra'][$pseudo_field_name])) {
2587      content_clear_type_cache();
2588      $type = content_types($type_name);
2589    }
2590  
2591    if (isset($type['extra'][$pseudo_field_name])) {
2592      return $type['extra'][$pseudo_field_name]['weight'];
2593    }
2594  }
2595  
2596  /**
2597   * Find max delta value actually in use for a field.
2598   *
2599   * Helper function to do things like tell when we should prevent a
2600   * change in multiple value settings that would result in data loss,
2601   * or know if content actually exists for a field.
2602   *
2603   * @param $field_name
2604   *   The field name to examine.
2605   * @param $type_name
2606   *   If provided, search only for existing data in that type,
2607   *   otherwise search for all instances of field data in all types.
2608   * @return
2609   *   NULL if field is not in use, or the maximum delta value in use.
2610   *
2611   * TODO
2612   * Go back to the field settings validation and use this function
2613   * to prevent (or confirm) changes in multiple values that
2614   * would destroy data.
2615   *
2616   * Fields with only NULL data will show up as being in use.
2617   * Do we want to eliminate them from the results?
2618   */
2619  function content_max_delta($field_name, $type_name = NULL) {
2620    $fields = content_fields();
2621    $field = $fields[$field_name];
2622  
2623    // Non-multiple value fields don't use the delta column,
2624    // but could exist in multiple databases. If any value
2625    // exists in any examined table, the max delta will be zero.
2626    if (empty($field['multiple'])) {
2627      $content_types = content_types();
2628      foreach ($content_types as $content_type) {
2629        if (empty($type_name) || $content_type['type'] == $type_name) {
2630          foreach ($content_type['fields'] as $field) {
2631            $db_info = content_database_info($field);
2632            if (db_result(db_query("SELECT COUNT(*) FROM {". $db_info['table'] ."}")) >= 1) {
2633              return 0;
2634            }
2635          }
2636        }
2637      }
2638    }
2639    // Multiple value fields always share the same table and use the delta.
2640    // If we want to find delta values for a particular type, we join
2641    // in the node table to limit the type.
2642    else {
2643      $db_info = content_database_info($field);
2644      if (!empty($type_name)) {
2645        $delta = db_result(db_query("SELECT MAX(delta) FROM {". $db_info['table'] ."} f LEFT JOIN {node} n ON f.vid = n.vid WHERE n.type = '%s'", $type_name));
2646      }
2647      else {
2648        $delta = db_result(db_query("SELECT MAX(delta) FROM {". $db_info['table'] ."}"));
2649      }
2650      if ($delta >= 0) {
2651        return $delta;
2652      }
2653    }
2654    // If we got this far, there is no data for this field.
2655    return NULL;
2656  }
2657  
2658  /**
2659   * Helper function to identify inactive fields.
2660   */
2661  function content_inactive_fields($type_name = NULL) {
2662    module_load_include('inc', 'content', 'includes/content.crud');
2663    if (!empty($type_name)) {
2664      $param = array('type_name' => $type_name);
2665      $inactive = array($type_name => array());
2666    }
2667    else {
2668      $param = array();
2669      $inactive = array();
2670    }
2671    $all = content_field_instance_read($param, TRUE);
2672    $active = array_keys(content_fields());
2673    foreach ($all as $field) {
2674      if (!in_array($field['field_name'], $active)) {
2675        $inactive[$field['type_name']][$field['field_name']] = content_field_instance_expand($field);
2676      }
2677    }
2678    if (!empty($type_name)) {
2679      return $inactive[$type_name];
2680    }
2681    return $inactive;
2682  }
2683  
2684   
2685  /**
2686   * Helper function to identify inactive instances.
2687   * This will be the same results as content_inactive_fields(),
2688   * EXCEPT that his function will return inactive instances even 
2689   * if the fields have other (shared) instances that are still active.
2690   */
2691  function content_inactive_instances($type_name = NULL) {
2692    module_load_include('inc', 'content', 'includes/content.crud');
2693    if (!empty($type_name)) {
2694      $param = array('type_name' => $type_name);
2695      $inactive = array($type_name => array());
2696    }
2697    else {
2698      $param = array();
2699      $inactive = array();
2700    }
2701    $all = content_field_instance_read($param, TRUE);
2702    foreach ($all as $field) {
2703      $inactive[$field['type_name']][$field['field_name']] = content_field_instance_expand($field);
2704    }
2705    if (!empty($type_name)) {
2706      return $inactive[$type_name];
2707    }
2708    return $inactive;
2709  }


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7