source: sipes/modules_contrib/cck/content.module @ 3514c84

stableversion-3.0
Last change on this file since 3514c84 was dafc8d8, checked in by José Gregorio Puentes <jpuentes@…>, 8 años ago

se actualizo el modulo

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