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