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