diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/includes/database.mysql.inc ./includes/database.mysql.inc --- ../head/includes/database.mysql.inc 2008-05-27 11:26:13.000000000 -0400 +++ ./includes/database.mysql.inc 2008-05-30 13:24:15.000000000 -0400 @@ -140,9 +140,9 @@ function _db_query($query, $debug = 0) { * An object representing the next row of the result, or FALSE. The attributes * of this object are the table fields selected by the query. */ -function db_fetch_object($result) { +function db_fetch_object($result, $class = 'stdClass') { if ($result) { - return mysql_fetch_object($result); + return mysql_fetch_object($result, $class); } } diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/includes/database.mysqli.inc ./includes/database.mysqli.inc --- ../head/includes/database.mysqli.inc 2008-05-27 11:26:13.000000000 -0400 +++ ./includes/database.mysqli.inc 2008-05-30 13:33:14.000000000 -0400 @@ -141,9 +141,9 @@ function _db_query($query, $debug = 0) { * An object representing the next row of the result, or FALSE. The attributes * of this object are the table fields selected by the query. */ -function db_fetch_object($result) { +function db_fetch_object($result, $class = 'stdClass') { if ($result) { - $object = mysqli_fetch_object($result); + $object = mysqli_fetch_object($result, $class); return isset($object) ? $object : FALSE; } } diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/includes/database.pgsql.inc ./includes/database.pgsql.inc --- ../head/includes/database.pgsql.inc 2008-05-27 11:26:13.000000000 -0400 +++ ./includes/database.pgsql.inc 2008-05-30 17:37:27.000000000 -0400 @@ -170,9 +170,9 @@ function _db_query($query, $debug = 0) { * An object representing the next row of the result, or FALSE. The attributes * of this object are the table fields selected by the query. */ -function db_fetch_object($result) { +function db_fetch_object($result, $class = 'stdClass') { if ($result) { - return pg_fetch_object($result); + return pg_fetch_object($result, NULL, $class); } } diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/content.info ./modules/fields/content.info --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/content.info 2008-05-31 11:52:48.000000000 -0400 @@ -0,0 +1,7 @@ +name = Fields +description = Fields in core. Whatever that means! +package = Core - fields +core = 7.x +files[] = content.module +files[] = includes/content.crud.inc + diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/content.install ./modules/fields/content.install --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/content.install 2008-05-31 11:52:48.000000000 -0400 @@ -0,0 +1,300 @@ + $types) { + foreach ($types as $type_name => $type) { + if ($field['type'] == $type_name) { + $field['module'] = $module; + } + } + } + foreach ($module_widgets as $module => $types) { + foreach ($types as $type_name => $type) { + if ($field['widget_type'] == $type_name) { + $field['widget_module'] = $module; + } + } + } + if (!empty($field['module']) && !empty($field['widget_module'])) { + $field['widget_settings'] = unserialize($field['widget_settings']); + $field['display_settings'] = unserialize($field['display_settings']); + $field['columns'] = module_invoke($field['module'], 'field_settings', 'database columns', $field); + $fields[$field['type_name']][$field['field_name']] = $field; + } + } + return $fields; +} + +/** + * Implementation of hook_install(). + */ +function content_install() { + variable_set('content_schema_version', 6002); + drupal_install_schema('content'); +} + + +/** + * Implementation of hook_uninstall(). + */ +function content_uninstall() { + variable_del('content_schema_version'); + drupal_uninstall_schema('content'); +} + +/** + * Implementation of hook_enable(). + */ +function content_enable() { + // Make sure old data is emptied out of the caches, since it + // may no longer be valid since the module was last enabled, + // especially if not all the same field modules are enabled + // as before. Especially needed during updates. + cache_clear_all('*', 'cache_content', TRUE); + content_clear_type_cache(TRUE); +} + +/** + * Implementation of hook_disable(). + */ +function content_disable() { + // Make sure old data is emptied out of the caches, since it + // may no longer be valid when the module is re-enabled. + cache_clear_all('*', 'cache_content', TRUE); + content_clear_type_cache(TRUE); +} + +/** + * Implementation of hook_schema. + */ +function content_schema() { + + // Static (meta) tables. + + $schema['content_node_field'] = array( + 'fields' => array( + 'field_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'type' => array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''), + 'global_settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE), + 'required' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0), + 'multiple' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0), + 'shareable' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0), + 'db_storage' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 1), + 'module' => array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''), + 'db_columns' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE), + 'active' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0), + ), + 'primary key' => array('field_name'), + ); + $schema['content_node_field_instance'] = array( + 'fields' => array( + 'field_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'type_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), + 'label' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''), + 'widget_type' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''), + 'widget_settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE), + 'display_settings' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE, 'serialize' => TRUE), + 'description' => array('type' => 'text', 'size' => 'medium', 'not null' => TRUE), + 'widget_module' => array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => ''), + 'widget_active' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0), + ), + 'primary key' => array('field_name', 'type_name'), + ); + $schema['cache_content'] = drupal_get_schema_unprocessed('system', 'cache'); + + // When the module is first installed, the remaining code in the schema + // will create errors, since these tables have not yet been created. + // We don't need to create data tables on initial installation anyway + // since no fields have been created yet, so just return with this much + // of the schema. + + if (!db_table_exists('content_node_field') || !db_table_exists('content_node_field_instance')) { + return $schema; + } + + // Dynamic (data) tables. + + // We can't use many helper functions here, like content_fields() or + // content_types() or we risk creating a fatal loop from circular + // logic when they call other functions that use this schema, so create + // the schema directly from a fresh query of the database. + + // content_table_schema() and content_database_info() have no + // circular logic and are safe to use here. + + $db_result = db_query("SELECT * FROM {". content_instance_tablename() ."} nfi ". + " LEFT JOIN {". content_field_tablename() ."} nf ON nf.field_name = nfi.field_name WHERE nf.active = 1 AND nfi.widget_active = 1"); + while ($field = db_fetch_array($db_result)) { + // 'columns' is a reserved word in MySQL4, so our db column is named 'db_columns'. + $field['columns'] = unserialize($field['db_columns']); + unset($field['db_columns']); + + $content_table = _content_tablename($field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE); + $field_table = _content_tablename($field['field_name'], CONTENT_DB_STORAGE_PER_FIELD); + + + // We always add a 'per content type' table for each content type that + // has fields. + if (!isset($schema[$content_table])) { + $schema[$content_table] = content_table_schema(); + } + + $base_schema = content_table_schema($field); + if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) { + // Per-field storage : add the 'per field' table if needed. + if (!isset($schema[$field_table])) { + $schema[$field_table] = $base_schema; + } + } + else { + // Per-type storage : merge the information for the field + // in the existing table. + $schema[$content_table]['fields'] = array_merge($schema[$content_table]['fields'], $base_schema['fields']); + $schema[$content_table]['content fields'] = array_merge($schema[$content_table]['content fields'], $base_schema['content fields']); + } + } + return $schema; +} + +function content_update_last_removed() { + return 1008; +} + +/** + * Add module name to fields table to make it easier to identify the fields to delete when a module + * is uninstalled. + * + * Needed because the value drops out of content_info() when module is disabled, so there + * is no other way to find the associated fields. + */ +function content_update_6000() { + $ret = array(); + if (db_column_exists(content_field_tablename(), 'active')) { + return $ret; + } + db_add_field($ret, content_field_tablename(), 'module', array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => '')); + db_add_field($ret, content_field_tablename(), 'db_columns', array('type' => 'text', 'not null' => TRUE, 'default' => '')); + db_add_field($ret, content_field_tablename(), 'active', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0)); + db_add_field($ret, content_instance_tablename(), 'widget_module', array('type' => 'varchar', 'length' => 127, 'not null' => TRUE, 'default' => '')); + db_add_field($ret, content_instance_tablename(), 'widget_active', array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0)); + + // This will update the table for any modules enabled at this time. + foreach (module_list() as $module) { + content_associate_fields($module); + } + + // Fix the cache_content schema + if (db_table_exists('cache_content')) { + db_drop_table($ret, 'cache_content'); + } + db_create_table($ret, 'cache_content', drupal_get_schema_unprocessed('system', 'cache')); + variable_set('content_schema_version', 6000); + + // The cache table had to be used to store data until this update ran, + // so clear cache out now that we're switching back to the cache_content table. + $ret[] = update_sql('DELETE FROM {cache}'); + + return $ret; +} + +/** + * Rename node_field and node_field_instance tables. + * + * This is a carryover from when the data tables were renamed, + * postponed so we wouldn't create any more havoc than necessary + * until a major version change. + * + * Using 'content_node_field' instead of 'content_field' + * to avoid conflicts with field tables that will be prefixed + * with 'content_field'. + */ +function content_update_6001() { + $ret = array(); + if (db_table_exists('content_node_field')) { + return $ret; + } + db_rename_table($ret, 'node_field', 'content_node_field'); + db_rename_table($ret, 'node_field_instance', 'content_node_field_instance'); + variable_set('content_schema_version', 6001); + content_clear_type_cache(TRUE); + return $ret; +} + +/** + * Get rid of automatic per content tables for content types that have no fields. + * Switching to adding those tables only when needed. + */ +function content_update_6002() { + $ret = array(); + + $db_types = content_types_install(); + $field_types = array(); + + $result = db_query("SELECT DISTINCT type_name FROM {". content_instance_tablename() ."}"); + while ($type = db_fetch_array($result)) { + $field_types[] = $type['type_name']; + } + + foreach ($db_types as $content_type => $content_info) { + if (!in_array($content_type, $field_types)) { + $table = _content_tablename($content_type, CONTENT_DB_STORAGE_PER_CONTENT_TYPE); + if (db_table_exists($table)) { + db_drop_table($ret, $table); + } + } + } + variable_set('content_schema_version', 6002); + content_clear_type_cache(TRUE); + return $ret; +} + +/** + * 'db_columns' column 1st got introduced as 'columns', which is forbidden in MySQL 4. + * This update function will only be useful for early testers... + */ +function content_update_6003() { + $ret = array(); + if (db_column_exists('content_node_field', 'columns')) { + db_change_field($ret, 'content_node_field', 'columns', 'db_columns', array('type' => 'text', 'size' => 'medium', 'not null' => TRUE)); + } + return $ret; +} \ No newline at end of file diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/content.module ./modules/fields/content.module --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/content.module 2008-05-31 11:52:48.000000000 -0400 @@ -0,0 +1,1780 @@ + array( + 'template' => 'field', + 'arguments' => array('element' => NULL), + 'path' => $path, + ), + 'content_view_multiple_field' => array( + 'arguments' => array('items' => NULL, 'field' => NULL, 'data' => NULL), + ), + 'content_multiple_values' => array( + 'arguments' => array('element' => NULL), + ), + ); +} + +/** + * 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, content_cache_tablename())) { + return $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) { + $node->$key = $value; + $default_additions[$key] = $value; + } + } + cache_set($cid, $default_additions, content_cache_tablename()); + return $default_additions; + } +} + +/** + * Nodeapi 'validate' op. + * + */ +function content_validate(&$node) { + _content_field_invoke('validate', $node); + _content_field_invoke_default('validate', $node); +} + +/** + * Nodeapi 'presave' op. + * + */ +function content_presave(&$node) { + _content_field_invoke('presave', $node); + _content_field_invoke_default('presave', $node); +} + +/** + * Nodeapi 'insert' op. + * + * Insert node type fields. + */ +function content_insert(&$node) { + _content_field_invoke('insert', $node); + _content_field_invoke_default('insert', $node); +} + +/** + * Nodeapi 'update' op. + * + * 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, content_cache_tablename()); +} + +/** + * Nodeapi 'delete' op. + * + * 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); + } + $table = _content_tablename($type['type'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE); + if (db_table_exists($table)) { + db_query('DELETE FROM {'. $table .'} WHERE nid = %d', $node->nid); + } + cache_clear_all('content:'. $node->nid, content_cache_tablename(), TRUE); +} + +/** + * Nodeapi 'delete_revision' op. + * + * 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); + } + $table = _content_tablename($type['type'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE); + if (db_table_exists($table)) { + db_query('DELETE FROM {'. $table .'} WHERE vid = %d', $node->vid); + } + cache_clear_all('content:'. $node->nid .':'. $node->vid, content_cache_tablename()); +} + +/** + * Nodeapi 'view' op. + * + * 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); + + // Merge fields. + $additions = _content_field_invoke_default('view', $node, $teaser, $page); + $node->content = array_merge((array) $node->content, $additions); + + // Adjust weights for non-CCK fields. + $type = content_types($node->type); + 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']; + } + } +} + +/** + * Nodeapi 'alter' op. + * + * 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); +} + +/** + * Nodeapi 'prepare translation' op. + * + * Generate field render arrays. + */ +function content_prepare_translation(&$node) { + $additions = _content_field_invoke_default('prepare translation', $node); + $node = (object) array_merge((array) $node, $additions); +} + +/** + * Implementation of hook_nodeapi(). + */ +function content_nodeapi(&$node, $op, $teaser, $page) { + switch ($op) { + case 'load': + return content_load($node); + + case 'validate': + content_validate($node); + break; + + case 'presave': + content_presave($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; + + case 'alter': + content_alter($node, $teaser, $page); + break; + + case 'prepare translation': + content_prepare_translation($node); + break; + + } +} + +/** + * 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) { + module_load_include('inc', 'content', 'includes/content.node_form'); + // Merge field widgets. + $form = array_merge($form, content_form($form, $form_state)); + // Adjust weights for non-CCK fields. + $type = content_types($form['type']['#value']); + foreach ($type['extra'] as $key => $value) { + if (isset($form[$key])) { + $form[$key]['#weight'] = $value['weight']; + // Special case for the 'menu path' fieldset : we keep it + // just below the title. + if ($key == 'title' && isset($form['menu'])) { + $form['menu']['#weight'] = $value['weight'] + .1; + } + } + } + } +} + +/** + * 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']) ? '*' : ''; + + $header = array( + array( + 'data' => t('!title: !required', array('!title' => filter_xss_admin($element['#title']), '!required' => $required)), + 'colspan' => 2 + ), + t('Order'), + ); + $rows = array(); + + foreach (element_children($element) as $key) { + if ($key !== $element['#field_name'] .'_add_more') { + $element[$key]['_weight']['#attributes']['class'] = $order_class; + $delta_element = drupal_render($element[$key]['_weight']); + $cells = array( + array('data' => '', 'class' => 'content-multiple-drag'), + drupal_render($element[$key]), + 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'] ? '
'. $element['#description'] .'
' : ''; + $output .= drupal_render($element[$element['#field_name'] .'_add_more']); + + drupal_add_tabledrag($table_id, 'order', 'sibling', $order_class); + + // Change the button title to reflect the behavior when using JavaScript. + // TODO : this should be made a behavior, so it can be reattached when the + // form is AHAH-updated + $field_name_css = str_replace('_', '-', $field_name); + drupal_add_js('if (Drupal.jsEnabled) { $(document).ready(function() { $("#edit-'. $field_name_css .'-'. $field_name_css .'-add-more").val("'. t('Add another item') .'"); }); }', 'inline'); + } + else { + foreach (element_children($element) as $key) { + $output .= drupal_render($element[$key]); + } + } + + return $output; +} + +/** + * 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': + content_clear_type_cache(); + break; + case 'uninstall': + module_load_include('inc', 'content', 'includes/content.crud'); + content_module_delete($module); + content_clear_type_cache(TRUE); + break; + case 'enable': + content_associate_fields($module); + content_clear_type_cache(); + break; + case 'disable': + content_clear_type_cache(TRUE); + break; + } +} + +/** + * Allows a module to update the database for fields and columns it controls. + * + * @param string $module + * The name of the module to update on. + */ +function content_associate_fields($module) { + $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)); + db_query("UPDATE {". content_field_tablename() ."} SET module = '%s', active = %d WHERE type = '%s'", $module, 1, $name); + } + } + $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)); + db_query("UPDATE {". content_instance_tablename() ."} SET widget_module = '%s', widget_active = %d WHERE widget_type = '%s'", $module, 1, $name); + } + } + // 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(); + 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']); + $fields_set[] = $field['field_name']; + } + } + } +} + +/** + * 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 default stuff common to all field types. + * + * 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. + * i.e. print drupal_render($node->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: + * $node->content['field_foo'] = array( + * '#type' => 'content_field_view', + * '#title' => 'label' + * '#field_name' => 'field_name', + * '#node' => $node, + * 'items' => + * 0 => array( + * '#item' => $items[0], + * // Only for 'single-value' formatters + * '#theme' => $theme, + * '#field_name' => 'field_name', + * '#type_name' => $node->type, + * '#formatter' => $formatter_name, + * ), + * 1 => array( + * '#item' => $items[1], + * // Only for 'single-value' formatters + * '#theme' => $theme, + * '#field_name' => 'field_name', + * '#type_name' => $node->type, + * '#formatter' => $formatter_name, + * ), + * // Only for 'multiple-value' formatters + * '#theme' => $theme, + * '#field_name' => 'field_name', + * '#type_name' => $node->type, + * '#formatter' => $formatter_name, + * ), + * ); + */ +function content_field($op, &$node, $field, &$items, $teaser, $page) { + switch ($op) { + case 'validate': + // 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. + break; + + case 'presave': + if ($node->devel_generate) { + include_once('./'. drupal_get_path('module', 'content') .'/includes/content.devel.inc'); + content_generate_fields($node); + } + + // 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; + } + + // 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']); + + // If content module is handling multiple values, don't save empty items. + if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) { + // Filter out empty values. + $items = content_set_empty($field, $items); + // Reorder items to account for drag-n-drop reordering. + $items = _content_sort_items($field, $items); + } + break; + + case 'view': + $element = array(); + + if ($node->build_mode == NODE_BUILD_NORMAL) { + $context = $teaser ? 'teaser' : 'full'; + } + else { + $context = $node->build_mode; + } + // Do not include field labels when indexing content. + if ($context == NODE_BUILD_SEARCH_INDEX) { + $field['display_settings']['label']['format'] = 'hidden'; + } + + $field_types = _content_field_types(); + $formatters = $field_types[$field['type']]['formatters']; + $formatter_name = isset($field['display_settings'][$context]['format']) ? $field['display_settings'][$context]['format'] : 'default'; + + if (!isset($formatters[$formatter_name]) && $formatter_name != 'hidden') { + // This might happen when the selected formatter has been renamed in the + // module, or if the module has been disabled since then. + $formatter_name = 'default'; + } + + if (isset($formatters[$formatter_name])) { + $formatter = $formatters[$formatter_name]; + $theme = $formatter['module'] .'_formatter_'. $formatter_name; + $single = (content_handle('formatter', 'multiple values', $formatter) == CONTENT_HANDLE_CORE); + + $element = array( + '#type' => 'content_field_view', + '#title' => $field['widget']['label'], + '#weight' => $field['widget']['weight'], + '#field_name' => $field['field_name'], + '#access' => $formatter_name != 'hidden', + '#node' => $node, + '#teaser' => $teaser, + '#page' => $page, + '#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, + ); + if ($single) { + foreach ($items as $delta => $item) { + $element['items'][$delta] += $format_info; + } + } + else { + $element['items'] += $format_info; + } + + } + + return array($field['field_name'] => $element); + + case 'alter': + // Add back the formatted values in the 'view' element, + // so that node templates can use it. + if (isset($node->content[$field['field_name']])) { + $element = $node->content[$field['field_name']]; + if ($element['#single']) { + // Single value formatter. + foreach (element_children($element['items']) as $delta) { + // Use isset() to avoid undefined index message on #children when field values are empty. + $items[$delta]['view'] = isset($element['items'][$delta]['#children']) ? $element['items'][$delta]['#children'] : ''; + } + } + else { + // Multiple values formatter. + $items[0]['view'] = $element['items']['#children']; + } + } + else { + $items[0]['view'] = ''; + } + break; + + 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; + } +} + +/** + * Helper function to set NULL values and unset items where needed. + * + * If a specified number of multiple values was requested or this is + * the zero delta, save even if empty and keep a NULL value for each + * of its columns so there is a marker row in the database, otherwise + * unset the item. + * + * @param array $field + * @param array $items + * @return array + * returns emptied and adjusted item array + */ +function content_set_empty($field, $items) { + $function = $field['module'] .'_content_is_empty'; + $max_delta = $field['multiple'] > 1 ? $field['multiple'] : 0; + foreach ((array) $items as $delta => $item) { + if ($function($item, $field)) { + if ($delta <= $max_delta) { + foreach (array_keys($field['columns']) as $column) { + $items[$delta][$column] = NULL; + } + } + else { + unset($items[$delta]); + } + } + } + return $items; +} + +/** + * 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) { + 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; +} + +/** + * TODO : PHPdoc + */ +function content_storage($op, $node) { + $type_name = $node->type; + $type = content_types($type_name); + + 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); + $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); + $db_info = content_database_info($field); + // For each column declared by the field, populate the item. + foreach ($db_info['columns'] as $column => $attributes) { + $item[$column] = $row[$attributes['column']]; + } + + // Add the item to the field values for the node. + if (!isset($additions[$field_name])) { + $additions[$field_name] = array(); + } + $additions[$field_name][] = $item; + } + } + } + return $additions; + + case 'insert': + case 'update': + foreach ($type['tables'] as $table) { + $schema = drupal_get_schema($table); + $record = array(); + foreach ($schema['content fields'] as $field_name) { + if (isset($node->$field_name)) { + $field = content_fields($field_name, $type_name); + // Multiple fields need specific handling, we'll deal with them later on. + if ($field['multiple']) { + continue; + } + $db_info = content_database_info($field); + foreach ($db_info['columns'] as $column => $attributes) { + $record[$attributes['column']] = $node->{$field_name}[0][$column]; + } + } + } + if (count($record)) { + $record['nid'] = $node->nid; + $record['vid'] = $node->vid; + // Can't rely on the insert/update op of the node to decide if this + // is an insert or an update, a node or revision may have existed + // before any fields were created, so there may not be an entry here. + + // TODO - should we auto create an entry for all existing nodes when + // fields are added to content types -- either a NULL value + // or the default value? May need to offer the user an option of + // how to handle that. + if (db_result(db_query("SELECT COUNT(*) FROM {". $table ."} WHERE vid = %d", $node->vid))) { + content_write_record($table, $record, array('vid')); + } + else { + content_write_record($table, $record); + } + } + } + + // Handle multiple fields. + foreach ($type['fields'] as $field) { + if ($field['multiple'] && isset($node->$field['field_name'])) { + $db_info = content_database_info($field); + // Delete and insert, rather than update, in case a value was added. + if ($op == 'update') { + db_query('DELETE FROM {'. $db_info['table'] .'} WHERE vid = %d', $node->vid); + } + foreach ($node->$field['field_name'] as $delta => $item) { + $record = array(); + foreach ($db_info['columns'] as $column => $attributes) { + $record[$attributes['column']] = $item[$column]; + } + $record['nid'] = $node->nid; + $record['vid'] = $node->vid; + $record['delta'] = $delta; + content_write_record($db_info['table'], $record); + } + } + } + break; + + case 'delete': + foreach ($type['tables'] as $table) { + db_query('DELETE FROM {'. $table .'} WHERE nid = %d', $node->nid); + } + break; + + case 'delete revision': + foreach ($type['tables'] as $table) { + db_query('DELETE FROM {'. $table .'} WHERE vid = %d', $node->vid); + } + break; + } +} + +/** + * Save a record to the database based upon the schema. + * + * Directly copied from core's drupal_write_record, which can't update a + * column to NULL. + * + * Default values are filled in for missing items, and 'serial' (auto increment) + * types are filled in with IDs. + * + * @param $table + * The name of the table; this must exist in schema API. + * @param $object + * The object to write. This is a reference, as defaults according to + * the schema may be filled in on the object, as well as ID on the serial + * type(s). Both array an object types may be passed. + * @param $update + * If this is an update, specify the primary keys' field names. It is the + * caller's responsibility to know if a record for this object already + * exists in the database. If there is only 1 key, you may pass a simple string. + * @return + * Failure to write a record will return FALSE. Otherwise SAVED_NEW or + * SAVED_UPDATED is returned depending on the operation performed. The + * $object parameter contains values for any serial fields defined by + * the $table. For example, $object->nid will be populated after inserting + * a new node. + */ +function content_write_record($table, &$object, $update = array()) { + // Standardize $update to an array. + if (is_string($update)) { + $update = array($update); + } + + // Convert to an object if needed. + if (is_array($object)) { + $object = (object) $object; + $array = TRUE; + } + else { + $array = FALSE; + } + + $schema = drupal_get_schema($table); + if (empty($schema)) { + return FALSE; + } + + $fields = $defs = $values = $serials = $placeholders = array(); + + // Go through our schema, build SQL, and when inserting, fill in defaults for + // fields that are not set. + foreach ($schema['fields'] as $field => $info) { + // Special case -- skip serial types if we are updating. + if ($info['type'] == 'serial' && count($update)) { + continue; + } + + // For inserts, populate defaults from Schema if not already provided + if (!isset($object->$field) && !count($update) && isset($info['default'])) { + $object->$field = $info['default']; + } + + // Track serial fields so we can helpfully populate them after the query. + if ($info['type'] == 'serial') { + $serials[] = $field; + // Ignore values for serials when inserting data. Unsupported. + unset($object->$field); + } + + // Build arrays for the fields, placeholders, and values in our query. + if (isset($object->$field) || array_key_exists($field, $object)) { + $fields[] = $field; + if (isset($object->$field)) { + $placeholders[] = db_type_placeholder($info['type']); + + if (empty($info['serialize'])) { + $values[] = $object->$field; + } + else { + $values[] = serialize($object->$field); + } + } + else { + $placeholders[] = 'NULL'; + } + } + } + + // Build the SQL. + $query = ''; + if (!count($update)) { + $query = "INSERT INTO {". $table ."} (". implode(', ', $fields) .') VALUES ('. implode(', ', $placeholders) .')'; + $return = SAVED_NEW; + } + else { + $query = ''; + foreach ($fields as $id => $field) { + if ($query) { + $query .= ', '; + } + $query .= $field .' = '. $placeholders[$id]; + } + + foreach ($update as $key){ + $conditions[] = "$key = ". db_type_placeholder($schema['fields'][$key]['type']); + $values[] = $object->$key; + } + + $query = "UPDATE {". $table ."} SET $query WHERE ". implode(' AND ', $conditions); + $return = SAVED_UPDATED; + } + + // Execute the SQL. + if (db_query($query, $values)) { + if ($serials) { + // Get last insert ids and fill them in. + foreach ($serials as $field) { + $object->$field = db_last_insert_id($table, $field); + } + } + + // If we began with an array, convert back so we don't surprise the caller. + if ($array) { + $object = (array) $object; + } + + return $return; + } + + return FALSE; +} + +/** + * 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(); + foreach ($type['fields'] as $field) { + $items = isset($node->$field['field_name']) ? $node->$field['field_name'] : array(); + + // Make sure AHAH 'add more' button isn't sent to the fields for processing. + unset($items[$field['field_name'] .'_add_more']); + + $module = $field_types[$field['type']]['module']; + $function = $module .'_field'; + if (function_exists($function)) { + $result = $function($op, $node, $field, $items, $teaser, $page); + if (is_array($result)) { + $return = array_merge($return, $result); + } + else if (isset($result)) { + $return[] = $result; + } + } + // test for values in $items in case modules added items on insert + if (isset($node->$field['field_name']) || count($items)) { + $node->$field['field_name'] = $items; + } + } + 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(); + // The operations involving database queries are better off handled by table + // rather than by field. + if (in_array($op, array('load', 'insert', 'update', 'delete', 'delete revision'))) { + return content_storage($op, $node); + } + else { + foreach ($type['fields'] as $field) { + $items = isset($node->$field['field_name']) ? $node->$field['field_name'] : array(); + $result = content_field($op, $node, $field, $items, $teaser, $page); + if (is_array($result)) { + $return = array_merge($return, $result); + } + else if (isset($result)) { + $return[] = $result; + } + if (isset($node->$field['field_name'])) { + $node->$field['field_name'] = $items; + } + } + } + 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($info['content types'])) { + if (!isset($type_name)) { + return $info['content types']; + } + if (isset($info['content types'][$type_name])) { + return $info['content types'][$type_name]; + } + } +} + +/** + * 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($info['fields'])) { + if (!isset($field_name)) { + return $info['fields']; + } + if (isset($info['fields'][$field_name])) { + if (!isset($content_type_name)) { + return $info['fields'][$field_name]; + } + if (isset($info['content types'][$content_type_name]['fields'][$field_name])) { + return $info['content types'][$content_type_name]['fields'][$field_name]; + } + } + } +} + +/** + * Return a list of field types. + */ +function _content_field_types() { + $info = _content_type_info(); + return isset($info['field types']) ? $info['field types'] : array(); +} + +/** + * Return a list of widget types. + */ +function _content_widget_types() { + $info = _content_type_info(); + return isset($info['widget types']) ? $info['widget types'] : array(); +} + +/** + * 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)) { + // Make sure this function doesn't run until the tables have been created, + // for instance, when first enabled and called from content_menu(). + if (!db_table_exists(content_field_tablename()) || !db_table_exists(content_instance_tablename())) { + return array(); + } + + if ($cached = cache_get('content_type_info', content_cache_tablename())) { + $info = $cached->data; + } + else { + $info = array( + 'field types' => array(), + 'widget types' => array(), + 'fields' => array(), + 'content types' => array(), + ); + + // Populate field types. + 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) { + // Truncate names to match the value that is stored in the database. + $db_name = substr($name, 0, 32); + $info['field types'][$db_name] = $field_info; + $info['field types'][$db_name]['module'] = $module; + $info['field types'][$db_name]['formatters'] = array(); + } + } + } + + // Populate widget types and formatters for known field types. + foreach (module_list() as $module) { + if ($module_widgets = module_invoke($module, 'widget_info')) { + foreach ($module_widgets as $name => $widget_info) { + // Truncate names to match the value that is stored in the database. + $db_name = substr($name, 0, 32); + $info['widget types'][$db_name] = $widget_info; + $info['widget types'][$db_name]['module'] = $module; + // Replace field types with db_compatible version of known field types. + $info['widget types'][$db_name]['field types'] = array(); + foreach ($widget_info['field types'] as $field_type) { + $field_type_db_name = substr($field_type, 0, 32); + if (isset($info['field types'][$field_type_db_name])) { + $info['widget types'][$db_name]['field types'][] = $field_type_db_name; + } + } + } + } + + if ($module_formatters = module_invoke($module, 'field_formatter_info')) { + foreach ($module_formatters as $name => $formatter_info) { + foreach ($formatter_info['field types'] as $field_type) { + // Truncate names to match the value that is stored in the database. + $db_name = substr($field_type, 0, 32); + if (isset($info['field types'][$db_name])) { + $info['field types'][$db_name]['formatters'][$name] = $formatter_info; + $info['field types'][$db_name]['formatters'][$name]['module'] = $module; + } + } + } + } + } + + // Populate actual field instances. + module_load_include('inc', 'content', 'includes/content.crud'); + foreach (node_get_types() as $type_name => $data) { + $type = (array) $data; + $type['url_str'] = str_replace('_', '-', $type['type']); + $type['fields'] = array(); + $type['tables'] = array(); + $fields = content_field_instance_read(array('type_name' => $type_name)); + foreach ($fields as $field) { + $type['fields'][$field['field_name']] = $field; + $db_info = content_database_info($field); + $type['tables'][$db_info['table']] = $db_info['table']; + $info['fields'][$field['field_name']] = $field; + } + + // Gather information about non-CCK 'fields'. + $extra = module_invoke_all('content_extra_fields', $type_name); + drupal_alter('content_extra_fields', $extra, $type_name); + // Add saved weights. + foreach (variable_get('content_extra_weights_'. $type_name, array()) as $key => $value) { + // Some stored entries might not exist anymore, for instance if uploads + // have been disabled, or vocabularies removed... + if (isset($extra[$key])) { + $extra[$key]['weight'] = $value; + } + } + $type['extra'] = $extra; + + $info['content types'][$type_name] = $type; + } + + cache_set('content_type_info', $info, content_cache_tablename()); + } + } + return $info; +} + +/** + * Implementation of hook_node_type() + * React to change in node types + */ +function content_node_type($op, $info) { + switch ($op) { + case 'insert': + module_load_include('inc', 'content', 'includes/content.crud'); + content_type_create($info); + break; + case 'update': + module_load_include('inc', 'content', 'includes/content.crud'); + content_type_update($info); + break; + case 'delete': + module_load_include('inc', 'content', 'includes/content.crud'); + 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($rebuild_schema = FALSE) { + cache_clear_all('*', content_cache_tablename(), TRUE); + _content_type_info(TRUE); + + // Refresh the schema to pick up new information. + if ($rebuild_schema) { + $schema = drupal_get_schema(NULL, TRUE); + } + + if (module_exists('views')) { + // Needed because this can be called from .install files + module_load_include('module', 'views'); + views_invalidate_cache(); + } +} + +/** + * Retrieve the database storage location(s) for a field. + * + * TODO : add a word about why it's not included in the global _content_type_info array. + * + * @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) { + $db_info = array(); + + if ($field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) { + $db_info['table'] = _content_tablename($field['field_name'], CONTENT_DB_STORAGE_PER_FIELD); + } + else { + $db_info['table'] = _content_tablename($field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE); + } + + $db_info['columns'] = (array) $field['columns']; + // Generate column names for this field from generic column names. + foreach ($db_info['columns'] as $column_name => $attributes) { + $db_info['columns'][$column_name]['column'] = $field['field_name'] .'_'. $column_name; + } + + return $db_info; +} + +/** + * Helper function for identifying the storage type for a field. + * + * TODO: This should depend on 'shareable', not # of instances. + */ +function content_storage_type($field) { + if ($field['multiple'] > 0 || $field['shareable']) { + return CONTENT_DB_STORAGE_PER_FIELD; + } + return CONTENT_DB_STORAGE_PER_CONTENT_TYPE; +} + +/** + * 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; +} + +/** + * Create an array of the allowed values for this field. + * + * Used by number and text fields, expects to find either + * PHP code that will return the correct value, or a string + * with keys and labels separated with '|' and with each + * new value on its own line. + */ +function content_allowed_values($field) { + static $allowed_values; + + if (isset($allowed_values[$field['field_name']])) { + return $allowed_values[$field['field_name']]; + } + + $allowed_values[$field['field_name']] = array(); + + if (isset($field['allowed_values_php'])) { + ob_start(); + $result = eval($field['allowed_values_php']); + if (is_array($result)) { + $allowed_values[$field['field_name']] = $result; + } + ob_end_clean(); + } + + if (empty($allowed_values[$field['field_name']]) && isset($field['allowed_values'])) { + $list = explode("\n", $field['allowed_values']); + $list = array_map('trim', $list); + $list = array_filter($list, 'strlen'); + foreach ($list as $opt) { + if (strpos($opt, '|') !== FALSE) { + list($key, $value) = explode('|', $opt); + $allowed_values[$field['field_name']][$key] = $value ? $value : $key; + } + else { + $allowed_values[$field['field_name']][$opt] = $opt; + } + } + } + return $allowed_values[$field['field_name']]; +} + +/** + * Format a field item for display. + * + * Used to display a field's values outside the context of the $node, as + * when fields are displayed in Views, or to display a field in a template + * using a different formatter than the one set up on the Display Fields tab + * for the node's context. + * + * @param $field + * Either a field array or the name of the field. + * @param $item + * The field item(s) to be formatted (such as $node->field_foo[0], + * or $node->field_foo if the formatter handles multiple values itself) + * @param $formatter + * The name of the formatter to use. + * @param $node + * Optionally, the containing node object for context purposes and + * field-instance options. + * + * @return + * A string containing the contents of the field item(s) 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($formatters[$formatter])) { + $formatter = 'default'; + } + + $formatter_name = $formatter; + $formatter = $formatters[$formatter_name]; + $theme = $formatter['module'] .'_formatter_'. $formatter_name; + + $element = array( + '#theme' => $theme, + '#field_name' => $field['field_name'], + '#type_name' => isset($node->type) ? $node->type : '', + '#formatter' => $formatter_name, + ); + + if (content_handle('formatter', 'multiple values', $formatter) == CONTENT_HANDLE_CORE) { + // Single value formatter. + + // hook_field('sanitize') expects an array of items, so we build one. + $items = array($item); + $function = $field['module'] .'_field'; + if (function_exists($function)) { + $function('sanitize', $node, $field, $items, FALSE, FALSE); + } + + $element['#item'] = $items[0]; + } + else { + // Multiple values formatter. + $items = $item; + $function = $field['module'] .'_field'; + if (function_exists($function)) { + $function('sanitize', $node, $field, $items, FALSE, FALSE); + } + + foreach ($items as $delta => $item) { + $element[$delta] = array( + '#item' => $item, + '#weight' => $delta, + ); + } + } + + return theme($theme, $element); +} + +/** + * Array of possible display contexts for fields. + */ +function _content_admin_display_contexts($selector = CONTENT_CONTEXTS_ALL) { + $contexts = array(); + + if ($selector == CONTENT_CONTEXTS_ALL || $selector == CONTENT_CONTEXTS_SIMPLE) { + $contexts['teaser'] = t('Teaser'); + $contexts['full'] = t('Full node'); + } + + if ($selector == CONTENT_CONTEXTS_ALL || $selector == CONTENT_CONTEXTS_ADVANCED) { + $contexts[NODE_BUILD_RSS] = t('RSS Item'); + if (module_exists('search')) { + $contexts[NODE_BUILD_SEARCH_INDEX] = t('Search Index'); + $contexts[NODE_BUILD_SEARCH_RESULT] = t('Search Result'); + } + } + + return $contexts; +} + +/** + * Generate a table name for a field or a content type. + * + * @param $name + * The name of the content type or content field + * @param $storage + * CONTENT_DB_STORAGE_PER_FIELD or CONTENT_DB_STORAGE_PER_CONTENT_TYPE + * @return + * A string containing the generated name for the database table + */ +function _content_tablename($name, $storage, $version = NULL) { + if (is_null($version)) { + $version = variable_get('content_schema_version', 0); + } + + if ($version < 1003) { + $version = 0; + } + else { + $version = 1003; + } + + $name = str_replace('-', '_', $name); + switch ("$version-$storage") { + case '0-'. CONTENT_DB_STORAGE_PER_CONTENT_TYPE : + return "node_$name"; + case '0-'. CONTENT_DB_STORAGE_PER_FIELD : + return "node_data_$name"; + case '1003-'. CONTENT_DB_STORAGE_PER_CONTENT_TYPE : + return "content_type_$name"; + case '1003-'. CONTENT_DB_STORAGE_PER_FIELD : + return "content_$name"; + } +} + +/** + * Generate table name for the content field table. + * + * Needed because the table name changes depending on version. + * Using 'content_node_field' instead of 'content_field' + * to avoid conflicts with field tables that will be prefixed + * with 'content_field'. + */ +function content_field_tablename($version = NULL) { + if (is_null($version)) { + $version = variable_get('content_schema_version', 0); + } + return $version < 6001 ? 'node_field' : 'content_node_field'; +} + +/** + * Generate table name for the content field instance table. + * + * Needed because the table name changes depending on version. + */ +function content_instance_tablename($version = NULL) { + if (is_null($version)) { + $version = variable_get('content_schema_version', 0); + } + return $version < 6001 ? 'node_field_instance' : 'content_node_field_instance'; +} + +/** + * Generate table name for the content cache table. + * + * Needed because the table name changes depending on version. Because of + * a new database column, the content_cache table will be unusable until + * update 6000 runs, so the cache table will be used instead. + */ +function content_cache_tablename() { + if (variable_get('content_schema_version', -1) < 6000) { + return 'cache'; + } + else { + return 'cache_content'; + } +} + +/** + * A basic schema used by all field and type tables. + * + * This will only add the columns relevant for the specified field. + * Leave $field['columns'] empty to get only the base schema, + * otherwise the function will return the whole thing. + */ +function content_table_schema($field = NULL) { + $schema = array( + 'fields' => array( + 'vid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0), + 'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0) + ), + 'primary key' => array('vid'), + ); + + // Add delta column if needed. + if (!empty($field['multiple'])) { + $schema['fields']['delta'] = array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0); + $schema['primary key'][] = 'delta'; + } + $schema['content fields'] = array(); + + // Add field columns column if needed. + // This function is called from install files where it is not safe + // to use content_fields() or content_database_info(), so we + // just used the column values stored in the $field. + // We also need the schema to include fields from disabled modules + // or there will be no way to delete those fields. + + if (!empty($field['columns'])) { + foreach ($field['columns'] as $column => $attributes) { + $column_name = $field['field_name'] .'_'. $column; + unset($attributes['column']); + unset($attributes['sortable']); + $schema['fields'][$column_name] = $attributes; + } + $schema['content fields'][] = $field['field_name']; + } + return $schema; +} + +/** + * Helper function for determining the behavior of a field or a widget + * with respect to a given operation. (currently used for field 'view', + * and widget 'default values' and 'multiple values') + * + * @param $entity + * 'field' or 'widget' + * @param $op + * the name of the operation ('view', 'default value'...) + * @param $field + * The field array, including widget info. + * @return + * CONTENT_CALLBACK_NONE - do nothing for this operation + * CONTENT_CALLBACK_CUSTOM - use the module's callback function. + * CONTENT_CALLBACK_DEFAULT - use content module default behavior + * + */ +function content_callback($entity, $op, $field) { + switch ($entity) { + case 'field': + $info = module_invoke($field['module'], "field_info"); + return isset($info[$field['type']]['callbacks'][$op]) ? $info[$field['type']]['callbacks'][$op] : CONTENT_CALLBACK_DEFAULT; + + case 'widget': + $info = module_invoke($field['widget']['module'], "widget_info"); + return isset($info[$field['widget']['type']]['callbacks'][$op]) ? $info[$field['widget']['type']]['callbacks'][$op] : CONTENT_CALLBACK_DEFAULT; + } +} + +/** + * Helper function for determining the handling of a field, widget or + * formatter with respect to a given operation. + * + * Currently used for widgets and formatters 'multiple values'. + * + * @param $entity + * 'field', 'widget' or 'formatter' + * @param $op + * the name of the operation ('default values'...) + * @param $object + * - if $entity is 'field' or 'widget' : the field array, + * including widget info. + * - if $entity is 'formater' : the formatter array. + * @return + * CONTENT_HANDLE_CORE - the content module handles this operation. + * CONTENT_HANDLE_MODULE - the implementing module handles this operation. + */ +function content_handle($entity, $op, $object) { + switch ($entity) { + case 'field': + $info = module_invoke($object['module'], "field_info"); + return isset($info[$object['type']][$op]) ? $info[$object['type']][$op] : CONTENT_HANDLE_CORE; + + case 'widget': + $info = module_invoke($object['widget']['module'], "widget_info"); + return isset($info[$object['widget']['type']][$op]) ? $info[$object['widget']['type']][$op] : CONTENT_HANDLE_CORE; + + case 'formatter': + // Much simpler, formatters arrays *are* the 'formatter_info' itself. + // We let content_handle deal with them only for code consistency. + return isset($object[$op]) ? $object[$op] : CONTENT_HANDLE_CORE; + } +} + +/** + * Helper function to return the correct default value for a field. + * + * @param $node + * The node. + * @param $field + * The field array. + * @param $items + * The value of the field in the node. + * @return + * The default value for that field. + */ +function content_default_value(&$form, &$form_state, $field, $delta) { + $widget_types = _content_widget_types(); + $module = $widget_types[$field['widget']['type']]['module']; + + $default_value = array(); + if (!empty($field['widget']['default_value_php'])) { + ob_start(); + $result = eval($field['widget']['default_value_php']); + ob_end_clean(); + if (is_array($result)) { + $default_value = $result; + } + } + elseif (!empty($field['widget']['default_value'])) { + $default_value = $field['widget']['default_value']; + } + return (array) $default_value; +} + +/** + * 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_view' => array(), + ); +} + +/** + * Process variables for field.tpl.php. + * + * The $variables array contains the following arguments: + * - $node + * - $field + * - $items + * - $teaser + * - $page + * + * @see field.tpl.php + */ +function template_preprocess_content_field_view(&$variables) { + $element = $variables['element']; + $field = content_fields($element['#field_name'], $element['#node']->type); + + $variables['node'] = $element['#node']; + $variables['field'] = $field; + $variables['items'] = array(); + + if ($element['#single']) { + // Single value formatter. + foreach (element_children($element['items']) as $delta) { + $variables['items'][$delta] = $element['items'][$delta]['#item']; + // Use isset() to avoid undefined index message on #children when field values are empty. + $variables['items'][$delta]['view'] = isset($element['items'][$delta]['#children']) ? $element['items'][$delta]['#children'] : ''; + } + } + else { + // Multiple values formatter. + // We display the 'all items' output as $items[0], as if it was the + // output of a single valued field. + // Raw values are still exposed for all items. + foreach (element_children($element['items']) as $delta) { + $variables['items'][$delta] = $element['items'][$delta]['#item']; + } + $variables['items'][0]['view'] = $element['items']['#children']; + } + + $variables['teaser'] = $element['#teaser']; + $variables['page'] = $element['#page']; + + $field_empty = TRUE; + + foreach ($variables['items'] as $delta => $item) { + if (!isset($item['view']) || (empty($item['view']) && $item['view'] !== 0)) { + $variables['items'][$delta]['empty'] = TRUE; + } + else { + $field_empty = FALSE; + $variables['items'][$delta]['empty'] = FALSE; + } + } + + $additions = array( + 'field_type' => $field['type'], + 'field_name' => $field['field_name'], + 'field_type_css' => strtr($field['type'], '_', '-'), + 'field_name_css' => strtr($field['field_name'], '_', '-'), + 'label' => $field['widget']['label'], + 'label_display' => isset($field['display_settings']['label']['format']) ? $field['display_settings']['label']['format'] : 'above', + 'field_empty' => $field_empty, + ); + $variables = array_merge($variables, $additions); + +} + +/** + * Debugging using hook_content_fieldapi. + * + * @TODO remove later + * + * @param $op + * @param $field + */ +function content_content_fieldapi($op, $field) { + if (module_exists('devel')) { + //dsm($op); + //dsm($field); + } +} + +/** + * Implementation of hook_content_extra_fields. + * + * Informations for non-CCK 'node fields' defined in core. + */ +function content_content_extra_fields($type_name) { + $type = node_get_types('type', $type_name); + $extra = array(); + + if ($type->has_title) { + $extra['title'] = array('label' => $type->title_label, 'weight' => -5); + } + if ($type->has_body) { + $extra['body_field'] = array('label' => $type->body_label, 'weight' => 0, 'view' => 'body'); + } + if (module_exists('locale') && variable_get("language_$type_name", 0)) { + $extra['language'] = array('label' => t('Language'), 'weight' => 0); + } + if (module_exists('taxonomy') && taxonomy_get_vocabularies($type_name)) { + $extra['taxonomy'] = array('label' => t('Taxonomy'), 'weight' => -3); + } + if (module_exists('upload') && variable_get("upload_$type_name", TRUE)) { + $extra['attachments'] = array('label' => t('File attachments'), 'weight' => 30, 'view' => 'files'); + } + + return $extra; +} + +/** + * Retrieve the user-defined weight for non-CCK node 'fields'. + * + * CCK's 'Manage fields' page lets users reorder node fields, including non-CCK + * items (body, taxonomy, other hook_nodeapi-added elements by contrib modules...). + * Contrib modules that want to have their 'fields' supported need to expose + * them with hook_content_extra_fields, and use this function to retrieve the + * user-defined weight. + * + * @param $type_name + * The content type name. + * @param $pseudo_field_name + * The name of the 'field'. + * @return + * The weight for the 'field', respecting the user settings stored + * by content.module. + */ +function content_extra_field_weight($type_name, $pseudo_field_name) { + $type = content_types($type_name); + + // If we don't have the requested item, this may be because the cached + // information for 'extra' fields hasn't been refreshed yet. + if (!isset($type['extra'][$pseudo_field_name])) { + content_clear_type_cache(); + } + + if (isset($type['extra'][$pseudo_field_name])) { + return $type['extra'][$pseudo_field_name]['weight']; + } +} diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/content.test ./modules/fields/content.test --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/content.test 2008-05-31 11:58:04.000000000 -0400 @@ -0,0 +1,162 @@ + t('Field API'), + 'description' => t('Test Drupal\'s Field API'), + 'group' => t('Fields') + ); + } + + function setUp() { + parent::setUp(); + + $this->drupalModuleEnable('content'); + $this->drupalModuleEnable('text'); + } + + function testCreateField() { + // Create a field. + $field = new Field($this->randomName(10), 'text'); + $field_name = $field->field_name; + $this->assertIdentical(field_create_field($field), TRUE, 'Create field.'); + + // Read it back and verify. + $fields = field_get_field(array('field_name' => $field_name)); + $this->assertIdentical($fields[0]->field_name, $field_name, 'Field created'); + $this->assertIdentical($fields[0]->module, 'text', 'Module set correctly'); + $this->assertEqual($fields[0]->required, FALSE, 'required set correctly'); + $this->assertEqual($fields[0]->shareable, FALSE, 'shareable set correctly'); + $this->assertEqual($fields[0]->multiple, 0, 'multiple set correctly'); + $this->assertEqual($fields[0]->db_storage, CONTENT_DB_STORAGE_PER_CONTENT_TYPE, 'db_storage set correctly'); + + // Field names must be unique. + $this->assertIdentical(field_create_field($field), FALSE, 'Cannot create duplicate field name.'); + + // Create another field with non-default values. + $field_name = $field->field_name = $this->randomName(10); + $field->required = TRUE; + $field->shareable = TRUE; + $field->multiple = 1; + $this->assertIdentical(field_create_field($field), TRUE, 'Create field.'); + + // Read it back and verify. + $fields = field_get_field(array('field_name' => $field_name)); + $this->assertIdentical($fields[0]->field_name, $field_name, 'Field created'); + $this->assertIdentical($fields[0]->module, 'text', 'Module set correctly'); + $this->assertEqual($fields[0]->required, TRUE, 'required set correctly'); + $this->assertEqual($fields[0]->shareable, TRUE, 'shareable set correctly'); + $this->assertEqual($fields[0]->multiple, 1, 'multiple set correctly'); + $this->assertEqual($fields[0]->db_storage, CONTENT_DB_STORAGE_PER_FIELD, 'db_storage set correctly'); + } + + function testCreateFieldInstance() { + // Create a field. + $field = new Field('', 'text'); + $field_name = $field->field_name = $this->randomName(10); + $this->assertIdentical(field_create_field($field), TRUE, 'Create field.'); + + // Create a field instance structure. Set some non-default values, + // including the text widget's "rows" setting, so we can verify them. + $content_type = 'article'; + $instance = new FieldInstance($field_name, $content_type, 'text_textarea'); + $instance->widget['weight'] = 12; + $label = $instance->widget['label'] = 'label:'. $this->randomName(32); + $description = $instance->widget['description'] = 'description:'. $this->randomName(32); + $rows = $instance->widget['rows'] = 17; + + // Cannot create an instance on a non-existant content type. + $instance->type_name = 'this_content_type_does_not_exist'; + $this->assertEqual(field_create_instance($instance), FALSE, 'Cannot create an instance on a non-existant content type.'); + + // Create the instance. + $instance->type_name = $content_type; + $this->assertEqual(field_create_instance($instance), TRUE, 'Create instance.'); + + // Read it back and verify. + $read = $this->_readAndVerifyInstance($instance); + $this->assertEqual($read->widget['rows'], $rows, 'widget[rows] matches'); + + // TODO: display settings + + // The field instance's database columns exist. + foreach ($field->columns as $column => $attrs) { + $this->assertIdentical(db_column_exists('content_type_article', $field_name .'_'. $column), TRUE, "Field's column $column exists."); + } + + // The field can be saved and loaded. + $node->type = $content_type; + $node->title = 'title'; + $field_value[0]['value'] = $this->randomName(64); + $node->$field_name = $field_value; + node_save($node); + + $newnode = node_load($node->nid); + $newfield = $newnode->$field_name; + $this->assertIdentical($field_value[0]['value'], $newfield[0]['value'], 'New field is saved and loaded.'); + + // The field's label and value is displayed + $this->drupalGet('node/'.$node->nid); + $this->assertText($label, 'New field label is displayed.'); + $this->assertText($field_value[0]['value'], 'New field value is displayed.'); + + // Field is shown on form, using the widget settings. + $web_user = $this->drupalCreateUser(array('edit own article content', 'create article content')); + $this->drupalLogin($web_user); + $this->drupalGet('node/add/article'); + $this->assertText($description, 'New field description is shown on form.'); + $this->assertPattern('@]+rows="'. $rows .'"@', 'New text field has correct number of rows.'); + + // Field submission works. + $edit['title'] = $this->randomName(32); + $newvalue = $edit[$field_name .'[0][value]'] = $this->randomName(32); + $this->drupalPost('node/add/article', $edit, t('Save')); + $node = node_load(array('title' => $edit['title'])); + $newfield = $node->$field_name; + $this->assertIdentical($edit[$field_name .'[0][value]'], $newfield[0]['value'], 'Field value submitted via form loads'); + + // Cannot re-instantiate a non-shared field. + $instance->type = 'page'; + $this->assertEqual(field_create_instance($instance), FALSE, 'Cannot re-instantiate non-shared field.'); + + // Update the instance + $newinstance = field_get_instance(array('field_name' => $field_name, 'type_name' => $content_type)); + $this->assertIdentical(count($newinstance), 1, 'Red existing field instance.'); + $newinstance = $newinstance[0]; + $newlabel = $newinstance->widget['label'] = 'label:'. $this->randomName(32); + $newdescription = $newinstance->widget['description'] = 'description:'. $this->randomName(32); + $newrows = $newinstance->widget['rows'] = $rows*2; + $this->assertEqual(field_update_instance($newinstance), TRUE, 'Update field.'); + $read = $this->_readAndVerifyInstance($newinstance); + $this->assertEqual($read->widget['rows'], $newrows, 'Updated widget[rows] set correctly'); + + // Verify the new instance settings + $this->drupalGet('node/'.$node->nid); + $this->assertNoText($label, 'Old field label is not displayed.'); + $this->assertText($newlabel, 'New field label is displayed.'); + $this->assertText($newvalue, 'Field value is still displayed.'); + $this->drupalGet('node/add/article'); + $this->assertNoText($description, 'Old field description is not shown on form.'); + $this->assertText($newdescription, 'New field description is shown on form.'); + $this->assertNoPattern('@]+rows="'. $rows .'"@', 'Old # of rows is not used.'); + $this->assertPattern('@]+rows="'. $newrows .'"@', 'New # of rows is used.'); + } + + protected function _readAndVerifyInstance($instance) { + $read = field_get_instance(array('field_name' => $instance->field_name, 'type_name' => $instance->type_name)); + $this->assertIdentical(count($read), 1, "Read instance $instance->field_name/$instance->type_name"); + $this->assertIdentical($read[0]->field_name, $instance->field_name, 'field_name matches'); + $this->assertIdentical($read[0]->type_name, $instance->type_name, 'type_name matches'); + $this->assertEqual($read[0]->widget['weight'], $instance->widget['weight'], 'widget[weight] matches'); + $this->assertIdentical($read[0]->widget['label'], $instance->widget['label'], 'widget[label] matches'); + $this->assertIdentical($read[0]->widget['description'], $instance->widget['description'], 'widget[description] matches'); + $this->assertIdentical($read[0]->widget['type'], $instance->widget['type'], 'widget[type] matches'); + $this->assertIdentical($read[0]->widget['module'], $instance->widget['module'], 'widget[module] matches'); + return $read[0]; + } +} +?> diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/includes/content.alter.inc ./modules/fields/includes/content.alter.inc --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/includes/content.alter.inc 2008-05-31 11:52:48.000000000 -0400 @@ -0,0 +1,216 @@ + $attributes) { + if (!in_array($column, array('nid', 'vid', 'delta'))) { + db_drop_field($ret, $previous_table, $column); + } + } + } + return content_alter_db_cleanup($ret); + } + + // All content types that have fields need a content type table. + if (!empty($new_field)) { + $base_tablename = _content_tablename($new_field['type_name'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE); + if (!db_table_exists($base_tablename)) { + db_create_table($ret, $base_tablename, content_table_schema()); + } + } + + // Create new table and columns, if not already created. + if (!db_table_exists($new_table)) { + db_create_table($ret, $new_table, $new_schema); + } + else { + // Or add fields to an existing table. + foreach ($new_schema['fields'] as $column => $attributes) { + if (!in_array($column, array('nid', 'vid', 'delta')) && !db_column_exists($new_table, $column)) { + db_add_field($ret, $new_table, $column, $attributes); + } + } + } + + // If this is a new field, we're done. + if (empty($previous_field)) { + return content_alter_db_cleanup($ret); + } + + // If the previous table doesn't exist, we're done. + // Could happen if someone tries to run a schema update from an + // content.install update function more than once. + if (!db_table_exists($previous_table)) { + return content_alter_db_cleanup($ret); + } + + // If changing data from one schema to another, see if changes require that + // we drop multiple values or migrate data from one storage type to another. + $migrate_columns = array_intersect_assoc($new_schema['fields'], $previous_schema['fields']); + unset($migrate_columns['nid'], $migrate_columns['vid'], $migrate_columns['delta']); + + // If we're going from one multiple value a smaller one or to single, + // drop all delta values higher than the new maximum delta value. + // Not needed if the new multiple is unlimited or if the new table is the content table. + if ($new_table != $base_tablename && $new_field['multiple'] < $previous_field['multiple'] && $new_field['multiple'] != 1) { + db_query("DELETE FROM {". $new_table ."} WHERE delta >= ". max(1, $new_field['multiple'])); + } + + // If going from multiple to non-multiple, make sure the field tables have + // the right database structure to accept migrated data. + if ($new_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) { + if ($previous_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD && count($previous_schema['fields'])) { + // Already using per-field storage; change multiplicity if needed. + if ($previous_field['multiple'] > 0 && $new_field['multiple'] == 0) { + db_drop_field($ret, $new_table, 'delta'); + db_drop_primary_key($ret, $new_table); + db_add_primary_key($ret, $new_table, array('vid')); + } + else if ($previous_field['multiple'] == 0 && $new_field['multiple'] > 0) { + db_add_field($ret, $new_table, 'delta', array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0)); + db_drop_primary_key($ret, $new_table); + db_add_primary_key($ret, $new_table, array('vid', 'delta')); + } + } + } + + // Migrate data from per-content-type storage. + if ($previous_field['db_storage'] == CONTENT_DB_STORAGE_PER_CONTENT_TYPE && + $new_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD) { + $columns = array_keys($migrate_columns); + if ($new_field['multiple']) { + db_query('INSERT INTO {'. $new_table .'} (vid, nid, delta, '. implode(', ', $columns) .') '. + ' SELECT vid, nid, 0, '. implode(', ', $columns) .' FROM {'. $previous_table .'}'); + } + else { + db_query('INSERT INTO {'. $new_table .'} (vid, nid, '. implode(', ', $columns) .') '. + ' SELECT vid, nid, '. implode(', ', $columns) .' FROM {'. $previous_table .'}'); + } + foreach ($columns as $column_name) { + db_drop_field($ret, $previous_table, $column_name); + } + } + + // Migrate data from per-field storage, and drop per-field table. + if ($previous_field['db_storage'] == CONTENT_DB_STORAGE_PER_FIELD && + $new_field['db_storage'] == CONTENT_DB_STORAGE_PER_CONTENT_TYPE) { + // In order to be able to use drupal_write_record, we need to + // rebuild the schema now. + content_alter_db_cleanup($ret); + if ($previous_field['multiple']) { + $result = db_query("SELECT * FROM {". $previous_table ."} c JOIN {node} n ON c.nid = n.nid WHERE delta = 0 AND n.type = '%s'", $new_field['type_name']); + } + else { + $result = db_query("SELECT * FROM {". $previous_table ."} c JOIN {node} n ON c.nid = n.nid WHERE n.type = '%s'", $new_field['type_name']); + } + $record = array(); + while ($data = db_fetch_array($result)) { + $record['nid'] = $data['nid']; + $record['vid'] = $data['vid']; + if ($previous_field['multiple']) { + $record['delta'] = $data['delta']; + } + foreach ($migrate_columns as $column => $attributes) { + $record[$column] = is_null($data[$column]) ? NULL : $data[$column]; + } + if (db_result(db_query('SELECT COUNT(*) FROM {'. $new_table . + '} WHERE vid = %d AND nid = %d', $data['vid'], $data['nid']))) { + $keys = $new_field['multiple'] ? array('vid', 'delta') : array('vid'); + drupal_write_record($new_table, $record, $keys); + } + else { + drupal_write_record($new_table, $record); + } + } + db_drop_table($ret, $previous_table); + } + + // Change modified columns that don't involve storage changes. + foreach ($new_schema['fields'] as $column => $attributes) { + if (isset($previous_schema['fields'][$column]) && + $previous_field['db_storage'] == $new_field['db_storage']) { + if ($attributes != $previous_schema['fields'][$column]) { + if (!in_array($column, array('nid', 'vid', 'delta'))) { + db_change_field($ret, $new_table, $column, $column, $attributes); + } + } + } + } + + // Remove obsolete columns. + foreach ($previous_schema['fields'] as $column => $attributes) { + if (!isset($new_schema['fields'][$column])) { + if (!in_array($column, array('nid', 'vid', 'delta'))) { + db_drop_field($ret, $previous_table, $column); + } + } + } + + // TODO : debugging stuff - should be removed + if (module_exists('devel')) { + //dsm($ret); + } + return $ret; +} + +/** + * Helper function for handling cleanup operations when schema changes are made. + */ +function content_alter_db_cleanup($ret) { + // Rebuild the whole database schema. + // TODO : this could be optimized. We don't need to rebuild in *every case*... + // Or do we? This affects the schema and menu and may have unfortunate + // delayed effects if we don't clear everything out at this point. + content_clear_type_cache(TRUE); + + // TODO : debugging stuff - should be removed + if (module_exists('devel')) { + //dsm($ret); + } + return $ret; + +} diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/includes/content.crud.inc ./modules/fields/includes/content.crud.inc --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/includes/content.crud.inc 2008-05-31 17:38:19.000000000 -0400 @@ -0,0 +1,757 @@ + widget array as an argument. + * + * The hook_content_fieldapi() $ops include: + * + * - create instance + * - read instance + * - update instance + * - delete instance + * + * Another function, content_module_delete($module) will clean up + * after a module that has been deleted by removing all data and + * settings information that was created by that module. + */ + +/** + * Base Field data structure. + * + * This class represents a Field object to the Field API. It is + * primarily just a data structure. + */ +class Field { + // Public properties + public $field_name; + public $type; + public $required = 0; + public $multiple = 0; + public $shareable = 0; + + // Internal properties. These are protected if possible. + public $module; + public $active = 0; + public $columns = array(); + public $db_storage; + + /** + * Construct a Field structure with default properties: + * + * required: FALSE + * shareable: FALSE + * multiple: 0 (single-valued) + * + * NOTE: No per-field_type settings (e.g. 'format' for text fields) + * are initialized by this function. For now, you always have to + * set those yourself. TODO: Fix this. + * + * @param $field_name + * The field name to create. + * @param $field_type + * The field type to create; e.g. text, number, nodereference. + */ + function __construct($field_name = NULL, $field_type = NULL) { + // When called by db_fetch_object() the properties corresponding + // to query columns are filled in. Do not re-initialize; just return. + if (isset($this->type)) { return; } + + $this->field_name = $field_name; + $this->type = $field_type; + $field_types = _content_field_types(); + $module = $field_types[$this->type]['module']; + $this->required = 0; + $this->multiple = 0; + $this->shareable = 0; + $this->module = $module; + $this->active = module_exists($module) ? 1 : 0; + $this->columns = module_invoke($module, 'field_settings', 'database columns', $this->toArray()); + // Ensure columns always default to NULL values. + foreach ((array) $this->columns as $column_name => $column) { + $this->columns[$column_name]['not null'] = FALSE; + } + + $this->db_storage = CONTENT_DB_STORAGE_PER_CONTENT_TYPE; + + // Create all per-field-type properties. + $setting_names = module_invoke($module, 'field_settings', 'save', $this->toArray()); + if (is_array($setting_names)) { + foreach ($setting_names as $setting) { + $field[$setting] = NULL; + } + } + } + + /** + * Temporary compatibility function for interacting with legacy CCK + * code. + */ + function toArray() { + return (array) $this; + } +} + +/** + * Base Field Instance data structure. + * + * This class represents a Field Instance object to the Field API. It + * is primarily just a data structure. + */ +class FieldInstance { + // Public properties + public $field_name; + public $type_name; + public $widget = array(); + public $default_value; + public $default_value_php; + public $display_settings = array(); + + // Internal properties. These are protected if possible. + public $widget_active; + protected $field; + protected $_field_name; + + /** + * Construct a Field Instance structure with default properties. + * + * NOTE: No per-widget-type settings (e.g. rows) are initialized by + * this function. For now, you always have to set those yourself. + * TODO: Fix this. + * + * @param $field_name + * The field name to create. + * @param $field_type + * The field type to create; e.g. text, number, nodereference. + */ + function __construct($field_name = NULL, $type_name = NULL, $widget_type = NULL) { + // When called by db_fetch_object() the properties corresponding + // to query columns are filled in. Do not re-initialize; just return. + if (isset($this->type_name)) { return; } + + $widget_types = _content_widget_types(); + $module = $widget_types[$widget_type]['module']; + + $this->field_name = $field_name; + $this->type_name = $type_name; + $this->widget = array( + 'weight' => 0, + 'label' => $field_name, + 'description' => '', + 'type' => $widget_type, + 'module' => $module); + $this->display_settings = array(); + + if (module_exists($module)) { + $this->widget_active = 1; + } + + $settings_names = array_merge(array('label'), array_keys(_content_admin_display_contexts())); + foreach ($settings_names as $name) { + $instance->display_settings[$name]['format'] = ($name == 'label') ? 'above' : 'default'; + } + + // Make sure widget settings all have an index in the array. + $settings_names = module_invoke($module, 'widget_settings', 'save', $this->toArray()); + foreach ((array) $settings_names as $name) { + $this->widget[$name] = NULL; + } + } + + /** + * Retrieve the Field this object is an instance of. + * + * @return + * The Field object or NULL if $this->field_name is not a valid + * field name. + */ + function getField() { + if (!isset($this->field) || $this->_field_name != $this->field_name) { + $fields = field_get_field(array('field_name' => $this->field_name)); + if (count($fields) != 1) { + return NULL; + } + $this->field = $fields[0]; + $this->_field_name = $this->field_name; + } + return $this->field; + } + + /** + * Temporary compatibility function for interacting with legacy CCK + * code. + */ + function toArray() { + return (array) $this; + } + + /** + * Temporary compatibility function for interacting with legacy CCK + * code. + */ + function toFieldArray() { + // If field_name is invalid, we cannot return a field array. + if ($this->getField() == NULL) { + return NULL; + } + return $this->field->toArray() + $this->toArray(); + } +} + +/** + * Create a new field in the database. This function does not assign + * the field to any content types or create storage tables for the + * field in the database. + * + * @param $field + * A Field structure to create. + * @return + * TRUE on success, FALSE on failure. + */ +function field_create_field(Field $field) { + // Field name is required. + if (empty($field->field_name)) { + field_error(t('Attempt to create an unnamed field.')); + return FALSE; + } + + // Ensure the field name is unique. + $prior_field = field_get_field(array('field_name' => $field->field_name)); + if (!empty($prior_field)) { + field_error(t('Attempt to create field name %name which already exists.', array('%name' => $field->field_name))); + return FALSE; + } + + // Make sure we know what module to invoke for field info. + if (empty($field->module) && !empty($field->type)) { + $field_types = _content_field_types(); + $field->module = $field_types[$field->type]['module']; + } + + // The storage type may need to be updated. + $field->db_storage = content_storage_type($field->toArray()); + + // Get a fresh copy of the column information whenever a field is created. + $field->columns = (array) module_invoke($field->module, 'field_settings', 'database columns', $field->toArray()); + + // Save the new field definition. + _field_write_field($field, 'create'); + + // Keep our caches up to date. + content_clear_type_cache(TRUE); + + return TRUE; +} + +/** + * Create a new field instance. This function assigns the field to a + * content type and create storage tables for the field in the + * database if necessary. + * + * @param $instance + * A Field Instance structure to create. + * @return + * TRUE on success, FALSE on failure. + */ +function field_create_instance(FieldInstance $instance) { + include_once('./'. drupal_get_path('module', 'content') .'/includes/content.alter.inc'); + + // $instance->type_name must exist + if (node_get_types('type', $instance->type_name) == FALSE) { + field_error(t('Attempt to instantiate field for non-existant content type %type.', array('%type' => $instance->type_name))); + return FALSE; + } + + // Get the Field structure this is will be an instance of. + $field = $instance->getField(); + if ($field == NULL) { + field_error(t('Attempt to instantiate non-existant field %name.', array('%name' => $instance->field_name))); + return FALSE; + } + + // Get prior instances of this field, if any. + $prior_instances = field_get_instance(array('field_name' => $instance->field_name)); + + // Non-shared fields cannot have more than one instance. + if (!$field->shareable && count($prior_instances) > 0) { + field_error(t('Attempt to re-instantiate non-shared field %name.', array('%name' => $field->field_name))); + return FALSE; + } + + // Invoke hook_content_fieldapi(). + module_invoke_all('content_fieldapi', 'create instance', $instance->toFieldArray()); + + // Create the new instance record. + _field_write_instance($instance, 'create'); + + // Create the columns/tables for the new instance, if necessary. + if (!$field->shareable || count($prior_instances) == 0) { + content_alter_schema(array(), $instance->toFieldArray()); + } + + // Keep our caches up to date. + content_clear_type_cache(TRUE); + + return $instance; +} + +/** + * Update an existing field instance. This function can only change + * Field Instance properties, not Field properties. + * + * @param $new_instance + * A Field Instance structure. The instance matching the field_name + * and type_name in this structure will be updated to match the + * other properties set in the structure. + * @return + * $instance on success, FALSE on failure. + */ +function field_update_instance($new_instance) { + // Verify that the instance already exists. + $previous = field_get_instance(array('field_name' => $new_instance->field_name, 'type_name' => $new_instance->type_name)); + if (count($previous) != 1) { + field_error(t('Attempt to update a non-existant field instance %name/%type.', array('%name' => $new_instance->field_name, '%type' => $new_instance->type_name))); + return FALSE; + } + + // Invoke hook_content_fieldapi(). + module_invoke_all('content_fieldapi', 'update instance', $new_instance->toFieldArray()); + + // Update the instance record with the latest values. + _field_write_instance($new_instance, 'update'); + content_clear_type_cache(TRUE); + + return $new_instance; +} + +/** + * Delete an existing field instance. Someone is going to say this + * should take a FieldInstance structure for uniformity, and they will + * probably be right. TODO: Untested! + * + * @param $field_name + * The name of the field instance to delete. + * @param $type_name + * The content type of the field instance to delete. + */ +function field_delete_instance($field_name, $type_name) { + include_once('./'. drupal_get_path('module', 'content') .'/includes/content.alter.inc'); + + // Get the previous field value. + $instance = array_pop(field_get_instance(array('field_name' => $field_name, 'type_name' => $type_name))); + + // Invoke hook_content_fieldapi(). + module_invoke_all('content_fieldapi', 'delete instance', $instance->toFieldArray()); + + // Delete the instance record from our table. + db_query("DELETE FROM {". content_instance_tablename() . + "} WHERE field_name = '%s' AND type_name = '%s'", $instance->field_name, $instance->type_name); + + // If no instances remain, delete the database storage entirely. + // This is always the case for non-shareable fields and may be the + // case for shareable fields. + $remaining = field_get_instance(array('field_name' => $field_name)); + if (count($remaining) == 0) { + content_alter_schema($instance, NULL); + } + + content_clear_type_cache(TRUE); + return $instance; +} + +/** + * Write a field record. + * + * @param $field + * The field array to process. + */ +function _field_write_field($field, $op = 'update') { + // Rearrange the data to create the global_settings array. + $field->global_settings = array(); + $setting_names = module_invoke($field->module, 'field_settings', 'save', $field->toArray()); + if (is_array($setting_names)) { + foreach ($setting_names as $setting) { + $field->global_settings[$setting] = isset($field->$setting) ? $field->$setting : ''; + unset($field->$setting); // XXX why do this? + } + } + // 'columns' is a reserved word in MySQL4, so our column is named 'db_columns'. + $field->db_columns = $field->columns; + + switch ($op) { + case 'create': + drupal_write_record(content_field_tablename(), $field); + break; + case 'update': + drupal_write_record(content_field_tablename(), $field, 'field_name'); + break; + } + unset($field->db_columns); + return $field; +} + +/** + * Collapse a field instance into a flattened structure for use by + * _field_write_instance(). + */ +function _field_collapse_instance(FieldInstance $instance) { + // don't alter the instance object we were given + $instance = clone($instance); + + $instance->widget_settings = !empty($instance->widget) ? $instance->widget : array(); + $instance->widget_type = !empty($instance->widget['type']) ? $instance->widget['type'] : ''; + $instance->weight = !empty($instance->widget['weight']) ? $instance->widget['weight'] : 0; + $instance->label = !empty($instance->widget['label']) ? $instance->widget['label'] : $instance->field_name; + $instance->description = !empty($instance->widget['description']) ? $instance->widget['description'] : ''; + $instance->type_name = !empty($instance->type_name) ? $instance->type_name : ''; + + if (!empty($instance->widget['module'])) { + $widget_module = $instance->widget['module']; + } + elseif (!empty($instance->widget['type'])) { + $widget_types = _content_widget_types(); + $widget_module = $widget_types[$instance->widget['type']]['module']; + } + else { + $widget_module = ''; + } + $instance->widget_module = $widget_module; + unset($instance->widget_settings['type']); + unset($instance->widget_settings['weight']); + unset($instance->widget_settings['label']); + unset($instance->widget_settings['description']); + unset($instance->widget_settings['module']); + unset($instance->widget); + return $instance; +} + +/** + * Expand a flattened field instance structure after it is read by + * _field_get_instance(). + */ +function _field_expand_instance(FieldInstance $instance) { + // don't modify the instance object we were given + $instance = clone($instance); + + if (!empty($instance->widget)) { + return $instance; + } + + $instance->widget = !empty($instance->widget_settings) ? $instance->widget_settings : array(); + $instance->widget['label'] = !empty($instance->label) ? $instance->label : $instance->field_name; + $instance->widget['weight'] = !empty($instance->weight) ? $instance->weight : NULL; + $instance->widget['description'] = !empty($instance->description) ? $instance->description : NULL; + + if (!empty($instance->widget_type)) { + $instance->widget['type'] = $instance->widget_type; + $widget_types = _content_widget_types(); + $instance->widget['module'] = $widget_types[$instance->widget_type]['module']; + } + elseif (!empty($instance->widget_module)) { + $instance->widget['module'] = $instance->widget_module; + } + + unset($instance->widget_type); + unset($instance->weight); + unset($instance->label); + unset($instance->description); + unset($instance->widget_module); + unset($instance->widget_settings); + + // If content.module is handling the default value, + // initialize $widget_settings with default values, + if (isset($instance->default_value) && isset($instance->default_value_php) && + content_callback('widget', 'default value', $instance) == CONTENT_CALLBACK_DEFAULT) { + $instance->widget['default_value'] = !empty($instance->default_value) ? $instance->default_value : NULL; + $instance->widget['default_value_php'] = !empty($instance->default_value_php) ? $instance->default_value_php : NULL; + unset($instance->default_value); + unset($instance->default_value_php); + } + return $instance; +} + +/** + * Write a field instance record. + * + * @param $instance + * The instance structure to process. + */ +function _field_write_instance($instance, $op = 'update') { + // Collapse the field => widget format. + $instance = _field_collapse_instance($instance); + + // Rearrange the data to create the widget_settings array. + $setting_names = module_invoke($instance->widget_module, 'widget_settings', 'save', $instance->toFieldArray()); + if (is_array($setting_names)) { + foreach ($setting_names as $setting) { + $instance->widget_settings[$setting] = isset($instance->widget_settings[$setting]) ? $instance->widget_settings[$setting] : ''; + } + } + + switch ($op) { + case 'create': + drupal_write_record(content_instance_tablename(), $instance); + break; + case 'update': + drupal_write_record(content_instance_tablename(), $instance, array('field_name', 'type_name')); + break; + } + return $instance; +} + +/** + * Load a field. + * + * @param $param + * An array of properties to use in selecting fields. Valid keys: + * - 'field_name' - The name of the field. + * - 'type' - The type of the field. + * If NULL, all instances will be returned. + * @return + * The fields array. + */ +function field_get_field($param) { + if (is_array($param)) { + // Turn the conditions into a query. + foreach ($param as $key => $value) { + $cond[] = 'nf.'. db_escape_string($key) ." = '%s'"; + $args[] = $value; + } + } + + if (count($cond)) { + $cond = " AND ". implode(' AND ', $cond); + } + + $db_result = db_query("SELECT * FROM {". content_field_tablename() ."} nf ". + " WHERE nf.active = 1 $cond", $args); + + $fields = array(); + while ($field = db_fetch_object($db_result, 'Field')) { + // 'columns' is a reserved word in MySQL4, so our column is named 'db_columns'. + $field->columns = (array) (!empty($field->db_columns) ? unserialize($field->db_columns) : array()); + unset($field->db_columns); + $field->global_settings = (array) (!empty($field->global_settings) ? unserialize($field->global_settings) : array()); + + foreach ($field->global_settings as $key => $value) { + $field->$key = $value; + } + unset($field->global_settings); + + // Invoke hook_content_fieldapi(). + module_invoke_all('content_fieldapi', 'read field', $field->toArray()); + $fields[] = $field; + } + return $fields; +} + +/** + * Load a field instance. + * + * @param $param + * An array of properties to use in selecting a field instance. Valid keys: + * - 'type_name' - The name of the content type in which the instance exists. + * - 'field_name' - The name of the field whose instance is to be loaded. + * if NULL, all instances will be returned. + * @return + * The field arrays. + */ +function field_get_instance($param = NULL) { + if (is_array($param)) { + // Turn the conditions into a query. + foreach ($param as $key => $value) { + $cond[] = 'nfi.'. db_escape_string($key) ." = '%s'"; + $args[] = $value; + } + } + + if (count($cond)) { + $cond = " AND ". implode(' AND ', $cond); + } + $db_result = db_query("SELECT nfi.* FROM {". content_instance_tablename() ."} nfi ". + " JOIN {". content_field_tablename() ."} nf ON nfi.field_name = nf.field_name ". + " WHERE nf.active = 1 AND nfi.widget_active = 1 ". $cond ." ORDER BY nfi.weight ASC, nfi.label ASC", $args); + + $instances = array(); + while ($instance = db_fetch_object($db_result, 'FieldInstance')) { + $instance->global_settings = (array) (!empty($instance->global_settings) ? unserialize($instance->global_settings) : array()); + + foreach ($instance->global_settings as $key => $value) { + $instance->$key = $value; + } + unset($instance->global_settings); + + if (!empty($instance->widget_settings)) { + $instance->widget_settings = (array) unserialize($instance->widget_settings); + } + else { + $instance->widget_settings = array(); + } + if (!empty($instance->display_settings)) { + $instance->display_settings = (array) unserialize($instance->display_settings); + } + else { + $instance->display_settings = array(); + } + + $instance = _field_expand_instance($instance); + + // Invoke hook_content_fieldapi(). + module_invoke_all('content_fieldapi', 'read instance', $instance->toFieldArray()); + $instances[] = $instance; + } + return $instances; +} + +/** + * Load merged Field + Field Instance structures. This is a temporary + * backwards-compatibility function. + * + * @param $param + * An array of properties to use in selecting a field instance. Valid keys: + * - 'type_name' - The name of the content type in which the instance exists. + * - 'field_name' - The name of the field whose instance is to be loaded. + * if NULL, all instances will be returned. + * @return + * The field arrays. + */ +function content_field_instance_read($param = NULL) { + $instances = field_get_instance($param); + $fields = array(); + foreach ($instances as $instance) { + $fields[] = $instance->toFieldArray(); + } + return $fields; +} + +/** + * Delete all data related to a module. + * + * @param string $module + */ +function content_module_delete($module) { + // Delete the field data. + $results = db_query("SELECT field_name, type_name FROM {". content_instance_tablename() ."} WHERE widget_module = '%s'", $module); + while ($field = db_fetch_array($results)) { + content_field_instance_delete($field['field_name'], $field['type_name']); + } + // Force the caches and static arrays to update to the new info. + _content_type_info(TRUE, TRUE); + +} + +/** + * Make changes needed when a content type is created. + * + * @param $info + * value supplied by hook_node_type() + * + * node_get_types() is still missing the new type at this point, so no + * use to call content_clear_type_cache() or menu_rebuild() here. Instead + * we set a flag to make sure it gets rebuilt on next page load. + */ +function content_type_create($info) { + content_clear_type_cache(TRUE); +} + +/** + * Make changes needed when an existing content type is updated. + * + * @param $info + * value supplied by hook_node_type() + */ +function content_type_update($info) { + if (!empty($info->old_type) && $info->old_type != $info->type) { + // Rename the content type in all fields that use changed content type. + db_query("UPDATE {". content_instance_tablename() ."} SET type_name='%s' WHERE type_name='%s'", array($info->type, $info->old_type)); + + // Rename the content fields table to match new content type name. + $old_type = content_types($info->old_type); + $old_name = _content_tablename($old_type['type'], CONTENT_DB_STORAGE_PER_CONTENT_TYPE); + $new_name = _content_tablename($info->type, CONTENT_DB_STORAGE_PER_CONTENT_TYPE); + if (db_table_exists($old_name)) { + $ret = array(); + db_rename_table($ret, $old_name, $new_name); + watchdog('content', 'Content fields table %old_name has been renamed to %new_name and field instances have been updated.', array( + '%old_name' => $old_name, '%new_name' => $new_name)); + } + + // Rename the variable storing weights for non-CCK fields. + if ($extra = variable_get('content_extra_weights_'. $info->old_type, array())) { + variable_set('content_extra_weights_'. $info->type, $extra); + variable_del('content_extra_weights_'. $info->old_type); + } + } + + // Reset all content type info and alter the menu paths. + content_clear_type_cache(TRUE); +} + +/** + * Make changes needed when a content type is deleted. + * + * @param $info + * value supplied by hook_node_type() + */ +function content_type_delete($info) { + $fields = field_get_instance(array('type_name' => $info->type)); + foreach ($fields as $field) { + content_field_instance_delete($field['field_name'], $info->type); + } + $table = _content_tablename($info->type, CONTENT_DB_STORAGE_PER_CONTENT_TYPE); + if (db_table_exists($table)) { + $ret = array(); + db_drop_table($ret, $table); + watchdog('content', 'The content fields table %name has been deleted.', array('%name' => $table)); + } + + content_clear_type_cache(TRUE); +} + +class FieldException extends Exception {} + +function field_error($msg) { + watchdog('content', $msg); + //throw new FieldException($msg); +} + +/** + * @} End of "defgroup fieldapi". + */ diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/includes/content.node_form.inc ./modules/fields/includes/content.node_form.inc --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/includes/content.node_form.inc 2008-05-31 11:52:48.000000000 -0400 @@ -0,0 +1,292 @@ + $field) { + $form['#field_info'][$field['field_name']] = $field; + $form += (array) content_field_form($form, $form_state, $field); + } + return $form; +} + +/** + * Create a separate form element for each field. + * + * // TODO : $count param ? not used anymore ? + * Hook_widget() picks up two new values, $count and $delta, to help + * widgets know what information to return since multiple values are + * sometimes controlled by the content module. + * + * @param $form + * the form to add this field element to + * @param $form_state + * the form_state for the above form + * @param $field + * the field array to use to create the form element + * @param $get_delta + * use to get only a specific delta value of a multiple value field, otherwise + * function will return the entire $field form element. + */ +function content_field_form(&$form, &$form_state, $field, $get_delta = NULL) { + $form['#cache'] = FALSE; + $node = $form['#node']; + $addition = array(); + $form_element = array(); + // TODO : is the "if (function_exists($function)) {" needed ? + // defining the $function here makes it unclear where it is actually called + $function = $field['widget']['module'] .'_widget'; + $field_name = $field['field_name']; + if (function_exists($function)) { + // Prepare the values to be filled in the widget. + // We look in the following places : + // - Form submitted values + // - Node values (when editing an existing node), or pre-filled values (when + // creating a new node translation) + // - Default values set for the field (when creating a new node). + $items = array(); + if (!empty($form_state['values'][$field['field_name']])) { + $items = $form_state['values'][$field['field_name']]; + } + elseif (!empty($node->$field['field_name'])) { + $items = $node->$field['field_name']; + } + elseif (empty($node->nid)) { + if (content_callback('widget', 'default value', $field) != CONTENT_CALLBACK_NONE) { + // If a module wants to insert custom default values here, + // it should provide a hook_default_value() function to call, + // otherwise the content module's content_default_value() function + // will be used. + $callback = content_callback('widget', 'default value', $field) == CONTENT_CALLBACK_CUSTOM ? $field['widget']['module'] .'_default_value' : 'content_default_value'; + if (function_exists($callback)) { + $items = $callback($form, $form_state, $field, 0); + } + } + } + + // If content module handles multiple values for this form element, + // and not selecting an individual $delta, process the multiple value form. + if (!isset($get_delta) && content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) { + $form_element = content_multiple_value_form($form, $form_state, $field, $items); + } + // If the widget is handling multiple values (e.g optionwidgets), + // or selecting an individual element, just get a single form + // element and make it the $delta value. + else { + $delta = isset($get_delta) ? $get_delta : 0; + if ($element = $function($form, $form_state, $field, $items, $delta)) { + $defaults = array( + '#field_name' => $field['field_name'], + '#required' => $get_delta > 0 ? FALSE : $field['required'], + // TODO : should we add some logic for title and description too ? + '#columns' => array_keys($field['columns']), + '#title' => t($field['widget']['label']), + '#delta' => $delta, + ); + // If we're processing a specific delta value for a field where the + // content module handles multiples, set the delta in the result. + // For fields that handle their own processing, we can't make assumptions + // about how the field is structured, just merge in the returned value. + if (content_handle('widget', 'multiple values', $field) == CONTENT_HANDLE_CORE) { + $form_element[$delta] = array_merge($element, $defaults); + } + else { + $form_element = array_merge($element, $defaults); + } + } + } + + // Field name is needed at top level as well as the individual elements + // so the multiple values or other field level theme or processing can find it. + if ($form_element) { + $defaults = array( + '#field_name' => $field['field_name'], + '#tree' => TRUE, + '#weight' => $field['widget']['weight'], + // TODO : what's the need for #count ? does not seem to be used anywhere ? + '#count' => count($form_element), + ); + $addition[$field['field_name']] = array_merge($form_element, $defaults); + } + } + return $addition; +} + +/** + * Special handling to create form elements for multiple values. + * + * Handles generic features for multiple fields : + * - number of widgets + * - AHAH-'add more' button + * - drag-n-drop value reordering + */ +function content_multiple_value_form(&$form, &$form_state, $field, $items) { + $field_name = $field['field_name']; + + switch ($field['multiple']) { + case 0: + $max = 0; + break; + case 1: + $max = (isset($form_state['item_count'][$field_name]) ? $form_state['item_count'][$field_name] : count($items)); + break; + default: + $max = $field['multiple'] - 1; + break; + } + + $form_element = array( + // TODO : Not sure about that one - when theming through '#type', + // children are already rendered, and we can't do the table layout. + //'#type' => 'content_multiple_values', + '#theme' => 'content_multiple_values', + '#title' => t($field['widget']['label']), + '#required' => $field['required'], + '#description' => t($field['widget']['description']), + ); + $function = $field['module'] .'_widget'; + + for ($delta = 0; $delta <= $max; $delta++) { + if ($element = $function($form, $form_state, $field, $items, $delta)) { + $defaults = array( + '#field_name' => $field_name, + '#title' => ($field['multiple'] >= 1) ? '' : t($field['widget']['label']), + '#description' => ($field['multiple'] >= 1) ? '' : t($field['widget']['description']), + '#required' => $delta == 0 && $field['required'], + '#weight' => $delta, + '#delta' => $delta, + '#columns' => array_keys($field['columns']), + ); + + // Add an input field for the delta (drag-n-drop reordering), which will + // be hidden by tabledrag js behavior. + if ($field['multiple'] >= 1) { + // We name the element '_weight' to avoid clashing with column names + // defined by field modules. + $element['_weight'] = array( + // When JS is disabled, a weight selector will be more convenient (?), + // but won't work well with more than 10 values. + '#type' => ($max < 10) ? 'weight' : 'textfield', + '#default_value' => $delta, + '#weight' => 100, + ); + } + + $form_element[$delta] = array_merge($element, $defaults); + } + } + + // Add AHAH add more button, if not working with a programmed form. + if ($field['multiple'] == 1 && empty($form['#programmed'])) { + // Make sure the form is cached so ahah can work. + $form['#cache'] = TRUE; + $content_type = content_types($form['type']['#value']); + $field_name_css = str_replace('_', '-', $field_name); + + $form_element[$field_name .'_add_more'] = array( + '#type' => 'submit', + '#value' => t('Add another !field value', array('!field' => t($field['widget']['label']))), + '#description' => t("If the amount of boxes above isn't enough, click here to add more items."), + '#weight' => $field['widget']['weight'] + $max + 1, + '#submit' => array('content_add_more_submit'), // If no JavaScript action. + '#ahah' => array( + 'path' => 'content/js_add_more/'. $content_type['url_str'] .'/'. $field_name, + 'wrapper' => $field_name_css .'-items', + 'method' => 'replace', + 'effect' => 'fade', + ), + // When JS is disabled, the content_add_more_submit handler will find + // the relevant field using these entries. + //'#field_name' => $field_name, + //'#type_name' => $content_type['type'], + ); + + // Add wrappers for the fields and 'more' button. + // TODO : could be simplified ? + $form_element['#prefix'] = '
'; + $form_element[$field_name .'_add_more']['#prefix'] = '
'; + $form_element[$field_name .'_add_more']['#suffix'] = '
'; + } + return $form_element; +} + +/** + * Submit handler to add more choices to a content form. This handler is used when + * JavaScript is not available. It makes changes to the form state and the + * entire form is rebuilt during the page reload. + */ +function content_add_more_submit($form, &$form_state) { + // Set the form to rebuild and run submit handlers. + node_form_submit_build_node($form, $form_state); + $field_name = $form_state['clicked_button']['#field_name']; + $type_name = $form_state['clicked_button']['#type_name']; + + foreach ($form['#field_info'] as $field_name => $field) { + // Make the changes we want to the form state. + if ($form_state['values'][$field_name][$field_name .'_add_more']) { + $form_state['item_count'][$field_name] = count($form_state['node'][$field_name]) + 1; + } + } +} + + +/** + * Menu callback for AHAH addition of new empty widgets. + */ +function content_add_more_js($type_name_url, $field_name) { + $type = content_types($type_name_url); + $field = content_fields($field_name, $type['type']); + if ($field['multiple'] != 1) { + // TODO : is that correct ? + drupal_json(array('status' => 0)); + } + + // Reorder values to account for drag-n-drop reordering + $_POST[$field_name] = _content_sort_items($field, $_POST[$field_name]); + $delta = max(array_keys($_POST[$field_name])) + 1; + // Retrieve the cached form. + $form_state = array('values' => $_POST); + $form = form_get_cache($_POST['form_build_id'], $form_state); + + // Build our new form element for the whole field, + // and let other modules alter it. + $form_element = content_field_form($form, $form_state, $field); + drupal_alter('form', $form_element, array(), 'content_add_more_js'); + + // Add the new element at the right place in the form. + if (module_exists('fieldgroup') && ($group_name = _fieldgroup_field_get_group($type['type'], $field_name))) { + $form[$group_name][$field_name] = $form_element[$field_name]; + } + else { + $form[$field_name] = $form_element[$field_name]; + } + + // Save the new definition of the form. + form_set_cache($_POST['form_build_id'], $form, $form_state); + + // Build the new form so that we can render the new element. + $_POST[$field_name][$delta]['_weight'] = $delta; // TODO : Hack !! + $form_state = array('submitted' => FALSE); + $form += array( + '#post' => $_POST, + '#programmed' => FALSE, + ); + $form = form_builder($_POST['form_id'], $form, $form_state); + + // TODO : bug - the new element gets an empty delta value + + // Render the new output. + $field_form = (!empty($group_name)) ? $form[$group_name][$field_name] : $form[$field_name]; + // We add a div around the new content to receive the ahah effect. + + $field_form[$delta]['#prefix'] = '
'. (isset($field_form[$delta]['#prefix']) ? $field_form[$delta]['#prefix'] : ''); + $field_form[$delta]['#suffix'] = (isset($field_form[$delta]['#suffix']) ? $field_form[$delta]['#suffix'] : '') .'
'; + $output = theme('status_messages') . drupal_render($field_form); + drupal_json(array('status' => TRUE, 'data' => $output)); +} diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/nodereference/nodereference.info ./modules/fields/nodereference/nodereference.info --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/nodereference/nodereference.info 2008-05-14 14:53:55.000000000 -0400 @@ -0,0 +1,10 @@ +; $Id: nodereference.info,v 1.8 2008/04/23 18:02:07 dww Exp $ +name = Node Reference +description = Defines a field type for referencing one node from another. +dependencies[] = content +dependencies[] = text +dependencies[] = optionwidgets +package = Core - fields +core = 7.x + +files[]=nodereference.module diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/nodereference/nodereference.install ./modules/fields/nodereference/nodereference.install --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/nodereference/nodereference.install 2008-04-23 14:02:09.000000000 -0400 @@ -0,0 +1,64 @@ + $fields) { + foreach ($fields as $field) { + switch ($field['type']) { + case 'nodereference': + $db_info = content_database_info($field); + $table = $db_info['table']; + $column = $db_info['columns']['nid']['column'] ; + db_change_field($ret, $table, $column, $column, array('type' => 'int', 'not null' => FALSE)); + db_field_set_no_default($ret, $db_info['table'], $column); + $ret[] = update_sql("UPDATE {". $db_info['table'] ."} SET ". $column ." = NULL WHERE ". $column ." = 0"); + } + } + } + return $ret; +} \ No newline at end of file diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/nodereference/nodereference.module ./modules/fields/nodereference/nodereference.module --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/nodereference/nodereference.module 2008-04-24 20:48:43.000000000 -0400 @@ -0,0 +1,705 @@ + 'Nodereference autocomplete', + 'page callback' => 'nodereference_autocomplete', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK + ); + return $items; +} + +/** + * Implementation of hook_theme(). + */ +function nodereference_theme() { + return array( + 'nodereference_item_simple' => array( + 'arguments' => array('item' => NULL), + ), + 'nodereference_item_advanced' => array( + 'arguments' => array('item' => NULL, 'view' => NULL), + ), + 'nodereference_select' => array( + 'arguments' => array('element' => NULL), + ), + 'nodereference_autocomplete' => array( + 'arguments' => array('element' => NULL), + ), + 'nodereference_formatter_default' => array( + 'arguments' => array('element'), + ), + 'nodereference_formatter_full' => array( + 'arguments' => array('element'), + 'function' => 'theme_nodereference_formatter_full_teaser', + ), + 'nodereference_formatter_teaser' => array( + 'arguments' => array('element'), + 'function' => 'theme_nodereference_formatter_full_teaser', + ), + ); +} + +/** + * Implementation of hook_field_info(). + * + * Here we indicate that the content module will use its default + * handling for the view of this field. + * + * Callbacks can be omitted if default handing is used. + * They're included here just so this module can be used + * as an example for custom modules that might do things + * differently. + */ +function nodereference_field_info() { + return array( + 'nodereference' => array( + 'label' => t('Node reference'), + 'description' => t('Store the ID of a related node as an integer value.'), + 'callbacks' => array( + 'tables' => CONTENT_CALLBACK_DEFAULT, + 'arguments' => CONTENT_CALLBACK_DEFAULT, + ), + ), + ); +} + +/** + * Implementation of hook_field_settings(). + */ +function nodereference_field_settings($op, $field) { + switch ($op) { + case 'form': + $form = array(); + $form['referenceable_types'] = array( + '#type' => 'checkboxes', + '#title' => t('Content types that can be referenced'), + '#multiple' => TRUE, + '#default_value' => is_array($field['referenceable_types']) ? $field['referenceable_types'] : array(), + '#options' => node_get_types('names'), + ); + if (module_exists('views')) { + $views = array('--' => '--'); + $all_views = views_get_all_views(); + foreach ($all_views as $view) { + $views[t('Existing Views')][$view->name] = $view->name; + } + + if (count($views) > 1) { + $form['advanced'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced - Nodes that can be referenced (View)'), + '#collapsible' => TRUE, + '#collapsed' => !isset($field['advanced_view']) || $field['advanced_view'] == '--', + ); + $form['advanced']['advanced_view'] = array( + '#type' => 'select', + '#title' => t('View'), + '#options' => $views, + '#default_value' => isset($field['advanced_view']) ? $field['advanced_view'] : '--', + '#description' => t('Choose the "Views module" view that selects the nodes that can be referenced.
Note :
  • This will discard the "Content types" settings above. Use the view\'s "filters" section instead.
  • Use the view\'s "fields" section to display additional informations about candidate nodes on node creation/edition form.
  • Use the view\'s "sort criteria" section to determine the order in which candidate nodes will be displayed.
'), + ); + $form['advanced']['advanced_view_args'] = array( + '#type' => 'textfield', + '#title' => t('View arguments'), + '#default_value' => isset($field['advanced_view_args']) ? $field['advanced_view_args'] : '', + '#required' => FALSE, + '#description' => t('Provide a comma separated list of arguments to pass to the view.'), + ); + } + } + return $form; + + case 'save': + $settings = array('referenceable_types'); + if (module_exists('views')) { + $settings[] = 'advanced_view'; + $settings[] = 'advanced_view_args'; + } + return $settings; + + case 'database columns': + $columns = array( + 'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE), + ); + return $columns; + + case 'views data': + $data = content_views_field_views_data($field); + $db_info = content_database_info($field); + $table_alias = content_views_tablename($field); + + // Swap the filter handler to the 'in' operator. + $data[$table_alias][$field['field_name'] .'_nid']['filter']['handler'] = 'views_handler_filter_many_to_one_content'; + + // Add a relationship for related node. + $data[$table_alias][$field['field_name'] .'_nid']['relationship'] = array( + 'base' => 'node', + 'field' => $db_info['columns']['nid']['column'], + 'handler' => 'views_handler_relationship', + ); + return $data; + } +} + +/** + * Implementation of hook_field(). + */ +function nodereference_field($op, &$node, $field, &$items, $teaser, $page) { + switch ($op) { + case 'validate': + $refs = _nodereference_potential_references($field, TRUE); + foreach ($items as $delta => $item) { + if (is_array($item) && !empty($item['error_field'])) { + $error_field = $item['error_field']; + unset($item['error_field']); + if (!empty($item['nid'])) { + if (!in_array($item['nid'], array_keys($refs))) { + form_set_error($error_field, t('%name : This post can\'t be referenced.', array('%name' => t($field['widget']['label'])))); + } + } + } + } + return $items; + } +} + +/** + * Implementation of hook_content_is_empty(). + */ +function nodereference_content_is_empty($item, $field) { + if (empty($item['nid'])) { + return TRUE; + } + return FALSE; +} + +/** + * Implementation of hook_field_formatter_info(). + */ +function nodereference_field_formatter_info() { + return array( + 'default' => array( + 'label' => t('Title (link)'), + 'field types' => array('nodereference'), + 'multiple values' => CONTENT_HANDLE_CORE, + ), + 'plain' => array( + 'label' => t('Title (no link)'), + 'field types' => array('nodereference'), + 'multiple values' => CONTENT_HANDLE_CORE, + ), + 'full' => array( + 'label' => t('Full node'), + 'field types' => array('nodereference'), + 'multiple values' => CONTENT_HANDLE_CORE, + ), + 'teaser' => array( + 'label' => t('Teaser'), + 'field types' => array('nodereference'), + 'multiple values' => CONTENT_HANDLE_CORE, + ), + ); +} + +/** + * Theme function for 'default' nodereference field formatter. + */ +function theme_nodereference_formatter_default($element) { + $output = ''; + if (!empty($element['#item']['nid']) && is_numeric($element['#item']['nid']) && ($title = _nodereference_titles($element['#item']['nid']))) { + $output = l($title, 'node/'. $element['#item']['nid']); + } + return $output; +} + +/** + * Theme function for 'plain' nodereference field formatter. + */ +function theme_nodereference_formatter_plain($element) { + $output = ''; + if (!empty($element['#item']['nid']) && is_numeric($element['#item']['nid']) && ($title = _nodereference_titles($element['#item']['nid']))) { + $output = check_plain($title); + } + return $output; +} + +/** + * Proxy theme function for 'full' and 'teaser' nodereference field formatters. + */ +function theme_nodereference_formatter_full_teaser($element) { + static $recursion_queue = array(); + $output = ''; + if (!empty($element['#item']['nid']) && is_numeric($element['#item']['nid'])) { + // If no 'referencing node' is set, we are starting a new 'reference thread' + if (!isset($node->referencing_node)) { + $recursion_queue = array(); + } + $recursion_queue[] = $node->nid; + if (in_array($element['#item']['nid'], $recursion_queue)) { + // Prevent infinite recursion caused by reference cycles : + // if the node has already been rendered earlier in this 'thread', + // we fall back to 'default' (node title) formatter. + return theme('nodereference_formatter_default', $element); + } + if ($referenced_node = node_load($element['#item']['nid'])) { + $referenced_node->referencing_node = $node; + $referenced_node->referencing_field = $field; + _nodereference_titles($element['#item']['nid'], $referenced_node->title); + $output = node_view($referenced_node, $element['#formatter'] == 'teaser'); + } + } + return $output; +} + +/** + * Helper function for formatters. + * + * Store node titles collected in the curent request. + */ +function _nodereference_titles($nid, $known_title = NULL) { + static $titles = array(); + if (!isset($titles[$nid])) { + $title = $known_title ? $known_title : db_result(db_query("SELECT title FROM {node} WHERE nid=%d", $nid)); + $titles[$nid] = $title ? $title : ''; + } + return $titles[$nid]; +} + +/** + * Implementation of hook_widget_info(). + * + * We need custom handling of multiple values for the nodereference_select + * widget because we need to combine them into a options list rather + * than display multiple elements. + * + * We will use the content module's default handling for default value. + * + * Callbacks can be omitted if default handing is used. + * They're included here just so this module can be used + * as an example for custom modules that might do things + * differently. + */ +function nodereference_widget_info() { + return array( + 'nodereference_select' => array( + 'label' => t('Select list'), + 'field types' => array('nodereference'), + 'multiple values' => CONTENT_HANDLE_MODULE, + 'callbacks' => array( + 'default value' => CONTENT_CALLBACK_DEFAULT, + ), + ), + 'nodereference_autocomplete' => array( + 'label' => t('Autocomplete text field'), + 'field types' => array('nodereference'), + 'multiple values' => CONTENT_HANDLE_CORE, + 'callbacks' => array( + 'default value' => CONTENT_CALLBACK_DEFAULT, + ), + ), + ); +} + +/** + * Implementation of FAPI hook_elements(). + * + * Any FAPI callbacks needed for individual widgets can be declared here, + * and the element will be passed to those callbacks for processing. + * + * Drupal will automatically theme the element using a theme with + * the same name as the hook_elements key. + * + * Autocomplete_path is not used by text_widget but other widgets can use it + * (see nodereference and userreference). + */ +function nodereference_elements() { + return array( + 'nodereference_select' => array( + '#input' => TRUE, + '#columns' => array('uid'), '#delta' => 0, + '#process' => array('nodereference_select_process'), + ), + 'nodereference_autocomplete' => array( + '#input' => TRUE, + '#columns' => array('name'), '#delta' => 0, + '#process' => array('nodereference_autocomplete_process'), + '#autocomplete_path' => FALSE, + ), + ); +} + +/** + * Implementation of hook_widget(). + * + * Attach a single form element to the form. It will be built out and + * validated in the callback(s) listed in hook_elements. We build it + * out in the callbacks rather than here in hook_widget so it can be + * plugged into any module that can provide it with valid + * $field information. + * + * Content module will set the weight, field name and delta values + * for each form element. This is a change from earlier CCK versions + * where the widget managed its own multiple values. + * + * If there are multiple values for this field, the content module will + * call this function as many times as needed. + * + * @param $form + * the entire form array, $form['#node'] holds node information + * @param $form_state + * the form_state, $form_state['values'][$field['field_name']] + * holds the field's form values. + * @param $field + * the field array + * @param $items + * array of default values for this field + * @param $delta + * the order of this item in the array of subelements (0, 1, 2, etc) + * + * @return + * the form item for a single element for this field + */ +function nodereference_widget(&$form, &$form_state, $field, $items, $delta = 0) { + switch ($field['widget']['type']) { + case 'nodereference_select': + $element = array( + '#type' => 'nodereference_select', + '#default_value' => $items, + ); + break; + + case 'nodereference_autocomplete': + $element = array( + '#type' => 'nodereference_autocomplete', + '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL, + '#value_callback' => 'nodereference_autocomplete_value', + ); + break; + } + return $element; +} + +/** + * Value for a nodereference autocomplete element. + * + * Substitute in the node title for the node nid. + */ +function nodereference_autocomplete_value($element, $edit = FALSE) { + $field_key = $element['#columns'][0]; + if (!empty($element['#default_value'][$field_key])) { + $nid = $element['#default_value'][$field_key]; + $value = db_result(db_query(db_rewrite_sql('SELECT n.title FROM {node} n WHERE n.nid = %d'), $nid)); + $value .= ' [nid:'. $nid .']'; + return array($field_key => $value); + } + return array($field_key => NULL); +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $fields array is in $form['#field_info'][$element['#field_name']]. + */ +function nodereference_select_process($element, $edit, $form_state, $form) { + // The nodereference_select widget doesn't need to create its own + // element, it can wrap around the optionwidgets_select element. + // Add a validation step where the value can be unwrapped. + $field_key = $element['#columns'][0]; + $element[$field_key] = array( + '#type' => 'optionwidgets_select', + '#default_value' => isset($element['#value']) ? $element['#value'] : '', + '#element_validate' => array('optionwidgets_validate', 'nodereference_select_validate'), + + // The following values were set by the content module and need + // to be passed down to the nested element. + '#field_name' => $element['#field_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + ); + return $element; +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + */ +function nodereference_autocomplete_process($element, $edit, $form_state, $form) { + // The nodereference autocomplete widget doesn't need to create its own + // element, it can wrap around the text_textfield element and add an autocomplete + // path and some extra processing to it. + // Add a validation step where the value can be unwrapped. + $field_key = $element['#columns'][0]; + + $element[$field_key] = array( + '#type' => 'text_textfield', + '#default_value' => isset($element['#value']) ? $element['#value'] : '', + '#autocomplete_path' => 'nodereference/autocomplete/'. $element['#field_name'], + '#element_validate' => array('nodereference_autocomplete_validate'), + + // The following values were set by the content module and need + // to be passed down to the nested element. + '#field_name' => $element['#field_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + ); + return $element; +} + +/** + * Validate an select element. + * + * Remove the wrapper layer and set the right element's value. + */ +function nodereference_select_validate($element, &$form_state) { + $field_key = $element['#columns'][0]; + array_pop($element['#parents']); + form_set_value($element, $form_state['values'][$element['#field_name']][$field_key], $form_state); +} + +/** + * Validate an autocomplete element. + * + * Remove the wrapper layer and set the right element's value. + */ +function nodereference_autocomplete_validate($element, &$form_state) { + $field_name = $element['#field_name']; + $field = content_fields($field_name); + $field_key = $element['#columns'][0]; + $delta = $element['#delta']; + $value = $element['#value'][$field_key]; + $nid = NULL; + if (!empty($value)) { + preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $value, $matches); + if (!empty($matches)) { + // explicit nid + list(, $title, $nid) = $matches; + if (!empty($title) && ($n = node_load($nid)) && $title != $n->title) { + form_set_error($element[$field_key], t('%name: Title mismatch. Please check your selection.'), array('%name' => t($element[$field_key]['#title']))); + } + } + else { + // no explicit nid + // TODO : + // the best thing would be to present the user with an additional form, + // allowing the user to choose between valid candidates with the same title + // ATM, we pick the first matching candidate... + $nids = _nodereference_potential_references($field, FALSE, $value, TRUE); + $nid = (!empty($nids)) ? array_shift(array_keys($nids)) : 0; + } + } + form_set_value($element, $nid, $form_state); + return $element; +} + +/** + * Implementation of hook_allowed_values(). + */ +function nodereference_allowed_values($field) { + $options = _nodereference_potential_references($field, TRUE); + foreach ($options as $key => $value) { + $options[$key] = _nodereference_item($field, $value); + } + if (!$field['required']) { + $options = array(0 => '<'. t('none') .'>') + $options; + } + return $options; +} + +/** + * Fetch an array of all candidate referenced nodes, + * for use in presenting the selection form to the user. + */ +function _nodereference_potential_references($field, $return_full_nodes = FALSE, $string = '', $exact_string = false) { + if (module_exists('views') + && isset($field['advanced_view']) + && $field['advanced_view'] != '--' + && ($view = views_get_view($field['advanced_view']))) { + // advanced field : referenceable nodes defined by a view + // let views.module build the query + + $view->init(); + + // TODO is this the right way to do this? + // make sure the fields get included in the query. + + // TODO Which display should we use here? + // In our Views 1 to Views 2 updates we are + // putting fields in the 'default' display. + $display = 'default'; + $view->set_display($display); + $view->display[$display]->style_plugin = 'list'; + + // Add arguments for the view. + if (isset($field['advanced_view_args'])) { + // TODO: Support Tokens using token.module ? + $view_args = array(); + $view_args = array_map('trim', explode(',', $field['advanced_view_args'])); + $view->set_arguments($view_args); + } + + if (isset($string)) { + //We do need title field, so add it if not present (unlikely, but...) + if (!array_key_exists('title', $view->display[$display]->display_options['fields'])) { + $view->add_item($display, 'field', 'node', 'title'); + } + // We use the title filter in the view, so make sure it's there. + if (!array_key_exists('title', $view->display[$display]->display_options['filters'])) { + $view->add_item($display, 'filter', 'node', 'title'); + } + // TODO Is this the best way to set values? + $view->display[$display]->display_options['filters']['title']['value'] = $string; + $view->display[$display]->display_options['filters']['title']['operator'] = $exact_string ? '=' : 'contains'; + } + + // Make sure the query is not cached + $view->is_cacheable = FALSE; + $view->render(); + $options = array(); + foreach ($view->result as $row) { + foreach ($view->field as $field) { + if (!empty($field['handler']) && is_object($field['handler'])) { + $options[$row->nid][] = theme('views_view_field', $view, $field, $row); + } + } + } + return $options; + } + else { + // standard field : referenceable nodes defined by content types + // build the appropriate query + $related_types = array(); + $args = array(); + + if (is_array($field['referenceable_types'])) { + foreach ($field['referenceable_types'] as $related_type) { + if ($related_type) { + $related_types[] = " n.type = '%s'"; + $args[] = $related_type; + } + } + } + + $related_clause = implode(' OR ', $related_types); + + if (!count($related_types)) { + return array(); + } + + if (isset($string)) { + $string_clause = $exact_string ? " AND n.title = '%s'" : " AND n.title LIKE '%%%s%'"; + $related_clause = "(". $related_clause .")". $string_clause; + $args[] = $string; + } + + $result = db_query(db_rewrite_sql("SELECT n.nid, n.title AS node_title, n.type AS node_type FROM {node} n WHERE ". $related_clause ." ORDER BY n.title, n.type"), $args); + } + + $rows = array(); + + while ($node = db_fetch_object($result)) { + if ($return_full_nodes) { + $rows[$node->nid] = $node; + } + else { + $rows[$node->nid] = $node->node_title; + } + } + + return $rows; +} + +/** + * Retrieve a pipe delimited string of autocomplete suggestions + */ +function nodereference_autocomplete($field_name, $string = '') { + $fields = content_fields(); + $field = $fields[$field_name]; + $matches = array(); + + foreach (_nodereference_potential_references($field, TRUE, $string) as $row) { + $matches[$row->node_title .' [nid:'. $row->nid .']'] = _nodereference_item($field, $row, TRUE); + } + drupal_json($matches); +} + +function _nodereference_item($field, $item, $html = FALSE) { + if (module_exists('views') && isset($field['advanced_view']) && $field['advanced_view'] != '--' && ($view = views_get_view($field['advanced_view']))) { + $field_names = array(); + $view->render(); + foreach ($view->field as $name => $viewfield) { + $field_names[] = isset($viewfield->definition['content_field_name']) ? $viewfield->definition['content_field_name'] : $name; + } + $output = theme('nodereference_item_advanced', $item, $field_names, $view); + if (!$html) { + // Views theming runs check_plain (htmlentities) on the values. + // We reverse that with html_entity_decode. + $output = html_entity_decode(strip_tags($output), ENT_QUOTES); + } + } + else { + $output = theme('nodereference_item_simple', $item); + $output = $html ? check_plain($output) : $output; + } + return $output; +} + +function theme_nodereference_item_advanced($item, $field_names, $view) { + $item_fields = array(); + foreach ($item as $delta => $value) { + // remove link tags (ex : for node titles) + $value = preg_replace('/]*>(.*)<\/a>/iU', '$1', $value); + if (!empty($value)) { + $item_fields[] = "$value";; + } + } + $output = implode(' - ', $item_fields); + $output = "$output"; + return $output; +} + +function theme_nodereference_item_simple($item) { + return $item->node_title; +} + +/** + * FAPI theme for an individual elements. + * + * The textfield or select is already rendered by the + * textfield or select themes and the html output + * lives in $element['#children']. Override this theme to + * make custom changes to the output. + * + * $element['#field_name'] contains the field name + * $element['#delta] is the position of this element in the group + */ +function theme_nodereference_select($element) { + return $element['#children']; +} + +function theme_nodereference_autocomplete($element) { + return $element['#children']; +} \ No newline at end of file diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/nodereference/translations/modules-nodereference.de.po ./modules/fields/nodereference/translations/modules-nodereference.de.po --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/nodereference/translations/modules-nodereference.de.po 2008-04-26 09:07:10.000000000 -0400 @@ -0,0 +1,98 @@ +# $Id: modules-nodereference.de.po,v 1.2 2008/04/26 13:07:10 hass Exp $ +# German translation of CCK +# Copyright 2006 Lukas Gangoly +# Copyright 2006 Jakob Petsovits +# Generated from files: +# field.php,v 1.3 2006/04/16 13:47:13 luke +# text.module,v 1.34 2006/06/12 19:59:53 luke +# number.module,v 1.28 2006/05/02 13:52:16 luke +# content_admin.inc,v 1.16 2006/06/12 19:36:54 luke +# content.module,v 1.64 2006/06/12 19:36:54 luke +# nodereference.module,v 1.28 2006/06/12 19:36:54 luke +# optionwidgets.module,v 1.8 2006/05/01 15:45:29 luke +# userreference.module,v 1.24 2006/05/05 14:10:44 luke +# weburl.module,v 1.8 2006/06/12 19:36:54 luke +# +msgid "" +msgstr "" +"Project-Id-Version: CCK\n" +"POT-Creation-Date: 2008-04-24 19:53+0200\n" +"PO-Revision-Date: 2008-04-26 14:17+0100\n" +"Last-Translator: Alexander Hass\n" +"Language-Team: German \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Poedit-Language: German\n" +"X-Poedit-Country: GERMANY\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: modules/nodereference/nodereference.module:68 +msgid "Node reference" +msgstr "Beitragsverweis" + +#: modules/nodereference/nodereference.module:69 +msgid "Store the ID of a related node as an integer value." +msgstr "Speichert die ID des ählichen Beitrages als ganzzahligen Wert." + +#: modules/nodereference/nodereference.module:87 +msgid "Content types that can be referenced" +msgstr "Inhaltstypen, auf die verwiesen werden kann." + +#: modules/nodereference/nodereference.module:96 +msgid "Existing Views" +msgstr "Vorhandene Ansichten" + +#: modules/nodereference/nodereference.module:102 +msgid "Advanced - Nodes that can be referenced (View)" +msgstr "Erweitert - Beiträge, auf die verwiesen werden kann (Ansicht)" + +#: modules/nodereference/nodereference.module:108 +msgid "View" +msgstr "Anzeigen" + +#: modules/nodereference/nodereference.module:111 +msgid "Choose the \"Views module\" view that selects the nodes that can be referenced.
Note :
  • This will discard the \"Content types\" settings above. Use the view's \"filters\" section instead.
  • Use the view's \"fields\" section to display additional informations about candidate nodes on node creation/edition form.
  • Use the view's \"sort criteria\" section to determine the order in which candidate nodes will be displayed.
" +msgstr "Wähle die „Views-Modul“-Ansicht das die Beiträge auswählt, die Referenziert werden können.
Hinweis:
  • Dies wird die Einstellungen der obigen „Inhaltsttypen“ verwerfen. Stattdessen sollte hierfür der „Filter“-Bereich der Ansicht verwendet werden.
  • Um weitere Informationen über Beitragskandidaten für das Erstellungs-/Bearbeitungsformular anzuzeigen, kann das Ansichten Feld verwendet werden.
  • Um die Reihenfolge der Beitragskandidaten anzuzeigen sollte „Sortierkriterium“ von Ansichten verwendet werden.
" + +#: modules/nodereference/nodereference.module:115 +msgid "View arguments" +msgstr "Argumente anzeigen" + +#: modules/nodereference/nodereference.module:118 +msgid "Provide a comma separated list of arguments to pass to the view." +msgstr "Eine kommagetrennte Liste von Argumenten angeben, welche an die Ansicht übergeben werden." + +#: modules/nodereference/nodereference.module:169 +msgid "%name : This post can't be referenced." +msgstr "%name: Auf diesen Beitrag kann nicht verwiesen werden." + +#: modules/nodereference/nodereference.module:194 +msgid "Title (link)" +msgstr "Titel (Verweis)" + +#: modules/nodereference/nodereference.module:199 +msgid "Title (no link)" +msgstr "Titel (kein Verweis)" + +#: modules/nodereference/nodereference.module:501 +msgid "%name: Title mismatch. Please check your selection." +msgstr "%name: Titel falsch. Bitte die Auswahl überprüfen." + +#: modules/nodereference/nodereference.module:15 +msgid "Nodereference autocomplete" +msgstr "Autovervollständigung der Beitragsverweise" + +#: modules/nodereference/nodereference.module:0 +msgid "nodereference" +msgstr "Beitragsverweis" + +#: modules/nodereference/nodereference.info:0 +msgid "Node Reference" +msgstr "Beitragsverweis" + +#: modules/nodereference/nodereference.info:0 +msgid "Defines a field type for referencing one node from another." +msgstr "Definiert einen Feldtyp für den Verweis von einem Beitrag zu einem anderen." + diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/nodereference/translations/modules-nodereference.pot ./modules/fields/nodereference/translations/modules-nodereference.pot --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/nodereference/translations/modules-nodereference.pot 2008-04-24 14:01:07.000000000 -0400 @@ -0,0 +1,89 @@ +# $Id: modules-nodereference.pot,v 1.1 2008/04/24 18:01:07 hass Exp $ +# +# LANGUAGE translation of Drupal (modules-nodereference) +# Copyright YEAR NAME +# Generated from files: +# nodereference.module,v 1.127 2008/04/23 18:02:14 dww +# nodereference.info,v 1.8 2008/04/23 18:02:07 dww +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2008-04-24 19:53+0200\n" +"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" +"Last-Translator: NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" + +#: modules/nodereference/nodereference.module:68 +msgid "Node reference" +msgstr "" + +#: modules/nodereference/nodereference.module:69 +msgid "Store the ID of a related node as an integer value." +msgstr "" + +#: modules/nodereference/nodereference.module:87 +msgid "Content types that can be referenced" +msgstr "" + +#: modules/nodereference/nodereference.module:96 +msgid "Existing Views" +msgstr "" + +#: modules/nodereference/nodereference.module:102 +msgid "Advanced - Nodes that can be referenced (View)" +msgstr "" + +#: modules/nodereference/nodereference.module:108 +msgid "View" +msgstr "" + +#: modules/nodereference/nodereference.module:111 +msgid "Choose the \"Views module\" view that selects the nodes that can be referenced.
Note :
  • This will discard the \"Content types\" settings above. Use the view's \"filters\" section instead.
  • Use the view's \"fields\" section to display additional informations about candidate nodes on node creation/edition form.
  • Use the view's \"sort criteria\" section to determine the order in which candidate nodes will be displayed.
" +msgstr "" + +#: modules/nodereference/nodereference.module:115 +msgid "View arguments" +msgstr "" + +#: modules/nodereference/nodereference.module:118 +msgid "Provide a comma separated list of arguments to pass to the view." +msgstr "" + +#: modules/nodereference/nodereference.module:169 +msgid "%name : This post can't be referenced." +msgstr "" + +#: modules/nodereference/nodereference.module:194 +msgid "Title (link)" +msgstr "" + +#: modules/nodereference/nodereference.module:199 +msgid "Title (no link)" +msgstr "" + +#: modules/nodereference/nodereference.module:501 +msgid "%name: Title mismatch. Please check your selection." +msgstr "" + +#: modules/nodereference/nodereference.module:15 +msgid "Nodereference autocomplete" +msgstr "" + +#: modules/nodereference/nodereference.module:0 +msgid "nodereference" +msgstr "" + +#: modules/nodereference/nodereference.info:0 +msgid "Node Reference" +msgstr "" + +#: modules/nodereference/nodereference.info:0 +msgid "Defines a field type for referencing one node from another." +msgstr "" + diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/number/number.info ./modules/fields/number/number.info --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/number/number.info 2008-05-14 14:53:55.000000000 -0400 @@ -0,0 +1,8 @@ +; $Id: number.info,v 1.7 2008/04/23 18:02:16 dww Exp $ +name = Number +description = Defines numeric field types. +dependencies[] = content +package = Core - fields +core = 7.x + +files[]=number.module diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/number/number.install ./modules/fields/number/number.install --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/number/number.install 2008-04-23 14:02:18.000000000 -0400 @@ -0,0 +1,53 @@ + array('arguments' => array('element' => NULL)), + 'number_formatter_default' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'), + 'number_formatter_us_0' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'), + 'number_formatter_us_1' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'), + 'number_formatter_us_2' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'), + 'number_formatter_be_0' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'), + 'number_formatter_be_1' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'), + 'number_formatter_fr_0' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'), + 'number_formatter_fr_1' => array('arguments' => array('element' => NULL), 'function' => 'theme_number_formatter_generic'), + 'number_formatter_unformatted' => array('arguments' => array('element' => NULL)), + ); +} + +/** + * Implementation of hook_field_info(). + * + * Here we indicate that the content module will use its default + * handling for the view of these fields. + * + * Callbacks can be omitted if default handing is used. + * They're included here just so this module can be used + * as an example for custom modules that might do things + * differently. + */ +function number_field_info() { + return array( + 'number_integer' => array( + 'label' => t('Integer'), + 'description' => t('Store a number in the database as an integer.'), + 'callbacks' => array( + 'tables' => CONTENT_CALLBACK_DEFAULT, + 'arguments' => CONTENT_CALLBACK_DEFAULT, + ), + ), + 'number_decimal' => array( + 'label' => t('Decimal'), + 'description' => t('Store a number in the database in a fixed decimal format.'), + 'callbacks' => array( + 'tables' => CONTENT_CALLBACK_DEFAULT, + 'arguments' => CONTENT_CALLBACK_DEFAULT, + ), + ), + 'number_float' => array( + 'label' => t('Float'), + 'description' => t('Store a number in the database in a floating point format.'), + 'callbacks' => array( + 'tables' => CONTENT_CALLBACK_DEFAULT, + 'arguments' => CONTENT_CALLBACK_DEFAULT, + ), + ), + ); +} + +/** + * Implementation of hook_field_settings(). + */ +function number_field_settings($op, $field) { + switch ($op) { + case 'form': + $form = array(); + $form['min'] = array( + '#type' => 'textfield', + '#title' => t('Minimum'), + '#default_value' => is_numeric($field['min']) ? $field['min'] : '', + ); + $form['max'] = array( + '#type' => 'textfield', + '#title' => t('Maximum'), + '#default_value' => is_numeric($field['max']) ? $field['max'] : '', + ); + if ($field['type'] == 'number_decimal') { + $form['precision'] = array( + '#type' => 'select', + '#options' => drupal_map_assoc(range(10, 32)), + '#title' => t('Precision'), + '#description' => t('The total number of digits to store in the database, including those to the right of the decimal.'), + '#default_value' => is_numeric($field['precision']) ? $field['precision'] : 10, + ); + $form['scale'] = array( + '#type' => 'select', + '#options' => drupal_map_assoc(range(0, 2)), + '#title' => t('Scale'), + '#description' => t('The number of digits to the right of the decimal.'), + '#default_value' => is_numeric($field['scale']) ? $field['scale'] : 2, + ); + $form['decimal'] = array( + '#type' => 'select', + '#options' => array('.' => 'decimal point', ',' => 'comma', ' ' => 'space'), + '#title' => t('Decimal marker'), + '#description' => t('The character users will input to mark the decimal point in forms.'), + '#default_value' => !empty($field['decimal']) ? $field['decimal'] : '.', + ); + } + $form['append']['prefix'] = array( + '#type' => 'textfield', + '#title' => t('Prefix'), + '#size' => 60, + '#default_value' => !empty($field['prefix']) ? $field['prefix'] : '', + '#description' => t('Define a string that should be prefixed to the value, like $ or €. Leave blank for none. Separate singular and plural values with a pipe (pound|pounds).'), + ); + $form['append']['suffix'] = array( + '#type' => 'textfield', + '#title' => t('Suffix'), + '#size' => 60, + '#default_value' => !empty($field['suffix']) ? $field['suffix'] : '', + '#description' => t('Define a string that should suffixed to the value, like m², m/s², kb/s. Leave blank for none. Separate singular and plural values with a pipe (pound|pounds).'), + ); + $form['allowed_values_fieldset'] = array( + '#type' => 'fieldset', + '#title' => t('Allowed values'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['allowed_values_fieldset']['allowed_values'] = array( + '#type' => 'textarea', + '#title' => t('Allowed values list'), + '#default_value' => !empty($field['allowed_values']) ? $field['allowed_values'] : '', + '#required' => FALSE, + '#rows' => 10, + '#description' => t('The possible values this field can contain. Enter one value per line, in the format key|label. The key is the value that will be stored in the database and it must match the field storage type, %type. The label is optional and the key will be used as the label if no label is specified.', array('%type' => $field['type'])), + ); + $form['allowed_values_fieldset']['advanced_options'] = array( + '#type' => 'fieldset', + '#title' => t('PHP code'), + '#collapsible' => TRUE, + '#collapsed' => empty($field['allowed_values_php']), + ); + $form['allowed_values_fieldset']['advanced_options']['allowed_values_php'] = array( + '#type' => 'textarea', + '#title' => t('Code'), + '#default_value' => !empty($field['allowed_values_php']) ? $field['allowed_values_php'] : '', + '#rows' => 6, + '#description' => t('Advanced usage only: PHP code that returns a keyed array of allowed values. Should not include <?php ?> delimiters. If this field is filled out, the array returned by this code will override the allowed values list above.'), + ); + return $form; + + case 'validate': + if ($field['min'] && !is_numeric($field['min'])) { + form_set_error('min', t('"Minimum" must be a number.')); + } + if ($field['max'] && !is_numeric($field['max'])) { + form_set_error('max', t('"Maximum" must be a number.')); + } + break; + + case 'save': + $values = array('prefix', 'suffix', 'min', 'max', 'allowed_values', 'allowed_values_php'); + if ($field['type'] == 'number_decimal') { + $values = array_merge($values, array('precision', 'scale', 'decimal')); + } + return $values; + + case 'database columns': + if ($field['type'] == 'number_integer') { + return array( + 'value' => array('type' => 'int', 'not null' => FALSE, 'sortable' => TRUE), + ); + } + if ($field['type'] == 'number_float') { + return array( + 'value' => array('type' => 'float', 'not null' => FALSE, 'sortable' => TRUE), + ); + } + if ($field['type'] == 'number_decimal') { + $precision = isset($field['precision']) ? $field['precision'] : 10; + $scale = isset($field['scale']) ? $field['scale'] : 2; + return array( + 'value' => array('type' => 'numeric', 'precision' => $precision, 'scale' => $scale, 'not null' => FALSE, 'sortable' => TRUE), + ); + } + + case 'views data': + $allowed_values = content_allowed_values($field); + if (count($allowed_values)) { + $data = content_views_field_views_data($field); + $db_info = content_database_info($field); + $table_alias = content_views_tablename($field); + + // Swap the filter handler to the 'in' operator. + $data[$table_alias][$field['field_name'] .'_value']['filter']['handler'] = 'views_handler_filter_many_to_one_content'; + return $data; + } + break; + } +} + +/** + * Implementation of hook_field(). + */ +function number_field($op, &$node, $field, &$items, $teaser, $page) { + switch ($op) { + case 'validate': + $allowed_values = content_allowed_values($field); + if (is_array($items)) { + foreach ($items as $delta => $item) { + $error_field = $field['field_name'] .']['. $delta .'][value'; + if ($item['value'] != '') { + if (is_numeric($field['min']) && $item['value'] < $field['min']) { + form_set_error($error_field, t('The value of %name may be no smaller than %min.', array('%name' => t($field['widget']['label']), '%min' => $field['min']))); + } + if (is_numeric($field['max']) && $item['value'] > $field['max']) { + form_set_error($error_field, t('The value of %name may be no larger than %max.', array('%name' => t($field['widget']['label']), '%max' => $field['max']))); + } + if (count($allowed_values) && !array_key_exists($item['value'], $allowed_values)) { + form_set_error($error_field, t('Illegal value for %name.', array('%name' => t($field['widget']['label'])))); + } + } + } + } + break; + } +} + +/** + * Implementation of hook_content_is_empty(). + */ +function number_content_is_empty($item, $field) { + if (empty($item['value'])) { + return TRUE; + } + return FALSE; +} + +/** + * Implementation of hook_field_formatter_info(). + */ +function number_field_formatter_info() { + + return array( + 'default' => array('label' => '9999', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_integer', 'number_decimal', 'number_float')), + 'us_0' => array('label' => '9,999', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_integer', 'number_decimal', 'number_float')), + 'us_1' => array('label' => '9,999.9', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_decimal', 'number_float')), + 'us_2' => array('label' => '9,999.99', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_decimal', 'number_float')), + 'be_0' => array('label' => '9.999', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_integer', 'number_decimal', 'number_float')), + 'be_1' => array('label' => '9.999,9', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_decimal', 'number_float')), + 'be_2' => array('label' => '9.999,99', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_decimal', 'number_float')), + 'fr_0' => array('label' => '9 999', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_integer', 'number_decimal', 'number_float')), + 'fr_1' => array('label' => '9 999, 9', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_decimal', 'number_float')), + 'fr_2' => array('label' => '9 999, 99', 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_decimal', 'number_float')), + 'unformatted' => array('label' => t('unformatted'), 'multiple values' => CONTENT_HANDLE_CORE, 'field types' => array('number_integer', 'number_decimal', 'number_float')), + ); +} + +/** + * Proxy theme function for 'unformatted' number field formatter. + */ +function theme_number_formatter_unformatted($element) { + return $element['#item']['value']; +} + +/** + * Proxy theme function for number field formatters. + */ +function theme_number_formatter_generic($element) { + $field = content_fields($element['#field_name'], $element['#type_name']); + $value = $element['#item']['value']; + + if (($allowed_values = content_allowed_values($field))) { + if (isset($allowed_values[$value]) && $allowed_values[$value] != $value) { + return $allowed_values[$value]; + } + } + + switch ($element['#formatter']) { + case 'us_0': + $output = number_format($value, 0, '.', ','); + break; + case 'us_1': + $output = number_format($value, 1, '.', ','); + break; + case 'us_2': + $output = number_format($value, 2, '.', ','); + break; + case 'be_0': + $output = number_format($value, 0, ',', '.'); + break; + case 'be_1': + $output = number_format($value, 1, ',', '.'); + break; + case 'be_2': + $output = number_format($value, 2, ',', '.'); + break; + case 'fr_0': + $output = number_format($value, 0, ', ', ' '); + break; + case 'fr_1': + $output = number_format($value, 1, ', ', ' '); + break; + case 'fr_2': + $output = number_format($value, 2, ', ', ' '); + break; + default: + $output = $value; + break; + } + + $prefixes = explode('|', $field['prefix']); + $suffixes = explode('|', $field['suffix']); + $prefix = (count($prefixes) > 1) ? format_plural($value, $prefixes[0], $prefixes[1]) : $prefixes[0]; + $suffix = (count($suffixes) > 1) ? format_plural($value, $suffixes[0], $suffixes[1]) : $suffixes[0]; + + return $prefix . $output . $suffix; +} + +/** + * Implementation of hook_widget_info(). + * + * Here we indicate that the content module will handle + * the default value and multiple values for these widgets. + * + * Callbacks can be omitted if default handing is used. + * They're included here just so this module can be used + * as an example for custom modules that might do things + * differently. + */ +function number_widget_info() { + return array( + 'number' => array( + 'label' => t('Text field'), + 'field types' => array('number_integer', 'number_decimal', 'number_float'), + 'multiple values' => CONTENT_HANDLE_CORE, + 'callbacks' => array( + 'default value' => CONTENT_CALLBACK_DEFAULT, + ), + ), + ); +} + +/** + * Implementation of FAPI hook_elements(). + * + * Any FAPI callbacks needed for individual widgets can be declared here, + * and the element will be passed to those callbacks for processing. + * + * Drupal will automatically theme the element using a theme with + * the same name as the hook_elements key. + * + * Includes a regex to check for valid values as an additional parameter + * the validator can use. The regex can be overridden if necessary. + */ +function number_elements() { + return array( + 'number' => array( + '#input' => TRUE, + '#columns' => array('value'), '#delta' => 0, + '#process' => array('number_process'), + ), + ); +} + +/** + * Implementation of hook_widget(). + * + * Attach a single form element to the form. It will be built out and + * validated in the callback(s) listed in hook_elements. We build it + * out in the callbacks rather than here in hook_widget so it can be + * plugged into any module that can provide it with valid + * $field information. + * + * Content module will set the weight, field name and delta values + * for each form element. This is a change from earlier CCK versions + * where the widget managed its own multiple values. + * + * If there are multiple values for this field, the content module will + * call this function as many times as needed. + * + * @param $form + * the entire form array, $form['#node'] holds node information + * @param $form_state + * the form_state, $form_state['values'] holds the form values. + * @param $field + * the field array + * @param $delta + * the order of this item in the array of subelements (0, 1, 2, etc) + * + * @return + * the form item for a single element for this field + */ +function number_widget(&$form, &$form_state, $field, $items, $delta = 0) { + $element = array( + '#type' => $field['widget']['type'], + '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL, + ); + return $element; +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $fields array is in $form['#field_info'][$element['#field_name']]. + */ +function number_process($element, $edit, $form_state, $form) { + $field_name = $element['#field_name']; + $field = $form['#field_info'][$field_name]; + $field_key = $element['#columns'][0]; + + $value = isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : ''; + $value = isset($field['decimal']) ? str_replace('.', $field['decimal'], $value) : $value; + $element[$field_key] = array( + '#type' => 'textfield', + '#title' => $element['#title'], + '#description' => $element['#description'], + '#required' => $element['#required'], + '#default_value' => $value, + // Need to allow a slightly larger size that the field length to allow + // for some configurations where all characters won't fit in input field. + '#size' => isset($field['precision']) ? $field['precision'] + 2 : 12, + '#maxlength' => isset($field['precision']) ? $field['precision'] : 10, + '#attributes' => array('class' => 'number'), + ); + + $prefixes = array(); + $suffixes = array(); + if (!empty($field['prefix'])) { + $prefixes = explode('|', $field['prefix']); + $element[$field_key]['#field_prefix'] = array_pop($prefixes); + } + if (!empty($field['suffix'])) { + $suffixes = explode('|', $field['suffix']); + $element[$field_key]['#field_suffix'] = array_pop($suffixes); + } + switch ($field['type']) { + case 'number_float': + $element['#element_validate'] = array('number_float_validate'); + break; + case 'number_integer': + $element['#element_validate'] = array('number_integer_validate'); + break; + case 'number_decimal': + $element['#element_validate'] = array('number_decimal_validate'); + $element['#decimal'] = isset($field['decimal']) ? $field['decimal'] : '.'; + $element['#precision'] = isset($field['precision']) ? $field['precision'] : 10; + $element['#scale'] = isset($field['scale']) ? $field['scale'] : 2; + break; + } + return $element; +} + +/** + * FAPI validation of an individual float element. + */ +function number_float_validate($element, &$form_state) { + $field_key = $element['#columns'][0]; + $value = $element['#value'][$field_key]; + if (($element[$field_key]['#required'] || !empty($value))) { + $start = $value; + $value = preg_replace('@[^-0-9\.]@', '', $value); + if ($start != $value) { + // No reason to make this an error, just need to inform user what has changed. + drupal_set_message(t('Only numbers and decimals are allowed in %field. %start was changed to %value.', array( + '%field' => t($element[$field_key]['#title']), '%start' => $start, '%value' => $value))); + } + form_set_value($element[$field_key], $value, $form_state); + } +} + +/** + * FAPI validation of an individual integer element. + */ +function number_integer_validate($element, &$form_state) { + $field_key = $element['#columns'][0]; + $value = $element['#value'][$field_key]; + if (($element[$field_key]['#required'] || !empty($value))) { + $start = $value; + $value = preg_replace('@[^-0-9]@', '', $value); + if ($start != $value) { + // No reason to make this an error, just need to inform user what has changed. + drupal_set_message(t('Only numbers are allowed in %field. %start was changed to %value.', array( + '%field' => t($element[$field_key]['#title']), '%start' => $start, '%value' => $value))); + } + form_set_value($element[$field_key], $value, $form_state); + } +} + +/** + * FAPI validation of an individual decimal element. + */ +function number_decimal_validate($element, &$form_state) { + $field_key = $element['#columns'][0]; + $value = $element['#value'][$field_key]; + if (($element[$field_key]['#required'] || !empty($value))) { + $start = $value; + $value = str_replace($element['#decimal'], '.', $value); + $value = preg_replace('@[^-0-9\.]@', '', $value); + if ($start != $value) { + // No reason to make this an error, just need to inform user what has changed. + drupal_set_message( t('Only numbers and the decimal character (%decimal) are allowed in %field. %start was changed to %value.', array( + '%decimal' => $element['#decimal'], '%field' => t($element[$field_key]['#title']), '%start' => $start, '%value' => $value))); + } + $value = round($value, $element['#scale']); + form_set_value($element[$field_key], $value, $form_state); + } +} + +/** + * FAPI theme for an individual number element. + * + * The textfield is already rendered by the textfield + * theme and the HTML output lives in $element['#children']. + * Override this theme to make custom changes to the output. + * + * $element['#field_name'] contains the field name + * $element['#delta] is the position of this element in the group + */ +function theme_number($element) { + return $element['#children']; +} \ No newline at end of file diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/number/translations/modules-number.de.po ./modules/fields/number/translations/modules-number.de.po --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/number/translations/modules-number.de.po 2008-04-26 09:07:10.000000000 -0400 @@ -0,0 +1,146 @@ +# $Id: modules-number.de.po,v 1.2 2008/04/26 13:07:10 hass Exp $ +# German translation of CCK +# Copyright 2006 Lukas Gangoly +# Copyright 2006 Jakob Petsovits +# Generated from files: +# field.php,v 1.3 2006/04/16 13:47:13 luke +# text.module,v 1.34 2006/06/12 19:59:53 luke +# number.module,v 1.28 2006/05/02 13:52:16 luke +# content_admin.inc,v 1.16 2006/06/12 19:36:54 luke +# content.module,v 1.64 2006/06/12 19:36:54 luke +# nodereference.module,v 1.28 2006/06/12 19:36:54 luke +# optionwidgets.module,v 1.8 2006/05/01 15:45:29 luke +# userreference.module,v 1.24 2006/05/05 14:10:44 luke +# weburl.module,v 1.8 2006/06/12 19:36:54 luke +# +msgid "" +msgstr "" +"Project-Id-Version: CCK\n" +"POT-Creation-Date: 2008-04-24 19:53+0200\n" +"PO-Revision-Date: 2008-04-26 14:43+0100\n" +"Last-Translator: Alexander Hass\n" +"Language-Team: German \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Poedit-Language: German\n" +"X-Poedit-Country: GERMANY\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: modules/number/number.module:41 +msgid "Integer" +msgstr "Ganzzahl" + +#: modules/number/number.module:42 +msgid "Store a number in the database as an integer." +msgstr "Speichert die Zahl in der Datenbank als Ganzzahl." + +#: modules/number/number.module:49 +msgid "Decimal" +msgstr "Dezimalzahl" + +#: modules/number/number.module:50 +msgid "Store a number in the database in a fixed decimal format." +msgstr "Speichert die Zahl in der Datenbank in einem festen Dezimalformat." + +#: modules/number/number.module:57 +msgid "Float" +msgstr "Fließkommazahl" + +#: modules/number/number.module:58 +msgid "Store a number in the database in a floating point format." +msgstr "Speichert die Zahl in der Datenbank in einem Fließkommaformat." + +#: modules/number/number.module:76 +msgid "Minimum" +msgstr "Minimum" + +#: modules/number/number.module:81 +msgid "Maximum" +msgstr "Maximum" + +#: modules/number/number.module:88 +msgid "Precision" +msgstr "Präzision" + +#: modules/number/number.module:89 +msgid "The total number of digits to store in the database, including those to the right of the decimal." +msgstr "Die gesamte Anzahl der in der Datenbank zu speichernden Stellen, inclusive der rechts vom Dezimaltrennzeichen angegebenen." + +#: modules/number/number.module:95 +msgid "Scale" +msgstr "Skalierung" + +#: modules/number/number.module:96 +msgid "The number of digits to the right of the decimal." +msgstr "Die Anzahl der rechts vom Dezimaltrennzeichen angegebenen Stellen." + +#: modules/number/number.module:102 +msgid "Decimal marker" +msgstr "Dezimalzeichen" + +#: modules/number/number.module:103 +msgid "The character users will input to mark the decimal point in forms." +msgstr "Das von Benutzern in Formularen zu verwendende Zeichen zur Trennung von Dezimalenstellen." + +#: modules/number/number.module:109 +msgid "Prefix" +msgstr "Präfix" + +#: modules/number/number.module:112 +msgid "Define a string that should be prefixed to the value, like $ or €. Leave blank for none. Separate singular and plural values with a pipe (pound|pounds)." +msgstr "Eine Zeichenkette angeben, welche dem Wert vorrangestellt werden soll, z.b. $ oder €. Ansonsten freilassen. Werte für Einzahl und Mehrzahl mit einer Pipe trennen (pound|pounds)." + +#: modules/number/number.module:116 +msgid "Suffix" +msgstr "Suffix" + +#: modules/number/number.module:119 +msgid "Define a string that should suffixed to the value, like m², m/s², kb/s. Leave blank for none. Separate singular and plural values with a pipe (pound|pounds)." +msgstr "Eine Zeichenkette angeben, welche dem Wert angehängt werden soll, z.b. m², m/s², kb/s. Ansonsten freilassen. Werte für Einzahl und Mehrzahl mit einer Pipe trennen (pound|pounds)." + +#: modules/number/number.module:152 +msgid "\"Minimum\" must be a number." +msgstr "„Minimum“ muss eine Zahl sein." + +#: modules/number/number.module:155 +msgid "\"Maximum\" must be a number." +msgstr "„Maximum“ muss eine Zahl sein." + +#: modules/number/number.module:212 +msgid "The value of %name may be no smaller than %min." +msgstr "Der Wert von %name darf nicht kleiner als %min sein." + +#: modules/number/number.module:215 +msgid "The value of %name may be no larger than %max." +msgstr "Der Wert von %name darf nicht grösser als %max sein." + +#: modules/number/number.module:253 +msgid "unformatted" +msgstr "unformatiert" + +#: modules/number/number.module:466 +msgid "Only numbers and decimals are allowed in %field. %start was changed to %value." +msgstr "In %field sind nur Dezimalzahlen zulässig. %start wurde auf %value geändert." + +#: modules/number/number.module:484 +msgid "Only numbers are allowed in %field. %start was changed to %value." +msgstr "In %field sind nur Zahlen zulässig. %start wurde auf %value geändert." + +#: modules/number/number.module:503 +msgid "Only numbers and the decimal character (%decimal) are allowed in %field. %start was changed to %value." +msgstr "In %field sind nur Zahlen und das Dezimaltrennzeichen (%decimal) zulässig. %start wurde auf %value geändert." + +#: modules/number/number.module:0 +msgid "number" +msgstr "Zahl" + +#: modules/number/number.info:0 +msgid "Number" +msgstr "Zahl" + +#: modules/number/number.info:0 +msgid "Defines numeric field types." +msgstr "Definiert einen numerischen Feldtyp." + diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/number/translations/modules-number.pot ./modules/fields/number/translations/modules-number.pot --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/number/translations/modules-number.pot 2008-04-24 14:01:08.000000000 -0400 @@ -0,0 +1,137 @@ +# $Id: modules-number.pot,v 1.1 2008/04/24 18:01:08 hass Exp $ +# +# LANGUAGE translation of Drupal (modules-number) +# Copyright YEAR NAME +# Generated from files: +# number.module,v 1.90 2008/04/23 18:02:22 dww +# number.info,v 1.7 2008/04/23 18:02:16 dww +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2008-04-24 19:53+0200\n" +"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" +"Last-Translator: NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" + +#: modules/number/number.module:41 +msgid "Integer" +msgstr "" + +#: modules/number/number.module:42 +msgid "Store a number in the database as an integer." +msgstr "" + +#: modules/number/number.module:49 +msgid "Decimal" +msgstr "" + +#: modules/number/number.module:50 +msgid "Store a number in the database in a fixed decimal format." +msgstr "" + +#: modules/number/number.module:57 +msgid "Float" +msgstr "" + +#: modules/number/number.module:58 +msgid "Store a number in the database in a floating point format." +msgstr "" + +#: modules/number/number.module:76 +msgid "Minimum" +msgstr "" + +#: modules/number/number.module:81 +msgid "Maximum" +msgstr "" + +#: modules/number/number.module:88 +msgid "Precision" +msgstr "" + +#: modules/number/number.module:89 +msgid "The total number of digits to store in the database, including those to the right of the decimal." +msgstr "" + +#: modules/number/number.module:95 +msgid "Scale" +msgstr "" + +#: modules/number/number.module:96 +msgid "The number of digits to the right of the decimal." +msgstr "" + +#: modules/number/number.module:102 +msgid "Decimal marker" +msgstr "" + +#: modules/number/number.module:103 +msgid "The character users will input to mark the decimal point in forms." +msgstr "" + +#: modules/number/number.module:109 +msgid "Prefix" +msgstr "" + +#: modules/number/number.module:112 +msgid "Define a string that should be prefixed to the value, like $ or €. Leave blank for none. Separate singular and plural values with a pipe (pound|pounds)." +msgstr "" + +#: modules/number/number.module:116 +msgid "Suffix" +msgstr "" + +#: modules/number/number.module:119 +msgid "Define a string that should suffixed to the value, like m², m/s², kb/s. Leave blank for none. Separate singular and plural values with a pipe (pound|pounds)." +msgstr "" + +#: modules/number/number.module:152 +msgid "\"Minimum\" must be a number." +msgstr "" + +#: modules/number/number.module:155 +msgid "\"Maximum\" must be a number." +msgstr "" + +#: modules/number/number.module:212 +msgid "The value of %name may be no smaller than %min." +msgstr "" + +#: modules/number/number.module:215 +msgid "The value of %name may be no larger than %max." +msgstr "" + +#: modules/number/number.module:253 +msgid "unformatted" +msgstr "" + +#: modules/number/number.module:466 +msgid "Only numbers and decimals are allowed in %field. %start was changed to %value." +msgstr "" + +#: modules/number/number.module:484 +msgid "Only numbers are allowed in %field. %start was changed to %value." +msgstr "" + +#: modules/number/number.module:503 +msgid "Only numbers and the decimal character (%decimal) are allowed in %field. %start was changed to %value." +msgstr "" + +#: modules/number/number.module:0 +msgid "number" +msgstr "" + +#: modules/number/number.info:0 +msgid "Number" +msgstr "" + +#: modules/number/number.info:0 +msgid "Defines numeric field types." +msgstr "" + diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/optionwidgets/optionwidgets.info ./modules/fields/optionwidgets/optionwidgets.info --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/optionwidgets/optionwidgets.info 2008-05-14 14:53:55.000000000 -0400 @@ -0,0 +1,8 @@ +; $Id: optionwidgets.info,v 1.7 2008/04/23 18:02:24 dww Exp $ +name = Option Widgets +description = Defines selection, check box and radio button widgets for text and numeric fields. +dependencies[] = content +package = Core - fields +core = 7.x + +files[]=optionwidgets.module diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/optionwidgets/optionwidgets.install ./modules/fields/optionwidgets/optionwidgets.install --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/optionwidgets/optionwidgets.install 2008-04-23 14:02:26.000000000 -0400 @@ -0,0 +1,59 @@ +'. t('Create a list of options as a list in Allowed values or as an array in PHP code. These values will be the same for %field in all content types.', array('%field' => $form['widget']['label']['#default_value'])) .'

'; + if ($form['widget']['widget_type']['#default_value'] == 'optionwidgets_onoff') { + $output .= '

'. t("For a 'single on/off checkbox' widget, define the 'off' value first, then the 'on' value in the Allowed values section. Note that the checkbox will be labeled with the label of the 'on' value.") .'

'; + } + elseif ($form['widget']['widget_type']['#default_value'] == 'optionwidgets_buttons') { + $output .= '

'. t("The 'checkboxes/radio buttons' widget will display checkboxes if the multiple values option is selected for this field, otherwise radios will be displayed.") .'

'; + } + if (in_array($form['type']['#value'], array('text', 'number_integer', 'number_float', 'number_decimal')) + && in_array($form['widget']['widget_type']['#default_value'], array('optionwidgets_onoff', 'optionwidgets_buttons', 'optionwidgets_select'))) { + $form['field']['allowed_values_fieldset']['#collapsed'] = FALSE; + $form['field']['allowed_values_fieldset']['#description'] = $output; + } + } +} + +/** + * Implementation of hook_theme(). + */ +function optionwidgets_theme() { + return array( + 'optionwidgets_select' => array( + 'arguments' => array('element' => NULL), + ), + 'optionwidgets_buttons' => array( + 'arguments' => array('element' => NULL), + ), + 'optionwidgets_onoff' => array( + 'arguments' => array('element' => NULL), + ), + 'optionwidgets_none' => array( + 'arguments' => array('widget_type' => NULL, 'field_name' => NULL, 'node_type' => NULL), + ), + ); +} + +/** + * Implementation of hook_widget_info(). + * + * We need custom handling of multiple values because we need + * to combine them into a options list rather than display + * multiple elements. We will use the content module's default + * handling for default values. + * + * Callbacks can be omitted if default handing is used. + * They're included here just so this module can be used + * as an example for custom modules that might do things + * differently. + */ +function optionwidgets_widget_info() { + + return array( + 'optionwidgets_select' => array( + 'label' => t('Select list'), + 'field types' => array('text', 'number_integer', 'number_decimal', 'number_float'), + 'multiple values' => CONTENT_HANDLE_MODULE, + 'callbacks' => array( + 'default value' => CONTENT_CALLBACK_DEFAULT, + ), + ), + 'optionwidgets_buttons' => array( + 'label' => t('Check boxes/radio buttons'), + 'field types' => array('text', 'number_integer', 'number_decimal', 'number_float'), + 'multiple values' => CONTENT_HANDLE_MODULE, + 'callbacks' => array( + 'default value' => CONTENT_CALLBACK_DEFAULT, + ), + ), + 'optionwidgets_onoff' => array( + 'label' => t('Single on/off checkbox'), + 'field types' => array('text', 'number_integer', 'number_decimal', 'number_float'), + 'multiple values' => CONTENT_HANDLE_MODULE, + 'callbacks' => array( + 'default value' => CONTENT_CALLBACK_DEFAULT, + ), + ), + ); +} + +/** + * Implementation of FAPI hook_elements(). + * + * Any FAPI callbacks needed for individual widgets can be declared here, + * and the element will be passed to those callbacks for processing. + * + * Drupal will automatically theme the element using a theme with + * the same name as the hook_elements key. + */ +function optionwidgets_elements() { + return array( + 'optionwidgets_select' => array( + '#input' => TRUE, + '#columns' => array('value'), '#delta' => 0, + '#element_validate' => array('optionwidgets_validate'), + '#process' => array('optionwidgets_select_process'), + ), + 'optionwidgets_buttons' => array( + '#input' => TRUE, + '#columns' => array('value'), '#delta' => 0, + '#element_validate' => array('optionwidgets_validate'), + '#process' => array('optionwidgets_buttons_process'), + ), + 'optionwidgets_onoff' => array( + '#input' => TRUE, + '#columns' => array('value'), '#delta' => 0, + '#element_validate' => array('optionwidgets_validate'), + '#process' => array('optionwidgets_onoff_process'), + ), + ); +} + +/** + * Implementation of hook_widget(). + * + * Attach a single form element to the form. It will be built out and + * validated in the callback(s) listed in hook_elements. We build it + * out in the callbacks rather than here in hook_widget so it can be + * plugged into any module that can provide it with valid + * $field information. + * + * Content module will set the weight, field name and delta values + * for each form element. This is a change from earlier CCK versions + * where the widget managed its own multiple values. + * + * If there are multiple values for this field, the content module will + * call this function as many times as needed. + * + * @param $form + * the entire form array, $form['#node'] holds node information + * @param $form_state + * the form_state, $form_state['values'] holds the form values. + * @param $field + * the field array + * @param $items + * an array of default values for this element + * @param $delta + * the order of this item in the array of subelements (0, 1, 2, etc) + * + * @return + * the form item for a single element for this field + */ +function optionwidgets_widget(&$form, &$form_state, $field, $items, $delta = NULL) { + $element = array( + '#type' => $field['widget']['type'], + '#default_value' => !empty($items) ? $items : array(), + ); + return $element; +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $fields array is in $form['#field_info'][$element['#field_name']]. + */ +function optionwidgets_buttons_process($element, $edit, &$form_state, $form) { + $field_name = $element['#field_name']; + $field = $form['#field_info'][$field_name]; + $field_key = $element['#columns'][0]; + + if (!$form_state['submitted']) { + $element['#value'] = optionwidgets_data2form($element, $element['#default_value'], $field); + } + $options = optionwidgets_options($field); + $element[$field_key] = array( + '#type' => $field['multiple'] ? 'checkboxes' : 'radios', + '#title' => $element['#title'], + '#description' => $element['#description'], + '#required' => $field['required'], + '#multiple' => $field['multiple'], + '#options' => $options, + '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL, + ); + + // Make sure field info will be available to the validator which + // does not get the values in $form. + $form_state['#field_info'][$field['field_name']] = $field; + return $element; +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $fields array is in $form['#field_info'][$element['#field_name']]. + */ +function optionwidgets_select_process($element, $edit, &$form_state, $form) { + $field_name = $element['#field_name']; + $field = $form['#field_info'][$field_name]; + $field_key = $element['#columns'][0]; + + if (!$form_state['submitted']) { + $element['#value'] = optionwidgets_data2form($element, $element['#default_value'], $field); + } + $options = optionwidgets_options($field); + $element[$field_key] = array( + '#type' => 'select', + '#title' => $element['#title'], + '#description' => $element['#description'], + '#required' => $field['required'], + '#multiple' => $field['multiple'], + '#options' => $options, + '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL, + ); + + // Make sure field info will be available to the validator which + // does not get the values in $form. + + // TODO for some reason putting the $field array into $form_state['storage'] + // causes the node's hook_form_alter to be invoked twice, garbling the + // results. Need to investigate why that is happening (a core bug?), but + // in the meantime avoid using $form_state['storage'] to store anything. + + $form_state['#field_info'][$field['field_name']] = $field; + return $element; +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $fields array is in $form['#field_info'][$element['#field_name']]. + */ +function optionwidgets_onoff_process($element, $edit, &$form_state, $form) { + $field_name = $element['#field_name']; + $field = $form['#field_info'][$field_name]; + $field_key = $element['#columns'][0]; + + if (!$form_state['submitted']) { + $element['#value'] = optionwidgets_data2form($element, $element['#value'], $field); + } + $options = optionwidgets_options($field); + $keys = array_keys($options); + $on_value = !empty($keys) ? $keys[1] : NULL; + $element[$field_key] = array( + '#type' => 'checkbox', + '#title' => $options[$on_value], + '#description' => t($field['widget']['description']), + '#default_value' => isset($element['#value'][$field_key][0]) ? $element['#value'][$field_key][0] == $on_value : FALSE, + '#return_value' => $on_value, + ); + + // Make sure field info will be available to the validator which + // does not get the values in $form. + $form_state['#field_info'][$field['field_name']] = $field; + return $element; +} + +/** + * FAPI function to validate optionwidgets element. + * + * Transpose selections from field => delta to delta => field, + * turning multiple selected options into multiple parent elements. + * Immediate parent is the delta, need to get back to parent's parent + * to create multiple elements. + */ +function optionwidgets_validate($element, &$form_state) { + $field = $form_state['#field_info'][$element['#field_name']]; + $updated = optionwidgets_form2data($element, $element['#value'], $field); + form_set_value($element, $updated, $form_state); +} + +/** + * Helper function to transpose the values as stored in the database + * to the format the widget needs. Can be called anywhere this + * transformation is needed. + */ +function optionwidgets_data2form($element, $items, $field) { + $field_key = $element['#columns'][0]; + $options = optionwidgets_options($field); + + $items_transposed = content_transpose_array_rows_cols($items); + $values = (isset($items_transposed[$field_key]) && is_array($items_transposed[$field_key])) ? $items_transposed[$field_key] : array(); + $keys = array(); + foreach ($values as $value) { + $key = array_search($value, array_keys($options)); + if (isset($key)) { + $keys[] = $value; + } + } + if ($field['multiple'] || $element['#type'] == 'optionwidgets_onoff') { + return array($field_key => $keys); + } + else { + return array($field_key => reset($keys)); + } +} + +/** + * Helper function to transpose the values returned by submitting the widget + * to the format to be stored in the field. Can be called anywhere this + * transformation is needed. + */ +function optionwidgets_form2data($element, $items, $field) { + $field_key = $element['#columns'][0]; + $options = optionwidgets_options($field); + + $keys = isset($items[$field_key]) ? $items[$field_key] : array(); + if (!is_array($keys)) { + $keys = array($keys); + } + $values = array(); + foreach ($keys as $key => $value) { + if (array_key_exists($value, $options)) { + $values[] = $value; + } + } + if (empty($values)) { + if ($element['#type'] == 'optionwidgets_onoff') { + $keys = array_keys($options); + $values[] = array_key_exists(0, $keys) ? $keys[0] : ''; + } + else { + $values[] = NULL; + } + } + return content_transpose_array_rows_cols(array($field_key => $values)); +} + +/** + * Helper function for finding the allowed values list for a field. + * + * See if there is a module hook for the option values. + * Otherwise, try content_allowed_values() for an options list. + */ +function optionwidgets_options($field) { + $function = $field['module'] .'_allowed_values'; + $options = function_exists($function) ? $function($field) : (array) content_allowed_values($field); + // Add an empty choice for non required radios and single-selects. + if ($field['widget']['type'] != 'optionwidgets_onoff' && !$field['required'] && !$field['multiple']) { + $options = array('' => theme('optionwidgets_none', $field)) + $options; + } + return $options; +} + +/** + * Theme the label for the empty value for options that are not required. + * The default theme will display N/A for a radio list and blank for a select. + */ +function theme_optionwidgets_none($field) { + switch ($field['widget']['type']) { + case 'optionwidgets_buttons': + return t('N/A'); + default : + return ''; + } +} + +/** + * FAPI themes for optionwidgets. + * + * The select, checkboxes or radios are already rendered by the + * select, checkboxes, or radios themes and the HTML output + * lives in $element['#children']. Override this theme to + * make custom changes to the output. + * + * $element['#field_name'] contains the field name + * $element['#delta] is the position of this element in the group + */ +function theme_optionwidgets_select($element) { + return $element['#children']; +} + +function theme_optionwidgets_onoff($element) { + return $element['#children']; +} + +function theme_optionwidgets_buttons($element) { + return $element['#children']; +} \ No newline at end of file diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/optionwidgets/translations/modules-optionwidgets.de.po ./modules/fields/optionwidgets/translations/modules-optionwidgets.de.po --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/optionwidgets/translations/modules-optionwidgets.de.po 2008-04-24 15:00:40.000000000 -0400 @@ -0,0 +1,66 @@ +# $Id: modules-optionwidgets.de.po,v 1.1 2008/04/24 19:00:40 hass Exp $ +# German translation of CCK +# Copyright 2006 Lukas Gangoly +# Copyright 2006 Jakob Petsovits +# Generated from files: +# field.php,v 1.3 2006/04/16 13:47:13 luke +# text.module,v 1.34 2006/06/12 19:59:53 luke +# number.module,v 1.28 2006/05/02 13:52:16 luke +# content_admin.inc,v 1.16 2006/06/12 19:36:54 luke +# content.module,v 1.64 2006/06/12 19:36:54 luke +# nodereference.module,v 1.28 2006/06/12 19:36:54 luke +# optionwidgets.module,v 1.8 2006/05/01 15:45:29 luke +# userreference.module,v 1.24 2006/05/05 14:10:44 luke +# weburl.module,v 1.8 2006/06/12 19:36:54 luke +# +msgid "" +msgstr "" +"Project-Id-Version: CCK\n" +"POT-Creation-Date: 2008-04-24 19:53+0200\n" +"PO-Revision-Date: 2008-04-24 20:34+0100\n" +"Last-Translator: Alexander Hass\n" +"Language-Team: German \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Poedit-Language: German\n" +"X-Poedit-Country: GERMANY\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: modules/optionwidgets/optionwidgets.module:10 +msgid "Create a list of options as a list in Allowed values or as an array in PHP code. These values will be the same for %field in all content types." +msgstr "Eine Liste von Optionen als eine Liste in gültigen Werten oder als ein Array in PHP-Code erstellen. Diese Werte werden in allen Inhaltsstypen mit %field gleich sein." + +#: modules/optionwidgets/optionwidgets.module:12 +msgid "For a 'single on/off checkbox' widget, define the 'off' value first, then the 'on' value in the Allowed values section. Note that the checkbox will be labeled with the label of the 'on' value." +msgstr "Für ein einzelnes „an/aus“ Checkbox-Steuerelement sollte zuerst der ‚aus‘ Wert und dann der ‚an‘ Wert im Bereich der gültigen Werte angegeben werden. Die Checkbox wird mit der Beschriftung des ‚an‘ Wertes beschriftet." + +#: modules/optionwidgets/optionwidgets.module:15 +msgid "The 'checkboxes/radio buttons' widget will display checkboxes if the multiple values option is selected for this field, otherwise radios will be displayed." +msgstr "Das ‚Checkboxen/Auswahl-Knöpfe‘-Steuerelement wird Checkboxen anzeigen, wenn die Option für mehrere Werte bei diesem Feld ausgewählt wurde, ansonsten werden Auswahl-Knöpfe anzeigt." + +#: modules/optionwidgets/optionwidgets.module:70 +msgid "Check boxes/radio buttons" +msgstr "Checkboxen/Auswahl-Knöpfe" + +#: modules/optionwidgets/optionwidgets.module:78 +msgid "Single on/off checkbox" +msgstr "Einzelne an/aus Checkbox" + +#: modules/optionwidgets/optionwidgets.module:357 +msgid "N/A" +msgstr "k.A." + +#: modules/optionwidgets/optionwidgets.module:0 +msgid "optionwidgets" +msgstr "Options-Steuerelemente" + +#: modules/optionwidgets/optionwidgets.info:0 +msgid "Option Widgets" +msgstr "Options-Steuerelemente" + +#: modules/optionwidgets/optionwidgets.info:0 +msgid "Defines selection, check box and radio button widgets for text and numeric fields." +msgstr "Definiert Auswahl-, Checkbox- und Radiobutton-Steuerelemente für Texte und numerische Felder." + diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/optionwidgets/translations/modules-optionwidgets.pot ./modules/fields/optionwidgets/translations/modules-optionwidgets.pot --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/optionwidgets/translations/modules-optionwidgets.pot 2008-04-24 14:01:08.000000000 -0400 @@ -0,0 +1,57 @@ +# $Id: modules-optionwidgets.pot,v 1.1 2008/04/24 18:01:08 hass Exp $ +# +# LANGUAGE translation of Drupal (modules-optionwidgets) +# Copyright YEAR NAME +# Generated from files: +# optionwidgets.module,v 1.61 2008/04/23 18:02:29 dww +# optionwidgets.info,v 1.7 2008/04/23 18:02:24 dww +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2008-04-24 19:53+0200\n" +"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" +"Last-Translator: NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" + +#: modules/optionwidgets/optionwidgets.module:10 +msgid "Create a list of options as a list in Allowed values or as an array in PHP code. These values will be the same for %field in all content types." +msgstr "" + +#: modules/optionwidgets/optionwidgets.module:12 +msgid "For a 'single on/off checkbox' widget, define the 'off' value first, then the 'on' value in the Allowed values section. Note that the checkbox will be labeled with the label of the 'on' value." +msgstr "" + +#: modules/optionwidgets/optionwidgets.module:15 +msgid "The 'checkboxes/radio buttons' widget will display checkboxes if the multiple values option is selected for this field, otherwise radios will be displayed." +msgstr "" + +#: modules/optionwidgets/optionwidgets.module:70 +msgid "Check boxes/radio buttons" +msgstr "" + +#: modules/optionwidgets/optionwidgets.module:78 +msgid "Single on/off checkbox" +msgstr "" + +#: modules/optionwidgets/optionwidgets.module:357 +msgid "N/A" +msgstr "" + +#: modules/optionwidgets/optionwidgets.module:0 +msgid "optionwidgets" +msgstr "" + +#: modules/optionwidgets/optionwidgets.info:0 +msgid "Option Widgets" +msgstr "" + +#: modules/optionwidgets/optionwidgets.info:0 +msgid "Defines selection, check box and radio button widgets for text and numeric fields." +msgstr "" + diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/text/text.info ./modules/fields/text/text.info --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/text/text.info 2008-05-14 14:53:55.000000000 -0400 @@ -0,0 +1,8 @@ +; $Id: text.info,v 1.9 2008/04/23 18:02:31 dww Exp $ +name = Text +description = Defines simple text field types. +dependencies[] = content +package = Core - fields +core = 7.x + +files[]=text.module diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/text/text.install ./modules/fields/text/text.install --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/text/text.install 2008-04-23 14:02:33.000000000 -0400 @@ -0,0 +1,105 @@ + 1) ? 'text_textarea' : 'text_textfield'; + $ret[] = update_sql("UPDATE {". content_instance_tablename() ."} SET widget_module = 'text', widget_type = '". $new_widget_type ."' WHERE field_name = '{$field_instance['field_name']}' AND type_name = '{$field_instance['type_name']}'"); + } + content_associate_fields('text'); + return $ret; +} + +/** + * Set all columns to accept NULL values and set empty string values in the + * database to NULL. + * + * Leaving it up to module developers to handle conversion of numbers to + * NULL values, since there are times when zeros are valid data and times + * when they should be NULL. + * + */ +function text_update_6001() { + $ret = array(); + + // Get the latest cache values and schema. + content_clear_type_cache(TRUE, TRUE); + + include_once('./'. drupal_get_path('module', 'content') .'/content.install'); + $types = content_types_install(); + foreach ($types as $type_name => $fields) { + foreach ($fields as $field) { + switch ($field['type']) { + case 'text': + $db_info = content_database_info($field); + $table = $db_info['table']; + foreach ($db_info['columns'] as $column => $attributes) { + $attributes['not null'] = FALSE; + $column = $attributes['column']; + db_change_field($ret, $table, $column, $column, $attributes); + // TODO : errors on text/blob columns : no default value allowed (!) + db_field_set_no_default($ret, $table, $column); + if ($attributes['type'] == 'varchar' || $attributes['type'] == 'text') { + $ret[] = update_sql("UPDATE {". $table ."} SET ". $column ." = NULL WHERE ". $column ." = ''"); + } + else { + // TODO : replace format = 0 with format = NULL ?? Is this right ? + $ret[] = update_sql("UPDATE {". $table ."} SET ". $column ." = NULL WHERE ". $column ." = 0"); + } + } + break; + } + } + } + return $ret; +} \ No newline at end of file diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/text/text.module ./modules/fields/text/text.module --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/text/text.module 2008-04-23 14:02:36.000000000 -0400 @@ -0,0 +1,452 @@ + array( + 'arguments' => array('element' => NULL), + ), + 'text_textfield' => array( + 'arguments' => array('element' => NULL), + ), + 'text_formatter_default' => array( + 'arguments' => array('element' => NULL), + ), + 'text_formatter_plain' => array( + 'arguments' => array('element' => NULL), + ), + 'text_formatter_trimmed' => array( + 'arguments' => array('element' => NULL), + ), + 'text_formatter_foo' => array( + 'arguments' => array('element' => NULL), + ), + ); +} + +/** + * Implementation of hook_field_info(). + * + * Here we indicate that the content module will use its default + * handling for the view of this field. + * + * Callbacks can be omitted if default handing is used. + * They're included here just so this module can be used + * as an example for custom modules that might do things + * differently. + */ +function text_field_info() { + return array( + 'text' => array( + 'label' => t('Text'), + 'description' => t('Store text in the database.'), + 'callbacks' => array( + 'tables' => CONTENT_CALLBACK_DEFAULT, + 'arguments' => CONTENT_CALLBACK_DEFAULT, + ), + ), + ); +} + +/** + * Implementation of hook_field_settings(). + */ +function text_field_settings($op, $field) { + switch ($op) { + case 'form': + $form = array(); + $options = array(0 => t('Plain text'), 1 => t('Filtered text (user selects input format)')); + $form['text_processing'] = array( + '#type' => 'radios', + '#title' => t('Text processing'), + '#default_value' => is_numeric($field['text_processing']) ? $field['text_processing'] : 0, + '#options' => $options, + ); + $form['max_length'] = array( + '#type' => 'textfield', + '#title' => t('Maximum length'), + '#default_value' => is_numeric($field['max_length']) ? $field['max_length'] : '', + '#required' => FALSE, + '#description' => t('The maximum length of the field in characters. Leave blank for an unlimited size.'), + ); + $form['allowed_values_fieldset'] = array( + '#type' => 'fieldset', + '#title' => t('Allowed values'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['allowed_values_fieldset']['allowed_values'] = array( + '#type' => 'textarea', + '#title' => t('Allowed values list'), + '#default_value' => !empty($field['allowed_values']) ? $field['allowed_values'] : '', + '#required' => FALSE, + '#rows' => 10, + '#description' => t('The possible values this field can contain. Enter one value per line, in the format key|label. The key is the value that will be stored in the database and it must match the field storage type, %type. The label is optional and the key will be used as the label if no label is specified.', array('%type' => $field['type'])), + ); + $form['allowed_values_fieldset']['advanced_options'] = array( + '#type' => 'fieldset', + '#title' => t('PHP code'), + '#collapsible' => TRUE, + '#collapsed' => empty($field['allowed_values_php']), + ); + $form['allowed_values_fieldset']['advanced_options']['allowed_values_php'] = array( + '#type' => 'textarea', + '#title' => t('Code'), + '#default_value' => !empty($field['allowed_values_php']) ? $field['allowed_values_php'] : '', + '#rows' => 6, + '#description' => t('Advanced usage only: PHP code that returns a keyed array of allowed values. Should not include <?php ?> delimiters. If this field is filled out, the array returned by this code will override the allowed values list above.'), + ); + return $form; + + case 'save': + return array('text_processing', 'max_length', 'allowed_values', 'allowed_values_php'); + + case 'database columns': + if (empty($field['max_length']) || $field['max_length'] > 255) { + $columns['value'] = array('type' => 'text', 'size' => 'big', 'not null' => FALSE, 'sortable' => TRUE); + } + else { + $columns['value'] = array('type' => 'varchar', 'length' => $field['max_length'], 'not null' => FALSE, 'sortable' => TRUE); + } + if (!empty($field['text_processing'])) { + $columns['format'] = array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE); + } + return $columns; + + case 'views data': + $allowed_values = content_allowed_values($field); + if (count($allowed_values)) { + $data = content_views_field_views_data($field); + $db_info = content_database_info($field); + $table_alias = content_views_tablename($field); + + // Swap the filter handler to the 'in' operator. + $data[$table_alias][$field['field_name'] .'_value']['filter']['handler'] = 'views_handler_filter_many_to_one_content'; + return $data; + } + } +} + +/** + * Implementation of hook_field(). + */ +function text_field($op, &$node, $field, &$items, $teaser, $page) { + switch ($op) { + case 'validate': + $allowed_values = content_allowed_values($field); + if (is_array($items)) { + foreach ($items as $delta => $item) { + $error_field = $field['field_name'] .']['. $delta .'][value'; + if (!empty($item['value'])) { + if (count($allowed_values) && !array_key_exists($item['value'], $allowed_values)) { + form_set_error($error_field, t('Illegal value for %name.', array('%name' => t($field['widget']['label'])))); + } + } + } + } + if (!empty($field['max_length'])) { + foreach ($items as $delta => $data) { + $error_field = $field['field_name'] .']['. $delta .'][value'; + if (strlen($data['value']) > $field['max_length']) { + form_set_error($error_field, t('%label is longer than %max characters.', array('%label' => $field['widget']['label'], '%max' => $field['max_length']))); + } + } + } + break; + + case 'sanitize': + foreach ($items as $delta => $item) { + if (!empty($field['text_processing'])) { + $text = check_markup($item['value'], $item['format'], is_null($node) || $node->build_mode == NODE_BUILD_PREVIEW); + } + else { + $text = check_plain($item['value']); + } + $items[$delta]['safe'] = $text; + } + } +} + +/** + * Implementation of hook_content_is_empty(). + */ +function text_content_is_empty($item, $field) { + if (empty($item['value'])) { + return TRUE; + } + return FALSE; +} + +/** + * Implementation of hook_field_formatter_info(). + */ +function text_field_formatter_info() { + return array( + 'default' => array( + 'label' => t('Default'), + 'field types' => array('text'), + 'multiple values' => CONTENT_HANDLE_CORE, + ), + 'plain' => array( + 'label' => t('Plain text'), + 'field types' => array('text'), + 'multiple values' => CONTENT_HANDLE_CORE, + ), + 'trimmed' => array( + 'label' => t('Trimmed'), + 'field types' => array('text'), + 'multiple values' => CONTENT_HANDLE_CORE, + ), + ); +} + +/** + * Theme function for 'default' text field formatter. + */ +function theme_text_formatter_default($element) { + return ($allowed =_text_allowed_values($element)) ? $allowed : $element['#item']['safe']; +} + +/** + * Theme function for 'plain' text field formatter. + */ +function theme_text_formatter_plain($element) { + return ($allowed =_text_allowed_values($element)) ? $allowed : strip_tags($element['#item']['safe']); +} + +/** + * Theme function for 'trimmed' text field formatter. + */ +function theme_text_formatter_trimmed($element) { + $field = content_fields($element['#field_name'], $element['#type_name']); + return ($allowed =_text_allowed_values($element)) ? $allowed : node_teaser($element['#item']['safe'], $field['text_processing'] ? $element['#item']['format'] : NULL); +} + +function _text_allowed_values($element) { + $field = content_fields($element['#field_name'], $element['#type_name']); + if (($allowed_values = content_allowed_values($field)) && isset($allowed_values[$element['#item']['value']])) { + return $allowed_values[$element['#item']['value']]; + } +} + +/** + * Implementation of hook_widget_info(). + * + * Here we indicate that the content module will handle + * the default value and multiple values for these widgets. + * + * Callbacks can be omitted if default handing is used. + * They're included here just so this module can be used + * as an example for custom modules that might do things + * differently. + */ +function text_widget_info() { + return array( + 'text_textfield' => array( + 'label' => t('Text field'), + 'field types' => array('text'), + 'multiple values' => CONTENT_HANDLE_CORE, + 'callbacks' => array( + 'default value' => CONTENT_CALLBACK_DEFAULT, + ), + ), + 'text_textarea' => array( + 'label' => t('Text area (multiple rows)'), + 'field types' => array('text'), + 'multiple values' => CONTENT_HANDLE_CORE, + 'callbacks' => array( + 'default value' => CONTENT_CALLBACK_DEFAULT, + ), + ), + ); +} + +/** + * Implementation of FAPI hook_elements(). + * + * Any FAPI callbacks needed for individual widgets can be declared here, + * and the element will be passed to those callbacks for processing. + * + * Drupal will automatically theme the element using a theme with + * the same name as the hook_elements key. + * + * Autocomplete_path is not used by text_widget but other widgets can use it + * (see nodereference and userreference). + */ +function text_elements() { + return array( + 'text_textfield' => array( + '#input' => TRUE, + '#columns' => array('value'), '#delta' => 0, + '#process' => array('text_textfield_process'), + '#autocomplete_path' => FALSE, + ), + 'text_textarea' => array( + '#input' => TRUE, + '#columns' => array('value', 'format'), '#delta' => 0, + '#process' => array('text_textarea_process'), + '#filter_value' => FILTER_FORMAT_DEFAULT, + ), + ); +} + +/** + * Implementation of hook_widget_settings(). + */ +function text_widget_settings($op, $widget) { + switch ($op) { + case 'form': + $form = array(); + if ($widget['type'] == 'text_textfield') { + $form['rows'] = array('#type' => 'hidden', '#value' => 1); + } + else { + $form['rows'] = array( + '#type' => 'textfield', + '#title' => t('Rows'), + '#default_value' => is_numeric($widget['rows']) ? $widget['rows'] : 5, + '#required' => TRUE, + ); + } + return $form; + + case 'validate': + if (!is_numeric($widget['rows']) || intval($widget['rows']) != $widget['rows'] || $widget['rows'] <= 0) { + form_set_error('rows', t('"Rows" must be a positive integer.')); + } + break; + + case 'save': + return array('rows'); + } +} + +/** + * Implementation of hook_widget(). + * + * Attach a single form element to the form. It will be built out and + * validated in the callback(s) listed in hook_elements. We build it + * out in the callbacks rather than here in hook_widget so it can be + * plugged into any module that can provide it with valid + * $field information. + * + * Content module will set the weight, field name and delta values + * for each form element. This is a change from earlier CCK versions + * where the widget managed its own multiple values. + * + * If there are multiple values for this field, the content module will + * call this function as many times as needed. + * + * @param $form + * the entire form array, $form['#node'] holds node information + * @param $form_state + * the form_state, $form_state['values'][$field['field_name']] + * holds the field's form values. + * @param $field + * the field array + * @param $items + * array of default values for this field + * @param $delta + * the order of this item in the array of subelements (0, 1, 2, etc) + * + * @return + * the form item for a single element for this field + */ +function text_widget(&$form, &$form_state, $field, $items, $delta = 0) { + $element = array( + '#type' => $field['widget']['type'], + '#default_value' => isset($items[$delta]) ? $items[$delta] : '', + ); + return $element; +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $fields array is in $form['#field_info'][$element['#field_name']]. + */ +function text_textfield_process($element, $edit, $form_state, $form) { + $field = $form['#field_info'][$element['#field_name']]; + $field_key = $element['#columns'][0]; + $delta = $element['#delta']; + $element[$field_key] = array( + '#type' => 'textfield', + '#title' => $element['#title'], + '#description' => $element['#description'], + '#required' => $element['#required'], + '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL, + '#autocomplete_path' => $element['#autocomplete_path'], + ); + + if (!empty($field['max_length'])) { + $element[$field_key]['#maxlength'] = $field['max_length']; + } + if (!empty($field['text_processing'])) { + $filter_key = $element['#columns'][1]; + $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT; + $parents = array_merge($element['#parents'] , array($filter_key)); + $element[$filter_key] = filter_form($format, 1, $parents); + } + return $element; +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $fields array is in $form['#field_info'][$element['#field_name']]. + */ +function text_textarea_process($element, $edit, $form_state, $form) { + $field = $form['#field_info'][$element['#field_name']]; + $field_key = $element['#columns'][0]; + $element[$field_key] = array( + '#type' => 'textarea', + '#title' => $element['#title'], + '#description' => $element['#description'], + '#required' => $element['#required'], + '#default_value' => isset($element['#value'][$field_key]) ? $element['#value'][$field_key] : NULL, + '#rows' => !empty($field['widget']['rows']) ? $field['widget']['rows'] : 10, + '#weight' => 0, + ); + + if (!empty($field['text_processing'])) { + $filter_key = $element['#columns'][1]; + $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT; + $parents = array_merge($element['#parents'] , array($filter_key)); + $element[$filter_key] = filter_form($format, 1, $parents); + } + + return $element; +} + +/** + * FAPI theme for an individual text elements. + * + * The textfield or textarea is already rendered by the + * textfield or textarea themes and the html output + * lives in $element['#children']. Override this theme to + * make custom changes to the output. + * + * $element['#field_name'] contains the field name + * $element['#delta] is the position of this element in the group + */ +function theme_text_textfield($element) { + return $element['#children']; +} + +function theme_text_textarea($element) { + return $element['#children']; +} \ No newline at end of file diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/text/translations/modules-text.de.po ./modules/fields/text/translations/modules-text.de.po --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/text/translations/modules-text.de.po 2008-04-24 15:00:40.000000000 -0400 @@ -0,0 +1,46 @@ +# $Id: modules-text.de.po,v 1.1 2008/04/24 19:00:40 hass Exp $ +# German translation of CCK +# Copyright 2006 Lukas Gangoly +# Copyright 2006 Jakob Petsovits +# Generated from files: +# field.php,v 1.3 2006/04/16 13:47:13 luke +# text.module,v 1.34 2006/06/12 19:59:53 luke +# number.module,v 1.28 2006/05/02 13:52:16 luke +# content_admin.inc,v 1.16 2006/06/12 19:36:54 luke +# content.module,v 1.64 2006/06/12 19:36:54 luke +# nodereference.module,v 1.28 2006/06/12 19:36:54 luke +# optionwidgets.module,v 1.8 2006/05/01 15:45:29 luke +# userreference.module,v 1.24 2006/05/05 14:10:44 luke +# weburl.module,v 1.8 2006/06/12 19:36:54 luke +# +msgid "" +msgstr "" +"Project-Id-Version: CCK\n" +"POT-Creation-Date: 2008-04-24 19:53+0200\n" +"PO-Revision-Date: 2008-04-24 20:59+0100\n" +"Last-Translator: Alexander Hass\n" +"Language-Team: German \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Poedit-Language: German\n" +"X-Poedit-Country: GERMANY\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: modules/text/text.module:50 +msgid "Store text in the database." +msgstr "Text in der Datenbank speichern." + +#: modules/text/text.module:262 +msgid "Text area (multiple rows)" +msgstr "Textfeld (mehrere Zeilen)" + +#: modules/text/text.module:0 +msgid "text" +msgstr "Text" + +#: modules/text/text.info:0 +msgid "Defines simple text field types." +msgstr "Definiert einfache Typen von Textfeldern." + diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/text/translations/modules-text.pot ./modules/fields/text/translations/modules-text.pot --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/text/translations/modules-text.pot 2008-04-24 14:01:08.000000000 -0400 @@ -0,0 +1,37 @@ +# $Id: modules-text.pot,v 1.1 2008/04/24 18:01:08 hass Exp $ +# +# LANGUAGE translation of Drupal (modules-text) +# Copyright YEAR NAME +# Generated from files: +# text.module,v 1.91 2008/04/23 18:02:36 dww +# text.info,v 1.9 2008/04/23 18:02:31 dww +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2008-04-24 19:53+0200\n" +"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" +"Last-Translator: NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" + +#: modules/text/text.module:50 +msgid "Store text in the database." +msgstr "" + +#: modules/text/text.module:262 +msgid "Text area (multiple rows)" +msgstr "" + +#: modules/text/text.module:0 +msgid "text" +msgstr "" + +#: modules/text/text.info:0 +msgid "Defines simple text field types." +msgstr "" + diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/theme/field.tpl.php ./modules/fields/theme/field.tpl.php --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/theme/field.tpl.php 2008-05-14 15:28:51.000000000 -0400 @@ -0,0 +1,47 @@ + + +
+ +
+ +
+ $item) : + if (!$item['empty']) : ?> +
+ +
+
+ + +
+ +
+
+ diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/userreference/translations/modules-userreference.de.po ./modules/fields/userreference/translations/modules-userreference.de.po --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/userreference/translations/modules-userreference.de.po 2008-04-26 09:07:10.000000000 -0400 @@ -0,0 +1,94 @@ +# $Id: modules-userreference.de.po,v 1.2 2008/04/26 13:07:10 hass Exp $ +# German translation of CCK +# Copyright 2006 Lukas Gangoly +# Copyright 2006 Jakob Petsovits +# Generated from files: +# field.php,v 1.3 2006/04/16 13:47:13 luke +# text.module,v 1.34 2006/06/12 19:59:53 luke +# number.module,v 1.28 2006/05/02 13:52:16 luke +# content_admin.inc,v 1.16 2006/06/12 19:36:54 luke +# content.module,v 1.64 2006/06/12 19:36:54 luke +# nodereference.module,v 1.28 2006/06/12 19:36:54 luke +# optionwidgets.module,v 1.8 2006/05/01 15:45:29 luke +# userreference.module,v 1.24 2006/05/05 14:10:44 luke +# weburl.module,v 1.8 2006/06/12 19:36:54 luke +# +msgid "" +msgstr "" +"Project-Id-Version: CCK\n" +"POT-Creation-Date: 2008-04-24 19:53+0200\n" +"PO-Revision-Date: 2008-04-26 15:05+0100\n" +"Last-Translator: Alexander Hass\n" +"Language-Team: German \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Poedit-Language: German\n" +"X-Poedit-Country: GERMANY\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: modules/userreference/userreference.module:52 +msgid "User reference" +msgstr "Benutzerverweis" + +#: modules/userreference/userreference.module:53 +msgid "Store the ID of a related user as an integer value." +msgstr "Speichert die ID eines ähnlichen Benutzers als ganzzahligen Wert." + +#: modules/userreference/userreference.module:71 +msgid "User roles that can be referenced" +msgstr "Benutzerrollen auf die verwiesen werden kann" + +#: modules/userreference/userreference.module:77 +msgid "User status that can be referenced" +msgstr "Benutzerstatus auf den verwiesen werden kann" + +#: modules/userreference/userreference.module:79 +msgid "Active" +msgstr "Aktiv" + +#: modules/userreference/userreference.module:79 +msgid "Blocked" +msgstr "Gesperrt" + +#: modules/userreference/userreference.module:122 +msgid "%name: Invalid user." +msgstr "%name: Ungültiger Benutzer." + +#: modules/userreference/userreference.module:253 +msgid "Reverse link" +msgstr "‚Zurück‘-Verweis" + +#: modules/userreference/userreference.module:255 +msgid "No" +msgstr "Nein" + +#: modules/userreference/userreference.module:255 +msgid "Yes" +msgstr "Ja" + +#: modules/userreference/userreference.module:257 +msgid "If selected, a reverse link back to the referencing node will displayed on the referenced user record." +msgstr "Sobald ausgewählt, wird auf dem verwiesenen Benutzerdatensatz ein ‚Zurück‘-Verweis zu dem verweisenden Beitrag angezeigt." + +#: modules/userreference/userreference.module:567 +msgid "Related content" +msgstr "Ähnlicher Inhalt" + +#: modules/userreference/userreference.module:15 +msgid "Userreference autocomplete" +msgstr "Autovervollständigung der Benutzerverweise" + +#: modules/userreference/userreference.module:0 +msgid "userreference" +msgstr "Benutzerverweis" + +#: modules/userreference/userreference.info:0 +msgid "User Reference" +msgstr "Benutzerverweis" + +#: modules/userreference/userreference.info:0 +msgid "Defines a field type for referencing a user from a node." +msgstr "Definiert einen Feldtyp mit dem von einem Beitrag auf einen Benutzer verwiesen werden kann." + diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/userreference/translations/modules-userreference.pot ./modules/fields/userreference/translations/modules-userreference.pot --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/userreference/translations/modules-userreference.pot 2008-04-24 14:01:07.000000000 -0400 @@ -0,0 +1,85 @@ +# $Id: modules-userreference.pot,v 1.1 2008/04/24 18:01:07 hass Exp $ +# +# LANGUAGE translation of Drupal (modules-userreference) +# Copyright YEAR NAME +# Generated from files: +# userreference.module,v 1.99 2008/04/23 18:02:43 dww +# userreference.info,v 1.8 2008/04/23 18:02:38 dww +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"POT-Creation-Date: 2008-04-24 19:53+0200\n" +"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n" +"Last-Translator: NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" + +#: modules/userreference/userreference.module:52 +msgid "User reference" +msgstr "" + +#: modules/userreference/userreference.module:53 +msgid "Store the ID of a related user as an integer value." +msgstr "" + +#: modules/userreference/userreference.module:71 +msgid "User roles that can be referenced" +msgstr "" + +#: modules/userreference/userreference.module:77 +msgid "User status that can be referenced" +msgstr "" + +#: modules/userreference/userreference.module:79 +msgid "Active" +msgstr "" + +#: modules/userreference/userreference.module:79 +msgid "Blocked" +msgstr "" + +#: modules/userreference/userreference.module:122 +msgid "%name: Invalid user." +msgstr "" + +#: modules/userreference/userreference.module:253 +msgid "Reverse link" +msgstr "" + +#: modules/userreference/userreference.module:255 +msgid "No" +msgstr "" + +#: modules/userreference/userreference.module:255 +msgid "Yes" +msgstr "" + +#: modules/userreference/userreference.module:257 +msgid "If selected, a reverse link back to the referencing node will displayed on the referenced user record." +msgstr "" + +#: modules/userreference/userreference.module:567 +msgid "Related content" +msgstr "" + +#: modules/userreference/userreference.module:15 +msgid "Userreference autocomplete" +msgstr "" + +#: modules/userreference/userreference.module:0 +msgid "userreference" +msgstr "" + +#: modules/userreference/userreference.info:0 +msgid "User Reference" +msgstr "" + +#: modules/userreference/userreference.info:0 +msgid "Defines a field type for referencing a user from a node." +msgstr "" + diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/userreference/userreference.info ./modules/fields/userreference/userreference.info --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/userreference/userreference.info 2008-05-14 14:53:55.000000000 -0400 @@ -0,0 +1,10 @@ +; $Id: userreference.info,v 1.8 2008/04/23 18:02:38 dww Exp $ +name = User Reference +description = Defines a field type for referencing a user from a node. +dependencies[] = content +dependencies[] = text +dependencies[] = optionwidgets +package = Core - fields +core = 7.x + +files[]=userreference.module diff -r -u -F'^f' -N -X DIFF-EXCLUDES ../head/modules/fields/userreference/userreference.install ./modules/fields/userreference/userreference.install --- /dev/null 1969-12-31 19:00:00.000000000 -0500 +++ ./modules/fields/userreference/userreference.install 2008-04-23 14:02:40.000000000 -0400 @@ -0,0 +1,50 @@ + 'Userreference autocomplete', + 'page callback' => 'userreference_autocomplete', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK + ); + return $items; +} + +/** + * Implementation of hook_theme(). + */ +function userreference_theme() { + return array( + 'userreference_select' => array( + 'arguments' => array('element' => NULL), + ), + 'userreference_autocomplete' => array( + 'arguments' => array('element' => NULL), + ), + 'userreference_formatter_default' => array( + 'arguments' => array('element'), + ), + 'userreference_formatter_plain' => array( + 'arguments' => array('element'), + ), + ); +} + +/** + * Implementation of hook_field_info(). + * + * Here we indicate that the content module will use its default + * handling for the view of this field. + */ +function userreference_field_info() { + return array( + 'userreference' => array( + 'label' => t('User reference'), + 'description' => t('Store the ID of a related user as an integer value.'), + 'callbacks' => array( + 'tables' => CONTENT_CALLBACK_DEFAULT, + 'arguments' => CONTENT_CALLBACK_DEFAULT, + ), + ), + ); +} + +/** + * Implementation of hook_field_settings(). + */ +function userreference_field_settings($op, $field) { + switch ($op) { + case 'form': + $form = array(); + $form['referenceable_roles'] = array( + '#type' => 'checkboxes', + '#title' => t('User roles that can be referenced'), + '#default_value' => is_array($field['referenceable_roles']) ? array_filter($field['referenceable_roles']) : array(), + '#options' => user_roles(1), + ); + $form['referenceable_status'] = array( + '#type' => 'checkboxes', + '#title' => t('User status that can be referenced'), + '#default_value' => is_array($field['referenceable_status']) ? array_filter($field['referenceable_status']) : array(1), + '#options' => array(1 => t('Active'), 0 => t('Blocked')), + ); + return $form; + + case 'save': + return array('referenceable_roles', 'referenceable_status'); + + case 'database columns': + $columns = array( + 'uid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE), + ); + return $columns; + + case 'views data': + $data = content_views_field_views_data($field); + $db_info = content_database_info($field); + $table_alias = content_views_tablename($field); + + // Swap the filter handler to the 'in' operator. + $data[$table_alias][$field['field_name'] .'_uid']['filter']['handler'] = 'views_handler_filter_many_to_one_content'; + + // Add a relationship for related user. + $data[$table_alias][$field['field_name'] .'_uid']['relationship'] = array( + 'base' => 'users', + 'field' => $db_info['columns']['uid']['column'], + 'handler' => 'views_handler_relationship', + ); + return $data; + + } +} + +/** + * Implementation of hook_field(). + */ +function userreference_field($op, &$node, $field, &$items, $teaser, $page) { + switch ($op) { + case 'validate': + foreach ($items as $delta => $item) { + if (is_array($item) && !empty($item['error_field'])) { + $error_field = $item['error_field']; + unset($item['error_field']); + if (!empty($item['uid']) && !array_key_exists($item['uid'], _userreference_potential_references($field, '', $item['uid']))) { + form_set_error($error_field, t('%name: Invalid user.', array('%name' => t($field['widget']['label'])))); + } + } + } + return; + } +} + +/** + * Implementation of hook_content_is_empty(). + */ +function userreference_content_is_empty($item, $field) { + if (empty($item['uid'])) { + return TRUE; + } + return FALSE; +} + +/** + * Implementation of hook_field_formatter_info(). + */ +function userreference_field_formatter_info() { + return array( + 'default' => array( + 'label' => t('Default'), + 'field types' => array('userreference'), + 'multiple values' => CONTENT_HANDLE_CORE, + ), + 'plain' => array( + 'label' => t('Plain text'), + 'field types' => array('userreference'), + 'multiple values' => CONTENT_HANDLE_CORE, + ), + ); +} + +/** + * Theme function for 'default' userreference field formatter. + */ +function theme_userreference_formatter_default($element) { + $output = ''; + + if (isset($element['#item']['uid']) && $account = user_load(array('uid' => $element['#item']['uid']))) { + $output = theme('username', $account); + } + return $output; +} + +/** + * Theme function for 'plain' userreference field formatter. + */ +function theme_userreference_formatter_plain($element) { + $output = ''; + if (isset($element['#item']['uid']) && $account = user_load(array('uid' => $element['#item']['uid']))) { + $output = $account->name; + } + return $output; +} + +/** + * Implementation of hook_widget_info(). + * + * We need custom handling of multiple values for userreference_select + * because we need to combine them into a options list rather than + * display multiple elements. + * + * We will use the content module's default handling for default value. + * + * Callbacks can be omitted if default handing is used. + * They're included here just so this module can be used + * as an example for custom modules that might do things + * differently. + */ +function userreference_widget_info() { + return array( + 'userreference_select' => array( + 'label' => t('Select list'), + 'field types' => array('userreference'), + 'multiple values' => CONTENT_HANDLE_MODULE, + 'callbacks' => array( + 'default value' => CONTENT_CALLBACK_DEFAULT, + ), + ), + 'userreference_autocomplete' => array( + 'label' => t('Autocomplete text field'), + 'field types' => array('userreference'), + 'multiple values' => CONTENT_HANDLE_CORE, + 'callbacks' => array( + 'default value' => CONTENT_CALLBACK_DEFAULT, + ), + ), + ); +} + +/** + * Implementation of FAPI hook_elements(). + * + * Any FAPI callbacks needed for individual widgets can be declared here, + * and the element will be passed to those callbacks for processing. + * + * Drupal will automatically theme the element using a theme with + * the same name as the hook_elements key. + * + * Autocomplete_path is not used by text_widget but other widgets can use it + * (see nodereference and userreference). + */ +function userreference_elements() { + return array( + 'userreference_select' => array( + '#input' => TRUE, + '#columns' => array('uid'), '#delta' => 0, + '#process' => array('userreference_select_process'), + ), + 'userreference_autocomplete' => array( + '#input' => TRUE, + '#columns' => array('name'), '#delta' => 0, + '#process' => array('userreference_autocomplete_process'), + '#autocomplete_path' => FALSE, + ), + ); +} + +/** + * Implementation of hook_widget_settings(). + */ +function userreference_widget_settings($op, $field) { + switch ($op) { + case 'form': + $form = array(); + $form['reverse_link'] = array( + '#type' => 'radios', + '#title' => t('Reverse link'), + '#default_value' => isset($field['reverse_link']) ? $field['reverse_link'] : 0, + '#options' => array(0 => t('No'), 1 => t('Yes')), + '#required' => TRUE, + '#description' => t('If selected, a reverse link back to the referencing node will displayed on the referenced user record.'), + ); + return $form; + break; + + case 'save': + return array('reverse_link'); + break; + } +} + +/** + * Implementation of hook_widget(). + * + * Attach a single form element to the form. It will be built out and + * validated in the callback(s) listed in hook_elements. We build it + * out in the callbacks rather than here in hook_widget so it can be + * plugged into any module that can provide it with valid + * $field information. + * + * Content module will set the weight, field name and delta values + * for each form element. This is a change from earlier CCK versions + * where the widget managed its own multiple values. + * + * If there are multiple values for this field, the content module will + * call this function as many times as needed. + * + * @param $form + * the entire form array, $form['#node'] holds node information + * @param $form_state + * the form_state, $form_state['values'][$field['field_name']] + * holds the field's form values. + * @param $field + * the field array + * @param $items + * array of default values for this field + * @param $delta + * the order of this item in the array of subelements (0, 1, 2, etc) + * + * @return + * the form item for a single element for this field + */ +function userreference_widget(&$form, &$form_state, $field, $items, $delta = 0) { + switch ($field['widget']['type']) { + case 'userreference_select': + $element = array( + '#type' => 'userreference_select', + '#default_value' => $items, + ); + break; + + case 'userreference_autocomplete': + $element = array( + '#type' => 'userreference_autocomplete', + '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL, + '#value_callback' => 'userreference_autocomplete_value', + ); + break; + } + return $element; +} + +/** + * Value for a userreference autocomplete element. + * + * Substitute in the user name for the uid. + */ +function userreference_autocomplete_value($element, $edit = FALSE) { + $field_key = $element['#columns'][0]; + if (!empty($element['#default_value'][$field_key])) { + $value = db_result(db_query("SELECT name FROM {users} WHERE uid = '%d'", $element['#default_value'][$field_key])); + return array($field_key => $value); + } + return array($field_key => NULL); +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $fields array is in $form['#field_info'][$element['#field_name']]. + */ +function userreference_select_process($element, $edit, $form_state, $form) { + // The userreference_select widget doesn't need to create its own + // element, it can wrap around the optionwidgets_select element. + // Add a validation step where the value can be unwrapped. + $field_key = $element['#columns'][0]; + $element[$field_key] = array( + '#type' => 'optionwidgets_select', + '#default_value' => isset($element['#value']) ? $element['#value'] : '', + '#element_validate' => array('optionwidgets_validate', 'userreference_select_validate'), + + // The following values were set by the content module and need + // to be passed down to the nested element. + '#field_name' => $element['#field_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + ); + return $element; +} + +/** + * Process an individual element. + * + * Build the form element. When creating a form using FAPI #process, + * note that $element['#value'] is already set. + * + * The $fields array is in $form['#field_info'][$element['#field_name']]. + */ +function userreference_autocomplete_process($element, $edit, $form_state, $form) { + // The userreference autocomplete widget doesn't need to create its own + // element, it can wrap around the text_textfield element and add an autocomplete + // path and some extra processing to it. + // Add a validation step where the value can be unwrapped. + $field_key = $element['#columns'][0]; + + $element[$field_key] = array( + '#type' => 'text_textfield', + '#default_value' => isset($element['#value']) ? $element['#value'] : '', + '#autocomplete_path' => 'userreference/autocomplete/'. $element['#field_name'], + '#element_validate' => array('userreference_autocomplete_validate'), + + // The following values were set by the content module and need + // to be passed down to the nested element. + '#field_name' => $element['#field_name'], + '#delta' => $element['#delta'], + '#columns' => $element['#columns'], + '#title' => $element['#title'], + '#required' => $element['#required'], + '#description' => $element['#description'], + ); + return $element; +} + +/** + * Afterbuild adjustment of the element. + * + * Remove the wrapper layer and set the right element's value. + */ +function userreference_select_validate($element, &$form_state) { + $field_key = $element['#columns'][0]; + array_pop($element['#parents']); + form_set_value($element, $form_state['values'][$element['#field_name']][$field_key], $form_state); +} + +/** + * Validate an autocomplete element. + * + * Remove the wrapper layer and set the right element's value. + */ +function userreference_autocomplete_validate($element, &$form_state) { + $field_key = $element['#columns'][0]; + $user = $element['#value'][$field_key]; + $uid = NULL; + if (!empty($user)) { + $uid = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $user)); + } + form_set_value($element, $uid, $form_state); +} + +/** + * Implementation of hook_allowed_values(). + */ +function userreference_allowed_values($field) { + $options = _userreference_potential_references($field); + if (!$field['required']) { + $options = array('none' => '<'. t('none') .'>') + $options; + } + return $options; +} + +/** + * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for existing users + */ +function userreference_autocomplete($field_name, $string = '') { + $fields = content_fields(); + $field = $fields[$field_name]; + + $matches = array(); + if ($string) { + foreach (_userreference_potential_references($field, $string) as $uid => $name) { + $matches[$name] = check_plain($name); + } + } + drupal_json($matches); +} + +/** + * Fetch an array of all candidate referenced users, for use in presenting the selection form to the user. + */ +function _userreference_potential_references($field, $string = '', $uid = NULL) { + $where = array(); + $args = array(); + $join = array(); + + if (!empty($string)) { + $where[] = "LOWER(name) LIKE LOWER('%s%%')"; + $args[] = $string; + } + elseif (!empty($uid) && is_numeric($uid)) { + $where[] = "u.uid = $uid"; + } + else { + $where[] = "u.uid > 0"; + } + + $roles = array(); + if (isset($field['referenceable_roles']) && is_array($field['referenceable_roles'])) { + // keep only selected checkboxes + $roles = array_filter($field['referenceable_roles']); + // filter invalid values that seems to get through sometimes ?? + $roles = array_intersect(array_keys(user_roles(1)), $roles); + } + if (!empty($roles) && !in_array(DRUPAL_AUTHENTICATED_RID, $roles)) { + $where[] = "r.rid IN (". implode($roles, ',') .")"; + $join[] = 'LEFT JOIN {users_roles} r ON u.uid = r.uid'; + } + + $status = array(); + if (isset($field['referenceable_status']) && is_array($field['referenceable_status'])) { + // keep only selected checkboxes + $status = array_filter($field['referenceable_status']); + } + if (!empty($status)) { + // Limit query if only one status should be referenced. + if (count($status) == 1) { + $where[] = "u.status = ". array_pop($status); + } + } + + $users = array(); + $result = db_query('SELECT u.name, u.uid FROM {users} u '. implode(' ', $join) .' WHERE '. implode(' AND ', $where) .' ORDER BY u.name ASC', $args); + while ($user = db_fetch_object($result)) { + $users[$user->uid] = $user->name; + } + return $users; +} + +/** + * Provide a list of users to filter on. + */ +function _userreference_filter_handler($op, $filterinfo) { + $options = views_handler_filter_usercurrent(); + $options = $options + _userreference_potential_references($filterinfo['extra']['field']); + return $options; +} + +/** + * Implementation of hook_user(). + */ +function userreference_user($type, &$edit, &$account) { + switch ($type) { + case 'load': + // Only add links if we are on the user 'view' page. + if (arg(0) != 'user' || arg(2)) { + return; + } + // find CCK userreference field tables + // search through them for matching user ids and load those nodes + $additions = array(); + $types = content_types(); + + // Find the table and columns to search through, if the same + // table comes up in more than one content type, we only need + // to search it once. + $search_tables = array(); + $search_links = array(); + foreach ($types as $type_name => $type) { + foreach ($type['fields'] as $field) { + // Only add tables when reverse link has been selected. + if ($field['type'] == 'userreference' && !empty($field['widget']['reverse_link'])) { + $db_info = content_database_info($field); + $search_tables[$db_info['table']] = $db_info['columns']['uid']['column']; + $search_links[$db_info['table']] = $field['widget']['reverse_link']; + } + } + } + foreach ($search_tables as $table => $column) { + $ids = db_query(db_rewrite_sql("SELECT DISTINCT(n.nid) FROM {node} n LEFT JOIN {". $table ."} f ON n.vid = f.vid WHERE f.". $column ."=". $account->uid. " AND n.status = 1")); + while ($data = db_fetch_object($ids)) { + // TODO, do we really want a complete node_load() here? We only need the title to create a link. + $node = node_load($data->nid); + $node->reverse_link = $search_links[$table]; + $additions[$node->type][] = $node; + } + } + $account->userreference = $additions; + return; + break; + + case 'view': + if (!empty($account->userreference)) { + $node_types = content_types(); + $additions = array(); + $values = array(); + foreach ($account->userreference as $node_type => $nodes) { + foreach ($nodes as $node) { + if ($node->reverse_link) { + $values[$node_type][] = l($node->title, 'node/'. $node->nid); + } + } + if (isset($values[$node_type])) { + $additions[] = array( + '#type' => 'user_profile_item', + '#title' => $node_types[$node_type]['name'], + '#value' => theme('item_list', $values[$node_type]), + ); + } + } + if ($additions) { + $account->content['userreference'] = $additions + array( + '#type' => 'user_profile_category', + '#attributes' => array('class' => 'user-member'), + '#title' => t('Related content'), + '#weight' => 10, + ); + } + } + break; + } +} + +/** + * FAPI theme for an individual elements. + * + * The textfield or select is already rendered by the + * textfield or select themes and the HTML output + * lives in $element['#children']. Override this theme to + * make custom changes to the output. + * + * $element['#field_name'] contains the field name + * $element['#delta] is the position of this element in the group + */ +function theme_userreference_select($element) { + return $element['#children']; +} + +function theme_userreference_autocomplete($element) { + return $element['#children']; +} \ No newline at end of file