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