'admin/content/types/fields', 'title' => t('Fields'), 'callback' => '_content_admin_type_fields', 'access' => $access, 'type' => MENU_LOCAL_TASK, ); } else { if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'types') { $content_type = content_types(arg(3)); $type = node_get_types('types', $content_type['type']); if (!empty($type) && arg(3) && arg(3) == $content_type['url_str']) { $items[] = array( 'path' => 'admin/content/types/'. $content_type['url_str'] .'/edit', 'title' => t('Edit'), 'callback' => 'drupal_get_form', 'callback arguments' => array('node_type_form', $type), 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items[] = array( 'path' => 'admin/content/types/'. $content_type['url_str'] .'/fields', 'title' => t('Manage fields'), 'callback' => 'drupal_get_form', 'access' => $access, 'callback arguments' => array('content_admin_field_overview_form', $content_type['type']), 'type' => MENU_LOCAL_TASK, 'weight' => 1, ); $items[] = array( 'path' => 'admin/content/types/'. $content_type['url_str'] .'/add_field', 'title' => t('Add field'), 'callback' => '_content_admin_field_add', 'access' => $access, 'callback arguments' => array($content_type['type']), 'type' => MENU_LOCAL_TASK, 'weight' => 2, ); if (arg(4) == 'fields' && arg(5) && isset($content_type['fields'][arg(5)])) { $items[] = array( 'path' => 'admin/content/types/'. $content_type['url_str'] .'/fields/'. arg(5), 'title' => t($content_type['fields'][arg(5)]['widget']['label']), 'callback' => 'drupal_get_form', 'access' => $access, 'callback arguments' => array('_content_admin_field', $content_type['type'], arg(5)), 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/content/types/'. $content_type['url_str'] .'/fields/'. arg(5) .'/remove', 'title' => t('Remove field'), 'callback' => 'drupal_get_form', 'access' => $access, 'callback arguments' => array('_content_admin_field_remove', $content_type['type'], arg(5)), 'type' => MENU_CALLBACK, ); } } } } return $items; } /** * Load data for a node type's fields. * * 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. */ function content_load($node) { $cid = 'content:'. $node->nid .':'. $node->vid; if ($cached = cache_get($cid, 'cache')) { return unserialize($cached->data); } else { $default_additions = _content_field_invoke_default('load', $node); if ($default_additions) { foreach ($default_additions as $key => $value) { $node->$key = $value; } } $additions = _content_field_invoke('load', $node); if ($additions) { foreach ($additions as $key => $value) { $default_additions[$key] = $value; } } cache_set($cid, 'cache', serialize($default_additions), CACHE_PERMANENT); return $default_additions; } } /** * Create fields' form for a content type. * * Each field defines its own component of the content entry form, via its * chosen widget. */ function content_form(&$node) { $form = array(); $type = content_types($node->type); // Set form parameters so we can accept file uploads. if (count($type['fields'])) { $form['#attributes'] = array("enctype" => "multipart/form-data"); } _content_widget_invoke('prepare form values', $node); $form = array_merge($form, _content_widget_invoke('form', $node)); return $form; } /** * Validate form callback to handle node type fields. * * Both widgets and fields have a chance to raise error flags when a node is * being validated. */ function content_validate(&$node) { _content_widget_invoke('validate', $node); _content_widget_invoke('process form values', $node); _content_field_invoke('validate', $node); _content_field_invoke_default('validate', $node); } /** * Submit form callback for node type fields. * * At submit time, the widget does whatever data massaging is necessary so that * the field has the content in the expected format and can commit the changes * to the database. */ function content_submit(&$node) { _content_widget_invoke('submit', $node); _content_widget_invoke('process form values', $node); _content_field_invoke('submit', $node); _content_field_invoke_default('submit', $node); } /** * Insert node type fields. */ function content_insert(&$node) { _content_field_invoke('insert', $node); _content_field_invoke_default('insert', $node); } /** * Update node type fields. */ function content_update(&$node) { _content_field_invoke('update', $node); _content_field_invoke_default('update', $node); cache_clear_all('content:'. $node->nid .':'. $node->vid, 'cache'); } /** * Delete node type fields. */ function content_delete(&$node) { $type = content_types($node->type); if (!empty($type['fields'])) { _content_field_invoke('delete', $node); _content_field_invoke_default('delete', $node); } if (!empty($type['table']) && db_table_exists($type['table'])) { db_query('DELETE FROM {'. $type['table'] .'} WHERE nid = %d', $node->nid); } cache_clear_all('content:'. $node->nid, 'cache', TRUE); } /** * delete node type fields for a revision. */ function content_delete_revision(&$node) { $type = content_types($node->type); if (!empty($type['fields'])) { _content_field_invoke('delete revision', $node); _content_field_invoke_default('delete revision', $node); } if (!empty($type['table']) && db_table_exists($type['table'])) { db_query('DELETE FROM {'. $type['table'] .'} WHERE vid = %d', $node->vid); } cache_clear_all('content:'. $node->nid .':'. $node->vid, 'cache'); } /** * Generate field render arrays. */ function content_view(&$node, $teaser = FALSE, $page = FALSE) { if ($node->in_preview) { _content_widget_invoke('process form values', $node); } $content = _content_field_invoke('view', $node, $teaser, $page); $node->content = array_merge((array) $node->content, $content); } /** * Implementation of hook_nodeapi(). * * When a revision is deleted, make sure the appropriate cache item is cleared. * @todo: deprecate op==validate & op==submit in favor of form callbacks. */ function content_nodeapi(&$node, $op, $teaser, $page) { switch ($op) { case 'load': return content_load($node); case 'validate': content_validate($node); break; case 'submit': content_submit($node); break; case 'insert': content_insert($node); break; case 'update': content_update($node); break; case 'delete': content_delete($node); break; case 'delete revision': content_delete_revision($node); break; case 'view': content_view($node, $teaser, $page); break; } } /** * Implementation of hook_form_alter(). */ function content_form_alter($form_id, &$form) { if (isset($form['type'])) { $node = $form['#node']; if ($form['type']['#value'] .'_node_form' == $form_id) { $form = array_merge($form, content_form($node)); } } if ($form_id == 'system_modules') { $form['#submit']['content_system_modules_submit'] = array(); } } /** * Make sure that CCK content type info is synched with node type data * any time the content module is submitted on the module installation page. */ function content_system_modules_submit($form_id, $form_values) { $content_status = $form_values['validation_modules']['content']; if ($content_status->status == 1) { include_once('./'. drupal_get_path('module', 'content') .'/content_admin.inc'); include_once('./'. drupal_get_path('module', 'content') .'/content_crud.inc'); content_types_rebuild(); } } /** * 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 * takes care of the database interface for field types that do not choose to * do their own storage. */ function content_field($op, &$node, $field, &$node_field, $teaser, $page) { $db_info = content_database_info($field); switch ($op) { case 'load': $column_names = array(); foreach ($db_info['columns'] as $column => $attributes) { $column_names[] = $attributes['column'] .' AS '. $column; } if ($field['multiple']) { $result = db_query('SELECT '. implode(', ', $column_names) .' FROM {'. $db_info['table'] .'} WHERE vid = %d ORDER BY delta', $node->vid); $values = array(); while ($value = db_fetch_array($result)) { $values[] = $value; } $additions = array($field['field_name'] => $values); } else { $result = db_query('SELECT '. implode(', ', $column_names) .' FROM {'. $db_info['table'] .'} WHERE vid = %d', $node->vid); $additions = array($field['field_name'] => array(db_fetch_array($result))); } return $additions; case 'insert': foreach ($node_field as $delta => $item) { $data = array(); $column_names = array(); $column_placeholders = array(); $column_assignments = array(); foreach ($db_info['columns'] as $column => $attributes) { $column_names[] = $attributes['column']; if ($item[$column] == '' && !$attributes['not null'] && !$field['required']) { $column_placeholders[] = '%s'; $column_assignments[] = $attributes['column'] .' = %s'; $item[$column] = 'NULL'; } else { switch ($attributes['type']) { case 'int': case 'mediumint': case 'tinyint': case 'bigint': $column_placeholders[] = '%d'; $column_assignments[] = $attributes['column'] .' = %d'; break; case 'float': $column_placeholders[] = '%f'; $column_assignments[] = $attributes['column'] .' = %f'; break; default: $column_placeholders[] = "'%s'"; $column_assignments[] = $attributes['column'] ." = '%s'"; } } $data[] = $item[$column]; } $data[] = $node->vid; $data[] = $node->nid; if ($field['multiple']) { $data[] = $delta; } if ($field['multiple']) { db_query('INSERT INTO {'. $db_info['table'] .'} ('. implode(', ', $column_names) .', vid, nid, delta) VALUES ('. implode(', ', $column_placeholders) .', %d, %d, %d)', $data); } else { if (db_result(db_query('SELECT COUNT(*) FROM {'. $db_info['table'] .'} WHERE vid = %d AND nid = %d', $node->vid, $node->nid))) { db_query('UPDATE {'. $db_info['table'] .'} SET '. implode(', ', $column_assignments) .' WHERE vid = %d AND nid = %d', $data); } else { db_query('INSERT INTO {'. $db_info['table'] .'} ('. implode(', ', $column_names) .', vid, nid) VALUES ('. implode(', ', $column_placeholders) .', %d, %d)', $data); } } } return; case 'update': if ($field['multiple']) { // Delete and insert, rather than update, in case a field was added. db_query('DELETE FROM {'. $db_info['table'] .'} WHERE vid = %d', $node->vid); } foreach ($node_field as $delta => $item) { $data = array(); $column_names = array(); $column_placeholders = array(); $column_assignments = array(); foreach ($db_info['columns'] as $column => $attributes) { $column_names[] = $attributes['column']; if ($item[$column] == '' && !$attributes['not null'] && !$field['required']) { $column_placeholders[] = '%s'; $column_assignments[] = $attributes['column'] .' = %s'; $item[$column] = 'NULL'; } else { switch ($attributes['type']) { case 'int': case 'mediumint': case 'tinyint': case 'bigint': $column_placeholders[] = '%d'; $column_assignments[] = $attributes['column'] .' = %d'; break; case 'float': $column_placeholders[] = '%f'; $column_assignments[] = $attributes['column'] .' = %f'; break; default: $column_placeholders[] = "'%s'"; $column_assignments[] = $attributes['column'] ." = '%s'"; } } $data[] = $item[$column]; } $data[] = $node->vid; $data[] = $node->nid; if ($field['multiple']) { $data[] = $delta; } if ($field['multiple']) { db_query('INSERT INTO {'. $db_info['table'] .'} ('. implode(', ', $column_names) .', vid, nid, delta) VALUES ('. implode(', ', $column_placeholders) .', %d, %d, %d)', $data); } else { if (db_result(db_query('SELECT COUNT(*) FROM {'. $db_info['table'] .'} WHERE vid = %d AND nid = %d', $node->vid, $node->nid))) { db_query('UPDATE {'. $db_info['table'] .'} SET '. implode(', ', $column_assignments) .' WHERE vid = %d AND nid = %d', $data); } else { db_query('INSERT INTO {'. $db_info['table'] .'} ('. implode(', ', $column_names) .', vid, nid) VALUES ('. implode(', ', $column_placeholders) .', %d, %d)', $data); } } } return; case 'delete': // Delete using nid rather than vid to purge all revisions. db_query('DELETE FROM {'. $db_info['table'] .'} WHERE nid = %d', $node->nid); return; case 'delete revision': db_query('DELETE FROM {'. $db_info['table'] .'} WHERE vid = %d', $node->vid); return; } } /** * Invoke a field hook. * * For each operation, both this function and _content_field_invoke_default() are * called so that the default database handling can occur. */ function _content_field_invoke($op, &$node, $teaser = NULL, $page = NULL) { $type_name = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type); $type = content_types($type_name); $field_types = _content_field_types(); $return = array(); if (count($type['fields'])) { foreach ($type['fields'] as $field) { $node_field = isset($node->$field['field_name']) ? $node->$field['field_name'] : array(); $module = $field_types[$field['type']]['module']; $function = $module .'_field'; if (function_exists($function)) { $result = $function($op, $node, $field, $node_field, $teaser, $page); if ($op == 'view') { if (!is_array($result)) $result = array($result); $return[$field['field_name']] = array('#weight' => $field['widget']['weight']); foreach ($result as $delta => $value) { $return[$field['field_name']][$delta] = array('#value' => $value); } } elseif (is_array($result)) { $return = array_merge($return, $result); } else if (isset($result)) { $return[] = $result; } } // test for values in $node_field in case modules added items on insert if (isset($node->$field['field_name']) || count($node_field)) { $node->$field['field_name'] = $node_field; } } } return $return; } /** * Invoke content.module's version of a field hook. */ function _content_field_invoke_default($op, &$node, $teaser = NULL, $page = NULL) { $type_name = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type); $type = content_types($type_name); $field_types = _content_field_types(); $return = array(); if (count($type['fields'])) { foreach ($type['fields'] as $field) { $node_field = isset($node->$field['field_name']) ? $node->$field['field_name'] : array(); $db_info = content_database_info($field); if (count($db_info['columns'])) { $result = content_field($op, $node, $field, $node_field, $teaser, $page); if ($op == 'view') { // do nothing, handled by field view hook return; } elseif (is_array($result)) { $return = array_merge($return, $result); } else if (isset($result)) { $return[] = $result; } } if (isset($node->$field['field_name'])) { $node->$field['field_name'] = $node_field; } } } return $return; } /** * Invoke a widget hook. */ function _content_widget_invoke($op, &$node) { $type_name = is_string($node) ? $node : (is_array($node) ? $node['type'] : $node->type); $type = content_types($type_name); $widget_types = _content_widget_types(); $return = array(); if (count($type['fields'])) { foreach ($type['fields'] as $field) { $node_field = isset($node->$field['field_name']) ? $node->$field['field_name'] : array(); $module = $widget_types[$field['widget']['type']]['module']; $function = $module .'_widget'; if (function_exists($function)) { $result = $function($op, $node, $field, $node_field); if (is_array($result) && $op == 'form') { $result[$field['field_name']]['#weight'] = $field['widget']['weight']; } if (is_array($result)) { $return = array_merge($return, $result); } else if (isset($result)) { $return[] = $result; } } // test for values in $node_field in case modules added items on insert if (is_object($node) && (isset($node->$field['field_name']) || count($node_field))) { $node->$field['field_name'] = $node_field; } } } return $return; } /** * Return a list of all content types. * * @param $content_type_name * If set, return information on just this type. */ function content_types($type_name = NULL) { // handle type name with either an underscore or a dash $type_name = !empty($type_name) ? str_replace('-', '_', $type_name) : NULL; $info = _content_type_info(); if (isset($type_name)) { if (isset($info['content types'][$type_name])) { return $info['content types'][$type_name]; } else { return NULL; } } return $info['content types']; } /** * Return a list of all fields. * * @param $field_name * If set, return information on just this field. * @param $content_type_name * If set, return information of the field within the context of this content * type. */ function content_fields($field_name = NULL, $content_type_name = NULL) { $info = _content_type_info(); if (isset($field_name)) { if (isset($info['fields'][$field_name])) { if (isset($content_type_name)) { if (isset($info['content types'][$content_type_name]['fields'][$field_name])) { return $info['content types'][$content_type_name]['fields'][$field_name]; } else { return NULL; } } else { return $info['fields'][$field_name]; } } else { return NULL; } } return $info['fields']; } /** * Return a list of field types. */ function _content_field_types() { $info = _content_type_info(); return $info['field types']; } /** * Return a list of widget types. */ function _content_widget_types() { $info = _content_type_info(); return $info['widget types']; } /** * Collate all information on content types, fields, and related structures. * * @param $reset * If TRUE, clear the cache and fetch the information from the database again. */ function _content_type_info($reset = FALSE) { static $info; if ($reset || !isset($info)) { if ($cached = cache_get('content_type_info', 'cache')) { $info = unserialize($cached->data); } else { $info = array( 'field types' => array(), 'widget types' => array(), 'fields' => array(), 'content types' => array(), ); foreach (module_list() as $module) { $module_field_types = module_invoke($module, 'field_info'); if ($module_field_types) { foreach ($module_field_types as $name => $field_info) { $info['field types'][$name] = $field_info; $info['field types'][$name]['module'] = $module; $info['field types'][$name]['formatters'] = array(); } } $module_widgets = module_invoke($module, 'widget_info'); if ($module_widgets) { foreach ($module_widgets as $name => $widget_info) { $info['widget types'][$name] = $widget_info; $info['widget types'][$name]['module'] = $module; } } } foreach (module_list() as $module) { $module_formatters = module_invoke($module, 'field_formatter_info'); if ($module_formatters) { foreach ($module_formatters as $name => $formatter_info) { foreach ($formatter_info['field types'] as $field_type) { $info['field types'][$field_type]['formatters'][$name] = $formatter_info; $info['field types'][$field_type]['formatters'][$name]['module'] = $module; } } } } $field_result = db_query('SELECT * FROM {node_field} nf'); while ($field = db_fetch_array($field_result)) { $global_settings = $field['global_settings'] ? unserialize($field['global_settings']) : array(); unset($field['global_settings']); // Preventative error handling for PHP5 if field nodule hasn't created an arrray. if (is_array($global_settings)) { $field = array_merge($field, $global_settings); } $instance_info = db_fetch_array(db_query("SELECT type_name, label FROM {node_field_instance} WHERE field_name = '%s'", $field['field_name'])); $field['widget']['label'] = $instance_info['label']; $field['type_name'] = $instance_info['type_name']; $info['fields'][$field['field_name']] = $field; } $type_result = db_query('SELECT * FROM {node_type} nt ORDER BY nt.type ASC'); while ($type = db_fetch_array($type_result)) { $type['url_str'] = str_replace('_', '-', $type['type']); $type['fields'] = array(); $type['table'] = _content_tablename($type['type']); $field_result = db_query("SELECT nfi.field_name, nfi.weight, nfi.label, nfi.widget_type, nfi.widget_settings, nfi.description FROM {node_field_instance} nfi WHERE nfi.type_name = '%s' ORDER BY nfi.weight ASC, nfi.label ASC", $type['type']); while ($field = db_fetch_array($field_result)) { // Overwrite global field information with specific information $field = array_merge($info['fields'][$field['field_name']], $field); $widget_settings = $field['widget_settings'] ? unserialize($field['widget_settings']) : array(); unset($field['widget_settings']); $field['widget'] = $widget_settings; $field['widget']['type'] = $field['widget_type']; unset($field['widget_type']); $field['widget']['weight'] = $field['weight']; unset($field['weight']); $field['widget']['label'] = $field['label']; unset($field['label']); $field['widget']['description'] = $field['description']; unset($field['description']); $field['type_name'] = $type['type']; $type['fields'][$field['field_name']] = $field; } $info['content types'][$type['type']] = $type; } cache_set('content_type_info', 'cache', serialize($info), CACHE_PERMANENT); } } return $info; } /** * Implementation of hook_node_type() * React to change in node types */ function content_node_type($op, $info) { switch ($op) { case 'insert': include_once('./'. drupal_get_path('module', 'content') .'/content_crud.inc'); content_type_create($info); break; case 'update': include_once('./'. drupal_get_path('module', 'content') .'/content_crud.inc'); content_type_update($info); break; case 'delete': include_once('./'. drupal_get_path('module', 'content') .'/content_crud.inc'); content_type_delete($info); break; } } /** * Clear the cache of content_types; called in several places when content * information is changed. */ function content_clear_type_cache() { cache_clear_all('content_type_info', 'cache'); _content_type_info(TRUE); if (module_exists('views')) { views_invalidate_cache(); } } /** * Retrieve the database storage location(s) for a field. * * @param $field * The field whose database information is requested. * @return * An array with the keys: * "table": The name of the database table where the field data is stored. * "columns": An array of columns stored for this field. Each is a collection * of information returned from hook_field_settings('database columns'), * with the addition of a "column" attribute which holds the name of the * database column that stores the data. */ function content_database_info($field) { $field_types = _content_field_types(); $module = $field_types[$field['type']]['module']; $columns = module_invoke($module, 'field_settings', 'database columns', $field); $db_info = array(); if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) { $db_info['table'] = _content_tablename($field['field_name'], 'field'); } else { $type = content_types($field['type_name']); $db_info['table'] = $type['table']; } if (is_array($columns) && count($columns)) { $db_info['columns'] = $columns; foreach ($columns as $column_name => $attributes) { $db_info['columns'][$column_name]['column'] = $field['field_name'] .'_'. $column_name; } } else { $db_info['columns'] = array(); } return $db_info; } /** * Manipulate a 2D array to reverse rows and columns. * * The default data storage for fields is delta first, column names second. * This is sometimes inconvenient for field modules, so this function can be * used to present the data in an alternate format. * * @param $array * The array to be transposed. It must be at least two-dimensional, and * the subarrays must all have the same keys or behavior is undefined. * * @return * The transposed array. */ function content_transpose_array_rows_cols($array) { $result = array(); if (is_array($array)) { foreach ($array as $key1 => $value1) { if (is_array($value1)) { foreach ($value1 as $key2 => $value2) { if (!isset($result[$key2])) { $result[$key2] = array(); } $result[$key2][$key1] = $value2; } } } } return $result; } /** * Format a field item for display. * * @param $field * Either a field array or the name of the field. * @param $item * The field item to be formatted (such as $node->field_foo[0]). * @param $formatter * The name of the formatter to use. * @param $node * Optionally, the containing node object for context purposes. * * @return * A string containing the contents of the field item sanitized for display. * It will have been passed through the necessary check_plain() or check_markup() * functions as necessary. */ function content_format($field, $item, $formatter = 'default', $node = NULL) { if (!is_array($field)) { $field = content_fields($field); } $field_types = _content_field_types(); $formatters = $field_types[$field['type']]['formatters']; if (!isset($formatter, $formatters)) { $formatter = 'default'; } return module_invoke($formatters[$formatter]['module'], 'field_formatter', $field, $item, $formatter, $node); } /** * Format an individual field for display. * * @param $node * The node being displayed (provided for context). * @param $field * The field that is to be displayed (for information about the label, field * type, and so forth). * @param $items * The actual items to theme. This will be a linear array, each element of * which has a "view" property which contains the filtered, formatted contents * of the item. * @param $teaser * Whether the node is being displayed as a teaser or full version. * @param $page * Whether the node is being displayed as a full web page. * * @return * An HTML string containing the fully themed field. */ function theme_field(&$node, &$field, &$items, $teaser, $page) { $items_output = ''; foreach ($items as $item) { if (!empty($item['view']) || $item['view'] === "0") { $items_output .= '