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