Newer
Older
Karen Stevenson
committed
* Allows administrators to associate custom fields to content types.
define('CONTENT_DB_STORAGE_PER_FIELD', 0);
define('CONTENT_DB_STORAGE_PER_CONTENT_TYPE', 1);
define('CONTENT_CALLBACK_NONE', 0x0001);
define('CONTENT_CALLBACK_DEFAULT', 0x0002);
define('CONTENT_CALLBACK_CUSTOM', 0x0004);
Karen Stevenson
committed
define('CONTENT_HANDLE_CORE', 0x0001);
define('CONTENT_HANDLE_MODULE', 0x0002);
Karen Stevenson
committed
function content_help($path, $arg) {
Yves Chedemois
committed
switch ($path) {
case 'admin/help#content':
$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>';
$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>';
$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>';
$output .= '<ul>';
$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>';
$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>';
$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>';
$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>';
$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>';
$output .= '</ul>';
$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>';
return $output;
}
Yves Chedemois
committed
}
Yves Chedemois
committed
/**
* Implementation of hook_flush_caches.
*/
function content_flush_caches() {
return array(content_cache_tablename());
}
Karen Stevenson
committed
/**
* Implementation of hook_init().
*/
function content_init() {
Karen Stevenson
committed
drupal_add_css(drupal_get_path('module', 'content') .'/theme/content-module.css');
Karen Stevenson
committed
if (module_exists('token') && !function_exists('content_token_values')) {
Karen Stevenson
committed
module_load_include('inc', 'content', 'includes/content.token');
Karen Stevenson
committed
}
if (module_exists('diff') && !function_exists('content_diff')) {
Karen Stevenson
committed
module_load_include('inc', 'content', 'includes/content.diff');
Karen Stevenson
committed
}
Karen Stevenson
committed
if (module_exists('panels')) {
module_load_include('inc', 'content', 'includes/content.panels');
}
Yves Chedemois
committed
/**
* Implementation of hook_perm().
*/
function content_perm() {
Karen Stevenson
committed
return array('Use PHP input for field settings (dangerous - grant with care)');
Yves Chedemois
committed
}
Yves Chedemois
committed
/**
* Implementation of hook_menu_alter().
*/
function content_menu_alter(&$items) {
// Customize the content types page with our own callback
$items['admin/content/types']['page callback'] = 'content_types_overview';
Yves Chedemois
committed
$items['admin/content/types']['file'] = 'content.admin.inc';
$items['admin/content/types']['file path'] = drupal_get_path('module', 'content') .'/includes';
}
$items['admin/content/types/fields'] = array(
Karen Stevenson
committed
'title' => 'Fields',
'page callback' => 'content_fields_list',
'access arguments' => array('administer content types'),
Karen Stevenson
committed
'file' => 'includes/content.admin.inc',
'type' => MENU_LOCAL_TASK,
);
Yves Chedemois
committed
// Callback for AHAH add more buttons.
$items['content/js_add_more'] = array(
'page callback' => 'content_add_more_js',
'access arguments' => array('access content'),
Karen Stevenson
committed
'file' => 'includes/content.node_form.inc',
Yves Chedemois
committed
'type' => MENU_CALLBACK,
);
Karen Stevenson
committed
Karen Stevenson
committed
// Make sure this doesn't fire until content_types is working,
Karen Stevenson
committed
// and tables are updated, needed to avoid errors on initial installation.
if (!defined('MAINTENANCE_MODE') && variable_get('content_schema_version', -1) >= 6007) {
Karen Stevenson
committed
foreach (node_get_types() as $type) {
$type_name = $type->type;
$items['admin/content/node-type/'. $type_url_str .'/fields'] = array(
'page callback' => 'drupal_get_form',
'page arguments' => array('content_field_overview_form', $type_name),
Yves Chedemois
committed
'access arguments' => array('administer content types'),
Karen Stevenson
committed
'file' => 'includes/content.admin.inc',
Karen Stevenson
committed
'type' => MENU_LOCAL_TASK,
Yves Chedemois
committed
'weight' => 1,
$items['admin/content/node-type/'. $type_url_str .'/display'] = array(
'page callback' => 'drupal_get_form',
'page arguments' => array('content_display_overview_form', $type_name),
'access arguments' => array('administer content types'),
Karen Stevenson
committed
'file' => 'includes/content.admin.inc',
Yves Chedemois
committed
'weight' => 2,
Karen Stevenson
committed
);
$contexts = content_build_modes('_tabs');
foreach ($contexts as $key => $tab) {
$items['admin/content/node-type/'. $type_url_str .'/display/'. $key] = array(
'title' => $tab['title'],
'page arguments' => array('content_display_overview_form', $type_name, $key),
'access arguments' => array('administer content types'),
'type' => $key == 'basic' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
'weight' => $key == 'basic' ? 0 : 1,
);
}
Karen Stevenson
committed
// Cast as an array in case this is called before any fields have
// been added, like when a new content type is created.
foreach ((array) $content_type['fields'] as $field) {
Karen Stevenson
committed
$field_name = $field['field_name'];
$items['admin/content/node-type/'. $type_url_str .'/fields/'. $field_name] = array(
'page arguments' => array('content_field_edit_form', $type_name, $field_name),
'access arguments' => array('administer content types'),
Karen Stevenson
committed
'file' => 'includes/content.admin.inc',
Karen Stevenson
committed
'type' => MENU_LOCAL_TASK,
$items['admin/content/node-type/'. $type_url_str .'/fields/'. $field_name .'/remove'] = array(
'title' => 'Remove field',
'page callback' => 'drupal_get_form',
'page arguments' => array('content_field_remove_form', $type_name, $field_name),
'access arguments' => array('administer content types'),
Karen Stevenson
committed
'file' => 'includes/content.admin.inc',
Yves Chedemois
committed
/**
* Hook elements().
*
* Used to add multiple value processing, validation, and themes.
*
* FAPI callbacks can be declared here, and the element will be
* passed to those callbacks.
*
* Drupal will automatically theme the element using a theme with
* the same name as the hook_elements key.
*/
function content_elements() {
return array(
'content_multiple_values' => array(),
'content_field' => array(),
);
}
Karen Stevenson
committed
/**
* Implementation of hook_theme().
*/
function content_theme() {
$path = drupal_get_path('module', 'content') .'/theme';
Yves Chedemois
committed
require_once "./$path/theme.inc";
Karen Stevenson
committed
return array(
'content_field' => array(
'template' => 'content-field',
'arguments' => array('element' => NULL),
'path' => $path,
Yves Chedemois
committed
),
Yves Chedemois
committed
'content_overview_links' => array(
'arguments' => array(),
),
'content_field_overview_form' => array(
Yves Chedemois
committed
'template' => 'content-admin-field-overview-form',
'file' => 'theme.inc',
'path' => $path,
Karen Stevenson
committed
'arguments' => array('form' => NULL),
Yves Chedemois
committed
),
'content_display_overview_form' => array(
Yves Chedemois
committed
'template' => 'content-admin-display-overview-form',
'file' => 'theme.inc',
'path' => $path,
Karen Stevenson
committed
'arguments' => array('form' => NULL),
Yves Chedemois
committed
),
Karen Stevenson
committed
'content_exclude' => array(
Karen Stevenson
committed
'arguments' => array('content' => NULL, 'object' => array(), 'context' => NULL),
),
Karen Stevenson
committed
'content_view_multiple_field' => array(
Karen Stevenson
committed
'arguments' => array('items' => NULL, 'field' => NULL, 'data' => NULL),
Yves Chedemois
committed
),
Karen Stevenson
committed
'content_multiple_values' => array(
'arguments' => array('element' => NULL),
),
Yves Chedemois
committed
);
Karen Stevenson
committed
}
Yves Chedemois
committed
/**
* Implementation of hook_views_api().
*/
function content_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'content') . '/includes/views',
);
}
Karen Stevenson
committed
* Load data for a node type's fields.
Darrel O'Pry
committed
* Implementation of hook_nodeapi 'load' op.
*
* When loading one of the content.module nodes, we need to let each field handle
* its own loading. This can make for a number of queries in some cases, so we
* cache the loaded object structure and invalidate it during the update process.
Darrel O'Pry
committed
function content_load(&$node) {
$cid = 'content:'. $node->nid .':'. $node->vid;
Karen Stevenson
committed
if ($cached = cache_get($cid, content_cache_tablename())) {
Darrel O'Pry
committed
foreach ($cached->data as $key => $value) {
$node->$key = $value;
}
$default_additions = _content_field_invoke_default('load', $node);
if ($default_additions) {
foreach ($default_additions as $key => $value) {
$node->$key = $value;
}
}
if ($additions) {
foreach ($additions as $key => $value) {
Yves Chedemois
committed
$node->$key = $value;
$default_additions[$key] = $value;
}
}
Karen Stevenson
committed
cache_set($cid, $default_additions, content_cache_tablename());
Karen Stevenson
committed
/**
Darrel O'Pry
committed
* Implementation of hook_nodeapi 'validate' op.
Karen Stevenson
committed
*
*/
Darrel O'Pry
committed
function content_validate(&$node, $form = NULL) {
_content_field_invoke('validate', $node, $form);
_content_field_invoke_default('validate', $node, $form);
Karen Stevenson
committed
}
/**
Darrel O'Pry
committed
* Implementation of hook_nodeapi 'presave' op.
Karen Stevenson
committed
*
*/
function content_presave(&$node) {
_content_field_invoke('presave', $node);
_content_field_invoke_default('presave', $node);
}
Jonathan Chaffer
committed
/**
Darrel O'Pry
committed
* Implementation of hook_nodeapi 'insert' op.
Karen Stevenson
committed
* Insert node type fields.
Jonathan Chaffer
committed
*/
function content_insert(&$node) {
_content_field_invoke('insert', $node);
_content_field_invoke_default('insert', $node);
Jonathan Chaffer
committed
}
/**
Darrel O'Pry
committed
* Implementation of hook_nodeapi 'update' op.
Karen Stevenson
committed
* Update node type fields.
Jonathan Chaffer
committed
*/
function content_update(&$node) {
_content_field_invoke('update', $node);
_content_field_invoke_default('update', $node);
Karen Stevenson
committed
cache_clear_all('content:'. $node->nid .':'. $node->vid, content_cache_tablename());
Jonathan Chaffer
committed
}
/**
Darrel O'Pry
committed
* Implementation of hook_nodeapi 'delete' op.
Karen Stevenson
committed
* Delete node type fields.
Jonathan Chaffer
committed
*/
function content_delete(&$node) {
Karen Stevenson
committed
_content_field_invoke('delete', $node);
_content_field_invoke_default('delete', $node);
Yves Chedemois
committed
cache_clear_all('content:'. $node->nid .':', content_cache_tablename(), TRUE);
Karen Stevenson
committed
}
/**
Darrel O'Pry
committed
* Implementation of hook_nodeapi 'delete_revision' op.
Karen Stevenson
committed
*/
function content_delete_revision(&$node) {
Karen Stevenson
committed
_content_field_invoke('delete revision', $node);
_content_field_invoke_default('delete revision', $node);
Karen Stevenson
committed
cache_clear_all('content:'. $node->nid .':'. $node->vid, content_cache_tablename());
Jonathan Chaffer
committed
}
Yves Chedemois
committed
* Implementation of hook_nodeapi 'view' op.
Karen Stevenson
committed
* Generate field render arrays.
*/
function content_view(&$node, $teaser = FALSE, $page = FALSE) {
// Let field modules sanitize their data for output.
_content_field_invoke('sanitize', $node, $teaser, $page);
$additions = _content_field_invoke_default('view', $node, $teaser, $page);
Yves Chedemois
committed
$node->content = array_merge((array) $node->content, $additions);
Yves Chedemois
committed
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
/**
* Render a single field, fully themed with label and multiple values.
*
* To be used by third-party code (Views, Panels...) that needs to output
* an isolated field. Do *not* use inside node templates, use the
* $FIELD_NAME_rendered variables instead.
*
* By default, the field is displayed using the settings defined for the
* 'full node' or 'teaser' contexts (depending on the value of the $teaser param).
* Set $node->build_mode to a different value to use a different context.
*
* Different settings can be specified by adjusting $field['display_settings'].
*
* @param $field
* The field definition.
* @param $node
* The node containing the field to display. Can be a 'pseudo-node', containing
* at least 'type', 'nid', 'vid', and the field data.
* @param $teaser
* @param $page
* Similar to hook_nodeapi('view')
* @return
* The themed output for the field.
*/
function content_view_field($field, $node, $teaser = FALSE, $page = FALSE) {
$output = '';
if (isset($node->$field['field_name'])) {
$items = $node->$field['field_name'];
// Use 'full'/'teaser' if not specified otherwise.
$node->build_mode = isset($node->build_mode) ? $node->build_mode : NODE_BUILD_NORMAL;
// One-field equivalent to _content_field_invoke('sanitize').
$field_types = _content_field_types();
$module = $field_types[$field['type']]['module'];
$function = $module .'_field';
if (function_exists($function)) {
$function('sanitize', $node, $field, $items, $teaser, $page);
$node->$field['field_name'] = $items;
}
$view = content_field('view', $node, $field, $items, $teaser, $page);
// content_field('view') adds a wrapper to handle variables and 'excluded'
// fields for node templates. We bypass it and render the actual field.
$output = drupal_render($view[$field['field_name']]['field']);
}
return $output;
}
Yves Chedemois
committed
/**
Darrel O'Pry
committed
* Implementation of hook_nodeapi 'alter' op.
Yves Chedemois
committed
*
* Add back the formatted values in the 'view' element for all fields,
* so that node templates can use it.
*/
function content_alter(&$node, $teaser = FALSE, $page = FALSE) {
_content_field_invoke_default('alter', $node, $teaser, $page);
}
Darrel O'Pry
committed
* Implementation of hook_nodeapi 'prepare translation' op.
*
* Generate field render arrays.
*/
Yves Chedemois
committed
$default_additions = _content_field_invoke_default('prepare translation', $node);
$additions = _content_field_invoke('prepare translation', $node);
Yves Chedemois
committed
// Merge module additions after the default ones to enable overriding
// of field values.
Yves Chedemois
committed
$node = (object) array_merge((array) $node, $default_additions, $additions);
Yves Chedemois
committed
/**
* Implementation of hook_nodeapi().
*/
Darrel O'Pry
committed
function content_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
Yves Chedemois
committed
// Prevent against invalid 'nodes' built by broken 3rd party code.
Yves Chedemois
committed
if (isset($node->type)) {
$type = content_types($node->type);
Yves Chedemois
committed
// Save cycles if the type has no CCK fields.
if (!empty($type['fields'])) {
$callback = 'content_'. str_replace(' ', '_', $op);
if (function_exists($callback)) {
$callback($node, $a3, $a4);
}
Karen Stevenson
committed
}
Yves Chedemois
committed
// Special case for 'view' op, we want to adjust weights of non-cck fields
// even if there are no actual fields for this type.
if ($op == 'view') {
foreach ($type['extra'] as $key => $value) {
// Some core 'fields' use a different key in node forms and in 'view'
// render arrays.
if (isset($value['view']) && isset($node->content[$value['view']])) {
$node->content[$value['view']]['#weight'] = $value['weight'];
}
elseif (isset($node->content[$key])) {
$node->content[$key]['#weight'] = $value['weight'];
}
}
}
Karen Stevenson
committed
}
}
/**
* Implementation of hook_form_alter().
*/
function content_form_alter(&$form, $form_state, $form_id) {
if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
Karen Stevenson
committed
$type = content_types($form['#node']->type);
if (!empty($type['fields'])) {
module_load_include('inc', 'content', 'includes/content.node_form');
// Merge field widgets.
$form = array_merge($form, content_form($form, $form_state));
Yves Chedemois
committed
}
// Adjust weights for non-CCK fields.
foreach ($type['extra'] as $key => $value) {
if (isset($form[$key])) {
$form[$key]['#weight'] = $value['weight'];
Karen Stevenson
committed
}
/**
* Proxy function to call content_add_more_submit(), because it might not be
* included yet when the form is processed and invokes the callback.
*/
function content_add_more_submit_proxy($form, &$form_state) {
module_load_include('inc', 'content', 'includes/content.node_form');
content_add_more_submit($form, $form_state);
}
/**
* Theme an individual form element.
*
* Combine multiple values into a table with drag-n-drop reordering.
*/
function theme_content_multiple_values($element) {
$field_name = $element['#field_name'];
$field = content_fields($field_name);
$output = '';
if ($field['multiple'] >= 1) {
$table_id = $element['#field_name'] .'_values';
$order_class = $element['#field_name'] .'-delta-order';
$required = !empty($element['#required']) ? '<span class="form-required" title="'. t('This field is required.') .'">*</span>' : '';
$header = array(
array(
Yves Chedemois
committed
'data' => t('!title: !required', array('!title' => $element['#title'], '!required' => $required)),
'colspan' => 2
),
t('Order'),
);
$rows = array();
Yves Chedemois
committed
// Sort items according to '_weight' (needed when the form comes back after
// preview or failed validation)
$items = array();
foreach (element_children($element) as $key) {
if ($key !== $element['#field_name'] .'_add_more') {
Yves Chedemois
committed
$items[] = &$element[$key];
}
}
Yves Chedemois
committed
usort($items, '_content_sort_items_value_helper');
// Add the items as table rows.
foreach ($items as $key => $item) {
$item['_weight']['#attributes']['class'] = $order_class;
$delta_element = drupal_render($item['_weight']);
$cells = array(
array('data' => '', 'class' => 'content-multiple-drag'),
drupal_render($item),
array('data' => $delta_element, 'class' => 'delta-order'),
);
$rows[] = array(
'data' => $cells,
'class' => 'draggable',
);
}
$output .= theme('table', $header, $rows, array('id' => $table_id, 'class' => 'content-multiple-table'));
$output .= $element['#description'] ? '<div class="description">'. $element['#description'] .'</div>' : '';
$output .= drupal_render($element[$element['#field_name'] .'_add_more']);
drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class);
}
else {
foreach (element_children($element) as $key) {
$output .= drupal_render($element[$key]);
}
}
return $output;
}
Karen Stevenson
committed
/**
* Modules notify Content module when uninstalled, disabled, etc.
*
* @param string $op
* the module operation: uninstall, install, enable, disable
* @param string $module
* the name of the affected module.
* @TODO
* figure out exactly what needs to be done by content module when
* field modules are installed, uninstalled, enabled or disabled.
*/
function content_notify($op, $module) {
switch ($op) {
case 'install':
Karen Stevenson
committed
content_clear_type_cache();
Karen Stevenson
committed
break;
case 'uninstall':
Karen Stevenson
committed
module_load_include('inc', 'content', 'includes/content.crud');
Karen Stevenson
committed
content_module_delete($module);
Karen Stevenson
committed
break;
case 'enable':
Karen Stevenson
committed
content_associate_fields($module);
Karen Stevenson
committed
content_clear_type_cache();
Karen Stevenson
committed
break;
case 'disable':
Karen Stevenson
committed
// When CCK modules are disabled before content module's update is run
// to add the active column, we can't do this.
Karen Stevenson
committed
if (variable_get('content_schema_version', -1) < 6007) {
Karen Stevenson
committed
return FALSE;
}
Karen Stevenson
committed
db_query("UPDATE {". content_field_tablename() ."} SET active=0 WHERE module='%s'", $module);
db_query("UPDATE {". content_instance_tablename() ."} SET widget_active=0 WHERE widget_module='%s'", $module);
Karen Stevenson
committed
content_clear_type_cache(TRUE);
Karen Stevenson
committed
break;
}
Karen Stevenson
committed
}
/**
* Allows a module to update the database for fields and columns it controls.
*
* @param string $module
* The name of the module to update on.
*/
Karen Stevenson
committed
function content_associate_fields($module) {
Karen Stevenson
committed
// When CCK modules are enabled before content module's update is run,
Karen Stevenson
committed
// to add module and active columns, we can't do this.
Karen Stevenson
committed
if (variable_get('content_schema_version', -1) < 6007) {
Karen Stevenson
committed
return FALSE;
}
Karen Stevenson
committed
$module_fields = module_invoke($module, 'field_info');
if ($module_fields) {
foreach ($module_fields as $name => $field_info) {
watchdog('content', 'Updating field type %type with module %module.', array('%type' => $name, '%module' => $module));
Karen Stevenson
committed
db_query("UPDATE {". content_field_tablename() ."} SET module = '%s', active = %d WHERE type = '%s'", $module, 1, $name);
Karen Stevenson
committed
}
}
$module_widgets = module_invoke($module, 'widget_info');
if ($module_widgets) {
foreach ($module_widgets as $name => $widget_info) {
watchdog('content', 'Updating widget type %type with module %module.', array('%type' => $name, '%module' => $module));
Karen Stevenson
committed
db_query("UPDATE {". content_instance_tablename() ."} SET widget_module = '%s', widget_active = %d WHERE widget_type = '%s'", $module, 1, $name);
Karen Stevenson
committed
}
}
// This is called from updates and installs, so get the install-safe
// version of a fields array.
$fields_set = array();
module_load_include('install', 'content');
$types = content_types_install();
Karen Stevenson
committed
foreach ($types as $type_name => $fields) {
foreach ($fields as $field) {
if ($field['module'] == $module && !in_array($field['field_name'], $fields_set)) {
$columns = module_invoke($field['module'], 'field_settings', 'database columns', $field);
db_query("UPDATE {". content_field_tablename() ."} SET db_columns = '%s' WHERE field_name = '%s'", serialize($columns), $field['field_name']);
Karen Stevenson
committed
$fields_set[] = $field['field_name'];
}
}
}
Karen Stevenson
committed
}
Jonathan Chaffer
committed
/**
* Implementation of hook_field(). Handles common field housekeeping.
*
* This implementation is special, as content.module does not define any field
* types. Instead, this function gets called after the type-specific hook, and
*
* Db-storage ops ('load', 'insert', 'update', 'delete', 'delete revisions')
* are not executed field by field, and are thus handled separately in
* content_storage.
*
* The 'view' operation constructs the $node in a way that you can use
* drupal_render() to display the formatted output for an individual field.
Yves Chedemois
committed
* i.e. print drupal_render($node->countent['field_foo']);
*
* The code now supports both single value formatters, which theme an
* individual item value as has been done in previous version of CCK,
* and multiple value formatters, which theme all values for the field
* in a single theme. The multiple value formatters could be used, for
* instance, to plot field values on a single map or display them
* in a graph. Single value formatters are the default, multiple value
* formatters can be designated as such in formatter_info().
*
* The node array will look like:
Yves Chedemois
committed
* $node->content['field_foo']['wrapper'] = array(
* '#type' => 'content_field',
* '#title' => 'label'
* '#field_name' => 'field_name',
* '#node' => $node,
Yves Chedemois
committed
* // Value of the $teaser param of hook_nodeapi('view').
* '#teaser' => $teaser,
* // Value of the $page param of hook_nodeapi('view').
* '#page' => $page,
* // The curent rendering context ('teaser', 'full', NODE_BUILD_SEARCH_INDEX...).
* '#context' => $context,
* 'items' =>
* 0 => array(
* '#item' => $items[0],
* // Only for 'single-value' formatters
* '#theme' => $theme,
* '#field_name' => 'field_name',
* '#type_name' => $node->type,
* '#formatter' => $formatter_name,
* '#node' => $node,
* '#delta' => 0,
* ),
* 1 => array(
* '#item' => $items[1],
* // Only for 'single-value' formatters
* '#theme' => $theme,
* '#field_name' => 'field_name',
* '#type_name' => $node->type,
* '#formatter' => $formatter_name,
* '#node' => $node,
* '#delta' => 1,
* // Only for 'multiple-value' formatters
* '#theme' => $theme,
* '#field_name' => 'field_name',
* '#type_name' => $node->type,
* '#formatter' => $formatter_name,
* ),
* );
Jonathan Chaffer
committed
*/
function content_field($op, &$node, $field, &$items, $teaser, $page) {
// TODO: here we could validate that the number of multiple data is correct ?
// We're controlling the number of fields to fill out and saving empty
// ones if a specified number is requested, so no reason to do any validation
// here right now, but if later create a method to indicate whether
// 'required' means all values must be filled out, we can come back
// here and check that they're not empty.
Yves Chedemois
committed
case 'presave':
if (!empty($node->devel_generate)) {
Karen Stevenson
committed
include_once('./'. drupal_get_path('module', 'content') .'/includes/content.devel.inc');
content_generate_fields($node, $field);
$items = $node->{$field['field_name']};
Karen Stevenson
committed
}
Yves Chedemois
committed
// Manual node_save calls might not have all fields filled in.
// On node insert, we need to make sure all tables get at least an empty
// record, or subsequent edits, using drupal_write_record() in update mode,
// won't insert any data.
// Missing fields on node update are handled in content_storage().
if (empty($items) && !isset($node->nid)) {
foreach (array_keys($field['columns']) as $column) {
$items[0][$column] = NULL;
}
$node->$field['field_name'] = $items;
}
Karen Stevenson
committed
// If there was an AHAH add more button in this field, don't save it.
// TODO: is it still needed ?
unset($items[$field['field_name'] .'_add_more']);
Karen Stevenson
committed
Karen Stevenson
committed
if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
// Reorder items to account for drag-n-drop reordering.
$items = _content_sort_items($field, $items);
// Filter out empty values.
$items = content_set_empty($field, $items);
break;
Yves Chedemois
committed
case 'view':
Yves Chedemois
committed
$addition = array();
Yves Chedemois
committed
// Previewed nodes bypass the 'presave' op, so we need to some massaging.
if ($node->build_mode == NODE_BUILD_PREVIEW && content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) {
// Reorder items to account for drag-n-drop reordering.
$items = _content_sort_items($field, $items);
}
// Filter out empty values.
$items = content_set_empty($field, $items);
// NODE_BUILD_NORMAL is 0, and ('whatever' == 0) is TRUE, so we need a ===.
if ($node->build_mode === NODE_BUILD_NORMAL || $node->build_mode == NODE_BUILD_PREVIEW) {
Yves Chedemois
committed
$context = $teaser ? 'teaser' : 'full';
}
else {
$context = $node->build_mode;
}
Karen Stevenson
committed
// The field may be missing info for $contexts added by modules
// enabled after the field was last edited.
$formatter_name = isset($field['display_settings'][$context]) && isset($field['display_settings'][$context]['format']) ? $field['display_settings'][$context]['format'] : 'default';
if ($formatter = _content_get_formatter($formatter_name, $field['type'])) {
$theme = $formatter['module'] .'_formatter_'. $formatter_name;
Yves Chedemois
committed
$single = (content_handle('formatter', 'multiple values', $formatter) == CONTENT_HANDLE_CORE);
Yves Chedemois
committed
$label_display = isset($field['display_settings']['label']['format']) ? $field['display_settings']['label']['format'] : 'above';
// Do not include field labels when indexing content.
if ($context == NODE_BUILD_SEARCH_INDEX) {
$label_display = 'hidden';
}
Yves Chedemois
committed
$element = array(
'#type' => 'content_field',
Yves Chedemois
committed
'#title' => check_plain(t($field['widget']['label'])),
Yves Chedemois
committed
'#field_name' => $field['field_name'],
'#access' => $formatter_name != 'hidden' && content_access('view', $field),
Yves Chedemois
committed
'#label_display' => $label_display,
Yves Chedemois
committed
'#node' => $node,
'#teaser' => $teaser,
'#page' => $page,
Yves Chedemois
committed
'#single' => $single,
'items' => array(),
);
// Fill-in items.
foreach ($items as $delta => $item) {
$element['items'][$delta] = array(
'#item' => $item,
'#weight' => $delta,
);
}
// Append formatter information either on each item ('single-value' formatter)
// or at the upper 'items' level ('multiple-value' formatter)
$format_info = array(
'#theme' => $theme,
'#field_name' => $field['field_name'],
'#type_name' => $node->type,
'#formatter' => $formatter_name,
Karen Stevenson
committed
'#node' => $node,
Yves Chedemois
committed
);
if ($single) {
foreach ($items as $delta => $item) {
$element['items'][$delta] += $format_info;
Karen Stevenson
committed
$element['items'][$delta]['#item']['#delta'] = $delta;
Yves Chedemois
committed
}
}
else {
$element['items'] += $format_info;
}
Yves Chedemois
committed
// The wrapper lets us get the themed output for the whole field
Karen Stevenson
committed
// to populate the $FIELD_NAME_rendered variable for node templates,
// and hide it from the $content variable if needed.
// See 'preprocess_node' op and theme_content_field_wrapper()?
Yves Chedemois
committed
$wrapper = array(
'field' => $element,
Yves Chedemois
committed
'#weight' => $field['widget']['weight'],
Karen Stevenson
committed
'#post_render' => array('content_field_wrapper_post_render'),
'#field_name' => $field['field_name'],
'#type_name' => $node->type,
'#context' => $context,
Yves Chedemois
committed
);
$addition = array($field['field_name'] => $wrapper);
Yves Chedemois
committed
}
Yves Chedemois
committed
return $addition;
Yves Chedemois
committed
case 'alter':
// Add back the formatted values in the 'view' element,
// so that tokens and node templates can use it.
// Note: Doing this in 'preprocess_node' breaks token integration.
Yves Chedemois
committed
// The location of the field's rendered output depends on whether the
// field is in a fieldgroup or not.
$wrapper = NULL;
Yves Chedemois
committed
if (isset($node->content[$field['field_name']])) {
Yves Chedemois
committed
$wrapper = $node->content[$field['field_name']];
}
elseif (module_exists('fieldgroup') && ($group_name = fieldgroup_get_group($node->type, $field['field_name'])) && isset($node->content[$group_name]['group'][$field['field_name']])) {
Yves Chedemois
committed
$wrapper = $node->content[$group_name]['group'][$field['field_name']];
}
if ($wrapper) {
$element = $wrapper['field'];
// '#single' is not set if the field is hidden or inaccessible.
if (isset($element['#single'])) {
if (!empty($element['#single'])) {
// Single value formatter.
foreach (element_children($element['items']) as $delta) {
// '#chilren' is not set if the field is empty.
$items[$delta]['view'] = isset($element['items'][$delta]['#children']) ? $element['items'][$delta]['#children'] : '';
}
}
elseif (isset($element['items']['#children'])) {
// Multiple values formatter.
$items[0]['view'] = $element['items']['#children'];
Yves Chedemois
committed
}
}
else {
// Hidden or inaccessible field.
$items[0]['view'] = '';
}
}
break;
case 'preprocess_node':
// Add $FIELD_NAME_rendered variables.
$addition = array();
// The location of the field's rendered output depends on whether the
// field is in a fieldgroup or not.
$wrapper = NULL;
if (isset($node->content[$field['field_name']])) {
$wrapper = $node->content[$field['field_name']];
}
elseif (module_exists('fieldgroup') && ($group_name = fieldgroup_get_group($node->type, $field['field_name'])) && isset($node->content[$group_name]['group'][$field['field_name']])) {
$wrapper = $node->content[$group_name]['group'][$field['field_name']];
}
if ($wrapper) {
// '#chilren' is not set if the field is empty.
$addition[$field['field_name'] .'_rendered'] = isset($wrapper['#children']) ? $wrapper['#children'] : '';
Yves Chedemois
committed
}
Yves Chedemois
committed
return $addition;
Yves Chedemois
committed
case 'prepare translation':
$addition = array();
if (isset($node->translation_source->$field['field_name'])) {
$addition[$field['field_name']] = $node->translation_source->$field['field_name'];
}
return $addition;
Karen Stevenson
committed
/**
* Helper function to filter out empty values.
Karen Stevenson
committed
*
* On order to keep marker rows in the database, the function ensures
* that the right number of 'all columns NULL' values is kept.
Karen Stevenson
committed
*
* @param array $field
* @param array $items
* @return array
* returns filtered and adjusted item array
Karen Stevenson
committed
*/
function content_set_empty($field, $items) {
// Filter out empty values.
$filtered = array();
$function = $field['module'] .'_content_is_empty';
Karen Stevenson
committed
foreach ((array) $items as $delta => $item) {
if (!$function($item, $field)) {
$filtered[] = $item;
Karen Stevenson
committed
}
}
// Make sure we store the right number of 'empty' values.
$empty = array();
foreach (array_keys($field['columns']) as $column) {
$empty[$column] = NULL;
}
$pad = $field['multiple'] > 1 ? $field['multiple'] : 1;
$filtered = array_pad($filtered, $pad, $empty);
return $filtered;
Karen Stevenson
committed
}
/**
* Helper function to sort items in a field according to
* user drag-n-drop reordering.
*/
function _content_sort_items($field, $items) {
if ($field['multiple'] >= 1 && isset($items[0]['_weight'])) {
usort($items, '_content_sort_items_helper');
foreach ($items as $delta => $item) {
Yves Chedemois
committed
if (is_array($items[$delta])) {
unset($items[$delta]['_weight']);
}
}
}
return $items;
}
/**
* Sort function for items order.
* (copied form element_sort(), which acts on #weight keys)
*/
function _content_sort_items_helper($a, $b) {
$a_weight = (is_array($a) && isset($a['_weight'])) ? $a['_weight'] : 0;
$b_weight = (is_array($b) && isset($b['_weight'])) ? $b['_weight'] : 0;
if ($a_weight == $b_weight) {
return 0;
}
return ($a_weight < $b_weight) ? -1 : 1;
}
Yves Chedemois
committed
/**
* Same as above, using ['_weight']['#value']
*/
function _content_sort_items_value_helper($a, $b) {
$a_weight = (is_array($a) && isset($a['_weight']['#value'])) ? $a['_weight']['#value'] : 0;
$b_weight = (is_array($b) && isset($b['_weight']['#value'])) ? $b['_weight']['#value'] : 0;
if ($a_weight == $b_weight) {
return 0;
}
return ($a_weight < $b_weight) ? -1 : 1;
}
* Handle storage ops for _content_field_invoke_default().
Karen Stevenson
committed
// Don't try this before content module's update is run to add
// the active and module columns.
Karen Stevenson
committed
if (variable_get('content_schema_version', -1) < 6007) {
Karen Stevenson
committed
return FALSE;
}
$type_name = $node->type;
$type = content_types($type_name);
Jonathan Chaffer
committed
switch ($op) {
case 'load':
// OPTIMIZE: load all non multiple fields in a single JOIN query ?
// warning: 61-join limit in MySQL ?
$additions = array();
// For each table used by this content type,
foreach ($type['tables'] as $table) {
$schema = drupal_get_schema($table);
// The per-type table might not have any fields actually stored in it.
if (!$schema['content fields']) {
continue;
}
$query = 'SELECT * FROM {'. $table .'} WHERE vid = %d';
// If we're loading a table for a multiple field,
// we fetch all rows (values) ordered by delta,
// else we only fetch one row.
$result = isset($schema['fields']['delta']) ? db_query($query .' ORDER BY delta', $node->vid) : db_query_range($query, $node->vid, 0, 1);
// For each table row, populate the fields.
while ($row = db_fetch_array($result)) {
// For each field stored in the table, add the field item.
foreach ($schema['content fields'] as $field_name) {
$item = array();
$field = content_fields($field_name, $type_name);
Karen Stevenson
committed
$db_info = content_database_info($field);
// For each column declared by the field, populate the item.
Karen Stevenson
committed
foreach ($db_info['columns'] as $column => $attributes) {